1//
2//  SOSTransportCircleKVS.c
3//  sec
4//
5#include <SecureObjectSync/SOSTransport.h>
6#include <SecureObjectSync/SOSTransportCircle.h>
7#include <SecureObjectSync/SOSTransportCircleKVS.h>
8#include <SecureObjectSync/SOSKVSKeys.h>
9#include <utilities/SecCFWrappers.h>
10#include <SecureObjectSync/SOSInternal.h>
11#include <SecureObjectSync/SOSAccountPriv.h>
12#include <SOSCloudKeychainClient.h>
13
14static bool SOSTransportCircleKVSUpdateRetirementRecords(SOSTransportCircleKVSRef transport, CFDictionaryRef updates, CFErrorRef* error);
15static bool SOSTransportCircleKVSUpdateKVS(SOSTransportCircleRef transport, CFDictionaryRef changes, CFErrorRef *error);
16static bool expireRetirementRecords(SOSTransportCircleRef transport, CFDictionaryRef retirements, CFErrorRef *error);
17static CFArrayRef handleCircleMessages(SOSTransportCircleRef transport, CFMutableDictionaryRef circle_circle_messages_table, CFErrorRef *error);
18static void destroy(SOSTransportCircleRef transport);
19static bool postCircle(SOSTransportCircleRef transport,  CFStringRef circleName, CFDataRef circle_data, CFErrorRef *error);
20static CFDictionaryRef handleRetirementMessages(SOSTransportCircleRef transport, CFMutableDictionaryRef circle_retirement_messages_table, CFErrorRef *error);
21static inline bool postRetirement(SOSTransportCircleRef transport,  CFStringRef circleName, CFStringRef peer_id, CFDataRef retirement_data, CFErrorRef *error);
22static inline bool flushChanges(SOSTransportCircleRef transport, CFErrorRef *error);
23
24
25struct __OpaqueSOSTransportCircleKVS{
26    struct __OpaqueSOSTransportCircle   c;
27    CFMutableDictionaryRef              pending_changes;
28    CFStringRef                         circleName;
29};
30
31
32SOSTransportCircleKVSRef SOSTransportCircleKVSCreate(SOSAccountRef account, CFStringRef circleName, CFErrorRef *error){
33    SOSTransportCircleKVSRef t = (SOSTransportCircleKVSRef) SOSTransportCircleCreateForSubclass(sizeof(struct __OpaqueSOSTransportCircleKVS) - sizeof(CFRuntimeBase), account, error);
34    if(t){
35        t->circleName = CFRetainSafe(circleName);
36        t->c.expireRetirementRecords = expireRetirementRecords;
37        t->c.postCircle = postCircle;
38        t->c.postRetirement = postRetirement;
39        t->c.flushChanges = flushChanges;
40        t->pending_changes  = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
41        t->c.handleRetirementMessages = handleRetirementMessages;
42        t->c.handleCircleMessages = handleCircleMessages;
43        t->c.destroy = destroy;
44        SOSRegisterTransportCircle((SOSTransportCircleRef)t);
45    }
46    return t;
47}
48
49static CFStringRef SOSTransportCircleKVSGetCircleName(SOSTransportCircleKVSRef transport){
50    return transport->circleName;
51}
52
53static void destroy(SOSTransportCircleRef transport){
54    SOSTransportCircleKVSRef tkvs = (SOSTransportCircleKVSRef)transport;
55    CFReleaseNull(tkvs->pending_changes);
56    CFReleaseNull(tkvs->circleName);
57
58    SOSUnregisterTransportCircle((SOSTransportCircleRef)tkvs);
59}
60
61bool SOSTransportCircleKVSSendPendingChanges(SOSTransportCircleKVSRef transport, CFErrorRef *error) {
62    CFErrorRef changeError = NULL;
63
64    if (transport->pending_changes == NULL || CFDictionaryGetCount(transport->pending_changes) == 0) {
65        CFReleaseNull(transport->pending_changes);
66        return true;
67    }
68
69    bool success = SOSTransportCircleKVSUpdateKVS((SOSTransportCircleRef)transport, transport->pending_changes, &changeError);
70    if (success) {
71        CFDictionaryRemoveAllValues(transport->pending_changes);
72    } else {
73        SOSCreateErrorWithFormat(kSOSErrorSendFailure, changeError, error, NULL,
74                                 CFSTR("Send changes block failed [%@]"), transport->pending_changes);
75    }
76
77    return success;
78}
79
80void SOSTransportCircleKVSAddToPendingChanges(SOSTransportCircleKVSRef transport, CFStringRef message_key, CFDataRef message_data){
81
82    if (transport->pending_changes == NULL) {
83        transport->pending_changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
84    }
85    if (message_data == NULL) {
86        CFDictionarySetValue(transport->pending_changes, message_key, kCFNull);
87    } else {
88        CFDictionarySetValue(transport->pending_changes, message_key, message_data);
89    }
90}
91
92static bool expireRetirementRecords(SOSTransportCircleRef transport, CFDictionaryRef retirements, CFErrorRef *error) {
93
94    bool success = true;
95    CFMutableDictionaryRef keysToWrite = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
96
97    CFDictionaryForEach(retirements, ^(const void *key, const void *value) {
98        if (isString(key) && isArray(value)) {
99            CFStringRef circle_name = (CFStringRef) key;
100            CFArrayRef retirees = (CFArrayRef) value;
101
102            CFArrayForEach(retirees, ^(const void *value) {
103                if (isString(value)) {
104                    CFStringRef retiree_id = (CFStringRef) value;
105
106                    CFStringRef kvsKey = SOSRetirementKeyCreateWithCircleNameAndPeer(circle_name, retiree_id);
107
108                    CFDictionaryAddValue(keysToWrite, kvsKey, kCFNull);
109
110                    CFReleaseSafe(kvsKey);
111                }
112            });
113        }
114    });
115
116    if(CFDictionaryGetCount(keysToWrite)) {
117        success = SOSTransportCircleKVSUpdateRetirementRecords((SOSTransportCircleKVSRef)transport, keysToWrite, error);
118    }
119    CFReleaseNull(keysToWrite);
120
121    return success;
122}
123
124static bool SOSTransportCircleKVSUpdateRetirementRecords(SOSTransportCircleKVSRef transport, CFDictionaryRef updates, CFErrorRef* error){
125    CFErrorRef updateError = NULL;
126    bool success = false;
127    if (SOSTransportCircleKVSUpdateKVS((SOSTransportCircleRef)transport, updates, &updateError)){
128        success = true;
129    } else {
130        SOSCreateErrorWithFormat(kSOSErrorSendFailure, updateError, error, NULL,
131                                 CFSTR("update parameters key failed [%@]"), updates);
132    }
133    return success;
134}
135
136static inline bool postRetirement(SOSTransportCircleRef transport,  CFStringRef circleName, CFStringRef peer_id, CFDataRef retirement_data, CFErrorRef *error)
137{
138    CFStringRef retirement_key = SOSRetirementKeyCreateWithCircleNameAndPeer(circleName, peer_id);
139    if (retirement_key)
140        SOSTransportCircleKVSAddToPendingChanges((SOSTransportCircleKVSRef)transport, retirement_key, retirement_data);
141
142    CFReleaseNull(retirement_key);
143    return true;
144}
145
146static inline bool flushChanges(SOSTransportCircleRef transport, CFErrorRef *error)
147{
148    SOSTransportCircleKVSRef tkvs = (SOSTransportCircleKVSRef) transport;
149
150    return SOSTransportCircleKVSSendPendingChanges(tkvs, error);
151}
152
153static bool postCircle(SOSTransportCircleRef transport,  CFStringRef circleName, CFDataRef circle_data, CFErrorRef *error){
154    SOSTransportCircleKVSRef tkvs = (SOSTransportCircleKVSRef)transport;
155    CFStringRef circle_key = SOSCircleKeyCreateWithName(circleName, error);
156    if (circle_key)
157        SOSTransportCircleKVSAddToPendingChanges(tkvs, circle_key, circle_data);
158    CFReleaseNull(circle_key);
159
160    return true;
161}
162
163static CFDictionaryRef handleRetirementMessages(SOSTransportCircleRef transport, CFMutableDictionaryRef circle_retirement_messages_table, CFErrorRef *error){
164    SOSAccountRef account = SOSTransportCircleGetAccount(transport);
165    CFMutableDictionaryRef handledRetirementMessages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
166    CFDictionaryForEach(circle_retirement_messages_table, ^(const void *key, const void *value) {
167        if (isString(key) && isDictionary(value)) {
168            CFStringRef circle_name = (CFStringRef) key;
169
170            CFDictionaryRef retirment_dictionary = (CFDictionaryRef) value;
171            CFDictionaryForEach(retirment_dictionary, ^(const void *key, const void *value) {
172                if(isData(value)) {
173                    SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
174                    if(pi && CFEqual(key, SOSPeerInfoGetPeerID(pi)) && SOSPeerInfoInspectRetirementTicket(pi, error)) {
175                        CFMutableDictionaryRef circle_retirements = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account->retired_peers, circle_name);
176                        CFDictionarySetValue(circle_retirements, key, value);
177
178                        SOSAccountRecordRetiredPeerInCircleNamed(account, circle_name, pi);
179
180                        CFMutableArrayRef handledRetirementIDs = CFDictionaryEnsureCFArrayAndGetCurrentValue(handledRetirementMessages, circle_name);
181                        CFArrayAppendValue(handledRetirementIDs, SOSPeerInfoGetPeerID(pi));
182                    }
183                    CFReleaseSafe(pi);
184                }
185            });
186        }
187    });
188    return handledRetirementMessages;
189}
190
191static CFArrayRef handleCircleMessages(SOSTransportCircleRef transport, CFMutableDictionaryRef circle_circle_messages_table, CFErrorRef *error){
192    CFMutableArrayRef handledKeys = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
193    CFDictionaryForEach(circle_circle_messages_table, ^(const void *key, const void *value) {
194        CFErrorRef circleMessageError = NULL;
195        if (!SOSAccountHandleCircleMessage(SOSTransportCircleGetAccount(transport), key, value, &circleMessageError)) {
196            secerror("Error handling circle message %@ (%@): %@", key, value, circleMessageError);
197        }
198        else{
199            CFStringRef circle_id = (CFStringRef) key;
200            CFArrayAppendValue(handledKeys, circle_id);
201        }
202        CFReleaseNull(circleMessageError);
203    });
204
205    return handledKeys;
206}
207
208bool SOSTransportCircleKVSAppendKeyInterest(SOSTransportCircleKVSRef transport, CFMutableArrayRef alwaysKeys, CFMutableArrayRef afterFirstUnlockKeys, CFMutableArrayRef unlockedKeys, CFErrorRef *error){
209
210    CFStringRef circle_name = NULL;
211    CFStringRef circle_key = NULL;
212
213    if(SOSAccountHasPublicKey(SOSTransportCircleGetAccount((SOSTransportCircleRef)transport), NULL)){
214        require_quiet(circle_name = SOSTransportCircleKVSGetCircleName(transport), fail);
215        require_quiet(circle_key = SOSCircleKeyCreateWithName(circle_name, error), fail);
216
217        SOSAccountRef account = SOSTransportCircleGetAccount((SOSTransportCircleRef)transport);
218        require_quiet(account, fail);
219        SOSCircleRef circle = SOSAccountFindCircle(account, circle_name, error);
220        require_quiet(circle, fail);
221
222        SOSCircleForEachActivePeer(circle, ^(SOSPeerInfoRef peer) {
223            CFStringRef retirement_key = SOSRetirementKeyCreateWithCircleNameAndPeer(circle_name, SOSPeerInfoGetPeerID(peer));
224            CFArrayAppendValue(alwaysKeys, retirement_key);
225            CFReleaseNull(retirement_key);
226        });
227
228        CFArrayAppendValue(alwaysKeys, circle_key);
229
230        CFReleaseNull(circle_key);
231    }
232    return true;
233
234fail:
235    CFReleaseNull(circle_key);
236    return false;
237}
238
239static bool SOSTransportCircleKVSUpdateKVS(SOSTransportCircleRef transport, CFDictionaryRef changes, CFErrorRef *error){
240    CloudKeychainReplyBlock log_error = ^(CFDictionaryRef returnedValues __unused, CFErrorRef error) {
241        if (error) {
242            secerror("Error putting: %@", error);
243            CFReleaseSafe(error);
244        }
245    };
246
247    SOSCloudKeychainPutObjectsInCloud(changes, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), log_error);
248    return true;
249}
250