5 // Created by Mitch Adler on 6/10/16.
10 #include <CoreFoundation/CoreFoundation.h>
12 #include "keychain/SecureObjectSync/SOSAccount.h"
14 #include "SOSAccountPriv.h"
16 #include <utilities/SecCFWrappers.h>
17 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
18 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
19 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
20 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
26 static CFMutableSetRef SOSAccountCopyOtherPeersViews(SOSAccount* account) {
27 __block CFMutableSetRef otherPeersViews = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
28 SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
29 SOSPeerInfoWithEnabledViewSet(peer, ^(CFSetRef enabled) {
30 CFSetUnion(otherPeersViews, enabled);
34 return otherPeersViews;
38 // MARK: Outstanding tracking
41 static bool isInitialSyncActive(void) {
42 static dispatch_once_t onceToken;
43 __block bool active = true;
45 dispatch_once(&onceToken, ^{
46 CFSetRef initialSyncViews = SOSViewCopyViewSet(kViewSetInitial);
47 active = CFSetGetCount(initialSyncViews) > 0;
48 CFReleaseNull(initialSyncViews);
54 void SOSAccountInitializeInitialSync(SOSAccount* account) {
58 if(isInitialSyncActive()) {
59 SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
61 SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanFalse, NULL);
66 CFMutableSetRef SOSAccountCopyOutstandingViews(SOSAccount* account) {
67 CFSetRef initialSyncViews = SOSViewCopyViewSet(kViewSetAll);
68 CFMutableSetRef result = SOSAccountCopyIntersectionWithOustanding(account, initialSyncViews);
69 CFReleaseNull(initialSyncViews);
72 bool SOSAccountIsViewOutstanding(SOSAccount* account, CFStringRef view) {
73 bool isOutstandingView;
74 require_action_quiet([account getCircleStatus:NULL] == kSOSCCInCircle, done, isOutstandingView = true);
75 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
76 require_action_quiet(unsyncedObject, done, isOutstandingView = false);
77 CFBooleanRef unsyncedBool = asBoolean(unsyncedObject, NULL);
79 isOutstandingView = CFBooleanGetValue(unsyncedBool);
81 CFSetRef unsyncedSet = asSet(unsyncedObject, NULL);
82 isOutstandingView = unsyncedSet && CFSetContainsValue(unsyncedSet, view);
85 return isOutstandingView;
87 CFMutableSetRef SOSAccountCopyIntersectionWithOustanding(SOSAccount* account, CFSetRef inSet) {
88 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
89 CFMutableSetRef result = NULL;
90 require_quiet([account getCircleStatus:NULL] == kSOSCCInCircle, done);
91 CFBooleanRef unsyncedBool = asBoolean(unsyncedObject, NULL);
93 if (!CFBooleanGetValue(unsyncedBool)) {
94 result = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
97 CFSetRef unsyncedSet = asSet(unsyncedObject, NULL);
99 result = CFSetCreateIntersection(kCFAllocatorDefault, unsyncedSet, inSet);
101 result = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
105 if (result == NULL) {
106 result = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, inSet);
110 bool SOSAccountIntersectsWithOutstanding(SOSAccount* account, CFSetRef views) {
111 CFSetRef nonInitiallySyncedViews = SOSAccountCopyIntersectionWithOustanding(account, views);
112 bool intersects = !CFSetIsEmpty(nonInitiallySyncedViews);
113 CFReleaseNull(nonInitiallySyncedViews);
116 bool SOSAccountHasOustandingViews(SOSAccount* account) {
117 bool hasOutstandingViews;
118 require_action_quiet([account getCircleStatus:NULL] == kSOSCCInCircle, done, hasOutstandingViews = true);
119 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
120 require_action_quiet(unsyncedObject, done, hasOutstandingViews = false);
121 CFBooleanRef unsyncedBool = asBoolean(unsyncedObject, NULL);
123 hasOutstandingViews = CFBooleanGetValue(unsyncedBool);
125 hasOutstandingViews = isSet(unsyncedBool);
128 return hasOutstandingViews;
131 // MARK: Initial sync functions
133 static bool SOSAccountHasCompletedInitialySyncWithSetKind(SOSAccount* account, ViewSetKind setKind) {
134 CFSetRef viewSet = SOSViewCopyViewSet(setKind);
135 bool completedSync = !SOSAccountIntersectsWithOutstanding(account, viewSet);
136 CFReleaseNull(viewSet);
137 return completedSync;
139 bool SOSAccountHasCompletedInitialSync(SOSAccount* account) {
140 return SOSAccountHasCompletedInitialySyncWithSetKind(account, kViewSetInitial);
142 bool SOSAccountHasCompletedRequiredBackupSync(SOSAccount* account) {
143 return SOSAccountHasCompletedInitialySyncWithSetKind(account, kViewSetRequiredForBackup);
148 // MARK: Handling initial sync being done
151 CFSetRef SOSAccountCopyEnabledViews(SOSAccount* account) {
152 if(!(account && account.peerInfo)) {
155 CFSetRef piViews = SOSPeerInfoCopyEnabledViews(account.peerInfo);
157 if(SOSAccountHasCompletedInitialSync(account)) {
160 CFSetRef pendingEnabled = asSet(SOSAccountGetValue(account, kSOSPendingEnableViewsToBeSetKey, NULL), NULL);
161 CFSetRef pendingDisabled = asSet(SOSAccountGetValue(account, kSOSPendingDisableViewsToBeSetKey, NULL), NULL);
163 CFMutableSetRef retvalViews = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, piViews);
164 CFSetUnion(retvalViews, pendingEnabled);
165 CFSetSubtract(retvalViews, pendingDisabled);
167 CFReleaseNull(piViews);
168 CFReleaseNull(pendingEnabled);
169 CFReleaseNull(pendingDisabled);
174 static bool SOSAccountResolvePendingViewSets(SOSAccount* account, CFErrorRef *error) {
175 CFMutableSetRef newPending = SOSViewCopyViewSet(kViewSetAlwaysOn);
176 CFMutableSetRef defaultOn = SOSViewCopyViewSet(kViewSetDefault);
177 CFSetRef pendingOn = asSet(SOSAccountGetValue(account, kSOSPendingEnableViewsToBeSetKey, NULL), NULL);
178 CFSetRef pendingDisabled = asSet(SOSAccountGetValue(account, kSOSPendingDisableViewsToBeSetKey, NULL), NULL);
181 CFSetUnion(newPending, defaultOn);
184 CFSetUnion(newPending, pendingOn);
186 CFReleaseNull(defaultOn);
188 bool status = [account.trust updateViewSets:account enabled:newPending disabled:pendingDisabled];
189 CFReleaseNull(newPending);
192 SOSAccountClearValue(account, kSOSPendingEnableViewsToBeSetKey, NULL);
193 SOSAccountClearValue(account, kSOSPendingDisableViewsToBeSetKey, NULL);
195 secnotice("views","updated view sets!");
198 secerror("Could not update view sets");
203 static void SOSAccountCallInitialSyncBlocks(SOSAccount* account) {
204 CFDictionaryRef syncBlocks = NULL;
205 syncBlocks = CFBridgingRetain(account.waitForInitialSync_blocks);
206 account.waitForInitialSync_blocks = nil;
209 CFDictionaryForEach(syncBlocks, ^(const void *key, const void *value) {
210 secnotice("updates", "calling in sync block [%@]", key);
211 ((__bridge SOSAccountWaitForInitialSyncBlock)value)(account);
214 CFReleaseNull(syncBlocks);
218 static void SOSAccountHandleRequiredBackupSyncDone(SOSAccount* account) {
219 secnotice("initial-sync", "Handling Required Backup Sync done");
222 static void SOSAccountHandleInitialSyncDone(SOSAccount* account) {
223 secnotice("initial-sync", "Handling initial sync done.");
225 if(!SOSAccountResolvePendingViewSets(account, NULL))
226 secnotice("initial-sync", "Account could not add the pending view sets");
228 SOSAccountCallInitialSyncBlocks(account);
234 // MARK: Waiting for in-sync
236 static CFStringRef CreateUUIDString() {
237 CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
238 CFStringRef result = CFUUIDCreateString(kCFAllocatorDefault, uuid);
243 CFStringRef SOSAccountCallWhenInSync(SOSAccount* account, SOSAccountWaitForInitialSyncBlock syncBlock) {
244 //if we are not initially synced
245 CFStringRef id = NULL;
246 CFTypeRef unSyncedViews = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
247 if (unSyncedViews != NULL) {
248 id = CreateUUIDString();
249 secnotice("initial-sync", "adding sync block [%@] to array!", id);
250 SOSAccountWaitForInitialSyncBlock copy = syncBlock;
251 if (account.waitForInitialSync_blocks == NULL) {
252 account.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
254 [account.waitForInitialSync_blocks setObject:copy forKey:(__bridge NSString*)id];
262 bool SOSAccountUnregisterCallWhenInSync(SOSAccount* account, CFStringRef id) {
263 if (account.waitForInitialSync_blocks == NULL) return false;
265 [account.waitForInitialSync_blocks removeObjectForKey: (__bridge NSString*)id];
269 static void performWithInitialSyncDescription(CFTypeRef object, void (^action)(CFStringRef description)) {
270 CFSetRef setObject = asSet(object, NULL);
272 CFStringSetPerformWithDescription(setObject, action);
274 CFStringRef format = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), object);
276 CFReleaseNull(format);
280 static bool CFSetIntersectionWentEmpty(CFSetRef interestingSet, CFSetRef before, CFSetRef after) {
281 return ((before != NULL) && !CFSetIntersectionIsEmpty(interestingSet, before)) &&
282 ((after == NULL) || CFSetIntersectionIsEmpty(interestingSet, after));
285 static bool SOSViewIntersectionWentEmpty(ViewSetKind kind, CFSetRef before, CFSetRef after) {
286 CFSetRef kindSet = SOSViewCopyViewSet(kind);
287 bool result = CFSetIntersectionWentEmpty(kindSet, before, after);
288 CFReleaseNull(kindSet);
293 bool SOSAccountHandleOutOfSyncUpdate(SOSAccount* account, CFSetRef oldOOSViews, CFSetRef newOOSViews) {
294 bool actionTaken = false;
296 if (SOSViewIntersectionWentEmpty(kViewSetInitial, oldOOSViews, newOOSViews)) {
297 SOSAccountHandleInitialSyncDone(account);
301 if (SOSViewIntersectionWentEmpty(kViewSetRequiredForBackup, oldOOSViews, newOOSViews)) {
302 SOSAccountHandleRequiredBackupSyncDone(account);
308 void SOSAccountUpdateOutOfSyncViews(SOSAccountTransaction* aTxn, CFSetRef viewsInSync) {
309 SOSAccount* account = aTxn.account;
310 SOSCCStatus circleStatus = [account getCircleStatus:NULL];
312 bool inOrApplying = (circleStatus == kSOSCCInCircle) || (circleStatus == kSOSCCRequestPending);
314 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
315 __block CFTypeRef newUnsyncedObject = CFRetainSafe(unsyncedObject);
317 CFSetRef unsyncedSet = NULL;
318 CFMutableSetRef newUnsyncedSet = NULL;
320 performWithInitialSyncDescription(viewsInSync, ^(CFStringRef viewsInSyncDescription) {
321 secnotice("initial-sync", "Views in sync: %@", viewsInSyncDescription);
326 if (unsyncedObject != NULL) {
327 secnotice("initial-sync", "not in circle nor applying: clearing pending");
328 CFReleaseNull(newUnsyncedObject);
330 } else if (circleStatus == kSOSCCInCircle) {
331 if (unsyncedObject == kCFBooleanTrue) {
332 unsyncedSet = SOSViewCopyViewSet(kViewSetAll);
333 CFAssignRetained(newUnsyncedObject, CFSetCreateCopy(kCFAllocatorDefault, unsyncedSet));
335 secnotice("initial-sync", "Pending views setting to all we can expect.");
336 } else if (isSet(unsyncedObject)) {
337 unsyncedSet = (CFSetRef) CFRetainSafe(unsyncedObject);
341 CFSetRef otherPeersViews = SOSAccountCopyOtherPeersViews(account);
343 newUnsyncedSet = CFSetCreateIntersection(kCFAllocatorDefault, unsyncedSet, otherPeersViews);
346 CFSetSubtract(newUnsyncedSet, viewsInSync);
349 CFRetainAssign(newUnsyncedObject, newUnsyncedSet);
350 CFReleaseNull(otherPeersViews);
353 performWithInitialSyncDescription(newUnsyncedSet, ^(CFStringRef unsynced) {
354 secnotice("initial-sync", "Unsynced: %@", unsynced);
359 if (isSet(newUnsyncedObject) && CFSetIsEmpty((CFSetRef) newUnsyncedObject)) {
360 secnotice("initial-sync", "Empty set, using NULL instead");
361 CFReleaseNull(newUnsyncedObject);
364 CFErrorRef localError = NULL;
365 if (!SOSAccountSetValue(account, kSOSUnsyncedViewsKey, newUnsyncedObject, &localError)) {
366 secnotice("initial-sync", "Failure saving new unsynced value: %@ value: %@", localError, newUnsyncedObject);
368 CFReleaseNull(localError);
370 CFReleaseNull(newUnsyncedObject);
371 CFReleaseNull(newUnsyncedSet);
372 CFReleaseNull(unsyncedSet);
375 void SOSAccountPeerGotInSync(SOSAccountTransaction* aTxn, CFStringRef peerID, CFSetRef views) {
376 SOSAccount* account = aTxn.account;
377 secnotice("initial-sync", "Peer %@ synced views: %@", peerID, views);
378 SOSCircleRef circle = NULL;
379 SOSAccountTrustClassic* trust = account.trust;
380 circle = trust.trustedCircle;
381 if (circle && [account isInCircle:NULL] && SOSCircleHasActivePeerWithID(circle, peerID, NULL)) {
382 SOSAccountUpdateOutOfSyncViews(aTxn, views);
386 void SOSAccountEnsureSyncChecking(SOSAccount* account) {
387 if (!account.isListeningForSync) {
388 SOSEngineRef engine = [account.trust getDataSourceEngine:account.factory];
391 secnotice("initial-sync", "Setting up notifications to monitor in-sync");
392 SOSEngineSetSyncCompleteListenerQueue(engine, account.queue);
393 SOSEngineSetSyncCompleteListener(engine, ^(CFStringRef peerID, CFSetRef views) {
394 [account performTransaction_Locked:^(SOSAccountTransaction * _Nonnull txn) {
395 SOSAccountPeerGotInSync(txn, peerID, views);
398 account.isListeningForSync = true;
400 secerror("Couldn't find engine to setup notifications!!!");
405 void SOSAccountCancelSyncChecking(SOSAccount* account) {
406 if (account.isListeningForSync) {
407 SOSEngineRef engine = [account.trust getDataSourceEngine:account.factory];
410 secnotice("initial-sync", "Cancelling notifications to monitor in-sync");
411 SOSEngineSetSyncCompleteListenerQueue(engine, NULL);
412 SOSEngineSetSyncCompleteListener(engine, NULL);
414 secnotice("initial-sync", "No engine to cancel notification from.");
416 account.isListeningForSync = false;
420 bool SOSAccountCheckForAlwaysOnViews(SOSAccount* account) {
421 bool changed = false;
422 SOSPeerInfoRef myPI = account.peerInfo;
423 require_quiet(myPI, done);
424 require_quiet([account isInCircle:NULL], done);
425 require_quiet(SOSAccountHasCompletedInitialSync(account), done);
426 CFMutableSetRef viewsToEnsure = SOSViewCopyViewSet(kViewSetAlwaysOn);
427 // Previous version PeerInfo if we were syncing legacy keychain, ensure we include those legacy views.
428 if(!SOSPeerInfoVersionIsCurrent(myPI)) {
429 CFSetRef V0toAdd = SOSViewCopyViewSet(kViewSetV0);
430 CFSetUnion(viewsToEnsure, V0toAdd);
431 CFReleaseNull(V0toAdd);
433 changed = [account.trust updateFullPeerInfo:account minimum:viewsToEnsure excluded:SOSViewsGetV0ViewSet()]; // We don't permit V0 view proper, only sub-views
434 CFReleaseNull(viewsToEnsure);