]> git.saurik.com Git - apple/security.git/blame - keychain/ot/OTManager.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / ot / OTManager.m
CommitLineData
ecaf5866
A
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
b54c578e 25#include <Security/SecEntitlements.h>
ecaf5866
A
26#import <Foundation/NSXPCConnection.h>
27#import <Foundation/NSXPCConnection_Private.h>
28
ecaf5866 29#if OCTAGON
b54c578e
A
30#import <TargetConditionals.h>
31#import "keychain/ot/ObjCImprovements.h"
ecaf5866
A
32#import "keychain/ot/OTControlProtocol.h"
33#import "keychain/ot/OTControl.h"
b54c578e 34#import "keychain/ot/OTClique.h"
ecaf5866
A
35#import "keychain/ot/OTManager.h"
36#import "keychain/ot/OTDefines.h"
37#import "keychain/ot/OTRamping.h"
ecaf5866
A
38#import "keychain/ot/OT.h"
39#import "keychain/ot/OTConstants.h"
b54c578e
A
40#import "keychain/ot/OTCuttlefishContext.h"
41#import "keychain/ot/OTClientStateMachine.h"
42#import "keychain/ot/OTStates.h"
43#import "keychain/ot/OTJoiningConfiguration.h"
44#import "keychain/ot/OTSOSAdapter.h"
ecaf5866 45
79b9da22 46#import "keychain/categories/NSError+UsefulConstructors.h"
ecaf5866
A
47#import "keychain/ckks/CKKSAnalytics.h"
48#import "keychain/ckks/CKKSNearFutureScheduler.h"
49#import "keychain/ckks/CKKS.h"
50#import "keychain/ckks/CKKSViewManager.h"
51#import "keychain/ckks/CKKSLockStateTracker.h"
b3971512 52#import "keychain/ckks/CKKSCloudKitClassDependencies.h"
ecaf5866
A
53
54#import <CloudKit/CloudKit.h>
55#import <CloudKit/CloudKit_Private.h>
56
57#import <SecurityFoundation/SFKey.h>
58#import <SecurityFoundation/SFKey_Private.h>
b54c578e
A
59#import "SecPasswordGenerate.h"
60
61#import "keychain/categories/NSError+UsefulConstructors.h"
62#include <CloudKit/CloudKit_Private.h>
63#import <KeychainCircle/PairingChannel.h>
64
65#import "keychain/escrowrequest/Framework/SecEscrowRequest.h"
66#import "keychain/escrowrequest/EscrowRequestServer.h"
67
68// If your callbacks might pass back a CK error, you should use XPCSanitizeError()
69// Otherwise, XPC might crash on the other side if they haven't linked CloudKit.framework.
70#define XPCSanitizeError CKXPCSuitableError
ecaf5866 71
ecaf5866
A
72#import <Accounts/Accounts.h>
73#import <Accounts/ACAccountStore_Private.h>
74#import <Accounts/ACAccountType_Private.h>
75#import <Accounts/ACAccountStore.h>
76#import <AppleAccount/ACAccountStore+AppleAccount.h>
77#import <AppleAccount/ACAccount+AppleAccount.h>
ecaf5866 78
b54c578e
A
79#import <CoreCDP/CDPFollowUpController.h>
80
67d61d2e
A
81#import <SoftLinking/SoftLinking.h>
82#import <CloudServices/SecureBackup.h>
83
b54c578e
A
84#import "keychain/TrustedPeersHelper/TPHObjcTranslation.h"
85#import "keychain/SecureObjectSync/SOSAccountTransaction.h"
ecaf5866
A
86#pragma clang diagnostic push
87#pragma clang diagnostic ignored "-Wdeprecated-declarations"
b54c578e 88#import "keychain/SecureObjectSync/SOSAccount.h"
ecaf5866
A
89#pragma clang diagnostic pop
90
b54c578e
A
91#import "utilities/SecTapToRadar.h"
92
67d61d2e
A
93SOFT_LINK_OPTIONAL_FRAMEWORK(PrivateFrameworks, CloudServices);
94SOFT_LINK_CLASS(CloudServices, SecureBackup);
95
ecaf5866
A
96static NSString* const kOTRampForEnrollmentRecordName = @"metadata_rampstate_enroll";
97static NSString* const kOTRampForRestoreRecordName = @"metadata_rampstate_restore";
98static NSString* const kOTRampForCFURecordName = @"metadata_rampstate_cfu";
b54c578e
A
99static NSString* const kOTRampForGhostBustMIDName = @"metadata_rampstate_ghostBustMID";
100static NSString* const kOTRampForghostBustSerialName = @"metadata_rampstate_ghostBustSerial";
101static NSString* const kOTRampForghostBustAgeName = @"metadata_rampstate_ghostBustAge";
ecaf5866
A
102static NSString* const kOTRampZoneName = @"metadata_zone";
103#define NUM_NSECS_IN_24_HRS (86400 * NSEC_PER_SEC)
104
b54c578e
A
105#if OCTAGON
106@interface OTManager (lockstateTracker) <CKKSLockStateNotification>
107@end
108#endif
109
7fb2cbd2 110@interface OTManager () <NSXPCListenerDelegate>
ecaf5866 111@property NSXPCListener *listener;
7fb2cbd2 112
b54c578e
A
113@property (nonatomic, strong) OTRamp *gbmidRamp;
114@property (nonatomic, strong) OTRamp *gbserialRamp;
115@property (nonatomic, strong) OTRamp *gbAgeRamp;
b54c578e
A
116@property (nonatomic, strong) CKKSLockStateTracker *lockStateTracker;
117@property (nonatomic, strong) id<OctagonFollowUpControllerProtocol> cdpd;
118
119// Current contexts
120@property NSMutableDictionary<NSString*, OTCuttlefishContext*>* contexts;
121@property NSMutableDictionary<NSString*, OTClientStateMachine*>* clients;
122@property dispatch_queue_t queue;
123
124@property id<NSXPCProxyCreating> cuttlefishXPCConnection;
125
126// Dependencies for injection
127@property (readonly) id<OTSOSAdapter> sosAdapter;
128@property (readonly) id<OTAuthKitAdapter> authKitAdapter;
129@property (readonly) id<OTDeviceInformationAdapter> deviceInformationAdapter;
130@property (readonly) Class<OctagonAPSConnection> apsConnectionClass;
131@property (readonly) Class<SecEscrowRequestable> escrowRequestClass;
132// If this is nil, all logging is disabled
133@property (readonly, nullable) Class<SFAnalyticsProtocol> loggerClass;
134@property (nonatomic) BOOL sosEnabledForPlatform;
ecaf5866
A
135@end
136
137@implementation OTManager
b54c578e
A
138@synthesize cuttlefishXPCConnection = _cuttlefishXPCConnection;
139@synthesize sosAdapter = _sosAdapter;
140@synthesize authKitAdapter = _authKitAdapter;
141@synthesize deviceInformationAdapter = _deviceInformationAdapter;
ecaf5866 142
7fb2cbd2 143- (instancetype)init
ecaf5866 144{
b3971512 145 // Under Octagon, the sos adapter is not considered essential.
b54c578e
A
146 id<OTSOSAdapter> sosAdapter = (OctagonPlatformSupportsSOS() ?
147 [[OTSOSActualAdapter alloc] initAsEssential:NO] :
148 [[OTSOSMissingAdapter alloc] init]);
b3971512 149
7fb2cbd2
A
150 return [self initWithSOSAdapter:sosAdapter
151 authKitAdapter:[[OTAuthKitActualAdapter alloc] init]
152 deviceInformationAdapter:[[OTDeviceInformationActualAdapter alloc] init]
153 apsConnectionClass:[APSConnection class]
154 escrowRequestClass:[EscrowRequestServer class] // Use the server class here to skip the XPC layer
155 loggerClass:[CKKSAnalytics class]
156 lockStateTracker:[CKKSLockStateTracker globalTracker]
b3971512 157 cloudKitClassDependencies:[CKKSCloudKitClassDependencies forLiveCloudKit]
7fb2cbd2
A
158 cuttlefishXPCConnection:nil
159 cdpd:[[CDPFollowUpController alloc] init]];
ecaf5866
A
160}
161
b3971512
A
162- (instancetype)initWithSOSAdapter:(id<OTSOSAdapter>)sosAdapter
163 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
164 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
165 apsConnectionClass:(Class<OctagonAPSConnection>)apsConnectionClass
166 escrowRequestClass:(Class<SecEscrowRequestable>)escrowRequestClass
167 loggerClass:(Class<SFAnalyticsProtocol>)loggerClass
168 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
169 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
170 cuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
171 cdpd:(id<OctagonFollowUpControllerProtocol>)cdpd
ecaf5866 172{
7fb2cbd2 173 if((self = [super init])) {
b54c578e
A
174 _sosAdapter = sosAdapter;
175 _authKitAdapter = authKitAdapter;
176 _deviceInformationAdapter = deviceInformationAdapter;
177 _loggerClass = loggerClass;
178 _lockStateTracker = lockStateTracker;
b54c578e
A
179 _sosEnabledForPlatform = OctagonPlatformSupportsSOS();
180 _cuttlefishXPCConnection = cuttlefishXPCConnection;
181
b3971512
A
182 _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
183 _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer
184 nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass];
185
b54c578e
A
186 self.contexts = [NSMutableDictionary dictionary];
187 self.clients = [NSMutableDictionary dictionary];
188
189 self.queue = dispatch_queue_create("otmanager", DISPATCH_QUEUE_SERIAL);
190
191 _apsConnectionClass = apsConnectionClass;
192 _escrowRequestClass = escrowRequestClass;
193
194 _cdpd = cdpd;
195
b3971512
A
196 _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer
197 sosAdapter:sosAdapter
198 accountStateTracker:_accountStateTracker
199 lockStateTracker:lockStateTracker
200 cloudKitClassDependencies:cloudKitClassDependencies];
201
b54c578e
A
202 // The default CuttlefishContext always exists:
203 (void) [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
ecaf5866
A
204
205 secnotice("octagon", "otmanager init");
206 }
207 return self;
208}
209
b3971512
A
210- (instancetype)initWithSOSAdapter:(id<OTSOSAdapter>)sosAdapter
211 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
212 cloudKitClassDependencies:(CKKSCloudKitClassDependencies*)cloudKitClassDependencies
213{
214 if((self = [super init])) {
215 _sosAdapter = sosAdapter;
216 _lockStateTracker = lockStateTracker;
217
218 _cloudKitContainer = [CKKSViewManager makeCKContainer:SecCKKSContainerName usePCS:SecCKKSContainerUsePCS];
219 _accountStateTracker = [[CKKSAccountStateTracker alloc] init:_cloudKitContainer
220 nsnotificationCenterClass:cloudKitClassDependencies.nsnotificationCenterClass];
221
222 _viewManager = [[CKKSViewManager alloc] initWithContainer:_cloudKitContainer
223 sosAdapter:sosAdapter
224 accountStateTracker:_accountStateTracker
225 lockStateTracker:lockStateTracker
226 cloudKitClassDependencies:cloudKitClassDependencies];
227 }
228 return self;
229}
230
b54c578e
A
231- (void)initializeOctagon
232{
233 secnotice("octagon", "Initializing Octagon...");
234
235 if(OctagonIsEnabled()) {
236 secnotice("octagon", "starting default state machine...");
237 OTCuttlefishContext* c = [self contextForContainerName:OTCKContainerName
238 contextID:OTDefaultContext];
239
240 [c startOctagonStateMachine];
241 [self registerForCircleChangedNotifications];
242 }
243}
244
7fb2cbd2
A
245- (BOOL)waitForReady:(NSString* _Nullable)containerName context:(NSString*)context wait:(int64_t)wait
246{
247 OTCuttlefishContext* c = [self contextForContainerName:containerName contextID:context];
248 return [c waitForReady:wait];
249}
250
b54c578e
A
251- (void) moveToCheckTrustedStateForContainer:(NSString* _Nullable)containerName context:(NSString*)context
252{
253 OTCuttlefishContext* c = [self contextForContainerName:containerName
254 contextID:context];
255 [c startOctagonStateMachine];
256 [c moveToCheckTrustedState];
257}
258
259- (void)registerForCircleChangedNotifications
260{
261 __weak __typeof(self) weakSelf = self;
262
263 // If we're not in the tests, go ahead and register for a notification
264 if(!SecCKKSTestsEnabled()) {
265 int token = 0;
266 notify_register_dispatch(kSOSCCCircleChangedNotification, &token, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(int t) {
267 secnotice("octagon", "circle changed notification called, checking trust state");
268 [weakSelf moveToCheckTrustedStateForContainer:OTCKContainerName context:OTDefaultContext];
269 });
270 }
271}
272
ecaf5866 273+ (instancetype _Nullable)manager {
b54c578e 274 if(!OctagonIsEnabled()) {
ecaf5866
A
275 secerror("octagon: Attempt to fetch a manager while Octagon is disabled");
276 return nil;
277 }
ecaf5866 278
d64be36e
A
279 if([CKDatabase class] == nil) {
280 // CloudKit is not linked. We cannot bring Octagon up.
281 secerror("Octagon: CloudKit.framework appears to not be linked. Cannot create an Octagon manager (on pain of crash).");
282 return nil;
283 }
284
b54c578e 285 return [self resetManager:false to:nil];
ecaf5866
A
286}
287
b54c578e
A
288+ (instancetype _Nullable)resetManager:(bool)reset to:(OTManager* _Nullable)obj
289{
290 static OTManager* manager = nil;
291
292 if(!manager || reset || obj) {
293 @synchronized([self class]) {
294 if(obj != nil) {
295 manager = obj;
296 } else {
297 if(reset) {
298 manager = nil;
299 } else if (manager == nil && OctagonIsEnabled()) {
300 manager = [[OTManager alloc] init];
301 }
302 }
303 }
304 }
305
306 return manager;
307}
ecaf5866 308
7fb2cbd2 309- (void)ensureRampsInitialized
ecaf5866 310{
ecaf5866
A
311 CKContainer* container = [CKKSViewManager manager].container;
312 CKDatabase* database = [container privateCloudDatabase];
313 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName:kOTRampZoneName ownerName:CKCurrentUserDefaultName];
314
b54c578e 315 CKKSAccountStateTracker *accountTracker = [CKKSViewManager manager].accountTracker;
ecaf5866
A
316 CKKSReachabilityTracker *reachabilityTracker = [CKKSViewManager manager].reachabilityTracker;
317 CKKSLockStateTracker *lockStateTracker = [CKKSViewManager manager].lockStateTracker;
318
7fb2cbd2
A
319 if(!self.gbmidRamp) {
320 self.gbmidRamp = [[OTRamp alloc]initWithRecordName:kOTRampForGhostBustMIDName
321 localSettingName:@"ghostBustMID"
322 container:container
323 database:database
324 zoneID:zoneID
325 accountTracker:accountTracker
326 lockStateTracker:lockStateTracker
327 reachabilityTracker:reachabilityTracker
328 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
329 }
330
331 if(!self.gbserialRamp) {
332 self.gbserialRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustSerialName
333 localSettingName:@"ghostBustSerial"
334 container:container
335 database:database
336 zoneID:zoneID
337 accountTracker:accountTracker
338 lockStateTracker:lockStateTracker
339 reachabilityTracker:reachabilityTracker
340 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
341 }
342
343 if(!self.gbAgeRamp) {
344 self.gbAgeRamp = [[OTRamp alloc]initWithRecordName:kOTRampForghostBustAgeName
345 localSettingName:@"ghostBustAge"
346 container:container
347 database:database
348 zoneID:zoneID
349 accountTracker:accountTracker
350 lockStateTracker:lockStateTracker
351 reachabilityTracker:reachabilityTracker
352 fetchRecordRecordsOperationClass:[CKFetchRecordsOperation class]];
ecaf5866 353 }
ecaf5866
A
354}
355
b54c578e
A
356////
357// MARK: SPI routines
358////
ecaf5866 359
b54c578e
A
360
361- (void)signIn:(NSString*)altDSID
362 container:(NSString* _Nullable)containerName
363 context:(NSString*)contextID
364 reply:(void (^)(NSError * _Nullable signedInError))reply
ecaf5866 365{
b54c578e 366 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountAvailable];
ecaf5866 367
b54c578e
A
368 if(containerName == nil) {
369 containerName = OTCKContainerName;
ecaf5866
A
370 }
371
b54c578e
A
372 NSError *error = nil;
373 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
374
375 secnotice("octagon","signing in %@ for altDSID: %@", context, altDSID);
376 [context accountAvailable:altDSID error:&error];
377
378 [tracker stopWithEvent:OctagonEventSignIn result:error];
379
380 reply(error);
ecaf5866
A
381}
382
b54c578e
A
383- (void)signOut:(NSString* _Nullable)containerName
384 context:(NSString*)contextID
385 reply:(void (^)(NSError * _Nullable signedOutError))reply
ecaf5866 386{
b54c578e
A
387 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityAccountNotAvailable];
388
389 if(containerName == nil) {
390 containerName = OTCKContainerName;
ecaf5866 391 }
b54c578e
A
392
393 NSError* error = nil;
394
395 // TODO: should we compare DSIDs here?
396 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
397
398 secnotice("octagon", "signing out of octagon trust: %@", context);
399
400 [context accountNoLongerAvailable:&error];
401 if(error) {
402 secnotice("octagon", "signing out failed: %@", error);
ecaf5866 403 }
b54c578e
A
404
405 [tracker stopWithEvent:OctagonEventSignOut result:error];
406
407 reply(error);
408}
409
410- (void)notifyIDMSTrustLevelChangeForContainer:(NSString* _Nullable)containerName
411 context:(NSString*)contextID
412 reply:(void (^)(NSError * _Nullable error))reply
413{
414 if(containerName == nil) {
415 containerName = OTCKContainerName;
ecaf5866 416 }
b54c578e
A
417
418 NSError *error = nil;
419 OTCuttlefishContext* context = [self contextForContainerName:containerName contextID:contextID];
420 secnotice("octagon","received a notification of IDMS trust level change in %@", context);
421 [context idmsTrustLevelChanged:&error];
422
423 reply(error);
ecaf5866 424}
b54c578e
A
425
426- (void)handleIdentityChangeForSigningKey:(SFECKeyPair*)peerSigningKey
427 ForEncryptionKey:(SFECKeyPair*)encryptionKey
428 ForPeerID:(NSString*)peerID
429 reply:(void (^)(BOOL result,
430 NSError* _Nullable error))reply
ecaf5866 431{
b54c578e 432 secnotice("octagon", "handleIdentityChangeForSigningKey: %@", peerID);
7fb2cbd2
A
433 reply(NO,
434 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
435 code:errSecUnimplemented
436 userInfo:nil]);
b54c578e
A
437}
438
439- (void)preflightBottledPeer:(NSString*)contextID
440 dsid:(NSString*)dsid
441 reply:(void (^)(NSData* _Nullable entropy,
442 NSString* _Nullable bottleID,
443 NSData* _Nullable signingPublicKey,
444 NSError* _Nullable error))reply
445{
446 secnotice("octagon", "preflightBottledPeer: %@ %@", contextID, dsid);
7fb2cbd2
A
447 reply(nil,
448 nil,
449 nil,
450 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
451 code:errSecUnimplemented
452 userInfo:nil]);
ecaf5866
A
453}
454
455- (void)launchBottledPeer:(NSString*)contextID
456 bottleID:(NSString*)bottleID
457 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
458{
459 secnotice("octagon", "launchBottledPeer");
7fb2cbd2
A
460 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
461 code:errSecUnimplemented
462 userInfo:nil]);
ecaf5866
A
463}
464
465- (void)restore:(NSString *)contextID dsid:(NSString *)dsid secret:(NSData*)secret escrowRecordID:(NSString*)escrowRecordID reply:(void (^)(NSData* signingKeyData, NSData* encryptionKeyData, NSError *))reply
466{
7fb2cbd2
A
467 secnotice("octagon", "restore");
468 reply(nil,
469 nil,
470 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
471 code:errSecUnimplemented
472 userInfo:nil]);
ecaf5866
A
473}
474
475- (void)scrubBottledPeer:(NSString*)contextID
476 bottleID:(NSString*)bottleID
477 reply:(void (^ _Nullable)(NSError* _Nullable error))reply
478{
7fb2cbd2
A
479 reply([NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
480 code:errSecUnimplemented
481 userInfo:nil]);
ecaf5866
A
482}
483
b54c578e
A
484////
485// MARK: OTCTL tool routines
486////
ecaf5866 487
b54c578e 488-(void)reset:(void (^)(BOOL result, NSError *))reply
ecaf5866 489{
7fb2cbd2
A
490 reply(NO,
491 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
492 code:errSecUnimplemented
493 userInfo:nil]);
ecaf5866
A
494}
495
b54c578e 496- (void)listOfEligibleBottledPeerRecords:(void (^)(NSArray* listOfRecords, NSError * _Nullable))reply
ecaf5866 497{
7fb2cbd2
A
498 reply(@[],
499 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
500 code:errSecUnimplemented
501 userInfo:nil]);
ecaf5866
A
502}
503
504- (void)octagonEncryptionPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
505{
7fb2cbd2
A
506 reply(nil,
507 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
508 code:errSecUnimplemented
509 userInfo:nil]);
ecaf5866
A
510}
511
512-(void)octagonSigningPublicKey:(void (^)(NSData* encryptionKey, NSError *))reply
513{
7fb2cbd2
A
514 reply(nil,
515 [NSError errorWithDomain:(__bridge NSString*)kSecErrorDomain
516 code:errSecUnimplemented
517 userInfo:nil]);
ecaf5866
A
518}
519
b54c578e
A
520- (void)setCuttlefishXPCConnection:(id<NSXPCProxyCreating>)cuttlefishXPCConnection
521{
522 _cuttlefishXPCConnection = cuttlefishXPCConnection;
523}
524
525- (id<NSXPCProxyCreating>)cuttlefishXPCConnection
526{
527 if(!_cuttlefishXPCConnection) {
528 NSXPCConnection* xpcConnection = [[NSXPCConnection alloc] initWithServiceName:@"com.apple.TrustedPeersHelper"];
529 xpcConnection.remoteObjectInterface = TrustedPeersHelperSetupProtocol([NSXPCInterface interfaceWithProtocol:@protocol(TrustedPeersHelperProtocol)]);
530 [xpcConnection resume];
531 _cuttlefishXPCConnection = xpcConnection;
532 }
533
534 return _cuttlefishXPCConnection;
535}
536
537- (OTClientStateMachine*)clientStateMachineForContainerName:(NSString* _Nullable)containerName
538 contextID:(NSString*)contextID
539 clientName:(NSString*)clientName
540{
541 __block OTClientStateMachine* client = nil;
542
543 if(containerName == nil) {
544 containerName = SecCKKSContainerName;
545 }
546
547 dispatch_sync(self.queue, ^{
548 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
549 secnotice("octagon-client", "fetching context for key: %@", key);
550 client = self.clients[key];
551 if(!client) {
552 client = [[OTClientStateMachine alloc] initWithContainerName:containerName
553 contextID:contextID
554 clientName:clientName
555 cuttlefish:self.cuttlefishXPCConnection];
556
557 self.clients[key] = client;
558 }
559 });
560
561 return client;
562}
563
564- (void)removeClientContextForContainerName:(NSString* _Nullable)containerName
565 clientName:(NSString*)clientName
566{
567 if(containerName == nil) {
568 containerName = SecCKKSContainerName;
569 }
570
571 dispatch_sync(self.queue, ^{
572 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, clientName];
573 [self.clients removeObjectForKey:key];
574 secnotice("octagon", "removed client context with key: %@", key);
575 });
576}
577
578- (void)removeContextForContainerName:(NSString*)containerName
579 contextID:(NSString*)contextID
580{
581 dispatch_sync(self.queue, ^{
582 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
583 self.contexts[key] = nil;
584 });
585}
586
587- (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
588 contextID:(NSString*)contextID
589{
590 return [self contextForContainerName:containerName
591 contextID:contextID
592 sosAdapter:self.sosAdapter
593 authKitAdapter:self.authKitAdapter
594 lockStateTracker:self.lockStateTracker
595 accountStateTracker:self.accountStateTracker
596 deviceInformationAdapter:self.deviceInformationAdapter];
597}
598
599- (OTCuttlefishContext*)contextForContainerName:(NSString* _Nullable)containerName
600 contextID:(NSString*)contextID
601 sosAdapter:(id<OTSOSAdapter>)sosAdapter
602 authKitAdapter:(id<OTAuthKitAdapter>)authKitAdapter
603 lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
604 accountStateTracker:(CKKSAccountStateTracker*)accountStateTracker
605 deviceInformationAdapter:(id<OTDeviceInformationAdapter>)deviceInformationAdapter
606{
607 __block OTCuttlefishContext* context = nil;
608
609 if(containerName == nil) {
610 containerName = SecCKKSContainerName;
611 }
612
613 dispatch_sync(self.queue, ^{
614 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
615
616 context = self.contexts[key];
617
618 // Right now, CKKS can only handle one session per address space (and SQL database).
619 // Therefore, only the primary OTCuttlefishContext gets to own the view manager.
620 CKKSViewManager* viewManager = nil;
621 if([containerName isEqualToString:SecCKKSContainerName] &&
622 [contextID isEqualToString:OTDefaultContext]) {
b3971512 623 viewManager = self.viewManager;
b54c578e
A
624 }
625
626 if(!context) {
627 context = [[OTCuttlefishContext alloc] initWithContainerName:containerName
628 contextID:contextID
629 cuttlefish:self.cuttlefishXPCConnection
630 sosAdapter:sosAdapter
631 authKitAdapter:authKitAdapter
632 ckksViewManager:viewManager
633 lockStateTracker:lockStateTracker
634 accountStateTracker:accountStateTracker
635 deviceInformationAdapter:deviceInformationAdapter
636 apsConnectionClass:self.apsConnectionClass
637 escrowRequestClass:self.escrowRequestClass
638 cdpd:self.cdpd];
639 self.contexts[key] = context;
640 }
641 });
642
643 return context;
644}
645
b3971512
A
646- (void)clearAllContexts
647{
648 if(self.contexts) {
649 dispatch_sync(self.queue, ^{
650 [self.contexts removeAllObjects];
651 });
652 }
653}
654
b54c578e
A
655- (void)fetchEgoPeerID:(NSString* _Nullable)container
656 context:(NSString*)context
657 reply:(void (^)(NSString* _Nullable peerID, NSError* _Nullable error))reply
658{
659 if(!container) {
660 container = OTCKContainerName;
661 }
662 secnotice("octagon", "Received a fetch peer ID for container (%@) and context (%@)", container, context);
663 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
664 [cfshContext rpcFetchEgoPeerID:^(NSString * _Nullable peerID,
665 NSError * _Nullable error) {
666 reply(peerID, XPCSanitizeError(error));
667 }];
668}
669
670- (void)fetchTrustStatus:(NSString *)container
671 context:(NSString *)context
672 configuration:(OTOperationConfiguration *)configuration
673 reply:(void (^)(CliqueStatus status,
674 NSString* _Nullable peerID,
675 NSNumber * _Nullable numberOfPeersInOctagon,
676 BOOL isExcluded,
677 NSError* _Nullable error))reply
678{
679 if(!container) {
680 container = OTCKContainerName;
681 }
682 secnotice("octagon", "Received a trust status for container (%@) and context (%@)", container, context);
683 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
684
685 [cfshContext rpcTrustStatus:configuration reply:^(CliqueStatus status,
686 NSString * _Nullable peerID,
687 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
688 BOOL isExcluded,
d64be36e 689 BOOL isLocked,
b54c578e
A
690 NSError * _Nullable error) {
691 // Our clients don't need the whole breakout of peers, so just count for them
692 long peerCount = 0;
693 for(NSNumber* n in peerCountByModelID.allValues) {
694 peerCount += [n longValue];
695 }
696
697 reply(status, peerID, @(peerCount), isExcluded, error);
698 }];
699}
700
701- (void)fetchCliqueStatus:(NSString* _Nullable)container
702 context:(NSString*)contextID
703 configuration:(OTOperationConfiguration *)configuration
704 reply:(void (^)(CliqueStatus cliqueStatus, NSError* _Nullable error))reply
705{
706 if(!container) {
707 container = OTCKContainerName;
708 }
709 if(configuration == nil) {
710 configuration = [[OTOperationConfiguration alloc] init];
711 }
712
713 __block OTCuttlefishContext* context = nil;
714 dispatch_sync(self.queue, ^{
715 NSString* key = [NSString stringWithFormat:@"%@-%@", container, contextID];
716
717 context = self.contexts[key];
718 });
719
720 if(!context) {
721 reply(-1, [NSError errorWithDomain:OctagonErrorDomain
722 code:OTErrorNoSuchContext
723 description:[NSString stringWithFormat:@"No context for (%@,%@)", container, contextID]]);
724 return;
725 }
726
727
728 [context rpcTrustStatus:configuration reply:^(CliqueStatus status,
729 NSString* egoPeerID,
730 NSDictionary<NSString *,NSNumber *> * _Nullable peerCountByModelID,
731 BOOL isExcluded,
d64be36e 732 BOOL isLocked,
b54c578e
A
733 NSError * _Nullable error) {
734 reply(status, error);
735 }];
736}
737
738- (void)status:(NSString* _Nullable)containerName
739 context:(NSString*)contextID
740 reply:(void (^)(NSDictionary* _Nullable result, NSError* _Nullable error))reply
741{
742 if(!containerName) {
743 containerName = OTCKContainerName;
744 }
745
746 secnotice("octagon", "Received a status RPC for container (%@) and context (%@)", containerName, contextID);
747
748 __block OTCuttlefishContext* context = nil;
749 dispatch_sync(self.queue, ^{
750 NSString* key = [NSString stringWithFormat:@"%@-%@", containerName, contextID];
751
752 context = self.contexts[key];
753 });
754
755 if(!context) {
756 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
757 code:OTErrorNoSuchContext
758 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
759 return;
760 }
761
762 [context rpcStatus:reply];
763}
764
765- (void)startOctagonStateMachine:(NSString* _Nullable)container
766 context:(NSString*)context
767 reply:(void (^)(NSError* _Nullable error))reply
768{
769 secnotice("octagon", "Received a start-state-machine RPC for container (%@) and context (%@)", container, context);
770
771 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
772 [cfshContext startOctagonStateMachine];
773 reply(nil);
774}
775
776- (void)resetAndEstablish:(NSString *)container
777 context:(NSString *)context
778 altDSID:(NSString*)altDSID
7fb2cbd2 779 resetReason:(CuttlefishResetReason)resetReason
b54c578e
A
780 reply:(void (^)(NSError * _Nullable))reply
781{
782 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityResetAndEstablish];
783
784 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
785 [cfshContext startOctagonStateMachine];
7fb2cbd2 786 [cfshContext rpcResetAndEstablish:resetReason reply:^(NSError* resetAndEstablishError) {
b54c578e
A
787 [tracker stopWithEvent:OctagonEventResetAndEstablish result:resetAndEstablishError];
788 reply(resetAndEstablishError);
789 }];
790}
791
792- (void)establish:(NSString *)container
793 context:(NSString *)context
794 altDSID:(NSString*)altDSID
795 reply:(void (^)(NSError * _Nullable))reply
796{
797 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityEstablish];
798
799 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
800 [cfshContext startOctagonStateMachine];
801 [cfshContext rpcEstablish:altDSID reply:^(NSError* establishError) {
802 [tracker stopWithEvent:OctagonEventEstablish result:establishError];
803 reply(establishError);
804 }];
805}
806
807- (void)leaveClique:(NSString* _Nullable)container
808 context:(NSString*)context
809 reply:(void (^)(NSError* _Nullable error))reply
810{
811 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityLeaveClique];
812
813 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
814 [cfshContext startOctagonStateMachine];
815 [cfshContext rpcLeaveClique:^(NSError* leaveError) {
816 [tracker stopWithEvent:OctagonEventLeaveClique result:leaveError];
817 reply(leaveError);
818 }];
819}
820
821- (void)removeFriendsInClique:(NSString* _Nullable)container
822 context:(NSString*)context
823 peerIDs:(NSArray<NSString*>*)peerIDs
824 reply:(void (^)(NSError* _Nullable error))reply
825{
826 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityRemoveFriendsInClique];
827
828 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
829 [cfshContext startOctagonStateMachine];
830 [cfshContext rpcRemoveFriendsInClique:peerIDs reply:^(NSError* removeFriendsError) {
831 [tracker stopWithEvent:OctagonEventRemoveFriendsInClique result:removeFriendsError];
832 reply(removeFriendsError);
833 }];
834}
835
836- (void)peerDeviceNamesByPeerID:(NSString* _Nullable)container
837 context:(NSString*)context
838 reply:(void (^)(NSDictionary<NSString*, NSString*>* _Nullable peers, NSError* _Nullable error))reply
839{
840 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
841 [cfshContext rpcFetchDeviceNamesByPeerID:reply];
842}
843
844- (void)fetchAllViableBottles:(NSString* _Nullable)container
845 context:(NSString*)context
846 reply:(void (^)(NSArray<NSString*>* _Nullable sortedBottleIDs, NSArray<NSString*>* _Nullable sortedPartialBottleIDs, NSError* _Nullable error))reply
847{
848 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchAllViableBottles];
849
850 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
851 [cfshContext rpcFetchAllViableBottles:^(NSArray<NSString *> * _Nullable sortedBottleIDs,
852 NSArray<NSString *> * _Nullable sortedPartialEscrowRecordIDs,
853 NSError * _Nullable error) {
854 [tracker stopWithEvent:OctagonEventFetchAllBottles result:error];
855 reply(sortedBottleIDs, sortedPartialEscrowRecordIDs, error);
856 }];
857}
858
859- (void)fetchEscrowContents:(NSString* _Nullable)containerName
860 contextID:(NSString *)contextID
861 reply:(void (^)(NSData* _Nullable entropy,
862 NSString* _Nullable bottleID,
863 NSData* _Nullable signingPublicKey,
864 NSError* _Nullable error))reply
865{
866 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityFetchEscrowContents];
867
868 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
869 [cfshContext fetchEscrowContents:^(NSData *entropy,
870 NSString *bottleID,
871 NSData *signingPublicKey,
872 NSError *error) {
873 [tracker stopWithEvent:OctagonEventFetchEscrowContents result:error];
874 reply(entropy, bottleID, signingPublicKey, error);
875 }];
876}
877
878////
879// MARK: Pairing Routines as Initiator
880////
881- (void)rpcPrepareIdentityAsApplicantWithConfiguration:(OTJoiningConfiguration*)config
882 reply:(void (^)(NSString * _Nullable peerID,
883 NSData * _Nullable permanentInfo,
884 NSData * _Nullable permanentInfoSig,
885 NSData * _Nullable stableInfo,
886 NSData * _Nullable stableInfoSig,
887 NSError * _Nullable error))reply
888{
889 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
890 [cfshContext handlePairingRestart:config];
891 [cfshContext startOctagonStateMachine];
b3971512
A
892 [cfshContext rpcPrepareIdentityAsApplicantWithConfiguration:config
893 epoch:config.epoch
894 reply:^(NSString * _Nullable peerID,
895 NSData * _Nullable permanentInfo,
896 NSData * _Nullable permanentInfoSig,
897 NSData * _Nullable stableInfo,
898 NSData * _Nullable stableInfoSig,
899 NSError * _Nullable error) {
b54c578e
A
900 reply(peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error);
901 }];
902}
903
904- (void)rpcJoinWithConfiguration:(OTJoiningConfiguration*)config
905 vouchData:(NSData*)vouchData
906 vouchSig:(NSData*)vouchSig
b54c578e
A
907 reply:(void (^)(NSError * _Nullable error))reply
908{
909 OTCuttlefishContext* cfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
910 [cfshContext handlePairingRestart:config];
911 [cfshContext startOctagonStateMachine];
b3971512 912 [cfshContext rpcJoin:vouchData vouchSig:vouchSig reply:^(NSError * _Nullable error) {
b54c578e
A
913 reply(error);
914 }];
915}
916
917////
918// MARK: Pairing Routines as Acceptor
919////
920
921- (void)rpcEpochWithConfiguration:(OTJoiningConfiguration*)config
922 reply:(void (^)(uint64_t epoch,
923 NSError * _Nullable error))reply
924{
925 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
926 [acceptorCfshContext startOctagonStateMachine];
927
928 // Let's assume that the new device's machine ID has made it to the IDMS list by now, and let's refresh our idea of that list
929 [acceptorCfshContext requestTrustedDeviceListRefresh];
930
931 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
932
933 [clientStateMachine startOctagonStateMachine];
934
935 [clientStateMachine rpcEpoch:acceptorCfshContext reply:^(uint64_t epoch, NSError * _Nullable error) {
936 reply(epoch, error);
937 }];
938}
939
940- (void)rpcVoucherWithConfiguration:(OTJoiningConfiguration*)config
941 peerID:(NSString*)peerID
942 permanentInfo:(NSData *)permanentInfo
943 permanentInfoSig:(NSData *)permanentInfoSig
944 stableInfo:(NSData *)stableInfo
945 stableInfoSig:(NSData *)stableInfoSig
946 reply:(void (^)(NSData* voucher, NSData* voucherSig, NSError * _Nullable error))reply
947{
948 OTCuttlefishContext* acceptorCfshContext = [self contextForContainerName:config.containerName contextID:config.contextID];
949 [acceptorCfshContext startOctagonStateMachine];
950
951 // Let's assume that the new device's machine ID has made it to the IDMS list by now, and let's refresh our idea of that list
952 [acceptorCfshContext requestTrustedDeviceListRefresh];
953 OTClientStateMachine *clientStateMachine = [self clientStateMachineForContainerName:config.containerName contextID:config.contextID clientName:config.pairingUUID];
954
955 [clientStateMachine rpcVoucher:acceptorCfshContext peerID:peerID permanentInfo:permanentInfo permanentInfoSig:permanentInfoSig stableInfo:stableInfo stableInfoSig:stableInfoSig reply:^(NSData *voucher, NSData *voucherSig, NSError *error) {
956 reply(voucher, voucherSig, error);
957 }];
958}
959
960- (void)restore:(NSString * _Nullable)containerName
961 contextID:(nonnull NSString *)contextID
962 bottleSalt:(nonnull NSString *)bottleSalt
963 entropy:(nonnull NSData *)entropy
964 bottleID:(nonnull NSString *)bottleID
965 reply:(nonnull void (^)(NSError * _Nullable))reply {
966 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityBottledPeerRestore];
967
968 secnotice("octagon", "restore via bottle invoked for container: %@, context: %@", containerName, contextID);
969
970 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
971
972 [cfshContext startOctagonStateMachine];
973
974 [cfshContext joinWithBottle:bottleID entropy:entropy bottleSalt:bottleSalt reply:^(NSError *error) {
975 [tracker stopWithEvent:OctagonEventBottledPeerRestore result:error];
976 reply(error);
977 }];
978}
979
980////
981// MARK: Ghost busting using ramp records
982////
983
984-(BOOL) ghostbustByMidEnabled {
7fb2cbd2 985 [self ensureRampsInitialized];
b54c578e
A
986 return [self.gbmidRamp checkRampStateWithError:nil];
987}
988
989-(BOOL) ghostbustBySerialEnabled {
7fb2cbd2 990 [self ensureRampsInitialized];
b54c578e
A
991 return [self.gbserialRamp checkRampStateWithError:nil];
992}
993
994-(BOOL) ghostbustByAgeEnabled {
7fb2cbd2 995 [self ensureRampsInitialized];
b54c578e
A
996 return [self.gbAgeRamp checkRampStateWithError:nil];
997}
998
999////
1000// MARK: Analytics
1001////
1002
1003- (void)setupAnalytics
1004{
1005 WEAKIFY(self);
1006
1007 [[self.loggerClass logger] AddMultiSamplerForName:@"Octagon-healthSummary"
1008 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1009 block:^NSDictionary<NSString *,NSNumber *> *{
1010 STRONGIFY(self);
1011
1012 // We actually only care about the default context for the default container
1013 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1014
1015 secnotice("octagon-analytics", "Reporting analytics for container: %@, context: %@", OTCKContainerName, OTDefaultContext);
1016
1017 NSMutableDictionary* values = [NSMutableDictionary dictionary];
1018
1019 NSError* error = nil;
1020 SOSCCStatus sosStatus = [self.sosAdapter circleStatus:&error];
1021 if(error) {
1022 secnotice("octagon-analytics", "Error fetching SOS status: %@", error);
1023 }
1024 values[OctagonAnalyticsSOSStatus] = @((int)sosStatus);
1025 NSDate* dateOfLastPPJ = [[CKKSAnalytics logger] datePropertyForKey:OctagonEventUpgradePreflightPreapprovedJoin];
1026 values[OctagonAnalyticsDateOfLastPreflightPreapprovedJoin] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastPPJ]);
1027
1028 values[OctagonAnalyticsStateMachineState] = OctagonStateMap()[cuttlefishContext.stateMachine.currentState];
1029
805875f8
A
1030 NSError* metadataError = nil;
1031 OTAccountMetadataClassC* metadata = [cuttlefishContext.accountMetadataStore loadOrCreateAccountMetadata:&metadataError];
1032 if(!metadata || metadataError) {
1033 secnotice("octagon-analytics", "Error fetching Octagon metadata: %@", metadataError);
1034 }
1035 values[OctagonAnalyticIcloudAccountState] = metadata ? @(metadata.icloudAccountState) : nil;
b3971512 1036 values[OctagonAnalyticCDPBitStatus] = metadata? @(metadata.cdpState) : nil;
805875f8
A
1037 values[OctagonAnalyticsTrustState] = metadata ? @(metadata.trustState) : nil;
1038
d64be36e
A
1039 TPSyncingPolicy* syncingPolicy = [metadata getTPSyncingPolicy];
1040 values[OctagonAnalyticsUserControllableViewsSyncing] = syncingPolicy ? @(syncingPolicy.syncUserControllableViewsAsBoolean) : nil;
1041
b54c578e
A
1042 NSDate* healthCheck = [cuttlefishContext currentMemoizedLastHealthCheck];
1043 values[OctagonAnalyticsLastHealthCheck] = @([CKKSAnalytics fuzzyDaysSinceDate:healthCheck]);
1044
1045 NSDate* dateOfLastKSR = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastKeystateReady];
1046 values[OctagonAnalyticsLastKeystateReady] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastKSR]);
1047
805875f8
A
1048 if(metadata && metadata.icloudAccountState == OTAccountMetadataClassC_AccountState_ACCOUNT_AVAILABLE) {
1049 values[OctagonAnalyticsAttemptedJoin] = @(metadata.attemptedJoin);
1050
1051 NSError* machineIDError = nil;
1052 NSString* machineID = [cuttlefishContext.authKitAdapter machineID:&machineIDError];
1053 if(machineIDError) {
1054 secnotice("octagon-analytics", "Error fetching machine ID: %@", metadataError);
1055 }
1056
1057 values[OctagonAnalyticsHaveMachineID] = @(machineID != nil);
1058
1059 if(machineID) {
1060 NSError* midOnListError = nil;
1061 BOOL midOnList = [cuttlefishContext machineIDOnMemoizedList:machineID error:&midOnListError];
1062
1063 if(midOnListError) {
1064 secnotice("octagon-analytics", "Error fetching 'mid on list': %@", midOnListError);
1065 } else {
1066 values[OctagonAnalyticsMIDOnMemoizedList] = @(midOnList);
1067 }
1068
1069 NSError* peersWithMIDError = nil;
1070 NSNumber* peersWithMID = [cuttlefishContext numberOfPeersInModelWithMachineID:machineID error:&peersWithMIDError];
1071 if(peersWithMID && peersWithMIDError == nil) {
1072 values[OctagonAnalyticsPeersWithMID] = peersWithMID;
1073 } else {
1074 secnotice("octagon-analytics", "Error fetching how many peers have our MID: %@", midOnListError);
1075 }
1076 }
1077 }
1078
b54c578e
A
1079 // Track CFU usage and success/failure metrics
1080 // 1. Users in a good state will have no outstanding CFU, and will not have done a CFU
1081 // 2. Users in a bad state who have not repsonded to the CFU (repaired) will have a pending CFU.
1082 // 3. Users in a bad state who have acted on the CFU will have no pending CFU, but will have CFU failures.
1083 //
1084 // We also record the time window between the last followup completion and invocation.
b54c578e
A
1085 NSDate* dateOfLastFollowup = [[CKKSAnalytics logger] datePropertyForKey: OctagonAnalyticsLastCoreFollowup];
1086 values[OctagonAnalyticsLastCoreFollowup] = @([CKKSAnalytics fuzzyDaysSinceDate:dateOfLastFollowup]);
7fb2cbd2
A
1087 // We used to report this, but it was never set
1088 //values[OctagonAnalyticsCoreFollowupStatus] = @(followupHandler.followupStatus);
b54c578e
A
1089
1090 for (NSString *type in [self cdpContextTypes]) {
1091 NSString *metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1092 NSString *countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1093
1094 NSDate *lastFailure = [[CKKSAnalytics logger] datePropertyForKey:metricName];
1095 if (lastFailure) {
1096 values[metricName] = @([CKKSAnalytics fuzzyDaysSinceDate:lastFailure]);
1097 values[countName] = [[CKKSAnalytics logger] numberPropertyForKey:countName];
1098 }
1099 }
1100
1101 // SecEscrowRequest
1102 id<SecEscrowRequestable> request = [self.escrowRequestClass request:&error];
1103 if (request) {
1104 values[OctagonAnalyticsPrerecordPending] = @([request pendingEscrowUpload:&error]);
1105 if (error) {
1106 secnotice("octagon-analytics", "Error fetching pendingEscrowUpload status: %@", error);
1107 }
1108 } else {
1109 secnotice("octagon-analytics", "Error fetching escrowRequestClass: %@", error);
1110 }
1111
1112 {
1113 ACAccountStore *store = [[ACAccountStore alloc] init];
1114 ACAccount* primaryAccount = [store aa_primaryAppleAccount];
1115 if(primaryAccount) {
1116 values[OctagonAnalyticsKVSProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeyValue]);
1117 values[OctagonAnalyticsKVSEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeyValue]);
1118 values[OctagonAnalyticsKeychainSyncProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassKeychainSync]);
1119 values[OctagonAnalyticsKeychainSyncEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassKeychainSync]);
1120 values[OctagonAnalyticsCloudKitProvisioned] = @([primaryAccount isProvisionedForDataclass:ACAccountDataclassCKDatabaseService]);
1121 values[OctagonAnalyticsCloudKitEnabled] = @([primaryAccount isEnabledForDataclass:ACAccountDataclassCKDatabaseService]);
67d61d2e
A
1122
1123 NSString *altDSID = primaryAccount.aa_altDSID;
1124 if(altDSID) {
1125 values[OctagonAnalyticsSecureBackupTermsAccepted] = @([getSecureBackupClass() getAcceptedTermsForAltDSID:altDSID withError:nil] != nil);
1126 }
b54c578e
A
1127 }
1128 }
1129
1130 return values;
1131 }];
1132
1133 [[self.loggerClass logger] AddMultiSamplerForName:@"CFU-healthSummary"
1134 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
1135 block:^NSDictionary<NSString *,NSNumber *> *{
1136 OTCuttlefishContext* cuttlefishContext = [self contextForContainerName:OTCKContainerName contextID:OTDefaultContext];
1137 return [cuttlefishContext.followupHandler sfaStatus];
1138 }];
1139}
1140
1141- (NSArray<OTCliqueCDPContextType> *)cdpContextTypes
1142{
1143 static NSArray<OTCliqueCDPContextType> *contextTypes = nil;
1144 static dispatch_once_t onceToken;
1145 dispatch_once(&onceToken, ^{
1146 contextTypes = @[OTCliqueCDPContextTypeNone,
1147 OTCliqueCDPContextTypeSignIn,
1148 OTCliqueCDPContextTypeRepair,
1149 OTCliqueCDPContextTypeFinishPasscodeChange,
1150 OTCliqueCDPContextTypeRecoveryKeyGenerate,
1151 OTCliqueCDPContextTypeRecoveryKeyNew,
1152 OTCliqueCDPContextTypeUpdatePasscode,
1153 ];
1154 });
1155 return contextTypes;
1156}
1157
1158////
1159// MARK: Recovery Key
1160////
1161
1162- (void) createRecoveryKey:(NSString* _Nullable)containerName
1163 contextID:(NSString *)contextID
1164 recoveryKey:(NSString *)recoveryKey
1165 reply:(void (^)( NSError *error))reply
1166{
1167 secnotice("octagon", "Setting recovery key for container: %@, context: %@", containerName, contextID);
1168
1169 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivitySetRecoveryKey];
1170
1171 if (!self.sosEnabledForPlatform) {
d64be36e 1172 secnotice("octagon-recovery", "Device does not participate in SOS; cannot enroll recovery key in Octagon");
b54c578e
A
1173 NSError* notFullPeerError = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorLimitedPeer userInfo:@{NSLocalizedDescriptionKey : @"Device is considered a limited peer, cannot enroll recovery key in Octagon"}];
1174 [tracker stopWithEvent:OctagonEventRecoveryKey result:notFullPeerError];
1175 reply(notFullPeerError);
1176 return;
1177 }
1178
1179 CFErrorRef validateError = NULL;
7fb2cbd2 1180 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
b54c578e 1181 if (!res) {
7fb2cbd2
A
1182 NSError *validateErrorWrapper = nil;
1183 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1184 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1185 if(validateError) {
1186 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1187 }
1188
1189 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1190
b54c578e
A
1191 secerror("recovery failed validation with error:%@", validateError);
1192
b54c578e 1193 [tracker stopWithEvent:OctagonEventSetRecoveryKeyValidationFailed result:validateErrorWrapper];
7fb2cbd2
A
1194 reply(validateErrorWrapper);
1195 return;
b54c578e
A
1196 }
1197
1198 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1199
1200 [cfshContext startOctagonStateMachine];
1201
1202 [cfshContext rpcSetRecoveryKey:recoveryKey reply:^(NSError * _Nullable error) {
1203 [tracker stopWithEvent:OctagonEventRecoveryKey result:error];
1204 reply(error);
1205 }];
1206}
1207
1208- (void) joinWithRecoveryKey:(NSString* _Nullable)containerName
1209 contextID:(NSString *)contextID
1210 recoveryKey:(NSString*)recoveryKey
1211 reply:(void (^)(NSError * _Nullable))reply
1212{
1213 secnotice("octagon", "join with recovery key invoked for container: %@, context: %@", containerName, contextID);
1214
1215 SFAnalyticsActivityTracker *tracker = [[self.loggerClass logger] startLogSystemMetricsForActivityNamed:OctagonActivityJoinWithRecoveryKey];
1216
1217 CFErrorRef validateError = NULL;
7fb2cbd2 1218 bool res = SecPasswordValidatePasswordFormat(kSecPasswordTypeiCloudRecoveryKey, (__bridge CFStringRef)recoveryKey, &validateError);
b54c578e 1219 if (!res) {
7fb2cbd2
A
1220 NSError *validateErrorWrapper = nil;
1221 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
1222 userInfo[NSLocalizedDescriptionKey] = @"malformed recovery key";
1223 if(validateError) {
1224 userInfo[NSUnderlyingErrorKey] = CFBridgingRelease(validateError);
1225 }
1226
1227 validateErrorWrapper = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorRecoveryKeyMalformed userInfo:userInfo];
1228
b54c578e
A
1229 secerror("recovery failed validation with error:%@", validateError);
1230
b54c578e 1231 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyValidationFailed result:validateErrorWrapper];
7fb2cbd2
A
1232 reply(validateErrorWrapper);
1233 return;
b54c578e
A
1234 }
1235
1236 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1237
1238 [cfshContext startOctagonStateMachine];
1239
1240 [cfshContext joinWithRecoveryKey:recoveryKey reply:^(NSError *error) {
b3971512
A
1241 if ((error.code == TrustedPeersHelperErrorCodeNotEnrolled || error.code == TrustedPeersHelperErrorCodeUntrustedRecoveryKeys)
1242 && [error.domain isEqualToString:TrustedPeersHelperErrorDomain]){
d64be36e
A
1243 // If we hit either of these errors, and the local device thinks it should be able to set a recovery key,
1244 // let's reset and establish octagon then enroll this recovery key in the new circle
1245
1246 if (!self.sosEnabledForPlatform) {
1247 secerror("octagon: recovery key is not enrolled in octagon, and current device can't set recovery keys");
1248 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyCircleResetFailed result:error];
1249 reply(error);
1250 return;
1251 }
1252
b54c578e
A
1253 secerror("octagon, recovery key is not enrolled in octagon, resetting octagon circle");
1254 [[self.loggerClass logger] logResultForEvent:OctagonEventJoinRecoveryKeyCircleReset hardFailure:NO result:error];
1255
7fb2cbd2 1256 [cfshContext rpcResetAndEstablish:CuttlefishResetReasonRecoveryKey reply:^(NSError *resetError) {
b54c578e
A
1257 if (resetError) {
1258 secerror("octagon, failed to reset octagon");
1259 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyCircleResetFailed result:resetError];
1260 reply(resetError);
1261 return;
1262 } else {
1263 // Now enroll the recovery key
1264 secnotice("octagon", "attempting enrolling recovery key");
d64be36e
A
1265 [self createRecoveryKey:containerName contextID:contextID recoveryKey:recoveryKey reply:^(NSError *enrollError) {
1266 if(enrollError){
1267 secerror("octagon, failed to enroll new recovery key: %@", enrollError);
1268 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyEnrollFailed result:enrollError];
1269 reply(enrollError);
1270 return;
1271 }else{
1272 secnotice("octagon", "successfully enrolled recovery key");
1273 [tracker stopWithEvent:OctagonEventRecoveryKey result:nil];
1274 reply (nil);
1275 return;
1276 }
1277 }];
b54c578e
A
1278 }
1279 }];
1280 } else {
1281 secerror("octagon, join with recovery key failed: %d", (int)[error code]);
1282 [tracker stopWithEvent:OctagonEventJoinRecoveryKeyFailed result:error];
1283 reply(error);
1284 }
1285 }];
1286}
1287
b3971512 1288- (void)xpc24HrNotification
b54c578e 1289{
b3971512 1290 secnotice("octagon-health", "Received 24hr xpc notification");
b54c578e 1291
b3971512
A
1292 [self healthCheck:OTCKContainerName context:OTDefaultContext skipRateLimitingCheck:NO reply:^(NSError * _Nullable error) {
1293 if(error) {
1294 secerror("octagon: error attempting to check octagon health: %@", error);
1295 } else {
1296 secnotice("octagon", "health check success");
1297 }
1298 }];
1299}
b54c578e 1300
b3971512 1301- (void)healthCheck:(NSString *)container context:(NSString *)context skipRateLimitingCheck:(BOOL)skipRateLimitingCheck reply:(void (^)(NSError *_Nullable error))reply
b54c578e 1302{
b3971512 1303 OTCuttlefishContext* cfshContext = [self contextForContainerName:container contextID:context];
b54c578e
A
1304
1305 secnotice("octagon", "notifying container of change");
1306
1307 [cfshContext notifyContainerChange:nil];
1308
1309 [cfshContext checkOctagonHealth:skipRateLimitingCheck reply:^(NSError *error) {
1310 if(error) {
1311 reply(error);
1312 } else {
1313 reply(nil);
1314 }
1315 }];
1316}
1317
1318- (void)setSOSEnabledForPlatformFlag:(bool) value
1319{
1320 self.sosEnabledForPlatform = value;
1321}
1322
1323- (void)allContextsHalt
1324{
1325 for(OTCuttlefishContext* context in self.contexts.allValues) {
1326 [context.stateMachine haltOperation];
b3971512
A
1327
1328 // Also, clear the viewManager strong pointer
1329 [context clearCKKSViewManager];
b54c578e
A
1330 }
1331}
1332
1333- (void)allContextsDisablePendingFlags
1334{
1335 for(OTCuttlefishContext* context in self.contexts.allValues) {
1336 [context.stateMachine disablePendingFlags];
1337 }
1338}
1339
1340- (bool)allContextsPause:(uint64_t)within
1341{
1342 for(OTCuttlefishContext* context in self.contexts.allValues) {
1343 if(context.stateMachine.currentState != OctagonStateMachineNotStarted) {
1344 if([context.stateMachine.paused wait:within] != 0) {
1345 return false;
1346 }
1347 }
1348 }
1349 return true;
1350}
1351
b54c578e
A
1352- (void)waitForOctagonUpgrade:(NSString* _Nullable)containerName
1353 context:(NSString*)context
1354 reply:(void (^)(NSError* error))reply
1355
1356{
d64be36e 1357 secnotice("octagon-sos", "Attempting wait for octagon upgrade");
b54c578e
A
1358 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:context];
1359
1360 [cfshContext startOctagonStateMachine];
1361
1362 [cfshContext waitForOctagonUpgrade:^(NSError * _Nonnull error) {
1363 reply(error);
1364 }];
1365}
1366
1367- (OTFollowupContextType)cliqueCDPTypeToFollowupContextType:(OTCliqueCDPContextType)type
1368{
1369 if ([type isEqualToString:OTCliqueCDPContextTypeNone]) {
1370 return OTFollowupContextTypeNone;
1371 } else if ([type isEqualToString:OTCliqueCDPContextTypeSignIn]) {
1372 return OTFollowupContextTypeNone;
1373 } else if ([type isEqualToString:OTCliqueCDPContextTypeRepair]) {
1374 return OTFollowupContextTypeStateRepair;
1375 } else if ([type isEqualToString:OTCliqueCDPContextTypeFinishPasscodeChange]) {
1376 return OTFollowupContextTypeOfflinePasscodeChange;
1377 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyGenerate]) {
1378 return OTFollowupContextTypeRecoveryKeyRepair;
1379 } else if ([type isEqualToString:OTCliqueCDPContextTypeRecoveryKeyNew]) {
1380 return OTFollowupContextTypeRecoveryKeyRepair;
1381 } else if ([type isEqualToString:OTCliqueCDPContextTypeUpdatePasscode]) {
1382 return OTFollowupContextTypeNone;
1383 } else {
1384 return OTFollowupContextTypeNone;
1385 }
1386}
1387
1388- (void)postCDPFollowupResult:(BOOL)success
1389 type:(OTCliqueCDPContextType)type
1390 error:(NSError * _Nullable)error
1391 containerName:(NSString* _Nullable)containerName
1392 contextName:(NSString *)contextName
1393 reply:(void (^)(NSError *error))reply
1394{
b54c578e
A
1395 NSString* metricName = [NSString stringWithFormat:@"%@%@", OctagonAnalyticsCDPStateRun, type];
1396 NSString* countName = [NSString stringWithFormat:@"%@%@Tries", OctagonAnalyticsCDPStateRun, type];
1397
1398 [[CKKSAnalytics logger] logResultForEvent:metricName
1399 hardFailure:NO
1400 result:error];
1401 if (error) {
1402 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:metricName];
1403 [[CKKSAnalytics logger] incrementIntegerPropertyForKey:countName];
1404 } else {
1405 [[CKKSAnalytics logger] setDateProperty:NULL forKey:metricName];
1406 [[CKKSAnalytics logger] setNumberProperty:NULL forKey:countName];
1407 }
1408
b54c578e
A
1409 // Always return without error
1410 reply(nil);
1411}
1412
1413- (void)tapToRadar:(NSString *)action
1414 description:(NSString *)description
1415 radar:(NSString *)radar
1416 reply:(void (^)(NSError * _Nullable))reply
1417{
1418 SecTapToRadar *ttr = [[SecTapToRadar alloc] initTapToRadar:action description:description radar:radar];
1419 [ttr trigger];
1420 reply(NULL);
1421}
1422
b3971512
A
1423- (void)refetchCKKSPolicy:(NSString * _Nullable)containerName
1424 contextID:(nonnull NSString *)contextID
1425 reply:(nonnull void (^)(NSError * _Nullable))reply {
1426 secnotice("octagon-ckks", "refetch-ckks-policy");
1427
1428 if(!containerName) {
1429 containerName = OTCKContainerName;
1430 }
1431
1432 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1433
1434 if(!cfshContext) {
1435 reply([NSError errorWithDomain:OctagonErrorDomain
1436 code:OTErrorNoSuchContext
1437 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1438 return;
1439 }
1440
1441 [cfshContext rpcRefetchCKKSPolicy:^(NSError* error) {
1442 secnotice("octagon-ckks", "refetch-ckks-policy result: %@", error ?: @"no error");
1443 reply(error);
1444 }];
1445}
1446
b3971512
A
1447- (void)getCDPStatus:(NSString * _Nullable)containerName
1448 contextID:(nonnull NSString *)contextID
1449 reply:(nonnull void (^)(OTCDPStatus, NSError * _Nullable))reply {
1450 secnotice("octagon-cdp", "get-cdp-status");
1451
1452 if(!containerName) {
1453 containerName = OTCKContainerName;
1454 }
1455
1456 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1457
1458 if(!cfshContext) {
1459 reply(OTCDPStatusUnknown, [NSError errorWithDomain:OctagonErrorDomain
1460 code:OTErrorNoSuchContext
1461 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1462 return;
1463 }
1464
1465 NSError* error = nil;
1466 OTCDPStatus status = [cfshContext getCDPStatus:&error];
1467
1468 reply(status, error);
1469}
1470
1471
1472- (void)setCDPEnabled:(NSString * _Nullable)containerName
1473 contextID:(nonnull NSString *)contextID
1474 reply:(nonnull void (^)(NSError * _Nullable))reply {
1475 secnotice("octagon-cdp", "set-cdp-enabled");
1476 if(!containerName) {
1477 containerName = OTCKContainerName;
1478 }
1479
1480 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1481
1482 if(!cfshContext) {
1483 reply([NSError errorWithDomain:OctagonErrorDomain
1484 code:OTErrorNoSuchContext
1485 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1486 return;
1487 }
1488
1489 NSError* localError = nil;
1490 [cfshContext setCDPEnabled:&localError];
1491
1492 reply(localError);
1493}
1494
d64be36e
A
1495- (void)fetchEscrowRecords:(NSString * _Nullable)containerName
1496 contextID:(NSString*)contextID
1497 forceFetch:(BOOL)forceFetch
1498 reply:(nonnull void (^)(NSArray<NSData *> * _Nullable records,
1499 NSError * _Nullable error))reply {
1500
1501 secnotice("octagon-fetch-escrow-records", "fetching records");
1502 if(!containerName) {
1503 containerName = OTCKContainerName;
1504 }
1505
1506 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1507
1508 if(!cfshContext) {
1509 reply(nil, [NSError errorWithDomain:OctagonErrorDomain
1510 code:OTErrorNoSuchContext
1511 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1512 return;
1513 }
1514
1515 [cfshContext rpcFetchAllViableEscrowRecords:forceFetch reply:^(NSArray<NSData *> * _Nullable records, NSError * _Nullable error) {
1516 if(error) {
1517 secerror("octagon-fetch-escrow-records: error fetching records: %@", error);
1518 reply(nil, error);
1519 return;
1520 }
1521 secnotice("octagon-fetch-escrow-records", "successfully fetched records");
1522 reply(records, nil);
1523 }];
1524}
1525
1526- (void)invalidateEscrowCache:(NSString * _Nullable)containerName
1527 contextID:(NSString*)contextID
1528 reply:(nonnull void (^)(NSError * _Nullable error))reply {
1529
1530 secnotice("octagon-remove-escrow-cache", "beginning removing escrow cache!");
1531 if(!containerName) {
1532 containerName = OTCKContainerName;
1533 }
1534
1535 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1536
1537 if(!cfshContext) {
1538 reply([NSError errorWithDomain:OctagonErrorDomain
1539 code:OTErrorNoSuchContext
1540 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1541 return;
1542 }
1543
1544 [cfshContext rpcInvalidateEscrowCache:^(NSError * _Nullable invalidateError) {
1545 if(invalidateError) {
1546 secerror("octagon-remove-escrow-cache: error invalidating escrow cache: %@", invalidateError);
1547 reply(invalidateError);
1548 return;
1549 }
1550 secnotice("octagon-remove-escrow-caches", "successfully invalidated escrow cache");
1551 reply(nil);
1552 }];
1553}
1554
1555- (void)setUserControllableViewsSyncStatus:(NSString* _Nullable)containerName
1556 contextID:(NSString*)contextID
1557 enabled:(BOOL)enabled
1558 reply:(void (^)(BOOL nowSyncing, NSError* _Nullable error))reply
1559{
1560 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1561
1562 if(!cfshContext) {
1563 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
1564 code:OTErrorNoSuchContext
1565 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1566 return;
1567 }
1568
1569 [cfshContext rpcSetUserControllableViewsSyncingStatus:enabled reply:^(BOOL areSyncing, NSError * _Nullable error) {
1570 if(error) {
1571 secerror("octagon-user-controllable-views: error setting status: %@", error);
1572 reply(NO, error);
1573 return;
1574 }
1575
1576 secnotice("octagon-user-controllable-views", "successfully set status to: %@", areSyncing ? @"enabled" : @"paused");
1577 reply(areSyncing, nil);
1578 }];
1579}
1580
1581- (void)fetchUserControllableViewsSyncStatus:(NSString* _Nullable)containerName
1582 contextID:(NSString*)contextID
1583 reply:(void (^)(BOOL nowSyncing, NSError* _Nullable error))reply
1584{
1585 OTCuttlefishContext* cfshContext = [self contextForContainerName:containerName contextID:contextID];
1586
1587 if(!cfshContext) {
1588 reply(NO, [NSError errorWithDomain:OctagonErrorDomain
1589 code:OTErrorNoSuchContext
1590 description:[NSString stringWithFormat:@"No context for (%@,%@)", containerName, contextID]]);
1591 return;
1592 }
1593
1594 [cfshContext rpcFetchUserControllableViewsSyncingStatus:^(BOOL areSyncing, NSError * _Nullable error) {
1595 if(error) {
1596 secerror("octagon-user-controllable-views: error fetching status: %@", error);
1597 reply(NO, error);
1598 return;
1599 }
1600
1601 secerror("octagon-user-controllable-views: succesfully fetched status as: %@", areSyncing ? @"enabled" : @"paused");
1602 reply(areSyncing, nil);
1603 }];
1604}
1605
ecaf5866
A
1606@end
1607
1608#endif