SOSAccount.c   [plain text]


/*
 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
 */

/*
 * SOSAccount.c -  Implementation of the secure object syncing account.
 * An account contains a SOSCircle for each protection domain synced.
 */

#include "SOSAccountPriv.h"
#include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
#include <Security/SecureObjectSync/SOSTransportCircle.h>
#include <Security/SecureObjectSync/SOSTransportMessage.h>
#include <Security/SecureObjectSync/SOSTransportMessageIDS.h>
#include <Security/SecureObjectSync/SOSKVSKeys.h>
#include <Security/SecureObjectSync/SOSTransport.h>
#include <Security/SecureObjectSync/SOSTransportKeyParameter.h>
#include <Security/SecureObjectSync/SOSTransportKeyParameterKVS.h>
#include <Security/SecureObjectSync/SOSEngine.h>
#include <Security/SecureObjectSync/SOSPeerCoder.h>
#include <Security/SecureObjectSync/SOSInternal.h>
#include <Security/SecureObjectSync/SOSRing.h>
#include <Security/SecureObjectSync/SOSRingUtils.h>
#include <Security/SecureObjectSync/SOSPeerInfoSecurityProperties.h>
#include <Security/SecureObjectSync/SOSPeerInfoV2.h>
#include <Security/SecItemInternal.h>
#include <SOSCircle/CKBridge/SOSCloudKeychainClient.h>
#include <SOSCircle/Regressions/SOSRegressionUtilities.h>

CFGiblisWithCompareFor(SOSAccount);

const CFStringRef SOSTransportMessageTypeIDS = CFSTR("IDS");
const CFStringRef SOSTransportMessageTypeKVS = CFSTR("KVS");
const CFStringRef kSOSDSIDKey = CFSTR("AccountDSID");

const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced");


#define DATE_LENGTH 25
const CFStringRef kSOSAccountDebugScope = CFSTR("Scope");


bool SOSAccountEnsureFactoryCircles(SOSAccountRef a)
{
    bool result = false;
    CFStringRef circle_name = NULL;

    require_quiet(a, xit);
    require_quiet(a->factory, xit);

    circle_name = SOSDataSourceFactoryCopyName(a->factory);
    require(circle_name, xit);

    SOSAccountEnsureCircle(a, circle_name, NULL);

    result = true;

xit:
    // We don't own name, so don't release it.
    CFReleaseNull(circle_name);
    return result;
}


SOSAccountRef SOSAccountCreateBasic(CFAllocatorRef allocator,
                                    CFDictionaryRef gestalt,
                                    SOSDataSourceFactoryRef factory) {
    SOSAccountRef a = CFTypeAllocate(SOSAccount, struct __OpaqueSOSAccount, allocator);

    a->queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);

    a->notification_cleanups = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);

    a->gestalt = CFRetainSafe(gestalt);

    a->trusted_circle = NULL;
    a->trusted_rings = CFDictionaryCreateMutableForCFTypes(allocator);
    a->backups = CFDictionaryCreateMutableForCFTypes(allocator);
    a->my_identity = NULL;
    a->retirees = CFSetCreateMutableForSOSPeerInfosByID(allocator);

    a->factory = factory; // We adopt the factory. kthanksbai.
    
    a->_user_private = NULL;
    a->_password_tmp = NULL;
    a->user_private_timer = NULL;

    a->change_blocks = CFArrayCreateMutableForCFTypes(allocator);

    a->departure_code = kSOSNeverAppliedToCircle;

    a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
    a->circle_transport = NULL;
    a->kvs_message_transport = NULL;
    a->ids_message_transport = NULL;
    a->expansion = CFDictionaryCreateMutableForCFTypes(allocator);
    
    return a;
}

SOSSecurityPropertyResultCode SOSAccountUpdateSecurityProperty(SOSAccountRef account, CFStringRef property, SOSSecurityPropertyActionCode actionCode, CFErrorRef *error) {
    SOSSecurityPropertyResultCode retval = kSOSCCGeneralSecurityPropertyError;
    bool updateCircle = false;
    require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
    require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
    retval = SOSFullPeerInfoUpdateSecurityProperty(account->my_identity, actionCode, property, error);
    
    if(actionCode == kSOSCCSecurityPropertyEnable && retval == kSOSCCSecurityPropertyValid) {
        updateCircle = true;
    } else if(actionCode == kSOSCCSecurityPropertyDisable && retval == kSOSCCSecurityPropertyNotValid) {
        updateCircle = true;
    } else if(actionCode == kSOSCCSecurityPropertyPending) {
        updateCircle = true;
    }
    
    if (updateCircle) {
        SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
            secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for security property change");
            return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
        });
    }
    
errOut:
    return retval;
}

SOSSecurityPropertyResultCode SOSAccountSecurityPropertyStatus(SOSAccountRef account, CFStringRef property, CFErrorRef *error) {
    SOSSecurityPropertyResultCode retval = kSOSCCGeneralViewError;
    require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
    require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
    retval = SOSFullPeerInfoSecurityPropertyStatus(account->my_identity, property, error);
errOut:
    return retval;
}

bool SOSAccountUpdateGestalt(SOSAccountRef account, CFDictionaryRef new_gestalt)
{
    if (CFEqualSafe(new_gestalt, account->gestalt))
        return false;

    if (account->trusted_circle && account->my_identity
        && SOSFullPeerInfoUpdateGestalt(account->my_identity, new_gestalt, NULL)) {
        SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
            secnotice("circleChange", "dCalling SOSCircleUpdatePeerInfo for gestalt change");
            return SOSCircleUpdatePeerInfo(circle_to_change, SOSAccountGetMyPeerInfo(account));
        });
    }

    CFRetainAssign(account->gestalt, new_gestalt);
    return true;
}

bool SOSAccountUpdateDSID(SOSAccountRef account, CFStringRef dsid){
    SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL);
    //send new DSID over account changed
    SOSTransportCircleSendOfficialDSID(account->circle_transport, dsid, NULL);
    
    return true;
}

bool SOSAccountUpdateFullPeerInfo(SOSAccountRef account, CFSetRef minimumViews) {
    if (account->trusted_circle && account->my_identity) {
        if(SOSFullPeerInfoUpdateToCurrent(account->my_identity, minimumViews)) {
            SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
                secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
                return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
            });
        }
    }
    
    return true;
}

SOSViewResultCode SOSAccountUpdateView(SOSAccountRef account, CFStringRef viewname, SOSViewActionCode actionCode, CFErrorRef *error) {
    SOSViewResultCode retval = kSOSCCGeneralViewError;
    SOSViewResultCode currentStatus = kSOSCCGeneralViewError;
    bool updateCircle = false;
    require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
    require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
    require_action_quiet((actionCode == kSOSCCViewEnable) || (actionCode == kSOSCCViewDisable), errOut, CFSTR("Invalid View Action"));
    currentStatus = SOSAccountViewStatus(account, viewname, error);
    require_action_quiet((currentStatus == kSOSCCViewNotMember) || (currentStatus == kSOSCCViewMember), errOut, CFSTR("View Membership Not Actionable"));

    if (CFEqualSafe(viewname, kSOSViewKeychainV0)) {
        // The V0 view switches on and off all on it's own, we allow people the delusion
        // of control and status if it's what we're stuck at., otherwise error.
        if (SOSAccountSyncingV0(account)) {
            require_action_quiet(actionCode = kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now"));
            retval = kSOSCCViewMember;
        } else {
            require_action_quiet(actionCode = kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now"));
            retval = kSOSCCViewNotMember;
        }
    } else if (SOSAccountSyncingV0(account) && SOSViewsIsV0Subview(viewname)) {
        // Subviews of V0 syncing can't be turned off if V0 is on.
        require_action_quiet(actionCode = kSOSCCViewDisable, errOut, CFSTR("Have V0 peer can't disable"));
        retval = kSOSCCViewMember;
    } else {
        if(actionCode == kSOSCCViewEnable && currentStatus == kSOSCCViewNotMember) {
            retval = SOSFullPeerInfoUpdateViews(account->my_identity, actionCode, viewname, error);
            if(retval == kSOSCCViewMember) updateCircle = true;
        } else if(actionCode == kSOSCCViewDisable && currentStatus == kSOSCCViewMember) {
            retval = SOSFullPeerInfoUpdateViews(account->my_identity, actionCode, viewname, error);
            if(retval == kSOSCCViewNotMember) updateCircle = true;
        } else {
            retval = currentStatus;
        }

        if (updateCircle) {
            SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
                secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for views change");
                return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
            });
        }
    }
    
errOut:
    return retval;
}

