1/*
2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
3 */
4
5/*
6 * SOSAccount.c -  Implementation of the secure object syncing account.
7 * An account contains a SOSCircle for each protection domain synced.
8 */
9
10#include "SOSAccountPriv.h"
11#include <SecureObjectSync/SOSPeerInfoCollections.h>
12#include <SecureObjectSync/SOSTransportCircle.h>
13#include <SecureObjectSync/SOSTransportMessage.h>
14#include <SecureObjectSync/SOSKVSKeys.h>
15#include <SecureObjectSync/SOSTransportKeyParameter.h>
16#include <SecureObjectSync/SOSTransportKeyParameterKVS.h>
17#include <SecureObjectSync/SOSEngine.h>
18#include <SecureObjectSync/SOSPeerCoder.h>
19
20CFGiblisWithCompareFor(SOSAccount);
21
22
23bool SOSAccountEnsureFactoryCircles(SOSAccountRef a)
24{
25    bool result = false;
26    if (a)
27    {
28        require(a->factory, xit);
29        CFArrayRef circle_names = a->factory->copy_names(a->factory);
30        require(circle_names, xit);
31        CFArrayForEach(circle_names, ^(const void*name) {
32            if (isString(name))
33                SOSAccountEnsureCircle(a, (CFStringRef)name, NULL);
34        });
35
36        CFReleaseNull(circle_names);
37        result = true;
38    }
39xit:
40    return result;
41}
42
43
44SOSAccountRef SOSAccountCreateBasic(CFAllocatorRef allocator,
45                                           CFDictionaryRef gestalt,
46                                           SOSDataSourceFactoryRef factory) {
47    SOSAccountRef a = CFTypeAllocate(SOSAccount, struct __OpaqueSOSAccount, allocator);
48
49    a->queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
50
51    a->gestalt = CFRetainSafe(gestalt);
52
53    a->circles = CFDictionaryCreateMutableForCFTypes(allocator);
54    a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
55
56    a->factory = factory; // We adopt the factory. kthanksbai.
57
58    a->change_blocks = CFArrayCreateMutableForCFTypes(allocator);
59
60    a->departure_code = kSOSNeverAppliedToCircle;
61
62    a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
63    a->circle_transports = CFDictionaryCreateMutableForCFTypes(allocator);
64    a->message_transports = CFDictionaryCreateMutableForCFTypes(allocator);
65
66    return a;
67}
68
69
70
71
72bool SOSAccountUpdateGestalt(SOSAccountRef account, CFDictionaryRef new_gestalt)
73{
74    if (CFEqual(new_gestalt, account->gestalt))
75        return false;
76    SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
77        if (SOSFullPeerInfoUpdateGestalt(full_peer, new_gestalt, NULL)) {
78            SOSAccountModifyCircle(account, SOSCircleGetName(circle),
79                                   NULL, ^(SOSCircleRef circle_to_change) {
80                                       return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(full_peer));
81                                   });
82        };
83    });
84
85    CFRetainAssign(account->gestalt, new_gestalt);
86    return true;
87}
88
89SOSAccountRef SOSAccountCreate(CFAllocatorRef allocator,
90                               CFDictionaryRef gestalt,
91                               SOSDataSourceFactoryRef factory) {
92    SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory);
93
94    a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
95
96    SOSAccountEnsureFactoryCircles(a);
97
98    return a;
99}
100
101static void SOSAccountDestroy(CFTypeRef aObj) {
102    SOSAccountRef a = (SOSAccountRef) aObj;
103
104    // We don't own the factory, meerly have a reference to the singleton
105    //    don't free it.
106    //   a->factory
107
108    CFReleaseNull(a->gestalt);
109    CFReleaseNull(a->circle_identities);
110    CFReleaseNull(a->circles);
111    CFReleaseNull(a->retired_peers);
112
113    a->user_public_trusted = false;
114    CFReleaseNull(a->user_public);
115    CFReleaseNull(a->user_key_parameters);
116
117    SOSAccountPurgePrivateCredential(a);
118    CFReleaseNull(a->previous_public);
119
120    a->departure_code = kSOSNeverAppliedToCircle;
121    CFReleaseNull(a->message_transports);
122    CFReleaseNull(a->key_transport);
123    CFReleaseNull(a->circle_transports);
124    dispatch_release(a->queue);
125}
126
127void SOSAccountSetToNew(SOSAccountRef a) {
128    CFAllocatorRef allocator = CFGetAllocator(a);
129    CFReleaseNull(a->circle_identities);
130    CFReleaseNull(a->circles);
131    CFReleaseNull(a->retired_peers);
132
133    CFReleaseNull(a->user_key_parameters);
134    CFReleaseNull(a->user_public);
135    CFReleaseNull(a->previous_public);
136    CFReleaseNull(a->_user_private);
137
138    CFReleaseNull(a->key_transport);
139    CFReleaseNull(a->circle_transports);
140    CFReleaseNull(a->message_transports);
141
142    a->user_public_trusted = false;
143    a->departure_code = kSOSNeverAppliedToCircle;
144    a->user_private_timer = 0;
145    a->lock_notification_token = 0;
146
147    // keeping gestalt;
148    // keeping factory;
149    // Live Notification
150    // change_blocks;
151    // update_interest_block;
152    // update_block;
153
154    a->circles = CFDictionaryCreateMutableForCFTypes(allocator);
155    a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
156    a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
157
158    a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
159    a->circle_transports = (CFMutableDictionaryRef)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
160    a->message_transports = (CFMutableDictionaryRef)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
161
162    SOSAccountEnsureFactoryCircles(a);
163}
164
165
166static CFStringRef SOSAccountCopyDescription(CFTypeRef aObj) {
167    SOSAccountRef a = (SOSAccountRef) aObj;
168
169    return CFStringCreateWithFormat(NULL, NULL, CFSTR("<SOSAccount@%p: Gestalt: %@\n Circles: %@ CircleIDs: %@>"), a, a->gestalt, a->circles, a->circle_identities);
170}
171
172static Boolean SOSAccountCompare(CFTypeRef lhs, CFTypeRef rhs)
173{
174    SOSAccountRef laccount = (SOSAccountRef) lhs;
175    SOSAccountRef raccount = (SOSAccountRef) rhs;
176
177    return CFEqual(laccount->gestalt, raccount->gestalt)
178        && CFEqual(laccount->circles, raccount->circles)
179        && CFEqual(laccount->circle_identities, raccount->circle_identities);
180    //  ??? retired_peers
181}
182
183dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) {
184    return account->queue;
185}
186
187CFDictionaryRef SOSAccountGetMessageTransports(SOSAccountRef account){
188    return account->message_transports;
189}
190
191
192void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account){
193    account->user_public_trusted = true;
194}
195
196CFArrayRef SOSAccountCopyAccountIdentityPeerInfos(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef* error)
197{
198    CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(allocator);
199
200    CFDictionaryForEach(account->circle_identities, ^(const void *key, const void *value) {
201        SOSFullPeerInfoRef fpi = (SOSFullPeerInfoRef) value;
202
203        CFArrayAppendValue(result, SOSFullPeerInfoGetPeerInfo(fpi));
204    });
205
206    return result;
207}
208
209static bool SOSAccountThisDeviceCanSyncWithCircle(SOSAccountRef account, SOSCircleRef circle) {
210    CFErrorRef error = NULL;
211    SOSFullPeerInfoRef myfpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, SOSCircleGetName(circle), &error);
212    SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(myfpi);
213    CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
214    return SOSCircleHasPeerWithID(circle, myPeerID, &error);
215}
216
217static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account, CFStringRef circleName, CFStringRef peerID) {
218    CFErrorRef error = NULL;
219    SOSFullPeerInfoRef myfpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, circleName, &error);
220    if (!myfpi) {
221        return false;
222    }
223    SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(myfpi);
224    CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
225    return CFEqualSafe(myPeerID, peerID);
226}
227
228bool SOSAccountSyncWithAllPeers(SOSAccountRef account, CFErrorRef *error)
229{
230    __block bool result = true;
231
232    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
233
234        if (SOSAccountThisDeviceCanSyncWithCircle(account, circle)) {
235            CFStringRef circleName = SOSCircleGetName(circle);
236            SOSTransportMessageRef thisPeerTransport = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
237;
238            CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
239
240            SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
241                // Figure out transport for peer; for now we always use KVS
242                CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
243                if (!SOSAccountIsThisPeerIDMe(account, circleName, peerID)) {
244                    CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circleName), peerID);
245                }
246            });
247
248            result &= SOSTransportMessageSyncWithPeers(thisPeerTransport, circleToPeerIDs, error);
249
250            CFReleaseNull(circleToPeerIDs);
251        }
252    });
253
254    // Tell each transport to sync with its collection of peers we know we should sync with.
255
256
257    if (result)
258        SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);
259
260
261    return result;
262}
263
264bool SOSAccountCleanupAfterPeer(SOSAccountRef account, size_t seconds, SOSCircleRef circle,
265                                SOSPeerInfoRef cleanupPeer, CFErrorRef* error)
266{
267    bool success = false;
268    if(!SOSAccountIsMyPeerActiveInCircle(account, circle, NULL)) return true;
269
270    SOSPeerInfoRef myPeerInfo = SOSAccountGetMyPeerInCircle(account, circle, error);
271    require(myPeerInfo, xit);
272
273    CFStringRef cleanupPeerID = SOSPeerInfoGetPeerID(cleanupPeer);
274    CFStringRef circle_name = SOSCircleGetName(circle);
275
276    if (CFEqual(cleanupPeerID, SOSPeerInfoGetPeerID(myPeerInfo))) {
277        CFErrorRef destroyError = NULL;
278        if (!SOSAccountDestroyCirclePeerInfo(account, circle, &destroyError)) {
279            secerror("Unable to destroy peer info: %@", destroyError);
280        }
281        CFReleaseSafe(destroyError);
282
283        account->departure_code = kSOSWithdrewMembership;
284
285        return true;
286    }
287
288    CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
289    CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circle_name), cleanupPeerID);
290
291
292    CFErrorRef localError = NULL;
293    SOSTransportMessageRef tMessage = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
294    if (!SOSTransportMessageCleanupAfterPeerMessages(tMessage, circleToPeerIDs, &localError)) {
295        secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID, localError);
296    }
297    CFReleaseNull(localError);
298    SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(circle));
299    if(SOSPeerInfoRetireRetirementTicket(seconds, cleanupPeer)) {
300        if (!SOSTransportCircleExpireRetirementRecords(tCircle, circleToPeerIDs, &localError)) {
301            secnotice("account", "Failed to cleanup after peer %@ retirement: %@", cleanupPeerID, localError);
302        }
303    }
304    CFReleaseNull(localError);
305
306    CFReleaseNull(circleToPeerIDs);
307
308xit:
309    return success;
310}
311
312bool SOSAccountCleanupRetirementTickets(SOSAccountRef account, size_t seconds, CFErrorRef* error) {
313    CFMutableDictionaryRef retirements_to_remove = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
314    CFDictionaryRef original_retired_peers = account->retired_peers;
315    __block bool success = true;
316    account->retired_peers = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
317
318    CFDictionaryForEach(original_retired_peers, ^(const void *key, const void *value) {
319        if (isString(key) && isDictionary(value)) {
320            CFStringRef circle_name = key;
321            __block CFMutableDictionaryRef still_active_circle_retirements = NULL;
322            CFDictionaryForEach((CFMutableDictionaryRef) value, ^(const void *key, const void *value) {
323                if (isString(key) && isData(value)) {
324                    CFStringRef retired_peer_id = (CFStringRef) key;
325                    SOSPeerInfoRef retired_peer = SOSPeerInfoCreateFromData(kCFAllocatorDefault, NULL, (CFDataRef) value);
326                    if (retired_peer && SOSPeerInfoIsRetirementTicket(retired_peer) && CFEqual(retired_peer_id, SOSPeerInfoGetPeerID(retired_peer))) {
327                        // He's a retired peer all right, if he's active or not yet expired we keep a record of his retirement.
328                        // if not, clear any recordings of his retirement from our transport.
329                        if (SOSAccountIsActivePeerInCircleNamed(account, circle_name, retired_peer_id, NULL) ||
330                            !SOSPeerInfoRetireRetirementTicket(seconds, retired_peer)) {
331                            // He's still around or not expired. Keep record.
332                            if (still_active_circle_retirements == NULL) {
333                                still_active_circle_retirements = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account->retired_peers, circle_name);
334                            }
335                            CFDictionarySetValue(still_active_circle_retirements, retired_peer_id, value);
336                        } else {
337                            CFMutableArrayRef retirements = CFDictionaryEnsureCFArrayAndGetCurrentValue(retirements_to_remove, circle_name);
338                            CFArrayAppendValue(retirements, retired_peer_id);
339                        }
340                    }
341                    CFReleaseNull(retired_peer);
342                }
343            });
344
345            SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, circle_name);
346            success &= SOSTransportCircleExpireRetirementRecords(tCircle, retirements_to_remove, error);
347        }
348    });
349
350    CFReleaseNull(original_retired_peers);
351    CFReleaseNull(retirements_to_remove);
352
353    return success;
354}
355
356bool SOSAccountScanForRetired(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
357    __block CFMutableDictionaryRef circle_retirees = (CFMutableDictionaryRef) CFDictionaryGetValue(account->retired_peers, SOSCircleGetName(circle));
358
359    SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
360        CFStringRef peer_id = SOSPeerInfoGetPeerID(peer);
361        if(!circle_retirees || !CFDictionaryGetValueIfPresent(circle_retirees, peer_id, NULL)) {
362            if (!circle_retirees) {
363                circle_retirees = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account->retired_peers, SOSCircleGetName(circle));
364            }
365            CFDataRef value = SOSPeerInfoCopyEncodedData(peer, NULL, NULL);
366            if(value) {
367                CFDictionarySetValue(circle_retirees, peer_id, value);
368                SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, peer, error);
369            }
370            CFReleaseSafe(value);
371        }
372    });
373    return true;
374}
375
376SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccountRef account, SOSCircleRef starting_circle, CFErrorRef *error) {
377    CFStringRef circle_to_mod = SOSCircleGetName(starting_circle);
378
379    SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
380    if(!new_circle) return NULL;
381
382    CFDictionaryRef circle_retirements = CFDictionaryGetValue(account->retired_peers, circle_to_mod);
383
384    if (isDictionary(circle_retirements)) {
385        CFDictionaryForEach(circle_retirements, ^(const void* id, const void* value) {
386            if (isData(value)) {
387                SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
388                if (pi && CFEqualSafe(id, SOSPeerInfoGetPeerID(pi))) {
389                    SOSCircleUpdatePeerInfo(new_circle, pi);
390                }
391                CFReleaseSafe(pi);
392            }
393        });
394    }
395
396    if(SOSCircleCountPeers(new_circle) == 0) {
397        SOSCircleResetToEmpty(new_circle, NULL);
398    }
399
400    return new_circle;
401}
402
403//
404// MARK: Circle Membership change notificaion
405//
406
407void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
408    CFArrayAppendValue(a->change_blocks, changeBlock);
409}
410
411void SOSAccountRemoveChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
412    CFArrayRemoveAllValue(a->change_blocks, changeBlock);
413}
414
415void SOSAccountAddSyncablePeerBlock(SOSAccountRef a, CFStringRef ds_name, SOSAccountSyncablePeersBlock changeBlock) {
416    if (!changeBlock) return;
417
418    CFRetainSafe(ds_name);
419    SOSAccountCircleMembershipChangeBlock block_to_register = ^void (SOSCircleRef new_circle,
420                                                                     CFSetRef added_peers, CFSetRef removed_peers,
421                                                                     CFSetRef added_applicants, CFSetRef removed_applicants) {
422
423        if (!CFEqualSafe(SOSCircleGetName(new_circle), ds_name))
424            return;
425
426        SOSPeerInfoRef myPi = SOSAccountGetMyPeerInCircle(a, new_circle, NULL);
427        CFStringRef myPi_id = myPi ? SOSPeerInfoGetPeerID(myPi) : NULL;
428
429        CFMutableArrayRef peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
430        CFMutableArrayRef added_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
431        CFMutableArrayRef removed_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
432
433        if (SOSCircleHasPeer(new_circle, myPi, NULL)) {
434            SOSCircleForEachPeer(new_circle, ^(SOSPeerInfoRef peer) {
435                CFArrayAppendValueIfNot(peer_ids, SOSPeerInfoGetPeerID(peer), myPi_id);
436            });
437
438            CFSetForEach(added_peers, ^(const void *value) {
439                CFArrayAppendValueIfNot(added_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
440            });
441
442            CFSetForEach(removed_peers, ^(const void *value) {
443                CFArrayAppendValueIfNot(removed_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
444            });
445        }
446
447        if (CFArrayGetCount(peer_ids) || CFSetContainsValue(removed_peers, myPi))
448            changeBlock(peer_ids, added_ids, removed_ids);
449
450        CFReleaseSafe(peer_ids);
451        CFReleaseSafe(added_ids);
452        CFReleaseSafe(removed_ids);
453    };
454
455    CFRetainSafe(changeBlock);
456    SOSAccountAddChangeBlock(a, Block_copy(block_to_register));
457
458    CFSetRef empty = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
459    SOSCircleRef circle = (SOSCircleRef) CFDictionaryGetValue(a->circles, ds_name);
460    if (circle) {
461        block_to_register(circle, empty, empty, empty, empty);
462    }
463    CFReleaseSafe(empty);
464}
465
466
467bool sosAccountLeaveCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
468    SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, NULL);
469    if(!fpi) return false;
470	CFErrorRef localError = NULL;
471	SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
472    CFStringRef retire_id = SOSPeerInfoGetPeerID(retire_peer);
473
474    // Account should move away from a dictionary of KVS keys to a Circle -> Peer -> Retirement ticket storage soonish.
475	CFStringRef retire_key = SOSRetirementKeyCreateWithCircleAndPeer(circle, retire_id);
476	CFDataRef retire_value = NULL;
477    bool retval = false;
478    bool writeCircle = false;
479
480    // Create a Retirement Ticket and store it in the retired_peers of the account.
481    require_action_quiet(retire_peer, errout, secerror("Create ticket failed for peer %@: %@", fpi, localError));
482	retire_value = SOSPeerInfoCopyEncodedData(retire_peer, NULL, &localError);
483    require_action_quiet(retire_value, errout, secerror("Failed to encode retirement peer %@: %@", retire_peer, localError));
484
485    // See if we need to repost the circle we could either be an applicant or a peer already in the circle
486    if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
487	    // Remove our application if we have one.
488	    SOSCircleWithdrawRequest(circle, retire_peer, NULL);
489        writeCircle = true;
490    } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
491        if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
492            CFErrorRef cleanupError = NULL;
493            if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retire_peer, &cleanupError))
494                secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
495            CFReleaseSafe(cleanupError);
496        }
497        writeCircle = true;
498    }
499
500    // Store the retirement record locally.
501    CFDictionarySetValue(account->retired_peers, retire_key, retire_value);
502
503    // Write retirement to Transport
504    SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(circle));
505    SOSTransportCirclePostRetirement(tCircle, SOSCircleGetName(circle), retire_id, retire_value, NULL); // TODO: Handle errors?
506
507    // Kill peer key but don't return error if we can't.
508    if(!SOSAccountDestroyCirclePeerInfo(account, circle, &localError))
509        secerror("Couldn't purge key for peer %@ on retirement: %@", fpi, localError);
510
511    if (writeCircle) {
512        CFDataRef circle_data = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, error);
513
514        if (circle_data) {
515            SOSTransportCirclePostCircle(tCircle, SOSCircleGetName(circle), circle_data, NULL); // TODO: Handle errors?
516        }
517        CFReleaseNull(circle_data);
518    }
519    retval = true;
520
521errout:
522    CFReleaseNull(localError);
523    CFReleaseNull(retire_peer);
524    CFReleaseNull(retire_key);
525    CFReleaseNull(retire_value);
526    return retval;
527}
528
529/*
530    NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
531    local value that has been overwritten by a distant value. If there is no
532    conflict between the local and the distant values when doing the initial
533    sync (e.g. if the cloud has no data stored or the client has not stored
534    any data yet), you'll never see that notification.
535
536    NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
537    with server but initial round trip with server does not imply
538    NSUbiquitousKeyValueStoreInitialSyncChange.
539 */
540
541
542//
543// MARK: Status summary
544//
545
546static SOSCCStatus SOSCCCircleStatus(SOSCircleRef circle) {
547    if (SOSCircleCountPeers(circle) == 0)
548        return kSOSCCCircleAbsent;
549
550    return kSOSCCNotInCircle;
551}
552
553static SOSCCStatus SOSCCThisDeviceStatusInCircle(SOSCircleRef circle, SOSPeerInfoRef this_peer) {
554    if (SOSCircleCountPeers(circle) == 0)
555        return kSOSCCCircleAbsent;
556
557    if (SOSCircleHasPeer(circle, this_peer, NULL))
558        return kSOSCCInCircle;
559
560    if (SOSCircleHasApplicant(circle, this_peer, NULL))
561        return kSOSCCRequestPending;
562
563    return kSOSCCNotInCircle;
564}
565
566static SOSCCStatus UnionStatus(SOSCCStatus accumulated_status, SOSCCStatus additional_circle_status) {
567    switch (additional_circle_status) {
568        case kSOSCCInCircle:
569            return accumulated_status;
570        case kSOSCCRequestPending:
571            return (accumulated_status == kSOSCCInCircle) ?
572            kSOSCCRequestPending :
573            accumulated_status;
574        case kSOSCCNotInCircle:
575            return (accumulated_status == kSOSCCInCircle ||
576                    accumulated_status == kSOSCCRequestPending) ?
577            kSOSCCNotInCircle :
578            accumulated_status;
579        case kSOSCCCircleAbsent:
580            return (accumulated_status == kSOSCCInCircle ||
581                    accumulated_status == kSOSCCRequestPending ||
582                    accumulated_status == kSOSCCNotInCircle) ?
583            kSOSCCCircleAbsent :
584            accumulated_status;
585        default:
586            return additional_circle_status;
587    };
588
589}
590
591SOSCCStatus SOSAccountIsInCircles(SOSAccountRef account, CFErrorRef* error) {
592    if (!SOSAccountHasPublicKey(account, error)) {
593        return kSOSCCError;
594    }
595
596    __block bool set_once = false;
597    __block SOSCCStatus status = kSOSCCInCircle;
598
599    SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
600        set_once = true;
601        status = UnionStatus(status, kSOSCCNotInCircle);
602    }, ^(SOSCircleRef circle) {
603        set_once = true;
604        status = UnionStatus(status, SOSCCCircleStatus(circle));
605    }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
606        set_once = true;
607        SOSCCStatus circle_status = SOSCCThisDeviceStatusInCircle(circle, SOSFullPeerInfoGetPeerInfo(full_peer));
608        status = UnionStatus(status, circle_status);
609    });
610
611    if (!set_once)
612        status = kSOSCCCircleAbsent;
613
614    return status;
615}
616
617//
618// MARK: Account Reset Circles
619//
620
621static bool SOSAccountResetThisCircleToOffering(SOSAccountRef account, SOSCircleRef circle, SecKeyRef user_key, CFErrorRef *error) {
622    SOSFullPeerInfoRef myCirclePeer = SOSAccountGetMyFullPeerInCircle(account, circle, error);
623    if (!myCirclePeer)
624        return false;
625
626    SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
627        bool result = false;
628        SOSFullPeerInfoRef cloud_identity = NULL;
629        CFErrorRef localError = NULL;
630
631        require_quiet(SOSCircleResetToOffering(circle, user_key, myCirclePeer, &localError), err_out);
632
633        {
634            SOSPeerInfoRef cloud_peer = GenerateNewCloudIdentityPeerInfo(error);
635            require_quiet(cloud_peer, err_out);
636            cloud_identity = CopyCloudKeychainIdentity(cloud_peer, error);
637            CFReleaseNull(cloud_peer);
638            require_quiet(cloud_identity, err_out);
639        }
640
641        account->departure_code = kSOSNeverLeftCircle;
642        require_quiet(SOSCircleRequestAdmission(circle, user_key, cloud_identity, &localError), err_out);
643        require_quiet(SOSCircleAcceptRequest(circle, user_key, myCirclePeer, SOSFullPeerInfoGetPeerInfo(cloud_identity), &localError), err_out);
644        result = true;
645        SOSAccountPublishCloudParameters(account, NULL);
646
647    err_out:
648        if (result == false)
649            secerror("error resetting circle (%@) to offering: %@", circle, localError);
650        if (localError && error && *error == NULL) {
651            *error = localError;
652            localError = NULL;
653        }
654        CFReleaseNull(localError);
655        CFReleaseNull(cloud_identity);
656        return result;
657    });
658
659    return true;
660}
661
662
663bool SOSAccountResetToOffering(SOSAccountRef account, CFErrorRef* error) {
664    SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
665    if (!user_key)
666        return false;
667
668    __block bool result = true;
669
670    SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
671        SOSCircleRef circle = SOSCircleCreate(NULL, name, NULL);
672        if (circle)
673            CFDictionaryAddValue(account->circles, name, circle);
674
675        SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
676    }, ^(SOSCircleRef circle) {
677        SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
678    }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
679        SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
680    });
681
682    return result;
683}
684
685bool SOSAccountResetToEmpty(SOSAccountRef account, CFErrorRef* error) {
686    if (!SOSAccountHasPublicKey(account, error))
687        return false;
688
689    __block bool result = true;
690    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
691        SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
692            if (!SOSCircleResetToEmpty(circle, error))
693            {
694                secerror("error: %@", *error);
695                result = false;
696            }
697            account->departure_code = kSOSWithdrewMembership;
698            return result;
699        });
700    });
701
702    return result;
703}
704
705
706//
707// MARK: Joining
708//
709
710static bool SOSAccountJoinThisCircle(SOSAccountRef account, SecKeyRef user_key,
711                                     SOSCircleRef circle, bool use_cloud_peer, CFErrorRef* error) {
712    __block bool result = false;
713    __block SOSFullPeerInfoRef cloud_full_peer = NULL;
714
715    SOSFullPeerInfoRef myCirclePeer = SOSAccountGetMyFullPeerInCircle(account, circle, error);
716
717    require_action_quiet(myCirclePeer, fail,
718                         SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Can't find/create peer for circle: %@"), circle));
719    if (use_cloud_peer) {
720        cloud_full_peer = SOSCircleGetiCloudFullPeerInfoRef(circle);
721    }
722
723    if (SOSCircleCountPeers(circle) == 0) {
724        result = SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
725    } else {
726        SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
727            result = SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
728            account->departure_code = kSOSNeverLeftCircle;
729            if(result && cloud_full_peer) {
730                CFErrorRef localError = NULL;
731                CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
732                require_quiet(cloudid, finish);
733                require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
734                require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
735            finish:
736                if (localError){
737                    secerror("Failed to join with cloud identity: %@", localError);
738                    CFReleaseNull(localError);
739                }
740            }
741            return result;
742        });
743    }
744
745fail:
746    CFReleaseNull(cloud_full_peer);
747    return result;
748}
749
750static bool SOSAccountJoinCircles_internal(SOSAccountRef account, bool use_cloud_identity, CFErrorRef* error) {
751    SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
752    if (!user_key)
753        return false;
754
755    __block bool success = true;
756
757    SOSAccountForEachKnownCircle(account, ^(CFStringRef name) { // Incompatible
758        success = false;
759        SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle"), NULL, error);
760    }, ^(SOSCircleRef circle) { //no peer
761        success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
762    }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {   // Have Peer
763        SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(full_peer);
764        if(SOSCircleHasPeer(circle, myPeer, NULL)) goto already_present;
765        if(SOSCircleHasApplicant(circle, myPeer, NULL))  goto already_applied;
766        if(SOSCircleHasRejectedApplicant(circle, myPeer, NULL)) {
767            SOSCircleRemoveRejectedPeer(circle, myPeer, NULL);
768        }
769
770        secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(circle));
771        CFErrorRef localError = NULL;
772        if (!SOSAccountDestroyCirclePeerInfo(account, circle, &localError)) {
773            secerror("Failed to destroy peer (%@) during application, error=%@", myPeer, localError);
774            CFReleaseNull(localError);
775        }
776    already_applied:
777        success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
778        return;
779    already_present:
780        success = true;
781        return;
782    });
783
784    if(success) account->departure_code = kSOSNeverLeftCircle;
785    return success;
786}
787
788bool SOSAccountJoinCircles(SOSAccountRef account, CFErrorRef* error) {
789    return SOSAccountJoinCircles_internal(account, false, error);
790}
791
792CFStringRef SOSAccountGetDeviceID(SOSAccountRef account, CFErrorRef *error){
793    __block CFStringRef result = NULL;
794    __block CFStringRef temp = NULL;
795    SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
796            SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
797            if(fpi){
798                SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
799                if(myPeer){
800                    temp = SOSPeerInfoGetDeviceID(myPeer);
801                    if(!isNull(temp)){
802                        result = CFStringCreateCopy(kCFAllocatorDefault, temp);
803                    }
804                }
805                else{
806                    secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle));
807                }
808            }
809            else{
810                secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle));
811            }
812    });
813    return result;
814}
815
816bool SOSAccountSetMyDSID(SOSAccountRef account, CFStringRef IDS, CFErrorRef* error){
817    __block bool result = false;
818
819    SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
820        SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
821            SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
822            if(fpi){
823                SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
824                if(myPeer){
825                    SOSPeerInfoSetDeviceID(myPeer, IDS);
826                    result = true;
827                }
828                else{
829                    secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle));
830                    result = false;
831                }
832            }
833            else{
834                secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle));
835                result = false;
836            }
837            return result;
838        });
839    });
840    return result;
841
842}
843bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account, CFErrorRef* error) {
844    return SOSAccountJoinCircles_internal(account, true, error);
845}
846
847
848bool SOSAccountLeaveCircles(SOSAccountRef account, CFErrorRef* error)
849{
850    __block bool result = true;
851    SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
852        SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
853            result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors!
854            return result;
855		});
856    });
857
858    account->departure_code = kSOSWithdrewMembership;
859    return result;
860}
861
862bool SOSAccountBail(SOSAccountRef account, uint64_t limit_in_seconds, CFErrorRef* error) {
863    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
864    dispatch_group_t group = dispatch_group_create();
865    __block bool result = false;
866    secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
867    // Add a task to the group
868    dispatch_group_async(group, queue, ^{
869        SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
870            SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
871                result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors!
872                return result;
873            });
874        });
875
876        account->departure_code = kSOSWithdrewMembership;
877    });
878    dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
879
880    dispatch_group_wait(group, milestone);
881    dispatch_release(group);
882    return result;
883}
884
885
886//
887// MARK: Application
888//
889
890static void for_each_applicant_in_each_circle(SOSAccountRef account, CFArrayRef peer_infos,
891                                              bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
892
893    SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
894        SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(full_peer);
895        CFErrorRef peer_error = NULL;
896        if (SOSCircleHasPeer(circle, me, &peer_error)) {
897            CFArrayForEach(peer_infos, ^(const void *value) {
898                SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
899                if (SOSCircleHasApplicant(circle, peer, NULL)) {
900                    SOSAccountModifyCircle(account, SOSCircleGetName(circle), NULL, ^(SOSCircleRef circle) {
901                        return action(circle, full_peer, peer);
902                    });
903                }
904            });
905        }
906        if (peer_error)
907            secerror("Got error in SOSCircleHasPeer: %@", peer_error);
908        CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
909    });
910}
911
912bool SOSAccountAcceptApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
913    SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
914    if (!user_key)
915        return false;
916
917    __block bool success = true;
918	__block int64_t num_peers = 0;
919
920    for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
921        bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
922        if (!accepted)
923            success = false;
924		else
925			num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
926        return accepted;
927    });
928
929    return success;
930}
931
932bool SOSAccountRejectApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
933    __block bool success = true;
934	__block int64_t num_peers = 0;
935
936    for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
937        bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
938        if (!rejected)
939            success = false;
940		else
941			num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
942        return rejected;
943    });
944
945    return success;
946}
947
948
949
950CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccountRef account, CFErrorRef* error) {
951    return CFSTR("We're compatible, go away");
952}
953
954enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccountRef account, CFErrorRef* error) {
955    return account->departure_code;
956}
957
958
959CFArrayRef SOSAccountCopyGeneration(SOSAccountRef account, CFErrorRef *error) {
960    if (!SOSAccountHasPublicKey(account, error))
961        return NULL;
962    CFMutableArrayRef generations = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
963
964    SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
965        CFNumberRef generation = (CFNumberRef)SOSCircleGetGeneration(circle);
966        CFArrayAppendValue(generations, SOSCircleGetName(circle));
967        CFArrayAppendValue(generations, generation);
968    });
969
970    return generations;
971
972}
973
974bool SOSValidateUserPublic(SOSAccountRef account, CFErrorRef *error) {
975    if (!SOSAccountHasPublicKey(account, error))
976        return NULL;
977
978    return account->user_public_trusted;
979}
980
981
982bool SOSAccountEnsurePeerRegistration(SOSAccountRef account, CFErrorRef *error) {
983    __block bool result = true;
984
985    secnotice("updates", "Ensuring peer registration.");
986
987    SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
988    }, ^(SOSCircleRef circle) {
989    }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
990        SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
991        SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(fpi);
992        CFMutableArrayRef trusted_peer_ids = NULL;
993        CFMutableArrayRef untrusted_peer_ids = NULL;
994        CFStringRef my_id = NULL;
995        if (SOSCircleHasPeer(circle, me, NULL)) {
996            my_id = SOSPeerInfoGetPeerID(me);
997            trusted_peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
998            untrusted_peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
999            SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
1000                CFMutableArrayRef arrayToAddTo = SOSPeerInfoApplicationVerify(peer, account->user_public, NULL) ? trusted_peer_ids : untrusted_peer_ids;
1001
1002                CFArrayAppendValueIfNot(arrayToAddTo, SOSPeerInfoGetPeerID(peer), my_id);
1003            });
1004        }
1005
1006        SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(circle), NULL);
1007        if (engine)
1008            SOSEngineCircleChanged(engine, my_id, trusted_peer_ids, untrusted_peer_ids);
1009
1010        CFReleaseNull(trusted_peer_ids);
1011        CFReleaseNull(untrusted_peer_ids);
1012
1013        SOSTransportMessageRef transport = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
1014        SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
1015            if (!CFEqualSafe(me, peer)) {
1016                CFErrorRef localError = NULL;
1017                SOSPeerCoderInitializeForPeer(transport, full_peer, peer, &localError);
1018                if (localError)
1019                    secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, full_peer, localError);
1020                CFReleaseSafe(localError);
1021            }
1022        });
1023    });
1024
1025    return result;
1026}
1027