]>
Commit | Line | Data |
---|---|---|
5c19dc3a A |
1 | /* |
2 | * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | ||
25 | /* | |
26 | * SOSPeer.c - Implementation of a secure object syncing peer | |
27 | */ | |
b54c578e A |
28 | #include "keychain/SecureObjectSync/SOSPeer.h" |
29 | #import "keychain/SecureObjectSync/SOSPeerRateLimiter.h" | |
30 | #include "keychain/SecureObjectSync/SOSDigestVector.h" | |
31 | #include "keychain/SecureObjectSync/SOSInternal.h" | |
32 | #include "keychain/SecureObjectSync/SOSTransport.h" | |
33 | #include "keychain/SecureObjectSync/SOSEngine.h" | |
5c19dc3a | 34 | #include <Security/SecureObjectSync/SOSViews.h> |
b54c578e | 35 | #import "keychain/SecureObjectSync/SOSChangeTracker.h" |
5c19dc3a A |
36 | #include <utilities/SecCFError.h> |
37 | #include <utilities/SecCFRelease.h> | |
38 | #include <utilities/SecCFWrappers.h> | |
39 | #include <utilities/SecDb.h> | |
40 | #include <utilities/SecFileLocations.h> | |
41 | #include <utilities/SecIOFormat.h> | |
42 | #include <utilities/array_size.h> | |
43 | #include <utilities/debugging.h> | |
b54c578e | 44 | #include "keychain/SecureObjectSync/SOSBackupEvent.h" |
5c19dc3a A |
45 | #include <Security/SecItemBackup.h> |
46 | ||
7fb2cbd2 | 47 | #include "keychain/securityd/SOSCloudCircleServer.h" |
5c19dc3a A |
48 | |
49 | #include <CoreFoundation/CoreFoundation.h> | |
50 | ||
51 | #include <stdlib.h> | |
52 | ||
53 | #include <AssertMacros.h> | |
54 | ||
55 | // Backup Peer Support | |
7fb2cbd2 | 56 | #include "keychain/securityd/SecKeybagSupport.h" |
5c19dc3a A |
57 | #include <notify.h> |
58 | ||
59 | ||
60 | // | |
61 | // MARK: - SOSPeerPersistence code | |
62 | // | |
63 | static CFStringRef kSOSPeerSequenceNumberKey = CFSTR("sequence-number"); | |
5c19dc3a A |
64 | |
65 | CFStringRef kSOSPeerDataLabel = CFSTR("iCloud Peer Data Meta-data"); | |
66 | ||
67 | // | |
68 | // MARK: SOSPeerState (dictionary keys) | |
69 | // | |
70 | ||
71 | // PeerState dictionary keys | |
72 | static CFStringRef kSOSPeerSendObjectsKey = CFSTR("send-objects"); // bool | |
73 | static CFStringRef kSOSPeerMustSendMessageKey = CFSTR("must-send"); // bool | |
6b200bc3 | 74 | static CFStringRef kSOSPeerHasBeenInSyncKey = CFSTR("has-been-in-sync"); // bool |
5c19dc3a A |
75 | static CFStringRef kSOSPeerPendingObjectsKey = CFSTR("pending-objects"); // digest |
76 | static CFStringRef kSOSPeerUnwantedManifestKey = CFSTR("unwanted-manifest"); // digest | |
77 | static CFStringRef kSOSPeerConfirmedManifestKey = CFSTR("confirmed-manifest"); //digest | |
78 | static CFStringRef kSOSPeerProposedManifestKey = CFSTR("pending-manifest"); // array of digests | |
79 | static CFStringRef kSOSPeerLocalManifestKey = CFSTR("local-manifest"); // array of digests | |
80 | static CFStringRef kSOSPeerVersionKey = CFSTR("vers"); // int | |
5c19dc3a A |
81 | |
82 | // | |
83 | // SOSPeerMeta keys that can also be used in peerstate... | |
84 | // | |
85 | static CFStringRef kSOSPeerPeerIDKey = CFSTR("peer-id"); // string | |
86 | static CFStringRef kSOSPeerViewsKey = CFSTR("views"); // set (or array) of string | |
87 | static CFStringRef kSOSPeerKeyBagKey = CFSTR("keybag"); // data | |
88 | ||
89 | /* | |
90 | Theory of syncing for both incoming and outgoing messages | |
91 | ||
92 | A peerstate consists of: | |
93 | (T, U, C, P, L, D->M, M->D, H->O, O->{(Op,Or)|Oa,(Op,Or)|(Om,Oa),(Op,Or)|Om,Oi|Oi|Ob|Oa,Ob|(Om,Oa),Ob|(Ol,Ou)|Oa,(Ol,Ou)|(Om,Oa)(Ol,Ou)}) | |
94 | T: to be sent (pendingObjects) manifest | |
95 | U: unwanted objects manifest | |
96 | C: confirmed manifest | |
97 | P: proposed manifest | |
98 | D->M? digest or manifest to optional manifest function | |
99 | M->D manifest to digest of manifest function | |
100 | H->O? hash (manifest entry) to optional object function (the datasource) | |
101 | O->{ Mapping from incoming O objects to one of: | |
102 | (Op,Or): Op = Peers object, Or is replaced local object. | |
103 | Oa,(Op,Or): Oa = appeared local object, Or = Oa, see above for (Op, Or) | |
104 | (Om,Oa),(Op,Or): Om missing local object, was apparently Oa instead see above for (Oa, Op, Or) | |
105 | Om,Oi: Om missing local object, inserted Oi (nothing replaced), but Om still disapeared from manifest | |
106 | Oi Oi inserted object from peer (nothing replaced) | |
107 | Ob: Ob both remote and local object are identical, nothing changed | |
108 | Oa,Ob: Oa = appeared local object equal to Oa = Ob. Equivalent to single Oi | |
109 | (Om,Oa),Ob: Om missing local object, must be O->H->Ob, Oa found in place, Ob != Oa, Oa magically appeared unrelated to O->H->Ob | |
110 | (Ol,Ou): Ol local object wins from peers Ou older object => append Ou to U | |
111 | Oa,(Ol,Ou): Oa appeared as a local object and Oa = Ol above | |
112 | (Om,Oa),(Ol,Ou): Om removed and Oa replaced Om and Oa = Ol as above | |
113 | } | |
114 | A message consists of | |
115 | (B,M,E,O) | |
116 | B: base digest | |
117 | M: missing manifest | |
118 | E: extra manifest | |
119 | O: objects | |
120 | ||
121 | To send a message we simply compute: | |
122 | B: D->M->C | |
123 | M: C \ L | |
124 | E: L \ C # subsetted if not all of O is sent | |
125 | O: H->O?->O # as size permits | |
126 | and change P to (C \ M:) union E: union O->H->O: | |
127 | ||
128 | To receive a message we compute | |
129 | Op,Oi,Ob,Oo and Om,Oa,Ol | |
130 | C: (D->M->B \ M) union E union O->H->O | |
131 | A = E \ (O->H->O) | |
132 | T: T \ (Om union Or union A union Oa,Ob.last union (Om,Oa),Ob.last ) union (M intersect L) | |
133 | */ | |
134 | ||
135 | ||
136 | // | |
137 | // MARK: SOSPeerMeta | |
138 | // | |
139 | ||
140 | static SOSPeerMetaRef SOSPeerMetaCreate(CFStringRef peerID) { | |
141 | return CFRetain(peerID); | |
142 | } | |
143 | ||
144 | static SOSPeerMetaRef SOSPeerMetaCreateWithViews(CFStringRef peerID, CFSetRef views) { | |
145 | const void *keys[] = { kSOSPeerPeerIDKey, kSOSPeerViewsKey }; | |
146 | const void *values[] = { peerID, views }; | |
147 | return CFDictionaryCreate(kCFAllocatorDefault, keys, values, array_size(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
148 | } | |
149 | ||
150 | static SOSPeerMetaRef SOSPeerMetaCreateWithViewsAndKeyBag(CFStringRef peerID, CFSetRef views, CFDataRef keybag) { | |
151 | const void *keys[] = { kSOSPeerPeerIDKey, kSOSPeerViewsKey, kSOSPeerKeyBagKey }; | |
152 | const void *values[] = { peerID, views, keybag }; | |
153 | return CFDictionaryCreate(kCFAllocatorDefault, keys, values, array_size(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
154 | } | |
155 | ||
156 | SOSPeerMetaRef SOSPeerMetaCreateWithComponents(CFStringRef peerID, CFSetRef views, CFDataRef keybag) { | |
157 | if (!isString(peerID)) | |
158 | return NULL; | |
159 | if (views) { | |
160 | if (keybag) | |
161 | return SOSPeerMetaCreateWithViewsAndKeyBag(peerID, views, keybag); | |
162 | else | |
163 | return SOSPeerMetaCreateWithViews(peerID, views); | |
164 | } else | |
165 | return SOSPeerMetaCreate(peerID); | |
166 | } | |
167 | ||
168 | SOSPeerMetaRef SOSPeerMetaCreateWithState(CFStringRef peerID, CFDictionaryRef state) { | |
169 | return SOSPeerMetaCreateWithComponents(peerID, CFDictionaryGetValue(state, kSOSPeerViewsKey), CFDictionaryGetValue(state, kSOSPeerKeyBagKey)); | |
170 | } | |
171 | ||
172 | CFStringRef SOSPeerMetaGetComponents(SOSPeerMetaRef peerMeta, CFSetRef *views, CFDataRef *keybag, CFErrorRef *error) { | |
173 | if (isDictionary(peerMeta)) { | |
174 | CFDictionaryRef meta = (CFDictionaryRef)peerMeta; | |
175 | CFStringRef peerID = asString(CFDictionaryGetValue(meta, kSOSPeerPeerIDKey), error); | |
176 | CFSetRef vns = asSet(CFDictionaryGetValue(meta, kSOSPeerViewsKey), error); | |
177 | if (vns && asDataOptional(CFDictionaryGetValue(meta, kSOSPeerKeyBagKey), keybag, error)) { | |
178 | if (views) | |
179 | *views = vns; | |
180 | return peerID; | |
181 | } | |
182 | return NULL; | |
183 | } else { | |
184 | if (views) { | |
185 | // Hack so tests can pass simple peerIDs | |
186 | *views = SOSViewsGetV0ViewSet(); | |
187 | } | |
188 | return asString(peerMeta, error); | |
189 | } | |
190 | } | |
191 | ||
192 | CFTypeRef SOSPeerOrStateSetViewsKeyBagAndCreateCopy(CFTypeRef peerOrState, CFSetRef views, CFDataRef keyBag) { | |
193 | assert(views); | |
194 | if (peerOrState && CFGetTypeID(peerOrState) == SOSPeerGetTypeID()) { | |
195 | // Inflated peer, update its views and move on | |
196 | SOSPeerRef peer = (SOSPeerRef)peerOrState; | |
197 | SOSPeerSetViewNameSet(peer, views); | |
198 | SOSPeerSetKeyBag(peer, keyBag); | |
199 | return CFRetainSafe(peer); | |
e0e0d90e A |
200 | } else if (peerOrState && CFGetTypeID(peerOrState) == CFDictionaryGetTypeID()) { |
201 | CFMutableDictionaryRef state = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, peerOrState); | |
5c19dc3a A |
202 | // Deserialized peer, just updated the serialized state with the new views |
203 | CFDictionarySetValue(state, kSOSPeerViewsKey, views); | |
204 | if (keyBag) | |
205 | CFDictionarySetValue(state, kSOSPeerKeyBagKey, keyBag); | |
206 | else | |
207 | CFDictionaryRemoveValue(state, kSOSPeerKeyBagKey); | |
e0e0d90e | 208 | return state; |
5c19dc3a A |
209 | } else { |
210 | // New peer, just create a state object. | |
211 | if (keyBag) | |
212 | return CFDictionaryCreateForCFTypes(kCFAllocatorDefault, kSOSPeerViewsKey, views, kSOSPeerKeyBagKey, keyBag, NULL); | |
213 | else | |
214 | return CFDictionaryCreateForCFTypes(kCFAllocatorDefault, kSOSPeerViewsKey, views, NULL); | |
215 | } | |
216 | } | |
217 | ||
218 | CFTypeRef SOSPeerOrStateSetViewsAndCopyState(CFTypeRef peerOrState, CFSetRef views) { | |
219 | assert(views); | |
6b200bc3 | 220 | |
5c19dc3a A |
221 | if (peerOrState && CFGetTypeID(peerOrState) == SOSPeerGetTypeID()) { |
222 | // Inflated peer, update its views and deflate it | |
223 | SOSPeerRef peer = (SOSPeerRef)peerOrState; | |
224 | SOSPeerSetViewNameSet(peer, views); | |
225 | return SOSPeerCopyState(peer, NULL); | |
e0e0d90e | 226 | } else if (peerOrState && CFGetTypeID(peerOrState) == CFDictionaryGetTypeID()) { |
5c19dc3a | 227 | // We have a deflated peer. Update its views and keep it deflated |
6b200bc3 | 228 | CFSetRef oldViews = (CFSetRef) CFDictionaryGetValue(peerOrState, kSOSPeerViewsKey); |
e0e0d90e | 229 | CFMutableDictionaryRef state = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, peerOrState); |
5c19dc3a | 230 | CFDictionarySetValue(state, kSOSPeerViewsKey, views); |
6b200bc3 A |
231 | if (oldViews && !CFSetIsSubset(views, oldViews)) { |
232 | CFDictionarySetValue(state, kSOSPeerHasBeenInSyncKey, kCFBooleanFalse); | |
233 | } | |
e0e0d90e | 234 | return state; |
5c19dc3a A |
235 | } else { |
236 | return NULL; | |
237 | } | |
238 | } | |
239 | ||
240 | bool SOSPeerMapEntryIsBackup(const void *mapEntry) { | |
241 | if (!mapEntry) return false; | |
242 | if (CFGetTypeID(mapEntry) == SOSPeerGetTypeID()) { | |
243 | return SOSPeerGetKeyBag((SOSPeerRef)mapEntry); | |
244 | } else { | |
245 | return CFDictionaryContainsKey(mapEntry, kSOSPeerKeyBagKey); | |
246 | } | |
247 | } | |
248 | ||
249 | // | |
250 | // MARK: - SOSManifest | |
251 | // | |
252 | ||
253 | enum { | |
254 | kSOSPeerMaxManifestWindowDepth = 4 | |
255 | }; | |
256 | ||
257 | static CFStringRef SOSManifestCreateOptionalDescriptionWithLabel(SOSManifestRef manifest, CFStringRef label) { | |
258 | if (!manifest) return CFSTR(" - "); | |
259 | return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(" %@%@"), label, manifest); | |
260 | } | |
261 | ||
262 | static CFStringRef SOSManifestArrayCreateOptionalDescriptionWithLabel(CFArrayRef manifests, CFStringRef label) { | |
263 | CFIndex count = manifests ? CFArrayGetCount(manifests) : 0; | |
264 | if (count == 0) return CFSTR(" - "); | |
265 | SOSManifestRef manifest = (SOSManifestRef)CFArrayGetValueAtIndex(manifests, 0); | |
266 | return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(" %@[%" PRIdCFIndex "]%@"), label, count, manifest); | |
267 | } | |
268 | ||
269 | static void SOSManifestArraySetManifest(CFMutableArrayRef *manifests, SOSManifestRef manifest) { | |
270 | if (manifest) { | |
271 | if (*manifests) | |
272 | CFArrayRemoveAllValues(*manifests); | |
273 | else | |
274 | *manifests = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
275 | CFArrayAppendValue(*manifests, manifest); | |
276 | } else { | |
277 | CFReleaseNull(*manifests); | |
278 | } | |
279 | } | |
280 | ||
281 | static void SOSManifestMutableArrayAppendManifest(CFMutableArrayRef manifests, SOSManifestRef manifest) { | |
282 | if (manifest) { | |
283 | CFIndex count = CFArrayGetCount(manifests); | |
284 | CFIndex ixOfManifest = CFArrayGetFirstIndexOfValue(manifests, CFRangeMake(0, count), manifest); | |
285 | if (ixOfManifest != 0) { | |
286 | // If the manifest isn't at the front of the array move it there. | |
287 | // If it's not in the array, remove enough entires from the end to | |
288 | // make room to put it in the front. | |
289 | if (ixOfManifest != kCFNotFound) { | |
290 | CFArrayRemoveValueAtIndex(manifests, ixOfManifest); | |
291 | } else { | |
292 | while (count >= kSOSPeerMaxManifestWindowDepth) | |
293 | CFArrayRemoveValueAtIndex(manifests, --count); | |
294 | } | |
295 | ||
296 | CFArrayInsertValueAtIndex(manifests, 0, manifest); | |
297 | } | |
298 | } else { | |
299 | // pending == NULL => nothing clear history | |
300 | CFArrayRemoveAllValues(manifests); | |
301 | } | |
302 | } | |
303 | ||
304 | static void SOSManifestArrayAppendManifest(CFMutableArrayRef *manifests, SOSManifestRef manifest) { | |
305 | if (*manifests) | |
306 | SOSManifestMutableArrayAppendManifest(*manifests, manifest); | |
307 | else | |
308 | SOSManifestArraySetManifest(manifests, manifest); | |
309 | } | |
310 | ||
311 | // | |
312 | // MARK: - SOSPeer | |
313 | // | |
314 | ||
315 | struct __OpaqueSOSPeer { | |
316 | CFRuntimeBase _base; | |
317 | ||
5c19dc3a A |
318 | CFStringRef peer_id; |
319 | CFSetRef views; | |
320 | CFIndex version; | |
321 | uint64_t sequenceNumber; | |
322 | bool mustSendMessage; | |
323 | bool sendObjects; | |
324 | ||
6b200bc3 A |
325 | bool hasBeenInSync; |
326 | ||
5c19dc3a A |
327 | SOSManifestRef pendingObjects; |
328 | SOSManifestRef unwantedManifest; | |
329 | SOSManifestRef confirmedManifest; | |
330 | CFMutableArrayRef proposedManifests; | |
331 | CFMutableArrayRef localManifests; | |
332 | ||
866f8763 A |
333 | CFTypeRef limiter; |
334 | ||
335 | CFMutableDictionaryRef otrTimers; | |
336 | ||
5c19dc3a A |
337 | // Only backup peers have these: |
338 | CFDataRef _keyBag; | |
339 | FILE *journalFile; | |
340 | }; | |
341 | ||
342 | CFGiblisWithCompareFor(SOSPeer) | |
343 | ||
344 | static CFStringRef SOSPeerCopyFormatDescription(CFTypeRef cf, CFDictionaryRef formatOptions) { | |
345 | SOSPeerRef peer = (SOSPeerRef)cf; | |
346 | if(peer){ | |
347 | CFStringRef po = SOSManifestCreateOptionalDescriptionWithLabel(peer->pendingObjects, CFSTR("O")); | |
348 | CFStringRef uo = SOSManifestCreateOptionalDescriptionWithLabel(peer->unwantedManifest, CFSTR("U")); | |
349 | CFStringRef co = SOSManifestCreateOptionalDescriptionWithLabel(peer->confirmedManifest, CFSTR("C")); | |
350 | CFStringRef pe = SOSManifestArrayCreateOptionalDescriptionWithLabel(peer->proposedManifests, CFSTR("P")); | |
351 | CFStringRef lo = SOSManifestArrayCreateOptionalDescriptionWithLabel(peer->localManifests, CFSTR("L")); | |
6b200bc3 | 352 | CFStringRef desc = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<%@ %s%s%s%@%@%@%@%@>"), |
5c19dc3a A |
353 | SOSPeerGetID(peer), |
354 | SOSPeerMustSendMessage(peer) ? "F" : "f", | |
355 | SOSPeerSendObjects(peer) ? "S" : "s", | |
6b200bc3 | 356 | SOSPeerHasBeenInSync(peer) ? "K" : "k", |
5c19dc3a A |
357 | po, uo, co, pe, lo); |
358 | CFReleaseSafe(lo); | |
359 | CFReleaseSafe(pe); | |
360 | CFReleaseSafe(co); | |
361 | CFReleaseSafe(uo); | |
362 | CFReleaseSafe(po); | |
363 | ||
364 | return desc; | |
365 | } | |
366 | else | |
367 | return CFSTR("NULL"); | |
368 | } | |
369 | ||
370 | static Boolean SOSPeerCompare(CFTypeRef cfA, CFTypeRef cfB) | |
371 | { | |
372 | SOSPeerRef peerA = (SOSPeerRef)cfA, peerB = (SOSPeerRef)cfB; | |
373 | // Use mainly to see if peerB is actually this device (peerA) | |
374 | return CFStringCompare(SOSPeerGetID(peerA), SOSPeerGetID(peerB), 0) == kCFCompareEqualTo; | |
375 | } | |
376 | ||
5c19dc3a A |
377 | |
378 | static bool SOSPeerGetPersistedBoolean(CFDictionaryRef persisted, CFStringRef key) { | |
379 | CFBooleanRef boolean = CFDictionaryGetValue(persisted, key); | |
380 | return boolean && CFBooleanGetValue(boolean); | |
381 | } | |
382 | ||
383 | static CFDataRef SOSPeerGetPersistedData(CFDictionaryRef persisted, CFStringRef key) { | |
384 | return asData(CFDictionaryGetValue(persisted, key), NULL); | |
385 | } | |
386 | ||
387 | static int64_t SOSPeerGetPersistedInt64(CFDictionaryRef persisted, CFStringRef key) { | |
388 | int64_t integer = 0; | |
389 | CFNumberRef number = CFDictionaryGetValue(persisted, key); | |
390 | if (number) { | |
391 | CFNumberGetValue(number, kCFNumberSInt64Type, &integer); | |
392 | } | |
393 | return integer; | |
394 | } | |
395 | ||
396 | static void SOSPeerGetOptionalPersistedCFIndex(CFDictionaryRef persisted, CFStringRef key, CFIndex *value) { | |
397 | CFNumberRef number = CFDictionaryGetValue(persisted, key); | |
398 | if (number) { | |
399 | CFNumberGetValue(number, kCFNumberCFIndexType, value); | |
400 | } | |
401 | } | |
402 | ||
403 | static CFSetRef SOSPeerGetPersistedViewNameSet(SOSPeerRef peer, CFDictionaryRef persisted, CFStringRef key) { | |
404 | CFSetRef vns = CFDictionaryGetValue(persisted, key); | |
405 | if (!vns) { | |
406 | // Engine state in db contained a v0 peer, thus it must be in the V0ViewSet. | |
407 | vns = SOSViewsGetV0ViewSet(); | |
408 | secnotice("peer", "%@ had no views, inferring: %@", peer->peer_id, vns); | |
409 | } | |
410 | return vns; | |
411 | } | |
412 | ||
413 | // | |
414 | // MARK: Backup Peers | |
415 | // | |
416 | ||
417 | void SOSBackupPeerPostNotification(const char *reason) { | |
418 | // Let sbd know when a notable event occurs | |
419 | // - Disk full | |
420 | // - Backup bag change | |
421 | secnotice("backup", "posting notification to CloudServices: %s", reason?reason:""); | |
422 | notify_post(kSecItemBackupNotification); | |
423 | } | |
424 | ||
425 | static bool SOSPeerDoWithJournalPath(SOSPeerRef peer, CFErrorRef *error, void(^with)(const char *journalPath)) { | |
426 | // TODO: Probably switch to using CFURL to construct the path. | |
427 | bool ok = true; | |
428 | char strBuffer[PATH_MAX + 1]; | |
429 | size_t userTempLen = confstr(_CS_DARWIN_USER_TEMP_DIR, strBuffer, sizeof(strBuffer)); | |
430 | if (userTempLen == 0) { | |
431 | ok = SecCheckErrno(-1, error, CFSTR("confstr on _CS_DARWIN_USER_TEMP_DIR returned an error.")); | |
432 | } else { | |
433 | CFStringRef journalName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s/SOSBackup-%@"), strBuffer, SOSPeerGetID(peer)); | |
434 | CFStringPerformWithCString(journalName, with); | |
435 | CFReleaseSafe(journalName); | |
436 | } | |
437 | return ok; | |
438 | } | |
439 | ||
440 | static FILE *fopen_journal(const char *journalPath, const char *mode, CFErrorRef *error) { | |
441 | FILE *file = fopen(journalPath, mode); | |
442 | SecCheckErrno(!file, error, CFSTR("fopen %s,%s"), journalPath, mode); | |
443 | return file; | |
444 | } | |
445 | ||
446 | #include <sys/stat.h> | |
447 | ||
448 | #if !defined(NDEBUG) | |
449 | static off_t getFileSize(int fd) { | |
6b200bc3 A |
450 | struct stat sb; |
451 | fstat(fd, &sb); | |
452 | return sb.st_size; | |
5c19dc3a A |
453 | } |
454 | #endif | |
455 | ||
456 | int SOSPeerHandoffFD(SOSPeerRef peer, CFErrorRef *error) { | |
457 | __block int fd = -1; | |
458 | SOSPeerDoWithJournalPath(peer, error, ^(const char *journalName) { | |
459 | fd = open(journalName, O_RDONLY | O_CLOEXEC); | |
460 | if (SecCheckErrno(fd < 0, error, CFSTR("open %s"), journalName)) { | |
461 | if (!SecCheckErrno(unlink(journalName), error, CFSTR("unlink %s"), journalName)) { | |
462 | close(fd); | |
463 | fd = -1; | |
464 | } else { | |
465 | secdebug("backup", "Handing off file %s with fd %d of size %llu", journalName, fd, getFileSize(fd)); | |
466 | } | |
467 | } else { | |
468 | secdebug("backup", "Handing off file %s failed, %@", journalName, error?*error:NULL); | |
469 | } | |
470 | }); | |
471 | return fd; | |
472 | } | |
473 | ||
474 | static CFDataRef SOSPeerCopyAKSKeyBag(SOSPeerRef peer, CFErrorRef *error) { | |
475 | if (CFEqual(peer->peer_id, kSOSViewKeychainV0_tomb)) { | |
476 | return CFRetainSafe(peer->_keyBag); | |
477 | } else { | |
478 | CFDataRef aksKeybag = NULL; | |
479 | SOSBackupSliceKeyBagRef backupSliceKeyBag = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, peer->_keyBag, error); | |
480 | if (backupSliceKeyBag) { | |
481 | aksKeybag = SOSBSKBCopyAKSBag(backupSliceKeyBag, error); | |
482 | CFRelease(backupSliceKeyBag); | |
483 | } | |
484 | return aksKeybag; | |
485 | } | |
486 | } | |
487 | ||
488 | bool SOSPeerAppendToJournal(SOSPeerRef peer, CFErrorRef *error, void(^with)(FILE *journalFile, keybag_handle_t kbhandle)) { | |
489 | __block bool ok = true; | |
490 | // We only need a keybag if we are writing ADDs. Since we don't know at this layer | |
491 | // what operations we may be doing, open keybag if we have one, otherwise don't | |
492 | ok &= SOSPeerDoWithJournalPath(peer, error, ^(const char *fname) { | |
493 | FILE *file = fopen_journal(fname, "a", error); | |
494 | if (file) { | |
495 | keybag_handle_t kbhandle = bad_keybag_handle; | |
496 | CFDataRef keybag = SOSPeerCopyAKSKeyBag(peer, error); | |
497 | ok = keybag; | |
498 | if (ok && (ok = ks_open_keybag(keybag, NULL, &kbhandle, error))) { | |
499 | with(file, kbhandle); | |
500 | if (kbhandle != bad_keybag_handle) | |
501 | ok &= ks_close_keybag(kbhandle, error); | |
502 | } | |
503 | CFReleaseSafe(keybag); | |
504 | fclose(file); | |
505 | } | |
506 | }); | |
507 | return ok; | |
508 | } | |
509 | ||
510 | static bool SOSPeerTruncateJournal(SOSPeerRef peer, CFErrorRef *error, void(^with)(FILE *journalFile)) { | |
511 | __block bool ok = true; | |
512 | ok &= SOSPeerDoWithJournalPath(peer, error, ^(const char *fname) { | |
513 | FILE *file = fopen_journal(fname, "w", error); | |
514 | if (file) { | |
515 | with(file); | |
516 | fclose(file); | |
517 | } | |
518 | }); | |
519 | return ok; | |
520 | } | |
521 | ||
522 | bool SOSPeerSetState(SOSPeerRef p, SOSEngineRef engine, CFDictionaryRef state, CFErrorRef *error) { | |
523 | bool ok = true; | |
524 | if (state) { | |
525 | SOSPeerGetOptionalPersistedCFIndex(state, kSOSPeerVersionKey, &p->version); | |
526 | ||
527 | p->sequenceNumber = SOSPeerGetPersistedInt64(state, kSOSPeerSequenceNumberKey); | |
528 | p->mustSendMessage = SOSPeerGetPersistedBoolean(state, kSOSPeerMustSendMessageKey); | |
529 | p->sendObjects = SOSPeerGetPersistedBoolean(state, kSOSPeerSendObjectsKey); | |
6b200bc3 | 530 | p->hasBeenInSync = SOSPeerGetPersistedBoolean(state, kSOSPeerHasBeenInSyncKey); |
5c19dc3a A |
531 | CFRetainAssign(p->views, SOSPeerGetPersistedViewNameSet(p, state, kSOSPeerViewsKey)); |
532 | SOSPeerSetKeyBag(p, SOSPeerGetPersistedData(state, kSOSPeerKeyBagKey)); | |
5c19dc3a A |
533 | CFAssignRetained(p->pendingObjects, SOSEngineCopyPersistedManifest(engine, state, kSOSPeerPendingObjectsKey)); |
534 | CFAssignRetained(p->unwantedManifest, SOSEngineCopyPersistedManifest(engine, state, kSOSPeerUnwantedManifestKey)); | |
535 | CFAssignRetained(p->confirmedManifest, SOSEngineCopyPersistedManifest(engine, state, kSOSPeerConfirmedManifestKey)); | |
536 | CFAssignRetained(p->proposedManifests, SOSEngineCopyPersistedManifestArray(engine, state, kSOSPeerProposedManifestKey, error)); | |
537 | ok &= p->proposedManifests != NULL; | |
538 | CFAssignRetained(p->localManifests, SOSEngineCopyPersistedManifestArray(engine, state, kSOSPeerLocalManifestKey, error)); | |
539 | ok &= p->localManifests != NULL; | |
540 | } | |
541 | return ok; | |
542 | } | |
543 | ||
544 | static SOSPeerRef SOSPeerCreate_Internal(SOSEngineRef engine, CFDictionaryRef state, CFStringRef theirPeerID, CFIndex version, CFErrorRef *error) { | |
545 | SOSPeerRef p = CFTypeAllocate(SOSPeer, struct __OpaqueSOSPeer, kCFAllocatorDefault); | |
546 | p->peer_id = CFRetainSafe(theirPeerID); | |
547 | p->version = version; | |
866f8763 A |
548 | p->otrTimers = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); |
549 | ||
5c19dc3a A |
550 | CFDictionaryRef empty = NULL; |
551 | if (!state) { | |
552 | empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); | |
553 | state = empty; | |
554 | } | |
555 | if (!SOSPeerSetState(p, engine, state, error)) { | |
556 | CFReleaseNull(p); | |
557 | } | |
558 | CFReleaseNull(empty); | |
559 | return p; | |
560 | } | |
561 | ||
5c19dc3a A |
562 | static void SOSPeerPersistBool(CFMutableDictionaryRef persist, CFStringRef key, bool value) { |
563 | CFDictionarySetValue(persist, key, value ? kCFBooleanTrue : kCFBooleanFalse); | |
564 | } | |
565 | ||
566 | static void SOSPeerPersistInt64(CFMutableDictionaryRef persist, CFStringRef key, int64_t value) { | |
567 | CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &value); | |
568 | CFDictionarySetValue(persist, key, number); | |
569 | CFReleaseSafe(number); | |
570 | } | |
571 | ||
572 | static void SOSPeerPersistCFIndex(CFMutableDictionaryRef persist, CFStringRef key, CFIndex value) { | |
573 | CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &value); | |
574 | CFDictionarySetValue(persist, key, number); | |
575 | CFReleaseSafe(number); | |
576 | } | |
577 | ||
578 | static bool SOSPeerPersistOptionalManifest(CFMutableDictionaryRef persist, CFStringRef key, SOSManifestRef manifest, CFErrorRef *error) { | |
579 | if (!manifest) | |
580 | return true; | |
581 | CFDataRef digest = SOSManifestGetDigest(manifest, error); | |
582 | bool ok = digest; | |
583 | if (ok) | |
584 | CFDictionarySetValue(persist, key, digest); | |
585 | return ok; | |
586 | } | |
587 | ||
588 | static bool SSOSPeerPersistManifestArray(CFMutableDictionaryRef persist, CFStringRef key, CFArrayRef manifests, CFErrorRef *error) { | |
589 | CFMutableArrayRef digests = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
590 | SOSManifestRef manifest; | |
591 | if (manifests) CFArrayForEachC(manifests, manifest) { | |
592 | CFDataRef digest = SOSManifestGetDigest(manifest, error); | |
593 | if (!digest) | |
594 | CFReleaseNull(digests); | |
595 | if (digests) { | |
596 | CFArrayAppendValue(digests, digest); | |
597 | } | |
598 | } | |
599 | if (digests) { | |
600 | CFDictionarySetValue(persist, key, digests); | |
601 | CFRelease(digests); | |
602 | } | |
603 | return digests; | |
604 | } | |
605 | ||
606 | static void SOSPeerPersistOptionalValue(CFMutableDictionaryRef persist, CFStringRef key, CFTypeRef value) { | |
607 | if (value) | |
608 | CFDictionarySetValue(persist, key, value); | |
609 | } | |
610 | ||
611 | CFDictionaryRef SOSPeerCopyState(SOSPeerRef peer, CFErrorRef *error) { | |
612 | CFMutableDictionaryRef state = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
613 | SOSPeerPersistInt64(state, kSOSPeerSequenceNumberKey, peer->sequenceNumber); | |
614 | if (peer->version) { | |
615 | SOSPeerPersistCFIndex(state, kSOSPeerVersionKey, peer->version); | |
616 | } | |
617 | ||
618 | SOSPeerPersistBool(state, kSOSPeerMustSendMessageKey, peer->mustSendMessage); | |
619 | SOSPeerPersistBool(state, kSOSPeerSendObjectsKey, peer->sendObjects); | |
6b200bc3 | 620 | SOSPeerPersistBool(state, kSOSPeerHasBeenInSyncKey, peer->hasBeenInSync); |
5c19dc3a A |
621 | SOSPeerPersistOptionalValue(state, kSOSPeerViewsKey, peer->views); |
622 | ||
623 | CFDataRef keybag = SOSPeerGetKeyBag(peer); | |
624 | if (keybag && !CFEqual(peer->peer_id, kSOSViewKeychainV0_tomb)) | |
625 | SOSPeerPersistOptionalValue(state, kSOSPeerKeyBagKey, keybag); | |
626 | ||
fa7225c8 | 627 | if (!SOSPeerPersistOptionalManifest(state, kSOSPeerPendingObjectsKey, peer->pendingObjects, error) |
5c19dc3a A |
628 | || !SOSPeerPersistOptionalManifest(state, kSOSPeerUnwantedManifestKey, peer->unwantedManifest, error) |
629 | || !SOSPeerPersistOptionalManifest(state, kSOSPeerConfirmedManifestKey, peer->confirmedManifest, error) | |
630 | || !SSOSPeerPersistManifestArray(state, kSOSPeerProposedManifestKey, peer->proposedManifests, error) | |
631 | || !SSOSPeerPersistManifestArray(state, kSOSPeerLocalManifestKey, peer->localManifests, error)) { | |
632 | CFReleaseNull(state); | |
633 | } | |
634 | return state; | |
635 | } | |
636 | ||
637 | SOSPeerRef SOSPeerCreateWithState(SOSEngineRef engine, CFStringRef peer_id, CFDictionaryRef state, CFErrorRef *error) { | |
638 | return SOSPeerCreate_Internal(engine, state, peer_id, 0, error); | |
639 | } | |
640 | ||
641 | static void SOSPeerDestroy(CFTypeRef cf) { | |
642 | SOSPeerRef peer = (SOSPeerRef)cf; | |
643 | CFReleaseNull(peer->peer_id); | |
644 | CFReleaseNull(peer->views); | |
5c19dc3a A |
645 | CFReleaseNull(peer->pendingObjects); |
646 | CFReleaseNull(peer->unwantedManifest); | |
647 | CFReleaseNull(peer->confirmedManifest); | |
648 | CFReleaseNull(peer->proposedManifests); | |
649 | CFReleaseNull(peer->localManifests); | |
866f8763 A |
650 | CFReleaseNull(peer->otrTimers); |
651 | CFReleaseNull(peer->limiter); | |
ecaf5866 | 652 | CFReleaseNull(peer->_keyBag); |
5c19dc3a A |
653 | } |
654 | ||
655 | bool SOSPeerDidConnect(SOSPeerRef peer) { | |
656 | SOSPeerSetMustSendMessage(peer, true); | |
657 | SOSPeerSetProposedManifest(peer, SOSPeerGetConfirmedManifest(peer)); | |
658 | // TODO: Return false if nothing changed. | |
659 | return true; | |
660 | } | |
661 | ||
662 | // MARK: accessors | |
663 | ||
664 | CFIndex SOSPeerGetVersion(SOSPeerRef peer) { | |
665 | return peer->version; | |
666 | } | |
667 | ||
866f8763 A |
668 | void SOSPeerSetRateLimiter(SOSPeerRef peer, CFTypeRef limiter) |
669 | { | |
670 | peer->limiter = CFRetainSafe(limiter); | |
671 | } | |
672 | ||
673 | CFTypeRef SOSPeerGetRateLimiter(SOSPeerRef peer) | |
674 | { | |
675 | return (peer->limiter ? peer->limiter : NULL); | |
676 | } | |
677 | ||
5c19dc3a A |
678 | CFStringRef SOSPeerGetID(SOSPeerRef peer) { |
679 | return peer->peer_id; | |
680 | } | |
681 | ||
682 | CFSetRef SOSPeerGetViewNameSet(SOSPeerRef peer) { | |
683 | return peer->views; | |
684 | } | |
685 | ||
686 | void SOSPeerSetViewNameSet(SOSPeerRef peer, CFSetRef views) { | |
6b200bc3 A |
687 | if (peer->views && !CFSetIsSubset(views, peer->views)) { |
688 | SOSPeerSetHasBeenInSync(peer, false); | |
689 | } | |
690 | ||
5c19dc3a A |
691 | CFRetainAssign(peer->views, views); |
692 | } | |
693 | ||
694 | CFDataRef SOSPeerGetKeyBag(SOSPeerRef peer) { | |
695 | return peer->_keyBag; | |
696 | } | |
697 | ||
698 | static bool SOSPeerUnlinkBackupJournal(SOSPeerRef peer, CFErrorRef *error) { | |
699 | __block bool ok = true; | |
700 | ok &= SOSPeerDoWithJournalPath(peer, error, ^(const char *journalName) { | |
701 | secnotice("backup", "%@ unlinking journal file %s", peer, journalName); | |
702 | ok &= SecCheckErrno(unlink(journalName), error, CFSTR("unlink %s"), journalName); | |
703 | }); | |
704 | return ok; | |
705 | } | |
706 | ||
707 | static bool SOSPeerWriteReset(SOSPeerRef peer, CFErrorRef *error) { | |
708 | __block bool ok = true; | |
709 | __block CFErrorRef localError = NULL; | |
710 | ok &= SOSPeerTruncateJournal(peer, &localError, ^(FILE *journalFile) { | |
711 | ok = SOSBackupEventWriteReset(journalFile, peer->_keyBag, &localError); | |
712 | if (ok && !peer->_keyBag) | |
713 | ok = SOSBackupEventWriteCompleteMarker(journalFile, 999, &localError); | |
714 | }); | |
6b200bc3 | 715 | |
5c19dc3a A |
716 | if (!ok) { |
717 | secwarning("%@ failed to write reset to backup journal: %@", peer->peer_id, localError); | |
718 | CFErrorPropagate(localError, error); | |
6b200bc3 A |
719 | } else { |
720 | secnotice("backup-peer", "%@ Wrote reset.", peer->peer_id); | |
5c19dc3a A |
721 | } |
722 | ||
723 | // Forget we ever wrote anything to the journal. | |
724 | SOSPeerSetConfirmedManifest(peer, NULL); | |
725 | SOSPeerSetProposedManifest(peer, NULL); | |
726 | ||
727 | SOSPeerSetMustSendMessage(peer, !ok); | |
728 | return ok; | |
729 | } | |
730 | ||
731 | void SOSPeerKeyBagDidChange(SOSPeerRef peer) { | |
732 | // If !keyBag unlink the file, instead of writing a reset. | |
733 | // CloudServices does not want to hear about empty keybags | |
734 | SOSPeerSetSendObjects(peer, false); | |
735 | if (!peer->_keyBag) { | |
736 | SOSPeerUnlinkBackupJournal(peer, NULL); | |
737 | } else { | |
738 | // Attempt to write a reset (ignoring failures since it will | |
739 | // be pended stickily if it fails). | |
740 | SOSPeerWriteReset(peer, NULL); | |
805875f8 | 741 | SOSCCAccountTriggerSyncWithBackupPeer_server(SOSPeerGetID(peer)); |
5c19dc3a A |
742 | } |
743 | } | |
744 | ||
745 | void SOSPeerSetKeyBag(SOSPeerRef peer, CFDataRef keyBag) { | |
746 | if (CFEqualSafe(keyBag, peer->_keyBag)) return; | |
747 | bool hadKeybag = peer->_keyBag; | |
748 | if (!keyBag) { | |
749 | secwarning("%@ keybag for backup unset", SOSPeerGetID(peer)); | |
750 | } else { | |
751 | secnotice("backup", "%@ backup bag: %@", SOSPeerGetID(peer), keyBag); | |
752 | } | |
753 | CFRetainAssign(peer->_keyBag, keyBag); | |
754 | // Don't call SOSPeerKeybagDidChange for the inital edge from NULL -> having a keybag. | |
755 | if (hadKeybag) { | |
756 | SOSPeerKeyBagDidChange(peer); | |
757 | } | |
758 | } | |
759 | ||
760 | bool SOSPeerWritePendingReset(SOSPeerRef peer, CFErrorRef *error) { | |
761 | return !SOSPeerMustSendMessage(peer) || SOSPeerWriteReset(peer, error); | |
762 | } | |
763 | ||
764 | uint64_t SOSPeerNextSequenceNumber(SOSPeerRef peer) { | |
765 | return ++peer->sequenceNumber; | |
766 | } | |
767 | ||
768 | uint64_t SOSPeerGetMessageVersion(SOSPeerRef peer) { | |
769 | return SOSPeerGetVersion(peer); | |
770 | } | |
771 | ||
772 | bool SOSPeerMustSendMessage(SOSPeerRef peer) { | |
773 | return peer->mustSendMessage; | |
774 | } | |
775 | ||
776 | void SOSPeerSetMustSendMessage(SOSPeerRef peer, bool sendMessage) { | |
777 | peer->mustSendMessage = sendMessage; | |
778 | } | |
779 | ||
780 | bool SOSPeerSendObjects(SOSPeerRef peer) { | |
781 | return peer->sendObjects; | |
782 | } | |
783 | ||
784 | void SOSPeerSetSendObjects(SOSPeerRef peer, bool sendObjects) { | |
785 | peer->sendObjects = sendObjects; | |
786 | } | |
787 | ||
6b200bc3 A |
788 | bool SOSPeerHasBeenInSync(SOSPeerRef peer) { |
789 | return peer->hasBeenInSync; | |
790 | } | |
791 | ||
792 | void SOSPeerSetHasBeenInSync(SOSPeerRef peer, bool hasBeenInSync) { | |
793 | peer->hasBeenInSync = hasBeenInSync; | |
794 | } | |
795 | ||
5c19dc3a A |
796 | // MARK: Manifests |
797 | ||
798 | SOSManifestRef SOSPeerGetProposedManifest(SOSPeerRef peer) { | |
799 | if (peer->proposedManifests && CFArrayGetCount(peer->proposedManifests)) | |
800 | return (SOSManifestRef)CFArrayGetValueAtIndex(peer->proposedManifests, 0); | |
801 | return NULL; | |
802 | } | |
803 | ||
804 | SOSManifestRef SOSPeerGetConfirmedManifest(SOSPeerRef peer) { | |
805 | return peer->confirmedManifest; | |
806 | } | |
807 | ||
808 | void SOSPeerSetConfirmedManifest(SOSPeerRef peer, SOSManifestRef confirmed) { | |
809 | CFRetainAssign(peer->confirmedManifest, confirmed); | |
810 | ||
811 | // TODO: Clear only expired pending and local manifests from the array - this clears them all | |
812 | // To do so we'd have to track the messageIds we sent to our peer and when we proposed a particular manifest. | |
813 | // Then we simply remove the entries from messages older than the one we are confirming now | |
814 | //CFArrayRemoveAllValues(SOSPeerGetDigestsWithKey(peer, kSOSPeerProposedManifestKey)); | |
815 | //CFArrayRemoveAllValues(SOSPeerGetDigestsWithKey(peer, kSOSPeerLocalManifestKey)); | |
816 | } | |
817 | ||
818 | void SOSPeerAddProposedManifest(SOSPeerRef peer, SOSManifestRef proposed) { | |
819 | SOSManifestArrayAppendManifest(&peer->proposedManifests, proposed); | |
820 | } | |
821 | ||
822 | void SOSPeerSetProposedManifest(SOSPeerRef peer, SOSManifestRef proposed) { | |
823 | SOSManifestArraySetManifest(&peer->proposedManifests, proposed); | |
824 | } | |
825 | ||
826 | void SOSPeerAddLocalManifest(SOSPeerRef peer, SOSManifestRef local) { | |
827 | SOSManifestArrayAppendManifest(&peer->localManifests, local); | |
828 | } | |
829 | ||
830 | SOSManifestRef SOSPeerGetPendingObjects(SOSPeerRef peer) { | |
831 | return peer->pendingObjects; | |
832 | } | |
833 | ||
834 | void SOSPeerSetPendingObjects(SOSPeerRef peer, SOSManifestRef pendingObjects) { | |
835 | CFRetainAssign(peer->pendingObjects, pendingObjects); | |
836 | } | |
837 | ||
838 | SOSManifestRef SOSPeerGetUnwantedManifest(SOSPeerRef peer) { | |
839 | return peer->unwantedManifest; | |
840 | } | |
841 | ||
842 | void SOSPeerSetUnwantedManifest(SOSPeerRef peer, SOSManifestRef unwantedManifest) { | |
843 | CFRetainAssign(peer->unwantedManifest, unwantedManifest); | |
844 | } | |
866f8763 A |
845 | bool SOSPeerTimerForPeerExist(SOSPeerRef peer){ |
846 | dispatch_source_t timer = SOSPeerGetOTRTimer(peer); | |
847 | return timer ? true : false; | |
848 | } | |
849 | void SOSPeerSetOTRTimer(SOSPeerRef peer, dispatch_source_t timer){ | |
ecaf5866 | 850 | NSMutableDictionary* timers = (NSMutableDictionary*)CFBridgingRelease(peer->otrTimers); |
866f8763 A |
851 | if(!timers) |
852 | timers = [[NSMutableDictionary alloc]init]; | |
853 | ||
854 | [timers setObject:timer forKey:(__bridge NSString*)SOSPeerGetID(peer)]; | |
855 | peer->otrTimers = (CFMutableDictionaryRef)CFBridgingRetain(timers); | |
856 | } | |
857 | ||
858 | dispatch_source_t SOSPeerGetOTRTimer(SOSPeerRef peer){ | |
859 | return (dispatch_source_t)CFDictionaryGetValue(peer->otrTimers, SOSPeerGetID(peer)); | |
860 | } | |
861 | ||
862 | void SOSPeerRemoveOTRTimerEntry(SOSPeerRef peer){ | |
863 | CFDictionaryRemoveValue(peer->otrTimers, SOSPeerGetID(peer)); | |
864 | } | |
5c19dc3a A |
865 | |
866 | SOSManifestRef SOSPeerCopyManifestForDigest(SOSPeerRef peer, CFDataRef digest) { | |
867 | if (!digest) return NULL; | |
868 | SOSManifestRef manifest; | |
869 | if (peer->proposedManifests) CFArrayForEachC(peer->proposedManifests, manifest) { | |
870 | if (CFEqual(digest, SOSManifestGetDigest(manifest, NULL))) | |
871 | return CFRetainSafe(manifest); | |
872 | } | |
873 | if (peer->localManifests) CFArrayForEachC(peer->localManifests, manifest) { | |
874 | if (CFEqual(digest, SOSManifestGetDigest(manifest, NULL))) | |
875 | return CFRetainSafe(manifest); | |
876 | } | |
877 | if (peer->confirmedManifest && CFEqual(digest, SOSManifestGetDigest(peer->confirmedManifest, NULL))) | |
878 | return CFRetainSafe(peer->confirmedManifest); | |
879 | ||
880 | return NULL; | |
881 | } | |
882 | ||
883 | static void SOSMarkManifestInUse(struct SOSDigestVector *mdInUse, SOSManifestRef manifest) { | |
884 | CFDataRef digest = SOSManifestGetDigest(manifest, NULL); | |
885 | if (digest) | |
886 | SOSDigestVectorAppend(mdInUse, CFDataGetBytePtr(digest)); | |
887 | } | |
888 | ||
889 | static void SOSMarkManifestsInUse(struct SOSDigestVector *mdInUse, CFArrayRef manifests) { | |
890 | if (!isArray(manifests)) return; | |
891 | SOSManifestRef manifest = NULL; | |
892 | CFArrayForEachC(manifests, manifest) { | |
893 | SOSMarkManifestInUse(mdInUse, manifest); | |
894 | } | |
895 | } | |
896 | ||
897 | // Add all digests we are using to mdInUse | |
898 | void SOSPeerMarkDigestsInUse(SOSPeerRef peer, struct SOSDigestVector *mdInUse) { | |
899 | SOSMarkManifestInUse(mdInUse, peer->pendingObjects); | |
900 | SOSMarkManifestInUse(mdInUse, peer->unwantedManifest); | |
901 | SOSMarkManifestInUse(mdInUse, peer->confirmedManifest); | |
902 | SOSMarkManifestsInUse(mdInUse, peer->localManifests); | |
903 | SOSMarkManifestsInUse(mdInUse, peer->proposedManifests); | |
904 | } | |
905 | ||
906 | static void SOSAddManifestInUse(CFMutableDictionaryRef mfc, SOSManifestRef manifest) { | |
907 | CFDataRef digest = SOSManifestGetDigest(manifest, NULL); | |
908 | CFDataRef data = SOSManifestGetData(manifest); | |
909 | if (digest && data) | |
910 | CFDictionarySetValue(mfc, digest, data); | |
911 | } | |
912 | ||
913 | static void SOSAddManifestsInUse(CFMutableDictionaryRef mfc, CFArrayRef manifests) { | |
914 | if (!isArray(manifests)) return; | |
915 | SOSManifestRef manifest = NULL; | |
916 | CFArrayForEachC(manifests, manifest) { | |
917 | SOSAddManifestInUse(mfc, manifest); | |
918 | } | |
919 | } | |
920 | ||
921 | void SOSPeerAddManifestsInUse(SOSPeerRef peer, CFMutableDictionaryRef mfc) { | |
922 | SOSAddManifestInUse(mfc, peer->pendingObjects); | |
923 | SOSAddManifestInUse(mfc, peer->unwantedManifest); | |
924 | SOSAddManifestInUse(mfc, peer->confirmedManifest); | |
925 | SOSAddManifestsInUse(mfc, peer->localManifests); | |
926 | SOSAddManifestsInUse(mfc, peer->proposedManifests); | |
927 | ||
928 | } | |
929 | ||
930 | // absentFromRemote | |
931 | // AbsentLocally | |
932 | // additionsFromRemote | |
933 | // original intent was that digests only got added to pendingObjects. We only know for sure if it is something added locally via api call | |
934 | ||
935 | ||
936 | bool SOSPeerDidReceiveRemovalsAndAdditions(SOSPeerRef peer, SOSManifestRef absentFromRemote, SOSManifestRef additionsFromRemote, SOSManifestRef unwantedFromRemote, | |
937 | SOSManifestRef local, CFErrorRef *error) { | |
938 | // We assume that incoming manifests are all sorted, and absentFromRemote is disjoint from additionsFromRemote | |
939 | bool ok = true; | |
940 | SOSManifestRef remoteMissing = NULL, sharedRemovals = NULL, sharedAdditions = NULL; | |
941 | ||
942 | // TODO: Simplify -- a lot. | |
943 | ok = ok && (remoteMissing = SOSManifestCreateIntersection(absentFromRemote, local, error)); // remoteMissing = absentFromRemote <Intersected> local | |
944 | ok = ok && (sharedRemovals = SOSManifestCreateComplement(remoteMissing, absentFromRemote, error)); // sharedRemovals = absentFromRemote - remoteMissing | |
945 | ok = ok && (sharedAdditions = SOSManifestCreateIntersection(additionsFromRemote, local, error)); // sharedAdditions = additionsFromRemote <Intersected> local | |
946 | //ok = ok && (remoteAdditions = SOSManifestCreateComplement(sharedAdditions, additionsFromRemote, error)); // remoteAdditions = additionsFromRemote - sharedAdditions | |
947 | ||
948 | // remoteMissing are things we have that remote has asked for => add to pendingObjects | |
949 | // sharedRemovals are things we don't have that remote has asked for => remove from pendingDeletes | |
950 | // sharedAdditions are things we have that remote has too => remove from pendingObjects | |
951 | // remoteAdditions are things that remote said they have that we don't and we should probably ask for => add to pendingDeletes? | |
952 | // unwantedFromRemote are things we received from remote for which we already have a newer object according to the conflict resolver. | |
953 | secnotice("peer", "%@ RM:%@ SR:%@ SA:%@ UR:%@", peer, remoteMissing, sharedRemovals, sharedAdditions, unwantedFromRemote); | |
954 | ||
955 | SOSManifestRef pendingObjectsManifest = SOSManifestCreateWithPatch(peer->pendingObjects, sharedAdditions, remoteMissing, error); | |
956 | SOSManifestRef unwantedManifest = SOSManifestCreateWithPatch(peer->unwantedManifest, sharedRemovals, unwantedFromRemote, error); | |
957 | CFAssignRetained(peer->pendingObjects, pendingObjectsManifest); // PO = PO - sharedAdditions + remoteMissing | |
958 | CFAssignRetained(peer->unwantedManifest, unwantedManifest); // U = U - sharedRemovals + unwantedFromRemote | |
959 | ||
960 | CFReleaseSafe(remoteMissing); | |
961 | CFReleaseSafe(sharedRemovals); | |
962 | CFReleaseSafe(sharedAdditions); | |
963 | ||
964 | secnotice("peer", "%@ C:%@ U:%@ O:%@", peer, SOSPeerGetConfirmedManifest(peer), SOSPeerGetUnwantedManifest(peer), SOSPeerGetPendingObjects(peer)); | |
965 | ||
966 | return ok; | |
967 | } | |
968 | ||
969 | // Called for a normal syncing peer. Only updates pendingObjects currently. | |
970 | bool SOSPeerDataSourceWillCommit(SOSPeerRef peer, SOSDataSourceTransactionSource source, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error) { | |
971 | bool isAPITransaction = source == kSOSDataSourceAPITransaction; | |
866f8763 | 972 | bool isCKKSTransaction = source == kSOSDataSourceCKKSTransaction; |
5c19dc3a | 973 | SOSManifestRef unconfirmedAdditions = NULL; |
866f8763 | 974 | if ((isAPITransaction || isCKKSTransaction) && SOSManifestGetCount(additions)) { |
5c19dc3a A |
975 | // Remove confirmed from additions, leaving us with additions to the local db that the remote peer doesn't have yet |
976 | unconfirmedAdditions = SOSManifestCreateComplement(SOSPeerGetConfirmedManifest(peer), additions, error); | |
977 | } | |
978 | ||
979 | if (SOSManifestGetCount(removals) || SOSManifestGetCount(unconfirmedAdditions)) { | |
980 | SOSManifestRef pendingObjectsManifest = SOSManifestCreateWithPatch(peer->pendingObjects, removals, unconfirmedAdditions, error); | |
981 | ||
982 | //#if DEBUG | |
983 | // TODO: Only do this if debugScope "peer", notice is enabled. | |
984 | // if (!SecIsScopeActive(kSecLevelNotice, "peer")) | |
985 | { | |
986 | // pended == UA unless the db is renotifying of an addition for something we already have | |
987 | SOSManifestRef unpended = NULL, pended = NULL; | |
988 | SOSManifestDiff(peer->pendingObjects, pendingObjectsManifest, &unpended, &pended, error); | |
989 | secinfo("peer", "%@: willCommit R:%@ A:%@ UA:%@ %s O%s%@%s%@", | |
990 | SOSPeerGetID(peer), removals, additions, unconfirmedAdditions, | |
866f8763 | 991 | (isAPITransaction ? "api": isCKKSTransaction ? "ckks" : "sos"), |
5c19dc3a A |
992 | (SOSManifestGetCount(unpended) ? "-" : ""), |
993 | (SOSManifestGetCount(unpended) ? (CFStringRef)unpended : CFSTR("")), | |
994 | (SOSManifestGetCount(pended) ? "+" : SOSManifestGetCount(unpended) ? "" : "="), | |
995 | (SOSManifestGetCount(pended) ? (CFStringRef)pended : CFSTR(""))); | |
996 | CFReleaseSafe(unpended); | |
997 | CFReleaseSafe(pended); | |
998 | } | |
999 | //#endif /* DEBUG */ | |
1000 | CFAssignRetained(peer->pendingObjects, pendingObjectsManifest); | |
1001 | } | |
1002 | CFReleaseSafe(unconfirmedAdditions); | |
1003 | ||
1004 | return true; | |
1005 | } | |
1006 | ||
1007 | bool SOSPeerWriteAddEvent(FILE *journalFile, keybag_handle_t kbhandle, SOSDataSourceRef dataSource, SOSObjectRef object, CFErrorRef *error) { | |
1008 | CFDictionaryRef backup_item = NULL; | |
1009 | bool ok = ((backup_item = SOSObjectCopyBackup(dataSource, object, kbhandle, error)) | |
1010 | && SOSBackupEventWriteAdd(journalFile, backup_item, error)); | |
1011 | CFReleaseSafe(backup_item); | |
1012 | return ok; | |
1013 | } | |
1014 | ||
1015 | // Called for a backup peer, should stream objects in changes right away | |
1016 | bool SOSPeerDataSourceWillChange(SOSPeerRef peer, SOSDataSourceRef dataSource, SOSDataSourceTransactionSource source, CFArrayRef changes, CFErrorRef *error) { | |
1017 | __block bool ok = true; | |
1018 | ok &= SOSPeerWritePendingReset(peer, error) && SOSPeerAppendToJournal(peer, error, ^(FILE *journalFile, keybag_handle_t kbhandle) { | |
1019 | struct SOSDigestVector dvdel = SOSDigestVectorInit; | |
1020 | struct SOSDigestVector dvadd = SOSDigestVectorInit; | |
1021 | SOSChangeRef change; | |
1022 | CFArrayForEachC(changes, change) { | |
1023 | bool isDelete = false; | |
1024 | CFErrorRef localError = NULL; | |
1025 | CFDataRef digest = NULL; | |
1026 | SOSObjectRef object = NULL; | |
1027 | bool ok = digest = SOSChangeCopyDigest(dataSource, change, &isDelete, &object, &localError); | |
1028 | if (isDelete) { | |
1029 | ok &= SOSBackupEventWriteDelete(journalFile, digest, &localError); | |
1030 | SOSDigestVectorAppend(&dvdel, CFDataGetBytePtr(digest)); | |
1031 | } else { | |
1032 | ok &= SOSPeerWriteAddEvent(journalFile, kbhandle, dataSource, object, &localError); | |
1033 | SOSDigestVectorAppend(&dvadd, CFDataGetBytePtr(digest)); | |
1034 | } | |
1035 | if (!ok) { | |
1036 | secerror("bad change %@: %@", change, localError); | |
1037 | } | |
1038 | CFReleaseSafe(digest); | |
1039 | CFReleaseSafe(localError); | |
1040 | }; | |
1041 | ||
1042 | if (ok) { | |
1043 | // Update our proposed manifest since we just wrote stuff | |
1044 | struct SOSDigestVector dvresult = SOSDigestVectorInit; | |
1045 | SOSDigestVectorSort(&dvdel); | |
1046 | SOSDigestVectorSort(&dvadd); | |
1047 | if ((ok = SOSDigestVectorPatchSorted(SOSManifestGetDigestVector(SOSPeerGetProposedManifest(peer)), &dvdel, | |
1048 | &dvadd, &dvresult, error))) { | |
1049 | SOSManifestRef proposed; | |
1050 | ok = proposed = SOSManifestCreateWithDigestVector(&dvresult, error); | |
1051 | SOSPeerSetProposedManifest(peer, proposed); | |
1052 | CFReleaseSafe(proposed); | |
1053 | } | |
1054 | SOSDigestVectorFree(&dvresult); | |
1055 | } | |
1056 | SOSDigestVectorFree(&dvdel); | |
1057 | SOSDigestVectorFree(&dvadd); | |
1058 | ||
1059 | // Only Write marker if we are actually in sync now (local == propopsed). | |
1060 | if (SOSPeerSendObjects(peer)) | |
1061 | SOSBackupEventWriteCompleteMarker(journalFile, 799, error); | |
1062 | }); | |
1063 | ||
1064 | if (!ok) { | |
1065 | // We were unable to stream everything out neatly | |
805875f8 A |
1066 | NSArray<NSString *> *backupPeerList = @[ (__bridge NSString *)SOSPeerGetID(peer) ]; |
1067 | SOSCCRequestSyncWithBackupPeerList((__bridge CFArrayRef)backupPeerList); | |
5c19dc3a A |
1068 | } |
1069 | return ok; | |
1070 | } | |
866f8763 | 1071 |