SOSViewResultCode SOSAccountViewStatus(SOSAccountRef account, CFStringRef viewname, CFErrorRef *error) {
    SOSViewResultCode retval = kSOSCCGeneralViewError;
    require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
    require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
    retval = SOSFullPeerInfoViewStatus(account->my_identity, viewname, error);
errOut:
    return retval;
}

static void dumpViewSet(CFStringRef label, CFSetRef views) {
    if(views) {
        secnotice("circleChange", "%@ list: %@", label, views);
    } else {
        secnotice("circleChange", "No %@ list provided.", label);
    }
}

bool SOSAccountUpdateViewSets(SOSAccountRef account, CFSetRef enabledViews, CFSetRef disabledViews) {
    bool updateCircle = false;
    dumpViewSet(CFSTR("Enabled"), enabledViews);
    dumpViewSet(CFSTR("Disabled"), disabledViews);
    
    require_action_quiet(account->trusted_circle, errOut, secnotice("views", "Attempt to set viewsets with no trusted circle"));
    require_action_quiet(account->my_identity, errOut, secnotice("views", "Attempt to set viewsets with no fullPeerInfo"));
    require_action_quiet(enabledViews || disabledViews, errOut, secnotice("views", "No work to do"));
    
    // Copy my views
    SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInfo(account);
    SOSPeerInfoRef  pi = SOSPeerInfoCreateCopy(kCFAllocatorDefault, SOSFullPeerInfoGetPeerInfo(fpi), NULL);
    
    require_action_quiet(pi, errOut, secnotice("views", "Couldn't copy PeerInfoRef"));
    
    
    if(!SOSPeerInfoVersionIsCurrent(pi)) {
        if(!SOSPeerInfoUpdateToV2(pi, NULL)) {
            secnotice("views", "Unable to update peer to V2- can't update views");
            return false;
        }
    }
    
    if(enabledViews) updateCircle = SOSViewSetEnable(pi, enabledViews);
    if(disabledViews) updateCircle |= SOSViewSetDisable(pi, disabledViews);
    
    /* UPDATE FULLPEERINFO VIEWS */
    
    if (updateCircle && SOSFullPeerInfoUpdateToThisPeer(fpi, pi, NULL)) {
        SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
            secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for views change");
            return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
        });
    }
    
errOut:
    return updateCircle;
}


SOSAccountRef SOSAccountCreate(CFAllocatorRef allocator,
                               CFDictionaryRef gestalt,
                               SOSDataSourceFactoryRef factory) {
    SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory);

    
    SOSAccountEnsureFactoryCircles(a);

    return a;
}

static void SOSAccountDestroy(CFTypeRef aObj) {
    SOSAccountRef a = (SOSAccountRef) aObj;

    // We don't own the factory, merely have a reference to the singleton
    //    Don't free it.
    //   a->factory

    SOSAccountCleanupNotificationForAllPeers(a);

    SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(a->factory, SOSCircleGetName(a->trusted_circle), NULL);

    if (engine)
        SOSEngineSetSyncCompleteListenerQueue(engine, NULL);

    dispatch_sync(a->queue, ^{
        CFReleaseNull(a->gestalt);

        CFReleaseNull(a->my_identity);
        CFReleaseNull(a->trusted_circle);
        CFReleaseNull(a->trusted_rings);
        CFReleaseNull(a->backups);
        CFReleaseNull(a->retirees);

        a->user_public_trusted = false;
        CFReleaseNull(a->user_public);
        CFReleaseNull(a->user_key_parameters);

        SOSAccountPurgePrivateCredential(a);
        CFReleaseNull(a->previous_public);
        CFReleaseNull(a->_user_private);
        CFReleaseNull(a->_password_tmp);

        a->departure_code = kSOSNeverAppliedToCircle;
        CFReleaseNull(a->kvs_message_transport);
        CFReleaseNull(a->ids_message_transport);
        CFReleaseNull(a->key_transport);
        CFReleaseNull(a->circle_transport);
        dispatch_release(a->queue);
        CFReleaseNull(a->notification_cleanups);

        dispatch_release(a->user_private_timer);
        CFReleaseNull(a->change_blocks);
        CFReleaseNull(a->expansion);

    });
}

static OSStatus do_delete(CFDictionaryRef query) {
    OSStatus result;
    
    result = SecItemDelete(query);
    if (result) {
        secerror("SecItemDelete: %d", (int)result);
    }
     return result;
}

static int
do_keychain_delete_aks_bags()
{
    OSStatus result;
    CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
                                 kSecClass,           kSecClassGenericPassword,
                                 kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
                                 kSecAttrAccount,     CFSTR("SecureBackupPublicKeybag"),
                                 kSecAttrService,     CFSTR("SecureBackupService"),
                                 kSecAttrSynchronizable, kCFBooleanTrue,
                                 kSecUseTombstones,     kCFBooleanFalse,
                                 NULL);

    result = do_delete(item);
    CFReleaseSafe(item);
    
    return result;
}

static int
do_keychain_delete_identities()
{
    OSStatus result;
    CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
                                kSecClass, kSecClassKey,
                                kSecAttrSynchronizable, kCFBooleanTrue,
                                kSecUseTombstones, kCFBooleanFalse,
                                kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
                                NULL);
  
    result = do_delete(item);
    CFReleaseSafe(item);
    
    return result;
}

static int
do_keychain_delete_lakitu()
{
    OSStatus result;
    CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
                                                        kSecClass, kSecClassGenericPassword,
                                                        kSecAttrSynchronizable, kCFBooleanTrue,
                                                        kSecUseTombstones, kCFBooleanFalse,
                                                        kSecAttrAccessGroup, CFSTR("com.apple.lakitu"),
                                                        kSecAttrAccount, CFSTR("EscrowServiceBypassToken"),
                                                        kSecAttrService, CFSTR("EscrowService"),
                                                        NULL);
    
    result = do_delete(item);
    CFReleaseSafe(item);
    
    return result;
}

static int
do_keychain_delete_sbd()
{
    OSStatus result;
    CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
                                                        kSecClass, kSecClassGenericPassword,
                                                        kSecAttrSynchronizable, kCFBooleanTrue,
                                                        kSecUseTombstones, kCFBooleanFalse,
                                                        kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
                                                        NULL);
    
    result = do_delete(item);
    CFReleaseSafe(item);
    
    return result;
}

