SOSAccountTesting.h   [plain text]


/*
 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */


#ifndef SEC_SOSAccountTesting_h
#define SEC_SOSAccountTesting_h

#include <CoreFoundation/CoreFoundation.h>
#include <SecureObjectSync/SOSAccount.h>
#include <SecureObjectSync/SOSAccountPriv.h>
#include <SecureObjectSync/SOSTransport.h>
#include "SOSTransportTestTransports.h"
//
// Account comparison
//

#define kAccountsAgreeTestMin 9
#define kAccountsAgreeTestPerPeer 1
#define accountsAgree(x) (kAccountsAgreeTestMin + kAccountsAgreeTestPerPeer * (x))


static SOSAccountRef SOSAccountCreateBasicTest(CFAllocatorRef allocator,
                                               CFStringRef accountName,
                                               CFDictionaryRef gestalt,
                                               SOSDataSourceFactoryRef factory) {
    SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory);
    
    return a;
}

static SOSAccountRef SOSAccountCreateTest(CFAllocatorRef allocator,
                                          CFStringRef accountName,
                                          CFDictionaryRef gestalt,
                                          SOSDataSourceFactoryRef factory) {
    SOSAccountRef a = SOSAccountCreateBasicTest(allocator, accountName, gestalt, factory);
    
    a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
    SOSUnregisterTransportKeyParameter(a->key_transport);

    CFReleaseNull(a->circle_transports);
    CFReleaseNull(a->message_transports);
    CFReleaseNull(a->key_transport);

    SOSAccountEnsureFactoryCirclesTest(a, accountName);
    
    return a;
}


static void unretired_peers_is_subset(const char* label, CFArrayRef peers, CFArrayRef allowed_peers)
{
    CFArrayForEach(peers, ^(const void *value) {
        SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
        CFErrorRef leftError = NULL;
        CFErrorRef rightError = NULL;
        
        ok(SOSPeerInfoIsRetirementTicket(pi) || SOSPeerInfoIsCloudIdentity(pi) || CFArrayContainsValue(allowed_peers, CFRangeMake(0, CFArrayGetCount(allowed_peers)), pi), "Peer is allowed (%s) Peer: %@, Allowed %@", label, pi, allowed_peers);
        
        CFReleaseNull(leftError);
        CFReleaseNull(rightError);
    });
}

static void accounts_agree_internal(char *label, SOSAccountRef left, SOSAccountRef right, bool check_peers)
{
    CFErrorRef error = NULL;
    {
        CFArrayRef leftPeers = SOSAccountCopyActivePeers(left, &error);
        ok(leftPeers, "Left peers (%@) - %s", error, label);
        CFReleaseNull(error);

        CFArrayRef rightPeers = SOSAccountCopyActivePeers(right, &error);
        ok(rightPeers, "Right peers (%@) - %s", error, label);
        CFReleaseNull(error);

        ok(CFEqual(leftPeers, rightPeers), "Matching peers (%s) Left: %@, Right: %@", label, leftPeers, rightPeers);

        if (check_peers) {
            CFMutableArrayRef allowed_identities = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);

            CFArrayRef leftIdentities = SOSAccountCopyAccountIdentityPeerInfos(left, kCFAllocatorDefault, &error);
            ok(leftIdentities, "Get identities (%@)", error);
            CFReleaseNull(error);
            
            CFArrayAppendArray(allowed_identities, leftIdentities, CFRangeMake(0, CFArrayGetCount(leftIdentities)));
            
            CFReleaseNull(leftIdentities);
            
            CFArrayRef rightIdentities = SOSAccountCopyAccountIdentityPeerInfos(right, kCFAllocatorDefault, &error);
            ok(rightIdentities, "Get identities (%@)", error);
            CFReleaseNull(error);
            
            CFArrayAppendArray(allowed_identities, rightIdentities, CFRangeMake(0, CFArrayGetCount(rightIdentities)));
            
            CFReleaseNull(rightIdentities);

            unretired_peers_is_subset(label, leftPeers, allowed_identities);
        }

        CFReleaseNull(leftPeers);
        CFReleaseNull(rightPeers);
    }
    {
        CFArrayRef leftConcurringPeers = SOSAccountCopyConcurringPeers(left, &error);
        ok(leftConcurringPeers, "Left peers (%@) - %s", error, label);

        CFArrayRef rightConcurringPeers = SOSAccountCopyConcurringPeers(right, &error);
        ok(rightConcurringPeers, "Right peers (%@) - %s", error, label);

        ok(CFEqual(leftConcurringPeers, rightConcurringPeers), "Matching concurring peers Left: %@, Right: %@", leftConcurringPeers, rightConcurringPeers);

        CFReleaseNull(leftConcurringPeers);
        CFReleaseNull(rightConcurringPeers);
    }
    {
        CFArrayRef leftApplicants = SOSAccountCopyApplicants(left, &error);
        ok(leftApplicants, "Left Applicants (%@) - %s", error, label);

        CFArrayRef rightApplicants = SOSAccountCopyApplicants(right, &error);
        ok(rightApplicants, "Left Applicants (%@) - %s", error, label);

        ok(CFEqual(leftApplicants, rightApplicants), "Matching applicants (%s) Left: %@, Right: %@", label, leftApplicants, rightApplicants);

        CFReleaseNull(leftApplicants);
        CFReleaseNull(rightApplicants);
    }
}

