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 "keychain/SecureObjectSync/SOSPeer.h"
29 #import "keychain/SecureObjectSync/SOSPeerRateLimiter.h"
30 #include "keychain/SecureObjectSync/SOSDigestVector.h"
31 #include "keychain/SecureObjectSync/SOSInternal.h"
32 #include "keychain/SecureObjectSync/SOSTransport.h"
33 #include "keychain/SecureObjectSync/SOSEngine.h"
34 #include <Security/SecureObjectSync/SOSViews.h>
35 #import "keychain/SecureObjectSync/SOSChangeTracker.h"
36 #include <utilities/SecCFError.h>
37 #include <utilities/SecCFRelease.h>
38 #include <utilities/SecCFWrappers.h>
39 #include <utilities/SecDb.h>
40 #include <utilities/SecFileLocations.h>
41 #include <utilities/SecIOFormat.h>
42 #include <utilities/array_size.h>
43 #include <utilities/debugging.h>
44 #include "keychain/SecureObjectSync/SOSBackupEvent.h"
45 #include <Security/SecItemBackup.h>
47 #include "keychain/securityd/SOSCloudCircleServer.h"
49 #include <CoreFoundation/CoreFoundation.h>
53 #include <AssertMacros.h>
55 // Backup Peer Support
56 #include "keychain/securityd/SecKeybagSupport.h"
61 // MARK: - SOSPeerPersistence code
63 static CFStringRef kSOSPeerSequenceNumberKey = CFSTR("sequence-number");
65 CFStringRef kSOSPeerDataLabel = CFSTR("iCloud Peer Data Meta-data");
68 // MARK: SOSPeerState (dictionary keys)
71 // PeerState dictionary keys
72 static CFStringRef kSOSPeerSendObjectsKey = CFSTR("send-objects"); // bool
73 static CFStringRef kSOSPeerMustSendMessageKey = CFSTR("must-send"); // bool
74 static CFStringRef kSOSPeerHasBeenInSyncKey = CFSTR("has-been-in-sync"); // bool
75 static CFStringRef kSOSPeerPendingObjectsKey = CFSTR("pending-objects"); // digest
76 static CFStringRef kSOSPeerUnwantedManifestKey = CFSTR("unwanted-manifest"); // digest
77 static CFStringRef kSOSPeerConfirmedManifestKey = CFSTR("confirmed-manifest"); //digest
78 static CFStringRef kSOSPeerProposedManifestKey = CFSTR("pending-manifest"); // array of digests
79 static CFStringRef kSOSPeerLocalManifestKey = CFSTR("local-manifest"); // array of digests
80 static CFStringRef kSOSPeerVersionKey = CFSTR("vers"); // int
83 // SOSPeerMeta keys that can also be used in peerstate...
85 static CFStringRef kSOSPeerPeerIDKey = CFSTR("peer-id"); // string
86 static CFStringRef kSOSPeerViewsKey = CFSTR("views"); // set (or array) of string
87 static CFStringRef kSOSPeerKeyBagKey = CFSTR("keybag"); // data
90 Theory of syncing for both incoming and outgoing messages
92 A peerstate consists of:
93 (T, U, C, P, L, D->M, M->D, H->O, O->{(Op,Or)|Oa,(Op,Or)|(Om,Oa),(Op,Or)|Om,Oi|Oi|Ob|Oa,Ob|(Om,Oa),Ob|(Ol,Ou)|Oa,(Ol,Ou)|(Om,Oa)(Ol,Ou)})
94 T: to be sent (pendingObjects) manifest
95 U: unwanted objects manifest
98 D->M? digest or manifest to optional manifest function
99 M->D manifest to digest of manifest function
100 H->O? hash (manifest entry) to optional object function (the datasource)
101 O->{ Mapping from incoming O objects to one of:
102 (Op,Or): Op = Peers object, Or is replaced local object.
103 Oa,(Op,Or): Oa = appeared local object, Or = Oa, see above for (Op, Or)
104 (Om,Oa),(Op,Or): Om missing local object, was apparently Oa instead see above for (Oa, Op, Or)
105 Om,Oi: Om missing local object, inserted Oi (nothing replaced), but Om still disapeared from manifest
106 Oi Oi inserted object from peer (nothing replaced)
107 Ob: Ob both remote and local object are identical, nothing changed
108 Oa,Ob: Oa = appeared local object equal to Oa = Ob. Equivalent to single Oi
109 (Om,Oa),Ob: Om missing local object, must be O->H->Ob, Oa found in place, Ob != Oa, Oa magically appeared unrelated to O->H->Ob
110 (Ol,Ou): Ol local object wins from peers Ou older object => append Ou to U
111 Oa,(Ol,Ou): Oa appeared as a local object and Oa = Ol above
112 (Om,Oa),(Ol,Ou): Om removed and Oa replaced Om and Oa = Ol as above
114 A message consists of
121 To send a message we simply compute:
124 E: L \ C # subsetted if not all of O is sent
125 O: H->O?->O # as size permits
126 and change P to (C \ M:) union E: union O->H->O:
128 To receive a message we compute
129 Op,Oi,Ob,Oo and Om,Oa,Ol
130 C: (D->M->B \ M) union E union O->H->O
132 T: T \ (Om union Or union A union Oa,Ob.last union (Om,Oa),Ob.last ) union (M intersect L)
140 static SOSPeerMetaRef SOSPeerMetaCreate(CFStringRef peerID) {
141 return CFRetain(peerID);
144 static SOSPeerMetaRef SOSPeerMetaCreateWithViews(CFStringRef peerID, CFSetRef views) {
145 const void *keys[] = { kSOSPeerPeerIDKey, kSOSPeerViewsKey };
146 const void *values[] = { peerID, views };
147 return CFDictionaryCreate(kCFAllocatorDefault, keys, values, array_size(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
150 static SOSPeerMetaRef SOSPeerMetaCreateWithViewsAndKeyBag(CFStringRef peerID, CFSetRef views, CFDataRef keybag) {
151 const void *keys[] = { kSOSPeerPeerIDKey, kSOSPeerViewsKey, kSOSPeerKeyBagKey };
152 const void *values[] = { peerID, views, keybag };
153 return CFDictionaryCreate(kCFAllocatorDefault, keys, values, array_size(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
156 SOSPeerMetaRef SOSPeerMetaCreateWithComponents(CFStringRef peerID, CFSetRef views, CFDataRef keybag) {
157 if (!isString(peerID))
161 return SOSPeerMetaCreateWithViewsAndKeyBag(peerID, views, keybag);
163 return SOSPeerMetaCreateWithViews(peerID, views);
165 return SOSPeerMetaCreate(peerID);
168 SOSPeerMetaRef SOSPeerMetaCreateWithState(CFStringRef peerID, CFDictionaryRef state) {
169 return SOSPeerMetaCreateWithComponents(peerID, CFDictionaryGetValue(state, kSOSPeerViewsKey), CFDictionaryGetValue(state, kSOSPeerKeyBagKey));
172 CFStringRef SOSPeerMetaGetComponents(SOSPeerMetaRef peerMeta, CFSetRef *views, CFDataRef *keybag, CFErrorRef *error) {
173 if (isDictionary(peerMeta)) {
174 CFDictionaryRef meta = (CFDictionaryRef)peerMeta;
175 CFStringRef peerID = asString(CFDictionaryGetValue(meta, kSOSPeerPeerIDKey), error);
176 CFSetRef vns = asSet(CFDictionaryGetValue(meta, kSOSPeerViewsKey), error);
177 if (vns && asDataOptional(CFDictionaryGetValue(meta, kSOSPeerKeyBagKey), keybag, error)) {
185 // Hack so tests can pass simple peerIDs
186 *views = SOSViewsGetV0ViewSet();
188 return asString(peerMeta, error);
192 CFTypeRef SOSPeerOrStateSetViewsKeyBagAndCreateCopy(CFTypeRef peerOrState, CFSetRef views, CFDataRef keyBag) {
194 if (peerOrState && CFGetTypeID(peerOrState) == SOSPeerGetTypeID()) {
195 // Inflated peer, update its views and move on
196 SOSPeerRef peer = (SOSPeerRef)peerOrState;
197 SOSPeerSetViewNameSet(peer, views);
198 SOSPeerSetKeyBag(peer, keyBag);
199 return CFRetainSafe(peer);
200 } else if (peerOrState && CFGetTypeID(peerOrState) == CFDictionaryGetTypeID()) {
201 CFMutableDictionaryRef state = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, peerOrState);
202 // Deserialized peer, just updated the serialized state with the new views
203 CFDictionarySetValue(state, kSOSPeerViewsKey, views);
205 CFDictionarySetValue(state, kSOSPeerKeyBagKey, keyBag);
207 CFDictionaryRemoveValue(state, kSOSPeerKeyBagKey);
210 // New peer, just create a state object.
212 return CFDictionaryCreateForCFTypes(kCFAllocatorDefault, kSOSPeerViewsKey, views, kSOSPeerKeyBagKey, keyBag, NULL);
214 return CFDictionaryCreateForCFTypes(kCFAllocatorDefault, kSOSPeerViewsKey, views, NULL);
218 CFTypeRef SOSPeerOrStateSetViewsAndCopyState(CFTypeRef peerOrState, CFSetRef views) {
221 if (peerOrState && CFGetTypeID(peerOrState) == SOSPeerGetTypeID()) {
222 // Inflated peer, update its views and deflate it
223 SOSPeerRef peer = (SOSPeerRef)peerOrState;
224 SOSPeerSetViewNameSet(peer, views);
225 return SOSPeerCopyState(peer, NULL);
226 } else if (peerOrState && CFGetTypeID(peerOrState) == CFDictionaryGetTypeID()) {
227 // We have a deflated peer. Update its views and keep it deflated
228 CFSetRef oldViews = (CFSetRef) CFDictionaryGetValue(peerOrState, kSOSPeerViewsKey);
229 CFMutableDictionaryRef state = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, peerOrState);
230 CFDictionarySetValue(state, kSOSPeerViewsKey, views);
231 if (oldViews && !CFSetIsSubset(views, oldViews)) {
232 CFDictionarySetValue(state, kSOSPeerHasBeenInSyncKey, kCFBooleanFalse);
240 bool SOSPeerMapEntryIsBackup(const void *mapEntry) {
241 if (!mapEntry) return false;
242 if (CFGetTypeID(mapEntry) == SOSPeerGetTypeID()) {
243 return SOSPeerGetKeyBag((SOSPeerRef)mapEntry);
245 return CFDictionaryContainsKey(mapEntry, kSOSPeerKeyBagKey);
250 // MARK: - SOSManifest
254 kSOSPeerMaxManifestWindowDepth = 4
257 static CFStringRef SOSManifestCreateOptionalDescriptionWithLabel(SOSManifestRef manifest, CFStringRef label) {
258 if (!manifest) return CFSTR(" - ");
259 return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(" %@%@"), label, manifest);
262 static CFStringRef SOSManifestArrayCreateOptionalDescriptionWithLabel(CFArrayRef manifests, CFStringRef label) {
263 CFIndex count = manifests ? CFArrayGetCount(manifests) : 0;
264 if (count == 0) return CFSTR(" - ");
265 SOSManifestRef manifest = (SOSManifestRef)CFArrayGetValueAtIndex(manifests, 0);
266 return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(" %@[%" PRIdCFIndex "]%@"), label, count, manifest);
269 static void SOSManifestArraySetManifest(CFMutableArrayRef *manifests, SOSManifestRef manifest) {
272 CFArrayRemoveAllValues(*manifests);
274 *manifests = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
275 CFArrayAppendValue(*manifests, manifest);
277 CFReleaseNull(*manifests);
281 static void SOSManifestMutableArrayAppendManifest(CFMutableArrayRef manifests, SOSManifestRef manifest) {
283 CFIndex count = CFArrayGetCount(manifests);
284 CFIndex ixOfManifest = CFArrayGetFirstIndexOfValue(manifests, CFRangeMake(0, count), manifest);
285 if (ixOfManifest != 0) {
286 // If the manifest isn't at the front of the array move it there.
287 // If it's not in the array, remove enough entires from the end to
288 // make room to put it in the front.
289 if (ixOfManifest != kCFNotFound) {
290 CFArrayRemoveValueAtIndex(manifests, ixOfManifest);
292 while (count >= kSOSPeerMaxManifestWindowDepth)
293 CFArrayRemoveValueAtIndex(manifests, --count);
296 CFArrayInsertValueAtIndex(manifests, 0, manifest);
299 // pending == NULL => nothing clear history
300 CFArrayRemoveAllValues(manifests);
304 static void SOSManifestArrayAppendManifest(CFMutableArrayRef *manifests, SOSManifestRef manifest) {
306 SOSManifestMutableArrayAppendManifest(*manifests, manifest);
308 SOSManifestArraySetManifest(manifests, manifest);
315 struct __OpaqueSOSPeer {
321 uint64_t sequenceNumber;
322 bool mustSendMessage;
327 SOSManifestRef pendingObjects;
328 SOSManifestRef unwantedManifest;
329 SOSManifestRef confirmedManifest;
330 CFMutableArrayRef proposedManifests;
331 CFMutableArrayRef localManifests;
335 CFMutableDictionaryRef otrTimers;
337 // Only backup peers have these:
342 CFGiblisWithCompareFor(SOSPeer)
344 static CFStringRef SOSPeerCopyFormatDescription(CFTypeRef cf, CFDictionaryRef formatOptions) {
345 SOSPeerRef peer = (SOSPeerRef)cf;
347 CFStringRef po = SOSManifestCreateOptionalDescriptionWithLabel(peer->pendingObjects, CFSTR("O"));
348 CFStringRef uo = SOSManifestCreateOptionalDescriptionWithLabel(peer->unwantedManifest, CFSTR("U"));
349 CFStringRef co = SOSManifestCreateOptionalDescriptionWithLabel(peer->confirmedManifest, CFSTR("C"));
350 CFStringRef pe = SOSManifestArrayCreateOptionalDescriptionWithLabel(peer->proposedManifests, CFSTR("P"));
351 CFStringRef lo = SOSManifestArrayCreateOptionalDescriptionWithLabel(peer->localManifests, CFSTR("L"));
352 CFStringRef desc = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<%@ %s%s%s%@%@%@%@%@>"),
354 SOSPeerMustSendMessage(peer) ? "F" : "f",
355 SOSPeerSendObjects(peer) ? "S" : "s",
356 SOSPeerHasBeenInSync(peer) ? "K" : "k",
367 return CFSTR("NULL");
370 static Boolean SOSPeerCompare(CFTypeRef cfA, CFTypeRef cfB)
372 SOSPeerRef peerA = (SOSPeerRef)cfA, peerB = (SOSPeerRef)cfB;
373 // Use mainly to see if peerB is actually this device (peerA)
374 return CFStringCompare(SOSPeerGetID(peerA), SOSPeerGetID(peerB), 0) == kCFCompareEqualTo;
378 static bool SOSPeerGetPersistedBoolean(CFDictionaryRef persisted, CFStringRef key) {
379 CFBooleanRef boolean = CFDictionaryGetValue(persisted, key);
380 return boolean && CFBooleanGetValue(boolean);
383 static CFDataRef SOSPeerGetPersistedData(CFDictionaryRef persisted, CFStringRef key) {
384 return asData(CFDictionaryGetValue(persisted, key), NULL);
387 static int64_t SOSPeerGetPersistedInt64(CFDictionaryRef persisted, CFStringRef key) {
389 CFNumberRef number = CFDictionaryGetValue(persisted, key);
391 CFNumberGetValue(number, kCFNumberSInt64Type, &integer);
396 static void SOSPeerGetOptionalPersistedCFIndex(CFDictionaryRef persisted, CFStringRef key, CFIndex *value) {
397 CFNumberRef number = CFDictionaryGetValue(persisted, key);
399 CFNumberGetValue(number, kCFNumberCFIndexType, value);
403 static CFSetRef SOSPeerGetPersistedViewNameSet(SOSPeerRef peer, CFDictionaryRef persisted, CFStringRef key) {
404 CFSetRef vns = CFDictionaryGetValue(persisted, key);
406 // Engine state in db contained a v0 peer, thus it must be in the V0ViewSet.
407 vns = SOSViewsGetV0ViewSet();
408 secnotice("peer", "%@ had no views, inferring: %@", peer->peer_id, vns);
414 // MARK: Backup Peers
417 void SOSBackupPeerPostNotification(const char *reason) {
418 // Let sbd know when a notable event occurs
420 // - Backup bag change
421 secnotice("backup", "posting notification to CloudServices: %s", reason?reason:"");
422 notify_post(kSecItemBackupNotification);
425 static bool SOSPeerDoWithJournalPath(SOSPeerRef peer, CFErrorRef *error, void(^with)(const char *journalPath)) {
426 // TODO: Probably switch to using CFURL to construct the path.
428 char strBuffer[PATH_MAX + 1];
429 size_t userTempLen = confstr(_CS_DARWIN_USER_TEMP_DIR, strBuffer, sizeof(strBuffer));
430 if (userTempLen == 0) {
431 ok = SecCheckErrno(-1, error, CFSTR("confstr on _CS_DARWIN_USER_TEMP_DIR returned an error."));
433 CFStringRef journalName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s/SOSBackup-%@"), strBuffer, SOSPeerGetID(peer));
434 CFStringPerformWithCString(journalName, with);
435 CFReleaseSafe(journalName);
440 static FILE *fopen_journal(const char *journalPath, const char *mode, CFErrorRef *error) {
441 FILE *file = fopen(journalPath, mode);
442 SecCheckErrno(!file, error, CFSTR("fopen %s,%s"), journalPath, mode);
446 #include <sys/stat.h>
449 static off_t getFileSize(int fd) {
456 int SOSPeerHandoffFD(SOSPeerRef peer, CFErrorRef *error) {
458 SOSPeerDoWithJournalPath(peer, error, ^(const char *journalName) {
459 fd = open(journalName, O_RDONLY | O_CLOEXEC);
460 if (SecCheckErrno(fd < 0, error, CFSTR("open %s"), journalName)) {
461 if (!SecCheckErrno(unlink(journalName), error, CFSTR("unlink %s"), journalName)) {
465 secdebug("backup", "Handing off file %s with fd %d of size %llu", journalName, fd, getFileSize(fd));
468 secdebug("backup", "Handing off file %s failed, %@", journalName, error?*error:NULL);
474 static CFDataRef SOSPeerCopyAKSKeyBag(SOSPeerRef peer, CFErrorRef *error) {
475 if (CFEqual(peer->peer_id, kSOSViewKeychainV0_tomb)) {
476 return CFRetainSafe(peer->_keyBag);
478 CFDataRef aksKeybag = NULL;
479 SOSBackupSliceKeyBagRef backupSliceKeyBag = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, peer->_keyBag, error);
480 if (backupSliceKeyBag) {
481 aksKeybag = SOSBSKBCopyAKSBag(backupSliceKeyBag, error);
482 CFRelease(backupSliceKeyBag);
488 bool SOSPeerAppendToJournal(SOSPeerRef peer, CFErrorRef *error, void(^with)(FILE *journalFile, keybag_handle_t kbhandle)) {
489 __block bool ok = true;
490 // We only need a keybag if we are writing ADDs. Since we don't know at this layer
491 // what operations we may be doing, open keybag if we have one, otherwise don't
492 ok &= SOSPeerDoWithJournalPath(peer, error, ^(const char *fname) {
493 FILE *file = fopen_journal(fname, "a", error);
495 keybag_handle_t kbhandle = bad_keybag_handle;
496 CFDataRef keybag = SOSPeerCopyAKSKeyBag(peer, error);
498 if (ok && (ok = ks_open_keybag(keybag, NULL, &kbhandle, error))) {
499 with(file, kbhandle);
500 if (kbhandle != bad_keybag_handle)
501 ok &= ks_close_keybag(kbhandle, error);
503 CFReleaseSafe(keybag);
510 static bool SOSPeerTruncateJournal(SOSPeerRef peer, CFErrorRef *error, void(^with)(FILE *journalFile)) {
511 __block bool ok = true;
512 ok &= SOSPeerDoWithJournalPath(peer, error, ^(const char *fname) {
513 FILE *file = fopen_journal(fname, "w", error);
522 bool SOSPeerSetState(SOSPeerRef p, SOSEngineRef engine, CFDictionaryRef state, CFErrorRef *error) {
525 SOSPeerGetOptionalPersistedCFIndex(state, kSOSPeerVersionKey, &p->version);
527 p->sequenceNumber = SOSPeerGetPersistedInt64(state, kSOSPeerSequenceNumberKey);
528 p->mustSendMessage = SOSPeerGetPersistedBoolean(state, kSOSPeerMustSendMessageKey);
529 p->sendObjects = SOSPeerGetPersistedBoolean(state, kSOSPeerSendObjectsKey);
530 p->hasBeenInSync = SOSPeerGetPersistedBoolean(state, kSOSPeerHasBeenInSyncKey);
531 CFRetainAssign(p->views, SOSPeerGetPersistedViewNameSet(p, state, kSOSPeerViewsKey));
532 SOSPeerSetKeyBag(p, SOSPeerGetPersistedData(state, kSOSPeerKeyBagKey));
533 CFAssignRetained(p->pendingObjects, SOSEngineCopyPersistedManifest(engine, state, kSOSPeerPendingObjectsKey));
534 CFAssignRetained(p->unwantedManifest, SOSEngineCopyPersistedManifest(engine, state, kSOSPeerUnwantedManifestKey));
535 CFAssignRetained(p->confirmedManifest, SOSEngineCopyPersistedManifest(engine, state, kSOSPeerConfirmedManifestKey));
536 CFAssignRetained(p->proposedManifests, SOSEngineCopyPersistedManifestArray(engine, state, kSOSPeerProposedManifestKey, error));
537 ok &= p->proposedManifests != NULL;
538 CFAssignRetained(p->localManifests, SOSEngineCopyPersistedManifestArray(engine, state, kSOSPeerLocalManifestKey, error));
539 ok &= p->localManifests != NULL;
544 static SOSPeerRef SOSPeerCreate_Internal(SOSEngineRef engine, CFDictionaryRef state, CFStringRef theirPeerID, CFIndex version, CFErrorRef *error) {
545 SOSPeerRef p = CFTypeAllocate(SOSPeer, struct __OpaqueSOSPeer, kCFAllocatorDefault);
546 p->peer_id = CFRetainSafe(theirPeerID);
547 p->version = version;
548 p->otrTimers = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
550 CFDictionaryRef empty = NULL;
552 empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
555 if (!SOSPeerSetState(p, engine, state, error)) {
558 CFReleaseNull(empty);
562 static void SOSPeerPersistBool(CFMutableDictionaryRef persist, CFStringRef key, bool value) {
563 CFDictionarySetValue(persist, key, value ? kCFBooleanTrue : kCFBooleanFalse);
566 static void SOSPeerPersistInt64(CFMutableDictionaryRef persist, CFStringRef key, int64_t value) {
567 CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &value);
568 CFDictionarySetValue(persist, key, number);
569 CFReleaseSafe(number);
572 static void SOSPeerPersistCFIndex(CFMutableDictionaryRef persist, CFStringRef key, CFIndex value) {
573 CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &value);
574 CFDictionarySetValue(persist, key, number);
575 CFReleaseSafe(number);
578 static bool SOSPeerPersistOptionalManifest(CFMutableDictionaryRef persist, CFStringRef key, SOSManifestRef manifest, CFErrorRef *error) {
581 CFDataRef digest = SOSManifestGetDigest(manifest, error);
584 CFDictionarySetValue(persist, key, digest);
588 static bool SSOSPeerPersistManifestArray(CFMutableDictionaryRef persist, CFStringRef key, CFArrayRef manifests, CFErrorRef *error) {
589 CFMutableArrayRef digests = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
590 SOSManifestRef manifest;
591 if (manifests) CFArrayForEachC(manifests, manifest) {
592 CFDataRef digest = SOSManifestGetDigest(manifest, error);
594 CFReleaseNull(digests);
596 CFArrayAppendValue(digests, digest);
600 CFDictionarySetValue(persist, key, digests);
606 static void SOSPeerPersistOptionalValue(CFMutableDictionaryRef persist, CFStringRef key, CFTypeRef value) {
608 CFDictionarySetValue(persist, key, value);
611 CFDictionaryRef SOSPeerCopyState(SOSPeerRef peer, CFErrorRef *error) {
612 CFMutableDictionaryRef state = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
613 SOSPeerPersistInt64(state, kSOSPeerSequenceNumberKey, peer->sequenceNumber);
615 SOSPeerPersistCFIndex(state, kSOSPeerVersionKey, peer->version);
618 SOSPeerPersistBool(state, kSOSPeerMustSendMessageKey, peer->mustSendMessage);
619 SOSPeerPersistBool(state, kSOSPeerSendObjectsKey, peer->sendObjects);
620 SOSPeerPersistBool(state, kSOSPeerHasBeenInSyncKey, peer->hasBeenInSync);
621 SOSPeerPersistOptionalValue(state, kSOSPeerViewsKey, peer->views);
623 CFDataRef keybag = SOSPeerGetKeyBag(peer);
624 if (keybag && !CFEqual(peer->peer_id, kSOSViewKeychainV0_tomb))
625 SOSPeerPersistOptionalValue(state, kSOSPeerKeyBagKey, keybag);
627 if (!SOSPeerPersistOptionalManifest(state, kSOSPeerPendingObjectsKey, peer->pendingObjects, error)
628 || !SOSPeerPersistOptionalManifest(state, kSOSPeerUnwantedManifestKey, peer->unwantedManifest, error)
629 || !SOSPeerPersistOptionalManifest(state, kSOSPeerConfirmedManifestKey, peer->confirmedManifest, error)
630 || !SSOSPeerPersistManifestArray(state, kSOSPeerProposedManifestKey, peer->proposedManifests, error)
631 || !SSOSPeerPersistManifestArray(state, kSOSPeerLocalManifestKey, peer->localManifests, error)) {
632 CFReleaseNull(state);
637 SOSPeerRef SOSPeerCreateWithState(SOSEngineRef engine, CFStringRef peer_id, CFDictionaryRef state, CFErrorRef *error) {
638 return SOSPeerCreate_Internal(engine, state, peer_id, 0, error);
641 static void SOSPeerDestroy(CFTypeRef cf) {
642 SOSPeerRef peer = (SOSPeerRef)cf;
643 CFReleaseNull(peer->peer_id);
644 CFReleaseNull(peer->views);
645 CFReleaseNull(peer->pendingObjects);
646 CFReleaseNull(peer->unwantedManifest);
647 CFReleaseNull(peer->confirmedManifest);
648 CFReleaseNull(peer->proposedManifests);
649 CFReleaseNull(peer->localManifests);
650 CFReleaseNull(peer->otrTimers);
651 CFReleaseNull(peer->limiter);
652 CFReleaseNull(peer->_keyBag);
655 bool SOSPeerDidConnect(SOSPeerRef peer) {
656 SOSPeerSetMustSendMessage(peer, true);
657 SOSPeerSetProposedManifest(peer, SOSPeerGetConfirmedManifest(peer));
658 // TODO: Return false if nothing changed.
664 CFIndex SOSPeerGetVersion(SOSPeerRef peer) {
665 return peer->version;
668 void SOSPeerSetRateLimiter(SOSPeerRef peer, CFTypeRef limiter)
670 peer->limiter = CFRetainSafe(limiter);
673 CFTypeRef SOSPeerGetRateLimiter(SOSPeerRef peer)
675 return (peer->limiter ? peer->limiter : NULL);
678 CFStringRef SOSPeerGetID(SOSPeerRef peer) {
679 return peer->peer_id;
682 CFSetRef SOSPeerGetViewNameSet(SOSPeerRef peer) {
686 void SOSPeerSetViewNameSet(SOSPeerRef peer, CFSetRef views) {
687 if (peer->views && !CFSetIsSubset(views, peer->views)) {
688 SOSPeerSetHasBeenInSync(peer, false);
691 CFRetainAssign(peer->views, views);
694 CFDataRef SOSPeerGetKeyBag(SOSPeerRef peer) {
695 return peer->_keyBag;
698 static bool SOSPeerUnlinkBackupJournal(SOSPeerRef peer, CFErrorRef *error) {
699 __block bool ok = true;
700 ok &= SOSPeerDoWithJournalPath(peer, error, ^(const char *journalName) {
701 secnotice("backup", "%@ unlinking journal file %s", peer, journalName);
702 ok &= SecCheckErrno(unlink(journalName), error, CFSTR("unlink %s"), journalName);
707 static bool SOSPeerWriteReset(SOSPeerRef peer, CFErrorRef *error) {
708 __block bool ok = true;
709 __block CFErrorRef localError = NULL;
710 ok &= SOSPeerTruncateJournal(peer, &localError, ^(FILE *journalFile) {
711 ok = SOSBackupEventWriteReset(journalFile, peer->_keyBag, &localError);
712 if (ok && !peer->_keyBag)
713 ok = SOSBackupEventWriteCompleteMarker(journalFile, 999, &localError);
717 secwarning("%@ failed to write reset to backup journal: %@", peer->peer_id, localError);
718 CFErrorPropagate(localError, error);
720 secnotice("backup-peer", "%@ Wrote reset.", peer->peer_id);
723 // Forget we ever wrote anything to the journal.
724 SOSPeerSetConfirmedManifest(peer, NULL);
725 SOSPeerSetProposedManifest(peer, NULL);
727 SOSPeerSetMustSendMessage(peer, !ok);
731 void SOSPeerKeyBagDidChange(SOSPeerRef peer) {
732 // If !keyBag unlink the file, instead of writing a reset.
733 // CloudServices does not want to hear about empty keybags
734 SOSPeerSetSendObjects(peer, false);
735 if (!peer->_keyBag) {
736 SOSPeerUnlinkBackupJournal(peer, NULL);
738 // Attempt to write a reset (ignoring failures since it will
739 // be pended stickily if it fails).
740 SOSPeerWriteReset(peer, NULL);
741 SOSCCRequestSyncWithBackupPeer(SOSPeerGetID(peer));
745 void SOSPeerSetKeyBag(SOSPeerRef peer, CFDataRef keyBag) {
746 if (CFEqualSafe(keyBag, peer->_keyBag)) return;
747 bool hadKeybag = peer->_keyBag;
749 secwarning("%@ keybag for backup unset", SOSPeerGetID(peer));
751 secnotice("backup", "%@ backup bag: %@", SOSPeerGetID(peer), keyBag);
753 CFRetainAssign(peer->_keyBag, keyBag);
754 // Don't call SOSPeerKeybagDidChange for the inital edge from NULL -> having a keybag.
756 SOSPeerKeyBagDidChange(peer);
760 bool SOSPeerWritePendingReset(SOSPeerRef peer, CFErrorRef *error) {
761 return !SOSPeerMustSendMessage(peer) || SOSPeerWriteReset(peer, error);
764 uint64_t SOSPeerNextSequenceNumber(SOSPeerRef peer) {
765 return ++peer->sequenceNumber;
768 uint64_t SOSPeerGetMessageVersion(SOSPeerRef peer) {
769 return SOSPeerGetVersion(peer);
772 bool SOSPeerMustSendMessage(SOSPeerRef peer) {
773 return peer->mustSendMessage;
776 void SOSPeerSetMustSendMessage(SOSPeerRef peer, bool sendMessage) {
777 peer->mustSendMessage = sendMessage;
780 bool SOSPeerSendObjects(SOSPeerRef peer) {
781 return peer->sendObjects;
784 void SOSPeerSetSendObjects(SOSPeerRef peer, bool sendObjects) {
785 peer->sendObjects = sendObjects;
788 bool SOSPeerHasBeenInSync(SOSPeerRef peer) {
789 return peer->hasBeenInSync;
792 void SOSPeerSetHasBeenInSync(SOSPeerRef peer, bool hasBeenInSync) {
793 peer->hasBeenInSync = hasBeenInSync;
798 SOSManifestRef SOSPeerGetProposedManifest(SOSPeerRef peer) {
799 if (peer->proposedManifests && CFArrayGetCount(peer->proposedManifests))
800 return (SOSManifestRef)CFArrayGetValueAtIndex(peer->proposedManifests, 0);
804 SOSManifestRef SOSPeerGetConfirmedManifest(SOSPeerRef peer) {
805 return peer->confirmedManifest;
808 void SOSPeerSetConfirmedManifest(SOSPeerRef peer, SOSManifestRef confirmed) {
809 CFRetainAssign(peer->confirmedManifest, confirmed);
811 // TODO: Clear only expired pending and local manifests from the array - this clears them all
812 // To do so we'd have to track the messageIds we sent to our peer and when we proposed a particular manifest.
813 // Then we simply remove the entries from messages older than the one we are confirming now
814 //CFArrayRemoveAllValues(SOSPeerGetDigestsWithKey(peer, kSOSPeerProposedManifestKey));
815 //CFArrayRemoveAllValues(SOSPeerGetDigestsWithKey(peer, kSOSPeerLocalManifestKey));
818 void SOSPeerAddProposedManifest(SOSPeerRef peer, SOSManifestRef proposed) {
819 SOSManifestArrayAppendManifest(&peer->proposedManifests, proposed);
822 void SOSPeerSetProposedManifest(SOSPeerRef peer, SOSManifestRef proposed) {
823 SOSManifestArraySetManifest(&peer->proposedManifests, proposed);
826 void SOSPeerAddLocalManifest(SOSPeerRef peer, SOSManifestRef local) {
827 SOSManifestArrayAppendManifest(&peer->localManifests, local);
830 SOSManifestRef SOSPeerGetPendingObjects(SOSPeerRef peer) {
831 return peer->pendingObjects;
834 void SOSPeerSetPendingObjects(SOSPeerRef peer, SOSManifestRef pendingObjects) {
835 CFRetainAssign(peer->pendingObjects, pendingObjects);
838 SOSManifestRef SOSPeerGetUnwantedManifest(SOSPeerRef peer) {
839 return peer->unwantedManifest;
842 void SOSPeerSetUnwantedManifest(SOSPeerRef peer, SOSManifestRef unwantedManifest) {
843 CFRetainAssign(peer->unwantedManifest, unwantedManifest);
845 bool SOSPeerTimerForPeerExist(SOSPeerRef peer){
846 dispatch_source_t timer = SOSPeerGetOTRTimer(peer);
847 return timer ? true : false;
849 void SOSPeerSetOTRTimer(SOSPeerRef peer, dispatch_source_t timer){
850 NSMutableDictionary* timers = (NSMutableDictionary*)CFBridgingRelease(peer->otrTimers);
852 timers = [[NSMutableDictionary alloc]init];
854 [timers setObject:timer forKey:(__bridge NSString*)SOSPeerGetID(peer)];
855 peer->otrTimers = (CFMutableDictionaryRef)CFBridgingRetain(timers);
858 dispatch_source_t SOSPeerGetOTRTimer(SOSPeerRef peer){
859 return (dispatch_source_t)CFDictionaryGetValue(peer->otrTimers, SOSPeerGetID(peer));
862 void SOSPeerRemoveOTRTimerEntry(SOSPeerRef peer){
863 CFDictionaryRemoveValue(peer->otrTimers, SOSPeerGetID(peer));
866 SOSManifestRef SOSPeerCopyManifestForDigest(SOSPeerRef peer, CFDataRef digest) {
867 if (!digest) return NULL;
868 SOSManifestRef manifest;
869 if (peer->proposedManifests) CFArrayForEachC(peer->proposedManifests, manifest) {
870 if (CFEqual(digest, SOSManifestGetDigest(manifest, NULL)))
871 return CFRetainSafe(manifest);
873 if (peer->localManifests) CFArrayForEachC(peer->localManifests, manifest) {
874 if (CFEqual(digest, SOSManifestGetDigest(manifest, NULL)))
875 return CFRetainSafe(manifest);
877 if (peer->confirmedManifest && CFEqual(digest, SOSManifestGetDigest(peer->confirmedManifest, NULL)))
878 return CFRetainSafe(peer->confirmedManifest);
883 static void SOSMarkManifestInUse(struct SOSDigestVector *mdInUse, SOSManifestRef manifest) {
884 CFDataRef digest = SOSManifestGetDigest(manifest, NULL);
886 SOSDigestVectorAppend(mdInUse, CFDataGetBytePtr(digest));
889 static void SOSMarkManifestsInUse(struct SOSDigestVector *mdInUse, CFArrayRef manifests) {
890 if (!isArray(manifests)) return;
891 SOSManifestRef manifest = NULL;
892 CFArrayForEachC(manifests, manifest) {
893 SOSMarkManifestInUse(mdInUse, manifest);
897 // Add all digests we are using to mdInUse
898 void SOSPeerMarkDigestsInUse(SOSPeerRef peer, struct SOSDigestVector *mdInUse) {
899 SOSMarkManifestInUse(mdInUse, peer->pendingObjects);
900 SOSMarkManifestInUse(mdInUse, peer->unwantedManifest);
901 SOSMarkManifestInUse(mdInUse, peer->confirmedManifest);
902 SOSMarkManifestsInUse(mdInUse, peer->localManifests);
903 SOSMarkManifestsInUse(mdInUse, peer->proposedManifests);
906 static void SOSAddManifestInUse(CFMutableDictionaryRef mfc, SOSManifestRef manifest) {
907 CFDataRef digest = SOSManifestGetDigest(manifest, NULL);
908 CFDataRef data = SOSManifestGetData(manifest);
910 CFDictionarySetValue(mfc, digest, data);
913 static void SOSAddManifestsInUse(CFMutableDictionaryRef mfc, CFArrayRef manifests) {
914 if (!isArray(manifests)) return;
915 SOSManifestRef manifest = NULL;
916 CFArrayForEachC(manifests, manifest) {
917 SOSAddManifestInUse(mfc, manifest);
921 void SOSPeerAddManifestsInUse(SOSPeerRef peer, CFMutableDictionaryRef mfc) {
922 SOSAddManifestInUse(mfc, peer->pendingObjects);
923 SOSAddManifestInUse(mfc, peer->unwantedManifest);
924 SOSAddManifestInUse(mfc, peer->confirmedManifest);
925 SOSAddManifestsInUse(mfc, peer->localManifests);
926 SOSAddManifestsInUse(mfc, peer->proposedManifests);
932 // additionsFromRemote
933 // original intent was that digests only got added to pendingObjects. We only know for sure if it is something added locally via api call
936 bool SOSPeerDidReceiveRemovalsAndAdditions(SOSPeerRef peer, SOSManifestRef absentFromRemote, SOSManifestRef additionsFromRemote, SOSManifestRef unwantedFromRemote,
937 SOSManifestRef local, CFErrorRef *error) {
938 // We assume that incoming manifests are all sorted, and absentFromRemote is disjoint from additionsFromRemote
940 SOSManifestRef remoteMissing = NULL, sharedRemovals = NULL, sharedAdditions = NULL;
942 // TODO: Simplify -- a lot.
943 ok = ok && (remoteMissing = SOSManifestCreateIntersection(absentFromRemote, local, error)); // remoteMissing = absentFromRemote <Intersected> local
944 ok = ok && (sharedRemovals = SOSManifestCreateComplement(remoteMissing, absentFromRemote, error)); // sharedRemovals = absentFromRemote - remoteMissing
945 ok = ok && (sharedAdditions = SOSManifestCreateIntersection(additionsFromRemote, local, error)); // sharedAdditions = additionsFromRemote <Intersected> local
946 //ok = ok && (remoteAdditions = SOSManifestCreateComplement(sharedAdditions, additionsFromRemote, error)); // remoteAdditions = additionsFromRemote - sharedAdditions
948 // remoteMissing are things we have that remote has asked for => add to pendingObjects
949 // sharedRemovals are things we don't have that remote has asked for => remove from pendingDeletes
950 // sharedAdditions are things we have that remote has too => remove from pendingObjects
951 // remoteAdditions are things that remote said they have that we don't and we should probably ask for => add to pendingDeletes?
952 // unwantedFromRemote are things we received from remote for which we already have a newer object according to the conflict resolver.
953 secnotice("peer", "%@ RM:%@ SR:%@ SA:%@ UR:%@", peer, remoteMissing, sharedRemovals, sharedAdditions, unwantedFromRemote);
955 SOSManifestRef pendingObjectsManifest = SOSManifestCreateWithPatch(peer->pendingObjects, sharedAdditions, remoteMissing, error);
956 SOSManifestRef unwantedManifest = SOSManifestCreateWithPatch(peer->unwantedManifest, sharedRemovals, unwantedFromRemote, error);
957 CFAssignRetained(peer->pendingObjects, pendingObjectsManifest); // PO = PO - sharedAdditions + remoteMissing
958 CFAssignRetained(peer->unwantedManifest, unwantedManifest); // U = U - sharedRemovals + unwantedFromRemote
960 CFReleaseSafe(remoteMissing);
961 CFReleaseSafe(sharedRemovals);
962 CFReleaseSafe(sharedAdditions);
964 secnotice("peer", "%@ C:%@ U:%@ O:%@", peer, SOSPeerGetConfirmedManifest(peer), SOSPeerGetUnwantedManifest(peer), SOSPeerGetPendingObjects(peer));
969 // Called for a normal syncing peer. Only updates pendingObjects currently.
970 bool SOSPeerDataSourceWillCommit(SOSPeerRef peer, SOSDataSourceTransactionSource source, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error) {
971 bool isAPITransaction = source == kSOSDataSourceAPITransaction;
972 bool isCKKSTransaction = source == kSOSDataSourceCKKSTransaction;
973 SOSManifestRef unconfirmedAdditions = NULL;
974 if ((isAPITransaction || isCKKSTransaction) && SOSManifestGetCount(additions)) {
975 // Remove confirmed from additions, leaving us with additions to the local db that the remote peer doesn't have yet
976 unconfirmedAdditions = SOSManifestCreateComplement(SOSPeerGetConfirmedManifest(peer), additions, error);
979 if (SOSManifestGetCount(removals) || SOSManifestGetCount(unconfirmedAdditions)) {
980 SOSManifestRef pendingObjectsManifest = SOSManifestCreateWithPatch(peer->pendingObjects, removals, unconfirmedAdditions, error);
983 // TODO: Only do this if debugScope "peer", notice is enabled.
984 // if (!SecIsScopeActive(kSecLevelNotice, "peer"))
986 // pended == UA unless the db is renotifying of an addition for something we already have
987 SOSManifestRef unpended = NULL, pended = NULL;
988 SOSManifestDiff(peer->pendingObjects, pendingObjectsManifest, &unpended, &pended, error);
989 secinfo("peer", "%@: willCommit R:%@ A:%@ UA:%@ %s O%s%@%s%@",
990 SOSPeerGetID(peer), removals, additions, unconfirmedAdditions,
991 (isAPITransaction ? "api": isCKKSTransaction ? "ckks" : "sos"),
992 (SOSManifestGetCount(unpended) ? "-" : ""),
993 (SOSManifestGetCount(unpended) ? (CFStringRef)unpended : CFSTR("")),
994 (SOSManifestGetCount(pended) ? "+" : SOSManifestGetCount(unpended) ? "" : "="),
995 (SOSManifestGetCount(pended) ? (CFStringRef)pended : CFSTR("")));
996 CFReleaseSafe(unpended);
997 CFReleaseSafe(pended);
1000 CFAssignRetained(peer->pendingObjects, pendingObjectsManifest);
1002 CFReleaseSafe(unconfirmedAdditions);
1007 bool SOSPeerWriteAddEvent(FILE *journalFile, keybag_handle_t kbhandle, SOSDataSourceRef dataSource, SOSObjectRef object, CFErrorRef *error) {
1008 CFDictionaryRef backup_item = NULL;
1009 bool ok = ((backup_item = SOSObjectCopyBackup(dataSource, object, kbhandle, error))
1010 && SOSBackupEventWriteAdd(journalFile, backup_item, error));
1011 CFReleaseSafe(backup_item);
1015 // Called for a backup peer, should stream objects in changes right away
1016 bool SOSPeerDataSourceWillChange(SOSPeerRef peer, SOSDataSourceRef dataSource, SOSDataSourceTransactionSource source, CFArrayRef changes, CFErrorRef *error) {
1017 __block bool ok = true;
1018 ok &= SOSPeerWritePendingReset(peer, error) && SOSPeerAppendToJournal(peer, error, ^(FILE *journalFile, keybag_handle_t kbhandle) {
1019 struct SOSDigestVector dvdel = SOSDigestVectorInit;
1020 struct SOSDigestVector dvadd = SOSDigestVectorInit;
1021 SOSChangeRef change;
1022 CFArrayForEachC(changes, change) {
1023 bool isDelete = false;
1024 CFErrorRef localError = NULL;
1025 CFDataRef digest = NULL;
1026 SOSObjectRef object = NULL;
1027 bool ok = digest = SOSChangeCopyDigest(dataSource, change, &isDelete, &object, &localError);
1029 ok &= SOSBackupEventWriteDelete(journalFile, digest, &localError);
1030 SOSDigestVectorAppend(&dvdel, CFDataGetBytePtr(digest));
1032 ok &= SOSPeerWriteAddEvent(journalFile, kbhandle, dataSource, object, &localError);
1033 SOSDigestVectorAppend(&dvadd, CFDataGetBytePtr(digest));
1036 secerror("bad change %@: %@", change, localError);
1038 CFReleaseSafe(digest);
1039 CFReleaseSafe(localError);
1043 // Update our proposed manifest since we just wrote stuff
1044 struct SOSDigestVector dvresult = SOSDigestVectorInit;
1045 SOSDigestVectorSort(&dvdel);
1046 SOSDigestVectorSort(&dvadd);
1047 if ((ok = SOSDigestVectorPatchSorted(SOSManifestGetDigestVector(SOSPeerGetProposedManifest(peer)), &dvdel,
1048 &dvadd, &dvresult, error))) {
1049 SOSManifestRef proposed;
1050 ok = proposed = SOSManifestCreateWithDigestVector(&dvresult, error);
1051 SOSPeerSetProposedManifest(peer, proposed);
1052 CFReleaseSafe(proposed);
1054 SOSDigestVectorFree(&dvresult);
1056 SOSDigestVectorFree(&dvdel);
1057 SOSDigestVectorFree(&dvadd);
1059 // Only Write marker if we are actually in sync now (local == propopsed).
1060 if (SOSPeerSendObjects(peer))
1061 SOSBackupEventWriteCompleteMarker(journalFile, 799, error);
1065 // We were unable to stream everything out neatly
1066 SOSCCRequestSyncWithBackupPeer(SOSPeerGetID(peer));