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