static inline void accounts_agree(char *label, SOSAccountRef left, SOSAccountRef right)
{
    accounts_agree_internal(label, left, right, true);
}


//
// Change handling
//

static void CFDictionaryOverlayDictionary(CFMutableDictionaryRef target, CFDictionaryRef overlay) {
    CFDictionaryForEach(overlay, ^(const void *key, const void *value) {
        CFDictionarySetValue(target, key, value);
    });
}

static void CFArrayAppendKeys(CFMutableArrayRef keys, CFDictionaryRef newKeysToAdd) {
    CFDictionaryForEach(newKeysToAdd, ^(const void *key, const void *value) {
        CFArrayAppendValue(keys, key);
    });
}

static bool AddNewChanges(CFMutableDictionaryRef changesRecord, CFMutableDictionaryRef newKeysAndValues, SOSAccountRef sender)
{
    __block bool changes_added = false;
    CFMutableDictionaryRef emptyDictionary = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
    CFDictionaryAddValue(changesRecord, kCFNull, emptyDictionary);
    CFReleaseNull(emptyDictionary);

    CFDictionaryOverlayDictionary((CFMutableDictionaryRef) CFDictionaryGetValue(changesRecord, kCFNull), newKeysAndValues);

    CFDictionaryForEach(changesRecord, ^(const void *key, const void *value) {
        if (isArray(value) && (sender == NULL || !CFEqual(sender, key))) {
            CFArrayAppendKeys((CFMutableArrayRef) value, newKeysAndValues);
            if (CFDictionaryGetCount(newKeysAndValues))
                changes_added = true;
        }
    });

    CFDictionaryRemoveAllValues(newKeysAndValues);

    return changes_added;
}

static bool FillAllChanges(CFMutableDictionaryRef changes) {
    __block bool changed = false;
    CFArrayForEach(key_transports, ^(const void *value) {
        SOSTransportKeyParameterTestRef tpt = (SOSTransportKeyParameterTestRef) value;
        changed |= AddNewChanges(changes, SOSTransportKeyParameterTestGetChanges(tpt), SOSTransportKeyParameterTestGetAccount(tpt));
    });
    CFArrayForEach(circle_transports, ^(const void *value) {
        SOSTransportCircleTestRef tpt = (SOSTransportCircleTestRef) value;
        changed |= AddNewChanges(changes, SOSTransportCircleTestGetChanges(tpt), SOSTransportCircleTestGetAccount(tpt));
    });
    CFArrayForEach(message_transports, ^(const void *value) {
        SOSTransportMessageTestRef tpt = (SOSTransportMessageTestRef) value;
        CFDictionaryRemoveValue(SOSTransportMessageTestGetChanges(tpt), kCFNull);
        changed |=AddNewChanges(changes, SOSTransportMessageTestGetChanges(tpt), SOSTransportMessageTestGetAccount(tpt));
    });

    return changed;
}

