2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 #ifndef SEC_SOSAccountTesting_h
26 #define SEC_SOSAccountTesting_h
28 #include <CoreFoundation/CoreFoundation.h>
29 #include "keychain/SecureObjectSync/SOSAccountPriv.h"
30 #include "keychain/SecureObjectSync/SOSTransport.h"
31 #include "keychain/SecureObjectSync/SOSPeerInfoCollections.h"
32 #include <Security/SecureObjectSync/SOSPeerInfo.h>
33 #include "keychain/SecureObjectSync/SOSPeerInfoV2.h"
34 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
35 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
36 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
37 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
39 #include "SOSTestDataSource.h"
40 #include "SOSRegressionUtilities.h"
42 #include "SOSTransportTestTransports.h"
44 #include <utilities/SecCFWrappers.h>
46 // Implicit transaction helpers
49 static inline bool SOSAccountResetToOffering_wTxn(SOSAccount
* acct
, CFErrorRef
* error
)
51 __block
bool result
= false;
52 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
53 SecKeyRef user_key
= SOSAccountGetPrivateCredential(txn
.account
, error
);
56 result
= [acct
.trust resetToOffering
:txn key
:user_key err
:error
];
61 static inline bool SOSAccountJoinCirclesAfterRestore_wTxn(SOSAccount
* acct
, CFErrorRef
* error
)
63 __block
bool result
= false;
64 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
65 result
= SOSAccountJoinCirclesAfterRestore(txn
, error
);
70 static inline bool SOSAccountJoinCircles_wTxn(SOSAccount
* acct
, CFErrorRef
* error
)
72 __block
bool result
= false;
73 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
74 result
= SOSAccountJoinCircles(txn
, error
);
79 static inline bool SOSAccountCheckHasBeenInSync_wTxn(SOSAccount
* account
)
81 return SOSAccountHasCompletedInitialSync(account
);
84 static inline void SOSAccountPeerGotInSync_wTxn(SOSAccount
* acct
, SOSPeerInfoRef peer
)
86 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
87 CFMutableSetRef views
= SOSPeerInfoCopyEnabledViews(peer
);
88 SOSAccountPeerGotInSync(txn
, SOSPeerInfoGetPeerID(peer
), views
);
93 static inline bool SOSAccountSetBackupPublicKey_wTxn(SOSAccount
* acct
, CFDataRef backupKey
, CFErrorRef
* error
)
95 __block
bool result
= false;
96 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
97 result
= SOSAccountSetBackupPublicKey(txn
, backupKey
, error
);
102 static inline bool SOSAccountRemoveBackupPublickey_wTxn(SOSAccount
* acct
, CFErrorRef
* error
)
104 __block
bool result
= false;
105 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
106 result
= SOSAccountRemoveBackupPublickey(txn
, error
);
111 static inline SOSViewResultCode
SOSAccountUpdateView_wTxn(SOSAccount
* acct
, CFStringRef viewname
, SOSViewActionCode actionCode
, CFErrorRef
*error
) {
112 __block SOSViewResultCode result
= false;
113 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
114 result
= [acct
.trust updateView
:acct name
:viewname code
:actionCode err
:error
];
120 // Account comparison
123 #define kAccountsAgreeTestMin 9
124 #define kAccountsAgreeTestPerPeer 1
125 #define accountsAgree(x) (kAccountsAgreeTestMin + kAccountsAgreeTestPerPeer * (x))
127 static void SOSAccountResetToTest(SOSAccount
* a
, CFStringRef accountName
) {
128 SOSUnregisterTransportKeyParameter(a
.key_transport
);
129 SOSUnregisterTransportCircle((SOSCircleStorageTransport
*)a
.circle_transport
);
130 SOSUnregisterTransportMessage((SOSMessage
*)a
.kvs_message_transport
);
133 CFArrayRemoveAllValue(key_transports
, (__bridge CFTypeRef
)(a
.key_transport
));
134 if(message_transports
){
135 CFArrayRemoveAllValue(message_transports
, (__bridge CFTypeRef
)a
.kvs_message_transport
);
137 if(circle_transports
)
138 CFArrayRemoveAllValue(circle_transports
, (__bridge CFTypeRef
)a
.circle_transport
);
140 a
.circle_transport
= nil
;
141 a
.key_transport
= nil
;
142 a
.kvs_message_transport
= nil
;
144 SOSAccountEnsureFactoryCirclesTest(a
, accountName
);
148 static SOSAccount
* SOSAccountCreateBasicTest(CFAllocatorRef allocator
,
149 CFStringRef accountName
,
150 CFDictionaryRef gestalt
,
151 SOSDataSourceFactoryRef factory
) {
153 a
= SOSAccountCreate(kCFAllocatorDefault
, gestalt
, factory
);
158 static SOSAccount
* SOSAccountCreateTest(CFAllocatorRef allocator
,
159 CFStringRef accountName
,
160 CFDictionaryRef gestalt
,
161 SOSDataSourceFactoryRef factory
) {
162 SOSAccount
* a
= SOSAccountCreateBasicTest(allocator
, accountName
, gestalt
, factory
);
164 SOSAccountResetToTest(a
, accountName
);
166 SOSAccountInflateTestTransportsForCircle(a
, SOSCircleGetName([a
.trust getCircle
:NULL
]), accountName
, NULL
);
170 static SOSAccount
* SOSAccountCreateTestFromData(CFAllocatorRef allocator
,
172 CFStringRef accountName
,
173 SOSDataSourceFactoryRef factory
) {
174 SOSAccount
* a
= [SOSAccount accountFromData
:(__bridge NSData
*) data
178 CFDictionaryRef gestalt
= SOSCreatePeerGestaltFromName(accountName
);
179 a
= SOSAccountCreate(allocator
, gestalt
, factory
);
180 CFReleaseNull(gestalt
);
183 SOSAccountResetToTest(a
, accountName
);
185 SOSAccountInflateTestTransportsForCircle(a
, SOSCircleGetName([a
.trust getCircle
:NULL
]), accountName
, NULL
);
191 static inline bool SOSAccountAssertUserCredentialsAndUpdate(SOSAccount
* account
,
192 CFStringRef user_account
, CFDataRef user_password
,
195 bool success
= false;
196 success
= SOSAccountAssertUserCredentials(account
, user_account
, user_password
, error
);
197 require_quiet(success
, done
);
199 success
= SOSAccountGenerationSignatureUpdate(account
, error
);
207 static void unretired_peers_is_subset(const char* label
, CFArrayRef peers
, CFSetRef allowed_peers
)
209 CFArrayForEach(peers
, ^(const void *value
) {
210 SOSPeerInfoRef pi
= (SOSPeerInfoRef
) value
;
212 CFErrorRef leftError
= NULL
;
213 CFErrorRef rightError
= NULL
;
215 ok(SOSPeerInfoIsRetirementTicket(pi
) || SOSPeerInfoIsCloudIdentity(pi
) || CFSetContainsValue(allowed_peers
, pi
), "Peer is allowed (%s) Peer: %@, Allowed %@", label
, pi
, allowed_peers
);
217 CFReleaseNull(leftError
);
218 CFReleaseNull(rightError
);
222 static void accounts_agree_internal(char *label
, SOSAccount
* left
, SOSAccount
* right
, bool check_peers
)
224 CFErrorRef error
= NULL
;
226 CFArrayRef leftPeers
= SOSAccountCopyActivePeers(left
, &error
);
227 ok(leftPeers
, "Left peers (%@) - %s", error
, label
);
228 CFReleaseNull(error
);
230 CFArrayRef rightPeers
= SOSAccountCopyActivePeers(right
, &error
);
231 ok(rightPeers
, "Right peers (%@) - %s", error
, label
);
232 CFReleaseNull(error
);
234 ok(CFEqual(leftPeers
, rightPeers
), "Matching peers (%s) Left: %@, Right: %@", label
, leftPeers
, rightPeers
);
237 CFMutableSetRef allowed_identities
= CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault
);
239 SOSFullPeerInfoRef leftFullPeer
= [left
.trust CopyAccountIdentityPeerInfo
];
242 CFSetAddValue(allowed_identities
, SOSFullPeerInfoGetPeerInfo(leftFullPeer
));
243 CFReleaseNull(leftFullPeer
);
245 SOSFullPeerInfoRef rightFullPeer
= [right
.trust CopyAccountIdentityPeerInfo
];
248 CFSetAddValue(allowed_identities
, SOSFullPeerInfoGetPeerInfo(rightFullPeer
));
249 CFReleaseNull(rightFullPeer
);
251 unretired_peers_is_subset(label
, leftPeers
, allowed_identities
);
253 CFReleaseNull(allowed_identities
);
256 CFReleaseNull(leftPeers
);
257 CFReleaseNull(rightPeers
);
260 CFArrayRef leftConcurringPeers
= SOSAccountCopyConcurringPeers(left
, &error
);
261 ok(leftConcurringPeers
, "Left peers (%@) - %s", error
, label
);
263 CFArrayRef rightConcurringPeers
= SOSAccountCopyConcurringPeers(right
, &error
);
264 ok(rightConcurringPeers
, "Right peers (%@) - %s", error
, label
);
266 ok(CFEqual(leftConcurringPeers
, rightConcurringPeers
), "Matching concurring peers Left: %@, Right: %@", leftConcurringPeers
, rightConcurringPeers
);
268 CFReleaseNull(leftConcurringPeers
);
269 CFReleaseNull(rightConcurringPeers
);
272 CFArrayRef leftApplicants
= SOSAccountCopyApplicants(left
, &error
);
273 ok(leftApplicants
, "Left Applicants (%@) - %s", error
, label
);
275 CFArrayRef rightApplicants
= SOSAccountCopyApplicants(right
, &error
);
276 ok(rightApplicants
, "Left Applicants (%@) - %s", error
, label
);
278 ok(CFEqual(leftApplicants
, rightApplicants
), "Matching applicants (%s) Left: %@, Right: %@", label
, leftApplicants
, rightApplicants
);
280 CFReleaseNull(leftApplicants
);
281 CFReleaseNull(rightApplicants
);
285 static inline void accounts_agree(char *label
, SOSAccount
* left
, SOSAccount
* right
)
287 accounts_agree_internal(label
, left
, right
, true);
295 static inline CFStringRef
CFArrayCopyCompactDescription(CFArrayRef array
) {
297 return CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("<Not an array! %@>"), array
);
299 CFMutableStringRef result
= CFStringCreateMutableCopy(kCFAllocatorDefault
, 0, CFSTR("["));
301 __block CFStringRef separator
= CFSTR("");
302 CFArrayForEach(array
, ^(const void *value
) {
303 CFStringAppendFormat(result
, NULL
, CFSTR("%@%@"), separator
, value
);
304 separator
= CFSTR(",");
307 CFStringAppend(result
, CFSTR("]"));
309 CFReleaseSafe(separator
);
314 static inline CFStringRef
SOSAccountCopyName(SOSAccount
* account
) {
315 SOSPeerInfoRef pi
= account
.peerInfo
;
317 return pi
? CFStringCreateCopy(kCFAllocatorDefault
, SOSPeerInfoGetPeerName(pi
)) : CFStringCreateWithFormat(kCFAllocatorDefault
, 0, CFSTR("%@"), account
);
320 static inline CFStringRef
CopyChangesDescription(CFDictionaryRef changes
) {
322 CFStringRef pendingChanges
= CFDictionaryCopyCompactDescription((CFDictionaryRef
) CFDictionaryGetValue(changes
, kCFNull
));
324 CFMutableStringRef peerTable
= CFStringCreateMutableCopy(kCFAllocatorDefault
, 0, CFSTR("["));
326 __block CFStringRef separator
= CFSTR("");
328 CFDictionaryForEach(changes
, ^(const void *key
, const void *value
) {
329 if (CFGetTypeID(key
) == SOSAccountGetTypeID()) {
330 CFStringRef accountName
= SOSAccountCopyName((__bridge SOSAccount
*)key
);
331 CFStringRef arrayDescription
= CFArrayCopyCompactDescription(value
);
333 CFStringAppendFormat(peerTable
, NULL
, CFSTR("%@%@:%@"), separator
, accountName
, arrayDescription
);
334 separator
= CFSTR(", ");
336 CFReleaseSafe(accountName
);
337 CFReleaseSafe(arrayDescription
);
341 CFStringAppend(peerTable
, CFSTR("]"));
343 CFStringRef result
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("<TestChanges %@ %@>"), pendingChanges
, peerTable
);
344 CFReleaseNull(pendingChanges
);
345 CFReleaseNull(peerTable
);
350 static void CFDictionaryOverlayDictionary(CFMutableDictionaryRef target
, CFMutableDictionaryRef overlay
) {
351 CFMutableSetRef keysToRemove
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
);
353 CFDictionaryForEach(overlay
, ^(const void *key
, const void *value
) {
354 const void *current_value
= CFDictionaryGetValue(target
, key
);
355 if (CFEqualSafe(current_value
, value
) || (isNull(value
) && current_value
== NULL
)) {
356 CFSetAddValue(keysToRemove
, key
);
358 CFDictionarySetValue(target
, key
, value
);
362 CFSetForEach(keysToRemove
, ^(const void *value
) {
363 CFDictionaryRemoveValue(overlay
, value
);
366 CFReleaseNull(keysToRemove
);
369 static void CFArrayAppendKeys(CFMutableArrayRef keys
, CFDictionaryRef newKeysToAdd
) {
370 CFDictionaryForEach(newKeysToAdd
, ^(const void *key
, const void *value
) {
371 CFArrayAppendValue(keys
, key
);
375 static bool AddNewChanges(CFMutableDictionaryRef changesRecord
, CFMutableDictionaryRef newKeysAndValues
, SOSAccount
* sender
)
377 __block
bool changes_added
= false;
378 CFMutableDictionaryRef emptyDictionary
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
379 CFDictionaryAddValue(changesRecord
, kCFNull
, emptyDictionary
);
380 CFReleaseNull(emptyDictionary
);
382 CFDictionaryOverlayDictionary((CFMutableDictionaryRef
) CFDictionaryGetValue(changesRecord
, kCFNull
), newKeysAndValues
);
384 CFDictionaryForEach(changesRecord
, ^(const void *key
, const void *value
) {
385 if (isArray(value
) && (sender
== NULL
|| sender
!= key
)) {
386 CFArrayAppendKeys((CFMutableArrayRef
) value
, newKeysAndValues
);
387 if (CFDictionaryGetCount(newKeysAndValues
))
388 changes_added
= true;
393 secnotice("changes", "Changes from %@: %@", sender
, newKeysAndValues
);
395 CFDictionaryRemoveAllValues(newKeysAndValues
);
397 return changes_added
;
400 static bool FillAllChanges(CFMutableDictionaryRef changes
) {
401 __block
bool changed
= false;
403 CFMutableSetRef changedAccounts
= CFSetCreateMutable(kCFAllocatorDefault
, 0, NULL
);
405 CFArrayForEach(key_transports
, ^(const void *value
) {
406 CKKeyParameterTest
* tpt
= (__bridge CKKeyParameterTest
*) value
;
407 if (AddNewChanges(changes
, SOSTransportKeyParameterTestGetChanges(tpt
), SOSTransportKeyParameterTestGetAccount(tpt
))) {
409 CFSetAddValue(changedAccounts
, (__bridge CFTypeRef
)(SOSTransportKeyParameterTestGetAccount(tpt
)));
411 SOSTransportKeyParameterTestClearChanges(tpt
);
413 CFArrayForEach(circle_transports
, ^(const void *value
) {
414 SOSCircleStorageTransportTest
*tpt
= (__bridge SOSCircleStorageTransportTest
*) value
;
415 if (AddNewChanges(changes
, [tpt SOSTransportCircleTestGetChanges
], [tpt getAccount
])) {
417 CFSetAddValue(changedAccounts
, (__bridge CFTypeRef
)SOSTransportCircleTestGetAccount(tpt
));
419 SOSTransportCircleTestClearChanges(tpt
);
421 CFArrayForEach(message_transports
, ^(const void *value
) {
422 if([(__bridge SOSMessage
*)value SOSTransportMessageGetTransportType
] == kKVSTest
){
423 SOSMessageKVSTest
* tpt
= (__bridge SOSMessageKVSTest
*) value
;
424 CFDictionaryRemoveValue(SOSTransportMessageKVSTestGetChanges(tpt
), kCFNull
);
425 if (AddNewChanges(changes
, SOSTransportMessageKVSTestGetChanges(tpt
), SOSTransportMessageKVSTestGetAccount(tpt
))) {
427 CFSetAddValue(changedAccounts
, (__bridge CFTypeRef
)(SOSTransportMessageKVSTestGetAccount(tpt
)));
429 SOSTransportMessageTestClearChanges(tpt
);
433 secnotice("process-changes", "Accounts with change (%@): %@", changed
? CFSTR("YES") : CFSTR("NO"), changedAccounts
);
435 CFReleaseNull(changedAccounts
);
440 static void FillChanges(CFMutableDictionaryRef changes
, SOSAccount
* forAccount
)
442 CFArrayForEach(key_transports
, ^(const void *value
) {
443 CKKeyParameterTest
* tpt
= (__bridge CKKeyParameterTest
*) value
;
444 if(CFEqualSafe((__bridge CFTypeRef
)(forAccount
), (__bridge CFTypeRef
)(SOSTransportKeyParameterTestGetAccount(tpt
)))){
445 AddNewChanges(changes
, SOSTransportKeyParameterTestGetChanges(tpt
), SOSTransportKeyParameterTestGetAccount(tpt
));
446 SOSTransportKeyParameterTestClearChanges(tpt
);
449 CFArrayForEach(circle_transports
, ^(const void *value
) {
450 SOSCircleStorageTransportTest
* tpt
= (__bridge SOSCircleStorageTransportTest
*) value
;
451 if([forAccount isEqual
: SOSTransportCircleTestGetAccount(tpt
)]){
452 AddNewChanges(changes
, [tpt SOSTransportCircleTestGetChanges
], SOSTransportCircleTestGetAccount(tpt
));
453 SOSTransportCircleTestClearChanges(tpt
);
456 CFArrayForEach(message_transports
, ^(const void *value
) {
457 if([(__bridge SOSMessage
*)value SOSTransportMessageGetTransportType
] == kKVSTest
){
458 SOSMessageKVSTest
* tpt
= (__bridge SOSMessageKVSTest
*) value
;
459 if(CFEqualSafe((__bridge CFTypeRef
)(forAccount
), (__bridge CFTypeRef
)(SOSTransportMessageKVSTestGetAccount(tpt
)))){
460 CFDictionaryRemoveValue(SOSTransportMessageKVSTestGetChanges(tpt
), kCFNull
);
461 AddNewChanges(changes
, SOSTransportMessageKVSTestGetChanges(tpt
), SOSTransportMessageKVSTestGetAccount(tpt
));
462 SOSTransportMessageTestClearChanges(tpt
);
469 static inline void FillChangesMulti(CFMutableDictionaryRef changes
, SOSAccount
* account
, ...)
471 SOSAccount
* next_account
= account
;
473 va_start(argp
, account
);
474 while(next_account
!= NULL
) {
475 FillChanges(changes
, next_account
);
476 next_account
= va_arg(argp
, SOSAccount
*);
480 static inline CFMutableArrayRef
CFDictionaryCopyKeys(CFDictionaryRef dictionary
)
482 CFMutableArrayRef result
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
484 CFArrayAppendKeys(result
, dictionary
);
489 #define kFeedChangesToTestCount 1
490 static inline void FeedChangesTo(CFMutableDictionaryRef changes
, SOSAccount
* acct
)
492 CFDictionaryRef full_list
= (CFDictionaryRef
) CFDictionaryGetValue(changes
, kCFNull
);
494 if (!isDictionary(full_list
))
495 return; // Nothing recorded to send!
497 CFMutableArrayRef account_pending_keys
= (CFMutableArrayRef
)CFDictionaryGetValue(changes
, (__bridge CFTypeRef
)(acct
));
499 if (!isArray(account_pending_keys
)) {
500 account_pending_keys
= CFDictionaryCopyKeys(full_list
);
501 CFDictionaryAddValue(changes
, (__bridge CFTypeRef
)(acct
), account_pending_keys
);
502 CFReleaseSafe(account_pending_keys
); // The dictionary keeps it, we don't retain it here.
505 CFMutableDictionaryRef account_pending_messages
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
506 CFArrayForEach(account_pending_keys
, ^(const void *value
) {
507 CFDictionaryAddValue(account_pending_messages
, value
, CFDictionaryGetValue(full_list
, value
));
510 secnotice("changes", "Changes for %@:", SOSTransportKeyParameterTestGetName((CKKeyParameterTest
*) acct
.key_transport
));
512 CFDictionaryForEach(account_pending_messages
, ^(const void *key
, const void *value
) {
513 secnotice("changes", " %@", key
);
516 if(CFDictionaryGetCount(account_pending_messages
) == 0) {
517 CFReleaseNull(account_pending_messages
);
521 __block CFMutableArrayRef handled
= NULL
;
522 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
523 __block CFErrorRef error
= NULL
;
524 ok(handled
= SOSTransportDispatchMessages(txn
, account_pending_messages
, &error
), "SOSTransportHandleMessages failed (%@)", error
);
525 CFReleaseNull(error
);
528 if (isArray(handled
)) {
529 CFArrayForEach(handled
, ^(const void *value
) {
530 CFArrayRemoveAllValue(account_pending_keys
, value
);
533 CFReleaseNull(account_pending_messages
);
534 CFReleaseNull(handled
);
537 #define kFeedChangesToMultieTestCountPer 1
539 static inline void FeedChangesToMultiV(CFMutableDictionaryRef changes
, va_list argp
)
541 SOSAccount
* account
= NULL
;
542 while((account
= va_arg(argp
, SOSAccount
*)) != NULL
) {
543 FeedChangesTo(changes
, account
);
547 static inline void FeedChangesToMulti(CFMutableDictionaryRef changes
, ...)
550 va_start(argp
, changes
);
552 FeedChangesToMultiV(changes
, argp
);
557 static inline void InjectChangeToMulti(CFMutableDictionaryRef changes
,
558 CFStringRef changeKey
, CFTypeRef changeValue
, ...)
560 CFMutableDictionaryRef changes_to_send
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
561 changeKey
, changeValue
,
563 AddNewChanges(changes
, changes_to_send
, NULL
);
564 CFReleaseNull(changes_to_send
);
567 va_start(argp
, changeValue
);
568 FeedChangesToMultiV(changes
, argp
);
572 static inline bool ProcessChangesOnceV(CFMutableDictionaryRef changes
, va_list argp
)
574 bool result
= FillAllChanges(changes
);
576 FeedChangesToMultiV(changes
, argp
);
582 static inline bool ProcessChangesOnce(CFMutableDictionaryRef changes
, ...)
585 va_start(argp
, changes
);
587 bool result
= ProcessChangesOnceV(changes
, argp
);
594 static inline int ProcessChangesUntilNoChange(CFMutableDictionaryRef changes
, ...)
597 va_start(argp
, changes
);
600 bool new_data
= false;
603 va_copy(argp_copy
, argp
);
605 new_data
= ProcessChangesOnceV(changes
, argp_copy
);
619 // MARK: Account creation
622 static CFStringRef
modelFromType(SOSPeerInfoDeviceClass cls
) {
624 case SOSPeerInfo_macOS
: return CFSTR("Mac Pro");
625 case SOSPeerInfo_iOS
: return CFSTR("iPhone");
626 case SOSPeerInfo_iCloud
: return CFSTR("iCloud");
627 case SOSPeerInfo_watchOS
: return CFSTR("needWatchOSDeviceName");
628 case SOSPeerInfo_tvOS
: return CFSTR("needTVOSDeviceName");
629 default: return CFSTR("GENERICOSTHING");
633 static inline SOSAccount
* CreateAccountForLocalChangesWithStartingAttributes(CFStringRef name
, CFStringRef data_source_name
, SOSPeerInfoDeviceClass devclass
, CFStringRef serial
, CFBooleanRef preferIDS
, CFBooleanRef preferIDSFragmentation
, CFBooleanRef preferIDSACKModel
, CFStringRef transportType
, CFStringRef deviceID
) {
635 SOSDataSourceFactoryRef factory
= SOSTestDataSourceFactoryCreate();
636 SOSDataSourceRef ds
= SOSTestDataSourceCreate();
637 SOSTestDataSourceFactorySetDataSource(factory
, data_source_name
, ds
);
638 SOSEngineRef engine
= SOSEngineCreate(ds
, NULL
);
641 CFMutableDictionaryRef gestalt
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
642 CFDictionaryAddValue(gestalt
, kPIUserDefinedDeviceNameKey
, name
);
643 CFDictionaryAddValue(gestalt
, kPIDeviceModelNameKey
, modelFromType(devclass
));
644 CFDictionaryAddValue(gestalt
, kPIOSVersionKey
, CFSTR("TESTRUN"));
646 CFMutableDictionaryRef testV2dict
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
647 CFDictionaryAddValue(testV2dict
, sSerialNumberKey
, serial
);
648 SOSAccount
* result
= SOSAccountCreateTest(kCFAllocatorDefault
, name
, gestalt
, factory
);
649 [result
.trust updateV2Dictionary
:result v2
:testV2dict
];
651 CFReleaseSafe(SOSAccountCopyUUID(result
));
653 CFReleaseNull(gestalt
);
654 CFReleaseNull(testV2dict
);
659 static CFStringRef sGestaltTest
= CFSTR("GestaltTest");
660 static CFStringRef sV2Test
= CFSTR("V2Test");
661 static inline CFDictionaryRef
SOSTestSaveStaticAccountState(SOSAccount
* account
) {
662 CFMutableDictionaryRef retval
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
663 CFDictionaryRef gestalt
= SOSAccountCopyGestalt(account
);
664 CFDictionaryRef v2dictionary
= SOSAccountCopyV2Dictionary(account
);
665 CFDictionaryAddValue(retval
, sGestaltTest
, gestalt
);
666 CFDictionaryAddValue(retval
, sV2Test
, v2dictionary
);
667 CFReleaseNull(gestalt
);
668 CFReleaseNull(v2dictionary
);
672 static inline void SOSTestRestoreAccountState(SOSAccount
* account
, CFDictionaryRef saved
) {
673 [account
.trust updateGestalt
:account newGestalt
:CFDictionaryGetValue(saved
, sGestaltTest
)];
674 [account
.trust updateV2Dictionary
:account v2
:CFDictionaryGetValue(saved
, sV2Test
)];
677 static CFStringRef
CFStringCreateRandomHexWithLength(size_t len
) {
679 CFDataRef data
= CFDataCreateWithRandomBytes(len
/2);
680 CFMutableStringRef retval
= CFStringCreateMutable(kCFAllocatorDefault
, len
);
681 CFStringAppendHexData(retval
, data
);
686 static inline SOSAccount
* CreateAccountForLocalChanges(CFStringRef name
, CFStringRef data_source_name
)
688 CFStringRef randomSerial
= CFStringCreateRandomHexWithLength(8);
689 CFStringRef randomDevID
= CFStringCreateRandomHexWithLength(16);
690 SOSAccount
* retval
= CreateAccountForLocalChangesWithStartingAttributes(name
, data_source_name
, SOSPeerInfo_iOS
, randomSerial
,
691 kCFBooleanTrue
, kCFBooleanTrue
, kCFBooleanTrue
, SOSTransportMessageTypeKVS
, randomDevID
);
693 CFReleaseNull(randomSerial
);
694 CFReleaseNull(randomDevID
);
698 static inline SOSAccount
* CreateAccountForLocalChangesFromData(CFDataRef flattenedData
, CFStringRef name
, CFStringRef data_source_name
)
700 SOSDataSourceFactoryRef factory
= SOSTestDataSourceFactoryCreate();
701 SOSDataSourceRef ds
= SOSTestDataSourceCreate();
702 SOSTestDataSourceFactorySetDataSource(factory
, data_source_name
, ds
);
703 SOSEngineRef engine
= SOSEngineCreate(ds
, NULL
);
706 SOSAccount
* result
= SOSAccountCreateTestFromData(kCFAllocatorDefault
, flattenedData
, name
, factory
);
713 static inline int countPeers(SOSAccount
* account
) {
714 CFErrorRef error
= NULL
;
717 peers
= SOSAccountCopyPeers(account
, &error
);
720 int retval
= (int) CFArrayGetCount(peers
);
721 CFReleaseNull(error
);
722 CFReleaseNull(peers
);
726 static inline int countActivePeers(SOSAccount
* account
) {
727 CFErrorRef error
= NULL
;
730 peers
= SOSAccountCopyActivePeers(account
, &error
);
733 int retval
= (int) CFArrayGetCount(peers
);
734 CFReleaseNull(error
);
735 CFReleaseNull(peers
);
739 static inline int countActiveValidPeers(SOSAccount
* account
) {
740 CFErrorRef error
= NULL
;
743 peers
= SOSAccountCopyActiveValidPeers(account
, &error
);
746 int retval
= (int) CFArrayGetCount(peers
);
747 CFReleaseNull(error
);
748 CFReleaseNull(peers
);
752 static inline int countApplicants(SOSAccount
* account
) {
753 CFErrorRef error
= NULL
;
754 CFArrayRef applicants
= SOSAccountCopyApplicants(account
, &error
);
757 if(applicants
) retval
= (int)CFArrayGetCount(applicants
);
758 CFReleaseNull(error
);
759 CFReleaseNull(applicants
);
764 static inline void showActiveValidPeers(SOSAccount
* account
) {
765 CFErrorRef error
= NULL
;
768 peers
= SOSAccountCopyActiveValidPeers(account
, &error
);
769 CFArrayForEach(peers
, ^(const void *value
) {
770 SOSPeerInfoRef pi
= (SOSPeerInfoRef
) value
;
771 ok(0, "Active Valid Peer %@", pi
);
773 CFReleaseNull(peers
);
776 #define ok_or_quit(COND,MESSAGE,LABEL) ok(COND, MESSAGE); if(!(COND)) goto LABEL
778 static inline bool testAccountPersistence(SOSAccount
* account
) {
779 SOSDataSourceFactoryRef test_factory
= SOSTestDataSourceFactoryCreate();
780 SOSDataSourceRef test_source
= SOSTestDataSourceCreate();
781 SOSTestDataSourceFactorySetDataSource(test_factory
, CFSTR("TestType"), test_source
);
782 NSError
* error
= nil
;
785 SOSAccount
* reinflatedAccount
= NULL
;
786 NSData
* accountDER
= NULL
;
788 SOSAccountCheckHasBeenInSync_wTxn(account
);
790 // DER encode account to accountData - this allows checking discreet DER functions
791 size_t size
= [account
.trust getDEREncodedSize
:account err
:&error
];
793 uint8_t buffer
[size
];
794 uint8_t* start
= [account
.trust encodeToDER
:account err
:&error start
:buffer end
:buffer
+ sizeof(buffer
)];
797 ok_or_quit(start
, "successful encoding", errOut
);
798 ok_or_quit(start
== buffer
, "Used whole buffer", errOut
);
800 accountDER
= [NSData dataWithBytes
:buffer length
:size
];
801 ok_or_quit(accountDER
, "Made CFData for Account", errOut
);
804 // Re-inflate to "inflated"
805 reinflatedAccount
= [SOSAccount accountFromData
:accountDER
810 ok(reinflatedAccount
, "inflated");
811 ok(CFEqualSafe((__bridge CFTypeRef
)reinflatedAccount
, (__bridge CFTypeRef
)account
), "Compares");
813 // Repeat through SOSAccountCopyEncodedData() interface - this is the normally called combined interface
814 accountDER
= [account encodedData
:&error
];
816 reinflatedAccount
= [SOSAccount accountFromData
:accountDER factory
:test_factory error
:&error
];
817 ok(reinflatedAccount
, "inflated2: %@", error
);
818 ok(CFEqual((__bridge CFTypeRef
)account
, (__bridge CFTypeRef
)reinflatedAccount
), "Compares");
827 static inline bool SOSTestStartCircleWithAccount(SOSAccount
* account
, CFMutableDictionaryRef changes
, CFStringRef cfaccount
, CFDataRef cfpassword
) {
829 if(!SOSAccountAssertUserCredentialsAndUpdate(account
, cfaccount
, cfpassword
, NULL
))
831 is(ProcessChangesUntilNoChange(changes
, account
, NULL
), 1, "updates");
832 if(!SOSAccountResetToOffering_wTxn(account
, NULL
))
834 is(ProcessChangesUntilNoChange(changes
, account
, NULL
), 1, "updates");
840 static inline bool SOSTestApproveRequest(SOSAccount
* approver
, CFIndex napplicants
) {
842 CFErrorRef error
= NULL
;
843 CFArrayRef applicants
= SOSAccountCopyApplicants(approver
, &error
);
845 ok(applicants
&& CFArrayGetCount(applicants
) == napplicants
, "See %ld applicant(s) %@ (%@)", napplicants
, applicants
, error
);
846 CFStringRef approvername
= SOSAccountCopyName(approver
);
847 ok((retval
= SOSAccountAcceptApplicants(approver
, applicants
, &error
)), "%@ accepts (%@)", approvername
, error
);
848 CFReleaseNull(error
);
849 CFReleaseNull(applicants
);
850 CFReleaseNull(approvername
);
855 #define DROP_USERKEY true
856 #define KEEP_USERKEY false
858 static inline bool SOSTestJoinWith(CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
, SOSAccount
* joiner
) {
859 CFErrorRef error
= NULL
;
860 // retval will return op failures, not count failures - we'll still report those from in here.
863 FeedChangesTo(changes
, joiner
);
865 ok(SOSAccountAssertUserCredentialsAndUpdate(joiner
, cfaccount
, cfpassword
, &error
), "Credential setting (%@)", error
);
866 CFReleaseNull(error
);
868 ProcessChangesUntilNoChange(changes
, joiner
, NULL
);
870 ok(retval
= SOSAccountJoinCircles_wTxn(joiner
, &error
), "Applying (%@)", error
);
871 CFReleaseNull(error
);
875 static inline bool SOSTestJoinWithApproval(CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
, SOSAccount
* approver
, SOSAccount
* joiner
, bool dropUserKey
, int expectedCount
, bool expectCleanup
) {
876 //CFErrorRef error = NULL;
877 // retval will return op failures, not count failures - we'll still report those from in here.
880 ok(retval
= SOSTestJoinWith(cfpassword
, cfaccount
, changes
, joiner
), "Application Made");
882 ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
);
885 if(dropUserKey
) SOSAccountPurgePrivateCredential(joiner
); // lose the userKey so we don't "fix" the ghost problem yet.
888 if(expectCleanup
) nrounds
++;
890 ok(retval
&= SOSTestApproveRequest(approver
, 1), "Accepting Request to Join");
891 ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
);
893 accounts_agree_internal("Successful join shows same circle view", joiner
, approver
, false);
894 is(countPeers(joiner
), expectedCount
, "There should be %d valid peers", expectedCount
);
899 static inline bool SOSTestChangeAccountDeviceName(SOSAccount
* account
, CFStringRef name
) {
901 CFMutableDictionaryRef mygestalt
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, SOSPeerGetGestalt(account
.peerInfo
));
902 require_quiet(mygestalt
, retOut
);
903 CFDictionarySetValue(mygestalt
, kPIUserDefinedDeviceNameKey
, name
);
904 retval
= [account
.trust updateGestalt
:account newGestalt
:mygestalt
];
906 CFReleaseNull(mygestalt
);
911 * this simulates a piggy-back join at the account level
914 static inline bool SOSTestJoinThroughPiggyBack(CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
, SOSAccount
* approver
, SOSAccount
* joiner
, bool dropUserKey
, int expectedCount
, bool expectCleanup
) {
915 // retval will return op failures, not count failures - we'll still report those from in here.
917 CFErrorRef error
= NULL
;
919 ok(SOSAccountAssertUserCredentialsAndUpdate(approver
, cfaccount
, cfpassword
, &error
), "Credential setting (%@)", error
);
920 CFReleaseNull(error
);
921 // This makes sure the joiner sees the current key parameters
922 ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
);
924 SecKeyRef privKey
= SOSAccountGetPrivateCredential(approver
, &error
);
925 ok(privKey
, "got privkey from approver (%@)", error
);
926 CFReleaseNull(error
);
928 ok(SOSAccountTryUserPrivateKey(joiner
, privKey
, &error
), "assert same credentials on joiner (%@)", error
);
929 CFReleaseNull(error
);
930 // This gives the joiner a chance to see the current circle - this is the account-level equivalent of the Flush added to stashAccountCredential
931 ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
);
933 SOSPeerInfoRef joinerPI
= SOSAccountCopyApplication(joiner
, &error
);
934 ok(joinerPI
, "Joiner peerinfo available as application %@", error
);
935 CFReleaseNull(error
);
937 CFDataRef theBlob
= SOSAccountCopyCircleJoiningBlob(approver
, joinerPI
, &error
);
938 ok(theBlob
, "Made a joining blob (%@)", error
);
939 CFReleaseNull(error
);
942 bool joined
= SOSAccountJoinWithCircleJoiningBlob(joiner
, theBlob
, kPiggyV1
, &error
);
943 ok(joined
, "Joiner posted circle with itself in it (%@)", error
);
944 CFReleaseNull(error
);
946 CFReleaseNull(joinerPI
);
947 CFReleaseNull(theBlob
);
949 is(ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
), 2, "updates");
951 ok((retval
= [joiner isInCircle
:NULL
]), "Joiner is in");
953 accounts_agree_internal("Successful join shows same circle view", joiner
, approver
, false);
954 is(countPeers(joiner
), expectedCount
, "There should be %d valid peers", expectedCount
);
959 static inline SOSAccount
* SOSTestCreateAccountAsSerialClone(CFStringRef name
, SOSPeerInfoDeviceClass devClass
, CFStringRef serial
, CFStringRef idsID
) {
960 return CreateAccountForLocalChangesWithStartingAttributes(name
, CFSTR("TestSource"), devClass
, serial
, kCFBooleanTrue
, kCFBooleanTrue
, kCFBooleanTrue
, SOSTransportMessageTypeKVS
, idsID
);
963 static inline bool SOSTestMakeGhostInCircle(CFStringRef name
, SOSPeerInfoDeviceClass devClass
, CFStringRef serial
, CFStringRef idsID
,
964 CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
,
965 SOSAccount
* approver
, int expectedCount
) {
967 SOSAccount
* ghostAccount
= SOSTestCreateAccountAsSerialClone(name
, devClass
, serial
, idsID
);
968 ok(ghostAccount
, "Created Ghost Account");
969 require_quiet(ghostAccount
, retOut
);
970 if(!ghostAccount
) return false;
971 ok(retval
= SOSTestJoinWithApproval(cfpassword
, cfaccount
, changes
, approver
, ghostAccount
, DROP_USERKEY
, expectedCount
, true), "Ghost Joined Circle with expected result");
976 static inline void SOSTestCleanup() {
977 SOSUnregisterAllTransportMessages();
978 SOSUnregisterAllTransportCircles();
979 SOSUnregisterAllTransportKeyParameters();
980 CFArrayRemoveAllValues(key_transports
);
981 CFArrayRemoveAllValues(circle_transports
);
982 CFArrayRemoveAllValues(message_transports
);