void SOSAccountSetToNew(SOSAccountRef a) {
    secnotice("accountChange", "Setting Account to New");
    int result = 0;
    
    CFReleaseNull(a->my_identity);
    CFReleaseNull(a->trusted_circle);
    CFReleaseNull(a->trusted_rings);
    CFReleaseNull(a->backups);
    CFReleaseNull(a->retirees);

    CFReleaseNull(a->user_key_parameters);
    CFReleaseNull(a->user_public);
    CFReleaseNull(a->previous_public);
    CFReleaseNull(a->_user_private);
    CFReleaseNull(a->_password_tmp);

    CFReleaseNull(a->key_transport);
    CFReleaseNull(a->circle_transport);
    CFReleaseNull(a->kvs_message_transport);
    CFReleaseNull(a->ids_message_transport);
    CFReleaseNull(a->expansion);
    
    /* remove all syncable items */
    result = do_keychain_delete_aks_bags();
    secdebug("set to new", "result for deleting aks bags: %d", result);

    result = do_keychain_delete_identities();
    secdebug("set to new", "result for deleting identities: %d", result);
 
    result = do_keychain_delete_lakitu();
    secdebug("set to new", "result for deleting lakitu: %d", result);
    
    result = do_keychain_delete_sbd();
    secdebug("set to new", "result for deleting sbd: %d", result);

    a->user_public_trusted = false;
    a->departure_code = kSOSNeverAppliedToCircle;
    a->user_private_timer = 0;
    a->lock_notification_token = 0;

    // keeping gestalt;
    // keeping factory;
    // Live Notification
    // change_blocks;
    // update_interest_block;
    // update_block;

    a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
    a->circle_transport = NULL;
    a->kvs_message_transport = NULL;
    a->ids_message_transport = NULL;

    a->trusted_rings = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
    a->backups = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);

    a->retirees = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
    a->expansion = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);

    SOSAccountEnsureFactoryCircles(a); // Does rings too
}


static CFStringRef SOSAccountCopyFormatDescription(CFTypeRef aObj, CFDictionaryRef formatOptions) {
    SOSAccountRef a = (SOSAccountRef) aObj;
    
    CFStringRef gestaltDescription = CFDictionaryCopyCompactDescription(a->gestalt);

    CFStringRef result = CFStringCreateWithFormat(NULL, NULL, CFSTR("<SOSAccount@%p: Gestalt: %@ Circle: %@ Me: %@>"), a, gestaltDescription, a->trusted_circle, a->my_identity);

    CFReleaseNull(gestaltDescription);

    return result;
}

CFStringRef SOSAccountCreateCompactDescription(SOSAccountRef a) {

    CFStringRef gestaltDescription = CFDictionaryCopySuperCompactDescription(a->gestalt);
    
    CFStringRef result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), gestaltDescription);
    
    CFReleaseNull(gestaltDescription);
    
    return result;
}

static Boolean SOSAccountCompare(CFTypeRef lhs, CFTypeRef rhs)
{
    SOSAccountRef laccount = (SOSAccountRef) lhs;
    SOSAccountRef raccount = (SOSAccountRef) rhs;

    return CFEqualSafe(laccount->gestalt, raccount->gestalt)
        && CFEqualSafe(laccount->trusted_circle, raccount->trusted_circle)
        && CFEqualSafe(laccount->trusted_rings, raccount->trusted_rings)
        && CFEqualSafe(laccount->my_identity, raccount->my_identity);
}

dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) {
    return account->queue;
}

void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account){
    account->user_public_trusted = true;
}

SOSFullPeerInfoRef SOSAccountCopyAccountIdentityPeerInfo(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef* error)
{
    return CFRetainSafe(account->my_identity);
}

static bool SOSAccountThisDeviceCanSyncWithCircle(SOSAccountRef account) {
    bool ok = false;
    __block CFErrorRef error = NULL;

    if (!SOSAccountHasPublicKey(account, &error)) {
        CFReleaseSafe(error);
        return false;
    }
    
    bool hasID = true;
    
    require_action_quiet(account->my_identity, xit,
                         SOSCreateError(kSOSErrorBadFormat, CFSTR("Account identity not set"), NULL, &error));
    
    if ((whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent) && account->ids_message_transport) {
        CFStringRef deviceID = SOSPeerInfoCopyDeviceID(SOSFullPeerInfoGetPeerInfo(account->my_identity));
        if(deviceID == NULL || CFStringGetLength(deviceID) == 0){
            hasID = false;
            secerror("Cannot sync with all peers at this time, securityd needs the IDS device ID first.");
            
            __block bool success = false;
            
            SOSCloudKeychainGetIDSDeviceID(^(CFDictionaryRef returnedValues, CFErrorRef sync_error){
                success = (sync_error == NULL);
                if (!success) {
                    CFRetainAssign(error, sync_error);
                }
            });
            
            if(!success){
                secerror("Could not ask IDSKeychainSyncingProxy for Device ID: %@", error);
            }
            else{
                secdebug("IDS Transport", "Attempting to retrieve the IDS Device ID");
            }
        }
        CFReleaseNull(deviceID);
    }
    
    require_action_quiet(account->trusted_circle, xit,
                         SOSCreateError(kSOSErrorBadFormat, CFSTR("Account trusted circle not set"), NULL, &error));
    
    require_action_quiet(hasID, xit,
                         SOSCreateError(kSOSErrorBadFormat, CFSTR("Missing IDS device ID"), NULL, &error));
    ok = SOSCircleHasPeerWithID(account->trusted_circle,
                                SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(account->my_identity)), &error);
xit:
    if (!ok) {
        secerror("sync with device failure: %@", error);
    }
    CFReleaseSafe(error);
    return ok;
}

static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account, CFStringRef circleName, CFStringRef peerID) {
    SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(account->my_identity);
    CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);

    return myPeerID && CFEqualSafe(myPeerID, peerID);
}

static bool isDefaultsWriteSetupToSyncOverIDS(){
    return ((whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent));
}

bool SOSAccountSyncWithAllPeers(SOSAccountRef account, CFErrorRef *error)
{
    bool result = true;
    __block bool SyncingCompletedOverIDS = true;
    __block bool SyncingCompletedOverKVS = true;
    __block CFErrorRef localError = NULL;
    SOSCircleRef circle  = SOSAccountGetCircle(account, error);
    CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
    CFMutableArrayRef peerIds = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
    
    require_action_quiet(SOSAccountThisDeviceCanSyncWithCircle(account), xit,
                         SOSCreateError(kSOSErrorNoCircle, CFSTR("This device cannot sync with circle"),
                                        NULL, &localError));

    SOSCircleForEachValidPeer(circle, account->user_public, ^(SOSPeerInfoRef peer) {
        if (!SOSAccountIsThisPeerIDMe(account, SOSCircleGetName(circle), SOSPeerInfoGetPeerID(peer))) {
            if (isDefaultsWriteSetupToSyncOverIDS() && SOSPeerInfoShouldUseIDSTransport(SOSFullPeerInfoGetPeerInfo(account->my_identity), peer)) {
                secdebug("IDS Transport", "Syncing with IDS capable peers using IDS!");
                CFMutableDictionaryRef circleToIdsId = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, SOSCircleGetName(circle), SOSPeerInfoGetPeerID((peer)), NULL);
                SyncingCompletedOverIDS = SOSTransportMessageSyncWithPeers(account->ids_message_transport, circleToIdsId, &localError);
                CFReleaseNull(circleToIdsId);
            } else {
                CFArrayAppendValue(peerIds, SOSPeerInfoGetPeerID(peer));
            }
        }
    });
    if (CFArrayGetCount(peerIds)) {
        secnotice("KVS", "Syncing with KVS capable peers");
        CFDictionarySetValue(circleToPeerIDs, SOSCircleGetName(circle), peerIds);
        SyncingCompletedOverKVS &= SOSTransportMessageSyncWithPeers(account->kvs_message_transport, circleToPeerIDs, &localError);
    }

    SOSEngineRef engine = SOSTransportMessageGetEngine(account->kvs_message_transport);
    result = SOSEngineSyncWithPeers(engine, account->ids_message_transport, account->kvs_message_transport, &localError);

    result &= ((SyncingCompletedOverIDS) &&
               (SyncingCompletedOverKVS || (CFDictionaryGetCount(circleToPeerIDs) == 0)));

    if (result)
        SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);