static void FillChanges(CFMutableDictionaryRef changes, SOSAccountRef forAccount)
{
    CFArrayForEach(key_transports, ^(const void *value) {
        SOSTransportKeyParameterTestRef tpt = (SOSTransportKeyParameterTestRef) value;
        if(CFEqualSafe(forAccount, SOSTransportKeyParameterTestGetAccount(tpt))){
            AddNewChanges(changes, SOSTransportKeyParameterTestGetChanges(tpt), SOSTransportKeyParameterTestGetAccount(tpt));
        }
    });
    CFArrayForEach(circle_transports, ^(const void *value) {
        SOSTransportCircleTestRef tpt = (SOSTransportCircleTestRef) value;
        if(CFEqualSafe(forAccount, SOSTransportCircleTestGetAccount(tpt))){
            AddNewChanges(changes, SOSTransportCircleTestGetChanges(tpt), SOSTransportCircleTestGetAccount(tpt));
        }
    });
    CFArrayForEach(message_transports, ^(const void *value) {
        SOSTransportMessageTestRef tpt = (SOSTransportMessageTestRef) value;
        if(CFEqualSafe(forAccount, SOSTransportMessageTestGetAccount(tpt))){
            CFDictionaryRemoveValue(SOSTransportMessageTestGetChanges(tpt), kCFNull);
            AddNewChanges(changes, SOSTransportMessageTestGetChanges(tpt), SOSTransportMessageTestGetAccount(tpt));
        }
    });

}

static inline void FillChangesMulti(CFMutableDictionaryRef changes, SOSAccountRef account, ...)
{
    SOSAccountRef next_account = account;
    va_list argp;
    va_start(argp, account);
    while(next_account != NULL) {
        FillChanges(changes, next_account);
        next_account = va_arg(argp, SOSAccountRef);
    }
}

static inline CFMutableArrayRef CFDictionaryCopyKeys(CFDictionaryRef dictionary)
{
    CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);

    CFArrayAppendKeys(result, dictionary);

    return result;
}

#define kFeedChangesToTestCount 1
static inline void FeedChangesTo(CFMutableDictionaryRef changes, SOSAccountRef account)
{
    CFDictionaryRef full_list = (CFDictionaryRef) CFDictionaryGetValue(changes, kCFNull);

    if (!isDictionary(full_list))
        return; // Nothing recorded to send!

    CFMutableArrayRef account_pending_keys = (CFMutableArrayRef)CFDictionaryGetValue(changes, account);
    if (!isArray(account_pending_keys)) {
        CFReleaseNull(account_pending_keys);

        account_pending_keys = CFDictionaryCopyKeys(full_list);
        CFDictionaryAddValue(changes, account, account_pending_keys);
        CFReleaseSafe(account_pending_keys); // The dictionary keeps it, we don't retain it here.
    }

    CFMutableArrayRef handled = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);

    secerror("Changes for %@: %@", SOSTransportKeyParameterTestGetName((SOSTransportKeyParameterTestRef) account->key_transport), account_pending_keys);

    CFErrorRef error = NULL;
    CFMutableDictionaryRef account_pending_messages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
    CFArrayForEach(account_pending_keys, ^(const void *value) {
        CFDictionaryAddValue(account_pending_messages, value, CFDictionaryGetValue(full_list, value));
    });
    
    ok(handled = SOSTransportDispatchMessages(account, account_pending_messages, &error), "SOSTransportHandleMessages failed (%@)", error);
    
    if (isArray(handled)) {
        CFArrayForEach(handled, ^(const void *value) {
            CFArrayRemoveAllValue(account_pending_keys, value);
        });
    }
    
    CFReleaseNull(handled);
    CFReleaseNull(error);
}

#define kFeedChangesToMultieTestCountPer 1

static inline void FeedChangesToMultiV(CFMutableDictionaryRef changes, va_list argp)
{
    SOSAccountRef account = NULL;
    while((account = va_arg(argp, SOSAccountRef)) != NULL) {
        FeedChangesTo(changes, account);
    }
}

