--- /dev/null
+/*
+ * 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 <SecureObjectSync/SOSPeerInfoCollections.h>
+#include <SecureObjectSync/SOSTransportCircle.h>
+#include <SecureObjectSync/SOSTransportMessage.h>
+#include <SecureObjectSync/SOSKVSKeys.h>
+#include <SecureObjectSync/SOSTransportKeyParameter.h>
+#include <SecureObjectSync/SOSTransportKeyParameterKVS.h>
+#include <SecureObjectSync/SOSEngine.h>
+#include <SecureObjectSync/SOSPeerCoder.h>
+
+CFGiblisWithCompareFor(SOSAccount);
+
+
+bool SOSAccountEnsureFactoryCircles(SOSAccountRef a)
+{
+ bool result = false;
+ if (a)
+ {
+ require(a->factory, xit);
+ CFArrayRef circle_names = a->factory->copy_names(a->factory);
+ require(circle_names, xit);
+ CFArrayForEach(circle_names, ^(const void*name) {
+ if (isString(name))
+ SOSAccountEnsureCircle(a, (CFStringRef)name, NULL);
+ });
+
+ CFReleaseNull(circle_names);
+ result = true;
+ }
+xit:
+ 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->gestalt = CFRetainSafe(gestalt);
+
+ a->circles = CFDictionaryCreateMutableForCFTypes(allocator);
+ a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
+
+ a->factory = factory; // We adopt the factory. kthanksbai.
+
+ a->change_blocks = CFArrayCreateMutableForCFTypes(allocator);
+
+ a->departure_code = kSOSNeverAppliedToCircle;
+
+ a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
+ a->circle_transports = CFDictionaryCreateMutableForCFTypes(allocator);
+ a->message_transports = CFDictionaryCreateMutableForCFTypes(allocator);
+
+ return a;
+}
+
+
+
+
+bool SOSAccountUpdateGestalt(SOSAccountRef account, CFDictionaryRef new_gestalt)
+{
+ if (CFEqual(new_gestalt, account->gestalt))
+ return false;
+ SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
+ if (SOSFullPeerInfoUpdateGestalt(full_peer, new_gestalt, NULL)) {
+ SOSAccountModifyCircle(account, SOSCircleGetName(circle),
+ NULL, ^(SOSCircleRef circle_to_change) {
+ return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(full_peer));
+ });
+ };
+ });
+
+ CFRetainAssign(account->gestalt, new_gestalt);
+ return true;
+}
+
+SOSAccountRef SOSAccountCreate(CFAllocatorRef allocator,
+ CFDictionaryRef gestalt,
+ SOSDataSourceFactoryRef factory) {
+ SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory);
+
+ a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
+
+ SOSAccountEnsureFactoryCircles(a);
+
+ return a;
+}
+
+static void SOSAccountDestroy(CFTypeRef aObj) {
+ SOSAccountRef a = (SOSAccountRef) aObj;
+
+ // We don't own the factory, meerly have a reference to the singleton
+ // don't free it.
+ // a->factory
+
+ CFReleaseNull(a->gestalt);
+ CFReleaseNull(a->circle_identities);
+ CFReleaseNull(a->circles);
+ CFReleaseNull(a->retired_peers);
+
+ a->user_public_trusted = false;
+ CFReleaseNull(a->user_public);
+ CFReleaseNull(a->user_key_parameters);
+
+ SOSAccountPurgePrivateCredential(a);
+ CFReleaseNull(a->previous_public);
+
+ a->departure_code = kSOSNeverAppliedToCircle;
+ CFReleaseNull(a->message_transports);
+ CFReleaseNull(a->key_transport);
+ CFReleaseNull(a->circle_transports);
+ dispatch_release(a->queue);
+}
+
+void SOSAccountSetToNew(SOSAccountRef a) {
+ CFAllocatorRef allocator = CFGetAllocator(a);
+ CFReleaseNull(a->circle_identities);
+ CFReleaseNull(a->circles);
+ CFReleaseNull(a->retired_peers);
+
+ CFReleaseNull(a->user_key_parameters);
+ CFReleaseNull(a->user_public);
+ CFReleaseNull(a->previous_public);
+ CFReleaseNull(a->_user_private);
+
+ CFReleaseNull(a->key_transport);
+ CFReleaseNull(a->circle_transports);
+ CFReleaseNull(a->message_transports);
+
+ 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->circles = CFDictionaryCreateMutableForCFTypes(allocator);
+ a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
+ a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
+
+ a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
+ a->circle_transports = (CFMutableDictionaryRef)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+ a->message_transports = (CFMutableDictionaryRef)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ SOSAccountEnsureFactoryCircles(a);
+}
+
+
+static CFStringRef SOSAccountCopyDescription(CFTypeRef aObj) {
+ SOSAccountRef a = (SOSAccountRef) aObj;
+
+ return CFStringCreateWithFormat(NULL, NULL, CFSTR("<SOSAccount@%p: Gestalt: %@\n Circles: %@ CircleIDs: %@>"), a, a->gestalt, a->circles, a->circle_identities);
+}
+
+static Boolean SOSAccountCompare(CFTypeRef lhs, CFTypeRef rhs)
+{
+ SOSAccountRef laccount = (SOSAccountRef) lhs;
+ SOSAccountRef raccount = (SOSAccountRef) rhs;
+
+ return CFEqual(laccount->gestalt, raccount->gestalt)
+ && CFEqual(laccount->circles, raccount->circles)
+ && CFEqual(laccount->circle_identities, raccount->circle_identities);
+ // ??? retired_peers
+}
+
+dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) {
+ return account->queue;
+}
+
+CFDictionaryRef SOSAccountGetMessageTransports(SOSAccountRef account){
+ return account->message_transports;
+}
+
+
+void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account){
+ account->user_public_trusted = true;
+}
+
+CFArrayRef SOSAccountCopyAccountIdentityPeerInfos(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef* error)
+{
+ CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(allocator);
+
+ CFDictionaryForEach(account->circle_identities, ^(const void *key, const void *value) {
+ SOSFullPeerInfoRef fpi = (SOSFullPeerInfoRef) value;
+
+ CFArrayAppendValue(result, SOSFullPeerInfoGetPeerInfo(fpi));
+ });
+
+ return result;
+}
+
+static bool SOSAccountThisDeviceCanSyncWithCircle(SOSAccountRef account, SOSCircleRef circle) {
+ CFErrorRef error = NULL;
+ SOSFullPeerInfoRef myfpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, SOSCircleGetName(circle), &error);
+ SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(myfpi);
+ CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
+ return SOSCircleHasPeerWithID(circle, myPeerID, &error);
+}
+
+static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account, CFStringRef circleName, CFStringRef peerID) {
+ CFErrorRef error = NULL;
+ SOSFullPeerInfoRef myfpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, circleName, &error);
+ if (!myfpi) {
+ return false;
+ }
+ SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(myfpi);
+ CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
+ return CFEqualSafe(myPeerID, peerID);
+}
+
+bool SOSAccountSyncWithAllPeers(SOSAccountRef account, CFErrorRef *error)
+{
+ __block bool result = true;
+
+ SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+
+ if (SOSAccountThisDeviceCanSyncWithCircle(account, circle)) {
+ CFStringRef circleName = SOSCircleGetName(circle);
+ SOSTransportMessageRef thisPeerTransport = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
+;
+ CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
+ // Figure out transport for peer; for now we always use KVS
+ CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
+ if (!SOSAccountIsThisPeerIDMe(account, circleName, peerID)) {
+ CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circleName), peerID);
+ }
+ });
+
+ result &= SOSTransportMessageSyncWithPeers(thisPeerTransport, circleToPeerIDs, error);
+
+ CFReleaseNull(circleToPeerIDs);
+ }
+ });
+
+ // Tell each transport to sync with its collection of peers we know we should sync with.
+
+
+ if (result)
+ SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);
+
+
+ return result;
+}
+
+bool SOSAccountCleanupAfterPeer(SOSAccountRef account, size_t seconds, SOSCircleRef circle,
+ SOSPeerInfoRef cleanupPeer, CFErrorRef* error)
+{
+ bool success = false;
+ if(!SOSAccountIsMyPeerActiveInCircle(account, circle, NULL)) return true;
+
+ SOSPeerInfoRef myPeerInfo = SOSAccountGetMyPeerInCircle(account, circle, error);
+ require(myPeerInfo, xit);
+
+ CFStringRef cleanupPeerID = SOSPeerInfoGetPeerID(cleanupPeer);
+ CFStringRef circle_name = SOSCircleGetName(circle);
+
+ if (CFEqual(cleanupPeerID, SOSPeerInfoGetPeerID(myPeerInfo))) {
+ CFErrorRef destroyError = NULL;
+ if (!SOSAccountDestroyCirclePeerInfo(account, circle, &destroyError)) {
+ secerror("Unable to destroy peer info: %@", destroyError);
+ }
+ CFReleaseSafe(destroyError);
+
+ account->departure_code = kSOSWithdrewMembership;
+
+ return true;
+ }
+
+ CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+ CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circle_name), cleanupPeerID);
+
+
+ CFErrorRef localError = NULL;
+ SOSTransportMessageRef tMessage = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
+ if (!SOSTransportMessageCleanupAfterPeerMessages(tMessage, circleToPeerIDs, &localError)) {
+ secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID, localError);
+ }
+ CFReleaseNull(localError);
+ SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(circle));
+ if(SOSPeerInfoRetireRetirementTicket(seconds, cleanupPeer)) {
+ if (!SOSTransportCircleExpireRetirementRecords(tCircle, 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) {
+ CFMutableDictionaryRef retirements_to_remove = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+ CFDictionaryRef original_retired_peers = account->retired_peers;
+ __block bool success = true;
+ account->retired_peers = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ CFDictionaryForEach(original_retired_peers, ^(const void *key, const void *value) {
+ if (isString(key) && isDictionary(value)) {
+ CFStringRef circle_name = key;
+ __block CFMutableDictionaryRef still_active_circle_retirements = NULL;
+ CFDictionaryForEach((CFMutableDictionaryRef) value, ^(const void *key, const void *value) {
+ if (isString(key) && isData(value)) {
+ CFStringRef retired_peer_id = (CFStringRef) key;
+ SOSPeerInfoRef retired_peer = SOSPeerInfoCreateFromData(kCFAllocatorDefault, NULL, (CFDataRef) value);
+ if (retired_peer && SOSPeerInfoIsRetirementTicket(retired_peer) && CFEqual(retired_peer_id, SOSPeerInfoGetPeerID(retired_peer))) {
+ // He's a retired peer all right, if he's active or not yet expired we keep a record of his retirement.
+ // if not, clear any recordings of his retirement from our transport.
+ if (SOSAccountIsActivePeerInCircleNamed(account, circle_name, retired_peer_id, NULL) ||
+ !SOSPeerInfoRetireRetirementTicket(seconds, retired_peer)) {
+ // He's still around or not expired. Keep record.
+ if (still_active_circle_retirements == NULL) {
+ still_active_circle_retirements = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account->retired_peers, circle_name);
+ }
+ CFDictionarySetValue(still_active_circle_retirements, retired_peer_id, value);
+ } else {
+ CFMutableArrayRef retirements = CFDictionaryEnsureCFArrayAndGetCurrentValue(retirements_to_remove, circle_name);
+ CFArrayAppendValue(retirements, retired_peer_id);
+ }
+ }
+ CFReleaseNull(retired_peer);
+ }
+ });
+
+ SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, circle_name);
+ success &= SOSTransportCircleExpireRetirementRecords(tCircle, retirements_to_remove, error);
+ }
+ });
+
+ CFReleaseNull(original_retired_peers);
+ CFReleaseNull(retirements_to_remove);
+
+ return success;
+}
+
+bool SOSAccountScanForRetired(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
+ __block CFMutableDictionaryRef circle_retirees = (CFMutableDictionaryRef) CFDictionaryGetValue(account->retired_peers, SOSCircleGetName(circle));
+
+ SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
+ CFStringRef peer_id = SOSPeerInfoGetPeerID(peer);
+ if(!circle_retirees || !CFDictionaryGetValueIfPresent(circle_retirees, peer_id, NULL)) {
+ if (!circle_retirees) {
+ circle_retirees = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account->retired_peers, SOSCircleGetName(circle));
+ }
+ CFDataRef value = SOSPeerInfoCopyEncodedData(peer, NULL, NULL);
+ if(value) {
+ CFDictionarySetValue(circle_retirees, peer_id, value);
+ SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, peer, error);
+ }
+ CFReleaseSafe(value);
+ }
+ });
+ return true;
+}
+
+SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccountRef account, SOSCircleRef starting_circle, CFErrorRef *error) {
+ CFStringRef circle_to_mod = SOSCircleGetName(starting_circle);
+
+ SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
+ if(!new_circle) return NULL;
+
+ CFDictionaryRef circle_retirements = CFDictionaryGetValue(account->retired_peers, circle_to_mod);
+
+ if (isDictionary(circle_retirements)) {
+ CFDictionaryForEach(circle_retirements, ^(const void* id, const void* value) {
+ if (isData(value)) {
+ SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
+ if (pi && CFEqualSafe(id, SOSPeerInfoGetPeerID(pi))) {
+ SOSCircleUpdatePeerInfo(new_circle, pi);
+ }
+ CFReleaseSafe(pi);
+ }
+ });
+ }
+
+ if(SOSCircleCountPeers(new_circle) == 0) {
+ SOSCircleResetToEmpty(new_circle, NULL);
+ }
+
+ return new_circle;
+}
+
+//
+// MARK: Circle Membership change notificaion
+//
+
+void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
+ CFArrayAppendValue(a->change_blocks, changeBlock);
+}
+
+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 = SOSAccountGetMyPeerInCircle(a, new_circle, NULL);
+ 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_copy(block_to_register));
+
+ CFSetRef empty = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
+ SOSCircleRef circle = (SOSCircleRef) CFDictionaryGetValue(a->circles, ds_name);
+ if (circle) {
+ block_to_register(circle, empty, empty, empty, empty);
+ }
+ CFReleaseSafe(empty);
+}
+
+
+bool sosAccountLeaveCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
+ SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, NULL);
+ if(!fpi) return false;
+ CFErrorRef localError = NULL;
+ SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
+ CFStringRef retire_id = SOSPeerInfoGetPeerID(retire_peer);
+
+ // Account should move away from a dictionary of KVS keys to a Circle -> Peer -> Retirement ticket storage soonish.
+ CFStringRef retire_key = SOSRetirementKeyCreateWithCircleAndPeer(circle, retire_id);
+ CFDataRef retire_value = NULL;
+ bool retval = false;
+ bool writeCircle = false;
+
+ // Create a Retirement Ticket and store it in the retired_peers of the account.
+ require_action_quiet(retire_peer, errout, secerror("Create ticket failed for peer %@: %@", fpi, localError));
+ retire_value = SOSPeerInfoCopyEncodedData(retire_peer, NULL, &localError);
+ require_action_quiet(retire_value, errout, secerror("Failed to encode retirement peer %@: %@", retire_peer, localError));
+
+ // 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);
+ writeCircle = true;
+ } 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);
+ }
+ writeCircle = true;
+ }
+
+ // Store the retirement record locally.
+ CFDictionarySetValue(account->retired_peers, retire_key, retire_value);
+
+ // Write retirement to Transport
+ SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(circle));
+ SOSTransportCirclePostRetirement(tCircle, SOSCircleGetName(circle), retire_id, retire_value, NULL); // TODO: Handle errors?
+
+ // Kill peer key but don't return error if we can't.
+ if(!SOSAccountDestroyCirclePeerInfo(account, circle, &localError))
+ secerror("Couldn't purge key for peer %@ on retirement: %@", fpi, localError);
+
+ if (writeCircle) {
+ CFDataRef circle_data = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, error);
+
+ if (circle_data) {
+ SOSTransportCirclePostCircle(tCircle, SOSCircleGetName(circle), circle_data, NULL); // TODO: Handle errors?
+ }
+ CFReleaseNull(circle_data);
+ }
+ retval = true;
+
+errout:
+ CFReleaseNull(localError);
+ CFReleaseNull(retire_peer);
+ CFReleaseNull(retire_key);
+ CFReleaseNull(retire_value);
+ return retval;
+}
+
+/*
+ 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 SOSCCCircleStatus(SOSCircleRef circle) {
+ if (SOSCircleCountPeers(circle) == 0)
+ return kSOSCCCircleAbsent;
+
+ return kSOSCCNotInCircle;
+}
+
+static SOSCCStatus SOSCCThisDeviceStatusInCircle(SOSCircleRef circle, SOSPeerInfoRef this_peer) {
+ if (SOSCircleCountPeers(circle) == 0)
+ return kSOSCCCircleAbsent;
+
+ if (SOSCircleHasPeer(circle, this_peer, NULL))
+ return kSOSCCInCircle;
+
+ if (SOSCircleHasApplicant(circle, this_peer, NULL))
+ return kSOSCCRequestPending;
+
+ return kSOSCCNotInCircle;
+}
+
+static SOSCCStatus UnionStatus(SOSCCStatus accumulated_status, SOSCCStatus additional_circle_status) {
+ switch (additional_circle_status) {
+ case kSOSCCInCircle:
+ return accumulated_status;
+ case kSOSCCRequestPending:
+ return (accumulated_status == kSOSCCInCircle) ?
+ kSOSCCRequestPending :
+ accumulated_status;
+ case kSOSCCNotInCircle:
+ return (accumulated_status == kSOSCCInCircle ||
+ accumulated_status == kSOSCCRequestPending) ?
+ kSOSCCNotInCircle :
+ accumulated_status;
+ case kSOSCCCircleAbsent:
+ return (accumulated_status == kSOSCCInCircle ||
+ accumulated_status == kSOSCCRequestPending ||
+ accumulated_status == kSOSCCNotInCircle) ?
+ kSOSCCCircleAbsent :
+ accumulated_status;
+ default:
+ return additional_circle_status;
+ };
+
+}
+
+SOSCCStatus SOSAccountIsInCircles(SOSAccountRef account, CFErrorRef* error) {
+ if (!SOSAccountHasPublicKey(account, error)) {
+ return kSOSCCError;
+ }
+
+ __block bool set_once = false;
+ __block SOSCCStatus status = kSOSCCInCircle;
+
+ SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
+ set_once = true;
+ status = UnionStatus(status, kSOSCCNotInCircle);
+ }, ^(SOSCircleRef circle) {
+ set_once = true;
+ status = UnionStatus(status, SOSCCCircleStatus(circle));
+ }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
+ set_once = true;
+ SOSCCStatus circle_status = SOSCCThisDeviceStatusInCircle(circle, SOSFullPeerInfoGetPeerInfo(full_peer));
+ status = UnionStatus(status, circle_status);
+ });
+
+ if (!set_once)
+ status = kSOSCCCircleAbsent;
+
+ return status;
+}
+
+//
+// MARK: Account Reset Circles
+//
+
+static bool SOSAccountResetThisCircleToOffering(SOSAccountRef account, SOSCircleRef circle, SecKeyRef user_key, CFErrorRef *error) {
+ SOSFullPeerInfoRef myCirclePeer = SOSAccountGetMyFullPeerInCircle(account, circle, error);
+ if (!myCirclePeer)
+ return false;
+
+ SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
+ bool result = false;
+ SOSFullPeerInfoRef cloud_identity = NULL;
+ CFErrorRef localError = NULL;
+
+ require_quiet(SOSCircleResetToOffering(circle, user_key, myCirclePeer, &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, myCirclePeer, 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;
+ });
+
+ return true;
+}
+
+
+bool SOSAccountResetToOffering(SOSAccountRef account, CFErrorRef* error) {
+ SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
+ if (!user_key)
+ return false;
+
+ __block bool result = true;
+
+ SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
+ SOSCircleRef circle = SOSCircleCreate(NULL, name, NULL);
+ if (circle)
+ CFDictionaryAddValue(account->circles, name, circle);
+
+ SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
+ }, ^(SOSCircleRef circle) {
+ SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
+ }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
+ SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
+ });
+
+ return result;
+}
+
+bool SOSAccountResetToEmpty(SOSAccountRef account, CFErrorRef* error) {
+ if (!SOSAccountHasPublicKey(account, error))
+ return false;
+
+ __block bool result = true;
+ SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+ SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
+ if (!SOSCircleResetToEmpty(circle, error))
+ {
+ secerror("error: %@", *error);
+ result = false;
+ }
+ account->departure_code = kSOSWithdrewMembership;
+ return result;
+ });
+ });
+
+ return result;
+}
+
+
+//
+// MARK: Joining
+//
+
+static bool SOSAccountJoinThisCircle(SOSAccountRef account, SecKeyRef user_key,
+ SOSCircleRef circle, bool use_cloud_peer, CFErrorRef* error) {
+ __block bool result = false;
+ __block SOSFullPeerInfoRef cloud_full_peer = NULL;
+
+ SOSFullPeerInfoRef myCirclePeer = SOSAccountGetMyFullPeerInCircle(account, circle, error);
+
+ require_action_quiet(myCirclePeer, fail,
+ SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Can't find/create peer for circle: %@"), circle));
+ if (use_cloud_peer) {
+ cloud_full_peer = SOSCircleGetiCloudFullPeerInfoRef(circle);
+ }
+
+ if (SOSCircleCountPeers(circle) == 0) {
+ result = SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
+ } else {
+ SOSAccountModifyCircle(account, SOSCircleGetName(circle), 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) {
+ SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
+ if (!user_key)
+ return false;
+
+ __block bool success = true;
+
+ SOSAccountForEachKnownCircle(account, ^(CFStringRef name) { // Incompatible
+ success = false;
+ SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle"), NULL, error);
+ }, ^(SOSCircleRef circle) { //no peer
+ success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
+ }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) { // Have Peer
+ SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(full_peer);
+ if(SOSCircleHasPeer(circle, myPeer, NULL)) goto already_present;
+ if(SOSCircleHasApplicant(circle, myPeer, NULL)) goto already_applied;
+ if(SOSCircleHasRejectedApplicant(circle, myPeer, NULL)) {
+ SOSCircleRemoveRejectedPeer(circle, myPeer, NULL);
+ }
+
+ secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(circle));
+ CFErrorRef localError = NULL;
+ if (!SOSAccountDestroyCirclePeerInfo(account, circle, &localError)) {
+ secerror("Failed to destroy peer (%@) during application, error=%@", myPeer, localError);
+ CFReleaseNull(localError);
+ }
+ already_applied:
+ success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
+ return;
+ already_present:
+ success = true;
+ return;
+ });
+
+ if(success) account->departure_code = kSOSNeverLeftCircle;
+ return success;
+}
+
+bool SOSAccountJoinCircles(SOSAccountRef account, CFErrorRef* error) {
+ return SOSAccountJoinCircles_internal(account, false, error);
+}
+
+CFStringRef SOSAccountGetDeviceID(SOSAccountRef account, CFErrorRef *error){
+ __block CFStringRef result = NULL;
+ __block CFStringRef temp = NULL;
+ SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
+ SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
+ if(fpi){
+ SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
+ if(myPeer){
+ temp = SOSPeerInfoGetDeviceID(myPeer);
+ if(!isNull(temp)){
+ result = CFStringCreateCopy(kCFAllocatorDefault, temp);
+ }
+ }
+ else{
+ secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle));
+ }
+ }
+ else{
+ secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle));
+ }
+ });
+ return result;
+}
+
+bool SOSAccountSetMyDSID(SOSAccountRef account, CFStringRef IDS, CFErrorRef* error){
+ __block bool result = false;
+
+ SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
+ SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
+ SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
+ if(fpi){
+ SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
+ if(myPeer){
+ SOSPeerInfoSetDeviceID(myPeer, IDS);
+ result = true;
+ }
+ else{
+ secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle));
+ result = false;
+ }
+ }
+ else{
+ secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle));
+ result = false;
+ }
+ return result;
+ });
+ });
+ return result;
+
+}
+bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account, CFErrorRef* error) {
+ return SOSAccountJoinCircles_internal(account, true, error);
+}
+
+
+bool SOSAccountLeaveCircles(SOSAccountRef account, CFErrorRef* error)
+{
+ __block bool result = true;
+ SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
+ SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
+ result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors!
+ return result;
+ });
+ });
+
+ 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, ^{
+ SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
+ SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
+ result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors!
+ return result;
+ });
+ });
+
+ account->departure_code = kSOSWithdrewMembership;
+ });
+ dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
+
+ dispatch_group_wait(group, milestone);
+ 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)) {
+
+ SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
+ SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(full_peer);
+ CFErrorRef peer_error = NULL;
+ if (SOSCircleHasPeer(circle, me, &peer_error)) {
+ CFArrayForEach(peer_infos, ^(const void *value) {
+ SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
+ if (SOSCircleHasApplicant(circle, peer, NULL)) {
+ SOSAccountModifyCircle(account, SOSCircleGetName(circle), NULL, ^(SOSCircleRef circle) {
+ return action(circle, full_peer, peer);
+ });
+ }
+ });
+ }
+ 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;
+}
+
+
+CFArrayRef SOSAccountCopyGeneration(SOSAccountRef account, CFErrorRef *error) {
+ if (!SOSAccountHasPublicKey(account, error))
+ return NULL;
+ CFMutableArrayRef generations = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+
+ SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
+ CFNumberRef generation = (CFNumberRef)SOSCircleGetGeneration(circle);
+ CFArrayAppendValue(generations, SOSCircleGetName(circle));
+ CFArrayAppendValue(generations, generation);
+ });
+
+ return generations;
+
+}
+
+bool SOSValidateUserPublic(SOSAccountRef account, CFErrorRef *error) {
+ if (!SOSAccountHasPublicKey(account, error))
+ return NULL;
+
+ return account->user_public_trusted;
+}
+
+
+bool SOSAccountEnsurePeerRegistration(SOSAccountRef account, CFErrorRef *error) {
+ __block bool result = true;
+
+ secnotice("updates", "Ensuring peer registration.");
+
+ SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
+ }, ^(SOSCircleRef circle) {
+ }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
+ SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
+ SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(fpi);
+ CFMutableArrayRef trusted_peer_ids = NULL;
+ CFMutableArrayRef untrusted_peer_ids = NULL;
+ CFStringRef my_id = NULL;
+ if (SOSCircleHasPeer(circle, me, NULL)) {
+ my_id = SOSPeerInfoGetPeerID(me);
+ trusted_peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+ untrusted_peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
+ SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
+ CFMutableArrayRef arrayToAddTo = SOSPeerInfoApplicationVerify(peer, account->user_public, NULL) ? trusted_peer_ids : untrusted_peer_ids;
+
+ CFArrayAppendValueIfNot(arrayToAddTo, SOSPeerInfoGetPeerID(peer), my_id);
+ });
+ }
+
+ SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(circle), NULL);
+ if (engine)
+ SOSEngineCircleChanged(engine, my_id, trusted_peer_ids, untrusted_peer_ids);
+
+ CFReleaseNull(trusted_peer_ids);
+ CFReleaseNull(untrusted_peer_ids);
+
+ SOSTransportMessageRef transport = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
+ SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
+ if (!CFEqualSafe(me, peer)) {
+ CFErrorRef localError = NULL;
+ SOSPeerCoderInitializeForPeer(transport, full_peer, peer, &localError);
+ if (localError)
+ secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, full_peer, localError);
+ CFReleaseSafe(localError);
+ }
+ });
+ });
+
+ return result;
+}