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>
21 #define UPDATE_RESUBMIT 4
23 @interface CKDKVSStore ()
24 @property (readwrite, weak) UbiqitousKVSProxy* proxy;
25 @property (readwrite) NSUbiquitousKeyValueStore* cloudStore;
28 @implementation CKDKVSStore
30 + (instancetype)kvsInterface {
31 return [[CKDKVSStore alloc] init];
34 - (instancetype)init {
37 self->_cloudStore = [NSUbiquitousKeyValueStore defaultStore];
40 if (!self.cloudStore) {
41 secerror("NO NSUbiquitousKeyValueStore defaultStore!!!");
48 - (void) connectToProxy: (UbiqitousKVSProxy*) proxy {
51 [[NSNotificationCenter defaultCenter] addObserver:self
52 selector:@selector(kvsStoreChangedAsync:)
53 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
58 - (void)setObject:(id)obj forKey:(NSString*)key {
59 secdebug("kvsdebug", "%@ key %@ set to: %@ from: %@", self, key, obj, [self.cloudStore objectForKey:key]);
60 [self.cloudStore setObject:obj forKey:key];
63 - (NSDictionary<NSString *, id>*) copyAsDictionary {
64 return [self.cloudStore dictionaryRepresentation];
67 - (void)addEntriesFromDictionary:(NSDictionary<NSString*, NSObject*> *)otherDictionary {
68 [otherDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSObject * _Nonnull obj, BOOL * _Nonnull stop) {
69 [self setObject:obj forKey:key];
73 - (id)objectForKey:(NSString*)key {
74 return [self.cloudStore objectForKey:key];
77 - (void)removeObjectForKey:(NSString*)key {
78 return [self.cloudStore removeObjectForKey:key];
81 - (void)removeAllObjects {
82 [[[[self.cloudStore dictionaryRepresentation] allKeys] copy] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
83 [self.cloudStore removeObjectForKey:obj];
88 [[self cloudStore] synchronize];
91 // Runs on the same thread that posted the notification, and that thread _may_ be the
92 // kdkvsproxy_queue (see 30470419). Avoid deadlock by bouncing through global queue.
93 - (void)kvsStoreChangedAsync:(NSNotification *)notification
95 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
96 [self kvsStoreChanged:notification];
100 - (void) kvsStoreChanged:(NSNotification *)notification {
102 Posted when the value of one or more keys in the local key-value store
103 changed due to incoming data pushed from iCloud. This notification is
104 sent only upon a change received from iCloud; it is not sent when your
107 The user info dictionary can contain the reason for the notification as
108 well as a list of which values changed, as follows:
110 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
111 present, indicates why the key-value store changed. Its value is one of
112 the constants in "Change Reason Values."
114 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
115 is an array of strings, each the name of a key whose value changed. The
116 notification object is the NSUbiquitousKeyValueStore object whose contents
119 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
120 local value that has been overwritten by a distant value. If there is no
121 conflict between the local and the distant values when doing the initial
122 sync (e.g. if the cloud has no data stored or the client has not stored
123 any data yet), you'll never see that notification.
125 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
126 with server but initial round trip with server does not imply
127 NSUbiquitousKeyValueStoreInitialSyncChange.
129 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
130 secdebug(XPROXYSCOPE, "%@ KVS Remote changed notification: %@", self, notification);
132 NSDictionary *userInfo = [notification userInfo];
133 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
134 NSArray *keysChangedArray = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
135 NSSet *keysChanged = keysChangedArray ? [NSSet setWithArray: keysChangedArray] : nil;
137 if (reason) switch ([reason integerValue]) {
138 case NSUbiquitousKeyValueStoreInitialSyncChange:
139 [self.proxy storeKeysChanged: keysChanged initial: YES];
142 case NSUbiquitousKeyValueStoreServerChange:
143 [self.proxy storeKeysChanged: keysChanged initial: NO];
146 case NSUbiquitousKeyValueStoreQuotaViolationChange:
147 seccritical("Received NSUbiquitousKeyValueStoreQuotaViolationChange");
150 case NSUbiquitousKeyValueStoreAccountChange:
151 [self.proxy storeAccountChanged];
155 secinfo("kvsstore", "ignoring unknown notification: %@", reason);
161 // try to synchronize asap, and invoke the handler on completion to take incoming changes.
163 static bool isResubmitError(NSError* error) {
164 return error && (CFErrorGetCode((__bridge CFErrorRef) error) == UPDATE_RESUBMIT) &&
165 (CFErrorGetDomain((__bridge CFErrorRef)error) == __SYDErrorKVSDomain);
168 - (BOOL) pullUpdates:(NSError **)failure
170 __block NSError *tempFailure = nil;
171 const int kMaximumTries = 10;
173 // Retry up to 10 times, since we're told this can fail and WE have to deal with it.
175 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
179 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH, try %d: %@", kWAIT2MINID, tryCount, self);
181 [[self cloudStore] synchronizeWithCompletionHandler:^(NSError *error) {
184 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
186 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
187 [[self cloudStore] synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
188 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
190 dispatch_semaphore_signal(freshSemaphore);
192 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
193 } while (tryCount < kMaximumTries && isResubmitError(tempFailure));
195 if (isResubmitError(tempFailure)) {
196 secerrorq("%s %d retry attempts to request freshness exceeded, failing", kWAIT2MINID, tryCount);
199 if (failure && (*failure == NULL)) {
200 *failure = tempFailure;
203 return tempFailure == nil;