--- /dev/null
+
+#include "SOSAccountPriv.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 <SOSCircle/CKBridge/SOSCloudKeychainClient.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <utilities/SecCFError.h>
+
+// MARK: Engine Logging
+#define LOG_ENGINE_STATE_INTERVAL 20
+
+static void SOSAccountConsiderLoggingEngineState(SOSAccountTransactionRef txn) {
+ static int engineLogCountDown = 0;
+
+ if(engineLogCountDown <= 0) {
+ SOSEngineRef engine = SOSTransportMessageGetEngine(txn->account->kvs_message_transport);
+
+ SOSEngineLogState(engine);
+ engineLogCountDown = LOG_ENGINE_STATE_INTERVAL;
+ } else {
+ engineLogCountDown--;
+ }
+}
+
+static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account, CFStringRef peerID) {
+ SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(account->my_identity);
+ CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
+
+ return myPeerID && CFEqualSafe(myPeerID, peerID);
+}
+
+bool SOSAccountSendIKSPSyncList(SOSAccountRef account, CFErrorRef *error){
+ bool result = true;
+ __block CFErrorRef localError = NULL;
+ __block CFMutableArrayRef ids = NULL;
+ SOSCircleRef circle = NULL;
+
+ require_action_quiet(SOSAccountIsInCircle(account, NULL), xit,
+ SOSCreateError(kSOSErrorNoCircle, CFSTR("This device is not in circle"),
+ NULL, &localError));
+
+ circle = SOSAccountGetCircle(account, error);
+ ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ SOSCircleForEachValidPeer(circle, account->user_public, ^(SOSPeerInfoRef peer) {
+ if (!SOSAccountIsThisPeerIDMe(account, SOSPeerInfoGetPeerID(peer))) {
+ if(SOSPeerInfoShouldUseIDSTransport(SOSFullPeerInfoGetPeerInfo(account->my_identity), peer) &&
+ SOSPeerInfoShouldUseIDSMessageFragmentation(SOSFullPeerInfoGetPeerInfo(account->my_identity), peer) &&
+ !SOSPeerInfoShouldUseACKModel(SOSFullPeerInfoGetPeerInfo(account->my_identity), peer)){
+ SOSTransportMessageIDSSetFragmentationPreference(account->ids_message_transport, kCFBooleanTrue);
+ CFStringRef deviceID = SOSPeerInfoCopyDeviceID(peer);
+ if(deviceID != NULL){
+ CFArrayAppendValue(ids, deviceID);
+ }
+ CFReleaseNull(deviceID);
+ }
+ }
+ });
+ require_quiet(CFArrayGetCount(ids) != 0, xit);
+ secnotice("IDS Transport", "List of IDS Peers to ping: %@", ids);
+
+ SOSCloudKeychainGetIDSDeviceAvailability(ids, SOSAccountGetMyPeerID(account), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(CFDictionaryRef returnedValues, CFErrorRef sync_error) {
+ bool success = (sync_error == NULL);
+ if(!success)
+ secerror("Failed to send list of IDS peers to IDSKSP: %@", sync_error);
+ });
+xit:
+ if(error && *error != NULL)
+ secerror("SOSAccountSendIKSPSyncList had an error: %@", *error);
+
+ if(localError)
+ secerror("SOSAccountSendIKSPSyncList had an error: %@", localError);
+
+ CFReleaseNull(ids);
+ CFReleaseNull(localError);
+
+ return result;
+}
+//
+// MARK: KVS Syncing
+//
+
+static bool SOSAccountSyncWithKVSPeers(SOSAccountTransactionRef txn, CFSetRef peerIDs, CFErrorRef *error) {
+ SOSAccountRef account = txn->account;
+ CFErrorRef localError = NULL;
+ bool result = false;
+
+ require_quiet(SOSAccountIsInCircle(account, &localError), xit);
+
+ result = SOSTransportMessageSyncWithPeers(account->kvs_message_transport, peerIDs, &localError);
+
+ if (result)
+ SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);
+
+xit:
+ if (!result) {
+ // Tell account to update SOSEngine with current trusted peers
+ if (isSOSErrorCoded(localError, kSOSErrorPeerNotFound)) {
+ secnotice("Account", "Arming account to update SOSEngine with current trusted peers");
+ account->engine_peer_state_needs_repair = true;
+ }
+ CFErrorPropagate(localError, error);
+ localError = NULL;
+ }
+ return result;
+
+}
+
+bool SOSAccountSyncWithKVSPeerWithMessage(SOSAccountTransactionRef txn, CFStringRef peerid, CFDataRef message, CFErrorRef *error) {
+ SOSAccountRef account = txn->account;
+ bool result = false;
+ CFErrorRef localError = NULL;
+ CFDictionaryRef encapsulatedMessage = NULL;
+
+ secnotice("KVS Transport","Syncing with KVS capable peer: %@", peerid);
+ secnotice("KVS Transport", "message: %@", message);
+
+ require_quiet(message, xit);
+ require_quiet(peerid, xit);
+
+ encapsulatedMessage = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, peerid, message, NULL);
+
+ result = SOSTransportMessageSendMessages(account->kvs_message_transport, encapsulatedMessage, &localError);
+ secerror("KVS sync %s. (%@)", result ? "succeeded" : "failed", localError);
+
+ SOSAccountConsiderLoggingEngineState(txn);
+
+xit:
+ CFReleaseNull(encapsulatedMessage);
+ CFErrorPropagate(localError, error);
+
+ return result;
+}
+
+
+static bool SOSAccountSyncWithKVSPeer(SOSAccountTransactionRef txn, CFStringRef peerID, CFErrorRef *error)
+{
+ bool result = false;
+ CFErrorRef localError = NULL;
+
+ secnotice("KVS Transport","Syncing with KVS capable peer: %@", peerID);
+
+ CFMutableSetRef peerIDs = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
+ CFSetAddValue(peerIDs, peerID);
+
+ result = SOSAccountSyncWithKVSPeers(txn, peerIDs, &localError);
+ secerror("KVS sync %s. (%@)", result ? "succeeded" : "failed", localError);
+
+ CFReleaseNull(peerIDs);
+ CFErrorPropagate(localError, error);
+
+ return result;
+}
+
+
+static CFMutableArrayRef SOSAccountCopyPeerIDsForDSID(SOSAccountRef account, CFStringRef deviceID, CFErrorRef* error) {
+ CFMutableArrayRef peerIDs = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ SOSCircleForEachValidPeer(account->trusted_circle, account->user_public, ^(SOSPeerInfoRef peer) {
+ CFStringRef peerDeviceID = SOSPeerInfoCopyDeviceID(peer);
+ if(peerDeviceID != NULL && CFStringCompare(peerDeviceID, deviceID, 0) == 0){
+ CFArrayAppendValue(peerIDs, SOSPeerInfoGetPeerID(peer));
+ }
+ CFReleaseNull(peerDeviceID);
+ });
+
+ if (peerIDs == NULL || CFArrayGetCount(peerIDs) == 0) {
+ CFReleaseNull(peerIDs);
+ SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer with DSID: %@"), deviceID);
+ }
+
+ return peerIDs;
+}
+
+static bool SOSAccountSyncWithKVSPeerFromPing(SOSAccountRef account, CFArrayRef peerIDs, CFErrorRef *error) {
+
+ CFErrorRef localError = NULL;
+ bool result = false;
+
+ CFSetRef peerSet = CFSetCreateCopyOfArrayForCFTypes(peerIDs);
+ result = SOSTransportMessageSyncWithPeers(account->kvs_message_transport, peerSet, &localError);
+
+ CFReleaseNull(peerSet);
+
+ return result;
+}
+
+bool SOSAccountSyncWithKVSUsingIDSID(SOSAccountRef account, CFStringRef deviceID, CFErrorRef *error) {
+ bool result = false;
+ CFErrorRef localError = NULL;
+
+ secnotice("KVS Transport","Syncing with KVS capable peer via DSID: %@", deviceID);
+
+ CFArrayRef peerIDs = SOSAccountCopyPeerIDsForDSID(account, deviceID, &localError);
+ require_quiet(peerIDs, xit);
+
+ CFStringArrayPerfromWithDescription(peerIDs, ^(CFStringRef peerIDList) {
+ secnotice("KVS Transport", "Syncing with KVS capable peers: %@", peerIDList);
+ });
+
+ result = SOSAccountSyncWithKVSPeerFromPing(account, peerIDs, &localError);
+ secerror("KVS sync %s. (%@)", result ? "succeeded" : "failed", localError);
+
+xit:
+ CFReleaseNull(peerIDs);
+ CFErrorPropagate(localError, error);
+
+ return result;
+}
+
+static __nonnull CF_RETURNS_RETAINED CFSetRef SOSAccountSyncWithPeersOverKVS(SOSAccountTransactionRef txn, CFSetRef peers) {
+ CFMutableSetRef handled = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ CFSetForEach(peers, ^(const void *value) {
+ CFStringRef peerID = asString(value, NULL);
+ CFErrorRef localError = NULL;
+ if (peerID && SOSAccountSyncWithKVSPeer(txn, peerID, &localError)) {
+ CFSetAddValue(handled, peerID);
+ secnotice("KVS Transport", "synced with peer: %@", peerID);
+ } else {
+ secnotice("KVS Transport", "failed to sync with peer: %@ error: %@", peerID, localError);
+ }
+ });
+
+ return handled;
+}
+
+static __nonnull CF_RETURNS_RETAINED CFSetRef SOSAccountSyncWithPeersOverIDS(SOSAccountTransactionRef txn, __nonnull CFSetRef peers) {
+ CFErrorRef localError = NULL;
+
+ CFStringSetPerformWithDescription(peers, ^(CFStringRef peerDescription) {
+ secnotice("IDS Transport","Syncing with IDS capable peers: %@", peerDescription);
+ });
+
+ // We should change this to return a set of peers we succeeded with, but for now assume they all worked.
+ bool result = SOSTransportMessageSyncWithPeers(txn->account->ids_message_transport, peers, &localError);
+ secnotice("IDS Transport", "IDS Sync result: %d", result);
+
+ return CFRetainSafe(peers);
+}
+
+static CF_RETURNS_RETAINED CFMutableSetRef SOSAccountSyncWithPeers(SOSAccountTransactionRef txn, CFSetRef /* CFStringRef */ peerIDs, CFErrorRef *error) {
+ CFMutableSetRef notMePeers = NULL;
+ CFMutableSetRef handledPeerIDs = NULL;
+ CFMutableSetRef peersForIDS = NULL;
+ CFMutableSetRef peersForKVS = NULL;
+
+ SOSAccountRef account = txn->account;
+
+ require_action_quiet(SOSAccountIsInCircle(account, error), done,
+ handledPeerIDs = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, peerIDs));
+
+ // Kick getting our device ID if we don't have it, and find out if we're setup to use IDS.
+ bool canUseIDS = SOSTransportMessageIDSGetIDSDeviceID(account);
+
+ handledPeerIDs = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
+ peersForIDS = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
+ peersForKVS = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ SOSPeerInfoRef myPeerInfo = SOSAccountGetMyPeerInfo(account);
+ require(myPeerInfo, done);
+
+ CFStringRef myPeerID = SOSPeerInfoGetPeerID(myPeerInfo);
+
+ notMePeers = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, peerIDs);
+ CFSetRemoveValue(notMePeers, myPeerID);
+
+ if(!SOSAccountSendIKSPSyncList(account, error)){
+ if(error != NULL)
+ secnotice("IDS Transport", "Did not send list of peers to ping (pre-E): %@", *error);
+ }
+
+ CFSetForEach(notMePeers, ^(const void *value) {
+ CFErrorRef localError = NULL;
+ CFStringRef peerID = asString(value, &localError);
+ SOSPeerInfoRef peerInfo = NULL;
+
+ require_quiet(peerID, skip);
+
+ peerInfo = SOSCircleCopyPeerWithID(account->trusted_circle, peerID, NULL);
+ if (peerInfo && SOSCircleHasValidSyncingPeer(account->trusted_circle, peerInfo, account->user_public, NULL)) {
+ if (canUseIDS && SOSPeerInfoShouldUseIDSTransport(myPeerInfo, peerInfo) && SOSPeerInfoShouldUseACKModel(myPeerInfo, peerInfo)) {
+ CFSetAddValue(peersForIDS, peerID);
+ } else {
+ CFSetAddValue(peersForKVS, peerID);
+ }
+ } else {
+ CFSetAddValue(handledPeerIDs, peerID);
+ }
+
+ skip:
+ CFReleaseNull(peerInfo);
+ if (localError) {
+ secnotice("sync-with-peers", "Skipped peer ID: %@ due to %@", peerID, localError);
+ }
+ CFReleaseNull(localError);
+ });
+
+ CFSetRef handledIDSPeerIDs = SOSAccountSyncWithPeersOverIDS(txn, peersForIDS);
+ CFSetUnion(handledPeerIDs, handledIDSPeerIDs);
+ CFReleaseNull(handledIDSPeerIDs);
+
+ CFSetRef handledKVSPeerIDs = SOSAccountSyncWithPeersOverKVS(txn, peersForKVS);
+ CFSetUnion(handledPeerIDs, handledKVSPeerIDs);
+ CFReleaseNull(handledKVSPeerIDs);
+
+ SOSAccountConsiderLoggingEngineState(txn);
+
+done:
+ CFReleaseNull(notMePeers);
+ CFReleaseNull(peersForIDS);
+ CFReleaseNull(peersForKVS);
+ return handledPeerIDs;
+}
+
+bool SOSAccountClearPeerMessageKey(SOSAccountTransactionRef txn, CFStringRef peerID, CFErrorRef *error)
+{
+ SOSAccountRef account = txn->account;
+
+ secnotice("IDS Transport", "clearing peer message for %@", peerID);
+ CFTypeRef dsid = SOSAccountGetValue(account, kSOSDSIDKey, error);
+
+ if(dsid == NULL)
+ dsid = kCFNull;
+
+ CFStringRef message_to_peer_key = SOSMessageKeyCreateFromTransportToPeer(account->kvs_message_transport, peerID);
+ CFDictionaryRef a_message_to_a_peer = CFDictionaryCreateForCFTypes(NULL, message_to_peer_key, kCFNull, kSOSKVSRequiredKey, dsid, NULL);
+
+ CloudKeychainReplyBlock log_error = ^(CFDictionaryRef returnedValues __unused, CFErrorRef block_error) {
+ if (block_error) {
+ secerror("Error putting: %@", block_error);
+ }
+ };
+
+ SOSCloudKeychainPutObjectsInCloud(a_message_to_a_peer, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), log_error);
+
+ CFReleaseNull(a_message_to_a_peer);
+ CFReleaseNull(message_to_peer_key);
+
+ return true;
+}
+
+CF_RETURNS_RETAINED CFSetRef SOSAccountProcessSyncWithPeers(SOSAccountTransactionRef txn, CFSetRef /* CFStringRef */ peers, CFSetRef /* CFStringRef */ backupPeers, CFErrorRef *error)
+{
+ CFErrorRef localError = NULL;
+ CFMutableSetRef handled = SOSAccountSyncWithPeers(txn, peers, &localError);
+
+ SOSTransportMessageIDSGetIDSDeviceID(txn->account);
+
+ if (!handled) {
+ secnotice("account-sync", "Peer Sync failed: %@", localError);
+ handled = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
+ }
+ CFReleaseNull(localError);
+
+ SOSEngineRef engine = SOSTransportMessageGetEngine(txn->account->kvs_message_transport);
+ CFSetRef engineHandled = SOSEngineSyncWithBackupPeers(engine, backupPeers, error);
+
+ if (engineHandled) {
+ CFSetUnion(handled, engineHandled);
+ } else {
+ secnotice("account-sync", "Engine Backup Sync failed: %@", localError);
+ }
+ CFReleaseNull(localError);
+ CFReleaseNull(engineHandled);
+
+ return handled;
+}
+
+bool SOSAccountRequestSyncWithAllPeers(SOSAccountTransactionRef txn, CFErrorRef *error)
+{
+ bool success = false;
+ CFMutableSetRef allSyncingPeers = NULL;
+
+ require_quiet(SOSAccountIsInCircle(txn->account, error), xit);
+
+ SOSTransportMessageIDSGetIDSDeviceID(txn->account);
+
+ allSyncingPeers = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ SOSCircleForEachValidSyncingPeer(txn->account->trusted_circle, txn->account->user_public, ^(SOSPeerInfoRef peer) {
+ CFSetAddValue(allSyncingPeers, SOSPeerInfoGetPeerID(peer));
+ });
+
+ SOSAccountTransactionAddSyncRequestForAllPeerIDs(txn, allSyncingPeers);
+
+ success = true;
+
+xit:
+ CFReleaseNull(allSyncingPeers);
+ return success;
+}
+
+//
+// MARK: Syncing status functions
+//
+bool SOSAccountMessageFromPeerIsPending(SOSAccountTransactionRef txn, SOSPeerInfoRef peer, CFErrorRef *error) {
+ bool success = false;
+ require_quiet(SOSAccountIsInCircle(txn->account, error), xit);
+
+ // This translation belongs inside KVS..way down in CKD, but for now we reach over and do it here.
+ CFStringRef peerMessage = SOSMessageKeyCreateFromPeerToTransport(txn->account->kvs_message_transport, SOSPeerInfoGetPeerID(peer));
+
+ success = SOSCloudKeychainHasPendingKey(peerMessage, error);
+
+xit:
+ return success;
+}
+
+bool SOSAccountSendToPeerIsPending(SOSAccountTransactionRef txn, SOSPeerInfoRef peer, CFErrorRef *error) {
+ bool success = false;
+ require_quiet(SOSAccountIsInCircle(txn->account, error), xit);
+
+ success = SOSCCIsSyncPendingFor(SOSPeerInfoGetPeerID(peer), error);
+xit:
+ return success;
+
+
+}