2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
26 * SOSPeer.c - Implementation of a secure object syncing peer
28 #include <Security/SecureObjectSync/SOSPeer.h>
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>
46 #include <securityd/SOSCloudCircleServer.h>
48 #include <CoreFoundation/CoreFoundation.h>
52 #include <AssertMacros.h>
54 // Backup Peer Support
55 #include <securityd/SecKeybagSupport.h>
60 // MARK: - SOSPeerPersistence code
62 static CFStringRef kSOSPeerSequenceNumberKey
= CFSTR("sequence-number");
64 CFStringRef kSOSPeerDataLabel
= CFSTR("iCloud Peer Data Meta-data");
67 // MARK: SOSPeerState (dictionary keys)
70 // PeerState dictionary keys
71 static CFStringRef kSOSPeerSendObjectsKey
= CFSTR("send-objects"); // bool
72 static CFStringRef kSOSPeerMustSendMessageKey
= CFSTR("must-send"); // bool
73 static CFStringRef kSOSPeerHasBeenInSyncKey
= CFSTR("has-been-in-sync"); // bool
74 static CFStringRef kSOSPeerPendingObjectsKey
= CFSTR("pending-objects"); // digest
75 static CFStringRef kSOSPeerUnwantedManifestKey
= CFSTR("unwanted-manifest"); // digest
76 static CFStringRef kSOSPeerConfirmedManifestKey
= CFSTR("confirmed-manifest"); //digest
77 static CFStringRef kSOSPeerProposedManifestKey
= CFSTR("pending-manifest"); // array of digests
78 static CFStringRef kSOSPeerLocalManifestKey
= CFSTR("local-manifest"); // array of digests
79 static CFStringRef kSOSPeerVersionKey
= CFSTR("vers"); // int
82 // SOSPeerMeta keys that can also be used in peerstate...
84 static CFStringRef kSOSPeerPeerIDKey
= CFSTR("peer-id"); // string
85 static CFStringRef kSOSPeerViewsKey
= CFSTR("views"); // set (or array) of string
86 static CFStringRef kSOSPeerKeyBagKey
= CFSTR("keybag"); // data
89 Theory of syncing for both incoming and outgoing messages
91 A peerstate consists of:
92 (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)})
93 T: to be sent (pendingObjects) manifest
94 U: unwanted objects manifest
97 D->M? digest or manifest to optional manifest function
98 M->D manifest to digest of manifest function
99 H->O? hash (manifest entry) to optional object function (the datasource)
100 O->{ Mapping from incoming O objects to one of:
101 (Op,Or): Op = Peers object, Or is replaced local object.
102 Oa,(Op,Or): Oa = appeared local object, Or = Oa, see above for (Op, Or)
103 (Om,Oa),(Op,Or): Om missing local object, was apparently Oa instead see above for (Oa, Op, Or)
104 Om,Oi: Om missing local object, inserted Oi (nothing replaced), but Om still disapeared from manifest
105 Oi Oi inserted object from peer (nothing replaced)
106 Ob: Ob both remote and local object are identical, nothing changed
107 Oa,Ob: Oa = appeared local object equal to Oa = Ob. Equivalent to single Oi
108 (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
109 (Ol,Ou): Ol local object wins from peers Ou older object => append Ou to U
110 Oa,(Ol,Ou): Oa appeared as a local object and Oa = Ol above
111 (Om,Oa),(Ol,Ou): Om removed and Oa replaced Om and Oa = Ol as above
113 A message consists of
120 To send a message we simply compute:
123 E: L \ C # subsetted if not all of O is sent
124 O: H->O?->O # as size permits
125 and change P to (C \ M:) union E: union O->H->O:
127 To receive a message we compute
128 Op,Oi,Ob,Oo and Om,Oa,Ol
129 C: (D->M->B \ M) union E union O->H->O
131 T: T \ (Om union Or union A union Oa,Ob.last union (Om,Oa),Ob.last ) union (M intersect L)
139 static SOSPeerMetaRef
SOSPeerMetaCreate(CFStringRef peerID
) {
140 return CFRetain(peerID
);
143 static SOSPeerMetaRef
SOSPeerMetaCreateWithViews(CFStringRef peerID
, CFSetRef views
) {
144 const void *keys
[] = { kSOSPeerPeerIDKey
, kSOSPeerViewsKey
};
145 const void *values
[] = { peerID
, views
};
146 return CFDictionaryCreate(kCFAllocatorDefault
, keys
, values
, array_size(keys
), &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
149 static SOSPeerMetaRef
SOSPeerMetaCreateWithViewsAndKeyBag(CFStringRef peerID
, CFSetRef views
, CFDataRef keybag
) {
150 const void *keys
[] = { kSOSPeerPeerIDKey
, kSOSPeerViewsKey
, kSOSPeerKeyBagKey
};
151 const void *values
[] = { peerID
, views
, keybag
};
152 return CFDictionaryCreate(kCFAllocatorDefault
, keys
, values
, array_size(keys
), &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
155 SOSPeerMetaRef
SOSPeerMetaCreateWithComponents(CFStringRef peerID
, CFSetRef views
, CFDataRef keybag
) {
156 if (!isString(peerID
))
160 return SOSPeerMetaCreateWithViewsAndKeyBag(peerID
, views
, keybag
);
162 return SOSPeerMetaCreateWithViews(peerID
, views
);
164 return SOSPeerMetaCreate(peerID
);
167 SOSPeerMetaRef
SOSPeerMetaCreateWithState(CFStringRef peerID
, CFDictionaryRef state
) {
168 return SOSPeerMetaCreateWithComponents(peerID
, CFDictionaryGetValue(state
, kSOSPeerViewsKey
), CFDictionaryGetValue(state
, kSOSPeerKeyBagKey
));
171 CFStringRef
SOSPeerMetaGetComponents(SOSPeerMetaRef peerMeta
, CFSetRef
*views
, CFDataRef
*keybag
, CFErrorRef
*error
) {
172 if (isDictionary(peerMeta
)) {
173 CFDictionaryRef meta
= (CFDictionaryRef
)peerMeta
;
174 CFStringRef peerID
= asString(CFDictionaryGetValue(meta
, kSOSPeerPeerIDKey
), error
);
175 CFSetRef vns
= asSet(CFDictionaryGetValue(meta
, kSOSPeerViewsKey
), error
);
176 if (vns
&& asDataOptional(CFDictionaryGetValue(meta
, kSOSPeerKeyBagKey
), keybag
, error
)) {
184 // Hack so tests can pass simple peerIDs
185 *views
= SOSViewsGetV0ViewSet();
187 return asString(peerMeta
, error
);
191 CFTypeRef
SOSPeerOrStateSetViewsKeyBagAndCreateCopy(CFTypeRef peerOrState
, CFSetRef views
, CFDataRef keyBag
) {
193 if (peerOrState
&& CFGetTypeID(peerOrState
) == SOSPeerGetTypeID()) {
194 // Inflated peer, update its views and move on
195 SOSPeerRef peer
= (SOSPeerRef
)peerOrState
;
196 SOSPeerSetViewNameSet(peer
, views
);
197 SOSPeerSetKeyBag(peer
, keyBag
);
198 return CFRetainSafe(peer
);
199 } else if (peerOrState
&& CFGetTypeID(peerOrState
) == CFDictionaryGetTypeID()) {
200 CFMutableDictionaryRef state
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, peerOrState
);
201 // Deserialized peer, just updated the serialized state with the new views
202 CFDictionarySetValue(state
, kSOSPeerViewsKey
, views
);
204 CFDictionarySetValue(state
, kSOSPeerKeyBagKey
, keyBag
);
206 CFDictionaryRemoveValue(state
, kSOSPeerKeyBagKey
);
209 // New peer, just create a state object.
211 return CFDictionaryCreateForCFTypes(kCFAllocatorDefault
, kSOSPeerViewsKey
, views
, kSOSPeerKeyBagKey
, keyBag
, NULL
);
213 return CFDictionaryCreateForCFTypes(kCFAllocatorDefault
, kSOSPeerViewsKey
, views
, NULL
);
217 CFTypeRef
SOSPeerOrStateSetViewsAndCopyState(CFTypeRef peerOrState
, CFSetRef views
) {
220 if (peerOrState
&& CFGetTypeID(peerOrState
) == SOSPeerGetTypeID()) {
221 // Inflated peer, update its views and deflate it
222 SOSPeerRef peer
= (SOSPeerRef
)peerOrState
;
223 SOSPeerSetViewNameSet(peer
, views
);
224 return SOSPeerCopyState(peer
, NULL
);
225 } else if (peerOrState
&& CFGetTypeID(peerOrState
) == CFDictionaryGetTypeID()) {
226 // We have a deflated peer. Update its views and keep it deflated
227 CFSetRef oldViews
= (CFSetRef
) CFDictionaryGetValue(peerOrState
, kSOSPeerViewsKey
);
228 CFMutableDictionaryRef state
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, peerOrState
);
229 CFDictionarySetValue(state
, kSOSPeerViewsKey
, views
);
230 if (oldViews
&& !CFSetIsSubset(views
, oldViews
)) {
231 CFDictionarySetValue(state
, kSOSPeerHasBeenInSyncKey
, kCFBooleanFalse
);
239 bool SOSPeerMapEntryIsBackup(const void *mapEntry
) {
240 if (!mapEntry
) return false;
241 if (CFGetTypeID(mapEntry
) == SOSPeerGetTypeID()) {
242 return SOSPeerGetKeyBag((SOSPeerRef
)mapEntry
);
244 return CFDictionaryContainsKey(mapEntry
, kSOSPeerKeyBagKey
);
249 // MARK: - SOSManifest
253 kSOSPeerMaxManifestWindowDepth
= 4
256 static CFStringRef
SOSManifestCreateOptionalDescriptionWithLabel(SOSManifestRef manifest
, CFStringRef label
) {
257 if (!manifest
) return CFSTR(" - ");
258 return CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR(" %@%@"), label
, manifest
);
261 static CFStringRef
SOSManifestArrayCreateOptionalDescriptionWithLabel(CFArrayRef manifests
, CFStringRef label
) {
262 CFIndex count
= manifests
? CFArrayGetCount(manifests
) : 0;
263 if (count
== 0) return CFSTR(" - ");
264 SOSManifestRef manifest
= (SOSManifestRef
)CFArrayGetValueAtIndex(manifests
, 0);
265 return CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR(" %@[%" PRIdCFIndex
"]%@"), label
, count
, manifest
);
268 static void SOSManifestArraySetManifest(CFMutableArrayRef
*manifests
, SOSManifestRef manifest
) {
271 CFArrayRemoveAllValues(*manifests
);
273 *manifests
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
274 CFArrayAppendValue(*manifests
, manifest
);
276 CFReleaseNull(*manifests
);
280 static void SOSManifestMutableArrayAppendManifest(CFMutableArrayRef manifests
, SOSManifestRef manifest
) {
282 CFIndex count
= CFArrayGetCount(manifests
);
283 CFIndex ixOfManifest
= CFArrayGetFirstIndexOfValue(manifests
, CFRangeMake(0, count
), manifest
);
284 if (ixOfManifest
!= 0) {
285 // If the manifest isn't at the front of the array move it there.
286 // If it's not in the array, remove enough entires from the end to
287 // make room to put it in the front.
288 if (ixOfManifest
!= kCFNotFound
) {
289 CFArrayRemoveValueAtIndex(manifests
, ixOfManifest
);
291 while (count
>= kSOSPeerMaxManifestWindowDepth
)
292 CFArrayRemoveValueAtIndex(manifests
, --count
);
295 CFArrayInsertValueAtIndex(manifests
, 0, manifest
);
298 // pending == NULL => nothing clear history
299 CFArrayRemoveAllValues(manifests
);
303 static void SOSManifestArrayAppendManifest(CFMutableArrayRef
*manifests
, SOSManifestRef manifest
) {
305 SOSManifestMutableArrayAppendManifest(*manifests
, manifest
);
307 SOSManifestArraySetManifest(manifests
, manifest
);
314 struct __OpaqueSOSPeer
{
320 uint64_t sequenceNumber
;
321 bool mustSendMessage
;
326 SOSManifestRef pendingObjects
;
327 SOSManifestRef unwantedManifest
;
328 SOSManifestRef confirmedManifest
;
329 CFMutableArrayRef proposedManifests
;
330 CFMutableArrayRef localManifests
;
332 // Only backup peers have these:
337 CFGiblisWithCompareFor(SOSPeer
)
339 static CFStringRef
SOSPeerCopyFormatDescription(CFTypeRef cf
, CFDictionaryRef formatOptions
) {
340 SOSPeerRef peer
= (SOSPeerRef
)cf
;
342 CFStringRef po
= SOSManifestCreateOptionalDescriptionWithLabel(peer
->pendingObjects
, CFSTR("O"));
343 CFStringRef uo
= SOSManifestCreateOptionalDescriptionWithLabel(peer
->unwantedManifest
, CFSTR("U"));
344 CFStringRef co
= SOSManifestCreateOptionalDescriptionWithLabel(peer
->confirmedManifest
, CFSTR("C"));
345 CFStringRef pe
= SOSManifestArrayCreateOptionalDescriptionWithLabel(peer
->proposedManifests
, CFSTR("P"));
346 CFStringRef lo
= SOSManifestArrayCreateOptionalDescriptionWithLabel(peer
->localManifests
, CFSTR("L"));
347 CFStringRef desc
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("<%@ %s%s%s%@%@%@%@%@>"),
349 SOSPeerMustSendMessage(peer
) ? "F" : "f",
350 SOSPeerSendObjects(peer
) ? "S" : "s",
351 SOSPeerHasBeenInSync(peer
) ? "K" : "k",
362 return CFSTR("NULL");
365 static Boolean
SOSPeerCompare(CFTypeRef cfA
, CFTypeRef cfB
)
367 SOSPeerRef peerA
= (SOSPeerRef
)cfA
, peerB
= (SOSPeerRef
)cfB
;
368 // Use mainly to see if peerB is actually this device (peerA)
369 return CFStringCompare(SOSPeerGetID(peerA
), SOSPeerGetID(peerB
), 0) == kCFCompareEqualTo
;
373 static bool SOSPeerGetPersistedBoolean(CFDictionaryRef persisted
, CFStringRef key
) {
374 CFBooleanRef boolean
= CFDictionaryGetValue(persisted
, key
);
375 return boolean
&& CFBooleanGetValue(boolean
);
378 static CFDataRef
SOSPeerGetPersistedData(CFDictionaryRef persisted
, CFStringRef key
) {
379 return asData(CFDictionaryGetValue(persisted
, key
), NULL
);
382 static int64_t SOSPeerGetPersistedInt64(CFDictionaryRef persisted
, CFStringRef key
) {
384 CFNumberRef number
= CFDictionaryGetValue(persisted
, key
);
386 CFNumberGetValue(number
, kCFNumberSInt64Type
, &integer
);
391 static void SOSPeerGetOptionalPersistedCFIndex(CFDictionaryRef persisted
, CFStringRef key
, CFIndex
*value
) {
392 CFNumberRef number
= CFDictionaryGetValue(persisted
, key
);
394 CFNumberGetValue(number
, kCFNumberCFIndexType
, value
);
398 static CFSetRef
SOSPeerGetPersistedViewNameSet(SOSPeerRef peer
, CFDictionaryRef persisted
, CFStringRef key
) {
399 CFSetRef vns
= CFDictionaryGetValue(persisted
, key
);
401 // Engine state in db contained a v0 peer, thus it must be in the V0ViewSet.
402 vns
= SOSViewsGetV0ViewSet();
403 secnotice("peer", "%@ had no views, inferring: %@", peer
->peer_id
, vns
);
409 // MARK: Backup Peers
412 void SOSBackupPeerPostNotification(const char *reason
) {
413 // Let sbd know when a notable event occurs
415 // - Backup bag change
416 secnotice("backup", "posting notification to CloudServices: %s", reason
?reason
:"");
417 notify_post(kSecItemBackupNotification
);
420 static bool SOSPeerDoWithJournalPath(SOSPeerRef peer
, CFErrorRef
*error
, void(^with
)(const char *journalPath
)) {
421 // TODO: Probably switch to using CFURL to construct the path.
423 char strBuffer
[PATH_MAX
+ 1];
424 size_t userTempLen
= confstr(_CS_DARWIN_USER_TEMP_DIR
, strBuffer
, sizeof(strBuffer
));
425 if (userTempLen
== 0) {
426 ok
= SecCheckErrno(-1, error
, CFSTR("confstr on _CS_DARWIN_USER_TEMP_DIR returned an error."));
428 CFStringRef journalName
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%s/SOSBackup-%@"), strBuffer
, SOSPeerGetID(peer
));
429 CFStringPerformWithCString(journalName
, with
);
430 CFReleaseSafe(journalName
);
435 static FILE *fopen_journal(const char *journalPath
, const char *mode
, CFErrorRef
*error
) {
436 FILE *file
= fopen(journalPath
, mode
);
437 SecCheckErrno(!file
, error
, CFSTR("fopen %s,%s"), journalPath
, mode
);
441 #include <sys/stat.h>
444 static off_t
getFileSize(int fd
) {
451 int SOSPeerHandoffFD(SOSPeerRef peer
, CFErrorRef
*error
) {
453 SOSPeerDoWithJournalPath(peer
, error
, ^(const char *journalName
) {
454 fd
= open(journalName
, O_RDONLY
| O_CLOEXEC
);
455 if (SecCheckErrno(fd
< 0, error
, CFSTR("open %s"), journalName
)) {
456 if (!SecCheckErrno(unlink(journalName
), error
, CFSTR("unlink %s"), journalName
)) {
460 secdebug("backup", "Handing off file %s with fd %d of size %llu", journalName
, fd
, getFileSize(fd
));
463 secdebug("backup", "Handing off file %s failed, %@", journalName
, error
?*error
:NULL
);
469 static CFDataRef
SOSPeerCopyAKSKeyBag(SOSPeerRef peer
, CFErrorRef
*error
) {
470 if (CFEqual(peer
->peer_id
, kSOSViewKeychainV0_tomb
)) {
471 return CFRetainSafe(peer
->_keyBag
);
473 CFDataRef aksKeybag
= NULL
;
474 SOSBackupSliceKeyBagRef backupSliceKeyBag
= SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault
, peer
->_keyBag
, error
);
475 if (backupSliceKeyBag
) {
476 aksKeybag
= SOSBSKBCopyAKSBag(backupSliceKeyBag
, error
);
477 CFRelease(backupSliceKeyBag
);
483 bool SOSPeerAppendToJournal(SOSPeerRef peer
, CFErrorRef
*error
, void(^with
)(FILE *journalFile
, keybag_handle_t kbhandle
)) {
484 __block
bool ok
= true;
485 // We only need a keybag if we are writing ADDs. Since we don't know at this layer
486 // what operations we may be doing, open keybag if we have one, otherwise don't
487 ok
&= SOSPeerDoWithJournalPath(peer
, error
, ^(const char *fname
) {
488 FILE *file
= fopen_journal(fname
, "a", error
);
490 keybag_handle_t kbhandle
= bad_keybag_handle
;
491 CFDataRef keybag
= SOSPeerCopyAKSKeyBag(peer
, error
);
493 if (ok
&& (ok
= ks_open_keybag(keybag
, NULL
, &kbhandle
, error
))) {
494 with(file
, kbhandle
);
495 if (kbhandle
!= bad_keybag_handle
)
496 ok
&= ks_close_keybag(kbhandle
, error
);
498 CFReleaseSafe(keybag
);
505 static bool SOSPeerTruncateJournal(SOSPeerRef peer
, CFErrorRef
*error
, void(^with
)(FILE *journalFile
)) {
506 __block
bool ok
= true;
507 ok
&= SOSPeerDoWithJournalPath(peer
, error
, ^(const char *fname
) {
508 FILE *file
= fopen_journal(fname
, "w", error
);
517 bool SOSPeerSetState(SOSPeerRef p
, SOSEngineRef engine
, CFDictionaryRef state
, CFErrorRef
*error
) {
520 SOSPeerGetOptionalPersistedCFIndex(state
, kSOSPeerVersionKey
, &p
->version
);
522 p
->sequenceNumber
= SOSPeerGetPersistedInt64(state
, kSOSPeerSequenceNumberKey
);
523 p
->mustSendMessage
= SOSPeerGetPersistedBoolean(state
, kSOSPeerMustSendMessageKey
);
524 p
->sendObjects
= SOSPeerGetPersistedBoolean(state
, kSOSPeerSendObjectsKey
);
525 p
->hasBeenInSync
= SOSPeerGetPersistedBoolean(state
, kSOSPeerHasBeenInSyncKey
);
526 CFRetainAssign(p
->views
, SOSPeerGetPersistedViewNameSet(p
, state
, kSOSPeerViewsKey
));
527 SOSPeerSetKeyBag(p
, SOSPeerGetPersistedData(state
, kSOSPeerKeyBagKey
));
528 CFAssignRetained(p
->pendingObjects
, SOSEngineCopyPersistedManifest(engine
, state
, kSOSPeerPendingObjectsKey
));
529 CFAssignRetained(p
->unwantedManifest
, SOSEngineCopyPersistedManifest(engine
, state
, kSOSPeerUnwantedManifestKey
));
530 CFAssignRetained(p
->confirmedManifest
, SOSEngineCopyPersistedManifest(engine
, state
, kSOSPeerConfirmedManifestKey
));
531 CFAssignRetained(p
->proposedManifests
, SOSEngineCopyPersistedManifestArray(engine
, state
, kSOSPeerProposedManifestKey
, error
));
532 ok
&= p
->proposedManifests
!= NULL
;
533 CFAssignRetained(p
->localManifests
, SOSEngineCopyPersistedManifestArray(engine
, state
, kSOSPeerLocalManifestKey
, error
));
534 ok
&= p
->localManifests
!= NULL
;
539 static SOSPeerRef
SOSPeerCreate_Internal(SOSEngineRef engine
, CFDictionaryRef state
, CFStringRef theirPeerID
, CFIndex version
, CFErrorRef
*error
) {
540 SOSPeerRef p
= CFTypeAllocate(SOSPeer
, struct __OpaqueSOSPeer
, kCFAllocatorDefault
);
541 p
->peer_id
= CFRetainSafe(theirPeerID
);
542 p
->version
= version
;
543 CFDictionaryRef empty
= NULL
;
545 empty
= CFDictionaryCreate(kCFAllocatorDefault
, NULL
, NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
548 if (!SOSPeerSetState(p
, engine
, state
, error
)) {
551 CFReleaseNull(empty
);
555 static void SOSPeerPersistBool(CFMutableDictionaryRef persist
, CFStringRef key
, bool value
) {
556 CFDictionarySetValue(persist
, key
, value
? kCFBooleanTrue
: kCFBooleanFalse
);
559 static void SOSPeerPersistInt64(CFMutableDictionaryRef persist
, CFStringRef key
, int64_t value
) {
560 CFNumberRef number
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberSInt64Type
, &value
);
561 CFDictionarySetValue(persist
, key
, number
);
562 CFReleaseSafe(number
);
565 static void SOSPeerPersistCFIndex(CFMutableDictionaryRef persist
, CFStringRef key
, CFIndex value
) {
566 CFNumberRef number
= CFNumberCreate(kCFAllocatorDefault
, kCFNumberCFIndexType
, &value
);
567 CFDictionarySetValue(persist
, key
, number
);
568 CFReleaseSafe(number
);
571 static bool SOSPeerPersistOptionalManifest(CFMutableDictionaryRef persist
, CFStringRef key
, SOSManifestRef manifest
, CFErrorRef
*error
) {
574 CFDataRef digest
= SOSManifestGetDigest(manifest
, error
);
577 CFDictionarySetValue(persist
, key
, digest
);
581 static bool SSOSPeerPersistManifestArray(CFMutableDictionaryRef persist
, CFStringRef key
, CFArrayRef manifests
, CFErrorRef
*error
) {
582 CFMutableArrayRef digests
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
583 SOSManifestRef manifest
;
584 if (manifests
) CFArrayForEachC(manifests
, manifest
) {
585 CFDataRef digest
= SOSManifestGetDigest(manifest
, error
);
587 CFReleaseNull(digests
);
589 CFArrayAppendValue(digests
, digest
);
593 CFDictionarySetValue(persist
, key
, digests
);
599 static void SOSPeerPersistOptionalValue(CFMutableDictionaryRef persist
, CFStringRef key
, CFTypeRef value
) {
601 CFDictionarySetValue(persist
, key
, value
);
604 CFDictionaryRef
SOSPeerCopyState(SOSPeerRef peer
, CFErrorRef
*error
) {
605 CFMutableDictionaryRef state
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
606 SOSPeerPersistInt64(state
, kSOSPeerSequenceNumberKey
, peer
->sequenceNumber
);
608 SOSPeerPersistCFIndex(state
, kSOSPeerVersionKey
, peer
->version
);
611 SOSPeerPersistBool(state
, kSOSPeerMustSendMessageKey
, peer
->mustSendMessage
);
612 SOSPeerPersistBool(state
, kSOSPeerSendObjectsKey
, peer
->sendObjects
);
613 SOSPeerPersistBool(state
, kSOSPeerHasBeenInSyncKey
, peer
->hasBeenInSync
);
614 SOSPeerPersistOptionalValue(state
, kSOSPeerViewsKey
, peer
->views
);
616 CFDataRef keybag
= SOSPeerGetKeyBag(peer
);
617 if (keybag
&& !CFEqual(peer
->peer_id
, kSOSViewKeychainV0_tomb
))
618 SOSPeerPersistOptionalValue(state
, kSOSPeerKeyBagKey
, keybag
);
620 if (!SOSPeerPersistOptionalManifest(state
, kSOSPeerPendingObjectsKey
, peer
->pendingObjects
, error
)
621 || !SOSPeerPersistOptionalManifest(state
, kSOSPeerUnwantedManifestKey
, peer
->unwantedManifest
, error
)
622 || !SOSPeerPersistOptionalManifest(state
, kSOSPeerConfirmedManifestKey
, peer
->confirmedManifest
, error
)
623 || !SSOSPeerPersistManifestArray(state
, kSOSPeerProposedManifestKey
, peer
->proposedManifests
, error
)
624 || !SSOSPeerPersistManifestArray(state
, kSOSPeerLocalManifestKey
, peer
->localManifests
, error
)) {
625 CFReleaseNull(state
);
630 SOSPeerRef
SOSPeerCreateWithState(SOSEngineRef engine
, CFStringRef peer_id
, CFDictionaryRef state
, CFErrorRef
*error
) {
631 return SOSPeerCreate_Internal(engine
, state
, peer_id
, 0, error
);
634 static void SOSPeerDestroy(CFTypeRef cf
) {
635 SOSPeerRef peer
= (SOSPeerRef
)cf
;
636 CFReleaseNull(peer
->peer_id
);
637 CFReleaseNull(peer
->views
);
638 CFReleaseNull(peer
->pendingObjects
);
639 CFReleaseNull(peer
->unwantedManifest
);
640 CFReleaseNull(peer
->confirmedManifest
);
641 CFReleaseNull(peer
->proposedManifests
);
642 CFReleaseNull(peer
->localManifests
);
645 bool SOSPeerDidConnect(SOSPeerRef peer
) {
646 SOSPeerSetMustSendMessage(peer
, true);
647 SOSPeerSetProposedManifest(peer
, SOSPeerGetConfirmedManifest(peer
));
648 // TODO: Return false if nothing changed.
654 CFIndex
SOSPeerGetVersion(SOSPeerRef peer
) {
655 return peer
->version
;
658 CFStringRef
SOSPeerGetID(SOSPeerRef peer
) {
659 return peer
->peer_id
;
662 CFSetRef
SOSPeerGetViewNameSet(SOSPeerRef peer
) {
666 void SOSPeerSetViewNameSet(SOSPeerRef peer
, CFSetRef views
) {
667 if (peer
->views
&& !CFSetIsSubset(views
, peer
->views
)) {
668 SOSPeerSetHasBeenInSync(peer
, false);
671 CFRetainAssign(peer
->views
, views
);
674 CFDataRef
SOSPeerGetKeyBag(SOSPeerRef peer
) {
675 return peer
->_keyBag
;
678 static bool SOSPeerUnlinkBackupJournal(SOSPeerRef peer
, CFErrorRef
*error
) {
679 __block
bool ok
= true;
680 ok
&= SOSPeerDoWithJournalPath(peer
, error
, ^(const char *journalName
) {
681 secnotice("backup", "%@ unlinking journal file %s", peer
, journalName
);
682 ok
&= SecCheckErrno(unlink(journalName
), error
, CFSTR("unlink %s"), journalName
);
687 static bool SOSPeerWriteReset(SOSPeerRef peer
, CFErrorRef
*error
) {
688 __block
bool ok
= true;
689 __block CFErrorRef localError
= NULL
;
690 ok
&= SOSPeerTruncateJournal(peer
, &localError
, ^(FILE *journalFile
) {
691 ok
= SOSBackupEventWriteReset(journalFile
, peer
->_keyBag
, &localError
);
692 if (ok
&& !peer
->_keyBag
)
693 ok
= SOSBackupEventWriteCompleteMarker(journalFile
, 999, &localError
);
697 secwarning("%@ failed to write reset to backup journal: %@", peer
->peer_id
, localError
);
698 CFErrorPropagate(localError
, error
);
700 secnotice("backup-peer", "%@ Wrote reset.", peer
->peer_id
);
703 // Forget we ever wrote anything to the journal.
704 SOSPeerSetConfirmedManifest(peer
, NULL
);
705 SOSPeerSetProposedManifest(peer
, NULL
);
707 SOSPeerSetMustSendMessage(peer
, !ok
);
711 void SOSPeerKeyBagDidChange(SOSPeerRef peer
) {
712 // If !keyBag unlink the file, instead of writing a reset.
713 // CloudServices does not want to hear about empty keybags
714 SOSPeerSetSendObjects(peer
, false);
715 if (!peer
->_keyBag
) {
716 SOSPeerUnlinkBackupJournal(peer
, NULL
);
718 // Attempt to write a reset (ignoring failures since it will
719 // be pended stickily if it fails).
720 SOSPeerWriteReset(peer
, NULL
);
721 SOSCCRequestSyncWithBackupPeer(SOSPeerGetID(peer
));
725 void SOSPeerSetKeyBag(SOSPeerRef peer
, CFDataRef keyBag
) {
726 if (CFEqualSafe(keyBag
, peer
->_keyBag
)) return;
727 bool hadKeybag
= peer
->_keyBag
;
729 secwarning("%@ keybag for backup unset", SOSPeerGetID(peer
));
731 secnotice("backup", "%@ backup bag: %@", SOSPeerGetID(peer
), keyBag
);
733 CFRetainAssign(peer
->_keyBag
, keyBag
);
734 // Don't call SOSPeerKeybagDidChange for the inital edge from NULL -> having a keybag.
736 SOSPeerKeyBagDidChange(peer
);
740 bool SOSPeerWritePendingReset(SOSPeerRef peer
, CFErrorRef
*error
) {
741 return !SOSPeerMustSendMessage(peer
) || SOSPeerWriteReset(peer
, error
);
744 uint64_t SOSPeerNextSequenceNumber(SOSPeerRef peer
) {
745 return ++peer
->sequenceNumber
;
748 uint64_t SOSPeerGetMessageVersion(SOSPeerRef peer
) {
749 return SOSPeerGetVersion(peer
);
752 bool SOSPeerMustSendMessage(SOSPeerRef peer
) {
753 return peer
->mustSendMessage
;
756 void SOSPeerSetMustSendMessage(SOSPeerRef peer
, bool sendMessage
) {
757 peer
->mustSendMessage
= sendMessage
;
760 bool SOSPeerSendObjects(SOSPeerRef peer
) {
761 return peer
->sendObjects
;
764 void SOSPeerSetSendObjects(SOSPeerRef peer
, bool sendObjects
) {
765 peer
->sendObjects
= sendObjects
;
768 bool SOSPeerHasBeenInSync(SOSPeerRef peer
) {
769 return peer
->hasBeenInSync
;
772 void SOSPeerSetHasBeenInSync(SOSPeerRef peer
, bool hasBeenInSync
) {
773 peer
->hasBeenInSync
= hasBeenInSync
;
778 SOSManifestRef
SOSPeerGetProposedManifest(SOSPeerRef peer
) {
779 if (peer
->proposedManifests
&& CFArrayGetCount(peer
->proposedManifests
))
780 return (SOSManifestRef
)CFArrayGetValueAtIndex(peer
->proposedManifests
, 0);
784 SOSManifestRef
SOSPeerGetConfirmedManifest(SOSPeerRef peer
) {
785 return peer
->confirmedManifest
;
788 void SOSPeerSetConfirmedManifest(SOSPeerRef peer
, SOSManifestRef confirmed
) {
789 CFRetainAssign(peer
->confirmedManifest
, confirmed
);
791 // TODO: Clear only expired pending and local manifests from the array - this clears them all
792 // To do so we'd have to track the messageIds we sent to our peer and when we proposed a particular manifest.
793 // Then we simply remove the entries from messages older than the one we are confirming now
794 //CFArrayRemoveAllValues(SOSPeerGetDigestsWithKey(peer, kSOSPeerProposedManifestKey));
795 //CFArrayRemoveAllValues(SOSPeerGetDigestsWithKey(peer, kSOSPeerLocalManifestKey));
798 void SOSPeerAddProposedManifest(SOSPeerRef peer
, SOSManifestRef proposed
) {
799 SOSManifestArrayAppendManifest(&peer
->proposedManifests
, proposed
);
802 void SOSPeerSetProposedManifest(SOSPeerRef peer
, SOSManifestRef proposed
) {
803 SOSManifestArraySetManifest(&peer
->proposedManifests
, proposed
);
806 void SOSPeerAddLocalManifest(SOSPeerRef peer
, SOSManifestRef local
) {
807 SOSManifestArrayAppendManifest(&peer
->localManifests
, local
);
810 SOSManifestRef
SOSPeerGetPendingObjects(SOSPeerRef peer
) {
811 return peer
->pendingObjects
;
814 void SOSPeerSetPendingObjects(SOSPeerRef peer
, SOSManifestRef pendingObjects
) {
815 CFRetainAssign(peer
->pendingObjects
, pendingObjects
);
818 SOSManifestRef
SOSPeerGetUnwantedManifest(SOSPeerRef peer
) {
819 return peer
->unwantedManifest
;
822 void SOSPeerSetUnwantedManifest(SOSPeerRef peer
, SOSManifestRef unwantedManifest
) {
823 CFRetainAssign(peer
->unwantedManifest
, unwantedManifest
);
826 SOSManifestRef
SOSPeerCopyManifestForDigest(SOSPeerRef peer
, CFDataRef digest
) {
827 if (!digest
) return NULL
;
828 SOSManifestRef manifest
;
829 if (peer
->proposedManifests
) CFArrayForEachC(peer
->proposedManifests
, manifest
) {
830 if (CFEqual(digest
, SOSManifestGetDigest(manifest
, NULL
)))
831 return CFRetainSafe(manifest
);
833 if (peer
->localManifests
) CFArrayForEachC(peer
->localManifests
, manifest
) {
834 if (CFEqual(digest
, SOSManifestGetDigest(manifest
, NULL
)))
835 return CFRetainSafe(manifest
);
837 if (peer
->confirmedManifest
&& CFEqual(digest
, SOSManifestGetDigest(peer
->confirmedManifest
, NULL
)))
838 return CFRetainSafe(peer
->confirmedManifest
);
843 static void SOSMarkManifestInUse(struct SOSDigestVector
*mdInUse
, SOSManifestRef manifest
) {
844 CFDataRef digest
= SOSManifestGetDigest(manifest
, NULL
);
846 SOSDigestVectorAppend(mdInUse
, CFDataGetBytePtr(digest
));
849 static void SOSMarkManifestsInUse(struct SOSDigestVector
*mdInUse
, CFArrayRef manifests
) {
850 if (!isArray(manifests
)) return;
851 SOSManifestRef manifest
= NULL
;
852 CFArrayForEachC(manifests
, manifest
) {
853 SOSMarkManifestInUse(mdInUse
, manifest
);
857 // Add all digests we are using to mdInUse
858 void SOSPeerMarkDigestsInUse(SOSPeerRef peer
, struct SOSDigestVector
*mdInUse
) {
859 SOSMarkManifestInUse(mdInUse
, peer
->pendingObjects
);
860 SOSMarkManifestInUse(mdInUse
, peer
->unwantedManifest
);
861 SOSMarkManifestInUse(mdInUse
, peer
->confirmedManifest
);
862 SOSMarkManifestsInUse(mdInUse
, peer
->localManifests
);
863 SOSMarkManifestsInUse(mdInUse
, peer
->proposedManifests
);
866 static void SOSAddManifestInUse(CFMutableDictionaryRef mfc
, SOSManifestRef manifest
) {
867 CFDataRef digest
= SOSManifestGetDigest(manifest
, NULL
);
868 CFDataRef data
= SOSManifestGetData(manifest
);
870 CFDictionarySetValue(mfc
, digest
, data
);
873 static void SOSAddManifestsInUse(CFMutableDictionaryRef mfc
, CFArrayRef manifests
) {
874 if (!isArray(manifests
)) return;
875 SOSManifestRef manifest
= NULL
;
876 CFArrayForEachC(manifests
, manifest
) {
877 SOSAddManifestInUse(mfc
, manifest
);
881 void SOSPeerAddManifestsInUse(SOSPeerRef peer
, CFMutableDictionaryRef mfc
) {
882 SOSAddManifestInUse(mfc
, peer
->pendingObjects
);
883 SOSAddManifestInUse(mfc
, peer
->unwantedManifest
);
884 SOSAddManifestInUse(mfc
, peer
->confirmedManifest
);
885 SOSAddManifestsInUse(mfc
, peer
->localManifests
);
886 SOSAddManifestsInUse(mfc
, peer
->proposedManifests
);
892 // additionsFromRemote
893 // original intent was that digests only got added to pendingObjects. We only know for sure if it is something added locally via api call
896 bool SOSPeerDidReceiveRemovalsAndAdditions(SOSPeerRef peer
, SOSManifestRef absentFromRemote
, SOSManifestRef additionsFromRemote
, SOSManifestRef unwantedFromRemote
,
897 SOSManifestRef local
, CFErrorRef
*error
) {
898 // We assume that incoming manifests are all sorted, and absentFromRemote is disjoint from additionsFromRemote
900 SOSManifestRef remoteMissing
= NULL
, sharedRemovals
= NULL
, sharedAdditions
= NULL
;
902 // TODO: Simplify -- a lot.
903 ok
= ok
&& (remoteMissing
= SOSManifestCreateIntersection(absentFromRemote
, local
, error
)); // remoteMissing = absentFromRemote <Intersected> local
904 ok
= ok
&& (sharedRemovals
= SOSManifestCreateComplement(remoteMissing
, absentFromRemote
, error
)); // sharedRemovals = absentFromRemote - remoteMissing
905 ok
= ok
&& (sharedAdditions
= SOSManifestCreateIntersection(additionsFromRemote
, local
, error
)); // sharedAdditions = additionsFromRemote <Intersected> local
906 //ok = ok && (remoteAdditions = SOSManifestCreateComplement(sharedAdditions, additionsFromRemote, error)); // remoteAdditions = additionsFromRemote - sharedAdditions
908 // remoteMissing are things we have that remote has asked for => add to pendingObjects
909 // sharedRemovals are things we don't have that remote has asked for => remove from pendingDeletes
910 // sharedAdditions are things we have that remote has too => remove from pendingObjects
911 // remoteAdditions are things that remote said they have that we don't and we should probably ask for => add to pendingDeletes?
912 // unwantedFromRemote are things we received from remote for which we already have a newer object according to the conflict resolver.
913 secnotice("peer", "%@ RM:%@ SR:%@ SA:%@ UR:%@", peer
, remoteMissing
, sharedRemovals
, sharedAdditions
, unwantedFromRemote
);
915 SOSManifestRef pendingObjectsManifest
= SOSManifestCreateWithPatch(peer
->pendingObjects
, sharedAdditions
, remoteMissing
, error
);
916 SOSManifestRef unwantedManifest
= SOSManifestCreateWithPatch(peer
->unwantedManifest
, sharedRemovals
, unwantedFromRemote
, error
);
917 CFAssignRetained(peer
->pendingObjects
, pendingObjectsManifest
); // PO = PO - sharedAdditions + remoteMissing
918 CFAssignRetained(peer
->unwantedManifest
, unwantedManifest
); // U = U - sharedRemovals + unwantedFromRemote
920 CFReleaseSafe(remoteMissing
);
921 CFReleaseSafe(sharedRemovals
);
922 CFReleaseSafe(sharedAdditions
);
924 secnotice("peer", "%@ C:%@ U:%@ O:%@", peer
, SOSPeerGetConfirmedManifest(peer
), SOSPeerGetUnwantedManifest(peer
), SOSPeerGetPendingObjects(peer
));
929 // Called for a normal syncing peer. Only updates pendingObjects currently.
930 bool SOSPeerDataSourceWillCommit(SOSPeerRef peer
, SOSDataSourceTransactionSource source
, SOSManifestRef removals
, SOSManifestRef additions
, CFErrorRef
*error
) {
931 bool isAPITransaction
= source
== kSOSDataSourceAPITransaction
;
932 SOSManifestRef unconfirmedAdditions
= NULL
;
933 if (isAPITransaction
&& SOSManifestGetCount(additions
)) {
934 // Remove confirmed from additions, leaving us with additions to the local db that the remote peer doesn't have yet
935 unconfirmedAdditions
= SOSManifestCreateComplement(SOSPeerGetConfirmedManifest(peer
), additions
, error
);
938 if (SOSManifestGetCount(removals
) || SOSManifestGetCount(unconfirmedAdditions
)) {
939 SOSManifestRef pendingObjectsManifest
= SOSManifestCreateWithPatch(peer
->pendingObjects
, removals
, unconfirmedAdditions
, error
);
942 // TODO: Only do this if debugScope "peer", notice is enabled.
943 // if (!SecIsScopeActive(kSecLevelNotice, "peer"))
945 // pended == UA unless the db is renotifying of an addition for something we already have
946 SOSManifestRef unpended
= NULL
, pended
= NULL
;
947 SOSManifestDiff(peer
->pendingObjects
, pendingObjectsManifest
, &unpended
, &pended
, error
);
948 secinfo("peer", "%@: willCommit R:%@ A:%@ UA:%@ %s O%s%@%s%@",
949 SOSPeerGetID(peer
), removals
, additions
, unconfirmedAdditions
,
950 (isAPITransaction
? "api": "sos"),
951 (SOSManifestGetCount(unpended
) ? "-" : ""),
952 (SOSManifestGetCount(unpended
) ? (CFStringRef
)unpended
: CFSTR("")),
953 (SOSManifestGetCount(pended
) ? "+" : SOSManifestGetCount(unpended
) ? "" : "="),
954 (SOSManifestGetCount(pended
) ? (CFStringRef
)pended
: CFSTR("")));
955 CFReleaseSafe(unpended
);
956 CFReleaseSafe(pended
);
959 CFAssignRetained(peer
->pendingObjects
, pendingObjectsManifest
);
961 CFReleaseSafe(unconfirmedAdditions
);
966 bool SOSPeerWriteAddEvent(FILE *journalFile
, keybag_handle_t kbhandle
, SOSDataSourceRef dataSource
, SOSObjectRef object
, CFErrorRef
*error
) {
967 CFDictionaryRef backup_item
= NULL
;
968 bool ok
= ((backup_item
= SOSObjectCopyBackup(dataSource
, object
, kbhandle
, error
))
969 && SOSBackupEventWriteAdd(journalFile
, backup_item
, error
));
970 CFReleaseSafe(backup_item
);
974 // Called for a backup peer, should stream objects in changes right away
975 bool SOSPeerDataSourceWillChange(SOSPeerRef peer
, SOSDataSourceRef dataSource
, SOSDataSourceTransactionSource source
, CFArrayRef changes
, CFErrorRef
*error
) {
976 __block
bool ok
= true;
977 ok
&= SOSPeerWritePendingReset(peer
, error
) && SOSPeerAppendToJournal(peer
, error
, ^(FILE *journalFile
, keybag_handle_t kbhandle
) {
978 struct SOSDigestVector dvdel
= SOSDigestVectorInit
;
979 struct SOSDigestVector dvadd
= SOSDigestVectorInit
;
981 CFArrayForEachC(changes
, change
) {
982 bool isDelete
= false;
983 CFErrorRef localError
= NULL
;
984 CFDataRef digest
= NULL
;
985 SOSObjectRef object
= NULL
;
986 bool ok
= digest
= SOSChangeCopyDigest(dataSource
, change
, &isDelete
, &object
, &localError
);
988 ok
&= SOSBackupEventWriteDelete(journalFile
, digest
, &localError
);
989 SOSDigestVectorAppend(&dvdel
, CFDataGetBytePtr(digest
));
991 ok
&= SOSPeerWriteAddEvent(journalFile
, kbhandle
, dataSource
, object
, &localError
);
992 SOSDigestVectorAppend(&dvadd
, CFDataGetBytePtr(digest
));
995 secerror("bad change %@: %@", change
, localError
);
997 CFReleaseSafe(digest
);
998 CFReleaseSafe(localError
);
1002 // Update our proposed manifest since we just wrote stuff
1003 struct SOSDigestVector dvresult
= SOSDigestVectorInit
;
1004 SOSDigestVectorSort(&dvdel
);
1005 SOSDigestVectorSort(&dvadd
);
1006 if ((ok
= SOSDigestVectorPatchSorted(SOSManifestGetDigestVector(SOSPeerGetProposedManifest(peer
)), &dvdel
,
1007 &dvadd
, &dvresult
, error
))) {
1008 SOSManifestRef proposed
;
1009 ok
= proposed
= SOSManifestCreateWithDigestVector(&dvresult
, error
);
1010 SOSPeerSetProposedManifest(peer
, proposed
);
1011 CFReleaseSafe(proposed
);
1013 SOSDigestVectorFree(&dvresult
);
1015 SOSDigestVectorFree(&dvdel
);
1016 SOSDigestVectorFree(&dvadd
);
1018 // Only Write marker if we are actually in sync now (local == propopsed).
1019 if (SOSPeerSendObjects(peer
))
1020 SOSBackupEventWriteCompleteMarker(journalFile
, 799, error
);
1024 // We were unable to stream everything out neatly
1025 SOSCCRequestSyncWithBackupPeer(SOSPeerGetID(peer
));