]> git.saurik.com Git - apple/security.git/blame - keychain/ckks/CKKSAccountStateTracker.m
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / ckks / CKKSAccountStateTracker.m
CommitLineData
866f8763
A
1/*
2 * Copyright (c) 2017 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#if OCTAGON
25
26#include <dispatch/dispatch.h>
27#include <utilities/debugging.h>
28#include <Security/SecureObjectSync/SOSCloudCircle.h>
29#include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
b54c578e 30#include "keychain/SecureObjectSync/SOSInternal.h"
866f8763
A
31#include <notify.h>
32
b54c578e
A
33#import "keychain/ot/OTManager.h"
34#import "keychain/ot/OTConstants.h"
866f8763 35#import "keychain/ckks/CKKS.h"
ecaf5866 36#import "keychain/ckks/CloudKitCategories.h"
b54c578e 37#import "keychain/ckks/CKKSAccountStateTracker.h"
ecaf5866 38#import "keychain/ckks/CKKSAnalytics.h"
79b9da22 39#import "keychain/categories/NSError+UsefulConstructors.h"
b54c578e 40#import "keychain/ot/ObjCImprovements.h"
866f8763 41
866f8763 42
b54c578e
A
43NSString* CKKSAccountStatusToString(CKKSAccountStatus status)
44{
45 switch(status) {
46 case CKKSAccountStatusAvailable:
47 return @"available";
48 case CKKSAccountStatusNoAccount:
49 return @"no account";
50 case CKKSAccountStatusUnknown:
51 return @"unknown";
52 }
53}
54
55@interface CKKSAccountStateTracker ()
56@property (readonly) Class<CKKSNSNotificationCenter> nsnotificationCenterClass;
866f8763
A
57
58@property dispatch_queue_t queue;
59
b54c578e 60@property NSMapTable<dispatch_queue_t, id<CKKSCloudKitAccountStateListener>>* ckChangeListeners;
866f8763 61
b54c578e 62@property CKContainer* container; // used only for fetching the CKAccountStatus
866f8763 63@property bool firstCKAccountFetch;
b54c578e
A
64
65// make writable
66@property (nullable) OTCliqueStatusWrapper* octagonStatus;
67@property (nullable) NSString* octagonPeerID;
68@property CKKSCondition* octagonInformationInitialized;
69
70@property CKKSAccountStatus hsa2iCloudAccountStatus;
71@property CKKSCondition* hsa2iCloudAccountInitialized;
866f8763
A
72@end
73
b54c578e
A
74@implementation CKKSAccountStateTracker
75@synthesize octagonPeerID = _octagonPeerID;
866f8763
A
76
77-(instancetype)init: (CKContainer*) container nsnotificationCenterClass: (Class<CKKSNSNotificationCenter>) nsnotificationCenterClass {
78 if((self = [super init])) {
79 _nsnotificationCenterClass = nsnotificationCenterClass;
b54c578e
A
80 // These map tables are backwards from how we'd like, but it's the best way to have weak pointers to CKKSCombinedAccountStateListener.
81 _ckChangeListeners = [NSMapTable strongToWeakObjectsMapTable];
82
866f8763 83 _currentCKAccountInfo = nil;
b54c578e 84 _ckAccountInfoInitialized = [[CKKSCondition alloc] init];
866f8763 85
b54c578e 86 _currentCircleStatus = [[SOSAccountStatus alloc] init:kSOSCCError error:nil];
866f8763
A
87
88 _container = container;
89
ecaf5866 90 _queue = dispatch_queue_create("ck-account-state", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
866f8763
A
91
92 _firstCKAccountFetch = false;
866f8763 93
3f0f0d49 94 _finishedInitialDispatches = [[CKKSCondition alloc] init];
866f8763
A
95 _ckdeviceIDInitialized = [[CKKSCondition alloc] init];
96
b54c578e
A
97 _octagonInformationInitialized = [[CKKSCondition alloc] init];
98
99 _hsa2iCloudAccountStatus = CKKSAccountStatusUnknown;
100 _hsa2iCloudAccountInitialized = [[CKKSCondition alloc] init];
101
866f8763
A
102 id<CKKSNSNotificationCenter> notificationCenter = [self.nsnotificationCenterClass defaultCenter];
103 secinfo("ckksaccount", "Registering with notification center %@", notificationCenter);
104 [notificationCenter addObserver:self selector:@selector(notifyCKAccountStatusChange:) name:CKAccountChangedNotification object:NULL];
105
b54c578e 106 WEAKIFY(self);
866f8763
A
107
108 // If this is a live server, register with notify
109 if(!SecCKKSTestsEnabled()) {
110 int token = 0;
111 notify_register_dispatch(kSOSCCCircleChangedNotification, &token, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(int t) {
b54c578e
A
112 STRONGIFY(self);
113 [self notifyCircleChange:nil];
866f8763 114 });
866f8763 115
b54c578e
A
116 // Fire off a fetch of the account status. Do not go on our local queue, because notifyCKAccountStatusChange will attempt to go back on it for thread-safety.
117 // Note: if you're in the tests, you must call performInitialDispatches yourself!
118 dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
119 STRONGIFY(self);
120 if(!self) {
121 return;
122 }
123 @autoreleasepool {
124 [self performInitialDispatches];
125 }
126 });
127 }
866f8763
A
128 }
129 return self;
130}
131
b54c578e
A
132- (void)performInitialDispatches
133{
134 @autoreleasepool {
135 [self notifyCKAccountStatusChange:nil];
136 [self notifyCircleChange:nil];
137 [self.finishedInitialDispatches fulfill];
138 }
139}
140
866f8763
A
141-(void)dealloc {
142 id<CKKSNSNotificationCenter> notificationCenter = [self.nsnotificationCenterClass defaultCenter];
143 [notificationCenter removeObserver:self];
144}
145
146-(NSString*)descriptionInternal: (NSString*) selfString {
b54c578e
A
147 return [NSString stringWithFormat:@"<%@: %@, hsa2: %@>",
148 selfString,
149 self.currentCKAccountInfo,
150 CKKSAccountStatusToString(self.hsa2iCloudAccountStatus)];
866f8763
A
151}
152
153-(NSString*)description {
154 return [self descriptionInternal: [[self class] description]];
155}
156
157-(NSString*)debugDescription {
158 return [self descriptionInternal: [super description]];
159}
160
b54c578e 161- (dispatch_semaphore_t)registerForNotificationsOfCloudKitAccountStatusChange:(id<CKKSCloudKitAccountStateListener>)listener {
3f0f0d49
A
162 // signals when we've successfully delivered the first account status
163 dispatch_semaphore_t finishedSema = dispatch_semaphore_create(0);
866f8763 164
3f0f0d49 165 dispatch_async(self.queue, ^{
866f8763 166 bool alreadyRegisteredListener = false;
b54c578e
A
167 NSEnumerator *enumerator = [self.ckChangeListeners objectEnumerator];
168 id<CKKSCloudKitAccountStateListener> value;
866f8763
A
169
170 while ((value = [enumerator nextObject])) {
171 // do pointer comparison
172 alreadyRegisteredListener |= (value == listener);
173 }
174
175 if(listener && !alreadyRegisteredListener) {
176 NSString* queueName = [NSString stringWithFormat: @"ck-account-state-%@", listener];
177
7512f6be 178 dispatch_queue_t objQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
b54c578e 179 [self.ckChangeListeners setObject:listener forKey: objQueue];
3f0f0d49 180
b54c578e 181 secinfo("ckksaccount-ck", "adding a new listener: %@", listener);
ecaf5866 182
3f0f0d49 183 // If we know the current account status, let this listener know
b54c578e
A
184 if(self.firstCKAccountFetch) {
185 secinfo("ckksaccount-ck", "notifying new listener %@ of current state %@", listener, self.currentCKAccountInfo);
3f0f0d49
A
186
187 dispatch_group_t g = dispatch_group_create();
188 if(!g) {
b54c578e
A
189 secnotice("ckksaccount-ck", "Unable to get dispatch group.");
190 dispatch_semaphore_signal(finishedSema);
3f0f0d49
A
191 return;
192 }
193
b54c578e 194 [self _onqueueDeliverCurrentCloudKitState:listener listenerQueue:objQueue oldStatus:nil group:g];
3f0f0d49
A
195
196 dispatch_group_notify(g, self.queue, ^{
197 dispatch_semaphore_signal(finishedSema);
198 });
199 } else {
200 dispatch_semaphore_signal(finishedSema);
201 }
202 } else {
203 dispatch_semaphore_signal(finishedSema);
866f8763
A
204 }
205 });
3f0f0d49
A
206
207 return finishedSema;
866f8763
A
208}
209
210- (dispatch_semaphore_t)notifyCKAccountStatusChange:(__unused id)object {
211 // signals when this notify is Complete, including all downcalls.
212 dispatch_semaphore_t finishedSema = dispatch_semaphore_create(0);
213
214 [self.container accountInfoWithCompletionHandler:^(CKAccountInfo* ckAccountInfo, NSError * _Nullable error) {
215 if(error) {
216 secerror("ckksaccount: error getting account info: %@", error);
217 dispatch_semaphore_signal(finishedSema);
218 return;
219 }
220
221 dispatch_sync(self.queue, ^{
222 self.firstCKAccountFetch = true;
7512f6be 223 secnotice("ckksaccount", "received CK Account info: %@", ckAccountInfo);
b54c578e 224 [self _onqueueUpdateAccountState:ckAccountInfo deliveredSemaphore:finishedSema];
866f8763
A
225 });
226 }];
227
228 return finishedSema;
229}
230
866f8763 231// Takes the new ckAccountInfo we're moving to
b54c578e 232-(void)_onqueueUpdateCKDeviceID:(CKAccountInfo*)ckAccountInfo {
866f8763 233 dispatch_assert_queue(self.queue);
b54c578e 234 WEAKIFY(self);
866f8763
A
235
236 // If we're in an account, opportunistically fill in the device id
237 if(ckAccountInfo.accountStatus == CKAccountStatusAvailable) {
238 [self.container fetchCurrentDeviceIDWithCompletionHandler:^(NSString* deviceID, NSError* ckerror) {
b54c578e
A
239 STRONGIFY(self);
240 if(!self) {
ecaf5866
A
241 secerror("ckksaccount: Received fetchCurrentDeviceIDWithCompletionHandler callback with null AccountStateTracker");
242 return;
243 }
866f8763
A
244
245 // Make sure you synchronize here; if we've logged out before the callback returns, don't record the result
b54c578e
A
246 dispatch_async(self.queue, ^{
247 STRONGIFY(self);
248 if(self.currentCKAccountInfo.accountStatus == CKAccountStatusAvailable) {
866f8763
A
249 secnotice("ckksaccount", "CloudKit deviceID is: %@ %@", deviceID, ckerror);
250
b54c578e
A
251 self.ckdeviceID = deviceID;
252 self.ckdeviceIDError = ckerror;
253 [self.ckdeviceIDInitialized fulfill];
866f8763
A
254 } else {
255 // Logged out! No ckdeviceid.
256 secerror("ckksaccount: Logged back out but still received a fetchCurrentDeviceIDWithCompletionHandler callback");
257
b54c578e
A
258 self.ckdeviceID = nil;
259 self.ckdeviceIDError = nil;
866f8763
A
260 // Don't touch the ckdeviceIDInitialized object; it should have been reset when the logout happened.
261 }
262 });
263 }];
264 } else {
265 // Logging out? no more device ID.
266 self.ckdeviceID = nil;
267 self.ckdeviceIDError = nil;
268 self.ckdeviceIDInitialized = [[CKKSCondition alloc] init];
269 }
270}
271
b54c578e
A
272- (dispatch_semaphore_t)notifyCircleChange:(__unused id)object {
273 dispatch_semaphore_t finishedSema = dispatch_semaphore_create(0);
274
275 SOSAccountStatus* sosstatus = [CKKSAccountStateTracker getCircleStatus];
276 dispatch_sync(self.queue, ^{
277 if(self.currentCircleStatus == nil || ![self.currentCircleStatus isEqual:sosstatus]) {
278 secnotice("ckksaccount", "moving to circle status: %@", sosstatus);
279 self.currentCircleStatus = sosstatus;
280
281 if (sosstatus.status == kSOSCCInCircle) {
282 [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:CKKSAnalyticsLastInCircle];
283 }
284 [self _onqueueUpdateCirclePeerID:sosstatus];
285 }
286 dispatch_semaphore_signal(finishedSema);
287 });
288
289 return finishedSema;
290}
291
866f8763 292// Pulled out for mocking purposes
b54c578e 293+ (void)fetchCirclePeerID:(void (^)(NSString* _Nullable peerID, NSError* _Nullable error))callback {
866f8763
A
294 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
295 CFErrorRef cferror = nil;
296 SOSPeerInfoRef egoPeerInfo = SOSCCCopyMyPeerInfo(&cferror);
297 NSString* egoPeerID = egoPeerInfo ? (NSString*)CFBridgingRelease(CFRetainSafe(SOSPeerInfoGetPeerID(egoPeerInfo))) : nil;
298 CFReleaseNull(egoPeerInfo);
299
300 callback(egoPeerID, CFBridgingRelease(cferror));
301 });
302}
303
304// Takes the new ckAccountInfo we're moving to
b54c578e 305- (void)_onqueueUpdateCirclePeerID:(SOSAccountStatus*)sosstatus {
866f8763 306 dispatch_assert_queue(self.queue);
b54c578e 307 WEAKIFY(self);
866f8763
A
308
309 // If we're in a circle, fetch the peer id
79b9da22 310 if(sosstatus.status == kSOSCCInCircle) {
b54c578e
A
311 [CKKSAccountStateTracker fetchCirclePeerID:^(NSString* peerID, NSError* error) {
312 STRONGIFY(self);
313 if(!self) {
ecaf5866
A
314 secerror("ckksaccount: Received fetchCirclePeerID callback with null AccountStateTracker");
315 return;
316 }
317
b54c578e
A
318 dispatch_async(self.queue, ^{
319 STRONGIFY(self);
866f8763 320
b54c578e 321 if(self.currentCircleStatus && self.currentCircleStatus.status == kSOSCCInCircle) {
866f8763
A
322 secnotice("ckksaccount", "Circle peerID is: %@ %@", peerID, error);
323 // Still in circle. Proceed.
b54c578e
A
324 self.accountCirclePeerID = peerID;
325 self.accountCirclePeerIDError = error;
326 [self.accountCirclePeerIDInitialized fulfill];
866f8763
A
327 } else {
328 secerror("ckksaccount: Out of circle but still received a fetchCirclePeerID callback");
329 // Not in-circle. Throw away circle id.
b54c578e
A
330 self.accountCirclePeerID = nil;
331 self.accountCirclePeerIDError = nil;
866f8763
A
332 // Don't touch the accountCirclePeerIDInitialized object; it should have been reset when the logout happened.
333 }
334 });
335 }];
336 } else {
337 // Not in-circle, reset circle ID
79b9da22 338 secnotice("ckksaccount", "out of circle(%@): resetting peer ID", sosstatus);
866f8763
A
339 self.accountCirclePeerID = nil;
340 self.accountCirclePeerIDError = nil;
341 self.accountCirclePeerIDInitialized = [[CKKSCondition alloc] init];
342 }
343}
344
b54c578e
A
345- (void)_onqueueUpdateAccountState:(CKAccountInfo*)ckAccountInfo
346 deliveredSemaphore:(dispatch_semaphore_t)finishedSema
347{
348 // Launder the finishedSema into a dispatch_group.
349 // _onqueueUpdateAccountState:circle:dispatchGroup: will then add any blocks it thinks is necessary,
350 // then the group will fire the semaphore.
ecaf5866 351 dispatch_assert_queue(self.queue);
ecaf5866 352
b54c578e
A
353 dispatch_group_t g = dispatch_group_create();
354 if(!g) {
355 secnotice("ckksaccount", "Unable to get dispatch group.");
356 dispatch_semaphore_signal(finishedSema);
357 return;
ecaf5866
A
358 }
359
b54c578e
A
360 [self _onqueueUpdateAccountState:ckAccountInfo
361 dispatchGroup:g];
362
363 dispatch_group_notify(g, self.queue, ^{
364 dispatch_semaphore_signal(finishedSema);
365 });
ecaf5866
A
366}
367
b54c578e
A
368- (void)_onqueueUpdateAccountState:(CKAccountInfo*)ckAccountInfo
369 dispatchGroup:(dispatch_group_t)g
370{
866f8763
A
371 dispatch_assert_queue(self.queue);
372
b54c578e 373 if([self.currentCKAccountInfo isEqual: ckAccountInfo]) {
866f8763 374 // no-op.
b54c578e 375 secinfo("ckksaccount", "received another notification of CK Account State %@", ckAccountInfo);
866f8763
A
376 return;
377 }
378
7512f6be 379 if((self.currentCKAccountInfo == nil && ckAccountInfo != nil) ||
b54c578e 380 !(self.currentCKAccountInfo == ckAccountInfo || [self.currentCKAccountInfo isEqual: ckAccountInfo])) {
866f8763 381 secnotice("ckksaccount", "moving to CK Account info: %@", ckAccountInfo);
b54c578e 382 CKAccountInfo* oldAccountInfo = self.currentCKAccountInfo;
866f8763 383 self.currentCKAccountInfo = ckAccountInfo;
b54c578e 384 [self.ckAccountInfoInitialized fulfill];
866f8763
A
385
386 [self _onqueueUpdateCKDeviceID: ckAccountInfo];
866f8763 387
b54c578e 388 [self _onqueueDeliverCloudKitStateChanges:oldAccountInfo dispatchGroup:g];
866f8763 389 }
3f0f0d49
A
390}
391
b54c578e
A
392- (void)_onqueueDeliverCloudKitStateChanges:(CKAccountInfo*)oldStatus
393 dispatchGroup:(dispatch_group_t)g
394{
3f0f0d49
A
395 dispatch_assert_queue(self.queue);
396
b54c578e 397 NSEnumerator *enumerator = [self.ckChangeListeners keyEnumerator];
866f8763
A
398 dispatch_queue_t dq;
399
400 // Queue up the changes for each listener.
401 while ((dq = [enumerator nextObject])) {
b54c578e
A
402 id<CKKSCloudKitAccountStateListener> listener = [self.ckChangeListeners objectForKey: dq];
403 [self _onqueueDeliverCurrentCloudKitState:listener listenerQueue:dq oldStatus:oldStatus group:g];
866f8763 404 }
866f8763
A
405}
406
b54c578e
A
407- (void)_onqueueDeliverCurrentCloudKitState:(id<CKKSCloudKitAccountStateListener>)listener
408 listenerQueue:(dispatch_queue_t)listenerQueue
409 oldStatus:(CKAccountInfo* _Nullable)oldStatus
410 group:(dispatch_group_t)g
411{
3f0f0d49
A
412 dispatch_assert_queue(self.queue);
413
414 __weak __typeof(listener) weakListener = listener;
415
416 if(listener) {
417 dispatch_group_async(g, listenerQueue, ^{
b54c578e 418 [weakListener cloudkitAccountStateChange:oldStatus to:self.currentCKAccountInfo];
3f0f0d49
A
419 });
420 }
421}
422
866f8763
A
423-(void)notifyCKAccountStatusChangeAndWaitForSignal {
424 dispatch_semaphore_wait([self notifyCKAccountStatusChange: nil], DISPATCH_TIME_FOREVER);
425}
426
427-(void)notifyCircleStatusChangeAndWaitForSignal {
428 dispatch_semaphore_wait([self notifyCircleChange: nil], DISPATCH_TIME_FOREVER);
429}
430
3f0f0d49
A
431-(dispatch_group_t)checkForAllDeliveries {
432
433 dispatch_group_t g = dispatch_group_create();
434 if(!g) {
435 secnotice("ckksaccount", "Unable to get dispatch group.");
436 return nil;
437 }
438
439 dispatch_sync(self.queue, ^{
b54c578e 440 NSEnumerator *enumerator = [self.ckChangeListeners keyEnumerator];
3f0f0d49
A
441 dispatch_queue_t dq;
442
443 // Queue up the changes for each listener.
444 while ((dq = [enumerator nextObject])) {
b54c578e 445 id<CKKSCloudKitAccountStateListener> listener = [self.ckChangeListeners objectForKey: dq];
3f0f0d49
A
446
447 secinfo("ckksaccountblock", "Starting blocking for listener %@", listener);
b54c578e 448 WEAKIFY(listener);
3f0f0d49 449 dispatch_group_async(g, dq, ^{
b54c578e 450 STRONGIFY(listener);
3f0f0d49 451 // Do nothing in particular. It's just important that this block runs.
b54c578e 452 secinfo("ckksaccountblock", "Done blocking for listener %@", listener);
3f0f0d49
A
453 });
454 }
455 });
456
457 return g;
458}
459
866f8763 460// This is its own function to allow OCMock to swoop in and replace the result during testing.
79b9da22 461+ (SOSAccountStatus*)getCircleStatus {
866f8763
A
462 CFErrorRef cferror = NULL;
463
464 SOSCCStatus status = SOSCCThisDeviceIsInCircle(&cferror);
465 if(cferror) {
466 secerror("ckksaccount: error getting circle status: %@", cferror);
79b9da22 467 return [[SOSAccountStatus alloc] init:kSOSCCError error:CFBridgingRelease(cferror)];
866f8763 468 }
79b9da22
A
469
470 return [[SOSAccountStatus alloc] init:status error:nil];
866f8763
A
471}
472
b54c578e 473+ (NSString*)stringFromAccountStatus: (CKKSAccountStatus) status {
866f8763
A
474 switch(status) {
475 case CKKSAccountStatusUnknown: return @"account state unknown";
476 case CKKSAccountStatusAvailable: return @"logged in";
477 case CKKSAccountStatusNoAccount: return @"no account";
478 }
479}
480
b54c578e
A
481- (void)triggerOctagonStatusFetch
482{
483 WEAKIFY(self);
484
485 __block CKKSCondition* blockPointer = nil;
486 dispatch_sync(self.queue, ^{
487 self.octagonInformationInitialized = [[CKKSCondition alloc] initToChain:self.octagonInformationInitialized];
488 blockPointer = self.octagonInformationInitialized;
489 });
490
491 // Explicitly do not use the OTClique API, as that might include SOS status as well
492 OTOperationConfiguration* config = [[OTOperationConfiguration alloc] init];
493 config.timeoutWaitForCKAccount = 100*NSEC_PER_MSEC;
494 [[OTManager manager] fetchTrustStatus:nil
495 context:OTDefaultContext
496 configuration:config
497 reply:^(CliqueStatus status, NSString * _Nullable peerID, NSNumber * _Nullable numberOfPeersInOctagon, BOOL isExcluded, NSError * _Nullable error) {
498 STRONGIFY(self);
499
500 dispatch_sync(self.queue, ^{
501 if(error) {
502 secerror("ckksaccount: error getting octagon status: %@", error);
503 self.octagonStatus = [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusError];
504 } else {
505 secnotice("ckksaccount", "Caching octagon status as (%@, %@)", OTCliqueStatusToString(status), peerID);
506 self.octagonStatus = [[OTCliqueStatusWrapper alloc] initWithStatus:status];
507 }
508
509 self.octagonPeerID = peerID;
510 [blockPointer fulfill];
511 });
512 }];
513}
514
515
516- (void)setHSA2iCloudAccountStatus:(CKKSAccountStatus)status
517{
518 self.hsa2iCloudAccountStatus = status;
519 if(status == CKKSAccountStatusUnknown) {
520 self.hsa2iCloudAccountInitialized = [[CKKSCondition alloc] initToChain:self.hsa2iCloudAccountInitialized];
521 } else {
522 [self.hsa2iCloudAccountInitialized fulfill];
523 }
524}
525
866f8763
A
526@end
527
79b9da22
A
528@implementation SOSAccountStatus
529- (instancetype)init:(SOSCCStatus)status error:(NSError*)error
530{
531 if((self = [super init])) {
532 _status = status;
533 _error = error;
534 }
535 return self;
536}
537
7512f6be
A
538- (BOOL)isEqual:(id)object
539{
540 if(![object isKindOfClass:[SOSAccountStatus class]]) {
541 return NO;
542 }
543
544 if(object == nil) {
545 return NO;
546 }
547
548 SOSAccountStatus* obj = (SOSAccountStatus*) object;
549 return self.status == obj.status &&
550 ((self.error == nil && obj.error == nil) || [self.error isEqual:obj.error]);
551}
552
79b9da22
A
553- (NSString*)description
554{
555 return [NSString stringWithFormat:@"<SOSStatus: %@ (%@)>", SOSCCGetStatusDescription(self.status), self.error];
556}
557@end
558
84aacf34
A
559
560@implementation OTCliqueStatusWrapper
561- (instancetype)initWithStatus:(CliqueStatus)status
562{
563 if((self = [super init])) {
564 _status = status;
565 }
566 return self;
567}
568
569- (BOOL)isEqual:(id)object
570{
571 if(![object isKindOfClass:[OTCliqueStatusWrapper class]]) {
572 return NO;
573 }
574
575 OTCliqueStatusWrapper* obj = (OTCliqueStatusWrapper*)object;
576 return obj.status == self.status;
577}
578- (NSString*)description
579{
580 return [NSString stringWithFormat:@"<CliqueStatus: %@>", OTCliqueStatusToString(self.status)];
581}
582@end
583
84aacf34 584
866f8763 585#endif // OCTAGON