5 // Created by Mitch Adler on 6/10/16.
10 #include <CoreFoundation/CoreFoundation.h>
12 #include <Security/SecureObjectSync/SOSAccount.h>
14 #include "SOSAccountPriv.h"
16 #include <utilities/SecCFWrappers.h>
17 #import <Security/SecureObjectSync/SOSAccountTrustClassic.h>
18 #import <Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h>
19 #import <Security/SecureObjectSync/SOSAccountTrustClassic+Expansion.h>
20 #import <Security/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
40 CFMutableSetRef SOSAccountCopyOutstandingViews(SOSAccount* account) {
41 CFSetRef initialSyncViews = SOSViewCopyViewSet(kViewSetAll);
42 CFMutableSetRef result = SOSAccountCopyIntersectionWithOustanding(account, initialSyncViews);
43 CFReleaseNull(initialSyncViews);
46 bool SOSAccountIsViewOutstanding(SOSAccount* account, CFStringRef view) {
47 bool isOutstandingView;
48 require_action_quiet([account getCircleStatus:NULL] == kSOSCCInCircle, done, isOutstandingView = true);
49 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
50 require_action_quiet(unsyncedObject, done, isOutstandingView = false);
51 CFBooleanRef unsyncedBool = asBoolean(unsyncedObject, NULL);
53 isOutstandingView = CFBooleanGetValue(unsyncedBool);
55 CFSetRef unsyncedSet = asSet(unsyncedObject, NULL);
56 isOutstandingView = unsyncedSet && CFSetContainsValue(unsyncedSet, view);
59 return isOutstandingView;
61 CFMutableSetRef SOSAccountCopyIntersectionWithOustanding(SOSAccount* account, CFSetRef inSet) {
62 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
63 CFMutableSetRef result = NULL;
64 require_quiet([account getCircleStatus:NULL] == kSOSCCInCircle, done);
65 CFBooleanRef unsyncedBool = asBoolean(unsyncedObject, NULL);
67 if (!CFBooleanGetValue(unsyncedBool)) {
68 result = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
71 CFSetRef unsyncedSet = asSet(unsyncedObject, NULL);
73 result = CFSetCreateIntersection(kCFAllocatorDefault, unsyncedSet, inSet);
75 result = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
80 result = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, inSet);
84 bool SOSAccountIntersectsWithOutstanding(SOSAccount* account, CFSetRef views) {
85 CFSetRef nonInitiallySyncedViews = SOSAccountCopyIntersectionWithOustanding(account, views);
86 bool intersects = !CFSetIsEmpty(nonInitiallySyncedViews);
87 CFReleaseNull(nonInitiallySyncedViews);
90 bool SOSAccountHasOustandingViews(SOSAccount* account) {
91 bool hasOutstandingViews;
92 require_action_quiet([account getCircleStatus:NULL] == kSOSCCInCircle, done, hasOutstandingViews = true);
93 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
94 require_action_quiet(unsyncedObject, done, hasOutstandingViews = false);
95 CFBooleanRef unsyncedBool = asBoolean(unsyncedObject, NULL);
97 hasOutstandingViews = CFBooleanGetValue(unsyncedBool);
99 hasOutstandingViews = isSet(unsyncedBool);
102 return hasOutstandingViews;
105 // MARK: Initial sync functions
107 static bool SOSAccountHasCompletedInitialySyncWithSetKind(SOSAccount* account, ViewSetKind setKind) {
108 CFSetRef viewSet = SOSViewCopyViewSet(setKind);
109 bool completedSync = !SOSAccountIntersectsWithOutstanding(account, viewSet);
110 CFReleaseNull(viewSet);
111 return completedSync;
113 bool SOSAccountHasCompletedInitialSync(SOSAccount* account) {
114 return SOSAccountHasCompletedInitialySyncWithSetKind(account, kViewSetInitial);
116 bool SOSAccountHasCompletedRequiredBackupSync(SOSAccount* account) {
117 return SOSAccountHasCompletedInitialySyncWithSetKind(account, kViewSetRequiredForBackup);
122 // MARK: Handling initial sync being done
125 static bool SOSAccountResolvePendingViewSets(SOSAccount* account, CFErrorRef *error) {
126 bool status = [account.trust updateViewSets:account enabled:asSet(SOSAccountGetValue(account, kSOSPendingEnableViewsToBeSetKey, NULL), NULL) disabled:asSet(SOSAccountGetValue(account, kSOSPendingDisableViewsToBeSetKey, NULL), NULL)];
128 SOSAccountClearValue(account, kSOSPendingEnableViewsToBeSetKey, NULL);
129 SOSAccountClearValue(account, kSOSPendingDisableViewsToBeSetKey, NULL);
131 secnotice("views","updated view sets!");
134 secerror("Could not update view sets");
139 static void SOSAccountCallInitialSyncBlocks(SOSAccount* account) {
140 CFDictionaryRef syncBlocks = NULL;
141 syncBlocks = CFBridgingRetain(account.waitForInitialSync_blocks);
142 account.waitForInitialSync_blocks = nil;
145 CFDictionaryForEach(syncBlocks, ^(const void *key, const void *value) {
146 secnotice("updates", "calling in sync block [%@]", key);
147 ((__bridge SOSAccountWaitForInitialSyncBlock)value)(account);
150 CFReleaseNull(syncBlocks);
154 static void SOSAccountHandleRequiredBackupSyncDone(SOSAccount* account) {
155 secnotice("initial-sync", "Handling Required Backup Sync done");
158 static void SOSAccountHandleInitialSyncDone(SOSAccount* account) {
159 secnotice("initial-sync", "Handling initial sync done.");
161 if(!SOSAccountResolvePendingViewSets(account, NULL))
162 secnotice("initial-sync", "Account could not add the pending view sets");
164 SOSAccountCallInitialSyncBlocks(account);
170 // MARK: Waiting for in-sync
172 static CFStringRef CreateUUIDString() {
173 CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
174 CFStringRef result = CFUUIDCreateString(kCFAllocatorDefault, uuid);
179 CFStringRef SOSAccountCallWhenInSync(SOSAccount* account, SOSAccountWaitForInitialSyncBlock syncBlock) {
180 //if we are not initially synced
181 CFStringRef id = NULL;
182 CFTypeRef unSyncedViews = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
183 if (unSyncedViews != NULL) {
184 id = CreateUUIDString();
185 secnotice("initial-sync", "adding sync block [%@] to array!", id);
186 SOSAccountWaitForInitialSyncBlock copy = syncBlock;
187 if (account.waitForInitialSync_blocks == NULL) {
188 account.waitForInitialSync_blocks = [NSMutableDictionary dictionary];
190 [account.waitForInitialSync_blocks setObject:copy forKey:(__bridge NSString*)id];
198 bool SOSAccountUnregisterCallWhenInSync(SOSAccount* account, CFStringRef id) {
199 if (account.waitForInitialSync_blocks == NULL) return false;
201 [account.waitForInitialSync_blocks removeObjectForKey: (__bridge NSString*)id];
205 static void performWithInitialSyncDescription(CFTypeRef object, void (^action)(CFStringRef description)) {
206 CFSetRef setObject = asSet(object, NULL);
208 CFStringSetPerformWithDescription(setObject, action);
210 CFStringRef format = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), object);
212 CFReleaseNull(format);
216 static bool CFSetIntersectionWentEmpty(CFSetRef interestingSet, CFSetRef before, CFSetRef after) {
217 return ((before != NULL) && !CFSetIntersectionIsEmpty(interestingSet, before)) &&
218 ((after == NULL) || CFSetIntersectionIsEmpty(interestingSet, after));
221 static bool SOSViewIntersectionWentEmpty(ViewSetKind kind, CFSetRef before, CFSetRef after) {
222 CFSetRef kindSet = SOSViewCopyViewSet(kind);
223 bool result = CFSetIntersectionWentEmpty(kindSet, before, after);
224 CFReleaseNull(kindSet);
228 bool SOSAccountHandleOutOfSyncUpdate(SOSAccount* account, CFSetRef oldOOSViews, CFSetRef newOOSViews) {
229 bool actionTaken = false;
231 if (SOSViewIntersectionWentEmpty(kViewSetInitial, oldOOSViews, newOOSViews)) {
232 SOSAccountHandleInitialSyncDone(account);
236 if (SOSViewIntersectionWentEmpty(kViewSetRequiredForBackup, oldOOSViews, newOOSViews)) {
237 SOSAccountHandleRequiredBackupSyncDone(account);
243 void SOSAccountUpdateOutOfSyncViews(SOSAccountTransaction* aTxn, CFSetRef viewsInSync) {
244 SOSAccount* account = aTxn.account;
245 SOSCCStatus circleStatus = [account getCircleStatus:NULL];
247 bool inOrApplying = (circleStatus == kSOSCCInCircle) || (circleStatus == kSOSCCRequestPending);
249 CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
250 __block CFTypeRef newUnsyncedObject = CFRetainSafe(unsyncedObject);
252 CFSetRef unsyncedSet = NULL;
253 CFMutableSetRef newUnsyncedSet = NULL;
255 performWithInitialSyncDescription(viewsInSync, ^(CFStringRef viewsInSyncDescription) {
256 secnotice("initial-sync", "Views in sync: %@", viewsInSyncDescription);
261 if (unsyncedObject != NULL) {
262 secnotice("initial-sync", "not in circle nor applying: clearing pending");
263 CFReleaseNull(newUnsyncedObject);
265 } else if (circleStatus == kSOSCCInCircle) {
266 if (unsyncedObject == kCFBooleanTrue) {
267 unsyncedSet = SOSViewCopyViewSet(kViewSetAll);
268 CFAssignRetained(newUnsyncedObject, CFSetCreateCopy(kCFAllocatorDefault, unsyncedSet));
270 secnotice("initial-sync", "Pending views setting to all we can expect.");
271 } else if (isSet(unsyncedObject)) {
272 unsyncedSet = (CFSetRef) CFRetainSafe(unsyncedObject);
276 CFSetRef otherPeersViews = SOSAccountCopyOtherPeersViews(account);
278 newUnsyncedSet = CFSetCreateIntersection(kCFAllocatorDefault, unsyncedSet, otherPeersViews);
281 CFSetSubtract(newUnsyncedSet, viewsInSync);
284 CFRetainAssign(newUnsyncedObject, newUnsyncedSet);
285 CFReleaseNull(otherPeersViews);
288 performWithInitialSyncDescription(newUnsyncedSet, ^(CFStringRef unsynced) {
289 secnotice("initial-sync", "Unsynced: %@", unsynced);
294 if (isSet(newUnsyncedObject) && CFSetIsEmpty((CFSetRef) newUnsyncedObject)) {
295 secnotice("initial-sync", "Empty set, using NULL instead");
296 CFReleaseNull(newUnsyncedObject);
299 CFErrorRef localError = NULL;
300 if (!SOSAccountSetValue(account, kSOSUnsyncedViewsKey, newUnsyncedObject, &localError)) {
301 secnotice("initial-sync", "Failure saving new unsynced value: %@ value: %@", localError, newUnsyncedObject);
303 CFReleaseNull(localError);
305 CFReleaseNull(newUnsyncedObject);
306 CFReleaseNull(newUnsyncedSet);
307 CFReleaseNull(unsyncedSet);
310 void SOSAccountPeerGotInSync(SOSAccountTransaction* aTxn, CFStringRef peerID, CFSetRef views) {
311 SOSAccount* account = aTxn.account;
312 secnotice("initial-sync", "Peer %@ synced views: %@", peerID, views);
313 SOSCircleRef circle = NULL;
314 SOSAccountTrustClassic* trust = account.trust;
315 circle = trust.trustedCircle;
316 if (circle && [account isInCircle:NULL] && SOSCircleHasActivePeerWithID(circle, peerID, NULL)) {
317 SOSAccountUpdateOutOfSyncViews(aTxn, views);
321 void SOSAccountEnsureSyncChecking(SOSAccount* account) {
322 if (!account.isListeningForSync) {
323 SOSEngineRef engine = [account.trust getDataSourceEngine:account.factory];
326 secnotice("initial-sync", "Setting up notifications to monitor in-sync");
327 SOSEngineSetSyncCompleteListenerQueue(engine, account.queue);
328 SOSEngineSetSyncCompleteListener(engine, ^(CFStringRef peerID, CFSetRef views) {
329 [account performTransaction_Locked:^(SOSAccountTransaction * _Nonnull txn) {
330 SOSAccountPeerGotInSync(txn, peerID, views);
333 account.isListeningForSync = true;
335 secerror("Couldn't find engine to setup notifications!!!");
340 void SOSAccountCancelSyncChecking(SOSAccount* account) {
341 if (account.isListeningForSync) {
342 SOSEngineRef engine = [account.trust getDataSourceEngine:account.factory];
345 secnotice("initial-sync", "Cancelling notifications to monitor in-sync");
346 SOSEngineSetSyncCompleteListenerQueue(engine, NULL);
347 SOSEngineSetSyncCompleteListener(engine, NULL);
349 secnotice("initial-sync", "No engine to cancel notification from.");
351 account.isListeningForSync = false;
355 bool SOSAccountCheckForAlwaysOnViews(SOSAccount* account) {
356 bool changed = false;
357 SOSPeerInfoRef myPI = account.peerInfo;
358 require_quiet(myPI, done);
359 require_quiet([account isInCircle:NULL], done);
360 require_quiet(SOSAccountHasCompletedInitialSync(account), done);
361 CFMutableSetRef viewsToEnsure = SOSViewCopyViewSet(kViewSetAlwaysOn);
362 // Previous version PeerInfo if we were syncing legacy keychain, ensure we include those legacy views.
363 if(!SOSPeerInfoVersionIsCurrent(myPI)) {
364 CFSetRef V0toAdd = SOSViewCopyViewSet(kViewSetV0);
365 CFSetUnion(viewsToEnsure, V0toAdd);
366 CFReleaseNull(V0toAdd);
368 changed = [account.trust updateFullPeerInfo:account minimum:viewsToEnsure excluded:SOSViewsGetV0ViewSet()]; // We don't permit V0 view proper, only sub-views
369 CFReleaseNull(viewsToEnsure);