xit:
    CFReleaseNull(circleToPeerIDs);

    if (!result) {
        secdebug("Account", "Could not sync with all peers: %@", localError);
        CFErrorPropagate(localError, error);
        localError = NULL;
    }
    CFReleaseNull(peerIds);
    CFReleaseSafe(localError);
    return result;
}

bool SOSAccountCleanupAfterPeer(SOSAccountRef account, size_t seconds, SOSCircleRef circle,
                                SOSPeerInfoRef cleanupPeer, CFErrorRef* error)
{
    bool success = true;
    
    SOSPeerInfoRef myPeerInfo = SOSFullPeerInfoGetPeerInfo(account->my_identity);
    require_action_quiet(account->my_identity && myPeerInfo, xit, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("I have no peer")));
    require_quiet(SOSCircleHasActivePeer(circle, SOSFullPeerInfoGetPeerInfo(account->my_identity), error), xit);
    
    CFStringRef cleanupPeerID = SOSPeerInfoGetPeerID(cleanupPeer);

    CFStringRef circle_name = SOSCircleGetName(circle);

    CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
    CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circle_name), cleanupPeerID);

    CFErrorRef localError = NULL;
    if (!(success &= SOSTransportMessageCleanupAfterPeerMessages(account->kvs_message_transport, circleToPeerIDs, &localError))) {
        secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID, localError);
    }

    if (account->ids_message_transport && !SOSTransportMessageCleanupAfterPeerMessages(account->ids_message_transport, circleToPeerIDs, &localError)) {
        secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID, localError);
    }

    CFReleaseNull(localError);

    if((success &= SOSPeerInfoRetireRetirementTicket(seconds, cleanupPeer))) {
        if (!(success &= SOSTransportCircleExpireRetirementRecords(account->circle_transport, circleToPeerIDs, &localError))) {
            secnotice("account", "Failed to cleanup after peer %@ retirement: %@", cleanupPeerID, localError);
        }
    }
    CFReleaseNull(localError);
    CFReleaseNull(circleToPeerIDs);

xit:
    return success;
}

bool SOSAccountCleanupRetirementTickets(SOSAccountRef account, size_t seconds, CFErrorRef* error) {
    CFMutableSetRef retirees_to_remove = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);

    __block bool success = true;

    CFSetForEach(account->retirees, ^(const void *value) {
        SOSPeerInfoRef retiree = (SOSPeerInfoRef) value;

        if (retiree) {
            // Remove the entry if it's not a retired peer or if it's retirment ticket has expired AND he's no longer in the circle.
            if (!SOSPeerInfoIsRetirementTicket(retiree) ||
                (SOSPeerInfoRetireRetirementTicket(seconds, retiree) && !SOSCircleHasActivePeer(account->trusted_circle, retiree, NULL))) {
                CFSetAddValue(retirees_to_remove, retiree);
            };
        }
    });

    CFMutableArrayRef retirees_to_cleanup = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
    CFSetForEach(retirees_to_remove, ^(const void *value) {
        CFArrayAppendValue(retirees_to_cleanup, value);
        CFSetRemoveValue(account->retirees, value);
    });

    CFReleaseNull(retirees_to_remove);

    CFDictionaryRef retirements_to_remove = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
                                                                         SOSCircleGetName(account->trusted_circle), retirees_to_cleanup,
                                                                         NULL);

    CFReleaseNull(retirees_to_cleanup);

    success = SOSTransportCircleExpireRetirementRecords(account->circle_transport, retirements_to_remove, error);

    CFReleaseNull(retirements_to_remove);

    return success;
}

bool SOSAccountScanForRetired(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
    SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
        CFSetSetValue(account->retirees, peer);
        CFErrorRef cleanupError = NULL;
        if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, peer, &cleanupError)) {
            secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError);
        }
        CFReleaseSafe(cleanupError);
    });
    return true;
}

SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccountRef account, SOSCircleRef starting_circle, CFErrorRef *error) {
    SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
    if(!new_circle) return NULL;

    if (account->retirees) {
        CFSetForEach(account->retirees, ^(const void* value) {
            SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
            if (isSOSPeerInfo(pi)) {
                SOSCircleUpdatePeerInfo(new_circle, pi);
            }
        });
    }

    if(SOSCircleCountPeers(new_circle) == 0) {
        SOSCircleResetToEmpty(new_circle, NULL);
    }

    return new_circle;
}

//
// MARK: Circle Membership change notificaion
//

void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
    SOSAccountCircleMembershipChangeBlock copy = Block_copy(changeBlock);
    CFArrayAppendValue(a->change_blocks, copy);
    CFReleaseNull(copy);
}

void SOSAccountRemoveChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
    CFArrayRemoveAllValue(a->change_blocks, changeBlock);
}

void SOSAccountAddSyncablePeerBlock(SOSAccountRef a, CFStringRef ds_name, SOSAccountSyncablePeersBlock changeBlock) {
    if (!changeBlock) return;

    CFRetainSafe(ds_name);
    SOSAccountCircleMembershipChangeBlock block_to_register = ^void (SOSCircleRef new_circle,
                                                                     CFSetRef added_peers, CFSetRef removed_peers,
                                                                     CFSetRef added_applicants, CFSetRef removed_applicants) {

        if (!CFEqualSafe(SOSCircleGetName(new_circle), ds_name))
            return;

        SOSPeerInfoRef myPi = SOSFullPeerInfoGetPeerInfo(a->my_identity);
        CFStringRef myPi_id = myPi ? SOSPeerInfoGetPeerID(myPi) : NULL;

        CFMutableArrayRef peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
        CFMutableArrayRef added_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
        CFMutableArrayRef removed_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);

        if (SOSCircleHasPeer(new_circle, myPi, NULL)) {
            SOSCircleForEachPeer(new_circle, ^(SOSPeerInfoRef peer) {
                CFArrayAppendValueIfNot(peer_ids, SOSPeerInfoGetPeerID(peer), myPi_id);
            });

            CFSetForEach(added_peers, ^(const void *value) {
                CFArrayAppendValueIfNot(added_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
            });

            CFSetForEach(removed_peers, ^(const void *value) {
                CFArrayAppendValueIfNot(removed_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
            });
        }

        if (CFArrayGetCount(peer_ids) || CFSetContainsValue(removed_peers, myPi))
            changeBlock(peer_ids, added_ids, removed_ids);

        CFReleaseSafe(peer_ids);
        CFReleaseSafe(added_ids);
        CFReleaseSafe(removed_ids);
    };

    CFRetainSafe(changeBlock);
    SOSAccountAddChangeBlock(a, block_to_register);

    CFSetRef empty = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
    if (a->trusted_circle && CFEqualSafe(ds_name, SOSCircleGetName(a->trusted_circle))) {
        block_to_register(a->trusted_circle, empty, empty, empty, empty);
    }
    CFReleaseSafe(empty);
}

void SOSAccountPurgeIdentity(SOSAccountRef account) {
    if (account->my_identity) {
        // Purge private key but don't return error if we can't.
        CFErrorRef purgeError = NULL;
        if (!SOSFullPeerInfoPurgePersistentKey(account->my_identity, &purgeError)) {
            secwarning("Couldn't purge persistent key for %@ [%@]", account->my_identity, purgeError);
        }
        CFReleaseNull(purgeError);

        CFReleaseNull(account->my_identity);
    }
}

bool sosAccountLeaveCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
    SOSFullPeerInfoRef fpi = account->my_identity;
    if(!fpi) return false;

	CFErrorRef localError = NULL;

    bool retval = false;

    SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
    if (!retire_peer) {
        secerror("Create ticket failed for peer %@: %@", fpi, localError);
    } else {
        // See if we need to repost the circle we could either be an applicant or a peer already in the circle
        if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
            // Remove our application if we have one.
            SOSCircleWithdrawRequest(circle, retire_peer, NULL);
        } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
            if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
                CFErrorRef cleanupError = NULL;
                if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retire_peer, &cleanupError)) {
                    secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
                }
                CFReleaseSafe(cleanupError);
            }
        }

        // Store the retirement record locally.
        CFSetAddValue(account->retirees, retire_peer);

        // Write retirement to Transport
        CFErrorRef postError = NULL;
        if (!SOSTransportCirclePostRetirement(account->circle_transport, SOSCircleGetName(circle), retire_peer, &postError)){
            secwarning("Couldn't post retirement (%@)", postError);
        }
        if(!SOSTransportCircleFlushChanges(account->circle_transport, &postError)){
            secwarning("Couldn't flush retirement data (%@)", postError);
        }
        CFReleaseNull(postError);
    }

    SOSAccountPurgeIdentity(account);

    retval = true;

    CFReleaseNull(localError);
    CFReleaseNull(retire_peer);
    return retval;
}

