5 // Created by Mitch Adler on 5/15/16.
9 #import "CKDKVSStore.h"
10 #import "CKDKVSProxy.h"
12 #include "SOSCloudKeychainConstants.h"
13 #include <utilities/debugging.h>
15 #import <Foundation/NSUbiquitousKeyValueStore.h>
16 #import <Foundation/NSUbiquitousKeyValueStore_Private.h>
17 #import "SyncedDefaults/SYDConstants.h"
18 #include <os/activity.h>
20 struct CKDKVSCounters {
22 uint64_t synchronizeWithCompletionHandler;
23 uint64_t incomingMessages;
24 uint64_t outgoingMessages;
25 uint64_t totalWaittimeSynchronize;
26 uint64_t longestWaittimeSynchronize;
27 uint64_t synchronizeFailures;
32 @interface CKDKVSStore ()
33 @property (readwrite, weak) UbiqitousKVSProxy* proxy;
34 @property (readwrite) NSUbiquitousKeyValueStore* cloudStore;
35 @property (assign,readwrite) struct CKDKVSCounters *perfCounters;
36 @property dispatch_queue_t perfQueue;
39 @implementation CKDKVSStore
41 + (instancetype)kvsInterface {
42 return [[CKDKVSStore alloc] init];
45 - (instancetype)init {
48 self->_cloudStore = [NSUbiquitousKeyValueStore defaultStore];
51 if (!self.cloudStore) {
52 secerror("NO NSUbiquitousKeyValueStore defaultStore!!!");
55 self.perfQueue = dispatch_queue_create("CKDKVSStorePerfQueue", NULL);
56 self.perfCounters = calloc(1, sizeof(struct CKDKVSCounters));
67 - (void) connectToProxy: (UbiqitousKVSProxy*) proxy {
70 [[NSNotificationCenter defaultCenter] addObserver:self
71 selector:@selector(kvsStoreChangedAsync:)
72 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
77 - (void)setObject:(id)obj forKey:(NSString*)key {
78 secdebug("kvsdebug", "%@ key %@ set to: %@ from: %@", self, key, obj, [self.cloudStore objectForKey:key]);
79 [self.cloudStore setObject:obj forKey:key];
82 - (NSDictionary<NSString *, id>*) copyAsDictionary {
83 return [self.cloudStore dictionaryRepresentation];
86 - (void)addEntriesFromDictionary:(NSDictionary<NSString*, NSObject*> *)otherDictionary {
87 [otherDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSObject * _Nonnull obj, BOOL * _Nonnull stop) {
88 [self setObject:obj forKey:key];
92 - (id)objectForKey:(NSString*)key {
93 return [self.cloudStore objectForKey:key];
96 - (void)removeObjectForKey:(NSString*)key {
97 return [self.cloudStore removeObjectForKey:key];
100 - (void)removeAllObjects {
101 [[[[self.cloudStore dictionaryRepresentation] allKeys] copy] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
102 [self.cloudStore removeObjectForKey:obj];
107 [[self cloudStore] synchronize];
108 dispatch_async(self.perfQueue, ^{
109 self.perfCounters->synchronize++;
113 // Runs on the same thread that posted the notification, and that thread _may_ be the
114 // kdkvsproxy_queue (see 30470419). Avoid deadlock by bouncing through global queue.
115 - (void)kvsStoreChangedAsync:(NSNotification *)notification
117 secnotice("CloudKeychainProxy", "%@ KVS Remote changed notification: %@", self, notification);
118 dispatch_async(self.perfQueue, ^{
119 self.perfCounters->incomingMessages++;
121 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
122 [self kvsStoreChanged:notification];
126 - (void) kvsStoreChanged:(NSNotification *)notification {
128 Posted when the value of one or more keys in the local key-value store
129 changed due to incoming data pushed from iCloud. This notification is
130 sent only upon a change received from iCloud; it is not sent when your
133 The user info dictionary can contain the reason for the notification as
134 well as a list of which values changed, as follows:
136 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
137 present, indicates why the key-value store changed. Its value is one of
138 the constants in "Change Reason Values."
140 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
141 is an array of strings, each the name of a key whose value changed. The
142 notification object is the NSUbiquitousKeyValueStore object whose contents
145 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
146 local value that has been overwritten by a distant value. If there is no
147 conflict between the local and the distant values when doing the initial
148 sync (e.g. if the cloud has no data stored or the client has not stored
149 any data yet), you'll never see that notification.
151 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
152 with server but initial round trip with server does not imply
153 NSUbiquitousKeyValueStoreInitialSyncChange.
155 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
156 secdebug(XPROXYSCOPE, "%@ KVS Remote changed notification: %@", self, notification);
158 UbiqitousKVSProxy* proxy = self.proxy;
160 // Weak reference went away.
164 NSDictionary *userInfo = [notification userInfo];
165 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
166 NSArray *keysChangedArray = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
167 NSSet *keysChanged = keysChangedArray ? [NSSet setWithArray: keysChangedArray] : nil;
169 if (reason) switch ([reason integerValue]) {
170 case NSUbiquitousKeyValueStoreInitialSyncChange:
171 [proxy storeKeysChanged: keysChanged initial: YES];
174 case NSUbiquitousKeyValueStoreServerChange:
175 [proxy storeKeysChanged: keysChanged initial: NO];
178 case NSUbiquitousKeyValueStoreQuotaViolationChange:
179 seccritical("Received NSUbiquitousKeyValueStoreQuotaViolationChange");
182 case NSUbiquitousKeyValueStoreAccountChange:
183 [proxy storeAccountChanged];
187 secinfo("kvsstore", "ignoring unknown notification: %@", reason);
193 - (BOOL) pullUpdates:(NSError **)failure
195 __block NSError *tempFailure = nil;
197 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
199 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
201 dispatch_async(self.perfQueue, ^{
202 self.perfCounters->synchronizeWithCompletionHandler++;
205 [[self cloudStore] synchronizeWithCompletionHandler:^(NSError *error) {
207 dispatch_async(self.perfQueue, ^{
208 self.perfCounters->synchronizeFailures++;
211 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
213 dispatch_async(self.perfQueue, ^{
214 self.perfCounters->synchronize++;
216 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
217 [[self cloudStore] synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
218 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
220 dispatch_semaphore_signal(freshSemaphore);
222 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
224 if (failure && (*failure == NULL)) {
225 *failure = tempFailure;
228 return tempFailure == nil;
231 - (void)perfCounters:(void(^)(NSDictionary *counters))callback
233 dispatch_async(self.perfQueue, ^{
235 @"CKDKVS-synchronize" : @(self.perfCounters->synchronize),
236 @"CKDKVS-synchronizeWithCompletionHandler" : @(self.perfCounters->synchronizeWithCompletionHandler),
237 @"CKDKVS-incomingMessages" : @(self.perfCounters->incomingMessages),
238 @"CKDKVS-outgoingMessages" : @(self.perfCounters->outgoingMessages),
239 @"CKDKVS-totalWaittimeSynchronize" : @(self.perfCounters->totalWaittimeSynchronize),
240 @"CKDKVS-longestWaittimeSynchronize" : @(self.perfCounters->longestWaittimeSynchronize),
241 @"CKDKVS-synchronizeFailures" : @(self.perfCounters->synchronizeFailures),
246 - (void)addOneToOutGoing
248 dispatch_async(self.perfQueue, ^{
249 self.perfCounters->outgoingMessages++;