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