bool sosAccountLeaveRing(SOSAccountRef account, SOSRingRef ring, CFErrorRef* error) {
    SOSFullPeerInfoRef fpi = account->my_identity;
    if(!fpi) return false;
    SOSPeerInfoRef     pi = SOSFullPeerInfoGetPeerInfo(fpi);
    CFStringRef        peerID = SOSPeerInfoGetPeerID(pi);
    
    CFErrorRef localError = NULL;
    
    bool retval = false;
    bool writeRing = false;
    bool writePeerInfo = false;
    
    if(SOSRingHasPeerID(ring, peerID)) {
        writePeerInfo = true;
    }
    
#if 0
    // this was circle behavior - at some point
    if(SOSRingHasApplicant(ring, peerID)) {
        writeRing = true;
    }
#endif
    
    if(writePeerInfo || writeRing) {
        SOSRingWithdraw(ring, NULL, fpi, error);
    }
    
    // Write leave thing to Transport
    CFDataRef peerInfoData = SOSFullPeerInfoCopyEncodedData(fpi, kCFAllocatorDefault, error);
    SOSTransportCircleSendPeerInfo(account->circle_transport, peerID, peerInfoData, NULL); // TODO: Handle errors?
    
    if (writeRing) {
        CFDataRef ring_data = SOSRingCopyEncodedData(ring, error);
        
        if (ring_data) {
            SOSTransportCircleRingPostRing(account->circle_transport, SOSRingGetName(ring), ring_data, NULL); // TODO: Handle errors?
        }
        CFReleaseNull(ring_data);
    }
    retval = true;
    CFReleaseNull(localError);
    return retval;
}

bool SOSAccountPostDebugScope(SOSAccountRef account, CFTypeRef scope, CFErrorRef *error) {
    bool result = false;
    SOSTransportCircleRef transport = account->circle_transport;
    if (transport) {
        result = SOSTransportCircleSendDebugInfo(transport, kSOSAccountDebugScope, scope, error);
    }
    return result;
}

/*
 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
 local value that has been overwritten by a distant value. If there is no
 conflict between the local and the distant values when doing the initial
 sync (e.g. if the cloud has no data stored or the client has not stored
 any data yet), you'll never see that notification.

 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
 with server but initial round trip with server does not imply
 NSUbiquitousKeyValueStoreInitialSyncChange.
 */


//
// MARK: Status summary
//

static SOSCCStatus SOSCCThisDeviceStatusInCircle(SOSCircleRef circle, SOSPeerInfoRef this_peer) {
    if (!circle)
        return kSOSCCNotInCircle;

    if (circle && SOSCircleCountPeers(circle) == 0)
        return kSOSCCCircleAbsent;

    if (this_peer) {
        
        if(SOSPeerInfoIsRetirementTicket(this_peer))
            return kSOSCCNotInCircle;
        
        if (SOSCircleHasPeer(circle, this_peer, NULL))
            return kSOSCCInCircle;

        if (SOSCircleHasApplicant(circle, this_peer, NULL))
            return kSOSCCRequestPending;
    }

    return kSOSCCNotInCircle;
}

bool SOSAccountIsInCircle(SOSAccountRef account, CFErrorRef *error) {
    SOSCCStatus result = SOSAccountGetCircleStatus(account, error);
    
    if (result != kSOSCCInCircle && result != kSOSCCError) {
        SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
        return false;
    }
    
    return true;
}

SOSCCStatus SOSAccountGetCircleStatus(SOSAccountRef account, CFErrorRef* error) {
    if (!SOSAccountHasPublicKey(account, error)) {
        return kSOSCCError;
    }

    return SOSCCThisDeviceStatusInCircle(account->trusted_circle, SOSAccountGetMyPeerInfo(account));
}

//
// MARK: Account Reset Circles
//

static bool SOSAccountResetCircleToOffering(SOSAccountRef account, SecKeyRef user_key, CFErrorRef *error) {
    bool result = false;

    require(SOSAccountHasCircle(account, error), fail);
    require(SOSAccountEnsureFullPeerAvailable(account, error), fail);
    
    SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
        bool result = false;
        SOSFullPeerInfoRef cloud_identity = NULL;
        CFErrorRef localError = NULL;

        require_quiet(SOSCircleResetToOffering(circle, user_key, account->my_identity, &localError), err_out);

        {
            SOSPeerInfoRef cloud_peer = GenerateNewCloudIdentityPeerInfo(error);
            require_quiet(cloud_peer, err_out);
            cloud_identity = CopyCloudKeychainIdentity(cloud_peer, error);
            CFReleaseNull(cloud_peer);
            require_quiet(cloud_identity, err_out);
        }

        account->departure_code = kSOSNeverLeftCircle;
        require_quiet(SOSCircleRequestAdmission(circle, user_key, cloud_identity, &localError), err_out);
        require_quiet(SOSCircleAcceptRequest(circle, user_key, account->my_identity, SOSFullPeerInfoGetPeerInfo(cloud_identity), &localError), err_out);
        result = true;
        SOSAccountPublishCloudParameters(account, NULL);

    err_out:
        if (result == false)
            secerror("error resetting circle (%@) to offering: %@", circle, localError);
        if (localError && error && *error == NULL) {
            *error = localError;
            localError = NULL;
        }
        CFReleaseNull(localError);
        CFReleaseNull(cloud_identity);
        return result;
    });

    result = true;

fail:
    return result;
}


bool SOSAccountResetToOffering(SOSAccountRef account, CFErrorRef* error) {
    SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
    if (!user_key)
        return false;

    CFReleaseNull(account->my_identity);

    return user_key && SOSAccountResetCircleToOffering(account, user_key, error);
}

