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
, nil
, 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
, nil
, 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
];
119 static inline bool SOSAccountIsMyPeerInBackupAndCurrentInView_wTxn(SOSAccount
*account
, CFStringRef viewname
) {
120 __block
bool result
= false;
121 [account performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
122 result
= SOSAccountIsMyPeerInBackupAndCurrentInView(account
, viewname
);
127 static inline bool SOSAccountIsPeerInBackupAndCurrentInView_wTxn(SOSAccount
*account
, SOSPeerInfoRef peerInfo
, CFStringRef viewname
) {
128 __block
bool result
= false;
129 [account performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
130 result
= SOSAccountIsPeerInBackupAndCurrentInView(account
, peerInfo
, viewname
);
135 static inline bool SOSAccountRecoveryKeyIsInBackupAndCurrentInView_wTxn(SOSAccount
*account
, CFStringRef viewname
) {
136 __block
bool result
= false;
137 [account performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
138 result
= SOSAccountRecoveryKeyIsInBackupAndCurrentInView(account
, viewname
);
143 static inline SOSBackupSliceKeyBagRef
SOSAccountBackupSliceKeyBagForView_wTxn(SOSAccount
*account
, CFStringRef viewname
, CFErrorRef
*error
) {
144 __block SOSBackupSliceKeyBagRef result
= NULL
;
145 [account performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
146 result
= SOSAccountBackupSliceKeyBagForView(account
, viewname
, error
);
155 // Account comparison
158 #define kAccountsAgreeTestMin 9
159 #define kAccountsAgreeTestPerPeer 1
160 #define accountsAgree(x) (kAccountsAgreeTestMin + kAccountsAgreeTestPerPeer * (x))
162 static void SOSAccountResetToTest(SOSAccount
* a
, CFStringRef accountName
) {
163 SOSUnregisterTransportKeyParameter(a
.key_transport
);
164 SOSUnregisterTransportCircle((SOSCircleStorageTransport
*)a
.circle_transport
);
165 SOSUnregisterTransportMessage((SOSMessage
*)a
.kvs_message_transport
);
168 CFArrayRemoveAllValue(key_transports
, (__bridge CFTypeRef
)(a
.key_transport
));
169 if(message_transports
){
170 CFArrayRemoveAllValue(message_transports
, (__bridge CFTypeRef
)a
.kvs_message_transport
);
172 if(circle_transports
)
173 CFArrayRemoveAllValue(circle_transports
, (__bridge CFTypeRef
)a
.circle_transport
);
175 a
.circle_transport
= nil
;
176 a
.key_transport
= nil
;
177 a
.kvs_message_transport
= nil
;
179 [a performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
180 SOSAccountEnsureFactoryCirclesTest(a
, accountName
);
185 static SOSAccount
* SOSAccountCreateBasicTest(CFAllocatorRef allocator
,
186 CFStringRef accountName
,
187 CFDictionaryRef gestalt
,
188 SOSDataSourceFactoryRef factory
) {
190 a
= SOSAccountCreate(kCFAllocatorDefault
, gestalt
, factory
);
195 static SOSAccount
* SOSAccountCreateTest(CFAllocatorRef allocator
,
196 CFStringRef accountName
,
197 CFDictionaryRef gestalt
,
198 SOSDataSourceFactoryRef factory
) {
199 SOSAccount
* a
= SOSAccountCreateBasicTest(allocator
, accountName
, gestalt
, factory
);
201 SOSAccountResetToTest(a
, accountName
);
203 SOSAccountInflateTestTransportsForCircle(a
, SOSCircleGetName([a
.trust getCircle
:NULL
]), accountName
, NULL
);
207 static SOSAccount
* SOSAccountCreateTestFromData(CFAllocatorRef allocator
,
209 CFStringRef accountName
,
210 SOSDataSourceFactoryRef factory
) {
211 SOSAccount
* a
= [SOSAccount accountFromData
:(__bridge NSData
*) data
215 CFDictionaryRef gestalt
= SOSCreatePeerGestaltFromName(accountName
);
216 a
= SOSAccountCreate(allocator
, gestalt
, factory
);
217 CFReleaseNull(gestalt
);
220 SOSAccountResetToTest(a
, accountName
);
222 SOSAccountInflateTestTransportsForCircle(a
, SOSCircleGetName([a
.trust getCircle
:NULL
]), accountName
, NULL
);
228 static inline bool SOSAccountAssertUserCredentialsAndUpdate(SOSAccount
* account
,
229 CFStringRef user_account
, CFDataRef user_password
,
232 bool success
= false;
233 success
= SOSAccountAssertUserCredentials(account
, user_account
, user_password
, error
);
234 require_quiet(success
, done
);
236 success
= SOSAccountGenerationSignatureUpdate(account
, error
);
244 static void unretired_peers_is_subset(const char* label
, CFArrayRef peers
, CFSetRef allowed_peers
)
246 CFArrayForEach(peers
, ^(const void *value
) {
247 SOSPeerInfoRef pi
= (SOSPeerInfoRef
) value
;
249 CFErrorRef leftError
= NULL
;
250 CFErrorRef rightError
= NULL
;
252 ok(SOSPeerInfoIsRetirementTicket(pi
) || SOSPeerInfoIsCloudIdentity(pi
) || CFSetContainsValue(allowed_peers
, pi
), "Peer is allowed (%s) Peer: %@, Allowed %@", label
, pi
, allowed_peers
);
254 CFReleaseNull(leftError
);
255 CFReleaseNull(rightError
);
259 static void accounts_agree_internal(char *label
, SOSAccount
* left
, SOSAccount
* right
, bool check_peers
)
261 CFErrorRef error
= NULL
;
263 CFArrayRef leftPeers
= SOSAccountCopyActivePeers(left
, &error
);
264 ok(leftPeers
, "Left peers (%@) - %s", error
, label
);
265 CFReleaseNull(error
);
267 CFArrayRef rightPeers
= SOSAccountCopyActivePeers(right
, &error
);
268 ok(rightPeers
, "Right peers (%@) - %s", error
, label
);
269 CFReleaseNull(error
);
271 ok(CFEqual(leftPeers
, rightPeers
), "Matching peers (%s) Left: %@, Right: %@", label
, leftPeers
, rightPeers
);
274 CFMutableSetRef allowed_identities
= CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault
);
276 SOSFullPeerInfoRef leftFullPeer
= [left
.trust CopyAccountIdentityPeerInfo
];
279 CFSetAddValue(allowed_identities
, SOSFullPeerInfoGetPeerInfo(leftFullPeer
));
280 CFReleaseNull(leftFullPeer
);
282 SOSFullPeerInfoRef rightFullPeer
= [right
.trust CopyAccountIdentityPeerInfo
];
285 CFSetAddValue(allowed_identities
, SOSFullPeerInfoGetPeerInfo(rightFullPeer
));
286 CFReleaseNull(rightFullPeer
);
288 unretired_peers_is_subset(label
, leftPeers
, allowed_identities
);
290 CFReleaseNull(allowed_identities
);
293 CFReleaseNull(leftPeers
);
294 CFReleaseNull(rightPeers
);
297 CFArrayRef leftConcurringPeers
= SOSAccountCopyConcurringPeers(left
, &error
);
298 ok(leftConcurringPeers
, "Left peers (%@) - %s", error
, label
);
300 CFArrayRef rightConcurringPeers
= SOSAccountCopyConcurringPeers(right
, &error
);
301 ok(rightConcurringPeers
, "Right peers (%@) - %s", error
, label
);
303 ok(CFEqual(leftConcurringPeers
, rightConcurringPeers
), "Matching concurring peers Left: %@, Right: %@", leftConcurringPeers
, rightConcurringPeers
);
305 CFReleaseNull(leftConcurringPeers
);
306 CFReleaseNull(rightConcurringPeers
);
309 CFArrayRef leftApplicants
= SOSAccountCopyApplicants(left
, &error
);
310 ok(leftApplicants
, "Left Applicants (%@) - %s", error
, label
);
312 CFArrayRef rightApplicants
= SOSAccountCopyApplicants(right
, &error
);
313 ok(rightApplicants
, "Left Applicants (%@) - %s", error
, label
);
315 ok(CFEqual(leftApplicants
, rightApplicants
), "Matching applicants (%s) Left: %@, Right: %@", label
, leftApplicants
, rightApplicants
);
317 CFReleaseNull(leftApplicants
);
318 CFReleaseNull(rightApplicants
);
322 static inline void accounts_agree(char *label
, SOSAccount
* left
, SOSAccount
* right
)
324 accounts_agree_internal(label
, left
, right
, true);
332 static inline CFStringRef
CFArrayCopyCompactDescription(CFArrayRef array
) {
334 return CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("<Not an array! %@>"), array
);
336 CFMutableStringRef result
= CFStringCreateMutableCopy(kCFAllocatorDefault
, 0, CFSTR("["));
338 __block CFStringRef separator
= CFSTR("");
339 CFArrayForEach(array
, ^(const void *value
) {
340 CFStringAppendFormat(result
, NULL
, CFSTR("%@%@"), separator
, value
);
341 separator
= CFSTR(",");
344 CFStringAppend(result
, CFSTR("]"));
346 CFReleaseSafe(separator
);
351 static inline CFStringRef
SOSAccountCopyName(SOSAccount
* account
) {
352 SOSPeerInfoRef pi
= account
.peerInfo
;
354 return pi
? CFStringCreateCopy(kCFAllocatorDefault
, SOSPeerInfoGetPeerName(pi
)) : CFStringCreateWithFormat(kCFAllocatorDefault
, 0, CFSTR("%@"), account
);
357 static inline CFStringRef
CopyChangesDescription(CFDictionaryRef changes
) {
359 CFStringRef pendingChanges
= CFDictionaryCopyCompactDescription((CFDictionaryRef
) CFDictionaryGetValue(changes
, kCFNull
));
361 CFMutableStringRef peerTable
= CFStringCreateMutableCopy(kCFAllocatorDefault
, 0, CFSTR("["));
363 __block CFStringRef separator
= CFSTR("");
365 CFDictionaryForEach(changes
, ^(const void *key
, const void *value
) {
366 if (CFGetTypeID(key
) == SOSAccountGetTypeID()) {
367 CFStringRef accountName
= SOSAccountCopyName((__bridge SOSAccount
*)key
);
368 CFStringRef arrayDescription
= CFArrayCopyCompactDescription(value
);
370 CFStringAppendFormat(peerTable
, NULL
, CFSTR("%@%@:%@"), separator
, accountName
, arrayDescription
);
371 separator
= CFSTR(", ");
373 CFReleaseSafe(accountName
);
374 CFReleaseSafe(arrayDescription
);
378 CFStringAppend(peerTable
, CFSTR("]"));
380 CFStringRef result
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("<TestChanges %@ %@>"), pendingChanges
, peerTable
);
381 CFReleaseNull(pendingChanges
);
382 CFReleaseNull(peerTable
);
387 static void CFDictionaryOverlayDictionary(CFMutableDictionaryRef target
, CFMutableDictionaryRef overlay
) {
388 CFMutableSetRef keysToRemove
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
);
390 CFDictionaryForEach(overlay
, ^(const void *key
, const void *value
) {
391 const void *current_value
= CFDictionaryGetValue(target
, key
);
392 if (CFEqualSafe(current_value
, value
) || (isNull(value
) && current_value
== NULL
)) {
393 CFSetAddValue(keysToRemove
, key
);
395 CFDictionarySetValue(target
, key
, value
);
399 CFSetForEach(keysToRemove
, ^(const void *value
) {
400 CFDictionaryRemoveValue(overlay
, value
);
403 CFReleaseNull(keysToRemove
);
406 static void CFArrayAppendKeys(CFMutableArrayRef keys
, CFDictionaryRef newKeysToAdd
) {
407 CFDictionaryForEach(newKeysToAdd
, ^(const void *key
, const void *value
) {
408 CFArrayAppendValue(keys
, key
);
412 static bool AddNewChanges(CFMutableDictionaryRef changesRecord
, CFMutableDictionaryRef newKeysAndValues
, SOSAccount
* sender
)
414 __block
bool changes_added
= false;
415 CFMutableDictionaryRef emptyDictionary
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
416 CFDictionaryAddValue(changesRecord
, kCFNull
, emptyDictionary
);
417 CFReleaseNull(emptyDictionary
);
419 CFDictionaryOverlayDictionary((CFMutableDictionaryRef
) CFDictionaryGetValue(changesRecord
, kCFNull
), newKeysAndValues
);
421 CFDictionaryForEach(changesRecord
, ^(const void *key
, const void *value
) {
422 if (isArray(value
) && (sender
== NULL
|| sender
!= key
)) {
423 CFArrayAppendKeys((CFMutableArrayRef
) value
, newKeysAndValues
);
424 if (CFDictionaryGetCount(newKeysAndValues
))
425 changes_added
= true;
430 secnotice("changes", "Changes from %@: %@", sender
, newKeysAndValues
);
432 CFDictionaryRemoveAllValues(newKeysAndValues
);
434 return changes_added
;
437 static bool FillAllChanges(CFMutableDictionaryRef changes
) {
438 __block
bool changed
= false;
440 CFMutableSetRef changedAccounts
= CFSetCreateMutable(kCFAllocatorDefault
, 0, NULL
);
442 CFArrayForEach(key_transports
, ^(const void *value
) {
443 CKKeyParameterTest
* tpt
= (__bridge CKKeyParameterTest
*) value
;
444 if (AddNewChanges(changes
, SOSTransportKeyParameterTestGetChanges(tpt
), SOSTransportKeyParameterTestGetAccount(tpt
))) {
446 CFSetAddValue(changedAccounts
, (__bridge CFTypeRef
)(SOSTransportKeyParameterTestGetAccount(tpt
)));
448 SOSTransportKeyParameterTestClearChanges(tpt
);
450 CFArrayForEach(circle_transports
, ^(const void *value
) {
451 SOSCircleStorageTransportTest
*tpt
= (__bridge SOSCircleStorageTransportTest
*) value
;
452 if (AddNewChanges(changes
, [tpt SOSTransportCircleTestGetChanges
], [tpt getAccount
])) {
454 CFSetAddValue(changedAccounts
, (__bridge CFTypeRef
)SOSTransportCircleTestGetAccount(tpt
));
456 SOSTransportCircleTestClearChanges(tpt
);
458 CFArrayForEach(message_transports
, ^(const void *value
) {
459 if([(__bridge SOSMessage
*)value SOSTransportMessageGetTransportType
] == kKVSTest
){
460 SOSMessageKVSTest
* tpt
= (__bridge SOSMessageKVSTest
*) value
;
461 CFDictionaryRemoveValue(SOSTransportMessageKVSTestGetChanges(tpt
), kCFNull
);
462 if (AddNewChanges(changes
, SOSTransportMessageKVSTestGetChanges(tpt
), SOSTransportMessageKVSTestGetAccount(tpt
))) {
464 CFSetAddValue(changedAccounts
, (__bridge CFTypeRef
)(SOSTransportMessageKVSTestGetAccount(tpt
)));
466 SOSTransportMessageTestClearChanges(tpt
);
470 secnotice("process-changes", "Accounts with change (%@): %@", changed
? CFSTR("YES") : CFSTR("NO"), changedAccounts
);
472 CFReleaseNull(changedAccounts
);
477 static void FillChanges(CFMutableDictionaryRef changes
, SOSAccount
* forAccount
)
479 CFArrayForEach(key_transports
, ^(const void *value
) {
480 CKKeyParameterTest
* tpt
= (__bridge CKKeyParameterTest
*) value
;
481 if(CFEqualSafe((__bridge CFTypeRef
)(forAccount
), (__bridge CFTypeRef
)(SOSTransportKeyParameterTestGetAccount(tpt
)))){
482 AddNewChanges(changes
, SOSTransportKeyParameterTestGetChanges(tpt
), SOSTransportKeyParameterTestGetAccount(tpt
));
483 SOSTransportKeyParameterTestClearChanges(tpt
);
486 CFArrayForEach(circle_transports
, ^(const void *value
) {
487 SOSCircleStorageTransportTest
* tpt
= (__bridge SOSCircleStorageTransportTest
*) value
;
488 if([forAccount isEqual
: SOSTransportCircleTestGetAccount(tpt
)]){
489 AddNewChanges(changes
, [tpt SOSTransportCircleTestGetChanges
], SOSTransportCircleTestGetAccount(tpt
));
490 SOSTransportCircleTestClearChanges(tpt
);
493 CFArrayForEach(message_transports
, ^(const void *value
) {
494 if([(__bridge SOSMessage
*)value SOSTransportMessageGetTransportType
] == kKVSTest
){
495 SOSMessageKVSTest
* tpt
= (__bridge SOSMessageKVSTest
*) value
;
496 if(CFEqualSafe((__bridge CFTypeRef
)(forAccount
), (__bridge CFTypeRef
)(SOSTransportMessageKVSTestGetAccount(tpt
)))){
497 CFDictionaryRemoveValue(SOSTransportMessageKVSTestGetChanges(tpt
), kCFNull
);
498 AddNewChanges(changes
, SOSTransportMessageKVSTestGetChanges(tpt
), SOSTransportMessageKVSTestGetAccount(tpt
));
499 SOSTransportMessageTestClearChanges(tpt
);
506 static inline void FillChangesMulti(CFMutableDictionaryRef changes
, SOSAccount
* account
, ...)
508 SOSAccount
* next_account
= account
;
510 va_start(argp
, account
);
511 while(next_account
!= NULL
) {
512 FillChanges(changes
, next_account
);
513 next_account
= va_arg(argp
, SOSAccount
*);
517 static inline CFMutableArrayRef
CFDictionaryCopyKeys(CFDictionaryRef dictionary
)
519 CFMutableArrayRef result
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
521 CFArrayAppendKeys(result
, dictionary
);
526 #define kFeedChangesToTestCount 1
527 static inline void FeedChangesTo(CFMutableDictionaryRef changes
, SOSAccount
* acct
)
529 CFDictionaryRef full_list
= (CFDictionaryRef
) CFDictionaryGetValue(changes
, kCFNull
);
531 if (!isDictionary(full_list
))
532 return; // Nothing recorded to send!
534 CFMutableArrayRef account_pending_keys
= (CFMutableArrayRef
)CFDictionaryGetValue(changes
, (__bridge CFTypeRef
)(acct
));
536 if (!isArray(account_pending_keys
)) {
537 account_pending_keys
= CFDictionaryCopyKeys(full_list
);
538 CFDictionaryAddValue(changes
, (__bridge CFTypeRef
)(acct
), account_pending_keys
);
539 CFReleaseSafe(account_pending_keys
); // The dictionary keeps it, we don't retain it here.
542 CFMutableDictionaryRef account_pending_messages
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
543 CFArrayForEach(account_pending_keys
, ^(const void *value
) {
544 CFDictionaryAddValue(account_pending_messages
, value
, CFDictionaryGetValue(full_list
, value
));
547 secnotice("changes", "Changes for %@:", SOSTransportKeyParameterTestGetName((CKKeyParameterTest
*) acct
.key_transport
));
549 CFDictionaryForEach(account_pending_messages
, ^(const void *key
, const void *value
) {
550 secnotice("changes", " %@", key
);
553 if(CFDictionaryGetCount(account_pending_messages
) == 0) {
554 CFReleaseNull(account_pending_messages
);
558 __block CFMutableArrayRef handled
= NULL
;
559 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
560 __block CFErrorRef error
= NULL
;
561 ok(handled
= SOSTransportDispatchMessages(txn
, account_pending_messages
, &error
), "SOSTransportHandleMessages failed (%@)", error
);
562 CFReleaseNull(error
);
565 if (isArray(handled
)) {
566 CFArrayForEach(handled
, ^(const void *value
) {
567 CFArrayRemoveAllValue(account_pending_keys
, value
);
570 CFReleaseNull(account_pending_messages
);
571 CFReleaseNull(handled
);
574 #define kFeedChangesToMultieTestCountPer 1
576 static inline void FeedChangesToMultiV(CFMutableDictionaryRef changes
, va_list argp
)
578 SOSAccount
* account
= NULL
;
579 while((account
= va_arg(argp
, SOSAccount
*)) != NULL
) {
580 FeedChangesTo(changes
, account
);
584 static inline void FeedChangesToMulti(CFMutableDictionaryRef changes
, ...)
587 va_start(argp
, changes
);
589 FeedChangesToMultiV(changes
, argp
);
594 static inline void InjectChangeToMulti(CFMutableDictionaryRef changes
,
595 CFStringRef changeKey
, CFTypeRef changeValue
, ...)
597 CFMutableDictionaryRef changes_to_send
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
598 changeKey
, changeValue
,
600 AddNewChanges(changes
, changes_to_send
, NULL
);
601 CFReleaseNull(changes_to_send
);
604 va_start(argp
, changeValue
);
605 FeedChangesToMultiV(changes
, argp
);
609 static inline bool ProcessChangesOnceV(CFMutableDictionaryRef changes
, va_list argp
)
611 bool result
= FillAllChanges(changes
);
613 FeedChangesToMultiV(changes
, argp
);
619 static inline bool ProcessChangesOnce(CFMutableDictionaryRef changes
, ...)
622 va_start(argp
, changes
);
624 bool result
= ProcessChangesOnceV(changes
, argp
);
631 static inline int ProcessChangesUntilNoChange(CFMutableDictionaryRef changes
, ...)
634 va_start(argp
, changes
);
637 bool new_data
= false;
640 va_copy(argp_copy
, argp
);
642 new_data
= ProcessChangesOnceV(changes
, argp_copy
);
656 // MARK: Account creation
659 static CFStringRef
modelFromType(SOSPeerInfoDeviceClass cls
) {
661 case SOSPeerInfo_macOS
: return CFSTR("Mac Pro");
662 case SOSPeerInfo_iOS
: return CFSTR("iPhone");
663 case SOSPeerInfo_iCloud
: return CFSTR("iCloud");
664 case SOSPeerInfo_watchOS
: return CFSTR("needWatchOSDeviceName");
665 case SOSPeerInfo_tvOS
: return CFSTR("needTVOSDeviceName");
666 default: return CFSTR("GENERICOSTHING");
670 static inline SOSAccount
* CreateAccountForLocalChangesWithStartingAttributes(CFStringRef name
, CFStringRef data_source_name
, SOSPeerInfoDeviceClass devclass
, CFStringRef serial
, CFBooleanRef preferIDS
, CFBooleanRef preferIDSFragmentation
, CFBooleanRef preferIDSACKModel
, CFStringRef transportType
, CFStringRef deviceID
) {
672 SOSDataSourceFactoryRef factory
= SOSTestDataSourceFactoryCreate();
673 SOSDataSourceRef ds
= SOSTestDataSourceCreate();
674 SOSTestDataSourceFactorySetDataSource(factory
, data_source_name
, ds
);
675 SOSEngineRef engine
= SOSEngineCreate(ds
, NULL
);
678 CFMutableDictionaryRef gestalt
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
679 CFDictionaryAddValue(gestalt
, kPIUserDefinedDeviceNameKey
, name
);
680 CFDictionaryAddValue(gestalt
, kPIDeviceModelNameKey
, modelFromType(devclass
));
681 CFDictionaryAddValue(gestalt
, kPIOSVersionKey
, CFSTR("TESTRUN"));
683 CFMutableDictionaryRef testV2dict
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
684 CFDictionaryAddValue(testV2dict
, sSerialNumberKey
, serial
);
685 SOSAccount
* result
= SOSAccountCreateTest(kCFAllocatorDefault
, name
, gestalt
, factory
);
686 [result
.trust updateV2Dictionary
:result v2
:testV2dict
];
688 CFReleaseSafe(SOSAccountCopyUUID(result
));
690 CFReleaseNull(gestalt
);
691 CFReleaseNull(testV2dict
);
696 static CFStringRef sGestaltTest
= CFSTR("GestaltTest");
697 static CFStringRef sV2Test
= CFSTR("V2Test");
698 static inline CFDictionaryRef
SOSTestSaveStaticAccountState(SOSAccount
* account
) {
699 CFMutableDictionaryRef retval
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
700 CFDictionaryRef gestalt
= SOSAccountCopyGestalt(account
);
701 CFDictionaryRef v2dictionary
= SOSAccountCopyV2Dictionary(account
);
702 CFDictionaryAddValue(retval
, sGestaltTest
, gestalt
);
703 CFDictionaryAddValue(retval
, sV2Test
, v2dictionary
);
704 CFReleaseNull(gestalt
);
705 CFReleaseNull(v2dictionary
);
709 static inline void SOSTestRestoreAccountState(SOSAccount
* account
, CFDictionaryRef saved
) {
710 [account
.trust updateGestalt
:account newGestalt
:CFDictionaryGetValue(saved
, sGestaltTest
)];
711 [account
.trust updateV2Dictionary
:account v2
:CFDictionaryGetValue(saved
, sV2Test
)];
714 static CFStringRef
CFStringCreateRandomHexWithLength(size_t len
) {
716 CFDataRef data
= CFDataCreateWithRandomBytes(len
/2);
717 CFMutableStringRef retval
= CFStringCreateMutable(kCFAllocatorDefault
, len
);
718 CFStringAppendHexData(retval
, data
);
723 static inline SOSAccount
* CreateAccountForLocalChanges(CFStringRef name
, CFStringRef data_source_name
)
725 CFStringRef randomSerial
= CFStringCreateRandomHexWithLength(8);
726 CFStringRef randomDevID
= CFStringCreateRandomHexWithLength(16);
727 SOSAccount
* retval
= CreateAccountForLocalChangesWithStartingAttributes(name
, data_source_name
, SOSPeerInfo_iOS
, randomSerial
,
728 kCFBooleanTrue
, kCFBooleanTrue
, kCFBooleanTrue
, SOSTransportMessageTypeKVS
, randomDevID
);
730 CFReleaseNull(randomSerial
);
731 CFReleaseNull(randomDevID
);
735 static inline SOSAccount
* CreateAccountForLocalChangesFromData(CFDataRef flattenedData
, CFStringRef name
, CFStringRef data_source_name
)
737 SOSDataSourceFactoryRef factory
= SOSTestDataSourceFactoryCreate();
738 SOSDataSourceRef ds
= SOSTestDataSourceCreate();
739 SOSTestDataSourceFactorySetDataSource(factory
, data_source_name
, ds
);
740 SOSEngineRef engine
= SOSEngineCreate(ds
, NULL
);
743 SOSAccount
* result
= SOSAccountCreateTestFromData(kCFAllocatorDefault
, flattenedData
, name
, factory
);
750 static inline int countPeers(SOSAccount
* account
) {
751 CFErrorRef error
= NULL
;
754 peers
= SOSAccountCopyPeers(account
, &error
);
757 int retval
= (int) CFArrayGetCount(peers
);
758 CFReleaseNull(error
);
759 CFReleaseNull(peers
);
763 static inline int countActivePeers(SOSAccount
* account
) {
764 CFErrorRef error
= NULL
;
767 peers
= SOSAccountCopyActivePeers(account
, &error
);
770 int retval
= (int) CFArrayGetCount(peers
);
771 CFReleaseNull(error
);
772 CFReleaseNull(peers
);
776 static inline int countActiveValidPeers(SOSAccount
* account
) {
777 CFErrorRef error
= NULL
;
780 peers
= SOSAccountCopyActiveValidPeers(account
, &error
);
783 int retval
= (int) CFArrayGetCount(peers
);
784 CFReleaseNull(error
);
785 CFReleaseNull(peers
);
789 static inline int countApplicants(SOSAccount
* account
) {
790 CFErrorRef error
= NULL
;
791 CFArrayRef applicants
= SOSAccountCopyApplicants(account
, &error
);
794 if(applicants
) retval
= (int)CFArrayGetCount(applicants
);
795 CFReleaseNull(error
);
796 CFReleaseNull(applicants
);
801 static inline void showActiveValidPeers(SOSAccount
* account
) {
802 CFErrorRef error
= NULL
;
805 peers
= SOSAccountCopyActiveValidPeers(account
, &error
);
806 CFArrayForEach(peers
, ^(const void *value
) {
807 SOSPeerInfoRef pi
= (SOSPeerInfoRef
) value
;
808 ok(0, "Active Valid Peer %@", pi
);
810 CFReleaseNull(peers
);
813 #define ok_or_quit(COND,MESSAGE,LABEL) ok(COND, MESSAGE); if(!(COND)) goto LABEL
815 static inline bool testAccountPersistence(SOSAccount
* account
) {
817 __block
bool retval
= false;
818 __block NSData
* accountDER
= NULL
;
820 SOSDataSourceFactoryRef test_factory
= SOSTestDataSourceFactoryCreate();
821 SOSDataSourceRef test_source
= SOSTestDataSourceCreate();
822 SOSTestDataSourceFactorySetDataSource(test_factory
, CFSTR("TestType"), test_source
);
824 SOSAccountCheckHasBeenInSync_wTxn(account
);
826 [account performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
827 NSError
* error
= nil
;
829 // DER encode account to accountData - this allows checking discreet DER functions
830 size_t size
= [account
.trust getDEREncodedSize
:account err
:&error
];
832 uint8_t buffer
[size
];
833 uint8_t* start
= [account
.trust encodeToDER
:account err
:&error start
:buffer end
:buffer
+ sizeof(buffer
)];
836 ok_or_quit(start
, "successful encoding", errOut
);
837 ok_or_quit(start
== buffer
, "Used whole buffer", errOut
);
839 accountDER
= [NSData dataWithBytes
:buffer length
:size
];
840 ok_or_quit(accountDER
, "Made CFData for Account", errOut
);
847 SOSAccount
* reinflatedAccount
= NULL
;
848 NSError
* error
= nil
;
855 // Re-inflate to "inflated"
856 reinflatedAccount
= [SOSAccount accountFromData
:accountDER
859 ok(reinflatedAccount
, "inflated: %@", error
);
862 ok(CFEqualSafe((__bridge CFTypeRef
)reinflatedAccount
, (__bridge CFTypeRef
)account
), "Compares");
864 // Repeat through SOSAccountCopyEncodedData() interface - this is the normally called combined interface
865 [account performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
866 NSError
* error
= nil
;
867 accountDER
= [account encodedData
:&error
];
871 SOSAccount
* reinflatedAccount2
= NULL
;
873 reinflatedAccount2
= [SOSAccount accountFromData
:accountDER factory
:test_factory error
:&error
];
874 ok(reinflatedAccount2
, "inflated2: %@", error
);
875 ok(CFEqual((__bridge CFTypeRef
)account
, (__bridge CFTypeRef
)reinflatedAccount2
), "Compares");
882 static inline bool SOSTestStartCircleWithAccount(SOSAccount
* account
, CFMutableDictionaryRef changes
, CFStringRef cfaccount
, CFDataRef cfpassword
) {
884 if(!SOSAccountAssertUserCredentialsAndUpdate(account
, cfaccount
, cfpassword
, NULL
))
886 is(ProcessChangesUntilNoChange(changes
, account
, NULL
), 1, "updates");
887 if(!SOSAccountResetToOffering_wTxn(account
, NULL
))
889 is(ProcessChangesUntilNoChange(changes
, account
, NULL
), 1, "updates");
895 static inline bool SOSTestApproveRequest(SOSAccount
* approver
, CFIndex napplicants
) {
897 CFErrorRef error
= NULL
;
898 CFArrayRef applicants
= SOSAccountCopyApplicants(approver
, &error
);
900 ok(applicants
&& CFArrayGetCount(applicants
) == napplicants
, "See %ld applicant(s) %@ (%@)", napplicants
, applicants
, error
);
901 CFStringRef approvername
= SOSAccountCopyName(approver
);
902 ok((retval
= SOSAccountAcceptApplicants(approver
, applicants
, &error
)), "%@ accepts (%@)", approvername
, error
);
903 CFReleaseNull(error
);
904 CFReleaseNull(applicants
);
905 CFReleaseNull(approvername
);
910 #define DROP_USERKEY true
911 #define KEEP_USERKEY false
913 static inline bool SOSTestJoinWith(CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
, SOSAccount
* joiner
) {
914 CFErrorRef error
= NULL
;
915 // retval will return op failures, not count failures - we'll still report those from in here.
918 FeedChangesTo(changes
, joiner
);
920 ok(SOSAccountAssertUserCredentialsAndUpdate(joiner
, cfaccount
, cfpassword
, &error
), "Credential setting (%@)", error
);
921 CFReleaseNull(error
);
923 ProcessChangesUntilNoChange(changes
, joiner
, NULL
);
925 ok(retval
= SOSAccountJoinCircles_wTxn(joiner
, &error
), "Applying (%@)", error
);
926 CFReleaseNull(error
);
930 static inline bool SOSTestJoinWithApproval(CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
, SOSAccount
* approver
, SOSAccount
* joiner
, bool dropUserKey
, int expectedCount
, bool expectCleanup
) {
931 //CFErrorRef error = NULL;
932 // retval will return op failures, not count failures - we'll still report those from in here.
935 ok(retval
= SOSTestJoinWith(cfpassword
, cfaccount
, changes
, joiner
), "Application Made");
937 ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
);
940 if(dropUserKey
) SOSAccountPurgePrivateCredential(joiner
); // lose the userKey so we don't "fix" the ghost problem yet.
943 if(expectCleanup
) nrounds
++;
945 ok(retval
&= SOSTestApproveRequest(approver
, 1), "Accepting Request to Join");
946 ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
);
948 accounts_agree_internal("Successful join shows same circle view", joiner
, approver
, false);
949 is(countPeers(joiner
), expectedCount
, "There should be %d valid peers", expectedCount
);
954 static inline bool SOSTestChangeAccountDeviceName(SOSAccount
* account
, CFStringRef name
) {
956 CFMutableDictionaryRef mygestalt
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, SOSPeerGetGestalt(account
.peerInfo
));
957 require_quiet(mygestalt
, retOut
);
958 CFDictionarySetValue(mygestalt
, kPIUserDefinedDeviceNameKey
, name
);
959 retval
= [account
.trust updateGestalt
:account newGestalt
:mygestalt
];
961 CFReleaseNull(mygestalt
);
966 * this simulates a piggy-back join at the account level
969 static inline bool SOSTestJoinThroughPiggyBack(CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
, SOSAccount
* approver
, SOSAccount
* joiner
, bool dropUserKey
, int expectedCount
, bool expectCleanup
) {
970 // retval will return op failures, not count failures - we'll still report those from in here.
972 CFErrorRef error
= NULL
;
974 ok(SOSAccountAssertUserCredentialsAndUpdate(approver
, cfaccount
, cfpassword
, &error
), "Credential setting (%@)", error
);
975 CFReleaseNull(error
);
976 // This makes sure the joiner sees the current key parameters
977 ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
);
979 SecKeyRef privKey
= SOSAccountGetPrivateCredential(approver
, &error
);
980 ok(privKey
, "got privkey from approver (%@)", error
);
981 CFReleaseNull(error
);
983 ok(SOSAccountTryUserPrivateKey(joiner
, privKey
, &error
), "assert same credentials on joiner (%@)", error
);
984 CFReleaseNull(error
);
985 // This gives the joiner a chance to see the current circle - this is the account-level equivalent of the Flush added to stashAccountCredential
986 ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
);
988 SOSPeerInfoRef joinerPI
= SOSAccountCopyApplication(joiner
, &error
);
989 ok(joinerPI
, "Joiner peerinfo available as application %@", error
);
990 CFReleaseNull(error
);
992 CFDataRef theBlob
= SOSAccountCopyCircleJoiningBlob(approver
, joinerPI
, &error
);
993 ok(theBlob
, "Made a joining blob (%@)", error
);
994 CFReleaseNull(error
);
997 bool joined
= SOSAccountJoinWithCircleJoiningBlob(joiner
, theBlob
, kPiggyV1
, &error
);
998 ok(joined
, "Joiner posted circle with itself in it (%@)", error
);
999 CFReleaseNull(error
);
1001 CFReleaseNull(joinerPI
);
1002 CFReleaseNull(theBlob
);
1004 is(ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
), 2, "updates");
1006 ok((retval
= [joiner isInCircle
:NULL
]), "Joiner is in");
1008 accounts_agree_internal("Successful join shows same circle view", joiner
, approver
, false);
1009 is(countPeers(joiner
), expectedCount
, "There should be %d valid peers", expectedCount
);
1014 static inline SOSAccount
* SOSTestCreateAccountAsSerialClone(CFStringRef name
, SOSPeerInfoDeviceClass devClass
, CFStringRef serial
, CFStringRef idsID
) {
1015 return CreateAccountForLocalChangesWithStartingAttributes(name
, CFSTR("TestSource"), devClass
, serial
, kCFBooleanTrue
, kCFBooleanTrue
, kCFBooleanTrue
, SOSTransportMessageTypeKVS
, idsID
);
1018 static inline bool SOSTestMakeGhostInCircle(CFStringRef name
, SOSPeerInfoDeviceClass devClass
, CFStringRef serial
, CFStringRef idsID
,
1019 CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
,
1020 SOSAccount
* approver
, int expectedCount
) {
1021 bool retval
= false;
1022 SOSAccount
* ghostAccount
= SOSTestCreateAccountAsSerialClone(name
, devClass
, serial
, idsID
);
1023 ok(ghostAccount
, "Created Ghost Account");
1024 require_quiet(ghostAccount
, retOut
);
1025 if(!ghostAccount
) return false;
1026 ok(retval
= SOSTestJoinWithApproval(cfpassword
, cfaccount
, changes
, approver
, ghostAccount
, DROP_USERKEY
, expectedCount
, true), "Ghost Joined Circle with expected result");
1031 static inline void SOSTestCleanup() {
1032 SOSUnregisterAllTransportMessages();
1033 SOSUnregisterAllTransportCircles();
1034 SOSUnregisterAllTransportKeyParameters();
1035 CFArrayRemoveAllValues(key_transports
);
1036 CFArrayRemoveAllValues(circle_transports
);
1037 CFArrayRemoveAllValues(message_transports
);