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(kvsStoreChanged:)
53 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
58 - (void)setObject:(id)obj forKey:(NSString*)key {
59 NSUbiquitousKeyValueStore *store = [self cloudStore];
62 id value = [store objectForKey:key];
64 secdebug("kvsdebug", "%@ key %@ changed: %@ to: %@", self, key, value, obj);
66 secdebug("kvsdebug", "%@ key %@ initialized to: %@", self, key, obj);
67 [store setObject:obj forKey:key];
70 secerror("Can't get kvs store, key: %@ not set to: %@", key, obj);
74 - (NSDictionary<NSString *, id>*) copyAsDictionary {
75 return [self.cloudStore dictionaryRepresentation];
78 - (void)addEntriesFromDictionary:(NSDictionary<NSString*, NSObject*> *)otherDictionary {
79 [otherDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSObject * _Nonnull obj, BOOL * _Nonnull stop) {
80 [self setObject:obj forKey:key];
84 - (id)objectForKey:(NSString*)key {
85 return [self.cloudStore objectForKey:key];
88 - (void)removeObjectForKey:(NSString*)key {
89 return [self.cloudStore removeObjectForKey:key];
92 - (void)removeAllObjects {
93 [[[[self.cloudStore dictionaryRepresentation] allKeys] copy] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
94 [self.cloudStore removeObjectForKey:obj];
99 [[self cloudStore] synchronize];
102 - (void) kvsStoreChanged:(NSNotification *)notification {
104 Posted when the value of one or more keys in the local key-value store
105 changed due to incoming data pushed from iCloud. This notification is
106 sent only upon a change received from iCloud; it is not sent when your
109 The user info dictionary can contain the reason for the notification as
110 well as a list of which values changed, as follows:
112 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
113 present, indicates why the key-value store changed. Its value is one of
114 the constants in "Change Reason Values."
116 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
117 is an array of strings, each the name of a key whose value changed. The
118 notification object is the NSUbiquitousKeyValueStore object whose contents
121 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
122 local value that has been overwritten by a distant value. If there is no
123 conflict between the local and the distant values when doing the initial
124 sync (e.g. if the cloud has no data stored or the client has not stored
125 any data yet), you'll never see that notification.
127 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
128 with server but initial round trip with server does not imply
129 NSUbiquitousKeyValueStoreInitialSyncChange.
131 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
132 secdebug(XPROXYSCOPE, "%@ KVS Remote changed notification: %@", self, notification);
134 NSDictionary *userInfo = [notification userInfo];
135 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
136 NSArray *keysChangedArray = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
137 NSSet *keysChanged = keysChangedArray ? [NSSet setWithArray: keysChangedArray] : nil;
139 if (reason) switch ([reason integerValue]) {
140 case NSUbiquitousKeyValueStoreInitialSyncChange:
141 [self.proxy storeKeysChanged: keysChanged initial: YES];
144 case NSUbiquitousKeyValueStoreServerChange:
145 [self.proxy storeKeysChanged: keysChanged initial: NO];
148 case NSUbiquitousKeyValueStoreQuotaViolationChange:
149 seccritical("Received NSUbiquitousKeyValueStoreQuotaViolationChange");
152 case NSUbiquitousKeyValueStoreAccountChange:
153 [self.proxy storeAccountChanged];
157 secinfo("kvsstore", "ignoring unknown notification: %@", reason);
163 // try to synchronize asap, and invoke the handler on completion to take incoming changes.
165 static bool isResubmitError(NSError* error) {
166 return error && (CFErrorGetCode((__bridge CFErrorRef) error) == UPDATE_RESUBMIT) &&
167 (CFErrorGetDomain((__bridge CFErrorRef)error) == __SYDErrorKVSDomain);
170 - (BOOL) pullUpdates:(NSError **)failure
172 __block NSError *tempFailure = nil;
173 const int kMaximumTries = 10;
175 // Retry up to 10 times, since we're told this can fail and WE have to deal with it.
177 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
181 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH, try %d: %@", kWAIT2MINID, tryCount, self);
183 [[self cloudStore] synchronizeWithCompletionHandler:^(NSError *error) {
186 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
188 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
189 [[self cloudStore] synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
190 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
192 dispatch_semaphore_signal(freshSemaphore);
194 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
195 } while (tryCount < kMaximumTries && isResubmitError(tempFailure));
197 if (isResubmitError(tempFailure)) {
198 secerrorq("%s %d retry attempts to request freshness exceeded, failing", kWAIT2MINID, tryCount);
201 if (failure && (*failure == NULL)) {
202 *failure = tempFailure;
205 return tempFailure == nil;