From eed078260563cd29769fedc74d428f8151c8f457 Mon Sep 17 00:00:00 2001 From: Tom Burgin Date: Wed, 4 Dec 2024 13:43:46 -0500 Subject: [PATCH] sync service: add support for APNS --- Source/common/SNTConfigurator.h | 6 ++ Source/common/SNTConfigurator.m | 13 ++++ Source/common/SNTXPCControlInterface.h | 1 + Source/common/SNTXPCNotifierInterface.h | 1 + Source/common/SNTXPCSyncServiceInterface.h | 16 +++- Source/gui/BUILD | 1 + Source/gui/SNTAppDelegate.m | 36 +++++++++ Source/gui/SNTNotificationManager.h | 3 +- Source/gui/SNTNotificationManager.m | 48 ++++++++++++ Source/gui/SNTNotificationManagerTest.m | 36 +++++++++ Source/santad/SNTDaemonControlController.mm | 7 +- Source/santasyncservice/BUILD | 10 ++- Source/santasyncservice/SNTPushClientAPNS.h | 21 +++++ Source/santasyncservice/SNTPushClientAPNS.m | 77 +++++++++++++++++++ Source/santasyncservice/SNTPushClientFCM.h | 20 +++++ ...PushNotifications.m => SNTPushClientFCM.m} | 33 +++++--- .../santasyncservice/SNTPushNotifications.h | 21 +++-- Source/santasyncservice/SNTSyncManager.h | 4 +- Source/santasyncservice/SNTSyncManager.m | 46 ++++++++--- Source/santasyncservice/SNTSyncService.m | 12 ++- 20 files changed, 374 insertions(+), 38 deletions(-) create mode 100644 Source/santasyncservice/SNTPushClientAPNS.h create mode 100644 Source/santasyncservice/SNTPushClientAPNS.m create mode 100644 Source/santasyncservice/SNTPushClientFCM.h rename Source/santasyncservice/{SNTPushNotifications.m => SNTPushClientFCM.m} (93%) diff --git a/Source/common/SNTConfigurator.h b/Source/common/SNTConfigurator.h index 2725f2da..7fb7d38d 100644 --- a/Source/common/SNTConfigurator.h +++ b/Source/common/SNTConfigurator.h @@ -656,6 +656,12 @@ /// @property(readonly, nonatomic) BOOL fcmEnabled; +/// +/// Set to true to use APNS. Defaults to false. +/// If fcmEnabled and enableAPNS are both enabled, only enableAPNS will be used. +/// +@property(readonly, nonatomic) BOOL enableAPNS; + /// /// True if metricsFormat and metricsURL are set. False otherwise. /// diff --git a/Source/common/SNTConfigurator.m b/Source/common/SNTConfigurator.m index 221a52ce..db488bd4 100644 --- a/Source/common/SNTConfigurator.m +++ b/Source/common/SNTConfigurator.m @@ -143,6 +143,8 @@ @implementation SNTConfigurator static NSString *const kFCMEntity = @"FCMEntity"; static NSString *const kFCMAPIKey = @"FCMAPIKey"; +static NSString *const kEnableAPNS = @"EnableAPNS"; + static NSString *const kEntitlementsPrefixFilterKey = @"EntitlementsPrefixFilter"; static NSString *const kEntitlementsTeamIDFilterKey = @"EntitlementsTeamIDFilter"; @@ -279,6 +281,7 @@ - (instancetype)initWithSyncStateFile:(NSString *)syncStateFilePath kFCMProject : string, kFCMEntity : string, kFCMAPIKey : string, + kEnableAPNS : number, kMetricFormat : string, kMetricURL : string, kMetricExportInterval : number, @@ -597,6 +600,10 @@ + (NSSet *)keyPathsForValuesAffectingFcmEnabled { return [self configStateSet]; } ++ (NSSet *)keyPathsForValuesAffectingEnableAPNS { + return [self configStateSet]; +} + + (NSSet *)keyPathsForValuesAffectingEnableBadSignatureProtection { return [self configStateSet]; } @@ -1113,6 +1120,12 @@ - (BOOL)fcmEnabled { return (self.fcmProject.length && self.fcmEntity.length && self.fcmAPIKey.length); } +- (BOOL)enableAPNS { + // TODO: Consider supporting enablement from the sync server. + NSNumber *number = self.configState[kEnableAPNS]; + return [number boolValue]; +} + - (void)setBlockUSBMount:(BOOL)enabled { [self updateSyncStateForKey:kBlockUSBMountKey value:@(enabled)]; } diff --git a/Source/common/SNTXPCControlInterface.h b/Source/common/SNTXPCControlInterface.h index 4194d80a..a8b9d7ef 100644 --- a/Source/common/SNTXPCControlInterface.h +++ b/Source/common/SNTXPCControlInterface.h @@ -59,6 +59,7 @@ /// Syncd Ops /// - (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)(void))reply; +- (void)requestAPNSToken:(void (^)(NSString *))reply; /// /// Control Ops diff --git a/Source/common/SNTXPCNotifierInterface.h b/Source/common/SNTXPCNotifierInterface.h index 8e922dbf..b2e0bf88 100644 --- a/Source/common/SNTXPCNotifierInterface.h +++ b/Source/common/SNTXPCNotifierInterface.h @@ -39,6 +39,7 @@ binaryCount:(uint64_t)binaryCount fileCount:(uint64_t)fileCount hashedCount:(uint64_t)hashedCount; +- (void)requestAPNSToken:(void (^)(NSString *))reply; @end @interface SNTXPCNotifierInterface : NSObject diff --git a/Source/common/SNTXPCSyncServiceInterface.h b/Source/common/SNTXPCSyncServiceInterface.h index 2803ff9a..08410090 100644 --- a/Source/common/SNTXPCSyncServiceInterface.h +++ b/Source/common/SNTXPCSyncServiceInterface.h @@ -28,7 +28,7 @@ - (void)postEventsToSyncServer:(NSArray *)events fromBundle:(BOOL)fromBundle; - (void)postBundleEventToSyncServer:(SNTStoredEvent *)event reply:(void (^)(SNTBundleEventAction))reply; -- (void)isFCMListening:(void (^)(BOOL))reply; +- (void)isPushConnected:(void (^)(BOOL))reply; // The syncservice regularly syncs with a configured sync server. Use this method to sync out of // band. The syncservice ensures syncs do not run concurrently. @@ -51,6 +51,20 @@ // A new connection to the syncservice will bring it back up. This allows us to avoid running // the syncservice needlessly when there is no configured sync server. - (void)spindown; + +// The GUI process registers with APNS. The token is then retrieved by the sync service. However, +// tokens are unique per-{device, app, and logged in user}. During fast user switching, a second +// GUI process spins up and registers with APNS. The sync service should then start using that APNS +// token. This method is called when the token has changed, letting the sync service know it needs +// to go and fetch the updated token. Why does the GUI process not send the token to the sync +// service when it changes? A few reasons. First, if the sync service restarts for any reason, it +// will be left without a token. Second, the "active" GUI process is already being negotiated +// between the GUIs and the daemon. Having the sync service fetch the token, via the daemon, +// utilizes the already negotiated active GUI process for token retrieval. +- (void)APNSTokenChanged; + +// The GUI process forwards APNS messages to the sync service. +- (void)handleAPNSMessage:(NSDictionary *)message; @end @interface SNTXPCSyncServiceInterface : NSObject diff --git a/Source/gui/BUILD b/Source/gui/BUILD index dc8878f8..e17af79a 100644 --- a/Source/gui/BUILD +++ b/Source/gui/BUILD @@ -107,6 +107,7 @@ objc_library( "//Source/common:SNTSyncConstants", "//Source/common:SNTXPCControlInterface", "//Source/common:SNTXPCNotifierInterface", + "//Source/common:SNTXPCSyncServiceInterface", "@MOLCertificate", "@MOLCodesignChecker", "@MOLXPCConnection", diff --git a/Source/gui/SNTAppDelegate.m b/Source/gui/SNTAppDelegate.m index 4d1cf88c..19ce15b8 100644 --- a/Source/gui/SNTAppDelegate.m +++ b/Source/gui/SNTAppDelegate.m @@ -20,6 +20,7 @@ #import "Source/common/SNTLogging.h" #import "Source/common/SNTStrengthify.h" #import "Source/common/SNTXPCControlInterface.h" +#import "Source/common/SNTXPCSyncServiceInterface.h" #import "Source/gui/SNTAboutWindowController.h" #import "Source/gui/SNTNotificationManager.h" @@ -34,6 +35,10 @@ @implementation SNTAppDelegate #pragma mark App Delegate methods - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + if ([SNTConfigurator configurator].enableAPNS) { + [NSApp registerForRemoteNotifications]; + } + [self setupMenu]; self.notificationManager = [[SNTNotificationManager alloc] init]; @@ -100,6 +105,10 @@ - (void)createDaemonConnection { // Now wait for the connection to come in. if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) { [self attemptDaemonReconnection]; + } else { + // Let the sync service know the APNS token may have changed. The sync service will call back on + // the above listener to get the updated token. + [self.notificationManager APNSTokenChanged]; } } @@ -125,4 +134,31 @@ - (void)setupMenu { [NSApp setMainMenu:mainMenu]; } +#pragma mark Push Notifications + +- (void)application:(NSApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)tokenData { + NSMutableString *deviceToken = [NSMutableString stringWithCapacity:tokenData.length * 2]; + const unsigned char *bytes = tokenData.bytes; + for (NSUInteger i = 0; i < tokenData.length; ++i) { + [deviceToken appendFormat:@"%02x", bytes[i]]; + } + LOGD(@"APNS Token: %@", deviceToken); + [self.notificationManager didRegisterForAPNS:deviceToken]; +} + +- (void)application:(NSApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + LOGE(@"Failed to register with APNS: %@", error); +} + +- (void)application:(NSApplication *)application + didReceiveRemoteNotification:(NSDictionary *)message { + LOGD(@"APNS Message: %@", message); + MOLXPCConnection *syncConn = [SNTXPCSyncServiceInterface configuredConnection]; + [syncConn resume]; + [[syncConn remoteObjectProxy] handleAPNSMessage:message]; + [syncConn invalidate]; +} + @end diff --git a/Source/gui/SNTNotificationManager.h b/Source/gui/SNTNotificationManager.h index ec7f98a2..0ea10c8b 100644 --- a/Source/gui/SNTNotificationManager.h +++ b/Source/gui/SNTNotificationManager.h @@ -23,5 +23,6 @@ @interface SNTNotificationManager : NSObject @property NSXPCListenerEndpoint *notificationListener; - +- (void)didRegisterForAPNS:(NSString *)deviceToken; +- (void)APNSTokenChanged; @end diff --git a/Source/gui/SNTNotificationManager.m b/Source/gui/SNTNotificationManager.m index 4ce0837d..fab9eefb 100644 --- a/Source/gui/SNTNotificationManager.m +++ b/Source/gui/SNTNotificationManager.m @@ -29,6 +29,8 @@ #import "Source/common/SNTStrengthify.h" #import "Source/common/SNTSyncConstants.h" #import "Source/common/SNTXPCControlInterface.h" +#import "Source/common/SNTXPCSyncServiceInterface.h" +#import "Source/gui/SNTAppDelegate.h" #import "Source/gui/SNTBinaryMessageWindowController.h" #import "Source/gui/SNTBinaryMessageWindowView-Swift.h" #import "Source/gui/SNTDeviceMessageWindowController.h" @@ -46,6 +48,17 @@ @interface SNTNotificationManager () // A serial queue for holding hashBundleBinaries requests @property dispatch_queue_t hashBundleBinariesQueue; +// The APNS device token. If configured, the GUI app registers with APNS. Once the registration is +// complete, the app delegate will notify this class. Any pending requests for the token will then +// be processed. +@property(atomic) NSString *APNSDeviceToken; + +// A queue for serializing APNS token requests. +@property dispatch_queue_t APNSQueue; + +// Stores APNS token requests, while waiting for APNS registration. +@property NSMutableArray *APNSTokenRequests; + @end @implementation SNTNotificationManager @@ -58,6 +71,8 @@ - (instancetype)init { _pendingNotifications = [[NSMutableArray alloc] init]; _hashBundleBinariesQueue = dispatch_queue_create("com.northpolesec.santagui.hashbundlebinaries", DISPATCH_QUEUE_SERIAL); + _APNSQueue = dispatch_queue_create("com.northpolesec.santagui.apns", DISPATCH_QUEUE_SERIAL); + _APNSTokenRequests = [NSMutableArray array]; } return self; } @@ -400,6 +415,39 @@ - (void)postFileAccessBlockNotification:(SNTFileAccessEvent *)event [self queueMessage:pendingMsg]; } +// XPC handler. The sync service requests the APNS token, by way of the daemon. +- (void)requestAPNSToken:(void (^)(NSString *))reply { + if (self.APNSDeviceToken.length) { + reply(self.APNSDeviceToken); + return; + } + + // If APNS is enabled, `-[NSApp registerForRemoteNotifications]` is run when the application + // finishes launching at startup. If APNS is enabled after startup, register now. + [NSApp registerForRemoteNotifications]; + dispatch_async(self.APNSQueue, ^{ + [self.APNSTokenRequests addObject:reply]; + }); +} + +- (void)didRegisterForAPNS:(NSString *)deviceToken { + self.APNSDeviceToken = deviceToken; + [self APNSTokenChanged]; + dispatch_async(self.APNSQueue, ^{ + for (void (^reply)(NSString *) in self.APNSTokenRequests) { + reply(deviceToken); + } + [self.APNSTokenRequests removeAllObjects]; + }); +} + +- (void)APNSTokenChanged { + MOLXPCConnection *syncConn = [SNTXPCSyncServiceInterface configuredConnection]; + [syncConn resume]; + [[syncConn remoteObjectProxy] APNSTokenChanged]; + [syncConn invalidate]; +} + #pragma mark SNTBundleNotifierXPC protocol methods - (void)updateCountsForEvent:(SNTStoredEvent *)event diff --git a/Source/gui/SNTNotificationManagerTest.m b/Source/gui/SNTNotificationManagerTest.m index acf46ab0..404c46a4 100644 --- a/Source/gui/SNTNotificationManagerTest.m +++ b/Source/gui/SNTNotificationManagerTest.m @@ -76,4 +76,40 @@ - (void)testPostBlockNotificationSendsDistributedNotification { deliverImmediately:YES]); } +- (void)testDidRegisterForAPNS { + SNTNotificationManager *nm = [[SNTNotificationManager alloc] init]; + __block NSString *token; + XCTestExpectation *exp = [self expectationWithDescription:@"Wait for APNS token"]; + [nm requestAPNSToken:^(NSString *reply) { + token = reply; + [exp fulfill]; + }]; + NSString *wantToken = @"123"; + [nm didRegisterForAPNS:wantToken]; + [self waitForExpectationsWithTimeout:5 handler:nil]; + XCTAssertEqualObjects(token, wantToken); + + // Subsequent requests should be handled by the cache. + token = nil; + [nm requestAPNSToken:^(NSString *reply) { + token = reply; + }]; + XCTAssertEqualObjects(token, wantToken); + + // Ensure multiple in-flight requests are handled. + nm = [[SNTNotificationManager alloc] init]; + int wantCount = 5; + __block int count = 0; + for (int i = 0; i < wantCount; ++i) { + exp = [self expectationWithDescription:@"Wait for multiple requests for the APNS token"]; + [nm requestAPNSToken:^(NSString *reply) { + ++count; + [exp fulfill]; + }]; + } + [nm didRegisterForAPNS:@"hello"]; + [self waitForExpectationsWithTimeout:5 handler:nil]; + XCTAssertEqual(count, wantCount); +} + @end diff --git a/Source/santad/SNTDaemonControlController.mm b/Source/santad/SNTDaemonControlController.mm index 6d9f3842..3c38985d 100644 --- a/Source/santad/SNTDaemonControlController.mm +++ b/Source/santad/SNTDaemonControlController.mm @@ -334,10 +334,15 @@ - (void)setNotificationListener:(NSXPCListenerEndpoint *)listener { self.notQueue.notifierConnection = c; } +- (void)requestAPNSToken:(void (^)(NSString *))reply { + // Simply forward request to the active GUI (if any). + [self.notQueue.notifierConnection.remoteObjectProxy requestAPNSToken:reply]; +} + #pragma mark syncd Ops - (void)pushNotifications:(void (^)(BOOL))reply { - [self.syncdQueue.syncConnection.remoteObjectProxy isFCMListening:^(BOOL response) { + [self.syncdQueue.syncConnection.remoteObjectProxy isPushConnected:^(BOOL response) { reply(response); }]; } diff --git a/Source/santasyncservice/BUILD b/Source/santasyncservice/BUILD index b4fca2d3..939ccb57 100644 --- a/Source/santasyncservice/BUILD +++ b/Source/santasyncservice/BUILD @@ -22,8 +22,11 @@ objc_library( srcs = [ "NSData+Zlib.h", "NSData+Zlib.m", + "SNTPushClientAPNS.h", + "SNTPushClientAPNS.m", + "SNTPushClientFCM.h", + "SNTPushClientFCM.m", "SNTPushNotifications.h", - "SNTPushNotifications.m", "SNTPushNotificationsTracker.h", "SNTPushNotificationsTracker.m", "SNTSyncEventUpload.h", @@ -76,8 +79,11 @@ santa_unit_test( srcs = [ "NSData+Zlib.h", "NSData+Zlib.m", + "SNTPushClientAPNS.h", + "SNTPushClientAPNS.m", + "SNTPushClientFCM.h", + "SNTPushClientFCM.m", "SNTPushNotifications.h", - "SNTPushNotifications.m", "SNTPushNotificationsTracker.h", "SNTPushNotificationsTracker.m", "SNTSyncEventUpload.h", diff --git a/Source/santasyncservice/SNTPushClientAPNS.h b/Source/santasyncservice/SNTPushClientAPNS.h new file mode 100644 index 00000000..d1a852c0 --- /dev/null +++ b/Source/santasyncservice/SNTPushClientAPNS.h @@ -0,0 +1,21 @@ +/// Copyright 2024 North Pole Security, Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License + +#import "Source/santasyncservice/SNTPushNotifications.h" + +@interface SNTPushClientAPNS : NSObject +- (instancetype)initWithSyncDelegate:(id)syncDelegate; +- (void)APNSTokenChanged; +- (void)handleAPNSMessage:(NSDictionary *)message; +@end diff --git a/Source/santasyncservice/SNTPushClientAPNS.m b/Source/santasyncservice/SNTPushClientAPNS.m new file mode 100644 index 00000000..221edc16 --- /dev/null +++ b/Source/santasyncservice/SNTPushClientAPNS.m @@ -0,0 +1,77 @@ +/// Copyright 2024 North Pole Security, Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License + +#import "Source/santasyncservice/SNTPushClientAPNS.h" + +#import + +#import "Source/common/SNTLogging.h" +#import "Source/common/SNTSyncConstants.h" +#import "Source/common/SNTXPCControlInterface.h" +#import "Source/santasyncservice/SNTSyncState.h" + +@interface SNTPushClientAPNS () +@property(weak) id delegate; +@property(atomic) NSString *deviceToken; +@property NSUInteger fullSyncInterval; +@property NSUInteger globalRuleSyncDeadline; +@end + +@implementation SNTPushClientAPNS + +- (instancetype)initWithSyncDelegate:(id)syncDelegate { + self = [super init]; + if (self) { + _delegate = syncDelegate; + _fullSyncInterval = kDefaultPushNotificationsFullSyncInterval; + _globalRuleSyncDeadline = kDefaultPushNotificationsGlobalRuleSyncDeadline; + [self updateToken]; + } + return self; +} + +- (void)updateToken { + [[self.delegate daemonConnection].remoteObjectProxy + requestAPNSToken:^void(NSString *deviceToken) { + self.deviceToken = deviceToken; + }]; +} + +- (BOOL)isConnected { + return self.deviceToken.length > 0; +} + +- (NSString *)getToken { + return self.deviceToken; +} + +- (NSUInteger)getFullSyncInterval { + return self.fullSyncInterval; +} + +- (void)APNSTokenChanged { + [self updateToken]; +} + +- (void)handlePreflightSyncState:(SNTSyncState *)syncState { + self.fullSyncInterval = syncState.pushNotificationsFullSyncInterval; + self.globalRuleSyncDeadline = syncState.pushNotificationsGlobalRuleSyncDeadline; +} + +- (void)handleAPNSMessage:(NSDictionary *)message { + // TODO: Parse and handle the message. + LOGI(@"handleAPNSMessage: %@", message); +} + +@end diff --git a/Source/santasyncservice/SNTPushClientFCM.h b/Source/santasyncservice/SNTPushClientFCM.h new file mode 100644 index 00000000..1c665203 --- /dev/null +++ b/Source/santasyncservice/SNTPushClientFCM.h @@ -0,0 +1,20 @@ +/// Copyright 2024 North Pole Security, Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License + +#import +#import "Source/santasyncservice/SNTPushNotifications.h" + +@interface SNTPushClientFCM : NSObject +- (instancetype)initWithSyncDelegate:(id)syncDelegate; +@end diff --git a/Source/santasyncservice/SNTPushNotifications.m b/Source/santasyncservice/SNTPushClientFCM.m similarity index 93% rename from Source/santasyncservice/SNTPushNotifications.m rename to Source/santasyncservice/SNTPushClientFCM.m index 25cfce32..8a43158a 100644 --- a/Source/santasyncservice/SNTPushNotifications.m +++ b/Source/santasyncservice/SNTPushClientFCM.m @@ -12,7 +12,7 @@ /// See the License for the specific language governing permissions and /// limitations under the License -#import "Source/santasyncservice/SNTPushNotifications.h" +#import "Source/santasyncservice/SNTPushClientFCM.h" #import "Source/common/SNTConfigurator.h" #import "Source/common/SNTLogging.h" @@ -27,7 +27,9 @@ static NSString *const kFCMFileNameKey = @"file_name"; static NSString *const kFCMTargetHostIDKey = @"target_host_id"; -@interface SNTPushNotifications () +@interface SNTPushClientFCM () + +@property(weak) id delegate; @property SNTSyncFCM *FCMClient; @property NSString *token; @@ -37,19 +39,36 @@ @interface SNTPushNotifications () @end -@implementation SNTPushNotifications +@implementation SNTPushClientFCM #pragma mark push notification methods -- (instancetype)init { +- (instancetype)initWithSyncDelegate:(id)syncDelegate { self = [super init]; if (self) { + _delegate = syncDelegate; _pushNotificationsFullSyncInterval = kDefaultPushNotificationsFullSyncInterval; _pushNotificationsGlobalRuleSyncDeadline = kDefaultPushNotificationsGlobalRuleSyncDeadline; } return self; } +- (BOOL)isConnected { + return self.FCMClient.isConnected; +} + +- (NSString *)getToken { + return self.token; +} + +- (NSUInteger)getFullSyncInterval { + return self.pushNotificationsFullSyncInterval; +} + +- (void)handlePreflightSyncState:(SNTSyncState *)syncState { + [self listenWithSyncState:syncState]; +} + - (void)listenWithSyncState:(SNTSyncState *)syncState { self.pushNotificationsFullSyncInterval = syncState.pushNotificationsFullSyncInterval; self.pushNotificationsGlobalRuleSyncDeadline = syncState.pushNotificationsGlobalRuleSyncDeadline; @@ -182,8 +201,4 @@ - (NSDictionary *)messageFromMessageData:(NSData *)messageData { return message.count ? [message copy] : nil; } -- (BOOL)isConnected { - return self.FCMClient.isConnected; -} - -@end \ No newline at end of file +@end diff --git a/Source/santasyncservice/SNTPushNotifications.h b/Source/santasyncservice/SNTPushNotifications.h index 0056b296..08daf837 100644 --- a/Source/santasyncservice/SNTPushNotifications.h +++ b/Source/santasyncservice/SNTPushNotifications.h @@ -14,24 +14,23 @@ #import -@protocol SNTPushNotificationsDelegate +#import + +@protocol SNTPushNotificationsSyncDelegate - (void)sync; - (void)syncSecondsFromNow:(uint64_t)seconds; - (void)ruleSync; - (void)ruleSyncSecondsFromNow:(uint64_t)seconds; - (void)preflightSync; +- (MOLXPCConnection *)daemonConnection; @end @class SNTSyncState; -@class SNTSyncFCM; - -@interface SNTPushNotifications : NSObject - -- (void)listenWithSyncState:(SNTSyncState *)syncState; -- (void)stop; -@property(weak) id delegate; -@property(readonly) BOOL isConnected; -@property(readonly) NSString *token; -@property(readonly) NSUInteger pushNotificationsFullSyncInterval; +@protocol SNTPushNotificationsClientDelegate +- (instancetype)initWithSyncDelegate:(id)syncDelegate; +- (BOOL)isConnected; +- (NSString *)getToken; +- (NSUInteger)getFullSyncInterval; +- (void)handlePreflightSyncState:(SNTSyncState *)syncState; @end diff --git a/Source/santasyncservice/SNTSyncManager.h b/Source/santasyncservice/SNTSyncManager.h index 0dcf934b..fd11ca11 100644 --- a/Source/santasyncservice/SNTSyncManager.h +++ b/Source/santasyncservice/SNTSyncManager.h @@ -69,6 +69,8 @@ - (void)postEventsToSyncServer:(NSArray *)events fromBundle:(BOOL)isFromBundle; - (void)postBundleEventToSyncServer:(SNTStoredEvent *)event reply:(void (^)(SNTBundleEventAction))reply; -- (void)isFCMListening:(void (^)(BOOL))reply; +- (void)isPushConnected:(void (^)(BOOL))reply; +- (void)APNSTokenChanged; +- (void)handleAPNSMessage:(NSDictionary *)message; @end diff --git a/Source/santasyncservice/SNTSyncManager.m b/Source/santasyncservice/SNTSyncManager.m index 7072cd2c..809952bf 100644 --- a/Source/santasyncservice/SNTSyncManager.m +++ b/Source/santasyncservice/SNTSyncManager.m @@ -25,6 +25,8 @@ #import "Source/common/SNTStrengthify.h" #import "Source/common/SNTSyncConstants.h" #import "Source/common/SNTXPCControlInterface.h" +#import "Source/santasyncservice/SNTPushClientAPNS.h" +#import "Source/santasyncservice/SNTPushClientFCM.h" #import "Source/santasyncservice/SNTPushNotifications.h" #import "Source/santasyncservice/SNTSyncEventUpload.h" #import "Source/santasyncservice/SNTSyncLogging.h" @@ -35,7 +37,7 @@ static const uint8_t kMaxEnqueuedSyncs = 2; -@interface SNTSyncManager () +@interface SNTSyncManager () @property(nonatomic) dispatch_source_t fullSyncTimer; @property(nonatomic) dispatch_source_t ruleSyncTimer; @@ -48,7 +50,8 @@ @interface SNTSyncManager () @property(nonatomic) BOOL reachable; @property nw_path_monitor_t pathMonitor; -@property SNTPushNotifications *pushNotifications; +// If set, push notifications are being used. +@property id pushNotifications; @property NSUInteger eventBatchSize; @@ -65,11 +68,18 @@ - (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn { self = [super init]; if (self) { _daemonConn = daemonConn; - _pushNotifications = [[SNTPushNotifications alloc] init]; - _pushNotifications.delegate = self; + + SNTConfigurator *config = [SNTConfigurator configurator]; + if (config.enableAPNS) { + _pushNotifications = [[SNTPushClientAPNS alloc] initWithSyncDelegate:self]; + } else if (config.fcmEnabled) { + _pushNotifications = [[SNTPushClientFCM alloc] initWithSyncDelegate:self]; + } + _fullSyncTimer = [self createSyncTimerWithBlock:^{ [self rescheduleTimerQueue:self.fullSyncTimer - secondsFromNow:_pushNotifications.pushNotificationsFullSyncInterval]; + secondsFromNow:_pushNotifications ? [_pushNotifications getFullSyncInterval] + : kDefaultFullSyncInterval]; [self syncType:SNTSyncTypeNormal withReply:NULL]; }]; _ruleSyncTimer = [self createSyncTimerWithBlock:^{ @@ -141,8 +151,20 @@ - (void)postBundleEventToSyncServer:(SNTStoredEvent *)event self.xsrfTokenHeader = syncState.xsrfTokenHeader; } -- (void)isFCMListening:(void (^)(BOOL))reply { - reply(self.pushNotifications.isConnected); +- (void)isPushConnected:(void (^)(BOOL))reply { + reply(self.pushNotifications ? self.pushNotifications.isConnected : NO); +} + +- (void)APNSTokenChanged { + if ([self.pushNotifications isKindOfClass:[SNTPushClientAPNS class]]) { + [(SNTPushClientAPNS *)self.pushNotifications APNSTokenChanged]; + } +} + +- (void)handleAPNSMessage:(NSDictionary *)message { + if ([self.pushNotifications isKindOfClass:[SNTPushClientAPNS class]]) { + [(SNTPushClientAPNS *)self.pushNotifications handleAPNSMessage:message]; + } } #pragma mark sync control / SNTPushNotificationsDelegate methods @@ -218,6 +240,10 @@ - (void)preflightSync { [self preflightOnly:YES]; } +- (MOLXPCConnection *)daemonConnection { + return self.daemonConn; +} + #pragma mark syncing chain - (SNTSyncStatusType)preflight { @@ -245,8 +271,8 @@ - (SNTSyncStatusType)preflightOnly:(BOOL)preflightOnly { // Start listening for push notifications with a full sync every // pushNotificationsFullSyncInterval. - if ([SNTConfigurator configurator].fcmEnabled) { - [self.pushNotifications listenWithSyncState:syncState]; + if (self.pushNotifications) { + [self.pushNotifications handlePreflightSyncState:syncState]; } else { LOGD(@"Push notifications are not enabled. Sync every %lu min.", syncState.fullSyncInterval / 60); @@ -376,7 +402,7 @@ - (SNTSyncState *)createSyncStateWithStatus:(SNTSyncStatusType *)status { syncState.session = [authURLSession session]; syncState.daemonConn = self.daemonConn; syncState.contentEncoding = config.syncClientContentEncoding; - syncState.pushNotificationsToken = self.pushNotifications.token; + syncState.pushNotificationsToken = [self.pushNotifications getToken]; return syncState; } diff --git a/Source/santasyncservice/SNTSyncService.m b/Source/santasyncservice/SNTSyncService.m index 28fb700d..4cdecf1f 100644 --- a/Source/santasyncservice/SNTSyncService.m +++ b/Source/santasyncservice/SNTSyncService.m @@ -81,8 +81,8 @@ - (void)postBundleEventToSyncServer:(SNTStoredEvent *)event [self.syncManager postBundleEventToSyncServer:event reply:reply]; } -- (void)isFCMListening:(void (^)(BOOL))reply { - [self.syncManager isFCMListening:reply]; +- (void)isPushConnected:(void (^)(BOOL))reply { + [self.syncManager isPushConnected:reply]; } // TODO(bur): Add support for santactl sync --debug to enable debug logging for that sync. @@ -110,4 +110,12 @@ - (void)spindown { exit(0); } +- (void)APNSTokenChanged { + [self.syncManager APNSTokenChanged]; +} + +- (void)handleAPNSMessage:(NSDictionary *)message { + [self.syncManager handleAPNSMessage:message]; +} + @end