SOSAccountTransaction.c   [plain text]


//
//  SOSAccountTransaction.c
//  sec
//
//

#include "SOSAccountTransaction.h"

#include <utilities/SecCFWrappers.h>
#include <CoreFoundation/CoreFoundation.h>

#include <Security/SecureObjectSync/SOSAccount.h>
#include <Security/SecureObjectSync/SOSAccountPriv.h>
#include <Security/SecureObjectSync/SOSPeerInfoV2.h>
#include <Security/SecureObjectSync/SOSTransport.h>
#include <Security/SecureObjectSync/SOSTransportCircle.h>

#define kPublicKeyNotAvailable "com.apple.security.publickeynotavailable"

CFGiblisFor(SOSAccountTransaction);

static void SOSAccountTransactionDestroy(CFTypeRef aObj) {
    SOSAccountTransactionRef at = (SOSAccountTransactionRef) aObj;
    
	CFReleaseNull(at->initialUnsyncedViews);
    CFReleaseNull(at->initialID);
    CFReleaseNull(at->account);
    CFReleaseNull(at->initialViews);
    CFReleaseNull(at->initialKeyParameters);
    CFReleaseNull(at->peersToRequestSync);
}

static CFStringRef SOSAccountTransactionCopyFormatDescription(CFTypeRef aObj, CFDictionaryRef formatOptions) {
    SOSAccountTransactionRef at = (SOSAccountTransactionRef) aObj;
    
    CFMutableStringRef description = CFStringCreateMutable(kCFAllocatorDefault, 0);
    
    CFStringAppendFormat(description, NULL, CFSTR("<SOSAccountTransactionRef@%p %ld>"),
                         at, at->initialViews ? CFSetGetCount(at->initialViews) : 0);
    
    return description;
}

static void SOSAccountTransactionRestart(SOSAccountTransactionRef txn) {
    txn->initialInCircle = SOSAccountIsInCircle(txn->account, NULL);

    if(txn->account)
        txn->initialTrusted = (txn->account)->user_public_trusted;

    if (txn->initialInCircle) {
        SOSAccountEnsureSyncChecking(txn->account);
    }

    CFAssignRetained(txn->initialUnsyncedViews, SOSAccountCopyOutstandingViews(txn->account));

    CFReleaseNull(txn->initialKeyParameters);
    
    if(txn->account && txn->account->user_key_parameters){
        CFReleaseNull(txn->initialKeyParameters);
        txn->initialKeyParameters  = CFDataCreateCopy(kCFAllocatorDefault, txn->account->user_key_parameters);
    }
    SOSPeerInfoRef mpi = SOSAccountGetMyPeerInfo(txn->account);
    CFAssignRetained(txn->initialViews, mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL);

    CFRetainAssign(txn->initialID, SOSPeerInfoGetPeerID(mpi));

    CFReleaseNull(txn->peersToRequestSync);
    
    CFStringSetPerformWithDescription(txn->initialViews, ^(CFStringRef description) {
        secnotice("acct-txn", "Starting as:%s v:%@", txn->initialInCircle ? "member" : "non-member", description);
    });
}


SOSAccountTransactionRef SOSAccountTransactionCreate(SOSAccountRef account) {
    SOSAccountTransactionRef at = CFTypeAllocate(SOSAccountTransaction, struct __OpaqueSOSAccountTransaction, kCFAllocatorDefault);
    
    at->account = CFRetainSafe(account);

    at->initialInCircle = false;
    at->initialViews = NULL;
    at->initialKeyParameters = NULL;
    at->initialTrusted = false;
    at->initialUnsyncedViews = NULL;
    at->initialID = NULL;
    at->peersToRequestSync = NULL;

    SOSAccountTransactionRestart(at);

    return at;
}

#define ACCOUNT_STATE_INTERVAL 20

