2 * Copyright (c) 2012-2017 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 * SOSEngine.c - Implementation of a secure object syncing engine
29 #import "keychain/SecureObjectSync/SOSChangeTracker.h"
30 #include "keychain/SecureObjectSync/SOSEnginePriv.h"
31 #include "keychain/SecureObjectSync/SOSDigestVector.h"
32 #include "keychain/SecureObjectSync/SOSInternal.h"
33 #include "keychain/SecureObjectSync/SOSPeer.h"
34 #include <Security/SecureObjectSync/SOSViews.h>
35 #include "keychain/SecureObjectSync/SOSBackupEvent.h"
36 #include "keychain/SecureObjectSync/SOSPersist.h"
37 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
39 #include <corecrypto/ccder.h>
42 #include <utilities/array_size.h>
43 #include <utilities/SecCFCCWrappers.h>
44 #include <utilities/SecCFError.h>
45 #include <utilities/SecCFRelease.h>
46 #include <utilities/SecCFWrappers.h>
47 #include <utilities/der_plist.h>
48 #include <utilities/der_plist_internal.h>
49 #include <utilities/debugging.h>
50 #include <utilities/iCloudKeychainTrace.h>
51 #include <utilities/SecCoreCrypto.h>
52 #include <utilities/SecFileLocations.h>
53 #include <utilities/SecTrace.h>
54 #include "utilities/SecCoreAnalytics.h"
56 #include <AssertMacros.h>
57 #include <CoreFoundation/CoreFoundation.h>
59 #include "keychain/securityd/SecItemServer.h" // TODO: We can't leave this here.
60 #include "keychain/securityd/SOSCloudCircleServer.h" // TODO: We can't leave this here.
61 #include <Security/SecItem.h> // TODO: We can't leave this here.
62 #include <Security/SecItemPriv.h> // TODO: We can't leave this here.
63 #include "keychain/securityd/SecItemSchema.h"
64 #include "keychain/securityd/iCloudTrace.h"
66 #include <keychain/ckks/CKKS.h>
68 #include <CoreFoundation/CFURL.h>
70 #include "keychain/SecureObjectSync/SOSEnsureBackup.h"
73 // MARK: SOSEngine The Keychain database with syncable keychain support.
76 //----------------------------------------------------------------------------------------
77 // MARK: Engine state v0
78 //----------------------------------------------------------------------------------------
80 // Key in dataSource for general engine state file.
81 // This file only has digest entries in it, no manifests.
82 static const CFStringRef kSOSEngineState
= CFSTR("engine-state");
84 // Keys in state dictionary
85 static CFStringRef kSOSEngineManifestCacheKey
= CFSTR("manifestCache");
86 static CFStringRef kSOSEnginePeerStateKey
= CFSTR("peerState");
87 static CFStringRef kSOSEnginePeerIDsKey
= CFSTR("peerIDs");
88 static CFStringRef kSOSEngineIDKey
= CFSTR("id");
89 static CFStringRef kSOSEngineTraceDateKey
= CFSTR("traceDate");
91 //----------------------------------------------------------------------------------------
92 // MARK: Engine state v2
93 //----------------------------------------------------------------------------------------
95 #if !TARGET_OS_SIMULATOR
96 static const CFIndex kCurrentEngineVersion
= 2;
98 // Keychain/datasource items
99 // Used for the kSecAttrAccount when saving in the datasource with dsSetStateWithKey
100 // Class D [kSecAttrAccessibleAlwaysPrivate/kSecAttrAccessibleAlwaysThisDeviceOnly]
101 CFStringRef kSOSEngineStatev2
= CFSTR("engine-state-v2");
102 CFStringRef kSOSEnginePeerStates
= CFSTR("engine-peer-states");
103 CFStringRef kSOSEngineManifestCache
= CFSTR("engine-manifest-cache");
104 CFStringRef kSOSEngineCoders
= CFSTR("engine-coders");
105 #define kSOSEngineProtectionDomainClassA kSecAttrAccessibleWhenUnlockedThisDeviceOnly
107 // Keys for individual dictionaries
109 CFStringRef kSOSEngineStateVersionKey
= CFSTR("engine-stateVersion");
111 // Current save/load routines
112 // SOSEngineCreate/SOSEngineLoad/SOSEngineSetState
113 // SOSEngineSave/SOSEngineDoSave/SOSEngineCopyState
114 // no save/load functions external to this file
117 Divide engine state into five pieces:
119 - General engine state
121 - List of other (trusted) peer IDs
123 - Coder data (formerly in peer state)
124 - Backup Keybags (backup peers only)
125 - Peer state (including manifest hashes -- just keys into ManifestCache)
126 [__OpaqueSOSPeer/SOSPeerRef]
139 - local manifest hashes (copy of local keychain)
140 - peer manifest hashes
142 These divisions are based on size, frequency of update, and protection domain
144 The Manifest Cache is a dictionary where each key is a hash over its entry,
145 which is a concatenation of 20 byte hashes of the keychain items. The local
146 keychain is present as one entry. The other entries are subsets of that, one
147 for each confirmed/pending/missing/unwanted shared with a peer. The local
148 keychain entry can be re-created by iterating over the databse, whereas the
149 others are built up through communicating with other peers.
151 83:d=2 hl=2 l= 13 prim: UTF8STRING :manifestCache
152 98:d=2 hl=4 l= 912 cons: SET
153 102:d=3 hl=2 l= 24 cons: SEQUENCE
154 104:d=4 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:DA39A3EE5E6B4B0D3255BFEF95601890AFD80709
155 126:d=4 hl=2 l= 0 prim: OCTET STRING
156 128:d=3 hl=2 l= 124 cons: SEQUENCE
157 130:d=4 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:F9B59370A4733F0D174E8D220C5BE3AF062C775B
158 152:d=4 hl=2 l= 100 prim: OCTET STRING [HEX DUMP]:5A574BB4EC90C3BBCC69EE73CBFE039133AE807265D6A58003B8D205997EAB96390AAB207E63A2E270A476CAB5B2D9D2F7B0E55512AA957B58D5658E7EF907B069B83AA6BA941790A3C3C4A68292D59DABA3CA342966EFF82E1ACAEB691FD6E20772E17E
159 254:d=3 hl=4 l= 366 cons: SEQUENCE
160 258:d=4 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:2E69C2F7F3E014075B30004CE0EC6C1AD419EBF5
161 280:d=4 hl=4 l= 340 prim: OCTET STRING [HEX DUMP]:07571E9678FD7D68812E409CC96C1F54834A099A0C3A2D12CCE2EA95F4505EA52F2C982B2ADEE3DA14D4712C000309BF63D54A98B61AA1D963C40E0E2531C83B28CA5BE6DA0D26400C3C77A618F711DD3CC0BF86CCBAF8AA3332973268B30EEBF21CD8184D9C8427CA13DECCC7BB83C80009A2EF45CCC07F586315C80CEEEEF5D5352FD000AAE6D9CBB4294D5959FD00198225AF9ABD09B341A2FDC278E9FD1465D6A58003B8D205997EAB96390AAB207E63A2E270A476CAB5B2D9D2F7B0E55512AA957B58D5658E7EF907B069B83AA6BA941790A3C3C4A68292D59D95C9D4D8A8BCA2E8242AB0D409F671F298B6DCAE9BC4238C09E07548CEFB300098606F9E4F230C99ABA3CA342966EFF82E1ACAEB691FD6E20772E17EB4FEFB84F8CF75C0C69C59532C354D175A59F961BA4D4DFA017FD8192288F14278AE76712E127D65FE616C7E4FD0713644F7C9A7ABA1CE065694A968
162 624:d=3 hl=4 l= 386 cons: SEQUENCE
163 628:d=4 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:CCF179FF718C10F151E7409EDF1A06F0DF10DCAD
164 650:d=4 hl=4 l= 360 prim: OCTET STRING [HEX DUMP]:07571E9678FD7D68812E409CC96C1F54834A099A0C3A2D12CCE2EA95F4505EA52F2C982B2ADEE3DA14D4712C000309BF63D54A98B61AA1D963C40E0E2531C83B28CA5BE6DA0D26400C3C77A618F711DD3CC0BF86CCBAF8AA3332973268B30EEBF21CD8184D9C8427CA13DECCC7BB83C80009A2EF45CCC07F586315C80CEEEEF5D5352FD000AAE6D9CBB4294D5959FD00198225AF9ABD09B341A2FDC278E9FD145A574BB4EC90C3BBCC69EE73CBFE039133AE807265D6A58003B8D205997EAB96390AAB207E63A2E270A476CAB5B2D9D2F7B0E55512AA957B58D5658E7EF907B069B83AA6BA941790A3C3C4A68292D59D95C9D4D8A8BCA2E8242AB0D409F671F298B6DCAE9BC4238C09E07548CEFB300098606F9E4F230C99ABA3CA342966EFF82E1ACAEB691FD6E20772E17EB4FEFB84F8CF75C0C69C59532C354D175A59F961BA4D4DFA017FD8192288F14278AE76712E127D65FE616C7E4FD0713644F7C9A7ABA1CE065694A968
170 static bool SOSEngineLoad(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
);
171 static bool SOSEngineSetPeers_locked(SOSEngineRef engine
, SOSPeerMetaRef myPeerMeta
, CFArrayRef trustedPeerMetas
, CFArrayRef untrustedPeerMetas
);
172 static void SOSEngineApplyPeerState(SOSEngineRef engine
, CFDictionaryRef peerStateMap
);
173 static void SOSEngineSynthesizePeerMetas(SOSEngineRef engine
, CFMutableArrayRef trustedPeersMetas
, CFMutableArrayRef untrustedPeers
);
174 static bool SOSEngineLoadCoders(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
);
175 #if !TARGET_OS_SIMULATOR
176 static bool SOSEngineDeleteV0State(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
);
179 static CFStringRef
SOSPeerIDArrayCreateString(CFArrayRef peerIDs
) {
180 return peerIDs
? CFStringCreateByCombiningStrings(kCFAllocatorDefault
, peerIDs
, CFSTR(" ")) : CFSTR("");
183 static CFStringRef
SOSEngineCopyFormattingDesc(CFTypeRef cf
, CFDictionaryRef formatOptions
) {
184 SOSEngineRef engine
= (SOSEngineRef
)cf
;
185 CFStringRef tpDesc
= SOSPeerIDArrayCreateString(engine
->peerIDs
);
186 CFStringRef desc
= CFStringCreateWithFormat(kCFAllocatorDefault
, formatOptions
, CFSTR("<Engine %@ peers %@ MC[%d] PS[%d]>"), engine
->myID
, tpDesc
, engine
->manifestCache
? (int)CFDictionaryGetCount(engine
->manifestCache
) : 0, engine
->peerMap
? (int)CFDictionaryGetCount(engine
->peerMap
) : 0);
187 CFReleaseSafe(tpDesc
);
191 static CFStringRef
SOSEngineCopyDebugDesc(CFTypeRef cf
) {
192 return SOSEngineCopyFormattingDesc(cf
, NULL
);
195 static dispatch_queue_t sEngineQueue
;
196 static CFDictionaryRef sEngineMap
;
198 CFGiblisWithFunctions(SOSEngine
, NULL
, NULL
, NULL
, NULL
, NULL
, SOSEngineCopyFormattingDesc
, SOSEngineCopyDebugDesc
, NULL
, NULL
, ^{
199 sEngineQueue
= dispatch_queue_create("SOSEngine queue", DISPATCH_QUEUE_SERIAL
);
200 sEngineMap
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
203 #define _LOG_RAW_MESSAGES 0
204 void logRawMessage(CFDataRef message
, bool sending
, uint64_t seqno
)
206 #if _LOG_RAW_MESSAGES
207 CFStringRef hexMessage
= NULL
;
209 hexMessage
= CFDataCopyHexString(message
);
211 secnoticeq("engine", "%s RAW%1d %@", sending
? "send" : "recv", seqno
?2:0, hexMessage
);
213 secnoticeq("engine", "%s RAWx %@", sending
? "send" : "recv", hexMessage
); // we don't know vers of received msg here
215 CFReleaseSafe(hexMessage
);
220 // Peer state layout. WRONG! It's an array now
221 // The peer state is an array.
222 // The first element of the array is a dictionary with any number of keys and
223 // values in it (for future expansion) such as changing the digest size or type
224 // or remembering boolean flags for a peers sake.
225 // The next three are special in that they are manifest digests with special
226 // meaning and rules as to how they are treated (These are dynamically updated
227 // based on database activity so they have a fully history of all changes made
228 // to the local db. The first is the manifest representing the pendingObjects
229 // to send to the other peer. This is normally only ever appending to, and in
230 // particular with transactions originating from the Keychain API that affect
231 // syncable items will need to add the new objects digests to the pendingObjects list
232 // while adding the digests of any tombstones encountered to the extra list.
234 CFStringRef
SOSEngineGetMyID(SOSEngineRef engine
) {
235 // TODO: this should not be needed
239 // TEMPORARY: Get the list of IDs for cleanup, this shouldn't be used instead it should iterate KVS.
240 CFArrayRef
SOSEngineGetPeerIDs(SOSEngineRef engine
) {
241 if(!engine
) return NULL
;
242 return engine
->peerIDs
;
245 void SOSEngineClearCache(SOSEngineRef engine
){
246 CFReleaseNull(engine
->manifestCache
);
247 CFReleaseNull(engine
->localMinusUnreadableDigest
);
248 if (engine
->save_timer
)
249 dispatch_source_cancel(engine
->save_timer
);
250 dispatch_release(engine
->queue
);
251 engine
->queue
= NULL
;
254 static SOSPeerRef
SOSEngineCopyPeerWithMapEntry_locked(SOSEngineRef engine
, CFStringRef peerID
, CFTypeRef mapEntry
, CFErrorRef
*error
) {
255 SOSPeerRef peer
= NULL
;
256 if (mapEntry
&& CFGetTypeID(mapEntry
) == SOSPeerGetTypeID()) {
257 // The mapEntry is an SOSPeer, so we're done.
258 peer
= (SOSPeerRef
)CFRetain(mapEntry
);
260 // The mapEntry is a peerState, attempt to initialize a new
261 // peer iff peerID is in the set of trusted peerIDs
262 if (engine
->peerIDs
&& CFArrayContainsValue(engine
->peerIDs
, CFRangeMake(0, CFArrayGetCount(engine
->peerIDs
)), peerID
)) {
263 CFErrorRef localError
= NULL
;
264 peer
= SOSPeerCreateWithState(engine
, peerID
, mapEntry
, &localError
);
266 secerror("error inflating peer: %@: %@ from state: %@", peerID
, localError
, mapEntry
);
267 CFReleaseNull(localError
);
268 peer
= SOSPeerCreateWithState(engine
, peerID
, NULL
, error
);
271 // Replace the map entry with the inflated peer.
272 CFDictionarySetValue(engine
->peerMap
, peerID
, peer
);
275 SOSErrorCreate(kSOSErrorPeerNotFound
, error
, NULL
, CFSTR("peer: %@ is untrusted inflating not allowed"), peerID
);
281 static SOSPeerRef
SOSEngineCopyPeerWithID_locked(SOSEngineRef engine
, CFStringRef peerID
, CFErrorRef
*error
) {
282 CFTypeRef mapEntry
= CFDictionaryGetValue(engine
->peerMap
, peerID
);
283 SOSPeerRef peer
= NULL
;
285 peer
= SOSEngineCopyPeerWithMapEntry_locked(engine
, peerID
, mapEntry
, error
);
288 secerror("peer: %@ not found, peerMap: %@, engine: %@", peerID
, engine
->peerMap
, engine
);
289 SOSErrorCreate(kSOSErrorPeerNotFound
, error
, NULL
, CFSTR("peer: %@ not found"), peerID
);
294 struct SOSEngineWithPeerContext
{
296 void (^with
)(SOSPeerRef peer
);
299 static void SOSEngineWithPeerMapEntry_locked(const void *peerID
, const void *mapEntry
, void *context
) {
300 struct SOSEngineWithPeerContext
*ewp
= context
;
301 SOSPeerRef peer
= SOSEngineCopyPeerWithMapEntry_locked(ewp
->engine
, peerID
, mapEntry
, NULL
);
308 static void SOSEngineForEachPeer_locked(SOSEngineRef engine
, void (^with
)(SOSPeerRef peer
)) {
309 struct SOSEngineWithPeerContext ewp
= { .engine
= engine
, .with
= with
};
310 CFDictionaryRef peerMapCopy
= CFDictionaryCreateCopy(NULL
, engine
->peerMap
);
311 CFDictionaryApplyFunction(peerMapCopy
, SOSEngineWithPeerMapEntry_locked
, &ewp
);
312 CFRelease(peerMapCopy
);
315 static void SOSEngineWithBackupPeerMapEntry_locked(const void *peerID
, const void *mapEntry
, void *context
) {
316 struct SOSEngineWithPeerContext
*ewp
= context
;
317 // v0 backup peer is always in map but we only consider it a backup peer if it has a keybag.
318 if (SOSPeerMapEntryIsBackup(mapEntry
)) {
319 SOSPeerRef peer
= SOSEngineCopyPeerWithMapEntry_locked(ewp
->engine
, peerID
, mapEntry
, NULL
);
327 static void SOSEngineForEachBackupPeer_locked(SOSEngineRef engine
, void (^with
)(SOSPeerRef peer
)) {
328 struct SOSEngineWithPeerContext ewp
= { .engine
= engine
, .with
= with
};
329 CFDictionaryRef peerMapCopy
= CFDictionaryCreateCopy(NULL
, engine
->peerMap
);
330 CFDictionaryApplyFunction(peerMapCopy
, SOSEngineWithBackupPeerMapEntry_locked
, &ewp
);
331 CFRelease(peerMapCopy
);
334 static void SOSEngineForBackupPeer_locked(SOSEngineRef engine
, CFStringRef backupPeerID
, void (^with
)(SOSPeerRef peer
)) {
335 struct SOSEngineWithPeerContext ewp
= { .engine
= engine
, .with
= with
};
336 CFMutableDictionaryRef singleEntryMap
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
337 CFDictionaryRef peerMapCopy
= CFDictionaryCreateCopy(NULL
, engine
->peerMap
);
338 SOSPeerRef peer
= (SOSPeerRef
)CFDictionaryGetValue(peerMapCopy
, backupPeerID
);
340 CFDictionaryAddValue(singleEntryMap
, backupPeerID
, peer
);
341 CFDictionaryApplyFunction(singleEntryMap
, SOSEngineWithBackupPeerMapEntry_locked
, &ewp
);
343 CFRelease(peerMapCopy
);
344 CFRelease(singleEntryMap
);
350 SOSManifestRef
SOSEngineGetManifestForDigest(SOSEngineRef engine
, CFDataRef digest
) {
351 if (!engine
->manifestCache
|| !digest
) return NULL
;
352 SOSManifestRef manifest
= (SOSManifestRef
)CFDictionaryGetValue(engine
->manifestCache
, digest
);
353 if (!manifest
) return NULL
;
354 if (CFGetTypeID(manifest
) != SOSManifestGetTypeID()) {
355 secerror("dropping corrupt manifest for %@ from cache", digest
);
356 CFDictionaryRemoveValue(engine
->manifestCache
, digest
);
363 void SOSEngineAddManifest(SOSEngineRef engine
, SOSManifestRef manifest
) {
364 CFDataRef digest
= SOSManifestGetDigest(manifest
, NULL
);
366 if (!engine
->manifestCache
)
367 engine
->manifestCache
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
368 CFDictionaryAddValue(engine
->manifestCache
, digest
, manifest
);
372 CFDataRef
SOSEnginePatchRecordAndCopyDigest(SOSEngineRef engine
, SOSManifestRef base
, SOSManifestRef removals
, SOSManifestRef additions
, CFErrorRef
*error
) {
373 CFDataRef digest
= NULL
;
374 SOSManifestRef manifest
= SOSManifestCreateWithPatch(base
, removals
, additions
, error
);
376 SOSEngineAddManifest(engine
, manifest
);
377 digest
= CFRetainSafe(SOSManifestGetDigest(manifest
, NULL
));
379 CFReleaseSafe(manifest
);
383 SOSManifestRef
SOSEngineCopyPersistedManifest(SOSEngineRef engine
, CFDictionaryRef persisted
, CFStringRef key
) {
384 return CFRetainSafe(SOSEngineGetManifestForDigest(engine
, asData(CFDictionaryGetValue(persisted
, key
), NULL
)));
387 CFMutableArrayRef
SOSEngineCopyPersistedManifestArray(SOSEngineRef engine
, CFDictionaryRef persisted
, CFStringRef key
, CFErrorRef
*error
) {
388 CFMutableArrayRef manifests
= NULL
;
389 CFArrayRef digests
= NULL
;
391 if (asArrayOptional(CFDictionaryGetValue(persisted
, key
), &digests
, error
))
392 manifests
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
393 if (digests
) CFArrayForEachC(digests
, digest
) {
394 SOSManifestRef manifest
= SOSEngineGetManifestForDigest(engine
, digest
);
396 CFArrayAppendValue(manifests
, manifest
);
401 #if !TARGET_OS_SIMULATOR
402 static CFDictionaryRef
SOSEngineCopyEncodedManifestCache_locked(SOSEngineRef engine
, CFErrorRef
*error
) {
403 CFMutableDictionaryRef mfc
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
404 SOSEngineForEachPeer_locked(engine
, ^(SOSPeerRef peer
) {
405 SOSPeerAddManifestsInUse(peer
, mfc
);
412 // End of Manifest cache
415 //----------------------------------------------------------------------------------------
417 //----------------------------------------------------------------------------------------
420 Each peer has an associated coder, whcih the engine keeps track of in a
421 CFDictionary indexed by peerID. The coders are read from disk when first needed,
422 then kept in memory as SOSCoders.
424 N.B. Don't rollback coder in memory if a transaction is rolled back, since this
425 might lead to reuse of an IV.
428 static bool SOSEngineCopyCoderData(SOSEngineRef engine
, CFStringRef peerID
, CFDataRef
*coderData
, CFErrorRef
*error
) {
430 SOSCoderRef coder
= (SOSCoderRef
)CFDictionaryGetValue(engine
->coders
, peerID
);
431 if (coder
&& (CFGetTypeID(coder
) == SOSCoderGetTypeID())) {
432 CFErrorRef localError
= NULL
;
433 ok
= *coderData
= SOSCoderCopyDER(coder
, &localError
);
435 secerror("failed to der encode coder for peer %@, dropping it: %@", peerID
, localError
);
436 CFDictionaryRemoveValue(engine
->coders
, peerID
);
437 CFErrorPropagate(localError
, error
);
445 static SOSCoderRef
SOSEngineGetCoderInTx_locked(SOSEngineRef engine
, SOSTransactionRef txn
, CFStringRef peerID
, CFErrorRef
*error
) {
446 if (!engine
->haveLoadedCoders
) {
447 engine
->haveLoadedCoders
= SOSEngineLoadCoders(engine
, txn
, error
);
449 if (!engine
->haveLoadedCoders
) {
454 SOSCoderRef coder
= (SOSCoderRef
)CFDictionaryGetValue(engine
->coders
, peerID
);
455 if (!coder
|| (CFGetTypeID(coder
) != SOSCoderGetTypeID())) {
456 SOSErrorCreate(kSOSErrorPeerNotFound
, error
, NULL
, CFSTR("No coder for peer: %@"), peerID
);
461 static bool SOSEngineEnsureCoder_locked(SOSEngineRef engine
, SOSTransactionRef txn
, CFStringRef peerID
, SOSFullPeerInfoRef myPeerInfo
, SOSPeerInfoRef peerInfo
, SOSCoderRef ourCoder
, CFErrorRef
*error
) {
462 //have to have caused coder loading, transactions do this.
463 if (!ourCoder
|| !SOSCoderIsFor(ourCoder
, peerInfo
, myPeerInfo
)) {
464 secinfo("coder", "New coder for id %@.", peerID
);
465 CFErrorRef localError
= NULL
;
466 SOSCoderRef coder
= SOSCoderCreate(peerInfo
, myPeerInfo
, kCFBooleanFalse
, &localError
);
468 secerror("Failed to create coder for %@: %@", peerID
, localError
);
469 CFErrorPropagate(localError
, error
);
472 CFDictionarySetValue(engine
->coders
, peerID
, coder
);
473 secdebug("coder", "setting coder for peerid: %@, coder: %@", peerID
, coder
);
474 CFReleaseNull(coder
);
475 engine
->codersNeedSaving
= true;
480 bool SOSEngineInitializePeerCoder(SOSEngineRef engine
, SOSFullPeerInfoRef myPeerInfo
, SOSPeerInfoRef peerInfo
, CFErrorRef
*error
) {
481 __block
bool ok
= true;
482 CFStringRef peerID
= SOSPeerInfoGetPeerID(peerInfo
);
484 ok
&= SOSEngineWithPeerID(engine
, peerID
, error
, ^(SOSPeerRef peer
, SOSCoderRef coder
, SOSDataSourceRef dataSource
, SOSTransactionRef txn
, bool *forceSaveState
) {
485 ok
= SOSEngineEnsureCoder_locked(engine
, txn
, peerID
, myPeerInfo
, peerInfo
, coder
, error
);
486 // Only set if the codersNeedSaving state gets set.
487 *forceSaveState
= engine
->codersNeedSaving
;
493 static bool SOSEngineGCPeerState_locked(SOSEngineRef engine
, CFErrorRef
*error
) {
496 //require_quiet(ok = SOSEngineGCManifests_locked(engine, error), exit);
501 #if !TARGET_OS_SIMULATOR
502 static CFMutableDictionaryRef
SOSEngineCopyPeerState_locked(SOSEngineRef engine
, CFErrorRef
*error
) {
503 CFMutableDictionaryRef peerState
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
504 CFDictionaryForEach(engine
->peerMap
, ^(const void *key
, const void *value
) {
505 CFDictionaryRef state
= NULL
;
506 if (value
&& CFGetTypeID(value
) == SOSPeerGetTypeID()) {
507 CFErrorRef localError
= NULL
;
509 state
= SOSPeerCopyState((SOSPeerRef
)value
, &localError
);
511 secnotice("engine", "%@ failed to encode peer: %@", key
, localError
);
512 CFReleaseNull(localError
);
513 // TODO: Potentially replace inflated peer with deflated peer in peerMap
515 // We have a deflated peer.
516 state
= CFRetainSafe(value
);
520 CFDictionarySetValue(peerState
, key
, state
);
521 CFReleaseSafe(state
);
527 static CFMutableDictionaryRef
SOSEngineCopyPeerCoders_locked(SOSEngineRef engine
, CFErrorRef
*error
) {
528 CFMutableDictionaryRef coders
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
529 CFDictionaryForEach(engine
->peerMap
, ^(const void *key
, const void *value
) {
530 CFDataRef coderData
= NULL
;
531 CFErrorRef localError
= NULL
;
532 bool ok
= SOSEngineCopyCoderData(engine
, (CFStringRef
)key
, &coderData
, &localError
);
535 secnotice("engine", "%@ no coder for peer: %@", key
, localError
);
537 if (ok
&& coderData
) {
538 CFDictionarySetValue(coders
, key
, coderData
);
540 CFReleaseNull(coderData
);
541 CFReleaseNull(localError
);
546 //----------------------------------------------------------------------------------------
547 // MARK: Engine state v2 Save
548 //----------------------------------------------------------------------------------------
550 // Coders and keybags
552 static CFDataRef
SOSEngineCopyCoders(SOSEngineRef engine
, CFErrorRef
*error
) {
553 // Copy the CFDataRef version of the coders into a dictionary, which is then DER-encoded for saving
554 CFDictionaryRef coders
= SOSEngineCopyPeerCoders_locked(engine
, error
);
555 secdebug("coders", "copying coders! %@", coders
);
556 CFDataRef der
= CFPropertyListCreateDERData(kCFAllocatorDefault
, coders
, error
);
557 CFReleaseSafe(coders
);
561 #pragma clang diagnostic push
562 #pragma clang diagnostic fatal "-Wshadow"
563 static bool SOSEngineSaveCoders(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
) {
564 // MUST hold engine lock
565 // Device must be unlocked for this to succeed
567 if(!engine
->haveLoadedCoders
){
568 secdebug("coders", "attempting to save coders before we have loaded them!");
572 if (engine
->codersNeedSaving
) {
573 CFErrorRef localError
= NULL
;
574 CFDataRef derCoders
= SOSEngineCopyCoders(engine
, &localError
);
575 ok
= derCoders
&& SOSDataSourceSetStateWithKey(engine
->dataSource
, txn
, kSOSEngineCoders
,
576 kSOSEngineProtectionDomainClassA
, derCoders
, &localError
);
578 engine
->codersNeedSaving
= false;
579 secnotice("coder", "saved coders: %@", engine
->coders
);
581 if(error
) CFTransferRetained(*error
, localError
);
582 secnotice("coder", "failed to save coders: %@ (%@)", engine
->coders
, localError
);
584 CFReleaseSafe(derCoders
);
585 CFReleaseSafe(localError
);
589 #pragma clang diagnostic pop
591 bool SOSTestEngineSaveCoders(CFTypeRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
){
592 return SOSEngineSaveCoders((SOSEngineRef
)engine
, txn
, error
);
594 #if !TARGET_OS_SIMULATOR
596 static CFDictionaryRef
SOSEngineCopyBasicState(SOSEngineRef engine
, CFErrorRef
*error
) {
597 // Create a version of the in-memory engine state for saving to disk
598 CFMutableDictionaryRef state
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
600 CFDictionarySetValue(state
, kSOSEngineIDKey
, engine
->myID
);
602 CFDictionarySetValue(state
, kSOSEnginePeerIDsKey
, engine
->peerIDs
);
603 if (engine
->lastTraceDate
)
604 CFDictionarySetValue(state
, kSOSEngineTraceDateKey
, engine
->lastTraceDate
);
606 SOSPersistCFIndex(state
, kSOSEngineStateVersionKey
, kCurrentEngineVersion
);
610 static bool SOSEngineDoSaveOneState(SOSEngineRef engine
, SOSTransactionRef txn
, CFStringRef key
, CFStringRef pdmn
,
611 CFDictionaryRef state
, CFErrorRef
*error
) {
612 CFDataRef derState
= CFPropertyListCreateDERData(kCFAllocatorDefault
, state
, error
);
613 bool ok
= derState
&& SOSDataSourceSetStateWithKey(engine
->dataSource
, txn
, key
, pdmn
, derState
, error
);
614 CFReleaseSafe(derState
);
618 static bool SOSEngineDoSave(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
) {
621 CFDictionaryRef state
= SOSEngineCopyBasicState(engine
, error
);
622 ok
&= state
&& SOSEngineDoSaveOneState(engine
, txn
, kSOSEngineStatev2
, kSOSEngineProtectionDomainClassD
, state
, error
);
623 CFReleaseNull(state
);
625 state
= SOSEngineCopyPeerState_locked(engine
, error
);
626 ok
&= state
&& SOSEngineDoSaveOneState(engine
, txn
, kSOSEnginePeerStates
, kSOSEngineProtectionDomainClassD
, state
, error
);
627 CFReleaseNull(state
);
629 state
= SOSEngineCopyEncodedManifestCache_locked(engine
, error
);
630 ok
&= state
&& SOSEngineDoSaveOneState(engine
, txn
, kSOSEngineManifestCache
, kSOSEngineProtectionDomainClassD
, state
, error
);
631 CFReleaseNull(state
);
633 ok
&= SOSEngineSaveCoders(engine
, txn
, error
);
635 SOSEngineDeleteV0State(engine
, txn
, NULL
);
641 static bool SOSEngineSave(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
) {
642 // Don't save engine state from tests
643 if (!engine
->dataSource
)
645 #if !TARGET_OS_SIMULATOR
646 return SOSEngineDoSave(engine
, txn
, error
);
651 //----------------------------------------------------------------------------------------
652 // MARK: Engine state v2 Load/Restore
653 //----------------------------------------------------------------------------------------
655 // Restore the in-memory state of engine from saved state loaded from the db
656 static bool SOSEngineSetManifestCacheWithDictionary(SOSEngineRef engine
, CFDictionaryRef manifestCache
, CFErrorRef
*error
) {
657 __block
bool ok
= true;
658 CFReleaseNull(engine
->manifestCache
);
660 engine
->manifestCache
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
661 CFDictionaryForEach(manifestCache
, ^(const void *key
, const void *value
) {
662 CFDataRef data
= (CFDataRef
)value
;
664 SOSManifestRef mf
= SOSManifestCreateWithData(data
, NULL
);
666 CFDictionarySetValue(engine
->manifestCache
, key
, mf
);
675 static bool SOSEngineUpdateStateWithDictionary(SOSEngineRef engine
, CFDictionaryRef stateDict
, CFErrorRef
*error
) {
679 // If kCurrentEngineVersion > 2, uncomment and fill in code below
680 CFIndex engineVersion
= 0 ;
681 bool versionPresent
= SOSPeerGetOptionalPersistedCFIndex(stateDict
, kSOSEngineStateVersionKey
, &engineVersion
);
682 if (versionPresent
&& (engineVersion
!= kCurrentEngineVersion
)) {
690 static bool SOSEngineSetStateWithDictionary(SOSEngineRef engine
, CFDictionaryRef stateDict
, CFErrorRef
*error
) {
693 SOSEngineUpdateStateWithDictionary(engine
, stateDict
, error
);
694 CFRetainAssign(engine
->myID
, asString(CFDictionaryGetValue(stateDict
, kSOSEngineIDKey
), NULL
));
695 CFRetainAssign(engine
->peerIDs
, asArray(CFDictionaryGetValue(stateDict
, kSOSEnginePeerIDsKey
), NULL
));
696 CFRetainAssign(engine
->lastTraceDate
, asDate(CFDictionaryGetValue(stateDict
, kSOSEngineTraceDateKey
), NULL
));
699 secnotice("engine", "%@", engine
);
703 static bool SOSEngineSetPeerStateWithDictionary(SOSEngineRef engine
, CFDictionaryRef peerStateDict
, CFErrorRef
*error
) {
704 // Set the in-memory peer state using the dictionary version of the DER-encoded version from disk
705 CFMutableArrayRef untrustedPeers
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
706 CFMutableArrayRef trustedPeersMetas
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
707 SOSEngineApplyPeerState(engine
, asDictionary(peerStateDict
, NULL
));
708 SOSEngineSynthesizePeerMetas(engine
, trustedPeersMetas
, untrustedPeers
);
709 SOSEngineSetPeers_locked(engine
, engine
->myID
, trustedPeersMetas
, untrustedPeers
);
710 CFReleaseNull(trustedPeersMetas
);
711 CFReleaseNull(untrustedPeers
);
715 CFMutableDictionaryRef
derStateToDictionaryCopy(CFDataRef state
, CFErrorRef
*error
) {
717 CFMutableDictionaryRef stateDict
= NULL
;
719 const uint8_t *der
= CFDataGetBytePtr(state
);
720 const uint8_t *der_end
= der
+ CFDataGetLength(state
);
721 ok
= der
= der_decode_dictionary(kCFAllocatorDefault
, (CFDictionaryRef
*)&stateDict
, error
, der
, der_end
);
722 if (der
&& der
!= der_end
) {
723 ok
= SOSErrorCreate(kSOSErrorDecodeFailure
, error
, NULL
, CFSTR("trailing %td bytes at end of state"), der_end
- der
);
726 CFReleaseNull(stateDict
);
731 bool TestSOSEngineLoadCoders(CFTypeRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
)
733 return SOSEngineLoadCoders((SOSEngineRef
)engine
, txn
, error
);
736 static bool SOSEngineLoadCoders(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
) {
737 // Read the serialized engine state from the datasource (aka keychain) and populate the in-memory engine
738 __block
bool needPeerRegistration
= false;
740 CFDataRef derCoders
= NULL
;
741 CFMutableDictionaryRef codersDict
= NULL
;
742 derCoders
= SOSDataSourceCopyStateWithKey(engine
->dataSource
, kSOSEngineCoders
, kSOSEngineProtectionDomainClassA
, txn
, error
);
743 require_quiet(derCoders
, xit
);
744 codersDict
= derStateToDictionaryCopy(derCoders
, error
);
745 require_quiet(codersDict
, xit
);
748 * Make sure all peer have coders
750 CFDictionaryForEach(engine
->peerMap
, ^(const void *peerID
, const void *peerState
) {
752 * Skip backup peer since they will never have coders
754 if (isString(peerID
) && CFStringHasSuffix(peerID
, CFSTR("-tomb"))) {
755 secnotice("coder", "Skipping coder check for peer: %@", peerID
);
759 CFTypeRef coderRef
= CFDictionaryGetValue(codersDict
, peerID
);
761 CFDataRef coderData
= asData(coderRef
, NULL
);
763 CFErrorRef createError
= NULL
;
764 SOSCoderRef coder
= SOSCoderCreateFromData(coderData
, &createError
);
766 CFDictionaryAddValue(engine
->coders
, peerID
, coder
);
767 secnotice("coder", "adding coder: %@ for peerid: %@", coder
, peerID
);
769 secnotice("coder", "Coder for '%@' failed to create: %@", peerID
, createError
);
771 CFReleaseNull(createError
);
772 CFReleaseNull(coder
);
774 // Needed a coder, didn't find one, notify the account to help us out.
775 // Next attempt to sync will fix this
776 secnotice("coder", "coder for %@ was not cf data: %@", peerID
, coderData
);
777 needPeerRegistration
= true;
780 secnotice("coder", "didn't find coder for peer: %@ engine dictionary: %@", peerID
, codersDict
);
781 needPeerRegistration
= true;
785 secnotice("coder", "Will force peer registration: %s",needPeerRegistration
? "yes" : "no");
787 if (needPeerRegistration
) {
788 dispatch_queue_t queue
= dispatch_get_global_queue(SOS_ENGINE_PRIORITY
, 0);
790 dispatch_async(queue
, ^{
791 CFErrorRef eprError
= NULL
;
792 if (!SOSCCProcessEnsurePeerRegistration_Server(&eprError
)) {
793 secnotice("coder", "SOSCCProcessEnsurePeerRegistration failed with: %@", eprError
);
795 CFReleaseNull(eprError
);
799 engine
->haveLoadedCoders
= true;
802 CFReleaseNull(derCoders
);
803 CFReleaseNull(codersDict
);
806 #if !TARGET_OS_SIMULATOR
807 static bool SOSEngineDeleteV0State(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
) {
808 // SOSDataSourceDeleteStateWithKey(engine->dataSource, kSOSEngineState, kSOSEngineProtectionDomainClassD, txn, error);
810 // Create effectively empty state until delete is working
811 CFMutableDictionaryRef state
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
813 CFDictionarySetValue(state
, kSOSEngineIDKey
, engine
->myID
);
814 CFDataRef derState
= CFPropertyListCreateDERData(kCFAllocatorDefault
, state
, error
);
815 CFReleaseNull(state
);
817 bool ok
= derState
&& SOSDataSourceSetStateWithKey(engine
->dataSource
, txn
, kSOSEngineState
, kSOSEngineProtectionDomainClassD
, derState
, error
);
818 CFReleaseSafe(derState
);
822 static bool SOSEngineLoad(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
) {
823 // Read the serialized engine state from the datasource (aka keychain) and populate the in-memory engine
825 CFDataRef basicEngineState
= NULL
;
826 CFMutableDictionaryRef engineState
= NULL
;
827 CFDictionaryRef manifestCache
= NULL
;
828 CFDictionaryRef peerStateDict
= NULL
;
829 CFMutableDictionaryRef codersDict
= NULL
;
830 // Look for the v2 engine state first
831 basicEngineState
= SOSDataSourceCopyStateWithKey(engine
->dataSource
, kSOSEngineStatev2
, kSOSEngineProtectionDomainClassD
, txn
, error
);
832 if (basicEngineState
) {
833 CFDataRef data
= NULL
;
834 engineState
= derStateToDictionaryCopy(basicEngineState
, error
);
836 data
= SOSDataSourceCopyStateWithKey(engine
->dataSource
, kSOSEngineManifestCache
, kSOSEngineProtectionDomainClassD
, txn
, error
);
837 manifestCache
= derStateToDictionaryCopy(data
, error
);
840 data
= SOSDataSourceCopyStateWithKey(engine
->dataSource
, kSOSEnginePeerStates
, kSOSEngineProtectionDomainClassD
, txn
, error
);
841 peerStateDict
= derStateToDictionaryCopy(data
, error
);
844 // Look for original V0 engine state next
845 CFDataRef v0EngineStateData
= SOSDataSourceCopyStateWithKey(engine
->dataSource
, kSOSEngineState
, kSOSEngineProtectionDomainClassD
, txn
, error
);
846 if (v0EngineStateData
) {
847 engineState
= derStateToDictionaryCopy(v0EngineStateData
, error
);
849 manifestCache
= CFRetainSafe(asDictionary(CFDictionaryGetValue(engineState
, kSOSEngineManifestCacheKey
), NULL
));
850 peerStateDict
= CFRetainSafe(asDictionary(CFDictionaryGetValue(engineState
, kSOSEnginePeerStateKey
), NULL
));
852 CFReleaseNull(v0EngineStateData
);
854 secnotice("coder", "Migrating from v0 engine state; dropping coders and forcing re-negotiation");
855 SOSCCEnsurePeerRegistration();
857 if (engine
->peerIDs
) {
858 SOSCCRequestSyncWithPeersList(engine
->peerIDs
);
862 ok
= engineState
&& SOSEngineSetStateWithDictionary(engine
, engineState
, error
);
864 ok
&= SOSEngineSetManifestCacheWithDictionary(engine
, manifestCache
, error
);
866 ok
&= peerStateDict
&& SOSEngineSetPeerStateWithDictionary(engine
, peerStateDict
, error
);
868 CFReleaseSafe(basicEngineState
);
869 CFReleaseSafe(engineState
);
870 CFReleaseSafe(manifestCache
);
871 CFReleaseSafe(peerStateDict
);
872 CFReleaseSafe(codersDict
);
876 bool SOSTestEngineSaveWithDER(SOSEngineRef engine
, CFDataRef derState
, CFErrorRef
*error
) {
881 bool SOSTestEngineSave(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
) {
882 bool bx
= SOSEngineSave(engine
, txn
, error
);
883 secnotice("test", "saved engine: %@", engine
);
887 bool SOSTestEngineLoad(SOSEngineRef engine
, SOSTransactionRef txn
, CFErrorRef
*error
) {
888 bool bx
= SOSEngineLoad(engine
, txn
, error
);
889 secnotice("test", "loaded engine: %@", engine
);
893 //----------------------------------------------------------------------------------------
894 // MARK: Change Trackers and Peer Manifests
895 //----------------------------------------------------------------------------------------
897 static SOSManifestRef
SOSEngineCreateManifestWithViewNameSet_locked(SOSEngineRef engine
, CFSetRef viewNameSet
, CFErrorRef
*error
) {
898 // TODO: Potentially tell all changeTrackers to track manifests ( //forall ct do SOSChangeTrackerSetConcrete(ct, true);
899 // and read the entire dataSource and pass all objects though the filter here, instead of
900 // forcing the datasource to be able to do "smart" queries
901 return SOSDataSourceCopyManifestWithViewNameSet(engine
->dataSource
, viewNameSet
, error
);
904 static SOSChangeTrackerRef
SOSEngineCopyChangeTrackerWithViewNameSet_locked(SOSEngineRef engine
, CFSetRef viewNameSet
, CFErrorRef
*error
) {
905 SOSChangeTrackerRef ct
= (SOSChangeTrackerRef
)CFDictionaryGetValue(engine
->viewNameSet2ChangeTracker
, viewNameSet
);
907 SOSErrorCreate(kSOSErrorPeerNotFound
, error
, NULL
, CFSTR("no change tracker for view set %@"), viewNameSet
);
908 return CFRetainSafe(ct
);
911 static SOSManifestRef
SOSEngineCopyManifestWithViewNameSet_locked(SOSEngineRef engine
, CFSetRef viewNameSet
, CFErrorRef
*error
) {
912 SOSChangeTrackerRef ct
= SOSEngineCopyChangeTrackerWithViewNameSet_locked(engine
, viewNameSet
, error
);
916 SOSManifestRef manifest
= SOSChangeTrackerCopyManifest(ct
, NULL
);
918 manifest
= SOSEngineCreateManifestWithViewNameSet_locked(engine
, viewNameSet
, error
); // Do the SQL query
919 SOSChangeTrackerSetManifest(ct
, manifest
);
925 SOSManifestRef
SOSEngineCopyLocalPeerManifest_locked(SOSEngineRef engine
, SOSPeerRef peer
, CFErrorRef
*error
) {
926 return SOSEngineCopyManifestWithViewNameSet_locked(engine
, SOSPeerGetViewNameSet(peer
), error
);
929 #define withViewAndBackup(VIEW) do { with(VIEW); if (!isTomb) with(VIEW ## _tomb); } while(0)
932 // Invoke with once for each view an object is in.
933 // TODO: Move this function into the DataSource
934 static void SOSEngineObjectWithView(SOSEngineRef engine
, SOSObjectRef object
, void (^with
)(CFStringRef view
)) {
935 // Filter items into v0 only view here
936 SecDbItemRef item
= (SecDbItemRef
)object
; // TODO: Layer violation, breaks tests
937 if (isDictionary(object
)) {
938 CFTypeRef isTombValue
= CFDictionaryGetValue((CFDictionaryRef
)object
, kSecAttrTombstone
);
939 bool isTomb
= isTombValue
&& CFBooleanGetValue(isTombValue
);
940 // We are in the test just assume v0 and v2 views.
941 withViewAndBackup(kSOSViewKeychainV0
);
942 } else if (SecDbItemIsSyncableOrCorrupted(item
)) {
943 const SecDbClass
*iclass
= SecDbItemGetClass(item
);
944 CFTypeRef pdmn
= SecDbItemGetCachedValueWithName(item
, kSecAttrAccessible
);
945 if ((iclass
== genp_class() || iclass
== inet_class() || iclass
== keys_class() || iclass
== cert_class())
947 && (CFEqual(pdmn
, kSecAttrAccessibleWhenUnlocked
)
948 || CFEqual(pdmn
, kSecAttrAccessibleAfterFirstUnlock
)
949 || CFEqual(pdmn
, kSecAttrAccessibleAlwaysPrivate
)
950 || CFEqual(pdmn
, kSecAttrAccessibleWhenUnlockedThisDeviceOnly
)
951 || CFEqual(pdmn
, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
)
952 || CFEqual(pdmn
, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate
)))
954 CFTypeRef tomb
= SecDbItemGetCachedValueWithName(item
, kSecAttrTombstone
);
956 bool isTomb
= (isNumber(tomb
) && CFNumberGetValue(tomb
, kCFNumberCharType
, &cvalue
) && cvalue
== 1);
957 CFStringRef viewHint
= SecDbItemGetCachedValueWithName(item
, kSecAttrSyncViewHint
);
959 // check that view hint is a string, if its unset it will be kCFNull
960 if (!isString(viewHint
)) {
964 // Intecept CKKS-handled items here and short-circuit function
965 if(SOSViewHintInCKKSSystem(viewHint
)) {
969 if (viewHint
== NULL
) {
970 if (iclass
== cert_class()) {
971 withViewAndBackup(kSOSViewOtherSyncable
);
973 if (!SecDbItemGetCachedValueWithName(item
, kSecAttrTokenID
)) {
974 withViewAndBackup(kSOSViewKeychainV0
);
976 CFTypeRef agrp
= SecDbItemGetCachedValueWithName(item
, kSecAttrAccessGroup
);
977 if (iclass
== keys_class() && CFEqualSafe(agrp
, CFSTR("com.apple.security.sos"))) {
978 withViewAndBackup(kSOSViewiCloudIdentity
);
979 } else if (CFEqualSafe(agrp
, CFSTR("com.apple.cfnetwork"))) {
980 withViewAndBackup(kSOSViewAutofillPasswords
);
981 } else if (CFEqualSafe(agrp
, CFSTR("com.apple.safari.credit-cards"))) {
982 withViewAndBackup(kSOSViewSafariCreditCards
);
983 } else if (iclass
== genp_class()) {
984 if (CFEqualSafe(agrp
, CFSTR("apple")) &&
985 CFEqualSafe(SecDbItemGetCachedValueWithName(item
, kSecAttrService
), CFSTR("AirPort"))) {
986 withViewAndBackup(kSOSViewWiFi
);
987 } else if (CFEqualSafe(agrp
, CFSTR("com.apple.sbd"))) {
988 withViewAndBackup(kSOSViewBackupBagV0
);
990 withViewAndBackup(kSOSViewOtherSyncable
); // (genp)
993 withViewAndBackup(kSOSViewOtherSyncable
); // (inet || keys)
999 CFStringRef viewHintTomb
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%@-tomb"), viewHint
);
1002 CFRelease(viewHintTomb
);
1008 // TODO: general queries
1011 CFArrayForEachC(engine
->views
, view
) {
1012 bool inView
= SOSViewQueryMatchItem(view
, item
);
1014 CFStringRef viewName
= SOSViewCopyName(view
);
1016 CFReleaseSafe(viewName
);
1024 // Deliver delayed notifiations of changes in keychain
1028 SOSSendViewNotification(CFSetRef viewNotifications
)
1030 CFNotificationCenterRef center
= CFNotificationCenterGetDarwinNotifyCenter();
1032 CFSetForEach(viewNotifications
, ^(const void *value
) {
1033 secinfo("view", "Sending view notification for view %@", value
);
1035 CFStringRef str
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("com.apple.security.view-change.%@"), value
);
1039 CFNotificationCenterPostNotificationWithOptions(center
, str
, NULL
, NULL
, 0);
1046 SOSArmViewNotificationEvents(CFSetRef viewNotifications
)
1048 static CFMutableSetRef pendingViewNotifications
;
1049 static dispatch_once_t onceToken
;
1050 static dispatch_queue_t queue
;
1052 dispatch_once(&onceToken
, ^{
1053 queue
= dispatch_queue_create("ViewNotificationQueue", NULL
);
1055 if (queue
== NULL
|| CFSetGetCount(viewNotifications
) == 0)
1059 * PendingViewNotifications is only modified on queue.
1060 * PendingViewNotifications is used as a signal if a timer is running.
1062 * If a timer is running, new events are just added to the existing
1063 * pendingViewNotifications.
1066 #define DELAY_OF_NOTIFICATION_IN_NS (NSEC_PER_SEC)
1068 CFRetain(viewNotifications
);
1070 dispatch_async(queue
, ^{
1071 if (pendingViewNotifications
== NULL
) {
1072 pendingViewNotifications
= CFSetCreateMutableCopy(NULL
, 0, viewNotifications
);
1074 dispatch_after(dispatch_time(DISPATCH_TIME_NOW
, (int64_t)DELAY_OF_NOTIFICATION_IN_NS
), queue
, ^{
1075 SOSSendViewNotification(pendingViewNotifications
);
1077 // when timer hits, clear out set of modified views
1078 CFRelease(pendingViewNotifications
);
1079 pendingViewNotifications
= NULL
;
1082 CFSetUnion(pendingViewNotifications
, viewNotifications
);
1084 CFRelease(viewNotifications
);
1090 // SOSChangeMapper - Helper for SOSEngineUpdateChanges_locked
1092 struct SOSChangeMapper
{
1093 SOSEngineRef engine
;
1094 SOSTransactionRef txn
;
1095 SOSDataSourceTransactionPhase phase
;
1096 SOSDataSourceTransactionSource source
;
1097 CFMutableDictionaryRef ct2changes
;
1098 CFMutableSetRef viewNotifications
;
1101 static void SOSChangeMapperInit(struct SOSChangeMapper
*cm
, SOSEngineRef engine
, SOSTransactionRef txn
, SOSDataSourceTransactionPhase phase
, SOSDataSourceTransactionSource source
) {
1102 cm
->engine
= engine
;
1105 cm
->source
= source
;
1106 cm
->ct2changes
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1107 cm
->viewNotifications
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
);
1110 static void SOSChangeMapperSendNotifications(struct SOSChangeMapper
*cm
)
1112 SOSArmViewNotificationEvents(cm
->viewNotifications
);
1115 static void SOSChangeMapperFree(struct SOSChangeMapper
*cm
) {
1116 CFReleaseSafe(cm
->ct2changes
);
1117 CFReleaseSafe(cm
->viewNotifications
);
1120 static void SOSChangeMapperAddViewNotification(struct SOSChangeMapper
*cm
, CFStringRef view
)
1122 assert(isString(view
));
1124 // aggregate the PCS view into one notification
1125 if (CFStringHasPrefix(view
, CFSTR("PCS-"))) {
1126 view
= CFSTR("PCS");
1128 CFSetSetValue(cm
->viewNotifications
, view
);
1131 static void SOSChangeMapperAppendObject(struct SOSChangeMapper
*cm
, SOSChangeTrackerRef ct
, bool isAdd
, CFTypeRef object
) {
1132 CFMutableArrayRef changes
= (CFMutableArrayRef
)CFDictionaryGetValue(cm
->ct2changes
, ct
);
1134 changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
1135 CFDictionarySetValue(cm
->ct2changes
, ct
, changes
);
1136 CFReleaseSafe(changes
);
1138 isAdd
? SOSChangesAppendAdd(changes
, object
) : SOSChangesAppendDelete(changes
, object
);
1141 static bool SOSChangeMapperIngestChange(struct SOSChangeMapper
*cm
, bool isAdd
, CFTypeRef change
) {
1142 bool someoneCares
= false;
1143 if (isData(change
)) {
1144 // TODO: Reenable assertion once the tests have been updated
1146 // We got a digest for a deleted object. Our dataSource probably couldn't find
1147 // an object with this digest, probably because it went missing, or it was
1148 // discovered to be corrupted.
1149 // Tell all our changeTrackers about this digest since we don't know who might need it.
1150 CFDictionaryForEach(cm
->engine
->viewNameSet2ChangeTracker
, ^(const void *viewNameSet
, const void *ct
) {
1151 SOSChangeMapperAppendObject(cm
, (SOSChangeTrackerRef
)ct
, isAdd
, change
);
1153 someoneCares
= CFDictionaryGetCount(cm
->engine
->viewNameSet2ChangeTracker
);
1155 // We got an object let's figure out which views it's in and schedule it for
1156 // delivery to all changeTrackers interested in any of those views.
1157 SOSObjectRef object
= (SOSObjectRef
)change
;
1158 CFMutableSetRef changeTrackerSet
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
);
1159 // First gather all the changeTrackers interested in this object (eliminating dupes by collecting them in a set)
1160 SOSEngineObjectWithView(cm
->engine
, object
, ^(CFStringRef viewName
) {
1161 const void *ctorset
= CFDictionaryGetValue(cm
->engine
->viewName2ChangeTracker
, viewName
);
1162 if (isSet(ctorset
)) {
1163 CFSetForEach((CFSetRef
)ctorset
, ^(const void *ct
) { CFSetAddValue(changeTrackerSet
, ct
); });
1164 } else if (ctorset
) {
1165 CFSetAddValue(changeTrackerSet
, ctorset
);
1169 SOSChangeMapperAddViewNotification(cm
, viewName
);
1171 // Then append the object to the changes array in the ct2changes dictionary keyed by viewSet
1172 CFSetForEach(changeTrackerSet
, ^(const void *ct
) {
1173 SOSChangeMapperAppendObject(cm
, (SOSChangeTrackerRef
)ct
, isAdd
, object
);
1175 someoneCares
= CFSetGetCount(changeTrackerSet
);
1176 CFReleaseSafe(changeTrackerSet
);
1178 return someoneCares
;
1181 static bool SOSChangeMapperSend(struct SOSChangeMapper
*cm
, CFErrorRef
*error
) {
1182 __block
bool ok
= true;
1183 CFDictionaryForEach(cm
->ct2changes
, ^(const void *ct
, const void *changes
) {
1184 ok
&= SOSChangeTrackerTrackChanges((SOSChangeTrackerRef
)ct
, cm
->engine
, cm
->txn
, cm
->source
, cm
->phase
, (CFArrayRef
)changes
, error
);
1189 static bool SOSEngineUpdateChanges_locked(SOSEngineRef engine
, SOSTransactionRef txn
, SOSDataSourceTransactionPhase phase
, SOSDataSourceTransactionSource source
, CFArrayRef changes
, CFErrorRef
*error
)
1191 secnoticeq("engine", "%@: %s %s %ld changes, txn=%@, %p", engine
->myID
, phase
== kSOSDataSourceTransactionWillCommit
? "will-commit" : phase
== kSOSDataSourceTransactionDidCommit
? "did-commit" : "did-rollback",
1192 source
== kSOSDataSourceSOSTransaction
? "sos" :
1193 source
== kSOSDataSourceCKKSTransaction
? "ckks" :
1194 source
== kSOSDataSourceAPITransaction
? "api" :
1196 CFArrayGetCount(changes
), txn
, txn
);
1199 case kSOSDataSourceTransactionDidRollback
:
1200 ok
&= SOSEngineLoad(engine
, txn
, error
);
1202 case kSOSDataSourceTransactionDidCommit
: // Corruption causes us to process items at DidCommit
1203 case kSOSDataSourceTransactionWillCommit
:
1205 bool mappedItemChanged
= false;
1207 struct SOSChangeMapper cm
;
1208 SOSChangeMapperInit(&cm
, engine
, txn
, phase
, source
);
1209 SecDbEventRef event
;
1210 CFArrayForEachC(changes
, event
) {
1211 CFTypeRef deleted
= NULL
;
1212 CFTypeRef inserted
= NULL
;
1213 SecDbEventGetComponents(event
, &deleted
, &inserted
, error
);
1215 bool someoneCares
= SOSChangeMapperIngestChange(&cm
, false, deleted
);
1217 mappedItemChanged
= true;
1221 bool someoneCares
= SOSChangeMapperIngestChange(&cm
, true, inserted
);
1223 mappedItemChanged
= true;
1225 if (!someoneCares
&& !isData(inserted
) && SecDbItemIsTombstone((SecDbItemRef
)inserted
) && !CFEqualSafe(SecDbItemGetValue((SecDbItemRef
)inserted
, &v7utomb
, NULL
), kCFBooleanTrue
)) {
1226 CFErrorRef localError
= NULL
;
1227 // A tombstone was inserted but there is no changetracker that
1229 if (!SecDbItemDoDeleteSilently((SecDbItemRef
)inserted
, (SecDbConnectionRef
)txn
, &localError
)) {
1230 secerror("failed to delete tombstone %@ that no one cares about: %@", inserted
, localError
);
1231 CFReleaseNull(localError
);
1237 ok
&= SOSChangeMapperSend(&cm
, error
);
1238 SOSChangeMapperSendNotifications(&cm
); // Trigger notifications for view that changes changed
1239 SOSChangeMapperFree(&cm
);
1241 if (ok
&& phase
== kSOSDataSourceTransactionWillCommit
) {
1242 // Only consider writing if we're in the WillCommit phase.
1243 // DidCommit phases happen outside the database lock and
1244 // writing to the DBConn will cause deadlocks.
1245 if (mappedItemChanged
|| source
== kSOSDataSourceSOSTransaction
) {
1246 // Write SOSEngine and SOSPeer state to disk
1248 if(!SecCKKSTestDisableSOS()) {
1250 secnotice("engine", "saving engine state");
1251 ok
&= SOSEngineSave(engine
, txn
, error
);
1253 if (kSOSDataSourceAPITransaction
== source
|| kSOSDataSourceCKKSTransaction
== source
)
1254 SOSCCRequestSyncWithPeersList(engine
->peerIDs
);
1259 secinfo("engine", "Not saving engine state, nothing changed.");
1269 static void SOSEngineSetNotifyPhaseBlock(SOSEngineRef engine
) {
1270 SOSDataSourceAddNotifyPhaseBlock(engine
->dataSource
, ^(SOSDataSourceRef ds
, SOSTransactionRef txn
, SOSDataSourceTransactionPhase phase
, SOSDataSourceTransactionSource source
, CFArrayRef changes
) {
1271 dispatch_sync(engine
->queue
, ^{
1272 CFErrorRef localError
= NULL
;
1273 if (!SOSEngineUpdateChanges_locked(engine
, txn
, phase
, source
, changes
, &localError
)) {
1274 secerror("updateChanged failed: %@", localError
);
1276 CFReleaseSafe(localError
);
1281 static SOSChangeTrackerRef
SOSReferenceAndGetChangeTracker(CFDictionaryRef lookup
, CFMutableDictionaryRef referenced
, CFSetRef viewNameSet
) {
1282 SOSChangeTrackerRef ct
= (SOSChangeTrackerRef
)CFDictionaryGetValue(referenced
, viewNameSet
);
1284 ct
= (SOSChangeTrackerRef
)CFDictionaryGetValue(lookup
, viewNameSet
);
1286 SOSChangeTrackerResetRegistration(ct
);
1287 CFDictionarySetValue(referenced
, viewNameSet
, ct
);
1289 ct
= SOSChangeTrackerCreate(kCFAllocatorDefault
, false, NULL
, NULL
);
1290 CFDictionarySetValue(referenced
, viewNameSet
, ct
);
1297 static void CFStringAppendPeerIDAndViews(CFMutableStringRef desc
, CFStringRef peerID
, CFSetRef vns
) {
1298 CFStringSetPerformWithDescription(vns
, ^(CFStringRef description
) {
1299 CFStringAppendFormat(desc
, NULL
, CFSTR(" %@ (%@)"), peerID
, description
);
1303 // Must be called after updating viewNameSet2ChangeTracker
1304 static void SOSEngineUpdateViewName2ChangeTracker(SOSEngineRef engine
) {
1305 // Create the mapping from viewName -> ChangeTracker used for lookup during change notification
1306 CFMutableDictionaryRef newViewName2ChangeTracker
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1307 CFDictionaryForEach(engine
->viewNameSet2ChangeTracker
, ^(const void *viewNameSet
, const void *ct
) {
1308 CFSetForEach(viewNameSet
, ^(const void *viewName
) {
1309 const void *ctorset
= NULL
;
1310 if (CFDictionaryGetValueIfPresent(newViewName2ChangeTracker
, viewName
, &ctorset
)) {
1311 if (isSet(ctorset
)) {
1312 CFSetAddValue((CFMutableSetRef
)ctorset
, ct
);
1313 } else if (!CFEqual(ct
, ctorset
)) {
1314 CFMutableSetRef set
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
);
1315 CFSetAddValue(set
, ctorset
);
1316 CFSetAddValue(set
, ct
);
1317 CFDictionaryReplaceValue(newViewName2ChangeTracker
, viewName
, set
);
1321 CFDictionarySetValue(newViewName2ChangeTracker
, viewName
, ct
);
1325 CFAssignRetained(engine
->viewName2ChangeTracker
, newViewName2ChangeTracker
);
1328 static void SOSEngineSetBackupBag(SOSEngineRef engine
, SOSObjectRef bagItem
);
1330 // This is called only if we are in a circle and we should listen for keybag changes
1331 static void SOSEngineRegisterBackupBagV0Tracker(SOSEngineRef engine
, CFMutableDictionaryRef newViewNameSet2ChangeTracker
, CFMutableStringRef desc
) {
1332 SOSChangeTrackerRef bbct
= SOSReferenceAndGetChangeTracker(engine
->viewNameSet2ChangeTracker
, newViewNameSet2ChangeTracker
, SOSViewsGetV0BackupBagViewSet());
1333 SOSChangeTrackerRegisterChangeUpdate(bbct
, ^bool(SOSChangeTrackerRef ct
, SOSEngineRef engine
, SOSTransactionRef txn
, SOSDataSourceTransactionSource source
, SOSDataSourceTransactionPhase phase
, CFArrayRef changes
, CFErrorRef
*error
) {
1334 SOSChangeRef change
;
1335 CFArrayForEachC(changes
, change
) {
1336 CFTypeRef object
= NULL
;
1337 bool isAdd
= SOSChangeGetObject(change
, &object
);
1338 SecDbItemRef dbi
= (SecDbItemRef
)object
;
1339 if (!isData(object
) &&
1340 CFEqualSafe(SecDbItemGetCachedValueWithName(dbi
, kSecAttrService
), CFSTR("SecureBackupService")) &&
1341 CFEqualSafe(SecDbItemGetCachedValueWithName(dbi
, kSecAttrAccessible
), kSecAttrAccessibleWhenUnlocked
) &&
1342 CFEqualSafe(SecDbItemGetCachedValueWithName(dbi
, kSecAttrAccount
), CFSTR("SecureBackupPublicKeybag"))) {
1343 SOSEngineSetBackupBag(engine
, isAdd
? (SOSObjectRef
)object
: NULL
);
1350 static void SOSEngineReferenceBackupPeer(SOSEngineRef engine
, CFStringRef peerID
, CFSetRef viewNameSet
, CFDataRef keyBag
, CFMutableDictionaryRef newViewNameSet2ChangeTracker
, CFMutableDictionaryRef newPeerMap
) {
1351 CFTypeRef oldEntry
= CFDictionaryGetValue(engine
->peerMap
, peerID
);
1352 CFTypeRef newEntry
= SOSPeerOrStateSetViewsKeyBagAndCreateCopy(oldEntry
, viewNameSet
, keyBag
);
1354 if (isDictionary(newEntry
)) {
1355 // Backup peers, are always inflated
1356 CFAssignRetained(newEntry
, SOSPeerCreateWithState(engine
, peerID
, newEntry
, NULL
));
1357 // If !oldEntry this is an edge (first creation of a peer).
1359 SOSPeerKeyBagDidChange((SOSPeerRef
)newEntry
);
1362 CFDictionarySetValue(newPeerMap
, peerID
, newEntry
);
1363 CFRelease(newEntry
);
1366 SOSChangeTrackerRef ct
= SOSReferenceAndGetChangeTracker(engine
->viewNameSet2ChangeTracker
, newViewNameSet2ChangeTracker
, viewNameSet
);
1368 SOSChangeTrackerUpdatesChanges child
= Block_copy(^bool(SOSChangeTrackerRef ct
, SOSEngineRef engine
, SOSTransactionRef txn
, SOSDataSourceTransactionSource source
, SOSDataSourceTransactionPhase phase
, CFArrayRef changes
, CFErrorRef
*error
) {
1369 return SOSPeerDataSourceWillChange((SOSPeerRef
)newEntry
, SOSEngineGetDataSource(engine
), source
, changes
, error
);
1372 SOSChangeTrackerRegisterChangeUpdate(ct
, child
);
1373 Block_release(child
);
1378 static void SOSEngineReferenceSyncPeer(SOSEngineRef engine
, CFStringRef peerID
, CFSetRef viewNameSet
, CFMutableDictionaryRef newViewNameSet2ChangeTracker
, CFMutableDictionaryRef newPeerMap
) {
1379 CFTypeRef newEntry
= SOSPeerOrStateSetViewsKeyBagAndCreateCopy(CFDictionaryGetValue(engine
->peerMap
, peerID
), viewNameSet
, NULL
);
1381 SOSChangeTrackerRef ct
= SOSReferenceAndGetChangeTracker(engine
->viewNameSet2ChangeTracker
, newViewNameSet2ChangeTracker
, viewNameSet
);
1382 // Standard peer, inflated on demand
1383 SOSChangeTrackerUpdatesManifests trackManifest
;
1384 if (isDictionary(newEntry
)) {
1385 // Uninflated peer, inflate on first notification.
1386 trackManifest
= ^bool(SOSChangeTrackerRef ct
, SOSEngineRef engine
, SOSTransactionRef txn
, SOSDataSourceTransactionSource source
, SOSDataSourceTransactionPhase phase
, SOSManifestRef removals
, SOSManifestRef additions
, CFErrorRef
*error
) {
1387 CFErrorRef localError
= NULL
;
1388 SOSPeerRef peer
= SOSEngineCopyPeerWithID_locked(engine
, peerID
, &localError
);
1391 secerror("%@: peer failed to inflate: %@", peerID
, localError
);
1392 CFReleaseSafe(localError
);
1395 ok
= SOSPeerDataSourceWillCommit(peer
, source
, removals
, additions
, error
);
1397 CFReleaseSafe(peer
);
1401 // Inflated peer, just forward the changes to the peer
1402 trackManifest
= ^bool(SOSChangeTrackerRef ct
, SOSEngineRef engine
, SOSTransactionRef txn
, SOSDataSourceTransactionSource source
, SOSDataSourceTransactionPhase phase
, SOSManifestRef removals
, SOSManifestRef additions
, CFErrorRef
*error
) {
1403 return SOSPeerDataSourceWillCommit((SOSPeerRef
)newEntry
, source
, removals
, additions
, error
);
1406 SOSChangeTrackerUpdatesManifests trackManifestCopy
= Block_copy(trackManifest
);
1407 SOSChangeTrackerRegisterManifestUpdate(ct
, trackManifestCopy
);
1408 Block_release(trackManifestCopy
);
1410 CFDictionarySetValue(newPeerMap
, peerID
, newEntry
);
1411 CFRelease(newEntry
);
1416 static void SOSEngineReferenceTrustedPeer(SOSEngineRef engine
, SOSPeerMetaRef peerMeta
, CFMutableDictionaryRef newViewNameSet2ChangeTracker
, CFMutableDictionaryRef newPeerMap
, CFMutableArrayRef peerIDs
, CFMutableStringRef desc
) {
1417 CFSetRef viewNameSet
= NULL
;
1418 CFDataRef keyBag
= NULL
;
1419 CFStringRef peerID
= SOSPeerMetaGetComponents(peerMeta
, &viewNameSet
, &keyBag
, NULL
);
1420 // We trust peerID so append it to peerIDs
1421 CFArrayAppendValue(peerIDs
, peerID
);
1422 if (desc
) CFStringAppendPeerIDAndViews(desc
, peerID
, viewNameSet
);
1423 // Update the viewNameSet for this peer, to appease tests, default to a viewset of the V0 view.
1425 viewNameSet
= SOSViewsGetV0ViewSet();
1427 // Always inflate backup peers, since they need to register with their changeTrackers right away.
1429 SOSEngineReferenceBackupPeer(engine
, peerID
, viewNameSet
, keyBag
, newViewNameSet2ChangeTracker
, newPeerMap
);
1431 SOSEngineReferenceSyncPeer(engine
, peerID
, viewNameSet
, newViewNameSet2ChangeTracker
, newPeerMap
);
1435 static CFDataRef
SOSEngineCopyV0KeyBag(SOSEngineRef engine
, CFErrorRef
*error
) {
1436 // Return the keybag for the given peerID.
1439 kSecAttrAccessGroup ==> CFSTR("com.apple.sbd")
1440 kSecAttrAccessible ==> kSecAttrAccessibleWhenUnlocked
1441 kSecAttrAccount ==> CFSTR("SecureBackupPublicKeybag")
1442 kSecAttrService ==> CFSTR("SecureBackupService")
1445 CFMutableDictionaryRef keys
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
1446 kSecAttrAccessGroup
, CFSTR("com.apple.sbd"),
1447 kSecAttrAccount
, CFSTR("SecureBackupPublicKeybag"),
1448 kSecAttrService
, CFSTR("SecureBackupService"),
1449 kSecAttrAccessible
, kSecAttrAccessibleWhenUnlocked
,
1450 kSecAttrSynchronizable
, kCFBooleanTrue
,
1453 CFDataRef keybag
= engine
->dataSource
->dsCopyItemDataWithKeys(engine
->dataSource
, keys
, error
);
1454 CFReleaseSafe(keys
);
1459 static void SOSEngineReferenceBackupV0Peer(SOSEngineRef engine
, CFMutableDictionaryRef newViewNameSet2ChangeTracker
, CFMutableDictionaryRef newPeerMap
, CFMutableArrayRef newPeerIDs
, CFMutableStringRef desc
) {
1460 SOSPeerRef backupPeer
= (SOSPeerRef
)CFDictionaryGetValue(engine
->peerMap
, kSOSViewKeychainV0_tomb
);
1461 CFDataRef bag
= NULL
;
1462 if (backupPeer
&& CFGetTypeID(backupPeer
) == SOSPeerGetTypeID()) {
1463 bag
= CFRetainSafe(SOSPeerGetKeyBag(backupPeer
));
1465 CFErrorRef localError
= NULL
;
1466 bag
= SOSEngineCopyV0KeyBag(engine
, &localError
);
1468 secnotice("engine", "No keybag found for v0 backup peer: %@", localError
);
1469 CFReleaseSafe(localError
);
1472 SOSEngineReferenceBackupPeer(engine
, kSOSViewKeychainV0_tomb
, SOSViewsGetV0BackupViewSet(), bag
, newViewNameSet2ChangeTracker
, newPeerMap
);
1476 static void SOSEngineReferenceTrustedPeers(SOSEngineRef engine
, CFMutableDictionaryRef newViewNameSet2ChangeTracker
, CFMutableDictionaryRef newPeerMap
, CFMutableArrayRef newPeerIDs
, CFArrayRef trustedPeerMetas
, CFMutableStringRef desc
) {
1477 // Then update the views for all trusted peers and add them to newPeerMap.
1478 if (trustedPeerMetas
!= NULL
&& CFArrayGetCount(trustedPeerMetas
) != 0) {
1479 if (desc
) CFStringAppend(desc
, CFSTR(" trusted"));
1480 // Remake engine->peerIDs
1481 SOSPeerMetaRef peerMeta
;
1482 CFArrayForEachC(trustedPeerMetas
, peerMeta
) {
1483 SOSEngineReferenceTrustedPeer(engine
, peerMeta
, newViewNameSet2ChangeTracker
, newPeerMap
, newPeerIDs
, desc
);
1488 static void SOSEngineReferenceUntrustedPeers(SOSEngineRef engine
, CFMutableDictionaryRef newPeerMap
, CFArrayRef untrustedPeerMetas
, CFMutableStringRef description
) {
1489 // Copy any untrustedPeers to newPeerMap as well if we have a state
1490 // for them, if not no big deal. We also serialize all the untrustedPeers
1491 // since they don't need to be deserializable
1492 if (untrustedPeerMetas
!= NULL
&& CFArrayGetCount(untrustedPeerMetas
) != 0) {
1493 if (description
) CFStringAppend(description
, CFSTR(" untrusted"));
1494 SOSPeerMetaRef peerMeta
;
1495 CFArrayForEachC(untrustedPeerMetas
, peerMeta
) {
1496 CFSetRef views
= NULL
;
1497 CFStringRef peerID
= SOSPeerMetaGetComponents(peerMeta
, &views
, NULL
, NULL
);
1498 if (description
) CFStringAppendPeerIDAndViews(description
, peerID
, views
);
1499 CFSetRef nviews
= NULL
;
1501 views
= nviews
= CFSetCreate(kCFAllocatorDefault
, NULL
, 0, &kCFTypeSetCallBacks
);
1502 CFTypeRef newEntry
= SOSPeerOrStateSetViewsAndCopyState(CFDictionaryGetValue(engine
->peerMap
, peerID
), views
);
1503 CFReleaseSafe(nviews
);
1505 CFDictionarySetValue(newPeerMap
, peerID
, newEntry
);
1506 CFReleaseSafe(newEntry
);
1512 static void SOSEngineReferenceChangeTrackers(SOSEngineRef engine
, CFArrayRef trustedPeerMetas
, CFArrayRef untrustedPeerMetas
, CFMutableStringRef desc
) {
1513 CFMutableArrayRef newPeerIDs
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
1514 CFMutableDictionaryRef newPeerMap
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1515 CFMutableDictionaryRef newViewNameSet2ChangeTracker
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1518 // We have an engineID => in a circle (with 0 or more peers)
1519 // Ensure we have a v0 backup peer and it's listening for backup bag changes
1520 SOSEngineReferenceBackupV0Peer(engine
, newViewNameSet2ChangeTracker
, newPeerMap
, newPeerIDs
, desc
);
1521 SOSEngineRegisterBackupBagV0Tracker(engine
, newViewNameSet2ChangeTracker
, desc
);
1523 SOSEngineReferenceTrustedPeers(engine
, newViewNameSet2ChangeTracker
, newPeerMap
, newPeerIDs
, trustedPeerMetas
, desc
);
1524 SOSEngineReferenceUntrustedPeers(engine
, newPeerMap
, untrustedPeerMetas
, desc
);
1526 CFAssignRetained(engine
->peerIDs
, newPeerIDs
);
1527 CFAssignRetained(engine
->peerMap
, newPeerMap
);
1528 CFAssignRetained(engine
->viewNameSet2ChangeTracker
, newViewNameSet2ChangeTracker
);
1529 SOSEngineUpdateViewName2ChangeTracker(engine
);
1532 // Return true iff peers or views changed
1533 static bool SOSEngineSetPeers_locked(SOSEngineRef engine
, SOSPeerMetaRef myPeerMeta
, CFArrayRef trustedPeerMetas
, CFArrayRef untrustedPeerMetas
) {
1534 CFErrorRef error
= NULL
;
1535 CFSetRef myViews
= NULL
;
1536 CFDataRef myKeyBag
= NULL
;
1537 CFMutableStringRef desc
= CFStringCreateMutableCopy(kCFAllocatorDefault
, 0, CFSTR("me"));
1538 CFStringRef myPeerID
= myPeerMeta
? SOSPeerMetaGetComponents(myPeerMeta
, &myViews
, &myKeyBag
, &error
) : NULL
;
1539 if (desc
) CFStringAppendPeerIDAndViews(desc
, myPeerID
, myViews
);
1541 // Start with no coders
1542 CFMutableDictionaryRef codersToKeep
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1544 if(engine
->haveLoadedCoders
){
1545 // If we're the same peerID we keep known peers (both trusted and untrusted)
1546 if (CFEqualSafe(myPeerID
, engine
->myID
)) {
1547 void (^copyPeerMetasCoder
)(const void *value
) = ^(const void*element
) {
1548 SOSPeerMetaRef peerMeta
= (SOSPeerMetaRef
) element
;
1550 CFStringRef currentID
= SOSPeerMetaGetComponents(peerMeta
, NULL
, NULL
, NULL
);
1552 SOSCoderRef coder
= (SOSCoderRef
) CFDictionaryGetValue(engine
->coders
, currentID
);
1554 CFDictionarySetValue(codersToKeep
, currentID
, coder
);
1559 if (trustedPeerMetas
) {
1560 CFArrayForEach(trustedPeerMetas
, copyPeerMetasCoder
);
1562 if (untrustedPeerMetas
) {
1563 CFArrayForEach(untrustedPeerMetas
, copyPeerMetasCoder
);
1567 engine
->codersNeedSaving
= true;
1569 CFRetainAssign(engine
->myID
, myPeerID
);
1570 CFTransferRetained(engine
->coders
, codersToKeep
);
1572 // Remake engine->peerMap from both trusted and untrusted peers
1573 SOSEngineReferenceChangeTrackers(engine
, trustedPeerMetas
, untrustedPeerMetas
, desc
);
1575 secnotice("engine", "%@", desc
);
1576 CFReleaseSafe(desc
);
1580 static void SOSEngineApplyPeerState(SOSEngineRef engine
, CFDictionaryRef peerStateMap
) {
1581 if (peerStateMap
) CFDictionaryForEach(peerStateMap
, ^(const void *peerID
, const void *peerState
) {
1582 CFTypeRef mapEntry
= CFDictionaryGetValue(engine
->peerMap
, peerID
);
1583 if (mapEntry
&& CFGetTypeID(mapEntry
) == SOSPeerGetTypeID()) {
1584 // Update the state of any already inflated peers
1585 SOSPeerRef peer
= (SOSPeerRef
)mapEntry
;
1586 CFErrorRef localError
= NULL
;
1587 if (!SOSPeerSetState(peer
, engine
, peerState
, &localError
)) {
1588 CFStringRef stateHex
= NULL
;
1589 stateHex
= CFDataCopyHexString(peerState
);
1590 secerror("peer: %@: bad state: %@ in engine state: %@", peerID
, localError
, stateHex
);
1591 CFReleaseSafe(stateHex
);
1592 CFReleaseNull(localError
);
1593 // Possibly ask for an ensurePeerRegistration so we have a good list of peers again.
1596 // Just record the state for non inflated peers for now.
1597 CFDictionarySetValue(engine
->peerMap
, peerID
, peerState
);
1602 static void SOSEngineSynthesizePeerMetas(SOSEngineRef engine
, CFMutableArrayRef trustedPeersMetas
, CFMutableArrayRef untrustedPeers
) {
1603 CFSetRef trustedPeerSet
= engine
->peerIDs
? CFSetCreateCopyOfArrayForCFTypes(engine
->peerIDs
) : NULL
;
1604 CFDictionaryForEach(engine
->peerMap
, ^(const void *peerID
, const void *peerState
) {
1605 SOSPeerMetaRef meta
= NULL
;
1606 if (peerState
&& CFGetTypeID(peerState
) == SOSPeerGetTypeID()) {
1607 SOSPeerRef peer
= (SOSPeerRef
)peerState
;
1608 meta
= SOSPeerMetaCreateWithComponents(peerID
, SOSPeerGetViewNameSet(peer
), SOSPeerGetKeyBag(peer
));
1610 // We don't need to add the meta for the backup case, since
1611 // SOSEngineReferenceBackupV0Peer will do the right thing
1612 if (!CFEqualSafe(peerID
, kSOSViewKeychainV0_tomb
)) {
1613 meta
= SOSPeerMetaCreateWithState(peerID
, peerState
);
1616 // Any peer in peerStateMap that is not in trustedPeers is an untrustedPeer unless it's the v0 backup peer
1617 if ((trustedPeerSet
&& CFSetContainsValue(trustedPeerSet
, peerID
)) || CFEqualSafe(peerID
, kSOSViewKeychainV0_tomb
)) {
1619 CFArrayAppendValue(trustedPeersMetas
, meta
);
1622 CFArrayAppendValue(untrustedPeers
, peerID
);
1624 CFReleaseNull(meta
);
1626 CFReleaseNull(trustedPeerSet
);
1629 static void SOSEngineSetBackupBag(SOSEngineRef engine
, SOSObjectRef bagItem
) {
1630 CFMutableStringRef desc
= NULL
;
1631 SOSPeerRef backupPeer
= SOSEngineCopyPeerWithID_locked(engine
, kSOSViewKeychainV0_tomb
, NULL
);
1632 CFDataRef keybag
= NULL
;
1634 keybag
= SecDbItemGetValue((SecDbItemRef
)bagItem
, &v6v_Data
, NULL
);
1637 // Since SOSPeerSetKeyBag() doesn't notify on the edge from NULL->initial keybag, since
1638 // that is the right behaviour for non v0 backup peers, we need to do it here for the v0 peer.
1639 bool hadBag
= SOSPeerGetKeyBag(backupPeer
);
1640 SOSPeerSetKeyBag(backupPeer
, keybag
);
1642 SOSPeerKeyBagDidChange(backupPeer
);
1644 CFReleaseSafe(backupPeer
);
1646 CFMutableArrayRef untrustedPeerMetas
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
1647 CFMutableArrayRef trustedPeersMetas
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
1648 SOSEngineSynthesizePeerMetas(engine
, trustedPeersMetas
, untrustedPeerMetas
);
1649 SOSEngineReferenceChangeTrackers(engine
, trustedPeersMetas
, untrustedPeerMetas
, desc
);
1650 CFReleaseSafe(trustedPeersMetas
);
1651 CFReleaseSafe(untrustedPeerMetas
);
1654 static bool SOSEngineCircleChanged_locked(SOSEngineRef engine
, SOSPeerMetaRef myPeerMeta
, CFArrayRef trustedPeers
, CFArrayRef untrustedPeers
) {
1655 // Sanity check params
1656 // SOSEngineCircleChanged_sanitycheck(engine, myPeerID, trustedPeers, untrustedPeers);
1658 // Transform from SOSPeerInfoRefs to CFDictionaries with the info we want per peer.
1659 // Or, Tell the real SOSPeerRef what the SOSPeerInfoRef is and have it copy out the data it needs.
1660 bool peersOrViewsChanged
= SOSEngineSetPeers_locked(engine
, myPeerMeta
, trustedPeers
, untrustedPeers
);
1662 // Run though all peers and only cache manifests for peers we still have
1663 CFErrorRef localError
= NULL
;
1664 if (!SOSEngineGCPeerState_locked(engine
, &localError
)) {
1665 secerror("SOSEngineGCPeerState_locked failed: %@", localError
);
1666 CFReleaseNull(localError
);
1668 return peersOrViewsChanged
;
1671 // Initialize the engine if a load fails. Basically this is our first time setup
1672 static bool SOSEngineInit(SOSEngineRef engine
, CFErrorRef
*error
) {
1674 secnotice("engine", "new engine for datasource named %@", SOSDataSourceGetName(engine
->dataSource
));
1675 CFAssignRetained(engine
->peerMap
, CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
));
1676 CFAssignRetained(engine
->viewNameSet2ChangeTracker
, CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
));
1677 CFAssignRetained(engine
->viewName2ChangeTracker
, CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
));
1678 CFReleaseNull(engine
->manifestCache
);
1679 CFReleaseNull(engine
->peerIDs
);
1680 // TODO: We shouldn't need to load the backup bag if there was no engine
1681 // state (load failed), since that means there was no circle nor were we an applicant.
1683 // Set up change trackers so we know when a backup peer needs to be created?
1684 // no, since myID is not set, we are not in a circle, so no need to back up
1685 SOSEngineSetPeers_locked(engine
, NULL
, NULL
, NULL
);
1689 // Called by our DataSource in its constructor
1690 SOSEngineRef
SOSEngineCreate(SOSDataSourceRef dataSource
, CFErrorRef
*error
) {
1691 SOSEngineRef engine
= NULL
;
1692 engine
= CFTypeAllocate(SOSEngine
, struct __OpaqueSOSEngine
, kCFAllocatorDefault
);
1693 engine
->dataSource
= dataSource
;
1694 engine
->queue
= dispatch_queue_create("engine", DISPATCH_QUEUE_SERIAL
);
1696 engine
->peerMap
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1697 engine
->viewNameSet2ChangeTracker
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1698 engine
->viewName2ChangeTracker
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1699 //engine->syncCompleteQueue = NULL;
1700 engine
->syncCompleteListener
= NULL
;
1701 engine
->coders
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
1702 engine
->haveLoadedCoders
= false;
1703 engine
->codersNeedSaving
= false;
1705 CFErrorRef engineError
= NULL
;
1706 if (!SOSEngineLoad(engine
, NULL
, &engineError
)) {
1707 secwarning("engine failed load state starting with nothing %@", engineError
);
1708 CFReleaseNull(engineError
);
1709 if (!SOSEngineInit(engine
, error
)) {
1710 secerror("engine failed to initialze %@ giving up", error
? *error
: NULL
);
1713 SOSEngineSetNotifyPhaseBlock(engine
);
1717 // --- Called from off the queue, need to move to on the queue
1719 static void SOSEngineDoOnQueue(SOSEngineRef engine
, dispatch_block_t action
)
1721 dispatch_sync(engine
->queue
, action
);
1724 static bool SOSEngineDoTxnOnQueue(SOSEngineRef engine
, CFErrorRef
*error
, void(^transaction
)(SOSTransactionRef txn
, bool *commit
))
1726 return SOSDataSourceWithCommitQueue(engine
->dataSource
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
1727 SOSEngineDoOnQueue(engine
, ^{ transaction(txn
, commit
); });
1732 // MARK: SOSEngine API
1735 void SOSEngineDispose(SOSEngineRef engine
) {
1736 // NOOP Engines stick around forever to monitor dataSource changes.
1737 engine
->dataSource
= NULL
;
1738 CFReleaseNull(engine
->coders
);
1741 void SOSEngineForEachPeer(SOSEngineRef engine
, void (^with
)(SOSPeerRef peer
)) {
1742 SOSEngineDoOnQueue(engine
, ^{
1743 SOSEngineForEachPeer_locked(engine
, with
);
1747 static void SOSEngineForEachBackupPeer(SOSEngineRef engine
, void (^with
)(SOSPeerRef peer
)) {
1748 SOSEngineDoOnQueue(engine
, ^{
1749 SOSEngineForEachBackupPeer_locked(engine
, with
);
1753 static void SOSEngineForBackupPeer(SOSEngineRef engine
, CFStringRef backupPeerID
, void (^with
)(SOSPeerRef peer
)) {
1754 SOSEngineDoOnQueue(engine
, ^{
1755 SOSEngineForBackupPeer_locked(engine
, backupPeerID
, with
);
1759 static const CFStringRef kSecADSecurityNewItemSyncTimeKey
= CFSTR("com.apple.security.secureobjectsync.itemtime.new");
1760 static const CFStringRef kSecADSecurityKnownItemSyncTimeKey
= CFSTR("com.apple.security.secureobjectsync.itemtime.known");
1763 static void ReportItemSyncTime(SOSDataSourceRef ds
, bool known
, SOSObjectRef object
)
1765 CFDateRef itemModDate
= SOSObjectCopyModificationDate(ds
, object
, NULL
);
1767 CFAbsoluteTime syncTime
= 0;
1768 CFAbsoluteTime now
= CFAbsoluteTimeGetCurrent();
1770 CFAbsoluteTime peerModificationAbsoluteTime
= CFDateGetAbsoluteTime(itemModDate
);
1771 if (peerModificationAbsoluteTime
> now
) {
1772 syncTime
= now
- peerModificationAbsoluteTime
;
1775 SecCoreAnalyticsSendValue(known
? kSecADSecurityKnownItemSyncTimeKey
: kSecADSecurityNewItemSyncTimeKey
,
1776 SecBucket2Significant(syncTime
));
1778 CFReleaseNull(itemModDate
);
1781 /* Handle incoming message from peer p. Return false if there was an error, true otherwise. */
1782 bool SOSEngineHandleMessage_locked(SOSEngineRef engine
, CFStringRef peerID
, SOSMessageRef message
,
1783 SOSTransactionRef txn
, bool *commit
, bool *somethingChanged
, CFErrorRef
*error
) {
1784 SOSPeerRef peer
= SOSEngineCopyPeerWithID_locked(engine
, peerID
, error
);
1785 if (!peer
) return false;
1787 CFStringRef peerDesc
= NULL
;
1788 SOSManifestRef localManifest
= NULL
;
1789 SOSManifestRef allAdditions
= NULL
;
1790 SOSManifestRef unwanted
= NULL
;
1791 SOSManifestRef confirmed
= NULL
;
1792 SOSManifestRef base
= NULL
;
1793 SOSManifestRef confirmedRemovals
= NULL
, confirmedAdditions
= NULL
;
1794 __block
struct SOSDigestVector receivedObjects
= SOSDigestVectorInit
;
1795 __block
struct SOSDigestVector unwantedObjects
= SOSDigestVectorInit
;
1797 // Check for unknown criticial extensions in the message, and handle
1798 // any other extensions we support
1799 __block
bool ok
= true;
1800 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
1802 require_action_quiet(peer
, exit
, ok
= SOSErrorCreate(errSecParam
, error
, NULL
, CFSTR("Couldn't create peer with Engine for %@"), peerID
));
1803 peerDesc
= CFCopyDescription(peer
);
1805 bool hadBeenInSyncAtStart
= SOSPeerHasBeenInSync(peer
);
1807 SOSMessageWithExtensions(message
, true, ^(CFDataRef oid
, bool isCritical
, CFDataRef extension
, bool *stop
) {
1808 // OMFG a Critical extension what shall I do!
1809 ok
= SOSErrorCreate(kSOSErrorNotReady
, error
, NULL
, CFSTR("Unknown criticial extension in peer message"));
1812 require_quiet(ok
, exit
);
1814 // Merge Objects from the message into our DataSource.
1815 // Should we move the transaction to the SOSAccount level?
1816 // TODO: Filter incoming objects
1817 //if (!SOSDataSourceForEachObjectInViewSet(engine->dataSource, pendingObjects, SOSPeerGetViewNameSet(peer), error, ^void(CFDataRef key, SOSObjectRef object, bool *stop) {
1818 require_quiet(ok
&= SOSMessageWithSOSObjects(message
, engine
->dataSource
, error
, ^(SOSObjectRef peersObject
, bool *stop
) {
1819 CFDataRef digest
= SOSObjectCopyDigest(engine
->dataSource
, peersObject
, error
);
1823 secerror("%@ peer sent bad object: %@, rolling back changes", SOSPeerGetID(peer
), error
? *error
: NULL
);
1826 SOSDigestVectorAppend(&receivedObjects
, CFDataGetBytePtr(digest
));
1827 SOSObjectRef mergedObject
= NULL
;
1828 SOSMergeResult mr
= SOSDataSourceMergeObject(engine
->dataSource
, txn
, peersObject
, &mergedObject
, error
);
1829 // TODO: If the mr is kSOSMergeLocalObject most of the time (or all of the time),
1830 // consider asking the peer to stop sending us objects, and send it objects instead.
1831 ok
&= (mr
!= kSOSMergeFailure
);
1835 // 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.
1836 secerror("%@ SOSDataSourceMergeObject failed %@ rolling back changes", SOSPeerGetID(peer
), error
? *error
: NULL
);
1837 } else if (mr
==kSOSMergePeersObject
|| mr
==kSOSMergeCreatedObject
) {
1838 *somethingChanged
= true;
1840 // mr == kSOSMergeLocalObject
1841 if (!CFEqual(mergedObject
, peersObject
)) {
1842 // Record this object as something we don't want peer to ever send us again. By adding it to
1843 // unwantedObjects we'll falsely claim to peer we have it until they tell us they don't have it anymore.
1844 SOSDigestVectorAppend(&unwantedObjects
, CFDataGetBytePtr(digest
));
1846 // Ensure localObject is in local manifest (possible corruption) by posting an update when we are done.
1847 SOSChangesAppendAdd(changes
, mergedObject
);
1850 if (ok
&& hadBeenInSyncAtStart
) {
1851 ReportItemSyncTime(engine
->dataSource
,
1852 mr
== kSOSMergeLocalObject
,
1856 CFReleaseSafe(mergedObject
);
1857 CFReleaseSafe(digest
);
1859 struct SOSDigestVector dvunion
= SOSDigestVectorInit
;
1860 SOSDigestVectorSort(&receivedObjects
);
1861 SOSDigestVectorUnionSorted(SOSManifestGetDigestVector(SOSMessageGetAdditions(message
)), &receivedObjects
, &dvunion
);
1862 allAdditions
= SOSManifestCreateWithDigestVector(&dvunion
, error
);
1863 SOSDigestVectorFree(&receivedObjects
);
1864 SOSDigestVectorFree(&dvunion
);
1866 unwanted
= SOSManifestCreateWithDigestVector(&unwantedObjects
, error
);
1867 SOSDigestVectorFree(&unwantedObjects
);
1869 if (CFArrayGetCount(changes
)) {
1870 // NOTE: This is always notifiying of all additions that end up choosing local, which should be rare, since we shouldn't
1871 // be receiving objects we already have. When we do we tell ourselves to add them all again so our views will properly
1872 // reflect that we actually have these objects if we didn't already.
1874 // Ensure any objects that we received and have locally already are actually in our local manifest
1875 SOSEngineUpdateChanges_locked(engine
, txn
, kSOSDataSourceTransactionDidCommit
, kSOSDataSourceSOSTransaction
, changes
, error
);
1877 CFReleaseSafe(changes
);
1879 // ---- Don't use local or peer manifests from above this line,
1880 // ---- since commiting the SOSDataSourceWith transaction might change them ---
1882 // Take a snapshot of our dataSource's local manifest.
1883 require_quiet(ok
= localManifest
= SOSEngineCopyLocalPeerManifest_locked(engine
, peer
, error
), exit
);
1885 CFDataRef baseDigest
= SOSMessageGetBaseDigest(message
);
1886 CFDataRef proposedDigest
= SOSMessageGetProposedDigest(message
);
1889 // I believe this is no longer needed now that we have eliminated extra,
1890 // since this is handled below once we get a confirmed manifest from our
1893 // If we just received a L00 reset pendingObjects to localManifest
1894 if (!baseDigest
&& !proposedDigest
) {
1895 // TODO: This is definitely busted for v0 peers since v0 peers always send a
1896 // L00 (ManifestDigestMessage as an ack) whereas in v2 this is a protocol restart
1897 // However if we can still find a confirmed manifest below we probably
1898 // don't want to do this even for v2.
1899 // Also I don't think we will ever send a ManifestMessage right now in
1900 // response to a ManifestDigest
1901 SOSPeerSetPendingObjects(peer
, localManifest
);
1902 secnoticeq("engine", "%@:%@ SOSPeerSetPendingObjects: %@", engine
->myID
, peerID
, localManifest
);
1906 base
= SOSPeerCopyManifestForDigest(peer
, baseDigest
);
1908 // Note that the sender digest will only exist if we receive a SOSManifestDigestMessageType (since we never receive v2 messages)
1909 confirmed
= SOSPeerCopyManifestForDigest(peer
, SOSMessageGetSenderDigest(message
));
1911 if (SOSManifestGetCount(SOSMessageGetRemovals(message
)) || SOSManifestGetCount(allAdditions
)) {
1912 if (base
|| !baseDigest
) {
1914 secnotice("engine", "SOSEngineHandleMessage_locked (%@): creating a confirmed manifest via a patch (base %zu %@, +%zu, -%zu)", SOSPeerGetID(peer
),
1915 SOSManifestGetCount(base
), SOSManifestGetDigest(base
, NULL
),
1916 SOSManifestGetCount(allAdditions
), SOSManifestGetCount(SOSMessageGetRemovals(message
)));
1918 confirmed
= SOSManifestCreateWithPatch(base
, SOSMessageGetRemovals(message
), allAdditions
, error
);
1921 confirmedRemovals
= CFRetainSafe(SOSMessageGetRemovals(message
));
1922 confirmedAdditions
= CFRetainSafe(allAdditions
);
1924 } else if (baseDigest
) {
1925 confirmed
= CFRetainSafe(base
);
1926 secerror("%@:%@ Protocol error send L00 - figure out later base: %@", engine
->myID
, peerID
, base
);
1930 secnotice("engine", "SOSEngineHandleMessage_locked (%@): got a confirmed manifest by digest: (%zu, %@)", SOSPeerGetID(peer
), SOSManifestGetCount(confirmed
), SOSMessageGetSenderDigest(message
));
1932 secnoticeq("engine", "%@:%@ confirmed: %@ base: %@", engine
->myID
, peerID
, confirmed
, base
);
1934 ok
&= SOSManifestDiff(SOSPeerGetConfirmedManifest(peer
), confirmed
, &confirmedRemovals
, &confirmedAdditions
, error
);
1935 if (SOSManifestGetCount(SOSMessageGetRemovals(message
)))
1936 CFAssignRetained(confirmedRemovals
, SOSManifestCreateUnion(confirmedRemovals
, SOSMessageGetRemovals(message
), error
));
1938 if (SOSManifestGetCount(confirmedRemovals
) || SOSManifestGetCount(confirmedAdditions
) || SOSManifestGetCount(unwanted
)) {
1939 ok
&= SOSPeerDidReceiveRemovalsAndAdditions(peer
, confirmedRemovals
, confirmedAdditions
, unwanted
, localManifest
, error
);
1943 // TODO: We should probably remove the if below and always call SOSPeerSetConfirmedManifest,
1944 // since having a NULL confirmed will force us to send a manifest message to get in sync again.
1947 SOSManifestRef previousConfirmedManifest
= SOSPeerGetConfirmedManifest(peer
);
1948 if(previousConfirmedManifest
) {
1949 secnotice("engine", "SOSEngineHandleMessage_locked (%@): new confirmed manifest (%zu, %@) will replace existing confirmed manifest (%zu, %@)", SOSPeerGetID(peer
),
1950 SOSManifestGetCount(confirmed
), SOSManifestGetDigest(confirmed
, NULL
),
1951 SOSManifestGetCount(previousConfirmedManifest
), SOSManifestGetDigest(previousConfirmedManifest
, NULL
));
1953 secnotice("engine", "SOSEngineHandleMessage_locked (%@): new confirmed manifest (%zu, %@) is first manifest for peer", SOSPeerGetID(peer
),
1954 SOSManifestGetCount(confirmed
), SOSManifestGetDigest(confirmed
, NULL
));
1957 SOSPeerSetConfirmedManifest(peer
, confirmed
);
1958 } else if (SOSPeerGetConfirmedManifest(peer
)) {
1959 secnoticeq("engine", "%@:%@ unable to find confirmed in %@, sync protocol reset", engine
->myID
, peer
, message
);
1961 SOSPeerSetConfirmedManifest(peer
, NULL
);
1962 //SOSPeerSetSendObjects(peer, true);
1965 // ---- SendObjects and extra->pendingObjects promotion dance ----
1967 // The first block of code below sets peer.sendObjects to true when we receive a L00 and the second block
1968 // moves extra to pendingObjects once we receive a confirmed manifest in or after the L00.
1969 if (!baseDigest
&& !proposedDigest
) {
1970 SOSPeerSetSendObjects(peer
, true);
1973 if (0 /* confirmed && SOSPeerSendObjects(peer) */) {
1974 SOSManifestRef allExtra
= NULL
;
1975 ok
&= SOSManifestDiff(confirmed
, localManifest
, NULL
, &allExtra
, error
);
1976 secnoticeq("engine", "%@:%@ confirmed %@ (re)setting O:%@", engine
->myID
, SOSPeerGetID(peer
), confirmed
, allExtra
);
1977 SOSPeerSetPendingObjects(peer
, allExtra
);
1978 CFReleaseSafe(allExtra
);
1982 secnotice("engine", "recv %@:%@ %@", engine
->myID
, SOSPeerGetID(peer
), message
);
1983 secnotice("peer", "recv %@ -> %@", peerDesc
, peer
);
1985 CFReleaseNull(base
);
1986 CFReleaseSafe(confirmed
);
1987 CFReleaseSafe(localManifest
);
1988 CFReleaseSafe(peerDesc
);
1989 CFReleaseSafe(allAdditions
);
1990 CFReleaseSafe(unwanted
);
1991 CFReleaseSafe(confirmedRemovals
);
1992 CFReleaseSafe(confirmedAdditions
);
1993 CFReleaseSafe(peer
);
1997 static CFDataRef
SOSEngineCopyObjectDER(SOSEngineRef engine
, SOSObjectRef object
, CFErrorRef
*error
) {
1998 CFDataRef der
= NULL
;
1999 CFDictionaryRef plist
= SOSObjectCopyPropertyList(engine
->dataSource
, object
, error
);
2001 der
= CFPropertyListCreateDERData(kCFAllocatorDefault
, plist
, error
);
2010 +-----------------------------+_
2014 _+=============================+ } L
2018 / +==============================
2021 \ | M +------------|
2025 \_+-------------+---------------+
2037 void SOSEngineSetSyncCompleteListener(SOSEngineRef engine
, SOSEnginePeerInSyncBlock notify_block
) {
2038 SOSEngineDoOnQueue(engine
, ^{
2039 CFAssignRetained(engine
->syncCompleteListener
, Block_copy(notify_block
));
2043 void SOSEngineSetSyncCompleteListenerQueue(SOSEngineRef engine
, dispatch_queue_t notify_queue
) {
2044 SOSEngineDoOnQueue(engine
, ^{
2045 CFRetainAssign(engine
->syncCompleteQueue
, notify_queue
);
2049 static void SOSEngineCompletedSyncWithPeer(SOSEngineRef engine
, SOSPeerRef peer
) {
2050 SOSEnginePeerInSyncBlock block_to_call
= engine
->syncCompleteListener
;
2052 if (block_to_call
&& engine
->syncCompleteQueue
) {
2053 CFStringRef ID
= CFRetainSafe(SOSPeerGetID(peer
));
2054 CFSetRef views
= CFRetainSafe(SOSPeerGetViewNameSet(peer
));
2055 CFRetainSafe(block_to_call
);
2057 dispatch_async(engine
->syncCompleteQueue
, ^{
2058 block_to_call(ID
, views
);
2060 CFReleaseSafe(views
);
2061 CFReleaseSafe(block_to_call
);
2065 SOSPeerSetHasBeenInSync(peer
, true);
2069 CFDataRef
SOSEngineCreateMessage_locked(SOSEngineRef engine
, SOSTransactionRef txn
, SOSPeerRef peer
,
2070 CFMutableArrayRef
*attributeList
, CFErrorRef
*error
, SOSEnginePeerMessageSentCallback
**sent
) {
2071 SOSManifestRef local
= SOSEngineCopyLocalPeerManifest_locked(engine
, peer
, error
);
2072 __block SOSMessageRef message
= SOSMessageCreate(kCFAllocatorDefault
, SOSPeerGetMessageVersion(peer
), error
);
2073 SOSManifestRef confirmed
= SOSPeerGetConfirmedManifest(peer
);
2074 SOSManifestRef pendingObjects
= SOSPeerGetPendingObjects(peer
);
2075 SOSManifestRef objectsSent
= NULL
;
2076 SOSManifestRef proposed
= NULL
;
2077 SOSManifestRef allMissing
= NULL
;
2078 SOSManifestRef allExtra
= NULL
;
2079 SOSManifestRef extra
= NULL
;
2080 SOSManifestRef excessPending
= NULL
;
2081 SOSManifestRef missing
= NULL
;
2082 SOSManifestRef unwanted
= SOSPeerGetUnwantedManifest(peer
);
2083 SOSManifestRef excessUnwanted
= NULL
;
2084 CFDataRef result
= NULL
;
2086 // Given (C, L, T, U) compute (T, U, M, A)
2090 // U \ (C \ L) => EU
2091 // T \ (L \ C) => ET
2092 // And assert that both EU and ET are empty and if not remove them from U and T respectively
2093 SOSManifestDiff(confirmed
, local
, &allMissing
, &allExtra
, error
);
2094 SOSManifestDiff(allExtra
, pendingObjects
, &extra
, &excessPending
, error
);
2095 if (SOSManifestGetCount(excessPending
)) {
2096 // T \ (L \ C) => excessPending (items both in L and C or in neither that are still pending)
2097 // Can only happen if a member of T was removed from L without us having a chance to update T
2098 secerror("%@ ASSERTION FAILURE purging excess pendingObjects: %@", peer
, excessPending
);
2099 SOSManifestRef newPendingObjects
= SOSManifestCreateComplement(excessPending
, pendingObjects
, error
);
2100 SOSPeerSetPendingObjects(peer
, newPendingObjects
);
2101 CFReleaseSafe(newPendingObjects
);
2102 pendingObjects
= SOSPeerGetPendingObjects(peer
);
2104 SOSManifestDiff(allMissing
, unwanted
, &missing
, &excessUnwanted
, error
);
2105 if (SOSManifestGetCount(excessUnwanted
)) {
2106 // U \ (C \ L) => excessUnwanted (items both in L and C or in neither that are still unwanted)
2107 // Can only happen if a member of U was added to L without us having a chance to update U.
2108 // Since U only contains items the conflict resolver rejected, this implies L somehow got rolled back
2109 // The other option (and more likely) is a member of U was removed from C and not from U.
2110 secerror("%@ ASSERTION FAILURE purging excess unwanted: %@", peer
, excessUnwanted
);
2111 SOSManifestRef newUnwanted
= SOSManifestCreateComplement(excessUnwanted
, unwanted
, error
);
2112 SOSPeerSetUnwantedManifest(peer
, newUnwanted
);
2113 CFReleaseSafe(newUnwanted
);
2114 unwanted
= SOSPeerGetUnwantedManifest(peer
);
2117 CFReleaseNull(allExtra
);
2118 CFReleaseNull(excessPending
);
2119 CFReleaseNull(allMissing
);
2120 CFReleaseNull(excessUnwanted
);
2122 secnoticeq("engine", "%@:%@: send state for peer [%s%s%s][%s%s] local:%zu confirmed:%zu pending:%zu, extra:%zu, missing:%zu unwanted:%zu", engine
->myID
, SOSPeerGetID(peer
),
2124 confirmed
? "C":"0",
2125 pendingObjects
? "P":"0",
2126 SOSPeerSendObjects(peer
) ? "O":"o",
2127 SOSPeerMustSendMessage(peer
) ? "S":"s",
2128 SOSManifestGetCount(local
),
2129 SOSManifestGetCount(confirmed
),
2130 SOSManifestGetCount(pendingObjects
),
2131 SOSManifestGetCount(extra
),
2132 SOSManifestGetCount(missing
),
2133 SOSManifestGetCount(unwanted
)
2137 // TODO: Because of not letting things terminate while we have extra left
2138 // we might send objects when we didn't need to, but there is always an
2139 // extra roundtrip required for objects that we assume the other peer
2140 // should have already.
2141 // TODO: If there are extra objects left, calling this function is not
2142 // idempotent we should check if pending is what we are about to send and not send anything in this case.
2143 if (SOSManifestGetCount(pendingObjects
) == 0 && SOSManifestGetCount(extra
) == 0)
2144 SOSPeerSetSendObjects(peer
, false);
2146 // If we aren't missing anything, we've gotten all their data, so we're sync even if they haven't seen ours.
2147 if (missing
&& SOSManifestGetCount(missing
) == 0) {
2148 SOSEngineCompletedSyncWithPeer(engine
, peer
);
2151 if (CFEqualSafe(local
, SOSPeerGetProposedManifest(peer
)) && !SOSPeerMustSendMessage(peer
)) {
2153 if (CFEqual(confirmed
, local
)) {
2154 secnoticeq("engine", "synced <No MSG> %@:%@", engine
->myID
, peer
);
2155 } else if (SOSManifestGetCount(pendingObjects
) == 0 /* TODO: No entries moved from extra to pendingObjects. */
2156 && SOSManifestGetCount(missing
) == 0) {
2157 secnoticeq("engine", "waiting <MSG not resent> %@:%@ extra: %@", engine
->myID
, peer
, extra
);
2162 CFReleaseNull(local
);
2163 CFReleaseNull(message
);
2164 CFReleaseNull(extra
);
2165 CFReleaseNull(missing
);
2166 return CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
2170 if (SOSManifestGetCount(pendingObjects
)) {
2171 // If we have additions and we need to send objects, do so.
2172 __block
size_t objectsSize
= 0;
2173 __block
struct SOSDigestVector dv
= SOSDigestVectorInit
;
2174 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2175 __block CFErrorRef dsfeError
= NULL
;
2177 if (!SOSDataSourceForEachObject(engine
->dataSource
, txn
, pendingObjects
, &dsfeError
, ^void(CFDataRef key
, SOSObjectRef object
, bool *stop
) {
2178 CFErrorRef localError
= NULL
;
2179 CFDataRef digest
= NULL
;
2180 CFDataRef der
= NULL
;
2181 #if !defined(NDEBUG)
2182 const uint8_t *d
= CFDataGetBytePtr(key
);
2184 secdebug("engine", "%@:%@ object %02X%02X%02X%02X error from SOSDataSourceForEachObject: %@",
2185 engine
->myID
, SOSPeerGetID(peer
), d
[0], d
[1], d
[2], d
[3], dsfeError
);
2187 const uint8_t *d
= CFDataGetBytePtr(key
);
2188 secerror("%@:%@ object %02X%02X%02X%02X dropping from manifest: not found in datasource: %@",
2189 engine
->myID
, SOSPeerGetID(peer
), d
[0], d
[1], d
[2], d
[3], dsfeError
);
2190 SOSChangesAppendDelete(changes
, key
);
2191 } else if (!(der
= SOSEngineCopyObjectDER(engine
, object
, &localError
))
2192 || !(digest
= SOSObjectCopyDigest(engine
->dataSource
, object
, &localError
))) {
2193 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
2194 // Decode error, we need to drop these objects from our manifests
2195 const uint8_t *d
= CFDataGetBytePtr(key
);
2196 secnoticeq("engine", "%@:%@ object %02X%02X%02X%02X dropping from manifest: %@",
2197 engine
->myID
, SOSPeerGetID(peer
), d
[0], d
[1], d
[2], d
[3], localError
);
2198 SOSChangesAppendDelete(changes
, key
);
2199 CFRelease(localError
);
2201 // Stop iterating and propagate out all other errors.
2202 const uint8_t *d
= CFDataGetBytePtr(key
);
2203 secnoticeq("engine", "%@:%@ object %02X%02X%02X%02X in SOSDataSourceForEachObject: %@",
2204 engine
->myID
, SOSPeerGetID(peer
), d
[0], d
[1], d
[2], d
[3], localError
);
2206 CFErrorPropagate(localError
, error
);
2207 CFReleaseNull(message
);
2210 if (!CFEqual(key
, digest
)) {
2211 const uint8_t *d
= CFDataGetBytePtr(key
);
2212 const uint8_t *e
= CFDataGetBytePtr(digest
);
2213 secnoticeq("engine", "%@:%@ object %02X%02X%02X%02X is really %02X%02X%02X%02X dropping from local manifest",
2214 engine
->myID
, SOSPeerGetID(peer
), d
[0], d
[1], d
[2], d
[3], e
[0], e
[1], e
[2], e
[3]);
2215 SOSChangesAppendDelete(changes
, key
);
2216 SOSChangesAppendAdd(changes
, object
); // This is new behaviour but we think it's more correct
2219 size_t objectLen
= (size_t)CFDataGetLength(der
);
2220 if (SOSMessageAppendObject(message
, der
, &localError
)) {
2221 SOSDigestVectorAppend(&dv
, CFDataGetBytePtr(digest
));
2223 *attributeList
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2224 CFDictionaryRef itemPlist
= SOSObjectCopyPropertyList(engine
->dataSource
, object
, &localError
);
2225 if(itemPlist
&& !CFArrayContainsValue(*attributeList
, CFRangeMake(0, CFArrayGetCount(*attributeList
)), (CFStringRef
)CFDictionaryGetValue(itemPlist
, kSecAttrAccessGroup
))){
2226 CFArrayAppendValue(*attributeList
, (CFStringRef
)CFDictionaryGetValue(itemPlist
, kSecAttrAccessGroup
));
2227 }//copy access group to array
2228 CFReleaseNull(itemPlist
);
2230 const uint8_t *d
= CFDataGetBytePtr(digest
);
2231 CFStringRef hexder
= CFDataCopyHexString(der
);
2232 secnoticeq("engine", "%@:%@ object %02X%02X%02X%02X der: %@ dropping from manifest: %@",
2233 engine
->myID
, SOSPeerGetID(peer
), d
[0], d
[1], d
[2], d
[3], hexder
, localError
);
2234 CFReleaseNull(hexder
);
2235 CFReleaseNull(message
);
2236 // Since we can't send these objects let's assume they are bad too?
2237 SOSChangesAppendDelete(changes
, digest
);
2240 objectsSize
+= objectLen
;
2241 if (objectsSize
> kSOSMessageMaxObjectsSize
)
2244 CFErrorPropagate(dsfeError
, error
); // this also releases dsfeError
2247 CFReleaseSafe(digest
);
2249 CFReleaseNull(message
);
2252 objectsSent
= SOSManifestCreateWithDigestVector(&dv
, error
);
2254 if (CFArrayGetCount(changes
)) {
2255 CFErrorRef localError
= NULL
;
2256 if (!SOSEngineUpdateChanges_locked(engine
, NULL
, kSOSDataSourceTransactionDidCommit
, kSOSDataSourceSOSTransaction
, changes
, &localError
))
2257 secerror("SOSEngineUpdateChanges_locked: %@ failed: %@", changes
, localError
);
2258 CFReleaseSafe(localError
);
2259 CFAssignRetained(local
, SOSEngineCopyLocalPeerManifest_locked(engine
, peer
, error
));
2261 CFReleaseSafe(changes
);
2262 SOSDigestVectorFree(&dv
);
2263 CFReleaseNull(dsfeError
);
2266 // If we have no confirmed manifest, we want all pendedObjects going out as a manifest
2267 objectsSent
= CFRetainSafe(pendingObjects
);
2270 if (confirmed
|| SOSManifestGetCount(missing
) || SOSManifestGetCount(extra
) || objectsSent
) {
2271 SOSManifestRef allExtra
= SOSManifestCreateUnion(extra
, objectsSent
, error
);
2272 proposed
= SOSManifestCreateWithPatch(confirmed
, missing
, allExtra
, error
);
2273 CFReleaseNull(allExtra
);
2276 SOSManifestRef sender
= local
;
2277 // We actually send the remote peer its own digest.
2278 // Note that both pendingObjects and unwanted may have been changed, so we get them again
2279 if (SOSManifestGetCount(SOSPeerGetPendingObjects(peer
))==0 && SOSManifestGetCount(extra
)==0 &&
2280 SOSManifestGetCount(missing
)==0 && SOSManifestGetCount(SOSPeerGetUnwantedManifest(peer
))!=0) {
2281 secnoticeq("engine", "%@:%@: only have differences in unwanted set; lying to peer to stop sync",engine
->myID
, SOSPeerGetID(peer
));
2285 if (!SOSMessageSetManifests(message
, sender
, confirmed
, proposed
, proposed
, confirmed
? objectsSent
: NULL
, error
)) {
2286 secnoticeq("engine", "%@:%@: failed to set message manifests",engine
->myID
, SOSPeerGetID(peer
));
2287 CFReleaseNull(message
);
2290 CFReleaseNull(objectsSent
);
2293 result
= SOSMessageCreateData(message
, SOSPeerNextSequenceNumber(peer
), error
);
2297 SOSEnginePeerMessageSentCallback
* pmsc
= malloc(sizeof(SOSEnginePeerMessageSentCallback
));
2298 memset(pmsc
, 0, sizeof(SOSEnginePeerMessageSentCallback
));
2299 pmsc
->engine
= engine
; CFRetain(pmsc
->engine
);
2300 pmsc
->peer
= CFRetainSafe(peer
);
2301 pmsc
->local
= CFRetainSafe(local
);
2302 pmsc
->proposed
= CFRetainSafe(proposed
);
2303 pmsc
->message
= CFRetainSafe(message
);
2304 pmsc
->confirmed
= CFRetainSafe(confirmed
);
2306 SOSEngineMessageCallbackSetCallback(pmsc
, ^(bool success
) {
2307 // Have to copy pmsc so it'll still be around during the dispatch_async
2308 SOSEnginePeerMessageSentCallback
* pmsc2
= malloc(sizeof(SOSEnginePeerMessageSentCallback
));
2309 memset(pmsc2
, 0, sizeof(SOSEnginePeerMessageSentCallback
));
2310 pmsc2
->engine
= pmsc
->engine
; CFRetain(pmsc2
->engine
);
2311 pmsc2
->peer
= CFRetainSafe(pmsc
->peer
);
2312 pmsc2
->local
= CFRetainSafe(pmsc
->local
);
2313 pmsc2
->proposed
= CFRetainSafe(pmsc
->proposed
);
2314 pmsc2
->message
= CFRetainSafe(pmsc
->message
);
2315 pmsc2
->confirmed
= CFRetainSafe(pmsc
->confirmed
);
2317 dispatch_async(pmsc
->engine
->queue
, ^{
2319 SOSPeerSetMustSendMessage(pmsc2
->peer
, false);
2320 if (!pmsc2
->confirmed
&& !pmsc2
->proposed
) {
2321 SOSPeerSetSendObjects(pmsc2
->peer
, true);
2322 secnoticeq("engine", "%@:%@ sendObjects=true L:%@", pmsc2
->engine
->myID
, SOSPeerGetID(pmsc2
->peer
), pmsc2
->local
);
2324 SOSPeerAddLocalManifest(pmsc2
->peer
, pmsc2
->local
);
2325 SOSPeerAddProposedManifest(pmsc2
->peer
, pmsc2
->proposed
);
2326 secnoticeq("engine", "send %@:%@ %@", pmsc2
->engine
->myID
, SOSPeerGetID(pmsc2
->peer
), pmsc2
->message
);
2328 secerror("%@:%@ failed to send %@", pmsc2
->engine
->myID
, SOSPeerGetID(pmsc2
->peer
), pmsc2
->message
);
2330 SOSEngineFreeMessageCallback(pmsc2
);
2337 CFReleaseNull(local
);
2338 CFReleaseNull(extra
);
2339 CFReleaseNull(missing
);
2340 CFReleaseNull(message
);
2341 CFReleaseNull(proposed
);
2342 if (error
&& *error
)
2343 secerror("%@:%@ error in send: %@", engine
->myID
, SOSPeerGetID(peer
), *error
);
2348 void SOSEngineMessageCallbackSetCallback(SOSEnginePeerMessageSentCallback
*sent
, SOSEnginePeerMessageSentBlock block
) {
2350 sent
->block
= Block_copy(block
);
2355 void SOSEngineMessageCallCallback(SOSEnginePeerMessageSentCallback
*sent
, bool ok
) {
2356 if (sent
&& sent
->block
) {
2361 void SOSEngineFreeMessageCallback(SOSEnginePeerMessageSentCallback
* psmc
) {
2363 CFReleaseNull(psmc
->engine
);
2364 CFReleaseNull(psmc
->peer
);
2365 CFReleaseNull(psmc
->coder
);
2366 CFReleaseNull(psmc
->local
);
2367 CFReleaseNull(psmc
->proposed
);
2368 CFReleaseNull(psmc
->message
);
2369 CFReleaseNull(psmc
->confirmed
);
2372 Block_release(psmc
->block
);
2379 static void SOSEngineLogItemError(SOSEngineRef engine
, CFStringRef peerID
, CFDataRef key
, CFDataRef optionalDigest
, const char *where
, CFErrorRef error
) {
2380 if (!optionalDigest
) {
2381 const uint8_t *d
= CFDataGetBytePtr(key
);
2382 secwarning("%@:%@ object %02X%02X%02X%02X %s: %@", engine
->myID
, peerID
, d
[0], d
[1], d
[2], d
[3], where
, error
? (CFTypeRef
)error
: CFSTR(""));
2384 const uint8_t *d
= CFDataGetBytePtr(key
);
2385 const uint8_t *e
= CFDataGetBytePtr(optionalDigest
);
2386 secwarning("%@:%@ object %02X%02X%02X%02X is really %02X%02X%02X%02X dropping from local manifest", engine
->myID
, peerID
, d
[0], d
[1], d
[2], d
[3], e
[0], e
[1], e
[2], e
[3]);
2390 static bool SOSEngineWriteToBackup_locked(SOSEngineRef engine
, SOSPeerRef peer
, bool rewriteComplete
, bool *didWrite
, bool *incomplete
, CFErrorRef
*error
) {
2391 __block
bool ok
= SOSPeerWritePendingReset(peer
, error
);
2392 if (!ok
|| !SOSPeerGetKeyBag(peer
))
2394 __block SOSManifestRef local
= SOSEngineCopyLocalPeerManifest_locked(engine
, peer
, error
);
2395 __block SOSManifestRef proposed
= SOSPeerGetProposedManifest(peer
);
2396 __block
bool notify
= true;
2397 SOSManifestRef pendingObjects
= NULL
;
2398 SOSManifestRef missing
= NULL
;
2399 CFStringRef peerID
= SOSPeerGetID(peer
);
2401 ok
&= SOSManifestDiff(proposed
, local
, &missing
, &pendingObjects
, error
);
2403 secnoticeq("engine", "%@:%@: Send state for peer [%s%s%s] O: %zu, M: %zu", engine
->myID
, peerID
,
2406 pendingObjects
? "O":"0",
2407 SOSManifestGetCount(pendingObjects
),
2408 SOSManifestGetCount(missing
));
2410 if (SOSManifestGetCount(missing
) == 0 && SOSManifestGetCount(pendingObjects
) == 0) {
2411 // proposed == local (faster test than CFEqualSafe above), since we
2412 // already did the SOSManifestDiff
2413 if (rewriteComplete
) {
2416 secnoticeq("engine", "%@:%@ backup still done", engine
->myID
, peer
);
2420 ok
&= SOSPeerAppendToJournal(peer
, error
, ^(FILE *journalFile
, keybag_handle_t kbhandle
) {
2421 SOSManifestRef objectsSent
= NULL
;
2422 __block
struct SOSDigestVector dvdel
= SOSDigestVectorInit
;
2423 __block
struct SOSDigestVector dvadd
= SOSDigestVectorInit
;
2424 SOSManifestForEach(missing
, ^(CFDataRef key
, bool *stop
) {
2425 CFErrorRef localError
= NULL
;
2426 if (ftello(journalFile
) > kSOSBackupMaxFileSize
) {
2427 // Highwatermark hit on file.
2429 } else if (SOSBackupEventWriteDelete(journalFile
, key
, &localError
)) {
2430 SOSDigestVectorAppend(&dvdel
, CFDataGetBytePtr(key
));
2432 SOSEngineLogItemError(engine
, peerID
, key
, NULL
, "in SOSPeerWriteDelete", localError
);
2433 CFErrorPropagate(localError
, error
);
2434 // TODO: Update of missing so proposed is updated properly
2435 *stop
= true; // Disk full?
2439 if (ok
&& SOSManifestGetCount(pendingObjects
)) {
2440 CFMutableArrayRef changes
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2441 ok
&= SOSDataSourceForEachObject(engine
->dataSource
, NULL
, pendingObjects
, error
, ^void(CFDataRef key
, SOSObjectRef object
, bool *stop
) {
2442 CFErrorRef localError
= NULL
;
2443 CFDataRef digest
= NULL
;
2444 CFDictionaryRef backupItem
= NULL
;
2445 if (ftello(journalFile
) > kSOSBackupMaxFileSize
) {
2446 // Highwatermark hit on file.
2448 } else if (!object
) {
2449 SOSEngineLogItemError(engine
, peerID
, key
, NULL
, "dropping from manifest: not found in datasource", localError
);
2450 SOSChangesAppendDelete(changes
, key
);
2451 } else if (!(backupItem
= SOSObjectCopyBackup(engine
->dataSource
, object
, kbhandle
, &localError
))
2452 || !(digest
= SOSObjectCopyDigest(engine
->dataSource
, object
, &localError
))) {
2453 if (SecErrorGetOSStatus(localError
) == errSecDecode
) {
2454 // Decode error, we need to drop these objects from our manifests
2455 SOSEngineLogItemError(engine
, peerID
, key
, NULL
, "dropping from manifest", localError
);
2456 SOSChangesAppendDelete(changes
, key
);
2457 CFRelease(localError
);
2459 // Stop iterating and propagate out all other errors.
2460 SOSEngineLogItemError(engine
, peerID
, key
, NULL
, "in SOSDataSourceForEachObject", localError
);
2462 CFErrorPropagate(localError
, error
);
2466 if (!CFEqual(key
, digest
)) {
2467 SOSEngineLogItemError(engine
, peerID
, key
, digest
, "", NULL
);
2468 SOSChangesAppendDelete(changes
, key
);
2469 SOSChangesAppendAdd(changes
, object
); // This is new behaviour but we think it's more correct
2472 if (SOSBackupEventWriteAdd(journalFile
, backupItem
, &localError
)) {
2473 SOSDigestVectorAppend(&dvadd
, CFDataGetBytePtr(digest
));
2475 SOSEngineLogItemError(engine
, peerID
, key
, NULL
, "in SOSPeerWriteAdd", localError
);
2476 *stop
= true; // Disk full?
2477 CFErrorPropagate(localError
, error
);
2481 CFReleaseSafe(backupItem
);
2482 CFReleaseSafe(digest
);
2484 if (CFArrayGetCount(changes
)) {
2485 CFErrorRef localError
= NULL
;
2486 if (!SOSEngineUpdateChanges_locked(engine
, NULL
, kSOSDataSourceTransactionDidCommit
, kSOSDataSourceSOSTransaction
, changes
, &localError
))
2487 secerror("SOSEngineUpdateChanges_locked: %@ failed: %@", changes
, localError
);
2488 CFReleaseSafe(localError
);
2489 // Since calling SOSEngineUpdateChanges_locked might cause local to change and might cause the backup peer to update proposed, refetch them here.
2490 CFAssignRetained(local
, SOSEngineCopyLocalPeerManifest_locked(engine
, peer
, error
));
2491 proposed
= SOSPeerGetProposedManifest(peer
);
2493 CFReleaseSafe(changes
);
2496 if (dvadd
.count
|| (proposed
&& dvdel
.count
)) {
2498 SOSManifestRef deleted
= SOSManifestCreateWithDigestVector(&dvdel
, error
);
2499 SOSManifestRef objectsSent
= SOSManifestCreateWithDigestVector(&dvadd
, error
);
2500 SOSManifestRef newProposed
= SOSManifestCreateWithPatch(proposed
, deleted
, objectsSent
, error
);
2501 CFReleaseSafe(deleted
);
2502 CFReleaseSafe(objectsSent
);
2503 SOSPeerSetProposedManifest(peer
, newProposed
);
2504 CFReleaseSafe(newProposed
);
2505 proposed
= SOSPeerGetProposedManifest(peer
);
2507 SOSDigestVectorFree(&dvdel
);
2508 SOSDigestVectorFree(&dvadd
);
2510 // TODO: If proposed is NULL, and local is empty we should still consider ourselves done.
2511 // It so happens this can't happen in practice today since there is at least a backupbag
2512 // in the backup, but this is a bug waiting to rear its head in the future.
2513 if (ok
&& CFEqualSafe(local
, proposed
)) {
2514 CFErrorRef localError
= NULL
;
2515 if (SOSBackupEventWriteCompleteMarker(journalFile
, 899, &localError
)) {
2516 SOSPeerSetSendObjects(peer
, true);
2518 secnoticeq("backup", "%@:%@ backup done%s", engine
->myID
, peerID
, notify
? " notifying sbd" : "");
2519 // TODO: Now switch to changes based writing to backup sync.
2520 // Currently we leave changes enabled but we probably shouldn't
2522 secwarning("%@:%@ in SOSBackupPeerWriteCompleteMarker: %@", engine
->myID
, peerID
, localError
);
2525 CFErrorPropagate(localError
, error
);
2528 secnoticeq("backup", "%@:%@ backup incomplete [%zu/%zu]%s", engine
->myID
, peerID
, SOSManifestGetCount(local
), SOSManifestGetCount(proposed
), notify
? " notifying sbd" : "");
2531 CFReleaseNull(objectsSent
);
2534 SOSBackupPeerPostNotification("writing changes to backup");
2537 CFReleaseSafe(local
);
2538 CFReleaseNull(pendingObjects
);
2539 CFReleaseNull(missing
);
2544 CF_RETURNS_RETAINED CFSetRef
SOSEngineSyncWithBackupPeers(SOSEngineRef engine
, CFSetRef
/* CFStringRef */ peers
, bool forceReset
, CFErrorRef
*error
)
2546 __block
bool incomplete
= false;
2547 CFMutableSetRef handledSet
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
);
2549 bool ok
= SOSEngineDoTxnOnQueue(engine
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
2550 __block
bool dirty
= false;
2551 CFSetForEach(peers
, ^(const void *value
) {
2552 bool report_handled
= true;
2553 CFErrorRef localError
= NULL
;
2554 SOSPeerRef peer
= NULL
;
2555 CFStringRef peerID
= asString(value
, &localError
);
2556 require_action_quiet(peerID
, done
, report_handled
= false);
2558 peer
= SOSEngineCopyPeerWithID_locked(engine
, peerID
, &localError
);
2559 require_quiet(peerID
, done
);
2561 if (SOSPeerMapEntryIsBackup(peer
)) {
2563 SOSPeerSetMustSendMessage(peer
, true);
2566 report_handled
= SOSEngineWriteToBackup_locked(engine
, peer
, false, &dirty
, &incomplete
, &localError
);
2571 secnotice("engine-sync", "Failed to process sync for %@: %@", peerID
, localError
);
2573 if (report_handled
) {
2574 CFSetAddValue(handledSet
, peerID
);
2576 CFReleaseNull(localError
);
2577 CFReleaseNull(peer
);
2581 CFErrorRef saveError
= NULL
;
2582 if (!SOSEngineSave(engine
, txn
, &saveError
)) {
2583 secnotice("engine-save", "Failed to save engine: %@", saveError
);
2588 // Ensure we get called again in a while (after a backup timeout)
2589 // sbd will do this since we never wrote a complete marker.
2590 // TODO: This relies on us not writing complete marker for update
2591 // event while we havn't finished a full backup, which we currently still do.
2594 CFReleaseNull(handledSet
);
2599 bool SOSEngineHandleMessage(SOSEngineRef engine
, CFStringRef peerID
,
2600 CFDataRef raw_message
, CFErrorRef
*error
)
2602 __block
bool result
= true;
2603 __block
bool somethingChanged
= false;
2604 SOSMessageRef message
= SOSMessageCreateWithData(kCFAllocatorDefault
, raw_message
, error
);
2605 result
&= message
&& SOSEngineDoTxnOnQueue(engine
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
2606 result
= SOSEngineHandleMessage_locked(engine
, peerID
, message
, txn
, commit
, &somethingChanged
, error
);
2608 CFReleaseSafe(message
);
2609 if (somethingChanged
)
2610 SecKeychainChanged();
2614 void SOSEngineCircleChanged(SOSEngineRef engine
, CFStringRef myPeerID
, CFArrayRef trustedPeers
, CFArrayRef untrustedPeers
) {
2615 __block
bool peersOrViewsChanged
= false;
2616 SOSEngineDoOnQueue(engine
, ^{
2617 peersOrViewsChanged
= SOSEngineCircleChanged_locked(engine
, myPeerID
, trustedPeers
, untrustedPeers
);
2619 // We should probably get a more precise list of peers that actually need talking to
2620 if (peersOrViewsChanged
&& engine
->myID
&& CFArrayGetCount(engine
->peerIDs
) != 0)
2621 SOSCCRequestSyncWithPeersList(engine
->peerIDs
);
2624 __block
bool ok
= true;
2625 __block CFErrorRef localError
= NULL
;
2626 ok
&= SOSEngineDoTxnOnQueue(engine
, &localError
, ^(SOSTransactionRef txn
, bool *commit
) {
2627 ok
= *commit
= SOSEngineSave(engine
, txn
, &localError
);
2630 secerror("failed to save engine state: %@", localError
);
2631 CFReleaseSafe(localError
);
2636 SOSManifestRef
SOSEngineCopyManifest(SOSEngineRef engine
, CFErrorRef
*error
) {
2637 __block SOSManifestRef result
= NULL
;
2638 SOSEngineDoOnQueue(engine
, ^{
2639 result
= SOSEngineCopyManifestWithViewNameSet_locked(engine
, SOSViewsGetV0ViewSet(), error
);
2644 SOSManifestRef
SOSEngineCopyLocalPeerManifest(SOSEngineRef engine
, SOSPeerRef peer
, CFErrorRef
*error
) {
2645 __block SOSManifestRef result
= NULL
;
2646 SOSEngineDoOnQueue(engine
, ^{
2647 result
= SOSEngineCopyLocalPeerManifest_locked(engine
, peer
, error
);
2652 bool SOSEngineUpdateChanges(SOSEngineRef engine
, SOSDataSourceTransactionSource source
, CFArrayRef changes
, CFErrorRef
*error
) {
2653 __block
bool result
= true;
2654 SOSEngineDoOnQueue(engine
, ^{
2655 result
= SOSEngineUpdateChanges_locked(engine
, NULL
, kSOSDataSourceTransactionDidCommit
, source
, changes
, error
);
2661 // Peer state layout. WRONG! It's an array now
2662 // The peer state is an array.
2663 // The first element of the array is a dictionary with any number of keys and
2664 // values in it (for future expansion) such as changing the digest size or type
2665 // or remebering boolean flags for a peers sake.
2666 // The next three are special in that they are manifest digests with special
2667 // meaning and rules as to how they are treated (These are dynamically updated
2668 // based on database activity so they have a fully history of all changes made
2669 // to the local db. The first is the manifest representing the pendingObjects
2670 // to send to the other peer. This is normally only ever appending to, and in
2671 // particular with transactions originating from the Keychain API that affect
2672 // syncable items will need to add the new objects digests to the pendingObjects list
2673 // while adding the digests of any tombstones encountered to the extra list.
2675 SOSPeerRef
SOSEngineCopyPeerWithID(SOSEngineRef engine
, CFStringRef peer_id
, CFErrorRef
*error
) {
2676 __block SOSPeerRef peer
= NULL
;
2677 SOSEngineDoOnQueue(engine
, ^{
2678 peer
= SOSEngineCopyPeerWithID_locked(engine
, peer_id
, error
);
2683 bool SOSEngineForPeerID(SOSEngineRef engine
, CFStringRef peerID
, CFErrorRef
*error
, void (^forPeer
)(SOSTransactionRef txn
, SOSPeerRef peer
)) {
2684 __block
bool ok
= true;
2685 SOSDataSourceReadWithCommitQueue(engine
->dataSource
, error
, ^(SOSTransactionRef txn
) {
2686 SOSEngineDoOnQueue(engine
, ^{
2687 SOSPeerRef peer
= SOSEngineCopyPeerWithID_locked(engine
, peerID
, error
);
2700 bool SOSEngineWithPeerID(SOSEngineRef engine
, CFStringRef peerID
, CFErrorRef
*error
, void (^with
)(SOSPeerRef peer
, SOSCoderRef coder
, SOSDataSourceRef dataSource
, SOSTransactionRef txn
, bool *forceSaveState
)) {
2701 __block
bool result
= true;
2702 result
&= SOSEngineDoTxnOnQueue(engine
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
2703 SOSPeerRef peer
= SOSEngineCopyPeerWithID_locked(engine
, peerID
, error
);
2705 result
= SOSErrorCreate(kSOSErrorPeerNotFound
, error
, NULL
, CFSTR("Engine has no peer for %@"), peerID
);
2707 bool saveState
= false;
2708 SOSCoderRef coder
= SOSEngineGetCoderInTx_locked(engine
, txn
, peerID
, error
);
2709 with(peer
, coder
, engine
->dataSource
, txn
, &saveState
);
2710 CFReleaseSafe(peer
);
2712 result
= SOSEngineSave(engine
, txn
, error
);
2713 // TODO: Don't commit if engineSave fails?
2720 CFDataRef
SOSEngineCreateMessageToSyncToPeer(SOSEngineRef engine
, CFStringRef peerID
, CFMutableArrayRef
*attributeList
, SOSEnginePeerMessageSentCallback
**sentCallback
, CFErrorRef
*error
){
2721 __block CFDataRef message
= NULL
;
2722 SOSEngineForPeerID(engine
, peerID
, error
, ^(SOSTransactionRef txn
, SOSPeerRef peer
) {
2723 message
= SOSEngineCreateMessage_locked(engine
, txn
, peer
, attributeList
, error
, sentCallback
);
2728 bool SOSEnginePeerDidConnect(SOSEngineRef engine
, CFStringRef peerID
, CFErrorRef
*error
) {
2729 return SOSEngineWithPeerID(engine
, peerID
, error
, ^(SOSPeerRef peer
, SOSCoderRef coder
, SOSDataSourceRef dataSource
, SOSTransactionRef txn
, bool *saveState
) {
2730 *saveState
= SOSPeerDidConnect(peer
);
2734 bool SOSEngineSetPeerConfirmedManifest(SOSEngineRef engine
, CFStringRef backupName
,
2735 CFDataRef keybagDigest
, CFDataRef manifestData
, CFErrorRef
*error
) {
2736 __block
bool ok
= true;
2738 ok
&= SOSEngineForPeerID(engine
, backupName
, error
, ^(SOSTransactionRef txn
, SOSPeerRef peer
) {
2740 bool incomplete
= false;
2741 SOSManifestRef confirmed
= NULL
;
2742 CFDataRef keybag
= SOSPeerGetKeyBag(peer
);
2743 CFDataRef computedKeybagDigest
= keybag
? CFDataCopySHA1Digest(keybag
, NULL
) : NULL
;
2744 if (CFEqualSafe(keybagDigest
, computedKeybagDigest
)) {
2745 ok
= confirmed
= SOSManifestCreateWithData(manifestData
, error
);
2747 // Set both confirmed and proposed (confirmed is just
2748 // for debug status, proposed is actually what's used
2749 // by the backup peer).
2750 SOSPeerSetConfirmedManifest(peer
, confirmed
);
2751 SOSPeerSetProposedManifest(peer
, confirmed
);
2754 // sbd missed a reset event, send it again
2755 // Force SOSEngineWriteToBackup_locked to call SOSPeerWriteReset, which clears
2756 // confirmed and proposed manifests and writes the keybag to the journal.
2757 SOSPeerSetMustSendMessage(peer
, true);
2760 // Stop changes from writing complete markers, unless SOSEngineWriteToBackup_locked() detects we are in sync
2761 SOSPeerSetSendObjects(peer
, false);
2762 // Write data for this peer if we can, technically not needed for non legacy protocol support all the time.
2763 ok
= SOSEngineWriteToBackup_locked(engine
, peer
, true, &dirty
, &incomplete
, error
);
2765 if (!ok
&& error
&& SecErrorGetOSStatus(*error
) == errSecInteractionNotAllowed
) {
2766 SOSEnsureBackupWhileUnlocked();
2769 CFReleaseSafe(confirmed
);
2770 CFReleaseSafe(computedKeybagDigest
);
2775 CFArrayRef
SOSEngineCopyBackupPeerNames(SOSEngineRef engine
, CFErrorRef
*error
) {
2776 __block CFMutableArrayRef backupNames
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2777 SOSEngineForEachBackupPeer(engine
, ^(SOSPeerRef peer
) {
2778 CFArrayAppendValue(backupNames
, SOSPeerGetID(peer
));
2783 CFStringRef
SOSEngineEnsureCopyBackupPeerForView(SOSEngineRef engine
, CFStringRef backupPeerID
, CFErrorRef
*error
) {
2784 __block CFStringRef backupName
= CFSTR("");
2785 SOSEngineForBackupPeer(engine
, backupPeerID
, ^(SOSPeerRef peer
) {
2786 backupName
= CFRetainSafe(SOSPeerGetID(peer
));
2791 static CFMutableDictionaryRef
SOSEngineCreateStateDictionary(CFStringRef peerID
, SOSManifestRef manifest
, CFSetRef vns
, CFStringRef coderString
) {
2792 CFNumberRef manifestCount
= CFNumberCreateWithCFIndex(kCFAllocatorDefault
, SOSManifestGetCount(manifest
));
2793 CFDataRef manifestHash
= SOSManifestGetDigest(manifest
, NULL
);
2794 CFMutableDictionaryRef result
= CFDictionaryCreateMutableForCFTypesWithSafe(kCFAllocatorDefault
,
2795 kSOSCCEngineStatePeerIDKey
, peerID
,
2796 kSOSCCEngineStateManifestCountKey
, manifestCount
,
2797 kSOSCCEngineStateManifestHashKey
, manifestHash
,
2798 kSOSCCEngineStateSyncSetKey
, asSet(vns
, NULL
),
2799 kSOSCCEngineStateCoderKey
, coderString
,
2801 CFReleaseNull(manifestCount
);
2805 static void SOSEngineAppendStateDictionary(CFMutableArrayRef stateArray
, CFStringRef peerID
, SOSManifestRef manifest
, CFSetRef vns
, CFStringRef coderString
) {
2806 CFMutableDictionaryRef newState
= SOSEngineCreateStateDictionary(peerID
, manifest
, vns
, coderString
);
2807 CFArrayAppendValue(stateArray
, newState
);
2808 CFReleaseNull(newState
);
2811 static CFArrayRef
SOSEngineCopyPeerConfirmedDigests_locked(SOSEngineRef engine
, CFErrorRef
*error
) {
2812 CFMutableArrayRef result
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
2813 CFDictionaryForEach(engine
->viewNameSet2ChangeTracker
, ^(const void *vns
, const void *ct
) {
2814 SOSManifestRef manifest
= SOSEngineCopyManifestWithViewNameSet_locked(engine
, vns
, error
);
2815 SOSEngineAppendStateDictionary(result
, NULL
, manifest
, vns
, NULL
);
2816 CFReleaseNull(manifest
);
2819 // Copy other peers even if we aren't in the circle, since we're observing it.
2820 SOSEngineForEachPeer_locked(engine
, ^(SOSPeerRef peer
) {
2821 CFTypeRef coderObject
= engine
->coders
? CFDictionaryGetValue(engine
->coders
, SOSPeerGetID(peer
)) : CFSTR("Coders not loaded.");
2822 CFStringRef coderState
= coderObject
? CFCopyDescription(coderObject
) : NULL
;
2823 SOSEngineAppendStateDictionary(result
, SOSPeerGetID(peer
), SOSPeerGetConfirmedManifest(peer
), SOSPeerGetViewNameSet(peer
), coderState
);
2824 CFReleaseNull(coderState
);
2829 CFArrayRef
SOSEngineCopyPeerConfirmedDigests(SOSEngineRef engine
, CFErrorRef
*error
) {
2830 __block CFArrayRef result
= NULL
;
2831 SOSEngineDoOnQueue(engine
, ^{
2832 result
= SOSEngineCopyPeerConfirmedDigests_locked(engine
, error
);
2837 SOSDataSourceRef
SOSEngineGetDataSource(SOSEngineRef engine
) {
2838 return engine
->dataSource
;
2841 #define ENGINELOGSTATE "engineLogState"
2842 void SOSEngineLogState(SOSEngineRef engine
) {
2843 CFErrorRef error
= NULL
;
2844 CFArrayRef confirmedDigests
= NULL
;
2846 secnotice(ENGINELOGSTATE
, "Start");
2848 require_action_quiet(engine
, retOut
, secnotice(ENGINELOGSTATE
, "No Engine Available"));
2849 confirmedDigests
= SOSEngineCopyPeerConfirmedDigests(engine
, &error
);
2850 require_action_quiet(confirmedDigests
, retOut
, secnotice(ENGINELOGSTATE
, "No engine peers: %@\n", error
));
2852 SOSCCForEachEngineStateAsStringFromArray(confirmedDigests
, ^(CFStringRef onePeerDescription
) {
2853 secnotice(ENGINELOGSTATE
, "%@", onePeerDescription
);
2857 CFReleaseNull(error
);
2858 CFReleaseNull(confirmedDigests
);
2859 secnotice(ENGINELOGSTATE
, "Finish");
2865 void TestSOSEngineDoOnQueue(CFTypeRef engine
, dispatch_block_t action
)
2867 dispatch_sync(((SOSEngineRef
)engine
)->queue
, action
);
2869 CFMutableDictionaryRef
TestSOSEngineGetCoders(CFTypeRef engine
){
2870 return ((SOSEngineRef
)engine
)->coders
;
2873 bool TestSOSEngineDoTxnOnQueue(CFTypeRef engine
, CFErrorRef
*error
, void(^transaction
)(SOSTransactionRef txn
, bool *commit
))
2875 return SOSDataSourceWithCommitQueue(((SOSEngineRef
)engine
)->dataSource
, error
, ^(SOSTransactionRef txn
, bool *commit
) {
2876 TestSOSEngineDoOnQueue((SOSEngineRef
)engine
, ^{ transaction(txn
, commit
); });
2879 bool SOSEngineGetCodersNeedSaving(SOSEngineRef engine
){
2880 return engine
->codersNeedSaving
;
2883 void SOSEngineSetCodersNeedSaving(SOSEngineRef engine
, bool saved
){
2884 engine
->codersNeedSaving
= saved
;