2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
6 * SOSAccount.c - Implementation of the secure object syncing account.
7 * An account contains a SOSCircle for each protection domain synced.
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>
20 CFGiblisWithCompareFor(SOSAccount
);
23 bool SOSAccountEnsureFactoryCircles(SOSAccountRef a
)
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
) {
33 SOSAccountEnsureCircle(a
, (CFStringRef
)name
, NULL
);
36 CFReleaseNull(circle_names
);
44 SOSAccountRef
SOSAccountCreateBasic(CFAllocatorRef allocator
,
45 CFDictionaryRef gestalt
,
46 SOSDataSourceFactoryRef factory
) {
47 SOSAccountRef a
= CFTypeAllocate(SOSAccount
, struct __OpaqueSOSAccount
, allocator
);
49 a
->queue
= dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL
);
51 a
->gestalt
= CFRetainSafe(gestalt
);
53 a
->circles
= CFDictionaryCreateMutableForCFTypes(allocator
);
54 a
->circle_identities
= CFDictionaryCreateMutableForCFTypes(allocator
);
56 a
->factory
= factory
; // We adopt the factory. kthanksbai.
58 a
->change_blocks
= CFArrayCreateMutableForCFTypes(allocator
);
60 a
->departure_code
= kSOSNeverAppliedToCircle
;
62 a
->key_transport
= (SOSTransportKeyParameterRef
)SOSTransportKeyParameterKVSCreate(a
, NULL
);
63 a
->circle_transports
= CFDictionaryCreateMutableForCFTypes(allocator
);
64 a
->message_transports
= CFDictionaryCreateMutableForCFTypes(allocator
);
72 bool SOSAccountUpdateGestalt(SOSAccountRef account
, CFDictionaryRef new_gestalt
)
74 if (CFEqual(new_gestalt
, account
->gestalt
))
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 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
81 return SOSCircleUpdatePeerInfo(circle_to_change
, SOSFullPeerInfoGetPeerInfo(full_peer
));
86 CFRetainAssign(account
->gestalt
, new_gestalt
);
90 SOSAccountRef
SOSAccountCreate(CFAllocatorRef allocator
,
91 CFDictionaryRef gestalt
,
92 SOSDataSourceFactoryRef factory
) {
93 SOSAccountRef a
= SOSAccountCreateBasic(allocator
, gestalt
, factory
);
95 a
->retired_peers
= CFDictionaryCreateMutableForCFTypes(allocator
);
97 SOSAccountEnsureFactoryCircles(a
);
102 static void SOSAccountDestroy(CFTypeRef aObj
) {
103 SOSAccountRef a
= (SOSAccountRef
) aObj
;
105 // We don't own the factory, meerly have a reference to the singleton
109 CFReleaseNull(a
->gestalt
);
110 CFReleaseNull(a
->circle_identities
);
111 CFReleaseNull(a
->circles
);
112 CFReleaseNull(a
->retired_peers
);
114 a
->user_public_trusted
= false;
115 CFReleaseNull(a
->user_public
);
116 CFReleaseNull(a
->user_key_parameters
);
118 SOSAccountPurgePrivateCredential(a
);
119 CFReleaseNull(a
->previous_public
);
121 a
->departure_code
= kSOSNeverAppliedToCircle
;
122 CFReleaseNull(a
->message_transports
);
123 CFReleaseNull(a
->key_transport
);
124 CFReleaseNull(a
->circle_transports
);
125 dispatch_release(a
->queue
);
128 void SOSAccountSetToNew(SOSAccountRef a
) {
129 secnotice("accountChange", "Setting Account to New");
130 CFAllocatorRef allocator
= CFGetAllocator(a
);
131 CFReleaseNull(a
->circle_identities
);
132 CFReleaseNull(a
->circles
);
133 CFReleaseNull(a
->retired_peers
);
135 CFReleaseNull(a
->user_key_parameters
);
136 CFReleaseNull(a
->user_public
);
137 CFReleaseNull(a
->previous_public
);
138 CFReleaseNull(a
->_user_private
);
140 CFReleaseNull(a
->key_transport
);
141 CFReleaseNull(a
->circle_transports
);
142 CFReleaseNull(a
->message_transports
);
144 a
->user_public_trusted
= false;
145 a
->departure_code
= kSOSNeverAppliedToCircle
;
146 a
->user_private_timer
= 0;
147 a
->lock_notification_token
= 0;
153 // update_interest_block;
156 a
->circles
= CFDictionaryCreateMutableForCFTypes(allocator
);
157 a
->circle_identities
= CFDictionaryCreateMutableForCFTypes(allocator
);
158 a
->retired_peers
= CFDictionaryCreateMutableForCFTypes(allocator
);
160 a
->key_transport
= (SOSTransportKeyParameterRef
)SOSTransportKeyParameterKVSCreate(a
, NULL
);
161 a
->circle_transports
= (CFMutableDictionaryRef
)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
162 a
->message_transports
= (CFMutableDictionaryRef
)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
164 SOSAccountEnsureFactoryCircles(a
);
168 static CFStringRef
SOSAccountCopyDescription(CFTypeRef aObj
) {
169 SOSAccountRef a
= (SOSAccountRef
) aObj
;
171 return CFStringCreateWithFormat(NULL
, NULL
, CFSTR("<SOSAccount@%p: Gestalt: %@\n Circles: %@ CircleIDs: %@>"), a
, a
->gestalt
, a
->circles
, a
->circle_identities
);
174 static Boolean
SOSAccountCompare(CFTypeRef lhs
, CFTypeRef rhs
)
176 SOSAccountRef laccount
= (SOSAccountRef
) lhs
;
177 SOSAccountRef raccount
= (SOSAccountRef
) rhs
;
179 return CFEqual(laccount
->gestalt
, raccount
->gestalt
)
180 && CFEqual(laccount
->circles
, raccount
->circles
)
181 && CFEqual(laccount
->circle_identities
, raccount
->circle_identities
);
185 dispatch_queue_t
SOSAccountGetQueue(SOSAccountRef account
) {
186 return account
->queue
;
189 CFDictionaryRef
SOSAccountGetMessageTransports(SOSAccountRef account
){
190 return account
->message_transports
;
194 void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account
){
195 account
->user_public_trusted
= true;
198 CFArrayRef
SOSAccountCopyAccountIdentityPeerInfos(SOSAccountRef account
, CFAllocatorRef allocator
, CFErrorRef
* error
)
200 CFMutableArrayRef result
= CFArrayCreateMutableForCFTypes(allocator
);
202 CFDictionaryForEach(account
->circle_identities
, ^(const void *key
, const void *value
) {
203 SOSFullPeerInfoRef fpi
= (SOSFullPeerInfoRef
) value
;
205 CFArrayAppendValue(result
, SOSFullPeerInfoGetPeerInfo(fpi
));
211 static bool SOSAccountThisDeviceCanSyncWithCircle(SOSAccountRef account
, SOSCircleRef circle
) {
212 CFErrorRef error
= NULL
;
213 SOSFullPeerInfoRef myfpi
= SOSAccountGetMyFullPeerInCircleNamedIfPresent(account
, SOSCircleGetName(circle
), &error
);
214 SOSPeerInfoRef mypi
= SOSFullPeerInfoGetPeerInfo(myfpi
);
215 CFStringRef myPeerID
= SOSPeerInfoGetPeerID(mypi
);
216 return SOSCircleHasPeerWithID(circle
, myPeerID
, &error
);
219 static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account
, CFStringRef circleName
, CFStringRef peerID
) {
220 CFErrorRef error
= NULL
;
221 SOSFullPeerInfoRef myfpi
= SOSAccountGetMyFullPeerInCircleNamedIfPresent(account
, circleName
, &error
);
225 SOSPeerInfoRef mypi
= SOSFullPeerInfoGetPeerInfo(myfpi
);
226 CFStringRef myPeerID
= SOSPeerInfoGetPeerID(mypi
);
227 return CFEqualSafe(myPeerID
, peerID
);
230 bool SOSAccountSyncWithAllPeers(SOSAccountRef account
, CFErrorRef
*error
)
232 __block
bool result
= true;
234 SOSAccountForEachCircle(account
, ^(SOSCircleRef circle
) {
236 if (SOSAccountThisDeviceCanSyncWithCircle(account
, circle
)) {
237 CFStringRef circleName
= SOSCircleGetName(circle
);
238 SOSTransportMessageRef thisPeerTransport
= (SOSTransportMessageRef
)CFDictionaryGetValue(account
->message_transports
, SOSCircleGetName(circle
));
240 CFMutableDictionaryRef circleToPeerIDs
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
242 SOSCircleForEachPeer(circle
, ^(SOSPeerInfoRef peer
) {
243 // Figure out transport for peer; for now we always use KVS
244 CFStringRef peerID
= SOSPeerInfoGetPeerID(peer
);
245 if (!SOSAccountIsThisPeerIDMe(account
, circleName
, peerID
)) {
246 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs
, circleName
), peerID
);
250 result
&= SOSTransportMessageSyncWithPeers(thisPeerTransport
, circleToPeerIDs
, error
);
252 CFReleaseNull(circleToPeerIDs
);
256 // Tell each transport to sync with its collection of peers we know we should sync with.
260 SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers
, 1);
266 bool SOSAccountCleanupAfterPeer(SOSAccountRef account
, size_t seconds
, SOSCircleRef circle
,
267 SOSPeerInfoRef cleanupPeer
, CFErrorRef
* error
)
269 bool success
= false;
270 if(!SOSAccountIsMyPeerActiveInCircle(account
, circle
, NULL
)) return true;
272 SOSPeerInfoRef myPeerInfo
= SOSAccountGetMyPeerInCircle(account
, circle
, error
);
273 require(myPeerInfo
, xit
);
275 CFStringRef cleanupPeerID
= SOSPeerInfoGetPeerID(cleanupPeer
);
276 CFStringRef circle_name
= SOSCircleGetName(circle
);
278 if (CFEqual(cleanupPeerID
, SOSPeerInfoGetPeerID(myPeerInfo
))) {
279 CFErrorRef destroyError
= NULL
;
280 if (!SOSAccountDestroyCirclePeerInfo(account
, circle
, &destroyError
)) {
281 secerror("Unable to destroy peer info: %@", destroyError
);
283 CFReleaseSafe(destroyError
);
285 account
->departure_code
= kSOSWithdrewMembership
;
290 CFMutableDictionaryRef circleToPeerIDs
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
291 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs
, circle_name
), cleanupPeerID
);
294 CFErrorRef localError
= NULL
;
295 SOSTransportMessageRef tMessage
= (SOSTransportMessageRef
)CFDictionaryGetValue(account
->message_transports
, SOSCircleGetName(circle
));
296 if (!SOSTransportMessageCleanupAfterPeerMessages(tMessage
, circleToPeerIDs
, &localError
)) {
297 secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID
, localError
);
299 CFReleaseNull(localError
);
300 SOSTransportCircleRef tCircle
= (SOSTransportCircleRef
)CFDictionaryGetValue(account
->circle_transports
, SOSCircleGetName(circle
));
301 if(SOSPeerInfoRetireRetirementTicket(seconds
, cleanupPeer
)) {
302 if (!SOSTransportCircleExpireRetirementRecords(tCircle
, circleToPeerIDs
, &localError
)) {
303 secnotice("account", "Failed to cleanup after peer %@ retirement: %@", cleanupPeerID
, localError
);
306 CFReleaseNull(localError
);
308 CFReleaseNull(circleToPeerIDs
);
314 bool SOSAccountCleanupRetirementTickets(SOSAccountRef account
, size_t seconds
, CFErrorRef
* error
) {
315 CFMutableDictionaryRef retirements_to_remove
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
316 CFDictionaryRef original_retired_peers
= account
->retired_peers
;
317 __block
bool success
= true;
318 account
->retired_peers
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
320 CFDictionaryForEach(original_retired_peers
, ^(const void *key
, const void *value
) {
321 if (isString(key
) && isDictionary(value
)) {
322 CFStringRef circle_name
= key
;
323 __block CFMutableDictionaryRef still_active_circle_retirements
= NULL
;
324 CFDictionaryForEach((CFMutableDictionaryRef
) value
, ^(const void *key
, const void *value
) {
325 if (isString(key
) && isData(value
)) {
326 CFStringRef retired_peer_id
= (CFStringRef
) key
;
327 SOSPeerInfoRef retired_peer
= SOSPeerInfoCreateFromData(kCFAllocatorDefault
, NULL
, (CFDataRef
) value
);
328 if (retired_peer
&& SOSPeerInfoIsRetirementTicket(retired_peer
) && CFEqual(retired_peer_id
, SOSPeerInfoGetPeerID(retired_peer
))) {
329 // He's a retired peer all right, if he's active or not yet expired we keep a record of his retirement.
330 // if not, clear any recordings of his retirement from our transport.
331 if (SOSAccountIsActivePeerInCircleNamed(account
, circle_name
, retired_peer_id
, NULL
) ||
332 !SOSPeerInfoRetireRetirementTicket(seconds
, retired_peer
)) {
333 // He's still around or not expired. Keep record.
334 if (still_active_circle_retirements
== NULL
) {
335 still_active_circle_retirements
= CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account
->retired_peers
, circle_name
);
337 CFDictionarySetValue(still_active_circle_retirements
, retired_peer_id
, value
);
339 CFMutableArrayRef retirements
= CFDictionaryEnsureCFArrayAndGetCurrentValue(retirements_to_remove
, circle_name
);
340 CFArrayAppendValue(retirements
, retired_peer_id
);
343 CFReleaseNull(retired_peer
);
347 SOSTransportCircleRef tCircle
= (SOSTransportCircleRef
)CFDictionaryGetValue(account
->circle_transports
, circle_name
);
348 success
&= SOSTransportCircleExpireRetirementRecords(tCircle
, retirements_to_remove
, error
);
352 CFReleaseNull(original_retired_peers
);
353 CFReleaseNull(retirements_to_remove
);
358 bool SOSAccountScanForRetired(SOSAccountRef account
, SOSCircleRef circle
, CFErrorRef
*error
) {
359 __block CFMutableDictionaryRef circle_retirees
= (CFMutableDictionaryRef
) CFDictionaryGetValue(account
->retired_peers
, SOSCircleGetName(circle
));
361 SOSCircleForEachRetiredPeer(circle
, ^(SOSPeerInfoRef peer
) {
362 CFStringRef peer_id
= SOSPeerInfoGetPeerID(peer
);
363 if(!circle_retirees
|| !CFDictionaryGetValueIfPresent(circle_retirees
, peer_id
, NULL
)) {
364 if (!circle_retirees
) {
365 circle_retirees
= CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account
->retired_peers
, SOSCircleGetName(circle
));
367 CFDataRef value
= SOSPeerInfoCopyEncodedData(peer
, NULL
, NULL
);
369 CFDictionarySetValue(circle_retirees
, peer_id
, value
);
370 SOSAccountCleanupAfterPeer(account
, RETIREMENT_FINALIZATION_SECONDS
, circle
, peer
, error
);
372 CFReleaseSafe(value
);
378 SOSCircleRef
SOSAccountCloneCircleWithRetirement(SOSAccountRef account
, SOSCircleRef starting_circle
, CFErrorRef
*error
) {
379 CFStringRef circle_to_mod
= SOSCircleGetName(starting_circle
);
381 SOSCircleRef new_circle
= SOSCircleCopyCircle(NULL
, starting_circle
, error
);
382 if(!new_circle
) return NULL
;
384 CFDictionaryRef circle_retirements
= CFDictionaryGetValue(account
->retired_peers
, circle_to_mod
);
386 if (isDictionary(circle_retirements
)) {
387 CFDictionaryForEach(circle_retirements
, ^(const void* id
, const void* value
) {
389 SOSPeerInfoRef pi
= SOSPeerInfoCreateFromData(NULL
, error
, (CFDataRef
) value
);
390 if (pi
&& CFEqualSafe(id
, SOSPeerInfoGetPeerID(pi
))) {
391 SOSCircleUpdatePeerInfo(new_circle
, pi
);
398 if(SOSCircleCountPeers(new_circle
) == 0) {
399 SOSCircleResetToEmpty(new_circle
, NULL
);
406 // MARK: Circle Membership change notificaion
409 void SOSAccountAddChangeBlock(SOSAccountRef a
, SOSAccountCircleMembershipChangeBlock changeBlock
) {
410 CFArrayAppendValue(a
->change_blocks
, changeBlock
);
413 void SOSAccountRemoveChangeBlock(SOSAccountRef a
, SOSAccountCircleMembershipChangeBlock changeBlock
) {
414 CFArrayRemoveAllValue(a
->change_blocks
, changeBlock
);
417 void SOSAccountAddSyncablePeerBlock(SOSAccountRef a
, CFStringRef ds_name
, SOSAccountSyncablePeersBlock changeBlock
) {
418 if (!changeBlock
) return;
420 CFRetainSafe(ds_name
);
421 SOSAccountCircleMembershipChangeBlock block_to_register
= ^void (SOSCircleRef new_circle
,
422 CFSetRef added_peers
, CFSetRef removed_peers
,
423 CFSetRef added_applicants
, CFSetRef removed_applicants
) {
425 if (!CFEqualSafe(SOSCircleGetName(new_circle
), ds_name
))
428 SOSPeerInfoRef myPi
= SOSAccountGetMyPeerInCircle(a
, new_circle
, NULL
);
429 CFStringRef myPi_id
= myPi
? SOSPeerInfoGetPeerID(myPi
) : NULL
;
431 CFMutableArrayRef peer_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
432 CFMutableArrayRef added_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
433 CFMutableArrayRef removed_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
435 if (SOSCircleHasPeer(new_circle
, myPi
, NULL
)) {
436 SOSCircleForEachPeer(new_circle
, ^(SOSPeerInfoRef peer
) {
437 CFArrayAppendValueIfNot(peer_ids
, SOSPeerInfoGetPeerID(peer
), myPi_id
);
440 CFSetForEach(added_peers
, ^(const void *value
) {
441 CFArrayAppendValueIfNot(added_ids
, SOSPeerInfoGetPeerID((SOSPeerInfoRef
) value
), myPi_id
);
444 CFSetForEach(removed_peers
, ^(const void *value
) {
445 CFArrayAppendValueIfNot(removed_ids
, SOSPeerInfoGetPeerID((SOSPeerInfoRef
) value
), myPi_id
);
449 if (CFArrayGetCount(peer_ids
) || CFSetContainsValue(removed_peers
, myPi
))
450 changeBlock(peer_ids
, added_ids
, removed_ids
);
452 CFReleaseSafe(peer_ids
);
453 CFReleaseSafe(added_ids
);
454 CFReleaseSafe(removed_ids
);
457 CFRetainSafe(changeBlock
);
458 SOSAccountAddChangeBlock(a
, Block_copy(block_to_register
));
460 CFSetRef empty
= CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault
);
461 SOSCircleRef circle
= (SOSCircleRef
) CFDictionaryGetValue(a
->circles
, ds_name
);
463 block_to_register(circle
, empty
, empty
, empty
, empty
);
465 CFReleaseSafe(empty
);
469 bool sosAccountLeaveCircle(SOSAccountRef account
, SOSCircleRef circle
, CFErrorRef
* error
) {
470 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInCircle(account
, circle
, NULL
);
471 if(!fpi
) return false;
472 if(!SOSFullPeerInfoValidate(fpi
, NULL
)) return false;
473 CFErrorRef localError
= NULL
;
474 SOSPeerInfoRef retire_peer
= SOSFullPeerInfoPromoteToRetiredAndCopy(fpi
, &localError
);
475 CFStringRef retire_id
= SOSPeerInfoGetPeerID(retire_peer
);
477 // Account should move away from a dictionary of KVS keys to a Circle -> Peer -> Retirement ticket storage soonish.
478 CFStringRef retire_key
= SOSRetirementKeyCreateWithCircleAndPeer(circle
, retire_id
);
479 CFDataRef retire_value
= NULL
;
481 bool writeCircle
= false;
483 // Create a Retirement Ticket and store it in the retired_peers of the account.
484 require_action_quiet(retire_peer
, errout
, secerror("Create ticket failed for peer %@: %@", fpi
, localError
));
485 retire_value
= SOSPeerInfoCopyEncodedData(retire_peer
, NULL
, &localError
);
486 require_action_quiet(retire_value
, errout
, secerror("Failed to encode retirement peer %@: %@", retire_peer
, localError
));
488 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
489 if(SOSCircleHasApplicant(circle
, retire_peer
, NULL
)) {
490 // Remove our application if we have one.
491 SOSCircleWithdrawRequest(circle
, retire_peer
, NULL
);
493 } else if (SOSCircleHasPeer(circle
, retire_peer
, NULL
)) {
494 if (SOSCircleUpdatePeerInfo(circle
, retire_peer
)) {
495 CFErrorRef cleanupError
= NULL
;
496 if (!SOSAccountCleanupAfterPeer(account
, RETIREMENT_FINALIZATION_SECONDS
, circle
, retire_peer
, &cleanupError
))
497 secerror("Error cleanup up after peer (%@): %@", retire_peer
, cleanupError
);
498 CFReleaseSafe(cleanupError
);
503 // Store the retirement record locally.
504 CFDictionarySetValue(account
->retired_peers
, retire_key
, retire_value
);
506 // Write retirement to Transport
507 SOSTransportCircleRef tCircle
= (SOSTransportCircleRef
)CFDictionaryGetValue(account
->circle_transports
, SOSCircleGetName(circle
));
508 SOSTransportCirclePostRetirement(tCircle
, SOSCircleGetName(circle
), retire_id
, retire_value
, NULL
); // TODO: Handle errors?
510 // Kill peer key but don't return error if we can't.
511 if(!SOSAccountDestroyCirclePeerInfo(account
, circle
, &localError
))
512 secerror("Couldn't purge key for peer %@ on retirement: %@", fpi
, localError
);
515 CFDataRef circle_data
= SOSCircleCopyEncodedData(circle
, kCFAllocatorDefault
, error
);
518 SOSTransportCirclePostCircle(tCircle
, SOSCircleGetName(circle
), circle_data
, NULL
); // TODO: Handle errors?
520 CFReleaseNull(circle_data
);
525 CFReleaseNull(localError
);
526 CFReleaseNull(retire_peer
);
527 CFReleaseNull(retire_key
);
528 CFReleaseNull(retire_value
);
533 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
534 local value that has been overwritten by a distant value. If there is no
535 conflict between the local and the distant values when doing the initial
536 sync (e.g. if the cloud has no data stored or the client has not stored
537 any data yet), you'll never see that notification.
539 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
540 with server but initial round trip with server does not imply
541 NSUbiquitousKeyValueStoreInitialSyncChange.
546 // MARK: Status summary
549 static SOSCCStatus
SOSCCCircleStatus(SOSCircleRef circle
) {
550 if (SOSCircleCountPeers(circle
) == 0)
551 return kSOSCCCircleAbsent
;
553 return kSOSCCNotInCircle
;
556 static SOSCCStatus
SOSCCThisDeviceStatusInCircle(SOSCircleRef circle
, SOSPeerInfoRef this_peer
) {
557 if (SOSCircleCountPeers(circle
) == 0)
558 return kSOSCCCircleAbsent
;
560 if (SOSCircleHasPeer(circle
, this_peer
, NULL
))
561 return kSOSCCInCircle
;
563 if (SOSCircleHasApplicant(circle
, this_peer
, NULL
))
564 return kSOSCCRequestPending
;
566 return kSOSCCNotInCircle
;
569 static SOSCCStatus
UnionStatus(SOSCCStatus accumulated_status
, SOSCCStatus additional_circle_status
) {
570 switch (additional_circle_status
) {
572 return accumulated_status
;
573 case kSOSCCRequestPending
:
574 return (accumulated_status
== kSOSCCInCircle
) ?
575 kSOSCCRequestPending
:
577 case kSOSCCNotInCircle
:
578 return (accumulated_status
== kSOSCCInCircle
||
579 accumulated_status
== kSOSCCRequestPending
) ?
582 case kSOSCCCircleAbsent
:
583 return (accumulated_status
== kSOSCCInCircle
||
584 accumulated_status
== kSOSCCRequestPending
||
585 accumulated_status
== kSOSCCNotInCircle
) ?
589 return additional_circle_status
;
594 SOSCCStatus
SOSAccountIsInCircles(SOSAccountRef account
, CFErrorRef
* error
) {
595 if (!SOSAccountHasPublicKey(account
, error
)) {
599 __block
bool set_once
= false;
600 __block SOSCCStatus status
= kSOSCCInCircle
;
602 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) {
604 status
= UnionStatus(status
, kSOSCCNotInCircle
);
605 }, ^(SOSCircleRef circle
) {
607 status
= UnionStatus(status
, SOSCCCircleStatus(circle
));
608 }, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) {
610 SOSCCStatus circle_status
= SOSCCThisDeviceStatusInCircle(circle
, SOSFullPeerInfoGetPeerInfo(full_peer
));
611 status
= UnionStatus(status
, circle_status
);
615 status
= kSOSCCCircleAbsent
;
621 // MARK: Account Reset Circles
624 static bool SOSAccountResetThisCircleToOffering(SOSAccountRef account
, SOSCircleRef circle
, SecKeyRef user_key
, CFErrorRef
*error
) {
625 SOSFullPeerInfoRef myCirclePeer
= SOSAccountMakeMyFullPeerInCircleNamed(account
, SOSCircleGetName(circle
), error
);
628 if(!SOSFullPeerInfoValidate(myCirclePeer
, NULL
)) return false;
631 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
633 SOSFullPeerInfoRef cloud_identity
= NULL
;
634 CFErrorRef localError
= NULL
;
636 require_quiet(SOSCircleResetToOffering(circle
, user_key
, myCirclePeer
, &localError
), err_out
);
639 SOSPeerInfoRef cloud_peer
= GenerateNewCloudIdentityPeerInfo(error
);
640 require_quiet(cloud_peer
, err_out
);
641 cloud_identity
= CopyCloudKeychainIdentity(cloud_peer
, error
);
642 CFReleaseNull(cloud_peer
);
643 require_quiet(cloud_identity
, err_out
);
646 account
->departure_code
= kSOSNeverLeftCircle
;
647 require_quiet(SOSCircleRequestAdmission(circle
, user_key
, cloud_identity
, &localError
), err_out
);
648 require_quiet(SOSCircleAcceptRequest(circle
, user_key
, myCirclePeer
, SOSFullPeerInfoGetPeerInfo(cloud_identity
), &localError
), err_out
);
650 SOSAccountPublishCloudParameters(account
, NULL
);
654 secerror("error resetting circle (%@) to offering: %@", circle
, localError
);
655 if (localError
&& error
&& *error
== NULL
) {
659 CFReleaseNull(localError
);
660 CFReleaseNull(cloud_identity
);
668 bool SOSAccountResetToOffering(SOSAccountRef account
, CFErrorRef
* error
) {
669 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
673 __block
bool result
= true;
675 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) {
676 SOSCircleRef circle
= SOSCircleCreate(NULL
, name
, NULL
);
678 CFDictionaryAddValue(account
->circles
, name
, circle
);
680 SOSAccountResetThisCircleToOffering(account
, circle
, user_key
, error
);
681 }, ^(SOSCircleRef circle
) {
682 SOSAccountResetThisCircleToOffering(account
, circle
, user_key
, error
);
683 }, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) {
684 SOSAccountResetThisCircleToOffering(account
, circle
, user_key
, error
);
690 bool SOSAccountResetToEmpty(SOSAccountRef account
, CFErrorRef
* error
) {
691 if (!SOSAccountHasPublicKey(account
, error
))
694 __block
bool result
= true;
695 SOSAccountForEachCircle(account
, ^(SOSCircleRef circle
) {
696 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
697 if (!SOSCircleResetToEmpty(circle
, error
))
699 secerror("error: %@", *error
);
702 account
->departure_code
= kSOSWithdrewMembership
;
715 static bool SOSAccountJoinThisCircle(SOSAccountRef account
, SecKeyRef user_key
,
716 SOSCircleRef circle
, bool use_cloud_peer
, CFErrorRef
* error
) {
717 __block
bool result
= false;
718 __block SOSFullPeerInfoRef cloud_full_peer
= NULL
;
720 SOSFullPeerInfoRef myCirclePeer
= SOSAccountMakeMyFullPeerInCircleNamed(account
, SOSCircleGetName(circle
), error
);
722 require_action_quiet(myCirclePeer
, fail
,
723 SOSCreateErrorWithFormat(kSOSErrorPeerNotFound
, NULL
, error
, NULL
, CFSTR("Can't find/create peer for circle: %@"), circle
));
724 if (use_cloud_peer
) {
725 cloud_full_peer
= SOSCircleGetiCloudFullPeerInfoRef(circle
);
728 if (SOSCircleCountPeers(circle
) == 0) {
729 result
= SOSAccountResetThisCircleToOffering(account
, circle
, user_key
, error
);
731 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
732 result
= SOSCircleRequestAdmission(circle
, user_key
, myCirclePeer
, error
);
733 account
->departure_code
= kSOSNeverLeftCircle
;
734 if(result
&& cloud_full_peer
) {
735 CFErrorRef localError
= NULL
;
736 CFStringRef cloudid
= SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer
));
737 require_quiet(cloudid
, finish
);
738 require_quiet(SOSCircleHasActivePeerWithID(circle
, cloudid
, &localError
), finish
);
739 require_quiet(SOSCircleAcceptRequest(circle
, user_key
, cloud_full_peer
, SOSFullPeerInfoGetPeerInfo(myCirclePeer
), &localError
), finish
);
742 secerror("Failed to join with cloud identity: %@", localError
);
743 CFReleaseNull(localError
);
751 CFReleaseNull(cloud_full_peer
);
755 static bool SOSAccountJoinCircles_internal(SOSAccountRef account
, bool use_cloud_identity
, CFErrorRef
* error
) {
756 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
760 __block
bool success
= true;
762 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) { // Incompatible
764 SOSCreateError(kSOSErrorIncompatibleCircle
, CFSTR("Incompatible circle"), NULL
, error
);
765 }, ^(SOSCircleRef circle
) { //no peer
766 success
= SOSAccountJoinThisCircle(account
, user_key
, circle
, use_cloud_identity
, error
) && success
;
767 }, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) { // Have Peer
768 SOSPeerInfoRef myPeer
= SOSFullPeerInfoGetPeerInfo(full_peer
);
769 if(SOSCircleHasPeer(circle
, myPeer
, NULL
)) goto already_present
;
770 if(SOSCircleHasApplicant(circle
, myPeer
, NULL
)) goto already_applied
;
771 if(SOSCircleHasRejectedApplicant(circle
, myPeer
, NULL
)) {
772 SOSCircleRemoveRejectedPeer(circle
, myPeer
, NULL
);
775 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer
), SOSCircleGetName(circle
));
776 CFErrorRef localError
= NULL
;
777 if (!SOSAccountDestroyCirclePeerInfo(account
, circle
, &localError
)) {
778 secerror("Failed to destroy peer (%@) during application, error=%@", myPeer
, localError
);
779 CFReleaseNull(localError
);
782 success
= SOSAccountJoinThisCircle(account
, user_key
, circle
, use_cloud_identity
, error
) && success
;
789 if(success
) account
->departure_code
= kSOSNeverLeftCircle
;
793 bool SOSAccountJoinCircles(SOSAccountRef account
, CFErrorRef
* error
) {
794 return SOSAccountJoinCircles_internal(account
, false, error
);
797 CFStringRef
SOSAccountGetDeviceID(SOSAccountRef account
, CFErrorRef
*error
){
798 __block CFStringRef result
= NULL
;
799 __block CFStringRef temp
= NULL
;
800 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
) {
801 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInCircle(account
, circle
, error
);
803 SOSPeerInfoRef myPeer
= SOSFullPeerInfoGetPeerInfo(fpi
);
805 temp
= SOSPeerInfoGetDeviceID(myPeer
);
807 result
= CFStringCreateCopy(kCFAllocatorDefault
, temp
);
811 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle
));
815 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle
));
821 bool SOSAccountSetMyDSID(SOSAccountRef account
, CFStringRef IDS
, CFErrorRef
* error
){
822 __block
bool result
= false;
824 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
) {
825 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
826 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInCircle(account
, circle
, error
);
828 SOSPeerInfoRef myPeer
= SOSFullPeerInfoGetPeerInfo(fpi
);
830 SOSPeerInfoSetDeviceID(myPeer
, IDS
);
834 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle
));
839 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle
));
848 bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account
, CFErrorRef
* error
) {
849 return SOSAccountJoinCircles_internal(account
, true, error
);
853 bool SOSAccountLeaveCircles(SOSAccountRef account
, CFErrorRef
* error
)
855 __block
bool result
= true;
856 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
) {
857 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
858 result
= sosAccountLeaveCircle(account
, circle
, error
); // TODO: What about multiple errors!
863 account
->departure_code
= kSOSWithdrewMembership
;
867 bool SOSAccountBail(SOSAccountRef account
, uint64_t limit_in_seconds
, CFErrorRef
* error
) {
868 dispatch_queue_t queue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0);
869 dispatch_group_t group
= dispatch_group_create();
870 __block
bool result
= false;
871 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds
);
872 // Add a task to the group
873 dispatch_group_async(group
, queue
, ^{
874 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
) {
875 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
876 result
= sosAccountLeaveCircle(account
, circle
, error
); // TODO: What about multiple errors!
881 account
->departure_code
= kSOSWithdrewMembership
;
883 dispatch_time_t milestone
= dispatch_time(DISPATCH_TIME_NOW
, limit_in_seconds
* NSEC_PER_SEC
);
885 dispatch_group_wait(group
, milestone
);
886 dispatch_release(group
);
895 static void for_each_applicant_in_each_circle(SOSAccountRef account
, CFArrayRef peer_infos
,
896 bool (^action
)(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
)) {
898 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) {
899 SOSPeerInfoRef me
= SOSFullPeerInfoGetPeerInfo(full_peer
);
900 CFErrorRef peer_error
= NULL
;
901 if (SOSCircleHasPeer(circle
, me
, &peer_error
)) {
902 CFArrayForEach(peer_infos
, ^(const void *value
) {
903 SOSPeerInfoRef peer
= (SOSPeerInfoRef
) value
;
904 if (SOSCircleHasApplicant(circle
, peer
, NULL
)) {
905 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), NULL
, ^(SOSCircleRef circle
) {
906 return action(circle
, full_peer
, peer
);
912 secerror("Got error in SOSCircleHasPeer: %@", peer_error
);
913 CFReleaseSafe(peer_error
); // TODO: We should be accumulating errors here.
917 bool SOSAccountAcceptApplicants(SOSAccountRef account
, CFArrayRef applicants
, CFErrorRef
* error
) {
918 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
922 __block
bool success
= true;
923 __block
int64_t num_peers
= 0;
925 for_each_applicant_in_each_circle(account
, applicants
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
) {
926 bool accepted
= SOSCircleAcceptRequest(circle
, user_key
, myCirclePeer
, peer
, error
);
930 num_peers
= MAX(num_peers
, SOSCircleCountPeers(circle
));
937 bool SOSAccountRejectApplicants(SOSAccountRef account
, CFArrayRef applicants
, CFErrorRef
* error
) {
938 __block
bool success
= true;
939 __block
int64_t num_peers
= 0;
941 for_each_applicant_in_each_circle(account
, applicants
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
) {
942 bool rejected
= SOSCircleRejectRequest(circle
, myCirclePeer
, peer
, error
);
946 num_peers
= MAX(num_peers
, SOSCircleCountPeers(circle
));
955 CFStringRef
SOSAccountCopyIncompatibilityInfo(SOSAccountRef account
, CFErrorRef
* error
) {
956 return CFSTR("We're compatible, go away");
959 enum DepartureReason
SOSAccountGetLastDepartureReason(SOSAccountRef account
, CFErrorRef
* error
) {
960 return account
->departure_code
;
964 CFArrayRef
SOSAccountCopyGeneration(SOSAccountRef account
, CFErrorRef
*error
) {
965 if (!SOSAccountHasPublicKey(account
, error
))
967 CFMutableArrayRef generations
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
969 SOSAccountForEachCircle(account
, ^(SOSCircleRef circle
) {
970 CFNumberRef generation
= (CFNumberRef
)SOSCircleGetGeneration(circle
);
971 CFArrayAppendValue(generations
, SOSCircleGetName(circle
));
972 CFArrayAppendValue(generations
, generation
);
979 bool SOSValidateUserPublic(SOSAccountRef account
, CFErrorRef
*error
) {
980 if (!SOSAccountHasPublicKey(account
, error
))
983 return account
->user_public_trusted
;
987 bool SOSAccountEnsurePeerRegistration(SOSAccountRef account
, CFErrorRef
*error
) {
988 __block
bool result
= true;
990 secnotice("updates", "Ensuring peer registration.");
992 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) {
993 }, ^(SOSCircleRef circle
) {
994 }, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) {
995 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInCircle(account
, circle
, error
);
996 SOSPeerInfoRef me
= SOSFullPeerInfoGetPeerInfo(fpi
);
997 CFMutableArrayRef trusted_peer_ids
= NULL
;
998 CFMutableArrayRef untrusted_peer_ids
= NULL
;
999 CFStringRef my_id
= NULL
;
1000 if (SOSCircleHasPeer(circle
, me
, NULL
)) {
1001 my_id
= SOSPeerInfoGetPeerID(me
);
1002 trusted_peer_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
1003 untrusted_peer_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
1004 SOSCircleForEachPeer(circle
, ^(SOSPeerInfoRef peer
) {
1005 CFMutableArrayRef arrayToAddTo
= SOSPeerInfoApplicationVerify(peer
, account
->user_public
, NULL
) ? trusted_peer_ids
: untrusted_peer_ids
;
1007 CFArrayAppendValueIfNot(arrayToAddTo
, SOSPeerInfoGetPeerID(peer
), my_id
);
1011 SOSEngineRef engine
= SOSDataSourceFactoryGetEngineForDataSourceName(account
->factory
, SOSCircleGetName(circle
), NULL
);
1013 SOSEngineCircleChanged(engine
, my_id
, trusted_peer_ids
, untrusted_peer_ids
);
1015 CFReleaseNull(trusted_peer_ids
);
1016 CFReleaseNull(untrusted_peer_ids
);
1018 SOSTransportMessageRef transport
= (SOSTransportMessageRef
)CFDictionaryGetValue(account
->message_transports
, SOSCircleGetName(circle
));
1019 SOSCircleForEachPeer(circle
, ^(SOSPeerInfoRef peer
) {
1020 if (!CFEqualSafe(me
, peer
)) {
1021 CFErrorRef localError
= NULL
;
1022 SOSPeerCoderInitializeForPeer(transport
, full_peer
, peer
, &localError
);
1024 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer
, full_peer
, localError
);
1025 CFReleaseSafe(localError
);