]>
Commit | Line | Data |
---|---|---|
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 |
43 | NSString* 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 |