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 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
81 // SOSPeerMeta keys that can also be used in peerstate...
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
88 Theory of syncing for both incoming and outgoing messages
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
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
112 A message consists of
119 To send a message we simply compute:
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:
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
130 T: T \ (Om union Or union A union Oa,Ob.last union (Om,Oa),Ob.last ) union (M intersect L)
138 static SOSPeerMetaRef
SOSPeerMetaCreate(CFStringRef peerID
) {
139 return CFRetain(peerID
);
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
);
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
);
154 SOSPeerMetaRef
SOSPeerMetaCreateWithComponents(CFStringRef peerID
, CFSetRef views
, CFDataRef keybag
) {
155 if (!isString(peerID
))
159 return SOSPeerMetaCreateWithViewsAndKeyBag(peerID
, views
, keybag
);
161 return SOSPeerMetaCreateWithViews(peerID
, views
);
163 return SOSPeerMetaCreate(peerID
);
166 SOSPeerMetaRef
SOSPeerMetaCreateWithState(CFStringRef peerID
, CFDictionaryRef state
) {
167 return SOSPeerMetaCreateWithComponents(peerID
, CFDictionaryGetValue(state
, kSOSPeerViewsKey
), CFDictionaryGetValue(state
, kSOSPeerKeyBagKey
));
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
)) {
183 // Hack so tests can pass simple peerIDs
184 *views
= SOSViewsGetV0ViewSet();
186 return asString(peerMeta
, error
);
190 CFTypeRef
SOSPeerOrStateSetViewsKeyBagAndCreateCopy(CFTypeRef peerOrState
, CFSetRef views
, CFDataRef keyBag
) {
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
);
198 } else if (peerOrState
&& CFGetTypeID(peerOrState
) == CFDictionaryGetTypeID()) {
199 CFMutableDictionaryRef state
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, peerOrState
);
200 // Deserialized peer, just updated the serialized state with the new views
201 CFDictionarySetValue(state
, kSOSPeerViewsKey
, views
);
203 CFDictionarySetValue(state
, kSOSPeerKeyBagKey
, keyBag
);
205 CFDictionaryRemoveValue(state
, kSOSPeerKeyBagKey
);
208 // New peer, just create a state object.
210 return CFDictionaryCreateForCFTypes(kCFAllocatorDefault
, kSOSPeerViewsKey
, views
, kSOSPeerKeyBagKey
, keyBag
, NULL
);
212 return CFDictionaryCreateForCFTypes(kCFAllocatorDefault
, kSOSPeerViewsKey
, views
, NULL
);
216 CFTypeRef
SOSPeerOrStateSetViewsAndCopyState(CFTypeRef peerOrState
, CFSetRef 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
);
223 } else if (peerOrState
&& CFGetTypeID(peerOrState
) == CFDictionaryGetTypeID()) {
224 // We have a deflated peer. Update its views and keep it deflated
225 CFMutableDictionaryRef state
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, peerOrState
);
226 CFDictionarySetValue(state
, kSOSPeerViewsKey
, views
);
233 bool SOSPeerMapEntryIsBackup(const void *mapEntry
) {
234 if (!mapEntry
) return false;
235 if (CFGetTypeID(mapEntry
) == SOSPeerGetTypeID()) {
236 return SOSPeerGetKeyBag((SOSPeerRef
)mapEntry
);
238 return CFDictionaryContainsKey(mapEntry
, kSOSPeerKeyBagKey
);
243 // MARK: - SOSManifest
247 kSOSPeerMaxManifestWindowDepth
= 4
250 static CFStringRef
SOSManifestCreateOptionalDescriptionWithLabel(SOSManifestRef manifest
, CFStringRef label
) {
251 if (!manifest
) return CFSTR(" - ");
252 return CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR(" %@%@"), label
, manifest
);
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
);
262 static void SOSManifestArraySetManifest(CFMutableArrayRef
*manifests
, SOSManifestRef manifest
) {
265 CFArrayRemoveAllValues(*manifests
);
267 *manifests
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
268 CFArrayAppendValue(*manifests
, manifest
);
270 CFReleaseNull(*manifests
);
274 static void SOSManifestMutableArrayAppendManifest(CFMutableArrayRef manifests
, SOSManifestRef 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
);
285 while (count
>= kSOSPeerMaxManifestWindowDepth
)
286 CFArrayRemoveValueAtIndex(manifests
, --count
);
289 CFArrayInsertValueAtIndex(manifests
, 0, manifest
);
292 // pending == NULL => nothing clear history
293 CFArrayRemoveAllValues(manifests
);
297 static void SOSManifestArrayAppendManifest(CFMutableArrayRef
*manifests
, SOSManifestRef manifest
) {
299 SOSManifestMutableArrayAppendManifest(*manifests
, manifest
);
301 SOSManifestArraySetManifest(manifests
, manifest
);
308 struct __OpaqueSOSPeer
{
314 uint64_t sequenceNumber
;
315 bool mustSendMessage
;
318 SOSManifestRef pendingObjects
;
319 SOSManifestRef unwantedManifest
;
320 SOSManifestRef confirmedManifest
;
321 CFMutableArrayRef proposedManifests
;
322 CFMutableArrayRef localManifests
;
324 // Only backup peers have these:
329 CFGiblisWithCompareFor(SOSPeer
)
331 static CFStringRef
SOSPeerCopyFormatDescription(CFTypeRef cf
, CFDictionaryRef formatOptions
) {
332 SOSPeerRef peer
= (SOSPeerRef
)cf
;
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%@%@%@%@%@>"),
341 SOSPeerMustSendMessage(peer
) ? "F" : "f",
342 SOSPeerSendObjects(peer
) ? "S" : "s",
353 return CFSTR("NULL");
356 static Boolean
SOSPeerCompare(CFTypeRef cfA
, CFTypeRef cfB
)
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
;
364 static bool SOSPeerGetPersistedBoolean(CFDictionaryRef persisted
, CFStringRef key
) {
365 CFBooleanRef boolean
= CFDictionaryGetValue(persisted
, key
);
366 return boolean
&& CFBooleanGetValue(boolean
);
369 static CFDataRef
SOSPeerGetPersistedData(CFDictionaryRef persisted
, CFStringRef key
) {
370 return asData(CFDictionaryGetValue(persisted
, key
), NULL
);
373 static int64_t SOSPeerGetPersistedInt64(CFDictionaryRef persisted
, CFStringRef key
) {
375 CFNumberRef number
= CFDictionaryGetValue(persisted
, key
);
377 CFNumberGetValue(number
, kCFNumberSInt64Type
, &integer
);
382 static void SOSPeerGetOptionalPersistedCFIndex(CFDictionaryRef persisted
, CFStringRef key
, CFIndex
*value
) {
383 CFNumberRef number
= CFDictionaryGetValue(persisted
, key
);
385 CFNumberGetValue(number
, kCFNumberCFIndexType
, value
);
389 static CFSetRef
SOSPeerGetPersistedViewNameSet(SOSPeerRef peer
, CFDictionaryRef persisted
, CFStringRef key
) {
390 CFSetRef vns
= CFDictionaryGetValue(persisted
, key
);
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
);
400 // MARK: Backup Peers
403 void SOSBackupPeerPostNotification(const char *reason
) {
404 // Let sbd know when a notable event occurs
406 // - Backup bag change
407 secnotice("backup", "posting notification to CloudServices: %s", reason
?reason
:"");
408 notify_post(kSecItemBackupNotification
);
411 static bool SOSPeerDoWithJournalPath(SOSPeerRef peer
, CFErrorRef
*error
, void(^with
)(const char *journalPath
)) {
412 // TODO: Probably switch to using CFURL to construct the path.
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."));
419 CFStringRef journalName
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%s/SOSBackup-%@"), strBuffer
, SOSPeerGetID(peer
));
420 CFStringPerformWithCString(journalName
, with
);
421 CFReleaseSafe(journalName
);
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
);
432 #include <sys/stat.h>
435 static off_t
getFileSize(int fd
) {
436 return lseek(fd
, 0, SEEK_END
);
440 int SOSPeerHandoffFD(SOSPeerRef peer
, CFErrorRef
*error
) {
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
)) {
449 secdebug("backup", "Handing off file %s with fd %d of size %llu", journalName
, fd
, getFileSize(fd
));
452 secdebug("backup", "Handing off file %s failed, %@", journalName
, error
?*error
:NULL
);
458 static CFDataRef
SOSPeerCopyAKSKeyBag(SOSPeerRef peer
, CFErrorRef
*error
) {
459 if (CFEqual(peer
->peer_id
, kSOSViewKeychainV0_tomb
)) {
460 return CFRetainSafe(peer
->_keyBag
);
462 CFDataRef aksKeybag
= NULL
;
463 SOSBackupSliceKeyBagRef backupSliceKeyBag
= SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault
, peer
->_keyBag
, error
);
464 if (backupSliceKeyBag
) {
465 aksKeybag
= SOSBSKBCopyAKSBag(backupSliceKeyBag
, error
);
466 CFRelease(backupSliceKeyBag
);
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
);
479 keybag_handle_t kbhandle
= bad_keybag_handle
;
480 CFDataRef keybag
= SOSPeerCopyAKSKeyBag(peer
, error
);
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
);
487 CFReleaseSafe(keybag
);
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
);
506 bool SOSPeerSetState(SOSPeerRef p
, SOSEngineRef engine
, CFDictionaryRef state
, CFErrorRef
*error
) {
509 SOSPeerGetOptionalPersistedCFIndex(state
, kSOSPeerVersionKey
, &p
->version
);
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
));
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
;
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
;
533 empty
= CFDictionaryCreate(kCFAllocatorDefault
, NULL
, NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
536 if (!SOSPeerSetState(p
, engine
, state
, error
)) {
539 CFReleaseNull(empty
);
543 static void SOSPeerPersistBool(CFMutableDictionaryRef persist
, CFStringRef key
, bool value
) {
544 CFDictionarySetValue(persist
, key
, value
? kCFBooleanTrue
: kCFBooleanFalse
);
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
);
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
);
559 static bool SOSPeerPersistOptionalManifest(CFMutableDictionaryRef persist
, CFStringRef key
, SOSManifestRef manifest
, CFErrorRef
*error
) {
562 CFDataRef digest
= SOSManifestGetDigest(manifest
, error
);
565 CFDictionarySetValue(persist
, key
, digest
);
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
);
575 CFReleaseNull(digests
);
577 CFArrayAppendValue(digests
, digest
);
581 CFDictionarySetValue(persist
, key
, digests
);
587 static void SOSPeerPersistOptionalValue(CFMutableDictionaryRef persist
, CFStringRef key
, CFTypeRef value
) {
589 CFDictionarySetValue(persist
, key
, value
);
592 CFDictionaryRef
SOSPeerCopyState(SOSPeerRef peer
, CFErrorRef
*error
) {
593 CFMutableDictionaryRef state
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
594 SOSPeerPersistInt64(state
, kSOSPeerSequenceNumberKey
, peer
->sequenceNumber
);
596 SOSPeerPersistCFIndex(state
, kSOSPeerVersionKey
, peer
->version
);
599 SOSPeerPersistBool(state
, kSOSPeerMustSendMessageKey
, peer
->mustSendMessage
);
600 SOSPeerPersistBool(state
, kSOSPeerSendObjectsKey
, peer
->sendObjects
);
601 SOSPeerPersistOptionalValue(state
, kSOSPeerViewsKey
, peer
->views
);
603 CFDataRef keybag
= SOSPeerGetKeyBag(peer
);
604 if (keybag
&& !CFEqual(peer
->peer_id
, kSOSViewKeychainV0_tomb
))
605 SOSPeerPersistOptionalValue(state
, kSOSPeerKeyBagKey
, keybag
);
607 if (!SOSPeerPersistOptionalManifest(state
, kSOSPeerPendingObjectsKey
, peer
->pendingObjects
, error
)
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
);
617 SOSPeerRef
SOSPeerCreateWithState(SOSEngineRef engine
, CFStringRef peer_id
, CFDictionaryRef state
, CFErrorRef
*error
) {
618 return SOSPeerCreate_Internal(engine
, state
, peer_id
, 0, error
);
621 static void SOSPeerDestroy(CFTypeRef cf
) {
622 SOSPeerRef peer
= (SOSPeerRef
)cf
;
623 CFReleaseNull(peer
->peer_id
);
624 CFReleaseNull(peer
->views
);
625 CFReleaseNull(peer
->pendingObjects
);
626 CFReleaseNull(peer
->unwantedManifest
);
627 CFReleaseNull(peer
->confirmedManifest
);
628 CFReleaseNull(peer
->proposedManifests
);
629 CFReleaseNull(peer
->localManifests
);
632 bool SOSPeerDidConnect(SOSPeerRef peer
) {
633 SOSPeerSetMustSendMessage(peer
, true);
634 SOSPeerSetProposedManifest(peer
, SOSPeerGetConfirmedManifest(peer
));
635 // TODO: Return false if nothing changed.
641 CFIndex
SOSPeerGetVersion(SOSPeerRef peer
) {
642 return peer
->version
;
645 CFStringRef
SOSPeerGetID(SOSPeerRef peer
) {
646 return peer
->peer_id
;
649 CFSetRef
SOSPeerGetViewNameSet(SOSPeerRef peer
) {
653 void SOSPeerSetViewNameSet(SOSPeerRef peer
, CFSetRef views
) {
654 CFRetainAssign(peer
->views
, views
);
657 CFDataRef
SOSPeerGetKeyBag(SOSPeerRef peer
) {
658 return peer
->_keyBag
;
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
);
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
);
679 secwarning("%@ failed to write reset to backup journal: %@", peer
->peer_id
, localError
);
680 CFErrorPropagate(localError
, error
);
683 // Forget we ever wrote anything to the journal.
684 SOSPeerSetConfirmedManifest(peer
, NULL
);
685 SOSPeerSetProposedManifest(peer
, NULL
);
687 SOSPeerSetMustSendMessage(peer
, !ok
);
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
);
698 // Attempt to write a reset (ignoring failures since it will
699 // be pended stickily if it fails).
700 SOSPeerWriteReset(peer
, NULL
);
701 SOSCCSyncWithAllPeers();
705 void SOSPeerSetKeyBag(SOSPeerRef peer
, CFDataRef keyBag
) {
706 if (CFEqualSafe(keyBag
, peer
->_keyBag
)) return;
707 bool hadKeybag
= peer
->_keyBag
;
709 secwarning("%@ keybag for backup unset", SOSPeerGetID(peer
));
711 secnotice("backup", "%@ backup bag: %@", SOSPeerGetID(peer
), keyBag
);
713 CFRetainAssign(peer
->_keyBag
, keyBag
);
714 // Don't call SOSPeerKeybagDidChange for the inital edge from NULL -> having a keybag.
716 SOSPeerKeyBagDidChange(peer
);
720 bool SOSPeerWritePendingReset(SOSPeerRef peer
, CFErrorRef
*error
) {
721 return !SOSPeerMustSendMessage(peer
) || SOSPeerWriteReset(peer
, error
);
724 uint64_t SOSPeerNextSequenceNumber(SOSPeerRef peer
) {
725 return ++peer
->sequenceNumber
;
728 uint64_t SOSPeerGetMessageVersion(SOSPeerRef peer
) {
729 return SOSPeerGetVersion(peer
);
732 bool SOSPeerMustSendMessage(SOSPeerRef peer
) {
733 return peer
->mustSendMessage
;
736 void SOSPeerSetMustSendMessage(SOSPeerRef peer
, bool sendMessage
) {
737 peer
->mustSendMessage
= sendMessage
;
740 bool SOSPeerSendObjects(SOSPeerRef peer
) {
741 return peer
->sendObjects
;
744 void SOSPeerSetSendObjects(SOSPeerRef peer
, bool sendObjects
) {
745 peer
->sendObjects
= sendObjects
;
750 SOSManifestRef
SOSPeerGetProposedManifest(SOSPeerRef peer
) {
751 if (peer
->proposedManifests
&& CFArrayGetCount(peer
->proposedManifests
))
752 return (SOSManifestRef
)CFArrayGetValueAtIndex(peer
->proposedManifests
, 0);
756 SOSManifestRef
SOSPeerGetConfirmedManifest(SOSPeerRef peer
) {
757 return peer
->confirmedManifest
;
760 void SOSPeerSetConfirmedManifest(SOSPeerRef peer
, SOSManifestRef confirmed
) {
761 CFRetainAssign(peer
->confirmedManifest
, confirmed
);
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));
770 void SOSPeerAddProposedManifest(SOSPeerRef peer
, SOSManifestRef proposed
) {
771 SOSManifestArrayAppendManifest(&peer
->proposedManifests
, proposed
);
774 void SOSPeerSetProposedManifest(SOSPeerRef peer
, SOSManifestRef proposed
) {
775 SOSManifestArraySetManifest(&peer
->proposedManifests
, proposed
);
778 void SOSPeerAddLocalManifest(SOSPeerRef peer
, SOSManifestRef local
) {
779 SOSManifestArrayAppendManifest(&peer
->localManifests
, local
);
782 SOSManifestRef
SOSPeerGetPendingObjects(SOSPeerRef peer
) {
783 return peer
->pendingObjects
;
786 void SOSPeerSetPendingObjects(SOSPeerRef peer
, SOSManifestRef pendingObjects
) {
787 CFRetainAssign(peer
->pendingObjects
, pendingObjects
);
790 SOSManifestRef
SOSPeerGetUnwantedManifest(SOSPeerRef peer
) {
791 return peer
->unwantedManifest
;
794 void SOSPeerSetUnwantedManifest(SOSPeerRef peer
, SOSManifestRef unwantedManifest
) {
795 CFRetainAssign(peer
->unwantedManifest
, unwantedManifest
);
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
);
805 if (peer
->localManifests
) CFArrayForEachC(peer
->localManifests
, manifest
) {
806 if (CFEqual(digest
, SOSManifestGetDigest(manifest
, NULL
)))
807 return CFRetainSafe(manifest
);
809 if (peer
->confirmedManifest
&& CFEqual(digest
, SOSManifestGetDigest(peer
->confirmedManifest
, NULL
)))
810 return CFRetainSafe(peer
->confirmedManifest
);
815 static void SOSMarkManifestInUse(struct SOSDigestVector
*mdInUse
, SOSManifestRef manifest
) {
816 CFDataRef digest
= SOSManifestGetDigest(manifest
, NULL
);
818 SOSDigestVectorAppend(mdInUse
, CFDataGetBytePtr(digest
));
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
);
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
);
838 static void SOSAddManifestInUse(CFMutableDictionaryRef mfc
, SOSManifestRef manifest
) {
839 CFDataRef digest
= SOSManifestGetDigest(manifest
, NULL
);
840 CFDataRef data
= SOSManifestGetData(manifest
);
842 CFDictionarySetValue(mfc
, digest
, data
);
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
);
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
);
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
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
872 SOSManifestRef remoteMissing
= NULL
, sharedRemovals
= NULL
, sharedAdditions
= NULL
;
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
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
);
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
892 CFReleaseSafe(remoteMissing
);
893 CFReleaseSafe(sharedRemovals
);
894 CFReleaseSafe(sharedAdditions
);
896 secnotice("peer", "%@ C:%@ U:%@ O:%@", peer
, SOSPeerGetConfirmedManifest(peer
), SOSPeerGetUnwantedManifest(peer
), SOSPeerGetPendingObjects(peer
));
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
);
910 if (SOSManifestGetCount(removals
) || SOSManifestGetCount(unconfirmedAdditions
)) {
911 SOSManifestRef pendingObjectsManifest
= SOSManifestCreateWithPatch(peer
->pendingObjects
, removals
, unconfirmedAdditions
, error
);
914 // TODO: Only do this if debugScope "peer", notice is enabled.
915 // if (!SecIsScopeActive(kSecLevelNotice, "peer"))
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
);
931 CFAssignRetained(peer
->pendingObjects
, pendingObjectsManifest
);
933 CFReleaseSafe(unconfirmedAdditions
);
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
);
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
;
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
);
960 ok
&= SOSBackupEventWriteDelete(journalFile
, digest
, &localError
);
961 SOSDigestVectorAppend(&dvdel
, CFDataGetBytePtr(digest
));
963 ok
&= SOSPeerWriteAddEvent(journalFile
, kbhandle
, dataSource
, object
, &localError
);
964 SOSDigestVectorAppend(&dvadd
, CFDataGetBytePtr(digest
));
967 secerror("bad change %@: %@", change
, localError
);
969 CFReleaseSafe(digest
);
970 CFReleaseSafe(localError
);
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
);
985 SOSDigestVectorFree(&dvresult
);
987 SOSDigestVectorFree(&dvdel
);
988 SOSDigestVectorFree(&dvadd
);
990 // Only Write marker if we are actually in sync now (local == propopsed).
991 if (SOSPeerSendObjects(peer
))
992 SOSBackupEventWriteCompleteMarker(journalFile
, 799, error
);
996 // We were unable to stream everything out neatly
997 SOSCCSyncWithAllPeers();