bool SOSAccountResetToEmpty(SOSAccountRef account, CFErrorRef* error) {
    if (!SOSAccountHasPublicKey(account, error))
        return false;

    CFReleaseNull(account->my_identity);

    __block bool result = true;
    account->departure_code = kSOSWithdrewMembership;
    result &= SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
        result = SOSCircleResetToEmpty(circle, error);
        return result;
    });

    if (!result) {
        secerror("error: %@", error ? *error : NULL);
    }

    return result;
}


//
// MARK: Waiting for in-sync
//

static bool SOSAccountHasBeenInSync(SOSAccountRef account) {
    CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
    CFSetRef unsynced = asSet(unsyncedObject, NULL);

    return !(unsyncedObject == kCFBooleanTrue || (unsynced && (CFSetGetCount(unsynced) > 0)));
}

static bool SOSAccountUpdateOutOfSyncViews(SOSAccountRef account, CFSetRef viewsInSync) {
    bool notifyOfChange = false;

    SOSCCStatus circleStatus = SOSAccountGetCircleStatus(account, NULL);
    bool inOrApplying = (circleStatus == kSOSCCInCircle) || (circleStatus == kSOSCCRequestPending);

    CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);

    if (!inOrApplying) {
        if (unsyncedObject != NULL) {
            SOSAccountClearValue(account, kSOSUnsyncedViewsKey, NULL);
            secnotice("initial-sync", "in sync, clearing pending");
            notifyOfChange = true;
        }
    } else if (circleStatus == kSOSCCInCircle) {
        __block CFMutableSetRef viewsToSync = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
        SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
            SOSPeerInfoWithEnabledViewSet(peer, ^(CFSetRef enabled) {
                CFSetUnion(viewsToSync, enabled);
            });
        });

        if (viewsInSync) {
            CFSetSubtract(viewsToSync, viewsInSync);

        }

        if (unsyncedObject == kCFBooleanTrue) {
            if (CFSetGetCount(viewsToSync) == 0) {
                secnotice("initial-sync", "No views to wait for");
                SOSAccountClearValue(account, kSOSUnsyncedViewsKey, NULL);
            } else {
                __block CFSetRef newViews = NULL;
                SOSPeerInfoWithEnabledViewSet(SOSAccountGetMyPeerInfo(account), ^(CFSetRef enabled) {
                    newViews = CFSetCreateIntersection(kCFAllocatorDefault, enabled, viewsToSync);
                });
                secnotice("initial-sync", "Pending views set from True: %@", newViews);
                SOSAccountSetValue(account, kSOSUnsyncedViewsKey, newViews, NULL);
                CFReleaseNull(newViews);
            }
            notifyOfChange = true;
        } else if (isSet(unsyncedObject)) {
            CFSetRef waiting = (CFMutableSetRef) unsyncedObject;
            CFSetRef newViews = CFSetCreateIntersection(kCFAllocatorDefault, waiting, viewsToSync);
            if (!CFEqualSafe(waiting, newViews)) {
                secnotice("initial-sync", "Pending views updated: %@", newViews);
                SOSAccountSetValue(account, kSOSUnsyncedViewsKey, newViews, NULL);
                notifyOfChange = true;
            }
            CFReleaseNull(newViews);
        }

        CFReleaseNull(viewsToSync);
    }

    if (notifyOfChange) {
        secnotice("initial-sync-notify", "In sync: Posting: %s", kSOSCCInitialSyncChangedNotification);
        notify_post(kSOSCCInitialSyncChangedNotification);
        // Make sure we update the engine
        account->circle_rings_retirements_need_attention = true;
    }

    return SOSAccountHasBeenInSync(account);
}

static void SOSAccountPeerGotInSync(SOSAccountRef account, CFStringRef peerID) {
    secnotice("initial-sync", "Heard PeerID is in sync: %@", peerID);

    if (account->trusted_circle) {
        SOSPeerInfoRef peer = SOSCircleCopyPeerWithID(account->trusted_circle, peerID, NULL);
        if (peer) {
            CFSetRef views = SOSPeerInfoCopyEnabledViews(peer);
            SOSAccountUpdateOutOfSyncViews(account, views);
            CFReleaseNull(views);
        }
        CFReleaseNull(peer);
    }
}

void SOSAccountCleanupNotificationForAllPeers(SOSAccountRef account) {
    SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(account->trusted_circle), NULL);

    CFDictionaryForEach(account->notification_cleanups, ^(const void *key, const void *value) {
        if (engine) {
            SOSEngineSetSyncCompleteListener(engine, key, NULL);
        }
        dispatch_async(account->queue, value);
    });

    CFDictionaryRemoveAllValues(account->notification_cleanups);
}

static void SOSAccountCleanupNotificationForPeer(SOSAccountRef account, CFStringRef peerID) {
    dispatch_block_t cleanup = CFDictionaryGetValue(account->notification_cleanups, peerID);

    if (cleanup) {
        SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(account->trusted_circle), NULL);

        if (engine) {
            SOSEngineSetSyncCompleteListener(engine, peerID, NULL);
        }

        dispatch_async(account->queue, cleanup);
    }

    CFDictionaryRemoveValue(account->notification_cleanups, peerID);

}

static void SOSAccountRegisterCleanupBlock(SOSAccountRef account, CFStringRef peerID, dispatch_block_t block) {
    dispatch_block_t copy = Block_copy(block);
    CFDictionarySetValue(account->notification_cleanups, peerID, copy);
    CFReleaseNull(copy);
}

void SOSAccountEnsureSyncChecking(SOSAccountRef account) {
    if (CFDictionaryGetCount(account->notification_cleanups) == 0) {
        SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(account->trusted_circle), NULL);

        SOSEngineSetSyncCompleteListenerQueue(engine, account->queue);

        if (engine) {
            SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
                CFStringRef peerID = CFStringCreateCopy(kCFAllocatorDefault, SOSPeerInfoGetPeerID(peer));

                SOSAccountRegisterCleanupBlock(account, peerID, ^{
                    CFReleaseSafe(peerID);
                });

                SOSEngineSetSyncCompleteListener(engine, peerID, ^{
                    SOSAccountPeerGotInSync(account, peerID);
                    SOSAccountCleanupNotificationForPeer(account, peerID);
                    SOSAccountFinishTransaction(account);
                });
            });
        } else {
            secerror("Couldn't find engine to setup notifications!!!");
        }
    }
}

void SOSAccountCancelSyncChecking(SOSAccountRef account) {
    SOSAccountCleanupNotificationForAllPeers(account);
    SOSAccountUpdateOutOfSyncViews(account, NULL);
}

bool SOSAccountCheckHasBeenInSync(SOSAccountRef account) {
    bool hasBeenInSync = false;

    if (!SOSAccountIsInCircle(account, NULL)) {
        SOSAccountCancelSyncChecking(account);
    } else {
        hasBeenInSync = SOSAccountHasBeenInSync(account);
        if (!hasBeenInSync) {
            hasBeenInSync = SOSAccountUpdateOutOfSyncViews(account, NULL);
            if (hasBeenInSync) {
                // Cancel and declare victory
                SOSAccountCancelSyncChecking(account);
            } else {
                // Make sure we're watching in case this is the fist attempt
                SOSAccountEnsureSyncChecking(account);
            }
        }
    }

    return hasBeenInSync;
}

//
// MARK: Joining
//

