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 #import "Analytics/Clients/SOSAnalytics.h"
22 struct CKDKVSCounters {
24 uint64_t synchronizeWithCompletionHandler;
25 uint64_t incomingMessages;
26 uint64_t outgoingMessages;
27 uint64_t totalWaittimeSynchronize;
28 uint64_t longestWaittimeSynchronize;
29 uint64_t synchronizeFailures;
34 @interface CKDKVSStore ()
35 @property (readwrite, weak) UbiqitousKVSProxy* proxy;
36 @property (readwrite) NSUbiquitousKeyValueStore* cloudStore;
37 @property (assign,readwrite) struct CKDKVSCounters* perfCounters;
38 @property dispatch_queue_t perfQueue;
41 @implementation CKDKVSStore
43 + (instancetype)kvsInterface {
44 return [[CKDKVSStore alloc] init];
47 - (instancetype)init {
50 self->_cloudStore = [NSUbiquitousKeyValueStore defaultStore];
53 if (!self.cloudStore) {
54 secerror("NO NSUbiquitousKeyValueStore defaultStore!!!");
57 self.perfQueue = dispatch_queue_create("CKDKVSStorePerfQueue", NULL);
58 self.perfCounters = calloc(1, sizeof(struct CKDKVSCounters));
70 - (void) connectToProxy: (UbiqitousKVSProxy*) proxy {
73 [[NSNotificationCenter defaultCenter] addObserver:self
74 selector:@selector(kvsStoreChangedAsync:)
75 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
80 - (void)setObject:(id)obj forKey:(NSString*)key {
81 secdebug("kvsdebug", "%@ key %@ set to: %@ from: %@", self, key, obj, [self.cloudStore objectForKey:key]);
82 [self.cloudStore setObject:obj forKey:key];
85 - (NSDictionary<NSString *, id>*) copyAsDictionary {
86 return [self.cloudStore dictionaryRepresentation];
89 - (void)addEntriesFromDictionary:(NSDictionary<NSString*, NSObject*> *)otherDictionary {
90 [otherDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSObject * _Nonnull obj, BOOL * _Nonnull stop) {
91 [self setObject:obj forKey:key];
95 - (id)objectForKey:(NSString*)key {
96 return [self.cloudStore objectForKey:key];
99 - (void)removeObjectForKey:(NSString*)key {
100 return [self.cloudStore removeObjectForKey:key];
103 - (void)removeAllObjects {
104 [[[[self.cloudStore dictionaryRepresentation] allKeys] copy] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
105 [self.cloudStore removeObjectForKey:obj];
110 [[self cloudStore] synchronize];
111 dispatch_async(self.perfQueue, ^{
112 self.perfCounters->synchronize++;
116 // Runs on the same thread that posted the notification, and that thread _may_ be the
117 // kdkvsproxy_queue (see 30470419). Avoid deadlock by bouncing through global queue.
118 - (void)kvsStoreChangedAsync:(NSNotification *)notification
120 secnotice("CloudKeychainProxy", "%@ KVS Remote changed notification: %@", self, notification);
121 dispatch_async(self.perfQueue, ^{
122 self.perfCounters->incomingMessages++;
124 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
125 [self kvsStoreChanged:notification];
129 - (void) kvsStoreChanged:(NSNotification *)notification {
131 Posted when the value of one or more keys in the local key-value store
132 changed due to incoming data pushed from iCloud. This notification is
133 sent only upon a change received from iCloud; it is not sent when your
136 The user info dictionary can contain the reason for the notification as
137 well as a list of which values changed, as follows:
139 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
140 present, indicates why the key-value store changed. Its value is one of
141 the constants in "Change Reason Values."
143 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
144 is an array of strings, each the name of a key whose value changed. The
145 notification object is the NSUbiquitousKeyValueStore object whose contents
148 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
149 local value that has been overwritten by a distant value. If there is no
150 conflict between the local and the distant values when doing the initial
151 sync (e.g. if the cloud has no data stored or the client has not stored
152 any data yet), you'll never see that notification.
154 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
155 with server but initial round trip with server does not imply
156 NSUbiquitousKeyValueStoreInitialSyncChange.
158 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
159 secdebug(XPROXYSCOPE, "%@ KVS Remote changed notification: %@", self, notification);
161 UbiqitousKVSProxy* proxy = self.proxy;
163 // Weak reference went away.
167 NSDictionary *userInfo = [notification userInfo];
168 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
169 NSArray *keysChangedArray = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
170 NSSet *keysChanged = keysChangedArray ? [NSSet setWithArray: keysChangedArray] : nil;
172 if (reason) switch ([reason integerValue]) {
173 case NSUbiquitousKeyValueStoreInitialSyncChange:
174 [proxy storeKeysChanged: keysChanged initial: YES];
177 case NSUbiquitousKeyValueStoreServerChange:
178 [proxy storeKeysChanged: keysChanged initial: NO];
181 case NSUbiquitousKeyValueStoreQuotaViolationChange:
182 seccritical("Received NSUbiquitousKeyValueStoreQuotaViolationChange");
185 case NSUbiquitousKeyValueStoreAccountChange:
186 [proxy storeAccountChanged];
190 secinfo("kvsstore", "ignoring unknown notification: %@", reason);
196 - (BOOL) pullUpdates:(NSError **)failure
198 __block NSError *tempFailure = nil;
200 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
202 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
204 dispatch_async(self.perfQueue, ^{
205 self.perfCounters->synchronizeWithCompletionHandler++;
208 [[self cloudStore] synchronizeWithCompletionHandler:^(NSError *error) {
210 dispatch_async(self.perfQueue, ^{
211 self.perfCounters->synchronizeFailures++;
214 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
216 dispatch_async(self.perfQueue, ^{
217 self.perfCounters->synchronize++;
219 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
220 [[self cloudStore] synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
221 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
223 dispatch_semaphore_signal(freshSemaphore);
225 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
227 if (failure && (*failure == NULL)) {
228 *failure = tempFailure;
231 return tempFailure == nil;
234 - (void)perfCounters:(void(^)(NSDictionary *counters))callback
236 dispatch_async(self.perfQueue, ^{
238 CKDKVSPerfCounterSynchronize : @(self.perfCounters->synchronize),
239 CKDKVSPerfCounterSynchronizeWithCompletionHandler : @(self.perfCounters->synchronizeWithCompletionHandler),
240 CKDKVSPerfCounterIncomingMessages : @(self.perfCounters->incomingMessages),
241 CKDKVSPerfCounterOutgoingMessages : @(self.perfCounters->outgoingMessages),
242 CKDKVSPerfCounterTotalWaitTimeSynchronize : @(self.perfCounters->totalWaittimeSynchronize),
243 CKDKVSPerfCounterLongestWaitTimeSynchronize : @(self.perfCounters->longestWaittimeSynchronize),
244 CKDKVSPerfCounterSynchronizeFailures : @(self.perfCounters->synchronizeFailures),
250 - (void)setupSamplers
252 [[SOSAnalytics logger] AddMultiSamplerForName:CKDKVSPerformanceCountersSampler
253 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
254 block:^NSDictionary<NSString *,NSNumber *> *{
255 __block NSDictionary<NSString *,NSNumber *>* data;
256 [self perfCounters:^(NSDictionary *counters) {
260 dispatch_sync(self.perfQueue, ^{
261 memset(self.perfCounters, 0, sizeof(struct CKDKVSCounters));
267 - (void)setupSamplers
269 // SFA is only for 64 bit cool kids
273 - (void)addOneToOutGoing
275 dispatch_async(self.perfQueue, ^{
276 self.perfCounters->outgoingMessages++;