X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/sec/SOSCircle/SecureObjectSync/SOSPeer.c diff --git a/Security/sec/SOSCircle/SecureObjectSync/SOSPeer.c b/Security/sec/SOSCircle/SecureObjectSync/SOSPeer.c new file mode 100644 index 00000000..07f10cb7 --- /dev/null +++ b/Security/sec/SOSCircle/SecureObjectSync/SOSPeer.c @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +/* + * SOSPeer.c - Implementation of a secure object syncing peer + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +// +// MARK: - SOSPeerPersistence code +// +static CFStringRef kSOSPeerSequenceNumberKey = CFSTR("sequence-number"); +static CFStringRef kSOSPeerGetObjectsKey = CFSTR("get-objects"); +static CFStringRef kSOSPeerReceivedUnknownConfirmedDigestKey = CFSTR("received-unknown"); +static CFStringRef kSOSPeerJoinRequestedKey = CFSTR("join-requested"); +static CFStringRef kSOSPeerSkipHelloKey = CFSTR("skip-hello"); + +CFStringRef kSOSPeerDataLabel = CFSTR("iCloud Peer Data Meta-data"); + +// +// MARK: SOSPeerState (dictionary keys) +// + +// PeerState dictionary keys +static CFStringRef kSOSPeerSendObjectsKey = CFSTR("send-objects"); // bool +static CFStringRef kSOSPeerMustSendMessageKey = CFSTR("must-send"); // bool +static CFStringRef kSOSPeerPendingObjectsKey = CFSTR("pending-objects"); // digest +static CFStringRef kSOSPeerPendingDeletesKey = CFSTR("pending-deletes"); // digest +static CFStringRef kSOSPeerConfirmedManifestKey = CFSTR("confirmed-manifest"); //digest +static CFStringRef kSOSPeerProposedManifestKey = CFSTR("pending-manifest"); // array of digests +static CFStringRef kSOSPeerLocalManifestKey = CFSTR("local-manifest"); // array of digests +static CFStringRef kSOSPeerVersionKey = CFSTR("version"); // int + +enum { + kSOSPeerMaxManifestWindowDepth = 4 +}; + +// +// MARK: - SOSPeer +// + +struct __OpaqueSOSPeer { + CFRuntimeBase _base; + SOSEngineRef engine; + + SOSCoderRef coder; + CFStringRef peer_id; + CFIndex version; + uint64_t sequenceNumber; + bool mustSendMessage; +}; + +CFGiblisWithCompareFor(SOSPeer) + +static CFStringRef SOSManifestCreateOptionalDescriptionWithLabel(SOSManifestRef manifest, CFStringRef label) { + if (!manifest) return CFSTR(" - "); + //return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@[%zu]"), label, SOSManifestGetCount(manifest)); + return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(" %@%@"), label, manifest); +} + +static CFMutableDictionaryRef SOSPeerGetState(SOSPeerRef peer) { + return SOSEngineGetPeerState(peer->engine, peer->peer_id); +} + +static CFStringRef SOSPeerCreateManifestArrayDescriptionWithKey(SOSPeerRef peer, CFStringRef key, CFStringRef label) { + CFMutableArrayRef digests = (CFMutableArrayRef)CFDictionaryGetValue(SOSPeerGetState(peer), key); + CFIndex count = digests ? CFArrayGetCount(digests) : 0; + if (count == 0) return CFSTR(" - "); + CFDataRef digest = CFArrayGetValueAtIndex(digests, 0); + return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(" %@[%" PRIdCFIndex "]%@"), label, count, SOSEngineGetManifestForDigest(peer->engine, digest)); +} + +static CFStringRef SOSPeerCopyDescription(CFTypeRef cf) { + SOSPeerRef peer = (SOSPeerRef)cf; + if(peer){ + CFStringRef po = SOSManifestCreateOptionalDescriptionWithLabel(SOSPeerGetPendingObjects(peer), CFSTR("O")); + CFStringRef de = SOSManifestCreateOptionalDescriptionWithLabel(SOSPeerGetPendingDeletes(peer), CFSTR("D")); + CFStringRef co = SOSManifestCreateOptionalDescriptionWithLabel(SOSPeerGetConfirmedManifest(peer), CFSTR("C")); + CFStringRef pe = SOSPeerCreateManifestArrayDescriptionWithKey(peer, kSOSPeerProposedManifestKey, CFSTR("P")); + CFStringRef lo = SOSPeerCreateManifestArrayDescriptionWithKey(peer, kSOSPeerLocalManifestKey, CFSTR("L")); + CFStringRef desc = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<%@ %s%s%@%@%@%@%@>"), + SOSPeerGetID(peer), + SOSPeerMustSendMessage(peer) ? "F" : "f", + SOSPeerSendObjects(peer) ? "S" : "s", + po, de, co, pe, lo); + CFReleaseSafe(lo); + CFReleaseSafe(pe); + CFReleaseSafe(co); + CFReleaseSafe(de); + CFReleaseSafe(po); + + return desc; + } + else + return CFSTR("NULL"); +} + +static Boolean SOSPeerCompare(CFTypeRef cfA, CFTypeRef cfB) +{ + SOSPeerRef peerA = (SOSPeerRef)cfA, peerB = (SOSPeerRef)cfB; + // Use mainly to see if peerB is actually this device (peerA) + return CFStringCompare(SOSPeerGetID(peerA), SOSPeerGetID(peerB), 0) == kCFCompareEqualTo; +} + +static CFMutableArrayRef SOSPeerGetDigestsWithKey(SOSPeerRef peer, CFStringRef key) { + CFMutableDictionaryRef peerState = SOSPeerGetState(peer); + CFMutableArrayRef digests = (CFMutableArrayRef)CFDictionaryGetValue(peerState, key); + if (!digests) { + digests = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); + CFDictionarySetValue(peerState, key, digests); + CFReleaseSafe(digests); + } + return digests; +} + +static void SOSPeerStateSetDigestForKey(CFMutableDictionaryRef peerState, CFStringRef key, CFDataRef digest) { + if (digest) + CFDictionarySetValue(peerState, key, digest); + else + CFDictionaryRemoveValue(peerState, key); +} + +static void SOSPeerAddManifestWithKey(SOSPeerRef peer, CFStringRef key, SOSManifestRef manifest) { + CFMutableArrayRef digests = SOSPeerGetDigestsWithKey(peer, key); + CFDataRef digest = SOSManifestGetDigest(manifest, NULL); + if (digest) { + CFIndex count = CFArrayGetCount(digests); + SOSEngineAddManifest(peer->engine, manifest); + CFIndex ixOfDigest = CFArrayGetFirstIndexOfValue(digests, CFRangeMake(0, count), digest); + if (ixOfDigest != 0) { + if (ixOfDigest != kCFNotFound) { + CFArrayRemoveValueAtIndex(digests, ixOfDigest); + } else { + while (count >= kSOSPeerMaxManifestWindowDepth) + CFArrayRemoveValueAtIndex(digests, --count); + } + + CFArrayInsertValueAtIndex(digests, 0, digest); + } + } else { + // pending == NULL => nothing clear history + CFArrayRemoveAllValues(digests); + } +} + +static SOSPeerRef SOSPeerCreate_Internal(SOSEngineRef engine, CFDictionaryRef persisted, CFStringRef theirPeerID, CFIndex version, CFErrorRef *error) { + SOSPeerRef p = CFTypeAllocate(SOSPeer, struct __OpaqueSOSPeer, kCFAllocatorDefault); + p->engine = engine; + p->peer_id = CFRetainSafe(theirPeerID); + p->version = version; + + if (persisted) { + CFDictionaryRef peer_dict = (CFDictionaryRef) persisted; + + int64_t sequenceNumber; + CFNumberRef seqNo = CFDictionaryGetValue(peer_dict, kSOSPeerSequenceNumberKey); + if (seqNo) { + CFNumberGetValue(seqNo, kCFNumberSInt64Type, &sequenceNumber); + p->sequenceNumber = sequenceNumber; + } + CFNumberRef version = CFDictionaryGetValue(peer_dict, kSOSPeerVersionKey); + if (version) + CFNumberGetValue(version, kCFNumberCFIndexType, &p->version); + } + + return p; +} + +SOSPeerRef SOSPeerCreateWithEngine(SOSEngineRef engine, CFStringRef peer_id) { + CFMutableDictionaryRef state = SOSEngineGetPeerState(engine, peer_id); + return SOSPeerCreate_Internal(engine, state, peer_id, 0, NULL); +} + +static bool SOSPeerPersistData(SOSPeerRef peer, CFErrorRef *error) +{ + CFMutableDictionaryRef data_dict = SOSPeerGetState(peer); + + int64_t sequenceNumber = peer->sequenceNumber; + CFNumberRef seqNo = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &sequenceNumber); + CFDictionarySetValue(data_dict, kSOSPeerSequenceNumberKey, seqNo); + CFReleaseNull(seqNo); + CFDictionarySetValue(data_dict, kSOSPeerMustSendMessageKey, peer->mustSendMessage ? kCFBooleanTrue : kCFBooleanFalse); + if (peer->version) { + CFNumberRef version = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &peer->version); + CFDictionarySetValue(data_dict, kSOSPeerVersionKey, version); + CFReleaseSafe(version); + } + return true; +} + +SOSPeerRef SOSPeerCreate(SOSEngineRef engine, SOSPeerInfoRef peerInfo, + CFErrorRef *error) { + if (peerInfo == NULL) { + SOSCreateError(kSOSErrorUnsupported, CFSTR("Can't create peer without their peer info!"), NULL, error); + return NULL; + } + + CFStringRef peer_id = SOSPeerInfoGetPeerID(peerInfo); + CFDictionaryRef persisted = SOSEngineGetPeerState(engine, peer_id); + SOSPeerRef peer = SOSPeerCreate_Internal(engine, + persisted, + peer_id, + SOSPeerInfoGetVersion(peerInfo), + error); + if (peer) + SOSPeerPersistData(peer, error); + return peer; +} + +SOSPeerRef SOSPeerCreateSimple(SOSEngineRef engine, CFStringRef peer_id, CFIndex version, CFErrorRef *error) { + + CFDictionaryRef persisted = SOSEngineGetPeerState(engine, peer_id); + SOSPeerRef peer = SOSPeerCreate_Internal(engine, persisted, peer_id, version, error); + if (peer) + SOSPeerPersistData(peer, error); + return peer; +} + +static void SOSPeerDestroy(CFTypeRef cf) { + SOSPeerRef peer = (SOSPeerRef)cf; + SOSPeerPersistData(peer, NULL); + CFReleaseSafe(peer->peer_id); +} + +void SOSPeerDidConnect(SOSPeerRef peer) { + SOSPeerSetMustSendMessage(peer, true); + SOSPeerSetProposedManifest(peer, SOSPeerGetConfirmedManifest(peer)); +} + +CFIndex SOSPeerGetVersion(SOSPeerRef peer) { + return peer->version; +} + +CFStringRef SOSPeerGetID(SOSPeerRef peer) { + return peer->peer_id; +} + +SOSEngineRef SOSPeerGetEngine(SOSPeerRef peer){ + return peer->engine; +} +SOSCoderRef SOSPeerGetCoder(SOSPeerRef peer){ + return peer->coder; +} +void SOSPeerSetCoder(SOSPeerRef peer, SOSCoderRef coder){ + peer->coder = coder; +} + +uint64_t SOSPeerNextSequenceNumber(SOSPeerRef peer) { + return ++peer->sequenceNumber; +} + +uint64_t SOSPeerGetMessageVersion(SOSPeerRef peer) { + return SOSPeerGetVersion(peer); + +} + +bool SOSPeerMustSendMessage(SOSPeerRef peer) { + CFBooleanRef must = CFDictionaryGetValue(SOSPeerGetState(peer), kSOSPeerMustSendMessageKey); + return must && CFBooleanGetValue(must); +} + +void SOSPeerSetMustSendMessage(SOSPeerRef peer, bool sendMessage) { + CFDictionarySetValue(SOSPeerGetState(peer), kSOSPeerMustSendMessageKey, sendMessage ? kCFBooleanTrue : kCFBooleanFalse); +} + +bool SOSPeerSendObjects(SOSPeerRef peer) { + CFBooleanRef send = CFDictionaryGetValue(SOSPeerGetState(peer), kSOSPeerSendObjectsKey); + return send && CFBooleanGetValue(send); +} + +void SOSPeerSetSendObjects(SOSPeerRef peer, bool sendObjects) { + CFDictionarySetValue(SOSPeerGetState(peer), kSOSPeerSendObjectsKey, sendObjects ? kCFBooleanTrue : kCFBooleanFalse); +} + +SOSManifestRef SOSPeerGetProposedManifest(SOSPeerRef peer) { + CFDataRef digest = NULL; + CFMutableArrayRef proposedDigests = (CFMutableArrayRef)CFDictionaryGetValue(SOSPeerGetState(peer), kSOSPeerProposedManifestKey); + if (proposedDigests && CFArrayGetCount(proposedDigests) > 0) + digest = CFArrayGetValueAtIndex(proposedDigests, 0); + return SOSEngineGetManifestForDigest(peer->engine, digest); +} + +#if 0 +static SOSManifestRef SOSPeerGetLocalManifest(SOSPeerRef peer) { + CFDataRef digest = NULL; + CFMutableArrayRef localDigests = (CFMutableArrayRef)CFDictionaryGetValue(SOSPeerGetState(peer), kSOSPeerLocalManifestKey); + if (localDigests && CFArrayGetCount(localDigests) > 0) + digest = CFArrayGetValueAtIndex(localDigests, 0); + return SOSEngineGetManifestForDigest(peer->engine, digest); +} +#endif + +SOSManifestRef SOSPeerGetConfirmedManifest(SOSPeerRef peer) { + return SOSEngineGetManifestForDigest(peer->engine, CFDictionaryGetValue(SOSPeerGetState(peer), kSOSPeerConfirmedManifestKey)); +} + +void SOSPeerSetConfirmedManifest(SOSPeerRef peer, SOSManifestRef confirmed) { + SOSEngineAddManifest(peer->engine, confirmed); + SOSPeerStateSetDigestForKey(SOSPeerGetState(peer), kSOSPeerConfirmedManifestKey, SOSManifestGetDigest(confirmed, NULL)); + + // TODO: Clear only expired pending and local manifests from the array - this clears them all + // To do so we'd have to track the messageIds we sent to our peer and when we proposed a particular manifest. + // Then we simply remove the entires from messages older that the one we are confirming now + //CFArrayRemoveAllValues(SOSPeerGetDigestsWithKey(peer, kSOSPeerProposedManifestKey)); + //CFArrayRemoveAllValues(SOSPeerGetDigestsWithKey(peer, kSOSPeerLocalManifestKey)); +} + +void SOSPeerAddProposedManifest(SOSPeerRef peer, SOSManifestRef pending) { + SOSPeerAddManifestWithKey(peer, kSOSPeerProposedManifestKey, pending); +} + +void SOSPeerSetProposedManifest(SOSPeerRef peer, SOSManifestRef pending) { + SOSEngineAddManifest(peer->engine, pending); + CFMutableArrayRef proposedDigests = SOSPeerGetDigestsWithKey(peer, kSOSPeerProposedManifestKey); + CFArrayRemoveAllValues(proposedDigests); + if (pending) + CFArrayAppendValue(proposedDigests, SOSManifestGetDigest(pending, NULL)); +} + +void SOSPeerAddLocalManifest(SOSPeerRef peer, SOSManifestRef local) { + SOSPeerAddManifestWithKey(peer, kSOSPeerLocalManifestKey, local); +} + +SOSManifestRef SOSPeerGetPendingObjects(SOSPeerRef peer) { + return SOSEngineGetManifestForDigest(peer->engine, CFDictionaryGetValue(SOSPeerGetState(peer), kSOSPeerPendingObjectsKey)); +} + +void SOSPeerSetPendingObjects(SOSPeerRef peer, SOSManifestRef pendingObjects) { + SOSEngineAddManifest(peer->engine, pendingObjects); + SOSPeerStateSetDigestForKey(SOSPeerGetState(peer), kSOSPeerPendingObjectsKey, SOSManifestGetDigest(pendingObjects, NULL)); +} + +SOSManifestRef SOSPeerGetPendingDeletes(SOSPeerRef peer) { + return SOSEngineGetManifestForDigest(peer->engine, CFDictionaryGetValue(SOSPeerGetState(peer), kSOSPeerPendingDeletesKey)); +} + +void SOSPeerSetPendingDeletes(SOSPeerRef peer, SOSManifestRef pendingDeletes) { + SOSEngineAddManifest(peer->engine, pendingDeletes); + SOSPeerStateSetDigestForKey(SOSPeerGetState(peer), kSOSPeerPendingDeletesKey, SOSManifestGetDigest(pendingDeletes, NULL)); +} + +static void SOSMarkDigestInUse(struct SOSDigestVector *mdInUse, CFDataRef digest) { + if (!isData(digest)) return; + SOSDigestVectorAppend(mdInUse, CFDataGetBytePtr(digest)); +} + +static void SOSMarkDigestsInUse(struct SOSDigestVector *mdInUse, CFArrayRef digests) { + if (!isArray(digests)) return; + CFDataRef digest = NULL; + CFArrayForEachC(digests, digest) { + SOSMarkDigestInUse(mdInUse, digest); + } +} + +// Add all digests we are using to mdInUse +void SOSPeerMarkDigestsInUse(SOSPeerRef peer, struct SOSDigestVector *mdInUse) { + CFMutableDictionaryRef peerState = SOSPeerGetState(peer); + SOSMarkDigestInUse(mdInUse, CFDictionaryGetValue(peerState, kSOSPeerPendingObjectsKey)); + SOSMarkDigestInUse(mdInUse, CFDictionaryGetValue(peerState, kSOSPeerPendingDeletesKey)); + SOSMarkDigestInUse(mdInUse, CFDictionaryGetValue(peerState, kSOSPeerConfirmedManifestKey)); + SOSMarkDigestsInUse(mdInUse, CFDictionaryGetValue(peerState, kSOSPeerLocalManifestKey)); + SOSMarkDigestsInUse(mdInUse, CFDictionaryGetValue(peerState, kSOSPeerProposedManifestKey)); +} + + +// absentFromRemote +// AbsentLocally +// additionsFromRemote +// original intent was that digests only got added to pendingObjects. We only know for sure if it is something added locally via api call + + +bool SOSPeerDidReceiveRemovalsAndAdditions(SOSPeerRef peer, SOSManifestRef absentFromRemote, SOSManifestRef additionsFromRemote, + SOSManifestRef local, CFErrorRef *error) { + // We assume that incoming manifests are all sorted, and absentFromRemote is disjoint from additionsFromRemote + bool ok = true; + SOSManifestRef remoteRemovals = NULL, sharedRemovals = NULL, sharedAdditions = NULL, remoteAdditions = NULL; + CFDataRef pendingObjectsDigest, pendingDeletesDigest; + + // TODO: Simplyfy -- a lot. + ok = ok && (remoteRemovals = SOSManifestCreateIntersection(absentFromRemote, local, error)); // remoteRemovals = absentFromRemote local + ok = ok && (sharedRemovals = SOSManifestCreateComplement(remoteRemovals, absentFromRemote, error)); // sharedRemovals = absentFromRemote - remoteRemovals + ok = ok && (sharedAdditions = SOSManifestCreateIntersection(additionsFromRemote, local, error)); // sharedAdditions = additionsFromRemote local + ok = ok && (remoteAdditions = SOSManifestCreateComplement(sharedAdditions, additionsFromRemote, error)); // remoteAdditions = additionsFromRemote - sharedAdditions + + secnotice("peer", "%@ R:%@ A:%@ C:%@ D:%@ O:%@", peer, absentFromRemote, additionsFromRemote, SOSPeerGetConfirmedManifest(peer), SOSPeerGetPendingDeletes(peer), SOSPeerGetPendingObjects(peer)); + + // TODO: Does the value of SOSPeerSendObjects() matter here? + pendingObjectsDigest = SOSEnginePatchRecordAndCopyDigest(peer->engine, SOSPeerGetPendingObjects(peer), sharedAdditions, NULL, error); // PO = PO - sharedAdditions + pendingDeletesDigest = SOSEnginePatchRecordAndCopyDigest(peer->engine, SOSPeerGetPendingDeletes(peer), sharedRemovals, NULL, error); // D = D - sharedRemovals + + CFMutableDictionaryRef peerState = SOSPeerGetState(peer); + SOSPeerStateSetDigestForKey(peerState, kSOSPeerPendingObjectsKey, pendingObjectsDigest); + SOSPeerStateSetDigestForKey(peerState, kSOSPeerPendingDeletesKey, pendingDeletesDigest); + + CFReleaseSafe(pendingDeletesDigest); + CFReleaseSafe(pendingObjectsDigest); + CFReleaseSafe(remoteRemovals); + CFReleaseSafe(sharedRemovals); + CFReleaseSafe(sharedAdditions); + CFReleaseSafe(remoteAdditions); + + secnotice("peer", "%@ C:%@ D:%@ O:%@", peer, SOSPeerGetConfirmedManifest(peer), SOSPeerGetPendingDeletes(peer), SOSPeerGetPendingObjects(peer)); + + return ok; +} + +bool SOSPeerDidReceiveConfirmedManifest(SOSPeerRef peer, SOSManifestRef confirmed, SOSManifestRef local, CFErrorRef *error) { + bool ok = true; + if (!confirmed) return ok; + SOSManifestRef confirmedRemovals = NULL, confirmedAdditions = NULL; + + // confirmedAdditions = confirmed - previous_confirmed, confirmedRemovals = previous_confirmed - confirmed + ok &= SOSManifestDiff(SOSPeerGetConfirmedManifest(peer), confirmed, &confirmedRemovals, &confirmedAdditions, error); + ok &= SOSPeerDidReceiveRemovalsAndAdditions(peer, confirmedRemovals, confirmedAdditions, local, error); + + CFReleaseSafe(confirmedRemovals); + CFReleaseSafe(confirmedAdditions); + return ok; +} + +bool SOSPeerDataSourceWillCommit(SOSPeerRef peer, SOSDataSourceTransactionSource source, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error) { + SOSManifestRef unconfirmedAdditions = NULL; + CFDataRef pendingObjectsDigest, pendingDeletesDigest = NULL; + + secnotice("peer", "%@ R:%@ A:%@ C:%@ D:%@ O:%@", peer, removals, additions, SOSPeerGetConfirmedManifest(peer), SOSPeerGetPendingDeletes(peer), SOSPeerGetPendingObjects(peer)); + + // Remove confirmed from additions + // TODO: Add require and check for error + unconfirmedAdditions = SOSManifestCreateComplement(SOSPeerGetConfirmedManifest(peer), additions, error); + secnotice("peer", "%@ UA: %@ source: %s", peer, unconfirmedAdditions, source == kSOSDataSourceSOSTransaction ? "sos" : "api"); + + pendingObjectsDigest = SOSEnginePatchRecordAndCopyDigest(peer->engine, SOSPeerGetPendingObjects(peer), removals, source == kSOSDataSourceAPITransaction ? unconfirmedAdditions : NULL, error); + // TODO: Figure out how to update pendingDeletes... + //pendingDeletesDigest = SOSEnginePatchRecordAndCopyDigest(peer->engine, SOSPeerGetPendingDeletes(peer), removals, NULL, error); + + CFMutableDictionaryRef peerState = SOSPeerGetState(peer); + + SOSPeerStateSetDigestForKey(peerState, kSOSPeerPendingObjectsKey, pendingObjectsDigest); + SOSPeerStateSetDigestForKey(peerState, kSOSPeerPendingDeletesKey, pendingDeletesDigest); + + CFReleaseSafe(pendingDeletesDigest); + CFReleaseSafe(pendingObjectsDigest); + CFReleaseSafe(unconfirmedAdditions); + + secnotice("peer", "%@ C:%@ D:%@ P:%@", peer, SOSPeerGetConfirmedManifest(peer), SOSPeerGetPendingDeletes(peer), SOSPeerGetPendingObjects(peer)); + + return true; +} +