static inline void FeedChangesToMulti(CFMutableDictionaryRef changes, ...)
{
    va_list argp;
    va_start(argp, changes);

    FeedChangesToMultiV(changes, argp);

    va_end(argp);
}

static inline void InjectChangeToMulti(CFMutableDictionaryRef changes,
                                       CFStringRef changeKey, CFTypeRef changeValue, ...)
{
    CFMutableDictionaryRef changes_to_send = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault,
                                                                                     changeKey, changeValue,
                                                                                     NULL);
    AddNewChanges(changes, changes_to_send, NULL);
    CFReleaseNull(changes_to_send);

    va_list argp;
    va_start(argp, changeValue);
    FeedChangesToMultiV(changes, argp);

}


static inline bool ProcessChangesOnceV(CFMutableDictionaryRef changes, va_list argp)
{
    bool result = FillAllChanges(changes);

    FeedChangesToMultiV(changes, argp);

    return result;
}


static inline bool ProcessChangesOnce(CFMutableDictionaryRef changes, ...)
{
    va_list argp;
    va_start(argp, changes);

    bool result = ProcessChangesOnceV(changes, argp);

    va_end(argp);

    return result;
}

static inline int ProcessChangesUntilNoChange(CFMutableDictionaryRef changes, ...)
{
    va_list argp;
    va_start(argp, changes);

    int result = 0;
    bool new_data = false;
    do {
        va_list argp_copy;
        va_copy(argp_copy, argp);

        new_data = ProcessChangesOnceV(changes, argp_copy);

        ++result;

        va_end(argp_copy);
    } while (new_data);
    
    va_end(argp);
    
    return result;
    
}

//
// MARK: Account creation
//

static SOSAccountRef CreateAccountForLocalChanges(CFStringRef name, CFStringRef data_source_name)
{
    SOSDataSourceFactoryRef factory = SOSTestDataSourceFactoryCreate();
    SOSDataSourceRef ds = SOSTestDataSourceCreate();
    SOSTestDataSourceFactoryAddDataSource(factory, data_source_name, ds);
    SOSEngineRef engine = SOSEngineCreate(ds, NULL);
    ds->engine = engine;
    CFDictionaryRef gestalt = SOSCreatePeerGestaltFromName(name);

    SOSAccountRef result = SOSAccountCreateTest(kCFAllocatorDefault, name, gestalt, factory);

    CFReleaseNull(gestalt);

    return result;
}


static inline int countPeers(SOSAccountRef account) {
    CFErrorRef error = NULL;
    CFArrayRef peers;
    
    peers = SOSAccountCopyPeers(account, &error);
    int retval = (int) CFArrayGetCount(peers);
    CFReleaseNull(error);
    CFReleaseNull(peers);
    return retval;
}

static inline int countActivePeers(SOSAccountRef account) {
    CFErrorRef error = NULL;
    CFArrayRef peers;
    
    peers = SOSAccountCopyActivePeers(account, &error);
    int retval = (int) CFArrayGetCount(peers);
    CFReleaseNull(error);
    CFReleaseNull(peers);
    return retval;
}

static inline int countActiveValidPeers(SOSAccountRef account) {
    CFErrorRef error = NULL;
    CFArrayRef peers;
    
    peers = SOSAccountCopyActiveValidPeers(account, &error);
    int retval = (int) CFArrayGetCount(peers);
    CFReleaseNull(error);
    CFReleaseNull(peers);
    return retval;
}

static inline int countApplicants(SOSAccountRef account) {
    CFErrorRef error = NULL;
    CFArrayRef applicants = SOSAccountCopyApplicants(account, &error);
    int retval = 0;
    
    if(applicants) retval = (int)CFArrayGetCount(applicants);
    CFReleaseNull(error);
    CFReleaseNull(applicants);
    return retval;
}


static inline void showActiveValidPeers(SOSAccountRef account) {
    CFErrorRef error = NULL;
    CFArrayRef peers;
    
    peers = SOSAccountCopyActiveValidPeers(account, &error);
    CFArrayForEach(peers, ^(const void *value) {
        SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
        ok(0, "Active Valid Peer %@", pi);
    });
    CFReleaseNull(peers);
}

#endif