static bool SOSAccountJoinCircle(SOSAccountRef account, SecKeyRef user_key,
                                bool use_cloud_peer, CFErrorRef* error) {
    __block bool result = false;
    __block SOSFullPeerInfoRef cloud_full_peer = NULL;

    require_action_quiet(account->trusted_circle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
    require_quiet(SOSAccountEnsureFullPeerAvailable(account, error), fail);

    SOSFullPeerInfoRef myCirclePeer = account->my_identity;

    if (use_cloud_peer) {
        cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(account->trusted_circle, NULL);
    } else {
        SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
    }

    if (SOSCircleCountPeers(account->trusted_circle) == 0) {
        result = SOSAccountResetCircleToOffering(account, user_key, error);
    } else {
        SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
            result = SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
            account->departure_code = kSOSNeverLeftCircle;
            if(result && cloud_full_peer) {
                CFErrorRef localError = NULL;
                CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
                require_quiet(cloudid, finish);
                require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
                require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
            finish:
                if (localError){
                    secerror("Failed to join with cloud identity: %@", localError);
                    CFReleaseNull(localError);
                }
            }
            return result;
        });
    }

fail:
    CFReleaseNull(cloud_full_peer);
    return result;
}

static bool SOSAccountJoinCircles_internal(SOSAccountRef account, bool use_cloud_identity, CFErrorRef* error) {
    bool success = false;

    SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
    require_quiet(user_key, done); // Fail if we don't get one.

    require_action_quiet(account->trusted_circle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
    
    if (account->my_identity != NULL) {
        SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(account->my_identity);
        success = SOSCircleHasPeer(account->trusted_circle, myPeer, NULL);
        require_quiet(!success, done);

        SOSCircleRemoveRejectedPeer(account->trusted_circle, myPeer, NULL); // If we were rejected we should remove it now.

        if (!SOSCircleHasApplicant(account->trusted_circle, myPeer, NULL)) {
        	secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(account->trusted_circle));
            
			CFReleaseNull(account->my_identity);
            myPeer = NULL;
        }
    }

    success = SOSAccountJoinCircle(account, user_key, use_cloud_identity, error);

    require_quiet(success, done);
    
    account->departure_code = kSOSNeverLeftCircle;

done:
    return success;
}

bool SOSAccountJoinCircles(SOSAccountRef account, CFErrorRef* error) {
    return SOSAccountJoinCircles_internal(account, false, error);
}

CFStringRef SOSAccountCopyDeviceID(SOSAccountRef account, CFErrorRef *error){
    CFStringRef result = NULL;

    require_action_quiet(account->my_identity, fail, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer for me")));

    result = SOSPeerInfoCopyDeviceID(SOSFullPeerInfoGetPeerInfo(account->my_identity));

fail:
    return result;
}

bool SOSAccountSetMyDSID(SOSAccountRef account, CFStringRef IDS, CFErrorRef* error){
    bool result = true;

    if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture){
        secdebug("IDS Transport", "We are setting our device ID: %@", IDS);
        if(IDS != NULL && (CFStringGetLength(IDS) > 0)){
            require_action_quiet(account->my_identity, fail, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer for me")));
            
            result = SOSAccountModifyCircle(account, error, ^bool(SOSCircleRef circle) {
                
                SOSFullPeerInfoUpdateDeviceID(account->my_identity, IDS, error);
                SOSFullPeerInfoUpdateTransportType(account->my_identity, SOSTransportMessageTypeIDS, error);
                SOSFullPeerInfoUpdateTransportPreference(account->my_identity, kCFBooleanTrue, error);
                
                return SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(account->my_identity), NULL);
            });
        }
        else
            result = false;
    }
    else if(whichTransportType == kSOSTransportPresent){
        secdebug("IDS Transport", "We are setting our device ID: %@", IDS);
        if(IDS != NULL && (CFStringGetLength(IDS) > 0)){
            require_action_quiet(account->my_identity, fail, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer for me")));
            
            result = SOSAccountModifyCircle(account, error, ^bool(SOSCircleRef circle) {
                
                SOSFullPeerInfoUpdateDeviceID(account->my_identity, IDS, error);
                SOSFullPeerInfoUpdateTransportType(account->my_identity, SOSTransportMessageTypeKVS, error);
                SOSFullPeerInfoUpdateTransportPreference(account->my_identity, kCFBooleanTrue, error);
                
                return SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(account->my_identity), NULL);
            });
        }
        else
            result = false;

    }
   
    SOSCCSyncWithAllPeers();
    
fail:
    return result;
}


bool SOSAccountSendIDSTestMessage(SOSAccountRef account, CFStringRef message, CFErrorRef *error){
    bool result = true;
    if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent){
        //construct message dictionary, circle -> peerID -> message
        
        CFMutableDictionaryRef circleToPeerMessages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
        CFMutableDictionaryRef peerToMessage = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
        
        CFStringRef operation = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), kIDSSendOneMessage);
        CFDataRef operationData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, operation, kCFStringEncodingUTF8, 0);
        
        CFDataRef messageData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, message, kCFStringEncodingUTF8, 0);
        CFMutableDataRef mutableData = CFDataCreateMutable(kCFAllocatorDefault, CFDataGetLength(operationData) +  CFDataGetLength(messageData));
        
        CFDataAppend(mutableData, operationData);
        CFDataAppend(mutableData, messageData);
        
        SOSCircleForEachPeer(account->trusted_circle, ^(SOSPeerInfoRef peer) {
            CFDictionaryAddValue(peerToMessage, SOSPeerInfoGetPeerID(peer), mutableData);
        });
        
        CFDictionaryAddValue(circleToPeerMessages, SOSCircleGetName(account->trusted_circle), peerToMessage);
        result = SOSTransportMessageSendMessages(account->ids_message_transport, circleToPeerMessages, error);
        
        CFReleaseNull(operation);
        CFReleaseNull(mutableData);
        CFReleaseNull(operationData);
        CFReleaseNull(messageData);
        CFReleaseNull(peerToMessage);
        CFReleaseNull(circleToPeerMessages);
    }
    return result;
}

bool SOSAccountStartPingTest(SOSAccountRef account, CFStringRef message, CFErrorRef *error){
    bool result = true;
    if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent){
        //construct message dictionary, circle -> peerID -> message
        
        CFMutableDictionaryRef circleToPeerMessages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
        CFMutableDictionaryRef peerToMessage = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
        
        CFStringRef operation = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), kIDSStartPingTestMessage);
        CFDataRef operationData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, operation, kCFStringEncodingUTF8, 0);
        
        CFDataRef messageData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, message, kCFStringEncodingUTF8, 0);
        CFMutableDataRef mutableData = CFDataCreateMutable(kCFAllocatorDefault, CFDataGetLength(operationData) +  CFDataGetLength(messageData));
        
        CFDataAppend(mutableData, operationData);
        CFDataAppend(mutableData, messageData);
        
        SOSCircleForEachPeer(account->trusted_circle, ^(SOSPeerInfoRef peer) {
            CFDictionaryAddValue(peerToMessage, SOSPeerInfoGetPeerID(peer), mutableData);
        });
        
        CFDictionaryAddValue(circleToPeerMessages, SOSCircleGetName(account->trusted_circle), peerToMessage);
        result = SOSTransportMessageSendMessages(account->ids_message_transport, circleToPeerMessages, error);
        
        CFReleaseNull(operation);
        CFReleaseNull(mutableData);
        CFReleaseNull(operationData);
        CFReleaseNull(messageData);
        CFReleaseNull(peerToMessage);
        CFReleaseNull(circleToPeerMessages);
    }
    return result;
}

