]> git.saurik.com Git - apple/security.git/blob - keychain/SecureObjectSync/Regressions/SOSTestDevice.c
Security-59306.80.4.tar.gz
[apple/security.git] / keychain / SecureObjectSync / Regressions / SOSTestDevice.c
1 /*
2 * Copyright (c) 2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 // Test syncing between SecItemDataSource and SOSTestDataSource
26
27 #include "SOSTestDevice.h"
28 #include "SOSTestDataSource.h"
29 #include <regressions/test/testmore.h>
30
31 #include <Security/SecureObjectSync/SOSCloudCircle.h>
32 #include "keychain/SecureObjectSync/SOSEngine.h"
33 #include "keychain/SecureObjectSync/SOSPeer.h"
34 #include <Security/SecureObjectSync/SOSViews.h>
35 #include <Security/SecBase64.h>
36 #include <Security/SecItem.h>
37 #include <Security/SecItemPriv.h>
38 #include <corecrypto/ccsha2.h>
39 #include "keychain/securityd/SecItemServer.h"
40 #include "keychain/securityd/SecItemDataSource.h"
41 #include <utilities/SecCFWrappers.h>
42 #include <utilities/SecFileLocations.h>
43 #include <utilities/SecIOFormat.h>
44
45 #include <stdint.h>
46 #include <AssertMacros.h>
47
48 CFStringRef SOSMessageCopyDigestHex(SOSMessageRef message) {
49 uint8_t digest[CCSHA1_OUTPUT_SIZE];
50 // TODO: Pass in real sequenceNumber.
51 CFDataRef msgData = SOSMessageCreateData(message, 0, NULL);
52 if (!msgData) return NULL;
53 ccdigest(ccsha1_di(), CFDataGetLength(msgData), CFDataGetBytePtr(msgData), digest);
54 CFMutableStringRef hex = CFStringCreateMutable(0, 2 * sizeof(digest));
55 for (unsigned int ix = 0; ix < sizeof(digest); ++ix) {
56 CFStringAppendFormat(hex, 0, CFSTR("%02X"), digest[ix]);
57 }
58 CFReleaseSafe(msgData);
59 return hex;
60 }
61
62 static void SOSTestDeviceDestroy(CFTypeRef cf) {
63 SOSTestDeviceRef td = (SOSTestDeviceRef)cf;
64 CFReleaseSafe(td->peers);
65 if (td->ds)
66 SOSDataSourceRelease(td->ds, NULL);
67 if (td->dsf)
68 SOSDataSourceFactoryRelease(td->dsf);
69 CFReleaseSafe(td->db);
70 }
71
72 void SOSTestDeviceDestroyEngine(CFMutableDictionaryRef testDevices) {
73
74 CFArrayRef deviceIDs = (CFArrayRef)CFDictionaryGetValue(testDevices, CFSTR("@devicesIDs"));
75
76 CFArrayForEach(deviceIDs, ^(const void *value) {
77 CFStringRef sourceID = (CFStringRef)value;
78 SOSTestDeviceRef source = (SOSTestDeviceRef)CFDictionaryGetValue(testDevices, sourceID);
79 SOSEngineRef engine = SOSDataSourceGetSharedEngine(source->ds, NULL);
80 SOSEngineClearCache(engine);
81 SOSEngineDispose(engine);
82 });
83 }
84
85 CFStringRef SOSTestDeviceGetID(SOSTestDeviceRef td) {
86 CFStringRef engineID = NULL;
87 SOSEngineRef engine = SOSDataSourceGetSharedEngine(td->ds, NULL);
88 if (engine)
89 engineID = SOSEngineGetMyID(engine);
90 return engineID;
91 }
92
93 void SOSTestDeviceForEachPeerID(SOSTestDeviceRef td, void(^peerBlock)(CFStringRef peerID, bool *stop)) {
94 SOSPeerRef peer;
95 bool stop = false;
96 CFArrayForEachC(td->peers, peer) {
97 peerBlock(SOSPeerGetID(peer), &stop);
98 if (stop)
99 break;
100 }
101 }
102
103 static CFStringRef SOSTestDeviceCopyFormatDescription(CFTypeRef cf, CFDictionaryRef formatOptions) {
104 SOSTestDeviceRef td = (SOSTestDeviceRef)cf;
105 CFMutableStringRef result = CFStringCreateMutable(kCFAllocatorDefault, 0);
106 CFStringAppendFormat(result, NULL, CFSTR("<SOSTestDevice %@"), td->ds->engine);
107 SOSTestDeviceForEachPeerID(td, ^(CFStringRef peerID, bool *stop) {
108 SOSPeerRef peer = SOSEngineCopyPeerWithID(td->ds->engine, peerID, NULL);
109 CFStringAppendFormat(result, NULL, CFSTR("\n%@"), peer);
110 CFReleaseSafe(peer);
111 });
112 CFStringAppendFormat(result, NULL, CFSTR(">"));
113 return result;
114 }
115
116 CFGiblisFor(SOSTestDevice)
117
118 static SOSTestDeviceRef SOSTestDeviceCreateInternal(CFAllocatorRef allocator, CFStringRef engineID) {
119 SOSTestDeviceRef td = CFTypeAllocate(SOSTestDevice, struct __OpaqueSOSTestDevice, allocator);
120 td->peers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
121 td->mute = false;
122 return td;
123 }
124
125 SOSTestDeviceRef SOSTestDeviceCreateWithDb(CFAllocatorRef allocator, CFStringRef engineID, SecDbRef db) {
126 setup("create device");
127 SOSTestDeviceRef td = SOSTestDeviceCreateInternal(allocator, engineID);
128 CFRetainAssign(td->db, db);
129 td->dsf = SecItemDataSourceFactoryGetShared(td->db);
130 CFStringRef sname = SOSDataSourceFactoryCopyName(td->dsf);
131 CFErrorRef error = NULL;
132 ok (td->ds = SOSDataSourceFactoryCreateDataSource(td->dsf, sname, &error), "%@ create datasource \"%@\" [error: %@]", engineID, sname, error);
133 CFReleaseNull(error);
134 CFReleaseNull(sname);
135 assert(td->ds); // Shut up static analyzer and test generally run in debug mode anyway
136 if (td->ds)
137 SOSEngineCircleChanged(SOSDataSourceGetSharedEngine(td->ds, NULL), engineID, NULL, NULL);
138 return td;
139 }
140
141 SOSTestDeviceRef SOSTestDeviceCreateWithDbNamed(CFAllocatorRef allocator, CFStringRef engineID, CFStringRef dbName) {
142 CFURLRef url = SecCopyURLForFileInKeychainDirectory(dbName);
143 CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
144 SecDbRef db = SecKeychainDbCreate(path, NULL);
145 SOSTestDeviceRef td = SOSTestDeviceCreateWithDb(allocator, engineID, db);
146 CFReleaseSafe(db);
147 CFReleaseSafe(path);
148 CFReleaseSafe(url);
149 return td;
150 }
151
152 SOSTestDeviceRef SOSTestDeviceCreateWithTestDataSource(CFAllocatorRef allocator, CFStringRef engineID,
153 void(^prepop)(SOSDataSourceRef ds)) {
154 setup("create device");
155 SOSTestDeviceRef td = SOSTestDeviceCreateInternal(allocator, engineID);
156
157 td->ds = SOSTestDataSourceCreate();
158 if (prepop)
159 prepop(td->ds);
160 CFErrorRef error = NULL;
161 ok(td->ds->engine = SOSEngineCreate(td->ds, &error), "create engine: %@", error);
162 SOSEngineCircleChanged(td->ds->engine, engineID, NULL, NULL);
163 CFReleaseNull(error);
164 return td;
165 }
166
167
168 CFSetRef SOSViewsCopyTestV0Default() {
169 const void *values[] = { kSOSViewKeychainV0 };
170 return CFSetCreate(kCFAllocatorDefault, values, array_size(values), &kCFTypeSetCallBacks);
171 }
172
173 CFSetRef SOSViewsCopyTestV2Default() { // this was originally listing all the views - not just the defaults - but those used to be the default. So we'll programatically get all - the actual test depends on that.
174 return SOSViewCopyViewSet(kViewSetAll);
175 }
176
177 SOSTestDeviceRef SOSTestDeviceSetPeerIDs(SOSTestDeviceRef td, CFArrayRef peerIDs, CFIndex version, CFSetRef defaultViews) {
178 setup("create device");
179 CFStringRef engineID = SOSTestDeviceGetID(td);
180 CFTypeRef peerMeta;
181 CFMutableArrayRef trustedPeersIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
182 CFErrorRef error = NULL;
183 CFArrayForEachC(peerIDs, peerMeta) {
184 CFDataRef keyBag = NULL;
185 CFStringRef peerID = SOSPeerMetaGetComponents(peerMeta, NULL, &keyBag, &error);
186 if (!peerID) {
187 fail("SOSPeerMetaGetComponents %@: %@", peerMeta, error);
188 CFReleaseNull(error);
189 } else if (!CFEqualSafe(peerID, engineID)) {
190 if (isString(peerMeta)) {
191 CFTypeRef meta = SOSPeerMetaCreateWithComponents(peerID, defaultViews, keyBag);
192 CFArrayAppendValue(trustedPeersIDs, meta);
193 CFReleaseSafe(meta);
194 } else {
195 CFArrayAppendValue(trustedPeersIDs, peerMeta);
196 }
197 }
198 }
199
200 SOSEngineCircleChanged(td->ds->engine, engineID, trustedPeersIDs, NULL);
201 SOSEngineForEachPeer(td->ds->engine, ^(SOSPeerRef peer) {
202 // TODO: Rewrite this to add version to the peerMeta and remove this hack
203 CFMutableDictionaryRef state = (CFMutableDictionaryRef)SOSPeerCopyState(peer, NULL);
204 CFNumberRef versionNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &version);
205 CFDictionarySetValue(state, CFSTR("version") /* kSOSPeerVersionKey */, versionNumber);
206 CFReleaseSafe(versionNumber);
207 CFErrorRef peerError = NULL;
208 ok(SOSPeerSetState(peer, td->ds->engine, state, &peerError), "SOSPeerSetState: %@", peerError);
209 CFReleaseNull(peerError);
210 CFReleaseSafe(state);
211 CFArrayAppendValue(td->peers, peer);
212 });
213 CFArrayForEachC(trustedPeersIDs, peerMeta) {
214 CFStringRef peerID = SOSPeerMetaGetComponents(peerMeta, NULL, NULL, &error);
215 ok(peerID && SOSEnginePeerDidConnect(td->ds->engine, peerID, &error), "tell %@ %@ connected: %@", engineID, peerID, error);
216 CFReleaseNull(error);
217 }
218 CFReleaseSafe(trustedPeersIDs);
219 return td;
220 }
221
222 SOSTestDeviceRef SOSTestDeviceSetMute(SOSTestDeviceRef td, bool mute) {
223 td->mute = mute;
224 return td;
225 }
226
227 bool SOSTestDeviceIsMute(SOSTestDeviceRef td) {
228 return td->mute;
229 }
230
231 bool SOSTestDeviceSetEngineState(SOSTestDeviceRef td, CFDataRef derEngineState) {
232 CFErrorRef localError = NULL;
233 SOSTestEngineSaveWithDER(td->ds->engine, derEngineState, &localError);
234 return true;
235 }
236
237 bool SOSTestDeviceEngineSave(SOSTestDeviceRef td, CFErrorRef *error) {
238 __block bool rx = false;
239 if (!SOSDataSourceWithAPI(td->ds, true, error, ^(SOSTransactionRef txn, bool *commit) {
240 rx = SOSTestEngineSave(td->ds->engine, txn, error);
241 }))
242 fail("ds transaction %@", error ? *error : NULL);
243 return rx;
244 }
245
246 bool SOSTestDeviceEngineLoad(SOSTestDeviceRef td, CFErrorRef *error) {
247 __block bool rx = false;
248 if (!SOSDataSourceWithAPI(td->ds, true, error, ^(SOSTransactionRef txn, bool *commit) {
249 rx = SOSTestEngineLoad(td->ds->engine, txn, error);
250 }))
251 fail("ds transaction %@", error ? *error : NULL);
252 return rx;
253 }
254
255 CFDataRef SOSTestDeviceCreateMessage(SOSTestDeviceRef td, CFStringRef peerID) {
256 setup("create message");
257 CFErrorRef error = NULL;
258 SOSEnginePeerMessageSentCallback* sent = NULL;
259 CFDataRef msgData;
260 CFMutableArrayRef attributeList = NULL;
261 ok(msgData = SOSEngineCreateMessageToSyncToPeer(td->ds->engine, peerID, &attributeList, &sent, &error),
262 "create message to %@: %@", peerID, error);
263
264 SOSEngineMessageCallCallback(sent, true);
265 SOSEngineFreeMessageCallback(sent);
266
267 return msgData;
268 }
269
270 bool SOSTestDeviceHandleMessage(SOSTestDeviceRef td, CFStringRef peerID, CFDataRef msgData) {
271 setup("handle message");
272 if (!msgData) return false;
273 CFErrorRef error = NULL;
274 bool handled;
275 SOSMessageRef message;
276
277 ok(message = SOSMessageCreateWithData(kCFAllocatorDefault, msgData, &error), "decode message %@: %@", msgData, error);
278 CFReleaseNull(error);
279 pass("handling %@->%@ %@", peerID, SOSEngineGetMyID(SOSDataSourceGetSharedEngine(td->ds, &error)), message);
280 ok(handled = SOSEngineHandleMessage(SOSDataSourceGetSharedEngine(td->ds, &error), peerID, msgData, &error),
281 "handled from %@ %@: %@", peerID, message, error);
282 CFReleaseNull(error);
283
284 CFReleaseNull(message);
285 return handled;
286 }
287
288 void SOSTestDeviceAddGenericItem(SOSTestDeviceRef td, CFStringRef account, CFStringRef server) {
289 __block CFErrorRef error = NULL;
290 if (!SOSDataSourceWithAPI(td->ds, true, &error, ^(SOSTransactionRef txn, bool *commit) {
291 SOSObjectRef object = SOSDataSourceCreateGenericItem(td->ds, account, server);
292 ok(SOSDataSourceMergeObject(td->ds, txn, object, NULL, &error), "%@ added API object %@", SOSTestDeviceGetID(td), error ? (CFTypeRef)error : (CFTypeRef)CFSTR("ok"));
293 CFReleaseSafe(object);
294 CFReleaseNull(error);
295 }))
296 fail("ds transaction %@", error);
297 CFReleaseNull(error);
298 }
299
300 void SOSTestDeviceAddGenericItemTombstone(SOSTestDeviceRef td, CFStringRef account, CFStringRef server) {
301 __block CFErrorRef error = NULL;
302 if (!SOSDataSourceWithAPI(td->ds, true, &error, ^(SOSTransactionRef txn, bool *commit) {
303 SOSObjectRef object = SOSDataSourceCreateGenericItemWithData(td->ds, account, server, true, NULL);
304 SOSDataSourceMergeObject(td->ds, txn, object, NULL, &error);
305 CFReleaseSafe(object);
306 CFReleaseNull(error);
307 }))
308 fail("ds transaction %@", error);
309 CFReleaseNull(error);
310 }
311
312 void SOSTestDeviceAddGenericItemWithData(SOSTestDeviceRef td, CFStringRef account, CFStringRef server, CFDataRef data) {
313 __block CFErrorRef error = NULL;
314 if (!SOSDataSourceWithAPI(td->ds, true, &error, ^(SOSTransactionRef txn, bool *commit) {
315 SOSObjectRef object = SOSDataSourceCreateGenericItemWithData(td->ds, account, server, false, data);
316 SOSDataSourceMergeObject(td->ds, txn, object, NULL, &error);
317 CFReleaseSafe(object);
318 CFReleaseNull(error);
319 }))
320 fail("ds transaction %@", error);
321 CFReleaseNull(error);
322 }
323
324 void SOSTestDeviceAddRemoteGenericItem(SOSTestDeviceRef td, CFStringRef account, CFStringRef server) {
325 __block CFErrorRef error = NULL;
326 if (!SOSDataSourceWithAPI(td->ds, false, &error, ^(SOSTransactionRef txn, bool *commit) {
327 SOSObjectRef object = SOSDataSourceCreateGenericItem(td->ds, account, server);
328 ok(SOSDataSourceMergeObject(td->ds, txn, object, NULL, &error), "%@ added remote object %@", SOSTestDeviceGetID(td), error ? (CFTypeRef)error : (CFTypeRef)CFSTR("ok"));
329 CFReleaseSafe(object);
330 CFReleaseNull(error);
331 }))
332 fail("ds transaction %@", error);
333 CFReleaseNull(error);
334 }
335
336 bool SOSTestDeviceAddGenericItems(SOSTestDeviceRef td, CFIndex count, CFStringRef account, CFStringRef server) {
337 __block bool didAdd = false;
338 __block CFErrorRef error = NULL;
339 if (!SOSDataSourceWithAPI(td->ds, true, &error, ^(SOSTransactionRef txn, bool *commit) {
340 bool success = true;
341 CFIndex ix = 0;
342 for (; success && ix < count; ++ix) {
343 CFStringRef accountStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%" PRIdCFIndex), account, ix);
344 SOSObjectRef object = SOSDataSourceCreateGenericItem(td->ds, accountStr, server);
345 success = SOSDataSourceMergeObject(td->ds, txn, object, NULL, &error);
346 CFReleaseNull(accountStr);
347 CFReleaseSafe(object);
348 }
349 ok(success, "%@ added %" PRIdCFIndex " API objects %@", SOSTestDeviceGetID(td), ix, error ? (CFTypeRef)error : (CFTypeRef)CFSTR("ok"));
350 didAdd = success && ix == count;
351 CFReleaseNull(error);
352 }))
353 fail("ds transaction %@", error);
354 CFReleaseNull(error);
355 return didAdd;
356 }
357
358 void SOSTestDeviceAddV0EngineStateWithData(SOSDataSourceRef ds, CFDataRef engineStateData) {
359 __block CFErrorRef error = NULL;
360 const CFStringRef kSOSEngineState = CFSTR("engine-state");
361
362 if (!SOSDataSourceWithAPI(ds, true, &error, ^(SOSTransactionRef txn, bool *commit) {
363 SOSObjectRef object = SOSDataSourceCreateV0EngineStateWithData(ds, engineStateData);
364
365 // Note that state doesn't use SOSDataSourceMergeObject
366 SOSDataSourceSetStateWithKey(ds, txn, kSOSEngineState, kSecAttrAccessibleAlwaysPrivate, engineStateData, &error);
367 CFReleaseSafe(object);
368 CFReleaseNull(error);
369 }))
370 fail("ds transaction %@", error);
371
372 CFReleaseNull(error);
373 }
374
375 CFMutableDictionaryRef SOSTestDeviceListCreate(bool realDb, CFIndex version, CFArrayRef deviceIDs,
376 void(^prepop)(SOSDataSourceRef ds)) {
377 CFMutableDictionaryRef testDevices = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
378 CFStringRef deviceID;
379 CFSetRef defaultViews = realDb ? SOSViewsCopyTestV2Default() : SOSViewsCopyTestV0Default();
380 CFArrayForEachC(deviceIDs, deviceID) {
381 SOSTestDeviceRef device;
382 if (!realDb) {
383 device = SOSTestDeviceCreateWithTestDataSource(kCFAllocatorDefault, deviceID, prepop);
384 } else {
385 device = SOSTestDeviceCreateWithDbNamed(kCFAllocatorDefault, deviceID, deviceID);
386 }
387 SOSTestDeviceSetPeerIDs(device, deviceIDs, version, defaultViews);
388 CFDictionarySetValue(testDevices, deviceID, device);
389 CFReleaseSafe(device);
390 }
391 CFReleaseSafe(defaultViews);
392
393 CFDictionarySetValue(testDevices, CFSTR("@devicesIDs"), deviceIDs);
394 return testDevices;
395 }
396
397 void SOSTestDeviceListSync(const char *name, const char *test_directive, const char *test_reason, CFMutableDictionaryRef testDevices, bool(^pre)(SOSTestDeviceRef source, SOSTestDeviceRef dest), bool(^post)(SOSTestDeviceRef source, SOSTestDeviceRef dest, SOSMessageRef message)) {
398 CFArrayRef deviceIDs = (CFArrayRef)CFDictionaryGetValue(testDevices, CFSTR("@devicesIDs"));
399 const CFIndex edgeCount = CFArrayGetCount(deviceIDs) * (CFArrayGetCount(deviceIDs) - 1);
400 CFIndex deviceIX = 0;
401 __block CFIndex noMsgSentCount = 0;
402 __block CFIndex msgSentSinceLastChangeCount = 0;
403 __block bool done = false;
404 do {
405 CFStringRef sourceID = (CFStringRef)CFArrayGetValueAtIndex(deviceIDs, deviceIX++);
406 if (deviceIX >= CFArrayGetCount(deviceIDs))
407 deviceIX = 0;
408
409 SOSTestDeviceRef source = (SOSTestDeviceRef)CFDictionaryGetValue(testDevices, sourceID);
410 SOSTestDeviceForEachPeerID(source, ^(CFStringRef destID, bool *stop) {
411 SOSTestDeviceRef dest = (SOSTestDeviceRef)CFDictionaryGetValue(testDevices, destID);
412 if (dest) {
413 SOSPeerRef peer = SOSEngineCopyPeerWithID(SOSDataSourceGetSharedEngine(source->ds, NULL), destID, NULL);
414 SOSManifestRef preCreateManifest = SOSEngineCopyLocalPeerManifest(SOSDataSourceGetSharedEngine(dest->ds, NULL), peer, NULL);
415 if (pre && pre(source, dest))
416 msgSentSinceLastChangeCount = 0;
417
418 CFDataRef msg = SOSTestDeviceCreateMessage(source, destID);
419 SOSMessageRef message = NULL;
420 bool handled = false;
421 if (msg && CFDataGetLength(msg) > 0) {
422 if (!source->mute) {
423 msgSentSinceLastChangeCount++;
424 handled = SOSTestDeviceHandleMessage(dest, sourceID, msg);
425 if (handled) {
426 noMsgSentCount = 0;
427 }
428
429 CFErrorRef error = NULL;
430 message = SOSMessageCreateWithData(kCFAllocatorDefault, msg, &error);
431 ok(handled, "%s %@->%@ %@", name, sourceID, destID, message);
432 CFReleaseNull(error);
433 }
434 } else {
435 msgSentSinceLastChangeCount++;
436 SOSManifestRef sourceManifest = SOSEngineCopyLocalPeerManifest(SOSDataSourceGetSharedEngine(source->ds, NULL), peer, NULL);
437 pass("%s %@->%@ done L:%@", name, sourceID, destID, sourceManifest);
438 CFReleaseSafe(sourceManifest);
439 noMsgSentCount++;
440 }
441 CFReleaseSafe(msg);
442 if (post && post(source, dest, message))
443 msgSentSinceLastChangeCount = 0;
444
445 CFReleaseNull(message);
446 if (preCreateManifest) {
447 SOSManifestRef postHandleManifest = SOSEngineCopyLocalPeerManifest(SOSDataSourceGetSharedEngine(dest->ds, NULL), peer, NULL);
448 if (postHandleManifest && !CFEqual(preCreateManifest, postHandleManifest)) {
449 //CFStringPerformWithCString(destID, ^(const char *destStr) { diag("device %s changed", destStr); });
450 msgSentSinceLastChangeCount = 0;
451 }
452 CFReleaseSafe(postHandleManifest);
453 CFRelease(preCreateManifest);
454 }
455 CFReleaseSafe(peer);
456 }
457 if (noMsgSentCount >= edgeCount) {
458 *stop = done = true;
459 } else if (msgSentSinceLastChangeCount >= /* 3 */9 * edgeCount + 1) {
460 fail("%s %" PRIdCFIndex" peers never stopped syncing %" PRIdCFIndex" messages since last change", name, CFArrayGetCount(deviceIDs), msgSentSinceLastChangeCount);
461 *stop = done = true;
462 }
463 });
464 } while (!done);
465 }
466
467 bool SOSTestDeviceListInSync(const char *name, const char *test_directive, const char *test_reason, CFMutableDictionaryRef testDevices) {
468 bool inSync = true;
469 CFArrayRef deviceIDs = (CFArrayRef)CFDictionaryGetValue(testDevices, CFSTR("@devicesIDs"));
470 for (CFIndex len = CFArrayGetCount(deviceIDs), source_ix = 0; source_ix < len; ++source_ix) {
471 CFStringRef sourceID = (CFStringRef)CFArrayGetValueAtIndex(deviceIDs, source_ix);
472 SOSTestDeviceRef source = (SOSTestDeviceRef)CFDictionaryGetValue(testDevices, sourceID);
473 for (CFIndex dest_ix = source_ix + 1; dest_ix < len; ++dest_ix) {
474 CFStringRef destID = (CFStringRef)CFArrayGetValueAtIndex(deviceIDs, dest_ix);
475 SOSTestDeviceRef dest = (SOSTestDeviceRef)CFDictionaryGetValue(testDevices, destID);
476
477 SOSPeerRef sourcePeer = SOSEngineCopyPeerWithID(SOSDataSourceGetSharedEngine(source->ds, NULL), destID, NULL);
478 SOSManifestRef sourceManifest = SOSEngineCopyLocalPeerManifest(SOSDataSourceGetSharedEngine(source->ds, NULL), sourcePeer, NULL);
479
480 SOSPeerRef destPeer = SOSEngineCopyPeerWithID(SOSDataSourceGetSharedEngine(dest->ds, NULL), sourceID, NULL);
481 SOSManifestRef destManifest = SOSEngineCopyLocalPeerManifest(SOSDataSourceGetSharedEngine(dest->ds, NULL), destPeer, NULL);
482
483 if (!CFEqualSafe(sourceManifest, destManifest)) {
484 fail("%s %@ manifest %@ != %@ manifest %@", name, sourceID, sourceManifest, destID, destManifest);
485 inSync = false;
486 }
487 CFReleaseSafe(sourcePeer);
488 CFReleaseSafe(sourceManifest);
489 CFReleaseSafe(destPeer);
490 CFReleaseSafe(destManifest);
491 }
492 }
493 if (inSync)
494 pass("%s all peers in sync", name);
495 return inSync;
496 }
497
498 void SOSTestDeviceListTestSync(const char *name, const char *test_directive, const char *test_reason, CFIndex version, bool use_db,
499 bool(^pre)(SOSTestDeviceRef source, SOSTestDeviceRef dest),
500 bool(^post)(SOSTestDeviceRef source, SOSTestDeviceRef dest, SOSMessageRef message), ...) {
501 va_list args;
502 va_start(args, post);
503 // Optionally prefix each peer with name to make them more unique.
504 CFArrayRef deviceIDs = CFArrayCreateForVC(kCFAllocatorDefault, &kCFTypeArrayCallBacks, args);
505 CFMutableDictionaryRef testDevices = SOSTestDeviceListCreate(use_db, version, deviceIDs, NULL);
506 CFReleaseSafe(deviceIDs);
507 SOSTestDeviceListSync(name, test_directive, test_reason, testDevices, pre, post);
508 SOSTestDeviceListInSync(name, test_directive, test_reason, testDevices);
509 SOSTestDeviceDestroyEngine(testDevices);
510 CFReleaseSafe(testDevices);
511 }