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 <Security/SecureObjectSync/SOSAccountPriv.h>
30 #include <Security/SecureObjectSync/SOSTransport.h>
31 #include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
32 #include <Security/SecureObjectSync/SOSPeerInfo.h>
33 #include <Security/SecureObjectSync/SOSPeerInfoV2.h>
34 #import "Security/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
35 #import <Security/SecureObjectSync/SOSAccountTrustClassic+Circle.h>
36 #import <Security/SecureObjectSync/SOSAccountTrustClassic+Identity.h>
37 #import <Security/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
];
119 static inline bool SOSAccountSetMyDSID_wTxn(SOSAccount
* acct
, CFStringRef dsid
, CFErrorRef
* error
)
121 __block
bool result
= false;
122 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
123 result
= SOSAccountSetMyDSID(txn
, dsid
, error
);
129 // Account comparison
132 #define kAccountsAgreeTestMin 9
133 #define kAccountsAgreeTestPerPeer 1
134 #define accountsAgree(x) (kAccountsAgreeTestMin + kAccountsAgreeTestPerPeer * (x))
136 static void SOSAccountResetToTest(SOSAccount
* a
, CFStringRef accountName
) {
137 SOSUnregisterTransportKeyParameter(a
.key_transport
);
138 SOSUnregisterTransportCircle((SOSCircleStorageTransport
*)a
.circle_transport
);
139 SOSUnregisterTransportMessage((SOSMessage
*)a
.kvs_message_transport
);
140 SOSUnregisterTransportMessage((SOSMessage
*)a
.ids_message_transport
);
143 CFArrayRemoveAllValue(key_transports
, (__bridge CFTypeRef
)(a
.key_transport
));
144 if(message_transports
){
145 CFArrayRemoveAllValue(message_transports
, (__bridge CFTypeRef
)a
.ids_message_transport
);
146 CFArrayRemoveAllValue(message_transports
, (__bridge CFTypeRef
)a
.kvs_message_transport
);
148 if(circle_transports
)
149 CFArrayRemoveAllValue(circle_transports
, (__bridge CFTypeRef
)a
.circle_transport
);
151 a
.circle_transport
= nil
;
152 a
.key_transport
= nil
;
153 a
.ids_message_transport
= nil
;
154 a
.kvs_message_transport
= nil
;
156 SOSAccountEnsureFactoryCirclesTest(a
, accountName
);
160 static SOSAccount
* SOSAccountCreateBasicTest(CFAllocatorRef allocator
,
161 CFStringRef accountName
,
162 CFDictionaryRef gestalt
,
163 SOSDataSourceFactoryRef factory
) {
165 a
= SOSAccountCreate(kCFAllocatorDefault
, gestalt
, factory
);
170 static SOSAccount
* SOSAccountCreateTest(CFAllocatorRef allocator
,
171 CFStringRef accountName
,
172 CFDictionaryRef gestalt
,
173 SOSDataSourceFactoryRef factory
) {
174 SOSAccount
* a
= SOSAccountCreateBasicTest(allocator
, accountName
, gestalt
, factory
);
176 SOSAccountResetToTest(a
, accountName
);
178 SOSAccountInflateTestTransportsForCircle(a
, SOSCircleGetName([a
.trust getCircle
:NULL
]), accountName
, NULL
);
182 static SOSAccount
* SOSAccountCreateTestFromData(CFAllocatorRef allocator
,
184 CFStringRef accountName
,
185 SOSDataSourceFactoryRef factory
) {
186 SOSAccount
* a
= [SOSAccount accountFromData
:(__bridge NSData
*) data
190 CFDictionaryRef gestalt
= SOSCreatePeerGestaltFromName(accountName
);
191 a
= SOSAccountCreate(allocator
, gestalt
, factory
);
192 CFReleaseNull(gestalt
);
195 SOSAccountResetToTest(a
, accountName
);
197 SOSAccountInflateTestTransportsForCircle(a
, SOSCircleGetName([a
.trust getCircle
:NULL
]), accountName
, NULL
);
203 static inline bool SOSAccountAssertUserCredentialsAndUpdate(SOSAccount
* account
,
204 CFStringRef user_account
, CFDataRef user_password
,
207 bool success
= false;
208 success
= SOSAccountAssertUserCredentials(account
, user_account
, user_password
, error
);
209 require_quiet(success
, done
);
211 success
= SOSAccountGenerationSignatureUpdate(account
, error
);
219 static void unretired_peers_is_subset(const char* label
, CFArrayRef peers
, CFSetRef allowed_peers
)
221 CFArrayForEach(peers
, ^(const void *value
) {
222 SOSPeerInfoRef pi
= (SOSPeerInfoRef
) value
;
224 CFErrorRef leftError
= NULL
;
225 CFErrorRef rightError
= NULL
;
227 ok(SOSPeerInfoIsRetirementTicket(pi
) || SOSPeerInfoIsCloudIdentity(pi
) || CFSetContainsValue(allowed_peers
, pi
), "Peer is allowed (%s) Peer: %@, Allowed %@", label
, pi
, allowed_peers
);
229 CFReleaseNull(leftError
);
230 CFReleaseNull(rightError
);
234 static void accounts_agree_internal(char *label
, SOSAccount
* left
, SOSAccount
* right
, bool check_peers
)
236 CFErrorRef error
= NULL
;
238 CFArrayRef leftPeers
= SOSAccountCopyActivePeers(left
, &error
);
239 ok(leftPeers
, "Left peers (%@) - %s", error
, label
);
240 CFReleaseNull(error
);
242 CFArrayRef rightPeers
= SOSAccountCopyActivePeers(right
, &error
);
243 ok(rightPeers
, "Right peers (%@) - %s", error
, label
);
244 CFReleaseNull(error
);
246 ok(CFEqual(leftPeers
, rightPeers
), "Matching peers (%s) Left: %@, Right: %@", label
, leftPeers
, rightPeers
);
249 CFMutableSetRef allowed_identities
= CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault
);
251 SOSFullPeerInfoRef leftFullPeer
= [left
.trust CopyAccountIdentityPeerInfo
];
254 CFSetAddValue(allowed_identities
, SOSFullPeerInfoGetPeerInfo(leftFullPeer
));
255 CFReleaseNull(leftFullPeer
);
257 SOSFullPeerInfoRef rightFullPeer
= [right
.trust CopyAccountIdentityPeerInfo
];
260 CFSetAddValue(allowed_identities
, SOSFullPeerInfoGetPeerInfo(rightFullPeer
));
261 CFReleaseNull(rightFullPeer
);
263 unretired_peers_is_subset(label
, leftPeers
, allowed_identities
);
265 CFReleaseNull(allowed_identities
);
268 CFReleaseNull(leftPeers
);
269 CFReleaseNull(rightPeers
);
272 CFArrayRef leftConcurringPeers
= SOSAccountCopyConcurringPeers(left
, &error
);
273 ok(leftConcurringPeers
, "Left peers (%@) - %s", error
, label
);
275 CFArrayRef rightConcurringPeers
= SOSAccountCopyConcurringPeers(right
, &error
);
276 ok(rightConcurringPeers
, "Right peers (%@) - %s", error
, label
);
278 ok(CFEqual(leftConcurringPeers
, rightConcurringPeers
), "Matching concurring peers Left: %@, Right: %@", leftConcurringPeers
, rightConcurringPeers
);
280 CFReleaseNull(leftConcurringPeers
);
281 CFReleaseNull(rightConcurringPeers
);
284 CFArrayRef leftApplicants
= SOSAccountCopyApplicants(left
, &error
);
285 ok(leftApplicants
, "Left Applicants (%@) - %s", error
, label
);
287 CFArrayRef rightApplicants
= SOSAccountCopyApplicants(right
, &error
);
288 ok(rightApplicants
, "Left Applicants (%@) - %s", error
, label
);
290 ok(CFEqual(leftApplicants
, rightApplicants
), "Matching applicants (%s) Left: %@, Right: %@", label
, leftApplicants
, rightApplicants
);
292 CFReleaseNull(leftApplicants
);
293 CFReleaseNull(rightApplicants
);
297 static inline void accounts_agree(char *label
, SOSAccount
* left
, SOSAccount
* right
)
299 accounts_agree_internal(label
, left
, right
, true);
307 static inline CFStringRef
CFArrayCopyCompactDescription(CFArrayRef array
) {
309 return CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("<Not an array! %@>"), array
);
311 CFMutableStringRef result
= CFStringCreateMutableCopy(kCFAllocatorDefault
, 0, CFSTR("["));
313 __block CFStringRef separator
= CFSTR("");
314 CFArrayForEach(array
, ^(const void *value
) {
315 CFStringAppendFormat(result
, NULL
, CFSTR("%@%@"), separator
, value
);
316 separator
= CFSTR(",");
319 CFStringAppend(result
, CFSTR("]"));
321 CFReleaseSafe(separator
);
326 static inline CFStringRef
SOSAccountCopyName(SOSAccount
* account
) {
327 SOSPeerInfoRef pi
= account
.peerInfo
;
329 return pi
? CFStringCreateCopy(kCFAllocatorDefault
, SOSPeerInfoGetPeerName(pi
)) : CFStringCreateWithFormat(kCFAllocatorDefault
, 0, CFSTR("%@"), account
);
332 static inline CFStringRef
CopyChangesDescription(CFDictionaryRef changes
) {
334 CFStringRef pendingChanges
= CFDictionaryCopyCompactDescription((CFDictionaryRef
) CFDictionaryGetValue(changes
, kCFNull
));
336 CFMutableStringRef peerTable
= CFStringCreateMutableCopy(kCFAllocatorDefault
, 0, CFSTR("["));
338 __block CFStringRef separator
= CFSTR("");
340 CFDictionaryForEach(changes
, ^(const void *key
, const void *value
) {
341 if (CFGetTypeID(key
) == SOSAccountGetTypeID()) {
342 CFStringRef accountName
= SOSAccountCopyName((__bridge SOSAccount
*)key
);
343 CFStringRef arrayDescription
= CFArrayCopyCompactDescription(value
);
345 CFStringAppendFormat(peerTable
, NULL
, CFSTR("%@%@:%@"), separator
, accountName
, arrayDescription
);
346 separator
= CFSTR(", ");
348 CFReleaseSafe(accountName
);
349 CFReleaseSafe(arrayDescription
);
353 CFStringAppend(peerTable
, CFSTR("]"));
355 CFStringRef result
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("<TestChanges %@ %@>"), pendingChanges
, peerTable
);
356 CFReleaseNull(pendingChanges
);
357 CFReleaseNull(peerTable
);
362 static void CFDictionaryOverlayDictionary(CFMutableDictionaryRef target
, CFMutableDictionaryRef overlay
) {
363 CFMutableSetRef keysToRemove
= CFSetCreateMutableForCFTypes(kCFAllocatorDefault
);
365 CFDictionaryForEach(overlay
, ^(const void *key
, const void *value
) {
366 const void *current_value
= CFDictionaryGetValue(target
, key
);
367 if (CFEqualSafe(current_value
, value
) || (isNull(value
) && current_value
== NULL
)) {
368 CFSetAddValue(keysToRemove
, key
);
370 CFDictionarySetValue(target
, key
, value
);
374 CFSetForEach(keysToRemove
, ^(const void *value
) {
375 CFDictionaryRemoveValue(overlay
, value
);
378 CFReleaseNull(keysToRemove
);
381 static void CFArrayAppendKeys(CFMutableArrayRef keys
, CFDictionaryRef newKeysToAdd
) {
382 CFDictionaryForEach(newKeysToAdd
, ^(const void *key
, const void *value
) {
383 CFArrayAppendValue(keys
, key
);
387 static bool AddNewChanges(CFMutableDictionaryRef changesRecord
, CFMutableDictionaryRef newKeysAndValues
, SOSAccount
* sender
)
389 __block
bool changes_added
= false;
390 CFMutableDictionaryRef emptyDictionary
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
391 CFDictionaryAddValue(changesRecord
, kCFNull
, emptyDictionary
);
392 CFReleaseNull(emptyDictionary
);
394 CFDictionaryOverlayDictionary((CFMutableDictionaryRef
) CFDictionaryGetValue(changesRecord
, kCFNull
), newKeysAndValues
);
396 CFDictionaryForEach(changesRecord
, ^(const void *key
, const void *value
) {
397 if (isArray(value
) && (sender
== NULL
|| sender
!= key
)) {
398 CFArrayAppendKeys((CFMutableArrayRef
) value
, newKeysAndValues
);
399 if (CFDictionaryGetCount(newKeysAndValues
))
400 changes_added
= true;
405 secnotice("changes", "Changes from %@: %@", sender
, newKeysAndValues
);
407 CFDictionaryRemoveAllValues(newKeysAndValues
);
409 return changes_added
;
412 static bool FillAllChanges(CFMutableDictionaryRef changes
) {
413 __block
bool changed
= false;
415 CFMutableSetRef changedAccounts
= CFSetCreateMutable(kCFAllocatorDefault
, 0, NULL
);
417 CFArrayForEach(key_transports
, ^(const void *value
) {
418 CKKeyParameterTest
* tpt
= (__bridge CKKeyParameterTest
*) value
;
419 if (AddNewChanges(changes
, SOSTransportKeyParameterTestGetChanges(tpt
), SOSTransportKeyParameterTestGetAccount(tpt
))) {
421 CFSetAddValue(changedAccounts
, (__bridge CFTypeRef
)(SOSTransportKeyParameterTestGetAccount(tpt
)));
423 SOSTransportKeyParameterTestClearChanges(tpt
);
425 CFArrayForEach(circle_transports
, ^(const void *value
) {
426 SOSCircleStorageTransportTest
*tpt
= (__bridge SOSCircleStorageTransportTest
*) value
;
427 if (AddNewChanges(changes
, [tpt SOSTransportCircleTestGetChanges
], [tpt getAccount
])) {
429 CFSetAddValue(changedAccounts
, (__bridge CFTypeRef
)SOSTransportCircleTestGetAccount(tpt
));
431 SOSTransportCircleTestClearChanges(tpt
);
433 CFArrayForEach(message_transports
, ^(const void *value
) {
434 if([(__bridge SOSMessage
*)value SOSTransportMessageGetTransportType
] == kKVSTest
){
435 SOSMessageKVSTest
* tpt
= (__bridge SOSMessageKVSTest
*) value
;
436 CFDictionaryRemoveValue(SOSTransportMessageKVSTestGetChanges(tpt
), kCFNull
);
437 if (AddNewChanges(changes
, SOSTransportMessageKVSTestGetChanges(tpt
), SOSTransportMessageKVSTestGetAccount(tpt
))) {
439 CFSetAddValue(changedAccounts
, (__bridge CFTypeRef
)(SOSTransportMessageKVSTestGetAccount(tpt
)));
441 SOSTransportMessageTestClearChanges(tpt
);
443 else if([(__bridge SOSMessage
*)value SOSTransportMessageGetTransportType
] == kIDSTest
){
444 SOSMessageIDSTest
* ids
= (__bridge SOSMessageIDSTest
*) value
;
445 CFDictionaryRemoveValue(SOSTransportMessageIDSTestGetChanges(ids
), kCFNull
);
446 if (AddNewChanges(changes
, SOSTransportMessageIDSTestGetChanges(ids
), SOSTransportMessageIDSTestGetAccount(ids
))) {
448 CFSetAddValue(changedAccounts
, (__bridge CFTypeRef
)(SOSTransportMessageIDSTestGetAccount(ids
)));
450 SOSTransportMessageIDSTestClearChanges(ids
);
454 secnotice("process-changes", "Accounts with change (%@): %@", changed
? CFSTR("YES") : CFSTR("NO"), changedAccounts
);
456 CFReleaseNull(changedAccounts
);
461 static void FillChanges(CFMutableDictionaryRef changes
, SOSAccount
* forAccount
)
463 CFArrayForEach(key_transports
, ^(const void *value
) {
464 CKKeyParameterTest
* tpt
= (__bridge CKKeyParameterTest
*) value
;
465 if(CFEqualSafe((__bridge CFTypeRef
)(forAccount
), (__bridge CFTypeRef
)(SOSTransportKeyParameterTestGetAccount(tpt
)))){
466 AddNewChanges(changes
, SOSTransportKeyParameterTestGetChanges(tpt
), SOSTransportKeyParameterTestGetAccount(tpt
));
467 SOSTransportKeyParameterTestClearChanges(tpt
);
470 CFArrayForEach(circle_transports
, ^(const void *value
) {
471 SOSCircleStorageTransportTest
* tpt
= (__bridge SOSCircleStorageTransportTest
*) value
;
472 if([forAccount isEqual
: SOSTransportCircleTestGetAccount(tpt
)]){
473 AddNewChanges(changes
, [tpt SOSTransportCircleTestGetChanges
], SOSTransportCircleTestGetAccount(tpt
));
474 SOSTransportCircleTestClearChanges(tpt
);
477 CFArrayForEach(message_transports
, ^(const void *value
) {
478 if([(__bridge SOSMessage
*)value SOSTransportMessageGetTransportType
] == kKVSTest
){
479 SOSMessageKVSTest
* tpt
= (__bridge SOSMessageKVSTest
*) value
;
480 if(CFEqualSafe((__bridge CFTypeRef
)(forAccount
), (__bridge CFTypeRef
)(SOSTransportMessageKVSTestGetAccount(tpt
)))){
481 CFDictionaryRemoveValue(SOSTransportMessageKVSTestGetChanges(tpt
), kCFNull
);
482 AddNewChanges(changes
, SOSTransportMessageKVSTestGetChanges(tpt
), SOSTransportMessageKVSTestGetAccount(tpt
));
483 SOSTransportMessageTestClearChanges(tpt
);
487 SOSMessageIDSTest
* tpt
= (__bridge SOSMessageIDSTest
*) value
;
488 if(CFEqualSafe((__bridge CFTypeRef
)(forAccount
), (__bridge CFTypeRef
)(SOSTransportMessageIDSTestGetAccount(tpt
)))){
489 CFDictionaryRemoveValue(SOSTransportMessageIDSTestGetChanges(tpt
), kCFNull
);
490 AddNewChanges(changes
, SOSTransportMessageIDSTestGetChanges(tpt
), SOSTransportMessageIDSTestGetAccount(tpt
));
491 SOSTransportMessageIDSTestClearChanges(tpt
);
498 static inline void FillChangesMulti(CFMutableDictionaryRef changes
, SOSAccount
* account
, ...)
500 SOSAccount
* next_account
= account
;
502 va_start(argp
, account
);
503 while(next_account
!= NULL
) {
504 FillChanges(changes
, next_account
);
505 next_account
= va_arg(argp
, SOSAccount
*);
509 static inline CFMutableArrayRef
CFDictionaryCopyKeys(CFDictionaryRef dictionary
)
511 CFMutableArrayRef result
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
513 CFArrayAppendKeys(result
, dictionary
);
518 #define kFeedChangesToTestCount 1
519 static inline void FeedChangesTo(CFMutableDictionaryRef changes
, SOSAccount
* acct
)
521 CFDictionaryRef full_list
= (CFDictionaryRef
) CFDictionaryGetValue(changes
, kCFNull
);
523 if (!isDictionary(full_list
))
524 return; // Nothing recorded to send!
526 CFMutableArrayRef account_pending_keys
= (CFMutableArrayRef
)CFDictionaryGetValue(changes
, (__bridge CFTypeRef
)(acct
));
528 if (!isArray(account_pending_keys
)) {
529 account_pending_keys
= CFDictionaryCopyKeys(full_list
);
530 CFDictionaryAddValue(changes
, (__bridge CFTypeRef
)(acct
), account_pending_keys
);
531 CFReleaseSafe(account_pending_keys
); // The dictionary keeps it, we don't retain it here.
534 CFMutableDictionaryRef account_pending_messages
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
535 CFArrayForEach(account_pending_keys
, ^(const void *value
) {
536 CFDictionaryAddValue(account_pending_messages
, value
, CFDictionaryGetValue(full_list
, value
));
539 secnotice("changes", "Changes for %@:", SOSTransportKeyParameterTestGetName((CKKeyParameterTest
*) acct
.key_transport
));
541 CFDictionaryForEach(account_pending_messages
, ^(const void *key
, const void *value
) {
542 secnotice("changes", " %@", key
);
545 if(CFDictionaryGetCount(account_pending_messages
) == 0) {
546 CFReleaseNull(account_pending_messages
);
550 __block CFMutableArrayRef handled
= NULL
;
551 [acct performTransaction
:^(SOSAccountTransaction
* _Nonnull txn
) {
552 __block CFErrorRef error
= NULL
;
553 ok(handled
= SOSTransportDispatchMessages(txn
, account_pending_messages
, &error
), "SOSTransportHandleMessages failed (%@)", error
);
554 CFReleaseNull(error
);
557 if (isArray(handled
)) {
558 CFArrayForEach(handled
, ^(const void *value
) {
559 CFArrayRemoveAllValue(account_pending_keys
, value
);
562 CFReleaseNull(account_pending_messages
);
563 CFReleaseNull(handled
);
566 #define kFeedChangesToMultieTestCountPer 1
568 static inline void FeedChangesToMultiV(CFMutableDictionaryRef changes
, va_list argp
)
570 SOSAccount
* account
= NULL
;
571 while((account
= va_arg(argp
, SOSAccount
*)) != NULL
) {
572 FeedChangesTo(changes
, account
);
576 static inline void FeedChangesToMulti(CFMutableDictionaryRef changes
, ...)
579 va_start(argp
, changes
);
581 FeedChangesToMultiV(changes
, argp
);
586 static inline void InjectChangeToMulti(CFMutableDictionaryRef changes
,
587 CFStringRef changeKey
, CFTypeRef changeValue
, ...)
589 CFMutableDictionaryRef changes_to_send
= CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault
,
590 changeKey
, changeValue
,
592 AddNewChanges(changes
, changes_to_send
, NULL
);
593 CFReleaseNull(changes_to_send
);
596 va_start(argp
, changeValue
);
597 FeedChangesToMultiV(changes
, argp
);
601 static inline bool ProcessChangesOnceV(CFMutableDictionaryRef changes
, va_list argp
)
603 bool result
= FillAllChanges(changes
);
605 FeedChangesToMultiV(changes
, argp
);
611 static inline bool ProcessChangesOnce(CFMutableDictionaryRef changes
, ...)
614 va_start(argp
, changes
);
616 bool result
= ProcessChangesOnceV(changes
, argp
);
623 static inline int ProcessChangesUntilNoChange(CFMutableDictionaryRef changes
, ...)
626 va_start(argp
, changes
);
629 bool new_data
= false;
632 va_copy(argp_copy
, argp
);
634 new_data
= ProcessChangesOnceV(changes
, argp_copy
);
648 // MARK: Account creation
651 static CFStringRef
modelFromType(SOSPeerInfoDeviceClass cls
) {
653 case SOSPeerInfo_macOS
: return CFSTR("Mac Pro");
654 case SOSPeerInfo_iOS
: return CFSTR("iPhone");
655 case SOSPeerInfo_iCloud
: return CFSTR("iCloud");
656 case SOSPeerInfo_watchOS
: return CFSTR("needWatchOSDeviceName");
657 case SOSPeerInfo_tvOS
: return CFSTR("needTVOSDeviceName");
658 default: return CFSTR("GENERICOSTHING");
662 static inline SOSAccount
* CreateAccountForLocalChangesWithStartingAttributes(CFStringRef name
, CFStringRef data_source_name
, SOSPeerInfoDeviceClass devclass
, CFStringRef serial
, CFBooleanRef preferIDS
, CFBooleanRef preferIDSFragmentation
, CFBooleanRef preferIDSACKModel
, CFStringRef transportType
, CFStringRef deviceID
) {
664 SOSDataSourceFactoryRef factory
= SOSTestDataSourceFactoryCreate();
665 SOSDataSourceRef ds
= SOSTestDataSourceCreate();
666 SOSTestDataSourceFactorySetDataSource(factory
, data_source_name
, ds
);
667 SOSEngineRef engine
= SOSEngineCreate(ds
, NULL
);
670 CFMutableDictionaryRef gestalt
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
671 CFDictionaryAddValue(gestalt
, kPIUserDefinedDeviceNameKey
, name
);
672 CFDictionaryAddValue(gestalt
, kPIDeviceModelNameKey
, modelFromType(devclass
));
673 CFDictionaryAddValue(gestalt
, kPIOSVersionKey
, CFSTR("TESTRUN"));
675 CFMutableDictionaryRef testV2dict
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
676 CFDictionaryAddValue(testV2dict
, sSerialNumberKey
, serial
);
677 CFDictionaryAddValue(testV2dict
, sPreferIDS
, preferIDS
);
678 CFDictionaryAddValue(testV2dict
, sPreferIDSFragmentation
, preferIDSFragmentation
);
679 CFDictionaryAddValue(testV2dict
, sPreferIDSACKModel
, preferIDSACKModel
);
680 CFDictionaryAddValue(testV2dict
, sTransportType
, transportType
);
681 CFDictionaryAddValue(testV2dict
, sDeviceID
, deviceID
);
682 SOSAccount
* result
= SOSAccountCreateTest(kCFAllocatorDefault
, name
, gestalt
, factory
);
683 [result
.trust updateV2Dictionary
:result v2
:testV2dict
];
685 CFReleaseSafe(SOSAccountCopyUUID(result
));
687 CFReleaseNull(gestalt
);
688 CFReleaseNull(testV2dict
);
693 static CFStringRef sGestaltTest
= CFSTR("GestaltTest");
694 static CFStringRef sV2Test
= CFSTR("V2Test");
695 static inline CFDictionaryRef
SOSTestSaveStaticAccountState(SOSAccount
* account
) {
696 CFMutableDictionaryRef retval
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
697 CFDictionaryRef gestalt
= SOSAccountCopyGestalt(account
);
698 CFDictionaryRef v2dictionary
= SOSAccountCopyV2Dictionary(account
);
699 CFDictionaryAddValue(retval
, sGestaltTest
, gestalt
);
700 CFDictionaryAddValue(retval
, sV2Test
, v2dictionary
);
701 CFReleaseNull(gestalt
);
702 CFReleaseNull(v2dictionary
);
706 static inline void SOSTestRestoreAccountState(SOSAccount
* account
, CFDictionaryRef saved
) {
707 [account
.trust updateGestalt
:account newGestalt
:CFDictionaryGetValue(saved
, sGestaltTest
)];
708 [account
.trust updateV2Dictionary
:account v2
:CFDictionaryGetValue(saved
, sV2Test
)];
711 static CFStringRef
CFStringCreateRandomHexWithLength(size_t len
) {
713 CFDataRef data
= CFDataCreateWithRandomBytes(len
/2);
714 CFMutableStringRef retval
= CFStringCreateMutable(kCFAllocatorDefault
, len
);
715 CFStringAppendHexData(retval
, data
);
720 static inline SOSAccount
* CreateAccountForLocalChanges(CFStringRef name
, CFStringRef data_source_name
)
722 CFStringRef randomSerial
= CFStringCreateRandomHexWithLength(8);
723 CFStringRef randomDevID
= CFStringCreateRandomHexWithLength(16);
724 SOSAccount
* retval
= CreateAccountForLocalChangesWithStartingAttributes(name
, data_source_name
, SOSPeerInfo_iOS
, randomSerial
,
725 kCFBooleanTrue
, kCFBooleanTrue
, kCFBooleanTrue
, SOSTransportMessageTypeKVS
, randomDevID
);
727 CFReleaseNull(randomSerial
);
728 CFReleaseNull(randomDevID
);
732 static inline SOSAccount
* CreateAccountForLocalChangesFromData(CFDataRef flattenedData
, CFStringRef name
, CFStringRef data_source_name
)
734 SOSDataSourceFactoryRef factory
= SOSTestDataSourceFactoryCreate();
735 SOSDataSourceRef ds
= SOSTestDataSourceCreate();
736 SOSTestDataSourceFactorySetDataSource(factory
, data_source_name
, ds
);
737 SOSEngineRef engine
= SOSEngineCreate(ds
, NULL
);
740 SOSAccount
* result
= SOSAccountCreateTestFromData(kCFAllocatorDefault
, flattenedData
, name
, factory
);
747 static inline int countPeers(SOSAccount
* account
) {
748 CFErrorRef error
= NULL
;
751 peers
= SOSAccountCopyPeers(account
, &error
);
754 int retval
= (int) CFArrayGetCount(peers
);
755 CFReleaseNull(error
);
756 CFReleaseNull(peers
);
760 static inline int countActivePeers(SOSAccount
* account
) {
761 CFErrorRef error
= NULL
;
764 peers
= SOSAccountCopyActivePeers(account
, &error
);
767 int retval
= (int) CFArrayGetCount(peers
);
768 CFReleaseNull(error
);
769 CFReleaseNull(peers
);
773 static inline int countActiveValidPeers(SOSAccount
* account
) {
774 CFErrorRef error
= NULL
;
777 peers
= SOSAccountCopyActiveValidPeers(account
, &error
);
780 int retval
= (int) CFArrayGetCount(peers
);
781 CFReleaseNull(error
);
782 CFReleaseNull(peers
);
786 static inline int countApplicants(SOSAccount
* account
) {
787 CFErrorRef error
= NULL
;
788 CFArrayRef applicants
= SOSAccountCopyApplicants(account
, &error
);
791 if(applicants
) retval
= (int)CFArrayGetCount(applicants
);
792 CFReleaseNull(error
);
793 CFReleaseNull(applicants
);
798 static inline void showActiveValidPeers(SOSAccount
* account
) {
799 CFErrorRef error
= NULL
;
802 peers
= SOSAccountCopyActiveValidPeers(account
, &error
);
803 CFArrayForEach(peers
, ^(const void *value
) {
804 SOSPeerInfoRef pi
= (SOSPeerInfoRef
) value
;
805 ok(0, "Active Valid Peer %@", pi
);
807 CFReleaseNull(peers
);
810 #define ok_or_quit(COND,MESSAGE,LABEL) ok(COND, MESSAGE); if(!(COND)) goto LABEL
812 static inline bool testAccountPersistence(SOSAccount
* account
) {
813 SOSDataSourceFactoryRef test_factory
= SOSTestDataSourceFactoryCreate();
814 SOSDataSourceRef test_source
= SOSTestDataSourceCreate();
815 SOSTestDataSourceFactorySetDataSource(test_factory
, CFSTR("TestType"), test_source
);
816 NSError
* error
= nil
;
819 SOSAccount
* reinflatedAccount
= NULL
;
820 NSData
* accountDER
= NULL
;
822 SOSAccountCheckHasBeenInSync_wTxn(account
);
824 // DER encode account to accountData - this allows checking discreet DER functions
825 size_t size
= [account
.trust getDEREncodedSize
:account err
:&error
];
827 uint8_t buffer
[size
];
828 uint8_t* start
= [account
.trust encodeToDER
:account err
:&error start
:buffer end
:buffer
+ sizeof(buffer
)];
831 ok_or_quit(start
, "successful encoding", errOut
);
832 ok_or_quit(start
== buffer
, "Used whole buffer", errOut
);
834 accountDER
= [NSData dataWithBytes
:buffer length
:size
];
835 ok_or_quit(accountDER
, "Made CFData for Account", errOut
);
838 // Re-inflate to "inflated"
839 reinflatedAccount
= [SOSAccount accountFromData
:accountDER
844 ok(reinflatedAccount
, "inflated");
845 ok(CFEqualSafe((__bridge CFTypeRef
)reinflatedAccount
, (__bridge CFTypeRef
)account
), "Compares");
847 // Repeat through SOSAccountCopyEncodedData() interface - this is the normally called combined interface
848 accountDER
= [reinflatedAccount encodedData
:&error
];
850 reinflatedAccount
= [SOSAccount accountFromData
:accountDER factory
:test_factory error
:&error
];
851 ok(reinflatedAccount
, "inflated2: %@", error
);
852 ok(CFEqual((__bridge CFTypeRef
)account
, (__bridge CFTypeRef
)reinflatedAccount
), "Compares");
861 static inline bool SOSTestStartCircleWithAccount(SOSAccount
* account
, CFMutableDictionaryRef changes
, CFStringRef cfaccount
, CFDataRef cfpassword
) {
863 if(!SOSAccountAssertUserCredentialsAndUpdate(account
, cfaccount
, cfpassword
, NULL
))
865 is(ProcessChangesUntilNoChange(changes
, account
, NULL
), 1, "updates");
866 if(!SOSAccountResetToOffering_wTxn(account
, NULL
))
868 is(ProcessChangesUntilNoChange(changes
, account
, NULL
), 1, "updates");
874 static inline bool SOSTestApproveRequest(SOSAccount
* approver
, CFIndex napplicants
) {
876 CFErrorRef error
= NULL
;
877 CFArrayRef applicants
= SOSAccountCopyApplicants(approver
, &error
);
879 ok(applicants
&& CFArrayGetCount(applicants
) == napplicants
, "See %ld applicant(s) %@ (%@)", napplicants
, applicants
, error
);
880 CFStringRef approvername
= SOSAccountCopyName(approver
);
881 ok((retval
= SOSAccountAcceptApplicants(approver
, applicants
, &error
)), "%@ accepts (%@)", approvername
, error
);
882 CFReleaseNull(error
);
883 CFReleaseNull(applicants
);
884 CFReleaseNull(approvername
);
889 #define DROP_USERKEY true
890 #define KEEP_USERKEY false
892 static inline bool SOSTestJoinWith(CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
, SOSAccount
* joiner
) {
893 CFErrorRef error
= NULL
;
894 // retval will return op failures, not count failures - we'll still report those from in here.
897 FeedChangesTo(changes
, joiner
);
899 ok(SOSAccountAssertUserCredentialsAndUpdate(joiner
, cfaccount
, cfpassword
, &error
), "Credential setting (%@)", error
);
900 CFReleaseNull(error
);
902 ProcessChangesUntilNoChange(changes
, joiner
, NULL
);
904 ok(retval
= SOSAccountJoinCircles_wTxn(joiner
, &error
), "Applying (%@)", error
);
905 CFReleaseNull(error
);
909 static inline bool SOSTestJoinWithApproval(CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
, SOSAccount
* approver
, SOSAccount
* joiner
, bool dropUserKey
, int expectedCount
, bool expectCleanup
) {
910 //CFErrorRef error = NULL;
911 // retval will return op failures, not count failures - we'll still report those from in here.
914 ok(retval
= SOSTestJoinWith(cfpassword
, cfaccount
, changes
, joiner
), "Applyication Made");
916 is(ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
), 2, "updates");
919 if(dropUserKey
) SOSAccountPurgePrivateCredential(joiner
); // lose the userKey so we don't "fix" the ghost problem yet.
922 if(expectCleanup
) nrounds
++;
924 ok(retval
&= SOSTestApproveRequest(approver
, 1), "Accepting Request to Join");
925 is(ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
), nrounds
, "updates");
927 accounts_agree_internal("Successful join shows same circle view", joiner
, approver
, false);
928 is(countPeers(joiner
), expectedCount
, "There should be %d valid peers", expectedCount
);
933 static inline bool SOSTestChangeAccountDeviceName(SOSAccount
* account
, CFStringRef name
) {
935 CFMutableDictionaryRef mygestalt
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, 0, SOSPeerGetGestalt(account
.peerInfo
));
936 require_quiet(mygestalt
, retOut
);
937 CFDictionarySetValue(mygestalt
, kPIUserDefinedDeviceNameKey
, name
);
938 retval
= [account
.trust updateGestalt
:account newGestalt
:mygestalt
];
940 CFReleaseNull(mygestalt
);
945 * this simulates a piggy-back join at the account level
948 static inline bool SOSTestJoinThroughPiggyBack(CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
, SOSAccount
* approver
, SOSAccount
* joiner
, bool dropUserKey
, int expectedCount
, bool expectCleanup
) {
949 // retval will return op failures, not count failures - we'll still report those from in here.
951 CFErrorRef error
= NULL
;
953 ok(SOSAccountAssertUserCredentialsAndUpdate(approver
, cfaccount
, cfpassword
, &error
), "Credential setting (%@)", error
);
954 CFReleaseNull(error
);
955 // This makes sure the joiner sees the current key parameters
956 ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
);
958 SecKeyRef privKey
= SOSAccountGetPrivateCredential(approver
, &error
);
959 ok(privKey
, "got privkey from approver (%@)", error
);
960 CFReleaseNull(error
);
962 ok(SOSAccountTryUserPrivateKey(joiner
, privKey
, &error
), "assert same credentials on joiner (%@)", error
);
963 CFReleaseNull(error
);
964 // This gives the joiner a chance to see the current circle - this is the account-level equivalent of the Flush added to stashAccountCredential
965 ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
);
967 SOSPeerInfoRef joinerPI
= SOSAccountCopyApplication(joiner
, &error
);
968 ok(joinerPI
, "Joiner peerinfo available as application %@", error
);
969 CFReleaseNull(error
);
971 CFDataRef theBlob
= SOSAccountCopyCircleJoiningBlob(approver
, joinerPI
, &error
);
972 ok(theBlob
, "Made a joining blob (%@)", error
);
973 CFReleaseNull(error
);
976 bool joined
= SOSAccountJoinWithCircleJoiningBlob(joiner
, theBlob
, kPiggyV1
, &error
);
977 ok(joined
, "Joiner posted circle with itself in it (%@)", error
);
978 CFReleaseNull(error
);
980 CFReleaseNull(joinerPI
);
981 CFReleaseNull(theBlob
);
983 is(ProcessChangesUntilNoChange(changes
, approver
, joiner
, NULL
), 2, "updates");
985 ok((retval
= [joiner
.trust isInCircle
:NULL
]), "Joiner is in");
987 accounts_agree_internal("Successful join shows same circle view", joiner
, approver
, false);
988 is(countPeers(joiner
), expectedCount
, "There should be %d valid peers", expectedCount
);
993 static inline SOSAccount
* SOSTestCreateAccountAsSerialClone(CFStringRef name
, SOSPeerInfoDeviceClass devClass
, CFStringRef serial
, CFStringRef idsID
) {
994 return CreateAccountForLocalChangesWithStartingAttributes(name
, CFSTR("TestSource"), devClass
, serial
, kCFBooleanTrue
, kCFBooleanTrue
, kCFBooleanTrue
, SOSTransportMessageTypeKVS
, idsID
);
997 static inline bool SOSTestMakeGhostInCircle(CFStringRef name
, SOSPeerInfoDeviceClass devClass
, CFStringRef serial
, CFStringRef idsID
,
998 CFDataRef cfpassword
, CFStringRef cfaccount
, CFMutableDictionaryRef changes
,
999 SOSAccount
* approver
, int expectedCount
) {
1000 bool retval
= false;
1001 SOSAccount
* ghostAccount
= SOSTestCreateAccountAsSerialClone(name
, devClass
, serial
, idsID
);
1002 ok(ghostAccount
, "Created Ghost Account");
1003 require_quiet(ghostAccount
, retOut
);
1004 if(!ghostAccount
) return false;
1005 ok(retval
= SOSTestJoinWithApproval(cfpassword
, cfaccount
, changes
, approver
, ghostAccount
, DROP_USERKEY
, expectedCount
, true), "Ghost Joined Circle with expected result");
1010 static inline void SOSTestCleanup() {
1011 SOSUnregisterAllTransportMessages();
1012 SOSUnregisterAllTransportCircles();
1013 SOSUnregisterAllTransportKeyParameters();
1014 CFArrayRemoveAllValues(key_transports
);
1015 CFArrayRemoveAllValues(circle_transports
);
1016 CFArrayRemoveAllValues(message_transports
);