]> git.saurik.com Git - apple/security.git/blob - KVSKeychainSyncingProxy/CKDKVSStore.m
Security-59306.80.4.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 #import "Analytics/Clients/SOSAnalytics.h"
21
22 struct CKDKVSCounters {
23 uint64_t synchronize;
24 uint64_t synchronizeWithCompletionHandler;
25 uint64_t incomingMessages;
26 uint64_t outgoingMessages;
27 uint64_t totalWaittimeSynchronize;
28 uint64_t longestWaittimeSynchronize;
29 uint64_t synchronizeFailures;
30 };
31
32
33
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;
39 @end
40
41 @implementation CKDKVSStore
42
43 + (instancetype)kvsInterface {
44 return [[CKDKVSStore alloc] init];
45 }
46
47 - (instancetype)init {
48 self = [super init];
49
50 self->_cloudStore = [NSUbiquitousKeyValueStore defaultStore];
51 self->_proxy = nil;
52
53 if (!self.cloudStore) {
54 secerror("NO NSUbiquitousKeyValueStore defaultStore!!!");
55 return nil;
56 }
57 self.perfQueue = dispatch_queue_create("CKDKVSStorePerfQueue", NULL);
58 self.perfCounters = calloc(1, sizeof(struct CKDKVSCounters));
59
60 [self setupSamplers];
61
62 return self;
63 }
64
65 - (void)dealloc {
66 if (_perfCounters)
67 free(_perfCounters);
68 }
69
70 - (void) connectToProxy: (UbiqitousKVSProxy*) proxy {
71 _proxy = proxy;
72
73 [[NSNotificationCenter defaultCenter] addObserver:self
74 selector:@selector(kvsStoreChangedAsync:)
75 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
76 object:nil];
77
78 }
79
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];
83 }
84
85 - (NSDictionary<NSString *, id>*) copyAsDictionary {
86 return [self.cloudStore dictionaryRepresentation];
87 }
88
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];
92 }];
93 }
94
95 - (id)objectForKey:(NSString*)key {
96 return [self.cloudStore objectForKey:key];
97 }
98
99 - (void)removeObjectForKey:(NSString*)key {
100 return [self.cloudStore removeObjectForKey:key];
101 }
102
103 - (void)removeAllObjects {
104 [[[[self.cloudStore dictionaryRepresentation] allKeys] copy] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
105 [self.cloudStore removeObjectForKey:obj];
106 }];
107 }
108
109 - (void)pushWrites {
110 [[self cloudStore] synchronize];
111 dispatch_async(self.perfQueue, ^{
112 self.perfCounters->synchronize++;
113 });
114 }
115
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
119 {
120 secnotice("CloudKeychainProxy", "%@ KVS Remote changed notification: %@", self, notification);
121 dispatch_async(self.perfQueue, ^{
122 self.perfCounters->incomingMessages++;
123 });
124 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
125 [self kvsStoreChanged:notification];
126 });
127 }
128
129 - (void) kvsStoreChanged:(NSNotification *)notification {
130 /*
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
134 app sets a value.
135
136 The user info dictionary can contain the reason for the notification as
137 well as a list of which values changed, as follows:
138
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."
142
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
146 changed.
147
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.
153
154 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
155 with server but initial round trip with server does not imply
156 NSUbiquitousKeyValueStoreInitialSyncChange.
157 */
158 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
159 secdebug(XPROXYSCOPE, "%@ KVS Remote changed notification: %@", self, notification);
160
161 UbiqitousKVSProxy* proxy = self.proxy;
162 if(!proxy) {
163 // Weak reference went away.
164 return;
165 }
166
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;
171
172 if (reason) switch ([reason integerValue]) {
173 case NSUbiquitousKeyValueStoreInitialSyncChange:
174 [proxy storeKeysChanged: keysChanged initial: YES];
175 break;
176
177 case NSUbiquitousKeyValueStoreServerChange:
178 [proxy storeKeysChanged: keysChanged initial: NO];
179 break;
180
181 case NSUbiquitousKeyValueStoreQuotaViolationChange:
182 seccritical("Received NSUbiquitousKeyValueStoreQuotaViolationChange");
183 break;
184
185 case NSUbiquitousKeyValueStoreAccountChange:
186 [proxy storeAccountChanged];
187 break;
188
189 default:
190 secinfo("kvsstore", "ignoring unknown notification: %@", reason);
191 break;
192 }
193 });
194 }
195
196 - (BOOL) pullUpdates:(NSError **)failure
197 {
198 __block NSError *tempFailure = nil;
199
200 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
201
202 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
203
204 dispatch_async(self.perfQueue, ^{
205 self.perfCounters->synchronizeWithCompletionHandler++;
206 });
207
208 [[self cloudStore] synchronizeWithCompletionHandler:^(NSError *error) {
209 if (error) {
210 dispatch_async(self.perfQueue, ^{
211 self.perfCounters->synchronizeFailures++;
212 });
213 tempFailure = error;
214 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
215 } else {
216 dispatch_async(self.perfQueue, ^{
217 self.perfCounters->synchronize++;
218 });
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);
222 }
223 dispatch_semaphore_signal(freshSemaphore);
224 }];
225 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
226
227 if (failure && (*failure == NULL)) {
228 *failure = tempFailure;
229 }
230
231 return tempFailure == nil;
232 }
233
234 - (void)perfCounters:(void(^)(NSDictionary *counters))callback
235 {
236 dispatch_async(self.perfQueue, ^{
237 callback(@{
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),
245 });
246 });
247 }
248
249 #if __OBJC2__
250 - (void)setupSamplers
251 {
252 [[SOSAnalytics logger] AddMultiSamplerForName:CKDKVSPerformanceCountersSampler
253 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
254 block:^NSDictionary<NSString *,NSNumber *> *{
255 __block NSDictionary<NSString *,NSNumber *>* data;
256 [self perfCounters:^(NSDictionary *counters) {
257 data = counters;
258 }];
259
260 dispatch_sync(self.perfQueue, ^{
261 memset(self.perfCounters, 0, sizeof(struct CKDKVSCounters));
262 });
263 return data;
264 }];
265 }
266 #else
267 - (void)setupSamplers
268 {
269 // SFA is only for 64 bit cool kids
270 }
271 #endif
272
273 - (void)addOneToOutGoing
274 {
275 dispatch_async(self.perfQueue, ^{
276 self.perfCounters->outgoingMessages++;
277 });
278 }
279
280
281 @end