void SOSAccountTransactionFinish(SOSAccountTransactionRef txn) {
    CFErrorRef localError = NULL;
    bool notifyEngines = false;
    static int do_account_state_at_zero = 0;

    SOSPeerInfoRef mpi = SOSAccountGetMyPeerInfo(txn->account);

    bool isInCircle = SOSAccountIsInCircle(txn->account, NULL);

    if (isInCircle && txn->peersToRequestSync) {
        SOSCCRequestSyncWithPeers(txn->peersToRequestSync);
    }
    CFReleaseNull(txn->peersToRequestSync);

    if (isInCircle) {
        SOSAccountEnsureSyncChecking(txn->account);
    } else {
        SOSAccountCancelSyncChecking(txn->account);
    }

    // If our identity changed our inital set should be everything.
    if (!CFEqualSafe(txn->initialID, SOSPeerInfoGetPeerID(mpi))) {
        CFAssignRetained(txn->initialUnsyncedViews, SOSViewCopyViewSet(kViewSetAll));
    }

    CFSetRef finalUnsyncedViews = SOSAccountCopyOutstandingViews(txn->account);
    if (!CFEqualSafe(txn->initialUnsyncedViews, finalUnsyncedViews)) {
        if (SOSAccountHandleOutOfSyncUpdate(txn->account, txn->initialUnsyncedViews, finalUnsyncedViews)) {
            notifyEngines = true;
        }

        CFStringSetPerformWithDescription(txn->initialUnsyncedViews, ^(CFStringRef newUnsyncedDescripion) {
            CFStringSetPerformWithDescription(finalUnsyncedViews, ^(CFStringRef unsyncedDescription) {
                secnotice("initial-sync", "Unsynced was: %@", unsyncedDescription);
                secnotice("initial-sync", "Unsynced is: %@", newUnsyncedDescripion);
            });
        });
    }
    CFReleaseNull(finalUnsyncedViews);

    if (txn->account->engine_peer_state_needs_repair) {
        // We currently only get here from a failed syncwithallpeers, so
        // that will retry. If this logic changes, force a syncwithallpeers
        if (!SOSAccountEnsurePeerRegistration(txn->account, &localError)) {
            secerror("Ensure peer registration while repairing failed: %@", localError);
        }
        CFReleaseNull(localError);
        
        notifyEngines = true;
    }

    if(txn->account->circle_rings_retirements_need_attention){
        SOSAccountRecordRetiredPeersInCircle(txn->account);

        SOSAccountEnsureRecoveryRing(txn->account);
        SOSAccountEnsureInBackupRings(txn->account);

        CFErrorRef localError = NULL;
        if(!SOSTransportCircleFlushChanges(txn->account->circle_transport, &localError)) {
            secerror("flush circle failed %@", localError);
        }
        CFReleaseSafe(localError);
        
        notifyEngines = true;
    }

    if (notifyEngines) {
        SOSAccountNotifyEngines(txn->account);
    }
    
    if(txn->account->key_interests_need_updating){
        SOSUpdateKeyInterest(txn->account);
    }

    txn->account->key_interests_need_updating = false;
    txn->account->circle_rings_retirements_need_attention = false;
    txn->account->engine_peer_state_needs_repair = false;

    SOSAccountFlattenToSaveBlock(txn->account);
    
    // Refresh isInCircle since we could have changed our mind
    isInCircle = SOSAccountIsInCircle(txn->account, NULL);
    
    mpi = SOSAccountGetMyPeerInfo(txn->account);
    CFSetRef views = mpi ? SOSPeerInfoCopyEnabledViews(mpi) : NULL;

    CFStringSetPerformWithDescription(views, ^(CFStringRef description) {
        secnotice("acct-txn", "Finished as:%s v:%@", isInCircle ? "member" : "non-member", description);
    });
    if(!CFEqualSafe(txn->initialViews, views) || txn->initialInCircle != isInCircle) {
        notify_post(kSOSCCViewMembershipChangedNotification);
        do_account_state_at_zero = 0;
    }
    
    if((txn->initialTrusted != (txn->account)->user_public_trusted) || (!CFEqualSafe(txn->initialKeyParameters, txn->account->user_key_parameters))){
        notify_post(kPublicKeyNotAvailable);
        do_account_state_at_zero = 0;
    }
    
    if(do_account_state_at_zero <= 0) {
        SOSAccountLogState(txn->account);
        SOSAccountLogViewState(txn->account);
        do_account_state_at_zero = ACCOUNT_STATE_INTERVAL;
    }
    do_account_state_at_zero--;
    
    CFReleaseNull(views);

}

void SOSAccountTransactionFinishAndRestart(SOSAccountTransactionRef txn) {
    SOSAccountTransactionFinish(txn);
    SOSAccountTransactionRestart(txn);
}

void SOSAccountTransactionAddSyncRequestForPeerID(SOSAccountTransactionRef txn, CFStringRef peerID) {
    if (!txn->peersToRequestSync) {
        txn->peersToRequestSync = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
    }

    CFSetAddValue(txn->peersToRequestSync, peerID);
}

void SOSAccountTransactionAddSyncRequestForAllPeerIDs(SOSAccountTransactionRef txn, CFSetRef /* CFStringRef */ peerIDs) {
    if (!txn->peersToRequestSync) {
        txn->peersToRequestSync = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
    }

    CFSetUnion(txn->peersToRequestSync, peerIDs);
}