]>
Commit | Line | Data |
---|---|---|
5c19dc3a A |
1 | /* |
2 | * Copyright (c) 2012-2015 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | ||
25 | /* | |
26 | * SOSEngine.c - Implementation of a secure object syncing engine | |
27 | */ | |
28 | ||
29 | #include <Security/SecureObjectSync/SOSChangeTracker.h> | |
30 | #include <Security/SecureObjectSync/SOSEngine.h> | |
31 | #include <Security/SecureObjectSync/SOSDigestVector.h> | |
32 | #include <Security/SecureObjectSync/SOSInternal.h> | |
33 | #include <Security/SecureObjectSync/SOSPeer.h> | |
34 | #include <Security/SecureObjectSync/SOSViews.h> | |
35 | #include <Security/SecureObjectSync/SOSBackupEvent.h> | |
fa7225c8 | 36 | #include <Security/SecureObjectSync/SOSPersist.h> |
5c19dc3a A |
37 | #include <corecrypto/ccder.h> |
38 | #include <stdlib.h> | |
39 | #include <stdbool.h> | |
40 | #include <utilities/array_size.h> | |
41 | #include <utilities/SecCFCCWrappers.h> | |
42 | #include <utilities/SecCFError.h> | |
43 | #include <utilities/SecCFRelease.h> | |
44 | #include <utilities/SecCFWrappers.h> | |
45 | #include <utilities/der_plist.h> | |
46 | #include <utilities/der_plist_internal.h> | |
47 | #include <utilities/debugging.h> | |
48 | #include <utilities/iCloudKeychainTrace.h> | |
49 | #include <utilities/SecCoreCrypto.h> | |
50 | #include <utilities/SecFileLocations.h> | |
51 | #include <AssertMacros.h> | |
52 | #include <CoreFoundation/CoreFoundation.h> | |
53 | ||
54 | #include <securityd/SecItemServer.h> // TODO: We can't leave this here. | |
55 | #include <securityd/SOSCloudCircleServer.h> // TODO: We can't leave this here. | |
56 | #include <Security/SecItem.h> // TODO: We can't leave this here. | |
57 | #include <Security/SecItemPriv.h> // TODO: We can't leave this here. | |
58 | #include <securityd/SecItemSchema.h> | |
59 | #include <securityd/iCloudTrace.h> | |
60 | ||
61 | #include <CoreFoundation/CFURL.h> | |
62 | ||
63 | // | |
64 | // MARK: SOSEngine The Keychain database with syncable keychain support. | |
65 | // | |
66 | ||
fa7225c8 A |
67 | //---------------------------------------------------------------------------------------- |
68 | // MARK: Engine state v0 | |
69 | //---------------------------------------------------------------------------------------- | |
70 | ||
5c19dc3a A |
71 | // Key in dataSource for general engine state file. |
72 | // This file only has digest entries in it, no manifests. | |
73 | static const CFStringRef kSOSEngineState = CFSTR("engine-state"); | |
74 | ||
75 | // Keys in state dictionary | |
76 | static CFStringRef kSOSEngineManifestCacheKey = CFSTR("manifestCache"); | |
77 | static CFStringRef kSOSEnginePeerStateKey = CFSTR("peerState"); | |
78 | static CFStringRef kSOSEnginePeerIDsKey = CFSTR("peerIDs"); | |
79 | static CFStringRef kSOSEngineIDKey = CFSTR("id"); | |
80 | static CFStringRef kSOSEngineTraceDateKey = CFSTR("traceDate"); | |
81 | ||
fa7225c8 A |
82 | //---------------------------------------------------------------------------------------- |
83 | // MARK: Engine state v2 | |
84 | //---------------------------------------------------------------------------------------- | |
85 | ||
86 | static const CFIndex kCurrentEngineVersion = 2; | |
87 | // Keychain/datasource items | |
88 | // Used for the kSecAttrAccount when saving in the datasource with dsSetStateWithKey | |
89 | // Class D [kSecAttrAccessibleAlwaysPrivate/kSecAttrAccessibleAlwaysThisDeviceOnly] | |
90 | static CFStringRef kSOSEngineStatev2 = CFSTR("engine-state-v2"); | |
91 | static CFStringRef kSOSEnginePeerStates = CFSTR("engine-peer-states"); | |
92 | static CFStringRef kSOSEngineManifestCache = CFSTR("engine-manifest-cache"); | |
93 | #define kSOSEngineProtectionDomainClassD kSecAttrAccessibleAlwaysPrivate // >>>> or kSecAttrAccessibleAlwaysThisDeviceOnly | |
94 | // Class A [kSecAttrAccessibleWhenUnlockedThisDeviceOnly] | |
95 | static CFStringRef kSOSEngineCoders = CFSTR("engine-coders"); | |
96 | #define kSOSEngineProtectionDomainClassA kSecAttrAccessibleWhenUnlockedThisDeviceOnly | |
97 | ||
98 | // Keys for individual dictionaries | |
99 | // engine-state-v2 | |
100 | static CFStringRef kSOSEngineStateVersionKey = CFSTR("engine-stateVersion"); | |
101 | ||
102 | // Current save/load routines | |
103 | // SOSEngineCreate/SOSEngineLoad/SOSEngineSetState | |
104 | // SOSEngineSave/SOSEngineDoSave/SOSEngineCopyState | |
105 | // no save/load functions external to this file | |
106 | ||
107 | /* | |
108 | Divide engine state into five pieces: | |
109 | ||
110 | - General engine state | |
111 | - My peer ID | |
112 | - List of other (trusted) peer IDs | |
113 | ||
114 | - Coder data (formerly in peer state) | |
115 | - Backup Keybags (backup peers only) | |
116 | - Peer state (including manifest hashes -- just keys into ManifestCache) | |
117 | [__OpaqueSOSPeer/SOSPeerRef] | |
118 | must-send | |
119 | send-objects | |
120 | sequence-number | |
121 | Peer object states: | |
122 | pending-objects | |
123 | unwanted-manifest | |
124 | confirmed-manifest | |
125 | local-manifest | |
126 | pending-manifest | |
127 | Views | |
128 | ||
129 | - Manifest Cache | |
130 | - local manifest hashes (copy of local keychain) | |
131 | - peer manifest hashes | |
132 | ||
133 | These divisions are based on size, frequency of update, and protection domain | |
134 | ||
135 | The Manifest Cache is a dictionary where each key is a hash over its entry, | |
136 | which is a concatenation of 20 byte hashes of the keychain items. The local | |
137 | keychain is present as one entry. The other entries are subsets of that, one | |
138 | for each confirmed/pending/missing/unwanted shared with a peer. The local | |
139 | keychain entry can be re-created by iterating over the databse, whereas the | |
140 | others are built up through communicating with other peers. | |
141 | ||
142 | 83:d=2 hl=2 l= 13 prim: UTF8STRING :manifestCache | |
143 | 98:d=2 hl=4 l= 912 cons: SET | |
144 | 102:d=3 hl=2 l= 24 cons: SEQUENCE | |
145 | 104:d=4 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:DA39A3EE5E6B4B0D3255BFEF95601890AFD80709 | |
146 | 126:d=4 hl=2 l= 0 prim: OCTET STRING | |
147 | 128:d=3 hl=2 l= 124 cons: SEQUENCE | |
148 | 130:d=4 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:F9B59370A4733F0D174E8D220C5BE3AF062C775B | |
149 | 152:d=4 hl=2 l= 100 prim: OCTET STRING [HEX DUMP]:5A574BB4EC90C3BBCC69EE73CBFE039133AE807265D6A58003B8D205997EAB96390AAB207E63A2E270A476CAB5B2D9D2F7B0E55512AA957B58D5658E7EF907B069B83AA6BA941790A3C3C4A68292D59DABA3CA342966EFF82E1ACAEB691FD6E20772E17E | |
150 | 254:d=3 hl=4 l= 366 cons: SEQUENCE | |
151 | 258:d=4 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:2E69C2F7F3E014075B30004CE0EC6C1AD419EBF5 | |
152 | 280:d=4 hl=4 l= 340 prim: OCTET STRING [HEX DUMP]:07571E9678FD7D68812E409CC96C1F54834A099A0C3A2D12CCE2EA95F4505EA52F2C982B2ADEE3DA14D4712C000309BF63D54A98B61AA1D963C40E0E2531C83B28CA5BE6DA0D26400C3C77A618F711DD3CC0BF86CCBAF8AA3332973268B30EEBF21CD8184D9C8427CA13DECCC7BB83C80009A2EF45CCC07F586315C80CEEEEF5D5352FD000AAE6D9CBB4294D5959FD00198225AF9ABD09B341A2FDC278E9FD1465D6A58003B8D205997EAB96390AAB207E63A2E270A476CAB5B2D9D2F7B0E55512AA957B58D5658E7EF907B069B83AA6BA941790A3C3C4A68292D59D95C9D4D8A8BCA2E8242AB0D409F671F298B6DCAE9BC4238C09E07548CEFB300098606F9E4F230C99ABA3CA342966EFF82E1ACAEB691FD6E20772E17EB4FEFB84F8CF75C0C69C59532C354D175A59F961BA4D4DFA017FD8192288F14278AE76712E127D65FE616C7E4FD0713644F7C9A7ABA1CE065694A968 | |
153 | 624:d=3 hl=4 l= 386 cons: SEQUENCE | |
154 | 628:d=4 hl=2 l= 20 prim: OCTET STRING [HEX DUMP]:CCF179FF718C10F151E7409EDF1A06F0DF10DCAD | |
155 | 650:d=4 hl=4 l= 360 prim: OCTET STRING [HEX DUMP]:07571E9678FD7D68812E409CC96C1F54834A099A0C3A2D12CCE2EA95F4505EA52F2C982B2ADEE3DA14D4712C000309BF63D54A98B61AA1D963C40E0E2531C83B28CA5BE6DA0D26400C3C77A618F711DD3CC0BF86CCBAF8AA3332973268B30EEBF21CD8184D9C8427CA13DECCC7BB83C80009A2EF45CCC07F586315C80CEEEEF5D5352FD000AAE6D9CBB4294D5959FD00198225AF9ABD09B341A2FDC278E9FD145A574BB4EC90C3BBCC69EE73CBFE039133AE807265D6A58003B8D205997EAB96390AAB207E63A2E270A476CAB5B2D9D2F7B0E55512AA957B58D5658E7EF907B069B83AA6BA941790A3C3C4A68292D59D95C9D4D8A8BCA2E8242AB0D409F671F298B6DCAE9BC4238C09E07548CEFB300098606F9E4F230C99ABA3CA342966EFF82E1ACAEB691FD6E20772E17EB4FEFB84F8CF75C0C69C59532C354D175A59F961BA4D4DFA017FD8192288F14278AE76712E127D65FE616C7E4FD0713644F7C9A7ABA1CE065694A968 | |
156 | ||
157 | */ | |
158 | ||
5c19dc3a A |
159 | /* SOSEngine implementation. */ |
160 | struct __OpaqueSOSEngine { | |
161 | CFRuntimeBase _base; | |
162 | SOSDataSourceRef dataSource; | |
163 | CFStringRef myID; // My peerID in the circle | |
164 | // We need to address the issues of corrupt keychain items | |
165 | SOSManifestRef unreadable; // Possibly by having a set of unreadable items, to which we | |
166 | // add any corrupted items in the db that have yet to be deleted. | |
167 | // This happens if we notce corruption during a (read only) query. | |
168 | // We would also perma-subtract unreadable from manifest whenever | |
169 | // anyone asked for manifest. This result would be cached in | |
170 | // The manifestCache below, so we just need a key into the cache | |
171 | CFDataRef localMinusUnreadableDigest; // or a digest (CFDataRef of the right size). | |
172 | ||
173 | CFMutableDictionaryRef manifestCache; // digest -> ( refcount, manifest ) | |
5c19dc3a A |
174 | CFMutableDictionaryRef peerMap; // peerId -> SOSPeerRef |
175 | CFDictionaryRef viewNameSet2ChangeTracker; // CFSetRef of CFStringRef -> SOSChangeTrackerRef | |
176 | CFDictionaryRef viewName2ChangeTracker; // CFStringRef -> SOSChangeTrackerRef | |
177 | CFArrayRef peerIDs; | |
178 | CFDateRef lastTraceDate; // Last time we did a CloudKeychainTrace | |
fa7225c8 A |
179 | CFMutableDictionaryRef coders; |
180 | bool haveLoadedCoders; | |
181 | ||
182 | bool dirty; | |
183 | bool codersNeedSaving; | |
5c19dc3a A |
184 | |
185 | dispatch_queue_t queue; // Engine queue | |
186 | ||
e0e0d90e | 187 | dispatch_source_t save_timer; // Engine state save timer |
fa7225c8 | 188 | bool save_timer_pending; // Engine state timer running, read/modify on engine queue |
e0e0d90e | 189 | |
fa7225c8 A |
190 | dispatch_queue_t syncCompleteQueue; // Non-retained queue for async notificaion |
191 | SOSEnginePeerInSyncBlock syncCompleteListener; // Block to call to notify the listener. | |
5c19dc3a A |
192 | }; |
193 | ||
fa7225c8 A |
194 | static bool SOSEngineLoad(SOSEngineRef engine, SOSTransactionRef txn, CFErrorRef *error); |
195 | static bool SOSEngineSetPeers_locked(SOSEngineRef engine, SOSPeerMetaRef myPeerMeta, CFArrayRef trustedPeerMetas, CFArrayRef untrustedPeerMetas); | |
196 | static void SOSEngineApplyPeerState(SOSEngineRef engine, CFDictionaryRef peerStateMap); | |
197 | static void SOSEngineSynthesizePeerMetas(SOSEngineRef engine, CFMutableArrayRef trustedPeersMetas, CFMutableArrayRef untrustedPeers); | |
198 | static bool SOSEngineLoadCoders(SOSEngineRef engine, SOSTransactionRef txn, CFErrorRef *error); | |
199 | static bool SOSEngineDeleteV0State(SOSEngineRef engine, SOSTransactionRef txn, CFErrorRef *error); | |
200 | ||
5c19dc3a A |
201 | static CFStringRef SOSPeerIDArrayCreateString(CFArrayRef peerIDs) { |
202 | return peerIDs ? CFStringCreateByCombiningStrings(kCFAllocatorDefault, peerIDs, CFSTR(" ")) : CFSTR(""); | |
203 | } | |
204 | ||
205 | static CFStringRef SOSEngineCopyFormattingDesc(CFTypeRef cf, CFDictionaryRef formatOptions) { | |
206 | SOSEngineRef engine = (SOSEngineRef)cf; | |
207 | CFStringRef tpDesc = SOSPeerIDArrayCreateString(engine->peerIDs); | |
208 | 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); | |
209 | CFReleaseSafe(tpDesc); | |
210 | return desc; | |
211 | } | |
212 | ||
213 | static CFStringRef SOSEngineCopyDebugDesc(CFTypeRef cf) { | |
214 | return SOSEngineCopyFormattingDesc(cf, NULL); | |
215 | } | |
216 | ||
217 | static dispatch_queue_t sEngineQueue; | |
218 | static CFDictionaryRef sEngineMap; | |
219 | ||
220 | CFGiblisWithFunctions(SOSEngine, NULL, NULL, NULL, NULL, NULL, SOSEngineCopyFormattingDesc, SOSEngineCopyDebugDesc, NULL, NULL, ^{ | |
221 | sEngineQueue = dispatch_queue_create("SOSEngine queue", DISPATCH_QUEUE_SERIAL); | |
222 | sEngineMap = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
223 | }); | |
224 | ||
225 | #define _LOG_RAW_MESSAGES 0 | |
226 | void logRawMessage(CFDataRef message, bool sending, uint64_t seqno) | |
227 | { | |
228 | #if _LOG_RAW_MESSAGES | |
229 | CFStringRef hexMessage = NULL; | |
230 | if (message) { | |
231 | hexMessage = CFDataCopyHexString(message); | |
232 | if (sending) | |
233 | secnoticeq("engine", "%s RAW%1d %@", sending ? "send" : "recv", seqno?2:0, hexMessage); | |
234 | else | |
235 | secnoticeq("engine", "%s RAWx %@", sending ? "send" : "recv", hexMessage); // we don't know vers of received msg here | |
236 | } | |
237 | CFReleaseSafe(hexMessage); | |
238 | #endif | |
239 | } | |
fa7225c8 | 240 | |
5c19dc3a A |
241 | // |
242 | // Peer state layout. WRONG! It's an array now | |
243 | // The peer state is an array. | |
244 | // The first element of the array is a dictionary with any number of keys and | |
245 | // values in it (for future expansion) such as changing the digest size or type | |
246 | // or remembering boolean flags for a peers sake. | |
247 | // The next three are special in that they are manifest digests with special | |
248 | // meaning and rules as to how they are treated (These are dynamically updated | |
249 | // based on database activity so they have a fully history of all changes made | |
250 | // to the local db. The first is the manifest representing the pendingObjects | |
251 | // to send to the other peer. This is normally only ever appending to, and in | |
252 | // particular with transactions originating from the Keychain API that affect | |
253 | // syncable items will need to add the new objects digests to the pendingObjects list | |
254 | // while adding the digests of any tombstones encountered to the extra list. | |
255 | ||
256 | CFStringRef SOSEngineGetMyID(SOSEngineRef engine) { | |
257 | // TODO: this should not be needed | |
258 | return engine->myID; | |
259 | } | |
260 | ||
261 | // TEMPORARY: Get the list of IDs for cleanup, this shouldn't be used instead it should iterate KVS. | |
262 | CFArrayRef SOSEngineGetPeerIDs(SOSEngineRef engine) { | |
fa7225c8 | 263 | if(!engine) return NULL; |
5c19dc3a A |
264 | return engine->peerIDs; |
265 | } | |
266 | ||
267 | void SOSEngineClearCache(SOSEngineRef engine){ | |
268 | CFReleaseNull(engine->manifestCache); | |
269 | CFReleaseNull(engine->localMinusUnreadableDigest); | |
fa7225c8 A |
270 | if (engine->save_timer) |
271 | dispatch_source_cancel(engine->save_timer); | |
272 | dispatch_release(engine->queue); | |
273 | engine->queue = NULL; | |
5c19dc3a A |
274 | } |
275 | ||
276 | static SOSPeerRef SOSEngineCopyPeerWithMapEntry_locked(SOSEngineRef engine, CFStringRef peerID, CFTypeRef mapEntry, CFErrorRef *error) { | |
277 | SOSPeerRef peer = NULL; | |
278 | if (mapEntry && CFGetTypeID(mapEntry) == SOSPeerGetTypeID()) { | |
279 | // The mapEntry is an SOSPeer, so we're done. | |
280 | peer = (SOSPeerRef)CFRetain(mapEntry); | |
281 | } else { | |
282 | // The mapEntry is a peerState, attempt to initialize a new | |
283 | // peer iff peerID is in the set of trusted peerIDs | |
284 | if (engine->peerIDs && CFArrayContainsValue(engine->peerIDs, CFRangeMake(0, CFArrayGetCount(engine->peerIDs)), peerID)) { | |
285 | CFErrorRef localError = NULL; | |
286 | peer = SOSPeerCreateWithState(engine, peerID, mapEntry, &localError); | |
287 | if (!peer) { | |
288 | secerror("error inflating peer: %@: %@ from state: %@", peerID, localError, mapEntry); | |
289 | CFReleaseNull(localError); | |
290 | peer = SOSPeerCreateWithState(engine, peerID, NULL, error); | |
291 | } | |
292 | if (peer) { | |
293 | // Replace the map entry with the inflated peer. | |
294 | CFDictionarySetValue(engine->peerMap, peerID, peer); | |
295 | } | |
296 | } else { | |
297 | SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("peer: %@ is untrusted inflating not allowed"), peerID); | |
298 | } | |
299 | } | |
300 | return peer; | |
301 | } | |
302 | ||
303 | static SOSPeerRef SOSEngineCopyPeerWithID_locked(SOSEngineRef engine, CFStringRef peerID, CFErrorRef *error) { | |
304 | CFTypeRef mapEntry = CFDictionaryGetValue(engine->peerMap, peerID); | |
305 | SOSPeerRef peer = NULL; | |
306 | if (mapEntry) { | |
307 | peer = SOSEngineCopyPeerWithMapEntry_locked(engine, peerID, mapEntry, error); | |
308 | } else { | |
309 | peer = NULL; | |
e3d460c9 | 310 | secerror("peer: %@ not found, peerMap: %@, engine: %@", peerID, engine->peerMap, engine); |
5c19dc3a A |
311 | SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("peer: %@ not found"), peerID); |
312 | } | |
313 | return peer; | |
314 | } | |
315 | ||
316 | struct SOSEngineWithPeerContext { | |
317 | SOSEngineRef engine; | |
318 | void (^with)(SOSPeerRef peer); | |
319 | }; | |
320 | ||
321 | static void SOSEngineWithPeerMapEntry_locked(const void *peerID, const void *mapEntry, void *context) { | |
322 | struct SOSEngineWithPeerContext *ewp = context; | |
323 | SOSPeerRef peer = SOSEngineCopyPeerWithMapEntry_locked(ewp->engine, peerID, mapEntry, NULL); | |
324 | if (peer) { | |
325 | ewp->with(peer); | |
326 | CFRelease(peer); | |
327 | } | |
328 | } | |
329 | ||
330 | static void SOSEngineForEachPeer_locked(SOSEngineRef engine, void (^with)(SOSPeerRef peer)) { | |
331 | struct SOSEngineWithPeerContext ewp = { .engine = engine, .with = with }; | |
332 | CFDictionaryRef peerMapCopy = CFDictionaryCreateCopy(NULL, engine->peerMap); | |
333 | CFDictionaryApplyFunction(peerMapCopy, SOSEngineWithPeerMapEntry_locked, &ewp); | |
334 | CFRelease(peerMapCopy); | |
335 | } | |
336 | ||
337 | static void SOSEngineWithBackupPeerMapEntry_locked(const void *peerID, const void *mapEntry, void *context) { | |
338 | struct SOSEngineWithPeerContext *ewp = context; | |
339 | // v0 backup peer is always in map but we only consider it a backup peer if it has a keybag. | |
340 | if (SOSPeerMapEntryIsBackup(mapEntry)) { | |
341 | SOSPeerRef peer = SOSEngineCopyPeerWithMapEntry_locked(ewp->engine, peerID, mapEntry, NULL); | |
342 | if (peer) { | |
343 | ewp->with(peer); | |
344 | CFRelease(peer); | |
345 | } | |
346 | } | |
347 | } | |
348 | ||
349 | static void SOSEngineForEachBackupPeer_locked(SOSEngineRef engine, void (^with)(SOSPeerRef peer)) { | |
350 | struct SOSEngineWithPeerContext ewp = { .engine = engine, .with = with }; | |
351 | CFDictionaryRef peerMapCopy = CFDictionaryCreateCopy(NULL, engine->peerMap); | |
352 | CFDictionaryApplyFunction(peerMapCopy, SOSEngineWithBackupPeerMapEntry_locked, &ewp); | |
353 | CFRelease(peerMapCopy); | |
354 | } | |
355 | ||
356 | // | |
357 | // Manifest cache | |
358 | // | |
359 | SOSManifestRef SOSEngineGetManifestForDigest(SOSEngineRef engine, CFDataRef digest) { | |
360 | if (!engine->manifestCache || !digest) return NULL; | |
361 | SOSManifestRef manifest = (SOSManifestRef)CFDictionaryGetValue(engine->manifestCache, digest); | |
362 | if (!manifest) return NULL; | |
363 | if (CFGetTypeID(manifest) != SOSManifestGetTypeID()) { | |
364 | secerror("dropping corrupt manifest for %@ from cache", digest); | |
365 | CFDictionaryRemoveValue(engine->manifestCache, digest); | |
366 | return NULL; | |
367 | } | |
368 | ||
369 | return manifest; | |
370 | } | |
371 | ||
372 | void SOSEngineAddManifest(SOSEngineRef engine, SOSManifestRef manifest) { | |
373 | CFDataRef digest = SOSManifestGetDigest(manifest, NULL); | |
374 | if (digest) { | |
375 | if (!engine->manifestCache) | |
376 | engine->manifestCache = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
377 | CFDictionaryAddValue(engine->manifestCache, digest, manifest); | |
378 | } | |
379 | } | |
380 | ||
381 | CFDataRef SOSEnginePatchRecordAndCopyDigest(SOSEngineRef engine, SOSManifestRef base, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error) { | |
382 | CFDataRef digest = NULL; | |
383 | SOSManifestRef manifest = SOSManifestCreateWithPatch(base, removals, additions, error); | |
384 | if (manifest) { | |
385 | SOSEngineAddManifest(engine, manifest); | |
386 | digest = CFRetainSafe(SOSManifestGetDigest(manifest, NULL)); | |
387 | } | |
388 | CFReleaseSafe(manifest); | |
389 | return digest; | |
390 | } | |
391 | ||
392 | SOSManifestRef SOSEngineCopyPersistedManifest(SOSEngineRef engine, CFDictionaryRef persisted, CFStringRef key) { | |
393 | return CFRetainSafe(SOSEngineGetManifestForDigest(engine, asData(CFDictionaryGetValue(persisted, key), NULL))); | |
394 | } | |
395 | ||
396 | CFMutableArrayRef SOSEngineCopyPersistedManifestArray(SOSEngineRef engine, CFDictionaryRef persisted, CFStringRef key, CFErrorRef *error) { | |
397 | CFMutableArrayRef manifests = NULL; | |
398 | CFArrayRef digests = NULL; | |
399 | CFDataRef digest; | |
400 | if (asArrayOptional(CFDictionaryGetValue(persisted, key), &digests, error)) | |
401 | manifests = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
402 | if (digests) CFArrayForEachC(digests, digest) { | |
403 | SOSManifestRef manifest = SOSEngineGetManifestForDigest(engine, digest); | |
404 | if (manifest) | |
405 | CFArrayAppendValue(manifests, manifest); | |
406 | } | |
407 | return manifests; | |
408 | } | |
409 | ||
410 | static CFDictionaryRef SOSEngineCopyEncodedManifestCache_locked(SOSEngineRef engine, CFErrorRef *error) { | |
411 | CFMutableDictionaryRef mfc = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
412 | SOSEngineForEachPeer_locked(engine, ^(SOSPeerRef peer) { | |
413 | SOSPeerAddManifestsInUse(peer, mfc); | |
414 | }); | |
415 | return mfc; | |
416 | } | |
417 | ||
418 | #if 0 | |
419 | static bool SOSEngineGCManifests_locked(SOSEngineRef engine, CFErrorRef *error) { | |
420 | __block struct SOSDigestVector mdInCache = SOSDigestVectorInit; | |
421 | __block struct SOSDigestVector mdInUse = SOSDigestVectorInit; | |
422 | struct SOSDigestVector mdUnused = SOSDigestVectorInit; | |
423 | struct SOSDigestVector mdMissing = SOSDigestVectorInit; | |
424 | bool ok = true; | |
425 | ||
426 | SOSEngineForEachPeer_locked(engine, ^(SOSPeerRef peer) { | |
427 | SOSPeerMarkDigestsInUse(peer, &mdInUse); | |
428 | }); | |
429 | ||
430 | if (engine->manifestCache) { | |
431 | CFDictionaryForEach(engine->manifestCache, ^(const void *key, const void *value) { | |
432 | CFDataRef digest = (CFDataRef)key; | |
433 | if (isData(digest)) | |
434 | SOSDigestVectorAppend(&mdInCache, CFDataGetBytePtr(digest)); | |
435 | }); | |
436 | ||
437 | // Delete unused manifests. | |
438 | SOSDigestVectorDiff(&mdInCache, &mdInUse, &mdUnused, &mdMissing); | |
439 | SOSManifestRef unused = SOSManifestCreateWithDigestVector(&mdUnused, NULL); | |
440 | SOSManifestForEach(unused, ^(CFDataRef digest, bool *stop) { | |
441 | if (digest) | |
442 | CFDictionaryRemoveValue(engine->manifestCache, digest); | |
443 | }); | |
444 | CFReleaseSafe(unused); | |
445 | } | |
446 | ||
447 | SOSDigestVectorFree(&mdInCache); | |
448 | SOSDigestVectorFree(&mdInUse); | |
449 | SOSDigestVectorFree(&mdUnused); | |
450 | SOSDigestVectorFree(&mdMissing); | |
451 | return ok; | |
452 | } | |
453 | #endif | |
454 | ||
455 | // | |
456 | // End of Manifest cache | |
457 | // | |
458 | ||
fa7225c8 A |
459 | //---------------------------------------------------------------------------------------- |
460 | // MARK: Coders | |
461 | //---------------------------------------------------------------------------------------- | |
462 | ||
463 | /* | |
464 | Each peer has an associated coder, whcih the engine keeps track of in a | |
465 | CFDictionary indexed by peerID. The coders are read from disk when first needed, | |
466 | then kept in memory as SOSCoders. | |
467 | ||
468 | N.B. Don't rollback coder in memory if a transaction is rolled back, since this | |
469 | might lead to reuse of an IV. | |
470 | */ | |
471 | ||
472 | static bool SOSEngineCopyCoderData(SOSEngineRef engine, CFStringRef peerID, CFDataRef *coderData, CFErrorRef *error) { | |
473 | bool ok = true; | |
474 | SOSCoderRef coder = (SOSCoderRef)CFDictionaryGetValue(engine->coders, peerID); | |
475 | if (coder && (CFGetTypeID(coder) == SOSCoderGetTypeID())) { | |
476 | CFErrorRef localError = NULL; | |
477 | ok = *coderData = SOSCoderCopyDER(coder, &localError); | |
478 | if (!ok) { | |
479 | secerror("failed to der encode coder for peer %@, dropping it: %@", peerID, localError); | |
480 | CFDictionaryRemoveValue(engine->coders, peerID); | |
481 | CFErrorPropagate(localError, error); | |
482 | } | |
483 | } else { | |
484 | *coderData = NULL; | |
485 | } | |
486 | return ok; | |
487 | } | |
488 | ||
489 | static SOSCoderRef SOSEngineGetCoderInTx_locked(SOSEngineRef engine, SOSTransactionRef txn, CFStringRef peerID, CFErrorRef *error) { | |
490 | if (!engine->haveLoadedCoders) { | |
491 | engine->haveLoadedCoders = SOSEngineLoadCoders(engine, txn, error); | |
492 | ||
493 | if (!engine->haveLoadedCoders) { | |
494 | return NULL; | |
495 | } | |
496 | } | |
497 | ||
498 | SOSCoderRef coder = (SOSCoderRef)CFDictionaryGetValue(engine->coders, peerID); | |
499 | if (!coder || (CFGetTypeID(coder) != SOSCoderGetTypeID())) { | |
500 | SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No coder for peer: %@"), peerID); | |
501 | } | |
502 | return coder; | |
503 | } | |
504 | ||
505 | static SOSCoderRef SOSEngineGetCoder_locked(SOSEngineRef engine, CFStringRef peerID, CFErrorRef *error) { | |
506 | return SOSEngineGetCoderInTx_locked(engine, NULL, peerID, error); | |
507 | } | |
508 | ||
509 | static bool SOSEngineEnsureCoder_locked(SOSEngineRef engine, CFStringRef peerID, SOSFullPeerInfoRef myPeerInfo, SOSPeerInfoRef peerInfo, SOSCoderRef ourCoder, CFErrorRef *error) { | |
510 | if (!ourCoder || !SOSCoderIsFor(ourCoder, peerInfo, myPeerInfo)) { | |
511 | secinfo("coder", "New coder for id %@.", peerID); | |
512 | CFErrorRef localError = NULL; | |
513 | SOSCoderRef coder = SOSCoderCreate(peerInfo, myPeerInfo, kCFBooleanFalse, &localError); | |
514 | if (!coder) { | |
515 | secerror("Failed to create coder for %@: %@", peerID, localError); | |
516 | CFErrorPropagate(localError, error); | |
517 | return false; | |
518 | } | |
519 | CFDictionarySetValue(engine->coders, peerID, coder); | |
520 | CFReleaseNull(coder); | |
521 | } | |
522 | return true; | |
523 | } | |
524 | ||
525 | bool SOSEngineInitializePeerCoder(SOSEngineRef engine, SOSFullPeerInfoRef myPeerInfo, SOSPeerInfoRef peerInfo, CFErrorRef *error) { | |
526 | __block bool ok = true; | |
527 | CFStringRef peerID = SOSPeerInfoGetPeerID(peerInfo); | |
528 | ok &= SOSEngineForPeerID(engine, peerID, error, ^(SOSTransactionRef txn, SOSPeerRef peer, SOSCoderRef coder) { | |
529 | ok = SOSEngineEnsureCoder_locked(engine, peerID, myPeerInfo, peerInfo, coder, error); | |
530 | }); | |
531 | return ok; | |
532 | } | |
533 | ||
5c19dc3a A |
534 | static bool SOSEngineGCPeerState_locked(SOSEngineRef engine, CFErrorRef *error) { |
535 | bool ok = true; | |
536 | ||
537 | //require_quiet(ok = SOSEngineGCManifests_locked(engine, error), exit); | |
538 | ||
539 | //exit: | |
540 | return ok; | |
541 | } | |
542 | ||
543 | static CFMutableDictionaryRef SOSEngineCopyPeerState_locked(SOSEngineRef engine, CFErrorRef *error) { | |
544 | CFMutableDictionaryRef peerState = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
545 | CFDictionaryForEach(engine->peerMap, ^(const void *key, const void *value) { | |
546 | CFDictionaryRef state = NULL; | |
547 | if (value && CFGetTypeID(value) == SOSPeerGetTypeID()) { | |
548 | CFErrorRef localError = NULL; | |
549 | // Inflated peer | |
550 | state = SOSPeerCopyState((SOSPeerRef)value, &localError); | |
551 | if (!state) | |
552 | secnotice("engine", "%@ failed to encode peer: %@", key, localError); | |
553 | CFReleaseNull(localError); | |
554 | // TODO: Potentially replace inflated peer with deflated peer in peerMap | |
555 | } else if (value) { | |
556 | // We have a deflated peer. | |
557 | state = CFRetainSafe(value); | |
558 | } | |
559 | ||
560 | if (state) { | |
561 | CFDictionarySetValue(peerState, key, state); | |
562 | CFReleaseSafe(state); | |
563 | } | |
564 | }); | |
565 | return peerState; | |
566 | } | |
567 | ||
fa7225c8 A |
568 | static CFMutableDictionaryRef SOSEngineCopyPeerCoders_locked(SOSEngineRef engine, CFErrorRef *error) { |
569 | CFMutableDictionaryRef coders = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
570 | CFDictionaryForEach(engine->peerMap, ^(const void *key, const void *value) { | |
571 | CFDataRef coderData = NULL; | |
572 | CFErrorRef localError = NULL; | |
573 | bool ok = SOSEngineCopyCoderData(engine, (CFStringRef)key, &coderData, &localError); | |
574 | if (!ok) { | |
575 | secnotice("engine", "%@ no coder for peer: %@", key, localError); | |
576 | } | |
577 | if (ok && coderData) { | |
578 | CFDictionarySetValue(coders, key, coderData); | |
579 | } | |
580 | CFReleaseNull(coderData); | |
581 | CFReleaseNull(localError); | |
582 | }); | |
583 | return coders; | |
584 | } | |
585 | ||
586 | //---------------------------------------------------------------------------------------- | |
587 | // MARK: Engine state v2 Save | |
588 | //---------------------------------------------------------------------------------------- | |
589 | ||
590 | // Coders and keybags | |
591 | ||
592 | static CFDataRef SOSEngineCopyCoders(SOSEngineRef engine, CFErrorRef *error) { | |
593 | // Copy the CFDataRef version of the coders into a dictionary, which is then DER-encoded for saving | |
594 | CFDictionaryRef coders = SOSEngineCopyPeerCoders_locked(engine, error); | |
595 | CFDataRef der = CFPropertyListCreateDERData(kCFAllocatorDefault, coders, error); | |
596 | CFReleaseSafe(coders); | |
5c19dc3a A |
597 | return der; |
598 | } | |
599 | ||
fa7225c8 A |
600 | static bool SOSEngineSaveCoders(SOSEngineRef engine, SOSTransactionRef txn, CFErrorRef *error) { |
601 | // MUST hold engine lock | |
602 | // Device must be unlocked for this to succeed | |
603 | bool ok = true; | |
604 | if (engine->codersNeedSaving) { | |
605 | CFDataRef derCoders = SOSEngineCopyCoders(engine, error); | |
606 | bool ok = derCoders && SOSDataSourceSetStateWithKey(engine->dataSource, txn, kSOSEngineCoders, | |
607 | kSOSEngineProtectionDomainClassA, derCoders, error); | |
608 | if (ok) { | |
609 | engine->codersNeedSaving = false; | |
610 | } | |
611 | CFReleaseSafe(derCoders); | |
612 | } | |
613 | return ok; | |
614 | } | |
615 | ||
616 | static CFDictionaryRef SOSEngineCopyBasicState(SOSEngineRef engine, CFErrorRef *error) { | |
617 | // Create a version of the in-memory engine state for saving to disk | |
618 | CFMutableDictionaryRef state = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
619 | if (engine->myID) | |
620 | CFDictionarySetValue(state, kSOSEngineIDKey, engine->myID); | |
621 | if (engine->peerIDs) | |
622 | CFDictionarySetValue(state, kSOSEnginePeerIDsKey, engine->peerIDs); | |
623 | if (engine->lastTraceDate) | |
624 | CFDictionarySetValue(state, kSOSEngineTraceDateKey, engine->lastTraceDate); | |
625 | ||
626 | SOSPersistCFIndex(state, kSOSEngineStateVersionKey, kCurrentEngineVersion); | |
627 | return state; | |
628 | } | |
629 | ||
630 | static bool SOSEngineDoSaveOneState(SOSEngineRef engine, SOSTransactionRef txn, CFStringRef key, CFStringRef pdmn, | |
631 | CFDictionaryRef state, CFErrorRef *error) { | |
632 | CFDataRef derState = CFPropertyListCreateDERData(kCFAllocatorDefault, state, error); | |
633 | bool ok = derState && SOSDataSourceSetStateWithKey(engine->dataSource, txn, key, pdmn, derState, error); | |
5c19dc3a A |
634 | CFReleaseSafe(derState); |
635 | return ok; | |
636 | } | |
637 | ||
fa7225c8 A |
638 | static bool SOSEngineDoSave(SOSEngineRef engine, SOSTransactionRef txn, CFErrorRef *error) { |
639 | bool ok = true; | |
640 | ||
641 | CFDictionaryRef state = SOSEngineCopyBasicState(engine, error); | |
642 | ok &= state && SOSEngineDoSaveOneState(engine, txn, kSOSEngineStatev2, kSOSEngineProtectionDomainClassD, state, error); | |
643 | CFReleaseNull(state); | |
644 | ||
645 | state = SOSEngineCopyPeerState_locked(engine, error); | |
646 | ok &= state && SOSEngineDoSaveOneState(engine, txn, kSOSEnginePeerStates, kSOSEngineProtectionDomainClassD, state, error); | |
647 | CFReleaseNull(state); | |
648 | ||
649 | state = SOSEngineCopyEncodedManifestCache_locked(engine, error); | |
650 | ok &= state && SOSEngineDoSaveOneState(engine, txn, kSOSEngineManifestCache, kSOSEngineProtectionDomainClassD, state, error); | |
651 | CFReleaseNull(state); | |
652 | ||
653 | ok &= SOSEngineSaveCoders(engine, txn, error); | |
654 | ||
655 | SOSEngineDeleteV0State(engine, txn, NULL); | |
656 | ||
657 | return ok; | |
658 | } | |
659 | ||
660 | #if ENGINE_DELAY_SAVE | |
661 | ||
e0e0d90e A |
662 | #define SOSENGINE_SAVE_TIMEOUT (NSEC_PER_MSEC * 500ull) |
663 | #define SOSENGINE_SAVE_LEEWAY (NSEC_PER_MSEC * 500ull) | |
664 | #define SOSENGINE_SAVE_MAX_DELAY (NSEC_PER_MSEC * 500ull) | |
665 | ||
666 | #if !(TARGET_IPHONE_SIMULATOR) | |
667 | static void SOSEngineShouldSave(SOSEngineRef engine) { | |
fa7225c8 A |
668 | bool start_timer = false; |
669 | ||
670 | if (engine->save_timer == NULL) { | |
671 | // Schedule the timer to fire on a concurrent queue, so we can follow | |
672 | // the proper procedure of acquiring a dataSource and then engine queues. | |
673 | engine->save_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)); | |
674 | dispatch_source_set_event_handler(engine->save_timer, ^{ | |
675 | CFErrorRef dsWithError = NULL; | |
676 | ||
677 | // Start with clearing the pending state so that any other caller | |
678 | // get their own timer, worse case it that we get a duplicate store. | |
679 | dispatch_sync(engine->queue, ^{ | |
680 | engine->save_timer_pending = false; | |
681 | }); | |
e0e0d90e | 682 | |
fa7225c8 A |
683 | if (engine->dataSource) { |
684 | if (!SOSDataSourceWith(engine->dataSource, &dsWithError, ^(SOSTransactionRef txn, bool *commit) { | |
685 | dispatch_sync(engine->queue, ^{ | |
686 | CFErrorRef saveError = NULL; | |
687 | if (!SOSEngineDoSave(engine, txn, &saveError)) { | |
688 | secerrorq("Failed to save engine state: %@", saveError); | |
689 | CFReleaseNull(saveError); | |
690 | } | |
691 | }); | |
692 | })) { | |
693 | secerrorq("Failed to open dataSource to save engine state: %@", dsWithError); | |
694 | CFReleaseNull(dsWithError); | |
695 | } | |
e3d460c9 | 696 | } |
fa7225c8 A |
697 | |
698 | xpc_transaction_end(); | |
699 | }); | |
700 | start_timer = true; | |
701 | assert(engine->save_timer_pending == false); | |
702 | } | |
703 | ||
704 | if (engine->save_timer_pending) | |
705 | return; | |
706 | ||
707 | engine->save_timer_pending = true; | |
708 | ||
709 | // Start a trasaction, then start the timer, the handler for the timer will end | |
710 | // the transaction. | |
711 | xpc_transaction_begin(); | |
712 | ||
e0e0d90e A |
713 | // Set the timer's fire time to now + SOSENGINE_SAVE_TIMEOUT seconds with a SOSENGINE_SAVE_LEEWAY fuzz factor. |
714 | dispatch_source_set_timer(engine->save_timer, | |
715 | dispatch_time(DISPATCH_TIME_NOW, SOSENGINE_SAVE_TIMEOUT), | |
716 | DISPATCH_TIME_FOREVER, SOSENGINE_SAVE_LEEWAY); | |
fa7225c8 A |
717 | |
718 | if (start_timer) | |
719 | dispatch_resume(engine->save_timer); | |
720 | ||
e0e0d90e A |
721 | } |
722 | #endif | |
723 | ||
fa7225c8 A |
724 | #endif /* ENGINE_DELAY_SAVE */ |
725 | ||
e0e0d90e | 726 | static bool SOSEngineSave(SOSEngineRef engine, SOSTransactionRef txn, CFErrorRef *error) { |
e3d460c9 A |
727 | // Don't save engine state from tests |
728 | if (!engine->dataSource) | |
729 | return true; | |
fa7225c8 | 730 | #if (TARGET_IPHONE_SIMULATOR) || !ENGINE_DELAY_SAVE |
e0e0d90e A |
731 | return SOSEngineDoSave(engine, txn, error); |
732 | #else | |
733 | SOSEngineShouldSave(engine); | |
734 | #endif | |
735 | return true; | |
736 | } | |
737 | ||
fa7225c8 A |
738 | //---------------------------------------------------------------------------------------- |
739 | // MARK: Engine state v2 Load/Restore | |
740 | //---------------------------------------------------------------------------------------- | |
741 | ||
742 | // Restore the in-memory state of engine from saved state loaded from the db | |
743 | static bool SOSEngineSetManifestCacheWithDictionary(SOSEngineRef engine, CFDictionaryRef manifestCache, CFErrorRef *error) { | |
744 | __block bool ok = true; | |
745 | CFReleaseNull(engine->manifestCache); | |
746 | if (manifestCache) { | |
747 | engine->manifestCache = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
748 | CFDictionaryForEach(manifestCache, ^(const void *key, const void *value) { | |
749 | CFDataRef data = (CFDataRef)value; | |
750 | if (isData(data)) { | |
751 | SOSManifestRef mf = SOSManifestCreateWithData(data, NULL); | |
752 | if (mf) | |
753 | CFDictionarySetValue(engine->manifestCache, key, mf); | |
754 | CFReleaseSafe(mf); | |
755 | } | |
756 | }); | |
757 | } | |
758 | ||
759 | return ok; | |
760 | } | |
761 | ||
762 | static bool SOSEngineUpdateStateWithDictionary(SOSEngineRef engine, CFDictionaryRef stateDict, CFErrorRef *error) { | |
763 | bool ok = true; | |
764 | #if 0 | |
765 | if (stateDict) { | |
766 | // If kCurrentEngineVersion > 2, uncomment and fill in code below | |
767 | CFIndex engineVersion = 0 ; | |
768 | bool versionPresent = SOSPeerGetOptionalPersistedCFIndex(stateDict, kSOSEngineStateVersionKey, &engineVersion); | |
769 | if (versionPresent && (engineVersion != kCurrentEngineVersion)) { | |
770 | // need migration | |
771 | } | |
772 | } | |
773 | #endif | |
774 | return ok; | |
775 | } | |
776 | ||
777 | static bool SOSEngineSetStateWithDictionary(SOSEngineRef engine, CFDictionaryRef stateDict, CFErrorRef *error) { | |
778 | bool ok = true; | |
779 | if (stateDict) { | |
780 | SOSEngineUpdateStateWithDictionary(engine, stateDict, error); | |
781 | CFRetainAssign(engine->myID, asString(CFDictionaryGetValue(stateDict, kSOSEngineIDKey), NULL)); | |
782 | CFRetainAssign(engine->peerIDs, asArray(CFDictionaryGetValue(stateDict, kSOSEnginePeerIDsKey), NULL)); | |
783 | CFRetainAssign(engine->lastTraceDate, asDate(CFDictionaryGetValue(stateDict, kSOSEngineTraceDateKey), NULL)); | |
784 | ||
785 | } | |
786 | secnotice("engine", "%@", engine); | |
787 | return ok; | |
788 | } | |
789 | ||
790 | static bool SOSEngineSetPeerStateWithDictionary(SOSEngineRef engine, CFDictionaryRef peerStateDict, CFErrorRef *error) { | |
791 | // Set the in-memory peer state using the dictionary version of the DER-encoded version from disk | |
792 | CFMutableArrayRef untrustedPeers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
793 | CFMutableArrayRef trustedPeersMetas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
794 | SOSEngineApplyPeerState(engine, asDictionary(peerStateDict, NULL)); | |
795 | SOSEngineSynthesizePeerMetas(engine, trustedPeersMetas, untrustedPeers); | |
796 | SOSEngineSetPeers_locked(engine, engine->myID, trustedPeersMetas, untrustedPeers); | |
797 | CFReleaseNull(trustedPeersMetas); | |
798 | CFReleaseNull(untrustedPeers); | |
799 | return true; | |
800 | } | |
801 | ||
802 | static CFMutableDictionaryRef derStateToDictionaryCopy(CFDataRef state, CFErrorRef *error) { | |
803 | bool ok = true; | |
804 | CFMutableDictionaryRef stateDict = NULL; | |
805 | if (state) { | |
806 | const uint8_t *der = CFDataGetBytePtr(state); | |
807 | const uint8_t *der_end = der + CFDataGetLength(state); | |
808 | ok = der = der_decode_dictionary(kCFAllocatorDefault, kCFPropertyListMutableContainers, (CFDictionaryRef *)&stateDict, error, der, der_end); | |
809 | if (der && der != der_end) { | |
810 | ok = SOSErrorCreate(kSOSErrorDecodeFailure, error, NULL, CFSTR("trailing %td bytes at end of state"), der_end - der); | |
811 | } | |
812 | if (!ok) { | |
813 | CFReleaseNull(stateDict); | |
814 | } | |
815 | } | |
816 | return stateDict; | |
817 | } | |
818 | ||
819 | static bool SOSEngineLoadCoders(SOSEngineRef engine, SOSTransactionRef txn, CFErrorRef *error) { | |
820 | // Read the serialized engine state from the datasource (aka keychain) and populate the in-memory engine | |
821 | bool ok = true; | |
822 | CFDataRef derCoders = NULL; | |
823 | CFMutableDictionaryRef codersDict = NULL; | |
824 | ||
825 | derCoders = SOSDataSourceCopyStateWithKey(engine->dataSource, kSOSEngineCoders, kSOSEngineProtectionDomainClassA, txn, error); | |
826 | require_quiet(derCoders, xit); | |
827 | codersDict = derStateToDictionaryCopy(derCoders, error); | |
828 | require_quiet(codersDict, xit); | |
829 | ||
830 | CFDictionaryForEach(engine->peerMap, ^(const void *peerID, const void *peerState) { | |
831 | if (peerID) { | |
832 | if (!CFDictionaryContainsKey(engine->coders, peerID)) { | |
833 | CFDataRef coderData = asData(CFDictionaryGetValue(codersDict, peerID), NULL); | |
834 | if (coderData) { | |
835 | CFErrorRef createError = NULL; | |
836 | SOSCoderRef coder = SOSCoderCreateFromData(coderData, &createError); | |
837 | if (coder) { | |
838 | // Sanity check | |
839 | CFStringRef coderid = SOSCoderGetID(coder); | |
840 | if (!CFEqualSafe(coderid, (CFStringRef)peerID)) { | |
841 | secerror("Coder id %@ on disk does not match: %@", coderid, peerID); | |
842 | } else { | |
843 | CFDictionaryAddValue(engine->coders, peerID, coder); | |
844 | } | |
845 | } else { | |
846 | secnotice("coder", "Coder for '%@' failed to create: %@", peerID, createError); | |
847 | } | |
848 | CFReleaseNull(createError); | |
849 | CFReleaseNull(coder); | |
850 | } else { | |
851 | // Needed a coder, didn't find one, notify the account to help us out. | |
852 | // Next attempt to sync will fix this | |
853 | SOSCCEnsurePeerRegistration(); | |
854 | } | |
855 | } | |
856 | ||
857 | } | |
858 | }); | |
859 | xit: | |
860 | CFReleaseNull(derCoders); | |
861 | CFReleaseNull(codersDict); | |
862 | return ok; | |
863 | } | |
864 | ||
865 | static bool SOSEngineDeleteV0State(SOSEngineRef engine, SOSTransactionRef txn, CFErrorRef *error) { | |
866 | // SOSDataSourceDeleteStateWithKey(engine->dataSource, kSOSEngineState, kSOSEngineProtectionDomainClassD, txn, error); | |
867 | ||
868 | // Create effectively empty state until delete is working | |
869 | CFMutableDictionaryRef state = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
870 | if (engine->myID) | |
871 | CFDictionarySetValue(state, kSOSEngineIDKey, engine->myID); | |
872 | CFDataRef derState = CFPropertyListCreateDERData(kCFAllocatorDefault, state, error); | |
873 | CFReleaseNull(state); | |
874 | ||
875 | bool ok = derState && SOSDataSourceSetStateWithKey(engine->dataSource, txn, kSOSEngineState, kSOSEngineProtectionDomainClassD, derState, error); | |
876 | CFReleaseSafe(derState); | |
877 | return ok; | |
878 | } | |
879 | ||
880 | static bool SOSEngineLoad(SOSEngineRef engine, SOSTransactionRef txn, CFErrorRef *error) { | |
881 | // Read the serialized engine state from the datasource (aka keychain) and populate the in-memory engine | |
882 | bool ok = true; | |
883 | CFDataRef basicEngineState = NULL; | |
884 | CFMutableDictionaryRef engineState = NULL; | |
885 | CFDictionaryRef manifestCache = NULL; | |
886 | CFDictionaryRef peerStateDict = NULL; | |
887 | CFMutableDictionaryRef codersDict = NULL; | |
888 | // Look for the v2 engine state first | |
889 | basicEngineState = SOSDataSourceCopyStateWithKey(engine->dataSource, kSOSEngineStatev2, kSOSEngineProtectionDomainClassD, txn, error); | |
890 | if (basicEngineState) { | |
891 | CFDataRef data = NULL; | |
892 | engineState = derStateToDictionaryCopy(basicEngineState, error); | |
893 | ||
894 | data = SOSDataSourceCopyStateWithKey(engine->dataSource, kSOSEngineManifestCache, kSOSEngineProtectionDomainClassD, txn, error); | |
895 | manifestCache = derStateToDictionaryCopy(data, error); | |
896 | CFReleaseNull(data); | |
897 | ||
898 | data = SOSDataSourceCopyStateWithKey(engine->dataSource, kSOSEnginePeerStates, kSOSEngineProtectionDomainClassD, txn, error); | |
899 | peerStateDict = derStateToDictionaryCopy(data, error); | |
900 | CFReleaseNull(data); | |
901 | } else { | |
902 | // Look for original V0 engine state next | |
903 | CFDataRef v0EngineStateData = SOSDataSourceCopyStateWithKey(engine->dataSource, kSOSEngineState, kSOSEngineProtectionDomainClassD, txn, error); | |
904 | if (v0EngineStateData) { | |
905 | engineState = derStateToDictionaryCopy(v0EngineStateData, error); | |
906 | if (engineState) { | |
907 | manifestCache = CFRetainSafe(asDictionary(CFDictionaryGetValue(engineState, kSOSEngineManifestCacheKey), NULL)); | |
908 | peerStateDict = CFRetainSafe(asDictionary(CFDictionaryGetValue(engineState, kSOSEnginePeerStateKey), NULL)); | |
909 | } | |
910 | CFReleaseNull(v0EngineStateData); | |
911 | } | |
912 | secnotice("coder", "Migrating from v0 engine state; dropping coders and forcing re-negotiation"); | |
913 | SOSCCEnsurePeerRegistration(); | |
914 | SOSCCSyncWithAllPeers(); | |
915 | } | |
916 | ||
917 | ok = engineState && SOSEngineSetStateWithDictionary(engine, engineState, error); | |
918 | ||
919 | ok &= SOSEngineSetManifestCacheWithDictionary(engine, manifestCache, error); | |
920 | ||
921 | ok &= peerStateDict && SOSEngineSetPeerStateWithDictionary(engine, peerStateDict, error); | |
922 | ||
923 | CFReleaseSafe(basicEngineState); | |
924 | CFReleaseSafe(engineState); | |
925 | CFReleaseSafe(manifestCache); | |
926 | CFReleaseSafe(peerStateDict); | |
927 | CFReleaseSafe(codersDict); | |
928 | return ok; | |
929 | } | |
930 | ||
931 | bool SOSTestEngineSaveWithDER(SOSEngineRef engine, CFDataRef derState, CFErrorRef *error) { | |
932 | assert(true); | |
933 | return true; | |
934 | } | |
935 | ||
936 | /* | |
937 | bool SOSTestEngineSave(SOSEngineRef engine, CFErrorRef *error) { | |
938 | ||
939 | } | |
940 | ||
941 | */ | |
942 | ||
943 | //---------------------------------------------------------------------------------------- | |
944 | // MARK: Change Trackers and Peer Manifests | |
945 | //---------------------------------------------------------------------------------------- | |
946 | ||
5c19dc3a A |
947 | static SOSManifestRef SOSEngineCreateManifestWithViewNameSet_locked(SOSEngineRef engine, CFSetRef viewNameSet, CFErrorRef *error) { |
948 | // TODO: Potentially tell all changeTrackers to track manifests ( //forall ct do SOSChangeTrackerSetConcrete(ct, true); | |
949 | // and read the entire dataSource and pass all objects though the filter here, instead of | |
950 | // forcing the datasource to be able to do "smart" queries | |
951 | return SOSDataSourceCopyManifestWithViewNameSet(engine->dataSource, viewNameSet, error); | |
952 | } | |
953 | ||
954 | static SOSChangeTrackerRef SOSEngineCopyChangeTrackerWithViewNameSet_locked(SOSEngineRef engine, CFSetRef viewNameSet, CFErrorRef *error) { | |
955 | SOSChangeTrackerRef ct = (SOSChangeTrackerRef)CFDictionaryGetValue(engine->viewNameSet2ChangeTracker, viewNameSet); | |
956 | if (!ct) | |
957 | SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("no change tracker for view set %@"), viewNameSet); | |
958 | return CFRetainSafe(ct); | |
959 | } | |
960 | ||
961 | static SOSManifestRef SOSEngineCopyManifestWithViewNameSet_locked(SOSEngineRef engine, CFSetRef viewNameSet, CFErrorRef *error) { | |
962 | SOSChangeTrackerRef ct = SOSEngineCopyChangeTrackerWithViewNameSet_locked(engine, viewNameSet, error); | |
963 | if (!ct) | |
964 | return NULL; | |
965 | ||
966 | SOSManifestRef manifest = SOSChangeTrackerCopyManifest(ct, NULL); | |
967 | if (!manifest) { | |
968 | manifest = SOSEngineCreateManifestWithViewNameSet_locked(engine, viewNameSet, error); // Do the SQL query | |
969 | SOSChangeTrackerSetManifest(ct, manifest); | |
970 | } | |
971 | CFReleaseSafe(ct); | |
972 | return manifest; | |
973 | } | |
974 | ||
975 | SOSManifestRef SOSEngineCopyLocalPeerManifest_locked(SOSEngineRef engine, SOSPeerRef peer, CFErrorRef *error) { | |
976 | return SOSEngineCopyManifestWithViewNameSet_locked(engine, SOSPeerGetViewNameSet(peer), error); | |
977 | } | |
978 | ||
979 | #define withViewAndBackup(VIEW) do { with(VIEW); if (!isTomb) with(VIEW ## _tomb); } while(0) | |
980 | ||
981 | ||
982 | // Invoke with once for each view an object is in. | |
983 | // TODO: Move this function into the DataSource | |
984 | static void SOSEngineObjectWithView(SOSEngineRef engine, SOSObjectRef object, void (^with)(CFStringRef view)) { | |
985 | // Filter items into v0 only view here | |
986 | SecDbItemRef item = (SecDbItemRef)object; // TODO: Layer violation, breaks tests | |
987 | if (isDictionary(object)) { | |
988 | CFTypeRef isTombValue = CFDictionaryGetValue((CFDictionaryRef)object, kSecAttrTombstone); | |
989 | bool isTomb = isTombValue && CFBooleanGetValue(isTombValue); | |
990 | // We are in the test just assume v0 and v2 views. | |
991 | withViewAndBackup(kSOSViewKeychainV0); | |
992 | } else if (SecDbItemIsSyncableOrCorrupted(item)) { | |
993 | const SecDbClass *iclass = SecDbItemGetClass(item); | |
994 | CFTypeRef pdmn = SecDbItemGetCachedValueWithName(item, kSecAttrAccessible); | |
995 | if ((iclass == &genp_class || iclass == &inet_class || iclass == &keys_class || iclass == &cert_class) | |
996 | && isString(pdmn) | |
997 | && (CFEqual(pdmn, kSecAttrAccessibleWhenUnlocked) | |
998 | || CFEqual(pdmn, kSecAttrAccessibleAfterFirstUnlock) | |
fa7225c8 | 999 | || CFEqual(pdmn, kSecAttrAccessibleAlwaysPrivate) |
5c19dc3a A |
1000 | || CFEqual(pdmn, kSecAttrAccessibleWhenUnlockedThisDeviceOnly) |
1001 | || CFEqual(pdmn, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) | |
fa7225c8 | 1002 | || CFEqual(pdmn, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate))) |
5c19dc3a A |
1003 | { |
1004 | CFTypeRef tomb = SecDbItemGetCachedValueWithName(item, kSecAttrTombstone); | |
1005 | char cvalue = 0; | |
1006 | bool isTomb = (isNumber(tomb) && CFNumberGetValue(tomb, kCFNumberCharType, &cvalue) && cvalue == 1); | |
1007 | CFTypeRef viewHint = SecDbItemGetCachedValueWithName(item, kSecAttrSyncViewHint); | |
1008 | if (viewHint == NULL) { | |
1009 | if (iclass == &cert_class) { | |
1010 | withViewAndBackup(kSOSViewOtherSyncable); | |
1011 | } else { | |
1012 | if (!SecDbItemGetCachedValueWithName(item, kSecAttrTokenID)) { | |
1013 | withViewAndBackup(kSOSViewKeychainV0); | |
1014 | } | |
1015 | CFTypeRef agrp = SecDbItemGetCachedValueWithName(item, kSecAttrAccessGroup); | |
1016 | if (iclass == &keys_class && CFEqualSafe(agrp, CFSTR("com.apple.security.sos"))) { | |
1017 | withViewAndBackup(kSOSViewiCloudIdentity); | |
1018 | } else if (CFEqualSafe(agrp, CFSTR("com.apple.cfnetwork"))) { | |
1019 | withViewAndBackup(kSOSViewAutofillPasswords); | |
1020 | } else if (CFEqualSafe(agrp, CFSTR("com.apple.safari.credit-cards"))) { | |
1021 | withViewAndBackup(kSOSViewSafariCreditCards); | |
1022 | } else if (iclass == &genp_class) { | |
1023 | if (CFEqualSafe(agrp, CFSTR("apple")) && | |
1024 | CFEqualSafe(SecDbItemGetCachedValueWithName(item, kSecAttrService), CFSTR("AirPort"))) { | |
1025 | withViewAndBackup(kSOSViewWiFi); | |
1026 | } else if (CFEqualSafe(agrp, CFSTR("com.apple.sbd"))) { | |
1027 | withViewAndBackup(kSOSViewBackupBagV0); | |
1028 | } else { | |
1029 | withViewAndBackup(kSOSViewOtherSyncable); // (genp) | |
1030 | } | |
1031 | } else { | |
1032 | withViewAndBackup(kSOSViewOtherSyncable); // (inet || keys) | |
1033 | } | |
1034 | } | |
1035 | } else { | |
1036 | with(viewHint); | |
1037 | if (!isTomb) { | |
1038 | CFStringRef viewHintTomb = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@-tomb"), viewHint); | |
1039 | if (viewHintTomb) { | |
1040 | with(viewHintTomb); | |
1041 | CFRelease(viewHintTomb); | |
1042 | } | |
1043 | } | |
1044 | } | |
1045 | } | |
1046 | } else { | |
1047 | // TODO: general queries | |
1048 | #if 0 | |
1049 | SOSViewRef view; | |
1050 | CFArrayForEachC(engine->views, view) { | |
1051 | bool inView = SOSViewQueryMatchItem(view, item); | |
1052 | if (inView) { | |
1053 | CFStringRef viewName = SOSViewCopyName(view); | |
1054 | with(viewName); | |
1055 | CFReleaseSafe(viewName); | |
1056 | } | |
1057 | } | |
1058 | #endif | |
1059 | } | |
1060 | } | |
1061 | ||
fa7225c8 A |
1062 | // |
1063 | // Deliver delayed notifiations of changes in keychain | |
1064 | // | |
1065 | ||
1066 | static void | |
1067 | SOSSendViewNotification(CFSetRef viewNotifications) | |
1068 | { | |
1069 | CFNotificationCenterRef center = CFNotificationCenterGetDarwinNotifyCenter(); | |
1070 | ||
1071 | CFSetForEach(viewNotifications, ^(const void *value) { | |
1072 | secinfo("view", "Sending view notification for view %@", value); | |
1073 | ||
1074 | CFStringRef str = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.apple.security.view-change.%@"), value); | |
1075 | if (str == NULL) | |
1076 | return; | |
1077 | ||
1078 | CFNotificationCenterPostNotificationWithOptions(center, str, NULL, NULL, 0); | |
1079 | CFRelease(str); | |
1080 | ||
1081 | }); | |
1082 | } | |
1083 | ||
1084 | static void | |
1085 | SOSArmViewNotificationEvents(CFSetRef viewNotifications) | |
1086 | { | |
1087 | static CFMutableSetRef pendingViewNotifications; | |
1088 | static dispatch_once_t onceToken; | |
1089 | static dispatch_queue_t queue; | |
1090 | ||
1091 | dispatch_once(&onceToken, ^{ | |
1092 | queue = dispatch_queue_create("ViewNotificationQueue", NULL); | |
1093 | }); | |
1094 | if (queue == NULL || CFSetGetCount(viewNotifications) == 0) | |
1095 | return; | |
1096 | ||
1097 | /* | |
1098 | * PendingViewNotifications is only modified on queue. | |
1099 | * PendingViewNotifications is used as a signal if a timer is running. | |
1100 | * | |
1101 | * If a timer is running, new events are just added to the existing | |
1102 | * pendingViewNotifications. | |
1103 | */ | |
1104 | ||
1105 | #define DELAY_OF_NOTIFICATION_IN_NS (NSEC_PER_SEC) | |
1106 | ||
1107 | CFRetain(viewNotifications); | |
1108 | ||
1109 | dispatch_async(queue, ^{ | |
1110 | if (pendingViewNotifications == NULL) { | |
1111 | pendingViewNotifications = CFSetCreateMutableCopy(NULL, 0, viewNotifications); | |
1112 | ||
1113 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)DELAY_OF_NOTIFICATION_IN_NS), queue, ^{ | |
1114 | SOSSendViewNotification(pendingViewNotifications); | |
1115 | ||
1116 | // when timer hits, clear out set of modified views | |
1117 | CFRelease(pendingViewNotifications); | |
1118 | pendingViewNotifications = NULL; | |
1119 | }); | |
1120 | } else { | |
1121 | CFSetUnion(pendingViewNotifications, viewNotifications); | |
1122 | } | |
1123 | CFRelease(viewNotifications); | |
1124 | }); | |
1125 | } | |
1126 | ||
1127 | ||
5c19dc3a A |
1128 | // |
1129 | // SOSChangeMapper - Helper for SOSEngineUpdateChanges_locked | |
1130 | // | |
1131 | struct SOSChangeMapper { | |
1132 | SOSEngineRef engine; | |
1133 | SOSTransactionRef txn; | |
1134 | SOSDataSourceTransactionPhase phase; | |
1135 | SOSDataSourceTransactionSource source; | |
1136 | CFMutableDictionaryRef ct2changes; | |
fa7225c8 | 1137 | CFMutableSetRef viewNotifications; |
5c19dc3a A |
1138 | }; |
1139 | ||
1140 | static void SOSChangeMapperInit(struct SOSChangeMapper *cm, SOSEngineRef engine, SOSTransactionRef txn, SOSDataSourceTransactionPhase phase, SOSDataSourceTransactionSource source) { | |
1141 | cm->engine = engine; | |
1142 | cm->txn = txn; | |
1143 | cm->phase = phase; | |
1144 | cm->source = source; | |
1145 | cm->ct2changes = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
fa7225c8 A |
1146 | cm->viewNotifications = CFSetCreateMutableForCFTypes(kCFAllocatorDefault); |
1147 | } | |
1148 | ||
1149 | static void SOSChangeMapperSendNotifications(struct SOSChangeMapper *cm) | |
1150 | { | |
1151 | SOSArmViewNotificationEvents(cm->viewNotifications); | |
5c19dc3a A |
1152 | } |
1153 | ||
1154 | static void SOSChangeMapperFree(struct SOSChangeMapper *cm) { | |
1155 | CFReleaseSafe(cm->ct2changes); | |
fa7225c8 A |
1156 | CFReleaseSafe(cm->viewNotifications); |
1157 | } | |
1158 | ||
1159 | static void SOSChangeMapperAddViewNotification(struct SOSChangeMapper *cm, CFStringRef view) | |
1160 | { | |
1161 | assert(isString(view)); | |
1162 | ||
1163 | // aggregate the PCS view into one notification | |
1164 | if (CFStringHasPrefix(view, CFSTR("PCS-"))) { | |
1165 | view = CFSTR("PCS"); | |
1166 | } | |
1167 | CFSetSetValue(cm->viewNotifications, view); | |
5c19dc3a A |
1168 | } |
1169 | ||
1170 | static void SOSChangeMapperAppendObject(struct SOSChangeMapper *cm, SOSChangeTrackerRef ct, bool isAdd, CFTypeRef object) { | |
1171 | CFMutableArrayRef changes = (CFMutableArrayRef)CFDictionaryGetValue(cm->ct2changes, ct); | |
1172 | if (!changes) { | |
1173 | changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
1174 | CFDictionarySetValue(cm->ct2changes, ct, changes); | |
1175 | CFReleaseSafe(changes); | |
1176 | } | |
1177 | isAdd ? SOSChangesAppendAdd(changes, object) : SOSChangesAppendDelete(changes, object); | |
1178 | } | |
1179 | ||
1180 | static bool SOSChangeMapperIngestChange(struct SOSChangeMapper *cm, bool isAdd, CFTypeRef change) { | |
1181 | bool someoneCares = false; | |
1182 | if (isData(change)) { | |
1183 | // TODO: Reenable assertion once the tests have been updated | |
1184 | //assert(!isAdd); | |
1185 | // We got a digest for a deleted object. Our dataSource probably couldn't find | |
1186 | // an object with this digest, probably because it went missing, or it was | |
1187 | // discovered to be corrupted. | |
1188 | // Tell all our changeTrackers about this digest since we don't know who might need it. | |
1189 | CFDictionaryForEach(cm->engine->viewNameSet2ChangeTracker, ^(const void *viewNameSet, const void *ct) { | |
1190 | SOSChangeMapperAppendObject(cm, (SOSChangeTrackerRef)ct, isAdd, change); | |
1191 | }); | |
1192 | someoneCares = CFDictionaryGetCount(cm->engine->viewNameSet2ChangeTracker); | |
1193 | } else { | |
1194 | // We got an object let's figure out which views it's in and schedule it for | |
1195 | // delivery to all changeTrackers interested in any of those views. | |
1196 | SOSObjectRef object = (SOSObjectRef)change; | |
1197 | CFMutableSetRef changeTrackerSet = CFSetCreateMutableForCFTypes(kCFAllocatorDefault); | |
fa7225c8 | 1198 | // First gather all the changeTrackers interested in this object (eliminating dupes by collecting them in a set) |
5c19dc3a A |
1199 | SOSEngineObjectWithView(cm->engine, object, ^(CFStringRef viewName) { |
1200 | const void *ctorset = CFDictionaryGetValue(cm->engine->viewName2ChangeTracker, viewName); | |
1201 | if (isSet(ctorset)) { | |
1202 | CFSetForEach((CFSetRef)ctorset, ^(const void *ct) { CFSetAddValue(changeTrackerSet, ct); }); | |
1203 | } else if (ctorset) { | |
1204 | CFSetAddValue(changeTrackerSet, ctorset); | |
1205 | } | |
fa7225c8 A |
1206 | |
1207 | ||
1208 | SOSChangeMapperAddViewNotification(cm, viewName); | |
5c19dc3a A |
1209 | }); |
1210 | // Then append the object to the changes array in the ct2changes dictionary keyed by viewSet | |
1211 | CFSetForEach(changeTrackerSet, ^(const void *ct) { | |
1212 | SOSChangeMapperAppendObject(cm, (SOSChangeTrackerRef)ct, isAdd, object); | |
1213 | }); | |
1214 | someoneCares = CFSetGetCount(changeTrackerSet); | |
1215 | CFReleaseSafe(changeTrackerSet); | |
1216 | } | |
1217 | return someoneCares; | |
1218 | } | |
1219 | ||
1220 | static bool SOSChangeMapperSend(struct SOSChangeMapper *cm, CFErrorRef *error) { | |
1221 | __block bool ok = true; | |
1222 | CFDictionaryForEach(cm->ct2changes, ^(const void *ct, const void *changes) { | |
1223 | ok &= SOSChangeTrackerTrackChanges((SOSChangeTrackerRef)ct, cm->engine, cm->txn, cm->source, cm->phase, (CFArrayRef)changes, error); | |
1224 | }); | |
1225 | return ok; | |
1226 | } | |
1227 | ||
1228 | static bool SOSEngineUpdateChanges_locked(SOSEngineRef engine, SOSTransactionRef txn, SOSDataSourceTransactionPhase phase, SOSDataSourceTransactionSource source, CFArrayRef changes, CFErrorRef *error) | |
1229 | { | |
fa7225c8 | 1230 | secnoticeq("engine", "%@: %s %s %ld changes, txn=%@, %p", engine->myID, phase == kSOSDataSourceTransactionWillCommit ? "will-commit" : phase == kSOSDataSourceTransactionDidCommit ? "did-commit" : "did-rollback", source == kSOSDataSourceSOSTransaction ? "sos" : "api", CFArrayGetCount(changes), txn, txn); |
5c19dc3a A |
1231 | bool ok = true; |
1232 | switch (phase) { | |
1233 | case kSOSDataSourceTransactionDidRollback: | |
fa7225c8 | 1234 | ok &= SOSEngineLoad(engine, txn, error); |
5c19dc3a | 1235 | break; |
fa7225c8 | 1236 | case kSOSDataSourceTransactionDidCommit: // Corruption causes us to process items at DidCommit |
5c19dc3a | 1237 | case kSOSDataSourceTransactionWillCommit: |
5c19dc3a | 1238 | { |
fa7225c8 A |
1239 | bool mappedItemChanged = false; |
1240 | ||
5c19dc3a A |
1241 | struct SOSChangeMapper cm; |
1242 | SOSChangeMapperInit(&cm, engine, txn, phase, source); | |
1243 | SecDbEventRef event; | |
1244 | CFArrayForEachC(changes, event) { | |
1245 | CFTypeRef deleted = NULL; | |
1246 | CFTypeRef inserted = NULL; | |
1247 | SecDbEventGetComponents(event, &deleted, &inserted, error); | |
fa7225c8 A |
1248 | if (deleted) { |
1249 | bool someoneCares = SOSChangeMapperIngestChange(&cm, false, deleted); | |
1250 | if (someoneCares) { | |
1251 | mappedItemChanged = true; | |
1252 | } | |
1253 | } | |
5c19dc3a A |
1254 | if (inserted) { |
1255 | bool someoneCares = SOSChangeMapperIngestChange(&cm, true, inserted); | |
fa7225c8 A |
1256 | if (someoneCares) { |
1257 | mappedItemChanged = true; | |
1258 | } | |
5c19dc3a A |
1259 | if (!someoneCares && !isData(inserted) && SecDbItemIsTombstone((SecDbItemRef)inserted) && !CFEqualSafe(SecDbItemGetValue((SecDbItemRef)inserted, &v7utomb, NULL), kCFBooleanTrue)) { |
1260 | CFErrorRef localError = NULL; | |
1261 | // A tombstone was inserted but there is no changetracker that | |
1262 | // cares about it. | |
1263 | if (!SecDbItemDoDeleteSilently((SecDbItemRef)inserted, (SecDbConnectionRef)txn, &localError)) { | |
1264 | secerror("failed to delete tombstone %@ that no one cares about: %@", inserted, localError); | |
1265 | CFReleaseNull(localError); | |
1266 | } | |
1267 | } | |
1268 | } | |
1269 | } | |
1270 | ||
1271 | ok &= SOSChangeMapperSend(&cm, error); | |
fa7225c8 | 1272 | SOSChangeMapperSendNotifications(&cm); // Trigger notifications for view that changes changed |
5c19dc3a A |
1273 | SOSChangeMapperFree(&cm); |
1274 | ||
fa7225c8 A |
1275 | if (ok && phase == kSOSDataSourceTransactionWillCommit) { |
1276 | // Only consider writing if we're in the WillCommit phase. | |
1277 | // DidCommit phases happen outside the database lock and | |
1278 | // writing to the DBConn will cause deadlocks. | |
1279 | if (mappedItemChanged) { | |
1280 | // Write SOSEngine and SOSPeer state to disk | |
1281 | ok &= SOSEngineSave(engine, txn, error); | |
1282 | } else { | |
1283 | secnotice("engine", "Not saving engine state, nothing changed."); | |
1284 | } | |
5c19dc3a A |
1285 | } |
1286 | break; | |
1287 | } | |
1288 | } | |
1289 | return ok; | |
1290 | } | |
1291 | ||
1292 | static void SOSEngineSetNotifyPhaseBlock(SOSEngineRef engine) { | |
fa7225c8 A |
1293 | SOSDataSourceAddNotifyPhaseBlock(engine->dataSource, ^(SOSDataSourceRef ds, SOSTransactionRef txn, SOSDataSourceTransactionPhase phase, SOSDataSourceTransactionSource source, CFArrayRef changes) { |
1294 | dispatch_sync(engine->queue, ^{ | |
1295 | CFErrorRef localError = NULL; | |
1296 | if (!SOSEngineUpdateChanges_locked(engine, txn, phase, source, changes, &localError)) { | |
1297 | secerror("updateChanged failed: %@", localError); | |
1298 | } | |
1299 | CFReleaseSafe(localError); | |
1300 | }); | |
5c19dc3a A |
1301 | }); |
1302 | } | |
1303 | ||
1304 | #if 0 // TODO: update these checks | |
1305 | static void SOSEngineCircleChanged_sanitycheck(SOSEngineRef engine, CFStringRef myPeerID, CFArrayRef trustedPeers, CFArrayRef untrustedPeers) { | |
1306 | // Logging code | |
1307 | CFMutableArrayRef addedPeers = CFArrayCreateDifference(kCFAllocatorDefault, trustedPeers, engine->peerIDs); | |
1308 | CFMutableArrayRef deletedPeers = CFArrayCreateDifference(kCFAllocatorDefault, engine->peerIDs, trustedPeers); | |
1309 | CFMutableArrayRef addedUntrustedPeers = CFArrayCreateDifference(kCFAllocatorDefault, untrustedPeers, engine->peerIDs); | |
1310 | CFMutableArrayRef deletedUntrustedPeers = CFArrayCreateDifference(kCFAllocatorDefault, engine->peerIDs, untrustedPeers); | |
1311 | ||
1312 | CFStringRef tpDesc = SOSPeerIDArrayCreateString(trustedPeers); | |
1313 | CFStringRef apDesc = SOSPeerIDArrayCreateString(addedPeers); | |
1314 | CFStringRef dpDesc = SOSPeerIDArrayCreateString(deletedPeers); | |
1315 | CFStringRef aupDesc = SOSPeerIDArrayCreateString(addedUntrustedPeers); | |
1316 | CFStringRef dupDesc = SOSPeerIDArrayCreateString(deletedUntrustedPeers); | |
1317 | secnotice("engine", "trusted %@ added %@ removed %@ add ut: %@ rem ut: %@", tpDesc, apDesc, dpDesc, aupDesc, dupDesc); | |
1318 | CFReleaseSafe(dupDesc); | |
1319 | CFReleaseSafe(aupDesc); | |
1320 | CFReleaseSafe(dpDesc); | |
1321 | CFReleaseSafe(apDesc); | |
1322 | CFReleaseSafe(tpDesc); | |
1323 | ||
1324 | // Assertions: | |
1325 | // Ensure SOSAccount isn't giving us the runaround. | |
1326 | // Assert that trustedPeers, untrustedPeers and myPeerId are disjoint sets | |
1327 | if (trustedPeers) { | |
1328 | CFMutableArrayRef allTrustedPeers = CFArrayCreateDifference(kCFAllocatorDefault, trustedPeers, untrustedPeers); | |
1329 | assert(CFEqual(trustedPeers, allTrustedPeers)); | |
1330 | CFReleaseSafe(allTrustedPeers); | |
1331 | assert(!CFArrayContainsValue(trustedPeers, CFRangeMake(0, CFArrayGetCount(trustedPeers)), myPeerID)); | |
1332 | } | |
1333 | if (untrustedPeers) { | |
1334 | CFMutableArrayRef allUntrustedPeers = CFArrayCreateDifference(kCFAllocatorDefault, untrustedPeers, trustedPeers); | |
1335 | assert(CFEqual(untrustedPeers, allUntrustedPeers)); | |
1336 | CFReleaseSafe(allUntrustedPeers); | |
1337 | assert(!CFArrayContainsValue(untrustedPeers, CFRangeMake(0, CFArrayGetCount(trustedPeers)), myPeerID)); | |
1338 | } | |
1339 | ||
1340 | CFReleaseNull(deletedUntrustedPeers); | |
1341 | CFReleaseNull(addedUntrustedPeers); | |
1342 | CFReleaseNull(deletedPeers); | |
1343 | CFReleaseNull(addedPeers); | |
1344 | ||
1345 | // End of logging and asertions, actual code here. | |
1346 | } | |
1347 | #endif | |
1348 | ||
1349 | static SOSChangeTrackerRef SOSReferenceAndGetChangeTracker(CFDictionaryRef lookup, CFMutableDictionaryRef referenced, CFSetRef viewNameSet) { | |
1350 | SOSChangeTrackerRef ct = (SOSChangeTrackerRef)CFDictionaryGetValue(referenced, viewNameSet); | |
1351 | if (!ct) { | |
1352 | ct = (SOSChangeTrackerRef)CFDictionaryGetValue(lookup, viewNameSet); | |
1353 | if (ct) { | |
1354 | SOSChangeTrackerResetRegistration(ct); | |
1355 | CFDictionarySetValue(referenced, viewNameSet, ct); | |
1356 | } else { | |
1357 | ct = SOSChangeTrackerCreate(kCFAllocatorDefault, false, NULL, NULL); | |
1358 | CFDictionarySetValue(referenced, viewNameSet, ct); | |
1359 | CFReleaseSafe(ct); | |
1360 | } | |
1361 | } | |
1362 | return ct; | |
1363 | } | |
1364 | ||
5c19dc3a | 1365 | static void CFStringAppendPeerIDAndViews(CFMutableStringRef desc, CFStringRef peerID, CFSetRef vns) { |
fa7225c8 A |
1366 | CFStringSetPerformWithDescription(vns, ^(CFStringRef description) { |
1367 | CFStringAppendFormat(desc, NULL, CFSTR(" %@ (%@)"), peerID, description); | |
1368 | }); | |
5c19dc3a A |
1369 | } |
1370 | ||
1371 | // Must be called after updating viewNameSet2ChangeTracker | |
1372 | static void SOSEngineUpdateViewName2ChangeTracker(SOSEngineRef engine) { | |
1373 | // Create the mapping from viewName -> ChangeTracker used for lookup during change notification | |
1374 | CFMutableDictionaryRef newViewName2ChangeTracker = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
1375 | CFDictionaryForEach(engine->viewNameSet2ChangeTracker, ^(const void *viewNameSet, const void *ct) { | |
1376 | CFSetForEach(viewNameSet, ^(const void *viewName) { | |
1377 | const void *ctorset = NULL; | |
1378 | if (CFDictionaryGetValueIfPresent(newViewName2ChangeTracker, viewName, &ctorset)) { | |
1379 | if (isSet(ctorset)) { | |
1380 | CFSetAddValue((CFMutableSetRef)ctorset, ct); | |
1381 | } else if (!CFEqual(ct, ctorset)) { | |
1382 | CFMutableSetRef set = CFSetCreateMutableForCFTypes(kCFAllocatorDefault); | |
1383 | CFSetAddValue(set, ctorset); | |
1384 | CFSetAddValue(set, ct); | |
1385 | CFDictionaryReplaceValue(newViewName2ChangeTracker, viewName, set); | |
1386 | CFRelease(set); | |
1387 | } | |
1388 | } else { | |
1389 | CFDictionarySetValue(newViewName2ChangeTracker, viewName, ct); | |
1390 | } | |
1391 | }); | |
1392 | }); | |
1393 | CFAssignRetained(engine->viewName2ChangeTracker, newViewName2ChangeTracker); | |
1394 | } | |
1395 | ||
1396 | static void SOSEngineSetBackupBag(SOSEngineRef engine, SOSObjectRef bagItem); | |
1397 | ||
1398 | // This is called only if we are in a circle and we should listen for keybag changes | |
1399 | static void SOSEngineRegisterBackupBagV0Tracker(SOSEngineRef engine, CFMutableDictionaryRef newViewNameSet2ChangeTracker, CFMutableStringRef desc) { | |
1400 | SOSChangeTrackerRef bbct = SOSReferenceAndGetChangeTracker(engine->viewNameSet2ChangeTracker, newViewNameSet2ChangeTracker, SOSViewsGetV0BackupBagViewSet()); | |
1401 | SOSChangeTrackerRegisterChangeUpdate(bbct, ^bool(SOSChangeTrackerRef ct, SOSEngineRef engine, SOSTransactionRef txn, SOSDataSourceTransactionSource source, SOSDataSourceTransactionPhase phase, CFArrayRef changes, CFErrorRef *error) { | |
1402 | SOSChangeRef change; | |
1403 | CFArrayForEachC(changes, change) { | |
1404 | CFTypeRef object = NULL; | |
1405 | bool isAdd = SOSChangeGetObject(change, &object); | |
1406 | SecDbItemRef dbi = (SecDbItemRef)object; | |
1407 | if (!isData(object) && | |
1408 | CFEqualSafe(SecDbItemGetCachedValueWithName(dbi, kSecAttrService), CFSTR("SecureBackupService")) && | |
1409 | CFEqualSafe(SecDbItemGetCachedValueWithName(dbi, kSecAttrAccessible), kSecAttrAccessibleWhenUnlocked) && | |
1410 | CFEqualSafe(SecDbItemGetCachedValueWithName(dbi, kSecAttrAccount), CFSTR("SecureBackupPublicKeybag"))) { | |
1411 | SOSEngineSetBackupBag(engine, isAdd ? (SOSObjectRef)object : NULL); | |
1412 | } | |
1413 | } | |
1414 | return true; | |
1415 | }); | |
1416 | } | |
1417 | ||
1418 | static void SOSEngineReferenceBackupPeer(SOSEngineRef engine, CFStringRef peerID, CFSetRef viewNameSet, CFDataRef keyBag, CFMutableDictionaryRef newViewNameSet2ChangeTracker, CFMutableDictionaryRef newPeerMap) { | |
1419 | CFTypeRef oldEntry = CFDictionaryGetValue(engine->peerMap, peerID); | |
1420 | CFTypeRef newEntry = SOSPeerOrStateSetViewsKeyBagAndCreateCopy(oldEntry, viewNameSet, keyBag); | |
1421 | if (newEntry) { | |
1422 | if (isDictionary(newEntry)) { | |
1423 | // Backup peers, are always inflated | |
1424 | CFAssignRetained(newEntry, SOSPeerCreateWithState(engine, peerID, newEntry, NULL)); | |
1425 | // If !oldEntry this is an edge (first creation of a peer). | |
1426 | if (!oldEntry) { | |
1427 | SOSPeerKeyBagDidChange((SOSPeerRef)newEntry); | |
1428 | } | |
1429 | } | |
1430 | CFDictionarySetValue(newPeerMap, peerID, newEntry); | |
1431 | CFRelease(newEntry); | |
1432 | ||
1433 | if (keyBag) { | |
1434 | SOSChangeTrackerRef ct = SOSReferenceAndGetChangeTracker(engine->viewNameSet2ChangeTracker, newViewNameSet2ChangeTracker, viewNameSet); | |
1435 | ||
1436 | SOSChangeTrackerUpdatesChanges child = Block_copy(^bool(SOSChangeTrackerRef ct, SOSEngineRef engine, SOSTransactionRef txn, SOSDataSourceTransactionSource source, SOSDataSourceTransactionPhase phase, CFArrayRef changes, CFErrorRef *error) { | |
1437 | return SOSPeerDataSourceWillChange((SOSPeerRef)newEntry, SOSEngineGetDataSource(engine), source, changes, error); | |
1438 | }); | |
1439 | ||
1440 | SOSChangeTrackerRegisterChangeUpdate(ct, child); | |
1441 | Block_release(child); | |
1442 | } | |
1443 | } | |
1444 | } | |
1445 | ||
1446 | static void SOSEngineReferenceSyncPeer(SOSEngineRef engine, CFStringRef peerID, CFSetRef viewNameSet, CFMutableDictionaryRef newViewNameSet2ChangeTracker, CFMutableDictionaryRef newPeerMap) { | |
1447 | CFTypeRef newEntry = SOSPeerOrStateSetViewsKeyBagAndCreateCopy(CFDictionaryGetValue(engine->peerMap, peerID), viewNameSet, NULL); | |
1448 | if (newEntry) { | |
1449 | SOSChangeTrackerRef ct = SOSReferenceAndGetChangeTracker(engine->viewNameSet2ChangeTracker, newViewNameSet2ChangeTracker, viewNameSet); | |
1450 | // Standard peer, inflated on demand | |
1451 | SOSChangeTrackerUpdatesManifests trackManifest; | |
1452 | if (isDictionary(newEntry)) { | |
1453 | // Uninflated peer, inflate on first notification. | |
1454 | trackManifest = ^bool(SOSChangeTrackerRef ct, SOSEngineRef engine, SOSTransactionRef txn, SOSDataSourceTransactionSource source, SOSDataSourceTransactionPhase phase, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error) { | |
1455 | CFErrorRef localError = NULL; | |
1456 | SOSPeerRef peer = SOSEngineCopyPeerWithID_locked(engine, peerID, &localError); | |
1457 | bool ok; | |
1458 | if (!peer) { | |
1459 | secerror("%@: peer failed to inflate: %@", peerID, localError); | |
1460 | CFReleaseSafe(localError); | |
1461 | ok = false; | |
1462 | } else { | |
1463 | ok = SOSPeerDataSourceWillCommit(peer, source, removals, additions, error); | |
1464 | } | |
1465 | CFReleaseSafe(peer); | |
1466 | return ok; | |
1467 | }; | |
1468 | } else { | |
1469 | // Inflated peer, just forward the changes to the peer | |
1470 | trackManifest = ^bool(SOSChangeTrackerRef ct, SOSEngineRef engine, SOSTransactionRef txn, SOSDataSourceTransactionSource source, SOSDataSourceTransactionPhase phase, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error) { | |
1471 | return SOSPeerDataSourceWillCommit((SOSPeerRef)newEntry, source, removals, additions, error); | |
1472 | }; | |
1473 | } | |
1474 | SOSChangeTrackerUpdatesManifests trackManifestCopy = Block_copy(trackManifest); | |
1475 | SOSChangeTrackerRegisterManifestUpdate(ct, trackManifestCopy); | |
1476 | Block_release(trackManifestCopy); | |
1477 | ||
1478 | CFDictionarySetValue(newPeerMap, peerID, newEntry); | |
1479 | CFRelease(newEntry); | |
1480 | } | |
1481 | } | |
1482 | ||
1483 | ||
1484 | static void SOSEngineReferenceTrustedPeer(SOSEngineRef engine, SOSPeerMetaRef peerMeta, CFMutableDictionaryRef newViewNameSet2ChangeTracker, CFMutableDictionaryRef newPeerMap, CFMutableArrayRef peerIDs, CFMutableStringRef desc) { | |
1485 | CFSetRef viewNameSet = NULL; | |
1486 | CFDataRef keyBag = NULL; | |
1487 | CFStringRef peerID = SOSPeerMetaGetComponents(peerMeta, &viewNameSet, &keyBag, NULL); | |
1488 | // We trust peerID so append it to peerIDs | |
1489 | CFArrayAppendValue(peerIDs, peerID); | |
1490 | if (desc) CFStringAppendPeerIDAndViews(desc, peerID, viewNameSet); | |
1491 | // Update the viewNameSet for this peer, to appease tests, default to a viewset of the V0 view. | |
1492 | if (!viewNameSet) | |
1493 | viewNameSet = SOSViewsGetV0ViewSet(); | |
1494 | ||
1495 | // Always inflate backup peers, since they need to register with their changeTrackers right away. | |
1496 | if (keyBag) { | |
1497 | SOSEngineReferenceBackupPeer(engine, peerID, viewNameSet, keyBag, newViewNameSet2ChangeTracker, newPeerMap); | |
1498 | } else { | |
1499 | SOSEngineReferenceSyncPeer(engine, peerID, viewNameSet, newViewNameSet2ChangeTracker, newPeerMap); | |
1500 | } | |
1501 | } | |
1502 | ||
1503 | static CFDataRef SOSEngineLoadV0KeyBag(SOSEngineRef engine, CFErrorRef *error) { | |
1504 | // Return the keybag for the given peerID. | |
1505 | /* | |
1506 | Values for V0 are: | |
1507 | kSecAttrAccessGroup ==> CFSTR("com.apple.sbd") | |
1508 | kSecAttrAccessible ==> kSecAttrAccessibleWhenUnlocked | |
1509 | kSecAttrAccount ==> CFSTR("SecureBackupPublicKeybag") | |
1510 | kSecAttrService ==> CFSTR("SecureBackupService") | |
1511 | */ | |
1512 | ||
1513 | CFMutableDictionaryRef keys = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, | |
1514 | kSecAttrAccessGroup, CFSTR("com.apple.sbd"), | |
1515 | kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"), | |
1516 | kSecAttrService, CFSTR("SecureBackupService"), | |
1517 | kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, | |
1518 | kSecAttrSynchronizable, kCFBooleanTrue, | |
1519 | NULL); | |
1520 | ||
1521 | CFDataRef keybag = engine->dataSource->dsCopyItemDataWithKeys(engine->dataSource, keys, error); | |
1522 | CFReleaseSafe(keys); | |
1523 | ||
1524 | return keybag; | |
1525 | } | |
1526 | ||
1527 | static void SOSEngineReferenceBackupV0Peer(SOSEngineRef engine, CFMutableDictionaryRef newViewNameSet2ChangeTracker, CFMutableDictionaryRef newPeerMap, CFMutableArrayRef newPeerIDs, CFMutableStringRef desc) { | |
1528 | SOSPeerRef backupPeer = (SOSPeerRef)CFDictionaryGetValue(engine->peerMap, kSOSViewKeychainV0_tomb); | |
1529 | CFDataRef bag = NULL; | |
1530 | if (backupPeer && CFGetTypeID(backupPeer) == SOSPeerGetTypeID()) { | |
1531 | bag = SOSPeerGetKeyBag(backupPeer); | |
1532 | } else { | |
1533 | CFErrorRef localError = NULL; | |
1534 | if (!(bag = SOSEngineLoadV0KeyBag(engine, &localError))) { | |
1535 | secnotice("engine", "No keybag found for v0 backup peer: %@", localError); | |
1536 | CFReleaseSafe(localError); | |
1537 | } | |
1538 | } | |
1539 | SOSEngineReferenceBackupPeer(engine, kSOSViewKeychainV0_tomb, SOSViewsGetV0BackupViewSet(), bag, newViewNameSet2ChangeTracker, newPeerMap); | |
1540 | } | |
1541 | ||
1542 | static void SOSEngineReferenceTrustedPeers(SOSEngineRef engine, CFMutableDictionaryRef newViewNameSet2ChangeTracker, CFMutableDictionaryRef newPeerMap, CFMutableArrayRef newPeerIDs, CFArrayRef trustedPeerMetas, CFMutableStringRef desc) { | |
1543 | // Then update the views for all trusted peers and add them to newPeerMap. | |
1544 | if (trustedPeerMetas != NULL && CFArrayGetCount(trustedPeerMetas) != 0) { | |
1545 | if (desc) CFStringAppend(desc, CFSTR(" trusted")); | |
1546 | // Remake engine->peerIDs | |
1547 | SOSPeerMetaRef peerMeta; | |
1548 | CFArrayForEachC(trustedPeerMetas, peerMeta) { | |
1549 | SOSEngineReferenceTrustedPeer(engine, peerMeta, newViewNameSet2ChangeTracker, newPeerMap, newPeerIDs, desc); | |
1550 | } | |
1551 | } | |
1552 | } | |
1553 | ||
1554 | static void SOSEngineReferenceUntrustedPeers(SOSEngineRef engine, CFMutableDictionaryRef newPeerMap, CFArrayRef untrustedPeerMetas, CFMutableStringRef description) { | |
1555 | // Copy any untrustedPeers to newPeerMap as well if we have a state | |
1556 | // for them, if not no big deal. We also serialize all the untrustedPeers | |
1557 | // since they don't need to be deserializable | |
1558 | if (untrustedPeerMetas != NULL && CFArrayGetCount(untrustedPeerMetas) != 0) { | |
1559 | if (description) CFStringAppend(description, CFSTR(" untrusted")); | |
1560 | SOSPeerMetaRef peerMeta; | |
1561 | CFArrayForEachC(untrustedPeerMetas, peerMeta) { | |
1562 | CFSetRef views = NULL; | |
1563 | CFStringRef peerID = SOSPeerMetaGetComponents(peerMeta, &views, NULL, NULL); | |
1564 | if (description) CFStringAppendPeerIDAndViews(description, peerID, views); | |
1565 | CFSetRef nviews = NULL; | |
1566 | if (!views) | |
1567 | views = nviews = CFSetCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeSetCallBacks); | |
1568 | CFTypeRef newEntry = SOSPeerOrStateSetViewsAndCopyState(CFDictionaryGetValue(engine->peerMap, peerID), views); | |
1569 | CFReleaseSafe(nviews); | |
1570 | if (newEntry) { | |
1571 | CFDictionarySetValue(newPeerMap, peerID, newEntry); | |
1572 | CFReleaseSafe(newEntry); | |
1573 | } | |
1574 | } | |
1575 | } | |
1576 | } | |
1577 | ||
1578 | static void SOSEngineReferenceChangeTrackers(SOSEngineRef engine, CFArrayRef trustedPeerMetas, CFArrayRef untrustedPeerMetas, CFMutableStringRef desc) { | |
1579 | CFMutableArrayRef newPeerIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
1580 | CFMutableDictionaryRef newPeerMap = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
1581 | CFMutableDictionaryRef newViewNameSet2ChangeTracker = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
1582 | ||
1583 | if (engine->myID) { | |
1584 | // We have an engineID => in a circle (with 0 or more peers) | |
1585 | // Ensure we have a v0 backup peer and it's listening for backup bag changes | |
1586 | SOSEngineReferenceBackupV0Peer(engine, newViewNameSet2ChangeTracker, newPeerMap, newPeerIDs, desc); | |
1587 | SOSEngineRegisterBackupBagV0Tracker(engine, newViewNameSet2ChangeTracker, desc); | |
1588 | } | |
1589 | SOSEngineReferenceTrustedPeers(engine, newViewNameSet2ChangeTracker, newPeerMap, newPeerIDs, trustedPeerMetas, desc); | |
1590 | SOSEngineReferenceUntrustedPeers(engine, newPeerMap, untrustedPeerMetas, desc); | |
1591 | ||
1592 | CFAssignRetained(engine->peerIDs, newPeerIDs); | |
1593 | CFAssignRetained(engine->peerMap, newPeerMap); | |
1594 | CFAssignRetained(engine->viewNameSet2ChangeTracker, newViewNameSet2ChangeTracker); | |
1595 | SOSEngineUpdateViewName2ChangeTracker(engine); | |
1596 | } | |
1597 | ||
1598 | // Return true iff peers or views changed | |
1599 | static bool SOSEngineSetPeers_locked(SOSEngineRef engine, SOSPeerMetaRef myPeerMeta, CFArrayRef trustedPeerMetas, CFArrayRef untrustedPeerMetas) { | |
1600 | CFErrorRef error = NULL; | |
1601 | CFSetRef myViews = NULL; | |
1602 | CFDataRef myKeyBag = NULL; | |
1603 | CFMutableStringRef desc = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("me")); | |
1604 | CFStringRef myPeerID = myPeerMeta ? SOSPeerMetaGetComponents(myPeerMeta, &myViews, &myKeyBag, &error) : NULL; | |
1605 | if (desc) CFStringAppendPeerIDAndViews(desc, myPeerID, myViews); | |
1606 | ||
fa7225c8 A |
1607 | // Start with no coders |
1608 | CFMutableDictionaryRef codersToKeep = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
1609 | ||
1610 | // If we're the same peerID we keep known peers (both trusted and untrusted) | |
1611 | if (CFEqualSafe(myPeerID, engine->myID)) { | |
1612 | void (^copyPeerMetasCoder)(const void *value) = ^(const void*element) { | |
1613 | SOSPeerMetaRef peerMeta = (SOSPeerMetaRef) element; | |
1614 | ||
1615 | CFStringRef currentID = SOSPeerMetaGetComponents(peerMeta, NULL, NULL, NULL); | |
1616 | if (currentID) { | |
1617 | SOSCoderRef coder = (SOSCoderRef) CFDictionaryGetValue(engine->coders, currentID); | |
1618 | if (coder) { | |
1619 | CFDictionarySetValue(codersToKeep, currentID, coder); | |
1620 | } | |
1621 | } | |
1622 | }; | |
1623 | ||
1624 | if (trustedPeerMetas) { | |
1625 | CFArrayForEach(trustedPeerMetas, copyPeerMetasCoder); | |
1626 | } | |
1627 | if (untrustedPeerMetas) { | |
1628 | CFArrayForEach(untrustedPeerMetas, copyPeerMetasCoder); | |
1629 | } | |
1630 | } | |
1631 | ||
1632 | CFTransferRetained(engine->coders, codersToKeep); | |
1633 | engine->codersNeedSaving = true; | |
1634 | ||
5c19dc3a A |
1635 | CFRetainAssign(engine->myID, myPeerID); |
1636 | ||
1637 | // Remake engine->peerMap from both trusted and untrusted peers | |
1638 | SOSEngineReferenceChangeTrackers(engine, trustedPeerMetas, untrustedPeerMetas, desc); | |
1639 | ||
1640 | secnotice("engine", "%@", desc); | |
1641 | CFReleaseSafe(desc); | |
1642 | return true; | |
1643 | } | |
1644 | ||
1645 | static void SOSEngineApplyPeerState(SOSEngineRef engine, CFDictionaryRef peerStateMap) { | |
1646 | if (peerStateMap) CFDictionaryForEach(peerStateMap, ^(const void *peerID, const void *peerState) { | |
1647 | CFTypeRef mapEntry = CFDictionaryGetValue(engine->peerMap, peerID); | |
1648 | if (mapEntry && CFGetTypeID(mapEntry) == SOSPeerGetTypeID()) { | |
1649 | // Update the state of any already inflated peers | |
1650 | SOSPeerRef peer = (SOSPeerRef)mapEntry; | |
1651 | CFErrorRef localError = NULL; | |
1652 | if (!SOSPeerSetState(peer, engine, peerState, &localError)) { | |
1653 | CFStringRef stateHex = NULL; | |
1654 | stateHex = CFDataCopyHexString(peerState); | |
1655 | secerror("peer: %@: bad state: %@ in engine state: %@", peerID, localError, stateHex); | |
1656 | CFReleaseSafe(stateHex); | |
1657 | CFReleaseNull(localError); | |
e3d460c9 | 1658 | // Possibly ask for an ensurePeerRegistration so we have a good list of peers again. |
5c19dc3a A |
1659 | } |
1660 | } else { | |
1661 | // Just record the state for non inflated peers for now. | |
1662 | CFDictionarySetValue(engine->peerMap, peerID, peerState); | |
1663 | } | |
1664 | }); | |
1665 | } | |
1666 | ||
1667 | static void SOSEngineSynthesizePeerMetas(SOSEngineRef engine, CFMutableArrayRef trustedPeersMetas, CFMutableArrayRef untrustedPeers) { | |
1668 | CFSetRef trustedPeerSet = engine->peerIDs ? CFSetCreateCopyOfArrayForCFTypes(engine->peerIDs) : NULL; | |
1669 | CFDictionaryForEach(engine->peerMap, ^(const void *peerID, const void *peerState) { | |
1670 | SOSPeerMetaRef meta = NULL; | |
1671 | if (peerState && CFGetTypeID(peerState) == SOSPeerGetTypeID()) { | |
1672 | SOSPeerRef peer = (SOSPeerRef)peerState; | |
1673 | meta = SOSPeerMetaCreateWithComponents(peerID, SOSPeerGetViewNameSet(peer), SOSPeerGetKeyBag(peer)); | |
1674 | } else { | |
1675 | // We don't need to add the meta for the backup case, since | |
1676 | // SOSEngineReferenceBackupV0Peer will do the right thing | |
1677 | if (!CFEqualSafe(peerID, kSOSViewKeychainV0_tomb)) { | |
1678 | meta = SOSPeerMetaCreateWithState(peerID, peerState); | |
1679 | } | |
1680 | } | |
1681 | // Any peer in peerStateMap that is not in trustedPeers is an untrustedPeer unless it's the v0 backup peer | |
1682 | if ((trustedPeerSet && CFSetContainsValue(trustedPeerSet, peerID)) || CFEqualSafe(peerID, kSOSViewKeychainV0_tomb)) { | |
1683 | if (meta) { | |
1684 | CFArrayAppendValue(trustedPeersMetas, meta); | |
1685 | } | |
1686 | } else { | |
1687 | CFArrayAppendValue(untrustedPeers, peerID); | |
1688 | } | |
1689 | CFReleaseNull(meta); | |
1690 | }); | |
1691 | CFReleaseNull(trustedPeerSet); | |
1692 | } | |
1693 | ||
1694 | static void SOSEngineSetBackupBag(SOSEngineRef engine, SOSObjectRef bagItem) { | |
1695 | CFMutableStringRef desc = NULL; | |
1696 | SOSPeerRef backupPeer = SOSEngineCopyPeerWithID_locked(engine, kSOSViewKeychainV0_tomb, NULL); | |
1697 | CFDataRef keybag = NULL; | |
1698 | if (bagItem) { | |
1699 | keybag = SecDbItemGetValue((SecDbItemRef)bagItem, &v6v_Data, NULL); | |
1700 | } | |
1701 | ||
1702 | // Since SOSPeerSetKeyBag() doesn't notify on the edge from NULL->initial keybag, since | |
1703 | // that is the right behaviour for non v0 backup peers, we need to do it here for the v0 peer. | |
1704 | bool hadBag = SOSPeerGetKeyBag(backupPeer); | |
1705 | SOSPeerSetKeyBag(backupPeer, keybag); | |
1706 | if (!hadBag) | |
1707 | SOSPeerKeyBagDidChange(backupPeer); | |
1708 | ||
1709 | CFReleaseSafe(backupPeer); | |
1710 | ||
1711 | CFMutableArrayRef untrustedPeerMetas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
1712 | CFMutableArrayRef trustedPeersMetas = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
1713 | SOSEngineSynthesizePeerMetas(engine, trustedPeersMetas, untrustedPeerMetas); | |
1714 | SOSEngineReferenceChangeTrackers(engine, trustedPeersMetas, untrustedPeerMetas, desc); | |
1715 | CFReleaseSafe(trustedPeersMetas); | |
1716 | CFReleaseSafe(untrustedPeerMetas); | |
1717 | } | |
1718 | ||
1719 | #define SECONDS_PER_DAY (86400.0) | |
1720 | ||
1721 | #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) | |
1722 | #define TRACE_INTERVAL (7 * SECONDS_PER_DAY) | |
1723 | #elif (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR) | |
1724 | #define TRACE_INTERVAL (1 * SECONDS_PER_DAY) | |
1725 | #endif | |
1726 | ||
1727 | #ifdef TRACE_INTERVAL | |
1728 | static void SOSEngineCloudKeychainTrace(SOSEngineRef engine, CFAbsoluteTime now) { | |
1729 | CFAssignRetained(engine->lastTraceDate, CFDateCreate(kCFAllocatorDefault, now)); | |
1730 | CFIndex num_peers = engine->peerIDs ? 1 + CFArrayGetCount(engine->peerIDs) : 1; | |
1731 | SOSManifestRef manifest = SOSEngineCopyManifestWithViewNameSet_locked(engine, SOSViewsGetV0ViewSet(), NULL); | |
1732 | if (!manifest) | |
1733 | manifest = SOSDataSourceCopyManifestWithViewNameSet(engine->dataSource, SOSViewsGetV0ViewSet(), NULL); | |
1734 | size_t num_items = SOSManifestGetCount(manifest); | |
1735 | CFReleaseSafe(manifest); | |
e0e0d90e A |
1736 | |
1737 | struct _SecServerKeyStats genpStats = { }; | |
1738 | struct _SecServerKeyStats inetStats = { }; | |
1739 | struct _SecServerKeyStats keysStats = { }; | |
1740 | ||
1741 | _SecServerGetKeyStats(&genp_class, &genpStats); | |
1742 | _SecServerGetKeyStats(&inet_class, &inetStats); | |
1743 | _SecServerGetKeyStats(&keys_class, &keysStats); | |
1744 | ||
5c19dc3a | 1745 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ |
e0e0d90e | 1746 | CloudKeychainTrace(num_peers, num_items, &genpStats, &inetStats, &keysStats); |
5c19dc3a A |
1747 | }); |
1748 | } | |
1749 | #endif | |
1750 | ||
1751 | static void SOSEngineCloudKeychainTraceIfNeeded(SOSEngineRef engine) { | |
1752 | #ifdef TRACE_INTERVAL | |
1753 | if (!engine->myID) | |
1754 | return; | |
1755 | CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); | |
1756 | if (engine->lastTraceDate) { | |
1757 | CFAbsoluteTime lastTraceTime = CFDateGetAbsoluteTime(engine->lastTraceDate); | |
1758 | if ((now - lastTraceTime) >= TRACE_INTERVAL) { | |
1759 | SOSEngineCloudKeychainTrace(engine, now); | |
1760 | } | |
1761 | } else { | |
1762 | SOSEngineCloudKeychainTrace(engine, now); | |
1763 | } | |
1764 | #endif | |
1765 | } | |
1766 | ||
5c19dc3a | 1767 | |
5c19dc3a A |
1768 | static bool SOSEngineCircleChanged_locked(SOSEngineRef engine, SOSPeerMetaRef myPeerMeta, CFArrayRef trustedPeers, CFArrayRef untrustedPeers) { |
1769 | // Sanity check params | |
1770 | // SOSEngineCircleChanged_sanitycheck(engine, myPeerID, trustedPeers, untrustedPeers); | |
1771 | ||
1772 | // Transform from SOSPeerInfoRefs to CFDictionaries with the info we want per peer. | |
1773 | // Or, Tell the real SOSPeerRef what the SOSPeerInfoRef is and have it copy out the data it needs. | |
1774 | bool peersOrViewsChanged = SOSEngineSetPeers_locked(engine, myPeerMeta, trustedPeers, untrustedPeers); | |
1775 | ||
1776 | // Run though all peers and only cache manifests for peers we still have | |
1777 | CFErrorRef localError = NULL; | |
1778 | if (!SOSEngineGCPeerState_locked(engine, &localError)) { | |
1779 | secerror("SOSEngineGCPeerState_locked failed: %@", localError); | |
1780 | CFReleaseNull(localError); | |
1781 | } | |
1782 | return peersOrViewsChanged; | |
1783 | } | |
1784 | ||
1785 | // Initialize the engine if a load fails. Basically this is our first time setup | |
1786 | static bool SOSEngineInit(SOSEngineRef engine, CFErrorRef *error) { | |
1787 | bool ok = true; | |
1788 | secnotice("engine", "new engine for datasource named %@", SOSDataSourceGetName(engine->dataSource)); | |
1789 | CFAssignRetained(engine->peerMap, CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault)); | |
1790 | CFAssignRetained(engine->viewNameSet2ChangeTracker, CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault)); | |
1791 | CFAssignRetained(engine->viewName2ChangeTracker, CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault)); | |
1792 | CFReleaseNull(engine->manifestCache); | |
1793 | CFReleaseNull(engine->peerIDs); | |
1794 | // TODO: We shouldn't need to load the backup bag if there was no engine | |
1795 | // state (load failed), since that means there was no circle nor were we an applicant. | |
1796 | ||
1797 | // Set up change trackers so we know when a backup peer needs to be created? | |
1798 | // no, since myID is not set, we are not in a circle, so no need to back up | |
1799 | SOSEngineSetPeers_locked(engine, NULL, NULL, NULL); | |
1800 | return ok; | |
1801 | } | |
1802 | ||
1803 | // Called by our DataSource in its constructor | |
1804 | SOSEngineRef SOSEngineCreate(SOSDataSourceRef dataSource, CFErrorRef *error) { | |
1805 | SOSEngineRef engine = NULL; | |
1806 | engine = CFTypeAllocate(SOSEngine, struct __OpaqueSOSEngine, kCFAllocatorDefault); | |
1807 | engine->dataSource = dataSource; | |
1808 | engine->queue = dispatch_queue_create("engine", DISPATCH_QUEUE_SERIAL); | |
1809 | ||
1810 | engine->peerMap = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
1811 | engine->viewNameSet2ChangeTracker = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
1812 | engine->viewName2ChangeTracker = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
1813 | //engine->syncCompleteQueue = NULL; | |
fa7225c8 A |
1814 | engine->syncCompleteListener = NULL; |
1815 | engine->coders = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
1816 | engine->haveLoadedCoders = false; | |
1817 | engine->codersNeedSaving = false; | |
1818 | ||
5c19dc3a | 1819 | CFErrorRef engineError = NULL; |
fa7225c8 | 1820 | if (!SOSEngineLoad(engine, NULL, &engineError)) { |
5c19dc3a A |
1821 | secwarning("engine failed load state starting with nothing %@", engineError); |
1822 | CFReleaseNull(engineError); | |
1823 | if (!SOSEngineInit(engine, error)) { | |
1824 | secerror("engine failed to initialze %@ giving up", error ? *error : NULL); | |
1825 | } | |
1826 | } else { | |
1827 | // Successfully loaded engine state, let's trace if we haven't in a while | |
1828 | SOSEngineCloudKeychainTraceIfNeeded(engine); | |
1829 | } | |
1830 | SOSEngineSetNotifyPhaseBlock(engine); | |
1831 | return engine; | |
1832 | } | |
1833 | ||
1834 | // --- Called from off the queue, need to move to on the queue | |
1835 | ||
1836 | static void SOSEngineDoOnQueue(SOSEngineRef engine, dispatch_block_t action) | |
1837 | { | |
1838 | dispatch_sync(engine->queue, action); | |
1839 | } | |
1840 | ||
1841 | static bool SOSEngineDoTxnOnQueue(SOSEngineRef engine, CFErrorRef *error, void(^transaction)(SOSTransactionRef txn, bool *commit)) | |
1842 | { | |
fa7225c8 | 1843 | return SOSDataSourceWithCommitQueue(engine->dataSource, error, ^(SOSTransactionRef txn, bool *commit) { |
5c19dc3a A |
1844 | SOSEngineDoOnQueue(engine, ^{ transaction(txn, commit); }); |
1845 | }); | |
1846 | } | |
1847 | ||
1848 | // | |
1849 | // MARK: SOSEngine API | |
1850 | // | |
1851 | ||
1852 | void SOSEngineDispose(SOSEngineRef engine) { | |
1853 | // NOOP Engines stick around forever to monitor dataSource changes. | |
e3d460c9 | 1854 | engine->dataSource = NULL; |
fa7225c8 | 1855 | CFReleaseNull(engine->coders); |
5c19dc3a A |
1856 | } |
1857 | ||
1858 | void SOSEngineForEachPeer(SOSEngineRef engine, void (^with)(SOSPeerRef peer)) { | |
1859 | SOSEngineDoOnQueue(engine, ^{ | |
1860 | SOSEngineForEachPeer_locked(engine, with); | |
1861 | }); | |
1862 | } | |
1863 | ||
1864 | static void SOSEngineForEachBackupPeer(SOSEngineRef engine, void (^with)(SOSPeerRef peer)) { | |
1865 | SOSEngineDoOnQueue(engine, ^{ | |
1866 | SOSEngineForEachBackupPeer_locked(engine, with); | |
1867 | }); | |
1868 | } | |
1869 | ||
1870 | ||
1871 | /* Handle incoming message from peer p. Return false if there was an error, true otherwise. */ | |
1872 | bool SOSEngineHandleMessage_locked(SOSEngineRef engine, CFStringRef peerID, SOSMessageRef message, | |
1873 | SOSTransactionRef txn, bool *commit, bool *somethingChanged, CFErrorRef *error) { | |
1874 | SOSPeerRef peer = SOSEngineCopyPeerWithID_locked(engine, peerID, error); | |
1875 | if (!peer) return false; | |
1876 | CFStringRef peerDesc = NULL; | |
1877 | SOSManifestRef localManifest = NULL; | |
1878 | SOSManifestRef allAdditions = NULL; | |
1879 | SOSManifestRef unwanted = NULL; | |
1880 | SOSManifestRef confirmed = NULL; | |
1881 | SOSManifestRef base = NULL; | |
1882 | SOSManifestRef confirmedRemovals = NULL, confirmedAdditions = NULL; | |
1883 | __block struct SOSDigestVector receivedObjects = SOSDigestVectorInit; | |
1884 | __block struct SOSDigestVector unwantedObjects = SOSDigestVectorInit; | |
1885 | ||
1886 | // Check for unknown criticial extensions in the message, and handle | |
1887 | // any other extensions we support | |
1888 | __block bool ok = true; | |
1889 | CFMutableArrayRef changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
1890 | ||
1891 | require_action_quiet(peer, exit, ok = SOSErrorCreate(errSecParam, error, NULL, CFSTR("Couldn't create peer with Engine for %@"), peerID)); | |
1892 | peerDesc = CFCopyDescription(peer); | |
1893 | ||
1894 | SOSMessageWithExtensions(message, true, ^(CFDataRef oid, bool isCritical, CFDataRef extension, bool *stop) { | |
1895 | // OMFG a Critical extension what shall I do! | |
1896 | ok = SOSErrorCreate(kSOSErrorNotReady, error, NULL, CFSTR("Unknown criticial extension in peer message")); | |
1897 | *stop = true; | |
1898 | }); | |
1899 | require_quiet(ok, exit); | |
1900 | ||
1901 | // Merge Objects from the message into our DataSource. | |
1902 | // Should we move the transaction to the SOSAccount level? | |
1903 | // TODO: Filter incoming objects | |
1904 | //if (!SOSDataSourceForEachObjectInViewSet(engine->dataSource, pendingObjects, SOSPeerGetViewNameSet(peer), error, ^void(CFDataRef key, SOSObjectRef object, bool *stop) { | |
1905 | require_quiet(ok &= SOSMessageWithSOSObjects(message, engine->dataSource, error, ^(SOSObjectRef peersObject, bool *stop) { | |
1906 | CFDataRef digest = SOSObjectCopyDigest(engine->dataSource, peersObject, error); | |
1907 | if (!digest) { | |
1908 | *stop = true; | |
1909 | *commit = false; | |
1910 | secerror("%@ peer sent bad object: %@, rolling back changes", SOSPeerGetID(peer), error ? *error : NULL); | |
1911 | return; | |
1912 | } | |
1913 | SOSDigestVectorAppend(&receivedObjects, CFDataGetBytePtr(digest)); | |
1914 | SOSObjectRef mergedObject = NULL; | |
1915 | SOSMergeResult mr = SOSDataSourceMergeObject(engine->dataSource, txn, peersObject, &mergedObject, error); | |
1916 | // TODO: If the mr is kSOSMergeLocalObject most of the time (or all of the time), | |
1917 | // consider asking the peer to stop sending us objects, and send it objects instead. | |
1918 | ok &= (mr != kSOSMergeFailure); | |
1919 | if (!ok) { | |
1920 | *stop = true; | |
1921 | *commit = false; | |
1922 | // 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. | |
1923 | secerror("%@ SOSDataSourceMergeObject failed %@ rolling back changes", SOSPeerGetID(peer), error ? *error : NULL); | |
1924 | } else if (mr==kSOSMergePeersObject || mr==kSOSMergeCreatedObject) { | |
1925 | *somethingChanged = true; | |
1926 | } else { | |
1927 | // mr == kSOSMergeLocalObject | |
1928 | if (!CFEqual(mergedObject, peersObject)) { | |
1929 | // Record this object as something we don't want peer to ever send us again. By adding it to | |
1930 | // unwantedObjects we'll falsely claim to peer we have it until they tell us they don't have it anymore. | |
1931 | SOSDigestVectorAppend(&unwantedObjects, CFDataGetBytePtr(digest)); | |
1932 | } | |
1933 | // Ensure localObject is in local manifest (possible corruption) by posting an update when we are done. | |
1934 | SOSChangesAppendAdd(changes, mergedObject); | |
1935 | } | |
1936 | CFReleaseSafe(mergedObject); | |
1937 | CFReleaseSafe(digest); | |
1938 | }), exit); | |
1939 | struct SOSDigestVector dvunion = SOSDigestVectorInit; | |
1940 | SOSDigestVectorSort(&receivedObjects); | |
1941 | SOSDigestVectorUnionSorted(SOSManifestGetDigestVector(SOSMessageGetAdditions(message)), &receivedObjects, &dvunion); | |
1942 | allAdditions = SOSManifestCreateWithDigestVector(&dvunion, error); | |
1943 | SOSDigestVectorFree(&receivedObjects); | |
1944 | SOSDigestVectorFree(&dvunion); | |
1945 | ||
1946 | unwanted = SOSManifestCreateWithDigestVector(&unwantedObjects, error); | |
1947 | SOSDigestVectorFree(&unwantedObjects); | |
1948 | ||
1949 | if (CFArrayGetCount(changes)) { | |
1950 | // NOTE: This is always notifiying of all additions that end up choosing local, which should be rare, since we shouldn't | |
1951 | // be receiving objects we already have. When we do we tell ourselves to add them all again so our views will properly | |
1952 | // reflect that we actually have these objects if we didn't already. | |
1953 | ||
1954 | // Ensure any objects that we received and have locally already are actually in our local manifest | |
822b670c | 1955 | SOSEngineUpdateChanges_locked(engine, txn, kSOSDataSourceTransactionDidCommit, kSOSDataSourceSOSTransaction, changes, error); |
5c19dc3a A |
1956 | } |
1957 | CFReleaseSafe(changes); | |
1958 | ||
1959 | // ---- Don't use local or peer manifests from above this line, | |
1960 | // ---- since commiting the SOSDataSourceWith transaction might change them --- | |
1961 | ||
1962 | // Take a snapshot of our dataSource's local manifest. | |
1963 | require_quiet(ok = localManifest = SOSEngineCopyLocalPeerManifest_locked(engine, peer, error), exit); | |
1964 | ||
1965 | CFDataRef baseDigest = SOSMessageGetBaseDigest(message); | |
1966 | CFDataRef proposedDigest = SOSMessageGetProposedDigest(message); | |
1967 | ||
1968 | #if 0 | |
1969 | // I believe this is no longer needed now that we have eliminated extra, | |
1970 | // since this is handled below once we get a confirmed manifest from our | |
1971 | // peer. | |
1972 | ||
1973 | // If we just received a L00 reset pendingObjects to localManifest | |
1974 | if (!baseDigest && !proposedDigest) { | |
1975 | // TODO: This is definitely busted for v0 peers since v0 peers always send a | |
1976 | // L00 (ManifestDigestMessage as an ack) whereas in v2 this is a protocol restart | |
1977 | // However if we can still find a confirmed manifest below we probably | |
1978 | // don't want to do this even for v2. | |
1979 | // Also I don't think we will ever send a ManifestMessage right now in | |
1980 | // response to a ManifestDigest | |
1981 | SOSPeerSetPendingObjects(peer, localManifest); | |
1982 | secnoticeq("engine", "%@:%@ SOSPeerSetPendingObjects: %@", engine->myID, peerID, localManifest); | |
1983 | } | |
1984 | #endif | |
1985 | ||
1986 | base = SOSPeerCopyManifestForDigest(peer, baseDigest); | |
1987 | confirmed = SOSPeerCopyManifestForDigest(peer, SOSMessageGetSenderDigest(message)); | |
1988 | if (!confirmed) { | |
1989 | if (SOSManifestGetCount(SOSMessageGetRemovals(message)) || SOSManifestGetCount(allAdditions)) { | |
1990 | if (base || !baseDigest) { | |
1991 | confirmed = SOSManifestCreateWithPatch(base, SOSMessageGetRemovals(message), allAdditions, error); | |
1992 | } | |
1993 | if (!confirmed) { | |
1994 | confirmedRemovals = CFRetainSafe(SOSMessageGetRemovals(message)); | |
1995 | confirmedAdditions = CFRetainSafe(allAdditions); | |
1996 | } | |
1997 | } else if (baseDigest) { | |
1998 | confirmed = CFRetainSafe(base); | |
1999 | secerror("%@:%@ Protocol error send L00 - figure out later base: %@", engine->myID, peerID, base); | |
2000 | } | |
2001 | } | |
2002 | secnoticeq("engine", "%@:%@ confirmed: %@ base: %@", engine->myID, peerID, confirmed, base); | |
2003 | if (confirmed) { | |
2004 | ok &= SOSManifestDiff(SOSPeerGetConfirmedManifest(peer), confirmed, &confirmedRemovals, &confirmedAdditions, error); | |
2005 | if (SOSManifestGetCount(SOSMessageGetRemovals(message))) | |
2006 | CFAssignRetained(confirmedRemovals, SOSManifestCreateUnion(confirmedRemovals, SOSMessageGetRemovals(message), error)); | |
2007 | } | |
2008 | if (SOSManifestGetCount(confirmedRemovals) || SOSManifestGetCount(confirmedAdditions) || SOSManifestGetCount(unwanted)) | |
2009 | ok &= SOSPeerDidReceiveRemovalsAndAdditions(peer, confirmedRemovals, confirmedAdditions, unwanted, localManifest, error); | |
2010 | // TODO: We should probably remove the if below and always call SOSPeerSetConfirmedManifest, | |
2011 | // since having a NULL confirmed will force us to send a manifest message to get in sync again. | |
2012 | if (confirmed) | |
2013 | SOSPeerSetConfirmedManifest(peer, confirmed); | |
2014 | else if (SOSPeerGetConfirmedManifest(peer)) { | |
2015 | secnoticeq("engine", "%@:%@ unable to find confirmed in %@, sync protocol reset", engine->myID, peer, message); | |
2016 | ||
2017 | SOSPeerSetConfirmedManifest(peer, NULL); | |
2018 | //SOSPeerSetSendObjects(peer, true); | |
2019 | } | |
2020 | ||
2021 | // ---- SendObjects and extra->pendingObjects promotion dance ---- | |
2022 | ||
2023 | // The first block of code below sets peer.sendObjects to true when we receive a L00 and the second block | |
2024 | // moves extra to pendingObjects once we receive a confirmed manifest in or after the L00. | |
2025 | if (!baseDigest && !proposedDigest) { | |
2026 | SOSPeerSetSendObjects(peer, true); | |
2027 | } | |
2028 | ||
2029 | if (0 /* confirmed && SOSPeerSendObjects(peer) */) { | |
2030 | SOSManifestRef allExtra = NULL; | |
2031 | ok &= SOSManifestDiff(confirmed, localManifest, NULL, &allExtra, error); | |
2032 | secnoticeq("engine", "%@:%@ confirmed %@ (re)setting O:%@", engine->myID, SOSPeerGetID(peer), confirmed, allExtra); | |
2033 | SOSPeerSetPendingObjects(peer, allExtra); | |
2034 | CFReleaseSafe(allExtra); | |
2035 | } | |
2036 | ||
2037 | exit: | |
e0e0d90e A |
2038 | secnotice("engine", "recv %@:%@ %@", engine->myID, SOSPeerGetID(peer), message); |
2039 | secnotice("peer", "recv %@ -> %@", peerDesc, peer); | |
5c19dc3a A |
2040 | |
2041 | CFReleaseNull(base); | |
2042 | CFReleaseSafe(confirmed); | |
2043 | CFReleaseSafe(localManifest); | |
2044 | CFReleaseSafe(peerDesc); | |
2045 | CFReleaseSafe(allAdditions); | |
2046 | CFReleaseSafe(unwanted); | |
2047 | CFReleaseSafe(confirmedRemovals); | |
2048 | CFReleaseSafe(confirmedAdditions); | |
2049 | CFReleaseSafe(peer); | |
2050 | return ok; | |
2051 | } | |
2052 | ||
2053 | static CFDataRef SOSEngineCopyObjectDER(SOSEngineRef engine, SOSObjectRef object, CFErrorRef *error) { | |
2054 | CFDataRef der = NULL; | |
2055 | CFDictionaryRef plist = SOSObjectCopyPropertyList(engine->dataSource, object, error); | |
2056 | if (plist) { | |
2057 | der = CFPropertyListCreateDERData(kCFAllocatorDefault, plist, error); | |
2058 | CFRelease(plist); | |
2059 | } | |
2060 | return der; | |
2061 | } | |
2062 | ||
2063 | ||
2064 | /* | |
2065 | ||
2066 | +-----------------------------+_ | |
2067 | | | | \ | |
2068 | | A | T | \ | |
2069 | | | | \ | |
2070 | _+=============================+ } L | |
2071 | / | | / | |
2072 | / | S | / | |
2073 | / | |_/ | |
2074 | / +============================== | |
2075 | / | | | |
2076 | C { | | | |
2077 | \ | M +------------| | |
2078 | \ | | | | |
2079 | \ | | U | | |
2080 | \ | | | | |
2081 | \_+-------------+---------------+ | |
2082 | ||
2083 | A assumed | |
2084 | T to be sent | |
2085 | S shared | |
2086 | M missing | |
2087 | U unwanted | |
2088 | L local | |
2089 | C confirmed | |
2090 | ||
2091 | */ | |
2092 | #if 0 | |
2093 | static bool SOSAppendRemoveToPatch(CFTypeRef remove, CFMutableDictionaryRef patch, CFErrorRef *error) { | |
2094 | } | |
2095 | ||
2096 | static bool SOSAppendAddToPatch(CFTypeRef add, CFMutableDictionaryRef patch, CFErrorRef *error) { | |
2097 | } | |
2098 | ||
2099 | static bool SOSAppendDiffToPatch(CFTypeRef left, CFTypeRef right, CFMutableDictionaryRef patch, CFErrorRef *error) { | |
2100 | bool ok = true; | |
2101 | if (!left && right) { | |
2102 | SOSAppendAddToPatch(right, patch, error); | |
2103 | } else if (left && !right) { | |
2104 | SOSAppendRemoveToPatch(left, patch, error); | |
2105 | } else if (left && right) { | |
2106 | CFTypeID ltype = CFGetTypeID(left); | |
2107 | CFTypeID rtype = CFGetTypeID(right); | |
2108 | if (ltype == rtype) { | |
2109 | if (CFArrayGetTypeID() == ltype) { | |
2110 | ok = SecError(errSecParam, error, CFSTR("unsupported type array"), ltype); | |
2111 | } else if (CFBooleanGetTypeID == ltype) { | |
2112 | ok = SecError(errSecParam, error, CFSTR("unsupported type boolean"), ltype); | |
2113 | } else if (CFDataGetTypeID == ltype) { | |
2114 | ok = SecError(errSecParam, error, CFSTR("unsupported type data"), ltype); | |
2115 | } else if (CFDictionaryGetTypeID == ltype) { | |
2116 | __block CFMutableDictionaryRef leftnotright = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
2117 | __block CFMutableDictionaryRef rightnotleft = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, right); | |
2118 | ||
2119 | CFDictionaryForEach(left, ^(const void *key, const void *lvalue) { | |
2120 | const void *rvalue = NULL; | |
2121 | if (CFDictionaryGetValueIfPresent(right, key, &rvalue)) { | |
2122 | CFDictionaryRemoveValue(rightnotleft, key); | |
2123 | ||
2124 | CFMutableDictionaryRef subpatch = CFDictionaryCreateForCFTypes(kCFAllocatorDefault); | |
2125 | CFDictionaryAddValue(patch, key, subpatch); | |
2126 | SOSAppendDiffToPatch(lvalue, rvalue, subpatch, error); | |
2127 | CFReleaseSafe(subpatch); | |
2128 | } else { | |
2129 | CFDictionaryAddValue(leftnotright, key, lvalue); | |
2130 | } | |
2131 | }); | |
2132 | // Proccess leftnotright and rightnotleft | |
2133 | CFReleaseSafe(leftnotright); | |
2134 | CFReleaseSafe(rightnotleft); | |
2135 | } else if (SOSManifestGetTypeID == ltype) { | |
2136 | SOSManifestRef removed = NULL, added = NULL; | |
2137 | ok &= SOSManifestDiff(left, right, &removed, &added, error); | |
2138 | if (SOSManifestGetCount(removed) || SOSManifestGetCount(added)) { | |
2139 | SOSAppendDiffToPatch(lvalue, rvalue, subpatch, error); | |
2140 | CFStringAppend(, <#CFStringRef appendedString#>) | |
2141 | } | |
2142 | CFReleaseSafe(removed); | |
2143 | CFReleaseSafe(added); | |
2144 | } else if (CFNumberGetTypeID == ltype) { | |
2145 | ok = SecError(errSecParam, error, CFSTR("unsupported type number"), ltype); | |
2146 | } else if (CFSetGetTypeID == ltype) { | |
2147 | ok = SecError(errSecParam, error, CFSTR("unsupported type set"), ltype); | |
2148 | } else if (CFStringGetTypeID == ltype) { | |
2149 | ok = SecError(errSecParam, error, CFSTR("unsupported type string"), ltype); | |
2150 | } else { | |
2151 | ok = SecError(errSecParam, error, CFSTR("unknown type %lu"), ltype); | |
2152 | } | |
2153 | } | |
2154 | } else if (!left && !right) { | |
2155 | // NOOP | |
2156 | } | |
2157 | } | |
2158 | #endif | |
2159 | ||
2160 | static __unused bool SOSEngineCheckPeerIntegrity(SOSEngineRef engine, SOSPeerRef peer, CFErrorRef *error) { | |
2161 | #if 0 | |
2162 | //static CFMutableDictionaryRef p2amtu; | |
2163 | if (!engine->p2amtu) | |
2164 | engine->p2amtu = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); | |
2165 | CFDictionaryRef amtu = CFDictionaryGetValue(engine->p2amtu, SOSPeerGetID(peer)); | |
2166 | #endif | |
2167 | ||
2168 | // Inputs | |
2169 | SOSManifestRef L = SOSEngineCopyLocalPeerManifest_locked(engine, peer, error); | |
2170 | SOSManifestRef T = SOSPeerGetPendingObjects(peer); | |
2171 | SOSManifestRef C = SOSPeerGetConfirmedManifest(peer); | |
2172 | SOSManifestRef U = SOSPeerGetUnwantedManifest(peer); | |
2173 | ||
2174 | // Computed | |
2175 | SOSManifestRef CunionU = SOSManifestCreateUnion(C, U, error); | |
2176 | SOSManifestRef S = SOSManifestCreateIntersection(L, CunionU, error); | |
2177 | ||
2178 | SOSManifestRef AunionT = NULL, MunionU = NULL; | |
2179 | SOSManifestDiff(L, C, &AunionT, &MunionU, error); | |
2180 | ||
2181 | SOSManifestRef A = SOSManifestCreateComplement(T, AunionT, error); | |
2182 | SOSManifestRef M = SOSManifestCreateComplement(U, MunionU, error); | |
2183 | ||
2184 | SOSManifestRef SunionAunionT = SOSManifestCreateUnion(S, AunionT, error); | |
2185 | SOSManifestRef SunionMunionU = SOSManifestCreateUnion(S, MunionU, error); | |
2186 | ||
2187 | SOSManifestRef AintersectM = SOSManifestCreateIntersection(A, M, error); | |
2188 | SOSManifestRef AintersectS = SOSManifestCreateIntersection(A, S, error); | |
2189 | SOSManifestRef AintersectT = SOSManifestCreateIntersection(A, T, error); | |
2190 | SOSManifestRef AintersectU = SOSManifestCreateIntersection(A, U, error); | |
2191 | SOSManifestRef MintersectS = SOSManifestCreateIntersection(M, S, error); | |
2192 | SOSManifestRef MintersectT = SOSManifestCreateIntersection(M, T, error); | |
2193 | SOSManifestRef MintersectU = SOSManifestCreateIntersection(M, U, error); | |
2194 | SOSManifestRef SintersectT = SOSManifestCreateIntersection(S, T, error); | |
2195 | SOSManifestRef SintersectU = SOSManifestCreateIntersection(S, U, error); | |
2196 | SOSManifestRef TintersectU = SOSManifestCreateIntersection(T, U, error); | |
2197 | ||
2198 | #if 0 | |
2199 | CFDictionaryRef newAmtu = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, CFSTR("A"), A, CFSTR("M"), M, CFSTR("T"), T, CFSTR("U") U, NULL); | |
2200 | CFDictionarySetValue(engine->p2amtu, SOSPeerGetID(peer), newAmtu); | |
2201 | CFMutableStringRef amtuChanges = CFStringCreateMutable(kCFAllocatorDefault, 0); | |
2202 | SOSAppendDiffToString(amtu, newAmtu, amtuChanges); | |
2203 | secnotice("engine", "%@: %@", SOSPeerGetID(peer), amtuChanges); | |
2204 | #endif | |
2205 | ||
2206 | #define SOSASSERT(e) (__builtin_expect(!(e), 0) ? secnotice("engine", "state-assertion %s", #e), assert(e) : (void)0) | |
2207 | ||
2208 | SOSASSERT(L ? CFEqual(L, SunionAunionT) : SOSManifestGetCount(SunionAunionT) == 0); | |
2209 | SOSASSERT(C ? CFEqual(C, SunionMunionU) : SOSManifestGetCount(SunionMunionU) == 0); | |
2210 | ||
2211 | SOSASSERT(SOSManifestGetCount(AintersectM) == 0); | |
2212 | SOSASSERT(SOSManifestGetCount(AintersectS) == 0); | |
2213 | SOSASSERT(SOSManifestGetCount(AintersectT) == 0); | |
2214 | SOSASSERT(SOSManifestGetCount(AintersectU) == 0); | |
2215 | SOSASSERT(SOSManifestGetCount(MintersectS) == 0); | |
2216 | SOSASSERT(SOSManifestGetCount(MintersectT) == 0); | |
2217 | SOSASSERT(SOSManifestGetCount(MintersectU) == 0); | |
2218 | SOSASSERT(SOSManifestGetCount(SintersectT) == 0); | |
2219 | SOSASSERT(SOSManifestGetCount(SintersectU) == 0); | |
2220 | SOSASSERT(SOSManifestGetCount(TintersectU) == 0); | |
2221 | ||
2222 | CFReleaseSafe(AintersectM); | |
2223 | CFReleaseSafe(AintersectS); | |
2224 | CFReleaseSafe(AintersectT); | |
2225 | CFReleaseSafe(AintersectU); | |
2226 | CFReleaseSafe(MintersectS); | |
2227 | CFReleaseSafe(MintersectT); | |
2228 | CFReleaseSafe(MintersectU); | |
2229 | CFReleaseSafe(SintersectT); | |
2230 | CFReleaseSafe(SintersectU); | |
2231 | CFReleaseSafe(TintersectU); | |
2232 | ||
2233 | CFReleaseSafe(AunionT); | |
2234 | CFReleaseSafe(MunionU); | |
2235 | ||
2236 | ||
2237 | CFReleaseSafe(A); | |
2238 | CFReleaseSafe(M); | |
2239 | CFReleaseSafe(S); | |
2240 | //CFReleaseSafe(T); // Get | |
2241 | //CFReleaseSafe(U); // Get | |
2242 | //CFReleaseSafe(C); // Get | |
2243 | CFReleaseSafe(L); | |
2244 | return true; | |
2245 | } | |
2246 | ||
fa7225c8 | 2247 | void SOSEngineSetSyncCompleteListener(SOSEngineRef engine, SOSEnginePeerInSyncBlock notify_block) { |
5c19dc3a | 2248 | SOSEngineDoOnQueue(engine, ^{ |
fa7225c8 | 2249 | CFAssignRetained(engine->syncCompleteListener, Block_copy(notify_block)); |
5c19dc3a | 2250 | }); |
5c19dc3a A |
2251 | } |
2252 | ||
2253 | void SOSEngineSetSyncCompleteListenerQueue(SOSEngineRef engine, dispatch_queue_t notify_queue) { | |
2254 | SOSEngineDoOnQueue(engine, ^{ | |
fa7225c8 | 2255 | CFRetainAssign(engine->syncCompleteQueue, notify_queue); |
5c19dc3a A |
2256 | }); |
2257 | } | |
2258 | ||
2259 | static void SOSEngineCompletedSyncWithPeer(SOSEngineRef engine, SOSPeerRef peer) { | |
fa7225c8 A |
2260 | SOSEnginePeerInSyncBlock block_to_call = engine->syncCompleteListener; |
2261 | ||
2262 | if (block_to_call && engine->syncCompleteQueue) { | |
2263 | CFStringRef ID = CFRetainSafe(SOSPeerGetID(peer)); | |
2264 | CFSetRef views = CFRetainSafe(SOSPeerGetViewNameSet(peer)); | |
2265 | CFRetainSafe(block_to_call); | |
2266 | ||
2267 | dispatch_async(engine->syncCompleteQueue, ^{ | |
2268 | block_to_call(ID, views); | |
2269 | CFReleaseSafe(ID); | |
2270 | CFReleaseSafe(views); | |
2271 | CFReleaseSafe(block_to_call); | |
2272 | }); | |
2273 | } | |
5c19dc3a A |
2274 | } |
2275 | ||
2276 | ||
fa7225c8 | 2277 | CFDataRef SOSEngineCreateMessage_locked(SOSEngineRef engine, SOSTransactionRef txn, SOSPeerRef peer, |
5c19dc3a A |
2278 | CFErrorRef *error, SOSEnginePeerMessageSentBlock *sent) { |
2279 | SOSManifestRef local = SOSEngineCopyLocalPeerManifest_locked(engine, peer, error); | |
2280 | __block SOSMessageRef message = SOSMessageCreate(kCFAllocatorDefault, SOSPeerGetMessageVersion(peer), error); | |
2281 | SOSManifestRef confirmed = SOSPeerGetConfirmedManifest(peer); | |
2282 | SOSManifestRef pendingObjects = SOSPeerGetPendingObjects(peer); | |
2283 | SOSManifestRef objectsSent = NULL; | |
2284 | SOSManifestRef proposed = NULL; | |
2285 | SOSManifestRef allMissing = NULL; | |
2286 | SOSManifestRef allExtra = NULL; | |
2287 | SOSManifestRef extra = NULL; | |
2288 | SOSManifestRef excessPending = NULL; | |
2289 | SOSManifestRef missing = NULL; | |
2290 | SOSManifestRef unwanted = SOSPeerGetUnwantedManifest(peer); | |
2291 | SOSManifestRef excessUnwanted = NULL; | |
2292 | CFDataRef result = NULL; | |
2293 | ||
2294 | // Given (C, L, T, U) compute (T, U, M, A) | |
2295 | // (C \ L) \ U => M | |
2296 | // (L \ C) \ T => A | |
2297 | // we also compute | |
2298 | // U \ (C \ L) => EU | |
2299 | // T \ (L \ C) => ET | |
2300 | // And assert that both EU and ET are empty and if not remove them from U and T respectively | |
2301 | SOSManifestDiff(confirmed, local, &allMissing, &allExtra, error); | |
2302 | SOSManifestDiff(allExtra, pendingObjects, &extra, &excessPending, error); | |
2303 | if (SOSManifestGetCount(excessPending)) { | |
2304 | // T \ (L \ C) => excessPending (items both in L and C or in neither that are still pending) | |
2305 | // Can only happen if a member of T was removed from L without us having a chance to update T | |
2306 | secerror("%@ ASSERTION FAILURE purging excess pendingObjects: %@", peer, excessPending); | |
2307 | SOSManifestRef newPendingObjects = SOSManifestCreateComplement(excessPending, pendingObjects, error); | |
2308 | SOSPeerSetPendingObjects(peer, newPendingObjects); | |
2309 | CFReleaseSafe(newPendingObjects); | |
2310 | pendingObjects = SOSPeerGetPendingObjects(peer); | |
2311 | } | |
2312 | SOSManifestDiff(allMissing, unwanted, &missing, &excessUnwanted, error); | |
2313 | if (SOSManifestGetCount(excessUnwanted)) { | |
2314 | // U \ (C \ L) => excessUnwanted (items both in L and C or in neither that are still unwanted) | |
2315 | // Can only happen if a member of U was added to L without us having a chance to update U. | |
2316 | // Since U only contains items the conflict resolver rejected, this implies L somehow got rolled back | |
2317 | // The other option (and more likely) is a member of U was removed from C and not from U. | |
2318 | secerror("%@ ASSERTION FAILURE purging excess unwanted: %@", peer, excessUnwanted); | |
2319 | SOSManifestRef newUnwanted = SOSManifestCreateComplement(excessUnwanted, unwanted, error); | |
2320 | SOSPeerSetUnwantedManifest(peer, newUnwanted); | |
2321 | CFReleaseSafe(newUnwanted); | |
2322 | unwanted = SOSPeerGetUnwantedManifest(peer); | |
2323 | } | |
2324 | ||
2325 | CFReleaseNull(allExtra); | |
2326 | CFReleaseNull(excessPending); | |
2327 | CFReleaseNull(allMissing); | |
2328 | CFReleaseNull(excessUnwanted); | |
2329 | ||
2330 | secnoticeq("engine", "%@:%@: send state for peer [%s%s%s][%s%s] P:%zu, E:%zu, M:%zu U:%zu", engine->myID, SOSPeerGetID(peer), | |
2331 | local ? "L":"l", | |
2332 | confirmed ? "C":"0", | |
2333 | pendingObjects ? "P":"0", | |
2334 | SOSPeerSendObjects(peer) ? "O":"o", | |
2335 | SOSPeerMustSendMessage(peer) ? "S":"s", | |
2336 | SOSManifestGetCount(pendingObjects), | |
2337 | SOSManifestGetCount(extra), | |
2338 | SOSManifestGetCount(missing), | |
2339 | SOSManifestGetCount(unwanted) | |
2340 | ); | |
2341 | ||
2342 | if (confirmed) { | |
2343 | // TODO: Because of not letting things terminate while we have extra left | |
2344 | // we might send objects when we didn't need to, but there is always an | |
2345 | // extra roundtrip required for objects that we assume the other peer | |
2346 | // should have already. | |
2347 | // TODO: If there are extra objects left, calling this function is not | |
2348 | // idempotent we should check if pending is what we are about to send and not send anything in this case. | |
2349 | if (SOSManifestGetCount(pendingObjects) == 0 && SOSManifestGetCount(extra) == 0) | |
2350 | SOSPeerSetSendObjects(peer, false); | |
2351 | ||
e0e0d90e A |
2352 | // If we aren't missing anything, we've gotten all their data, so we're sync even if they haven't seen ours. |
2353 | if (missing && SOSManifestGetCount(missing) == 0) { | |
2354 | SOSEngineCompletedSyncWithPeer(engine, peer); | |
2355 | } | |
2356 | ||
5c19dc3a A |
2357 | if (CFEqualSafe(local, SOSPeerGetProposedManifest(peer)) && !SOSPeerMustSendMessage(peer)) { |
2358 | bool send = false; | |
2359 | if (CFEqual(confirmed, local)) { | |
5c19dc3a A |
2360 | secnoticeq("engine", "synced <No MSG> %@:%@", engine->myID, peer); |
2361 | } else if (SOSManifestGetCount(pendingObjects) == 0 /* TODO: No entries moved from extra to pendingObjects. */ | |
2362 | && SOSManifestGetCount(missing) == 0) { | |
2363 | secnoticeq("engine", "waiting <MSG not resent> %@:%@ extra: %@", engine->myID, peer, extra); | |
2364 | } else { | |
2365 | send = true; | |
2366 | } | |
2367 | if (!send) { | |
2368 | CFReleaseSafe(local); | |
2369 | CFReleaseSafe(message); | |
2370 | CFReleaseNull(extra); | |
2371 | CFReleaseNull(missing); | |
2372 | return CFDataCreate(kCFAllocatorDefault, NULL, 0); | |
2373 | } | |
2374 | } | |
2375 | ||
2376 | if (SOSManifestGetCount(pendingObjects)) { | |
2377 | // If we have additions and we need to send objects, do so. | |
2378 | __block size_t objectsSize = 0; | |
2379 | __block struct SOSDigestVector dv = SOSDigestVectorInit; | |
2380 | CFMutableArrayRef changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
fa7225c8 A |
2381 | __block CFErrorRef dsfeError = NULL; |
2382 | ||
2383 | if (!SOSDataSourceForEachObject(engine->dataSource, txn, pendingObjects, &dsfeError, ^void(CFDataRef key, SOSObjectRef object, bool *stop) { | |
5c19dc3a A |
2384 | CFErrorRef localError = NULL; |
2385 | CFDataRef digest = NULL; | |
2386 | CFDataRef der = NULL; | |
fa7225c8 A |
2387 | #if !defined(NDEBUG) |
2388 | const uint8_t *d = CFDataGetBytePtr(key); | |
2389 | #endif | |
2390 | secdebug("engine", "%@:%@ object %02X%02X%02X%02X error from SOSDataSourceForEachObject: %@", | |
2391 | engine->myID, SOSPeerGetID(peer), d[0], d[1], d[2], d[3], dsfeError); | |
5c19dc3a A |
2392 | if (!object) { |
2393 | const uint8_t *d = CFDataGetBytePtr(key); | |
fa7225c8 A |
2394 | secerror("%@:%@ object %02X%02X%02X%02X dropping from manifest: not found in datasource: %@", |
2395 | engine->myID, SOSPeerGetID(peer), d[0], d[1], d[2], d[3], dsfeError); | |
5c19dc3a A |
2396 | SOSChangesAppendDelete(changes, key); |
2397 | } else if (!(der = SOSEngineCopyObjectDER(engine, object, &localError)) | |
2398 | || !(digest = SOSObjectCopyDigest(engine->dataSource, object, &localError))) { | |
2399 | if (SecErrorGetOSStatus(localError) == errSecDecode) { | |
2400 | // Decode error, we need to drop these objects from our manifests | |
2401 | const uint8_t *d = CFDataGetBytePtr(key); | |
2402 | secnoticeq("engine", "%@:%@ object %02X%02X%02X%02X dropping from manifest: %@", | |
2403 | engine->myID, SOSPeerGetID(peer), d[0], d[1], d[2], d[3], localError); | |
2404 | SOSChangesAppendDelete(changes, key); | |
2405 | CFRelease(localError); | |
2406 | } else { | |
2407 | // Stop iterating and propagate out all other errors. | |
2408 | const uint8_t *d = CFDataGetBytePtr(key); | |
2409 | secnoticeq("engine", "%@:%@ object %02X%02X%02X%02X in SOSDataSourceForEachObject: %@", | |
2410 | engine->myID, SOSPeerGetID(peer), d[0], d[1], d[2], d[3], localError); | |
2411 | *stop = true; | |
2412 | CFErrorPropagate(localError, error); | |
2413 | CFReleaseNull(message); | |
2414 | } | |
2415 | } else { | |
2416 | if (!CFEqual(key, digest)) { | |
2417 | const uint8_t *d = CFDataGetBytePtr(key); | |
2418 | const uint8_t *e = CFDataGetBytePtr(digest); | |
2419 | secnoticeq("engine", "%@:%@ object %02X%02X%02X%02X is really %02X%02X%02X%02X dropping from local manifest", | |
2420 | engine->myID, SOSPeerGetID(peer), d[0], d[1], d[2], d[3], e[0], e[1], e[2], e[3]); | |
2421 | SOSChangesAppendDelete(changes, key); | |
2422 | SOSChangesAppendAdd(changes, object); // This is new behaviour but we think it's more correct | |
2423 | } | |
2424 | ||
2425 | size_t objectLen = (size_t)CFDataGetLength(der); | |
2426 | if (SOSMessageAppendObject(message, der, &localError)) { | |
2427 | SOSDigestVectorAppend(&dv, CFDataGetBytePtr(digest)); | |
2428 | } else { | |
2429 | const uint8_t *d = CFDataGetBytePtr(digest); | |
2430 | CFStringRef hexder = CFDataCopyHexString(der); | |
2431 | secnoticeq("engine", "%@:%@ object %02X%02X%02X%02X der: %@ dropping from manifest: %@", | |
2432 | engine->myID, SOSPeerGetID(peer), d[0], d[1], d[2], d[3], hexder, localError); | |
2433 | CFReleaseNull(hexder); | |
2434 | CFReleaseNull(message); | |
2435 | // Since we can't send these objects let's assume they are bad too? | |
2436 | SOSChangesAppendDelete(changes, digest); | |
2437 | } | |
2438 | objectsSize += objectLen; | |
2439 | if (objectsSize > kSOSMessageMaxObjectsSize) | |
2440 | *stop = true; | |
2441 | } | |
fa7225c8 A |
2442 | CFErrorPropagate(dsfeError, error); // this also releases dsfeError |
2443 | dsfeError = NULL; | |
5c19dc3a A |
2444 | CFReleaseSafe(der); |
2445 | CFReleaseSafe(digest); | |
2446 | })) { | |
2447 | CFReleaseNull(message); | |
2448 | } | |
fa7225c8 | 2449 | if (dv.count){ |
5c19dc3a | 2450 | objectsSent = SOSManifestCreateWithDigestVector(&dv, error); |
fa7225c8 | 2451 | } |
5c19dc3a A |
2452 | if (CFArrayGetCount(changes)) { |
2453 | CFErrorRef localError = NULL; | |
2454 | if (!SOSEngineUpdateChanges_locked(engine, NULL, kSOSDataSourceTransactionDidCommit, kSOSDataSourceSOSTransaction, changes, &localError)) | |
2455 | secerror("SOSEngineUpdateChanges_locked: %@ failed: %@", changes, localError); | |
2456 | CFReleaseSafe(localError); | |
2457 | CFAssignRetained(local, SOSEngineCopyLocalPeerManifest_locked(engine, peer, error)); | |
2458 | } | |
2459 | CFReleaseSafe(changes); | |
fa7225c8 A |
2460 | SOSDigestVectorFree(&dv); |
2461 | CFReleaseNull(dsfeError); | |
5c19dc3a A |
2462 | } |
2463 | } else { | |
2464 | // If we have no confirmed manifest, we want all pendedObjects going out as a manifest | |
2465 | objectsSent = CFRetainSafe(pendingObjects); | |
2466 | } | |
2467 | ||
2468 | if (confirmed || SOSManifestGetCount(missing) || SOSManifestGetCount(extra) || objectsSent) { | |
2469 | SOSManifestRef allExtra = SOSManifestCreateUnion(extra, objectsSent, error); | |
2470 | proposed = SOSManifestCreateWithPatch(confirmed, missing, allExtra, error); | |
2471 | CFReleaseNull(allExtra); | |
2472 | } | |
2473 | ||
e3d460c9 A |
2474 | SOSManifestRef sender = local; |
2475 | // We actually send the remote peer its own digest. | |
2476 | // Note that both pendingObjects and unwanted may have been changed, so we get them again | |
2477 | if (SOSManifestGetCount(SOSPeerGetPendingObjects(peer))==0 && SOSManifestGetCount(extra)==0 && | |
2478 | SOSManifestGetCount(missing)==0 && SOSManifestGetCount(SOSPeerGetUnwantedManifest(peer))!=0) { | |
2479 | secnoticeq("engine", "%@:%@: only have differences in unwanted set; lying to peer to stop sync",engine->myID, SOSPeerGetID(peer)); | |
2480 | sender = confirmed; | |
2481 | } | |
2482 | ||
2483 | if (!SOSMessageSetManifests(message, sender, confirmed, proposed, proposed, confirmed ? objectsSent : NULL, error)) { | |
2484 | secnoticeq("engine", "%@:%@: failed to set message manifests",engine->myID, SOSPeerGetID(peer)); | |
5c19dc3a | 2485 | CFReleaseNull(message); |
e3d460c9 | 2486 | } |
5c19dc3a A |
2487 | |
2488 | CFReleaseNull(objectsSent); | |
2489 | ||
2490 | if (message) { | |
2491 | result = SOSMessageCreateData(message, SOSPeerNextSequenceNumber(peer), error); | |
2492 | } | |
2493 | ||
2494 | if (result) { | |
2495 | // Capture the peer in our block (SOSEnginePeerMessageSentBlock) | |
2496 | CFRetainSafe(peer); | |
2497 | *sent = Block_copy(^(bool success) { | |
2498 | dispatch_async(engine->queue, ^{ | |
2499 | if (success) { | |
2500 | SOSPeerSetMustSendMessage(peer, false); | |
2501 | if (!confirmed && !proposed) { | |
2502 | SOSPeerSetSendObjects(peer, true); | |
2503 | secnoticeq("engine", "%@:%@ sendObjects=true L:%@", engine->myID, SOSPeerGetID(peer), local); | |
2504 | } | |
2505 | SOSPeerAddLocalManifest(peer, local); | |
2506 | SOSPeerAddProposedManifest(peer, proposed); | |
2507 | secnoticeq("engine", "send %@:%@ %@", engine->myID, SOSPeerGetID(peer), message); | |
2508 | //SOSEngineCheckPeerIntegrity(engine, peer, NULL); | |
2509 | } else { | |
2510 | secerror("%@:%@ failed to send %@", engine->myID, SOSPeerGetID(peer), message); | |
2511 | } | |
2512 | CFReleaseSafe(peer); | |
2513 | CFReleaseSafe(local); | |
2514 | CFReleaseSafe(proposed); | |
2515 | CFReleaseSafe(message); | |
2516 | }); | |
2517 | }); | |
2518 | } else { | |
2519 | CFReleaseSafe(local); | |
2520 | CFReleaseSafe(proposed); | |
2521 | CFReleaseSafe(message); | |
2522 | } | |
2523 | CFReleaseNull(extra); | |
2524 | CFReleaseNull(missing); | |
2525 | if (error && *error) | |
2526 | secerror("%@:%@ error in send: %@", engine->myID, SOSPeerGetID(peer), *error); | |
2527 | ||
2528 | return result; | |
2529 | } | |
2530 | ||
2531 | static void SOSEngineLogItemError(SOSEngineRef engine, CFStringRef peerID, CFDataRef key, CFDataRef optionalDigest, const char *where, CFErrorRef error) { | |
2532 | if (!optionalDigest) { | |
2533 | const uint8_t *d = CFDataGetBytePtr(key); | |
2534 | secwarning("%@:%@ object %02X%02X%02X%02X %s: %@", engine->myID, peerID, d[0], d[1], d[2], d[3], where, error ? (CFTypeRef)error : CFSTR("")); | |
2535 | } else { | |
2536 | const uint8_t *d = CFDataGetBytePtr(key); | |
2537 | const uint8_t *e = CFDataGetBytePtr(optionalDigest); | |
2538 | 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]); | |
2539 | } | |
2540 | } | |
2541 | ||
2542 | static bool SOSEngineWriteToBackup_locked(SOSEngineRef engine, SOSPeerRef peer, bool rewriteComplete, bool *didWrite, bool *incomplete, CFErrorRef *error) { | |
2543 | __block bool ok = SOSPeerWritePendingReset(peer, error); | |
2544 | if (!ok || !SOSPeerGetKeyBag(peer)) | |
2545 | return ok; | |
2546 | __block SOSManifestRef local = SOSEngineCopyLocalPeerManifest_locked(engine, peer, error); | |
2547 | __block SOSManifestRef proposed = SOSPeerGetProposedManifest(peer); | |
2548 | __block bool notify = true; | |
2549 | SOSManifestRef pendingObjects = NULL; | |
2550 | SOSManifestRef missing = NULL; | |
2551 | CFStringRef peerID = SOSPeerGetID(peer); | |
2552 | ||
2553 | ok &= SOSManifestDiff(proposed, local, &missing, &pendingObjects, error); | |
2554 | ||
2555 | secnoticeq("engine", "%@:%@: Send state for peer [%s%s%s] O: %zu, M: %zu", engine->myID, peerID, | |
2556 | local ? "L":"l", | |
2557 | proposed ? "P":"0", | |
2558 | pendingObjects ? "O":"0", | |
2559 | SOSManifestGetCount(pendingObjects), | |
2560 | SOSManifestGetCount(missing)); | |
2561 | ||
2562 | if (SOSManifestGetCount(missing) == 0 && SOSManifestGetCount(pendingObjects) == 0) { | |
2563 | // proposed == local (faster test than CFEqualSafe above), since we | |
2564 | // already did the SOSManifestDiff | |
2565 | if (rewriteComplete) { | |
2566 | notify = false; | |
2567 | } else { | |
2568 | secnoticeq("engine", "%@:%@ backup still done", engine->myID, peer); | |
2569 | goto done; | |
2570 | } | |
2571 | } | |
2572 | ok &= SOSPeerAppendToJournal(peer, error, ^(FILE *journalFile, keybag_handle_t kbhandle) { | |
2573 | SOSManifestRef objectsSent = NULL; | |
2574 | __block struct SOSDigestVector dvdel = SOSDigestVectorInit; | |
2575 | __block struct SOSDigestVector dvadd = SOSDigestVectorInit; | |
2576 | SOSManifestForEach(missing, ^(CFDataRef key, bool *stop) { | |
2577 | CFErrorRef localError = NULL; | |
2578 | if (ftello(journalFile) > kSOSBackupMaxFileSize) { | |
2579 | // Highwatermark hit on file. | |
2580 | *stop = true; | |
2581 | } else if (SOSBackupEventWriteDelete(journalFile, key, &localError)) { | |
2582 | SOSDigestVectorAppend(&dvdel, CFDataGetBytePtr(key)); | |
2583 | } else { | |
2584 | SOSEngineLogItemError(engine, peerID, key, NULL, "in SOSPeerWriteDelete", localError); | |
2585 | CFErrorPropagate(localError, error); | |
2586 | // TODO: Update of missing so proposed is updated properly | |
2587 | *stop = true; // Disk full? | |
2588 | ok = false; | |
2589 | } | |
2590 | }); | |
2591 | if (ok && SOSManifestGetCount(pendingObjects)) { | |
2592 | CFMutableArrayRef changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
fa7225c8 | 2593 | ok &= SOSDataSourceForEachObject(engine->dataSource, NULL, pendingObjects, error, ^void(CFDataRef key, SOSObjectRef object, bool *stop) { |
5c19dc3a A |
2594 | CFErrorRef localError = NULL; |
2595 | CFDataRef digest = NULL; | |
2596 | CFDictionaryRef backupItem = NULL; | |
2597 | if (ftello(journalFile) > kSOSBackupMaxFileSize) { | |
2598 | // Highwatermark hit on file. | |
2599 | *stop = true; | |
2600 | } else if (!object) { | |
2601 | SOSEngineLogItemError(engine, peerID, key, NULL, "dropping from manifest: not found in datasource", localError); | |
2602 | SOSChangesAppendDelete(changes, key); | |
2603 | } else if (!(backupItem = SOSObjectCopyBackup(engine->dataSource, object, kbhandle, &localError)) | |
2604 | || !(digest = SOSObjectCopyDigest(engine->dataSource, object, &localError))) { | |
2605 | if (SecErrorGetOSStatus(localError) == errSecDecode) { | |
2606 | // Decode error, we need to drop these objects from our manifests | |
2607 | SOSEngineLogItemError(engine, peerID, key, NULL, "dropping from manifest", localError); | |
2608 | SOSChangesAppendDelete(changes, key); | |
2609 | CFRelease(localError); | |
2610 | } else { | |
2611 | // Stop iterating and propagate out all other errors. | |
2612 | SOSEngineLogItemError(engine, peerID, key, NULL, "in SOSDataSourceForEachObject", localError); | |
2613 | *stop = true; | |
2614 | CFErrorPropagate(localError, error); | |
2615 | ok = false; | |
2616 | } | |
2617 | } else { | |
2618 | if (!CFEqual(key, digest)) { | |
2619 | SOSEngineLogItemError(engine, peerID, key, digest, "", NULL); | |
2620 | SOSChangesAppendDelete(changes, key); | |
2621 | SOSChangesAppendAdd(changes, object); // This is new behaviour but we think it's more correct | |
2622 | } | |
2623 | ||
2624 | if (SOSBackupEventWriteAdd(journalFile, backupItem, &localError)) { | |
2625 | SOSDigestVectorAppend(&dvadd, CFDataGetBytePtr(digest)); | |
2626 | } else { | |
2627 | SOSEngineLogItemError(engine, peerID, key, NULL, "in SOSPeerWriteAdd", localError); | |
2628 | *stop = true; // Disk full? | |
2629 | CFErrorPropagate(localError, error); | |
2630 | ok = false; | |
2631 | } | |
2632 | } | |
2633 | CFReleaseSafe(backupItem); | |
2634 | CFReleaseSafe(digest); | |
2635 | }); | |
2636 | if (CFArrayGetCount(changes)) { | |
2637 | CFErrorRef localError = NULL; | |
2638 | if (!SOSEngineUpdateChanges_locked(engine, NULL, kSOSDataSourceTransactionDidCommit, kSOSDataSourceSOSTransaction, changes, &localError)) | |
2639 | secerror("SOSEngineUpdateChanges_locked: %@ failed: %@", changes, localError); | |
2640 | CFReleaseSafe(localError); | |
2641 | // Since calling SOSEngineUpdateChanges_locked might cause local to change and might cause the backup peer to update proposed, refetch them here. | |
2642 | CFAssignRetained(local, SOSEngineCopyLocalPeerManifest_locked(engine, peer, error)); | |
2643 | proposed = SOSPeerGetProposedManifest(peer); | |
2644 | } | |
2645 | CFReleaseSafe(changes); | |
2646 | } | |
2647 | ||
2648 | if (dvadd.count || (proposed && dvdel.count)) { | |
2649 | *didWrite = true; | |
2650 | SOSManifestRef deleted = SOSManifestCreateWithDigestVector(&dvdel, error); | |
2651 | SOSManifestRef objectsSent = SOSManifestCreateWithDigestVector(&dvadd, error); | |
2652 | SOSManifestRef newProposed = SOSManifestCreateWithPatch(proposed, deleted, objectsSent, error); | |
2653 | CFReleaseSafe(deleted); | |
2654 | CFReleaseSafe(objectsSent); | |
2655 | SOSPeerSetProposedManifest(peer, newProposed); | |
2656 | CFReleaseSafe(newProposed); | |
2657 | proposed = SOSPeerGetProposedManifest(peer); | |
2658 | } | |
2659 | SOSDigestVectorFree(&dvdel); | |
2660 | SOSDigestVectorFree(&dvadd); | |
2661 | ||
2662 | // TODO: If proposed is NULL, and local is empty we should still consider ourselves done. | |
2663 | // It so happens this can't happen in practice today since there is at least a backupbag | |
2664 | // in the backup, but this is a bug waiting to rear its head in the future. | |
2665 | if (ok && CFEqualSafe(local, proposed)) { | |
2666 | CFErrorRef localError = NULL; | |
2667 | if (SOSBackupEventWriteCompleteMarker(journalFile, 899, &localError)) { | |
2668 | SOSPeerSetSendObjects(peer, true); | |
2669 | *didWrite = true; | |
2670 | secnoticeq("backup", "%@:%@ backup done%s", engine->myID, peerID, notify ? " notifying sbd" : ""); | |
2671 | // TODO: Now switch to changes based writing to backup sync. | |
2672 | // Currently we leave changes enabled but we probably shouldn't | |
2673 | } else { | |
2674 | secwarning("%@:%@ in SOSBackupPeerWriteCompleteMarker: %@", engine->myID, peerID, localError); | |
2675 | ok = false; | |
2676 | *incomplete = true; | |
2677 | CFErrorPropagate(localError, error); | |
2678 | } | |
2679 | } else { | |
2680 | secnoticeq("backup", "%@:%@ backup incomplete [%zu/%zu]%s", engine->myID, peerID, SOSManifestGetCount(local), SOSManifestGetCount(proposed), notify ? " notifying sbd" : ""); | |
2681 | *incomplete = true; | |
2682 | } | |
2683 | CFReleaseNull(objectsSent); | |
2684 | }); | |
2685 | if (notify) | |
2686 | SOSBackupPeerPostNotification("writing changes to backup"); | |
2687 | ||
2688 | done: | |
2689 | CFReleaseSafe(local); | |
2690 | CFReleaseNull(pendingObjects); | |
2691 | CFReleaseNull(missing); | |
2692 | ||
2693 | return ok; | |
2694 | } | |
2695 | ||
fa7225c8 | 2696 | bool SOSEngineSyncWithPeers(SOSEngineRef engine, CFErrorRef *error) { |
5c19dc3a A |
2697 | __block bool ok = true; |
2698 | __block bool incomplete = false; | |
2699 | ok &= SOSEngineDoTxnOnQueue(engine, error, ^(SOSTransactionRef txn, bool *commit) { | |
2700 | __block bool dirty = false; | |
2701 | SOSEngineForEachBackupPeer_locked(engine, ^(SOSPeerRef peer) { | |
2702 | ok = SOSEngineWriteToBackup_locked(engine, peer, false, &dirty, &incomplete, error); | |
2703 | }); | |
2704 | ||
2705 | if (dirty) | |
2706 | ok = SOSEngineSave(engine, txn, error); | |
2707 | }); | |
2708 | if (incomplete) { | |
2709 | // Ensure we get called again in a while (after a backup timeout) | |
2710 | // sbd will do this since we never wrote a complete marker. | |
2711 | // TODO: This relies on us not writing complete marker for update | |
2712 | // event while we havn't finished a full backup, which we currently still do. | |
2713 | } | |
2714 | return ok; | |
2715 | } | |
2716 | ||
2717 | bool SOSEngineHandleMessage(SOSEngineRef engine, CFStringRef peerID, | |
2718 | CFDataRef raw_message, CFErrorRef *error) | |
2719 | { | |
2720 | __block bool result = true; | |
2721 | __block bool somethingChanged = false; | |
2722 | SOSMessageRef message = SOSMessageCreateWithData(kCFAllocatorDefault, raw_message, error); | |
2723 | result &= message && SOSEngineDoTxnOnQueue(engine, error, ^(SOSTransactionRef txn, bool *commit) { | |
2724 | result = SOSEngineHandleMessage_locked(engine, peerID, message, txn, commit, &somethingChanged, error); | |
2725 | }); | |
2726 | CFReleaseSafe(message); | |
2727 | if (somethingChanged) | |
2728 | SecKeychainChanged(false); | |
2729 | return result; | |
2730 | } | |
2731 | ||
2732 | void SOSEngineCircleChanged(SOSEngineRef engine, CFStringRef myPeerID, CFArrayRef trustedPeers, CFArrayRef untrustedPeers) { | |
2733 | __block bool peersOrViewsChanged = false; | |
2734 | SOSEngineDoOnQueue(engine, ^{ | |
2735 | peersOrViewsChanged = SOSEngineCircleChanged_locked(engine, myPeerID, trustedPeers, untrustedPeers); | |
fa7225c8 A |
2736 | engine->dirty = peersOrViewsChanged; |
2737 | engine->codersNeedSaving = peersOrViewsChanged; | |
5c19dc3a A |
2738 | }); |
2739 | ||
2740 | __block bool ok = true; | |
2741 | __block CFErrorRef localError = NULL; | |
2742 | ok &= SOSEngineDoTxnOnQueue(engine, &localError, ^(SOSTransactionRef txn, bool *commit) { | |
2743 | ok = *commit = SOSEngineSave(engine, txn, &localError); | |
2744 | }); | |
2745 | if (!ok) { | |
2746 | secerror("failed to save engine state: %@", localError); | |
2747 | CFReleaseSafe(localError); | |
2748 | } | |
2749 | ||
2750 | if (peersOrViewsChanged) | |
2751 | SOSCCSyncWithAllPeers(); | |
2752 | } | |
2753 | ||
2754 | SOSManifestRef SOSEngineCopyManifest(SOSEngineRef engine, CFErrorRef *error) { | |
2755 | __block SOSManifestRef result = NULL; | |
2756 | SOSEngineDoOnQueue(engine, ^{ | |
2757 | result = SOSEngineCopyManifestWithViewNameSet_locked(engine, SOSViewsGetV0ViewSet(), error); | |
2758 | }); | |
2759 | return result; | |
2760 | } | |
2761 | ||
2762 | SOSManifestRef SOSEngineCopyLocalPeerManifest(SOSEngineRef engine, SOSPeerRef peer, CFErrorRef *error) { | |
2763 | __block SOSManifestRef result = NULL; | |
2764 | SOSEngineDoOnQueue(engine, ^{ | |
2765 | result = SOSEngineCopyLocalPeerManifest_locked(engine, peer, error); | |
2766 | }); | |
2767 | return result; | |
2768 | } | |
2769 | ||
2770 | bool SOSEngineUpdateChanges(SOSEngineRef engine, SOSDataSourceTransactionSource source, CFArrayRef changes, CFErrorRef *error) { | |
2771 | __block bool result = true; | |
2772 | SOSEngineDoOnQueue(engine, ^{ | |
2773 | result = SOSEngineUpdateChanges_locked(engine, NULL, kSOSDataSourceTransactionDidCommit, source, changes, error); | |
2774 | }); | |
2775 | return result; | |
2776 | } | |
2777 | ||
2778 | // | |
2779 | // Peer state layout. WRONG! It's an array now | |
2780 | // The peer state is an array. | |
2781 | // The first element of the array is a dictionary with any number of keys and | |
2782 | // values in it (for future expansion) such as changing the digest size or type | |
2783 | // or remebering boolean flags for a peers sake. | |
2784 | // The next three are special in that they are manifest digests with special | |
2785 | // meaning and rules as to how they are treated (These are dynamically updated | |
2786 | // based on database activity so they have a fully history of all changes made | |
2787 | // to the local db. The first is the manifest representing the pendingObjects | |
2788 | // to send to the other peer. This is normally only ever appending to, and in | |
2789 | // particular with transactions originating from the Keychain API that affect | |
2790 | // syncable items will need to add the new objects digests to the pendingObjects list | |
2791 | // while adding the digests of any tombstones encountered to the extra list. | |
2792 | ||
2793 | SOSPeerRef SOSEngineCopyPeerWithID(SOSEngineRef engine, CFStringRef peer_id, CFErrorRef *error) { | |
2794 | __block SOSPeerRef peer = NULL; | |
2795 | SOSEngineDoOnQueue(engine, ^{ | |
2796 | peer = SOSEngineCopyPeerWithID_locked(engine, peer_id, error); | |
2797 | }); | |
2798 | return peer; | |
2799 | } | |
2800 | ||
fa7225c8 | 2801 | bool SOSEngineForPeerIDNoCoder(SOSEngineRef engine, CFStringRef peerID, CFErrorRef *error, void (^forPeer)(SOSTransactionRef txn, SOSPeerRef peer)) { |
5c19dc3a | 2802 | __block bool ok = true; |
fa7225c8 A |
2803 | SOSDataSourceReadWithCommitQueue(engine->dataSource, error, ^(SOSTransactionRef txn) { |
2804 | SOSEngineDoOnQueue(engine, ^{ | |
2805 | SOSPeerRef peer = SOSEngineCopyPeerWithID_locked(engine, peerID, error); | |
2806 | if (peer) { | |
2807 | forPeer(txn, peer); | |
2808 | CFRelease(peer); | |
2809 | } else { | |
2810 | ok = false; | |
2811 | } | |
2812 | }); | |
2813 | }); | |
2814 | ||
2815 | return ok; | |
2816 | } | |
2817 | ||
2818 | bool SOSEngineForPeerID(SOSEngineRef engine, CFStringRef peerID, CFErrorRef *error, void (^forPeer)(SOSTransactionRef txn, SOSPeerRef peer, SOSCoderRef coder)) { | |
2819 | __block bool ok = true; | |
2820 | SOSDataSourceReadWithCommitQueue(engine->dataSource, error, ^(SOSTransactionRef txn) { | |
2821 | SOSEngineDoOnQueue(engine, ^{ | |
2822 | SOSPeerRef peer = SOSEngineCopyPeerWithID_locked(engine, peerID, error); | |
2823 | if (peer) { | |
2824 | SOSCoderRef coder = SOSEngineGetCoder_locked(engine, peerID, NULL); | |
2825 | forPeer(txn, peer, coder); | |
2826 | CFRelease(peer); | |
2827 | } else { | |
2828 | ok = false; | |
2829 | } | |
2830 | }); | |
5c19dc3a | 2831 | }); |
fa7225c8 | 2832 | |
5c19dc3a A |
2833 | return ok; |
2834 | } | |
2835 | ||
fa7225c8 | 2836 | bool SOSEngineWithPeerID(SOSEngineRef engine, CFStringRef peerID, CFErrorRef *error, void (^with)(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *forceSaveState)) { |
5c19dc3a A |
2837 | __block bool result = true; |
2838 | result &= SOSEngineDoTxnOnQueue(engine, error, ^(SOSTransactionRef txn, bool *commit) { | |
2839 | SOSPeerRef peer = SOSEngineCopyPeerWithID_locked(engine, peerID, error); | |
2840 | if (!peer) { | |
2841 | result = SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("Engine has no peer for %@"), peerID); | |
2842 | } else { | |
2843 | bool saveState = false; | |
fa7225c8 A |
2844 | SOSCoderRef coder = SOSEngineGetCoderInTx_locked(engine, txn, peerID, NULL); |
2845 | with(peer, coder, engine->dataSource, txn, &saveState); | |
5c19dc3a A |
2846 | CFReleaseSafe(peer); |
2847 | if (saveState) | |
2848 | result = SOSEngineSave(engine, txn, error); | |
2849 | // TODO: Don't commit if engineSave fails? | |
2850 | } | |
2851 | }); | |
2852 | ||
2853 | return result; | |
2854 | } | |
2855 | ||
2856 | CFDataRef SOSEngineCreateMessageToSyncToPeer(SOSEngineRef engine, CFStringRef peerID, SOSEnginePeerMessageSentBlock *sentBlock, CFErrorRef *error) { | |
2857 | __block CFDataRef message = NULL; | |
fa7225c8 A |
2858 | SOSEngineForPeerID(engine, peerID, error, ^(SOSTransactionRef txn, SOSPeerRef peer, SOSCoderRef coder) { |
2859 | message = SOSEngineCreateMessage_locked(engine, txn, peer, error, sentBlock); | |
5c19dc3a A |
2860 | }); |
2861 | return message; | |
2862 | } | |
2863 | ||
2864 | bool SOSEnginePeerDidConnect(SOSEngineRef engine, CFStringRef peerID, CFErrorRef *error) { | |
fa7225c8 | 2865 | return SOSEngineWithPeerID(engine, peerID, error, ^(SOSPeerRef peer, SOSCoderRef coder, SOSDataSourceRef dataSource, SOSTransactionRef txn, bool *saveState) { |
5c19dc3a A |
2866 | *saveState = SOSPeerDidConnect(peer); |
2867 | }); | |
2868 | } | |
2869 | ||
2870 | bool SOSEngineSetPeerConfirmedManifest(SOSEngineRef engine, CFStringRef backupName, | |
2871 | CFDataRef keybagDigest, CFDataRef manifestData, CFErrorRef *error) { | |
2872 | __block bool ok = true; | |
2873 | ||
fa7225c8 | 2874 | ok &= SOSEngineForPeerID(engine, backupName, error, ^(SOSTransactionRef txn, SOSPeerRef peer, SOSCoderRef coder) { |
5c19dc3a A |
2875 | bool dirty = false; |
2876 | bool incomplete = false; | |
2877 | SOSManifestRef confirmed = NULL; | |
2878 | CFDataRef keybag = SOSPeerGetKeyBag(peer); | |
2879 | CFDataRef computedKeybagDigest = keybag ? CFDataCopySHA1Digest(keybag, NULL) : NULL; | |
2880 | if (CFEqualSafe(keybagDigest, computedKeybagDigest)) { | |
2881 | ok = confirmed = SOSManifestCreateWithData(manifestData, error); | |
2882 | if (ok) { | |
2883 | // Set both confirmed and proposed (confirmed is just | |
2884 | // for debug status, proposed is actually what's used | |
2885 | // by the backup peer). | |
2886 | SOSPeerSetConfirmedManifest(peer, confirmed); | |
2887 | SOSPeerSetProposedManifest(peer, confirmed); | |
2888 | } | |
2889 | } else { | |
2890 | // sbd missed a reset event, send it again | |
2891 | // Force SOSEngineWriteToBackup_locked to call SOSPeerWriteReset, which clears | |
2892 | // confirmed and proposed manifests and writes the keybag to the journal. | |
2893 | SOSPeerSetMustSendMessage(peer, true); | |
2894 | } | |
2895 | ||
2896 | // Stop changes from writing complete markers, unless SOSEngineWriteToBackup_locked() detects we are in sync | |
2897 | SOSPeerSetSendObjects(peer, false); | |
2898 | // Write data for this peer if we can, technically not needed for non legacy protocol support all the time. | |
2899 | ok = SOSEngineWriteToBackup_locked(engine, peer, true, &dirty, &incomplete, error); | |
2900 | ||
2901 | CFReleaseSafe(confirmed); | |
2902 | CFReleaseSafe(computedKeybagDigest); | |
2903 | }); | |
2904 | return ok; | |
2905 | } | |
2906 | ||
2907 | CFArrayRef SOSEngineCopyBackupPeerNames(SOSEngineRef engine, CFErrorRef *error) { | |
2908 | __block CFMutableArrayRef backupNames = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
2909 | SOSEngineForEachBackupPeer(engine, ^(SOSPeerRef peer) { | |
2910 | CFArrayAppendValue(backupNames, SOSPeerGetID(peer)); | |
2911 | }); | |
2912 | return backupNames; | |
2913 | } | |
2914 | ||
5c19dc3a | 2915 | static CFStringRef CFStringCreateWithLabelAndViewNameSetDescription(CFStringRef label, CFStringRef peerID, CFSetRef vns, SOSManifestRef manifest) { |
fa7225c8 A |
2916 | __block CFStringRef desc; |
2917 | CFStringSetPerformWithDescription(vns, ^(CFStringRef description) { | |
2918 | desc = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ %@ (%@) [%lu]"), label, peerID, description, (manifest) ? SOSManifestGetCount(manifest): 0); | |
2919 | }); | |
5c19dc3a A |
2920 | return desc; |
2921 | } | |
2922 | ||
2923 | static void CFArrayAppendConfirmedDigestsEntry(CFMutableArrayRef array, CFStringRef label, CFStringRef peerID, CFSetRef vns, SOSManifestRef manifest) { | |
2924 | CFStringRef desc = CFStringCreateWithLabelAndViewNameSetDescription(label, peerID, vns, manifest); | |
2925 | CFArrayAppendValue(array, desc); | |
2926 | CFReleaseSafe(desc); | |
2927 | CFDataRef digest = SOSManifestGetDigest(manifest, NULL); | |
2928 | if (digest) { | |
2929 | CFArrayAppendValue(array, digest); | |
2930 | } else { | |
2931 | CFDataRef nullDigest = CFDataCreate(kCFAllocatorDefault, NULL, 0); | |
2932 | CFArrayAppendValue(array, nullDigest); | |
2933 | CFReleaseSafe(nullDigest); | |
2934 | } | |
2935 | } | |
2936 | ||
2937 | static CFArrayRef SOSEngineCopyPeerConfirmedDigests_locked(SOSEngineRef engine, CFErrorRef *error) { | |
2938 | CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
2939 | CFDictionaryForEach(engine->viewNameSet2ChangeTracker, ^(const void *vns, const void *ct) { | |
2940 | SOSManifestRef manifest = SOSEngineCopyManifestWithViewNameSet_locked(engine, vns, error); | |
2941 | CFArrayAppendConfirmedDigestsEntry(result, CFSTR("local "), engine->myID, (CFSetRef)vns, manifest); | |
2942 | CFReleaseSafe(manifest); | |
2943 | }); | |
2944 | ||
2945 | // Copy other peers even if we aren't in the circle, since we're observing it. | |
2946 | SOSEngineForEachPeer_locked(engine, ^(SOSPeerRef peer) { | |
2947 | CFArrayAppendConfirmedDigestsEntry(result, CFSTR("remote"), SOSPeerGetID(peer), SOSPeerGetViewNameSet(peer), | |
2948 | SOSPeerGetConfirmedManifest(peer)); | |
2949 | }); | |
2950 | return result; | |
2951 | } | |
2952 | ||
2953 | CFArrayRef SOSEngineCopyPeerConfirmedDigests(SOSEngineRef engine, CFErrorRef *error) { | |
2954 | __block CFArrayRef result = NULL; | |
2955 | SOSEngineDoOnQueue(engine, ^{ | |
2956 | result = SOSEngineCopyPeerConfirmedDigests_locked(engine, error); | |
2957 | }); | |
2958 | return result; | |
2959 | } | |
2960 | ||
2961 | SOSDataSourceRef SOSEngineGetDataSource(SOSEngineRef engine) { | |
2962 | return engine->dataSource; | |
2963 | } | |
fa7225c8 A |
2964 | |
2965 | #define ENGINELOGSTATE "engineLogState" | |
2966 | void SOSEngineLogState(SOSEngineRef engine) { | |
2967 | CFErrorRef error = NULL; | |
2968 | CFArrayRef confirmedDigests = NULL; | |
2969 | ||
2970 | secnotice(ENGINELOGSTATE, "Start"); | |
2971 | ||
2972 | require_action_quiet(engine, retOut, secnotice(ENGINELOGSTATE, "No Engine Available")); | |
2973 | confirmedDigests = SOSEngineCopyPeerConfirmedDigests(engine, &error); | |
2974 | require_action_quiet(confirmedDigests, retOut, secnotice(ENGINELOGSTATE, "No engine peers: %@\n", error)); | |
2975 | CFIndex entries = CFArrayGetCount(confirmedDigests) / 2; | |
2976 | for(CFIndex i = 0; i < entries; i++) { | |
2977 | CFStringRef partA = asString(CFArrayGetValueAtIndex(confirmedDigests, i*2), NULL); | |
2978 | CFDataRef partB = asData(CFArrayGetValueAtIndex(confirmedDigests, i*2+1), NULL); | |
2979 | if(partA && partB) { | |
2980 | CFStringRef hexDigest = CFDataCopyHexString(partB); | |
2981 | secnotice(ENGINELOGSTATE, "%@ %@", partA, hexDigest); | |
2982 | CFReleaseNull(hexDigest); | |
2983 | } | |
2984 | } | |
2985 | retOut: | |
2986 | CFReleaseNull(error); | |
2987 | CFReleaseNull(confirmedDigests); | |
2988 | secnotice(ENGINELOGSTATE, "Finish"); | |
2989 | ||
2990 | return; | |
2991 | } |