]> git.saurik.com Git - apple/security.git/blob - KVSKeychainSyncingProxy/CKDKVSStore.m
Security-58286.1.32.tar.gz
[apple/security.git] / KVSKeychainSyncingProxy / CKDKVSStore.m
1 //
2 // CKDKVSStore.m
3 // Security
4 //
5 // Created by Mitch Adler on 5/15/16.
6 //
7 //
8
9 #import "CKDKVSStore.h"
10 #import "CKDKVSProxy.h"
11
12 #include "SOSCloudKeychainConstants.h"
13 #include <utilities/debugging.h>
14
15 #import <Foundation/NSUbiquitousKeyValueStore.h>
16 #import <Foundation/NSUbiquitousKeyValueStore_Private.h>
17 #import "SyncedDefaults/SYDConstants.h"
18 #include <os/activity.h>
19
20 struct CKDKVSCounters {
21 uint64_t synchronize;
22 uint64_t synchronizeWithCompletionHandler;
23 uint64_t incomingMessages;
24 uint64_t outgoingMessages;
25 uint64_t totalWaittimeSynchronize;
26 uint64_t longestWaittimeSynchronize;
27 uint64_t synchronizeFailures;
28 };
29
30
31
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;
37 @end
38
39 @implementation CKDKVSStore
40
41 + (instancetype)kvsInterface {
42 return [[CKDKVSStore alloc] init];
43 }
44
45 - (instancetype)init {
46 self = [super init];
47
48 self->_cloudStore = [NSUbiquitousKeyValueStore defaultStore];
49 self->_proxy = nil;
50
51 if (!self.cloudStore) {
52 secerror("NO NSUbiquitousKeyValueStore defaultStore!!!");
53 return nil;
54 }
55 self.perfQueue = dispatch_queue_create("CKDKVSStorePerfQueue", NULL);
56 self.perfCounters = calloc(1, sizeof(struct CKDKVSCounters));
57
58
59 return self;
60 }
61
62 - (void)dealloc {
63 if (_perfCounters)
64 free(_perfCounters);
65 }
66
67 - (void) connectToProxy: (UbiqitousKVSProxy*) proxy {
68 _proxy = proxy;
69
70 [[NSNotificationCenter defaultCenter] addObserver:self
71 selector:@selector(kvsStoreChangedAsync:)
72 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
73 object:nil];
74
75 }
76
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];
80 }
81
82 - (NSDictionary<NSString *, id>*) copyAsDictionary {
83 return [self.cloudStore dictionaryRepresentation];
84 }
85
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];
89 }];
90 }
91
92 - (id)objectForKey:(NSString*)key {
93 return [self.cloudStore objectForKey:key];
94 }
95
96 - (void)removeObjectForKey:(NSString*)key {
97 return [self.cloudStore removeObjectForKey:key];
98 }
99
100 - (void)removeAllObjects {
101 [[[[self.cloudStore dictionaryRepresentation] allKeys] copy] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
102 [self.cloudStore removeObjectForKey:obj];
103 }];
104 }
105
106 - (void)pushWrites {
107 [[self cloudStore] synchronize];
108 dispatch_async(self.perfQueue, ^{
109 self.perfCounters->synchronize++;
110 });
111 }
112
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
116 {
117 secnotice("CloudKeychainProxy", "%@ KVS Remote changed notification: %@", self, notification);
118 dispatch_async(self.perfQueue, ^{
119 self.perfCounters->incomingMessages++;
120 });
121 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
122 [self kvsStoreChanged:notification];
123 });
124 }
125
126 - (void) kvsStoreChanged:(NSNotification *)notification {
127 /*
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
131 app sets a value.
132
133 The user info dictionary can contain the reason for the notification as
134 well as a list of which values changed, as follows:
135
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."
139
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
143 changed.
144
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.
150
151 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
152 with server but initial round trip with server does not imply
153 NSUbiquitousKeyValueStoreInitialSyncChange.
154 */
155 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
156 secdebug(XPROXYSCOPE, "%@ KVS Remote changed notification: %@", self, notification);
157
158 UbiqitousKVSProxy* proxy = self.proxy;
159 if(!proxy) {
160 // Weak reference went away.
161 return;
162 }
163
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;
168
169 if (reason) switch ([reason integerValue]) {
170 case NSUbiquitousKeyValueStoreInitialSyncChange:
171 [proxy storeKeysChanged: keysChanged initial: YES];
172 break;
173
174 case NSUbiquitousKeyValueStoreServerChange:
175 [proxy storeKeysChanged: keysChanged initial: NO];
176 break;
177
178 case NSUbiquitousKeyValueStoreQuotaViolationChange:
179 seccritical("Received NSUbiquitousKeyValueStoreQuotaViolationChange");
180 break;
181
182 case NSUbiquitousKeyValueStoreAccountChange:
183 [proxy storeAccountChanged];
184 break;
185
186 default:
187 secinfo("kvsstore", "ignoring unknown notification: %@", reason);
188 break;
189 }
190 });
191 }
192
193 - (BOOL) pullUpdates:(NSError **)failure
194 {
195 __block NSError *tempFailure = nil;
196
197 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
198
199 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
200
201 dispatch_async(self.perfQueue, ^{
202 self.perfCounters->synchronizeWithCompletionHandler++;
203 });
204
205 [[self cloudStore] synchronizeWithCompletionHandler:^(NSError *error) {
206 if (error) {
207 dispatch_async(self.perfQueue, ^{
208 self.perfCounters->synchronizeFailures++;
209 });
210 tempFailure = error;
211 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
212 } else {
213 dispatch_async(self.perfQueue, ^{
214 self.perfCounters->synchronize++;
215 });
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);
219 }
220 dispatch_semaphore_signal(freshSemaphore);
221 }];
222 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
223
224 if (failure && (*failure == NULL)) {
225 *failure = tempFailure;
226 }
227
228 return tempFailure == nil;
229 }
230
231 - (void)perfCounters:(void(^)(NSDictionary *counters))callback
232 {
233 dispatch_async(self.perfQueue, ^{
234 callback(@{
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),
242 });
243 });
244 }
245
246 - (void)addOneToOutGoing
247 {
248 dispatch_async(self.perfQueue, ^{
249 self.perfCounters->outgoingMessages++;
250 });
251 }
252
253
254 @end