2 * Copyright (c) 2012-2014,2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
29 #import <Foundation/Foundation.h>
31 #import <utilities/debugging.h>
32 #import <os/activity.h>
34 #import "CKDKVSProxy.h"
35 #import "CKDKVSStore.h"
36 #import "CKDAKSLockMonitor.h"
37 #import "CKDSecuritydAccount.h"
38 #import "NSURL+SOSPlistStore.h"
40 #include <Security/SecureObjectSync/SOSARCDefines.h>
41 #include <utilities/SecCFWrappers.h>
43 #include "SOSCloudKeychainConstants.h"
45 #include <utilities/SecAKSWrappers.h>
46 #include <utilities/SecADWrapper.h>
48 #import "XPCNotificationDispatcher.h"
51 The total space available in your app’s iCloud key-value storage is 1 MB.
52 The maximum number of keys you can specify is 1024, and the size limit for
53 each value associated with a key is 1 MB. So, for example, if you store a
54 single large value of 1 MB for a single key, that consumes your total
55 available storage. If you store 1 KB of data for each key, you can use
56 1,000 key-value pairs.
59 static NSString *kKeyKeyParameterKeys = @"KeyParameterKeys";
60 static NSString *kKeyCircleKeys = @"CircleKeys";
61 static NSString *kKeyMessageKeys = @"MessageKeys";
63 static NSString *kKeyAlwaysKeys = @"AlwaysKeys";
64 static NSString *kKeyFirstUnlockKeys = @"FirstUnlockKeys";
65 static NSString *kKeyUnlockedKeys = @"UnlockedKeys";
66 static NSString *kKeyPendingKeys = @"PendingKeys";
67 static NSString *kKeyUnsentChangedKeys = @"unsentChangedKeys";
68 static NSString *kKeyUnlockNotificationRequested = @"unlockNotificationRequested";
69 static NSString *kKeySyncWithPeersPending = @"SyncWithPeersPending";
71 static NSString *kKeyPendingSyncPeerIDs = @"SyncPeerIDs";
72 static NSString *kKeyPendingSyncBackupPeerIDs = @"SyncBackupPeerIDs";
74 static NSString *kKeyEnsurePeerRegistration = @"EnsurePeerRegistration";
75 static NSString *kKeyDSID = @"DSID";
76 static NSString *kMonitorState = @"MonitorState";
77 static NSString *kKeyAccountUUID = @"MonitorState";
79 static NSString *kMonitorPenaltyBoxKey = @"Penalty";
80 static NSString *kMonitorMessageKey = @"Message";
81 static NSString *kMonitorConsecutiveWrites = @"ConsecutiveWrites";
82 static NSString *kMonitorLastWriteTimestamp = @"LastWriteTimestamp";
83 static NSString *kMonitorMessageQueue = @"MessageQueue";
84 static NSString *kMonitorPenaltyTimer = @"PenaltyTimer";
85 static NSString *kMonitorDidWriteDuringPenalty = @"DidWriteDuringPenalty";
87 static NSString *kMonitorTimeTable = @"TimeTable";
88 static NSString *kMonitorFirstMinute = @"AFirstMinute";
89 static NSString *kMonitorSecondMinute = @"BSecondMinute";
90 static NSString *kMonitorThirdMinute = @"CThirdMinute";
91 static NSString *kMonitorFourthMinute = @"DFourthMinute";
92 static NSString *kMonitorFifthMinute = @"EFifthMinute";
93 static NSString *kMonitorWroteInTimeSlice = @"TimeSlice";
94 const CFStringRef kSOSKVSKeyParametersKey = CFSTR(">KeyParameters");
95 const CFStringRef kSOSKVSInitialSyncKey = CFSTR("^InitialSync");
96 const CFStringRef kSOSKVSAccountChangedKey = CFSTR("^AccountChanged");
97 const CFStringRef kSOSKVSRequiredKey = CFSTR("^Required");
98 const CFStringRef kSOSKVSOfficialDSIDKey = CFSTR("^OfficialDSID");
100 #define kSecServerKeychainChangedNotification "com.apple.security.keychainchanged"
102 static int max_penalty_timeout = 32;
103 static int seconds_per_minute = 60;
105 static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250); // 250ms leeway for sync events.
107 static NSString* asNSString(NSObject* object) {
108 return [object isKindOfClass:[NSString class]] ? (NSString*) object : nil;
111 @interface NSMutableDictionary (FindAndRemove)
112 -(NSObject*)extractObjectForKey:(NSString*)key;
115 @implementation NSMutableDictionary (FindAndRemove)
116 -(NSObject*)extractObjectForKey:(NSString*)key
118 NSObject* result = [self objectForKey:key];
119 [self removeObjectForKey: key];
124 @interface NSSet (Emptiness)
128 @implementation NSSet (Emptiness)
131 return [self count] == 0;
135 @interface NSSet (HasElements)
136 - (bool) containsElementsNotIn: (NSSet*) other;
139 @implementation NSSet (HasElements)
140 - (bool) containsElementsNotIn: (NSSet*) other
142 __block bool hasElements = false;
143 [self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
144 if (![other containsObject:obj]) {
154 @implementation NSSet (Stringizing)
155 - (NSString*) sortedElementsJoinedByString: (NSString*) separator {
156 return [self sortedElementsTruncated: 0 JoinedByString: separator];
159 - (NSString*) sortedElementsTruncated: (NSUInteger) length JoinedByString: (NSString*) separator
161 NSMutableArray* strings = [NSMutableArray array];
163 [self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
164 NSString *stringToInsert = nil;
165 if ([obj isNSString__]) {
166 stringToInsert = obj;
168 stringToInsert = [obj description];
171 if (length > 0 && length < stringToInsert.length) {
172 stringToInsert = [stringToInsert substringToIndex:length];
175 [strings insertObject:stringToInsert atIndex:0];
178 [strings sortUsingSelector: @selector(compare:)];
180 return [strings componentsJoinedByString:separator];
184 @implementation NSSet (CKDLogging)
185 - (NSString*) logKeys {
186 return [self sortedElementsJoinedByString:@" "];
189 - (NSString*) logIDs {
190 return [self sortedElementsTruncated:8 JoinedByString:@" "];
195 @interface NSDictionary (SOSDictionaryFormat)
196 - (NSString*) compactDescription;
199 @implementation NSDictionary (SOSDictionaryFormat)
200 - (NSString*) compactDescription
202 NSMutableArray *elements = [NSMutableArray array];
203 [self enumerateKeysAndObjectsUsingBlock: ^(NSString *key, id obj, BOOL *stop) {
204 [elements addObject: [key stringByAppendingString: @":"]];
205 if ([obj isKindOfClass:[NSArray class]]) {
206 [elements addObject: [(NSArray *)obj componentsJoinedByString: @" "]];
208 [elements addObject: [NSString stringWithFormat:@"%@", obj]];
211 return [elements componentsJoinedByString: @" "];
215 @interface UbiqitousKVSProxy ()
216 @property (nonatomic) NSDictionary* persistentData;
217 - (void) doSyncWithAllPeers;
218 - (void) persistState;
221 @implementation UbiqitousKVSProxy
223 + (instancetype)withAccount:(NSObject<CKDAccount>*) account
224 store:(NSObject<CKDStore>*) store
225 lockMonitor:(NSObject<CKDLockMonitor>*) lockMonitor
226 persistence:(NSURL*) localPersistence
228 return [[self alloc] initWithAccount:account
230 lockMonitor:lockMonitor
231 persistence:localPersistence];
234 - (instancetype)initWithAccount:(NSObject<CKDAccount>*) account
235 store:(NSObject<CKDStore>*) store
236 lockMonitor:(NSObject<CKDLockMonitor>*) lockMonitor
237 persistence:(NSURL*) localPersistence
239 if (self = [super init])
241 secnotice("event", "%@ start", self);
243 #if !(TARGET_OS_EMBEDDED)
244 // rdar://problem/26247270
245 if (geteuid() == 0) {
246 secerror("Cannot run CloudKeychainProxy as root");
250 _ensurePeerRegistration = NO;
252 _pendingSyncPeerIDs = [NSMutableSet set];
253 _pendingSyncBackupPeerIDs = [NSMutableSet set];
254 _shadowPendingSyncPeerIDs = nil;
255 _shadowPendingSyncBackupPeerIDs = nil;
257 _persistenceURL = localPersistence;
261 _lockMonitor = lockMonitor;
264 _calloutQueue = dispatch_queue_create("CKDCallout", DISPATCH_QUEUE_SERIAL);
265 _ckdkvsproxy_queue = dispatch_queue_create("CKDKVSProxy", DISPATCH_QUEUE_SERIAL);
267 _freshnessCompletions = [NSMutableArray<FreshnessResponseBlock> array];
269 _monitor = [NSMutableDictionary dictionary];
271 [[XPCNotificationDispatcher dispatcher] addListener: self];
273 int notificationToken;
274 notify_register_dispatch(kSecServerKeychainChangedNotification, ¬ificationToken, _ckdkvsproxy_queue,
275 ^ (int token __unused)
277 secinfo("backoff", "keychain changed, wiping backoff monitor state");
278 self->_monitor = [NSMutableDictionary dictionary];
281 [self setPersistentData: [self.persistenceURL readPlist]];
286 [[self store] connectToProxy: self];
287 [[self lockMonitor] connectTo:self];
289 secdebug(XPROXYSCOPE, "%@ done", self);
294 - (NSString *)description
296 return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s%s>",
297 [[self lockMonitor] locked] ? "L" : "U",
298 [[self lockMonitor] unlockedSinceBoot] ? "B" : "-",
299 _seenKVSStoreChange ? "K" : "-",
300 [self hasPendingNonShadowSyncIDs] ? "s" : "-",
301 _ensurePeerRegistration ? "e" : "-",
302 [_pendingKeys count] ? "p" : "-",
303 _inCallout ? "C" : "-",
304 [self hasPendingShadowSyncIDs] ? "S" : "-",
305 _shadowEnsurePeerRegistration ? "E" : "-",
306 [_shadowPendingKeys count] ? "P" : "-"];
310 // MARK: XPC Function commands
312 - (void) clearStore {
313 [self.store removeAllObjects];
316 - (void)synchronizeStore {
317 [self.store pushWrites];
320 - (id) objectForKey: (NSString*) key {
321 return [self.store objectForKey: key];
323 - (NSDictionary<NSString *, id>*) copyAsDictionary {
324 return [self.store copyAsDictionary];
327 - (void)_queue_processAllItems
329 dispatch_assert_queue(_ckdkvsproxy_queue);
331 NSDictionary *allItems = [self.store copyAsDictionary];
334 secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
335 [self processKeyChangedEvent:allItems];
338 secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
343 secdebug(XPROXYSCOPE, "%@", self);
344 [[NSNotificationCenter defaultCenter] removeObserver:self
345 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
348 [[NSNotificationCenter defaultCenter] removeObserver:self
349 name:NSUbiquityIdentityDidChangeNotification
355 - (NSDictionary*) persistentData
357 return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
358 kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
359 kKeyUnlockedKeys:[_unlockedKeys allObjects],
360 kKeyPendingKeys:[_pendingKeys allObjects],
361 kKeyPendingSyncPeerIDs:[_pendingSyncPeerIDs allObjects],
362 kKeyPendingSyncBackupPeerIDs:[_pendingSyncBackupPeerIDs allObjects],
363 kMonitorState:_monitor,
364 kKeyEnsurePeerRegistration:[NSNumber numberWithBool:_ensurePeerRegistration],
366 kKeyAccountUUID:_accountUUID
370 - (void) setPersistentData: (NSDictionary*) interests
372 _alwaysKeys = [NSMutableSet setWithArray: interests[kKeyAlwaysKeys]];
373 _firstUnlockKeys = [NSMutableSet setWithArray: interests[kKeyFirstUnlockKeys]];
374 _unlockedKeys = [NSMutableSet setWithArray: interests[kKeyUnlockedKeys]];
376 _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
378 _pendingSyncPeerIDs = [NSMutableSet setWithArray: interests[kKeyPendingSyncPeerIDs]];
379 _pendingSyncBackupPeerIDs = [NSMutableSet setWithArray: interests[kKeyPendingSyncBackupPeerIDs]];
381 _monitor = interests[kMonitorState];
383 _monitor = [NSMutableDictionary dictionary];
385 _ensurePeerRegistration = [interests[kKeyEnsurePeerRegistration] boolValue];
387 _dsid = interests[kKeyDSID];
388 _accountUUID = interests[kKeyAccountUUID];
390 // If we had a sync pending, we kick it off and migrate to sync with these peers
391 if ([interests[kKeySyncWithPeersPending] boolValue]) {
392 [self doSyncWithAllPeers];
398 NSDictionary* dataToSave = self.persistentData;
400 secdebug("persistence", "Writing registeredKeys: %@", [dataToSave compactDescription]);
401 if (![self.persistenceURL writePlist:dataToSave]) {
402 secerror("Failed to write persistence data to %@", self.persistenceURL);
407 #if TARGET_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR
408 CFStringRef const CKDAggdIncreaseThrottlingKey = CFSTR("com.apple.cloudkeychainproxy.backoff.increase");
409 CFStringRef const CKDAggdDecreaseThrottlingKey = CFSTR("com.apple.cloudkeychainproxy.backoff.decrease");
412 // MARK: Penalty measurement and handling
413 -(dispatch_source_t)setNewTimer:(int)timeout key:(NSString*)key
415 __block dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _ckdkvsproxy_queue);
416 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
417 dispatch_source_set_event_handler(timer, ^{
418 [self penaltyTimerFired:key];
420 dispatch_resume(timer);
424 -(void) increasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
426 #if TARGET_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR
427 SecADAddValueForScalarKey(CKDAggdIncreaseThrottlingKey, 1);
430 secnotice("backoff", "increasing penalty!");
432 if([currentPenalty intValue] == max_penalty_timeout){
433 newPenalty = max_penalty_timeout;
435 else if ([currentPenalty intValue] == 0)
438 newPenalty = [currentPenalty intValue]*2;
440 secnotice("backoff", "key %@, waiting %d minutes long to send next messages", key, newPenalty);
442 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
443 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
445 if(existingTimer != nil){
446 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
447 dispatch_suspend(existingTimer);
448 dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
449 dispatch_resume(existingTimer);
450 [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
453 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
454 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
457 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
458 [_monitor setObject:*keyEntry forKey:key];
461 -(void) decreasePenalty:(NSNumber*)currentPenalty key:(NSString*)key keyEntry:(NSMutableDictionary**)keyEntry
463 #if TARGET_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR
464 SecADAddValueForScalarKey(CKDAggdDecreaseThrottlingKey, 1);
468 secnotice("backoff","decreasing penalty!");
469 if([currentPenalty intValue] == 0 || [currentPenalty intValue] == 1)
472 newPenalty = [currentPenalty intValue]/2;
474 secnotice("backoff","key %@, waiting %d minutes long to send next messages", key, newPenalty);
476 NSNumber* penalty_timeout = [[NSNumber alloc]initWithInt:newPenalty];
478 dispatch_source_t existingTimer = [*keyEntry valueForKey:kMonitorPenaltyTimer];
479 if(existingTimer != nil){
480 [*keyEntry removeObjectForKey:kMonitorPenaltyTimer];
481 dispatch_suspend(existingTimer);
483 dispatch_source_set_timer(existingTimer,dispatch_time(DISPATCH_TIME_NOW, newPenalty * NSEC_PER_SEC * seconds_per_minute), DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
484 dispatch_resume(existingTimer);
485 [*keyEntry setObject:existingTimer forKey:kMonitorPenaltyTimer];
488 dispatch_resume(existingTimer);
489 dispatch_source_cancel(existingTimer);
494 dispatch_source_t timer = [self setNewTimer:newPenalty key:key];
495 [*keyEntry setObject:timer forKey:kMonitorPenaltyTimer];
499 [*keyEntry setObject:penalty_timeout forKey:kMonitorPenaltyBoxKey];
500 [_monitor setObject:*keyEntry forKey:key];
504 - (void)penaltyTimerFired:(NSString*)key
506 secnotice("backoff", "key: %@, !!!!!!!!!!!!!!!!penalty timeout is up!!!!!!!!!!!!", key);
508 NSMutableDictionary *keyEntry = [_monitor objectForKey:key];
509 NSMutableDictionary *queuedMessages = [keyEntry objectForKey:kMonitorMessageQueue];
510 secnotice("backoff","key: %@, queuedMessages: %@", key, queuedMessages);
511 if(queuedMessages && [queuedMessages count] != 0){
512 secnotice("backoff","key: %@, message queue not empty, writing to KVS!", key);
513 [self setObjectsFromDictionary:queuedMessages];
514 [keyEntry setObject:[NSMutableDictionary dictionary] forKey:kMonitorMessageQueue];
517 NSNumber *penalty_timeout = [keyEntry valueForKey:kMonitorPenaltyBoxKey];
518 secnotice("backoff", "key: %@, current penalty timeout: %@", key, penalty_timeout);
520 NSString* didWriteDuringTimeout = [keyEntry objectForKey:kMonitorDidWriteDuringPenalty];
521 if( didWriteDuringTimeout && [didWriteDuringTimeout isEqualToString:@"YES"] )
523 //increase timeout since we wrote during out penalty timeout
524 [self increasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
527 //decrease timeout since we successfully wrote messages out
528 [self decreasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
531 //resetting the check
532 [keyEntry setObject: @"NO" forKey:kMonitorDidWriteDuringPenalty];
534 //recompute the timetable and number of consecutive writes to KVS
535 NSMutableDictionary *timetable = [keyEntry valueForKey:kMonitorTimeTable];
536 NSNumber *consecutiveWrites = [keyEntry valueForKey:kMonitorConsecutiveWrites];
537 [self recordTimestampForAppropriateInterval:&timetable key:key consecutiveWrites:&consecutiveWrites];
539 [keyEntry setObject:consecutiveWrites forKey:kMonitorConsecutiveWrites];
540 [keyEntry setObject:timetable forKey:kMonitorTimeTable];
541 [_monitor setObject:keyEntry forKey:key];
544 -(NSMutableDictionary*)initializeTimeTable:(NSString*)key
546 NSDate *currentTime = [NSDate date];
547 NSMutableDictionary *firstMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute], kMonitorFirstMinute, @"YES", kMonitorWroteInTimeSlice, nil];
548 NSMutableDictionary *secondMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 2],kMonitorSecondMinute, @"NO", kMonitorWroteInTimeSlice, nil];
549 NSMutableDictionary *thirdMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 3], kMonitorThirdMinute, @"NO",kMonitorWroteInTimeSlice, nil];
550 NSMutableDictionary *fourthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 4],kMonitorFourthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
551 NSMutableDictionary *fifthMinute = [NSMutableDictionary dictionaryWithObjectsAndKeys:[currentTime dateByAddingTimeInterval: seconds_per_minute * 5], kMonitorFifthMinute, @"NO", kMonitorWroteInTimeSlice, nil];
553 NSMutableDictionary *timeTable = [NSMutableDictionary dictionaryWithObjectsAndKeys: firstMinute, kMonitorFirstMinute,
554 secondMinute, kMonitorSecondMinute,
555 thirdMinute, kMonitorThirdMinute,
556 fourthMinute, kMonitorFourthMinute,
557 fifthMinute, kMonitorFifthMinute, nil];
561 - (void)initializeKeyEntry:(NSString*)key
563 NSMutableDictionary *timeTable = [self initializeTimeTable:key];
564 NSDate *currentTime = [NSDate date];
566 NSMutableDictionary *keyEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys: key, kMonitorMessageKey, @0, kMonitorConsecutiveWrites, currentTime, kMonitorLastWriteTimestamp, @0, kMonitorPenaltyBoxKey, timeTable, kMonitorTimeTable,[NSMutableDictionary dictionary], kMonitorMessageQueue, nil];
568 [_monitor setObject:keyEntry forKey:key];
572 - (void)recordTimestampForAppropriateInterval:(NSMutableDictionary**)timeTable key:(NSString*)key consecutiveWrites:(NSNumber**)consecutiveWrites
574 NSDate *currentTime = [NSDate date];
575 __block int cWrites = [*consecutiveWrites intValue];
576 __block BOOL foundTimeSlot = NO;
577 __block NSMutableDictionary *previousTable = nil;
578 NSArray *sorted = [[*timeTable allKeys] sortedArrayUsingSelector:@selector(compare:)];
579 [sorted enumerateObjectsUsingBlock:^(id sortedKey, NSUInteger idx, BOOL *stop)
581 if(foundTimeSlot == YES)
583 [*timeTable enumerateKeysAndObjectsUsingBlock: ^(id minute, id obj, BOOL *stop2)
585 if(foundTimeSlot == YES)
587 if([sortedKey isEqualToString:minute]){
588 NSMutableDictionary *minutesTable = (NSMutableDictionary*)obj;
589 NSString *minuteKey = (NSString*)minute;
590 NSDate *date = [minutesTable valueForKey:minuteKey];
591 if([date compare:currentTime] == NSOrderedDescending){
593 NSString* written = [minutesTable valueForKey:kMonitorWroteInTimeSlice];
594 if([written isEqualToString:@"NO"]){
595 [minutesTable setObject:@"YES" forKey:kMonitorWroteInTimeSlice];
596 if(previousTable != nil){
597 written = [previousTable valueForKey:kMonitorWroteInTimeSlice];
598 if([written isEqualToString:@"YES"]){
601 else if ([written isEqualToString:@"NO"]){
608 previousTable = minutesTable;
613 if(foundTimeSlot == NO){
614 //reset the time table
615 secnotice("backoff","didn't find a time slot, resetting the table");
616 NSMutableDictionary *lastTable = [*timeTable valueForKey:kMonitorFifthMinute];
617 NSDate *lastDate = [lastTable valueForKey:kMonitorFifthMinute];
619 if((double)[currentTime timeIntervalSinceDate: lastDate] >= seconds_per_minute){
620 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
623 NSString* written = [lastTable valueForKey:kMonitorWroteInTimeSlice];
624 if([written isEqualToString:@"YES"]){
626 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
629 *consecutiveWrites = [[NSNumber alloc]initWithInt:0];
633 *timeTable = [self initializeTimeTable:key];
636 *consecutiveWrites = [[NSNumber alloc]initWithInt:cWrites];
638 - (void)recordWriteToKVS:(NSDictionary *)values
640 if([_monitor count] == 0){
641 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
643 [self initializeKeyEntry: key];
647 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
649 NSMutableDictionary *keyEntry = [self->_monitor objectForKey:key];
651 [self initializeKeyEntry: key];
654 NSNumber *penalty_timeout = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
655 NSDate *lastWriteTimestamp = [keyEntry objectForKey:kMonitorLastWriteTimestamp];
656 NSMutableDictionary *timeTable = [keyEntry objectForKey: kMonitorTimeTable];
657 NSNumber *existingWrites = [keyEntry objectForKey: kMonitorConsecutiveWrites];
658 NSDate *currentTime = [NSDate date];
660 [self recordTimestampForAppropriateInterval:&timeTable key:key consecutiveWrites:&existingWrites];
662 int consecutiveWrites = [existingWrites intValue];
663 secnotice("backoff","consecutive writes: %d", consecutiveWrites);
664 [keyEntry setObject:existingWrites forKey:kMonitorConsecutiveWrites];
665 [keyEntry setObject:timeTable forKey:kMonitorTimeTable];
666 [keyEntry setObject:currentTime forKey:kMonitorLastWriteTimestamp];
667 [self->_monitor setObject:keyEntry forKey:key];
669 if([penalty_timeout intValue] != 0 || ((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites >= 5)){
670 if([penalty_timeout intValue] != 0 && consecutiveWrites == 5){
671 secnotice("backoff","written for 5 consecutive minutes, time to start throttling");
672 [self increasePenalty:penalty_timeout key:key keyEntry:&keyEntry];
675 secnotice("backoff","monitor: keys have been written for 5 or more minutes, recording we wrote during timeout");
677 //record we wrote during a timeout
678 [keyEntry setObject: @"YES" forKey:kMonitorDidWriteDuringPenalty];
680 //keep writing freely but record it
681 else if((double)[currentTime timeIntervalSinceDate: lastWriteTimestamp] <= 60 && consecutiveWrites < 5){
682 secnotice("backoff","monitor: still writing freely");
689 - (NSDictionary*)recordHaltedValuesAndReturnValuesToSafelyWrite:(NSDictionary *)values
691 NSMutableDictionary *SafeMessages = [NSMutableDictionary dictionary];
692 [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
694 NSMutableDictionary *keyEntry = [self->_monitor objectForKey:key];
695 NSNumber *penalty = [keyEntry objectForKey:kMonitorPenaltyBoxKey];
696 if([penalty intValue] != 0){
697 NSMutableDictionary* existingQueue = [keyEntry valueForKey:kMonitorMessageQueue];
699 [existingQueue setObject:obj forKey:key];
701 [keyEntry setObject:existingQueue forKey:kMonitorMessageQueue];
702 [self->_monitor setObject:keyEntry forKey:key];
705 [SafeMessages setObject:obj forKey:key];
711 // MARK: Object setting
714 - (void)setStoreObjectsFromDictionary:(NSDictionary *)values
717 secdebug(XPROXYSCOPE, "%@ NULL? values: %@", self, values);
721 NSMutableDictionary<NSString*, NSObject*> *mutableValues = [values mutableCopy];
722 NSString* newDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSOfficialDSIDKey]);
727 NSString* requiredDSID = asNSString([mutableValues extractObjectForKey:(__bridge NSString*) kSOSKVSRequiredKey]);
729 if (_dsid == nil || [_dsid isEqualToString: @""]) {
730 secdebug("dsid", "CloudKeychainProxy setting dsid to :%@ from securityd", requiredDSID);
731 _dsid = requiredDSID;
732 } else if (![_dsid isEqual: requiredDSID]) {
733 secerror("Account DSIDs do not match, cloud keychain proxy: %@, securityd: %@", _dsid, requiredDSID);
734 secerror("Not going to write these: %@ into KVS!", values);
737 secnoticeq("dsid", "DSIDs match, writing");
741 secnoticeq("keytrace", "%@ sending: %@", self, [[mutableValues allKeys] componentsJoinedByString: @" "]);
742 [mutableValues enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
744 if (obj == NULL || obj == [NSNull null]) {
745 [self.store removeObjectForKey:key];
747 if ([key hasPrefix:@"ak|"]) { // TODO: somewhat of a hack
748 id oldObj = [self.store objectForKey:key];
749 if ([oldObj isEqual: obj]) {
750 // Fix KVS repeated message undelivery by sending a NULL first (deafness)
751 secnoticeq("keytrace", "forcing resend of key write: %@", key);
752 [self.store removeObjectForKey:key];
755 [self.store setObject:obj forKey:key];
759 [self.store pushWrites];
762 - (void)setObjectsFromDictionary:(NSDictionary<NSString*, NSObject*> *)values
764 [self recordWriteToKVS: values];
765 NSDictionary *safeValues = [self recordHaltedValuesAndReturnValuesToSafelyWrite: values];
766 if([safeValues count] !=0){
767 [self setStoreObjectsFromDictionary:safeValues];
771 - (void)waitForSynchronization:(void (^)(NSDictionary<NSString*, NSObject*> *results, NSError *err))handler
773 secnoticeq("fresh", "%s Requesting WFS", kWAIT2MINID);
775 [_freshnessCompletions addObject: ^(bool success, NSError *error){
776 secnoticeq("fresh", "%s WFS Done", kWAIT2MINID);
780 if ([self.freshnessCompletions count] == 1) {
781 // We can't talk to synchronize on the _ckdkvsproxy_queue or we deadlock,
782 // bounce to a global concurrent queue
783 dispatch_after(_nextFreshnessTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
784 NSError *error = nil;
785 bool success = [self.store pullUpdates:&error];
787 dispatch_async(self->_ckdkvsproxy_queue, ^{
788 [self waitForSyncDone: success error: error];
794 - (void) waitForSyncDone: (bool) success error: (NSError*) error{
796 const uint64_t delayBeforeCallingAgainInSeconds = 5ull * NSEC_PER_SEC;
797 _nextFreshnessTime = dispatch_time(DISPATCH_TIME_NOW, delayBeforeCallingAgainInSeconds);
800 secnoticeq("fresh", "%s Completing WFS", kWAIT2MINID);
801 [_freshnessCompletions enumerateObjectsUsingBlock:^(FreshnessResponseBlock _Nonnull block,
803 BOOL * _Nonnull stop) {
804 block(success, error);
806 [_freshnessCompletions removeAllObjects];
811 // MARK: ----- KVS key lists -----
814 - (NSMutableSet *)copyAllKeyInterests
816 NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
817 [allKeys unionSet: _firstUnlockKeys];
818 [allKeys unionSet: _unlockedKeys];
822 -(void)registerAtTimeKeys:(NSDictionary*)keyparms
827 NSArray *alwaysArray = [keyparms valueForKey: kKeyAlwaysKeys];
828 NSArray *firstUnlockedKeysArray = [keyparms valueForKey: kKeyFirstUnlockKeys];
829 NSArray *whenUnlockedKeysArray = [keyparms valueForKey: kKeyUnlockedKeys];
832 [_alwaysKeys unionSet: [NSMutableSet setWithArray: alwaysArray]];
833 if(firstUnlockedKeysArray)
834 [_firstUnlockKeys unionSet: [NSMutableSet setWithArray: firstUnlockedKeysArray]];
835 if(whenUnlockedKeysArray)
836 [_unlockedKeys unionSet: [NSMutableSet setWithArray: whenUnlockedKeysArray]];
840 - (void)registerKeys: (NSDictionary*)keys forAccount: (NSString*) accountUUID
842 secdebug(XPROXYSCOPE, "registerKeys: keys: %@", keys);
844 // We only reset when we know the ID and they send the ID and it changes.
845 bool newAccount = accountUUID != nil && self.accountUUID != nil && ![accountUUID isEqualToString: self.accountUUID];
848 self.accountUUID = accountUUID;
851 // If we're a new account we don't exclude the old keys
852 NSMutableSet *allOldKeys = newAccount ? [NSMutableSet set] : [self copyAllKeyInterests];
855 NSDictionary *keyparms = [keys valueForKey: [NSString stringWithUTF8String: kMessageKeyParameter]];
856 NSDictionary *circles = [keys valueForKey: [NSString stringWithUTF8String: kMessageCircle]];
857 NSDictionary *messages = [keys valueForKey: [NSString stringWithUTF8String: kMessageMessage]];
859 _alwaysKeys = [NSMutableSet set];
860 _firstUnlockKeys = [NSMutableSet set];
861 _unlockedKeys = [NSMutableSet set];
863 [self registerAtTimeKeys: keyparms];
864 [self registerAtTimeKeys: circles];
865 [self registerAtTimeKeys: messages];
867 NSMutableSet *allNewKeys = [self copyAllKeyInterests];
869 // Make sure keys we no longer care about are not pending
870 [_pendingKeys intersectSet:allNewKeys];
871 if (_shadowPendingKeys) {
872 [_shadowPendingKeys intersectSet:allNewKeys];
875 // All new keys only is new keys (remove old keys)
876 [allNewKeys minusSet:allOldKeys];
878 // Mark new keys pending, they're new!
879 NSMutableSet *newKeysForCurrentLockState = [self pendKeysAndGetNewlyPended:allNewKeys];
881 [self persistState]; // Before we might call out, save our state so we recover if we crash
883 [self intersectWithCurrentLockState: newKeysForCurrentLockState];
884 // TODO: Don't processPendingKeysForCurrentLockState if none of the new keys have values.
885 if ([newKeysForCurrentLockState count] != 0) {
886 [self processPendingKeysForCurrentLockState];
890 // MARK: ----- Event Handling -----
892 - (void)_queue_handleNotification:(const char *) name
894 dispatch_assert_queue(_ckdkvsproxy_queue);
896 if (strcmp(name, kNotifyTokenForceUpdate)==0) {
897 // DEBUG -- Possibly remove in future
898 [self _queue_processAllItems];
899 } else if (strcmp(name, kCloudKeychainStorechangeChangeNotification)==0) {
900 // DEBUG -- Possibly remove in future
901 [self _queue_kvsStoreChange];
905 - (void)_queue_storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
907 dispatch_assert_queue(_ckdkvsproxy_queue);
909 // Mark that our store is talking to us, so we don't have to make up for missing anything previous.
910 _seenKVSStoreChange = YES;
912 // Unmark them as pending as they have just changed and we'll process them.
913 [_pendingKeys minusSet:changedKeys];
915 // Only send values that we're currently interested in.
916 NSSet *keysOfInterestThatChanged = [self pendKeysAndGetPendingForCurrentLockState:changedKeys];
917 NSMutableDictionary *changedValues = [self copyValues:keysOfInterestThatChanged];
919 changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] = @"true";
921 secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@ initial: %@",
923 [[changedKeys allObjects] componentsJoinedByString: @" "],
924 [[changedValues allKeys] componentsJoinedByString: @" "],
925 initial ? @"YES" : @"NO");
927 if ([changedValues count])
928 [self processKeyChangedEvent:changedValues];
931 - (void)_queue_storeAccountChanged
933 dispatch_assert_queue(_ckdkvsproxy_queue);
935 secnotice("event", "%@", self);
937 NSDictionary *changedValues = nil;
939 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: _dsid };
941 changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey: @"true" };
943 [self processKeyChangedEvent:changedValues];
946 - (void) doAfterFlush: (dispatch_block_t) block
948 //Flush any pending communication to Securityd.
950 dispatch_async(_calloutQueue, block);
952 _shadowFlushBlock = block;
955 - (void) calloutWith: (void(^)(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error))) callout
957 // In CKDKVSProxy's serial queue
959 // dispatch_get_global_queue - well-known global concurrent queue
960 // dispatch_get_main_queue - default queue that is bound to the main thread
961 xpc_transaction_begin();
962 dispatch_async(_calloutQueue, ^{
963 __block NSSet *myPending;
964 __block NSSet *mySyncPeerIDs;
965 __block NSSet *mySyncBackupPeerIDs;
966 __block bool myEnsurePeerRegistration;
967 __block bool wasLocked;
968 dispatch_sync(self->_ckdkvsproxy_queue, ^{
969 myPending = [self->_pendingKeys copy];
970 mySyncPeerIDs = [self->_pendingSyncPeerIDs copy];
971 mySyncBackupPeerIDs = [self->_pendingSyncBackupPeerIDs copy];
973 myEnsurePeerRegistration = self->_ensurePeerRegistration;
974 wasLocked = [self.lockMonitor locked];
976 self->_inCallout = YES;
978 self->_shadowPendingKeys = [NSMutableSet set];
979 self->_shadowPendingSyncPeerIDs = [NSMutableSet set];
980 self->_shadowPendingSyncBackupPeerIDs = [NSMutableSet set];
983 callout(myPending, mySyncPeerIDs, mySyncBackupPeerIDs, myEnsurePeerRegistration, self->_ckdkvsproxy_queue, ^(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* failure) {
984 secdebug("event", "%@ %s%s before callout handled: %s%s", self,
985 ![mySyncPeerIDs isEmpty] || ![mySyncBackupPeerIDs isEmpty] ? "S" : "s",
986 myEnsurePeerRegistration ? "E" : "e",
987 ![handledKeys isEmpty] ? "S" : "s",
988 handledEnsurePeerRegistration ? "E" : "e");
990 // In CKDKVSProxy's serial queue
991 self->_inCallout = NO;
993 // Update ensurePeerRegistration
994 self->_ensurePeerRegistration = ((self->_ensurePeerRegistration && !handledEnsurePeerRegistration) || self->_shadowEnsurePeerRegistration);
996 self->_shadowEnsurePeerRegistration = NO;
998 if(self->_ensurePeerRegistration && ![self.lockMonitor locked])
999 [self doEnsurePeerRegistration];
1001 bool hadShadowPeerIDs = ![self->_shadowPendingSyncPeerIDs isEmpty] || ![self->_shadowPendingSyncBackupPeerIDs isEmpty];
1003 // Update SyncWithPeers stuff.
1005 [self->_pendingSyncPeerIDs minusSet: handledSyncs];
1006 [self->_pendingSyncBackupPeerIDs minusSet: handledSyncs];
1008 if (![handledSyncs isEmpty]) {
1009 secnotice("sync-ids", "handled syncIDs: %@", [handledSyncs logIDs]);
1010 secnotice("sync-ids", "remaining peerIDs: %@", [self->_pendingSyncPeerIDs logIDs]);
1011 secnotice("sync-ids", "remaining backupIDs: %@", [self->_pendingSyncBackupPeerIDs logIDs]);
1013 if (hadShadowPeerIDs) {
1014 secnotice("sync-ids", "signaled peerIDs: %@", [self->_shadowPendingSyncPeerIDs logIDs]);
1015 secnotice("sync-ids", "signaled backupIDs: %@", [self->_shadowPendingSyncBackupPeerIDs logIDs]);
1019 self->_shadowPendingSyncPeerIDs = nil;
1020 self->_shadowPendingSyncBackupPeerIDs = nil;
1024 // Update pendingKeys and handle them
1025 [self->_pendingKeys removeObject: [NSNull null]]; // Don't let NULL hang around
1027 [self->_pendingKeys minusSet: handledKeys];
1028 bool hadShadowPendingKeys = [self->_shadowPendingKeys count];
1029 // Move away shadownPendingKeys first, because pendKeysAndGetPendingForCurrentLockState
1030 // will look at them. See rdar://problem/20733166.
1031 NSSet *oldShadowPendingKeys = self->_shadowPendingKeys;
1032 self->_shadowPendingKeys = nil;
1034 NSSet *filteredKeys = [self pendKeysAndGetPendingForCurrentLockState:oldShadowPendingKeys];
1036 secnoticeq("keytrace", "%@ account handled: %@ pending: %@", self,
1037 [[handledKeys allObjects] componentsJoinedByString: @" "],
1038 [[filteredKeys allObjects] componentsJoinedByString: @" "]);
1040 // Write state to disk
1041 [self persistState];
1043 // Handle shadow pended stuff
1045 // We only kick off another sync if we got new stuff during handling
1046 if (hadShadowPeerIDs && ![self.lockMonitor locked])
1047 [self newPeersToSyncWith];
1049 /* We don't want to call processKeyChangedEvent if we failed to
1050 handle pending keys and the device didn't unlock nor receive
1051 any kvs changes while we were in our callout.
1052 Doing so will lead to securityd and CloudKeychainProxy
1053 talking to each other forever in a tight loop if securityd
1054 repeatedly returns an error processing the same message.
1055 Instead we leave any old pending keys until the next event. */
1056 if (hadShadowPendingKeys || (![self.lockMonitor locked] && wasLocked)){
1057 [self processKeyChangedEvent:[self copyValues:filteredKeys]];
1058 if(self->_shadowFlushBlock != NULL)
1059 secerror("Flush block is not null and sending new keys");
1062 if(self->_shadowFlushBlock != NULL){
1063 dispatch_async(self->_calloutQueue, self->_shadowFlushBlock);
1064 self->_shadowFlushBlock = NULL;
1068 [self.lockMonitor recheck];
1071 xpc_transaction_end();
1076 - (void) sendKeysCallout: (NSSet *(^)(NSSet* pending, NSError** error)) handleKeys {
1077 [self calloutWith: ^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
1078 NSError* error = NULL;
1080 secnotice("CloudKeychainProxy", "send keys: %@", pending);
1081 NSSet * handled = handleKeys(pending, &error);
1083 dispatch_async(queue, ^{
1085 secerror("%@ ensurePeerRegistration failed: %@", self, error);
1088 done(handled, nil, NO, error);
1093 - (void) doEnsurePeerRegistration
1095 NSObject<CKDAccount>* accountDelegate = [self account];
1096 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
1097 NSError* error = nil;
1098 bool handledEnsurePeerRegistration = [accountDelegate ensurePeerRegistration:&error];
1099 secnotice("EnsurePeerRegistration", "%@ ensurePeerRegistration called, %@ (%@)", self, handledEnsurePeerRegistration ? @"success" : @"failure", error);
1100 if (!handledEnsurePeerRegistration) {
1101 [self.lockMonitor recheck];
1102 handledEnsurePeerRegistration = ![self.lockMonitor locked]; // If we're unlocked we handled it, if we're locked we didn't.
1103 // This means we get to fail once per unlock and then cut that spinning out.
1105 dispatch_async(queue, ^{
1106 done(nil, nil, handledEnsurePeerRegistration, error);
1111 - (void) doSyncWithPendingPeers
1113 NSObject<CKDAccount>* accountDelegate = [self account];
1114 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError* error)) {
1115 NSError* error = NULL;
1116 secnotice("syncwith", "%@ syncwith peers: %@", self, [[pendingSyncIDs allObjects] componentsJoinedByString:@" "]);
1117 secnotice("syncwith", "%@ syncwith backups: %@", self, [[pendingBackupSyncIDs allObjects] componentsJoinedByString:@" "]);
1118 NSSet<NSString*>* handled = [accountDelegate syncWithPeers:pendingSyncIDs backups:pendingBackupSyncIDs error:&error];
1119 secnotice("syncwith", "%@ syncwith handled: %@", self, [[handled allObjects] componentsJoinedByString:@" "]);
1120 dispatch_async(queue, ^{
1122 // We might be confused about lock state
1123 [self.lockMonitor recheck];
1126 done(nil, handled, false, error);
1131 - (void) doSyncWithAllPeers
1133 NSObject<CKDAccount>* accountDelegate = [self account];
1134 [self calloutWith:^(NSSet *pending, NSSet* pendingSyncIDs, NSSet* pendingBackupSyncIDs, bool ensurePeerRegistration, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, NSSet *handledSyncs, bool handledEnsurePeerRegistration, NSError*error)) {
1135 NSError* error = NULL;
1136 bool handled = [accountDelegate syncWithAllPeers:&error];
1138 secerror("Failed to syncWithAllPeers: %@", error);
1140 dispatch_async(queue, ^{
1141 done(nil, nil, false, error);
1146 - (void)newPeersToSyncWith
1148 secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, [self hasPendingSyncIDs], _inCallout, [self.lockMonitor locked]);
1149 if(_ensurePeerRegistration){
1150 [self doEnsurePeerRegistration];
1152 if ([self hasPendingSyncIDs] && !_inCallout && ![self.lockMonitor locked]){
1153 [self doSyncWithPendingPeers];
1157 - (bool)hasPendingNonShadowSyncIDs {
1158 return ![_pendingSyncPeerIDs isEmpty] || ![_pendingSyncBackupPeerIDs isEmpty];
1161 - (bool)hasPendingShadowSyncIDs {
1162 return (_shadowPendingSyncPeerIDs && ![_shadowPendingSyncPeerIDs isEmpty]) ||
1163 (_shadowPendingSyncBackupPeerIDs && ![_shadowPendingSyncBackupPeerIDs isEmpty]);
1166 - (bool)hasPendingSyncIDs
1168 bool pendingIDs = [self hasPendingNonShadowSyncIDs];
1171 pendingIDs |= [self hasPendingShadowSyncIDs];
1177 - (void)requestSyncWithPeerIDs: (NSArray<NSString*>*) peerIDs backupPeerIDs: (NSArray<NSString*>*) backupPeerIDs
1179 if ([peerIDs count] == 0 && [backupPeerIDs count] == 0)
1180 return; // Nothing to do;
1182 NSSet<NSString*>* peerIDsSet = [NSSet setWithArray: peerIDs];
1183 NSSet<NSString*>* backupPeerIDsSet = [NSSet setWithArray: backupPeerIDs];
1185 [_pendingSyncPeerIDs unionSet: peerIDsSet];
1186 [_pendingSyncBackupPeerIDs unionSet: backupPeerIDsSet];
1189 [_shadowPendingSyncPeerIDs unionSet: peerIDsSet];
1190 [_shadowPendingSyncBackupPeerIDs unionSet: backupPeerIDsSet];
1193 [self persistState];
1195 if(_ensurePeerRegistration){
1196 [self doEnsurePeerRegistration];
1198 if ([self hasPendingSyncIDs] && !_inCallout && ![self.lockMonitor locked]){
1199 [self doSyncWithPendingPeers];
1203 - (BOOL)hasSyncPendingFor: (NSString*) peerID {
1204 return [_pendingSyncPeerIDs containsObject: peerID] ||
1205 (_shadowPendingSyncPeerIDs && [_shadowPendingSyncPeerIDs containsObject: peerID]);
1208 - (BOOL)hasPendingKey: (NSString*) keyName {
1209 return [self.pendingKeys containsObject: keyName]
1210 || (_shadowPendingKeys && [self.shadowPendingKeys containsObject: keyName]);
1213 - (void)requestEnsurePeerRegistration
1215 #if !defined(NDEBUG)
1216 NSString *desc = [self description];
1220 _shadowEnsurePeerRegistration = YES;
1222 _ensurePeerRegistration = YES;
1223 if (![self.lockMonitor locked]){
1224 [self doEnsurePeerRegistration];
1226 [self persistState];
1229 secdebug("event", "%@ %@", desc, self);
1232 - (void)_queue_locked
1234 dispatch_assert_queue(_ckdkvsproxy_queue);
1236 secnotice("event", "%@ Locked", self);
1239 - (void)_queue_unlocked
1241 dispatch_assert_queue(_ckdkvsproxy_queue);
1243 secnotice("event", "%@ Unlocked", self);
1244 if (_ensurePeerRegistration) {
1245 [self doEnsurePeerRegistration];
1248 // First send changed keys to securityd so it can proccess updates
1249 [self processPendingKeysForCurrentLockState];
1251 // Then, tickle securityd to perform a sync if needed.
1252 if ([self hasPendingSyncIDs]) {
1253 [self doSyncWithPendingPeers];
1257 - (void) _queue_kvsStoreChange {
1258 dispatch_assert_queue(_ckdkvsproxy_queue);
1260 os_activity_initiate("kvsStoreChange", OS_ACTIVITY_FLAG_DEFAULT, ^{
1261 if (!self->_seenKVSStoreChange) {
1262 secnotice("event", "%@ received darwin notification before first NSNotification", self);
1263 // TODO This might not be needed if we always get the NSNotification
1264 // deleived even if we were launched due to a kvsStoreChange
1265 // Send all keys for current lock state to securityd so it can proccess them
1266 [self pendKeysAndGetNewlyPended: [self copyAllKeyInterests]];
1267 [self processPendingKeysForCurrentLockState];
1269 secdebug("event", "%@ ignored, waiting for NSNotification", self);
1275 #pragma mark XPCNotificationListener
1277 - (void)handleNotification:(const char *) name
1279 // sync because we cannot ensure the lifetime of name
1280 dispatch_sync(_ckdkvsproxy_queue, ^{
1281 [self _queue_handleNotification:name];
1286 #pragma mark Calls from -[CKDKVSStore kvsStoreChanged:]
1288 - (void)storeKeysChanged: (NSSet<NSString*>*) changedKeys initial: (bool) initial
1290 // sync, caller must wait to ensure correct state
1291 dispatch_sync(_ckdkvsproxy_queue, ^{
1292 [self _queue_storeKeysChanged:changedKeys initial:initial];
1296 - (void)storeAccountChanged
1298 // sync, caller must wait to ensure correct state
1299 dispatch_sync(_ckdkvsproxy_queue, ^{
1300 [self _queue_storeAccountChanged];
1305 #pragma mark CKDLockListener
1309 // sync, otherwise tests fail
1310 dispatch_sync(_ckdkvsproxy_queue, ^{
1311 [self _queue_locked];
1317 // sync, otherwise tests fail
1318 dispatch_sync(_ckdkvsproxy_queue, ^{
1319 [self _queue_unlocked];
1324 // MARK: ----- Key Filtering -----
1327 - (NSSet*) keysForCurrentLockState
1329 secdebug("filtering", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, keysOfInterest: <%@>", self, (int) [self.lockMonitor unlockedSinceBoot], (int) ![self.lockMonitor locked], [self.persistentData compactDescription]);
1331 NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
1332 if ([self.lockMonitor unlockedSinceBoot])
1333 [currentStateKeys unionSet: _firstUnlockKeys];
1335 if (![self.lockMonitor locked])
1336 [currentStateKeys unionSet: _unlockedKeys];
1338 return currentStateKeys;
1342 - (NSMutableSet*) pendKeysAndGetNewlyPended: (NSSet*) keysToPend
1344 NSMutableSet *filteredKeysToPend = [self copyAllKeyInterests];
1345 [filteredKeysToPend intersectSet: keysToPend];
1347 NSMutableSet *newlyPendedKeys = [filteredKeysToPend mutableCopy];
1348 [newlyPendedKeys minusSet: _pendingKeys];
1349 if (_shadowPendingKeys) {
1350 [newlyPendedKeys minusSet: _shadowPendingKeys];
1353 if (_shadowPendingKeys) {
1354 [_shadowPendingKeys unionSet:filteredKeysToPend];
1357 [_pendingKeys unionSet:filteredKeysToPend];
1360 return newlyPendedKeys;
1363 - (void) intersectWithCurrentLockState: (NSMutableSet*) set
1365 [set intersectSet: [self keysForCurrentLockState]];
1368 - (NSMutableSet*) pendingKeysForCurrentLockState
1370 NSMutableSet * result = [_pendingKeys mutableCopy];
1371 [self intersectWithCurrentLockState:result];
1375 - (NSMutableSet*) pendKeysAndGetPendingForCurrentLockState: (NSSet*) startingSet
1377 [self pendKeysAndGetNewlyPended: startingSet];
1379 return [self pendingKeysForCurrentLockState];
1382 - (NSMutableDictionary *)copyValues:(NSSet*)keysOfInterest
1384 // Grab values from store.
1385 NSObject<CKDStore> *store = [self store];
1386 NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
1387 [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
1389 NSString* key = (NSString*) obj;
1390 id objval = [store objectForKey:key];
1391 if (!objval) objval = [NSNull null];
1393 [changedValues setObject:objval forKey:key];
1394 secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
1396 return changedValues;
1400 During RegisterKeys, separate keys-of-interest into three disjoint sets:
1401 - keys that we always want to be notified about; this means we can get the
1403 - keys that require the device to have been unlocked at least once
1404 - keys that require the device to be unlocked now
1406 Typically, the sets of keys will be:
1412 The caller is responsible for making sure that the keys in e.g. alwaysKeys are
1413 values that can be handled at any time (that is, not when unlocked)
1415 Each time we get a notification from ubiquity that keys have changed, we need to
1416 see if anything of interest changed. If we don't care, then done.
1418 For each key-of-interest that changed, we either notify the client that things
1419 changed, or add it to a pendingNotifications list. If the notification to the
1420 client fails, also add it to the pendingNotifications list. This pending list
1421 should be written to persistent storage and consulted any time we either get an
1422 item changed notification, or get a stream event signalling a change in lock state.
1424 We can notify the client either through XPC if a connection is set up, or call a
1425 routine in securityd to launch it.
1429 - (void)processKeyChangedEvent:(NSDictionary *)changedValues
1431 NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
1433 secnotice("processKeyChangedEvent", "changedValues:%@", changedValues);
1434 NSMutableArray* nullKeys = [NSMutableArray array];
1435 // Remove nulls because we don't want them in securityd.
1436 [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
1437 if (obj == [NSNull null]){
1438 [nullKeys addObject:key];
1440 filtered[key] = obj;
1443 if ([nullKeys count])
1444 [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
1446 if([filtered count] != 0 ) {
1447 [self sendKeysCallout:^NSSet *(NSSet *pending, NSError** error) {
1448 secnotice("processing keys", "pending:%@", pending);
1449 NSError *updateError = nil;
1450 return [[self account] keysChanged: filtered error: &updateError];
1453 secnoticeq("keytrace", "%@ null: %@ pending: %@", self,
1454 [nullKeys componentsJoinedByString: @" "],
1455 [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
1459 - (void) processPendingKeysForCurrentLockState
1461 [self processKeyChangedEvent: [self copyValues: [self pendingKeysForCurrentLockState]]];