CircleJoinRequested.m [plain text]
//
// CircleJoinRequested.m
// CircleJoinRequested
//
// Created by J Osborne on 3/5/13.
// Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
//
#import <Accounts/Accounts.h>
#import <Accounts/ACAccountStore_Private.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnewline-eof"
#import <AppleAccount/AppleAccount.h>
#pragma clang diagnostic pop
#import <AppleAccount/ACAccountStore+AppleAccount.h>
#import <Accounts/ACAccountType_Private.h>
#import <Foundation/Foundation.h>
#include <dispatch/dispatch.h>
#include "SecureObjectSync/SOSCloudCircle.h"
#include "SecureObjectSync/SOSPeerInfo.h"
#import <CoreFoundation/CFUserNotification.h>
#import <SpringBoardServices/SBSCFUserNotificationKeys.h>
#include <notify.h>
#include <sysexits.h>
#import "Applicant.h"
#import "NSArray+map.h"
#import <ManagedConfiguration/MCProfileConnection.h>
#import <ManagedConfiguration/MCFeatures.h>
#import <Security/SecFrameworkStrings.h>
#import "PersistantState.h"
#include <xpc/private.h>
#include <sys/time.h>
#import "NSDate+TimeIntervalDescription.h"
#include <MobileGestalt.h>
#include <xpc/activity.h>
#include <xpc/private.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <MobileCoreServices/LSApplicationWorkspace.h>
#import <CloudServices/SecureBackup.h>
#import <syslog.h>
// As long as we are logging the failure use exit code of zero to make launchd happy
#define EXIT_LOGGED_FAILURE(code) xpc_transaction_end(); exit(0)
const char *kLaunchLaterXPCName = "com.apple.security.CircleJoinRequestedTick";
CFRunLoopSourceRef currentAlertSource = NULL;
CFUserNotificationRef currentAlert = NULL;
bool currentAlertIsForApplicants = true;
bool currentAlertIsForKickOut = false;
NSMutableDictionary *applicants = nil;
volatile NSString *debugState = @"main?";
dispatch_block_t doOnceInMainBlockChain = NULL;
NSString *castleKeychainUrl = @"prefs:root=CASTLE&path=Keychain/ADVANCED";
#if 0
// For use with: __attribute__((cleanup(CFReleaseSafeIndirect))) CFType auto_var;
static void CFReleaseSafeIndirect(void *o_)
{
void **o = o_;
if (o && *o) {
CFRelease(*o);
}
}
#endif
static void doOnceInMain(dispatch_block_t block)
{
if (doOnceInMainBlockChain) {
doOnceInMainBlockChain = ^{
doOnceInMainBlockChain();
block();
};
} else {
doOnceInMainBlockChain = block;
}
}
static NSString *appleIDAccountName()
{
ACAccountStore *accountStore = [[ACAccountStore alloc] init];
ACAccount *primaryAppleAccount = [accountStore aa_primaryAppleAccount];
return primaryAppleAccount.username;
}
static CFOptionFlags flagsForAsk(Applicant *applicant)
{
return kCFUserNotificationPlainAlertLevel|CFUserNotificationSecureTextField(0);
}
// NOTE: gives precedence to OnScreen
static Applicant *firstApplicantWaitingOrOnScreen()
{
Applicant *waiting = nil;
for (Applicant *applicant in [applicants objectEnumerator]) {
if (applicant.applicantUIState == ApplicantOnScreen) {
return applicant;
} else if (applicant.applicantUIState == ApplicantWaiting) {
waiting = applicant;
}
}
return waiting;
}
static NSMutableArray *applicantsInState(ApplicantUIState state)
{
NSMutableArray *results = [NSMutableArray new];
for (Applicant *applicant in [applicants objectEnumerator]) {
if (applicant.applicantUIState == state) {
[results addObject:applicant];
}
}
return results;
}
static BOOL processRequests(CFErrorRef *error)
{
bool ok = true;
NSMutableArray *toAccept = [[applicantsInState(ApplicantAccepted) mapWithBlock:^id(id obj) {
return (id)[obj rawPeerInfo];
}] mutableCopy];
NSMutableArray *toReject = [[applicantsInState(ApplicantRejected) mapWithBlock:^id(id obj) {
return (id)[obj rawPeerInfo];
}] mutableCopy];
NSLog(@"Process accept: NSLog(@"Process reject:
if ([toAccept count]) {
ok = ok && SOSCCAcceptApplicants((__bridge CFArrayRef)(toAccept), error);
}
if ([toReject count]) {
ok = ok && SOSCCRejectApplicants((__bridge CFArrayRef)(toReject), error);
}
return ok;
}
static void cancelCurrentAlert(bool stopRunLoop)
{
if (currentAlertSource) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode);
CFRelease(currentAlertSource);
currentAlertSource = NULL;
}
if (currentAlert) {
CFUserNotificationCancel(currentAlert);
CFRelease(currentAlert);
currentAlert = NULL;
}
if (stopRunLoop) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
currentAlertIsForKickOut = currentAlertIsForApplicants = false;
}
static void askAboutAll(bool passwordFailure);
static void applicantChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags)
{
ApplicantUIState choice;
if (kCFUserNotificationAlternateResponse == responseFlags) {
choice = ApplicantRejected;
} else if (kCFUserNotificationDefaultResponse == responseFlags) {
choice = ApplicantAccepted;
} else {
NSLog(@"Unexpected response choice = ApplicantRejected;
}
BOOL processed = NO;
CFErrorRef error = NULL;
NSArray *onScreen = applicantsInState(ApplicantOnScreen);
[onScreen enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Applicant* applicant = (Applicant*) obj;
applicant.applicantUIState = choice;
}];
if (choice == ApplicantRejected) {
// If this device has ever set up the public key this should work without the password...
processed = processRequests(&error);
if (processed) {
NSLog(@"Didn't need password to process cancelCurrentAlert(true);
return;
} else {
// ...however if the public key gets lost we should "just" fall through to the validate
// password path.
NSLog(@"Couldn't process reject without password (e= }
}
NSString *password = (__bridge NSString *)(CFUserNotificationGetResponseValue(userNotification, kCFUserNotificationTextFieldValuesKey, 0));
if (!password) {
NSLog(@"No password given, retry");
askAboutAll(true);
return;
}
const char *passwordUTF8 = [password UTF8String];
NSData *passwordBytes = [NSData dataWithBytes:passwordUTF8 length:strlen(passwordUTF8)];
// Sometimes securityd crashes between the SOSCCRegisterUserCredentials and the processRequests,
// (which results in a process error -- I think this is 13355140); as a workaround we retry
// failure a few times before we give up.
for (int try = 0; try < 5 && !processed; try++) {
if (!SOSCCTryUserCredentials(CFSTR(""), (__bridge CFDataRef)(passwordBytes), &error)) {
NSLog(@"Try user credentials failed if ((error==NULL) || (CFEqual(kSOSErrorDomain, CFErrorGetDomain(error)) && kSOSErrorWrongPassword == CFErrorGetCode(error))) {
NSLog(@"Calling askAboutAll again...");
[onScreen enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Applicant* applicant = (Applicant*) obj;
applicant.applicantUIState = ApplicantWaiting;
}];
askAboutAll(true);
return;
}
EXIT_LOGGED_FAILURE(EX_DATAERR);
}
processed = processRequests(&error);
if (!processed) {
NSLog(@"Can't processRequests: }
}
if (processed && firstApplicantWaitingOrOnScreen()) {
cancelCurrentAlert(false);
askAboutAll(false);
} else {
cancelCurrentAlert(true);
}
}
static void passwordFailurePrompt()
{
//CFBridgingRelease
NSString *pwIncorrect = [NSString stringWithFormat:(NSString *)CFBridgingRelease(SecCopyCKString(SEC_CK_PASSWORD_INCORRECT)), appleIDAccountName()];
NSString *tryAgain = CFBridgingRelease(SecCopyCKString(SEC_CK_TRY_AGAIN));
NSDictionary *noteAttributes = @{
(id)kCFUserNotificationAlertHeaderKey: pwIncorrect,
(id)kCFUserNotificationDefaultButtonTitleKey: tryAgain,
// TopMost gets us onto the lock screen
(id)kCFUserNotificationAlertTopMostKey: (id)kCFBooleanTrue,
(__bridge id)SBUserNotificationDontDismissOnUnlock: @YES,
(__bridge id)SBUserNotificationDismissOnLock: @NO,
};
CFOptionFlags flags = kCFUserNotificationPlainAlertLevel;
SInt32 err;
CFUserNotificationRef note = CFUserNotificationCreate(NULL, 0.0, flags, &err, (__bridge CFDictionaryRef)noteAttributes);
CFUserNotificationReceiveResponse(note, 0.0, &flags);
CFRelease(note);
}
static NSDictionary *createNote(Applicant *applicantToAskAbout) {
if(!applicantToAskAbout) return NULL;
NSString *appName = applicantToAskAbout.name;
if(!appName) return NULL;
NSString *devType = applicantToAskAbout.deviceType;
if(!devType) return NULL;
return @{
(id)kCFUserNotificationAlertHeaderKey: [NSString stringWithFormat:(__bridge_transfer NSString*)SecCopyCKString(SEC_CK_JOIN_TITLE), appName],
(id)kCFUserNotificationAlertMessageKey: [NSString stringWithFormat:(__bridge_transfer NSString*)SecCopyCKString(SEC_CK_JOIN_PROMPT), appleIDAccountName(), devType],
(id)kCFUserNotificationDefaultButtonTitleKey: (__bridge_transfer NSString*)SecCopyCKString(SEC_CK_ALLOW),
(id)kCFUserNotificationAlternateButtonTitleKey: (__bridge_transfer NSString*)SecCopyCKString(SEC_CK_DONT_ALLOW),
(id)kCFUserNotificationTextFieldTitlesKey: (__bridge_transfer NSString*)SecCopyCKString(SEC_CK_ICLOUD_PASSWORD),
// TopMost gets us onto the lock screen
(id)kCFUserNotificationAlertTopMostKey: (id)kCFBooleanTrue,
(__bridge_transfer id)SBUserNotificationDontDismissOnUnlock: @YES,
(__bridge_transfer id)SBUserNotificationDismissOnLock: @NO,
};
}
static void askAboutAll(bool passwordFailure)
{
if ([[MCProfileConnection sharedConnection] effectiveBoolValueForSetting: MCFeatureAccountModificationAllowed] == MCRestrictedBoolExplicitNo) {
NSLog(@"Account modifications not allowed.");
return;
}
if (passwordFailure) {
passwordFailurePrompt();
}
if ((passwordFailure || !currentAlertIsForApplicants) && currentAlert) {
if (!currentAlertIsForApplicants) {
CFUserNotificationCancel(currentAlert);
}
// after password failure we need to remove the existing alert and supporting objects
// because we can't reuse them.
CFRelease(currentAlert);
currentAlert = NULL;
if (currentAlertSource) {
CFRelease(currentAlertSource);
currentAlertSource = NULL;
}
}
currentAlertIsForApplicants = true;
Applicant *applicantToAskAbout = firstApplicantWaitingOrOnScreen();
NSLog(@"Asking about:
NSDictionary *noteAttributes = createNote(applicantToAskAbout);
if(!noteAttributes) {
NSLog(@"NULL data for cancelCurrentAlert(true);
return;
}
CFOptionFlags flags = flagsForAsk(applicantToAskAbout);
if (currentAlert) {
SInt32 err = CFUserNotificationUpdate(currentAlert, 0, flags, (__bridge CFDictionaryRef)noteAttributes);
if (err) {
NSLog(@"CFUserNotificationUpdate err= EXIT_LOGGED_FAILURE(EX_SOFTWARE);
}
} else {
SInt32 err = 0;
currentAlert = CFUserNotificationCreate(NULL, 0.0, flags, &err, (__bridge CFDictionaryRef)(noteAttributes));
if (err) {
NSLog(@"Can't make notification for EXIT_LOGGED_FAILURE(EX_SOFTWARE);
}
currentAlertSource = CFUserNotificationCreateRunLoopSource(NULL, currentAlert, applicantChoice, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode);
}
applicantToAskAbout.applicantUIState = ApplicantOnScreen;
}
static void scheduleActivity(int alertInterval)
{
xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval);
xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN);
xpc_dictionary_set_bool(options, XPC_ACTIVITY_REPEATING, false);
xpc_dictionary_set_bool(options, XPC_ACTIVITY_ALLOW_BATTERY, true);
xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) {
NSLog(@"activity handler fired");
});
}
static void reminderChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags)
{
if (kCFUserNotificationAlternateResponse == responseFlags || kCFUserNotificationDefaultResponse == responseFlags) {
PersistantState *state = [PersistantState loadFromStorage];
NSDate *nowish = [NSDate new];
state.pendingApplicationReminder = [nowish dateByAddingTimeInterval: state.pendingApplicationReminderAlertInterval];
scheduleActivity(state.pendingApplicationReminderAlertInterval);
[state writeToStorage];
if (kCFUserNotificationAlternateResponse == responseFlags) {
BOOL ok = [[LSApplicationWorkspace defaultWorkspace] openSensitiveURL:[NSURL URLWithString:castleKeychainUrl] withOptions:nil];
NSLog(@"ok= }
}
cancelCurrentAlert(true);
}
static NSString* getLocalizedDeviceClass(void) {
NSString *deviceType = NULL;
switch (MGGetSInt32Answer(kMGQDeviceClassNumber, MGDeviceClassInvalid)) {
case MGDeviceClassiPhone:
deviceType = (__bridge NSString*)SecCopyCKString(SEC_CK_THIS_IPHONE);
break;
case MGDeviceClassiPod:
deviceType = (__bridge NSString*)SecCopyCKString(SEC_CK_THIS_IPOD);
break;
case MGDeviceClassiPad:
deviceType = (__bridge NSString*)SecCopyCKString(SEC_CK_THIS_IPAD);
break;
default:
deviceType = (__bridge NSString*)SecCopyCKString(SEC_CK_THIS_DEVICE);
break;
}
return deviceType;
}
static bool iCloudResetAvailable()
{
SecureBackup *backupd = [SecureBackup new];
NSDictionary *backupdResults;
NSError *error = [backupd getAccountInfoWithInfo:nil results:&backupdResults];
NSLog(@"SecureBackup e= return (nil == error && [backupdResults[kSecureBackupIsEnabledKey] isEqualToNumber:@YES]);
}
static void postApplicationReminderAlert(NSDate *nowish, PersistantState *state, unsigned int alertInterval)
{
NSString *deviceType = getLocalizedDeviceClass();
bool has_iCSC = iCloudResetAvailable();
NSString *alertMessage = [NSString stringWithFormat:(__bridge NSString*)SecCopyCKString(has_iCSC ? SEC_CK_ARS1_BODY : SEC_CK_ARS0_BODY), deviceType];
if (state.defaultPendingApplicationReminderAlertInterval != state.pendingApplicationReminderAlertInterval) {
alertMessage = [NSString stringWithFormat:@" alertMessage,
state.pendingApplicationReminderAlertInterval,
[nowish copyDescriptionOfIntervalSince:state.applcationDate]];
}
NSDictionary *pendingAttributes = @{
(id)kCFUserNotificationAlertHeaderKey: (__bridge NSString*)SecCopyCKString(has_iCSC ? SEC_CK_ARS1_TITLE : SEC_CK_ARS0_TITLE),
(id)kCFUserNotificationAlertMessageKey: alertMessage,
(id)kCFUserNotificationDefaultButtonTitleKey: (__bridge NSString*)SecCopyCKString(SEC_CK_AR_APPROVE_OTHER),
(id)kCFUserNotificationAlternateButtonTitleKey: has_iCSC ? (__bridge NSString*)SecCopyCKString(SEC_CK_AR_USE_CODE) : @"",
(id)kCFUserNotificationAlertTopMostKey: (id)kCFBooleanTrue,
(__bridge id)SBUserNotificationHideButtonsInAwayView: @YES,
(__bridge id)SBUserNotificationDontDismissOnUnlock: @YES,
(__bridge id)SBUserNotificationDismissOnLock: @NO,
(__bridge id)SBUserNotificationOneButtonPerLine: @YES,
};
SInt32 err = 0;
currentAlert = CFUserNotificationCreate(NULL, 0.0, kCFUserNotificationPlainAlertLevel, &err, (__bridge CFDictionaryRef)(pendingAttributes));
if (err) {
NSLog(@"Can't make pending notification err= } else {
currentAlertIsForApplicants = false;
currentAlertSource = CFUserNotificationCreateRunLoopSource(NULL, currentAlert, reminderChoice, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode);
}
}
static void kickOutChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags)
{
NSLog(@"kOC if (kCFUserNotificationAlternateResponse == responseFlags) {
// We need to let things unwind to main for the new state to get saved
doOnceInMain(^{
BOOL ok = [[LSApplicationWorkspace defaultWorkspace] openSensitiveURL:[NSURL URLWithString:castleKeychainUrl] withOptions:nil];
NSLog(@"ok= });
}
cancelCurrentAlert(true);
}
static void postKickedOutAlert(enum DepartureReason reason)
{
NSString *deviceType = getLocalizedDeviceClass();
NSString *message = nil;
debugState = @"pKOA A";
bool ok_to_use_code = iCloudResetAvailable();
debugState = @"pKOA B";
switch (reason) {
case kSOSNeverLeftCircle:
// Was: SEC_CK_CR_BODY_NEVER_LEFT
return;
break;
case kSOSWithdrewMembership:
// Was: SEC_CK_CR_BODY_WITHDREW
// "... if you turn off a switch you have some idea why the light is off" - Murf
return;
break;
case kSOSMembershipRevoked:
message = [NSString stringWithFormat:(__bridge NSString*)SecCopyCKString(SEC_CK_CR_BODY_REVOKED), deviceType];
break;
case kSOSLeftUntrustedCircle:
message = [NSString stringWithFormat:(__bridge NSString*)SecCopyCKString(SEC_CK_CR_BODY_LEFT_UNTRUSTED), deviceType];
ok_to_use_code = false;
break;
case kSOSNeverAppliedToCircle:
// We didn't get kicked out, we were never here. This should only happen if we changed iCloud accounts
// (and we had sync on in the previous one, and never had it on in the new one). As this is explicit
// user action alot of thd "Light switch" argument (above) applies.
return;
break;
default:
message = [NSString stringWithFormat:(__bridge NSString*)SecCopyCKString(SEC_CK_CR_BODY_UNKNOWN), deviceType];
ok_to_use_code = false;
syslog(LOG_ERR, "Unknown DepartureReason break;
}
NSDictionary *kickedAttributes = @{
(id)kCFUserNotificationAlertHeaderKey: (__bridge NSString*)SecCopyCKString(SEC_CK_CR_TITLE),
(id)kCFUserNotificationAlertMessageKey: message,
(id)kCFUserNotificationDefaultButtonTitleKey: (__bridge NSString*)SecCopyCKString(SEC_CK_CR_OK),
(id)kCFUserNotificationAlternateButtonTitleKey: ok_to_use_code ? (__bridge NSString*)SecCopyCKString(SEC_CK_CR_USE_CODE)
: @"",
(id)kCFUserNotificationAlertTopMostKey: (id)kCFBooleanTrue,
(__bridge id)SBUserNotificationHideButtonsInAwayView: @YES,
(__bridge id)SBUserNotificationDontDismissOnUnlock: @YES,
(__bridge id)SBUserNotificationDismissOnLock: @NO,
(__bridge id)SBUserNotificationOneButtonPerLine: @YES,
};
SInt32 err = 0;
if (currentAlertIsForKickOut) {
debugState = @"pKOA C";
NSLog(@"Updating existing alert CFUserNotificationUpdate(currentAlert, 0, kCFUserNotificationPlainAlertLevel, (__bridge CFDictionaryRef)(kickedAttributes));
} else {
debugState = @"pKOA D";
CFUserNotificationRef note = CFUserNotificationCreate(NULL, 0.0, kCFUserNotificationPlainAlertLevel, &err, (__bridge CFDictionaryRef)(kickedAttributes));
if (err) {
NSLog(@"Can't make kicked out notification err= } else {
currentAlertIsForApplicants = false;
currentAlertIsForKickOut = true;
currentAlert = note;
NSLog(@"New ko alert currentAlertSource = CFUserNotificationCreateRunLoopSource(NULL, currentAlert, kickOutChoice, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode);
int backupStateChangeToken;
notify_register_dispatch("com.apple.EscrowSecurityAlert.reset", &backupStateChangeToken, dispatch_get_main_queue(), ^(int token) {
if (currentAlert == note) {
NSLog(@"Backup state might have changed (dS= postKickedOutAlert(reason);
} else {
NSLog(@"Backup state may have changed, but we don't care anymore (dS= }
});
debugState = @"pKOA E";
CFRunLoopRun();
debugState = @"pKOA F";
notify_cancel(backupStateChangeToken);
}
}
debugState = @"pKOA Z";
}
static bool processEvents()
{
debugState = @"processEvents A";
CFErrorRef error = NULL;
CFErrorRef departError = NULL;
SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&error);
NSDate *nowish = [NSDate date];
PersistantState *state = [PersistantState loadFromStorage];
enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&departError);
NSLog(@"CircleStatus
NSTimeInterval timeUntilApplicationAlert = [state.pendingApplicationReminder timeIntervalSinceDate:nowish];
NSLog(@"Time until pendingApplicationReminder (
if (circleStatus == kSOSCCRequestPending && timeUntilApplicationAlert <= 0) {
debugState = @"reminderAlert";
postApplicationReminderAlert(nowish, state, state.pendingApplicationReminderAlertInterval);
} else if (circleStatus == kSOSCCRequestPending) {
scheduleActivity(ceil(timeUntilApplicationAlert));
}
if (((circleStatus == kSOSCCNotInCircle || circleStatus == kSOSCCCircleAbsent) && state.lastCircleStatus == kSOSCCInCircle) || state.debugShowLeftReason || (circleStatus == kSOSCCNotInCircle && state.lastCircleStatus == kSOSCCCircleAbsent && state.absentCircleWithNoReason)) {
debugState = @"processEvents B";
// Use to be in the circle, now we aren't. We ought to tell the user why.
if (state.debugShowLeftReason) {
NSLog(@"debugShowLeftReason is departureReason = [state.debugShowLeftReason intValue];
state.debugShowLeftReason = nil;
departError = NULL;
[state writeToStorage];
}
if (kSOSDepartureReasonError != departureReason) {
if (circleStatus == kSOSCCCircleAbsent && departureReason == kSOSNeverLeftCircle) {
// We don't yet know why the circle has vanished, remember our current ignorance
state.absentCircleWithNoReason = YES;
} else {
state.absentCircleWithNoReason = NO;
}
NSLog(@"Depature reason postKickedOutAlert(departureReason);
NSLog(@"pKOA returned (cS } else {
NSLog(@"Can't get last depature reason: }
}
debugState = @"processEvents C";
if (circleStatus != state.lastCircleStatus) {
SOSCCStatus lastCircleStatus = state.lastCircleStatus;
state.lastCircleStatus = circleStatus;
if (lastCircleStatus != kSOSCCRequestPending && circleStatus == kSOSCCRequestPending) {
state.applcationDate = nowish;
state.pendingApplicationReminder = [nowish dateByAddingTimeInterval: state.pendingApplicationReminderAlertInterval];
scheduleActivity(state.pendingApplicationReminderAlertInterval);
}
if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) {
NSLog(@"Pending request completed");
state.applcationDate = [NSDate distantPast];
state.pendingApplicationReminder = [NSDate distantFuture];
}
[state writeToStorage];
}
if (circleStatus != kSOSCCInCircle) {
if (circleStatus == kSOSCCRequestPending && currentAlert) {
int notifyToken = 0;
CFUserNotificationRef postedAlert = currentAlert;
debugState = @"processEvents D1";
notify_register_dispatch(kSOSCCCircleChangedNotification, ¬ifyToken, dispatch_get_main_queue(), ^(int token) {
if (postedAlert != currentAlert) {
NSLog(@"-- CC after original alert gone (currentAlertIsForApplicants notify_cancel(token);
} else {
CFErrorRef localError = NULL;
SOSCCStatus newCircleStatus = SOSCCThisDeviceIsInCircle(&localError);
if (newCircleStatus != kSOSCCRequestPending) {
if (newCircleStatus == kSOSCCError)
NSLog(@"No longer pending (nCS= else
NSLog(@"No longer pending (nCS= cancelCurrentAlert(true);
} else {
NSLog(@"Still pending...");
}
}
});
debugState = @"processEvents D2";
NSLog(@"NOTE: currentAlertIsForApplicants CFRunLoopRun();
return true;
}
debugState = @"processEvents D3";
NSLog(@"SOSCCThisDeviceIsInCircle status return false;
}
debugState = @"processEvents E";
applicants = [NSMutableDictionary new];
for (id applicantInfo in (__bridge_transfer NSArray *)(SOSCCCopyApplicantPeerInfo(&error))) {
Applicant *applicant = [[Applicant alloc] initWithPeerInfo:(__bridge SOSPeerInfoRef)(applicantInfo)];
applicants[applicant.idString] = applicant;
}
int notify_token = -42;
debugState = @"processEvents F";
int notify_register_status = notify_register_dispatch(kSOSCCCircleChangedNotification, ¬ify_token, dispatch_get_main_queue(), ^(int token) {
NSLog(@"Notified: CFErrorRef circleStatusError = NULL;
bool needsUpdate = false;
CFErrorRef copyPeerError = NULL;
NSMutableSet *newIds = [NSMutableSet new];
for (id applicantInfo in (__bridge_transfer NSArray *)(SOSCCCopyApplicantPeerInfo(©PeerError))) {
Applicant *newApplicant = [[Applicant alloc] initWithPeerInfo:(__bridge SOSPeerInfoRef)(applicantInfo)];
[newIds addObject:newApplicant.idString];
Applicant *existingApplicant = applicants[newApplicant.idString];
if (existingApplicant) {
switch (existingApplicant.applicantUIState) {
case ApplicantWaiting:
applicants[newApplicant.idString] = newApplicant;
break;
case ApplicantOnScreen:
newApplicant.applicantUIState = ApplicantOnScreen;
applicants[newApplicant.idString] = newApplicant;
break;
default:
NSLog(@"Update to break;
}
} else {
needsUpdate = true;
applicants[newApplicant.idString] = newApplicant;
}
}
if (copyPeerError) {
NSLog(@"Could not update peer info array: return;
}
NSMutableArray *idsToRemoveFromApplicants = [NSMutableArray new];
for (NSString *exisitngId in [applicants keyEnumerator]) {
if (![newIds containsObject:exisitngId]) {
[idsToRemoveFromApplicants addObject:exisitngId];
needsUpdate = true;
}
}
[applicants removeObjectsForKeys:idsToRemoveFromApplicants];
if (newIds.count == 0) {
NSLog(@"All applicants were handled elsewhere");
cancelCurrentAlert(true);
}
SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&circleStatusError);
if (kSOSCCInCircle != currentCircleStatus) {
NSLog(@"Left circle ( cancelCurrentAlert(true);
}
if (needsUpdate) {
askAboutAll(false);
} else {
NSLog(@"needsUpdate false, not updating alert");
}
});
NSLog(@"ACC token debugState = @"processEvents F2";
if (applicants.count == 0) {
NSLog(@"No applicants");
} else {
debugState = @"processEvents F3";
askAboutAll(false);
debugState = @"processEvents F4";
if (currentAlert) {
debugState = @"processEvents F5";
CFRunLoopRun();
}
}
debugState = @"processEvents F6";
notify_cancel(notify_token);
debugState = @"processEvents DONE";
return false;
}
int main (int argc, const char * argv[])
{
@autoreleasepool {
xpc_transaction_begin();
// NOTE: DISPATCH_QUEUE_PRIORITY_LOW will not actually manage to drain events
// in a lot of cases (like circleStatus != kSOSCCInCircle)
xpc_set_event_stream_handler("com.apple.notifyd.matching", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(xpc_object_t object) {
char *event_description = xpc_copy_description(object);
NSLog(@"notifyd event: free(event_description);
});
xpc_activity_register(kLaunchLaterXPCName, XPC_ACTIVITY_CHECK_IN, ^(xpc_activity_t activity) {
});
int falseInARow = 0;
while (falseInARow < 2) {
if (processEvents()) {
falseInARow = 0;
} else {
falseInARow++;
}
cancelCurrentAlert(false);
if (doOnceInMainBlockChain) {
doOnceInMainBlockChain();
doOnceInMainBlockChain = NULL;
}
}
}
NSLog(@"Done");
xpc_transaction_end();
return(0);
}