1#include <SecureObjectSync/SOSTransport.h>
2#include <SecureObjectSync/SOSTransportMessage.h>
3#include <SecureObjectSync/SOSTransportMessageKVS.h>
4#include <SecureObjectSync/SOSKVSKeys.h>
5
6#include <utilities/SecCFWrappers.h>
7#include <SOSInternal.h>
8#include <AssertMacros.h>
9#include <SOSCloudKeychainClient.h>
10
11struct __OpaqueSOSTransportMessageKVS {
12    struct __OpaqueSOSTransportMessage          m;
13
14    CFStringRef             circleName;
15    CFMutableDictionaryRef  pending_changes;
16
17};
18
19//
20// V-table implementation forward declarations
21//
22static bool sendToPeer(SOSTransportMessageRef transport, CFStringRef circleName, CFStringRef peerID, CFDataRef message, CFErrorRef *error);
23static bool syncWithPeers(SOSTransportMessageRef transport, CFDictionaryRef circleToPeerIDs, CFErrorRef *error);
24static bool sendMessages(SOSTransportMessageRef transport, CFDictionaryRef circleToPeersToMessage, CFErrorRef *error);
25static bool cleanupAfterPeer(SOSTransportMessageRef transport, CFDictionaryRef circle_to_peer_ids, CFErrorRef *error);
26static void destroy(SOSTransportMessageRef transport);
27static CF_RETURNS_RETAINED
28CFDictionaryRef handleMessages(SOSTransportMessageRef transport, CFMutableDictionaryRef circle_peer_messages_table, CFErrorRef *error);
29
30static bool flushChanges(SOSTransportMessageRef transport, CFErrorRef *error);
31
32
33CFStringRef SOSTransportMessageKVSGetCircleName(SOSTransportMessageKVSRef transport){
34    return transport->circleName;
35}
36
37SOSTransportMessageKVSRef SOSTransportMessageKVSCreate(SOSAccountRef account, CFStringRef circleName, CFErrorRef *error){
38    SOSTransportMessageKVSRef tkvs = (SOSTransportMessageKVSRef) SOSTransportMessageCreateForSubclass(sizeof(struct __OpaqueSOSTransportMessageKVS) - sizeof(CFRuntimeBase), account, circleName, error);
39
40    if (tkvs) {
41        // Fill in vtable:
42        tkvs->m.sendMessages = sendMessages;
43        tkvs->m.syncWithPeers = syncWithPeers;
44        tkvs->m.flushChanges = flushChanges;
45        tkvs->m.cleanupAfterPeerMessages = cleanupAfterPeer;
46        tkvs->m.destroy = destroy;
47        tkvs->m.handleMessages = handleMessages;
48        // Initialize ourselves
49        tkvs->pending_changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
50        tkvs->circleName = CFRetainSafe(circleName);
51        SOSRegisterTransportMessage((SOSTransportMessageRef)tkvs);
52    }
53
54    return tkvs;
55}
56bool SOSTransportMessageKVSAppendKeyInterest(SOSTransportMessageKVSRef transport, CFMutableArrayRef alwaysKeys, CFMutableArrayRef afterFirstUnlockKeys, CFMutableArrayRef unlockedKeys, CFErrorRef *localError){
57    SOSEngineRef engine = SOSTransportMessageGetEngine((SOSTransportMessageRef)transport);
58    require_quiet(engine, fail);
59    CFArrayRef peer_ids = SOSEngineGetPeerIDs(engine);
60    if(peer_ids){
61        CFArrayForEach(peer_ids, ^(const void *value) {
62            CFStringRef peerMessage = SOSMessageKeyCreateFromPeerToTransport(transport, value);
63            CFArrayAppendValue(unlockedKeys, peerMessage);
64            CFReleaseNull(peerMessage);
65        });
66    }
67    return true;
68fail:
69    return false;
70}
71static void destroy(SOSTransportMessageRef transport){
72    SOSTransportMessageKVSRef tkvs = (SOSTransportMessageKVSRef)transport;
73    CFReleaseNull(tkvs->circleName);
74    CFReleaseNull(tkvs->pending_changes);
75    SOSUnregisterTransportMessage((SOSTransportMessageRef)tkvs);
76
77}
78
79static bool SOSTransportMessageKVSUpdateKVS(SOSTransportMessageKVSRef transport, CFDictionaryRef changes, CFErrorRef *error){
80    CloudKeychainReplyBlock log_error = ^(CFDictionaryRef returnedValues __unused, CFErrorRef error) {
81        if (error) {
82            secerror("Error putting: %@", error);
83            CFReleaseSafe(error);
84        }
85    };
86
87    SOSCloudKeychainPutObjectsInCloud(changes, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), log_error);
88    return true;
89}
90
91static bool SOSTransportMessageKVSSendPendingChanges(SOSTransportMessageKVSRef transport, CFErrorRef *error) {
92    CFErrorRef changeError = NULL;
93
94    if (transport->pending_changes == NULL || CFDictionaryGetCount(transport->pending_changes) == 0) {
95        CFReleaseNull(transport->pending_changes);
96        return true;
97    }
98
99    bool success = SOSTransportMessageKVSUpdateKVS(transport, transport->pending_changes, &changeError);
100    if (success) {
101        CFDictionaryRemoveAllValues(transport->pending_changes);
102    } else {
103        SOSCreateErrorWithFormat(kSOSErrorSendFailure, changeError, error, NULL,
104                                 CFSTR("Send changes block failed [%@]"), transport->pending_changes);
105    }
106
107    return success;
108}
109
110static void SOSTransportMessageKVSAddToPendingChanges(SOSTransportMessageKVSRef transport, CFStringRef message_key, CFDataRef message_data){
111    if (transport->pending_changes == NULL) {
112        transport->pending_changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
113    }
114    if (message_data == NULL) {
115        CFDictionarySetValue(transport->pending_changes, message_key, kCFNull);
116    } else {
117        CFDictionarySetValue(transport->pending_changes, message_key, message_data);
118    }
119}
120
121static bool SOSTransportMessageKVSCleanupAfterPeerMessages(SOSTransportMessageKVSRef transport, CFDictionaryRef circle_to_peer_ids, CFErrorRef *error)
122{
123    CFArrayRef enginePeers = SOSEngineGetPeerIDs(SOSTransportMessageGetEngine((SOSTransportMessageRef)transport));
124
125    CFDictionaryForEach(circle_to_peer_ids, ^(const void *key, const void *value) {
126        if (isString(key) && isArray(value)) {
127            CFStringRef circle_name = (CFStringRef) key;
128            CFArrayRef peers_to_cleanup_after = (CFArrayRef) value;
129
130            CFArrayForEach(peers_to_cleanup_after, ^(const void *value) {
131                if (isString(value)) {
132                    CFStringRef cleanup_id = (CFStringRef) value;
133                    // TODO: Since the enginePeers list is not authorative (the Account is) this could inadvertently clean up active peers or leave behind stale peers
134                    if (enginePeers) CFArrayForEach(enginePeers, ^(const void *value) {
135                        if (isString(value)) {
136                            CFStringRef in_circle_id = (CFStringRef) value;
137
138                            CFStringRef kvsKey = SOSMessageKeyCreateWithCircleNameAndPeerNames(circle_name, cleanup_id, in_circle_id);
139                            SOSTransportMessageKVSAddToPendingChanges(transport, kvsKey, NULL);
140                            CFReleaseSafe(kvsKey);
141
142                            kvsKey = SOSMessageKeyCreateWithCircleNameAndPeerNames(circle_name, in_circle_id, cleanup_id);
143                            SOSTransportMessageKVSAddToPendingChanges(transport, kvsKey, NULL);
144                            CFReleaseSafe(kvsKey);
145                        }
146                    });
147
148                }
149            });
150        }
151    });
152
153    return SOSTransportMessageFlushChanges((SOSTransportMessageRef)transport, error);
154}
155
156static CF_RETURNS_RETAINED
157CFDictionaryRef handleMessages(SOSTransportMessageRef transport, CFMutableDictionaryRef circle_peer_messages_table, CFErrorRef *error) {
158    SOSTransportMessageKVSRef tpt = (SOSTransportMessageKVSRef)transport;
159    CFMutableDictionaryRef handled = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
160    CFDictionaryRef peerToMessage = CFDictionaryGetValue(circle_peer_messages_table, tpt->circleName);
161    CFMutableArrayRef handled_peers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
162    CFDictionaryAddValue(handled, tpt->circleName, handled_peers);
163
164    if(peerToMessage){
165        CFDictionaryForEach(peerToMessage, ^(const void *key, const void *value) {
166            CFStringRef peer_id = (CFStringRef) key;
167            CFDataRef peer_message = (CFDataRef) value;
168            CFErrorRef localError = NULL;
169
170            if (SOSTransportMessageHandlePeerMessage(transport, peer_id, peer_message, &localError)) {
171                CFArrayAppendValue(handled_peers, key);
172            } else {
173                secdebug("transport", "%@ KVSTransport handle message failed: %@", peer_id, localError);
174            }
175            CFReleaseNull(localError);
176        });
177    }
178    CFReleaseNull(handled_peers);
179
180    return handled;
181}
182
183
184static bool sendToPeer(SOSTransportMessageRef transport, CFStringRef circleName, CFStringRef peerID, CFDataRef message, CFErrorRef *error) {
185    SOSTransportMessageKVSRef kvsTransport = (SOSTransportMessageKVSRef) transport;
186    bool result = true;
187    CFStringRef message_to_peer_key = SOSMessageKeyCreateFromTransportToPeer(kvsTransport, peerID);
188    CFDictionaryRef a_message_to_a_peer = CFDictionaryCreateForCFTypes(NULL, message_to_peer_key, message, NULL);
189
190    if (!SOSTransportMessageKVSUpdateKVS(kvsTransport, a_message_to_a_peer, error)) {
191        secerror("Sync with peers failed to send to %@ [%@], %@", peerID, a_message_to_a_peer, *error);
192        result = false;
193    }
194    CFReleaseNull(a_message_to_a_peer);
195    CFReleaseNull(message_to_peer_key);
196
197    return result;
198}
199
200static bool syncWithPeers(SOSTransportMessageRef transport, CFDictionaryRef circleToPeerIDs, CFErrorRef *error){
201    // Each entry is keyed by circle name and contains a list of peerIDs
202
203    __block bool result = true;
204
205    CFDictionaryForEach(circleToPeerIDs, ^(const void *key, const void *value) {
206        if (isString(key) && isArray(value)) {
207            CFStringRef circleName = (CFStringRef) key;
208            CFArrayForEach(value, ^(const void *value) {
209                if (isString(value)) {
210                    CFStringRef peerID = (CFStringRef) value;
211                    result &= SOSTransportMessageSendMessageIfNeeded(transport, circleName, peerID, error);
212                }
213            });
214        }
215    });
216
217    return result;
218}
219
220static bool sendMessages(SOSTransportMessageRef transport, CFDictionaryRef circleToPeersToMessage, CFErrorRef *error) {
221    __block bool result = true;
222
223    CFDictionaryForEach(circleToPeersToMessage, ^(const void *key, const void *value) {
224        if (isString(key) && isDictionary(value)) {
225            CFStringRef circleName = (CFStringRef) key;
226            CFDictionaryForEach(value, ^(const void *key, const void *value) {
227                if (isString(key) && isData(value)) {
228                    CFStringRef peerID = (CFStringRef) key;
229                    CFDataRef message = (CFDataRef) value;
230                    bool rx = sendToPeer(transport, circleName, peerID, message, error);
231                    result &= rx;
232                }
233            });
234        }
235    });
236
237    return true;
238}
239
240static bool flushChanges(SOSTransportMessageRef transport, CFErrorRef *error)
241{
242    return SOSTransportMessageKVSSendPendingChanges((SOSTransportMessageKVSRef) transport, error);
243}
244
245static bool cleanupAfterPeer(SOSTransportMessageRef transport, CFDictionaryRef circle_to_peer_ids, CFErrorRef *error)
246{
247    return SOSTransportMessageKVSCleanupAfterPeerMessages((SOSTransportMessageKVSRef) transport, circle_to_peer_ids, error);
248}
249
250