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> 
  22 static CFMutableSetRef 
SOSAccountCopyOtherPeersViews(SOSAccountRef account
) { 
  23     __block CFMutableSetRef otherPeersViews 
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
); 
  24     SOSAccountForEachCirclePeerExceptMe(account
, ^(SOSPeerInfoRef peer
) { 
  25         SOSPeerInfoWithEnabledViewSet(peer
, ^(CFSetRef enabled
) { 
  26             CFSetUnion(otherPeersViews
, enabled
); 
  30     return otherPeersViews
; 
  34 // MARK: Outstanding tracking 
  37 CFMutableSetRef 
SOSAccountCopyOutstandingViews(SOSAccountRef account
) { 
  38     CFSetRef initialSyncViews 
= SOSViewCopyViewSet(kViewSetAll
); 
  39     CFMutableSetRef result 
= SOSAccountCopyIntersectionWithOustanding(account
, initialSyncViews
); 
  40     CFReleaseNull(initialSyncViews
); 
  45 bool SOSAccountIsViewOutstanding(SOSAccountRef account
, CFStringRef view
) { 
  46     bool isOutstandingView
; 
  48     require_action_quiet(SOSAccountIsInCircle(account
, NULL
), done
, isOutstandingView 
= true); 
  50     CFTypeRef unsyncedObject 
= SOSAccountGetValue(account
, kSOSUnsyncedViewsKey
, NULL
); 
  51     require_action_quiet(unsyncedObject
, done
, isOutstandingView 
= false); 
  53     CFBooleanRef unsyncedBool 
= asBoolean(unsyncedObject
, NULL
); 
  55         isOutstandingView 
= CFBooleanGetValue(unsyncedBool
); 
  57         CFSetRef unsyncedSet 
= asSet(unsyncedObject
, NULL
); 
  58         isOutstandingView 
= unsyncedSet 
&& CFSetContainsValue(unsyncedSet
, view
); 
  62     return isOutstandingView
; 
  65 CFMutableSetRef 
SOSAccountCopyIntersectionWithOustanding(SOSAccountRef account
, CFSetRef inSet
) { 
  66     CFTypeRef unsyncedObject 
= SOSAccountGetValue(account
, kSOSUnsyncedViewsKey
, NULL
); 
  67     CFMutableSetRef result 
= NULL
; 
  69     require_quiet(SOSAccountIsInCircle(account
, NULL
), done
); 
  71     CFBooleanRef unsyncedBool 
= asBoolean(unsyncedObject
, NULL
); 
  73         if (!CFBooleanGetValue(unsyncedBool
)) { 
  74             result 
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
); 
  77         CFSetRef unsyncedSet 
= asSet(unsyncedObject
, NULL
); 
  79             result 
= CFSetCreateIntersection(kCFAllocatorDefault
, unsyncedSet
, inSet
); 
  81             result 
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
); 
  87         result 
= CFSetCreateMutableCopy(kCFAllocatorDefault
, 0, inSet
); 
  93 bool SOSAccountIntersectsWithOutstanding(SOSAccountRef account
, CFSetRef views
) { 
  94     CFSetRef nonInitiallySyncedViews 
= SOSAccountCopyIntersectionWithOustanding(account
, views
); 
  95     bool intersects 
= !CFSetIsEmpty(nonInitiallySyncedViews
); 
  96     CFReleaseNull(nonInitiallySyncedViews
); 
 100 bool SOSAccountHasOustandingViews(SOSAccountRef account
) { 
 101     bool hasOutstandingViews
; 
 103     require_action_quiet(SOSAccountIsInCircle(account
, NULL
), done
, hasOutstandingViews 
= true); 
 105     CFTypeRef unsyncedObject 
= SOSAccountGetValue(account
, kSOSUnsyncedViewsKey
, NULL
); 
 106     require_action_quiet(unsyncedObject
, done
, hasOutstandingViews 
= false); 
 108     CFBooleanRef unsyncedBool 
= asBoolean(unsyncedObject
, NULL
); 
 110         hasOutstandingViews 
= CFBooleanGetValue(unsyncedBool
); 
 112         hasOutstandingViews 
= isSet(unsyncedBool
); 
 116     return hasOutstandingViews
; 
 121 // MARK: Initial sync functions 
 124 static bool SOSAccountHasCompletedInitialySyncWithSetKind(SOSAccountRef account
, ViewSetKind setKind
) { 
 125     CFSetRef viewSet 
= SOSViewCopyViewSet(setKind
); 
 126     bool completedSync 
= !SOSAccountIntersectsWithOutstanding(account
, viewSet
); 
 127     CFReleaseNull(viewSet
); 
 129     return completedSync
; 
 132 bool SOSAccountHasCompletedInitialSync(SOSAccountRef account
) { 
 133     return SOSAccountHasCompletedInitialySyncWithSetKind(account
, kViewSetInitial
); 
 136 bool SOSAccountHasCompletedRequiredBackupSync(SOSAccountRef account
) { 
 137     return SOSAccountHasCompletedInitialySyncWithSetKind(account
, kViewSetRequiredForBackup
); 
 144 // MARK: Handling initial sync being done 
 147 static bool SOSAccountResolvePendingViewSets(SOSAccountRef account
, CFErrorRef 
*error
) { 
 148     bool status 
= SOSAccountUpdateViewSets(account
, 
 149                                            asSet(SOSAccountGetValue(account
, kSOSPendingEnableViewsToBeSetKey
, NULL
), NULL
), 
 150                                            asSet(SOSAccountGetValue(account
, kSOSPendingDisableViewsToBeSetKey
, NULL
), NULL
)); 
 152         SOSAccountClearValue(account
, kSOSPendingEnableViewsToBeSetKey
, NULL
); 
 153         SOSAccountClearValue(account
, kSOSPendingDisableViewsToBeSetKey
, NULL
); 
 155         secnotice("views","updated view sets!"); 
 158         secerror("Could not update view sets"); 
 163 static void SOSAccountCallInitialSyncBlocks(SOSAccountRef account
) { 
 164     CFDictionaryRef syncBlocks 
= NULL
; 
 165     CFTransferRetained(syncBlocks
, account
->waitForInitialSync_blocks
); 
 168         CFDictionaryForEach(syncBlocks
, ^(const void *key
, const void *value
) { 
 169             secnotice("updates", "calling in sync block [%@]", key
); 
 170             ((SOSAccountWaitForInitialSyncBlock
)value
)(account
); 
 173     CFReleaseNull(syncBlocks
); 
 177 static void SOSAccountHandleRequiredBackupSyncDone(SOSAccountRef account
) { 
 178     secnotice("initial-sync", "Handling Required Backup Sync done"); 
 181 static void SOSAccountHandleInitialSyncDone(SOSAccountRef account
) { 
 182     secnotice("initial-sync", "Handling initial sync done."); 
 184     if(!SOSAccountResolvePendingViewSets(account
, NULL
)) 
 185         secnotice("initial-sync", "Account could not add the pending view sets"); 
 187     SOSAccountCallInitialSyncBlocks(account
); 
 193 // MARK: Waiting for in-sync 
 195 static CFStringRef 
CreateUUIDString() { 
 196     CFUUIDRef uuid 
= CFUUIDCreate(kCFAllocatorDefault
); 
 197     CFStringRef result 
= CFUUIDCreateString(kCFAllocatorDefault
, uuid
); 
 202 CFStringRef 
SOSAccountCallWhenInSync(SOSAccountRef account
, SOSAccountWaitForInitialSyncBlock syncBlock
) { 
 203     //if we are not initially synced 
 204     CFStringRef id 
= NULL
; 
 205     CFTypeRef unSyncedViews 
= SOSAccountGetValue(account
, kSOSUnsyncedViewsKey
, NULL
); 
 206     if (unSyncedViews 
!= NULL
) { 
 207         id 
= CreateUUIDString(); 
 208         secnotice("initial-sync", "adding sync block [%@] to array!", id
); 
 209         SOSAccountWaitForInitialSyncBlock copy 
= Block_copy(syncBlock
); 
 210         if (account
->waitForInitialSync_blocks 
== NULL
) { 
 211             account
->waitForInitialSync_blocks 
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
); 
 213         CFDictionarySetValue(account
->waitForInitialSync_blocks
, id
, copy
); 
 222 bool SOSAccountUnregisterCallWhenInSync(SOSAccountRef account
, CFStringRef id
) { 
 223     if (account
->waitForInitialSync_blocks 
== NULL
) return false; 
 225     bool removed 
= CFDictionaryGetValueIfPresent(account
->waitForInitialSync_blocks
, id
, NULL
); 
 226     CFDictionaryRemoveValue(account
->waitForInitialSync_blocks
, id
); 
 230 static void performWithInitialSyncDescription(CFTypeRef object
, void (^action
)(CFStringRef description
)) { 
 231     CFSetRef setObject 
= asSet(object
, NULL
); 
 233         CFStringSetPerformWithDescription(setObject
, action
); 
 235         CFStringRef format 
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@"), object
); 
 237         CFReleaseNull(format
); 
 241 static bool CFSetIntersectionWentEmpty(CFSetRef interestingSet
, CFSetRef before
, CFSetRef after
) { 
 242     return ((before 
!= NULL
) && !CFSetIntersectionIsEmpty(interestingSet
, before
)) && 
 243            ((after 
== NULL
) || CFSetIntersectionIsEmpty(interestingSet
, after
)); 
 246 static bool SOSViewIntersectionWentEmpty(ViewSetKind kind
, CFSetRef before
, CFSetRef after
) { 
 247     CFSetRef kindSet 
= SOSViewCopyViewSet(kind
); 
 248     bool result 
= CFSetIntersectionWentEmpty(kindSet
, before
, after
); 
 249     CFReleaseNull(kindSet
); 
 253 bool SOSAccountHandleOutOfSyncUpdate(SOSAccountRef account
, CFSetRef oldOOSViews
, CFSetRef newOOSViews
) { 
 254     bool actionTaken 
= false; 
 256     if (SOSViewIntersectionWentEmpty(kViewSetInitial
, oldOOSViews
, newOOSViews
)) { 
 257         SOSAccountHandleInitialSyncDone(account
); 
 261     if (SOSViewIntersectionWentEmpty(kViewSetRequiredForBackup
, oldOOSViews
, newOOSViews
)) { 
 262         SOSAccountHandleRequiredBackupSyncDone(account
); 
 268 void SOSAccountUpdateOutOfSyncViews(SOSAccountTransactionRef aTxn
, CFSetRef viewsInSync
) { 
 269     SOSAccountRef account 
= aTxn
->account
; 
 270     SOSCCStatus circleStatus 
= SOSAccountGetCircleStatus(account
, NULL
); 
 271     bool inOrApplying 
= (circleStatus 
== kSOSCCInCircle
) || (circleStatus 
== kSOSCCRequestPending
); 
 273     CFTypeRef unsyncedObject 
= SOSAccountGetValue(account
, kSOSUnsyncedViewsKey
, NULL
); 
 274     __block CFTypeRef newUnsyncedObject 
= CFRetainSafe(unsyncedObject
); 
 276     CFSetRef unsyncedSet 
= NULL
; 
 277     CFMutableSetRef newUnsyncedSet 
= NULL
; 
 279     performWithInitialSyncDescription(viewsInSync
, ^(CFStringRef viewsInSyncDescription
) { 
 280         secnotice("initial-sync", "Views in sync: %@", viewsInSyncDescription
); 
 285         if (unsyncedObject 
!= NULL
) { 
 286             secnotice("initial-sync", "not in circle nor applying: clearing pending"); 
 287             CFReleaseNull(newUnsyncedObject
); 
 289     } else if (circleStatus 
== kSOSCCInCircle
) { 
 290         if (unsyncedObject 
== kCFBooleanTrue
) { 
 291             unsyncedSet 
= SOSViewCopyViewSet(kViewSetAll
); 
 292             CFAssignRetained(newUnsyncedObject
, CFSetCreateCopy(kCFAllocatorDefault
, unsyncedSet
)); 
 294             secnotice("initial-sync", "Pending views setting to all we can expect."); 
 295         } else if (isSet(unsyncedObject
)) { 
 296             unsyncedSet 
= (CFSetRef
) CFRetainSafe(unsyncedObject
); 
 300             CFSetRef otherPeersViews 
= SOSAccountCopyOtherPeersViews(account
); 
 302             newUnsyncedSet 
= CFSetCreateIntersection(kCFAllocatorDefault
, unsyncedSet
, otherPeersViews
); 
 305                 CFSetSubtract(newUnsyncedSet
, viewsInSync
); 
 308             CFRetainAssign(newUnsyncedObject
, newUnsyncedSet
); 
 309             CFReleaseNull(otherPeersViews
); 
 312         performWithInitialSyncDescription(newUnsyncedSet
, ^(CFStringRef unsynced
) { 
 313             secnotice("initial-sync", "Unsynced: %@", unsynced
); 
 318     if (isSet(newUnsyncedObject
) && CFSetIsEmpty((CFSetRef
) newUnsyncedObject
)) { 
 319         secnotice("initial-sync", "Empty set, using NULL instead"); 
 320         CFReleaseNull(newUnsyncedObject
); 
 323     CFErrorRef localError 
= NULL
; 
 324     if (!SOSAccountSetValue(account
, kSOSUnsyncedViewsKey
, newUnsyncedObject
, &localError
)) { 
 325         secnotice("initial-sync", "Failure saving new unsynced value: %@ value: %@", localError
, newUnsyncedObject
); 
 327     CFReleaseNull(localError
); 
 329     CFReleaseNull(newUnsyncedObject
); 
 330     CFReleaseNull(newUnsyncedSet
); 
 331     CFReleaseNull(unsyncedSet
); 
 334 void SOSAccountPeerGotInSync(SOSAccountTransactionRef aTxn
, CFStringRef peerID
, CFSetRef views
) { 
 335     SOSAccountRef account 
= aTxn
->account
; 
 336     secnotice("initial-sync", "Peer %@ synced views: %@", peerID
, views
); 
 337     if (account
->trusted_circle 
&& SOSAccountIsInCircle(account
, NULL
) && SOSCircleHasActivePeerWithID(account
->trusted_circle
, peerID
, NULL
)) { 
 338         SOSAccountUpdateOutOfSyncViews(aTxn
, views
); 
 342 static SOSEngineRef 
SOSAccountGetDataSourceEngine(SOSAccountRef account
) { 
 343     return SOSDataSourceFactoryGetEngineForDataSourceName(account
->factory
, SOSCircleGetName(account
->trusted_circle
), NULL
); 
 346 void SOSAccountEnsureSyncChecking(SOSAccountRef account
) { 
 347     if (!account
->isListeningForSync
) { 
 348         SOSEngineRef engine 
= SOSAccountGetDataSourceEngine(account
); 
 351             secnotice("initial-sync", "Setting up notifications to monitor in-sync"); 
 352             SOSEngineSetSyncCompleteListenerQueue(engine
, account
->queue
); 
 353             SOSEngineSetSyncCompleteListener(engine
, ^(CFStringRef peerID
, CFSetRef views
) { 
 354                 SOSAccountWithTransaction_Locked(account
, ^(SOSAccountRef account
, SOSAccountTransactionRef txn
) { 
 355                     SOSAccountPeerGotInSync(txn
, peerID
, views
); 
 358             account
->isListeningForSync 
= true; 
 360             secerror("Couldn't find engine to setup notifications!!!"); 
 365 void SOSAccountCancelSyncChecking(SOSAccountRef account
) { 
 366     if (account
->isListeningForSync
) { 
 367         SOSEngineRef engine 
= SOSAccountGetDataSourceEngine(account
); 
 370             secnotice("initial-sync", "Cancelling notifications to monitor in-sync"); 
 371             SOSEngineSetSyncCompleteListenerQueue(engine
, NULL
); 
 372             SOSEngineSetSyncCompleteListener(engine
, NULL
); 
 374             secnotice("initial-sync", "No engine to cancel notification from."); 
 376         account
->isListeningForSync 
= false; 
 380 bool SOSAccountCheckForAlwaysOnViews(SOSAccountRef account
) { 
 381     bool changed 
= false; 
 382     SOSPeerInfoRef myPI 
= SOSAccountGetMyPeerInfo(account
); 
 383     require_quiet(myPI
, done
); 
 384     require_quiet(SOSAccountIsInCircle(account
, NULL
), done
); 
 385     require_quiet(SOSAccountHasCompletedInitialSync(account
), done
); 
 386     CFMutableSetRef viewsToEnsure 
= SOSViewCopyViewSet(kViewSetAlwaysOn
); 
 387     // Previous version PeerInfo if we were syncing legacy keychain, ensure we include those legacy views. 
 388     if(!SOSPeerInfoVersionIsCurrent(myPI
)) { 
 389         CFSetRef V0toAdd 
= SOSViewCopyViewSet(kViewSetV0
); 
 390         CFSetUnion(viewsToEnsure
, V0toAdd
); 
 391         CFReleaseNull(V0toAdd
); 
 393     changed 
= SOSAccountUpdateFullPeerInfo(account
, viewsToEnsure
, SOSViewsGetV0ViewSet()); // We don't permit V0 view proper, only sub-views 
 394     CFReleaseNull(viewsToEnsure
);