bool SOSAccountRetrieveDeviceIDFromIDSKeychainSyncingProxy(SOSAccountRef account, CFErrorRef *error){
    bool result = true;
    if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent){
        
        __block bool success = false;
        
        dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
        dispatch_retain(wait_for); // Both this scope and the block own it
        
        SOSCloudKeychainGetIDSDeviceID(^(CFDictionaryRef returnedValues, CFErrorRef sync_error){
            success = (sync_error == NULL);
            if (error) {
                CFRetainAssign(*error, sync_error);
            }
            
            dispatch_semaphore_signal(wait_for);
            dispatch_release(wait_for);
        });
        
        dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
        dispatch_release(wait_for);
        
        if(!success){
            secerror("Could not ask IDSKeychainSyncingProxy for Device ID: %@", *error);
        }
        else{
            secdebug("IDS Transport", "Attempting to retrieve the IDS Device ID");
        }
    }
    return result;
}

bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account, CFErrorRef* error) {
    return SOSAccountJoinCircles_internal(account, true, error);
}


bool SOSAccountLeaveCircle(SOSAccountRef account, CFErrorRef* error)
{
    bool result = true;

    result &= SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
        return sosAccountLeaveCircle(account, circle, error);
    });

    account->departure_code = kSOSWithdrewMembership;

    return result;
}

bool SOSAccountBail(SOSAccountRef account, uint64_t limit_in_seconds, CFErrorRef* error) {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    __block bool result = false;
    secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
    // Add a task to the group
    dispatch_group_async(group, queue, ^{
        SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
            return sosAccountLeaveCircle(account, circle, error);
        });
    });
    dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
    dispatch_group_wait(group, milestone);

    account->departure_code = kSOSWithdrewMembership;

    dispatch_release(group);
    return result;
}


//
// MARK: Application
//

static void for_each_applicant_in_each_circle(SOSAccountRef account, CFArrayRef peer_infos,
                                              bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
        SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(account->my_identity);
        CFErrorRef peer_error = NULL;
        if (account->trusted_circle && me &&
            SOSCircleHasPeer(account->trusted_circle, me, &peer_error)) {
            SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle) {
                __block bool modified = false;
                CFArrayForEach(peer_infos, ^(const void *value) {
                    SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
                    if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
                        if (action(circle, account->my_identity, peer)) {
                            modified = true;
                        }
                    }
                });
                return modified;
            });
        }
        if (peer_error)
            secerror("Got error in SOSCircleHasPeer: %@", peer_error);
        CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
}

bool SOSAccountAcceptApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
    SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
    if (!user_key)
        return false;

    __block bool success = true;
    __block int64_t num_peers = 0;

    for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
        bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
        if (!accepted)
            success = false;
		else
			num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
        return accepted;
    });

    return success;
}

bool SOSAccountRejectApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
    __block bool success = true;
    __block int64_t num_peers = 0;

    for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
        bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
        if (!rejected)
            success = false;
		else
			num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
        return rejected;
    });

    return success;
}


CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccountRef account, CFErrorRef* error) {
    return CFSTR("We're compatible, go away");
}

enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccountRef account, CFErrorRef* error) {
    return account->departure_code;
}

void SOSAccountSetLastDepartureReason(SOSAccountRef account, enum DepartureReason reason) {
	account->departure_code = reason;
}


CFArrayRef SOSAccountCopyGeneration(SOSAccountRef account, CFErrorRef *error) {
    CFArrayRef result = NULL;
    CFNumberRef generation = NULL;

    require_quiet(SOSAccountHasPublicKey(account, error), fail);
    require_action_quiet(account->trusted_circle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));

    generation = (CFNumberRef)SOSCircleGetGeneration(account->trusted_circle);
    result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);

fail:
    return result;
}

bool SOSValidateUserPublic(SOSAccountRef account, CFErrorRef *error) {
    if (!SOSAccountHasPublicKey(account, error))
        return NULL;

    return account->user_public_trusted;
}

bool SOSAccountEnsurePeerRegistration(SOSAccountRef account, CFErrorRef *error) {
    // TODO: this result is never set or used
    bool result = true;

    secnotice("updates", "Ensuring peer registration.");

    require_quiet(account->trusted_circle, done);
    require_quiet(account->my_identity, done);
    // If we are not in the circle, there is no point in setting up peers
    require_quiet(SOSAccountIsMyPeerActive(account, NULL), done);

    // This code only uses the SOSFullPeerInfoRef for two things:
    //  - Finding out if this device is in the trusted circle
    //  - Using the peerID for this device to see if the current peer is "me"
    //  - It is used indirectly by passing account->my_identity to SOSPeerCoderInitializeForPeer
    
    CFStringRef my_id = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(account->my_identity));

    SOSCircleForEachPeer(account->trusted_circle, ^(SOSPeerInfoRef peer) {
        if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
            CFErrorRef localError = NULL;
            SOSTransportMessageRef messageTransport = NULL;
            
            if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent){
                 messageTransport = SOSPeerInfoHasDeviceID(peer) ? account->ids_message_transport : account->kvs_message_transport;
            }
            else
                messageTransport = account->kvs_message_transport;
            
            SOSPeerCoderInitializeForPeer(messageTransport->engine, account->my_identity, peer, &localError);
            if (localError)
                secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, account->my_identity, localError);
            CFReleaseSafe(localError);
        }
    });

    //Initialize our device ID
    if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent){
        CFStringRef deviceID = SOSPeerInfoCopyDeviceID(SOSFullPeerInfoGetPeerInfo(account->my_identity));
        if( deviceID == NULL || CFStringGetLength(deviceID) == 0){
            
            __block bool success = false;
            __block CFErrorRef localError = NULL;
            
                SOSCloudKeychainGetIDSDeviceID(^(CFDictionaryRef returnedValues, CFErrorRef sync_error){
                    success = (sync_error == NULL);
                    if (localError) {
                        CFRetainAssign(localError, sync_error);
                    }
                });
                
                if(!success){
                    secerror("Could not ask IDSKeychainSyncingProxy for Device ID: %@", localError);
                }
                else{
                    secdebug("IDS Transport", "Attempting to retrieve the IDS Device ID");
                }
            CFReleaseNull(localError);
        }
        CFReleaseNull(deviceID);
    }
    
done:
    return result;
}

static inline bool SOSAccountEnsureExpansion(SOSAccountRef account, CFErrorRef *error) {
    if (!account->expansion) {
        account->expansion = CFDictionaryCreateMutableForCFTypes(NULL);
    }

    return SecAllocationError(account->expansion, error, CFSTR("Can't Alloc Account Expansion dictionary"));
}

bool SOSAccountClearValue(SOSAccountRef account, const void *key, CFErrorRef *error) {
    bool success = SOSAccountEnsureExpansion(account, error);
    require_quiet(success, errOut);

    CFDictionaryRemoveValue(account->expansion, key);
errOut:
    return success;
}

bool SOSAccountSetValue(SOSAccountRef account, const void *key, const void *value, CFErrorRef *error) {
    bool success = SOSAccountEnsureExpansion(account, error);
    require_quiet(success, errOut);

    CFDictionarySetValue(account->expansion, key, value);
errOut:
    return success;
}


const void *SOSAccountGetValue(SOSAccountRef account, const void *key, CFErrorRef *error) {
    if (!account->expansion) {
        return NULL;
    }
    return CFDictionaryGetValue(account->expansion, key);
}

void SOSAccountFinishTransaction(SOSAccountRef account) {
    if(account->circle_rings_retirements_need_attention){
        CFErrorRef localError = NULL;
        if(!SOSTransportCircleFlushChanges(account->circle_transport, &localError)) {
            secerror("flush circle failed %@", localError);
        }
        CFReleaseSafe(localError);

        SOSAccountNotifyEngines(account); // For now our only rings are backup rings.
    }

    SOSAccountCheckHasBeenInSync(account);

    account->circle_rings_retirements_need_attention = false;
}