1/*
2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25/*
26 * SOSEngine.c -  Implementation of a secure object syncing engine
27 */
28
29#include <SecureObjectSync/SOSEngine.h>
30#include <SecureObjectSync/SOSDigestVector.h>
31#include <SecureObjectSync/SOSInternal.h>
32#include <SecureObjectSync/SOSPeerInfo.h>
33#include <corecrypto/ccder.h>
34#include <stdlib.h>
35#include <stdbool.h>
36#include <utilities/SecCFError.h>
37#include <utilities/SecCFRelease.h>
38#include <utilities/SecCFWrappers.h>
39#include <utilities/der_plist.h>
40#include <utilities/der_plist_internal.h>
41#include <utilities/debugging.h>
42#include <utilities/iCloudKeychainTrace.h>
43#include <AssertMacros.h>
44#include <CoreFoundation/CoreFoundation.h>
45#include <securityd/SecItemDataSource.h> // TODO: We can't leave this here.
46#include <securityd/SecDbItem.h> // TODO: We can't leave this here.
47#include <securityd/SecItemServer.h>// TODO: We can't leave this here.
48#include <Security/SecItemPriv.h>// TODO: We can't leave this here.
49#include <securityd/SOSCloudCircleServer.h>
50
51//
52// MARK: SOSEngine The Keychain database with syncable keychain support.
53//
54
55// Key in dataSource for general engine state file.
56// This file only has digest entries in it, no manifests.
57static const CFStringRef kSOSEngineState = CFSTR("engine-state");
58
59// Keys in state dictionary
60static CFStringRef kSOSPeerCoderKey = CFSTR("coder");
61static CFStringRef kSOSEngineManifestCacheKey = CFSTR("manifestCache");
62static CFStringRef kSOSEnginePeerStateKey = CFSTR("peerState");
63static CFStringRef kSOSEnginePeerIDsKey = CFSTR("peerIDs");
64static CFStringRef kSOSEngineIDKey = CFSTR("id");
65
66/* SOSEngine implementation. */
67struct __OpaqueSOSEngine {
68    CFRuntimeBase _base;
69    SOSDataSourceRef dataSource;
70    CFStringRef myID;                       // My peerID in the circle
71    SOSManifestRef manifest;                // Explicitly not in cache since it's not persisted?
72    // We need to address the issues of corrupt keychain items
73    SOSManifestRef unreadble;               // Possibly by having a set of unreadble items, to which we
74    // add any corrupted items in the db that have yet to be deleted.
75    // This happens if we notce corruption during a (read only) query.
76    // We would also perma-subtract unreadable from manifest whenever
77    // anyone asked for manifest.  This result would be cached in
78    // The manifestCache below, so we just need a key into the cache
79    CFDataRef localMinusUnreadableDigest;   // or a digest (CFDataRef of the right size).
80
81    CFMutableDictionaryRef manifestCache;   // digest -> ( refcount, manifest )
82    CFMutableDictionaryRef peerState;       // peerId -> mutable array of digests
83    CFArrayRef peerIDs;
84
85    dispatch_queue_t queue;
86};
87
88static bool SOSEngineLoad(SOSEngineRef engine, CFErrorRef *error);
89
90
91static CFStringRef SOSPeerIDArrayCreateString(CFArrayRef peerIDs) {
92    return peerIDs ? CFStringCreateByCombiningStrings(kCFAllocatorDefault, peerIDs, CFSTR(" ")) : CFSTR("");
93 }
94
95static CFStringRef SOSEngineCopyFormattingDesc(CFTypeRef cf, CFDictionaryRef formatOptions) {
96    SOSEngineRef engine = (SOSEngineRef)cf;
97    CFStringRef tpDesc = SOSPeerIDArrayCreateString(engine->peerIDs);
98    CFStringRef desc = CFStringCreateWithFormat(kCFAllocatorDefault, formatOptions, CFSTR("<Engine %@ peers %@ MC[%d] PS[%d]>"), engine->myID, tpDesc, engine->manifestCache ? (int)CFDictionaryGetCount(engine->manifestCache) : 0, engine->peerState ? (int)CFDictionaryGetCount(engine->peerState) : 0);
99    CFReleaseSafe(tpDesc);
100    return desc;
101 }
102
103static CFStringRef SOSEngineCopyDebugDesc(CFTypeRef cf) {
104    return SOSEngineCopyFormattingDesc(cf, NULL);
105 }
106
107static dispatch_queue_t sEngineQueue;
108static CFDictionaryRef sEngineMap;
109
110CFGiblisWithFunctions(SOSEngine, NULL, NULL, NULL, NULL, NULL, SOSEngineCopyFormattingDesc, SOSEngineCopyDebugDesc, NULL, NULL, ^{
111    sEngineQueue = dispatch_queue_create("SOSEngine queue", DISPATCH_QUEUE_SERIAL);
112    sEngineMap = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
113});
114
115#define _LOG_RAW_MESSAGES 0
116void logRawMessage(CFDataRef message, bool sending, uint64_t seqno)
117{
118#if _LOG_RAW_MESSAGES
119    CFStringRef hexMessage = NULL;
120    if (message) {
121        hexMessage = CFDataCopyHexString(message);
122        if (sending)
123            secnoticeq("engine", "%s RAW%1d %@", sending ? "send" : "recv", seqno?2:0, hexMessage);
124        else
125            secnoticeq("engine", "%s RAWx %@", sending ? "send" : "recv", hexMessage);  // we don't know vers of received msg here
126    }
127    CFReleaseSafe(hexMessage);
128#endif
129}
130//
131// Peer state layout.  WRONG! It's an array now
132// The peer state is an array.
133// The first element of the array is a dictionary with any number of keys and
134// values in it (for future expansion) such as changing the digest size or type
135// or remebering boolean flags for a peers sake.
136// The next three are special in that they are manifest digests with special
137// meaning and rules as to how they are treated (These are dynamically updated
138// based on database activity so they have a fully history of all changes made
139// to the local db. The first is the manifest representing the pendingObjects
140// to send to the other peer.  This is normally only ever appending to, and in
141// particular with transactions originating from the Keychain API that affect
142// syncable items will need to add the new objects digests to the pendingObjects list
143// while adding the digests of any tombstones encountered to the extra list.
144
145CFStringRef SOSEngineGetMyID(SOSEngineRef engine) {
146    // TODO: this should not be needed
147    return engine->myID;
148}
149
150// TEMPORARY: Get the list of IDs for cleanup, this shouldn't be used instead it should iterate KVS.
151CFArrayRef SOSEngineGetPeerIDs(SOSEngineRef engine) {
152    return engine->peerIDs;
153}
154
155SOSManifestRef SOSEngineGetManifestForDigest(SOSEngineRef engine, CFDataRef digest) {
156    if (!engine->manifestCache || !digest) return NULL;
157    SOSManifestRef manifest = (SOSManifestRef)CFDictionaryGetValue(engine->manifestCache, digest);
158    if (!manifest) return NULL;
159    if (CFGetTypeID(manifest) != SOSManifestGetTypeID()) {
160        secerror("dropping corrupt manifest for %@ from cache", digest);
161        CFDictionaryRemoveValue(engine->manifestCache, digest);
162        return NULL;
163    }
164
165    return manifest;
166}
167
168void SOSEngineAddManifest(SOSEngineRef engine, SOSManifestRef manifest) {
169    CFDataRef digest = SOSManifestGetDigest(manifest, NULL);
170    if (digest) {
171        if (!engine->manifestCache)
172            engine->manifestCache = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
173        CFDictionaryAddValue(engine->manifestCache, digest, manifest);
174    }
175}
176
177CFDataRef SOSEnginePatchRecordAndCopyDigest(SOSEngineRef engine, SOSManifestRef base, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error) {
178    CFDataRef digest = NULL;
179    SOSManifestRef manifest = SOSManifestCreateWithPatch(base, removals, additions, error);
180    if (manifest) {
181        SOSEngineAddManifest(engine, manifest);
182        digest = CFRetainSafe(SOSManifestGetDigest(manifest, NULL));
183    }
184    CFReleaseSafe(manifest);
185    return digest;
186}
187
188static bool SOSEngineHandleManifestUpdates(SOSEngineRef engine, SOSDataSourceTransactionSource source, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error) {
189    __block struct SOSDigestVector mdInCache = SOSDigestVectorInit;
190    struct SOSDigestVector mdInUse = SOSDigestVectorInit;
191    struct SOSDigestVector mdUnused = SOSDigestVectorInit;
192    struct SOSDigestVector mdMissing = SOSDigestVectorInit;
193    CFStringRef peerID = NULL;
194    bool ok = true;
195
196    require_quiet(engine->peerState, exit); // Not a failure no work to do
197
198    if(engine->peerIDs){
199        CFArrayForEachC(engine->peerIDs, peerID) {
200            SOSPeerRef peer = SOSPeerCreateWithEngine(engine, peerID);
201            if (removals || additions)
202                ok &= SOSPeerDataSourceWillCommit(peer, source, removals, additions, error);
203            SOSPeerMarkDigestsInUse(peer, &mdInUse);
204            CFReleaseSafe(peer);
205        }
206    }
207    if(engine->manifestCache){
208        CFDictionaryForEach(engine->manifestCache, ^(const void *key, const void *value) {
209            CFDataRef digest = (CFDataRef)key;
210            if (isData(digest))
211                SOSDigestVectorAppend(&mdInCache, CFDataGetBytePtr(digest));
212        });
213
214        // Delete unused manifests.
215        SOSDigestVectorDiff(&mdInCache, &mdInUse, &mdUnused, &mdMissing);
216        SOSManifestRef unused = SOSManifestCreateWithDigestVector(&mdUnused, NULL);
217        SOSManifestForEach(unused, ^(CFDataRef digest, bool *stop) {
218            if (digest)
219                CFDictionaryRemoveValue(engine->manifestCache, digest);
220        });
221        CFReleaseSafe(unused);
222    }
223    // Delete unused peerState
224    if (engine->peerState && engine->peerIDs) {
225        CFMutableDictionaryRef newPeerState = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
226        CFArrayForEachC(engine->peerIDs, peerID) {
227            CFTypeRef value = CFDictionaryGetValue(engine->peerState, peerID);
228            if (value)
229                CFDictionarySetValue(newPeerState, peerID, value);
230        }
231        CFDictionaryForEach(engine->peerState, ^(const void *key, const void *value) {
232            if(isDictionary(value) && !CFDictionaryContainsKey(newPeerState, key)){
233                CFMutableDictionaryRef untrustedStuff = (CFMutableDictionaryRef)value;
234                CFDataRef untrustedCoder = (CFDataRef)CFDictionaryGetValue(untrustedStuff, kSOSPeerCoderKey);
235                if(untrustedCoder){
236                    CFMutableDictionaryRef untrustedDict = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, kSOSPeerCoderKey, untrustedCoder, NULL);
237                    CFDictionarySetValue(newPeerState, key, untrustedDict);
238                    CFReleaseNull(untrustedDict);
239                }
240            }
241        });
242        CFReleaseSafe(engine->peerState);
243        engine->peerState = newPeerState;
244    }
245
246exit:
247    SOSDigestVectorFree(&mdInCache);
248    SOSDigestVectorFree(&mdInUse);
249    SOSDigestVectorFree(&mdUnused);
250    SOSDigestVectorFree(&mdMissing);
251    return ok;
252}
253
254static CFDataRef SOSEngineCopyState(SOSEngineRef engine, CFErrorRef *error) {
255    CFDataRef der = NULL;
256    CFMutableDictionaryRef state = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
257    if (engine->myID) CFDictionarySetValue(state, kSOSEngineIDKey, engine->myID);
258    if (engine->peerIDs) CFDictionarySetValue(state, kSOSEnginePeerIDsKey, engine->peerIDs);
259    if (engine->peerState) CFDictionarySetValue(state, kSOSEnginePeerStateKey, engine->peerState);
260    if (engine->manifestCache) {
261        CFMutableDictionaryRef mfc = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
262        CFDictionarySetValue(state, kSOSEngineManifestCacheKey, mfc);
263        CFDictionaryForEach(engine->manifestCache, ^(const void *key, const void *value) {
264            SOSManifestRef mf = (SOSManifestRef)value;
265            if (mf && (CFGetTypeID(mf) == SOSManifestGetTypeID()))
266                CFDictionarySetValue(mfc, key, SOSManifestGetData(mf));
267        });
268        CFReleaseSafe(mfc);
269    }
270    der = kc_plist_copy_der(state, error);
271    CFReleaseSafe(state);
272    secnotice("engine", "%@", engine);
273    return der;
274}
275
276static bool SOSEngineSave(SOSEngineRef engine, SOSTransactionRef txn, CFErrorRef *error) {
277    CFDataRef derState = SOSEngineCopyState(engine, error);
278    bool ok = derState && SOSDataSourceSetStateWithKey(engine->dataSource, txn, kSOSEngineState, kSecAttrAccessibleAlways, derState, error);
279    CFReleaseSafe(derState);
280    return ok;
281}
282
283static bool SOSEngineUpdateLocalManifest_locked(SOSEngineRef engine, SOSDataSourceTransactionSource source, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error) {
284    bool ok = true;
285    if (engine->manifest) {
286        SOSManifestRef updatedManifest = SOSManifestCreateWithPatch(engine->manifest, removals, additions, error);
287        if (updatedManifest)
288            CFAssignRetained(engine->manifest, updatedManifest);
289
290        // Update Peer Manifests. -- Shouldn't this be deferred until we apply our toAdd and toDel to the local manifest?
291        ok &= SOSEngineHandleManifestUpdates(engine, source, removals, additions, error);
292    }
293    return ok;
294}
295
296static bool SOSEngineUpdateChanges(SOSEngineRef engine, SOSTransactionRef txn, SOSDataSourceTransactionPhase phase, SOSDataSourceTransactionSource source, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error)
297{
298    secnotice("engine", "%s %s dels:%@ adds:%@", phase == kSOSDataSourceTransactionWillCommit ? "will-commit" : phase == kSOSDataSourceTransactionDidCommit ? "did-commit" : "did-rollback", source == kSOSDataSourceSOSTransaction ? "sos" : "api", removals, additions);
299    bool ok = true;
300    switch (phase) {
301        case kSOSDataSourceTransactionDidRollback:
302            ok &= SOSEngineLoad(engine, error);
303            break;
304        case kSOSDataSourceTransactionWillCommit: {
305            ok &= SOSEngineUpdateLocalManifest_locked(engine, source, removals, additions, error);
306            // Write SOSEngine and SOSPeer state to disk if dirty
307            ok &= SOSEngineSave(engine, txn, error);
308            break;
309        }
310        case kSOSDataSourceTransactionDidCommit:
311            break;
312    }
313    return ok;
314}
315
316static void SOSEngineSetTrustedPeers(SOSEngineRef engine, CFStringRef myPeerID, CFArrayRef trustedPeers) {
317    const bool wasInCircle = engine->myID;
318    const bool isInCircle = myPeerID;
319    const bool inCircleChanged = wasInCircle != isInCircle;
320
321    CFStringRef peerID = NULL;
322    CFRetainAssign(engine->myID, myPeerID);
323
324    if(trustedPeers != NULL && CFArrayGetCount(trustedPeers) != 0){
325        CFReleaseNull(engine->peerIDs);
326        engine->peerIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
327        CFArrayForEachC(trustedPeers, peerID){
328            CFArrayAppendValue((CFMutableArrayRef)engine->peerIDs, peerID);
329        };
330    }
331    else{
332        engine->peerIDs = NULL;
333    }
334    // If we entered a circle of more than 2 or our last peer left we need to do stuff
335    if (inCircleChanged) {
336        if (isInCircle) {
337            CFErrorRef dsError = NULL;
338            if (!(engine->manifest = SOSDataSourceCopyManifest(engine->dataSource, &dsError))) {
339                secerror("failed to load manifest from datasource: %@", dsError);
340                CFReleaseNull(dsError);
341            }
342            SOSDataSourceSetNotifyPhaseBlock(engine->dataSource, ^(SOSDataSourceRef ds, SOSTransactionRef txn, SOSDataSourceTransactionPhase phase, SOSDataSourceTransactionSource source, struct SOSDigestVector *removals, struct SOSDigestVector *additions) {
343                SOSManifestRef mfdel = SOSManifestCreateWithDigestVector(removals, NULL);
344                SOSManifestRef mfadd = SOSManifestCreateWithDigestVector(additions, NULL);
345                dispatch_block_t processUpdates = ^{
346                    CFErrorRef localError = NULL;
347                    if (!SOSEngineUpdateChanges(engine, txn, phase, source, mfdel, mfadd, &localError)) {
348                        secerror("updateChanged failed: %@", localError);
349                    }
350                    CFReleaseSafe(localError);
351                    CFReleaseSafe(mfdel);
352                    CFReleaseSafe(mfadd);
353                };
354
355                if (source == kSOSDataSourceSOSTransaction) {
356                    processUpdates();
357                } else {
358                    // WARNING: This will deadlock the engine if you call a
359                    // SecItem API function while holding the engine lock!
360                    // However making this async right now isn't safe yet either
361                    // Due to some code in the enginer using Get v/s copy to
362                    // access some of the values that would be modified
363                    // asynchronously here since the engine is coded as if
364                    // running on a serial queue.
365                    dispatch_sync(engine->queue, processUpdates);
366                }
367            });
368        } else {
369            SOSDataSourceSetNotifyPhaseBlock(engine->dataSource, ^(SOSDataSourceRef ds, SOSTransactionRef txn, SOSDataSourceTransactionPhase phase, SOSDataSourceTransactionSource source, struct SOSDigestVector *removals, struct SOSDigestVector *additions) {
370                secnoticeq("engine", "No peers to notify");     // TODO: DEBUG - remove this
371            });
372            CFReleaseNull(engine->manifest);
373        }
374    }
375}
376
377static bool SOSEngineSetState(SOSEngineRef engine, CFDataRef state, CFErrorRef *error) {
378    bool ok = true;
379    if (state) {
380        CFMutableDictionaryRef dict = NULL;
381        const uint8_t *der = CFDataGetBytePtr(state);
382        const uint8_t *der_end = der + CFDataGetLength(state);
383        der = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef *)&dict, error, der, der_end);
384        if (der && der != der_end) {
385            ok = SOSErrorCreate(kSOSErrorDecodeFailure, error, NULL, CFSTR("trailing %td bytes at end of state"), der_end - der);
386        }
387        if (ok) {
388            SOSEngineSetTrustedPeers(engine, (CFStringRef)CFDictionaryGetValue(dict, kSOSEngineIDKey),
389                                     (CFArrayRef)CFDictionaryGetValue(dict, kSOSEnginePeerIDsKey));
390            CFRetainAssign(engine->peerState, (CFMutableDictionaryRef)CFDictionaryGetValue(dict, kSOSEnginePeerStateKey));
391
392            CFReleaseNull(engine->manifestCache);
393            CFMutableDictionaryRef mfc = (CFMutableDictionaryRef)CFDictionaryGetValue(dict, kSOSEngineManifestCacheKey);
394            if (mfc) {
395                engine->manifestCache = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
396                CFDictionaryForEach(mfc, ^(const void *key, const void *value) {
397                    CFDataRef data = (CFDataRef)value;
398                    if (isData(data)) {
399                        SOSManifestRef mf = SOSManifestCreateWithData(data, NULL);
400                        if (mf)
401                            CFDictionarySetValue(engine->manifestCache, key, mf);
402                        CFReleaseSafe(mf);
403                    }
404                });
405            }
406        }
407        CFReleaseNull(dict);
408    }
409    secnotice("engine", "%@", engine);
410    return ok;
411}
412
413static bool SOSEngineLoad(SOSEngineRef engine, CFErrorRef *error) {
414    CFDataRef state = SOSDataSourceCopyStateWithKey(engine->dataSource, kSOSEngineState, kSecAttrAccessibleAlways, error);
415    bool ok = state && SOSEngineSetState(engine, state, error);
416    CFReleaseSafe(state);
417    return ok;
418}
419
420static void CFArraySubtract(CFMutableArrayRef from, CFArrayRef remove) {
421    if (remove) {
422        CFArrayForEach(remove, ^(const void *value) {
423            CFArrayRemoveAllValue(from, value);
424        });
425    }
426}
427
428static CFMutableArrayRef CFArrayCreateDifference(CFAllocatorRef alloc, CFArrayRef set, CFArrayRef remove) {
429    CFMutableArrayRef result;
430    if (!set) {
431        result = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
432    } else {
433        result = CFArrayCreateMutableCopy(alloc, 0, set);
434
435        if (remove)
436            CFArraySubtract(result, remove);
437    }
438
439    return result;
440}
441
442void SOSEngineCircleChanged_locked(SOSEngineRef engine, CFStringRef myPeerID, CFArrayRef trustedPeers, CFArrayRef untrustedPeers) {
443    CFMutableArrayRef addedPeers = CFArrayCreateDifference(kCFAllocatorDefault, trustedPeers, engine->peerIDs);
444    CFMutableArrayRef deletedPeers = CFArrayCreateDifference(kCFAllocatorDefault, engine->peerIDs, trustedPeers);
445
446    CFStringRef tpDesc = SOSPeerIDArrayCreateString(trustedPeers);
447    CFStringRef apDesc = SOSPeerIDArrayCreateString(addedPeers);
448    CFStringRef dpDesc = SOSPeerIDArrayCreateString(deletedPeers);
449    secnotice("engine", "trusted %@ added %@ removed %@", tpDesc, apDesc, dpDesc);
450    CFReleaseSafe(dpDesc);
451    CFReleaseSafe(apDesc);
452    CFReleaseSafe(tpDesc);
453
454    SOSEngineSetTrustedPeers(engine, myPeerID, trustedPeers);
455
456    // Remove any cached state for peers we no longer use but keep coders alive
457    if (deletedPeers && CFArrayGetCount(deletedPeers) && engine->peerState) {
458        CFStringRef peerID = NULL;
459        CFArrayForEachC(deletedPeers, peerID) {
460            CFMutableDictionaryRef peer_data = (CFMutableDictionaryRef) CFDictionaryGetValue(engine->peerState, peerID);
461            CFDataRef coder_data = isDictionary(peer_data) ? (CFDataRef) CFDictionaryGetValue(peer_data, kSOSPeerCoderKey) : NULL;
462
463            if(isData(coder_data) &&
464               untrustedPeers && CFArrayContainsValue(untrustedPeers, CFRangeMake(0, CFArrayGetCount(untrustedPeers)), peerID)) {
465                CFRetainSafe(coder_data);
466                CFDictionaryRemoveAllValues(peer_data);
467                CFDictionaryAddValue(peer_data, kSOSPeerCoderKey, coder_data);
468                CFReleaseSafe(coder_data);
469            } else {
470                CFDictionaryRemoveValue(engine->peerState, peerID);
471            }
472        }
473        // Run though all peers and only cache manifests for peers we still have
474        // TODO: Factor out gc from SOSEngineHandleManifestUpdates and just call that
475        SOSEngineHandleManifestUpdates(engine, kSOSDataSourceSOSTransaction, NULL, NULL, NULL);
476    }
477
478    CFReleaseNull(addedPeers);
479    CFReleaseNull(deletedPeers);
480
481}
482
483#if 0
484static SOSManifestRef SOSEngineCopyCleanManifest(SOSEngineRef engine, CFErrorRef *error) {
485    SOSManifestRef localMinusUnreadable;
486    }
487#endif
488
489// Initialize the engine if a load fails.  Basically this is our first time setup
490static bool SOSEngineInit(SOSEngineRef engine, CFErrorRef *error) {
491    bool ok = true;
492    secnotice("engine", "new engine for datasource named %@", SOSDataSourceGetName(engine->dataSource));
493    return ok;
494}
495
496// Called by our DataSource in its constructor
497SOSEngineRef SOSEngineCreate(SOSDataSourceRef dataSource, CFErrorRef *error) {
498    SOSEngineRef engine = NULL;
499    engine = CFTypeAllocate(SOSEngine, struct __OpaqueSOSEngine, kCFAllocatorDefault);
500    engine->dataSource = dataSource;
501    engine->queue = dispatch_queue_create("engine", DISPATCH_QUEUE_SERIAL);
502    CFErrorRef engineError = NULL;
503    if (!SOSEngineLoad(engine, &engineError)) {
504        secwarning("engine failed load state starting with nothing %@", engineError);
505        CFReleaseNull(engineError);
506        if (!SOSEngineInit(engine, error)) {
507            secerror("engine failed to initialze %@ giving up", engineError);
508        }
509    }
510    return engine;
511}
512
513
514//
515// MARK: SOSEngine API
516//
517
518void SOSEngineDispose(SOSEngineRef engine) {
519    // NOOP Engines stick around forever to monitor dataSource changes.
520}
521
522static SOSManifestRef SOSEngineCopyManifest_locked(SOSEngineRef engine, CFErrorRef *error) {
523    return CFRetainSafe(engine->manifest);
524}
525
526/* Handle incoming message from peer p.  Return false if there was an error, true otherwise. */
527static bool SOSEngineHandleMessage_locked(SOSEngineRef engine, CFStringRef peerID, SOSMessageRef message,
528                                          SOSTransactionRef txn, bool *commit, bool *somethingChanged, CFErrorRef *error) {
529    SOSPeerRef peer = SOSPeerCreateWithEngine(engine, peerID);
530    CFStringRef peerDesc = NULL;
531    SOSManifestRef localManifest = NULL;
532    SOSManifestRef allAdditions = NULL;
533    SOSManifestRef confirmed = NULL;
534    SOSManifestRef base = NULL;
535    SOSManifestRef confirmedRemovals = NULL, confirmedAdditions = NULL;
536    __block struct SOSDigestVector receivedObjects = SOSDigestVectorInit;
537
538    // Check for unknown criticial extensions in the message, and handle
539    // any other extensions we support
540    __block bool ok = true;
541    __block struct SOSDigestVector dvadd = SOSDigestVectorInit;
542
543    require_action_quiet(peer, exit, ok = SOSErrorCreate(errSecParam, error, NULL, CFSTR("Couldn't create peer with Engine for %@"), peerID));
544    peerDesc = CFCopyDescription(peer);
545
546    SOSMessageWithExtensions(message, true, ^(CFDataRef oid, bool isCritical, CFDataRef extension, bool *stop) {
547        // OMFG a Critical extension what shall I do!
548        ok = SOSErrorCreate(kSOSErrorNotReady, error, NULL, CFSTR("Unknown criticial extension in peer message"));
549        *stop = true;
550    });
551    require_quiet(ok, exit);
552
553    // Merge Objects from the message into our DataSource.
554    // Should we move the transaction to the SOSAccount level?
555    require_quiet(ok &= SOSMessageWithSOSObjects(message, engine->dataSource, error, ^(SOSObjectRef peersObject, bool *stop) {
556        CFDataRef digest = SOSObjectCopyDigest(engine->dataSource, peersObject, error);
557        if (!digest) {
558            *stop = true;
559            *commit = false;
560            secerror("%@ peer sent bad object: %@, rolling back changes", SOSPeerGetID(peer), error ? *error : NULL);
561            return;
562        }
563        SOSDigestVectorAppend(&receivedObjects, CFDataGetBytePtr(digest));
564        SOSMergeResult mr = SOSDataSourceMergeObject(engine->dataSource, txn, peersObject, NULL, error);
565        // TODO: If the mr is kSOSMergeLocalObject most of the time (or all of the time),
566        // consider asking the peer to stop sending us objects, and send it objects instead.
567        ok &= (mr != kSOSMergeFailure);
568        if (!ok) {
569            *stop = true;
570            *commit = false;
571            // TODO: Might want to change to warning since the race of us locking after ckd sends us a message could cause db locked errors here.
572            secerror("%@ SOSDataSourceMergeObject failed %@ rolling back changes", SOSPeerGetID(peer), error ? *error : NULL);
573        } else if (mr==kSOSMergePeersObject || mr==kSOSMergeCreatedObject) {
574            *somethingChanged = true;
575        } else {
576            // mr == kSOSMergeLocalObject
577            // Ensure localObject is in local manifest (possible corruption) by posting an update when we are done.
578            SOSDigestVectorAppend(&dvadd, CFDataGetBytePtr(digest));
579        }
580        CFReleaseSafe(digest);
581    }), exit);
582    struct SOSDigestVector dvunion = SOSDigestVectorInit;
583    SOSDigestVectorSort(&receivedObjects);
584    SOSDigestVectorUnionSorted(SOSManifestGetDigestVector(SOSMessageGetAdditions(message)), &receivedObjects, &dvunion);
585    allAdditions = SOSManifestCreateWithDigestVector(&dvunion, error);
586    SOSDigestVectorFree(&receivedObjects);
587    SOSDigestVectorFree(&dvunion);
588
589    if (dvadd.count) {
590        // Ensure any objects that we received and have localally already are actually in our local manifest
591        SOSManifestRef mfadd = SOSManifestCreateWithDigestVector(&dvadd, error);
592        SOSDigestVectorFree(&dvadd);
593        SOSEngineUpdateLocalManifest_locked(engine, kSOSDataSourceSOSTransaction, NULL, mfadd, error);
594        CFReleaseSafe(mfadd);
595    }
596
597    // ---- Don't use local or peer manifests from above this line, since commiting the SOSDataSourceWith transaction might change them ---
598
599    // Take a snapshot of our dataSource's local manifest.
600    require_quiet(ok = localManifest = SOSEngineCopyManifest_locked(engine, error), exit);
601
602    CFDataRef baseDigest = SOSMessageGetBaseDigest(message);
603    CFDataRef proposedDigest = SOSMessageGetProposedDigest(message);
604
605#if 0
606    // I believe this is no longer needed now that we have eliminated extra,
607    // Since this is handeled below once we get a confirmed manifest from our
608    // peer.
609
610    // If we just received a L00 reset pendingObjects to localManifest
611    if (!baseDigest && !proposedDigest) {
612        SOSPeerSetPendingObjects(peer, localManifest);
613        secnotice("engine", "SOSPeerSetPendingObjects: %@", localManifest);
614    }
615#endif
616
617    base = CFRetainSafe(SOSEngineGetManifestForDigest(engine, baseDigest));
618    confirmed = CFRetainSafe(SOSEngineGetManifestForDigest(engine, SOSMessageGetSenderDigest(message)));
619    if (!confirmed) {
620        if (SOSManifestGetCount(SOSMessageGetRemovals(message)) || SOSManifestGetCount(allAdditions)) {
621            confirmed = SOSManifestCreateWithPatch(base, SOSMessageGetRemovals(message), allAdditions, error);
622            if (!confirmed) {
623                confirmedRemovals = CFRetainSafe(SOSMessageGetRemovals(message));
624                confirmedAdditions = CFRetainSafe(allAdditions);
625            }
626        } else if (baseDigest) {
627            confirmed = CFRetainSafe(base);
628            secerror("Protocol error send L00 - figure out later base: %@", base);
629        }
630    }
631    secnotice("engine", "Confirmed: %@ base: %@", confirmed, base);
632    if (confirmed)
633        ok &= SOSManifestDiff(SOSPeerGetConfirmedManifest(peer), confirmed, &confirmedRemovals, &confirmedAdditions, error);
634    if (confirmedRemovals || confirmedAdditions)
635        ok &= SOSPeerDidReceiveRemovalsAndAdditions(peer, confirmedRemovals, confirmedAdditions, localManifest, error);
636    SOSPeerSetConfirmedManifest(peer, confirmed);
637
638    // ---- SendObjects and extra->pendingObjects promotion dance ----
639
640    // The first block of code below sets peer.sendObjects to true when we receive a L00 and the second block
641    // moves extra to pendingObjects once we receive a confirmed manifest in or after the L00.
642    if (!baseDigest && !proposedDigest) {
643        SOSPeerSetSendObjects(peer, true);
644    }
645
646    // TODO: should this not depend on SOSPeerSendObjects?:
647    if (confirmed /* && SOSPeerSendObjects(peer)*/) {
648        SOSManifestRef allExtra = NULL;
649        ok &= SOSManifestDiff(confirmed, localManifest, NULL, &allExtra, error);
650        secnotice("engine", "%@ confirmed %@ setting O:%@", SOSPeerGetID(peer), confirmed, allExtra);
651        SOSPeerSetPendingObjects(peer, allExtra);
652        CFReleaseSafe(allExtra);
653    }
654
655exit:
656    secnoticeq("engine", "recv %@ %@", SOSPeerGetID(peer), message);
657    secnoticeq("peer", "recv %@ -> %@", peerDesc, peer);
658
659    CFReleaseNull(base);
660    CFReleaseSafe(confirmed);
661    CFReleaseSafe(localManifest);
662    CFReleaseSafe(peerDesc);
663    CFReleaseSafe(allAdditions);
664    CFReleaseSafe(confirmedRemovals);
665    CFReleaseSafe(confirmedAdditions);
666    CFReleaseSafe(peer);
667    return ok;
668}
669
670static CFDataRef SOSEngineCopyObjectDER(SOSEngineRef engine, SOSObjectRef object, CFErrorRef *error) {
671    CFDataRef der = NULL;
672    CFDictionaryRef plist = SOSObjectCopyPropertyList(engine->dataSource, object, error);
673    if (plist) {
674        der = kc_plist_copy_der(plist, error);
675        CFRelease(plist);
676                            }
677    return der;
678                        }
679
680static CFDataRef SOSEngineCreateMessage_locked(SOSEngineRef engine, SOSPeerRef peer,
681                                               CFErrorRef *error, SOSEnginePeerMessageSentBlock *sent) {
682    SOSManifestRef local = SOSEngineCopyManifest_locked(engine, error);
683    __block SOSMessageRef message = SOSMessageCreate(kCFAllocatorDefault, SOSPeerGetMessageVersion(peer), error);
684    SOSManifestRef confirmed = SOSPeerGetConfirmedManifest(peer);
685    SOSManifestRef pendingObjects = SOSPeerGetPendingObjects(peer);
686    SOSManifestRef objectsSent = NULL;
687    SOSManifestRef proposed = NULL;
688    SOSManifestRef allMissing = NULL;
689    SOSManifestRef allExtra = NULL;
690    SOSManifestRef extra = NULL;
691    SOSManifestRef excessPending = NULL;
692    SOSManifestRef missing = NULL;
693    SOSManifestRef deleted = SOSPeerGetPendingDeletes(peer);
694    SOSManifestRef excessDeleted = NULL;
695    CFDataRef result = NULL;
696    bool ok;
697
698    ok = SOSManifestDiff(confirmed, local, &allMissing, &allExtra, error);
699    ok = ok && SOSManifestDiff(allExtra, pendingObjects, &extra, &excessPending, error);
700    if (SOSManifestGetCount(excessPending)) {
701        secerror("%@ ASSERTION FAILURE excess pendingObjects: %@", peer, excessPending);
702        // Remove excessPending from pendingObjects since they are either
703        // already in confirmed or not in local, either way there is no point
704        // keeping them in pendingObjects.
705
706        pendingObjects = SOSManifestCreateComplement(excessPending, pendingObjects, error);
707        SOSPeerSetPendingObjects(peer, pendingObjects);
708        CFReleaseSafe(pendingObjects);
709        ok = false;
710    }
711    ok = ok && SOSManifestDiff(allMissing, deleted, &missing, &excessDeleted, error);
712    if (SOSManifestGetCount(excessDeleted)) {
713        secerror("%@ ASSERTION FAILURE excess deleted: %@", peer, excessDeleted);
714        ok = false;
715    }
716    (void)ok;   // Dead store
717    CFReleaseNull(allExtra);
718    CFReleaseNull(excessPending);
719    CFReleaseNull(allMissing);
720    CFReleaseNull(excessDeleted);
721
722    // Send state for peer 7T0M+TD+A7HZ0frC5oHZnmdR0G: [LCP][os] P: 0, E: 0, M: 0
723    secnoticeq("engine", "Send state for peer %@: [%s%s%s][%s%s] P: %zu, E: %zu, M: %zu", SOSPeerGetID(peer),
724               local ? "L":"l",
725               confirmed ? "C":"0",
726               pendingObjects ? "P":"0",
727               SOSPeerSendObjects(peer) ? "O":"o",
728               SOSPeerMustSendMessage(peer) ? "S":"s",
729               SOSManifestGetCount(pendingObjects),
730               SOSManifestGetCount(extra),
731               SOSManifestGetCount(missing)
732               );
733
734    if (confirmed) {
735        // TODO: Because of not letting things terminate while we have extra left
736        // we might send objects when we didn't need to, but there is always an
737        // extra roundtrip required for objects that we assume the other peer
738        // should have already.
739        // TODO: If there are extra objects left, calling this function is not
740        // idempotent we should check if pending is what we are about to send and not send anything in this case.
741        if (SOSManifestGetCount(pendingObjects) == 0 && SOSManifestGetCount(extra) == 0)
742            SOSPeerSetSendObjects(peer, false);
743
744        if (CFEqualSafe(local, SOSPeerGetProposedManifest(peer)) && !SOSPeerMustSendMessage(peer)) {
745            bool send = false;
746            if (CFEqual(confirmed, local)) {
747                secnoticeq("engine", "synced <No MSG> %@",  peer);
748            } else if (SOSManifestGetCount(pendingObjects) == 0 /* TODO: No entries moved from extra to pendingObjects. */
749                && SOSManifestGetCount(missing) == 0) {
750                secnoticeq("engine", "waiting <MSG not resent> %@", peer);
751            } else {
752                send = true;
753            }
754            if (!send) {
755                CFReleaseSafe(local);
756                CFReleaseSafe(message);
757                CFReleaseNull(extra);
758                CFReleaseNull(missing);
759                return CFDataCreate(kCFAllocatorDefault, NULL, 0);
760            }
761        }
762
763        if (SOSManifestGetCount(pendingObjects)) {
764            // If we have additions and we need to send objects send them.
765            __block size_t objectsSize = 0;
766            __block struct SOSDigestVector dv = SOSDigestVectorInit;
767            __block struct SOSDigestVector dvdel = SOSDigestVectorInit;
768            if (!SOSDataSourceForEachObject(engine->dataSource, pendingObjects, error, ^void(CFDataRef key, SOSObjectRef object, bool *stop) {
769                CFErrorRef localError = NULL;
770                CFDataRef digest = NULL;
771                CFDataRef der = NULL;
772                if (!object) {
773                    const uint8_t *d = CFDataGetBytePtr(key);
774                    secerrorq("%@ object %02X%02X%02X%02X dropping from manifest: not found in datasource",
775                               SOSPeerGetID(peer), d[0], d[1], d[2], d[3]);
776                    SOSDigestVectorAppend(&dvdel, CFDataGetBytePtr(key));
777                } else if (!(der = SOSEngineCopyObjectDER(engine, object, &localError))
778                           || !(digest = SOSObjectCopyDigest(engine->dataSource, object, &localError))) {
779                    if (SecErrorGetOSStatus(localError) == errSecDecode) {
780                        // Decode error, we need to drop these objects from our manifests
781                        const uint8_t *d = CFDataGetBytePtr(key);
782                        secerrorq("%@ object %02X%02X%02X%02X dropping from manifest: %@",
783                            SOSPeerGetID(peer), d[0], d[1], d[2], d[3], localError);
784                        SOSDigestVectorAppend(&dvdel, CFDataGetBytePtr(key));
785                        CFRelease(localError);
786                    } else {
787                        // Stop iterating and propagate out all other errors.
788                        const uint8_t *d = CFDataGetBytePtr(key);
789                        secwarning("%@ object %02X%02X%02X%02X in SOSDataSourceForEachObject: %@",
790                            SOSPeerGetID(peer), d[0], d[1], d[2], d[3], localError);
791                        *stop = true;
792                        CFErrorPropagate(localError, error);
793                        CFReleaseNull(message);
794                    }
795                } else {
796                    if (!CFEqual(key, digest)) {
797                        const uint8_t *d = CFDataGetBytePtr(key);
798                        const uint8_t *e = CFDataGetBytePtr(digest);
799                        secwarning("@ object %02X%02X%02X%02X is really %02X%02X%02X%02X dropping from local manifest", d[0], d[1], d[2], d[3], e[0], e[1], e[2], e[3]);
800                        SOSDigestVectorAppend(&dvdel, CFDataGetBytePtr(key));
801                    }
802
803                    size_t objectLen = (size_t)CFDataGetLength(der);
804                    if (SOSMessageAppendObject(message, der, &localError)) {
805                        SOSDigestVectorAppend(&dv, CFDataGetBytePtr(digest));
806                    } else {
807                        const uint8_t *d = CFDataGetBytePtr(digest);
808                        CFStringRef hexder = CFDataCopyHexString(der);
809                        secerrorq("%@ object %02X%02X%02X%02X der: %@ dropping from manifest: %@",
810                                  SOSPeerGetID(peer), d[0], d[1], d[2], d[3], hexder, localError);
811                        CFReleaseNull(hexder);
812                        CFReleaseNull(message);
813                        // Since we can't send these objects let's assume they are bad too?
814                        SOSDigestVectorAppend(&dvdel, CFDataGetBytePtr(digest));
815                    }
816                    objectsSize += objectLen;
817                    if (objectsSize > kSOSMessageMaxObjectsSize)
818                        *stop = true;
819                }
820                CFReleaseSafe(der);
821                CFReleaseSafe(digest);
822            })) {
823                CFReleaseNull(message);
824            }
825            if (dv.count)
826                objectsSent = SOSManifestCreateWithDigestVector(&dv, error);
827            if (dvdel.count) {
828                CFErrorRef localError = NULL;
829                SOSManifestRef mfdel = SOSManifestCreateWithDigestVector(&dvdel, error);
830                SOSDigestVectorFree(&dvdel);
831                if (!SOSEngineUpdateLocalManifest_locked(engine, kSOSDataSourceSOSTransaction, mfdel, NULL, &localError))
832                    secerror("SOSEngineUpdateLocalManifest deleting: %@ failed: %@", mfdel, localError);
833                CFReleaseSafe(localError);
834                CFReleaseSafe(mfdel);
835                CFAssignRetained(local, SOSEngineCopyManifest_locked(engine, error));
836            }
837            SOSDigestVectorFree(&dv);
838        }
839    } else {
840        // If we have no confirmed manifest, we want all pendedObjects going out as a manifest
841        objectsSent = CFRetainSafe(pendingObjects);
842    }
843
844    if (confirmed || SOSManifestGetCount(missing) || SOSManifestGetCount(extra) || objectsSent) {
845        SOSManifestRef allExtra = SOSManifestCreateUnion(extra, objectsSent, error);
846        proposed = SOSManifestCreateWithPatch(confirmed, missing, allExtra, error);
847        CFReleaseNull(allExtra);
848    }
849
850    if (!SOSMessageSetManifests(message, local, confirmed, proposed, proposed, confirmed ? objectsSent : NULL, error))
851        CFReleaseNull(message);
852
853    CFReleaseNull(objectsSent);
854
855    if (message) {
856        result = SOSMessageCreateData(message, SOSPeerNextSequenceNumber(peer), error);
857    }
858
859    if (result) {
860        // Capture the peer in our block (SOSEnginePeerMessageSentBlock)
861        CFRetainSafe(peer);
862        *sent = Block_copy(^(bool success) {
863            dispatch_async(engine->queue, ^{
864            if (success) {
865                if (!confirmed && !proposed) {
866                    SOSPeerSetSendObjects(peer, true);
867                    secnotice("engine", "SOSPeerSetSendObjects(true) L:%@", local);
868                }
869                SOSPeerAddLocalManifest(peer, local);
870                SOSPeerAddProposedManifest(peer, proposed);
871                secnoticeq("engine", "send %@ %@", SOSPeerGetID(peer), message);
872            } else {
873                secerror("%@ failed to send %@", SOSPeerGetID(peer), message);
874            }
875            CFReleaseSafe(peer);
876            CFReleaseSafe(local);
877            CFReleaseSafe(proposed);
878            CFReleaseSafe(message);
879            });
880        });
881    } else {
882        CFReleaseSafe(local);
883        CFReleaseSafe(proposed);
884        CFReleaseSafe(message);
885    }
886    CFReleaseNull(extra);
887    CFReleaseNull(missing);
888    if (error && *error)
889        secerror("%@ error in send: %@", SOSPeerGetID(peer), *error);
890
891    return result;
892}
893
894static CFDataRef SOSEngineCreateMessageToSyncToPeer_locked(SOSEngineRef engine, CFStringRef peerID, SOSEnginePeerMessageSentBlock *sentBlock, CFErrorRef *error)
895{
896    SOSPeerRef peer = SOSPeerCreateWithEngine(engine, peerID);
897    CFDataRef message = SOSEngineCreateMessage_locked(engine, peer, error, sentBlock);
898    CFReleaseSafe(peer);
899
900    return message;
901}
902
903bool SOSEngineHandleMessage(SOSEngineRef engine, CFStringRef peerID,
904                            CFDataRef raw_message, CFErrorRef *error)
905{
906    __block bool result = false;
907    __block bool somethingChanged = false;
908    SOSMessageRef message = SOSMessageCreateWithData(kCFAllocatorDefault, raw_message, error);
909    result = message && SOSDataSourceWith(engine->dataSource, error, ^(SOSTransactionRef txn, bool *commit) {
910        result = SOSEngineHandleMessage_locked(engine, peerID, message, txn, commit, &somethingChanged, error);
911        });
912    CFReleaseSafe(message);
913    if (somethingChanged)
914        SecKeychainChanged(false);
915    return result;
916}
917
918// --- Called from off the queue, need to move to on the queue
919
920static void SOSEngineDoOnQueue(SOSEngineRef engine, dispatch_block_t action)
921{
922    dispatch_sync(engine->queue, action);
923}
924
925void SOSEngineCircleChanged(SOSEngineRef engine, CFStringRef myPeerID, CFArrayRef trustedPeers, CFArrayRef untrustedPeers) {
926    SOSEngineDoOnQueue(engine, ^{
927        SOSEngineCircleChanged_locked(engine, myPeerID, trustedPeers, untrustedPeers);
928    });
929
930    __block CFErrorRef localError = NULL;
931    SOSDataSourceWith(engine->dataSource, &localError, ^(SOSTransactionRef txn, bool *commit) {
932        SOSEngineDoOnQueue(engine, ^{
933            *commit = SOSEngineSave(engine, txn, &localError);
934        });
935    });
936    if (localError)
937        secerror("failed to save engine state: %@", localError);
938    CFReleaseSafe(localError);
939
940}
941
942SOSManifestRef SOSEngineCopyManifest(SOSEngineRef engine, CFErrorRef *error) {
943    __block SOSManifestRef result = NULL;
944    SOSEngineDoOnQueue(engine, ^{
945        result = SOSEngineCopyManifest_locked(engine, error);
946    });
947    return result;
948}
949
950bool SOSEngineUpdateLocalManifest(SOSEngineRef engine, SOSDataSourceTransactionSource source, struct SOSDigestVector *removals, struct SOSDigestVector *additions, CFErrorRef *error) {
951    __block bool result = true;
952    SOSManifestRef mfdel = SOSManifestCreateWithDigestVector(removals, error);
953    SOSManifestRef mfadd = SOSManifestCreateWithDigestVector(additions, error);
954    SOSEngineDoOnQueue(engine, ^{
955        // Safe to run async if needed...
956        result = SOSEngineUpdateLocalManifest_locked(engine, source, mfdel, mfadd, error);
957        CFReleaseSafe(mfdel);
958        CFReleaseSafe(mfadd);
959    });
960    return result;
961}
962
963static bool SOSEngineSetCoderData_locked(SOSEngineRef engine, CFStringRef peer_id, CFDataRef data, CFErrorRef *error) {
964    CFMutableDictionaryRef state = NULL;
965    if (data) {
966        if (!engine->peerState) {
967            engine->peerState = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
968            state = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
969        }
970        else{
971            state = (CFMutableDictionaryRef)CFDictionaryGetValue(engine->peerState, peer_id);
972            if(!state)
973                state = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
974        }
975        CFDictionarySetValue(state, kSOSPeerCoderKey, data);
976        CFDictionarySetValue(engine->peerState, peer_id, state);
977
978    }else if (engine->peerState) {
979        if(CFDictionaryContainsKey(engine->peerState, peer_id)){
980            CFMutableDictionaryRef state = (CFMutableDictionaryRef)CFDictionaryGetValue(engine->peerState, peer_id);
981            if(CFDictionaryContainsKey(state, kSOSPeerCoderKey))
982                CFDictionaryRemoveValue(state, kSOSPeerCoderKey);
983        }
984        if (CFDictionaryGetCount(engine->peerState) == 0) {
985            CFReleaseNull(engine->peerState);
986        }
987    }
988    return true;
989}
990
991bool SOSEngineSetCoderData(SOSEngineRef engine, CFStringRef peer_id, CFDataRef data, CFErrorRef *error) {
992    __block bool result = false;
993
994    SOSDataSourceWith(engine->dataSource, error, ^(SOSTransactionRef txn, bool *commit) {
995        dispatch_sync(engine->queue, ^{
996            result = SOSEngineSetCoderData_locked(engine, peer_id, data, error);
997        });
998    });
999
1000    return true;
1001}
1002
1003static CFDataRef SOSEngineGetCoderData_locked(SOSEngineRef engine, CFStringRef peer_id) {
1004    // TODO: probably remove these secnotices
1005    CFDataRef result = NULL;
1006    CFMutableDictionaryRef peerState = NULL;
1007
1008    if (!engine->peerState)
1009        secdebug("engine", "No engine coderData");
1010    else
1011        peerState = (CFMutableDictionaryRef)CFDictionaryGetValue(engine->peerState, peer_id);
1012    if (!peerState)
1013        secdebug("engine", "No peerState for peer %@", peer_id);
1014    else{
1015        result = CFDictionaryGetValue(peerState, kSOSPeerCoderKey);
1016        if(!result)
1017            secdebug("engine", "No coder data for peer %@", peer_id);
1018    }
1019    return result;
1020}
1021
1022
1023CFDataRef SOSEngineGetCoderData(SOSEngineRef engine, CFStringRef peer_id) {
1024    __block CFDataRef result = NULL;
1025    SOSDataSourceWith(engine->dataSource, NULL, ^(SOSTransactionRef txn, bool *commit) {
1026        dispatch_sync(engine->queue, ^{
1027            result = SOSEngineGetCoderData_locked(engine, peer_id);
1028        });
1029    });
1030
1031    return result;
1032}
1033
1034//
1035// Peer state layout.  WRONG! It's an array now
1036// The peer state is an array.
1037// The first element of the array is a dictionary with any number of keys and
1038// values in it (for future expansion) such as changing the digest size or type
1039// or remebering boolean flags for a peers sake.
1040// The next three are special in that they are manifest digests with special
1041// meaning and rules as to how they are treated (These are dynamically updated
1042// based on database activity so they have a fully history of all changes made
1043// to the local db. The first is the manifest representing the pendingObjects
1044// to send to the other peer.  This is normally only ever appending to, and in
1045// particular with transactions originating from the Keychain API that affect
1046// syncable items will need to add the new objects digests to the pendingObjects list
1047// while adding the digests of any tombstones encountered to the extra list.
1048
1049CFMutableDictionaryRef SOSEngineGetPeerState(SOSEngineRef engine, CFStringRef peerID) {
1050    CFMutableDictionaryRef peerState = NULL;
1051    if (engine->peerState)
1052        peerState = (CFMutableDictionaryRef)CFDictionaryGetValue(engine->peerState, peerID);
1053    else
1054        engine->peerState = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
1055    if (!peerState) {
1056        peerState = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
1057        CFDictionaryAddValue(engine->peerState, peerID, peerState);
1058        CFReleaseSafe(peerState);
1059    }
1060    return peerState;
1061}
1062
1063CFDataRef SOSEngineCreateMessageToSyncToPeer(SOSEngineRef engine, CFStringRef peerID, SOSEnginePeerMessageSentBlock *sentBlock, CFErrorRef *error) {
1064    __block CFDataRef result = NULL;
1065    SOSEngineDoOnQueue(engine, ^{
1066        result = SOSEngineCreateMessageToSyncToPeer_locked(engine, peerID, sentBlock, error);
1067    });
1068    return result;
1069}
1070
1071bool SOSEnginePeerDidConnect(SOSEngineRef engine, CFStringRef peerID, CFErrorRef *error) {
1072    __block bool result = true;
1073    result &= SOSDataSourceWith(engine->dataSource, error, ^(SOSTransactionRef txn, bool *commit) {
1074        dispatch_sync(engine->queue, ^{
1075            SOSPeerRef peer = SOSPeerCreateWithEngine(engine, peerID);
1076            if (!peer) {
1077                result = SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Engine has no peer for %@"), peerID);
1078            } else {
1079                SOSPeerDidConnect(peer);
1080                result = SOSEngineSave(engine, txn, error);
1081                CFReleaseSafe(peer);
1082            }
1083        });
1084    });
1085
1086    return result;
1087}
1088