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 <test/testmore.h> 30 31#include <SecureObjectSync/SOSEngine.h> 32#include <SecureObjectSync/SOSPeer.h> 33#include <Security/SecBase64.h> 34#include <Security/SecItem.h> 35#include <Security/SecItemPriv.h> 36#include <corecrypto/ccsha2.h> 37#include <securityd/SecItemServer.h> 38#include <securityd/SecItemDataSource.h> 39#include <utilities/SecCFWrappers.h> 40#include <utilities/SecFileLocations.h> 41#include <utilities/SecIOFormat.h> 42 43#include <stdint.h> 44#include <AssertMacros.h> 45 46CFStringRef SOSMessageCopyDigestHex(SOSMessageRef message) { 47 uint8_t digest[CCSHA1_OUTPUT_SIZE]; 48 // TODO: Pass in real sequenceNumber. 49 CFDataRef msgData = SOSMessageCreateData(message, 0, NULL); 50 if (!msgData) return NULL; 51 ccdigest(ccsha1_di(), CFDataGetLength(msgData), CFDataGetBytePtr(msgData), digest); 52 CFMutableStringRef hex = CFStringCreateMutable(0, 2 * sizeof(digest)); 53 for (unsigned int ix = 0; ix < sizeof(digest); ++ix) { 54 CFStringAppendFormat(hex, 0, CFSTR("%02X"), digest[ix]); 55 } 56 CFReleaseSafe(msgData); 57 return hex; 58} 59 60static void SOSTestDeviceDestroy(CFTypeRef cf) { 61 SOSTestDeviceRef td = (SOSTestDeviceRef)cf; 62 CFReleaseSafe(td->peers); 63 if (td->ds) 64 SOSDataSourceRelease(td->ds, NULL); 65 if (td->dsf) 66 td->dsf->release(td->dsf); 67 CFReleaseSafe(td->db); 68} 69 70CFStringRef SOSTestDeviceGetID(SOSTestDeviceRef td) { 71 CFStringRef engineID = NULL; 72 SOSEngineRef engine = SOSDataSourceGetSharedEngine(td->ds, NULL); 73 if (engine) 74 engineID = SOSEngineGetMyID(engine); 75 return engineID; 76} 77 78void SOSTestDeviceForEachPeerID(SOSTestDeviceRef td, void(^peerBlock)(CFStringRef peerID, bool *stop)) { 79 SOSPeerRef peer; 80 bool stop = false; 81 CFArrayForEachC(td->peers, peer) { 82 peerBlock(SOSPeerGetID(peer), &stop); 83 if (stop) 84 break; 85 } 86} 87 88static CFStringRef SOSTestDeviceCopyDescription(CFTypeRef cf) { 89 SOSTestDeviceRef td = (SOSTestDeviceRef)cf; 90 CFMutableStringRef result = CFStringCreateMutable(kCFAllocatorDefault, 0); 91 CFStringAppendFormat(result, NULL, CFSTR("<SOSTestDevice %@"), td->ds->engine); 92 SOSTestDeviceForEachPeerID(td, ^(CFStringRef peerID, bool *stop) { 93 SOSPeerRef peer = SOSPeerCreateWithEngine(td->ds->engine, peerID); 94 CFStringAppendFormat(result, NULL, CFSTR("\n%@"), peer); 95 CFReleaseSafe(peer); 96 }); 97 CFStringAppendFormat(result, NULL, CFSTR(">")); 98 return result; 99} 100 101CFGiblisFor(SOSTestDevice) 102 103static SOSTestDeviceRef SOSTestDeviceCreateInternal(CFAllocatorRef allocator, CFStringRef engineID) { 104 SOSTestDeviceRef td = CFTypeAllocate(SOSTestDevice, struct __OpaqueSOSTestDevice, allocator); 105 td->peers = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); 106 return td; 107} 108 109SOSTestDeviceRef SOSTestDeviceCreateWithDb(CFAllocatorRef allocator, CFStringRef engineID, SecDbRef db) { 110 setup("create device"); 111 SOSTestDeviceRef td = SOSTestDeviceCreateInternal(allocator, engineID); 112 CFRetainAssign(td->db, db); 113 td->dsf = SecItemDataSourceFactoryGetShared(td->db); 114 CFArrayRef ds_names = td->dsf->copy_names(td->dsf); 115 CFErrorRef error = NULL; 116 if (ds_names && CFArrayGetCount(ds_names) > 0) { 117 CFStringRef sname = CFArrayGetValueAtIndex(ds_names, 0); 118 ok (td->ds = td->dsf->create_datasource(td->dsf, sname, &error), "%@ create datasource \"%@\" [error: %@]", engineID, sname, error); 119 CFReleaseNull(error); 120 } 121 CFReleaseNull(ds_names); 122 assert(td->ds); // Shut up static analyzer and test generally run in debug mode anyway 123 if (td->ds) 124 SOSEngineCircleChanged(SOSDataSourceGetSharedEngine(td->ds, NULL), engineID, NULL, NULL); 125 return td; 126} 127 128SOSTestDeviceRef SOSTestDeviceCreateWithDbNamed(CFAllocatorRef allocator, CFStringRef engineID, CFStringRef dbName) { 129 CFURLRef url = SecCopyURLForFileInKeychainDirectory(dbName); 130 CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); 131 SecDbRef db = SecKeychainDbCreate(path); 132 SOSTestDeviceRef td = SOSTestDeviceCreateWithDb(allocator, engineID, db); 133 CFReleaseSafe(db); 134 CFReleaseSafe(path); 135 CFReleaseSafe(url); 136 return td; 137} 138 139SOSTestDeviceRef SOSTestDeviceCreateWithTestDataSource(CFAllocatorRef allocator, CFStringRef engineID) { 140 setup("create device"); 141 SOSTestDeviceRef td = SOSTestDeviceCreateInternal(allocator, engineID); 142 143 td->ds = SOSTestDataSourceCreate(); 144 CFErrorRef error = NULL; 145 ok(td->ds->engine = SOSEngineCreate(td->ds, &error), "create engine: %@", error); 146 SOSEngineCircleChanged(td->ds->engine, engineID, NULL, NULL); 147 CFReleaseNull(error); 148 return td; 149} 150 151SOSTestDeviceRef SOSTestDeviceSetPeerIDs(SOSTestDeviceRef td, CFArrayRef peerIDs, CFIndex version) { 152 setup("create device"); 153 CFStringRef engineID = SOSTestDeviceGetID(td); 154 CFStringRef peerID; 155 CFMutableArrayRef trustedPeersIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); 156 CFErrorRef error = NULL; 157 CFArrayForEachC(peerIDs, peerID) { 158 if (isString(peerID) && !CFEqualSafe(peerID, engineID)) { 159 SOSPeerRef peer; 160 ok(peer = SOSPeerCreateSimple(td->ds->engine, peerID, version, &error), "create peer: %@", error); 161 CFReleaseNull(error); 162 CFArrayAppendValue(td->peers, peer); 163 CFArrayAppendValue(trustedPeersIDs, peerID); 164 } 165 } 166 167 SOSEngineCircleChanged(td->ds->engine, engineID, trustedPeersIDs, NULL); 168 CFArrayForEachC(trustedPeersIDs, peerID) { 169 ok(SOSEnginePeerDidConnect(td->ds->engine, peerID, &error), "tell %@ %@ connected: %@", engineID, peerID, error); 170 CFReleaseNull(error); 171 } 172 CFReleaseSafe(trustedPeersIDs); 173 return td; 174} 175 176CFDataRef SOSTestDeviceCreateMessage(SOSTestDeviceRef td, CFStringRef peerID) { 177 setup("create message"); 178 CFErrorRef error = NULL; 179 SOSEnginePeerMessageSentBlock sent = NULL; 180 CFDataRef msgData; 181 ok(msgData = SOSEngineCreateMessageToSyncToPeer(td->ds->engine, peerID, &sent, &error), 182 "create message to %@: %@", peerID, error); 183 if (sent) 184 sent(true); 185 186 return msgData; 187} 188 189#if 0 190CFDictionaryRef SOSTestDeviceCreateMessages(SOSTestDeviceRef td) { 191 CFTypeRef peer = NULL; 192 CFMutableDictionaryRef messages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); 193 CFArrayForEachC(td->peers, peer) { 194 CFStringRef peerID = SOSPeerGetID((SOSPeerRef)peer); 195 CFDataRef msg = SOSTestDeviceCreateMessage(td, peerID); 196 if (msg) { 197 CFDictionaryAddValue(messages, peerID, msg); 198 CFRelease(msg); 199 } 200 } 201 return messages; 202} 203#endif 204 205bool SOSTestDeviceHandleMessage(SOSTestDeviceRef td, CFStringRef peerID, CFDataRef msgData) { 206 setup("handle message"); 207 if (!msgData) return false; 208 CFErrorRef error = NULL; 209 bool handled; 210 SOSMessageRef message; 211 212 ok(message = SOSMessageCreateWithData(kCFAllocatorDefault, msgData, &error), "decode message %@: %@", msgData, error); 213 CFReleaseNull(error); 214 pass("handeling %@->%@ %@", peerID, SOSEngineGetMyID(SOSDataSourceGetSharedEngine(td->ds, &error)), message); 215 ok(handled = SOSEngineHandleMessage(SOSDataSourceGetSharedEngine(td->ds, &error), peerID, msgData, &error), 216 "handled from %@ %@: %@", peerID, message, error); 217 CFReleaseNull(error); 218 219 CFReleaseNull(message); 220 return handled; 221} 222 223void SOSTestDeviceAddGenericItem(SOSTestDeviceRef td, CFStringRef account, CFStringRef server) { 224 __block CFErrorRef error = NULL; 225 if (!SOSDataSourceWithAPI(td->ds, true, &error, ^(SOSTransactionRef txn, bool *commit) { 226 SOSObjectRef object = SOSDataSourceCreateGenericItem(td->ds, account, server); 227 ok(SOSDataSourceMergeObject(td->ds, txn, object, NULL, &error), "%@ added API object %@", SOSTestDeviceGetID(td), error ? (CFTypeRef)error : (CFTypeRef)CFSTR("ok")); 228 CFReleaseSafe(object); 229 CFReleaseNull(error); 230 })) 231 fail("ds transaction %@", error); 232 CFReleaseNull(error); 233} 234 235void SOSTestDeviceAddRemoteGenericItem(SOSTestDeviceRef td, CFStringRef account, CFStringRef server) { 236 __block CFErrorRef error = NULL; 237 if (!SOSDataSourceWithAPI(td->ds, false, &error, ^(SOSTransactionRef txn, bool *commit) { 238 SOSObjectRef object = SOSDataSourceCreateGenericItem(td->ds, account, server); 239 ok(SOSDataSourceMergeObject(td->ds, txn, object, NULL, &error), "%@ added remote object %@", SOSTestDeviceGetID(td), error ? (CFTypeRef)error : (CFTypeRef)CFSTR("ok")); 240 CFReleaseSafe(object); 241 CFReleaseNull(error); 242 })) 243 fail("ds transaction %@", error); 244 CFReleaseNull(error); 245} 246 247bool SOSTestDeviceAddGenericItems(SOSTestDeviceRef td, CFIndex count, CFStringRef account, CFStringRef server) { 248 __block bool didAdd = false; 249 __block CFErrorRef error = NULL; 250 if (!SOSDataSourceWithAPI(td->ds, true, &error, ^(SOSTransactionRef txn, bool *commit) { 251 bool success = true; 252 CFIndex ix = 0; 253 for (; success && ix < count; ++ix) { 254 CFStringRef accountStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%" PRIdCFIndex), account, ix); 255 SOSObjectRef object = SOSDataSourceCreateGenericItem(td->ds, accountStr, server); 256 success = SOSDataSourceMergeObject(td->ds, txn, object, NULL, &error); 257 CFReleaseSafe(object); 258 } 259 ok(success, "%@ added %" PRIdCFIndex " API objects %@", SOSTestDeviceGetID(td), ix, error ? (CFTypeRef)error : (CFTypeRef)CFSTR("ok")); 260 didAdd = success && ix == count; 261 CFReleaseNull(error); 262 })) 263 fail("ds transaction %@", error); 264 CFReleaseNull(error); 265 return didAdd; 266} 267 268CFMutableDictionaryRef SOSTestDeviceListCreate(bool realDb, CFIndex version, CFArrayRef deviceIDs) { 269 CFMutableDictionaryRef testDevices = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault); 270 CFStringRef deviceID; 271 CFArrayForEachC(deviceIDs, deviceID) { 272 SOSTestDeviceRef device; 273 if (!realDb) 274 device = SOSTestDeviceCreateWithTestDataSource(kCFAllocatorDefault, deviceID); 275 else 276 device = SOSTestDeviceCreateWithDbNamed(kCFAllocatorDefault, deviceID, deviceID); 277 SOSTestDeviceSetPeerIDs(device, deviceIDs, version); 278 CFDictionarySetValue(testDevices, deviceID, device); 279 CFReleaseSafe(device); 280 } 281 CFDictionarySetValue(testDevices, CFSTR("@devicesIDs"), deviceIDs); 282 return testDevices; 283} 284 285void 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)) { 286 CFArrayRef deviceIDs = (CFArrayRef)CFDictionaryGetValue(testDevices, CFSTR("@devicesIDs")); 287 const CFIndex edgeCount = CFArrayGetCount(deviceIDs) * (CFArrayGetCount(deviceIDs) - 1); 288 CFIndex deviceIX = 0; 289 __block CFIndex noMsgSentCount = 0; 290 __block CFIndex msgSentSinceLastChangeCount = 0; 291 __block bool done = false; 292 do { 293 CFStringRef sourceID = (CFStringRef)CFArrayGetValueAtIndex(deviceIDs, deviceIX++); 294 if (deviceIX >= CFArrayGetCount(deviceIDs)) 295 deviceIX = 0; 296 297 SOSTestDeviceRef source = (SOSTestDeviceRef)CFDictionaryGetValue(testDevices, sourceID); 298 SOSTestDeviceForEachPeerID(source, ^(CFStringRef destID, bool *stop) { 299 SOSTestDeviceRef dest = (SOSTestDeviceRef)CFDictionaryGetValue(testDevices, destID); 300 if (dest) { 301 if (pre) { 302 if (pre(source, dest)) 303 msgSentSinceLastChangeCount = 0; 304 } 305 CFDataRef msg = SOSTestDeviceCreateMessage(source, destID); 306 SOSMessageRef message = NULL; 307 bool handled = false; 308 msgSentSinceLastChangeCount++; 309 if (msg && CFDataGetLength(msg) > 0) { 310 handled = SOSTestDeviceHandleMessage(dest, sourceID, msg); 311 if (handled) { 312 noMsgSentCount = 0; 313 } 314 315 CFErrorRef error = NULL; 316 message = SOSMessageCreateWithData(kCFAllocatorDefault, msg, &error); 317 ok(handled, "%s %@->%@ %@", name, sourceID, destID, message); 318 CFReleaseNull(error); 319 } else { 320 SOSManifestRef sourceManifest = SOSEngineCopyManifest(SOSDataSourceGetSharedEngine(source->ds, NULL), NULL); 321 pass("%s %@->%@ done L:%@", name, sourceID, destID, sourceManifest); 322 CFReleaseSafe(sourceManifest); 323 noMsgSentCount++; 324 //msgSentSinceLastChangeCount = 0; 325 } 326 CFReleaseSafe(msg); 327 if (post) { 328 if (post(source, dest, message)) 329 msgSentSinceLastChangeCount = 0; 330 } 331 CFReleaseNull(message); 332 } 333 if (noMsgSentCount >= edgeCount) { 334 *stop = done = true; 335 } else if (msgSentSinceLastChangeCount >= 9 * edgeCount + 1) { 336 fail("%s %" PRIdCFIndex" peers never stopped syncing %" PRIdCFIndex" messages since last change", name, CFArrayGetCount(deviceIDs), msgSentSinceLastChangeCount); 337 *stop = done = true; 338 } 339 }); 340 } while (!done); 341} 342 343bool SOSTestDeviceListInSync(const char *name, const char *test_directive, const char *test_reason, CFMutableDictionaryRef testDevices) { 344 bool inSync = true; 345 CFArrayRef deviceIDs = (CFArrayRef)CFDictionaryGetValue(testDevices, CFSTR("@devicesIDs")); 346 CFStringRef sourceID = NULL; 347 SOSManifestRef sourceManifest = NULL; 348 CFStringRef currentID; 349 CFArrayForEachC(deviceIDs, currentID) { 350 SOSTestDeviceRef source = (SOSTestDeviceRef)CFDictionaryGetValue(testDevices, currentID); 351 SOSManifestRef manifest = SOSEngineCopyManifest(SOSDataSourceGetSharedEngine(source->ds, NULL), NULL); 352 if (!sourceManifest) { 353 sourceManifest = CFRetainSafe(manifest); 354 sourceID = currentID; 355 } else if (!CFEqual(manifest, sourceManifest)) { 356 fail("%s %@ manifest %@ != %@ manifest %@", name, currentID, manifest, sourceID, sourceManifest); 357 inSync = false; 358 } 359 CFReleaseSafe(manifest); 360 } 361 CFReleaseSafe(sourceManifest); 362 if (inSync) 363 pass("%s all peers in sync", name); 364 return inSync; 365} 366 367void SOSTestDeviceListTestSync(const char *name, const char *test_directive, const char *test_reason, CFIndex version, bool use_db, 368 bool(^pre)(SOSTestDeviceRef source, SOSTestDeviceRef dest), 369 bool(^post)(SOSTestDeviceRef source, SOSTestDeviceRef dest, SOSMessageRef message), ...) { 370 va_list args; 371 va_start(args, post); 372 // Optionally prefix each peer with name to make them more unique. 373 CFArrayRef deviceIDs = CFArrayCreateForVC(kCFAllocatorDefault, &kCFTypeArrayCallBacks, args); 374 CFMutableDictionaryRef testDevices = SOSTestDeviceListCreate(use_db, version, deviceIDs); 375 CFReleaseSafe(deviceIDs); 376 SOSTestDeviceListSync(name, test_directive, test_reason, testDevices, pre, post); 377 SOSTestDeviceListInSync(name, test_directive, test_reason, testDevices); 378 CFReleaseSafe(testDevices); 379} 380