/ keychain / ckks / CKKSViewManager.m
CKKSViewManager.m
   1  /*
   2   * Copyright (c) 2016 Apple Inc. All Rights Reserved.
   3   *
   4   * @APPLE_LICENSE_HEADER_START@
   5   *
   6   * This file contains Original Code and/or Modifications of Original Code
   7   * as defined in and that are subject to the Apple Public Source License
   8   * Version 2.0 (the 'License'). You may not use this file except in
   9   * compliance with the License. Please obtain a copy of the License at
  10   * http://www.opensource.apple.com/apsl/ and read it before using this
  11   * file.
  12   *
  13   * The Original Code and all software distributed under the License are
  14   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  15   * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
  16   * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
  17   * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
  18   * Please see the License for the specific language governing rights and
  19   * limitations under the License.
  20   *
  21   * @APPLE_LICENSE_HEADER_END@
  22   */
  23  
  24  #import <os/feature_private.h>
  25  
  26  #import "keychain/ckks/CKKSAccountStateTracker.h"
  27  #import "keychain/ckks/CKKSViewManager.h"
  28  #import "keychain/ckks/CKKSKeychainView.h"
  29  #import "keychain/ckks/CKKSSynchronizeOperation.h"
  30  #import "keychain/ckks/CKKSKey.h"
  31  #import "keychain/ckks/CKKSZoneStateEntry.h"
  32  #import "keychain/ckks/CKKSNearFutureScheduler.h"
  33  #import "keychain/ckks/CKKSNotifier.h"
  34  #import "keychain/ckks/CKKSCondition.h"
  35  #import "keychain/ckks/CKKSListenerCollection.h"
  36  #import "keychain/ckks/CloudKitCategories.h"
  37  #import "keychain/ckks/OctagonAPSReceiver.h"
  38  #import "keychain/categories/NSError+UsefulConstructors.h"
  39  #import "keychain/analytics/SecEventMetric.h"
  40  #import "keychain/analytics/SecMetrics.h"
  41  
  42  #import "keychain/ot/OTManager.h"
  43  #import "keychain/ot/OTDefines.h"
  44  #import "keychain/ot/OTConstants.h"
  45  #import "keychain/ot/ObjCImprovements.h"
  46  
  47  #import "keychain/trust/TrustedPeers/TPSyncingPolicy.h"
  48  
  49  #import "SecEntitlements.h"
  50  
  51  #include "keychain/securityd/SecDbItem.h"
  52  #include "keychain/securityd/SecDbKeychainItem.h"
  53  #include "keychain/securityd/SecItemSchema.h"
  54  #include <Security/SecureObjectSync/SOSViews.h>
  55  
  56  #import <Foundation/NSXPCConnection.h>
  57  #import <Foundation/NSXPCConnection_Private.h>
  58  
  59  #include "keychain/SecureObjectSync/SOSAccount.h"
  60  #include <Security/SecItemBackup.h>
  61  
  62  #if OCTAGON
  63  #import <CloudKit/CloudKit.h>
  64  #import <CloudKit/CloudKit_Private.h>
  65  
  66  #import <SecurityFoundation/SFKey.h>
  67  #import <SecurityFoundation/SFKey_Private.h>
  68  
  69  #import "CKKSAnalytics.h"
  70  #endif
  71  
  72  #if !OCTAGON
  73  @interface CKKSViewManager () <NSXPCListenerDelegate>
  74  #else
  75  @interface CKKSViewManager () <NSXPCListenerDelegate,
  76                                 CKKSCloudKitAccountStateListener>
  77  
  78  @property NSXPCListener *listener;
  79  
  80  @property (nullable) NSSet<NSString*>* viewAllowList;
  81  
  82  // Once you set these, all CKKSKeychainViews created will use them
  83  @property CKKSCloudKitClassDependencies* cloudKitClassDependencies;
  84  
  85  @property NSMutableDictionary<NSString*, SecBoolNSErrorCallback>* pendingSyncCallbacks;
  86  @property CKKSNearFutureScheduler* savedTLKNotifier;;
  87  @property NSOperationQueue* operationQueue;
  88  
  89  @property CKKSListenerCollection<id<CKKSPeerUpdateListener>>* peerChangeListenerCollection;
  90  
  91  @property (nonatomic) BOOL overrideCKKSViewsFromPolicy;
  92  @property (nonatomic) BOOL valueCKKSViewsFromPolicy;
  93  @property (nonatomic) BOOL startCKOperationAtViewCreation;
  94  
  95  @property BOOL itemModificationsBeforePolicyLoaded;
  96  
  97  // Make writable
  98  @property (nullable) TPSyncingPolicy* policy;
  99  @property CKKSCondition* policyLoaded;
 100  
 101  #endif
 102  @end
 103  
 104  #if OCTAGON
 105  @interface CKKSViewManager (lockstateTracker) <CKKSLockStateNotification>
 106  @end
 107  #endif
 108  
 109  @implementation CKKSViewManager
 110  #if OCTAGON
 111  
 112  - (instancetype)initWithContainer:(CKContainer*)container
 113                         sosAdapter:(id<OTSOSAdapter> _Nullable)sosAdapter
 114                accountStateTracker:(CKKSAccountStateTracker*)accountTracker
 115                   lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
 116                reachabilityTracker:(CKKSReachabilityTracker*)reachabilityTracker
 117          cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
 118  {
 119      if(self = [super init]) {
 120          _cloudKitClassDependencies = cloudKitClassDependencies;
 121          _sosPeerAdapter = sosAdapter;
 122  
 123          _viewAllowList = nil;
 124          _container = container;
 125          _accountTracker = accountTracker;
 126          _lockStateTracker = lockStateTracker;
 127          [_lockStateTracker addLockStateObserver:self];
 128          _reachabilityTracker = reachabilityTracker;
 129          _itemModificationsBeforePolicyLoaded = NO;
 130  
 131          _zoneChangeFetcher = [[CKKSZoneChangeFetcher alloc] initWithContainer:_container
 132                                                                     fetchClass:cloudKitClassDependencies.fetchRecordZoneChangesOperationClass
 133                                                            reachabilityTracker:_reachabilityTracker];
 134          OctagonAPSReceiver* globalAPSReceiver = [OctagonAPSReceiver receiverForNamedDelegatePort:SecCKKSAPSNamedPort
 135                                                                                apsConnectionClass:cloudKitClassDependencies.apsConnectionClass];
 136          [globalAPSReceiver registerCKKSReceiver:_zoneChangeFetcher];
 137  
 138          _zoneModifier = [[CKKSZoneModifier alloc] initWithContainer:_container
 139                                                  reachabilityTracker:_reachabilityTracker
 140                                                 cloudkitDependencies:cloudKitClassDependencies];
 141  
 142          _operationQueue = [[NSOperationQueue alloc] init];
 143  
 144          _peerChangeListenerCollection = [[CKKSListenerCollection alloc] initWithName:@"sos-peer-set"];
 145  
 146          _views = [[NSMutableDictionary alloc] init];
 147          _pendingSyncCallbacks = [[NSMutableDictionary alloc] init];
 148  
 149          _startCKOperationAtViewCreation = NO;
 150  
 151          _completedSecCKKSInitialize = [[CKKSCondition alloc] init];
 152  
 153          WEAKIFY(self);
 154          _savedTLKNotifier = [[CKKSNearFutureScheduler alloc] initWithName:@"newtlks"
 155                                                                      delay:5*NSEC_PER_SEC
 156                                                           keepProcessAlive:true
 157                                                  dependencyDescriptionCode:CKKSResultDescriptionNone
 158                                                                      block:^{
 159              STRONGIFY(self);
 160              [self notifyNewTLKsInKeychain];
 161          }];
 162  
 163          _policy = nil;
 164          _policyLoaded = [[CKKSCondition alloc] init];
 165  
 166          _listener = [NSXPCListener anonymousListener];
 167          _listener.delegate = self;
 168          [_listener resume];
 169  
 170          // Start listening for CK account status (for sync callbacks)
 171          [_accountTracker registerForNotificationsOfCloudKitAccountStatusChange:self];
 172      }
 173      return self;
 174  }
 175  
 176  + (CKContainer*)makeCKContainer:(NSString*)containerName
 177                           usePCS:(bool)usePCS
 178  {
 179      CKContainer* container = [CKContainer containerWithIdentifier:containerName];
 180      if(!usePCS) {
 181          CKContainerOptions* containerOptions = [[CKContainerOptions alloc] init];
 182          containerOptions.bypassPCSEncryption = YES;
 183  
 184          // We don't have a great way to set these, so replace the entire container object
 185          container = [[CKContainer alloc] initWithContainerID: container.containerID options:containerOptions];
 186      }
 187      return container;
 188  }
 189  
 190  - (BOOL)waitForTrustReady {
 191      static dispatch_once_t onceToken;
 192      __block BOOL success = YES;
 193      dispatch_once(&onceToken, ^{
 194          OTManager* manager = [OTManager manager];
 195          if (![manager waitForReady:OTCKContainerName context:OTDefaultContext wait:2*NSEC_PER_SEC]) {
 196              success = NO;
 197          }
 198      });
 199      return success;
 200  }
 201  
 202  - (void)setupAnalytics
 203  {
 204      WEAKIFY(self);
 205  
 206      // Tests shouldn't continue here; it leads to entitlement crashes with CloudKit if the mocks aren't enabled when this function runs
 207      if(SecCKKSTestsEnabled()) {
 208          return;
 209      }
 210  
 211      [[CKKSAnalytics logger] AddMultiSamplerForName:@"CKKS-healthSummary" withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSDictionary<NSString *,NSNumber *> *{
 212          STRONGIFY(self);
 213          if(!self) {
 214              return nil;
 215          }
 216  
 217          NSError* sosCircleError = nil;
 218          SOSCCStatus sosStatus = [self.sosPeerAdapter circleStatus:&sosCircleError];
 219          if(sosCircleError) {
 220              ckkserror_global("manager", " couldn't fetch sos status for SF report: %@", sosCircleError);
 221          }
 222  
 223          NSMutableDictionary* values = [NSMutableDictionary dictionary];
 224          BOOL inCircle = (sosStatus == kSOSCCInCircle);
 225          if (inCircle) {
 226              [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:CKKSAnalyticsLastInCircle];
 227          }
 228          values[CKKSAnalyticsInCircle] = @(inCircle);
 229  
 230          BOOL validCredentials = self.accountTracker.currentCKAccountInfo.hasValidCredentials;
 231          if (!validCredentials) {
 232              values[CKKSAnalyticsValidCredentials] = @(validCredentials);
 233          }
 234  
 235          NSArray<NSString *>* keys = @[ CKKSAnalyticsLastUnlock, CKKSAnalyticsLastInCircle];
 236          for (NSString * key in keys) {
 237              NSDate *date = [[CKKSAnalytics logger] datePropertyForKey:key];
 238              values[key] = @([CKKSAnalytics fuzzyDaysSinceDate:date]);
 239          }
 240          return values;
 241      }];
 242  
 243      for (NSString* viewName in [self viewList]) {
 244          [[CKKSAnalytics logger] AddMultiSamplerForName:[NSString stringWithFormat:@"CKKS-%@-healthSummary", viewName] withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport block:^NSDictionary<NSString *,NSNumber *> *{
 245              STRONGIFY(self);
 246              if(!self) {
 247                  return nil;
 248              }
 249  
 250              NSError* sosCircleError = nil;
 251              SOSCCStatus sosStatus = [self.sosPeerAdapter circleStatus:&sosCircleError];
 252              if(sosCircleError) {
 253                  ckkserror_global("manager", " couldn't fetch sos status for SF report: %@", sosCircleError);
 254              }
 255              BOOL inCircle = (sosStatus == kSOSCCInCircle);
 256              NSMutableDictionary* values = [NSMutableDictionary dictionary];
 257              CKKSKeychainView* view = [self findOrCreateView:viewName];
 258              NSDate* dateOfLastSyncClassA = [[CKKSAnalytics logger] dateOfLastSuccessForEvent:CKKSEventProcessIncomingQueueClassA zoneName:view.zoneName];
 259              NSDate* dateOfLastSyncClassC = [[CKKSAnalytics logger] dateOfLastSuccessForEvent:CKKSEventProcessIncomingQueueClassC zoneName:view.zoneName];
 260              NSDate* dateOfLastKSR = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastKeystateReady zoneName:view.zoneName];
 261  
 262              NSInteger fuzzyDaysSinceClassASync = [CKKSAnalytics fuzzyDaysSinceDate:dateOfLastSyncClassA];
 263              NSInteger fuzzyDaysSinceClassCSync = [CKKSAnalytics fuzzyDaysSinceDate:dateOfLastSyncClassC];
 264              NSInteger fuzzyDaysSinceKSR = [CKKSAnalytics fuzzyDaysSinceDate:dateOfLastKSR];
 265              [values setValue:@(fuzzyDaysSinceClassASync) forKey:[NSString stringWithFormat:@"%@-daysSinceClassASync", viewName]];
 266              [values setValue:@(fuzzyDaysSinceClassCSync) forKey:[NSString stringWithFormat:@"%@-daysSinceClassCSync", viewName]];
 267              [values setValue:@(fuzzyDaysSinceKSR) forKey:[NSString stringWithFormat:@"%@-daysSinceLastKeystateReady", viewName]];
 268  
 269              NSError* ckmeError = nil;
 270              NSNumber* syncedItemRecords = [CKKSMirrorEntry counts:view.zoneID error:&ckmeError];
 271              if(ckmeError || !syncedItemRecords) {
 272                  ckkserror_global("manager", "couldn't fetch CKMirror counts for %@: %@", view.zoneID, ckmeError);
 273              } else {
 274                  ckksnotice("metrics", view, "View has %@ item ckrecords", syncedItemRecords);
 275                  values[[NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsNumberOfSyncItems]] = [CKKSAnalytics fuzzyNumber:syncedItemRecords];
 276              }
 277  
 278              NSError* tlkShareCountError = nil;
 279              NSNumber* tlkShareCount = [CKKSTLKShareRecord counts:view.zoneID error:&tlkShareCountError];
 280              if(tlkShareCountError || !tlkShareCount) {
 281                  ckkserror_global("manager", "couldn't fetch CKKSTLKShare counts for %@: %@", view.zoneID, tlkShareCountError);
 282              } else {
 283                  ckksnotice("metrics", view, "View has %@ tlkshares", tlkShareCount);
 284                  values[[NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsNumberOfTLKShares]] = [CKKSAnalytics fuzzyNumber:tlkShareCount];
 285              }
 286  
 287              NSError* syncKeyCountError = nil;
 288              NSNumber* syncKeyCount = [CKKSKey counts:view.zoneID error:&syncKeyCountError];
 289              if(syncKeyCountError || !syncKeyCount) {
 290                  ckkserror_global("manager", "couldn't fetch CKKSKey counts for %@: %@", view.zoneID, syncKeyCountError);
 291              } else {
 292                  ckksnotice("metrics", view, "View has %@ sync keys", syncKeyCount);
 293                  values[[NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsNumberOfSyncKeys]] = syncKeyCount;
 294              }
 295  
 296              BOOL hasTLKs = [view.stateMachine.currentState isEqualToString:SecCKKSZoneKeyStateReady] || [view.stateMachine.currentState isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock];
 297              /* only synced recently if between [0...7, ie within 7 days */
 298              BOOL syncedClassARecently = fuzzyDaysSinceClassASync >= 0 && fuzzyDaysSinceClassASync < 7;
 299              BOOL syncedClassCRecently = fuzzyDaysSinceClassCSync >= 0 && fuzzyDaysSinceClassCSync < 7;
 300              BOOL incomingQueueIsErrorFree = view.lastIncomingQueueOperation.error == nil;
 301              BOOL outgoingQueueIsErrorFree = view.lastOutgoingQueueOperation.error == nil;
 302  
 303              NSString* hasTLKsKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsHasTLKs];
 304              NSString* syncedClassARecentlyKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsSyncedClassARecently];
 305              NSString* syncedClassCRecentlyKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsSyncedClassCRecently];
 306              NSString* incomingQueueIsErrorFreeKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsIncomingQueueIsErrorFree];
 307              NSString* outgoingQueueIsErrorFreeKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsOutgoingQueueIsErrorFree];
 308  
 309              values[hasTLKsKey] = @(hasTLKs);
 310              values[syncedClassARecentlyKey] = @(syncedClassARecently);
 311              values[syncedClassCRecentlyKey] = @(syncedClassCRecently);
 312              values[incomingQueueIsErrorFreeKey] = @(incomingQueueIsErrorFree);
 313              values[outgoingQueueIsErrorFreeKey] = @(outgoingQueueIsErrorFree);
 314  
 315              BOOL weThinkWeAreInSync = inCircle && hasTLKs && syncedClassARecently && syncedClassCRecently && incomingQueueIsErrorFree && outgoingQueueIsErrorFree;
 316              NSString* inSyncKey = [NSString stringWithFormat:@"%@-%@", viewName, CKKSAnalyticsInSync];
 317              values[inSyncKey] = @(weThinkWeAreInSync);
 318  
 319              return values;
 320          }];
 321      }
 322  }
 323  
 324  -(void)dealloc {
 325      [self clearAllViews];
 326  }
 327  
 328  dispatch_queue_t globalZoneStateQueue = NULL;
 329  dispatch_once_t globalZoneStateQueueOnce;
 330  
 331  // We can't load the rate limiter in an init method, as the method might end up calling itself (if the database layer isn't yet initialized).
 332  // Lazy-load it here.
 333  - (CKKSRateLimiter*)getGlobalRateLimiter {
 334      dispatch_once(&globalZoneStateQueueOnce, ^{
 335          globalZoneStateQueue = dispatch_queue_create("CKKS global zone state", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
 336      });
 337  
 338      if(_globalRateLimiter != nil) {
 339          return _globalRateLimiter;
 340      }
 341  
 342      __block CKKSRateLimiter* blocklimit = nil;
 343  
 344      dispatch_sync(globalZoneStateQueue, ^{
 345          NSError* error = nil;
 346  
 347          // Special object containing state for all zones. Currently, just the rate limiter.
 348          CKKSZoneStateEntry* allEntry = [CKKSZoneStateEntry tryFromDatabase: @"all" error:&error];
 349  
 350          if(error) {
 351              ckkserror_global("manager", " couldn't load global zone state: %@", error);
 352          }
 353  
 354          if(!error && allEntry.rateLimiter) {
 355              blocklimit = allEntry.rateLimiter;
 356          } else {
 357              blocklimit = [[CKKSRateLimiter alloc] init];
 358          }
 359      });
 360      _globalRateLimiter = blocklimit;
 361      return _globalRateLimiter;
 362  }
 363  
 364  - (void)lockStateChangeNotification:(bool)unlocked
 365  {
 366      if (unlocked) {
 367          [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:CKKSAnalyticsLastUnlock];
 368      }
 369  }
 370  
 371  #pragma mark - View List handling
 372  
 373  - (NSSet<NSString*>*)defaultViewList {
 374      NSSet<NSString*>* fullList = [OTSOSActualAdapter sosCKKSViewList];
 375  
 376      // Not a great hack: if this platform is an aTV or a HomePod, then filter its list of views
 377      bool filter = false;
 378  
 379  #if TARGET_OS_TV
 380      filter = true;
 381  #elif TARGET_OS_WATCH
 382      filter = false;
 383  #elif TARGET_OS_IOS
 384      filter = !OctagonPlatformSupportsSOS();
 385  #elif TARGET_OS_OSX
 386      filter = false;
 387  #endif
 388  
 389      if(filter) {
 390          // For now, use a hardcoded allow list for TV/HomePod
 391          NSMutableSet<NSString*>* allowList = [NSMutableSet setWithArray:@[@"Home", @"LimitedPeersAllowed"]];
 392          [allowList intersectSet:fullList];
 393          return allowList;
 394      }
 395  
 396      return fullList;
 397  }
 398  
 399  - (NSSet<NSString*>*)viewList {
 400      return [self.views.allKeys copy];
 401  }
 402  
 403  - (BOOL)setCurrentSyncingPolicy:(TPSyncingPolicy* _Nullable)syncingPolicy
 404  {
 405      return [self setCurrentSyncingPolicy:syncingPolicy policyIsFresh:NO];
 406  }
 407  
 408  - (BOOL)setCurrentSyncingPolicy:(TPSyncingPolicy* _Nullable)syncingPolicy policyIsFresh:(BOOL)policyIsFresh
 409  {
 410      if(syncingPolicy == nil) {
 411          ckksnotice_global("ckks-policy", "Nil syncing policy presented; ignoring");
 412          return NO;
 413      }
 414  
 415      NSSet<NSString*>* viewNames = syncingPolicy.viewList;
 416      ckksnotice_global("ckks-policy", "New syncing policy: %@ views: %@", syncingPolicy, viewNames);
 417  
 418      if(![self useCKKSViewsFromPolicy]) {
 419          // Thanks, but no thanks.
 420          viewNames = [self defaultViewList];
 421          ckksnotice_global("ckks-policy", "Reverting to default view list: %@", viewNames);
 422      }
 423  
 424      if(self.viewAllowList) {
 425          ckksnotice_global("ckks-policy", "Intersecting view list with allow list: %@", self.viewAllowList);
 426          NSMutableSet<NSString*>* set = [viewNames mutableCopy];
 427          [set intersectSet:self.viewAllowList];
 428  
 429          viewNames = set;
 430          ckksnotice_global("ckks-policy", "Final list: %@", viewNames);
 431      }
 432  
 433      // We need to not be synchronized on self.views before issuing commands to CKKS views.
 434      // So, store the pointers for use after the critical section.
 435      NSArray<CKKSKeychainView*>* activeViews = nil;
 436      BOOL scanAll = NO;
 437      BOOL viewsChanged = NO;
 438  
 439      @synchronized(self.views) {
 440          self.policy = syncingPolicy;
 441  
 442          NSArray* previousViewNames = [self.views.allKeys copy];
 443  
 444          // First, shut down any views that are no longer in the set
 445          for(NSString* viewName in previousViewNames) {
 446              if(![viewNames containsObject:viewName]) {
 447                  ckksnotice_global("ckks-policy", "Stopping old view %@", viewName);
 448                  [self clearView:viewName];
 449                  viewsChanged = YES;
 450              }
 451          }
 452  
 453          for(NSString* viewName in viewNames) {
 454              CKKSKeychainView* view = nil;
 455  
 456              if([previousViewNames containsObject:viewName]) {
 457                  view = [self findView:viewName];
 458                  ckksinfo_global("ckks-policy", "Already have view %@", view);
 459  
 460              } else {
 461                  view = [self findOrCreateView:viewName];
 462                  ckksnotice_global("ckks-policy", "Created new view %@", view);
 463                  viewsChanged = YES;
 464              }
 465          }
 466  
 467          activeViews = [self.views.allValues copy];
 468  
 469          if(self.itemModificationsBeforePolicyLoaded) {
 470              ckksnotice_global("ckks-policy", "Issuing scan suggestions to handle missed items");
 471              scanAll = YES;
 472              self.itemModificationsBeforePolicyLoaded = NO;
 473          }
 474      }
 475  
 476      for(CKKSKeychainView* view in activeViews) {
 477          [view setCurrentSyncingPolicy:self.policy policyIsFresh:policyIsFresh];
 478  
 479          if(scanAll) {
 480              [view scanLocalItems:@"item-added-before-policy"];
 481          }
 482      }
 483  
 484      // Retrigger the analytics setup, so that our views will report status
 485      [self setupAnalytics];
 486  
 487      // The policy is considered loaded once the views have been created
 488      [self.policyLoaded fulfill];
 489      return viewsChanged;
 490  }
 491  
 492  - (void)setSyncingViewsAllowList:(NSSet<NSString*>*)viewNames
 493  {
 494      self.viewAllowList = viewNames;
 495  }
 496  
 497  - (void)resetSyncingPolicy
 498  {
 499      ckksnotice_global("ckks-policy", "Setting policy to nil");
 500      self.policy = nil;
 501      self.policyLoaded = [[CKKSCondition alloc] init];
 502  
 503      self.startCKOperationAtViewCreation = NO;
 504  }
 505  
 506  #pragma mark - View Handling
 507  
 508  - (void)setView: (CKKSKeychainView*) obj {
 509      CKKSKeychainView* kcv = nil;
 510  
 511      @synchronized(self.views) {
 512          kcv = self.views[obj.zoneName];
 513          self.views[obj.zoneName] = obj;
 514      }
 515  
 516      if(kcv) {
 517          [kcv cancelAllOperations];
 518      }
 519  }
 520  
 521  - (void)clearAllViews {
 522      NSArray<CKKSKeychainView*>* tempviews = nil;
 523      @synchronized(self.views) {
 524          tempviews = [self.views.allValues copy];
 525          [self.views removeAllObjects];
 526  
 527          self.startCKOperationAtViewCreation = NO;
 528      }
 529  
 530      for(CKKSKeychainView* view in tempviews) {
 531          [view cancelAllOperations];
 532      }
 533  }
 534  
 535  - (void)clearView:(NSString*) viewName {
 536      CKKSKeychainView* kcv = nil;
 537      @synchronized(self.views) {
 538          kcv = self.views[viewName];
 539          self.views[viewName] = nil;
 540      }
 541  
 542      if(kcv) {
 543          [kcv cancelAllOperations];
 544      }
 545  }
 546  
 547  - (CKKSKeychainView*)findView:(NSString*)viewName {
 548      if(!viewName) {
 549          return nil;
 550      }
 551  
 552      @synchronized(self.views) {
 553          return self.views[viewName];
 554      }
 555  }
 556  
 557  - (CKKSKeychainView* _Nullable)findView:(NSString*)viewName error:(NSError**)error
 558  {
 559      if([self.policyLoaded wait:5*NSEC_PER_SEC] != 0) {
 560          ckkserror_global("ckks", "Haven't yet received a syncing policy; expect failure finding views");
 561  
 562          if([self useCKKSViewsFromPolicy]) {
 563              if(error) {
 564                  *error = [NSError errorWithDomain:CKKSErrorDomain
 565                                               code:CKKSErrorPolicyNotLoaded
 566                                           userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"CKKS syncing policy not yet loaded; cannot find view '%@'", viewName]}];
 567  
 568              }
 569              return nil;
 570          }
 571      }
 572  
 573      @synchronized(self.views) {
 574          CKKSKeychainView* view = self.views[viewName];
 575          if(!view) {
 576              if(error) {
 577                  *error = [NSError errorWithDomain:CKKSErrorDomain
 578                                               code:CKKSNoSuchView
 579                                           userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No syncing view for '%@'", viewName]}];
 580              }
 581              return nil;
 582          }
 583  
 584          return view;
 585      }
 586  }
 587  
 588  - (CKKSKeychainView*)findOrCreateView:(NSString*)viewName {
 589      @synchronized(self.views) {
 590          CKKSKeychainView* kcv = self.views[viewName];
 591          if(kcv) {
 592              return kcv;
 593          }
 594  
 595          self.views[viewName] = [[CKKSKeychainView alloc] initWithContainer: self.container
 596                                                                    zoneName: viewName
 597                                                              accountTracker: self.accountTracker
 598                                                            lockStateTracker: self.lockStateTracker
 599                                                         reachabilityTracker: self.reachabilityTracker
 600                                                               changeFetcher:self.zoneChangeFetcher
 601                                                                zoneModifier:self.zoneModifier
 602                                                            savedTLKNotifier: self.savedTLKNotifier
 603                                                   cloudKitClassDependencies:self.cloudKitClassDependencies];
 604  
 605          if(self.startCKOperationAtViewCreation) {
 606              [self.views[viewName] beginCloudKitOperation];
 607          }
 608          return self.views[viewName];
 609      }
 610  }
 611  
 612  - (NSSet<CKKSKeychainView*>*)currentViews
 613  {
 614      @synchronized (self.views) {
 615          NSMutableSet<CKKSKeychainView*>* viewObjects = [NSMutableSet set];
 616          for(NSString* viewName in self.views) {
 617              [viewObjects addObject:self.views[viewName]];
 618          }
 619          return viewObjects;
 620      }
 621  }
 622  
 623  - (void)createViews
 624  {
 625      if(![self useCKKSViewsFromPolicy]) {
 626          // In the future, the CKKSViewManager needs to persist its policy property through daemon restarts
 627          // and load it here, before creating whatever views it was told to (in a previous daemon lifetime)
 628          for (NSString* viewName in [self defaultViewList]) {
 629              CKKSKeychainView* view = [self findOrCreateView:viewName];
 630              (void)view;
 631          }
 632      } else {
 633          ckksnotice_global("ckks-views", "Not loading default view list due to enabled CKKS4All");
 634      }
 635  }
 636  
 637  - (void)beginCloudKitOperationOfAllViews
 638  {
 639      self.startCKOperationAtViewCreation = YES;
 640  
 641      for (CKKSKeychainView* view in self.views.allValues) {
 642          [view beginCloudKitOperation];
 643      }
 644  }
 645  
 646  - (void)haltZone:(NSString*)viewName
 647  {
 648      @synchronized(self.views) {
 649          CKKSKeychainView* view = self.views[viewName];
 650          [view halt];
 651          [view cancelAllOperations];
 652          self.views[viewName] = nil;
 653      }
 654  }
 655  
 656  - (CKKSKeychainView*)restartZone:(NSString*)viewName {
 657      [self haltZone:viewName];
 658      CKKSKeychainView* view = [self findOrCreateView: viewName];
 659  
 660      [view setCurrentSyncingPolicy:self.policy policyIsFresh:NO];
 661  
 662      return view;
 663  }
 664  
 665  - (NSString*)viewNameForViewHint: (NSString*) viewHint {
 666      // For now, choose view based on viewhints.
 667      if(viewHint && ![viewHint isEqual: [NSNull null]]) {
 668          return viewHint;
 669      }
 670  
 671      //  If there isn't a provided view hint, use the "keychain" view if we're testing. Otherwise, nil.
 672      if(SecCKKSTestsEnabled()) {
 673          return @"keychain";
 674      } else {
 675          return nil;
 676      }
 677  }
 678  
 679  - (void) setOverrideCKKSViewsFromPolicy:(BOOL)value {
 680      _overrideCKKSViewsFromPolicy = YES;
 681      _valueCKKSViewsFromPolicy = value;
 682  }
 683  
 684  - (BOOL)useCKKSViewsFromPolicy {
 685      if (self.overrideCKKSViewsFromPolicy) {
 686          return self.valueCKKSViewsFromPolicy;
 687      } else {
 688          BOOL viewsFromPolicy = os_feature_enabled(Security, CKKSViewsFromPolicy);
 689  
 690          static dispatch_once_t onceToken;
 691          dispatch_once(&onceToken, ^{
 692              ckksnotice_global("ckks", "ViewsFromPolicy feature flag: %@", viewsFromPolicy ? @"on" : @"off");
 693          });
 694          return viewsFromPolicy;
 695      }
 696  }
 697  
 698  - (NSString* _Nullable)viewNameForItem:(SecDbItemRef)item
 699  {
 700      if ([self useCKKSViewsFromPolicy]) {
 701          CFErrorRef cferror = NULL;
 702          NSMutableDictionary *dict = (__bridge_transfer NSMutableDictionary*) SecDbItemCopyPListWithMask(item, kSecDbSyncFlag, &cferror);
 703  
 704          if(cferror) {
 705              ckkserror_global("ckks", "Couldn't fetch attributes from item: %@", cferror);
 706              CFReleaseNull(cferror);
 707              return nil;
 708          }
 709  
 710          // Ensure that we've added the class name, because SecDbItemCopyPListWithMask doesn't do that for some reason.
 711          dict[(__bridge NSString*)kSecClass] = (__bridge NSString*)item->class->name;
 712  
 713          NSString* view = [self.policy mapDictionaryToView:dict];
 714          if (view == nil) {
 715              ckkserror_global("ckks", "No view returned from policy (%@): %@", self.policy, item);
 716              return nil;
 717          }
 718  
 719          return view;
 720      } else {
 721          CFErrorRef cferror = NULL;
 722          NSString* viewHint = (__bridge NSString*) SecDbItemGetValue(item, &v7vwht, &cferror);
 723  
 724          if(cferror) {
 725              ckkserror_global("ckks", "Couldn't fetch the viewhint for some reason: %@", cferror);
 726              CFReleaseNull(cferror);
 727              viewHint = nil;
 728          }
 729  
 730          return [self viewNameForViewHint: viewHint];
 731      }
 732  }
 733  
 734  - (void)registerSyncStatusCallback: (NSString*) uuid callback: (SecBoolNSErrorCallback) callback {
 735      // Someone is requesting future notification of this item.
 736      @synchronized(self.pendingSyncCallbacks) {
 737          ckksnotice_global("ckkscallback", "registered callback for UUID: %@", uuid);
 738          self.pendingSyncCallbacks[uuid] = callback;
 739      }
 740  }
 741  
 742  - (SecBoolNSErrorCallback _Nullable)claimCallbackForUUID:(NSString* _Nullable)uuid
 743  {
 744      if(!uuid) {
 745          return nil;
 746      }
 747  
 748      @synchronized(self.pendingSyncCallbacks) {
 749          SecBoolNSErrorCallback callback = self.pendingSyncCallbacks[uuid];
 750  
 751          if(callback) {
 752              ckksnotice_global("ckkscallback", "fetched UUID: %@", uuid);
 753          }
 754  
 755          self.pendingSyncCallbacks[uuid] = nil;
 756          return callback;
 757      }
 758  }
 759  
 760  - (NSSet<NSString*>*)pendingCallbackUUIDs
 761  {
 762      @synchronized(self.pendingSyncCallbacks) {
 763          return [[self.pendingSyncCallbacks allKeys] copy];
 764      }
 765  }
 766  
 767  - (void)cloudkitAccountStateChange:(CKAccountInfo* _Nullable)oldAccountInfo to:(CKAccountInfo*)currentAccountInfo
 768  {
 769      if(currentAccountInfo.accountStatus == CKAccountStatusAvailable && currentAccountInfo.hasValidCredentials) {
 770          // Account is okay!
 771      } else {
 772          @synchronized(self.pendingSyncCallbacks) {
 773              if(self.pendingSyncCallbacks.count > 0) {
 774                  ckksnotice_global("ckkscallback", "No CK account; failing all pending sync callbacks");
 775  
 776                  for(NSString* uuid in [self.pendingSyncCallbacks allKeys]) {
 777                      [CKKSViewManager callSyncCallbackWithErrorNoAccount:self.pendingSyncCallbacks[uuid]];
 778                  }
 779  
 780                  [self.pendingSyncCallbacks removeAllObjects];
 781              }
 782          }
 783      }
 784  }
 785  
 786  + (void)callSyncCallbackWithErrorNoAccount:(SecBoolNSErrorCallback)syncCallback
 787  {
 788      // I don't love using this domain, but PCS depends on it
 789      syncCallback(false, [NSError errorWithDomain:@"securityd"
 790                                              code:errSecNotLoggedIn
 791                                       description:@"No iCloud account available; item is not expected to sync"]);
 792  }
 793  
 794  - (void) handleKeychainEventDbConnection: (SecDbConnectionRef) dbconn source:(SecDbTransactionSource)txionSource added: (SecDbItemRef) added deleted: (SecDbItemRef) deleted {
 795  
 796      SecDbItemRef modified = added ? added : deleted;
 797  
 798      NSString* keyViewName = [CKKSKey isItemKeyForKeychainView: modified];
 799  
 800      if(keyViewName) {
 801          // This might be some key material for this view! Poke it.
 802          CKKSKeychainView* view = [self findView: keyViewName];
 803  
 804          if(!SecCKKSTestDisableKeyNotifications()) {
 805              ckksnotice("ckks", view, "Potential new key material from %@ (source %lu)",
 806                         keyViewName, (unsigned long)txionSource);
 807              [view keyStateMachineRequestProcess];
 808          } else {
 809              ckksnotice("ckks", view, "Ignoring potential new key material from %@ (source %lu)",
 810                         keyViewName, (unsigned long)txionSource);
 811          }
 812          return;
 813      }
 814  
 815      bool addedSync   = added   && SecDbItemIsSyncable(added);
 816      bool deletedSync = deleted && SecDbItemIsSyncable(deleted);
 817  
 818      if(!addedSync && !deletedSync) {
 819          // Local-only change. Skip with prejudice.
 820          ckksinfo_global("ckks", "skipping sync of non-sync item (%d, %d)", addedSync, deletedSync);
 821          return;
 822      }
 823  
 824      NSString* viewName = nil;
 825  
 826      @synchronized(self.views) {
 827          if([self useCKKSViewsFromPolicy] && !self.policy) {
 828              ckkserror_global("ckks", "No policy configured(%@). Skipping item: %@", self.policy, modified);
 829              self.itemModificationsBeforePolicyLoaded = YES;
 830  
 831              return;
 832          }
 833      }
 834  
 835      viewName = [self viewNameForItem:modified];
 836  
 837      if(!viewName) {
 838          ckksnotice_global("ckks", "No intended CKKS view for item; skipping: %@", modified);
 839          return;
 840      }
 841  
 842      // Looks like a normal item. Proceed!
 843      CKKSKeychainView* view = [self findView:viewName];
 844  
 845      if(!view) {
 846          ckksnotice_global("ckks", "No CKKS view for %@, skipping: %@", viewName, modified);
 847  
 848          NSString* uuid = (__bridge NSString*) SecDbItemGetValue(modified, &v10itemuuid, NULL);
 849          SecBoolNSErrorCallback syncCallback = [self claimCallbackForUUID:uuid];
 850  
 851          if(syncCallback) {
 852              syncCallback(false, [NSError errorWithDomain:CKKSErrorDomain
 853                                                      code:CKKSNoSuchView
 854                                                  userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No syncing view for '%@'", viewName]}]);
 855          }
 856          return;
 857      }
 858  
 859      ckksnotice("ckks", view, "Routing item to zone %@: %@", viewName, modified);
 860      [view handleKeychainEventDbConnection:dbconn
 861                                     source:txionSource
 862                                      added:added
 863                                    deleted:deleted
 864                                rateLimiter:self.globalRateLimiter];
 865  }
 866  
 867  -(void)setCurrentItemForAccessGroup:(NSData* _Nonnull)newItemPersistentRef
 868                                 hash:(NSData*)newItemSHA1
 869                          accessGroup:(NSString*)accessGroup
 870                           identifier:(NSString*)identifier
 871                             viewHint:(NSString*)viewHint
 872                            replacing:(NSData* _Nullable)oldCurrentItemPersistentRef
 873                                 hash:(NSData*)oldItemSHA1
 874                             complete:(void (^) (NSError* operror)) complete
 875  {
 876      NSError* viewError = nil;
 877      CKKSKeychainView* view = [self findView:viewHint error:&viewError];
 878  
 879      if(!view) {
 880          ckksnotice_global("ckks", "No CKKS view for %@, skipping setcurrent request: %@", viewHint, viewError);
 881          complete(viewError);
 882          return;
 883      }
 884  
 885      [view setCurrentItemForAccessGroup:newItemPersistentRef
 886                                    hash:newItemSHA1
 887                             accessGroup:accessGroup
 888                              identifier:identifier
 889                               replacing:oldCurrentItemPersistentRef
 890                                    hash:oldItemSHA1
 891                                complete:complete];
 892  }
 893  
 894  -(void)getCurrentItemForAccessGroup:(NSString*)accessGroup
 895                           identifier:(NSString*)identifier
 896                             viewHint:(NSString*)viewHint
 897                      fetchCloudValue:(bool)fetchCloudValue
 898                             complete:(void (^) (NSString* uuid, NSError* operror)) complete
 899  {
 900      NSError* viewError = nil;
 901      CKKSKeychainView* view = [self findView:viewHint error:&viewError];
 902      if(!view) {
 903          ckksnotice_global("ckks", "No CKKS view for %@, skipping current fetch request: %@", viewHint, viewError);
 904          complete(NULL, viewError);
 905          return;
 906      }
 907  
 908      [view getCurrentItemForAccessGroup:accessGroup
 909                              identifier:identifier
 910                         fetchCloudValue:fetchCloudValue
 911                                complete:complete];
 912  }
 913  
 914  + (instancetype)manager
 915  {
 916      return [OTManager manager].viewManager;
 917  }
 918  
 919  - (void)cancelPendingOperations {
 920      [self.savedTLKNotifier cancel];
 921  }
 922  
 923  -(void)notifyNewTLKsInKeychain {
 924      // Why two functions here? Limitation of OCMock, unfortunately: can't stub and expect the same method
 925      ckksnotice_global("ckksbackup", "New TLKs have arrived");
 926      [self syncBackupAndNotifyAboutSync];
 927  }
 928  
 929  - (void)syncBackupAndNotifyAboutSync {
 930      SOSAccount* account = (__bridge SOSAccount*)SOSKeychainAccountGetSharedAccount();
 931      
 932      if(!account) {
 933          ckksnotice_global("ckks", "Failed to get account object");
 934          return;
 935      }
 936  
 937      [account performTransaction:^(SOSAccountTransaction * _Nonnull txn) {
 938          CFErrorRef error = NULL;
 939          NSSet* ignore = CFBridgingRelease(SOSAccountCopyBackupPeersAndForceSync(txn, &error));
 940          (void)ignore;
 941  
 942          if(error) {
 943              ckkserror_global("backup", "Couldn't process sync with backup peers: %@", error);
 944          } else {
 945              ckksnotice_global("ckksbackup", "telling CloudServices about TLK arrival");
 946              notify_post(kSecItemBackupNotification);
 947          };
 948      }];
 949  }
 950  
 951  
 952  
 953  - (NSArray<CKKSKeychainBackedKey*>* _Nullable)currentTLKsFilteredByPolicy:(BOOL)restrictToPolicy error:(NSError**)error
 954  {
 955      NSError* localError = nil;
 956      NSArray<CKKSKeychainView*>* actualViews = [self views:nil operation:@"current TLKs" error:&localError];
 957      if(localError) {
 958          ckkserror_global("ckks", "Error getting views: %@", localError);
 959          if(error) {
 960              *error = localError;
 961          }
 962          return nil;
 963      }
 964  
 965      NSMutableArray<CKKSResultOperation<CKKSKeySetProviderOperationProtocol>*>* keyFetchOperations = [NSMutableArray array];
 966      for (CKKSKeychainView* view in actualViews) {
 967          if(restrictToPolicy && [self useCKKSViewsFromPolicy] && ![self.policy.viewsToPiggybackTLKs containsObject:view.zoneName]) {
 968              continue;
 969          }
 970  
 971          CKKSResultOperation<CKKSKeySetProviderOperationProtocol>* op = [view findKeySet:NO];
 972          [op timeout:10*NSEC_PER_SEC];
 973          [keyFetchOperations addObject:op];
 974      }
 975  
 976      NSMutableArray<CKKSKeychainBackedKey*>* tlks = [NSMutableArray array];
 977  
 978      for(CKKSResultOperation<CKKSKeySetProviderOperationProtocol>* op in keyFetchOperations) {
 979          [op waitUntilFinished];
 980  
 981          if(op.error) {
 982              ckkserror_global("ckks", "Error getting keyset: %@", op.error);
 983              if(error) {
 984                  *error = op.error;
 985              }
 986          } else {
 987              if(op.keyset.tlk) {
 988                  // Keys provided by this function must have the key material loaded
 989                  NSError* loadError = nil;
 990                  [op.keyset.tlk ensureKeyLoaded:&loadError];
 991                  if(loadError) {
 992                      ckkserror_global("ckks", "Error loading key: %@", loadError);
 993                      if(error) {
 994                          *error = loadError;
 995                      }
 996                  } else {
 997                      [tlks addObject:[op.keyset.tlk.keycore copy]];
 998                  }
 999              } else {
1000                  ckkserror_global("ckks", "Do not have TLK: %@", op.keyset);
1001              }
1002          }
1003      }
1004  
1005      return tlks;
1006  }
1007  
1008  #pragma mark - RPCs to manage and report state
1009  
1010  - (void)performanceCounters:(void(^)(NSDictionary <NSString *, NSNumber *> *counter))reply {
1011      reply(@{});
1012  }
1013  
1014  - (NSArray<CKKSKeychainView*>*)views:(NSString*)viewName operation:(NSString*)opName error:(NSError**)error
1015  {
1016      return [self views:viewName operation:opName errorOnPolicyMissing:YES error:error];
1017  }
1018  
1019  - (NSArray<CKKSKeychainView*>*)views:(NSString*)viewName
1020                             operation:(NSString*)opName
1021                  errorOnPolicyMissing:(BOOL)errorOnPolicyMissing
1022                                 error:(NSError**)error
1023  {
1024      NSArray* actualViews = nil;
1025  
1026      // Ensure we've actually set up, but don't wait too long. Clients get impatient.
1027      if([self.completedSecCKKSInitialize wait:5*NSEC_PER_SEC]) {
1028          ckkserror_global("ckks", "Haven't yet initialized zones; expect failure fetching views");
1029      }
1030  
1031      // If the caller doesn't mind if the policy is missing, wait some, but not the full 5s
1032      BOOL policyLoaded = [self.policyLoaded wait:(errorOnPolicyMissing ? 5 : 0.5)*NSEC_PER_SEC] == 0;
1033      if(!policyLoaded) {
1034          ckkserror_global("ckks", "Haven't yet received a policy; expect failure fetching views");
1035      }
1036  
1037      if(viewName) {
1038          CKKSKeychainView* view = errorOnPolicyMissing ? [self findView:viewName error:error] : [self findView:viewName];
1039          ckksnotice_global("ckks", "Received a %@ request for zone %@ (%@)", opName, viewName, view);
1040  
1041          if(!view) {
1042              return nil;
1043          }
1044  
1045          actualViews = @[view];
1046  
1047      } else {
1048          if(!policyLoaded && [self useCKKSViewsFromPolicy] && errorOnPolicyMissing) {
1049              if(error) {
1050                  if(error) {
1051                      *error = [NSError errorWithDomain:CKKSErrorDomain
1052                                                   code:CKKSErrorPolicyNotLoaded
1053                                               userInfo:@{NSLocalizedDescriptionKey: @"CKKS syncing policy not yet loaded; cannot list all views"}];
1054                  }
1055              }
1056              return nil;
1057          }
1058  
1059          @synchronized(self.views) {
1060              actualViews = [self.views.allValues copy];
1061              ckksnotice_global("ckks", "Received a %@ request for all zones: %@", opName, actualViews);
1062          }
1063      }
1064      actualViews = [actualViews sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"zoneName" ascending:YES]]];
1065      return actualViews;
1066  }
1067  
1068  - (void)rpcResetLocal:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
1069      NSError* localError = nil;
1070      NSArray* actualViews = [self views:viewName operation:@"local reset" error:&localError];
1071      if(localError) {
1072          ckkserror_global("ckks", "Error getting view %@: %@", viewName, localError);
1073          reply(localError);
1074          return;
1075      }
1076  
1077      CKKSResultOperation* op = [CKKSResultOperation named:@"local-reset-zones-waiter" withBlockTakingSelf:^(CKKSResultOperation * _Nonnull strongOp) {
1078          if(!strongOp.error) {
1079              ckksnotice_global("ckksreset", "Completed rpcResetLocal");
1080          } else {
1081              ckksnotice_global("ckks", "Completed rpcResetLocal with error: %@", strongOp.error);
1082          }
1083          reply(CKXPCSuitableError(strongOp.error));
1084      }];
1085  
1086      for(CKKSKeychainView* view in actualViews) {
1087          ckksnotice("ckksreset", view, "Beginning local reset for %@", view);
1088          [op addSuccessDependency:[view resetLocalData]];
1089      }
1090  
1091      [op timeout:120*NSEC_PER_SEC];
1092      [self.operationQueue addOperation: op];
1093  }
1094  
1095  - (void)rpcResetCloudKit:(NSString*)viewName reason:(NSString *)reason reply:(void(^)(NSError* result)) reply {
1096      NSError* localError = nil;
1097      NSArray* actualViews = [self views:viewName operation:@"CloudKit reset" error:&localError];
1098      if(localError) {
1099          ckkserror_global("ckks", "Error getting view %@: %@", viewName, localError);
1100          reply(localError);
1101          return;
1102      }
1103  
1104      CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-reset-zones-waiter" withBlock:^() {}];
1105  
1106      // Use the completion block instead of the operation block, so that it runs even if the cancel fires
1107      __weak __typeof(op) weakOp = op;
1108      [op setCompletionBlock:^{
1109          __strong __typeof(op) strongOp = weakOp;
1110          if(!strongOp.error) {
1111              ckksnotice_global("ckksreset", "Completed rpcResetCloudKit");
1112          } else {
1113              ckksnotice_global("ckksreset", "Completed rpcResetCloudKit with error: %@", strongOp.error);
1114          }
1115          reply(CKXPCSuitableError(strongOp.error));
1116      }];
1117  
1118      for(CKKSKeychainView* view in actualViews) {
1119          NSString *operationGroupName = [NSString stringWithFormat:@"api-reset-%@", reason];
1120          ckksnotice("ckksreset", view, "Beginning CloudKit reset for %@: %@", view, reason);
1121          [op addSuccessDependency:[view resetCloudKitZone:[CKOperationGroup CKKSGroupWithName:operationGroupName]]];
1122      }
1123  
1124      [op timeout:120*NSEC_PER_SEC];
1125      [self.operationQueue addOperation: op];
1126  }
1127  
1128  - (void)rpcResync:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
1129      NSError* localError = nil;
1130      NSArray* actualViews = [self views:viewName operation:@"CloudKit resync" error:&localError];
1131      if(localError) {
1132          ckkserror_global("ckks", "Error getting view %@: %@", viewName, localError);
1133          reply(localError);
1134          return;
1135      }
1136  
1137      CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
1138      op.name = @"rpc-resync-cloudkit";
1139      __weak __typeof(op) weakOp = op;
1140  
1141      // Use the completion block instead of the operation block, so that it runs even if the cancel fires
1142      [op setCompletionBlock:^{
1143          __strong __typeof(op) strongOp = weakOp;
1144          ckksnotice_global("ckks", "Ending rsync-CloudKit rpc with %@", strongOp.error);
1145          reply(CKXPCSuitableError(strongOp.error));
1146      }];
1147  
1148      for(CKKSKeychainView* view in actualViews) {
1149          ckksnotice("ckksresync", view, "Beginning resync (CloudKit) for %@", view);
1150  
1151          CKKSSynchronizeOperation* resyncOp = [view resyncWithCloud];
1152          [op addSuccessDependency:resyncOp];
1153      }
1154  
1155      [op timeout:120*NSEC_PER_SEC];
1156      [self.operationQueue addOperation:op];
1157  }
1158  
1159  - (void)rpcResyncLocal:(NSString*)viewName reply:(void(^)(NSError* result))reply {
1160      NSError* localError = nil;
1161      NSArray* actualViews = [self views:viewName operation:@"local resync" error:&localError];
1162      if(localError) {
1163          ckkserror_global("ckks", "Error getting view %@: %@", viewName, localError);
1164          reply(localError);
1165          return;
1166      }
1167  
1168      CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
1169      op.name = @"rpc-resync-local";
1170      __weak __typeof(op) weakOp = op;
1171      // Use the completion block instead of the operation block, so that it runs even if the cancel fires
1172      [op setCompletionBlock:^{
1173          __strong __typeof(op) strongOp = weakOp;
1174          ckksnotice_global("ckks", "Ending rsync-local rpc with %@", strongOp.error);
1175          reply(CKXPCSuitableError(strongOp.error));
1176      }];
1177  
1178      for(CKKSKeychainView* view in actualViews) {
1179          ckksnotice("ckksresync", view, "Beginning resync (local) for %@", view);
1180  
1181          CKKSLocalSynchronizeOperation* resyncOp = [view resyncLocal];
1182          [op addSuccessDependency:resyncOp];
1183      }
1184  
1185      [op timeout:120*NSEC_PER_SEC];
1186  }
1187  
1188  - (void)rpcStatus: (NSString*)viewName
1189               fast:(bool)fast
1190              reply:(void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply
1191  {
1192      NSMutableArray* a = [[NSMutableArray alloc] init];
1193  
1194      // Now, query the views about their status. Don't wait for the policy to be loaded
1195      NSError* error = nil;
1196      NSArray* actualViews = [self views:viewName operation:@"status" errorOnPolicyMissing:NO error:&error];
1197      if(!actualViews || error) {
1198          reply(nil, error);
1199          return;
1200      }
1201  
1202      WEAKIFY(self);
1203      CKKSResultOperation* statusOp = [CKKSResultOperation named:@"status-rpc" withBlock:^{
1204          STRONGIFY(self);
1205  
1206          if (fast == false) {
1207              // Get account state, even wait for it a little
1208              [self.accountTracker.ckdeviceIDInitialized wait:1*NSEC_PER_SEC];
1209              NSString *deviceID = self.accountTracker.ckdeviceID;
1210              NSError *deviceIDError = self.accountTracker.ckdeviceIDError;
1211              NSDate *lastCKKSPush = [[CKKSAnalytics logger] datePropertyForKey:CKKSAnalyticsLastCKKSPush];
1212  
1213  #define stringify(obj) CKKSNilToNSNull([obj description])
1214              NSDictionary* global = @{
1215                                       @"view":                @"global",
1216                                       @"reachability":        self.reachabilityTracker.currentReachability ? @"network" : @"no-network",
1217                                       @"ckdeviceID":          CKKSNilToNSNull(deviceID),
1218                                       @"ckdeviceIDError":     CKKSNilToNSNull(deviceIDError),
1219                                       @"lockstatetracker":    stringify(self.lockStateTracker),
1220                                       @"cloudkitRetryAfter":  stringify(self.zoneModifier.cloudkitRetryAfter),
1221                                       @"lastCKKSPush":        CKKSNilToNSNull(lastCKKSPush),
1222                                       @"policy":              stringify(self.policy),
1223                                       @"viewsFromPolicy":     [self useCKKSViewsFromPolicy] ? @"yes" : @"no",
1224                                       };
1225              [a addObject: global];
1226          }
1227  
1228          for(CKKSKeychainView* view in actualViews) {
1229              NSDictionary* status = nil;
1230              ckksnotice("ckks", view, "Fetching status for %@", view.zoneName);
1231              if (fast) {
1232                  status = [view fastStatus];
1233              } else {
1234                  status = [view status];
1235              }
1236              ckksinfo("ckks", view, "Status is %@", status);
1237              if(status) {
1238                  [a addObject: status];
1239              }
1240          }
1241          reply(a, nil);
1242      }];
1243  
1244      // If we're signed in, give the views a few seconds to enter what they consider to be a non-transient state (in case this daemon just launched)
1245      if([self.accountTracker.ckAccountInfoInitialized wait:2*NSEC_PER_SEC]) {
1246          ckkserror_global("account", "Haven't yet figured out cloudkit account state");
1247      }
1248  
1249      if(self.accountTracker.currentCKAccountInfo.accountStatus == CKAccountStatusAvailable) {
1250          if (![self waitForTrustReady]) {
1251              ckkserror_global("trust", "Haven't yet figured out trust status");
1252          }
1253  
1254          for(CKKSKeychainView* view in actualViews) {
1255              OctagonStateMultiStateArrivalWatcher* waitForTransient = [[OctagonStateMultiStateArrivalWatcher alloc] initNamed:@"rpc-watcher"
1256                                                                                                                   serialQueue:view.queue
1257                                                                                                                        states:CKKSKeyStateNonTransientStates()];
1258              [waitForTransient timeout:2*NSEC_PER_SEC];
1259              [view.stateMachine registerMultiStateArrivalWatcher:waitForTransient];
1260  
1261              [statusOp addDependency:waitForTransient.result];
1262          }
1263      }
1264      [self.operationQueue addOperation:statusOp];
1265  
1266      return;
1267  }
1268  
1269  - (void)rpcStatus:(NSString*)viewName reply:(void (^)(NSArray<NSDictionary*>* result, NSError* error))reply
1270  {
1271      [self rpcStatus:viewName fast:false reply:reply];
1272  }
1273  
1274  - (void)rpcFastStatus:(NSString*)viewName reply:(void (^)(NSArray<NSDictionary*>* result, NSError* error))reply
1275  {
1276      [self rpcStatus:viewName fast:true reply:reply];
1277  }
1278  
1279  - (void)rpcFetchAndProcessChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
1280      [self rpcFetchAndProcessChanges:viewName classA:false reply: (void(^)(NSError* result))reply];
1281  }
1282  
1283  - (void)rpcFetchAndProcessClassAChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
1284      [self rpcFetchAndProcessChanges:viewName classA:true reply:(void(^)(NSError* result))reply];
1285  }
1286  
1287  - (void)rpcFetchAndProcessChanges:(NSString*)viewName classA:(bool)classAError reply: (void(^)(NSError* result)) reply {
1288      NSError* error = nil;
1289      NSArray* actualViews = [self views:viewName operation:@"fetch" error:&error];
1290      if(!actualViews || error) {
1291          reply(error);
1292          return;
1293      }
1294  
1295      CKKSResultOperation* blockOp = [[CKKSResultOperation alloc] init];
1296      blockOp.name = @"rpc-fetch-and-process-result";
1297      __weak __typeof(blockOp) weakBlockOp = blockOp;
1298      // Use the completion block instead of the operation block, so that it runs even if the cancel fires
1299      [blockOp setCompletionBlock:^{
1300          __strong __typeof(blockOp) strongBlockOp = weakBlockOp;
1301          [strongBlockOp allDependentsSuccessful];
1302          reply(CKXPCSuitableError(strongBlockOp.error));
1303      }];
1304  
1305      for(CKKSKeychainView* view in actualViews) {
1306          ckksnotice("ckks", view, "Beginning fetch for %@", view);
1307  
1308          CKKSResultOperation* op = [view processIncomingQueue:classAError after:[view.zoneChangeFetcher requestSuccessfulFetch: CKKSFetchBecauseAPIFetchRequest]];
1309          [blockOp addSuccessDependency:op];
1310      }
1311  
1312      [self.operationQueue addOperation: [blockOp timeout:(SecCKKSTestsEnabled() ? NSEC_PER_SEC * 5 : NSEC_PER_SEC * 120)]];
1313  }
1314  
1315  - (void)rpcPushOutgoingChanges:(NSString*)viewName reply: (void(^)(NSError* result))reply {
1316      NSError* error = nil;
1317      NSArray* actualViews = [self views:viewName operation:@"push" error:&error];
1318      if(!actualViews || error) {
1319          reply(error);
1320          return;
1321      }
1322  
1323      CKKSResultOperation* blockOp = [[CKKSResultOperation alloc] init];
1324      blockOp.name = @"rpc-push";
1325      __weak __typeof(blockOp) weakBlockOp = blockOp;
1326      // Use the completion block instead of the operation block, so that it runs even if the cancel fires
1327      [blockOp setCompletionBlock:^{
1328          __strong __typeof(blockOp) strongBlockOp = weakBlockOp;
1329          [strongBlockOp allDependentsSuccessful];
1330          reply(CKXPCSuitableError(strongBlockOp.error));
1331      }];
1332  
1333      for(CKKSKeychainView* view in actualViews) {
1334          ckksnotice("ckks-rpc", view, "Beginning push for %@", view);
1335  
1336          CKKSResultOperation* op = [view processOutgoingQueue: [CKOperationGroup CKKSGroupWithName:@"rpc-push"]];
1337          [blockOp addSuccessDependency:op];
1338      }
1339  
1340      [self.operationQueue addOperation: [blockOp timeout:(SecCKKSTestsEnabled() ? NSEC_PER_SEC * 2 : NSEC_PER_SEC * 120)]];
1341  }
1342  
1343  - (void)rpcGetCKDeviceIDWithReply:(void (^)(NSString *))reply {
1344      reply(self.accountTracker.ckdeviceID);
1345  }
1346  
1347  - (void)rpcCKMetric:(NSString *)eventName attributes:(NSDictionary *)attributes reply:(void (^)(NSError *))reply
1348  {
1349      if (eventName == NULL) {
1350          reply([NSError errorWithDomain:CKKSErrorDomain
1351                                    code:CKKSNoMetric
1352                             description:@"No metric name"]);
1353          return;
1354      }
1355      SecEventMetric *metric = [[SecEventMetric alloc] initWithEventName:eventName];
1356      [attributes enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull __unused stop) {
1357          metric[key] = obj;
1358      }];
1359      [[SecMetrics managerObject] submitEvent:metric];
1360      reply(NULL);
1361  }
1362  
1363  -(void)xpc24HrNotification {
1364      // XPC has poked us and said we should do some cleanup!
1365      ckksnotice_global("ckks", "Received a 24hr notification from XPC");
1366  
1367      if (![self waitForTrustReady]) {
1368          ckksnotice_global("ckks", "Trust not ready, still going ahead");
1369      }
1370  
1371      [[CKKSAnalytics logger] dailyCoreAnalyticsMetrics:@"com.apple.security.CKKSHealthSummary"];
1372  
1373      // For now, poke the views and tell them to update their device states if they'd like
1374      NSArray* actualViews = nil;
1375      @synchronized(self.views) {
1376          // Can't safely iterate a mutable collection, so copy it.
1377          actualViews = self.views.allValues;
1378      }
1379  
1380      CKOperationGroup* group = [CKOperationGroup CKKSGroupWithName:@"periodic-device-state-update"];
1381      for(CKKSKeychainView* view in actualViews) {
1382          ckksnotice("ckks", view, "Starting device state XPC update");
1383          // Let the update know it should rate-limit itself
1384          [view updateDeviceState:true waitForKeyHierarchyInitialization:30*NSEC_PER_SEC ckoperationGroup:group];
1385          [view xpc24HrNotification];
1386      }
1387  }
1388  
1389  - (void)haltAll
1390  {
1391      @synchronized(self.views) {
1392          for(CKKSKeychainView* view in self.views.allValues) {
1393              [view halt];
1394          }
1395      }
1396  
1397      [self.zoneModifier halt];
1398  }
1399  
1400  #endif // OCTAGON
1401  @end