diff options
author | Paul Beusterien <paulbeusterien@google.com> | 2017-05-15 12:27:07 -0700 |
---|---|---|
committer | Paul Beusterien <paulbeusterien@google.com> | 2017-05-15 12:27:07 -0700 |
commit | 98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch) | |
tree | 131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Firebase/Messaging | |
parent | 32461366c9e204a527ca05e6e9b9404a2454ac51 (diff) |
Initial
Diffstat (limited to 'Firebase/Messaging')
74 files changed, 17450 insertions, 0 deletions
diff --git a/Firebase/Messaging/FIRMMessageCode.h b/Firebase/Messaging/FIRMMessageCode.h new file mode 100644 index 0000000..dc381ee --- /dev/null +++ b/Firebase/Messaging/FIRMMessageCode.h @@ -0,0 +1,169 @@ +/* + * Copyright 2017 Google + * + * 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. + */ + +typedef NS_ENUM(NSInteger, FIRMessagingMessageCode) { + // FIRMessaging+FIRApp.m + kFIRMessagingMessageCodeFIRApp000 = 1000, // I-FCM001000 + // FIRMessaging.m + kFIRMessagingMessageCodeMessaging000 = 2000, // I-FCM002000 + kFIRMessagingMessageCodeMessaging001 = 2001, // I-FCM002001 + kFIRMessagingMessageCodeMessaging002 = 2002, // I-FCM002002 - no longer used + kFIRMessagingMessageCodeMessaging003 = 2003, // I-FCM002003 + kFIRMessagingMessageCodeMessaging004 = 2004, // I-FCM002004 + kFIRMessagingMessageCodeMessaging005 = 2005, // I-FCM002005 + kFIRMessagingMessageCodeMessaging006 = 2006, // I-FCM002006 - no longer used + kFIRMessagingMessageCodeMessaging007 = 2007, // I-FCM002007 - no longer used + kFIRMessagingMessageCodeMessaging008 = 2008, // I-FCM002008 - no longer used + kFIRMessagingMessageCodeMessaging009 = 2009, // I-FCM002009 + kFIRMessagingMessageCodeMessaging010 = 2010, // I-FCM002010 + kFIRMessagingMessageCodeMessaging011 = 2011, // I-FCM002011 + kFIRMessagingMessageCodeMessaging012 = 2012, // I-FCM002012 + kFIRMessagingMessageCodeMessaging013 = 2013, // I-FCM002013 + kFIRMessagingMessageCodeMessaging014 = 2014, // I-FCM002014 + kFIRMessagingMessageCodeMessaging015 = 2015, // I-FCM002015 + kFIRMessagingMessageCodeMessaging016 = 2016, // I-FCM002016 + kFIRMessagingMessageCodeMessaging017 = 2017, // I-FCM002017 + kFIRMessagingMessageCodeMessaging018 = 2018, // I-FCM002018 + kFIRMessagingMessageCodeRemoteMessageDelegateMethodNotImplemented = 2019, // I-FCM002019 + kFIRMessagingMessageCodeSenderIDNotSuppliedForTokenFetch = 2020, // I-FCM002020 + kFIRMessagingMessageCodeSenderIDNotSuppliedForTokenDelete = 2021, // I-FCM002021 + kFIRMessagingMessageCodeAPNSTokenNotAvailableDuringTokenFetch = 2022, // I-FCM002022 + // FIRMessagingClient.m + kFIRMessagingMessageCodeClient000 = 4000, // I-FCM004000 + kFIRMessagingMessageCodeClient001 = 4001, // I-FCM004001 + kFIRMessagingMessageCodeClient002 = 4002, // I-FCM004002 + kFIRMessagingMessageCodeClient003 = 4003, // I-FCM004003 + kFIRMessagingMessageCodeClient004 = 4004, // I-FCM004004 + kFIRMessagingMessageCodeClient005 = 4005, // I-FCM004005 + kFIRMessagingMessageCodeClient006 = 4006, // I-FCM004006 + kFIRMessagingMessageCodeClient007 = 4007, // I-FCM004007 + kFIRMessagingMessageCodeClient008 = 4008, // I-FCM004008 + kFIRMessagingMessageCodeClient009 = 4009, // I-FCM004009 + kFIRMessagingMessageCodeClient010 = 4010, // I-FCM004010 + kFIRMessagingMessageCodeClient011 = 4011, // I-FCM004011 + // FIRMessagingConnection.m + kFIRMessagingMessageCodeConnection000 = 5000, // I-FCM005000 + kFIRMessagingMessageCodeConnection001 = 5001, // I-FCM005001 + kFIRMessagingMessageCodeConnection002 = 5002, // I-FCM005002 + kFIRMessagingMessageCodeConnection003 = 5003, // I-FCM005003 + kFIRMessagingMessageCodeConnection004 = 5004, // I-FCM005004 + kFIRMessagingMessageCodeConnection005 = 5005, // I-FCM005005 + kFIRMessagingMessageCodeConnection006 = 5006, // I-FCM005006 + kFIRMessagingMessageCodeConnection007 = 5007, // I-FCM005007 + kFIRMessagingMessageCodeConnection008 = 5008, // I-FCM005008 + kFIRMessagingMessageCodeConnection009 = 5009, // I-FCM005009 + kFIRMessagingMessageCodeConnection010 = 5010, // I-FCM005010 + kFIRMessagingMessageCodeConnection011 = 5011, // I-FCM005011 + kFIRMessagingMessageCodeConnection012 = 5012, // I-FCM005012 + kFIRMessagingMessageCodeConnection013 = 5013, // I-FCM005013 + kFIRMessagingMessageCodeConnection014 = 5014, // I-FCM005014 + kFIRMessagingMessageCodeConnection015 = 5015, // I-FCM005015 + kFIRMessagingMessageCodeConnection016 = 5016, // I-FCM005016 + kFIRMessagingMessageCodeConnection017 = 5017, // I-FCM005017 + kFIRMessagingMessageCodeConnection018 = 5018, // I-FCM005018 + kFIRMessagingMessageCodeConnection019 = 5019, // I-FCM005019 + kFIRMessagingMessageCodeConnection020 = 5020, // I-FCM005020 + kFIRMessagingMessageCodeConnection021 = 5021, // I-FCM005021 + kFIRMessagingMessageCodeConnection022 = 5022, // I-FCM005022 + kFIRMessagingMessageCodeConnection023 = 5023, // I-FCM005023 + // FIRMessagingContextManagerService.m + kFIRMessagingMessageCodeContextManagerService000 = 6000, // I-FCM006000 + kFIRMessagingMessageCodeContextManagerService001 = 6001, // I-FCM006001 + kFIRMessagingMessageCodeContextManagerService002 = 6002, // I-FCM006002 + kFIRMessagingMessageCodeContextManagerService003 = 6003, // I-FCM006003 + kFIRMessagingMessageCodeContextManagerService004 = 6004, // I-FCM006004 + kFIRMessagingMessageCodeContextManagerService005 = 6005, // I-FCM006005 + // FIRMessagingDataMessageManager.m + kFIRMessagingMessageCodeDataMessageManager000 = 7000, // I-FCM007000 + kFIRMessagingMessageCodeDataMessageManager001 = 7001, // I-FCM007001 + kFIRMessagingMessageCodeDataMessageManager002 = 7002, // I-FCM007002 + kFIRMessagingMessageCodeDataMessageManager003 = 7003, // I-FCM007003 + kFIRMessagingMessageCodeDataMessageManager004 = 7004, // I-FCM007004 + kFIRMessagingMessageCodeDataMessageManager005 = 7005, // I-FCM007005 + kFIRMessagingMessageCodeDataMessageManager006 = 7006, // I-FCM007006 + kFIRMessagingMessageCodeDataMessageManager007 = 7007, // I-FCM007007 + kFIRMessagingMessageCodeDataMessageManager008 = 7008, // I-FCM007008 + kFIRMessagingMessageCodeDataMessageManager009 = 7009, // I-FCM007009 + kFIRMessagingMessageCodeDataMessageManager010 = 7010, // I-FCM007010 + kFIRMessagingMessageCodeDataMessageManager011 = 7011, // I-FCM007011 + kFIRMessagingMessageCodeDataMessageManager012 = 7012, // I-FCM007012 + // FIRMessagingPendingTopicsList.m + kFIRMessagingMessageCodePendingTopicsList000 = 8000, // I-FCM008000 + // FIRMessagingPubSub.m + kFIRMessagingMessageCodePubSub000 = 9000, // I-FCM009000 + kFIRMessagingMessageCodePubSub001 = 9001, // I-FCM009001 + kFIRMessagingMessageCodePubSub002 = 9002, // I-FCM009002 + kFIRMessagingMessageCodePubSub003 = 9003, // I-FCM009003 + // FIRMessagingReceiver.m + kFIRMessagingMessageCodeReceiver000 = 10000, // I-FCM010000 + kFIRMessagingMessageCodeReceiver001 = 10001, // I-FCM010001 + kFIRMessagingMessageCodeReceiver002 = 10002, // I-FCM010002 + kFIRMessagingMessageCodeReceiver003 = 10003, // I-FCM010003 + kFIRMessagingMessageCodeReceiver004 = 10004, // I-FCM010004 - no longer used + kFIRMessagingMessageCodeReceiver005 = 10005, // I-FCM010005 + // FIRMessagingRegistrar.m + kFIRMessagingMessageCodeRegistrar000 = 11000, // I-FCM011000 + // FIRMessagingRemoteNotificationsProxy.m + kFIRMessagingMessageCodeRemoteNotificationsProxy000 = 12000, // I-FCM012000 + kFIRMessagingMessageCodeRemoteNotificationsProxy001 = 12001, // I-FCM012001 + kFIRMessagingMessageCodeRemoteNotificationsProxyAPNSFailed = 12002, // I-FCM012002 + // FIRMessagingRmq2PersistentStore.m + kFIRMessagingMessageCodeRmq2PersistentStore000 = 13000, // I-FCM013000 + kFIRMessagingMessageCodeRmq2PersistentStore001 = 13001, // I-FCM013001 + kFIRMessagingMessageCodeRmq2PersistentStore002 = 13002, // I-FCM013002 + kFIRMessagingMessageCodeRmq2PersistentStore003 = 13003, // I-FCM013003 + kFIRMessagingMessageCodeRmq2PersistentStore004 = 13004, // I-FCM013004 + kFIRMessagingMessageCodeRmq2PersistentStore005 = 13005, // I-FCM013005 + kFIRMessagingMessageCodeRmq2PersistentStore006 = 13006, // I-FCM013006 + // FIRMessagingRmqManager.m + kFIRMessagingMessageCodeRmqManager000 = 14000, // I-FCM014000 + // FIRMessagingSecureSocket.m + kFIRMessagingMessageCodeSecureSocket000 = 15000, // I-FCM015000 + kFIRMessagingMessageCodeSecureSocket001 = 15001, // I-FCM015001 + kFIRMessagingMessageCodeSecureSocket002 = 15002, // I-FCM015002 + kFIRMessagingMessageCodeSecureSocket003 = 15003, // I-FCM015003 + kFIRMessagingMessageCodeSecureSocket004 = 15004, // I-FCM015004 + kFIRMessagingMessageCodeSecureSocket005 = 15005, // I-FCM015005 + kFIRMessagingMessageCodeSecureSocket006 = 15006, // I-FCM015006 + kFIRMessagingMessageCodeSecureSocket007 = 15007, // I-FCM015007 + kFIRMessagingMessageCodeSecureSocket008 = 15008, // I-FCM015008 + kFIRMessagingMessageCodeSecureSocket009 = 15009, // I-FCM015009 + kFIRMessagingMessageCodeSecureSocket010 = 15010, // I-FCM015010 + kFIRMessagingMessageCodeSecureSocket011 = 15011, // I-FCM015011 + kFIRMessagingMessageCodeSecureSocket012 = 15012, // I-FCM015012 + kFIRMessagingMessageCodeSecureSocket013 = 15013, // I-FCM015013 + kFIRMessagingMessageCodeSecureSocket014 = 15014, // I-FCM015014 + kFIRMessagingMessageCodeSecureSocket015 = 15015, // I-FCM015015 + kFIRMessagingMessageCodeSecureSocket016 = 15016, // I-FCM015016 + // FIRMessagingSyncMessageManager.m + kFIRMessagingMessageCodeSyncMessageManager000 = 16000, // I-FCM016000 + kFIRMessagingMessageCodeSyncMessageManager001 = 16001, // I-FCM016001 + kFIRMessagingMessageCodeSyncMessageManager002 = 16002, // I-FCM016002 + kFIRMessagingMessageCodeSyncMessageManager003 = 16003, // I-FCM016003 + kFIRMessagingMessageCodeSyncMessageManager004 = 16004, // I-FCM016004 + kFIRMessagingMessageCodeSyncMessageManager005 = 16005, // I-FCM016005 + kFIRMessagingMessageCodeSyncMessageManager006 = 16006, // I-FCM016006 + kFIRMessagingMessageCodeSyncMessageManager007 = 16007, // I-FCM016007 + kFIRMessagingMessageCodeSyncMessageManager008 = 16008, // I-FCM016008 + // FIRMessagingTopicOperation.m + kFIRMessagingMessageCodeTopicOption000 = 17000, // I-FCM017000 + kFIRMessagingMessageCodeTopicOption001 = 17001, // I-FCM017001 + kFIRMessagingMessageCodeTopicOption002 = 17002, // I-FCM017002 + // FIRMessagingUtilities.m + kFIRMessagingMessageCodeUtilities000 = 18000, // I-FCM018000 + kFIRMessagingMessageCodeUtilities001 = 18001, // I-FCM018001 + kFIRMessagingMessageCodeUtilities002 = 18002, // I-FCM018002 +}; diff --git a/Firebase/Messaging/FIRMessaging+FIRApp.h b/Firebase/Messaging/FIRMessaging+FIRApp.h new file mode 100644 index 0000000..743b0f4 --- /dev/null +++ b/Firebase/Messaging/FIRMessaging+FIRApp.h @@ -0,0 +1,24 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessaging.h" + +/** + * This category extends FIRMessaging with the configuration for using Cloud Messaging. + */ +@interface FIRMessaging (FIRApp) + +@end diff --git a/Firebase/Messaging/FIRMessaging+FIRApp.m b/Firebase/Messaging/FIRMessaging+FIRApp.m new file mode 100644 index 0000000..fc53286 --- /dev/null +++ b/Firebase/Messaging/FIRMessaging+FIRApp.m @@ -0,0 +1,111 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessaging+FIRApp.h" + +#import "FIRAppInternal.h" +#import "FIROptionsInternal.h" + +#import "FIRMessagingConfig.h" +#import "FIRMessagingConstants.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingPubSub.h" +#import "FIRMessagingRemoteNotificationsProxy.h" +#import "FIRMessagingVersionUtilities.h" +#import "FIRMessaging_Private.h" + +@interface FIRMessaging () + +@property(nonatomic, readwrite, strong) NSString *fcmSenderID; + +@end + +@implementation FIRMessaging (FIRApp) + ++ (void)load { + // FIRMessaging by default removes itself from observing any notifications. + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didReceiveConfigureSDKNotification:) + name:kFIRAppReadyToConfigureSDKNotification + object:[FIRApp class]]; +} + ++ (void)didReceiveConfigureSDKNotification:(NSNotification *)notification { + NSDictionary *appInfoDict = notification.userInfo; + NSString *appName = appInfoDict[kFIRAppNameKey]; + FIRApp *app = [FIRApp appNamed:appName]; + [[FIRMessaging messaging] configureMessaging:app]; +} + +- (void)configureMessaging:(FIRApp *)app { + FIROptions *options = app.options; + NSError *error; + if (!options.GCMSenderID.length) { + error = + [FIRApp errorForSubspecConfigurationFailureWithDomain:kFirebaseCloudMessagingErrorDomain + errorCode:FIRErrorCodeCloudMessagingFailed + service:kFIRServiceMessaging + reason:@"Google Sender ID must not be nil" + @" or empty."]; + [self exitApp:app withError:error]; + return; + } + + self.fcmSenderID = [options.GCMSenderID copy]; + + // Swizzle remote-notification-related methods (app delegate and UNUserNotificationCenter) + if ([FIRMessagingRemoteNotificationsProxy canSwizzleMethods]) { + FIRMessagingLoggerNotice(kFIRMessagingMessageCodeFIRApp000, + @"FIRMessaging Remote Notifications proxy enabled, will swizzle " + @"remote notification receiver handlers. Add \"%@\" to your " + @"Info.plist and set it to NO", + kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey); + [FIRMessagingRemoteNotificationsProxy swizzleMethods]; + } +} + +- (void)exitApp:(FIRApp *)app withError:(NSError *)error { + [app sendLogsWithServiceName:kFIRServiceMessaging + version:FIRMessagingCurrentLibraryVersion() + error:error]; + if (error) { + NSString *message = nil; + if (app.options.usingOptionsFromDefaultPlist) { + // Configured using plist file + message = [NSString stringWithFormat:@"Firebase Messaging has stopped your project because " + @"there are missing or incorrect values provided in %@.%@ that may prevent " + @"your app from behaving as expected:\n\n" + @"Error: %@\n\n" + @"Please fix these issues to ensure that Firebase is correctly configured in " + @"your project.", + kServiceInfoFileName, + kServiceInfoFileType, + error.localizedFailureReason]; + } else { + // Configured manually + message = [NSString stringWithFormat:@"Firebase Messaging has stopped your project because " + @"there are missing or incorrect values in Firebase's configuration options " + @"that may prevent your app from behaving as expected:\n\n" + @"Error:%@\n\n" + @"Please fix these issues to ensure that Firebase is correctly configured in " + @"your project.", + error.localizedFailureReason]; + } + [NSException raise:kFirebaseCloudMessagingErrorDomain format:@"%@", message]; + } +} + +@end diff --git a/Firebase/Messaging/FIRMessaging.m b/Firebase/Messaging/FIRMessaging.m new file mode 100644 index 0000000..94347c8 --- /dev/null +++ b/Firebase/Messaging/FIRMessaging.m @@ -0,0 +1,1071 @@ +/* + * Copyright 2017 Google + * + * 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. + */ + +#if !__has_feature(objc_arc) +#error FIRMessagingLib should be compiled with ARC. +#endif + +#import "FIRMessaging.h" +#import "FIRMessaging_Private.h" + +#import <UIKit/UIKit.h> + +#import "FIRMessagingClient.h" +#import "FIRMessagingConfig.h" +#import "FIRMessagingConstants.h" +#import "FIRMessagingContextManagerService.h" +#import "FIRMessagingDataMessageManager.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingInstanceIDProxy.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingPubSub.h" +#import "FIRMessagingReceiver.h" +#import "FIRMessagingRmqManager.h" +#import "FIRMessagingSyncMessageManager.h" +#import "FIRMessagingUtilities.h" +#import "FIRMessagingVersionUtilities.h" + +#import "FIRReachabilityChecker.h" + +#import "NSError+FIRMessaging.h" + +static NSString *const kFIRMessagingMessageViaAPNSRootKey = @"aps"; +static NSString *const kFIRMessagingReachabilityHostname = @"www.google.com"; +static NSString *const kFIRMessagingDefaultTokenScope = @"*"; +static NSString *const kFIRMessagingFCMTokenFetchAPNSOption = @"apns_token"; + +#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +const NSNotificationName FIRMessagingSendSuccessNotification = + @"com.firebase.messaging.notif.send-success"; +const NSNotificationName FIRMessagingSendErrorNotification = + @"com.firebase.messaging.notif.send-error"; +const NSNotificationName FIRMessagingMessagesDeletedNotification = + @"com.firebase.messaging.notif.messages-deleted"; +const NSNotificationName FIRMessagingConnectionStateChangedNotification = + @"com.firebase.messaging.notif.connection-state-changed"; +const NSNotificationName FIRMessagingRegistrationTokenRefreshedNotification = + @"com.firebase.messaging.notif.fcm-token-refreshed"; +#else +NSString *const FIRMessagingSendSuccessNotification = + @"com.firebase.messaging.notif.send-success"; +NSString *const FIRMessagingSendErrorNotification = + @"com.firebase.messaging.notif.send-error"; +NSString * const FIRMessagingMessagesDeletedNotification = + @"com.firebase.messaging.notif.messages-deleted"; +NSString * const FIRMessagingConnectionStateChangedNotification = + @"com.firebase.messaging.notif.connection-state-changed"; +NSString * const FIRMessagingRegistrationTokenRefreshedNotification = + @"com.firebase.messaging.notif.fcm-token-refreshed"; +#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + +// Copied from Apple's header in case it is missing in some cases (e.g. pre-Xcode 8 builds). +#ifndef NSFoundationVersionNumber_iOS_8_x_Max +#define NSFoundationVersionNumber_iOS_8_x_Max 1199 +#endif + +@interface FIRMessagingMessageInfo () + +@property(nonatomic, readwrite, assign) FIRMessagingMessageStatus status; + +@end + +@implementation FIRMessagingMessageInfo + +- (instancetype)init { + FIRMessagingInvalidateInitializer(); +} + +- (instancetype)initWithStatus:(FIRMessagingMessageStatus)status { + self = [super init]; + if (self) { + _status = status; + } + return self; +} + +@end + +#pragma mark - for iOS 10 compatibility +@implementation FIRMessagingRemoteMessage + +- (instancetype)init { + self = [super init]; + if (self) { + _appData = [[NSMutableDictionary alloc] init]; + } + + return self; +} + +- (instancetype)initWithMessage:(FIRMessagingRemoteMessage *)message { + self = [self init]; + if (self) { + _appData = [message.appData copy]; + } + + return self; +} + +@end + +@interface FIRMessaging () + <FIRMessagingClientDelegate, FIRMessagingReceiverDelegate, FIRReachabilityDelegate> + +// FIRApp properties +@property(nonatomic, readwrite, copy) NSString *fcmSenderID; +@property(nonatomic, readwrite, strong) NSData *apnsTokenData; +@property(nonatomic, readwrite, strong) NSString *defaultFcmToken; + +// This object is used as a proxy for reflection-based calls to FIRInstanceID. +// Due to our packaging requirements, we can't directly depend on FIRInstanceID currently. +@property(nonatomic, readwrite, strong) FIRMessagingInstanceIDProxy *instanceIDProxy; + +@property(nonatomic, readwrite, strong) FIRMessagingConfig *config; +@property(nonatomic, readwrite, assign) BOOL isClientSetup; + +@property(nonatomic, readwrite, strong) FIRMessagingClient *client; +@property(nonatomic, readwrite, strong) FIRReachabilityChecker *reachability; +@property(nonatomic, readwrite, strong) FIRMessagingDataMessageManager *dataMessageManager; +@property(nonatomic, readwrite, strong) FIRMessagingPubSub *pubsub; +@property(nonatomic, readwrite, strong) FIRMessagingRmqManager *rmq2Manager; +@property(nonatomic, readwrite, strong) FIRMessagingReceiver *receiver; +@property(nonatomic, readwrite, strong) FIRMessagingSyncMessageManager *syncMessageManager; + +/// Message ID's logged for analytics. This prevents us from logging the same message twice +/// which can happen if the user inadvertently calls `appDidReceiveMessage` along with us +/// calling it implicitly during swizzling. +@property(nonatomic, readwrite, strong) NSMutableSet *loggedMessageIDs; + +@end + +@implementation FIRMessaging + ++ (FIRMessaging *)messaging { + static FIRMessaging *messaging; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Start Messaging (Fully initialize in one place). + FIRMessagingConfig *config = [FIRMessagingConfig defaultConfig]; + messaging = [[FIRMessaging alloc] initWithConfig:config]; + [messaging start]; + }); + return messaging; +} + +- (instancetype)initWithConfig:(FIRMessagingConfig *)config { + self = [super init]; + if (self) { + _config = config; + _loggedMessageIDs = [NSMutableSet set]; + _instanceIDProxy = [[FIRMessagingInstanceIDProxy alloc] init]; + } + return self; +} + +- (void)dealloc { + [self.reachability stop]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self teardown]; +} + +- (void)setRemoteMessageDelegate:(id<FIRMessagingDelegate>)delegate { + _delegate = delegate; +} + +- (id<FIRMessagingDelegate>)remoteMessageDelegate { + return self.delegate; +} + +#pragma mark - Config + +- (void)start { + _FIRMessagingDevAssert(self.config, @"Invalid nil config in FIRMessagingService"); + + [self saveLibraryVersion]; + [self setupLogger:self.config.logLevel]; + [self setupReceiverWithConfig:self.config]; + + NSString *hostname = kFIRMessagingReachabilityHostname; + self.reachability = [[FIRReachabilityChecker alloc] initWithReachabilityDelegate:self + loggerDelegate:nil + withHost:hostname]; + [self.reachability start]; + + [self setupApplicationSupportSubDirectory]; + // setup FIRMessaging objects + [self setupRmqManager]; + [self setupClient]; + [self setupSyncMessageManager]; + [self setupDataMessageManager]; + [self setupTopics]; + + self.isClientSetup = YES; + [self setupNotificationListeners]; +} + +- (void)setupApplicationSupportSubDirectory { + NSString *messagingSubDirectory = kFIRMessagingApplicationSupportSubDirectory; + if (![[self class] hasApplicationSupportSubDirectory:messagingSubDirectory]) { + [[self class] createApplicationSupportSubDirectory:messagingSubDirectory]; + } +} + +- (void)setupNotificationListeners { + // To prevent multiple notifications remove self as observer for all events. + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center removeObserver:self]; + + [center addObserver:self + selector:@selector(didReceiveDefaultInstanceIDToken:) + name:kFIRMessagingFCMTokenNotification + object:nil]; + [center addObserver:self + selector:@selector(defaultInstanceIDTokenWasRefreshed:) + name:kFIRMessagingInstanceIDTokenRefreshNotification + object:nil]; + [center addObserver:self + selector:@selector(didReceiveAPNSToken:) + name:kFIRMessagingAPNSTokenNotification + object:nil]; + + [center addObserver:self + selector:@selector(applicationStateChanged) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + [center addObserver:self + selector:@selector(applicationStateChanged) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; +} + +- (void)saveLibraryVersion { + NSString *currentLibraryVersion = FIRMessagingCurrentLibraryVersion(); + [[NSUserDefaults standardUserDefaults] setObject:currentLibraryVersion + forKey:kFIRMessagingLibraryVersion]; + FIRMessagingLoggerInfo(kFIRMessagingMessageCodeMessaging000, @"FIRMessaging library version %@", + currentLibraryVersion); +} + +- (void)setupLogger:(FIRMessagingLogLevel)loggerLevel { +#if FIRMessaging_PROBER + // do nothing +#else + FIRMessagingLogger *logger = FIRMessagingSharedLogger(); + FIRMessagingLogLevelFilter *filter = + [[FIRMessagingLogLevelFilter alloc] initWithLevel:loggerLevel]; + [logger setFilter:filter]; +#endif +} + +- (void)setupReceiverWithConfig:(FIRMessagingConfig *)config { + self.receiver = [[FIRMessagingReceiver alloc] init]; + self.receiver.delegate = self; +} + +- (void)setupClient { + self.client = [[FIRMessagingClient alloc] initWithDelegate:self + reachability:self.reachability + rmq2Manager:self.rmq2Manager]; +} + +- (void)setupDataMessageManager { + self.dataMessageManager = + [[FIRMessagingDataMessageManager alloc] initWithDelegate:self.receiver + client:self.client + rmq2Manager:self.rmq2Manager + syncMessageManager:self.syncMessageManager]; + + [self.dataMessageManager refreshDelayedMessages]; + [self.client setDataMessageManager:self.dataMessageManager]; +} + +- (void)setupRmqManager { + self.rmq2Manager = [[FIRMessagingRmqManager alloc] initWithDatabaseName:@"rmq2"]; + [self.rmq2Manager loadRmqId]; +} + +- (void)setupTopics { + _FIRMessagingDevAssert(self.client, @"Invalid nil client before init pubsub."); + self.pubsub = [[FIRMessagingPubSub alloc] initWithClient:self.client]; +} + +- (void)setupSyncMessageManager { + self.syncMessageManager = + [[FIRMessagingSyncMessageManager alloc] initWithRmqManager:self.rmq2Manager]; + + // Delete the expired messages with a delay. We don't want to block startup with a somewhat + // expensive db call. + FIRMessaging_WEAKIFY(self); + dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)); + dispatch_after(time, dispatch_get_main_queue(), ^{ + FIRMessaging_STRONGIFY(self); + [self.syncMessageManager removeExpiredSyncMessages]; + }); +} + +- (void)teardown { + _FIRMessagingDevAssert([NSThread isMainThread], + @"FIRMessaging should be called from main thread only."); + [self.client teardown]; + self.pubsub = nil; + self.syncMessageManager = nil; + self.rmq2Manager = nil; + self.dataMessageManager = nil; + self.client = nil; + self.isClientSetup = NO; + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeMessaging001, @"Did successfully teardown"); +} + +#pragma mark - Messages + +- (FIRMessagingMessageInfo *)appDidReceiveMessage:(NSDictionary *)message { + if (!message.count) { + return [[FIRMessagingMessageInfo alloc] initWithStatus:FIRMessagingMessageStatusUnknown]; + } + + // For downstream messages that go via MCS we should strip out this key before sending + // the message to the device. + BOOL isOldMessage = NO; + NSString *messageID = message[kFIRMessagingMessageIDKey]; + if ([messageID length]) { + [self.rmq2Manager saveS2dMessageWithRmqId:messageID]; + + BOOL isSyncMessage = [[self class] isAPNSSyncMessage:message]; + if (isSyncMessage) { + isOldMessage = [self.syncMessageManager didReceiveAPNSSyncMessage:message]; + } + } + // Prevent duplicates by keeping a cache of all the logged messages during each session. + // The duplicates only happen when the 3P app calls `appDidReceiveMessage:` along with + // us swizzling their implementation to call the same method implicitly. + if (!isOldMessage && messageID.length) { + isOldMessage = [self.loggedMessageIDs containsObject:messageID]; + if (!isOldMessage) { + [self.loggedMessageIDs addObject:messageID]; + } + } + + if (!isOldMessage) { + Class firMessagingLogClass = NSClassFromString(@"FIRMessagingLog"); + SEL logMessageSelector = NSSelectorFromString(@"logMessage:"); + + if ([firMessagingLogClass respondsToSelector:logMessageSelector]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [firMessagingLogClass performSelector:logMessageSelector + withObject:message]; + } +#pragma clang diagnostic pop + [self handleContextManagerMessage:message]; + [self handleIncomingLinkIfNeededFromMessage:message]; + } + return [[FIRMessagingMessageInfo alloc] initWithStatus:FIRMessagingMessageStatusNew]; +} + +- (BOOL)handleContextManagerMessage:(NSDictionary *)message { + if ([FIRMessagingContextManagerService isContextManagerMessage:message]) { + return [FIRMessagingContextManagerService handleContextManagerMessage:message]; + } + return NO; +} + ++ (BOOL)isAPNSSyncMessage:(NSDictionary *)message { + if ([message[kFIRMessagingMessageViaAPNSRootKey] isKindOfClass:[NSDictionary class]]) { + NSDictionary *aps = message[kFIRMessagingMessageViaAPNSRootKey]; + return [aps[kFIRMessagingMessageAPNSContentAvailableKey] boolValue]; + } + return NO; +} + +- (void)handleIncomingLinkIfNeededFromMessage:(NSDictionary *)message { + NSURL *url = [self linkURLFromMessage:message]; + if (url == nil) { + return; + } + if (![NSThread isMainThread]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self handleIncomingLinkIfNeededFromMessage:message]; + + }); + return; + } + UIApplication *application = [UIApplication sharedApplication]; + id<UIApplicationDelegate> appDelegate = application.delegate; + SEL continueUserActivitySelector = + @selector(application:continueUserActivity:restorationHandler:); + SEL openURLWithOptionsSelector = @selector(application:openURL:options:); + SEL openURLWithSourceApplicationSelector = + @selector(application:openURL:sourceApplication:annotation:); + SEL handleOpenURLSelector = @selector(application:handleOpenURL:); + // Due to FIRAAppDelegateProxy swizzling, this selector will most likely get chosen, whether or + // not the actual application has implemented + // |application:continueUserActivity:restorationHandler:|. A warning will be displayed to the user + // if they haven't implemented it. + if ([NSUserActivity class] != nil && + [appDelegate respondsToSelector:continueUserActivitySelector]) { + NSUserActivity *userActivity = + [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb]; + userActivity.webpageURL = url; + [appDelegate application:application + continueUserActivity:userActivity + restorationHandler:^(NSArray * _Nullable restorableObjects) { + // Do nothing, as we don't support the app calling this block + }]; + + } else if ([appDelegate respondsToSelector:openURLWithOptionsSelector]) { + [appDelegate application:application openURL:url options:@{}]; + + // Similarly, |application:openURL:sourceApplication:annotation:| will also always be called, due + // to the default swizzling done by FIRAAppDelegateProxy in Firebase Analytics + } else if ([appDelegate respondsToSelector:openURLWithSourceApplicationSelector]) { + [appDelegate application:application + openURL:url + sourceApplication:FIRMessagingAppIdentifier() + annotation:@{}]; + + } else if ([appDelegate respondsToSelector:handleOpenURLSelector]) { + [appDelegate application:application handleOpenURL:url]; + } +} + +- (NSURL *)linkURLFromMessage:(NSDictionary *)message { + NSString *urlString = message[kFIRMessagingMessageLinkKey]; + if (urlString == nil || ![urlString isKindOfClass:[NSString class]] || urlString.length == 0) { + return nil; + } + NSURL *url = [NSURL URLWithString:urlString]; + return url; +} + +#pragma mark - APNS + +- (NSData *)APNSToken { + return self.apnsTokenData; +} + +- (void)setAPNSToken:(NSData *)APNSToken { + [self setAPNSToken:APNSToken type:FIRMessagingAPNSTokenTypeUnknown]; +} + +- (void)setAPNSToken:(NSData *)apnsToken type:(FIRMessagingAPNSTokenType)type { + if ([apnsToken isEqual:self.apnsTokenData]) { + return; + } + self.apnsTokenData = apnsToken; + [self.instanceIDProxy setAPNSToken:apnsToken type:(FIRMessagingInstanceIDProxyAPNSTokenType)type]; +} + +#pragma mark - FCM + +- (NSString *)FCMToken { + NSString *token = self.defaultFcmToken; + if (!token) { + // We may not have received it from Instance ID yet (via NSNotification), so extract it directly + token = [self.instanceIDProxy token]; + } + return token; +} + +- (void)retrieveFCMTokenForSenderID:(nonnull NSString *)senderID + completion:(nonnull FIRMessagingFCMTokenFetchCompletion)completion { + if (!senderID.length) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeSenderIDNotSuppliedForTokenFetch, + @"Sender ID not supplied. It is required for a token fetch, " + @"to identify the sender."); + if (completion) { + NSString *description = @"Couldn't fetch token because a Sender ID was not supplied. A valid " + @"Sender ID is required to fetch an FCM token"; + NSError *error = [NSError fcm_errorWithCode:FIRMessagingErrorInvalidRequest + userInfo:@{NSLocalizedDescriptionKey : description}]; + completion(nil, error); + } + return; + } + NSDictionary *options = nil; + if (self.APNSToken) { + options = @{kFIRMessagingFCMTokenFetchAPNSOption : self.APNSToken}; + } else { + FIRMessagingLoggerWarn(kFIRMessagingMessageCodeAPNSTokenNotAvailableDuringTokenFetch, + @"APNS device token not set before retrieving FCM Token for Sender ID " + @"'%@'. Notifications to this FCM Token will not be delivered over APNS." + @"Be sure to re-retrieve the FCM token once the APNS device token is " + @"set.", senderID); + } + [self.instanceIDProxy tokenWithAuthorizedEntity:senderID + scope:kFIRMessagingDefaultTokenScope + options:options + handler:completion]; +} + +- (void)deleteFCMTokenForSenderID:(nonnull NSString *)senderID + completion:(nonnull FIRMessagingDeleteFCMTokenCompletion)completion { + if (!senderID.length) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeSenderIDNotSuppliedForTokenDelete, + @"Sender ID not supplied. It is required to delete an FCM token."); + if (completion) { + NSString *description = @"Couldn't delete token because a Sender ID was not supplied. A " + @"valid Sender ID is required to delete an FCM token"; + NSError *error = [NSError fcm_errorWithCode:FIRMessagingErrorInvalidRequest + userInfo:@{NSLocalizedDescriptionKey : description}]; + completion(error); + } + return; + } + [self.instanceIDProxy deleteTokenWithAuthorizedEntity:senderID + scope:kFIRMessagingDefaultTokenScope + handler:completion]; +} + +#pragma mark - Application State Changes + +- (void)applicationStateChanged { + if (self.shouldEstablishDirectChannel) { + [self updateAutomaticClientConnection]; + } +} + +#pragma mark - Direct Channel + +- (void)setShouldEstablishDirectChannel:(BOOL)shouldEstablishDirectChannel { + if (_shouldEstablishDirectChannel == shouldEstablishDirectChannel) { + return; + } + _shouldEstablishDirectChannel = shouldEstablishDirectChannel; + [self updateAutomaticClientConnection]; +} + +- (BOOL)isDirectChannelEstablished { + return self.client.isConnectionActive; +} + +- (BOOL)shouldBeConnectedAutomatically { + // We require a token from Instance ID + NSString *token = self.defaultFcmToken; + // Only on foreground connections + UIApplicationState applicationState = [UIApplication sharedApplication].applicationState; + BOOL shouldBeConnected = _shouldEstablishDirectChannel && + (token.length > 0) && + applicationState == UIApplicationStateActive; + return shouldBeConnected; +} + +- (void)updateAutomaticClientConnection { + BOOL shouldBeConnected = [self shouldBeConnectedAutomatically]; + if (shouldBeConnected && !self.client.isConnected) { + [self.client connectWithHandler:^(NSError *error) { + if (!error) { + // It means we connected. Fire connection change notification + [self notifyOfDirectChannelConnectionChange]; + } + }]; + } else if (!shouldBeConnected && self.client.isConnected) { + [self.client disconnect]; + [self notifyOfDirectChannelConnectionChange]; + } +} + +- (void)notifyOfDirectChannelConnectionChange { + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:FIRMessagingConnectionStateChangedNotification object:self]; +} + +#pragma mark - Connect + +- (void)connectWithCompletion:(FIRMessagingConnectCompletion)handler { + _FIRMessagingDevAssert([NSThread isMainThread], + @"FIRMessaging connect should be called from main thread only."); + _FIRMessagingDevAssert(self.isClientSetup, @"FIRMessaging client not setup."); + [self.client connectWithHandler:^(NSError *error) { + if (handler) { + handler(error); + } + if (!error) { + // It means we connected. Fire connection change notification + [self notifyOfDirectChannelConnectionChange]; + } + }]; + +} + +- (void)disconnect { + _FIRMessagingDevAssert([NSThread isMainThread], + @"FIRMessaging should be called from main thread only."); + if ([self.client isConnected]) { + [self.client disconnect]; + [self notifyOfDirectChannelConnectionChange]; + } +} + +#pragma mark - Topics + ++ (NSString *)normalizeTopic:(NSString *)topic { + if (![FIRMessagingPubSub hasTopicsPrefix:topic]) { + topic = [FIRMessagingPubSub addPrefixToTopic:topic]; + } + if ([FIRMessagingPubSub isValidTopicWithPrefix:topic]) { + return [topic copy]; + } + return nil; +} + +- (void)subscribeToTopic:(NSString *)topic { + if (self.defaultFcmToken.length && topic.length) { + NSString *normalizeTopic = [[self class ] normalizeTopic:topic]; + if (normalizeTopic.length) { + [self.pubsub subscribeToTopic:normalizeTopic]; + } else { + FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging009, + @"Cannot parse topic name %@. Will not subscribe.", topic); + } + } else { + FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging010, + @"Cannot subscribe to topic: %@ with token: %@", topic, + self.defaultFcmToken); + } +} + +- (void)unsubscribeFromTopic:(NSString *)topic { + if (self.defaultFcmToken.length && topic.length) { + NSString *normalizeTopic = [[self class] normalizeTopic:topic]; + if (normalizeTopic.length) { + [self.pubsub unsubscribeFromTopic:normalizeTopic]; + } else { + FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging011, + @"Cannot parse topic name %@. Will not unsubscribe.", topic); + } + } else { + FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging012, + @"Cannot unsubscribe to topic: %@ with token: %@", topic, + self.defaultFcmToken); + } +} + +#pragma mark - Send + +- (void)sendMessage:(NSDictionary *)message + to:(NSString *)to + withMessageID:(NSString *)messageID + timeToLive:(int64_t)ttl { + _FIRMessagingDevAssert([to length] != 0, @"Invalid receiver id for FIRMessaging-message"); + + NSMutableDictionary *fcmMessage = [[self class] createFIRMessagingMessageWithMessage:message + to:to + withID:messageID + timeToLive:ttl + delay:0]; + FIRMessagingLoggerInfo(kFIRMessagingMessageCodeMessaging013, @"Sending message: %@ with id: %@", + message, messageID); + [self.dataMessageManager sendDataMessageStanza:fcmMessage]; +} + ++ (NSMutableDictionary *)createFIRMessagingMessageWithMessage:(NSDictionary *)message + to:(NSString *)to + withID:(NSString *)msgID + timeToLive:(int64_t)ttl + delay:(int)delay { + NSMutableDictionary *fcmMessage = [NSMutableDictionary dictionary]; + fcmMessage[kFIRMessagingSendTo] = [to copy]; + fcmMessage[kFIRMessagingSendMessageID] = msgID ? [msgID copy] : @""; + fcmMessage[kFIRMessagingSendTTL] = @(ttl); + fcmMessage[kFIRMessagingSendDelay] = @(delay); + fcmMessage[KFIRMessagingSendMessageAppData] = + [NSMutableDictionary dictionaryWithDictionary:message]; + return fcmMessage; +} + +#pragma mark - IID dependencies + +// FIRMessagingInternalUtilities.h to see usage. ++ (NSString *)FIRMessagingSDKVersion { + NSString *semanticVersion = FIRMessagingCurrentLibraryVersion(); + // Use prefix fcm for all FCM libs. This allows us to differentiate b/w + // the new and old FCM registrations. + return [NSString stringWithFormat:@"fcm-%@", semanticVersion]; +} + ++ (NSString *)FIRMessagingSDKCurrentLocale { + return [self currentLocale]; +} + +- (void)setAPNSToken:(NSData *)apnsToken error:(NSError *)error { + if (apnsToken) { + self.apnsTokenData = [apnsToken copy]; + } +} + +#pragma mark - FIRMessagingReceiverDelegate + +- (void)receiver:(FIRMessagingReceiver *)receiver + receivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage { + if ([self.delegate respondsToSelector:@selector(messaging:didReceiveMessage:)]) { + [self.delegate messaging:self didReceiveMessage:remoteMessage]; + } else if ([self.delegate respondsToSelector:@selector(applicationReceivedRemoteMessage:)]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self.delegate applicationReceivedRemoteMessage:remoteMessage]; +#pragma clang diagnostic pop + } else { + // Delegate methods weren't implemented, so messages are being dropped, log a warning + FIRMessagingLoggerWarn(kFIRMessagingMessageCodeRemoteMessageDelegateMethodNotImplemented, + @"FIRMessaging received data-message, but FIRMessagingDelegate's" + @"-messaging:didReceiveMessage: not implemented"); + } +} + +#pragma mark - FIRReachabilityDelegate + +- (void)reachability:(FIRReachabilityChecker *)reachability + statusChanged:(FIRReachabilityStatus)status { + [self onNetworkStatusChanged]; +} + +#pragma mark - Network + +- (BOOL)isNetworkAvailable { + FIRReachabilityStatus status = self.reachability.reachabilityStatus; + return (status == kFIRReachabilityViaCellular || status == kFIRReachabilityViaWifi); +} + +- (FIRMessagingNetworkStatus)networkType { + FIRReachabilityStatus status = self.reachability.reachabilityStatus; + if (![self isNetworkAvailable]) { + return kFIRMessagingReachabilityNotReachable; + } else if (status == kFIRReachabilityViaCellular) { + return kFIRMessagingReachabilityReachableViaWWAN; + } else { + return kFIRMessagingReachabilityReachableViaWiFi; + } +} + +#pragma mark - Notifications + +- (void)onNetworkStatusChanged { + if (![self.client isConnected] && [self isNetworkAvailable]) { + if (self.client.shouldStayConnected) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeMessaging014, + @"Attempting to establish direct channel."); + [self.client retryConnectionImmediately:YES]; + } + [self.pubsub scheduleSync:YES]; + } +} + +#pragma mark - Notifications + +- (void)didReceiveDefaultInstanceIDToken:(NSNotification *)notification { + if (![notification.object isKindOfClass:[NSString class]]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeMessaging015, + @"Invalid default FCM token type %@", + NSStringFromClass([notification.object class])); + return; + } + self.defaultFcmToken = [(NSString *)notification.object copy]; + [self.pubsub scheduleSync:YES]; + if (self.shouldEstablishDirectChannel) { + [self updateAutomaticClientConnection]; + } +} + +- (void)defaultInstanceIDTokenWasRefreshed:(NSNotification *)notification { + // Retrieve the Instance ID default token, and if it is non-nil, post it + NSString *token = [self.instanceIDProxy token]; + // Sometimes Instance ID doesn't yet have a token, so wait until the default + // token is fetched, and then notify. This ensures that this token should not + // be nil when the developer accesses it. + if (token != nil) { + self.defaultFcmToken = [token copy]; + [self.delegate messaging:self didRefreshRegistrationToken:token]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:FIRMessagingRegistrationTokenRefreshedNotification object:nil]; + } +} + +- (void)didReceiveAPNSToken:(NSNotification *)notification { + NSData *apnsToken = notification.object; + if (![apnsToken isKindOfClass:[NSData class]]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeMessaging016, @"Invalid APNS token type %@", + NSStringFromClass([notification.object class])); + return; + } + // Set this value directly, and since this came from InstanceID, don't set it back to InstanceID + self.apnsTokenData = [apnsToken copy]; +} + +#pragma mark - Application Support Directory + ++ (BOOL)hasApplicationSupportSubDirectory:(NSString *)subDirectoryName { + NSString *subDirectoryPath = [self pathForApplicationSupportSubDirectory:subDirectoryName]; + BOOL isDirectory; + if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath + isDirectory:&isDirectory]) { + return NO; + } else if (!isDirectory) { + return NO; + } + return YES; +} + ++ (NSString *)pathForApplicationSupportSubDirectory:(NSString *)subDirectoryName { + NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, + NSUserDomainMask, YES); + NSString *applicationSupportDirPath = directoryPaths.lastObject; + NSArray *components = @[applicationSupportDirPath, subDirectoryName]; + return [NSString pathWithComponents:components]; +} + ++ (BOOL)createApplicationSupportSubDirectory:(NSString *)subDirectoryName { + NSString *subDirectoryPath = [self pathForApplicationSupportSubDirectory:subDirectoryName]; + BOOL hasSubDirectory; + + if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath + isDirectory:&hasSubDirectory]) { + NSError *error; + [[NSFileManager defaultManager] createDirectoryAtPath:subDirectoryPath + withIntermediateDirectories:YES + attributes:nil + error:&error]; + if (error) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging017, + @"Cannot create directory %@, error: %@", subDirectoryPath, error); + return NO; + } + } else { + if (!hasSubDirectory) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging018, + @"Found file instead of directory at %@", subDirectoryPath); + return NO; + } + } + return YES; +} + +#pragma mark - Locales + ++ (NSString *)currentLocale { + NSArray *locales = [self firebaseLocales]; + NSArray *preferredLocalizations = + [NSBundle preferredLocalizationsFromArray:locales + forPreferences:[NSLocale preferredLanguages]]; + NSString *legalDocsLanguage = [preferredLocalizations firstObject]; + // Use en as the default language + return legalDocsLanguage ? legalDocsLanguage : @"en"; +} + ++ (NSArray *)firebaseLocales { + NSMutableArray *locales = [NSMutableArray array]; + NSDictionary *localesMap = [self firebaselocalesMap]; + for (NSString *key in localesMap) { + [locales addObjectsFromArray:localesMap[key]]; + } + return locales; +} + ++ (NSDictionary *)firebaselocalesMap { + return @{ + // Albanian + @"sq" : @[ @"sq_AL" ], + // Belarusian + @"be" : @[ @"be_BY" ], + // Bulgarian + @"bg" : @[ @"bg_BG" ], + // Catalan + @"ca" : @[ @"ca", @"ca_ES" ], + // Croatian + @"hr" : @[ @"hr", @"hr_HR" ], + // Czech + @"cs" : @[ @"cs", @"cs_CZ" ], + // Danish + @"da" : @[ @"da", @"da_DK" ], + // Estonian + @"et" : @[ @"et_EE" ], + // Finnish + @"fi" : @[ @"fi", @"fi_FI" ], + // Hebrew + @"he" : @[ @"he", @"iw_IL" ], + // Hindi + @"hi" : @[ @"hi_IN" ], + // Hungarian + @"hu" : @[ @"hu", @"hu_HU" ], + // Icelandic + @"is" : @[ @"is_IS" ], + // Indonesian + @"id" : @[ @"id", @"in_ID", @"id_ID" ], + // Irish + @"ga" : @[ @"ga_IE" ], + // Korean + @"ko" : @[ @"ko", @"ko_KR", @"ko-KR" ], + // Latvian + @"lv" : @[ @"lv_LV" ], + // Lithuanian + @"lt" : @[ @"lt_LT" ], + // Macedonian + @"mk" : @[ @"mk_MK" ], + // Malay + @"ms" : @[ @"ms_MY" ], + // Maltese + @"ms" : @[ @"mt_MT" ], + // Polish + @"pl" : @[ @"pl", @"pl_PL", @"pl-PL" ], + // Romanian + @"ro" : @[ @"ro", @"ro_RO" ], + // Russian + @"ru" : @[ @"ru_RU", @"ru", @"ru_BY", @"ru_KZ", @"ru-RU" ], + // Slovak + @"sk" : @[ @"sk", @"sk_SK" ], + // Slovenian + @"sl" : @[ @"sl_SI" ], + // Swedish + @"sv" : @[ @"sv", @"sv_SE", @"sv-SE" ], + // Turkish + @"tr" : @[ @"tr", @"tr-TR", @"tr_TR" ], + // Ukrainian + @"uk" : @[ @"uk", @"uk_UA" ], + // Vietnamese + @"vi" : @[ @"vi", @"vi_VN" ], + // The following are groups of locales or locales that sub-divide a + // language). + // Arabic + @"ar" : @[ + @"ar", + @"ar_DZ", + @"ar_BH", + @"ar_EG", + @"ar_IQ", + @"ar_JO", + @"ar_KW", + @"ar_LB", + @"ar_LY", + @"ar_MA", + @"ar_OM", + @"ar_QA", + @"ar_SA", + @"ar_SD", + @"ar_SY", + @"ar_TN", + @"ar_AE", + @"ar_YE", + @"ar_GB", + @"ar-IQ", + @"ar_US" + ], + // Simplified Chinese + @"zh_Hans" : @[ @"zh_CN", @"zh_SG", @"zh-Hans" ], + // Traditional Chinese + @"zh_Hant" : @[ @"zh_HK", @"zh_TW", @"zh-Hant", @"zh-HK", @"zh-TW" ], + // Dutch + @"nl" : @[ @"nl", @"nl_BE", @"nl_NL", @"nl-NL" ], + // English + @"en" : @[ + @"en", + @"en_AU", + @"en_CA", + @"en_IN", + @"en_IE", + @"en_MT", + @"en_NZ", + @"en_PH", + @"en_SG", + @"en_ZA", + @"en_GB", + @"en_US", + @"en_AE", + @"en-AE", + @"en_AS", + @"en-AU", + @"en_BD", + @"en-CA", + @"en_EG", + @"en_ES", + @"en_GB", + @"en-GB", + @"en_HK", + @"en_ID", + @"en-IN", + @"en_NG", + @"en-PH", + @"en_PK", + @"en-SG", + @"en-US" + ], + // French + + @"fr" : @[ + @"fr", + @"fr_BE", + @"fr_CA", + @"fr_FR", + @"fr_LU", + @"fr_CH", + @"fr-CA", + @"fr-FR", + @"fr_MA" + ], + // German + @"de" : @[ @"de", @"de_AT", @"de_DE", @"de_LU", @"de_CH", @"de-DE" ], + // Greek + @"el" : @[ @"el", @"el_CY", @"el_GR" ], + // Italian + @"it" : @[ @"it", @"it_IT", @"it_CH", @"it-IT" ], + // Japanese + @"ja" : @[ @"ja", @"ja_JP", @"ja_JP_JP", @"ja-JP" ], + // Norwegian + @"no" : @[ @"nb", @"no_NO", @"no_NO_NY", @"nb_NO" ], + // Brazilian Portuguese + @"pt_BR" : @[ @"pt_BR", @"pt-BR" ], + // European Portuguese + @"pt_PT" : @[ @"pt", @"pt_PT", @"pt-PT" ], + // Serbian + @"sr" : @[ + @"sr_BA", + @"sr_ME", + @"sr_RS", + @"sr_Latn_BA", + @"sr_Latn_ME", + @"sr_Latn_RS" + ], + // European Spanish + @"es_ES" : @[ @"es", @"es_ES", @"es-ES" ], + // Mexican Spanish + @"es_MX" : @[ @"es-MX", @"es_MX", @"es_US", @"es-US" ], + // Latin American Spanish + @"es_419" : @[ + @"es_AR", + @"es_BO", + @"es_CL", + @"es_CO", + @"es_CR", + @"es_DO", + @"es_EC", + @"es_SV", + @"es_GT", + @"es_HN", + @"es_NI", + @"es_PA", + @"es_PY", + @"es_PE", + @"es_PR", + @"es_UY", + @"es_VE", + @"es-AR", + @"es-CL", + @"es-CO" + ], + // Thai + @"th" : @[ @"th", @"th_TH", @"th_TH_TH" ], + }; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingCheckinService.h b/Firebase/Messaging/FIRMessagingCheckinService.h new file mode 100644 index 0000000..155143a --- /dev/null +++ b/Firebase/Messaging/FIRMessagingCheckinService.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +/** + * Register the device with Checkin Service and get back the `authID`, `secret token` etc. for the + * client. Checkin results are cached in the `FIRMessagingDefaultsManager` and periodically refreshed to + * prevent them from being stale. Each client needs to register with checkin before registering + * with FIRMessaging. + */ +@interface FIRMessagingCheckinService : NSObject + +@property(nonatomic, readonly, strong) NSString *deviceAuthID; +@property(nonatomic, readonly, strong) NSString *secretToken; +@property(nonatomic, readonly, strong) NSString *versionInfo; +@property(nonatomic, readonly, assign) BOOL hasValidCheckinInfo; + +/** + * Verify if valid checkin preferences have been loaded in memory. + * + * @return YES if valid checkin preferences exist in memory else NO. + */ +- (BOOL)hasValidCheckinInfo; + +/** + * Try to load prefetched checkin preferences from the cache. This supports the use case where + * InstanceID library has already obtained a valid checkin and we should be using that. + * + * This should be used as a last gasp effort to retreive any cached checkin preferences before + * hitting the FIRMessaging backend to retrieve new preferences. + * + * Note this is only required because InstanceID and FIRMessaging both require checkin preferences which + * need to be synced with each other. + * + * @return YES if successfully loaded cached checkin preferences into memory else NO. + */ +- (BOOL)tryToLoadPrefetchedCheckinPreferences; + +@end diff --git a/Firebase/Messaging/FIRMessagingCheckinService.m b/Firebase/Messaging/FIRMessagingCheckinService.m new file mode 100644 index 0000000..9dad847 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingCheckinService.m @@ -0,0 +1,132 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingCheckinService.h" + +#import "FIRMessagingUtilities.h" +#import "NSError+FIRMessaging.h" + +@interface FIRMessagingCheckinService () + +// This property is of type FIRInstanceIDCheckinPreferences, if InstanceID was directly linkable +@property(nonatomic, readwrite, strong) id checkinPreferences; + +@end + +@implementation FIRMessagingCheckinService; + +#pragma mark - Reflection-Based Getter Functions + +// Encapsulates the -hasValidCheckinInfo method of FIRInstanceIDCheckinPreferences +BOOL FIRMessagingCheckinService_hasValidCheckinInfo(id checkinPreferences) { + SEL hasValidCheckinInfoSelector = NSSelectorFromString(@"hasValidCheckinInfo"); + if (![checkinPreferences respondsToSelector:hasValidCheckinInfoSelector]) { + // Can't check hasValidCheckinInfo + return NO; + } + + // Since hasValidCheckinInfo returns a BOOL, use NSInvocation + NSMethodSignature *methodSignature = + [[checkinPreferences class] instanceMethodSignatureForSelector:hasValidCheckinInfoSelector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + invocation.selector = hasValidCheckinInfoSelector; + invocation.target = checkinPreferences; + [invocation invoke]; + BOOL returnValue; + [invocation getReturnValue:&returnValue]; + return returnValue; +} + +// Returns a non-scalar (id) object based on the property name +id FIRMessagingCheckinService_propertyNamed(id checkinPreferences, NSString *propertyName) { + SEL propertyGetterSelector = NSSelectorFromString(propertyName); + if ([checkinPreferences respondsToSelector:propertyGetterSelector]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + return [checkinPreferences performSelector:propertyGetterSelector]; +#pragma clang diagnostic pop + } + return nil; +} + +#pragma mark - Methods + +- (BOOL)tryToLoadPrefetchedCheckinPreferences { + Class instanceIDClass = NSClassFromString(@"FIRInstanceID"); + if (!instanceIDClass) { + // InstanceID is not linked + return NO; + } + + // [FIRInstanceID instanceID] + SEL instanceIDSelector = NSSelectorFromString(@"instanceID"); + if (![instanceIDClass respondsToSelector:instanceIDSelector]) { + return NO; + } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + id instanceID = [instanceIDClass performSelector:instanceIDSelector]; +#pragma clang diagnostic pop + if (!instanceID) { + // Instance ID singleton not available + return NO; + } + + // [[FIRInstanceID instanceID] cachedCheckinPreferences] + SEL cachedCheckinPrefsSelector = NSSelectorFromString(@"cachedCheckinPreferences"); + if (![instanceID respondsToSelector:cachedCheckinPrefsSelector]) { + // cachedCheckinPreferences is not accessible + return NO; + } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + id checkinPreferences = [instanceID performSelector:cachedCheckinPrefsSelector]; +#pragma clang diagnostic pop + if (!checkinPreferences) { + // No cached checkin prefs + return NO; + } + + BOOL hasValidInfo = FIRMessagingCheckinService_hasValidCheckinInfo(checkinPreferences); + if (hasValidInfo) { + self.checkinPreferences = checkinPreferences; + } + return hasValidInfo; +} + +#pragma mark - API + +- (NSString *)deviceAuthID { + return FIRMessagingCheckinService_propertyNamed(self.checkinPreferences, @"deviceID"); +} + +- (NSString *)secretToken { + return FIRMessagingCheckinService_propertyNamed(self.checkinPreferences, @"secretToken"); +} + +- (NSString *)versionInfo { + return FIRMessagingCheckinService_propertyNamed(self.checkinPreferences, @"versionInfo"); +} + +- (NSString *)digest { + return FIRMessagingCheckinService_propertyNamed(self.checkinPreferences, @"digest"); +} + +- (BOOL)hasValidCheckinInfo { + return FIRMessagingCheckinService_hasValidCheckinInfo(self.checkinPreferences); +} + +@end diff --git a/Firebase/Messaging/FIRMessagingClient.h b/Firebase/Messaging/FIRMessagingClient.h new file mode 100644 index 0000000..1726428 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingClient.h @@ -0,0 +1,156 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingTopicsCommon.h" + +@class FIRReachabilityChecker; +@class GPBMessage; + +@class FIRMessagingConnection; +@class FIRMessagingDataMessageManager; +@class FIRMessagingRmqManager; + +/** + * Callback to handle MCS connection requests. + * + * @param error The error object if any while trying to connect with MCS else nil. + */ +typedef void(^FIRMessagingConnectCompletionHandler)(NSError *error); + +@protocol FIRMessagingClientDelegate <NSObject> + +@end + +/** + * The client handles the subscribe/unsubscribe for an unregistered senderID + * and device. It also manages the FIRMessaging data connection, the exponential backoff + * algorithm in case of registration failures, sign in failures and unregister + * failures. It also handles the reconnect logic if the FIRMessaging connection is + * broken off by some error during an active session. + */ +@interface FIRMessagingClient : NSObject + +@property(nonatomic, readonly, strong) FIRMessagingConnection *connection; +@property(nonatomic, readwrite, weak) FIRMessagingDataMessageManager *dataMessageManager; + +// Designated initializer +- (instancetype)initWithDelegate:(id<FIRMessagingClientDelegate>)delegate + reachability:(FIRReachabilityChecker *)reachability + rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager; + +- (void)teardown; + +- (void)cancelAllRequests; + +#pragma mark - FIRMessaging subscribe + +/** + * Update the subscription associated with the given token and topic. + * + * For a to-be-created subscription we check if the client is already + * subscribed to the topic or not. If subscribed we should have the + * subscriptionID in the cache and we return from there itself, else we call + * the FIRMessaging backend to create a new subscription for the topic for this client. + * + * For delete subscription requests we delete the stored subscription in the + * client and then invoke the FIRMessaging backend to delete the existing subscription + * completely. + * + * @param token The token associated with the device. + * @param topic The topic for which the subscription should be updated. + * @param options The options to be passed in to the subscription request. + * @param shouldDelete If YES this would delete the subscription from the cache + * and also let the FIRMessaging backend know that we need to delete + * the subscriptionID associated with this topic. + * If NO we try to create a new subscription for the given + * token and topic. + * @param handler The handler to invoke once the subscription request + * finishes. + */ +- (void)updateSubscriptionWithToken:(NSString *)token + topic:(NSString *)topic + options:(NSDictionary *)options + shouldDelete:(BOOL)shouldDelete + handler:(FIRMessagingTopicOperationCompletion)handler; + +#pragma mark - MCS Connection + +/** + * Create a MCS connection. + * + * @param handler The handler to be invokend once the connection is setup. If + * setting up the connection fails we invoke the handler with + * an appropriate error object. + */ +- (void)connectWithHandler:(FIRMessagingConnectCompletionHandler)handler; + +/** + * Disconnect the current MCS connection. If there is no valid connection this + * should be a NO-OP. + */ +- (void)disconnect; + +#pragma mark - MCS Connection State + +/** + * If we are connected to MCS or not. This doesn't take into account the fact if + * the client has been signed in(verified) by MCS. + * + * @return YES if we are signed in or connecting and trying to sign-in else NO. + */ +@property(nonatomic, readonly) BOOL isConnected; + +/** + * If we have an active MCS connection + * + * @return YES if we have an active MCS connection else NO. + */ +@property(nonatomic, readonly) BOOL isConnectionActive; + +/** + * If we should be connected to MCS + * + * @return YES if we have attempted a connection and not requested to disconect. + */ +@property(nonatomic, readonly) BOOL shouldStayConnected; + +/** + * Schedule a retry to connect to MCS. If `immediately` is `YES` try to + * schedule a retry now else retry with some delay. + * + * @param immediately Should retry right now. + */ +- (void)retryConnectionImmediately:(BOOL)immediately; + +#pragma mark - Messages + +/** + * Send a message over the MCS connection. + * + * @param message Message to be sent. + */ +- (void)sendMessage:(GPBMessage *)message; + +/** + * Send message if we have an active MCS connection. If not cache the message + * for this session and in case we are able to re-establish the connection try + * again else drop it. This should only be used for TTL=0 messages for now. + * + * @param message Message to be sent. + */ +- (void)sendOnConnectOrDrop:(GPBMessage *)message; + +@end diff --git a/Firebase/Messaging/FIRMessagingClient.m b/Firebase/Messaging/FIRMessagingClient.m new file mode 100644 index 0000000..c01aecc --- /dev/null +++ b/Firebase/Messaging/FIRMessagingClient.m @@ -0,0 +1,490 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingClient.h" + +#import "FIRMessagingConnection.h" +#import "FIRMessagingDataMessageManager.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingRegistrar.h" +#import "FIRMessagingRmqManager.h" +#import "FIRMessagingTopicsCommon.h" +#import "FIRMessagingUtilities.h" +#import "FIRReachabilityChecker.h" +#import "NSError+FIRMessaging.h" + +static const NSTimeInterval kConnectTimeoutInterval = 40.0; +static const NSTimeInterval kReconnectDelayInSeconds = 2 * 60; // 2 minutes + +static const NSUInteger kMaxRetryExponent = 10; // 2^10 = 1024 seconds ~= 17 minutes + +static NSString *const kFIRMessagingMCSServerHost = @"mtalk.google.com"; +static NSUInteger const kFIRMessagingMCSServerPort = 5228; + +// register device with checkin +typedef void(^FIRMessagingRegisterDeviceHandler)(NSError *error); + +static NSString *FIRMessagingServerHost() { + static NSString *serverHost = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSDictionary *environment = [[NSProcessInfo processInfo] environment]; + NSString *customServerHostAndPort = environment[@"FCM_MCS_HOST"]; + NSString *host = [customServerHostAndPort componentsSeparatedByString:@":"].firstObject; + if (host) { + serverHost = host; + } else { + serverHost = kFIRMessagingMCSServerHost; + } + }); + return serverHost; +} + +static NSUInteger FIRMessagingServerPort() { + static NSUInteger serverPort = kFIRMessagingMCSServerPort; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSDictionary *environment = [[NSProcessInfo processInfo] environment]; + NSString *customServerHostAndPort = environment[@"FCM_MCS_HOST"]; + NSArray<NSString *> *components = [customServerHostAndPort componentsSeparatedByString:@":"]; + NSUInteger port = (NSUInteger)[components.lastObject integerValue]; + if (port != 0) { + serverPort = port; + } + }); + return serverPort; +} + +@interface FIRMessagingClient () <FIRMessagingConnectionDelegate> + +@property(nonatomic, readwrite, weak) id<FIRMessagingClientDelegate> clientDelegate; +@property(nonatomic, readwrite, strong) FIRMessagingConnection *connection; +@property(nonatomic, readwrite, strong) FIRMessagingRegistrar *registrar; + +@property(nonatomic, readwrite, strong) NSString *senderId; + +// FIRMessagingService owns these instances +@property(nonatomic, readwrite, weak) FIRMessagingRmqManager *rmq2Manager; +@property(nonatomic, readwrite, weak) FIRReachabilityChecker *reachability; + +@property(nonatomic, readwrite, assign) int64_t lastConnectedTimestamp; +@property(nonatomic, readwrite, assign) int64_t lastDisconnectedTimestamp; +@property(nonatomic, readwrite, assign) NSUInteger connectRetryCount; + +// Should we stay connected to MCS or not. Should be YES throughout the lifetime +// of a MCS connection. If set to NO it signifies that an existing MCS connection +// should be disconnected. +@property(nonatomic, readwrite, assign) BOOL stayConnected; +@property(nonatomic, readwrite, assign) NSTimeInterval connectionTimeoutInterval; + +// Used if the MCS connection suddenly breaksdown in the middle and we want to reconnect +// with some permissible delay we schedule a reconnect and set it to YES and when it's +// scheduled this will be set back to NO. +@property(nonatomic, readwrite, assign) BOOL didScheduleReconnect; + +// handlers +@property(nonatomic, readwrite, copy) FIRMessagingConnectCompletionHandler connectHandler; + +@end + +@implementation FIRMessagingClient + +- (instancetype)init { + FIRMessagingInvalidateInitializer(); +} + +- (instancetype)initWithDelegate:(id<FIRMessagingClientDelegate>)delegate + reachability:(FIRReachabilityChecker *)reachability + rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager { + self = [super init]; + if (self) { + _reachability = reachability; + _clientDelegate = delegate; + _rmq2Manager = rmq2Manager; + _registrar = [[FIRMessagingRegistrar alloc] init]; + _connectionTimeoutInterval = kConnectTimeoutInterval; + } + return self; +} + +- (void)teardown { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeClient000, @""); + self.stayConnected = NO; + + // Clear all the handlers + self.connectHandler = nil; + + [self.connection teardown]; + + // Stop all subscription requests + [self.registrar cancelAllRequests]; + + _FIRMessagingDevAssert(self.connection.state == kFIRMessagingConnectionNotConnected, @"Did not disconnect"); + [NSObject cancelPreviousPerformRequestsWithTarget:self]; +} + +- (void)cancelAllRequests { + // Stop any checkin requests or any subscription requests + [self.registrar cancelAllRequests]; + + // Stop any future connection requests to MCS + if (self.stayConnected && self.isConnected && !self.isConnectionActive) { + self.stayConnected = NO; + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + } +} + +#pragma mark - FIRMessaging subscribe + +- (void)updateSubscriptionWithToken:(NSString *)token + topic:(NSString *)topic + options:(NSDictionary *)options + shouldDelete:(BOOL)shouldDelete + handler:(FIRMessagingTopicOperationCompletion)handler { + + _FIRMessagingDevAssert(handler != nil, @"Invalid handler to FIRMessaging subscribe"); + + FIRMessagingTopicOperationCompletion completion = + ^void(FIRMessagingTopicOperationResult result, NSError * error) { + if (error) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeClient001, @"Failed to subscribe to topic %@", + error); + } else { + if (shouldDelete) { + FIRMessagingLoggerInfo(kFIRMessagingMessageCodeClient002, + @"Successfully unsubscribed from topic %@", topic); + } else { + FIRMessagingLoggerInfo(kFIRMessagingMessageCodeClient003, + @"Successfully subscribed to topic %@", topic); + } + } + handler(result, error); + }; + + [self.registrar tryToLoadValidCheckinInfo]; + [self.registrar updateSubscriptionToTopic:topic + withToken:token + options:options + shouldDelete:shouldDelete + handler:completion]; +} + +#pragma mark - MCS Connection + +- (BOOL)isConnected { + return self.stayConnected && self.connection.state != kFIRMessagingConnectionNotConnected; +} + +- (BOOL)isConnectionActive { + return self.stayConnected && self.connection.state == kFIRMessagingConnectionSignedIn; +} + +- (BOOL)shouldStayConnected { + return self.stayConnected; +} + +- (void)retryConnectionImmediately:(BOOL)immediately { + // Do not connect to an invalid host or an invalid port + if (!self.stayConnected || !self.connection.host || self.connection.port == 0) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeClient004, + @"FIRMessaging connection will not reconnect to MCS. " + @"Stay connected: %d", + self.stayConnected); + return; + } + if (self.isConnectionActive) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeClient005, + @"FIRMessaging Connection skip retry, active"); + // already connected and logged in. + // Heartbeat alarm is set and will force close the connection + return; + } + if (self.isConnected) { + // already connected and logged in. + // Heartbeat alarm is set and will force close the connection + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeClient006, + @"FIRMessaging Connection skip retry, connected"); + return; + } + + if (immediately) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeClient007, + @"Try to connect to MCS immediately"); + [self tryToConnect]; + } else { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeClient008, @"Try to connect to MCS lazily"); + // Avoid all the other logic that we have in other clients, since this would always happen + // when the app is in the foreground and since the FIRMessaging connection isn't shared with any other + // app we can be more aggressive in reconnections + if (!self.didScheduleReconnect) { + FIRMessaging_WEAKIFY(self); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, + (int64_t)(kReconnectDelayInSeconds * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + FIRMessaging_STRONGIFY(self); + self.didScheduleReconnect = NO; + [self tryToConnect]; + }); + + self.didScheduleReconnect = YES; + } + } +} + +- (void)connectWithHandler:(FIRMessagingConnectCompletionHandler)handler { + if (self.isConnected) { + NSError *error = [NSError fcm_errorWithCode:kFIRMessagingErrorCodeAlreadyConnected + userInfo:@{ + NSLocalizedFailureReasonErrorKey: @"FIRMessaging is already connected", + }]; + handler(error); + return; + } + self.lastDisconnectedTimestamp = FIRMessagingCurrentTimestampInMilliseconds(); + self.connectHandler = handler; + [self.registrar tryToLoadValidCheckinInfo]; + [self connect]; +} + +- (void)connect { + // reset retry counts + self.connectRetryCount = 0; + + if (self.isConnected) { + return; + } + + self.stayConnected = YES; + BOOL isRegistrationComplete = [self.registrar hasValidCheckinInfo]; + + if (!isRegistrationComplete) { + if (![self.registrar tryToLoadValidCheckinInfo]) { + if (self.connectHandler) { + NSError *error = [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeMissingDeviceID]; + self.connectHandler(error); + } + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeClient009, + @"Failed to connect to MCS. No deviceID and secret found."); + return; + } + } + [self setupConnectionAndConnect]; +} + +- (void)disconnect { + // user called disconnect + // We don't want to connect later even if no network is available. + [self disconnectWithTryToConnectLater:NO]; +} + +/** + * Disconnect the current client connection. Also explicitly stop and connction retries. + * + * @param tryToConnectLater If YES will try to connect later when sending upstream messages + * else if NO do not connect again until user explicitly calls + * connect. + */ +- (void)disconnectWithTryToConnectLater:(BOOL)tryToConnectLater { + + self.stayConnected = tryToConnectLater; + [self.connection signOut]; + _FIRMessagingDevAssert(self.connection.state == kFIRMessagingConnectionNotConnected, + @"FIRMessaging connection did not disconnect"); + + // since we can disconnect while still trying to establish the connection it's required to + // cancel all performSelectors else the object might be retained + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(tryToConnect) + object:nil]; + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(didConnectTimeout) + object:nil]; + self.connectHandler = nil; +} + + +#pragma mark - Messages + +- (void)sendMessage:(GPBMessage *)message { + [self.connection sendProto:message]; +} + +- (void)sendOnConnectOrDrop:(GPBMessage *)message { + [self.connection sendOnConnectOrDrop:message]; +} + +#pragma mark - FIRMessagingConnectionDelegate + +- (void)connection:(FIRMessagingConnection *)fcmConnection + didCloseForReason:(FIRMessagingConnectionCloseReason)reason { + + self.lastDisconnectedTimestamp = FIRMessagingCurrentTimestampInMilliseconds(); + + if (reason == kFIRMessagingConnectionCloseReasonSocketDisconnected) { + // Cancel the not-yet-triggered timeout task before rescheduling, in case the previous sign in + // failed, due to a connection error caused by bad network. + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(didConnectTimeout) + object:nil]; + } + if (self.stayConnected) { + [self scheduleConnectRetry]; + } +} + +- (void)didLoginWithConnection:(FIRMessagingConnection *)fcmConnection { + // Cancel the not-yet-triggered timeout task. + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(didConnectTimeout) + object:nil]; + self.connectRetryCount = 0; + self.lastConnectedTimestamp = FIRMessagingCurrentTimestampInMilliseconds(); + + + [self.dataMessageManager setDeviceAuthID:self.registrar.deviceAuthID + secretToken:self.registrar.secretToken]; + if (self.connectHandler) { + self.connectHandler(nil); + // notified the third party app with the registrationId. + // we don't want them to know about the connection status and how it changes + // so remove this handler + self.connectHandler = nil; + } +} + +- (void)connectionDidRecieveMessage:(GtalkDataMessageStanza *)message { + NSDictionary *parsedMessage = [self.dataMessageManager processPacket:message]; + if ([parsedMessage count]) { + [self.dataMessageManager didReceiveParsedMessage:parsedMessage]; + } +} + +- (int)connectionDidReceiveAckForRmqIds:(NSArray *)rmqIds { + NSSet *rmqIDSet = [NSSet setWithArray:rmqIds]; + NSMutableArray *messagesSent = [NSMutableArray arrayWithCapacity:rmqIds.count]; + [self.rmq2Manager scanWithRmqMessageHandler:nil + dataMessageHandler:^(int64_t rmqId, GtalkDataMessageStanza *stanza) { + NSString *rmqIdString = [NSString stringWithFormat:@"%lld", rmqId]; + if ([rmqIDSet containsObject:rmqIdString]) { + [messagesSent addObject:stanza]; + } + }]; + for (GtalkDataMessageStanza *message in messagesSent) { + [self.dataMessageManager didSendDataMessageStanza:message]; + } + return [self.rmq2Manager removeRmqMessagesWithRmqIds:rmqIds]; +} + +#pragma mark - Private + +- (void)setupConnectionAndConnect { + [self setupConnection]; + [self tryToConnect]; +} + +- (void)setupConnection { + NSString *host = FIRMessagingServerHost(); + NSUInteger port = FIRMessagingServerPort(); + _FIRMessagingDevAssert([host length] > 0 && port != 0, @"Invalid port or host"); + + if (self.connection != nil) { + // if there is an old connection, explicitly sign it off. + [self.connection signOut]; + self.connection.delegate = nil; + } + self.connection = [[FIRMessagingConnection alloc] initWithAuthID:self.registrar.deviceAuthID + token:self.registrar.secretToken + host:host + port:port + runLoop:[NSRunLoop mainRunLoop] + rmq2Manager:self.rmq2Manager + fcmManager:self.dataMessageManager]; + self.connection.delegate = self; +} + +- (void)tryToConnect { + if (!self.stayConnected) { + return; + } + + // Cancel any other pending signin requests. + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(tryToConnect) + object:nil]; + + // Do not re-sign in if there is already a connection in progress. + if (self.connection.state != kFIRMessagingConnectionNotConnected) { + return; + } + + _FIRMessagingDevAssert(self.registrar.deviceAuthID.length > 0 && + self.registrar.secretToken.length > 0 && + self.connection != nil, + @"Invalid state cannot connect"); + + self.connectRetryCount = MIN(kMaxRetryExponent, self.connectRetryCount + 1); + [self performSelector:@selector(didConnectTimeout) + withObject:nil + afterDelay:self.connectionTimeoutInterval]; + [self.connection signIn]; +} + +- (void)didConnectTimeout { + _FIRMessagingDevAssert(self.connection.state != kFIRMessagingConnectionSignedIn, + @"Invalid state for MCS connection"); + + if (self.stayConnected) { + [self.connection signOut]; + [self scheduleConnectRetry]; + } +} + +#pragma mark - Schedulers + +- (void)scheduleConnectRetry { + FIRReachabilityStatus status = self.reachability.reachabilityStatus; + BOOL isReachable = (status == kFIRReachabilityViaWifi || status == kFIRReachabilityViaCellular); + if (!isReachable) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeClient010, + @"Internet not reachable when signing into MCS during a retry"); + + FIRMessagingConnectCompletionHandler handler = [self.connectHandler copy]; + // disconnect before issuing a callback + [self disconnectWithTryToConnectLater:YES]; + NSError *error = [NSError errorWithDomain:@"No internet available, cannot connect to FIRMessaging" + code:kFIRMessagingErrorCodeNetwork + userInfo:nil]; + if (handler) { + handler(error); + self.connectHandler = nil; + } + return; + } + + NSUInteger retryInterval = [self nextRetryInterval]; + + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeClient011, + @"Failed to sign in to MCS, retry in %lu seconds", + _FIRMessaging_UL(retryInterval)); + [self performSelector:@selector(tryToConnect) withObject:nil afterDelay:retryInterval]; +} + +- (NSUInteger)nextRetryInterval { + return 1u << self.connectRetryCount; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingCodedInputStream.h b/Firebase/Messaging/FIRMessagingCodedInputStream.h new file mode 100644 index 0000000..8f22290 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingCodedInputStream.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +@interface FIRMessagingCodedInputStream : NSObject + +@property(nonatomic, readonly, assign) size_t offset; + +- (instancetype)initWithData:(NSData *)data; +- (BOOL)readTag:(int8_t *)tag; +- (BOOL)readLength:(int32_t *)length; +- (NSData *)readDataWithLength:(uint32_t)length; + +@end diff --git a/Firebase/Messaging/FIRMessagingCodedInputStream.m b/Firebase/Messaging/FIRMessagingCodedInputStream.m new file mode 100644 index 0000000..82c0677 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingCodedInputStream.m @@ -0,0 +1,142 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingCodedInputStream.h" +#import "FIRMessagingDefines.h" + +typedef struct { + const void *bytes; + size_t bufferSize; + size_t bufferPos; +} BufferState; + +static BOOL CheckSize(BufferState *state, size_t size) { + size_t newSize = state->bufferPos + size; + if (newSize > state->bufferSize) { + return NO; + } + return YES; +} + +static BOOL ReadRawByte(BufferState *state, int8_t *output) { + _FIRMessagingDevAssert(output != NULL && state != NULL, @"Invalid parameters"); + + if (CheckSize(state, sizeof(int8_t))) { + *output = ((int8_t *)state->bytes)[state->bufferPos++]; + return YES; + } + return NO; +} + +static BOOL ReadRawVarInt32(BufferState *state, int32_t *output) { + _FIRMessagingDevAssert(output != NULL && state != NULL, @"Invalid parameters"); + + int8_t tmp = 0; + if (!ReadRawByte(state, &tmp)) { + return NO; + } + if (tmp >= 0) { + *output = tmp; + return YES; + } + int32_t result = tmp & 0x7f; + if (!ReadRawByte(state, &tmp)) { + return NO; + } + if (tmp >= 0) { + result |= tmp << 7; + } else { + result |= (tmp & 0x7f) << 7; + if (!ReadRawByte(state, &tmp)) { + return NO; + } + if (tmp >= 0) { + result |= tmp << 14; + } else { + result |= (tmp & 0x7f) << 14; + if (!ReadRawByte(state, &tmp)) { + return NO; + } + if (tmp >= 0) { + result |= tmp << 21; + } else { + result |= (tmp & 0x7f) << 21; + if (!ReadRawByte(state, &tmp)) { + return NO; + } + result |= tmp << 28; + if (tmp < 0) { + // Discard upper 32 bits. + for (int i = 0; i < 5; ++i) { + if (!ReadRawByte(state, &tmp)) { + return NO; + } + if (tmp >= 0) { + *output = result; + return YES; + } + } + return NO; + } + } + } + } + *output = result; + return YES; +} + +@interface FIRMessagingCodedInputStream() + +@property(nonatomic, readwrite, strong) NSData *buffer; +@property(nonatomic, readwrite, assign) BufferState state; + +@end + +@implementation FIRMessagingCodedInputStream; + +- (instancetype)initWithData:(NSData *)data { + self = [super init]; + if (self) { + _buffer = data; + _state.bytes = _buffer.bytes; + _state.bufferSize = _buffer.length; + } + return self; +} + +- (size_t)offset { + return _state.bufferPos; +} + +- (BOOL)readTag:(int8_t *)tag { + return ReadRawByte(&_state, tag); +} + +- (BOOL)readLength:(int32_t *)length { + return ReadRawVarInt32(&_state, length); +} + +- (NSData *)readDataWithLength:(uint32_t)length { + if (!CheckSize(&_state, length)) { + return nil; + } + const void *bytesToRead = _state.bytes + _state.bufferPos; + NSData *result = [NSData dataWithBytes:bytesToRead length:length]; + _state.bufferPos += length; + return result; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingConfig.h b/Firebase/Messaging/FIRMessagingConfig.h new file mode 100644 index 0000000..09a9ec7 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingConfig.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +typedef NS_ENUM(int8_t, FIRMessagingLogLevel) { + kFIRMessagingLogLevelDebug, + kFIRMessagingLogLevelInfo, + kFIRMessagingLogLevelError, + kFIRMessagingLogLevelAssert, +}; + +/** + * Config used to set different options in Firebase Messaging. + */ +@interface FIRMessagingConfig : NSObject + +/** + * The log level for the FIRMessaging library. Valid values are `kFIRMessagingLogLevelDebug`, + * `kFIRMessagingLogLevelInfo`, `kFIRMessagingLogLevelError`, and `kFIRMessagingLogLevelAssert`. + */ +@property(nonatomic, readwrite, assign) FIRMessagingLogLevel logLevel; + +/** + * Get default configuration for FIRMessaging. The default config has logLevel set to + * `kFIRMessagingLogLevelError` and `receiverDelegate` is set to nil. + * + * @return FIRMessagingConfig sharedInstance. + */ ++ (instancetype)defaultConfig; + +@end + diff --git a/Firebase/Messaging/FIRMessagingConfig.m b/Firebase/Messaging/FIRMessagingConfig.m new file mode 100644 index 0000000..e7674c3 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingConfig.m @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingConfig.h" +#import "FIRMessagingDefines.h" + +@implementation FIRMessagingConfig + +- (instancetype)init { + FIRMessagingInvalidateInitializer(); +} + ++ (instancetype)defaultConfig { + return [[FIRMessagingConfig alloc] initWithDefaultConfig]; +} + +- (instancetype)initWithDefaultConfig { + self = [super init]; + if (self) { + self.logLevel = kFIRMessagingLogLevelError; + } + return self; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingConnection.h b/Firebase/Messaging/FIRMessagingConnection.h new file mode 100644 index 0000000..e78adbf --- /dev/null +++ b/Firebase/Messaging/FIRMessagingConnection.h @@ -0,0 +1,107 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +@class FIRMessagingConnection; +@class FIRMessagingDataMessageManager; +@class FIRMessagingRmqManager; + +@class GtalkDataMessageStanza; +@class GPBMessage; + +typedef void (^FIRMessagingMessageHandler)(NSDictionary *); + +typedef NS_ENUM(NSUInteger, FIRMessagingConnectionState) { + kFIRMessagingConnectionNotConnected = 0, + kFIRMessagingConnectionConnecting, + kFIRMessagingConnectionConnected, + kFIRMessagingConnectionSignedIn, +}; + +typedef NS_ENUM(NSUInteger, FIRMessagingConnectionCloseReason) { + kFIRMessagingConnectionCloseReasonSocketDisconnected = 0, + kFIRMessagingConnectionCloseReasonTimeout, + kFIRMessagingConnectionCloseReasonUserDisconnect, +}; + +@protocol FIRMessagingConnectionDelegate<NSObject> + +- (void)connection:(FIRMessagingConnection *)fcmConnection + didCloseForReason:(FIRMessagingConnectionCloseReason)reason; +- (void)didLoginWithConnection:(FIRMessagingConnection *)fcmConnection; +- (void)connectionDidRecieveMessage:(GtalkDataMessageStanza *)message; +/** + * Called when a stream ACK or a selective ACK are received - this indicates the + * message has been received by MCS. + * @return The count of rmqIds deleted from the client RMQ store. + */ +- (int)connectionDidReceiveAckForRmqIds:(NSArray *)rmqIds; + +@end + + +/** + * This class maintains the actual FIRMessaging connection that we use to receive and send messages + * while the app is in foreground. Once we have a registrationID from the FIRMessaging backend we + * are able to set up this connection which is used for any further communication with FIRMessaging + * backend. In case the connection breaks off while the app is still being used we try to rebuild + * the connection with an exponential backoff. + * + * This class also notifies the delegate about the main events happening in the lifcycle of the + * FIRMessaging connection (read FIRMessagingConnectionDelegate). All of the `on-the-wire` + * interactions with FIRMessaging are channelled through here. + */ +@interface FIRMessagingConnection : NSObject + +@property(nonatomic, readwrite, assign) int64_t lastHeartbeatPingTimestamp; +@property(nonatomic, readonly, assign) FIRMessagingConnectionState state; +@property(nonatomic, readonly, copy) NSString *host; +@property(nonatomic, readonly, assign) NSUInteger port; +@property(nonatomic, readwrite, weak) id<FIRMessagingConnectionDelegate> delegate; + +- (instancetype)initWithAuthID:(NSString *)authId + token:(NSString *)token + host:(NSString *)host + port:(NSUInteger)port + runLoop:(NSRunLoop *)runLoop + rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager + fcmManager:(FIRMessagingDataMessageManager *)dataMessageManager; + +- (void)signIn; // connect +- (void)signOut; // disconnect + +/** + * Teardown the FIRMessaging connection and deallocate the resources being held up by the + * connection. + */ +- (void)teardown; + +/** + * Send proto to the wire. The message will be cached before we try to send so that in case of + * failure we can send it again later on when we have connection. + */ +- (void)sendProto:(GPBMessage *)proto; + +/** + * Send a message after the currently in progress connection succeeds, otherwise drop it. + * + * This should be used for TTL=0 messages that force a reconnect. They shouldn't be persisted + * in the RMQ, but they should be sent if the reconnect is successful. + */ +- (void)sendOnConnectOrDrop:(GPBMessage *)message; + +@end diff --git a/Firebase/Messaging/FIRMessagingConnection.m b/Firebase/Messaging/FIRMessagingConnection.m new file mode 100644 index 0000000..afbd0ba --- /dev/null +++ b/Firebase/Messaging/FIRMessagingConnection.m @@ -0,0 +1,711 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingConnection.h" + +#import "Protos/GtalkCore.pbobjc.h" +#import "Protos/GtalkExtensions.pbobjc.h" + +#import "FIRMessaging.h" +#import "FIRMessagingDataMessageManager.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingRmqManager.h" +#import "FIRMessagingSecureSocket.h" +#import "FIRMessagingUtilities.h" +#import "FIRMessagingVersionUtilities.h" +#import "FIRMessaging_Private.h" + +static NSInteger const kIqSelectiveAck = 12; +static NSInteger const kIqStreamAck = 13; +static int const kInvalidStreamId = -1; +// Threshold for number of messages removed that we will ack, for short lived connections +static int const kMessageRemoveAckThresholdCount = 5; + +static NSTimeInterval const kHeartbeatInterval = 30.0; +static NSTimeInterval const kConnectionTimeout = 20.0; +static int32_t const kAckingInterval = 10; + +static NSString *const kUnackedS2dIdKey = @"FIRMessagingUnackedS2dIdKey"; +static NSString *const kAckedS2dIdMapKey = @"FIRMessagingAckedS2dIdMapKey"; + +static NSString *const kRemoteFromAddress = @"from"; + +@interface FIRMessagingD2SInfo : NSObject + +@property(nonatomic, readwrite, assign) int streamId; +@property(nonatomic, readwrite, strong) NSString *d2sID; +- (instancetype)initWithStreamId:(int)streamId d2sId:(NSString *)d2sID; + +@end + +@implementation FIRMessagingD2SInfo + +- (instancetype)initWithStreamId:(int)streamId d2sId:(NSString *)d2sID { + self = [super init]; + if (self) { + _streamId = streamId; + _d2sID = [d2sID copy]; + } + return self; +} + +- (BOOL)isEqual:(id)object { + if ([object isKindOfClass:[self class]]) { + FIRMessagingD2SInfo *other = (FIRMessagingD2SInfo *)object; + return self.streamId == other.streamId && [self.d2sID isEqualToString:other.d2sID]; + } + return NO; +} + +- (NSUInteger)hash { + return [self.d2sID hash]; +} + +@end + +@interface FIRMessagingConnection ()<FIRMessagingSecureSocketDelegate> + +@property(nonatomic, readwrite, weak) FIRMessagingRmqManager *rmq2Manager; +@property(nonatomic, readwrite, weak) FIRMessagingDataMessageManager *dataMessageManager; + +@property(nonatomic, readwrite, assign) FIRMessagingConnectionState state; +@property(nonatomic, readwrite, copy) NSString *host; +@property(nonatomic, readwrite, assign) NSUInteger port; + +@property(nonatomic, readwrite, strong) NSString *authId; +@property(nonatomic, readwrite, strong) NSString *token; + +@property(nonatomic, readwrite, strong) FIRMessagingSecureSocket *socket; + +@property(nonatomic, readwrite, assign) int64_t lastLoginServerTimestamp; +@property(nonatomic, readwrite, assign) int lastStreamIdAcked; +@property(nonatomic, readwrite, assign) int inStreamId; +@property(nonatomic, readwrite, assign) int outStreamId; + +@property(nonatomic, readwrite, strong) NSMutableArray *unackedS2dIds; +@property(nonatomic, readwrite, strong) NSMutableDictionary *ackedS2dMap; +@property(nonatomic, readwrite, strong) NSMutableArray *d2sInfos; +// ttl=0 messages that need to be sent as soon as we establish a connection +@property(nonatomic, readwrite, strong) NSMutableArray *sendOnConnectMessages; + +@property(nonatomic, readwrite, strong) NSRunLoop *runLoop; + +@end + + +@implementation FIRMessagingConnection; + +- (instancetype)initWithAuthID:(NSString *)authId + token:(NSString *)token + host:(NSString *)host + port:(NSUInteger)port + runLoop:(NSRunLoop *)runLoop + rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager + fcmManager:(FIRMessagingDataMessageManager *)dataMessageManager { + self = [super init]; + if (self) { + _authId = [authId copy]; + _token = [token copy]; + _host = [host copy]; + _port = port; + _runLoop = runLoop; + _rmq2Manager = rmq2Manager; + _dataMessageManager = dataMessageManager; + + _d2sInfos = [NSMutableArray array]; + + _unackedS2dIds = [NSMutableArray arrayWithArray:[_rmq2Manager unackedS2dRmqIds]]; + _ackedS2dMap = [NSMutableDictionary dictionary]; + _sendOnConnectMessages = [NSMutableArray array]; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"host: %@, port: %lu, stream id in: %d, stream id out: %d", + self.host, + _FIRMessaging_UL(self.port), + self.inStreamId, + self.outStreamId]; +} + +- (void)signIn { + _FIRMessagingDevAssert(self.state == kFIRMessagingConnectionNotConnected, @"Invalid connection state."); + if (self.state != kFIRMessagingConnectionNotConnected) { + return; + } + + // break it up for testing + [self setupConnectionSocket]; + [self connectToSocket:self.socket]; +} + +- (void)setupConnectionSocket { + self.socket = [[FIRMessagingSecureSocket alloc] init]; + self.socket.delegate = self; +} + +- (void)connectToSocket:(FIRMessagingSecureSocket *)socket { + self.state = kFIRMessagingConnectionConnecting; + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection000, + @"Start connecting to FIRMessaging service."); + [socket connectToHost:self.host port:self.port onRunLoop:self.runLoop]; +} + +- (void)signOut { + // Clear the list of messages to be sent on connect. This will only + // have messages in it if an error happened before receiving the LoginResponse. + [self.sendOnConnectMessages removeAllObjects]; + + if (self.state == kFIRMessagingConnectionSignedIn) { + [self sendClose]; + } + if (self.state != kFIRMessagingConnectionNotConnected) { + [self disconnect]; + } +} + +- (void)teardown { + if (self.state != kFIRMessagingConnectionNotConnected) { + [self disconnect]; + } +} + +#pragma mark - FIRMessagingSecureSocketDelegate + +- (void)secureSocketDidConnect:(FIRMessagingSecureSocket *)socket { + self.state = kFIRMessagingConnectionConnected; + self.lastStreamIdAcked = 0; + self.inStreamId = 0; + self.outStreamId = 0; + + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection001, + @"Connected to FIRMessaging service."); + [self resetUnconfirmedAcks]; + [self sendLoginRequest:self.authId token:self.token]; +} + +- (void)didDisconnectWithSecureSocket:(FIRMessagingSecureSocket *)socket { + _FIRMessagingDevAssert(self.socket == socket, @"Invalid socket"); + _FIRMessagingDevAssert(self.socket.state == kFIRMessagingSecureSocketClosed, @"Socket already closed"); + + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection002, + @"Secure socket disconnected from FIRMessaging service."); + [self disconnect]; + [self.delegate connection:self didCloseForReason:kFIRMessagingConnectionCloseReasonSocketDisconnected]; +} + +- (void)secureSocket:(FIRMessagingSecureSocket *)socket + didReceiveData:(NSData *)data + withTag:(int8_t)tag { + if (tag < 0) { + // Invalid proto tag + return; + } + + Class klassForTag = FIRMessagingGetClassForTag((FIRMessagingProtoTag)tag); + if ([klassForTag isSubclassOfClass:[NSNull class]]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeConnection003, @"Invalid tag %d for proto", + tag); + return; + } + + GPBMessage *proto = [klassForTag parseFromData:data error:NULL]; + if (tag == kFIRMessagingProtoTagLoginResponse && self.state != kFIRMessagingConnectionConnected) { + FIRMessagingLoggerDebug( + kFIRMessagingMessageCodeConnection004, + @"Should not receive generated message when the connection is not connected."); + return; + } else if (tag != kFIRMessagingProtoTagLoginResponse && self.state != kFIRMessagingConnectionSignedIn) { + FIRMessagingLoggerDebug( + kFIRMessagingMessageCodeConnection005, + @"Should not receive generated message when the connection is not signed in."); + return; + } + + // If traffic is received after a heartbeat it is safe to assume the connection is healthy. + [self cancelConnectionTimeoutTask]; + [self performSelector:@selector(sendHeartbeatPing) + withObject:nil + afterDelay:kHeartbeatInterval]; + + [self willProcessProto:proto]; + switch (tag) { + case kFIRMessagingProtoTagLoginResponse: + [self didReceiveLoginResponse:(GtalkLoginResponse *)proto]; + break; + case kFIRMessagingProtoTagDataMessageStanza: + [self didReceiveDataMessageStanza:(GtalkDataMessageStanza *)proto]; + break; + case kFIRMessagingProtoTagHeartbeatPing: + [self didReceiveHeartbeatPing:(GtalkHeartbeatPing *)proto]; + break; + case kFIRMessagingProtoTagHeartbeatAck: + [self didReceiveHeartbeatAck:(GtalkHeartbeatAck *)proto]; + break; + case kFIRMessagingProtoTagClose: + [self didReceiveClose:(GtalkClose *)proto]; + break; + case kFIRMessagingProtoTagIqStanza: + [self handleIqStanza:(GtalkIqStanza *)proto]; + break; + default: + [self didReceiveUnhandledProto:proto]; + break; + } +} + +// Called from secure socket once we have send the proto with given rmqId over the wire +// since we are mostly concerned with user facing messages which certainly have a rmqId +// we can retrieve them from the Rmq if necessary to look at stuff but for now we just +// log it. +- (void)secureSocket:(FIRMessagingSecureSocket *)socket + didSendProtoWithTag:(int8_t)tag + rmqId:(NSString *)rmqId { + // log the message + [self logMessage:rmqId messageType:tag isOut:YES]; +} + +#pragma mark - FIRMessagingTestConnection + +- (void)sendProto:(GPBMessage *)proto { + FIRMessagingProtoTag tag = FIRMessagingGetTagForProto(proto); + if (tag == kFIRMessagingProtoTagLoginRequest && self.state != kFIRMessagingConnectionConnected) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection006, + @"Cannot send generated message when the connection is not connected."); + return; + } else if (tag != kFIRMessagingProtoTagLoginRequest && self.state != kFIRMessagingConnectionSignedIn) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection007, + @"Cannot send generated message when the connection is not signed in."); + return; + } + + _FIRMessagingDevAssert(self.socket != nil, @"Socket shouldn't be nil"); + if (self.socket == nil) { + return; + } + + [self willSendProto:proto]; + + [self.socket sendData:proto.data withTag:tag rmqId:FIRMessagingGetRmq2Id(proto)]; +} + +- (void)sendOnConnectOrDrop:(GPBMessage *)message { + if (self.state == kFIRMessagingConnectionSignedIn) { + // If a connection has already been established, send normally + [self sendProto:message]; + } else { + // Otherwise add them to the list of messages to send after login + [self.sendOnConnectMessages addObject:message]; + } +} + ++ (GtalkLoginRequest *)loginRequestWithToken:(NSString *)token authID:(NSString *)authID { + GtalkLoginRequest *login = [[GtalkLoginRequest alloc] init]; + login.accountId = 1000000; + login.authService = GtalkLoginRequest_AuthService_AndroidId; + login.authToken = token; + login.id_p = [NSString stringWithFormat:@"%@-%@", @"ios", FIRMessagingCurrentLibraryVersion()]; + login.domain = @"mcs.android.com"; + login.deviceId = [NSString stringWithFormat:@"android-%llx", authID.longLongValue]; + login.networkType = [self currentNetworkType]; + login.resource = authID; + login.user = authID; + login.useRmq2 = YES; + login.lastRmqId = 1; // Sending not enabled yet so this stays as 1. + return login; +} + ++ (int32_t)currentNetworkType { + // http://developer.android.com/reference/android/net/ConnectivityManager.html + int32_t fcmNetworkType; + FIRMessagingNetworkStatus type = [[FIRMessaging messaging] networkType]; + switch (type) { + case kFIRMessagingReachabilityReachableViaWiFi: + fcmNetworkType = 1; + break; + + case kFIRMessagingReachabilityReachableViaWWAN: + fcmNetworkType = 0; + break; + + default: + fcmNetworkType = -1; + break; + } + return fcmNetworkType; +} + +- (void)sendLoginRequest:(NSString *)authId + token:(NSString *)token { + GtalkLoginRequest *login = [[self class] loginRequestWithToken:token authID:authId]; + + // clear the messages sent during last connection + if ([self.d2sInfos count]) { + [self.d2sInfos removeAllObjects]; + } + + if (self.unackedS2dIds.count > 0) { + FIRMessagingLoggerDebug( + kFIRMessagingMessageCodeConnection008, + @"There are unacked persistent Ids in the login request: %@", + [self.unackedS2dIds.description stringByReplacingOccurrencesOfString:@"%" + withString:@"%%"]); + } + // Send out acks. + for (NSString *unackedPersistentS2dId in self.unackedS2dIds) { + [login.receivedPersistentIdArray addObject:unackedPersistentS2dId]; + } + + GtalkSetting *setting = [[GtalkSetting alloc] init]; + setting.name = @"new_vc"; + setting.value = @"1"; + [login.settingArray addObject:setting]; + + [self sendProto:login]; +} + +- (void)sendHeartbeatAck { + [self sendProto:[[GtalkHeartbeatAck alloc] init]]; +} + +- (void)sendHeartbeatPing { + // cancel the previous heartbeat request. + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(sendHeartbeatPing) + object:nil]; + [self scheduleConnectionTimeoutTask]; + [self sendProto:[[GtalkHeartbeatPing alloc] init]]; +} + ++ (GtalkIqStanza *)createStreamAck { + GtalkIqStanza *iq = [[GtalkIqStanza alloc] init]; + iq.type = GtalkIqStanza_IqType_Set; + iq.id_p = @""; + GtalkExtension *ext = [[GtalkExtension alloc] init]; + ext.id_p = kIqStreamAck; + ext.data_p = @""; + iq.extension = ext; + return iq; +} + +- (void)sendStreamAck { + GtalkIqStanza *iq = [[self class] createStreamAck]; + [self sendProto:iq]; +} + +- (void)sendClose { + [self sendProto:[[GtalkClose alloc] init]]; +} + +- (void)handleIqStanza:(GtalkIqStanza *)iq { + if (iq.hasExtension) { + if (iq.extension.id_p == kIqStreamAck) { + [self didReceiveStreamAck:iq]; + return; + } + if (iq.extension.id_p == kIqSelectiveAck) { + [self didReceiveSelectiveAck:iq]; + return; + } + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection009, @"Unknown ack extension id %d.", + iq.extension.id_p); + } else { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection010, @"Ip stanza without extension."); + } + [self didReceiveUnhandledProto:iq]; +} + +- (void)didReceiveLoginResponse:(GtalkLoginResponse *)loginResponse { + if (loginResponse.hasError) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection011, + @"Login error with type: %@, message: %@.", loginResponse.error.type, + loginResponse.error.message); + return; + } + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection012, @"Logged onto MCS service."); + // We sent the persisted list of unack'd messages with login so we can assume they have been ack'd + // by the server. + _FIRMessagingDevAssert(self.unackedS2dIds.count == 0, @"No ids present"); + _FIRMessagingDevAssert(self.outStreamId == 1, @"Login should be the first stream id"); + + self.state = kFIRMessagingConnectionSignedIn; + self.lastLoginServerTimestamp = loginResponse.serverTimestamp; + [self.delegate didLoginWithConnection:self]; + [self sendHeartbeatPing]; + + // Add all the TTL=0 messages on connect + for (GPBMessage *message in self.sendOnConnectMessages) { + [self sendProto:message]; + } + [self.sendOnConnectMessages removeAllObjects]; +} + +- (void)didReceiveHeartbeatPing:(GtalkHeartbeatPing *)heartbeatPing { + [self sendHeartbeatAck]; +} + +- (void)didReceiveHeartbeatAck:(GtalkHeartbeatAck *)heartbeatAck { +#if FIRMessaging_PROBER + self.lastHeartbeatPingTimestamp = FIRMessagingCurrentTimestampInSeconds(); +#endif +} + +- (void)didReceiveDataMessageStanza:(GtalkDataMessageStanza *)dataMessageStanza { + // TODO: Maybe add support raw data later + [self.delegate connectionDidRecieveMessage:dataMessageStanza]; +} + +- (void)didReceiveUnhandledProto:(GPBMessage *)proto { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection013, @"Received unhandled proto"); +} + +- (void)didReceiveStreamAck:(GtalkIqStanza *)iq { + // Server received some stuff from us we don't really need to do anything special +} + +- (void)didReceiveSelectiveAck:(GtalkIqStanza *)iq { + GtalkExtension *extension = iq.extension; + if (extension) { + int extensionId = extension.id_p; + if (extensionId == kIqSelectiveAck) { + + NSString *dataString = extension.data_p; + GtalkSelectiveAck *selectiveAck = [[GtalkSelectiveAck alloc] init]; + [selectiveAck mergeFromData:[dataString dataUsingEncoding:NSUTF8StringEncoding] + extensionRegistry:nil]; + + NSArray <NSString *>*acks = [selectiveAck idArray]; + + // we've received ACK's + [self.delegate connectionDidReceiveAckForRmqIds:acks]; + + // resend unacked messages + [self.dataMessageManager resendMessagesWithConnection:self]; + } + } +} + +- (void)didReceiveClose:(GtalkClose *)close { + [self disconnect]; +} + +- (void)willProcessProto:(GPBMessage *)proto { + self.inStreamId++; + + if ([proto isKindOfClass:GtalkDataMessageStanza.class]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection014, + @"RMQ: Receiving %@ with rmq_id: %@ incoming stream Id: %d", + proto.class, FIRMessagingGetRmq2Id(proto), self.inStreamId); + } else { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection015, + @"RMQ: Receiving %@ with incoming stream Id: %d.", proto.class, + self.inStreamId); + } + int streamId = FIRMessagingGetLastStreamId(proto); + if (streamId != kInvalidStreamId) { + // confirm the D2S messages that were sent by us + [self confirmAckedD2sIdsWithStreamId:streamId]; + + // We can now confirm that our ack was received by the server and start our unack'd list fresh + // with the proto we just received. + [self confirmAckedS2dIdsWithStreamId:streamId]; + } + NSString *rmq2Id = FIRMessagingGetRmq2Id(proto); + if (rmq2Id != nil) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection016, + @"RMQ: Add unacked persistent Id: %@.", + [rmq2Id stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]); + [self.unackedS2dIds addObject:rmq2Id]; + [self.rmq2Manager saveS2dMessageWithRmqId:rmq2Id]; // RMQ save + } + BOOL explicitAck = ([proto isKindOfClass:[GtalkDataMessageStanza class]] && + [(GtalkDataMessageStanza *)proto immediateAck]); + // If we have not sent anything and the ack threshold has been reached then explicitly send one + // to notify the server that we have received messages. + if (self.inStreamId - self.lastStreamIdAcked >= kAckingInterval || explicitAck) { + [self sendStreamAck]; + } +} + +- (void)willSendProto:(GPBMessage *)proto { + self.outStreamId++; + + NSString *rmq2Id = FIRMessagingGetRmq2Id(proto); + if ([rmq2Id length]) { + FIRMessagingD2SInfo *d2sInfo = [[FIRMessagingD2SInfo alloc] initWithStreamId:self.outStreamId d2sId:rmq2Id]; + [self.d2sInfos addObject:d2sInfo]; + } + + // each time we send a d2s message, it acks previously received + // s2d messages via the last (s2d) stream id received. + + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection017, + @"RMQ: Sending %@ with outgoing stream Id: %d.", proto.class, + self.outStreamId); + // We have received messages since last time we sent something - send ack info to server. + if (self.inStreamId > self.lastStreamIdAcked) { + FIRMessagingSetLastStreamId(proto, self.inStreamId); + self.lastStreamIdAcked = self.inStreamId; + } + + if (self.unackedS2dIds.count > 0) { + // Move all 'unack'd' messages to the ack'd map so they can be removed once the + // ack is confirmed. + NSArray *ackedS2dIds = [NSArray arrayWithArray:self.unackedS2dIds]; + FIRMessagingLoggerDebug( + kFIRMessagingMessageCodeConnection018, @"RMQ: Mark persistent Ids as acked: %@.", + [ackedS2dIds.description stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]); + [self.unackedS2dIds removeAllObjects]; + self.ackedS2dMap[[@(self.outStreamId) stringValue]] = ackedS2dIds; + } +} + +#pragma mark - Private + +/** + * This processes the s2d message received in reference to the d2s messages + * that we have sent before. + */ +- (void)confirmAckedD2sIdsWithStreamId:(int)lastReceivedStreamId { + NSMutableArray *d2sIdsAcked = [NSMutableArray array]; + for (FIRMessagingD2SInfo *d2sInfo in self.d2sInfos) { + if (lastReceivedStreamId < d2sInfo.streamId) { + break; + } + [d2sIdsAcked addObject:d2sInfo]; + } + + NSMutableArray *rmqIds = [NSMutableArray arrayWithCapacity:[d2sIdsAcked count]]; + // remove ACK'ed messages + for (FIRMessagingD2SInfo *d2sInfo in d2sIdsAcked) { + if ([d2sInfo.d2sID length]) { + [rmqIds addObject:d2sInfo.d2sID]; + } + [self.d2sInfos removeObject:d2sInfo]; + } + [self.delegate connectionDidReceiveAckForRmqIds:rmqIds]; + int count = [self.delegate connectionDidReceiveAckForRmqIds:rmqIds]; + if (kMessageRemoveAckThresholdCount > 0 && count >= kMessageRemoveAckThresholdCount) { + // For short lived connections, if a large number of messages are removed, send an + // ack straight away so the server knows that this message was received. + [self sendStreamAck]; + } +} + +/** + * Called when a stream ACK or a selective ACK are received - this indicates the message has + * been received by MCS. + */ +- (void)didReceiveAckForRmqIds:(NSArray *)rmqIds { + // TODO: let the user know that the following messages were received by the server +} + +- (void)confirmAckedS2dIdsWithStreamId:(int)lastReceivedStreamId { + // If the server hasn't received the streamId yet. + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection019, + @"RMQ: Server last received stream Id: %d.", lastReceivedStreamId); + if (lastReceivedStreamId < self.outStreamId) { + // TODO: This could be a good indicator that we need to re-send something (acks)? + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection020, + @"RMQ: There are unsent messages that should be send...\n" + "server received: %d\nlast stream id sent: %d", + lastReceivedStreamId, self.outStreamId); + } + + NSSet *ackedStreamIds = + [self.ackedS2dMap keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { + NSString *streamId = key; + return streamId.intValue <= lastReceivedStreamId; + }]; + NSMutableArray *s2dIdsToDelete = [NSMutableArray array]; + + for (NSString *streamId in ackedStreamIds) { + NSArray *ackedS2dIds = self.ackedS2dMap[streamId]; + if (ackedS2dIds.count > 0) { + FIRMessagingLoggerDebug( + kFIRMessagingMessageCodeConnection021, + @"RMQ: Mark persistent Ids as confirmed by stream id %@: %@.", streamId, + [ackedS2dIds.description stringByReplacingOccurrencesOfString:@"%" withString:@"%%"]); + [self.ackedS2dMap removeObjectForKey:streamId]; + } + + [s2dIdsToDelete addObjectsFromArray:ackedS2dIds]; + } + + // clean up s2d ids that the server knows we've received. + // we let the server know via a s2d last stream id received in a + // d2s message. the server lets us know it has received our d2s + // message via a d2s last stream id received in a s2d message. + [self.rmq2Manager removeS2dIds:s2dIdsToDelete]; +} + +- (void)resetUnconfirmedAcks { + [self.ackedS2dMap enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + [self.unackedS2dIds addObjectsFromArray:obj]; + }]; + [self.ackedS2dMap removeAllObjects]; +} + +- (void)disconnect { + _FIRMessagingDevAssert(self.state != kFIRMessagingConnectionNotConnected, @"Connection already not connected"); + // cancel pending timeout tasks. + [self cancelConnectionTimeoutTask]; + // cancel pending heartbeat. + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(sendHeartbeatPing) + object:nil]; + // Unset the delegate. FIRMessagingConnection will not receive further events from the socket from now on. + self.socket.delegate = nil; + [self.socket disconnect]; + self.state = kFIRMessagingConnectionNotConnected; +} + +- (void)connectionTimedOut { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection022, + @"Connection to FIRMessaging service timed out."); + [self disconnect]; + [self.delegate connection:self didCloseForReason:kFIRMessagingConnectionCloseReasonTimeout]; +} + +- (void)scheduleConnectionTimeoutTask { + // cancel the previous heartbeat timeout event and schedule a new one. + [self cancelConnectionTimeoutTask]; + [self performSelector:@selector(connectionTimedOut) + withObject:nil + afterDelay:[self connectionTimeoutInterval]]; +} + +- (void)cancelConnectionTimeoutTask { + // cancel pending timeout tasks. + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(connectionTimedOut) + object:nil]; +} + +- (void)logMessage:(NSString *)description messageType:(int)messageType isOut:(BOOL)isOut { + messageType = isOut ? -messageType : messageType; + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeConnection023, + @"Send msg: %@ type: %d inStreamId: %d outStreamId: %d", description, + messageType, self.inStreamId, self.outStreamId); +} + +- (NSTimeInterval)connectionTimeoutInterval { + return kConnectionTimeout; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingConstants.h b/Firebase/Messaging/FIRMessagingConstants.h new file mode 100644 index 0000000..0e244a5 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingConstants.h @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Google + * + * 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. + */ + +/** + * Global constants to be put here. + * + */ +#import <Foundation/Foundation.h> + +#ifndef _FIRMessaging_CONSTANTS_H +#define _FIRMessaging_CONSTANTS_H + +FOUNDATION_EXPORT NSString *const kFIRMessagingRawDataKey; +FOUNDATION_EXPORT NSString *const kFIRMessagingCollapseKey; +FOUNDATION_EXPORT NSString *const kFIRMessagingFromKey; + +FOUNDATION_EXPORT NSString *const kFIRMessagingSendTo; +FOUNDATION_EXPORT NSString *const kFIRMessagingSendTTL; +FOUNDATION_EXPORT NSString *const kFIRMessagingSendDelay; +FOUNDATION_EXPORT NSString *const kFIRMessagingSendMessageID; +FOUNDATION_EXPORT NSString *const KFIRMessagingSendMessageAppData; + +FOUNDATION_EXPORT NSString *const kFIRMessagingMessageInternalReservedKeyword; +FOUNDATION_EXPORT NSString *const kFIRMessagingMessagePersistentIDKey; + +FOUNDATION_EXPORT NSString *const kFIRMessagingMessageIDKey; +FOUNDATION_EXPORT NSString *const kFIRMessagingMessageAPNSContentAvailableKey; +FOUNDATION_EXPORT NSString *const kFIRMessagingMessageSyncViaMCSKey; +FOUNDATION_EXPORT NSString *const kFIRMessagingMessageSyncMessageTTLKey; +FOUNDATION_EXPORT NSString *const kFIRMessagingMessageLinkKey; + +FOUNDATION_EXPORT NSString *const kFIRMessagingLibraryVersion; + +FOUNDATION_EXPORT NSString *const kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey; + +FOUNDATION_EXPORT NSString *const kFIRMessagingApplicationSupportSubDirectory; + +// Notifications +FOUNDATION_EXPORT NSString *const kFIRMessagingAPNSTokenNotification; +FOUNDATION_EXPORT NSString *const kFIRMessagingFCMTokenNotification; +FOUNDATION_EXPORT NSString *const kFIRMessagingInstanceIDTokenRefreshNotification; + +FOUNDATION_EXPORT const int kFIRMessagingSendTtlDefault; // 24 hours + +#endif diff --git a/Firebase/Messaging/FIRMessagingConstants.m b/Firebase/Messaging/FIRMessagingConstants.m new file mode 100644 index 0000000..f8e420c --- /dev/null +++ b/Firebase/Messaging/FIRMessagingConstants.m @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingConstants.h" + +NSString *const kFIRMessagingRawDataKey = @"rawData"; +NSString *const kFIRMessagingCollapseKey = @"collapse_key"; +NSString *const kFIRMessagingFromKey = @"from"; + +NSString *const kFIRMessagingSendTo = @"google." @"to"; +NSString *const kFIRMessagingSendTTL = @"google." @"ttl"; +NSString *const kFIRMessagingSendDelay = @"google." @"delay"; +NSString *const kFIRMessagingSendMessageID = @"google." @"msg_id"; +NSString *const KFIRMessagingSendMessageAppData = @"google." @"data"; + +NSString *const kFIRMessagingMessageInternalReservedKeyword = @"gcm."; +NSString *const kFIRMessagingMessagePersistentIDKey = @"persistent_id"; + +NSString *const kFIRMessagingMessageIDKey = @"gcm." @"message_id"; +NSString *const kFIRMessagingMessageAPNSContentAvailableKey = @"content-available"; +NSString *const kFIRMessagingMessageSyncViaMCSKey = @"gcm." @"duplex"; +NSString *const kFIRMessagingMessageSyncMessageTTLKey = @"gcm." @"ttl"; +NSString *const kFIRMessagingMessageLinkKey = @"gcm." @"app_link"; + +NSString *const kFIRMessagingLibraryVersion = @"FIRMessaging-version"; + +NSString *const kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey = + @"FirebaseAppDelegateProxyEnabled"; + +NSString *const kFIRMessagingApplicationSupportSubDirectory = @"Google/FirebaseMessaging"; + +// Notifications +NSString *const kFIRMessagingAPNSTokenNotification = @"com.firebase.iid.notif.apns-token"; +NSString *const kFIRMessagingFCMTokenNotification = @"com.firebase.iid.notif.fcm-token"; +NSString *const kFIRMessagingInstanceIDTokenRefreshNotification = + @"com.firebase.iid.notif.refresh-token"; + +const int kFIRMessagingSendTtlDefault = 24 * 60 * 60; // 24 hours diff --git a/Firebase/Messaging/FIRMessagingContextManagerService.h b/Firebase/Messaging/FIRMessagingContextManagerService.h new file mode 100644 index 0000000..83e6444 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingContextManagerService.h @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +FOUNDATION_EXPORT NSString *const kFIRMessagingContextManagerCategory; +FOUNDATION_EXPORT NSString *const kFIRMessagingContextManagerLocalTimeStart; +FOUNDATION_EXPORT NSString *const kFIRMessagingContextManagerLocalTimeEnd; +FOUNDATION_EXPORT NSString *const kFIRMessagingContextManagerBodyKey; + +@interface FIRMessagingContextManagerService : NSObject + +/** + * Check if the message is a context manager message or not. + * + * @param message The message to verify. + * + * @return YES if the message is a context manager message else NO. + */ ++ (BOOL)isContextManagerMessage:(NSDictionary *)message; + +/** + * Handle context manager message. + * + * @param message The message to handle. + * + * @return YES if the message was handled successfully else NO. + */ ++ (BOOL)handleContextManagerMessage:(NSDictionary *)message; + +@end diff --git a/Firebase/Messaging/FIRMessagingContextManagerService.m b/Firebase/Messaging/FIRMessagingContextManagerService.m new file mode 100644 index 0000000..1c9f653 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingContextManagerService.m @@ -0,0 +1,189 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingContextManagerService.h" + +#import <UIKit/UIKit.h> + +#import "FIRMessagingDefines.h" +#import "FIRMessagingLogger.h" + +#define kFIRMessagingContextManagerPrefixKey @"google.c.cm." +#define kFIRMessagingContextManagerNotificationKeyPrefix @"gcm.notification." + +static NSString *const kLogTag = @"FIRMessagingAnalytics"; + +static NSString *const kLocalTimeFormatString = @"yyyy-MM-dd HH:mm:ss"; + +static NSString *const kContextManagerPrefixKey = kFIRMessagingContextManagerPrefixKey; + +// Local timed messages (format yyyy-mm-dd HH:mm:ss) +NSString *const kFIRMessagingContextManagerLocalTimeStart = kFIRMessagingContextManagerPrefixKey @"lt_start"; +NSString *const kFIRMessagingContextManagerLocalTimeEnd = kFIRMessagingContextManagerPrefixKey @"lt_end"; + +// Local Notification Params +NSString *const kFIRMessagingContextManagerBodyKey = kFIRMessagingContextManagerNotificationKeyPrefix @"body"; +NSString *const kFIRMessagingContextManagerTitleKey = kFIRMessagingContextManagerNotificationKeyPrefix @"title"; +NSString *const kFIRMessagingContextManagerBadgeKey = kFIRMessagingContextManagerNotificationKeyPrefix @"badge"; +NSString *const kFIRMessagingContextManagerCategoryKey = + kFIRMessagingContextManagerNotificationKeyPrefix @"click_action"; +NSString *const kFIRMessagingContextManagerSoundKey = kFIRMessagingContextManagerNotificationKeyPrefix @"sound"; +NSString *const kFIRMessagingContextManagerContentAvailableKey = + kFIRMessagingContextManagerNotificationKeyPrefix @"content-available"; + +typedef NS_ENUM(NSUInteger, FIRMessagingContextManagerMessageType) { + FIRMessagingContextManagerMessageTypeNone, + FIRMessagingContextManagerMessageTypeLocalTime, +}; + +@implementation FIRMessagingContextManagerService + ++ (BOOL)isContextManagerMessage:(NSDictionary *)message { + // For now we only support local time in ContextManager. + if (![message[kFIRMessagingContextManagerLocalTimeStart] length]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeContextManagerService000, + @"Received message missing local start time, dropped."); + return NO; + } + + return YES; +} + ++ (BOOL)handleContextManagerMessage:(NSDictionary *)message { + NSString *startTimeString = message[kFIRMessagingContextManagerLocalTimeStart]; + if (startTimeString.length) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeContextManagerService001, + @"%@ Received context manager message with local time %@", kLogTag, + startTimeString); + return [self handleContextManagerLocalTimeMessage:message]; + } + + return NO; +} + ++ (BOOL)handleContextManagerLocalTimeMessage:(NSDictionary *)message { + NSString *startTimeString = message[kFIRMessagingContextManagerLocalTimeStart]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:kLocalTimeFormatString]; + NSDate *startDate = [dateFormatter dateFromString:startTimeString]; + + _FIRMessagingDevAssert(startDate, @"Invalid local start date format %@", startTimeString); + if (!startTimeString) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeContextManagerService002, + @"Invalid local start date format %@. Message dropped", + startTimeString); + return NO; + } + + NSDate *currentDate = [NSDate date]; + + if ([currentDate compare:startDate] == NSOrderedAscending) { + [self scheduleLocalNotificationForMessage:message + atDate:startDate]; + } else { + // check end time has not passed + NSString *endTimeString = message[kFIRMessagingContextManagerLocalTimeEnd]; + if (!endTimeString) { + FIRMessagingLoggerInfo( + kFIRMessagingMessageCodeContextManagerService003, + @"No end date specified for message, start date elapsed. Message dropped."); + return YES; + } + + NSDate *endDate = [dateFormatter dateFromString:endTimeString]; + + _FIRMessagingDevAssert(endDate, @"Invalid local end date format %@", endTimeString); + if (!endTimeString) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeContextManagerService004, + @"Invalid local end date format %@. Message dropped", endTimeString); + return NO; + } + + if ([endDate compare:currentDate] == NSOrderedAscending) { + // end date has already passed drop the message + FIRMessagingLoggerInfo(kFIRMessagingMessageCodeContextManagerService005, + @"End date %@ has already passed. Message dropped.", endTimeString); + return YES; + } + + // schedule message right now (buffer 10s) + [self scheduleLocalNotificationForMessage:message + atDate:[currentDate dateByAddingTimeInterval:10]]; + } + return YES; +} + ++ (void)scheduleLocalNotificationForMessage:(NSDictionary *)message + atDate:(NSDate *)date { + NSDictionary *apsDictionary = message; + UILocalNotification *notification = [[UILocalNotification alloc] init]; + + // A great way to understand timezones and UILocalNotifications + // http://stackoverflow.com/questions/18424569/understanding-uilocalnotification-timezone + notification.timeZone = [NSTimeZone defaultTimeZone]; + notification.fireDate = date; + + // In the current solution all of the display stuff goes into a special "aps" dictionary + // being sent in the message. + if ([apsDictionary[kFIRMessagingContextManagerBodyKey] length]) { + notification.alertBody = apsDictionary[kFIRMessagingContextManagerBodyKey]; + } + if ([apsDictionary[kFIRMessagingContextManagerTitleKey] length]) { + // |alertTitle| is iOS 8.2+, so check if we can set it + if ([notification respondsToSelector:@selector(setAlertTitle:)]) { + notification.alertTitle = apsDictionary[kFIRMessagingContextManagerTitleKey]; + } + } + + if (apsDictionary[kFIRMessagingContextManagerSoundKey]) { + notification.soundName = apsDictionary[kFIRMessagingContextManagerSoundKey]; + } + if (apsDictionary[kFIRMessagingContextManagerBadgeKey]) { + notification.applicationIconBadgeNumber = + [apsDictionary[kFIRMessagingContextManagerBadgeKey] integerValue]; + } + if (apsDictionary[kFIRMessagingContextManagerCategoryKey]) { + // |category| is iOS 8.0+, so check if we can set it + if ([notification respondsToSelector:@selector(setCategory:)]) { + notification.category = apsDictionary[kFIRMessagingContextManagerCategoryKey]; + } + } + + NSDictionary *userInfo = [self parseDataFromMessage:message]; + if (userInfo.count) { + notification.userInfo = userInfo; + } + + [[UIApplication sharedApplication] scheduleLocalNotification:notification]; +} + ++ (NSDictionary *)parseDataFromMessage:(NSDictionary *)message { + NSMutableDictionary *data = [NSMutableDictionary dictionary]; + for (NSObject<NSCopying> *key in message) { + if ([key isKindOfClass:[NSString class]]) { + NSString *keyString = (NSString *)key; + if ([keyString isEqualToString:kFIRMessagingContextManagerContentAvailableKey]) { + continue; + } else if ([keyString hasPrefix:kContextManagerPrefixKey]) { + continue; + } + } + data[[key copy]] = message[key]; + } + return [data copy]; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingDataMessageManager.h b/Firebase/Messaging/FIRMessagingDataMessageManager.h new file mode 100644 index 0000000..8eaecc1 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingDataMessageManager.h @@ -0,0 +1,101 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +@class GtalkDataMessageStanza; + +@class FIRMessagingClient; +@class FIRMessagingConnection; +@class FIRMessagingReceiver; +@class FIRMessagingRmqManager; +@class FIRMessagingSyncMessageManager; + +@protocol FIRMessagingDataMessageManagerDelegate <NSObject> + +#pragma mark - Downstream Callbacks + +/** + * Invoked when FIRMessaging receives a downstream message via the MCS connection. + * Let's the user know that they have received a new message by invoking the + * App's remoteNotification callback. + * + * @param message The downstream message received by the MCS connection. + */ +- (void)didReceiveMessage:(nonnull NSDictionary *)message + withIdentifier:(nullable NSString *)messageID; + +#pragma mark - Upstream Callbacks + +/** + * Notify the app that FIRMessaging will soon be sending the upstream message requested by the app. + * + * @param messageID The messageId passed in by the app to track this particular message. + * @param error The error in case FIRMessaging cannot send the message upstream. + */ +- (void)willSendDataMessageWithID:(nonnull NSString *)messageID error:(nullable NSError *)error; + +/** + * Notify the app that FIRMessaging did successfully send it's message via the MCS + * connection and the message was successfully delivered. + * + * @param messageId The messageId passed in by the app to track this particular + * message. + */ +- (void)didSendDataMessageWithID:(nonnull NSString *)messageId; + +#pragma mark - Server Callbacks + +/** + * Notify the app that FIRMessaging server deleted some messages which exceeded storage limits. This + * indicates the "deleted_messages" message type we received from the server. + */ +- (void)didDeleteMessagesOnServer; + +@end + +/** + * This manages all of the data messages being sent by the client and also the messages that + * were received from the server. + */ +@interface FIRMessagingDataMessageManager : NSObject + +NS_ASSUME_NONNULL_BEGIN + +- (instancetype)initWithDelegate:(id<FIRMessagingDataMessageManagerDelegate>)delegate + client:(FIRMessagingClient *)client + rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager + syncMessageManager:(FIRMessagingSyncMessageManager *)syncMessageManager; + +- (void)setDeviceAuthID:(NSString *)deviceAuthID secretToken:(NSString *)secretToken; + +- (void)refreshDelayedMessages; + +#pragma mark - Receive + +- (NSDictionary *)processPacket:(GtalkDataMessageStanza *)packet; +- (void)didReceiveParsedMessage:(NSDictionary *)message; + +#pragma mark - Send + +- (void)sendDataMessageStanza:(NSMutableDictionary *)dataMessage; +- (void)didSendDataMessageStanza:(GtalkDataMessageStanza *)message; + +- (void)resendMessagesWithConnection:(FIRMessagingConnection *)connection; + +NS_ASSUME_NONNULL_END + +@end diff --git a/Firebase/Messaging/FIRMessagingDataMessageManager.m b/Firebase/Messaging/FIRMessagingDataMessageManager.m new file mode 100644 index 0000000..2433bd4 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingDataMessageManager.m @@ -0,0 +1,545 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingDataMessageManager.h" + +#import "Protos/GtalkCore.pbobjc.h" + +#import "FIRMessagingClient.h" +#import "FIRMessagingConnection.h" +#import "FIRMessagingConstants.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingDelayedMessageQueue.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingReceiver.h" +#import "FIRMessagingRmqManager.h" +#import "FIRMessaging_Private.h" +#import "FIRMessagingSyncMessageManager.h" +#import "FIRMessagingUtilities.h" +#import "NSError+FIRMessaging.h" + +// The Notification used to send InstanceID messages that FIRMessaging receives. +static NSString *const NOTIFICATION_IID_MESSAGE = @"com.google.gcm/notification/iid"; + +static const int kMaxAppDataSizeDefault = 4 * 1024; // 4k +static const int kMinDelaySeconds = 1; // 1 second +static const int kMaxDelaySeconds = 60 * 60; // 1 hour + +static NSString *const kFromForInstanceIDMessages = @"google.com/iid"; +static NSString *const kFromForFIRMessagingMessages = @"mcs.android.com"; +static NSString *const kGSFMessageCategory = @"com.google.android.gsf.gtalkservice"; +// TODO: Update Gcm to FIRMessaging in the constants below +static NSString *const kFCMMessageCategory = @"com.google.gcm"; +static NSString *const kMessageReservedPrefix = @"google."; + +static NSString *const kFCMMessageSpecialMessage = @"message_type"; + +// special messages sent by the server +static NSString *const kFCMMessageTypeDeletedMessages = @"deleted_messages"; + +static NSString *const kMCSNotificationPrefix = @"gcm.notification."; +static NSString *const kDataMessageNotificationKey = @"notification"; + + +typedef NS_ENUM(int8_t, UpstreamForceReconnect) { + // Never force reconnect on upstream messages + kUpstreamForceReconnectOff = 0, + // Force reconnect for TTL=0 upstream messages + kUpstreamForceReconnectTTL0 = 1, + // Force reconnect for all upstream messages + kUpstreamForceReconnectAll = 2, +}; + +@interface FIRMessagingDataMessageManager () + +@property(nonatomic, readwrite, weak) FIRMessagingClient *client; +@property(nonatomic, readwrite, weak) FIRMessagingRmqManager *rmq2Manager; +@property(nonatomic, readwrite, weak) FIRMessagingSyncMessageManager *syncMessageManager; +@property(nonatomic, readwrite, weak) id<FIRMessagingDataMessageManagerDelegate> delegate; +@property(nonatomic, readwrite, strong) FIRMessagingDelayedMessageQueue *delayedMessagesQueue; + +@property(nonatomic, readwrite, assign) int ttl; +@property(nonatomic, readwrite, copy) NSString *deviceAuthID; +@property(nonatomic, readwrite, copy) NSString *secretToken; +@property(nonatomic, readwrite, assign) int maxAppDataSize; +@property(nonatomic, readwrite, assign) UpstreamForceReconnect upstreamForceReconnect; + +@end + +@implementation FIRMessagingDataMessageManager + +- (instancetype)initWithDelegate:(id<FIRMessagingDataMessageManagerDelegate>)delegate + client:(FIRMessagingClient *)client + rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager + syncMessageManager:(FIRMessagingSyncMessageManager *)syncMessageManager { + self = [super init]; + if (self) { + _delegate = delegate; + _client = client; + _rmq2Manager = rmq2Manager; + _syncMessageManager = syncMessageManager; + _ttl = kFIRMessagingSendTtlDefault; + _maxAppDataSize = kMaxAppDataSizeDefault; + // on by default + _upstreamForceReconnect = kUpstreamForceReconnectAll; + } + return self; +} + +- (void)setDeviceAuthID:(NSString *)deviceAuthID secretToken:(NSString *)secretToken { + _FIRMessagingDevAssert([deviceAuthID length] && [secretToken length], + @"Invalid credentials for FIRMessaging"); + self.deviceAuthID = deviceAuthID; + self.secretToken = secretToken; +} + +- (void)refreshDelayedMessages { + FIRMessaging_WEAKIFY(self); + self.delayedMessagesQueue = + [[FIRMessagingDelayedMessageQueue alloc] initWithRmqScanner:self.rmq2Manager + sendDelayedMessagesHandler:^(NSArray *messages) { + FIRMessaging_STRONGIFY(self); + [self sendDelayedMessages:messages]; + }]; +} + +- (NSDictionary *)processPacket:(GtalkDataMessageStanza *)dataMessage { + NSString *category = dataMessage.category; + NSString *from = dataMessage.from; + if ([kFCMMessageCategory isEqualToString:category] || + [kGSFMessageCategory isEqualToString:category]) { + [self handleMCSDataMessage:dataMessage]; + return nil; + } else if ([kFromForFIRMessagingMessages isEqualToString:from]) { + [self handleMCSDataMessage:dataMessage]; + return nil; + } else if ([kFromForInstanceIDMessages isEqualToString:from]) { + // send message to InstanceID library. + NSMutableDictionary *message = [NSMutableDictionary dictionary]; + for (GtalkAppData *item in dataMessage.appDataArray) { + _FIRMessagingDevAssert(item.key && item.value, @"Invalid app data item"); + if (item.key && item.value) { + message[item.key] = item.value; + } + } + + [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_IID_MESSAGE + object:message]; + return nil; + } + + return [self parseDataMessage:dataMessage]; +} + +- (void)handleMCSDataMessage:(GtalkDataMessageStanza *)dataMessage { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager000, + @"Received message for FIRMessaging from downstream %@", dataMessage); +} + +- (NSDictionary *)parseDataMessage:(GtalkDataMessageStanza *)dataMessage { + NSMutableDictionary *message = [NSMutableDictionary dictionary]; + NSString *from = [dataMessage from]; + if ([from length]) { + message[kFIRMessagingFromKey] = from; + } + + // raw data + NSData *rawData = [dataMessage rawData]; + if ([rawData length]) { + message[kFIRMessagingRawDataKey] = rawData; + } + + NSString *token = [dataMessage token]; + if ([token length]) { + message[kFIRMessagingCollapseKey] = token; + } + + // Add the persistent_id. This would be removed later before sending the message to the device. + NSString *persistentID = [dataMessage persistentId]; + _FIRMessagingDevAssert([persistentID length], @"Invalid MCS message without persistentID"); + if ([persistentID length]) { + message[kFIRMessagingMessageIDKey] = persistentID; + } + + // third-party data + for (GtalkAppData *item in dataMessage.appDataArray) { + _FIRMessagingDevAssert(item.hasKey && item.hasValue, @"Invalid AppData"); + + // do not process the "from" key -- is not useful + if ([kFIRMessagingFromKey isEqualToString:item.key]) { + continue; + } + + // Filter the "gcm.notification." keys in the message + if ([item.key hasPrefix:kMCSNotificationPrefix]) { + NSString *key = [item.key substringFromIndex:[kMCSNotificationPrefix length]]; + if ([key length]) { + if (!message[kDataMessageNotificationKey]) { + message[kDataMessageNotificationKey] = [NSMutableDictionary dictionary]; + } + message[kDataMessageNotificationKey][key] = item.value; + } else { + _FIRMessagingDevAssert([key length], @"Invalid key in MCS message: %@", key); + FIRMessagingLoggerError(kFIRMessagingMessageCodeDataMessageManager001, + @"Invalid key in MCS message: %@", key); + } + continue; + } + + // Filter the "gcm.duplex" key + if ([item.key isEqualToString:kFIRMessagingMessageSyncViaMCSKey]) { + BOOL value = [item.value boolValue]; + message[kFIRMessagingMessageSyncViaMCSKey] = @(value); + continue; + } + + // do not allow keys with "reserved" keyword + if ([[item.key lowercaseString] hasPrefix:kMessageReservedPrefix]) { + continue; + } + + [message setObject:item.value forKey:item.key]; + } + // TODO: Add support for encrypting raw data later + return [NSDictionary dictionaryWithDictionary:message]; +} + +- (void)didReceiveParsedMessage:(NSDictionary *)message { + if ([message[kFCMMessageSpecialMessage] length]) { + NSString *messageType = message[kFCMMessageSpecialMessage]; + if ([kFCMMessageTypeDeletedMessages isEqualToString:messageType]) { + // TODO: Maybe trim down message to remove some unnecessary fields. + // tell the FCM receiver of deleted messages + [self.delegate didDeleteMessagesOnServer]; + return; + } + FIRMessagingLoggerError(kFIRMessagingMessageCodeDataMessageManager002, + @"Invalid message type received: %@", messageType); + } else if (message[kFIRMessagingMessageSyncViaMCSKey]) { + // Update SYNC_RMQ with the message + BOOL isDuplicate = [self.syncMessageManager didReceiveMCSSyncMessage:message]; + if (isDuplicate) { + return; + } + } + NSString *messageId = message[kFIRMessagingMessageIDKey]; + NSDictionary *filteredMessage = [self filterInternalFIRMessagingKeysFromMessage:message]; + [self.delegate didReceiveMessage:filteredMessage withIdentifier:messageId]; +} + +- (NSDictionary *)filterInternalFIRMessagingKeysFromMessage:(NSDictionary *)message { + NSMutableDictionary *newMessage = [NSMutableDictionary dictionaryWithDictionary:message]; + for (NSString *key in message) { + if ([key hasPrefix:kFIRMessagingMessageInternalReservedKeyword]) { + [newMessage removeObjectForKey:key]; + } + } + return [newMessage copy]; +} + +- (void)sendDataMessageStanza:(NSMutableDictionary *)dataMessage { + NSNumber *ttlNumber = dataMessage[kFIRMessagingSendTTL]; + NSString *to = dataMessage[kFIRMessagingSendTo]; + NSString *msgId = dataMessage[kFIRMessagingSendMessageID]; + NSString *appPackage = [self categoryForUpstreamMessages]; + GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init]; + + // TODO: enforce TTL (right now only ttl=0 is special, means no storage) + int ttl = [ttlNumber intValue]; + if (ttl < 0 || ttl > self.ttl) { + ttl = self.ttl; + } + [stanza setTtl:ttl]; + [stanza setSent:FIRMessagingCurrentTimestampInSeconds()]; + + int delay = [self delayForMessage:dataMessage]; + if (delay > 0) { + [stanza setMaxDelay:delay]; + } + + if (msgId) { + [stanza setId_p:msgId]; + } + + // collapse key as given by the sender + NSString *token = dataMessage[KFIRMessagingSendMessageAppData][kFIRMessagingCollapseKey]; + if ([token length]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager003, + @"FIRMessaging using %@ as collapse key", token); + [stanza setToken:token]; + } + + if (!self.secretToken) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager004, + @"Trying to send data message without a secret token. " + @"Authentication failed."); + [self willSendDataMessageFail:stanza + withMessageId:msgId + error:kFIRMessagingErrorCodeMissingDeviceID]; + return; + } + + if (![to length]) { + [self willSendDataMessageFail:stanza withMessageId:msgId error:kFIRMessagingErrorMissingTo]; + return; + } + [stanza setTo:to]; + [stanza setCategory:appPackage]; + // required field in the proto this is set by the server + // set it to a sentinel so the runtime doesn't throw an exception + [stanza setFrom:@""]; + + // MCS itself would set the registration ID + // [stanza setRegId:nil]; + + int size = [self addData:dataMessage[KFIRMessagingSendMessageAppData] toStanza:stanza]; + if (size > kMaxAppDataSizeDefault) { + [self willSendDataMessageFail:stanza withMessageId:msgId error:kFIRMessagingErrorSizeExceeded]; + return; + } + + BOOL useRmq = (ttl != 0) && (msgId != nil); + if (useRmq) { + if (!self.client.isConnected) { + // do nothing assuming rmq save is enabled + } + + NSError *error; + if (![self.rmq2Manager saveRmqMessage:stanza error:&error]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager005, @"%@", error); + [self willSendDataMessageFail:stanza withMessageId:msgId error:kFIRMessagingErrorSave]; + return; + } + + [self willSendDataMessageSuccess:stanza withMessageId:msgId]; + } + + // if delay > 0 we don't really care about sending the message right now + // so we piggy-back on any other urgent(delay = 0) message that we are sending + if (delay > 0 && [self delayMessage:stanza]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager006, @"Delaying Message %@", + dataMessage); + return; + } + // send delayed messages + [self sendDelayedMessages:[self.delayedMessagesQueue removeDelayedMessages]]; + + BOOL sending = [self tryToSendDataMessageStanza:stanza]; + if (!sending) { + if (useRmq) { + NSString *event __unused = [NSString stringWithFormat:@"Queued message: %@", [stanza id_p]]; + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager007, @"%@", event); + } else { + [self willSendDataMessageFail:stanza + withMessageId:msgId + error:kFIRMessagingErrorCodeNetwork]; + return; + } + } +} + +- (void)sendDelayedMessages:(NSArray *)delayedMessages { + for (GtalkDataMessageStanza *message in delayedMessages) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager008, + @"%@ Sending delayed message %@", @"DMM", message); + [message setActualDelay:(int)(FIRMessagingCurrentTimestampInSeconds() - message.sent)]; + [self tryToSendDataMessageStanza:message]; + } +} + +- (void)didSendDataMessageStanza:(GtalkDataMessageStanza *)message { + NSString *msgId = [message id_p] ?: @""; + [self.delegate didSendDataMessageWithID:msgId]; +} + +- (void)addParamWithKey:(NSString *)key + value:(NSString *)val + toStanza:(GtalkDataMessageStanza *)stanza { + if (!key || !val) { + return; + } + GtalkAppData *appData = [[GtalkAppData alloc] init]; + [appData setKey:key]; + [appData setValue:val]; + [[stanza appDataArray] addObject:appData]; +} + +/** + @return The size of the data being added to stanza. + */ +- (int)addData:(NSDictionary *)data toStanza:(GtalkDataMessageStanza *)stanza { + int size = 0; + for (NSString *key in data) { + NSObject *val = data[key]; + if ([val isKindOfClass:[NSString class]]) { + NSString *strVal = (NSString *)val; + [self addParamWithKey:key value:strVal toStanza:stanza]; + size += [key length] + [strVal length]; + } else if ([val isKindOfClass:[NSNumber class]]) { + NSString *strVal = [(NSNumber *)val stringValue]; + [self addParamWithKey:key value:strVal toStanza:stanza]; + size += [key length] + [strVal length]; + } else if ([kFIRMessagingRawDataKey isEqualToString:key] && + [val isKindOfClass:[NSData class]]) { + NSData *rawData = (NSData *)val; + [stanza setRawData:[rawData copy]]; + size += [rawData length]; + } else { + FIRMessagingLoggerError(kFIRMessagingMessageCodeDataMessageManager009, @"Ignoring key: %@", + key); + } + } + return size; +} + +/** + * Notify the messenger that send data message completed with success. This is called for + * TTL=0, after the message has been sent, or when message is saved, to unlock the send() + * method. + */ +- (void)willSendDataMessageSuccess:(GtalkDataMessageStanza *)stanza + withMessageId:(NSString *)messageId { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager010, + @"send message success: %@", messageId); + [self.delegate willSendDataMessageWithID:messageId error:nil]; +} + +/** + * We send 'send failures' from server as normal FIRMessaging messages, with a 'message_type' + * extra - same as 'message deleted'. + * + * For TTL=0 or errors that can be detected during send ( too many messages, invalid, etc) + * we throw IOExceptions + */ +- (void)willSendDataMessageFail:(GtalkDataMessageStanza *)stanza + withMessageId:(NSString *)messageId + error:(FIRMessagingInternalErrorCode)errorCode { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager011, + @"Send message fail: %@ error: %lu", messageId, (unsigned long)errorCode); + + NSError *error = [NSError errorWithFCMErrorCode:errorCode]; + if ([self.delegate respondsToSelector:@selector(willSendDataMessageWithID:error:)]) { + [self.delegate willSendDataMessageWithID:messageId error:error]; + } +} + +- (void)resendMessagesWithConnection:(FIRMessagingConnection *)connection { + NSMutableString *rmqIdsResent = [NSMutableString string]; + NSMutableArray *toRemoveRmqIds = [NSMutableArray array]; + FIRMessaging_WEAKIFY(self); + FIRMessaging_WEAKIFY(connection); + FIRMessagingRmqMessageHandler messageHandler = ^(int64_t rmqId, int8_t tag, NSData *data) { + FIRMessaging_STRONGIFY(self); + FIRMessaging_STRONGIFY(connection); + GPBMessage *proto = + [FIRMessagingGetClassForTag((FIRMessagingProtoTag)tag) parseFromData:data error:NULL]; + if ([proto isKindOfClass:GtalkDataMessageStanza.class]) { + GtalkDataMessageStanza *stanza = (GtalkDataMessageStanza *)proto; + + if (![self handleExpirationForDataMessage:stanza]) { + // time expired let's delete from RMQ + [toRemoveRmqIds addObject:stanza.persistentId]; + return; + } + [rmqIdsResent appendString:[NSString stringWithFormat:@"%@,", stanza.id_p]]; + } + + [connection sendProto:proto]; + }; + [self.rmq2Manager scanWithRmqMessageHandler:messageHandler + dataMessageHandler:nil]; + + if ([rmqIdsResent length]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeDataMessageManager012, @"Resent: %@", + rmqIdsResent); + } + + if ([toRemoveRmqIds count]) { + [self.rmq2Manager removeRmqMessagesWithRmqIds:toRemoveRmqIds]; + } +} + +/** + * Check the TTL and generate an error if needed. + * + * @return false if the message needs to be deleted + */ +- (BOOL)handleExpirationForDataMessage:(GtalkDataMessageStanza *)message { + if (message.ttl == 0) { + return NO; + } + + int64_t now = FIRMessagingCurrentTimestampInSeconds(); + if (now > message.sent + message.ttl) { + [self willSendDataMessageFail:message + withMessageId:message.id_p + error:kFIRMessagingErrorServiceNotAvailable]; + return NO; + } + return YES; +} + +#pragma mark - Private + +- (int)delayForMessage:(NSMutableDictionary *)message { + int delay = 0; // default + if (message[kFIRMessagingSendDelay]) { + delay = [message[kFIRMessagingSendDelay] intValue]; + [message removeObjectForKey:kFIRMessagingSendDelay]; + if (delay < kMinDelaySeconds) { + delay = 0; + } else if (delay > kMaxDelaySeconds) { + delay = kMaxDelaySeconds; + } + } + return delay; +} + +// return True if successfully delayed else False +- (BOOL)delayMessage:(GtalkDataMessageStanza *)message { + return [self.delayedMessagesQueue queueMessage:message]; +} + +- (BOOL)tryToSendDataMessageStanza:(GtalkDataMessageStanza *)stanza { + if (self.client.isConnectionActive) { + [self.client sendMessage:stanza]; + return YES; + } + + // if we only reconnect for TTL = 0 messages check if we ttl = 0 or + // if we reconnect for all messages try to reconnect + if ((self.upstreamForceReconnect == kUpstreamForceReconnectTTL0 && stanza.ttl == 0) || + self.upstreamForceReconnect == kUpstreamForceReconnectAll) { + BOOL isNetworkAvailable = [[FIRMessaging messaging] isNetworkAvailable]; + if (isNetworkAvailable) { + if (stanza.ttl == 0) { + // Add TTL = 0 messages to be sent on next connect. TTL != 0 messages are + // persisted, and will be sent from the RMQ. + [self.client sendOnConnectOrDrop:stanza]; + } + + [self.client retryConnectionImmediately:YES]; + return YES; + } + } + return NO; +} + +- (NSString *)categoryForUpstreamMessages { + return FIRMessagingAppIdentifier(); +} + +@end diff --git a/Firebase/Messaging/FIRMessagingDefines.h b/Firebase/Messaging/FIRMessagingDefines.h new file mode 100644 index 0000000..36448ed --- /dev/null +++ b/Firebase/Messaging/FIRMessagingDefines.h @@ -0,0 +1,96 @@ +/* + * Copyright 2017 Google + * + * 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. + */ + +#ifndef FIRMessaging_xcodeproj_FIRMessagingDefines_h +#define FIRMessaging_xcodeproj_FIRMessagingDefines_h + +#define _FIRMessaging_VERBOSE_LOGGING 1 + +// Verbose Logging +#if (_FIRMessaging_VERBOSE_LOGGING) +#define FIRMessaging_DEV_VERBOSE_LOG(...) NSLog(__VA_ARGS__) +#else +#define FIRMessaging_DEV_VERBOSE_LOG(...) do { } while (0) +#endif // FIRMessaging_VERBOSE_LOGGING + + +// FIRMessaging_FAIL +#ifdef DEBUG +#define FIRMessaging_FAIL(format, ...) \ +do { \ + NSLog(format, ##__VA_ARGS__); \ + __builtin_trap(); \ +} while (false) +#else +#define FIRMessaging_FAIL(...) do { } while (0) +#endif + + +// WEAKIFY & STRONGIFY +// Helper macro. +#define _FIRMessaging_WEAKNAME(VAR) VAR ## _weak_ + +#define FIRMessaging_WEAKIFY(VAR) __weak __typeof__(VAR) _FIRMessaging_WEAKNAME(VAR) = (VAR); + +#define FIRMessaging_STRONGIFY(VAR) \ +_Pragma("clang diagnostic push") \ +_Pragma("clang diagnostic ignored \"-Wshadow\"") \ +__strong __typeof__(VAR) VAR = _FIRMessaging_WEAKNAME(VAR); \ +_Pragma("clang diagnostic pop") + + +// Type Conversions (used for NSInteger etc) +#ifndef _FIRMessaging_L +#define _FIRMessaging_L(v) (long)(v) +#endif + +#ifndef _FIRMessaging_UL +#define _FIRMessaging_UL(v) (unsigned long)(v) +#endif + +#endif + +// Debug Assert +#ifndef _FIRMessagingDevAssert +// we directly invoke the NSAssert handler so we can pass on the varargs +// (NSAssert doesn't have a macro we can use that takes varargs) +#if !defined(NS_BLOCK_ASSERTIONS) +#define _FIRMessagingDevAssert(condition, ...) \ + do { \ + if (!(condition)) { \ + [[NSAssertionHandler currentHandler] \ + handleFailureInFunction:(NSString *) \ + [NSString stringWithUTF8String:__PRETTY_FUNCTION__] \ + file:(NSString *)[NSString stringWithUTF8String:__FILE__] \ + lineNumber:__LINE__ \ + description:__VA_ARGS__]; \ + } \ + } while(0) +#else // !defined(NS_BLOCK_ASSERTIONS) +#define _FIRMessagingDevAssert(condition, ...) do { } while (0) +#endif // !defined(NS_BLOCK_ASSERTIONS) + +#endif // _FIRMessagingDevAssert + +// Invalidates the initializer from which it's called. +#ifndef FIRMessagingInvalidateInitializer +#define FIRMessagingInvalidateInitializer() \ + do { \ + [self class]; /* Avoid warning of dead store to |self|. */ \ + _FIRMessagingDevAssert(NO, @"Invalid initializer."); \ + return nil; \ + } while (0) +#endif diff --git a/Firebase/Messaging/FIRMessagingDelayedMessageQueue.h b/Firebase/Messaging/FIRMessagingDelayedMessageQueue.h new file mode 100644 index 0000000..d20ec91 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingDelayedMessageQueue.h @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +@class GtalkDataMessageStanza; +@class FIRMessagingRmqManager; + +@protocol FIRMessagingRmqScanner; + +typedef void(^FIRMessagingSendDelayedMessagesHandler)(NSArray *messages); + +@interface FIRMessagingDelayedMessageQueue : NSObject + +- (instancetype)initWithRmqScanner:(id<FIRMessagingRmqScanner>)rmqScanner + sendDelayedMessagesHandler:(FIRMessagingSendDelayedMessagesHandler)sendDelayedMessagesHandler; + +- (BOOL)queueMessage:(GtalkDataMessageStanza *)message; + +- (NSArray *)removeDelayedMessages; + +@end diff --git a/Firebase/Messaging/FIRMessagingDelayedMessageQueue.m b/Firebase/Messaging/FIRMessagingDelayedMessageQueue.m new file mode 100644 index 0000000..0371c02 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingDelayedMessageQueue.m @@ -0,0 +1,146 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingDelayedMessageQueue.h" + +#import "Protos/GtalkCore.pbobjc.h" + +#import "FIRMessagingDefines.h" +#import "FIRMessagingRmqManager.h" +#import "FIRMessagingUtilities.h" + +static const int kMaxQueuedMessageCount = 10; + +@interface FIRMessagingDelayedMessageQueue () + +@property(nonatomic, readonly, weak) id<FIRMessagingRmqScanner> rmqScanner; +@property(nonatomic, readonly, copy) FIRMessagingSendDelayedMessagesHandler sendDelayedMessagesHandler; + +@property(nonatomic, readwrite, assign) int persistedMessageCount; +// the scheduled timeout or -1 if not set +@property(nonatomic, readwrite, assign) int64_t scheduledTimeoutMilliseconds; +// The time of the last scan of the message DB, +// used to avoid retrieving messages more than once. +@property(nonatomic, readwrite, assign) int64_t lastDBScanTimestampSeconds; + +@property(nonatomic, readwrite, strong) NSMutableArray *messages; +@property(nonatomic, readwrite, strong) NSTimer *sendTimer; + +@end + +@implementation FIRMessagingDelayedMessageQueue + +- (instancetype)init { + FIRMessagingInvalidateInitializer(); +} + +- (instancetype)initWithRmqScanner:(id<FIRMessagingRmqScanner>)rmqScanner + sendDelayedMessagesHandler:(FIRMessagingSendDelayedMessagesHandler)sendDelayedMessagesHandler { + _FIRMessagingDevAssert(sendDelayedMessagesHandler, @"Invalid nil callback for delayed messages"); + self = [super init]; + if (self) { + _rmqScanner = rmqScanner; + _sendDelayedMessagesHandler = sendDelayedMessagesHandler; + _messages = [NSMutableArray arrayWithCapacity:10]; + _scheduledTimeoutMilliseconds = -1; + } + return self; +} + +- (BOOL)queueMessage:(GtalkDataMessageStanza *)message { + if (self.messages.count >= kMaxQueuedMessageCount) { + return NO; + } + if (message.ttl == 0) { + // ttl=0 messages aren't persisted, add it to memory + [self.messages addObject:message]; + } else { + self.persistedMessageCount++; + } + int64_t timeoutMillis = [self calculateTimeoutInMillisWithDelayInSeconds:message.maxDelay]; + if (![self isTimeoutScheduled] || timeoutMillis < self.scheduledTimeoutMilliseconds) { + [self scheduleTimeoutInMillis:timeoutMillis]; + } + return YES; +} + +- (NSArray *)removeDelayedMessages { + [self cancelTimeout]; + if ([self messageCount] == 0) { + return @[]; + } + + NSMutableArray *delayedMessages = [NSMutableArray array]; + // add the ttl=0 messages + if (self.messages.count) { + [delayedMessages addObjectsFromArray:delayedMessages]; + [self.messages removeAllObjects]; + } + + // add persistent messages + if (self.persistedMessageCount > 0) { + FIRMessaging_WEAKIFY(self); + [self.rmqScanner scanWithRmqMessageHandler:nil + dataMessageHandler:^(int64_t rmqId, GtalkDataMessageStanza *stanza) { + FIRMessaging_STRONGIFY(self); + if ([stanza hasMaxDelay] && + [stanza sent] >= self.lastDBScanTimestampSeconds) { + [delayedMessages addObject:stanza]; + } + }]; + self.lastDBScanTimestampSeconds = FIRMessagingCurrentTimestampInSeconds(); + self.persistedMessageCount = 0; + } + return delayedMessages; +} + +- (void)sendMessages { + if (self.sendDelayedMessagesHandler) { + self.sendDelayedMessagesHandler([self removeDelayedMessages]); + } +} + +#pragma mark - Private + +- (NSInteger)messageCount { + return self.messages.count + self.persistedMessageCount; +} + +- (BOOL)isTimeoutScheduled { + return self.scheduledTimeoutMilliseconds > 0; +} + +- (int64_t)calculateTimeoutInMillisWithDelayInSeconds:(int)delay { + return FIRMessagingCurrentTimestampInMilliseconds() + delay * 1000.0; +} + +- (void)scheduleTimeoutInMillis:(int64_t)time { + [self cancelTimeout]; + self.scheduledTimeoutMilliseconds = time; + double delay = (time - FIRMessagingCurrentTimestampInMilliseconds()) / 1000.0; + [self performSelector:@selector(sendMessages) withObject:self afterDelay:delay]; +} + +- (void)cancelTimeout { + if ([self isTimeoutScheduled]) { + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(sendMessages) + object:nil]; + self.scheduledTimeoutMilliseconds = -1; + } +} + +@end diff --git a/Firebase/Messaging/FIRMessagingFileLogger.h b/Firebase/Messaging/FIRMessagingFileLogger.h new file mode 100644 index 0000000..ec11369 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingFileLogger.h @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingLogger.h" + +#if FIRMessaging_PROBER +@interface FIRMessagingFileLogFilter : NSObject <FIRMessagingLogFilter> + +@end + +@interface FIRMessagingFileLogFormatter : NSObject <FIRMessagingLogFormatter> + +@end + +@interface FIRMessagingFileLogWriter : NSObject <FIRMessagingLogWriter> + +@end +#endif diff --git a/Firebase/Messaging/FIRMessagingFileLogger.m b/Firebase/Messaging/FIRMessagingFileLogger.m new file mode 100644 index 0000000..7570a79 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingFileLogger.m @@ -0,0 +1,108 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingFileLogger.h" + +#if FIRMessaging_PROBER + +#import "DDFileLogger.h" +#import "DDLog.h" + +@interface FIRMessagingFileLogFilter () + +@property(nonatomic, readwrite, assign) FIRMessagingLogLevel level; +@end + +@implementation FIRMessagingFileLogFilter + +#pragma mark - GTMLogFilter protocol + +- (BOOL)filterAllowsMessage:(NSString *)msg level:(FIRMessagingLogLevel)level { + // allow everything + return YES; +} + +@end + +@interface FIRMessagingFileLogFormatter () + +@property(nonatomic, readwrite, strong) NSDateFormatter *dateFormatter; + +@end + +@implementation FIRMessagingFileLogFormatter + +static NSString *const kFIRMessagingLogPrefix = @"FIRMessaging"; + +- (id)init { + if ((self = [super init])) { + _dateFormatter = [[NSDateFormatter alloc] init]; + [_dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [_dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"]; + } + return self; +} + +#pragma mark - GTMLogFormatter protocol + +static DDLogMessage *currentMessage; +- (NSString *)stringForFunc:(NSString *)func + withFormat:(NSString *)fmt + valist:(va_list)args + level:(FIRMessagingLogLevel)level { + NSString *logMessage = [[NSString alloc] initWithFormat:fmt arguments:args]; + currentMessage = [[DDLogMessage alloc] initWithMessage:logMessage + level:0 + flag:0 + context:0 + file:NULL + function:NULL + line:0 + tag:0 + options:0 + timestamp:[NSDate date]]; + return logMessage; +} + +@end + +@interface FIRMessagingFileLogWriter () + +@property(nonatomic, readwrite, strong) DDFileLogger *fileLogger; + +@end + +@implementation FIRMessagingFileLogWriter + +- (instancetype)init { + self = [super init]; + if (self) { + _fileLogger = [[DDFileLogger alloc] init]; + } + return self; +} + +#pragma mark - GTMLogWriter protocol + +- (void)logMessage:(NSString *)msg level:(FIRMessagingLogLevel)level { + // log to stdout + NSLog(@"%@", msg); + [self.fileLogger logMessage:currentMessage]; +} + +@end + +#endif diff --git a/Firebase/Messaging/FIRMessagingInstanceIDProxy.h b/Firebase/Messaging/FIRMessagingInstanceIDProxy.h new file mode 100644 index 0000000..b7ebd4b --- /dev/null +++ b/Firebase/Messaging/FIRMessagingInstanceIDProxy.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +typedef void(^FIRMessagingInstanceIDProxyTokenHandler)(NSString * __nullable token, + NSError * __nullable error); + +typedef void(^FIRMessagingInstanceIDProxyDeleteTokenHandler)(NSError * __nullable error); + +typedef NS_ENUM(NSInteger, FIRMessagingInstanceIDProxyAPNSTokenType) { + /// Unknown token type. + FIRMessagingInstanceIDProxyAPNSTokenTypeUnknown, + /// Sandbox token type. + FIRMessagingInstanceIDProxyAPNSTokenTypeSandbox, + /// Production token type. + FIRMessagingInstanceIDProxyAPNSTokenTypeProd, +}; + +/** + * FIRMessaging cannot always depend on FIRInstanceID directly, due to how FIRMessaging is + * packaged. To make it easier to make calls to FIRInstanceID, this proxy class, will provide + * method names duplicated from FIRInstanceID, while using reflection-based called to proxy + * the requests. + */ +@interface FIRMessagingInstanceIDProxy : NSObject + +- (void)setAPNSToken:(nonnull NSData *)token type:(FIRMessagingInstanceIDProxyAPNSTokenType)type; + +#pragma mark - Tokens + +- (nullable NSString *)token; + +- (void)tokenWithAuthorizedEntity:(nonnull NSString *)authorizedEntity + scope:(nonnull NSString *)scope + options:(nullable NSDictionary *)options + handler:(nonnull FIRMessagingInstanceIDProxyTokenHandler)handler; + +- (void)deleteTokenWithAuthorizedEntity:(nonnull NSString *)authorizedEntity + scope:(nonnull NSString *)scope + handler: + (nonnull FIRMessagingInstanceIDProxyDeleteTokenHandler)handler; +@end diff --git a/Firebase/Messaging/FIRMessagingInstanceIDProxy.m b/Firebase/Messaging/FIRMessagingInstanceIDProxy.m new file mode 100644 index 0000000..01b4e73 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingInstanceIDProxy.m @@ -0,0 +1,123 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingInstanceIDProxy.h" + +@implementation FIRMessagingInstanceIDProxy + ++ (nonnull instancetype)instanceIDProxy { + static id proxyInstanceID = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class instanceIDClass = NSClassFromString(@"FIRInstanceID"); + if (!instanceIDClass) { + proxyInstanceID = nil; + return; + } + SEL instanceIDSelector = NSSelectorFromString(@"instanceID"); + if (![instanceIDClass respondsToSelector:instanceIDSelector]) { + proxyInstanceID = nil; + return; + } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + proxyInstanceID = [instanceIDClass performSelector:instanceIDSelector]; +#pragma clang diagnostic pop + }); + return (FIRMessagingInstanceIDProxy *)proxyInstanceID; + +} + +- (void)setAPNSToken:(nonnull NSData *)token + type:(FIRMessagingInstanceIDProxyAPNSTokenType)type { + id proxy = [[self class] instanceIDProxy]; + + SEL setAPNSTokenSelector = NSSelectorFromString(@"setAPNSToken:type:"); + if (![proxy respondsToSelector:setAPNSTokenSelector]) { + return; + } + // Since setAPNSToken takes a scalar value, use NSInvocation + NSMethodSignature *methodSignature = + [[proxy class] instanceMethodSignatureForSelector:setAPNSTokenSelector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + invocation.selector = setAPNSTokenSelector; + invocation.target = proxy; + [invocation setArgument:&token atIndex:2]; + [invocation setArgument:&type atIndex:3]; + [invocation invoke]; +} + +#pragma mark - Tokens + +- (nullable NSString *)token { + id proxy = [[self class] instanceIDProxy]; + SEL getTokenSelector = NSSelectorFromString(@"token"); + if (![proxy respondsToSelector:getTokenSelector]) { + return nil; + } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + return [proxy performSelector:getTokenSelector]; +#pragma clang diagnostic pop +} + + +- (void)tokenWithAuthorizedEntity:(nonnull NSString *)authorizedEntity + scope:(nonnull NSString *)scope + options:(nullable NSDictionary *)options + handler:(nonnull FIRMessagingInstanceIDProxyTokenHandler)handler { + + id proxy = [[self class] instanceIDProxy]; + SEL getTokenSelector = NSSelectorFromString(@"tokenWithAuthorizedEntity:scope:options:handler:"); + if (![proxy respondsToSelector:getTokenSelector]) { + return; + } + // Since there are >2 arguments, use NSInvocation + NSMethodSignature *methodSignature = + [[proxy class] instanceMethodSignatureForSelector:getTokenSelector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + invocation.selector = getTokenSelector; + invocation.target = proxy; + [invocation setArgument:&authorizedEntity atIndex:2]; + [invocation setArgument:&scope atIndex:3]; + [invocation setArgument:&options atIndex:4]; + [invocation setArgument:&handler atIndex:5]; + [invocation invoke]; +} + +- (void)deleteTokenWithAuthorizedEntity:(nonnull NSString *)authorizedEntity + scope:(nonnull NSString *)scope + handler: + (nonnull FIRMessagingInstanceIDProxyDeleteTokenHandler)handler { + + id proxy = [[self class] instanceIDProxy]; + SEL deleteTokenSelector = NSSelectorFromString(@"deleteTokenWithAuthorizedEntity:scope:handler:"); + if (![proxy respondsToSelector:deleteTokenSelector]) { + return; + } + // Since there are >2 arguments, use NSInvocation + NSMethodSignature *methodSignature = + [[proxy class] instanceMethodSignatureForSelector:deleteTokenSelector]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; + invocation.selector = deleteTokenSelector; + invocation.target = proxy; + [invocation setArgument:&authorizedEntity atIndex:2]; + [invocation setArgument:&scope atIndex:3]; + [invocation setArgument:&handler atIndex:4]; + [invocation invoke]; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingLogger.h b/Firebase/Messaging/FIRMessagingLogger.h new file mode 100644 index 0000000..cd3c29a --- /dev/null +++ b/Firebase/Messaging/FIRMessagingLogger.h @@ -0,0 +1,97 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingConfig.h" +#import "FIRMMessageCode.h" + +// The convenience macros are only defined if they haven't already been defined. +#ifndef FIRMessagingLoggerInfo + +// Convenience macros that log to the shared FIRMessagingLogger instance. These macros +// are how users should typically log to FIRMessagingLogger. +#define FIRMessagingLoggerDebug(code, ...) \ + [FIRMessagingSharedLogger() logFuncDebug:__func__ messageCode:code msg:__VA_ARGS__] +#define FIRMessagingLoggerInfo(code, ...) \ + [FIRMessagingSharedLogger() logFuncInfo:__func__ messageCode:code msg:__VA_ARGS__] +#define FIRMessagingLoggerNotice(code, ...) \ + [FIRMessagingSharedLogger() logFuncNotice:__func__ messageCode:code msg:__VA_ARGS__] +#define FIRMessagingLoggerWarn(code, ...) \ + [FIRMessagingSharedLogger() logFuncWarning:__func__ messageCode:code msg:__VA_ARGS__] +#define FIRMessagingLoggerError(code, ...) \ + [FIRMessagingSharedLogger() logFuncError:__func__ messageCode:code msg:__VA_ARGS__] + +#endif // !defined(FIRMessagingLoggerInfo) + +/// Protocols +@protocol FIRMessagingLogFormatter <NSObject> +- (NSString *)stringForFunc:(NSString *)func + withFormat:(NSString *)fmt + valist:(va_list)args + level:(FIRMessagingLogLevel)level NS_FORMAT_FUNCTION(2, 0); +@end + +/// FIRMessagingLogWriter +@protocol FIRMessagingLogWriter <NSObject> +// Writes the given log message to where the log writer is configured to write. +- (void)logMessage:(NSString *)msg level:(FIRMessagingLogLevel)level; +@end + +/// FIRMessagingLogFilter +@protocol FIRMessagingLogFilter <NSObject> +// Returns YES if |msg| at |level| should be logged; NO otherwise. +- (BOOL)filterAllowsMessage:(NSString *)msg level:(FIRMessagingLogLevel)level; +@end + +@interface FIRMessagingLogLevelFilter : NSObject <FIRMessagingLogFilter> +- (instancetype)initWithLevel:(FIRMessagingLogLevel)level; +@end + + +@interface FIRMessagingLogger : NSObject + +@property(nonatomic, readwrite, strong) id<FIRMessagingLogFilter> filter; +@property(nonatomic, readwrite, strong) id<FIRMessagingLogWriter> writer; +@property(nonatomic, readwrite, strong) id<FIRMessagingLogFormatter> formatter; + +- (void)logFuncDebug:(const char *)func + messageCode:(FIRMessagingMessageCode)messageCode + msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4); + +- (void)logFuncInfo:(const char *)func + messageCode:(FIRMessagingMessageCode)messageCode + msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4); + +- (void)logFuncNotice:(const char *)func + messageCode:(FIRMessagingMessageCode)messageCode + msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4); + +- (void)logFuncWarning:(const char *)func + messageCode:(FIRMessagingMessageCode)messageCode + msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4); + +- (void)logFuncError:(const char *)func + messageCode:(FIRMessagingMessageCode)messageCode + msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4); + +@end + +/** + * Instantiates and/or returns a shared FIRMessagingLogger used exclusively + * for FIRMessaging log messages. + * + * @return the shared FIRMessagingLogger instance + */ +FIRMessagingLogger *FIRMessagingSharedLogger(); diff --git a/Firebase/Messaging/FIRMessagingLogger.m b/Firebase/Messaging/FIRMessagingLogger.m new file mode 100644 index 0000000..0ded97c --- /dev/null +++ b/Firebase/Messaging/FIRMessagingLogger.m @@ -0,0 +1,305 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingLogger.h" + +#import "FIRLogger.h" +#import "FIRMessagingFileLogger.h" + +/** + * A log formatter that prefixes log messages with "FIRMessaging". + */ +@interface FIRMessagingLogStandardFormatter : NSObject<FIRMessagingLogFormatter> + +@property(nonatomic, readwrite, strong) NSDateFormatter *dateFormatter; + +@end + +@implementation FIRMessagingLogStandardFormatter + +static NSString *const kFIRMessagingLogPrefix = @"FIRMessaging"; + +- (id)init { + if ((self = [super init])) { + _dateFormatter = [[NSDateFormatter alloc] init]; + [_dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [_dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"]; + } + return self; +} +/** + * Returns a formatted string prefixed with "FIRMessaging" to allow + * FIRMessaging output to be easily differentiated in logs. + * + * @param func the name of the function calling the logger + * @param fmt the format string + * @param args the list of arguments for the format string + * @param level the logging level (eg. debug, info) + * @return the formatted string prefixed with "FIRMessaging". + */ +- (NSString *)stringForFunc:(NSString *)func + withFormat:(NSString *)fmt + valist:(va_list)args + level:(FIRMessagingLogLevel)level NS_FORMAT_FUNCTION(2, 0) { + if (!(fmt && args)) { + return nil; + } + + NSString *logMessage = [[NSString alloc] initWithFormat:fmt arguments:args]; + NSString *logLevelString = [self stringForLogLevel:level]; + NSString *dateString = [self.dateFormatter stringFromDate:[NSDate date]]; + return [NSString stringWithFormat:@"%@: <%@/%@> %@", + dateString, kFIRMessagingLogPrefix, logLevelString, logMessage]; +} + +- (NSString *)stringForLogLevel:(FIRMessagingLogLevel)level { + switch (level) { + case kFIRMessagingLogLevelDebug: + return @"DEBUG"; + + case kFIRMessagingLogLevelInfo: + return @"INFO"; + + case kFIRMessagingLogLevelError: + return @"WARNING"; + + case kFIRMessagingLogLevelAssert: + return @"ERROR"; + + default: + return @"INFO"; + } +} + +@end + +@interface FIRMessagingLogLevelFilter () + +@property(nonatomic, readwrite, assign) FIRMessagingLogLevel level; + +@end + +@implementation FIRMessagingLogLevelFilter + +- (instancetype)initWithLevel:(FIRMessagingLogLevel)level { + self = [super init]; + if (self) { + _level = level; + } + return self; +} + +- (BOOL)filterAllowsMessage:(NSString *)msg level:(FIRMessagingLogLevel)level { +#if defined(DEBUG) && DEBUG + return YES; +#endif + + BOOL allow = YES; + + switch (level) { + case kFIRMessagingLogLevelDebug: + allow = NO; + break; + case kFIRMessagingLogLevelInfo: + case kFIRMessagingLogLevelError: + case kFIRMessagingLogLevelAssert: + allow = (level >= self.level); + break; + default: + allow = NO; + break; + } + + return allow; +} + +@end + + +// Copied from FIRMessagingLogger. Standard implementation to write logs to console. +@interface NSFileHandle (FIRMessagingFileHandleLogWriter) <FIRMessagingLogWriter> +@end + +@implementation NSFileHandle (FIRMessagingFileHandleLogWriter) +- (void)logMessage:(NSString *)msg level:(FIRMessagingLogLevel)level { + @synchronized(self) { + // Closed pipes should not generate exceptions in our caller. Catch here + // as well [FIRMessagingLogger logInternalFunc:...] so that an exception in this + // writer does not prevent other writers from having a chance. + @try { + NSString *line = [NSString stringWithFormat:@"%@\n", msg]; + [self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]]; + } + @catch (id e) { + // Ignored + } + } +} +@end + +@interface FIRMessagingLogger () + +@end + +@implementation FIRMessagingLogger + ++ (instancetype)standardLogger { + + id<FIRMessagingLogWriter> writer; + id<FIRMessagingLogFormatter> formatter; + id<FIRMessagingLogFilter> filter; + +#if FIRMessaging_PROBER + writer = [[FIRMessagingFileLogWriter alloc] init]; + formatter = [[FIRMessagingFileLogFormatter alloc] init]; + filter = [[FIRMessagingFileLogFilter alloc] init]; +#else + writer = [NSFileHandle fileHandleWithStandardOutput]; + formatter = [[FIRMessagingLogStandardFormatter alloc] init]; + filter = [[FIRMessagingLogLevelFilter alloc] init]; +#endif + + return [[FIRMessagingLogger alloc] initWithFilter:filter formatter:formatter writer:writer]; +} + +- (instancetype)initWithFilter:(id<FIRMessagingLogFilter>)filter + formatter:(id<FIRMessagingLogFormatter>)formatter + writer:(id<FIRMessagingLogWriter>)writer { + self = [super init]; + if (self) { + _filter = filter; + _formatter = formatter; + _writer = writer; + } + return self; +} + +#pragma mark - Log Helpers + ++ (NSString *)formatMessageCode:(FIRMessagingMessageCode)messageCode { + return [NSString stringWithFormat:@"I-FCM%06ld", (long)messageCode]; +} + +- (void)logFuncDebug:(const char *)func + messageCode:(FIRMessagingMessageCode)messageCode + msg:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + FIRLogBasic(FIRLoggerLevelDebug, kFIRLoggerMessaging, + [FIRMessagingLogger formatMessageCode:messageCode], fmt, args); + va_end(args); +#if FIRMessaging_PROBER + va_start(args, fmt); + [self logInternalFunc:func format:fmt valist:args level:kFIRMessagingLogLevelDebug]; + va_end(args); +#endif +} + +- (void)logFuncInfo:(const char *)func + messageCode:(FIRMessagingMessageCode)messageCode + msg:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + FIRLogBasic(FIRLoggerLevelInfo, kFIRLoggerMessaging, + [FIRMessagingLogger formatMessageCode:messageCode], fmt, args); + va_end(args); +#if FIRMessaging_PROBER + va_start(args, fmt); + [self logInternalFunc:func format:fmt valist:args level:kFIRMessagingLogLevelInfo]; + va_end(args); +#endif +} + +- (void)logFuncNotice:(const char *)func + messageCode:(FIRMessagingMessageCode)messageCode + msg:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + FIRLogBasic(FIRLoggerLevelNotice, kFIRLoggerMessaging, + [FIRMessagingLogger formatMessageCode:messageCode], fmt, args); + va_end(args); +#if FIRMessaging_PROBER + va_start(args, fmt); + // Treat FIRLoggerLevelNotice as "info" locally, since we don't have an equivalent + [self logInternalFunc:func format:fmt valist:args level:kFIRMessagingLogLevelInfo]; + va_end(args); +#endif +} + +- (void)logFuncWarning:(const char *)func + messageCode:(FIRMessagingMessageCode)messageCode + msg:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + FIRLogBasic(FIRLoggerLevelWarning, kFIRLoggerMessaging, + [FIRMessagingLogger formatMessageCode:messageCode], fmt, args); + va_end(args); +#if FIRMessaging_PROBER + va_start(args, fmt); + // Treat FIRLoggerLevelWarning as "error" locally, since we don't have an equivalent + [self logInternalFunc:func format:fmt valist:args level:kFIRMessagingLogLevelError]; + va_end(args); +#endif +} + +- (void)logFuncError:(const char *)func + messageCode:(FIRMessagingMessageCode)messageCode + msg:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + FIRLogBasic(FIRLoggerLevelError, kFIRLoggerMessaging, + [FIRMessagingLogger formatMessageCode:messageCode], fmt, args); + va_end(args); +#if FIRMessaging_PROBER + va_start(args, fmt); + [self logInternalFunc:func format:fmt valist:args level:kFIRMessagingLogLevelError]; + va_end(args); +#endif +} + +#pragma mark - Internal Helpers + +- (void)logInternalFunc:(const char *)func + format:(NSString *)fmt + valist:(va_list)args + level:(FIRMessagingLogLevel)level { + // Primary point where logging happens, logging should never throw, catch + // everything. + @try { + NSString *fname = func ? [NSString stringWithUTF8String:func] : nil; + NSString *msg = [self.formatter stringForFunc:fname + withFormat:fmt + valist:args + level:level]; + if (msg && [self.filter filterAllowsMessage:msg level:level]) + [self.writer logMessage:msg level:level]; + } + @catch (id e) { + // Ignored + } +} + +@end + +FIRMessagingLogger *FIRMessagingSharedLogger() { + static dispatch_once_t onceToken; + static FIRMessagingLogger *logger; + dispatch_once(&onceToken, ^{ + logger = [FIRMessagingLogger standardLogger]; + }); + + return logger; +} diff --git a/Firebase/Messaging/FIRMessagingPacketQueue.h b/Firebase/Messaging/FIRMessagingPacketQueue.h new file mode 100644 index 0000000..1f528ab --- /dev/null +++ b/Firebase/Messaging/FIRMessagingPacketQueue.h @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +@interface FIRMessagingPacket : NSObject + ++ (FIRMessagingPacket *)packetWithTag:(int8_t)tag rmqId:(NSString *)rmqId data:(NSData *)data; + +@property(nonatomic, readonly, strong) NSData *data; +@property(nonatomic, readonly, assign) int8_t tag; +// not sent over the wire required for bookkeeping +@property(nonatomic, readonly, assign) NSString *rmqId; + +@end + + +/** + * A queue of the packets(protos) that need to be send over the wire. + */ +@interface FIRMessagingPacketQueue : NSObject + +@property(nonatomic, readonly, assign) NSUInteger count; +@property(nonatomic, readonly, assign) BOOL isEmpty; + +- (void)push:(FIRMessagingPacket *)packet; +- (void)pushHead:(FIRMessagingPacket *)packet; +- (FIRMessagingPacket *)pop; + +@end diff --git a/Firebase/Messaging/FIRMessagingPacketQueue.m b/Firebase/Messaging/FIRMessagingPacketQueue.m new file mode 100644 index 0000000..2b3410a --- /dev/null +++ b/Firebase/Messaging/FIRMessagingPacketQueue.m @@ -0,0 +1,103 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingPacketQueue.h" + +#import "FIRMessagingDefines.h" + +@interface FIRMessagingPacket () + +@property(nonatomic, readwrite, strong) NSData *data; +@property(nonatomic, readwrite, assign) int8_t tag; +@property(nonatomic, readwrite, assign) NSString *rmqId; + +@end + +@implementation FIRMessagingPacket + ++ (FIRMessagingPacket *)packetWithTag:(int8_t)tag rmqId:(NSString *)rmqId data:(NSData *)data { + return [[self alloc] initWithTag:tag rmqId:rmqId data:data]; +} + +- (instancetype)init { + FIRMessagingInvalidateInitializer(); +} + +- (instancetype)initWithTag:(int8_t)tag rmqId:(NSString *)rmqId data:(NSData *)data { + self = [super init]; + if (self != nil) { + _data = data; + _tag = tag; + _rmqId = rmqId; + } + return self; +} + +- (NSString *)description { + if ([self.rmqId length]) { + return [NSString stringWithFormat:@"<Packet: Tag - %d, Length - %lu>, RmqId - %@", + self.tag, _FIRMessaging_UL(self.data.length), self.rmqId]; + } else { + return [NSString stringWithFormat:@"<Packet: Tag - %d, Length - %lu>", + self.tag, _FIRMessaging_UL(self.data.length)]; + } +} + +@end + +@interface FIRMessagingPacketQueue () + +@property(nonatomic, readwrite, strong) NSMutableArray *packetsContainer; + +@end + + +@implementation FIRMessagingPacketQueue; + +- (id)init { + self = [super init]; + if (self) { + _packetsContainer = [[NSMutableArray alloc] init]; + } + return self; +} + +- (BOOL)isEmpty { + return self.packetsContainer.count == 0; +} + +- (NSUInteger)count { + return self.packetsContainer.count; +} + +- (void)push:(FIRMessagingPacket *)packet { + [self.packetsContainer addObject:packet]; +} + +- (void)pushHead:(FIRMessagingPacket *)packet { + [self.packetsContainer insertObject:packet atIndex:0]; +} + +- (FIRMessagingPacket *)pop { + if (!self.isEmpty) { + FIRMessagingPacket *packet = self.packetsContainer[0]; + [self.packetsContainer removeObjectAtIndex:0]; + return packet; + } + return nil; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingPendingTopicsList.h b/Firebase/Messaging/FIRMessagingPendingTopicsList.h new file mode 100644 index 0000000..c5a306a --- /dev/null +++ b/Firebase/Messaging/FIRMessagingPendingTopicsList.h @@ -0,0 +1,118 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +#import "FIRMessagingTopicsCommon.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Represents a single batch of topics, with the same action. + * + * Topic operations which have the same action (subscribe or unsubscribe) can be executed + * simultaneously, as the order of operations do not matter with the same action. The set of + * topics is unique, as it doesn't make sense to apply the same action to the same topic + * repeatedly; the result would be the same as the first time. + */ +@interface FIRMessagingTopicBatch : NSObject <NSCoding> + +@property(nonatomic, readonly, assign) FIRMessagingTopicAction action; +@property(nonatomic, readonly, copy) NSMutableSet <NSString *> *topics; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithAction:(FIRMessagingTopicAction)action NS_DESIGNATED_INITIALIZER; + +@end + +@class FIRMessagingPendingTopicsList; +/** + * This delegate must be supplied to the instance of FIRMessagingPendingTopicsList, via the + * @cdelegate property. It lets the + * pending topics list know whether or not it can begin making requests via + * @c-pendingTopicsListCanRequestTopicUpdates:, and handles the request to actually + * perform the topic operation. The delegate also handles when the pending topics list is updated, + * so that it can be archived or persisted. + * + * @see FIRMessagingPendingTopicsList + */ +@protocol FIRMessagingPendingTopicsListDelegate <NSObject> + +- (void)pendingTopicsList:(FIRMessagingPendingTopicsList *)list + requestedUpdateForTopic:(NSString *)topic + action:(FIRMessagingTopicAction)action + completion:(FIRMessagingTopicOperationCompletion)completion; +- (void)pendingTopicsListDidUpdate:(FIRMessagingPendingTopicsList *)list; +- (BOOL)pendingTopicsListCanRequestTopicUpdates:(FIRMessagingPendingTopicsList *)list; + +@end + +/** + * FIRMessagingPendingTopicsList manages a list of topic subscription updates, batched by the same + * action (subscribe or unsubscribe). The list roughly maintains the order of the topic operations, + * batched together whenever the topic action (subscribe or unsubscribe) changes. + * + * Topics operations are batched by action because it is safe to perform the same topic action + * (subscribe or unsubscribe) on many topics simultaneously. After each batch is successfully + * completed, the next batch operations can begin. + * + * When asked to resume its operations, FIRMessagingPendingTopicsList will begin performing updates + * of its current batch of topics. For example, it may begin subscription operations for topics + * [A, B, C] simultaneously. + * + * When the current batch is completed, the next batch of operations will be started. For example + * the list may begin unsubscribe operations for [D, A, E]. Note that because A is in both batches, + * A will be correctly subscribed in the first batch, then unsubscribed as part of the second batch + * of operations. Without batching, it would be ambiguous whether A's subscription operation or the + * unsubscription operation would be completed first. + * + * An app can subscribe and unsubscribe from many topics, and this class helps persist the pending + * topics and perform the operation safely and correctly. + * + * When a topic fails to subscribe or unsubscribe due to a network error, it is considered a + * recoverable error, and so it remains in the current batch until it is succesfully completed. + * Topic updates are completed when they either (a) succeed, (b) are cancelled, or (c) result in an + * unrecoverable error. Any error outside of `NSURLErrorDomain` is considered an unrecoverable + * error. + * + * In addition to maintaining the list of pending topic updates, FIRMessagingPendingTopicsList also + * can track completion handlers for topic operations. + * + * @discussion Completion handlers for topic updates are not maintained if it was restored from a + * keyed archive. They are only called if the topic operation finished within the same app session. + * + * You must supply an object conforming to FIRMessagingPendingTopicsListDelegate in order for the + * topic operations to execute. + * + * @see FIRMessagingPendingTopicsListDelegate + */ +@interface FIRMessagingPendingTopicsList : NSObject <NSCoding> + +@property(nonatomic, weak) NSObject <FIRMessagingPendingTopicsListDelegate> *delegate; + +@property(nonatomic, readonly, strong, nullable) NSDate *archiveDate; +@property(nonatomic, readonly) NSUInteger numberOfBatches; + + +- (instancetype)init NS_DESIGNATED_INITIALIZER; +- (void)addOperationForTopic:(NSString *)topic + withAction:(FIRMessagingTopicAction)action + completion:(nullable FIRMessagingTopicOperationCompletion)completion; +- (void)resumeOperationsIfNeeded; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Messaging/FIRMessagingPendingTopicsList.m b/Firebase/Messaging/FIRMessagingPendingTopicsList.m new file mode 100644 index 0000000..792090e --- /dev/null +++ b/Firebase/Messaging/FIRMessagingPendingTopicsList.m @@ -0,0 +1,261 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingPendingTopicsList.h" + +#import "FIRMessaging_Private.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingPubSub.h" + +#import "FIRMessagingDefines.h" + +NSString *const kPendingTopicBatchActionKey = @"action"; +NSString *const kPendingTopicBatchTopicsKey = @"topics"; + +NSString *const kPendingBatchesEncodingKey = @"batches"; +NSString *const kPendingTopicsTimestampEncodingKey = @"ts"; + +#pragma mark - FIRMessagingTopicBatch + +@interface FIRMessagingTopicBatch () + +@property(nonatomic, strong, nonnull) NSMutableDictionary + <NSString *, NSMutableArray <FIRMessagingTopicOperationCompletion> *> *topicHandlers; + +@end + +@implementation FIRMessagingTopicBatch + +- (instancetype)initWithAction:(FIRMessagingTopicAction)action { + if (self = [super init]) { + _action = action; + _topics = [NSMutableSet set]; + _topicHandlers = [NSMutableDictionary dictionary]; + } + return self; +} + +#pragma mark NSCoding + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeInteger:self.action forKey:kPendingTopicBatchActionKey]; + [aCoder encodeObject:self.topics forKey:kPendingTopicBatchTopicsKey]; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + + // Ensure that our integer -> enum casting is safe + NSInteger actionRawValue = [aDecoder decodeIntegerForKey:kPendingTopicBatchActionKey]; + FIRMessagingTopicAction action = FIRMessagingTopicActionSubscribe; + if (actionRawValue == FIRMessagingTopicActionUnsubscribe) { + action = FIRMessagingTopicActionUnsubscribe; + } + + if (self = [self initWithAction:action]) { + NSSet *topics = [aDecoder decodeObjectForKey:kPendingTopicBatchTopicsKey]; + if ([topics isKindOfClass:[NSSet class]]) { + _topics = [topics mutableCopy]; + } + _topicHandlers = [NSMutableDictionary dictionary]; + } + return self; +} + +@end + +#pragma mark - FIRMessagingPendingTopicsList + +@interface FIRMessagingPendingTopicsList () + +@property(nonatomic, readwrite, strong) NSDate *archiveDate; +@property(nonatomic, strong) NSMutableArray <FIRMessagingTopicBatch *> *topicBatches; + +@property(nonatomic, strong) FIRMessagingTopicBatch *currentBatch; +@property(nonatomic, strong) NSMutableSet <NSString *> *topicsInFlight; + +@end + +@implementation FIRMessagingPendingTopicsList + +- (instancetype)init { + if (self = [super init]) { + _topicBatches = [NSMutableArray array]; + _topicsInFlight = [NSMutableSet set]; + } + return self; +} + ++ (void)pruneTopicBatches:(NSMutableArray <FIRMessagingTopicBatch *> *)topicBatches { + // For now, just remove empty batches. In the future we can use this to make the subscriptions + // more efficient, by actually pruning topic actions that cancel each other out, for example. + for (NSInteger i = topicBatches.count-1; i >= 0; i--) { + FIRMessagingTopicBatch *batch = topicBatches[i]; + if (batch.topics.count == 0) { + [topicBatches removeObjectAtIndex:i]; + } + } +} + +#pragma mark NSCoding + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:[NSDate date] forKey:kPendingTopicsTimestampEncodingKey]; + [aCoder encodeObject:self.topicBatches forKey:kPendingBatchesEncodingKey]; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { + + if (self = [self init]) { + _archiveDate = [aDecoder decodeObjectForKey:kPendingTopicsTimestampEncodingKey]; + NSArray *archivedBatches = [aDecoder decodeObjectForKey:kPendingBatchesEncodingKey]; + if (archivedBatches) { + _topicBatches = [archivedBatches mutableCopy]; + [FIRMessagingPendingTopicsList pruneTopicBatches:_topicBatches]; + } + _topicsInFlight = [NSMutableSet set]; + } + return self; +} + +#pragma mark Getters + +- (NSUInteger)numberOfBatches { + return self.topicBatches.count; +} + +#pragma mark Adding/Removing topics + +- (void)addOperationForTopic:(NSString *)topic + withAction:(FIRMessagingTopicAction)action + completion:(nullable FIRMessagingTopicOperationCompletion)completion { + + FIRMessagingTopicBatch *lastBatch = nil; + @synchronized (self) { + lastBatch = self.topicBatches.lastObject; + if (!lastBatch || lastBatch.action != action) { + // There either was no last batch, or our last batch's action was not the same, so we have to + // create a new batch + lastBatch = [[FIRMessagingTopicBatch alloc] initWithAction:action]; + [self.topicBatches addObject:lastBatch]; + } + BOOL topicExistedBefore = ([lastBatch.topics member:topic] != nil); + if (!topicExistedBefore) { + [lastBatch.topics addObject:topic]; + [self.delegate pendingTopicsListDidUpdate:self]; + } + // Add the completion handler to the batch + if (completion) { + NSMutableArray *handlers = lastBatch.topicHandlers[topic]; + if (!handlers) { + handlers = [NSMutableArray arrayWithCapacity:1]; + } + [handlers addObject:completion]; + } + if (!self.currentBatch) { + self.currentBatch = lastBatch; + } + // This may have been the first topic added, or was added to an ongoing batch + if (self.currentBatch == lastBatch && !topicExistedBefore) { + // Add this topic to our ongoing operations + FIRMessaging_WEAKIFY(self); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + FIRMessaging_STRONGIFY(self); + [self resumeOperationsIfNeeded]; + }); + } + } +} + +- (void)resumeOperationsIfNeeded { + @synchronized (self) { + // If current batch is not set, set it now + if (!self.currentBatch) { + self.currentBatch = self.topicBatches.firstObject; + } + if (self.currentBatch.topics.count == 0) { + return; + } + if (!self.delegate) { + FIRMessagingLoggerError(kFIRMessagingMessageCodePendingTopicsList000, + @"Attempted to update pending topics without a delegate"); + return; + } + if (![self.delegate pendingTopicsListCanRequestTopicUpdates:self]) { + return; + } + for (NSString *topic in self.currentBatch.topics) { + if ([self.topicsInFlight member:topic]) { + // This topic is already active, so skip + continue; + } + [self beginUpdateForCurrentBatchTopic:topic]; + } + } +} + +- (BOOL)subscriptionErrorIsRecoverable:(NSError *)error { + return [error.domain isEqualToString:NSURLErrorDomain]; +} + +- (void)beginUpdateForCurrentBatchTopic:(NSString *)topic { + + @synchronized (self) { + [self.topicsInFlight addObject:topic]; + } + FIRMessaging_WEAKIFY(self); + [self.delegate pendingTopicsList:self + requestedUpdateForTopic:topic + action:self.currentBatch.action + completion:^(FIRMessagingTopicOperationResult result, NSError * error) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + FIRMessaging_STRONGIFY(self); + @synchronized (self) { + [self.topicsInFlight removeObject:topic]; + + BOOL recoverableError = [self subscriptionErrorIsRecoverable:error]; + if (result == FIRMessagingTopicOperationResultSucceeded || + result == FIRMessagingTopicOperationResultCancelled || + !recoverableError) { + // Notify our handlers and remove the topic from our batch + NSMutableArray *handlers = self.currentBatch.topicHandlers[topic]; + if (handlers.count) { + dispatch_async(dispatch_get_main_queue(), ^{ + for (FIRMessagingTopicOperationCompletion handler in handlers) { + handler(result, error); + } + [handlers removeAllObjects]; + }); + } + [self.currentBatch.topics removeObject:topic]; + [self.currentBatch.topicHandlers removeObjectForKey:topic]; + if (self.currentBatch.topics.count == 0) { + // All topic updates successfully finished in this batch, move on to the next batch + [self.topicBatches removeObject:self.currentBatch]; + self.currentBatch = nil; + } + [self.delegate pendingTopicsListDidUpdate:self]; + FIRMessaging_WEAKIFY(self) + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + FIRMessaging_STRONGIFY(self) + [self resumeOperationsIfNeeded]; + }); + } + } + }); + }]; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingPersistentSyncMessage.h b/Firebase/Messaging/FIRMessagingPersistentSyncMessage.h new file mode 100644 index 0000000..5a48e99 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingPersistentSyncMessage.h @@ -0,0 +1,28 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +@interface FIRMessagingPersistentSyncMessage : NSObject + +@property(nonatomic, readonly, strong) NSString *rmqID; +@property(nonatomic, readwrite, assign) BOOL apnsReceived; +@property(nonatomic, readwrite, assign) BOOL mcsReceived; +@property(nonatomic, readonly, assign) int64_t expirationTime; + +- (instancetype)initWithRMQID:(NSString *)rmqID expirationTime:(int64_t)expirationTime; + +@end diff --git a/Firebase/Messaging/FIRMessagingPersistentSyncMessage.m b/Firebase/Messaging/FIRMessagingPersistentSyncMessage.m new file mode 100644 index 0000000..bf7d05b --- /dev/null +++ b/Firebase/Messaging/FIRMessagingPersistentSyncMessage.m @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingPersistentSyncMessage.h" + +#import "FIRMessagingDefines.h" + +@interface FIRMessagingPersistentSyncMessage () + +@property(nonatomic, readwrite, strong) NSString *rmqID; +@property(nonatomic, readwrite, assign) int64_t expirationTime; + +@end + +@implementation FIRMessagingPersistentSyncMessage + +- (instancetype)init { + FIRMessagingInvalidateInitializer(); +} + +- (instancetype)initWithRMQID:(NSString *)rmqID expirationTime:(int64_t)expirationTime { + self = [super init]; + if (self) { + _rmqID = [rmqID copy]; + _expirationTime = expirationTime; + } + return self; +} + +- (NSString *)description { + NSString *classDescription = NSStringFromClass([self class]); + NSDate *date = [NSDate dateWithTimeIntervalSince1970:self.expirationTime]; + return [NSString stringWithFormat:@"%@: (rmqID: %@, apns: %d, mcs: %d, expiry: %@", + classDescription, self.rmqID, self.mcsReceived, self.apnsReceived, date]; +} + +- (NSString *)debugDescription { + return [self description]; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingPubSub.h b/Firebase/Messaging/FIRMessagingPubSub.h new file mode 100644 index 0000000..3a03494 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingPubSub.h @@ -0,0 +1,148 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingTopicsCommon.h" + +@class FIRMessagingClient; +@class FIRMessagingPubSubCache; + +/** + * FIRMessagingPubSub provides a publish-subscribe model for sending FIRMessaging topic messages. + * + * An app can subscribe to different topics defined by the + * developer. The app server can then send messages to the subscribed devices + * without having to maintain topic-subscribers mapping. Topics do not + * need to be explicitly created before subscribing or publishing—they + * are automatically created when publishing or subscribing. + * + * Messages published to the topic will be received as regular FIRMessaging messages + * with `"from"` set to `"/topics/myTopic"`. + * + * Only topic names that match the pattern `"/topics/[a-zA-Z0-9-_.~%]{1,900}"` + * are allowed for subscribing and publishing. + */ +@interface FIRMessagingPubSub : NSObject + +@property(nonatomic, readonly, strong) FIRMessagingPubSubCache *cache; +@property(nonatomic, readonly, strong) FIRMessagingClient *client; + +/** + * Initializes an instance of FIRMessagingPubSub. + * + * @return An instance of FIRMessagingPubSub. + */ +- (instancetype)initWithClient:(FIRMessagingClient *)client NS_DESIGNATED_INITIALIZER; + +/** + * Subscribes an app instance to a topic, enabling it to receive messages + * sent to that topic. + * + * This is an asynchronous call. If subscription fails, FIRMessaging + * invokes the completion callback with the appropriate error. + * + * @see FIRMessagingPubSub unsubscribeWithToken:topic:handler: + * + * @param token The registration token as received from the InstanceID + * library for a given `authorizedEntity` and "gcm" scope. + * @param topic The topic to subscribe to. Should be of the form + * `"/topics/<topic-name>"`. + * @param handler The callback handler invoked when the subscribe call + * ends. In case of success, a nil error is returned. Otherwise, + * an appropriate error object is returned. + * @discussion This method is thread-safe. However, it is not guaranteed to + * return on the main thread. + */ +- (void)subscribeWithToken:(NSString *)token + topic:(NSString *)topic + options:(NSDictionary *)options + handler:(FIRMessagingTopicOperationCompletion)handler; + + +/** + * Unsubscribes an app instance from a topic, stopping it from receiving + * any further messages sent to that topic. + * + * This is an asynchronous call. If the attempt to unsubscribe fails, + * we invoke the `completion` callback passed in with an appropriate error. + * + * @param token The token used to subscribe to this topic. + * @param topic The topic to unsubscribe from. Should be of the form + * `"/topics/<topic-name>"`. + * @param handler The handler that is invoked once the unsubscribe call ends. + * In case of success, nil error is returned. Otherwise, an + * appropriate error object is returned. + * @discussion This method is thread-safe. However, it is not guaranteed to + * return on the main thread. + */ +- (void)unsubscribeWithToken:(NSString *)token + topic:(NSString *)topic + options:(NSDictionary *)options + handler:(FIRMessagingTopicOperationCompletion)handler; + +/** + * Asynchronously subscribe to the topic. Adds to the pending list of topic operations. + * Retry in case of failures. This makes a repeated attempt to subscribe to the topic + * as compared to the `subscribe` method above which tries once. + * + * @param topic The topic name to subscribe to. Should be of the form `"/topics/<topic-name>"`. + */ +- (void)subscribeToTopic:(NSString *)topic; + +/** + * Asynchronously unsubscribe from the topic. Adds to the pending list of topic operations. + * Retry in case of failures. This makes a repeated attempt to unsubscribe from the topic + * as compared to the `unsubscribe` method above which tries once. + * + * @param topic The topic name to unsubscribe from. Should be of the form `"/topics/<topic-name>"`. + */ +- (void)unsubscribeFromTopic:(NSString *)topic; + +/** + * Schedule subscriptions sync. + * + * @param immediately YES if the sync should be scheduled immediately else NO if we can delay + * the sync. + */ +- (void)scheduleSync:(BOOL)immediately; + +/** + * Adds the "/topics/" prefix to the topic. + * + * @param topic The topic to add the prefix to. + * + * @return The new topic name with the "/topics/" prefix added. + */ ++ (NSString *)addPrefixToTopic:(NSString *)topic; + +/** + * Check if the topic name has "/topics/" prefix. + * + * @param topic The topic name to verify. + * + * @return YES if the topic name has "/topics/" prefix else NO. + */ ++ (BOOL)hasTopicsPrefix:(NSString *)topic; + +/** + * Check if it's a valid topic name. This includes "/topics/" prefix in the topic name. + * + * @param topic The topic name to verify. + * + * @return YES if the topic name satisfies the regex "/topics/[a-zA-Z0-9-_.~%]{1,900}". + */ ++ (BOOL)isValidTopicWithPrefix:(NSString *)topic; + +@end diff --git a/Firebase/Messaging/FIRMessagingPubSub.m b/Firebase/Messaging/FIRMessagingPubSub.m new file mode 100644 index 0000000..c8293e0 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingPubSub.m @@ -0,0 +1,278 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingPubSub.h" + +#import "FIRMessaging.h" +#import "FIRMessagingClient.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingPendingTopicsList.h" +#import "FIRMessagingUtilities.h" +#import "FIRMessaging_Private.h" +#import "NSDictionary+FIRMessaging.h" +#import "NSError+FIRMessaging.h" + +static NSString *const kPendingSubscriptionsListKey = + @"com.firebase.messaging.pending-subscriptions"; + +@interface FIRMessagingPubSub () <FIRMessagingPendingTopicsListDelegate> + +@property(nonatomic, readwrite, strong) FIRMessagingPendingTopicsList *pendingTopicUpdates; +@property(nonatomic, readwrite, strong) FIRMessagingClient *client; + +@end + +@implementation FIRMessagingPubSub + +- (instancetype)init { + FIRMessagingInvalidateInitializer(); + // Need this to disable an Xcode warning. + return [self initWithClient:nil]; +} + +- (instancetype)initWithClient:(FIRMessagingClient *)client { + self = [super init]; + if (self) { + _client = client; + [self restorePendingTopicsList]; + } + return self; +} + +- (void)subscribeWithToken:(NSString *)token + topic:(NSString *)topic + options:(NSDictionary *)options + handler:(FIRMessagingTopicOperationCompletion)handler { + _FIRMessagingDevAssert([token length], @"FIRMessaging error no token specified"); + _FIRMessagingDevAssert([topic length], @"FIRMessaging error Invalid empty topic specified"); + if (!self.client) { + handler(FIRMessagingTopicOperationResultError, + [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubFIRMessagingNotSetup]); + return; + } + + token = [token copy]; + topic = [topic copy]; + + if (![options count]) { + options = @{}; + } + + if (![[self class] isValidTopicWithPrefix:topic]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub000, + @"Invalid FIRMessaging Pubsub topic %@", topic); + handler(FIRMessagingTopicOperationResultError, + [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubInvalidTopic]); + return; + } + + if (![self verifyPubSubOptions:options]) { + // we do not want to quit even if options have some invalid values. + FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub001, + @"Invalid options passed to FIRMessagingPubSub with non-string keys or " + "values."); + } + // copy the dictionary would trim non-string keys or values if any. + options = [options fcm_trimNonStringValues]; + + [self.client updateSubscriptionWithToken:token + topic:topic + options:options + shouldDelete:NO + handler: + ^void(FIRMessagingTopicOperationResult result, NSError * error) { + + handler(result, error); + }]; +} + +- (void)unsubscribeWithToken:(NSString *)token + topic:(NSString *)topic + options:(NSDictionary *)options + handler:(FIRMessagingTopicOperationCompletion)handler { + _FIRMessagingDevAssert([token length], @"FIRMessaging error no token specified"); + _FIRMessagingDevAssert([topic length], @"FIRMessaging error Invalid empty topic specified"); + + if (!self.client) { + handler(FIRMessagingTopicOperationResultError, + [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubFIRMessagingNotSetup]); + return; + } + + token = [token copy]; + topic = [topic copy]; + if (![options count]) { + options = @{}; + } + + if (![[self class] isValidTopicWithPrefix:topic]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub002, + @"Invalid FIRMessaging Pubsub topic %@", topic); + handler(FIRMessagingTopicOperationResultError, + [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodePubSubInvalidTopic]); + return; + } + if (![self verifyPubSubOptions:options]) { + // we do not want to quit even if options have some invalid values. + FIRMessagingLoggerError( + kFIRMessagingMessageCodePubSub003, + @"Invalid options passed to FIRMessagingPubSub with non-string keys or values."); + } + // copy the dictionary would trim non-string keys or values if any. + options = [options fcm_trimNonStringValues]; + + [self.client updateSubscriptionWithToken:token + topic:topic + options:options + shouldDelete:YES + handler: + ^void(FIRMessagingTopicOperationResult result, NSError * error) { + + handler(result, error); + }]; +} + +- (void)subscribeToTopic:(NSString *)topic { + [self.pendingTopicUpdates addOperationForTopic:topic + withAction:FIRMessagingTopicActionSubscribe + completion:nil]; +} + +- (void)unsubscribeFromTopic:(NSString *)topic { + [self.pendingTopicUpdates addOperationForTopic:topic + withAction:FIRMessagingTopicActionUnsubscribe + completion:nil]; +} + +- (void)scheduleSync:(BOOL)immediately { + NSString *fcmToken = [[FIRMessaging messaging] defaultFcmToken]; + if (fcmToken.length) { + [self.pendingTopicUpdates resumeOperationsIfNeeded]; + } +} + +#pragma mark - FIRMessagingPendingTopicsListDelegate + +- (void)pendingTopicsList:(FIRMessagingPendingTopicsList *)list + requestedUpdateForTopic:(NSString *)topic + action:(FIRMessagingTopicAction)action + completion:(FIRMessagingTopicOperationCompletion)completion { + + NSString *fcmToken = [[FIRMessaging messaging] defaultFcmToken]; + if (action == FIRMessagingTopicActionSubscribe) { + [self subscribeWithToken:fcmToken topic:topic options:nil handler:completion]; + } else { + [self unsubscribeWithToken:fcmToken topic:topic options:nil handler:completion]; + } +} + +- (void)pendingTopicsListDidUpdate:(FIRMessagingPendingTopicsList *)list { + [self archivePendingTopicsList:list]; +} + +- (BOOL)pendingTopicsListCanRequestTopicUpdates:(FIRMessagingPendingTopicsList *)list { + NSString *fcmToken = [[FIRMessaging messaging] defaultFcmToken]; + return (fcmToken.length > 0); +} + +#pragma mark - Storing Pending Topics + +- (void)archivePendingTopicsList:(FIRMessagingPendingTopicsList *)topicsList { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSData *pendingData = [NSKeyedArchiver archivedDataWithRootObject:topicsList]; + [defaults setObject:pendingData forKey:kPendingSubscriptionsListKey]; + [defaults synchronize]; +} + +- (void)restorePendingTopicsList { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSData *pendingData = [defaults objectForKey:kPendingSubscriptionsListKey]; + FIRMessagingPendingTopicsList *subscriptions; + @try { + if (pendingData) { + subscriptions = [NSKeyedUnarchiver unarchiveObjectWithData:pendingData]; + } + } @catch (NSException *exception) { + // Nothing we can do, just continue as if we don't have pending subscriptions + } @finally { + if (subscriptions) { + self.pendingTopicUpdates = subscriptions; + } else { + self.pendingTopicUpdates = [[FIRMessagingPendingTopicsList alloc] init]; + } + self.pendingTopicUpdates.delegate = self; + } +} + +#pragma mark - Private Helpers + +- (BOOL)verifyPubSubOptions:(NSDictionary *)options { + return ![options fcm_hasNonStringKeysOrValues]; +} + +#pragma mark - Topic Name Helpers + +static NSString *const kTopicsPrefix = @"/topics/"; +static NSString *const kTopicRegexPattern = @"/topics/([a-zA-Z0-9-_.~%]+)"; + ++ (NSString *)addPrefixToTopic:(NSString *)topic { + if (![self hasTopicsPrefix:topic]) { + return [NSString stringWithFormat:@"%@%@", kTopicsPrefix, topic]; + } else { + return [topic copy]; + } +} + ++ (BOOL)hasTopicsPrefix:(NSString *)topic { + return [topic hasPrefix:kTopicsPrefix]; +} + +/** + * Returns a regular expression for matching a topic sender. + * + * @return The topic matching regular expression + */ ++ (NSRegularExpression *)topicRegex { + // Since this is a static regex pattern, we only only need to declare it once. + static NSRegularExpression *topicRegex; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSError *error; + topicRegex = + [NSRegularExpression regularExpressionWithPattern:kTopicRegexPattern + options:NSRegularExpressionAnchorsMatchLines + error:&error]; + }); + return topicRegex; +} + +/** + * Gets the class describing occurences of topic names and sender IDs in the sender. + * + * @param expression The topic expression used to generate a pubsub topic + * + * @return Representation of captured subexpressions in topic regular expression + */ ++ (BOOL)isValidTopicWithPrefix:(NSString *)topic { + NSRange topicRange = NSMakeRange(0, topic.length); + NSRange regexMatchRange = [[self topicRegex] rangeOfFirstMatchInString:topic + options:NSMatchingAnchored + range:topicRange]; + return NSEqualRanges(topicRange, regexMatchRange); +} + +@end diff --git a/Firebase/Messaging/FIRMessagingPubSubRegistrar.h b/Firebase/Messaging/FIRMessagingPubSubRegistrar.h new file mode 100644 index 0000000..b51813f --- /dev/null +++ b/Firebase/Messaging/FIRMessagingPubSubRegistrar.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingTopicOperation.h" + +@class FIRMessagingCheckinService; + +@interface FIRMessagingPubSubRegistrar : NSObject + +/** + * Designated Initializer. + * + * @param checkinService The checkin service used to register with Checkin + * server. + * + * @return A new FIRMessagingPubSubRegistrar instance used to subscribe/unsubscribe. + */ +- (instancetype)initWithCheckinService:(FIRMessagingCheckinService *)checkinService; + +/** + * Stops all the subscription requests going on in parallel. This would + * invalidate all the handlers associated with the subscription requests. + */ +- (void)stopAllSubscriptionRequests; + +/** + * Update subscription status for a given topic with FIRMessaging's backend. + * + * @param topic The topic to subscribe to. + * @param token The registration token to be used. + * @param options The options to be passed in during subscription request. + * @param shouldDelete NO if the subscription is being added else YES if being + * removed. + * @param handler The handler invoked once the update subscription request + * finishes. + */ +- (void)updateSubscriptionToTopic:(NSString *)topic + withToken:(NSString *)token + options:(NSDictionary *)options + shouldDelete:(BOOL)shouldDelete + handler:(FIRMessagingTopicOperationCompletion)handler; + +@end diff --git a/Firebase/Messaging/FIRMessagingPubSubRegistrar.m b/Firebase/Messaging/FIRMessagingPubSubRegistrar.m new file mode 100644 index 0000000..6268302 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingPubSubRegistrar.m @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingPubSubRegistrar.h" + +#import "FIRMessagingCheckinService.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingPubSubRegistrar.h" +#import "FIRMessagingTopicsCommon.h" +#import "NSError+FIRMessaging.h" + +@interface FIRMessagingPubSubRegistrar () + +@property(nonatomic, readwrite, strong) FIRMessagingCheckinService *checkinService; + +@property(nonatomic, readonly, strong) NSOperationQueue *topicOperations; +// Common errors, instantiated, to avoid generating multiple copies +@property(nonatomic, readwrite, strong) NSError *operationInProgressError; + +@end + +@implementation FIRMessagingPubSubRegistrar + +- (instancetype)init { + FIRMessagingInvalidateInitializer(); +} + +- (instancetype)initWithCheckinService:(FIRMessagingCheckinService *)checkinService { + self = [super init]; + if (self) { + _checkinService = checkinService; + _topicOperations = [[NSOperationQueue alloc] init]; + // Do 10 topic operations at a time; it's enough to keep the TCP connection to the host alive, + // saving hundreds of milliseconds on each request (compared to a serial queue). + _topicOperations.maxConcurrentOperationCount = 10; + } + return self; +} + +- (void)stopAllSubscriptionRequests { + [self.topicOperations cancelAllOperations]; +} + +- (void)updateSubscriptionToTopic:(NSString *)topic + withToken:(NSString *)token + options:(NSDictionary *)options + shouldDelete:(BOOL)shouldDelete + handler:(FIRMessagingTopicOperationCompletion)handler { + + FIRMessagingTopicAction action = FIRMessagingTopicActionSubscribe; + if (shouldDelete) { + action = FIRMessagingTopicActionUnsubscribe; + } + FIRMessagingTopicOperation *operation = + [[FIRMessagingTopicOperation alloc] initWithTopic:topic + action:action + token:token + options:options + checkinService:self.checkinService + completion:handler]; + [self.topicOperations addOperation:operation]; + +} + +@end diff --git a/Firebase/Messaging/FIRMessagingReceiver.h b/Firebase/Messaging/FIRMessagingReceiver.h new file mode 100644 index 0000000..6e4a693 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingReceiver.h @@ -0,0 +1,31 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingDataMessageManager.h" +#import "FIRMessaging.h" + +@class FIRMessagingReceiver; +@protocol FIRMessagingReceiverDelegate <NSObject> + +- (void)receiver:(nonnull FIRMessagingReceiver *)receiver + receivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage; + +@end + + +@interface FIRMessagingReceiver : NSObject <FIRMessagingDataMessageManagerDelegate> +@property(nonatomic, weak, nullable) id<FIRMessagingReceiverDelegate> delegate; +@end diff --git a/Firebase/Messaging/FIRMessagingReceiver.m b/Firebase/Messaging/FIRMessagingReceiver.m new file mode 100644 index 0000000..7a99c92 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingReceiver.m @@ -0,0 +1,141 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingReceiver.h" + +#import <UIKit/UIKit.h> + +#import "FIRMessaging.h" +#import "FIRMessaging_Private.h" +#import "FIRMessagingLogger.h" + +static NSString *const kUpstreamMessageIDUserInfoKey = @"messageID"; +static NSString *const kUpstreamErrorUserInfoKey = @"error"; + +// Copied from Apple's header in case it is missing in some cases. +#ifndef NSFoundationVersionNumber_iOS_9_x_Max +#define NSFoundationVersionNumber_iOS_9_x_Max 1299 +#endif + +static int downstreamMessageID = 0; + +@implementation FIRMessagingReceiver + +#pragma mark - FIRMessagingDataMessageManager protocol + +- (void)didReceiveMessage:(NSDictionary *)message withIdentifier:(nullable NSString *)messageID { + if (![messageID length]) { + messageID = [[self class] nextMessageID]; + } + + if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) { + // Use delegate method for iOS 10 + [self scheduleIos10NotificationForMessage:message withIdentifier:messageID]; + } else { + // Post notification directly to AppDelegate handlers. This is valid pre-iOS 10. + [self scheduleNotificationForMessage:message]; + } +} + +- (void)willSendDataMessageWithID:(NSString *)messageID error:(NSError *)error { + NSNotification *notification; + if (error) { + NSDictionary *userInfo = @{ + kUpstreamMessageIDUserInfoKey : [messageID copy], + kUpstreamErrorUserInfoKey : error + }; + notification = [NSNotification notificationWithName:FIRMessagingSendErrorNotification + object:nil + userInfo:userInfo]; + [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP]; + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeReceiver000, + @"Fail to send upstream message: %@ error: %@", messageID, error); + } else { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeReceiver001, @"Will send upstream message: %@", + messageID); + } +} + +- (void)didSendDataMessageWithID:(NSString *)messageID { + // invoke the callbacks asynchronously + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeReceiver002, @"Did send upstream message: %@", + messageID); + NSNotification * notification = + [NSNotification notificationWithName:FIRMessagingSendSuccessNotification + object:nil + userInfo:@{ kUpstreamMessageIDUserInfoKey : [messageID copy] }]; + + [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP]; +} + +- (void)didDeleteMessagesOnServer { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeReceiver003, + @"Will send deleted messages notification"); + NSNotification * notification = + [NSNotification notificationWithName:FIRMessagingMessagesDeletedNotification + object:nil]; + + [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP]; +} + +#pragma mark - Private Helpers +// As the new UserNotifications framework in iOS 10 doesn't support constructor/mutation for +// UNNotification object, FCM can't inject the message to the app with UserNotifications framework. +// Define our own protocol, which means app developers need to implement two interfaces to receive +// display notifications and data messages respectively for devices running iOS 10 or above. Devices +// running iOS 9 or below are not affected. +- (void)scheduleIos10NotificationForMessage:(NSDictionary *)message + withIdentifier:(NSString *)messageID { + FIRMessagingRemoteMessage *wrappedMessage = [[FIRMessagingRemoteMessage alloc] init]; + // TODO: wrap title, body, badge and other fields + wrappedMessage.appData = [message copy]; + [self.delegate receiver:self receivedRemoteMessage:wrappedMessage]; +} + +- (void)scheduleNotificationForMessage:(NSDictionary *)message { + SEL newNotificationSelector = + @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); + SEL oldNotificationSelector = @selector(application:didReceiveRemoteNotification:); + + dispatch_async(dispatch_get_main_queue(), ^{ + id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate]; + if ([appDelegate respondsToSelector:newNotificationSelector]) { + // Try the new remote notification callback + [appDelegate application:[UIApplication sharedApplication] + didReceiveRemoteNotification:message + fetchCompletionHandler:^(UIBackgroundFetchResult result) {}]; + + } else if ([appDelegate respondsToSelector:oldNotificationSelector]) { + // Try the old remote notification callback + [appDelegate application: + [UIApplication sharedApplication] didReceiveRemoteNotification:message]; + + } else { + FIRMessagingLoggerError(kFIRMessagingMessageCodeReceiver005, + @"None of the remote notification callbacks implemented by " + @"UIApplicationDelegate"); + } + }); +} + ++ (NSString *)nextMessageID { + @synchronized (self) { + ++downstreamMessageID; + return [NSString stringWithFormat:@"gcm-%d", downstreamMessageID]; + } +} + +@end diff --git a/Firebase/Messaging/FIRMessagingRegistrar.h b/Firebase/Messaging/FIRMessagingRegistrar.h new file mode 100644 index 0000000..5b60437 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingRegistrar.h @@ -0,0 +1,87 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingCheckinService.h" +#import "FIRMessagingTopicsCommon.h" + +@class FIRMessagingCheckinStore; +@class FIRMessagingPubSubRegistrar; + +/** + * Handle the registration process for the client. Fetch checkin information from the Checkin + * service if not cached on the device and then try to register the client with FIRMessaging backend. + */ +@interface FIRMessagingRegistrar : NSObject + +@property(nonatomic, readonly, strong) FIRMessagingPubSubRegistrar *pubsubRegistrar; +@property(nonatomic, readonly, strong) NSString *deviceAuthID; +@property(nonatomic, readonly, strong) NSString *secretToken; + +/** + * Initialize a FIRMessaging Registrar. + * + * @return A FIRMessaging Registrar object. + */ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +#pragma mark - Checkin + +/** + * Try to load checkin info from the disk if not currently loaded into memory. + * + * @return YES if successfully loaded valid checkin info to memory else NO. + */ +- (BOOL)tryToLoadValidCheckinInfo; + +/** + * Check if we have a valid checkin info in memory. + * + * @return YES if we have valid checkin info in memory else NO. + */ +- (BOOL)hasValidCheckinInfo; + +#pragma mark - Subscribe/Unsubscribe + +/** + * Update the subscription for a given topic for the client. + * + * @param topic The topic for which the subscription should be updated. + * @param token The registration token to be used by the client. + * @param options The extra options if any being passed as part of + * subscription request. + * @param shouldDelete YES if we want to delete an existing subscription else NO + * if we want to create a new subscription. + * @param handler The handler to invoke once the subscription request is + * complete. + */ +- (void)updateSubscriptionToTopic:(NSString *)topic + withToken:(NSString *)token + options:(NSDictionary *)options + shouldDelete:(BOOL)shouldDelete + handler:(FIRMessagingTopicOperationCompletion)handler; + +/** + * Cancel all subscription requests as well as any requests to checkin. Note if + * there are subscription requests waiting on checkin to complete those requests + * would be marked as stale and be NO-OP's if they happen in the future. + * + * Also note this is a one time operation, you should only call this if you want + * to immediately stop all requests and deallocate the registrar. After calling + * this once you would no longer be able to use this registrar object. + */ +- (void)cancelAllRequests; + +@end diff --git a/Firebase/Messaging/FIRMessagingRegistrar.m b/Firebase/Messaging/FIRMessagingRegistrar.m new file mode 100644 index 0000000..ab57b9e --- /dev/null +++ b/Firebase/Messaging/FIRMessagingRegistrar.m @@ -0,0 +1,112 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingRegistrar.h" + +#import "FIRMessagingDefines.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingPubSubRegistrar.h" +#import "FIRMessagingUtilities.h" +#import "NSError+FIRMessaging.h" + +@interface FIRMessagingRegistrar () + +@property(nonatomic, readwrite, assign) BOOL stopAllSubscriptions; + +@property(nonatomic, readwrite, strong) FIRMessagingCheckinService *checkinService; +@property(nonatomic, readwrite, strong) FIRMessagingPubSubRegistrar *pubsubRegistrar; + +@end + +@implementation FIRMessagingRegistrar + +- (NSString *)deviceAuthID { + return self.checkinService.deviceAuthID; +} + +- (NSString *)secretToken { + return self.checkinService.secretToken; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _checkinService = [[FIRMessagingCheckinService alloc] init]; + _pubsubRegistrar = [[FIRMessagingPubSubRegistrar alloc] initWithCheckinService:_checkinService]; + } + return self; +} + +#pragma mark - Checkin + +- (BOOL)tryToLoadValidCheckinInfo { + if (![self.checkinService hasValidCheckinInfo]) { + [self.checkinService tryToLoadPrefetchedCheckinPreferences]; + } + return [self.checkinService hasValidCheckinInfo]; +} + +- (BOOL)hasValidCheckinInfo { + return [self.checkinService hasValidCheckinInfo]; +} + +#pragma mark - Subscribe/Unsubscribe + +- (void)updateSubscriptionToTopic:(NSString *)topic + withToken:(NSString *)token + options:(NSDictionary *)options + shouldDelete:(BOOL)shouldDelete + handler:(FIRMessagingTopicOperationCompletion)handler { + _FIRMessagingDevAssert(handler, @"Invalid nil handler"); + + if ([self tryToLoadValidCheckinInfo]) { + [self doUpdateSubscriptionForTopic:topic + token:token + options:options + shouldDelete:shouldDelete + completion:handler]; + + } else { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRegistrar000, + @"Device check in error, no auth credentials found"); + NSError *error = [NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeMissingDeviceID]; + handler(FIRMessagingTopicOperationResultError, error); + } +} + +- (void)cancelAllRequests { + self.stopAllSubscriptions = YES; + [self.pubsubRegistrar stopAllSubscriptionRequests]; +} + +#pragma mark - Private + +- (void)doUpdateSubscriptionForTopic:(NSString *)topic + token:(NSString *)token + options:(NSDictionary *)options + shouldDelete:(BOOL)shouldDelete + completion:(FIRMessagingTopicOperationCompletion)completion { + _FIRMessagingDevAssert([self.checkinService hasValidCheckinInfo], + @"No valid checkin info found before subscribe"); + + [self.pubsubRegistrar updateSubscriptionToTopic:topic + withToken:token + options:options + shouldDelete:shouldDelete + handler:completion]; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h new file mode 100644 index 0000000..59c3c15 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h @@ -0,0 +1,40 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +/** + * Swizzle remote-notification callbacks to invoke FIRMessaging methods + * before calling original implementations. + */ +@interface FIRMessagingRemoteNotificationsProxy : NSObject + +/** + * Checks the `FirebaseAppDelegateProxyEnabled` key in the App's Info.plist. If the key is + * missing or incorrectly formatted, returns `YES`. + * + * @return YES if the Application Delegate and User Notification Center methods can be swizzled. + * Otherwise, returns NO. + */ ++ (BOOL)canSwizzleMethods; + +/** + * Swizzles Application Delegate's remote-notification callbacks and User Notification Center + * delegate callback, and invokes the original selectors once done. + */ ++ (void)swizzleMethods; + +@end diff --git a/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m new file mode 100644 index 0000000..5432c79 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m @@ -0,0 +1,613 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingRemoteNotificationsProxy.h" + +#import <objc/runtime.h> +#import <UIKit/UIKit.h> + +#import "FIRMessagingConstants.h" +#import "FIRMessagingLogger.h" +#import "FIRMessaging_Private.h" + +static const BOOL kDefaultAutoRegisterEnabledValue = YES; +static void * UserNotificationObserverContext = &UserNotificationObserverContext; + +static NSString *kUserNotificationWillPresentSelectorString = + @"userNotificationCenter:willPresentNotification:withCompletionHandler:"; + +@interface FIRMessagingRemoteNotificationsProxy () + +@property(strong, nonatomic) NSMutableDictionary<NSString *, NSValue *> *originalAppDelegateImps; +@property(strong, nonatomic) NSMutableDictionary<NSString *, NSArray *> *swizzledSelectorsByClass; + +@property(nonatomic) BOOL didSwizzleMethods; +@property(nonatomic) BOOL didSwizzleAppDelegateMethods; + +@property(nonatomic) BOOL hasSwizzledUserNotificationDelegate; +@property(nonatomic) BOOL isObservingUserNotificationDelegateChanges; + +@property(strong, nonatomic) id userNotificationCenter; +@property(strong, nonatomic) id currentUserNotificationCenterDelegate; + +@end + +@implementation FIRMessagingRemoteNotificationsProxy + ++ (BOOL)canSwizzleMethods { + id canSwizzleValue = + [[NSBundle mainBundle] + objectForInfoDictionaryKey: kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey]; + if (canSwizzleValue && [canSwizzleValue isKindOfClass:[NSNumber class]]) { + NSNumber *canSwizzleNumberValue = (NSNumber *)canSwizzleValue; + return canSwizzleNumberValue.boolValue; + } else { + return kDefaultAutoRegisterEnabledValue; + } +} + ++ (void)swizzleMethods { + [[FIRMessagingRemoteNotificationsProxy sharedProxy] swizzleMethodsIfPossible]; +} + ++ (instancetype)sharedProxy { + static FIRMessagingRemoteNotificationsProxy *proxy; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + proxy = [[FIRMessagingRemoteNotificationsProxy alloc] init]; + }); + return proxy; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _originalAppDelegateImps = [[NSMutableDictionary alloc] init]; + _swizzledSelectorsByClass = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)dealloc { + [self unswizzleAllMethods]; + self.swizzledSelectorsByClass = nil; + [self.originalAppDelegateImps removeAllObjects]; + self.originalAppDelegateImps = nil; + [self removeUserNotificationCenterDelegateObserver]; +} + +- (void)swizzleMethodsIfPossible { + // Already swizzled. + if (self.didSwizzleMethods) { + return; + } + + NSObject<UIApplicationDelegate> *appDelegate = [[UIApplication sharedApplication] delegate]; + [self swizzleAppDelegateMethods:appDelegate]; + + // Add KVO listener on [UNUserNotificationCenter currentNotificationCenter]'s delegate property + Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter"); + if (notificationCenterClass) { + // We are linked against iOS 10 SDK or above + id notificationCenter = getNamedPropertyFromObject(notificationCenterClass, + @"currentNotificationCenter", + notificationCenterClass); + if (notificationCenter) { + [self listenForDelegateChangesInUserNotificationCenter:notificationCenter]; + } + } + + self.didSwizzleMethods = YES; +} + +- (void)unswizzleAllMethods { + for (NSString *className in self.swizzledSelectorsByClass) { + Class klass = NSClassFromString(className); + NSArray *selectorStrings = self.swizzledSelectorsByClass[className]; + for (NSString *selectorString in selectorStrings) { + SEL selector = NSSelectorFromString(selectorString); + [self unswizzleSelector:selector inClass:klass]; + } + } + [self.swizzledSelectorsByClass removeAllObjects]; +} + +- (void)swizzleAppDelegateMethods:(id<UIApplicationDelegate>)appDelegate { + if (![appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) { + return; + } + Class appDelegateClass = [appDelegate class]; + + BOOL didSwizzleAppDelegate = NO; + // Message receiving handler for iOS 9, 8, 7 devices (both display notification and data message). + SEL remoteNotificationSelector = + @selector(application:didReceiveRemoteNotification:); + + SEL remoteNotificationWithFetchHandlerSelector = + @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); + + // For data message from MCS. + SEL receiveDataMessageSelector = NSSelectorFromString(@"applicationReceivedRemoteMessage:"); + + // For recording when APNS tokens are registered (or fail to register) + SEL registerForAPNSFailSelector = + @selector(application:didFailToRegisterForRemoteNotificationsWithError:); + + SEL registerForAPNSSuccessSelector = + @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); + + + // Receive Remote Notifications. + BOOL selectorWithFetchHandlerImplemented = NO; + if ([appDelegate respondsToSelector:remoteNotificationWithFetchHandlerSelector]) { + selectorWithFetchHandlerImplemented = YES; + [self swizzleSelector:remoteNotificationWithFetchHandlerSelector + inClass:appDelegateClass + withImplementation:(IMP)FCM_swizzle_appDidReceiveRemoteNotificationWithHandler + inProtocol:@protocol(UIApplicationDelegate)]; + didSwizzleAppDelegate = YES; + } + + if ([appDelegate respondsToSelector:remoteNotificationSelector] || + !selectorWithFetchHandlerImplemented) { + [self swizzleSelector:remoteNotificationSelector + inClass:appDelegateClass + withImplementation:(IMP)FCM_swizzle_appDidReceiveRemoteNotification + inProtocol:@protocol(UIApplicationDelegate)]; + didSwizzleAppDelegate = YES; + } + + if ([appDelegate respondsToSelector:receiveDataMessageSelector]) { + [self swizzleSelector:receiveDataMessageSelector + inClass:appDelegateClass + withImplementation:(IMP)FCM_swizzle_applicationReceivedRemoteMessage + inProtocol:@protocol(UIApplicationDelegate)]; + didSwizzleAppDelegate = YES; + } + + // Receive APNS token + [self swizzleSelector:registerForAPNSSuccessSelector + inClass:appDelegateClass + withImplementation:(IMP)FCM_swizzle_appDidRegisterForRemoteNotifications + inProtocol:@protocol(UIApplicationDelegate)]; + + [self swizzleSelector:registerForAPNSFailSelector + inClass:appDelegateClass + withImplementation:(IMP)FCM_swizzle_appDidFailToRegisterForRemoteNotifications + inProtocol:@protocol(UIApplicationDelegate)]; + + self.didSwizzleAppDelegateMethods = didSwizzleAppDelegate; +} + +- (void)listenForDelegateChangesInUserNotificationCenter:(id)notificationCenter { + Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter"); + if (![notificationCenter isKindOfClass:notificationCenterClass]) { + return; + } + id delegate = getNamedPropertyFromObject(notificationCenter, @"delegate", nil); + Protocol *delegateProtocol = NSProtocolFromString(@"UNUserNotificationCenterDelegate"); + if ([delegate conformsToProtocol:delegateProtocol]) { + // Swizzle this object now, if available + [self swizzleUserNotificationCenterDelegate:delegate]; + } + // Add KVO observer for "delegate" keyPath for future changes + [self addDelegateObserverToUserNotificationCenter:notificationCenter]; +} + +#pragma mark - UNNotificationCenter Swizzling + +- (void)swizzleUserNotificationCenterDelegate:(id)delegate { + if (self.currentUserNotificationCenterDelegate == delegate) { + // Via pointer-check, compare if we have already swizzled this item. + return; + } + Protocol *userNotificationCenterProtocol = + NSProtocolFromString(@"UNUserNotificationCenterDelegate"); + if ([delegate conformsToProtocol:userNotificationCenterProtocol]) { + SEL willPresentNotificationSelector = + NSSelectorFromString(kUserNotificationWillPresentSelectorString); + [self swizzleSelector:willPresentNotificationSelector + inClass:[delegate class] + withImplementation:(IMP)FCM_swizzle_willPresentNotificationWithHandler + inProtocol:userNotificationCenterProtocol]; + self.currentUserNotificationCenterDelegate = delegate; + self.hasSwizzledUserNotificationDelegate = YES; + } +} + +- (void)unswizzleUserNotificationCenterDelegate:(id)delegate { + if (self.currentUserNotificationCenterDelegate != delegate) { + // We aren't swizzling this delegate, so don't do anything. + return; + } + SEL willPresentNotificationSelector = + NSSelectorFromString(kUserNotificationWillPresentSelectorString); + [self unswizzleSelector:willPresentNotificationSelector + inClass:[self.currentUserNotificationCenterDelegate class]]; + self.currentUserNotificationCenterDelegate = nil; + self.hasSwizzledUserNotificationDelegate = NO; +} + +#pragma mark - KVO for UNUserNotificationCenter + +- (void)addDelegateObserverToUserNotificationCenter:(id)userNotificationCenter { + [self removeUserNotificationCenterDelegateObserver]; + @try { + [userNotificationCenter addObserver:self + forKeyPath:NSStringFromSelector(@selector(delegate)) + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + context:UserNotificationObserverContext]; + self.userNotificationCenter = userNotificationCenter; + self.isObservingUserNotificationDelegateChanges = YES; + } @catch (NSException *exception) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxy000, + @"Encountered exception trying to add a KVO observer for " + @"UNUserNotificationCenter's 'delegate' property: %@", + exception); + } @finally { + + } +} + +- (void)removeUserNotificationCenterDelegateObserver { + if (!self.userNotificationCenter) { + return; + } + @try { + [self.userNotificationCenter removeObserver:self + forKeyPath:NSStringFromSelector(@selector(delegate)) + context:UserNotificationObserverContext]; + self.userNotificationCenter = nil; + self.isObservingUserNotificationDelegateChanges = NO; + } @catch (NSException *exception) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxy001, + @"Encountered exception trying to remove a KVO observer for " + @"UNUserNotificationCenter's 'delegate' property: %@", + exception); + } @finally { + + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary<NSKeyValueChangeKey, id> *)change + context:(void *)context { + if (context == UserNotificationObserverContext) { + if ([keyPath isEqualToString:NSStringFromSelector(@selector(delegate))]) { + id oldDelegate = change[NSKeyValueChangeOldKey]; + if (oldDelegate && oldDelegate != [NSNull null]) { + [self unswizzleUserNotificationCenterDelegate:oldDelegate]; + } + id newDelegate = change[NSKeyValueChangeNewKey]; + if (newDelegate && newDelegate != [NSNull null]) { + [self swizzleUserNotificationCenterDelegate:newDelegate]; + } + } + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +#pragma mark - NSProxy methods + +- (void)saveOriginalImplementation:(IMP)imp forSelector:(SEL)selector { + if (imp && selector) { + NSValue *IMPValue = [NSValue valueWithPointer:imp]; + NSString *selectorString = NSStringFromSelector(selector); + self.originalAppDelegateImps[selectorString] = IMPValue; + } +} + +- (IMP)originalImplementationForSelector:(SEL)selector { + NSString *selectorString = NSStringFromSelector(selector); + NSValue *implementation_value = self.originalAppDelegateImps[selectorString]; + if (!implementation_value) { + return nil; + } + + IMP imp; + [implementation_value getValue:&imp]; + return imp; +} + +- (void)trackSwizzledSelector:(SEL)selector ofClass:(Class)klass { + NSString *className = NSStringFromClass(klass); + NSString *selectorString = NSStringFromSelector(selector); + NSArray *selectors = self.swizzledSelectorsByClass[selectorString]; + if (selectors) { + selectors = [selectors arrayByAddingObject:selectorString]; + } else { + selectors = @[selectorString]; + } + self.swizzledSelectorsByClass[className] = selectors; +} + +- (void)removeImplementationForSelector:(SEL)selector { + NSString *selectorString = NSStringFromSelector(selector); + [self.originalAppDelegateImps removeObjectForKey:selectorString]; +} + +- (void)swizzleSelector:(SEL)originalSelector + inClass:(Class)klass + withImplementation:(IMP)swizzledImplementation + inProtocol:(Protocol *)protocol { + Method originalMethod = class_getInstanceMethod(klass, originalSelector); + + if (originalMethod) { + // This class implements this method, so replace the original implementation + // with our new implementation and save the old implementation. + + IMP __original_method_implementation = + method_setImplementation(originalMethod, swizzledImplementation); + + IMP __nonexistant_method_implementation = [self nonExistantMethodImplementationForClass:klass]; + + if (__original_method_implementation && + __original_method_implementation != __nonexistant_method_implementation && + __original_method_implementation != swizzledImplementation) { + [self saveOriginalImplementation:__original_method_implementation + forSelector:originalSelector]; + } + } else { + // The class doesn't have this method, so add our swizzled implementation as the + // original implementation of the original method. + struct objc_method_description method_description = + protocol_getMethodDescription(protocol, originalSelector, NO, YES); + + class_addMethod(klass, + originalSelector, + swizzledImplementation, + method_description.types); + } + [self trackSwizzledSelector:originalSelector ofClass:klass]; +} + +- (void)unswizzleSelector:(SEL)selector inClass:(Class)klass { + + Method swizzledMethod = class_getInstanceMethod(klass, selector); + if (!swizzledMethod) { + // This class doesn't seem to have this selector as an instance method? Bail out. + return; + } + + IMP original_imp = [self originalImplementationForSelector:selector]; + if (original_imp) { + // Restore the original implementation as the current implementation + method_setImplementation(swizzledMethod, original_imp); + [self removeImplementationForSelector:selector]; + } else { + // This class originally did not have an implementation for this selector. + + // We can't actually remove methods in Objective C 2.0, but we could set + // its method to something non-existent. This should give us the same + // behavior as if the method was not implemented. + // See: http://stackoverflow.com/a/8276527/9849 + + IMP nonExistantMethodImplementation = [self nonExistantMethodImplementationForClass:klass]; + method_setImplementation(swizzledMethod, nonExistantMethodImplementation); + } +} + +#pragma mark - Reflection Helpers + +// This is useful to generate from a stable, "known missing" selector, as the IMP can be compared +// in case we are setting an implementation for a class that was previously "unswizzled" into a +// non-existant implementation. +- (IMP)nonExistantMethodImplementationForClass:(Class)klass { + SEL nonExistantSelector = NSSelectorFromString(@"aNonExistantMethod"); + IMP nonExistantMethodImplementation = class_getMethodImplementation(klass, nonExistantSelector); + return nonExistantMethodImplementation; +} + +// A safe, non-leaky way return a property object by its name +id getNamedPropertyFromObject(id object, NSString *propertyName, Class klass) { + SEL selector = NSSelectorFromString(propertyName); + if (![object respondsToSelector:selector]) { + return nil; + } + if (!klass) { + klass = [NSObject class]; + } + // Suppress clang warning about leaks in performSelector + // The alternative way to perform this is to invoke + // the method as a block (see http://stackoverflow.com/a/20058585), + // but this approach sometimes returns incomplete objects. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + id property = [object performSelector:selector]; +#pragma clang diagnostic pop + if (![property isKindOfClass:klass]) { + return nil; + } + return property; +} + +#pragma mark - Swizzled Methods + +void FCM_swizzle_appDidReceiveRemoteNotification(id self, + SEL _cmd, + UIApplication *app, + NSDictionary *userInfo) { + [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; + + IMP original_imp = + [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; + if (original_imp) { + ((void (*)(id, SEL, UIApplication *, NSDictionary *))original_imp)(self, + _cmd, + app, + userInfo); + } +} + +void FCM_swizzle_appDidReceiveRemoteNotificationWithHandler( + id self, SEL _cmd, UIApplication *app, NSDictionary *userInfo, + void (^handler)(UIBackgroundFetchResult)) { + + [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; + + IMP original_imp = + [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; + if (original_imp) { + ((void (*)(id, SEL, UIApplication *, NSDictionary *, + void (^)(UIBackgroundFetchResult)))original_imp)( + self, _cmd, app, userInfo, handler); + } +} + +/** + * Swizzle the notification handler for iOS 10+ devices. + * Signature of original handler is as below: + * - (void)userNotificationCenter:(UNUserNotificationCenter *)center + * willPresentNotification:(UNNotification *)notification + * withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler + * In order to make FCM SDK compile and compatible with iOS SDKs before iOS 10, hide the + * parameter types from the swizzling implementation. + */ +void FCM_swizzle_willPresentNotificationWithHandler( + id self, SEL _cmd, id center, id notification, void (^handler)(NSUInteger)) { + + FIRMessagingRemoteNotificationsProxy *proxy = [FIRMessagingRemoteNotificationsProxy sharedProxy]; + IMP original_imp = [proxy originalImplementationForSelector:_cmd]; + + void (^callOriginalMethodIfAvailable)() = ^{ + if (original_imp) { + ((void (*)(id, SEL, id, id, void (^)(NSUInteger)))original_imp)( + self, _cmd, center, notification, handler); + } + return; + }; + + Class notificationCenterClass = NSClassFromString(@"UNUserNotificationCenter"); + Class notificationClass = NSClassFromString(@"UNNotification"); + if (!notificationCenterClass || !notificationClass) { + // Can't find UserNotifications framework. Do not swizzle, just execute the original method. + callOriginalMethodIfAvailable(); + } + + if (!center || ![center isKindOfClass:[notificationCenterClass class]]) { + // Invalid parameter type from the original method. + // Do not swizzle, just execute the original method. + callOriginalMethodIfAvailable(); + return; + } + + if (!notification || ![notification isKindOfClass:[notificationClass class]]) { + // Invalid parameter type from the original method. + // Do not swizzle, just execute the original method. + callOriginalMethodIfAvailable(); + return; + } + + if (!handler) { + // Invalid parameter type from the original method. + // Do not swizzle, just execute the original method. + callOriginalMethodIfAvailable(); + return; + } + + // Valid original method signature, go ahead to swizzle. + // Select the userInfo field from UNNotification.request.content.userInfo. + SEL requestSelector = NSSelectorFromString(@"request"); + if (![notification respondsToSelector:requestSelector]) { + // This is not the expected notification handler. Do not swizzle, just execute the original + // method. + callOriginalMethodIfAvailable(); + return; + } + Class requestClass = NSClassFromString(@"UNNotificationRequest"); + id notificationRequest = getNamedPropertyFromObject(notification, @"request", requestClass); + + SEL notificationContentSelector = NSSelectorFromString(@"content"); + if (!notificationRequest + || ![notificationRequest respondsToSelector:notificationContentSelector]) { + // This is not the expected notification handler. Do not swizzle, just execute the original + // method. + callOriginalMethodIfAvailable(); + return; + } + Class contentClass = NSClassFromString(@"UNNotificationContent"); + id notificationContent = getNamedPropertyFromObject(notificationRequest, + @"content", + contentClass); + + SEL notificationUserInfoSelector = NSSelectorFromString(@"userInfo"); + if (!notificationContent + || ![notificationContent respondsToSelector:notificationUserInfoSelector]) { + // This is not the expected notification handler. Do not swizzle, just execute the original + // method. + callOriginalMethodIfAvailable(); + return; + } + id notificationUserInfo = getNamedPropertyFromObject(notificationContent, + @"userInfo", + [NSDictionary class]); + + if (!notificationUserInfo) { + // This is not the expected notification handler. Do not swizzle, just execute the original + // method. + callOriginalMethodIfAvailable(); + return; + } + + [[FIRMessaging messaging] appDidReceiveMessage:notificationUserInfo]; + // Execute the original implementation. + callOriginalMethodIfAvailable(); +} + +void FCM_swizzle_applicationReceivedRemoteMessage( + id self, SEL _cmd, FIRMessagingRemoteMessage *remoteMessage) { + [[FIRMessaging messaging] appDidReceiveMessage:remoteMessage.appData]; + + IMP original_imp = + [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; + if (original_imp) { + ((void (*)(id, SEL, FIRMessagingRemoteMessage *))original_imp)(self, _cmd, remoteMessage); + } +} + +void FCM_swizzle_appDidFailToRegisterForRemoteNotifications(id self, + SEL _cmd, + UIApplication *app, + NSError *error) { + // Log the fact that we failed to register for remote notifications + FIRMessagingLoggerError(kFIRMessagingMessageCodeRemoteNotificationsProxyAPNSFailed, + @"Error in " + @"application:didFailToRegisterForRemoteNotificationsWithError: %@", + error.localizedDescription); + IMP original_imp = + [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; + if (original_imp) { + ((void (*)(id, SEL, UIApplication *, NSError *))original_imp)(self, _cmd, app, error); + } +} + +void FCM_swizzle_appDidRegisterForRemoteNotifications(id self, + SEL _cmd, + UIApplication *app, + NSData *deviceToken) { + // Pass the APNSToken along to FIRMessaging (and auto-detect the token type) + [FIRMessaging messaging].APNSToken = deviceToken; + + IMP original_imp = + [[FIRMessagingRemoteNotificationsProxy sharedProxy] originalImplementationForSelector:_cmd]; + if (original_imp) { + ((void (*)(id, SEL, UIApplication *, NSData *))original_imp)(self, _cmd, app, deviceToken); + } +} + +@end diff --git a/Firebase/Messaging/FIRMessagingRmq2PersistentStore.h b/Firebase/Messaging/FIRMessagingRmq2PersistentStore.h new file mode 100644 index 0000000..09f1d44 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingRmq2PersistentStore.h @@ -0,0 +1,201 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +@class FIRMessagingPersistentSyncMessage; + +// table data handlers +/** + * Handle message stored in the outgoing RMQ messages table. + * + * @param rmqId The rmqID of the message. + * @param tag The message tag. + * @param data The data stored in the message. + */ +typedef void(^FCMOutgoingRmqMessagesTableHandler)(int64_t rmqId, int8_t tag, NSData *data); + +/// Outgoing messages RMQ table +extern NSString *const kTableOutgoingRmqMessages; +/// Server to device RMQ table +extern NSString *const kTableS2DRmqIds; + +@interface FIRMessagingRmq2PersistentStore : NSObject + +/** + * Initialize and open the RMQ database on the client. + * + * @param databaseName The name for RMQ database. + * + * @return A store used to persist messages on the client. + */ +- (instancetype)initWithDatabaseName:(NSString *)databaseName; + +/** + * Save outgoing message in RMQ. + * + * @param rmqId The rmqID for the message. + * @param tag The tag of the message proto. + * @param data The data being sent in the message. + * @param error The error if any while saving the message to the persistent store. + * + * @return YES if the message was successfully saved to the persistent store else NO. + */ +- (BOOL)saveMessageWithRmqId:(int64_t)rmqId + tag:(int8_t)tag + data:(NSData *)data + error:(NSError **)error; + +/** + * Add unacked server to device message with a given rmqID to the persistent store. + * + * @param rmqId The rmqID of the message that was not acked by the cient. + * + * @return YES if the save was successful else NO. + */ +- (BOOL)saveUnackedS2dMessageWithRmqId:(NSString *)rmqId; + +/** + * Update the last RMQ ID that was sent by the client. + * + * @param rmqID The latest rmqID sent by the device. + * + * @return YES if the last rmqID was successfully saved else NO. + */ +- (BOOL)updateLastOutgoingRmqId:(int64_t)rmqID; + +#pragma mark - Query + +/** + * Query the highest rmqID saved in the Outgoing messages table. + * + * @return The highest rmqID amongst all the messages in the Outgoing RMQ table. If no message + * was ever persisted return 0. + */ +- (int64_t)queryHighestRmqId; + +/** + * The last rmqID that was saved on the client. + * + * @return The last rmqID that was saved. If no rmqID was ever persisted return 0. + */ +- (int64_t)queryLastRmqId; + +/** + * Get a list of all unacked server to device messages stored on the client. + * + * @return List of all unacked s2d messages in the persistent store. + */ +- (NSArray *)unackedS2dRmqIds; + +/** + * Iterate over all outgoing messages in the RMQ table. + * + * @param handler The handler invoked with each message in the outgoing RMQ table. + */ +- (void)scanOutgoingRmqMessagesWithHandler:(FCMOutgoingRmqMessagesTableHandler)handler; + +#pragma mark - Delete + +/** + * Delete messages with given rmqID's from a table. + * + * @param tableName The table name from which to delete the rmq messages. + * @param rmqIds The rmqID's of the messages to be deleted. + * + * @return The number of messages that were successfully deleted. + */ +- (int)deleteMessagesFromTable:(NSString *)tableName + withRmqIds:(NSArray *)rmqIds; + +/** + * Remove database from the device. + * + * @param dbName The database name to be deleted. + */ ++ (void)removeDatabase:(NSString *)dbName; + +#pragma mark - Sync Messages + +/** + * Save sync message to persistent store to check for duplicates. + * + * @param rmqID The rmqID of the message to save. + * @param expirationTime The expiration time of the message to save. + * @param apnsReceived YES if the message was received via APNS else NO. + * @param mcsReceived YES if the message was received via MCS else NO. + * @param error The error if any while saving the message to store. + * + * @return YES if the message was saved successfully else NO. + */ +- (BOOL)saveSyncMessageWithRmqID:(NSString *)rmqID + expirationTime:(int64_t)expirationTime + apnsReceived:(BOOL)apnsReceived + mcsReceived:(BOOL)mcsReceived + error:(NSError **)error; + +/** + * Update sync message received via APNS. + * + * @param rmqID The rmqID of the sync message. + * @param error The error if any while updating the sync message in persistence. + * + * @return YES if the update was successful else NO. + */ +- (BOOL)updateSyncMessageViaAPNSWithRmqID:(NSString *)rmqID + error:(NSError **)error; + +/** + * Update sync message received via MCS. + * + * @param rmqID The rmqID of the sync message. + * @param error The error if any while updating the sync message in persistence. + * + * @return YES if the update was successful else NO. + */ +- (BOOL)updateSyncMessageViaMCSWithRmqID:(NSString *)rmqID + error:(NSError **)error; + +/** + * Query sync message table for a given rmqID. + * + * @param rmqID The rmqID to search for in SYNC_RMQ. + * + * @return The sync message that was persisted with `rmqID`. If no such message was persisted + * return nil. + */ +- (FIRMessagingPersistentSyncMessage *)querySyncMessageWithRmqID:(NSString *)rmqID; + +/** + * Delete sync message with rmqID. + * + * @param rmqID The rmqID of the message to delete. + * + * @return YES if a sync message with rmqID was found and deleted successfully else NO. + */ +- (BOOL)deleteSyncMessageWithRmqID:(NSString *)rmqID; + +/** + * Delete the expired sync messages from persisten store. Also deletes messages that have been + * delivered both via APNS and MCS. + * + * @param error The error if any while deleting the messages. + * + * @return The total number of messages that were deleted from the persistent store. + */ +- (int)deleteExpiredOrFinishedSyncMessages:(NSError **)error; + +@end diff --git a/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m b/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m new file mode 100644 index 0000000..9edac40 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingRmq2PersistentStore.m @@ -0,0 +1,770 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingRmq2PersistentStore.h" + +#import "sqlite3.h" + +#import "Protos/GtalkCore.pbobjc.h" + +#import "FIRMessagingConstants.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingPersistentSyncMessage.h" +#import "FIRMessagingUtilities.h" +#import "NSError+FIRMessaging.h" + +#ifndef _FIRMessagingRmqLogAndExit +#define _FIRMessagingRmqLogAndExit(stmt, return_value) \ +do { \ +[self logErrorAndFinalizeStatement:stmt]; \ +return return_value; \ +} while(0) +#endif + +typedef enum : NSUInteger { + FIRMessagingRmqDirectoryUnknown, + FIRMessagingRmqDirectoryDocuments, + FIRMessagingRmqDirectoryApplicationSupport, +} FIRMessagingRmqDirectory; + +static NSString *const kFCMRmqStoreTag = @"FIRMessagingRmqStore:"; + +// table names +NSString *const kTableOutgoingRmqMessages = @"outgoingRmqMessages"; +NSString *const kTableLastRmqId = @"lastrmqid"; +NSString *const kOldTableS2DRmqIds = @"s2dRmqIds"; +NSString *const kTableS2DRmqIds = @"s2dRmqIds_1"; + +// Used to prevent de-duping of sync messages received both via APNS and MCS. +NSString *const kTableSyncMessages = @"incomingSyncMessages"; + +static NSString *const kTablePrefix = @""; + +// create tables +static NSString *const kCreateTableOutgoingRmqMessages = + @"create TABLE IF NOT EXISTS %@%@ " + @"(_id INTEGER PRIMARY KEY, " + @"rmq_id INTEGER, " + @"type INTEGER, " + @"ts INTEGER, " + @"data BLOB)"; + +static NSString *const kCreateTableLastRmqId = + @"create TABLE IF NOT EXISTS %@%@ " + @"(_id INTEGER PRIMARY KEY, " + @"rmq_id INTEGER)"; + +static NSString *const kCreateTableS2DRmqIds = + @"create TABLE IF NOT EXISTS %@%@ " + @"(_id INTEGER PRIMARY KEY, " + @"rmq_id TEXT)"; + +static NSString *const kCreateTableSyncMessages = + @"create TABLE IF NOT EXISTS %@%@ " + @"(_id INTEGER PRIMARY KEY, " + @"rmq_id TEXT, " + @"expiration_ts INTEGER, " + @"apns_recv INTEGER, " + @"mcs_recv INTEGER)"; + +static NSString *const kDropTableCommand = + @"drop TABLE if exists %@%@"; + +// table infos +static NSString *const kRmqIdColumn = @"rmq_id"; +static NSString *const kDataColumn = @"data"; +static NSString *const kProtobufTagColumn = @"type"; +static NSString *const kIdColumn = @"_id"; + +static NSString *const kOutgoingRmqMessagesColumns = @"rmq_id, type, data"; + +// Sync message columns +static NSString *const kSyncMessagesColumns = @"rmq_id, expiration_ts, apns_recv, mcs_recv"; +// Message time expiration in seconds since 1970 +static NSString *const kSyncMessageExpirationTimestampColumn = @"expiration_ts"; +static NSString *const kSyncMessageAPNSReceivedColumn = @"apns_recv"; +static NSString *const kSyncMessageMCSReceivedColumn = @"mcs_recv"; + +// table data handlers +typedef void(^FCMOutgoingRmqMessagesTableHandler)(int64_t rmqId, int8_t tag, NSData *data); + +@interface FIRMessagingRmq2PersistentStore () { + sqlite3 *_database; +} + +@property(nonatomic, readwrite, strong) NSString *databaseName; +@property(nonatomic, readwrite, assign) FIRMessagingRmqDirectory currentDirectory; + +@end + +@implementation FIRMessagingRmq2PersistentStore + +- (instancetype)initWithDatabaseName:(NSString *)databaseName { + self = [super init]; + if (self) { + _databaseName = [databaseName copy]; + BOOL didMoveToApplicationSupport = + [self moveToApplicationSupportSubDirectory:kFIRMessagingApplicationSupportSubDirectory]; + + _currentDirectory = didMoveToApplicationSupport + ? FIRMessagingRmqDirectoryApplicationSupport + : FIRMessagingRmqDirectoryDocuments; + + [self openDatabase:_databaseName]; + } + return self; +} + +- (void)dealloc { + sqlite3_close(_database); +} + +- (BOOL)moveToApplicationSupportSubDirectory:(NSString *)subDirectoryName { + NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, + NSUserDomainMask, YES); + NSString *applicationSupportDirPath = directoryPaths.lastObject; + NSArray *components = @[applicationSupportDirPath, subDirectoryName]; + NSString *subDirectoryPath = [NSString pathWithComponents:components]; + BOOL hasSubDirectory; + + if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath + isDirectory:&hasSubDirectory]) { + // Cannot move to non-existent directory + return NO; + } + + if ([self doesFileExistInDirectory:FIRMessagingRmqDirectoryDocuments]) { + NSString *oldPlistPath = [[self class] pathForDatabase:self.databaseName + inDirectory:FIRMessagingRmqDirectoryDocuments]; + NSString *newPlistPath = [[self class] + pathForDatabase:self.databaseName + inDirectory:FIRMessagingRmqDirectoryApplicationSupport]; + + if ([self doesFileExistInDirectory:FIRMessagingRmqDirectoryApplicationSupport]) { + // File exists in both Documents and ApplicationSupport, delete the one in Documents + NSError *deleteError; + if (![[NSFileManager defaultManager] removeItemAtPath:oldPlistPath error:&deleteError]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStore000, + @"Failed to delete old copy of %@.sqlite in Documents %@", + self.databaseName, deleteError); + } + return NO; + } + NSError *moveError; + if (![[NSFileManager defaultManager] moveItemAtPath:oldPlistPath + toPath:newPlistPath + error:&moveError]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStore001, + @"Failed to move file %@ from %@ to %@. Error: %@", self.databaseName, + oldPlistPath, newPlistPath, moveError); + return NO; + } + } + // We moved the file if it existed, otherwise we didn't need to do anything + return YES; +} + +- (BOOL)doesFileExistInDirectory:(FIRMessagingRmqDirectory)directory { + NSString *path = [[self class] pathForDatabase:self.databaseName inDirectory:directory]; + return [[NSFileManager defaultManager] fileExistsAtPath:path]; +} + ++ (NSString *)pathForDatabase:(NSString *)dbName inDirectory:(FIRMessagingRmqDirectory)directory { + NSArray *paths; + NSArray *components; + NSString *dbNameWithExtension = [NSString stringWithFormat:@"%@.sqlite", dbName]; + + switch (directory) { + case FIRMessagingRmqDirectoryDocuments: + paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + components = @[paths.lastObject, dbNameWithExtension]; + break; + + case FIRMessagingRmqDirectoryApplicationSupport: + paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, + NSUserDomainMask, + YES); + components = @[ + paths.lastObject, + kFIRMessagingApplicationSupportSubDirectory, + dbNameWithExtension + ]; + break; + + default: + FIRMessaging_FAIL(@"Invalid directory type %zd", directory); + break; + } + + return [NSString pathWithComponents:components]; +} + +- (void)createTableWithName:(NSString *)tableName command:(NSString *)command { + char *error; + NSString *createDatabase = [NSString stringWithFormat:command, kTablePrefix, tableName]; + if (sqlite3_exec(_database, [createDatabase UTF8String], NULL, NULL, &error) != SQLITE_OK) { + // remove db before failing + [self removeDatabase]; + FIRMessaging_FAIL(@"Couldn't create table: %@ %@", + kCreateTableOutgoingRmqMessages, + [NSString stringWithCString:error encoding:NSUTF8StringEncoding]); + } +} + +- (void)dropTableWithName:(NSString *)tableName { + char *error; + NSString *dropTableSQL = [NSString stringWithFormat:kDropTableCommand, kTablePrefix, tableName]; + if (sqlite3_exec(_database, [dropTableSQL UTF8String], NULL, NULL, &error) != SQLITE_OK) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStore002, + @"Failed to remove table %@", tableName); + } +} + +- (void)removeDatabase { + NSString *path = [[self class] pathForDatabase:self.databaseName + inDirectory:self.currentDirectory]; + [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; +} + ++ (void)removeDatabase:(NSString *)dbName { + NSString *documentsDirPath = [self pathForDatabase:dbName + inDirectory:FIRMessagingRmqDirectoryDocuments]; + NSString *applicationSupportDirPath = + [self pathForDatabase:dbName inDirectory:FIRMessagingRmqDirectoryApplicationSupport]; + [[NSFileManager defaultManager] removeItemAtPath:documentsDirPath error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:applicationSupportDirPath error:nil]; +} + +- (void)openDatabase:(NSString *)dbName { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *path = [[self class] pathForDatabase:dbName inDirectory:self.currentDirectory]; + + BOOL didOpenDatabase = YES; + if (![fileManager fileExistsAtPath:path]) { + // We've to separate between different versions here because of backwards compatbility issues. + if (sqlite3_open([path UTF8String], &_database) != SQLITE_OK) { + FIRMessaging_FAIL(@"%@ Could not open rmq database: %@", kFCMRmqStoreTag, path); + didOpenDatabase = NO; + return; + } + [self createTableWithName:kTableOutgoingRmqMessages + command:kCreateTableOutgoingRmqMessages]; + + [self createTableWithName:kTableLastRmqId command:kCreateTableLastRmqId]; + [self createTableWithName:kTableS2DRmqIds command:kCreateTableS2DRmqIds]; + } else { + if (sqlite3_open([path UTF8String], &_database) != SQLITE_OK) { + FIRMessaging_FAIL(@"%@ Could not open rmq database: %@", kFCMRmqStoreTag, path); + didOpenDatabase = NO; + } else { + [self updateDbWithStringRmqID]; + } + } + + if (didOpenDatabase) { + [self createTableWithName:kTableSyncMessages command:kCreateTableSyncMessages]; + } +} + +- (void)updateDbWithStringRmqID { + [self createTableWithName:kTableS2DRmqIds command:kCreateTableS2DRmqIds]; + [self dropTableWithName:kOldTableS2DRmqIds]; +} + +#pragma mark - Insert + +- (BOOL)saveUnackedS2dMessageWithRmqId:(NSString *)rmqId { + NSString *insertFormat = @"INSERT INTO %@ (%@) VALUES (?)"; + NSString *insertSQL = [NSString stringWithFormat:insertFormat, + kTableS2DRmqIds, + kRmqIdColumn]; + sqlite3_stmt *insert_statement; + if (sqlite3_prepare_v2(_database, [insertSQL UTF8String], -1, &insert_statement, NULL) + != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(insert_statement, NO); + } + if (sqlite3_bind_text(insert_statement, + 1, + [rmqId UTF8String], + (int)[rmqId length], + SQLITE_STATIC) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(insert_statement, NO); + } + if (sqlite3_step(insert_statement) != SQLITE_DONE) { + _FIRMessagingRmqLogAndExit(insert_statement, NO); + } + sqlite3_finalize(insert_statement); + return YES; +} + +- (BOOL)saveMessageWithRmqId:(int64_t)rmqId + tag:(int8_t)tag + data:(NSData *)data + error:(NSError **)error { + NSString *insertFormat = @"INSERT INTO %@ (%@, %@, %@) VALUES (?, ?, ?)"; + NSString *insertSQL = [NSString stringWithFormat:insertFormat, + kTableOutgoingRmqMessages, // table + kRmqIdColumn, kProtobufTagColumn, kDataColumn /* columns */]; + sqlite3_stmt *insert_statement; + if (sqlite3_prepare_v2(_database, [insertSQL UTF8String], -1, &insert_statement, NULL) + != SQLITE_OK) { + if (error) { + *error = [NSError errorWithDomain:[NSString stringWithFormat:@"%s", sqlite3_errmsg(_database)] + code:sqlite3_errcode(_database) + userInfo:nil]; + } + _FIRMessagingRmqLogAndExit(insert_statement, NO); + } + if (sqlite3_bind_int64(insert_statement, 1, rmqId) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(insert_statement, NO); + } + if (sqlite3_bind_int(insert_statement, 2, tag) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(insert_statement, NO); + } + if (sqlite3_bind_blob(insert_statement, 3, [data bytes], (int)[data length], NULL) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(insert_statement, NO); + } + if (sqlite3_step(insert_statement) != SQLITE_DONE) { + _FIRMessagingRmqLogAndExit(insert_statement, NO); + } + + sqlite3_finalize(insert_statement); + return YES; +} + +- (int)deleteMessagesFromTable:(NSString *)tableName + withRmqIds:(NSArray *)rmqIds { + _FIRMessagingDevAssert([tableName isEqualToString:kTableOutgoingRmqMessages] || + [tableName isEqualToString:kTableLastRmqId] || + [tableName isEqualToString:kTableS2DRmqIds] || + [tableName isEqualToString:kTableSyncMessages], + @"%@: Invalid Table Name %@", kFCMRmqStoreTag, tableName); + + BOOL isRmqIDString = NO; + // RmqID is a string only for outgoing messages + if ([tableName isEqualToString:kTableS2DRmqIds] || + [tableName isEqualToString:kTableSyncMessages]) { + isRmqIDString = YES; + } + + NSMutableString *delete = [NSMutableString stringWithFormat:@"DELETE FROM %@ WHERE ", tableName]; + + NSString *toDeleteArgument = [NSString stringWithFormat:@"%@ = ? OR ", kRmqIdColumn]; + + int toDelete = (int)[rmqIds count]; + if (toDelete == 0) { + return 0; + } + int maxBatchSize = 100; + int start = 0; + int deleteCount = 0; + while (start < toDelete) { + + // construct the WHERE argument + int end = MIN(start + maxBatchSize, toDelete); + NSMutableString *whereArgument = [NSMutableString string]; + for (int i = start; i < end; i++) { + [whereArgument appendString:toDeleteArgument]; + } + // remove the last * OR * from argument + NSRange range = NSMakeRange([whereArgument length] -4, 4); + [whereArgument deleteCharactersInRange:range]; + NSString *deleteQuery = [NSString stringWithFormat:@"%@ %@", delete, whereArgument]; + + + // sqlite update + sqlite3_stmt *delete_statement; + if (sqlite3_prepare_v2(_database, [deleteQuery UTF8String], + -1, &delete_statement, NULL) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(delete_statement, 0); + } + + // bind values + int rmqIndex = 0; + int placeholderIndex = 1; // placeholders in sqlite3 start with 1 + for (NSString *rmqId in rmqIds) { // objectAtIndex: is O(n) -- would make it slow + if (rmqIndex < start) { + rmqIndex++; + continue; + } else if (rmqIndex >= end) { + break; + } else { + if (isRmqIDString) { + if (sqlite3_bind_text(delete_statement, + placeholderIndex, + [rmqId UTF8String], + (int)[rmqId length], + SQLITE_STATIC) != SQLITE_OK) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRmq2PersistentStore003, + @"Failed to bind rmqID %@", rmqId); + continue; + } + } else { + int64_t rmqIdValue = [rmqId longLongValue]; + sqlite3_bind_int64(delete_statement, placeholderIndex, rmqIdValue); + } + placeholderIndex++; + } + rmqIndex++; + } + if (sqlite3_step(delete_statement) != SQLITE_DONE) { + _FIRMessagingRmqLogAndExit(delete_statement, deleteCount); + } + sqlite3_finalize(delete_statement); + deleteCount += sqlite3_changes(_database); + start = end; + } + + // if we are here all of our sqlite queries should have succeeded + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRmq2PersistentStore004, + @"%@ Trying to delete %d s2D ID's, successfully deleted %d", + kFCMRmqStoreTag, toDelete, deleteCount); + return deleteCount; +} + +#pragma mark - Query + +- (int64_t)queryHighestRmqId { + NSString *queryFormat = @"SELECT %@ FROM %@ ORDER BY %@ DESC LIMIT %d"; + NSString *query = [NSString stringWithFormat:queryFormat, + kRmqIdColumn, // column + kTableOutgoingRmqMessages, // table + kRmqIdColumn, // order by column + 1]; // limit + + sqlite3_stmt *statement; + int64_t highestRmqId = 0; + if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, NULL) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(statement, highestRmqId); + } + if (sqlite3_step(statement) == SQLITE_ROW) { + highestRmqId = sqlite3_column_int64(statement, 0); + } + sqlite3_finalize(statement); + return highestRmqId; +} + +- (int64_t)queryLastRmqId { + NSString *queryFormat = @"SELECT %@ FROM %@ ORDER BY %@ DESC LIMIT %d"; + NSString *query = [NSString stringWithFormat:queryFormat, + kRmqIdColumn, // column + kTableLastRmqId, // table + kRmqIdColumn, // order by column + 1]; // limit + + sqlite3_stmt *statement; + int64_t lastRmqId = 0; + if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, NULL) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(statement, lastRmqId); + } + if (sqlite3_step(statement) == SQLITE_ROW) { + lastRmqId = sqlite3_column_int64(statement, 0); + } + sqlite3_finalize(statement); + return lastRmqId; +} + +- (BOOL)updateLastOutgoingRmqId:(int64_t)rmqID { + NSString *queryFormat = @"INSERT OR REPLACE INTO %@ (%@, %@) VALUES (?, ?)"; + NSString *query = [NSString stringWithFormat:queryFormat, + kTableLastRmqId, // table + kIdColumn, kRmqIdColumn]; // columns + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, NULL) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(statement, NO); + } + if (sqlite3_bind_int(statement, 1, 1) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(statement, NO); + } + if (sqlite3_bind_int64(statement, 2, rmqID) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(statement, NO); + } + if (sqlite3_step(statement) != SQLITE_DONE) { + _FIRMessagingRmqLogAndExit(statement, NO); + } + sqlite3_finalize(statement); + return YES; +} + +- (NSArray *)unackedS2dRmqIds { + NSString *queryFormat = @"SELECT %@ FROM %@ ORDER BY %@ ASC"; + NSString *query = [NSString stringWithFormat:queryFormat, + kRmqIdColumn, + kTableS2DRmqIds, + kRmqIdColumn]; + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, NULL) != SQLITE_OK) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRmq2PersistentStore005, + @"%@: Could not find s2d ids", kFCMRmqStoreTag); + _FIRMessagingRmqLogAndExit(statement, @[]); + } + NSMutableArray *rmqIDArray = [NSMutableArray array]; + while (sqlite3_step(statement) == SQLITE_ROW) { + const char *rmqID = (char *)sqlite3_column_text(statement, 0); + [rmqIDArray addObject:[NSString stringWithUTF8String:rmqID]]; + } + sqlite3_finalize(statement); + return rmqIDArray; +} + +#pragma mark - Scan + +- (void)scanOutgoingRmqMessagesWithHandler:(FCMOutgoingRmqMessagesTableHandler)handler { + static NSString *queryFormat = @"SELECT %@ FROM %@ WHERE %@ != 0 ORDER BY %@ ASC"; + NSString *query = [NSString stringWithFormat:queryFormat, + kOutgoingRmqMessagesColumns, // select (rmq_id, type, data) + kTableOutgoingRmqMessages, // from table + kRmqIdColumn, // where + kRmqIdColumn]; // order by + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, NULL) != SQLITE_OK) { + [self logError]; + sqlite3_finalize(statement); + return; + } + // can query sqlite3 for this but this is fine + const int rmqIdColumnNumber = 0; + const int typeColumnNumber = 1; + const int dataColumnNumber = 2; + while (sqlite3_step(statement) == SQLITE_ROW) { + int64_t rmqId = sqlite3_column_int64(statement, rmqIdColumnNumber); + int8_t type = sqlite3_column_int(statement, typeColumnNumber); + const void *bytes = sqlite3_column_blob(statement, dataColumnNumber); + int length = sqlite3_column_bytes(statement, dataColumnNumber); + _FIRMessagingDevAssert(bytes != NULL, + @"%@ Message with no data being stored in Rmq", + kFCMRmqStoreTag); + NSData *data = [NSData dataWithBytes:bytes length:length]; + handler(rmqId, type, data); + } + sqlite3_finalize(statement); +} + +#pragma mark - Sync Messages + +- (FIRMessagingPersistentSyncMessage *)querySyncMessageWithRmqID:(NSString *)rmqID { + _FIRMessagingDevAssert([rmqID length], @"Invalid rmqID key %@ to search in SYNC_RMQ", rmqID); + + NSString *queryFormat = @"SELECT %@ FROM %@ WHERE %@ = '%@'"; + NSString *query = [NSString stringWithFormat:queryFormat, + kSyncMessagesColumns, // SELECT (rmq_id, expiration_ts, apns_recv, mcs_recv) + kTableSyncMessages, // FROM sync_rmq + kRmqIdColumn, // WHERE rmq_id + rmqID]; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &stmt, NULL) != SQLITE_OK) { + [self logError]; + sqlite3_finalize(stmt); + return nil; + } + + const int rmqIDColumn = 0; + const int expirationTimestampColumn = 1; + const int apnsReceivedColumn = 2; + const int mcsReceivedColumn = 3; + + int count = 0; + FIRMessagingPersistentSyncMessage *persistentMessage; + + while (sqlite3_step(stmt) == SQLITE_ROW) { + NSString *rmqID = + [NSString stringWithUTF8String:(char *)sqlite3_column_text(stmt, rmqIDColumn)]; + int64_t expirationTimestamp = sqlite3_column_int64(stmt, expirationTimestampColumn); + BOOL apnsReceived = sqlite3_column_int(stmt, apnsReceivedColumn); + BOOL mcsReceived = sqlite3_column_int(stmt, mcsReceivedColumn); + + // create a new persistent message + persistentMessage = + [[FIRMessagingPersistentSyncMessage alloc] initWithRMQID:rmqID expirationTime:expirationTimestamp]; + persistentMessage.apnsReceived = apnsReceived; + persistentMessage.mcsReceived = mcsReceived; + + count++; + } + sqlite3_finalize(stmt); + + _FIRMessagingDevAssert(count <= 1, @"Found multiple messages in %@ with same RMQ ID", kTableSyncMessages); + return persistentMessage; +} + +- (BOOL)deleteSyncMessageWithRmqID:(NSString *)rmqID { + _FIRMessagingDevAssert([rmqID length], @"Invalid rmqID key %@ to delete in SYNC_RMQ", rmqID); + return [self deleteMessagesFromTable:kTableSyncMessages withRmqIds:@[rmqID]] > 0; +} + +- (int)deleteExpiredOrFinishedSyncMessages:(NSError *__autoreleasing *)error { + int64_t now = FIRMessagingCurrentTimestampInSeconds(); + NSString *deleteSQL = @"DELETE FROM %@ " + @"WHERE %@ < %lld OR " // expirationTime < now + @"(%@ = 1 AND %@ = 1)"; // apns_received = 1 AND mcs_received = 1 + NSString *query = [NSString stringWithFormat:deleteSQL, + kTableSyncMessages, + kSyncMessageExpirationTimestampColumn, + now, + kSyncMessageAPNSReceivedColumn, + kSyncMessageMCSReceivedColumn]; + + NSString *errorReason = @"Failed to save delete expired sync messages from store."; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &stmt, NULL) != SQLITE_OK) { + if (error) { + *error = [NSError fcm_errorWithCode:sqlite3_errcode(_database) + userInfo:@{ @"error" : errorReason }]; + } + _FIRMessagingRmqLogAndExit(stmt, 0); + } + + if (sqlite3_step(stmt) != SQLITE_DONE) { + if (error) { + *error = [NSError fcm_errorWithCode:sqlite3_errcode(_database) + userInfo:@{ @"error" : errorReason }]; + } + _FIRMessagingRmqLogAndExit(stmt, 0); + } + + sqlite3_finalize(stmt); + int deleteCount = sqlite3_changes(_database); + return deleteCount; +} + +- (BOOL)saveSyncMessageWithRmqID:(NSString *)rmqID + expirationTime:(int64_t)expirationTime + apnsReceived:(BOOL)apnsReceived + mcsReceived:(BOOL)mcsReceived + error:(NSError **)error { + _FIRMessagingDevAssert([rmqID length], @"Invalid nil message to persist to SYNC_RMQ"); + + NSString *insertFormat = @"INSERT INTO %@ (%@, %@, %@, %@) VALUES (?, ?, ?, ?)"; + NSString *insertSQL = [NSString stringWithFormat:insertFormat, + kTableSyncMessages, // Table name + kRmqIdColumn, // rmq_id + kSyncMessageExpirationTimestampColumn, // expiration_ts + kSyncMessageAPNSReceivedColumn, // apns_recv + kSyncMessageMCSReceivedColumn /* mcs_recv */]; + + sqlite3_stmt *stmt; + + if (sqlite3_prepare_v2(_database, [insertSQL UTF8String], -1, &stmt, NULL) != SQLITE_OK) { + if (error) { + *error = [NSError fcm_errorWithCode:sqlite3_errcode(_database) + userInfo:@{ @"error" : @"Failed to save sync message to store." }]; + } + _FIRMessagingRmqLogAndExit(stmt, NO); + } + + if (sqlite3_bind_text(stmt, 1, [rmqID UTF8String], (int)[rmqID length], NULL) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(stmt, NO); + } + + if (sqlite3_bind_int64(stmt, 2, expirationTime) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(stmt, NO); + } + + if (sqlite3_bind_int(stmt, 3, apnsReceived ? 1 : 0) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(stmt, NO); + } + + if (sqlite3_bind_int(stmt, 4, mcsReceived ? 1 : 0) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(stmt, NO); + } + + if (sqlite3_step(stmt) != SQLITE_DONE) { + _FIRMessagingRmqLogAndExit(stmt, NO); + } + + sqlite3_finalize(stmt); + return YES; +} + +- (BOOL)updateSyncMessageViaAPNSWithRmqID:(NSString *)rmqID + error:(NSError **)error { + return [self updateSyncMessageWithRmqID:rmqID + column:kSyncMessageAPNSReceivedColumn + value:YES + error:error]; +} + +- (BOOL)updateSyncMessageViaMCSWithRmqID:(NSString *)rmqID + error:(NSError *__autoreleasing *)error { + return [self updateSyncMessageWithRmqID:rmqID + column:kSyncMessageMCSReceivedColumn + value:YES + error:error]; +} + +- (BOOL)updateSyncMessageWithRmqID:(NSString *)rmqID + column:(NSString *)column + value:(BOOL)value + error:(NSError **)error { + _FIRMessagingDevAssert([column isEqualToString:kSyncMessageAPNSReceivedColumn] || + [column isEqualToString:kSyncMessageMCSReceivedColumn], + @"Invalid column name %@ for SYNC_RMQ", column); + NSString *queryFormat = @"UPDATE %@ " // Table name + @"SET %@ = %d " // column=value + @"WHERE %@ = ?"; // condition + NSString *query = [NSString stringWithFormat:queryFormat, + kTableSyncMessages, + column, + value ? 1 : 0, + kRmqIdColumn]; + sqlite3_stmt *stmt; + + if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &stmt, NULL) != SQLITE_OK) { + if (error) { + *error = [NSError fcm_errorWithCode:sqlite3_errcode(_database) + userInfo:@{ @"error" : @"Failed to update sync message"}]; + } + _FIRMessagingRmqLogAndExit(stmt, NO); + } + + if (sqlite3_bind_text(stmt, 1, [rmqID UTF8String], (int)[rmqID length], NULL) != SQLITE_OK) { + _FIRMessagingRmqLogAndExit(stmt, NO); + } + + if (sqlite3_step(stmt) != SQLITE_DONE) { + _FIRMessagingRmqLogAndExit(stmt, NO); + } + + sqlite3_finalize(stmt); + return YES; + +} + +#pragma mark - Private + +- (NSString *)lastErrorMessage { + return [NSString stringWithFormat:@"%s", sqlite3_errmsg(_database)]; +} + +- (int)lastErrorCode { + return sqlite3_errcode(_database); +} + +- (void)logError { + FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStore006, + @"%@ error: code (%d) message: %@", kFCMRmqStoreTag, [self lastErrorCode], + [self lastErrorMessage]); +} + +- (void)logErrorAndFinalizeStatement:(sqlite3_stmt *)stmt { + [self logError]; + sqlite3_finalize(stmt); +} + +@end diff --git a/Firebase/Messaging/FIRMessagingRmqManager.h b/Firebase/Messaging/FIRMessagingRmqManager.h new file mode 100644 index 0000000..ba48b98 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingRmqManager.h @@ -0,0 +1,190 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +@class GtalkDataMessageStanza; +@class GPBMessage; + +@class FIRMessagingPersistentSyncMessage; + +/** + * Called on each raw message. + */ +typedef void(^FIRMessagingRmqMessageHandler)(int64_t rmqId, int8_t tag, NSData *data); + +/** + * Called on each DataMessageStanza. + */ +typedef void(^FIRMessagingDataMessageHandler)(int64_t rmqId, GtalkDataMessageStanza *stanza); + +/** + * Used to scan through the rmq and perform actions on messages as required. + */ +@protocol FIRMessagingRmqScanner <NSObject> + +/** + * Scan the RMQ for outgoing messages and process them as required. + */ +- (void)scanWithRmqMessageHandler:(FIRMessagingRmqMessageHandler)rmqMessageHandler + dataMessageHandler:(FIRMessagingDataMessageHandler)dataMessageHandler; + +@end + +/** + * This manages the RMQ persistent store. + * + * The store is used to store all the S2D id's that were received by the client and were ACK'ed + * by us but the server hasn't confirmed the ACK. We don't delete these id's until the server + * ACK's us that they have received them. + * + * We also store the upstream messages(d2s) that were sent by the client. + * + * Also store the lastRMQId that was sent by us so that for a new connection being setup we don't + * duplicate RMQ Id's for the new messages. + */ +@interface FIRMessagingRmqManager : NSObject <FIRMessagingRmqScanner> + +// designated initializer +- (instancetype)initWithDatabaseName:(NSString *)databaseName; + +- (void)loadRmqId; + +/** + * Save an upstream message to RMQ. If the message send fails for some reason we would not + * lose the message since it would be saved in the RMQ. + * + * @param message The upstream message to be saved. + * @param error The error if any while saving the message else nil. + * + * @return YES if the message was successfully saved to RMQ else NO. + */ +- (BOOL)saveRmqMessage:(GPBMessage *)message error:(NSError **)error; + +/** + * Save Server to device message with the given RMQ-ID. + * + * @param rmqID The rmqID of the s2d message to save. + * + * @return YES if the save was successfull else NO. + */ +- (BOOL)saveS2dMessageWithRmqId:(NSString *)rmqID; + +/** + * A list of all unacked Server to device RMQ IDs. + * + * @return A list of unacked Server to Device RMQ ID's. All values are Strings. + */ +- (NSArray *)unackedS2dRmqIds; + +/** + * Removes the outgoing message from RMQ store. + * + * @param rmqId The rmqID to remove from the store. + * + * @return The number of messages deleted successfully. + */ +- (int)removeRmqMessagesWithRmqId:(NSString *)rmqId; + +/** + * Removes the messages with the given rmqIDs from RMQ store. + * + * @param rmqIds The lsit of rmqID's to remove from the store. + * + * @return The number of messages deleted successfully. + */ +- (int)removeRmqMessagesWithRmqIds:(NSArray *)rmqIds; + +/** + * Removes a list of downstream messages from the RMQ. + * + * @param s2dIds The list of messages ACK'ed by the server that we should remove + * from the RMQ store. + */ +- (void)removeS2dIds:(NSArray *)s2dIds; + +#pragma mark - Sync Messages + +/** + * Get persisted sync message with rmqID. + * + * @param rmqID The rmqID of the persisted sync message. + * + * @return A valid persistent sync message with the given rmqID if found in the RMQ else nil. + */ +- (FIRMessagingPersistentSyncMessage *)querySyncMessageWithRmqID:(NSString *)rmqID; + +/** + * Delete sync message with rmqID. + * + * @param rmqID The rmqID of the persisted sync message. + * + * @return YES if the message was successfully deleted else NO. + */ +- (BOOL)deleteSyncMessageWithRmqID:(NSString *)rmqID; + +/** + * Delete the expired sync messages from persisten store. Also deletes messages that have been + * delivered both via APNS and MCS. + * + * @param error The error if any while deleting the messages. + * + * @return The total number of messages that were deleted from the persistent store. + */ +- (int)deleteExpiredOrFinishedSyncMessages:(NSError **)error; + +/** + * Save sync message received by the device. + * + * @param rmqID The rmqID of the message received. + * @param expirationTime The expiration time of the sync message received. + * @param apnsReceived YES if the message was received via APNS else NO. + * @param mcsReceived YES if the message was received via MCS else NO. + * @param error The error if any while saving the sync message to persistent store. + * + * @return YES if the message save was successful else NO. + */ +- (BOOL)saveSyncMessageWithRmqID:(NSString *)rmqID + expirationTime:(int64_t)expirationTime + apnsReceived:(BOOL)apnsReceived + mcsReceived:(BOOL)mcsReceived + error:(NSError **)error; + +/** + * Update sync message received via APNS. + * + * @param rmqID The rmqID of the received message. + * @param error The error if any while updating the sync message. + * + * @return YES if the persistent sync message was successfully updated else NO. + */ +- (BOOL)updateSyncMessageViaAPNSWithRmqID:(NSString *)rmqID error:(NSError **)error; + +/** + * Update sync message received via MCS. + * + * @param rmqID The rmqID of the received message. + * @param error The error if any while updating the sync message. + * + * @return YES if the persistent sync message was successfully updated else NO. + */ +- (BOOL)updateSyncMessageViaMCSWithRmqID:(NSString *)rmqID error:(NSError **)error; + +#pragma mark - Testing + ++ (void)removeDatabaseWithName:(NSString *)dbName; + +@end diff --git a/Firebase/Messaging/FIRMessagingRmqManager.m b/Firebase/Messaging/FIRMessagingRmqManager.m new file mode 100644 index 0000000..de63a73 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingRmqManager.m @@ -0,0 +1,264 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingRmqManager.h" + +#import "Protos/GtalkCore.pbobjc.h" +#import "sqlite3.h" + +#import "FIRMessagingDefines.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingRmq2PersistentStore.h" +#import "FIRMessagingUtilities.h" + +#ifndef _FIRMessagingRmqLogAndExit +#define _FIRMessagingRmqLogAndExit(stmt, return_value) \ +do { \ + [self logErrorAndFinalizeStatement:stmt]; \ + return return_value; \ +} while(0) +#endif + +static NSString *const kFCMRmqTag = @"FIRMessagingRmq:"; + +@interface FIRMessagingRmqManager () + +@property(nonatomic, readwrite, strong) FIRMessagingRmq2PersistentStore *rmq2Store; +// map the category of an outgoing message with the number of messages for that category +// should always have two keys -- the app, gcm +@property(nonatomic, readwrite, strong) NSMutableDictionary *outstandingMessages; + +// Outgoing RMQ persistent id +@property(nonatomic, readwrite, assign) int64_t rmqId; + +@end + +@implementation FIRMessagingRmqManager + +- (instancetype)initWithDatabaseName:(NSString *)databaseName { + self = [super init]; + if (self) { + _FIRMessagingDevAssert([databaseName length] > 0, @"RMQ: Invalid rmq db name"); + _rmq2Store = [[FIRMessagingRmq2PersistentStore alloc] initWithDatabaseName:databaseName]; + _outstandingMessages = [NSMutableDictionary dictionaryWithCapacity:2]; + _rmqId = -1; + } + return self; +} + +- (void)loadRmqId { + if (self.rmqId >= 0) { + return; // already done + } + + [self loadInitialOutgoingPersistentId]; + if (self.outstandingMessages.count) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRmqManager000, + @"%@: outstanding categories %ld", kFCMRmqTag, + _FIRMessaging_UL(self.outstandingMessages.count)); + } +} + +/** + * Initialize the 'initial RMQ': + * - max ID of any message in the queue + * - if the queue is empty, stored value in separate DB. + * + * Stream acks will remove from RMQ, when we remove the highest message we keep track + * of its ID. + */ +- (void)loadInitialOutgoingPersistentId { + + // we shouldn't always trust the lastRmqId stored in the LastRmqId table, because + // we only save to the LastRmqId table once in a while (after getting the lastRmqId sent + // by the server after reconnect, and after getting a rmq ack from the server). The + // rmq message with the highest rmq id tells the real story, so check against that first. + + int64_t rmqId = [self queryHighestRmqId]; + if (rmqId == 0) { + rmqId = [self querylastRmqId]; + } + self.rmqId = rmqId + 1; +} + +#pragma mark - Save + +/** + * Save a message to RMQ2. Will populate the rmq2 persistent ID. + */ +- (BOOL)saveRmqMessage:(GPBMessage *)message + error:(NSError **)error { + // send using rmq2manager + // the wire format of rmq2 id is a string. However, we keep it as a long internally + // in the database. So only convert the id to string when preparing for sending over + // the wire. + NSString *rmq2Id = FIRMessagingGetRmq2Id(message); + if (![rmq2Id length]) { + int64_t rmqId = [self nextRmqId]; + rmq2Id = [NSString stringWithFormat:@"%lld", rmqId]; + FIRMessagingSetRmq2Id(message, rmq2Id); + } + FIRMessagingProtoTag tag = FIRMessagingGetTagForProto(message); + return [self saveMessage:message withRmqId:[rmq2Id integerValue] tag:tag error:error]; +} + +- (BOOL)saveMessage:(GPBMessage *)message + withRmqId:(int64_t)rmqId + tag:(int8_t)tag + error:(NSError **)error { + NSData *data = [message data]; + return [self.rmq2Store saveMessageWithRmqId:rmqId tag:tag data:data error:error]; +} + +/** + * This is called when we delete the largest outgoing message from queue. + */ +- (void)saveLastOutgoingRmqId:(int64_t)rmqID { + [self.rmq2Store updateLastOutgoingRmqId:rmqID]; +} + +- (BOOL)saveS2dMessageWithRmqId:(NSString *)rmqID { + return [self.rmq2Store saveUnackedS2dMessageWithRmqId:rmqID]; +} + +#pragma mark - Query + +- (int64_t)queryHighestRmqId { + return [self.rmq2Store queryHighestRmqId]; +} + +- (int64_t)querylastRmqId { + return [self.rmq2Store queryLastRmqId]; +} + +- (NSArray *)unackedS2dRmqIds { + return [self.rmq2Store unackedS2dRmqIds]; +} + +#pragma mark - FIRMessagingRMQScanner protocol + +/** + * We don't have a 'getMessages' method - it would require loading in memory + * the entire content body of all messages. + * + * Instead we iterate and call 'resend' for each message. + * + * This is called: + * - on connect MCS, to resend any outstanding messages + * - init + */ +- (void)scanWithRmqMessageHandler:(FIRMessagingRmqMessageHandler)rmqMessageHandler + dataMessageHandler:(FIRMessagingDataMessageHandler)dataMessageHandler { + // no need to scan database with no callbacks + if (rmqMessageHandler || dataMessageHandler) { + [self.rmq2Store scanOutgoingRmqMessagesWithHandler:^(int64_t rmqId, int8_t tag, NSData *data) { + if (rmqMessageHandler != nil) { + rmqMessageHandler(rmqId, tag, data); + } + if (dataMessageHandler != nil && kFIRMessagingProtoTagDataMessageStanza == tag) { + GPBMessage *proto = + [FIRMessagingGetClassForTag((FIRMessagingProtoTag)tag) parseFromData:data error:NULL]; + GtalkDataMessageStanza *stanza = (GtalkDataMessageStanza *)proto; + dataMessageHandler(rmqId, stanza); + } + }]; + } +} + +#pragma mark - Remove + +- (void)ackReceivedForRmqId:(NSString *)rmqId { + // TODO: Optional book-keeping +} + +- (int)removeRmqMessagesWithRmqId:(NSString *)rmqId { + return [self removeRmqMessagesWithRmqIds:@[rmqId]]; +} + +- (int)removeRmqMessagesWithRmqIds:(NSArray *)rmqIds { + if (![rmqIds count]) { + return 0; + } + for (NSString *rmqId in rmqIds) { + [self ackReceivedForRmqId:rmqId]; + } + int64_t maxRmqId = -1; + for (NSString *rmqId in rmqIds) { + int64_t rmqIdValue = [rmqId longLongValue]; + if (rmqIdValue > maxRmqId) { + maxRmqId = rmqIdValue; + } + } + maxRmqId++; + if (maxRmqId >= self.rmqId) { + [self saveLastOutgoingRmqId:maxRmqId]; + } + return [self.rmq2Store deleteMessagesFromTable:kTableOutgoingRmqMessages withRmqIds:rmqIds]; +} + +- (void)removeS2dIds:(NSArray *)s2dIds { + [self.rmq2Store deleteMessagesFromTable:kTableS2DRmqIds withRmqIds:s2dIds]; +} + +#pragma mark - Sync Messages + +// TODO: RMQManager should also have a cache for all the sync messages +// so we don't hit the DB each time. +- (FIRMessagingPersistentSyncMessage *)querySyncMessageWithRmqID:(NSString *)rmqID { + return [self.rmq2Store querySyncMessageWithRmqID:rmqID]; +} + +- (BOOL)deleteSyncMessageWithRmqID:(NSString *)rmqID { + return [self.rmq2Store deleteSyncMessageWithRmqID:rmqID]; +} + +- (int)deleteExpiredOrFinishedSyncMessages:(NSError **)error { + return [self.rmq2Store deleteExpiredOrFinishedSyncMessages:error]; +} + +- (BOOL)saveSyncMessageWithRmqID:(NSString *)rmqID + expirationTime:(int64_t)expirationTime + apnsReceived:(BOOL)apnsReceived + mcsReceived:(BOOL)mcsReceived + error:(NSError *__autoreleasing *)error { + return [self.rmq2Store saveSyncMessageWithRmqID:rmqID + expirationTime:expirationTime + apnsReceived:apnsReceived + mcsReceived:mcsReceived + error:error]; +} + +- (BOOL)updateSyncMessageViaAPNSWithRmqID:(NSString *)rmqID error:(NSError **)error { + return [self.rmq2Store updateSyncMessageViaAPNSWithRmqID:rmqID error:error]; +} + +- (BOOL)updateSyncMessageViaMCSWithRmqID:(NSString *)rmqID error:(NSError **)error { + return [self.rmq2Store updateSyncMessageViaMCSWithRmqID:rmqID error:error]; +} + +#pragma mark - Testing + ++ (void)removeDatabaseWithName:(NSString *)dbName { + [FIRMessagingRmq2PersistentStore removeDatabase:dbName]; +} + +#pragma mark - Private + +- (int64_t)nextRmqId { + return ++self.rmqId; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingSecureSocket.h b/Firebase/Messaging/FIRMessagingSecureSocket.h new file mode 100644 index 0000000..169f60e --- /dev/null +++ b/Firebase/Messaging/FIRMessagingSecureSocket.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +typedef NS_ENUM(NSUInteger, FIRMessagingSecureSocketState){ + kFIRMessagingSecureSocketNotOpen = 0, + kFIRMessagingSecureSocketOpening, + kFIRMessagingSecureSocketOpen, + kFIRMessagingSecureSocketClosing, + kFIRMessagingSecureSocketClosed, + kFIRMessagingSecureSocketError +}; + +@class FIRMessagingSecureSocket; + +@protocol FIRMessagingSecureSocketDelegate<NSObject> + +- (void)secureSocket:(FIRMessagingSecureSocket *)socket + didReceiveData:(NSData *)data + withTag:(int8_t)tag; +- (void)secureSocket:(FIRMessagingSecureSocket *)socket + didSendProtoWithTag:(int8_t)tag + rmqId:(NSString *)rmqId; +- (void)secureSocketDidConnect:(FIRMessagingSecureSocket *)socket; +- (void)didDisconnectWithSecureSocket:(FIRMessagingSecureSocket *)socket; + +@end + +/** + * This manages the input/output streams connected to the MCS server. Used to receive data from + * the server and send to it over the wire. + */ +@interface FIRMessagingSecureSocket : NSObject + +@property(nonatomic, readwrite, weak) id<FIRMessagingSecureSocketDelegate> delegate; +@property(nonatomic, readonly, assign) FIRMessagingSecureSocketState state; + +- (void)connectToHost:(NSString *)host port:(NSUInteger)port onRunLoop:(NSRunLoop *)runLoop; +- (void)disconnect; +- (void)sendData:(NSData *)data withTag:(int8_t)tag rmqId:(NSString *)rmqId; + +@end diff --git a/Firebase/Messaging/FIRMessagingSecureSocket.m b/Firebase/Messaging/FIRMessagingSecureSocket.m new file mode 100644 index 0000000..b7e8133 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingSecureSocket.m @@ -0,0 +1,448 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingSecureSocket.h" + +#import "GPBMessage.h" +#import "GPBCodedOutputStream.h" +#import "GPBUtilities.h" + +#import "FIRMessagingCodedInputStream.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingPacketQueue.h" + +static const NSUInteger kMaxBufferLength = 1024 * 1024; // 1M +static const NSUInteger kBufferLengthIncrement = 16 * 1024; // 16k +static const uint8_t kVersion = 40; +static const uint8_t kInvalidTag = -1; + +typedef NS_ENUM(NSUInteger, FIRMessagingSecureSocketReadResult) { + kFIRMessagingSecureSocketReadResultNone, + kFIRMessagingSecureSocketReadResultIncomplete, + kFIRMessagingSecureSocketReadResultCorrupt, + kFIRMessagingSecureSocketReadResultSuccess +}; + +static int32_t LogicalRightShift32(int32_t value, int32_t spaces) { + return (int32_t)((uint32_t)(value) >> spaces); +} + +static NSUInteger SerializedSize(int32_t value) { + NSUInteger bytes = 0; + while (YES) { + if ((value & ~0x7F) == 0) { + bytes += sizeof(uint8_t); + return bytes; + } else { + bytes += sizeof(uint8_t); + value = LogicalRightShift32(value, 7); + } + } +} + +@interface FIRMessagingSecureSocket() <NSStreamDelegate> + +@property(nonatomic, readwrite, assign) FIRMessagingSecureSocketState state; +@property(nonatomic, readwrite, strong) NSInputStream *inStream; +@property(nonatomic, readwrite, strong) NSOutputStream *outStream; + +@property(nonatomic, readwrite, strong) NSMutableData *inputBuffer; +@property(nonatomic, readwrite, assign) NSUInteger inputBufferLength; +@property(nonatomic, readwrite, strong) NSMutableData *outputBuffer; +@property(nonatomic, readwrite, assign) NSUInteger outputBufferLength; + +@property(nonatomic, readwrite, strong) FIRMessagingPacketQueue *packetQueue; +@property(nonatomic, readwrite, assign) BOOL isVersionSent; +@property(nonatomic, readwrite, assign) BOOL isVersionReceived; +@property(nonatomic, readwrite, assign) BOOL isInStreamOpen; +@property(nonatomic, readwrite, assign) BOOL isOutStreamOpen; + +@property(nonatomic, readwrite, strong) NSRunLoop *runLoop; +@property(nonatomic, readwrite, strong) NSString *currentRmqIdBeingSent; +@property(nonatomic, readwrite, assign) int8_t currentProtoTypeBeingSent; + +@end + +@implementation FIRMessagingSecureSocket + +- (instancetype)init { + self = [super init]; + if (self) { + _state = kFIRMessagingSecureSocketNotOpen; + _inputBuffer = [NSMutableData dataWithLength:kBufferLengthIncrement]; + _packetQueue = [[FIRMessagingPacketQueue alloc] init]; + _currentProtoTypeBeingSent = kInvalidTag; + } + return self; +} + +- (void)dealloc { + [self disconnect]; +} + +- (void)connectToHost:(NSString *)host + port:(NSUInteger)port + onRunLoop:(NSRunLoop *)runLoop { + _FIRMessagingDevAssert(host != nil, @"Invalid host"); + _FIRMessagingDevAssert(runLoop != nil, @"Invalid runloop"); + _FIRMessagingDevAssert(self.state == kFIRMessagingSecureSocketNotOpen, @"Socket is already connected"); + + if (!host || self.state != kFIRMessagingSecureSocketNotOpen) { + return; + } + + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket000, + @"Opening secure socket to FIRMessaging service"); + self.state = kFIRMessagingSecureSocketOpening; + self.runLoop = runLoop; + CFReadStreamRef inputStreamRef; + CFWriteStreamRef outputStreamRef; + CFStreamCreatePairWithSocketToHost(NULL, + (__bridge CFStringRef)host, + (int)port, + &inputStreamRef, + &outputStreamRef); + self.inStream = CFBridgingRelease(inputStreamRef); + self.outStream = CFBridgingRelease(outputStreamRef); + if (!self.inStream || !self.outStream) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket001, + @"Failed to initialize socket."); + return; + } + + self.isInStreamOpen = NO; + self.isOutStreamOpen = NO; + + BOOL isVOIPSocket = NO; + +#if FIRMessaging_PROBER + isVOIPSocket = YES; +#endif + + [self openStream:self.outStream isVOIPStream:isVOIPSocket]; + [self openStream:self.inStream isVOIPStream:isVOIPSocket]; +} + +- (void)disconnect { + if (self.state == kFIRMessagingSecureSocketClosing) { + return; + } + if (!self.inStream && !self.outStream) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket002, + @"The socket is not open or already closed."); + _FIRMessagingDevAssert(self.state == kFIRMessagingSecureSocketClosed || self.state == kFIRMessagingSecureSocketNotOpen, + @"Socket is already disconnected."); + return; + } + + self.state = kFIRMessagingSecureSocketClosing; + if (self.inStream) { + [self closeStream:self.inStream]; + self.inStream = nil; + } + if (self.outStream) { + [self closeStream:self.outStream]; + self.outStream = nil; + } + self.state = kFIRMessagingSecureSocketClosed; + [self.delegate didDisconnectWithSecureSocket:self]; +} + +- (void)sendData:(NSData *)data withTag:(int8_t)tag rmqId:(NSString *)rmqId { + [self.packetQueue push:[FIRMessagingPacket packetWithTag:tag rmqId:rmqId data:data]]; + if ([self.outStream hasSpaceAvailable]) { + [self performWrite]; + } +} + +#pragma mark - NSStreamDelegate + +- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode { + switch (eventCode) { + case NSStreamEventHasBytesAvailable: + if (self.state != kFIRMessagingSecureSocketOpen) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket003, + @"Try to read from socket that is not opened"); + return; + } + _FIRMessagingDevAssert(stream == self.inStream, @"Incorrect stream"); + if (![self performRead]) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket004, + @"Error occured when reading incoming stream"); + [self disconnect]; + } + break; + case NSStreamEventEndEncountered: + FIRMessagingLoggerDebug( + kFIRMessagingMessageCodeSecureSocket005, @"%@ end encountered", + stream == self.inStream + ? @"Input stream" + : (stream == self.outStream ? @"Output stream" : @"Unknown stream")); + [self disconnect]; + break; + case NSStreamEventOpenCompleted: + if (stream == self.inStream) { + self.isInStreamOpen = YES; + } else if (stream == self.outStream) { + self.isOutStreamOpen = YES; + } + if (self.isInStreamOpen && self.isOutStreamOpen) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket006, + @"Secure socket to FIRMessaging service opened"); + self.state = kFIRMessagingSecureSocketOpen; + [self.delegate secureSocketDidConnect:self]; + } + break; + case NSStreamEventErrorOccurred: { + FIRMessagingLoggerDebug( + kFIRMessagingMessageCodeSecureSocket007, @"%@ error occurred", + stream == self.inStream + ? @"Input stream" + : (stream == self.outStream ? @"Output stream" : @"Unknown stream")); + [self disconnect]; + break; + } + case NSStreamEventHasSpaceAvailable: + if (self.state != kFIRMessagingSecureSocketOpen) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket008, + @"Try to write to socket that is not opened"); + return; + } + _FIRMessagingDevAssert(stream == self.outStream, @"Incorrect stream"); + [self performWrite]; + break; + default: + break; + } +} + +#pragma mark - Private + +- (void)openStream:(NSStream *)stream isVOIPStream:(BOOL)isVOIPStream { + _FIRMessagingDevAssert(stream != nil, @"Invalid stream"); + _FIRMessagingDevAssert(self.runLoop != nil, @"Invalid runloop"); + + if (stream) { + _FIRMessagingDevAssert([stream streamStatus] == NSStreamStatusNotOpen, @"Stream already open"); + if ([stream streamStatus] != NSStreamStatusNotOpen) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket009, + @"stream should not be open."); + return; + } + [stream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL + forKey:NSStreamSocketSecurityLevelKey]; + if (isVOIPStream) { + [stream setProperty:NSStreamNetworkServiceTypeVoIP + forKey:NSStreamNetworkServiceType]; + } + stream.delegate = self; + [stream scheduleInRunLoop:self.runLoop forMode:NSDefaultRunLoopMode]; + [stream open]; + } +} + +- (void)closeStream:(NSStream *)stream { + _FIRMessagingDevAssert(stream != nil, @"Invalid stream"); + _FIRMessagingDevAssert(self.runLoop != nil, @"Invalid runloop"); + + if (stream) { + [stream close]; + [stream removeFromRunLoop:self.runLoop forMode:NSDefaultRunLoopMode]; + stream.delegate = nil; + } +} + +- (BOOL)performRead { + _FIRMessagingDevAssert(self.state == kFIRMessagingSecureSocketOpen, @"Socket should be open"); + + if (!self.isVersionReceived) { + self.isVersionReceived = YES; + uint8_t versionByte = 0; + NSInteger bytesRead = [self.inStream read:&versionByte maxLength:sizeof(uint8_t)]; + if (bytesRead != sizeof(uint8_t) || kVersion != versionByte) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket010, + @"Version do not match. Received %d, Expecting %d", versionByte, + kVersion); + return NO; + } + } + + while (YES) { + BOOL isInputBufferValid = [self.inputBuffer length] > 0; + _FIRMessagingDevAssert(isInputBufferValid, + @"Invalid input buffer size %lu. Used bytes length %lu, buffer content: %@", + _FIRMessaging_UL([self.inputBuffer length]), + _FIRMessaging_UL(self.inputBufferLength), + self.inputBuffer); + if (!isInputBufferValid) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket011, + @"Input buffer is not valid."); + return NO; + } + + if (![self.inStream hasBytesAvailable]) { + break; + } + + // try to read more data + uint8_t *unusedBufferPtr = (uint8_t *)self.inputBuffer.mutableBytes + self.inputBufferLength; + NSUInteger unusedBufferLength = [self.inputBuffer length] - self.inputBufferLength; + NSInteger bytesRead = [self.inStream read:unusedBufferPtr maxLength:unusedBufferLength]; + if (bytesRead <= 0) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket012, + @"Failed to read input stream. Bytes read %ld, Used buffer size %lu, " + @"Unused buffer size %lu", + _FIRMessaging_UL(bytesRead), _FIRMessaging_UL(self.inputBufferLength), + _FIRMessaging_UL(unusedBufferLength)); + break; + } + // did successfully read some more data + self.inputBufferLength += (NSUInteger)bytesRead; + + if ([self.inputBuffer length] <= self.inputBufferLength) { + // shouldn't be reading more than 1MB of data in one go + if ([self.inputBuffer length] + kBufferLengthIncrement > kMaxBufferLength) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket013, + @"Input buffer exceed 1M, disconnect socket"); + return NO; + } + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket014, + @"Input buffer limit exceeded. Used input buffer size %lu, " + @"Total input buffer size %lu. No unused buffer left. " + @"Increase buffer size.", + _FIRMessaging_UL(self.inputBufferLength), + _FIRMessaging_UL([self.inputBuffer length])); + [self.inputBuffer increaseLengthBy:kBufferLengthIncrement]; + _FIRMessagingDevAssert([self.inputBuffer length] > self.inputBufferLength, @"Invalid buffer size"); + } + + while (self.inputBufferLength > 0 && [self.inputBuffer length] > 0) { + _FIRMessagingDevAssert([self.inputBuffer length] >= self.inputBufferLength, + @"Buffer longer than length"); + NSRange inputRange = NSMakeRange(0, self.inputBufferLength); + size_t protoBytes = 0; + // read the actual proto data coming in + FIRMessagingSecureSocketReadResult readResult = + [self processCurrentInputBuffer:[self.inputBuffer subdataWithRange:inputRange] + outOffset:&protoBytes]; + // Corrupt data encountered, stop processing. + if (readResult == kFIRMessagingSecureSocketReadResultCorrupt) { + return NO; + // Incomplete data, keep trying to read by loading more from the stream. + } else if (readResult == kFIRMessagingSecureSocketReadResultIncomplete) { + break; + } + _FIRMessagingDevAssert(self.inputBufferLength >= protoBytes, @"More bytes than buffer can handle"); + // we have read (0, protoBytes) of data in the inputBuffer + if (protoBytes == self.inputBufferLength) { + // did completely read the buffer data can be reset for further processing + self.inputBufferLength = 0; + } else { + // delete processed bytes while maintaining the buffer size. + NSUInteger prevLength __unused = [self.inputBuffer length]; + // delete the processed bytes + [self.inputBuffer replaceBytesInRange:NSMakeRange(0, protoBytes) withBytes:NULL length:0]; + // reallocate more data + [self.inputBuffer increaseLengthBy:protoBytes]; + _FIRMessagingDevAssert([self.inputBuffer length] == prevLength, + @"Invalid input buffer size %lu. Used bytes length %lu, " + @"buffer content: %@", + _FIRMessaging_UL([self.inputBuffer length]), + _FIRMessaging_UL(self.inputBufferLength), + self.inputBuffer); + self.inputBufferLength -= protoBytes; + } + } + } + return YES; +} + +- (FIRMessagingSecureSocketReadResult)processCurrentInputBuffer:(NSData *)readData + outOffset:(size_t *)outOffset { + *outOffset = 0; + + FIRMessagingCodedInputStream *input = [[FIRMessagingCodedInputStream alloc] initWithData:readData]; + int8_t rawTag; + if (![input readTag:&rawTag]) { + return kFIRMessagingSecureSocketReadResultIncomplete; + } + int32_t length; + if (![input readLength:&length]) { + return kFIRMessagingSecureSocketReadResultIncomplete; + } + // NOTE tag can be zero for |HeartbeatPing|, and length can be zero for |Close| proto + _FIRMessagingDevAssert(rawTag >= 0 && length >= 0, @"Invalid tag or length"); + if (rawTag < 0 || length < 0) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket015, @"Buffer data corrupted."); + return kFIRMessagingSecureSocketReadResultCorrupt; + } + NSData *data = [input readDataWithLength:(uint32_t)length]; + if (data == nil) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSecureSocket016, + @"Incomplete data, buffered data length %ld, expected length %d", + _FIRMessaging_UL(self.inputBufferLength), length); + return kFIRMessagingSecureSocketReadResultIncomplete; + } + [self.delegate secureSocket:self didReceiveData:data withTag:rawTag]; + *outOffset = input.offset; + return kFIRMessagingSecureSocketReadResultSuccess; +} + +- (void)performWrite { + _FIRMessagingDevAssert(self.state == kFIRMessagingSecureSocketOpen, @"Invalid socket state"); + + if (!self.isVersionSent) { + self.isVersionSent = YES; + uint8_t versionByte = kVersion; + [self.outStream write:&versionByte maxLength:sizeof(uint8_t)]; + } + + while (!self.packetQueue.isEmpty && self.outStream.hasSpaceAvailable) { + if (self.outputBuffer.length == 0) { + // serialize new packets only when the output buffer is flushed. + FIRMessagingPacket *packet = [self.packetQueue pop]; + self.currentRmqIdBeingSent = packet.rmqId; + self.currentProtoTypeBeingSent = packet.tag; + NSUInteger length = SerializedSize(packet.tag) + + SerializedSize((int)packet.data.length) + packet.data.length; + self.outputBuffer = [NSMutableData dataWithLength:length]; + GPBCodedOutputStream *output = [GPBCodedOutputStream streamWithData:self.outputBuffer]; + [output writeRawVarint32:packet.tag]; + [output writeBytesNoTag:packet.data]; + self.outputBufferLength = 0; + } + + // flush the output buffer. + NSInteger written = [self.outStream write:self.outputBuffer.bytes + self.outputBufferLength + maxLength:self.outputBuffer.length - self.outputBufferLength]; + if (written <= 0) { + continue; + } + self.outputBufferLength += (NSUInteger)written; + if (self.outputBufferLength >= self.outputBuffer.length) { + self.outputBufferLength = 0; + self.outputBuffer = nil; + [self.delegate secureSocket:self + didSendProtoWithTag:self.currentProtoTypeBeingSent + rmqId:self.currentRmqIdBeingSent]; + self.currentRmqIdBeingSent = nil; + self.currentProtoTypeBeingSent = kInvalidTag; + } + } +} + +@end diff --git a/Firebase/Messaging/FIRMessagingSyncMessageManager.h b/Firebase/Messaging/FIRMessagingSyncMessageManager.h new file mode 100644 index 0000000..3d30bdb --- /dev/null +++ b/Firebase/Messaging/FIRMessagingSyncMessageManager.h @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +@class FIRMessagingRmqManager; + +/** + * Handle sync messages being received both via MCS and APNS. + */ +@interface FIRMessagingSyncMessageManager : NSObject + +/** + * Initialize sync message manager. + * + * @param rmqManager The RMQ manager on the client. + * + * @return Sync message manager. + */ +- (instancetype)initWithRmqManager:(FIRMessagingRmqManager *)rmqManager; + +/** + * Remove expired sync message from persistent store. Also removes messages that have + * been received both via APNS and MCS. + */ +- (void)removeExpiredSyncMessages; + +/** + * App did recive a sync message via APNS. + * + * @param message The sync message received. + * + * @return YES if the message is a duplicate of an already received sync message else NO. + */ +- (BOOL)didReceiveAPNSSyncMessage:(NSDictionary *)message; + +/** + * App did receive a sync message via MCS. + * + * @param message The sync message received. + * + * @return YES if the message is a duplicate of an already received sync message else NO. + */ +- (BOOL)didReceiveMCSSyncMessage:(NSDictionary *)message; + +@end diff --git a/Firebase/Messaging/FIRMessagingSyncMessageManager.m b/Firebase/Messaging/FIRMessagingSyncMessageManager.m new file mode 100644 index 0000000..1257b02 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingSyncMessageManager.m @@ -0,0 +1,147 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingSyncMessageManager.h" + +#import "FIRMessagingConstants.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingPersistentSyncMessage.h" +#import "FIRMessagingRmqManager.h" +#import "FIRMessagingUtilities.h" + +static const int64_t kDefaultSyncMessageTTL = 4 * 7 * 24 * 60 * 60; // 4 weeks +// 4 MB of free space is required to persist Sync messages +static const uint64_t kMinFreeDiskSpaceInMB = 1; + +@interface FIRMessagingSyncMessageManager() + +@property(nonatomic, readwrite, strong) FIRMessagingRmqManager *rmqManager; + +@end + +@implementation FIRMessagingSyncMessageManager + +- (instancetype)init { + FIRMessagingInvalidateInitializer(); +} + +- (instancetype)initWithRmqManager:(FIRMessagingRmqManager *)rmqManager { + _FIRMessagingDevAssert(rmqManager, @"Invalid nil rmq manager while initalizing sync message manager"); + self = [super init]; + if (self) { + _rmqManager = rmqManager; + } + return self; +} + +- (void)removeExpiredSyncMessages { + NSError *error; + int deleteCount = [self.rmqManager deleteExpiredOrFinishedSyncMessages:&error]; + if (error) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager000, + @"Error while deleting expired sync messages %@", error); + } else if (deleteCount > 0) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSyncMessageManager001, + @"Successfully deleted %d sync messages from store", deleteCount); + } +} + +- (BOOL)didReceiveAPNSSyncMessage:(NSDictionary *)message { + return [self didReceiveSyncMessage:message viaAPNS:YES viaMCS:NO]; +} + +- (BOOL)didReceiveMCSSyncMessage:(NSDictionary *)message { + return [self didReceiveSyncMessage:message viaAPNS:NO viaMCS:YES]; +} + +- (BOOL)didReceiveSyncMessage:(NSDictionary *)message + viaAPNS:(BOOL)viaAPNS + viaMCS:(BOOL)viaMCS { + NSString *rmqID = message[kFIRMessagingMessageIDKey]; + _FIRMessagingDevAssert([rmqID length], @"Invalid nil rmqID for message"); + if (![rmqID length]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager002, + @"Invalid nil rmqID for sync message."); + return NO; + } + + FIRMessagingPersistentSyncMessage *persistentMessage = + [self.rmqManager querySyncMessageWithRmqID:rmqID]; + + NSError *error; + if (!persistentMessage) { + + // Do not persist the new message if we don't have enough disk space + uint64_t freeDiskSpace = FIRMessagingGetFreeDiskSpaceInMB(); + if (freeDiskSpace < kMinFreeDiskSpaceInMB) { + return NO; + } + + int64_t expirationTime = [[self class] expirationTimeForSyncMessage:message]; + if (![self.rmqManager saveSyncMessageWithRmqID:rmqID + expirationTime:expirationTime + apnsReceived:viaAPNS + mcsReceived:viaMCS + error:&error]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager003, + @"Failed to save sync message with rmqID %@", rmqID); + } else { + FIRMessagingLoggerInfo(kFIRMessagingMessageCodeSyncMessageManager004, + @"Added sync message to cache: %@", rmqID); + } + return NO; + } + + if (viaAPNS && !persistentMessage.apnsReceived) { + persistentMessage.apnsReceived = YES; + if (![self.rmqManager updateSyncMessageViaAPNSWithRmqID:rmqID error:&error]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager005, + @"Failed to update APNS state for sync message %@", rmqID); + } + } else if (viaMCS && !persistentMessage.mcsReceived) { + persistentMessage.mcsReceived = YES; + if (![self.rmqManager updateSyncMessageViaMCSWithRmqID:rmqID error:&error]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager006, + @"Failed to update MCS state for sync message %@", rmqID); + } + } + + // Received message via both ways we can safely delete it. + if (persistentMessage.apnsReceived && persistentMessage.mcsReceived) { + if (![self.rmqManager deleteSyncMessageWithRmqID:rmqID]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager007, + @"Failed to delete sync message %@", rmqID); + } else { + FIRMessagingLoggerInfo(kFIRMessagingMessageCodeSyncMessageManager008, + @"Successfully deleted sync message from cache %@", rmqID); + } + } + + // Already received this message either via MCS or APNS. + return YES; +} + ++ (int64_t)expirationTimeForSyncMessage:(NSDictionary *)message { + int64_t ttl = kDefaultSyncMessageTTL; + if (message[kFIRMessagingMessageSyncMessageTTLKey]) { + ttl = [message[kFIRMessagingMessageSyncMessageTTLKey] longLongValue]; + } + int64_t currentTime = FIRMessagingCurrentTimestampInSeconds(); + return currentTime + ttl; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingTopicOperation.h b/Firebase/Messaging/FIRMessagingTopicOperation.h new file mode 100644 index 0000000..e4bbde8 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingTopicOperation.h @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +#import "FIRMessagingCheckinService.h" +#import "FIRMessagingTopicsCommon.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * An asynchronous NSOperation subclass which performs a single network request for a topic + * subscription operation. Once completed, it calls its provided completion handler. + */ +@interface FIRMessagingTopicOperation : NSOperation + +@property(nonatomic, readonly, copy) NSString *topic; +@property(nonatomic, readonly, assign) FIRMessagingTopicAction action; +@property(nonatomic, readonly, copy) NSString *token; +@property(nonatomic, readonly, copy, nullable) NSDictionary *options; +@property(nonatomic, readonly, strong) FIRMessagingCheckinService *checkinService; + +- (instancetype)initWithTopic:(NSString *)topic + action:(FIRMessagingTopicAction)action + token:(NSString *)token + options:(nullable NSDictionary *)options + checkinService:(FIRMessagingCheckinService *)checkinService + completion:(FIRMessagingTopicOperationCompletion)completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Messaging/FIRMessagingTopicOperation.m b/Firebase/Messaging/FIRMessagingTopicOperation.m new file mode 100644 index 0000000..955c4a6 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingTopicOperation.m @@ -0,0 +1,246 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingTopicOperation.h" + +#import "FIRMessagingCheckinService.h" +#import "FIRMessagingDefines.h" +#import "FIRMessagingLogger.h" +#import "FIRMessagingUtilities.h" +#import "NSError+FIRMessaging.h" + +#define DEBUG_LOG_SUBSCRIPTION_OPERATION_DURATIONS 0 + +static NSString *const kFIRMessagingSubscribeServerHost = + @"https://iid.googleapis.com/iid/register"; + +NSString *FIRMessagingSubscriptionsServer() { + static NSString *serverHost = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSDictionary *environment = [[NSProcessInfo processInfo] environment]; + NSString *customServerHost = environment[@"FCM_SERVER_ENDPOINT"]; + if (customServerHost.length) { + serverHost = customServerHost; + } else { + serverHost = kFIRMessagingSubscribeServerHost; + } + }); + return serverHost; +} + +@interface FIRMessagingTopicOperation () { + BOOL _isFinished; + BOOL _isExecuting; +} + +@property(nonatomic, readwrite, copy) NSString *topic; +@property(nonatomic, readwrite, assign) FIRMessagingTopicAction action; +@property(nonatomic, readwrite, copy) NSString *token; +@property(nonatomic, readwrite, copy) NSDictionary *options; +@property(nonatomic, readwrite, strong) FIRMessagingCheckinService *checkinService; +@property(nonatomic, readwrite, copy) FIRMessagingTopicOperationCompletion completion; + +@property(atomic, strong) NSURLSessionDataTask *dataTask; + +@end + +@implementation FIRMessagingTopicOperation + ++ (NSURLSession *)sharedSession { + static NSURLSession *subscriptionOperationSharedSession; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + config.timeoutIntervalForResource = 60.0f; // 1 minute + subscriptionOperationSharedSession = [NSURLSession sessionWithConfiguration:config]; + subscriptionOperationSharedSession.sessionDescription = @"com.google.fcm.topics.session"; + }); + return subscriptionOperationSharedSession; +} + +- (instancetype)initWithTopic:(NSString *)topic + action:(FIRMessagingTopicAction)action + token:(NSString *)token + options:(NSDictionary *)options + checkinService:(FIRMessagingCheckinService *)checkinService + completion:(FIRMessagingTopicOperationCompletion)completion { + if (self = [super init]) { + _topic = topic; + _action = action; + _token = token; + _checkinService = checkinService; + _completion = completion; + + _isExecuting = NO; + _isFinished = NO; + } + return self; +} + +- (void)dealloc { + _topic = nil; + _token = nil; + _checkinService = nil; + _completion = nil; +} + +- (BOOL)isAsynchronous { + return YES; +} + +- (BOOL)isExecuting { + return _isExecuting; +} + +- (void)setExecuting:(BOOL)executing { + [self willChangeValueForKey:@"isExecuting"]; + _isExecuting = executing; + [self didChangeValueForKey:@"isExecuting"]; +} + +- (BOOL)isFinished { + return _isFinished; +} + +- (void)setFinished:(BOOL)finished { + [self willChangeValueForKey:@"isFinished"]; + _isFinished = finished; + [self didChangeValueForKey:@"isFinished"]; +} + +- (void)start { + if (self.isCancelled) { + [self finishWithResult:FIRMessagingTopicOperationResultCancelled error:nil]; + return; + } + + [self setExecuting:YES]; + + [self performSubscriptionChange]; +} + +- (void)finishWithResult:(FIRMessagingTopicOperationResult)result error:(NSError *)error { + // Add a check to prevent this finish from being called more than once. + if (self.isFinished) { + return; + } + self.dataTask = nil; + if (self.completion) { + self.completion(result, error); + } + + [self setExecuting:NO]; + [self setFinished:YES]; +} + +- (void)cancel { + [super cancel]; + [self.dataTask cancel]; + [self finishWithResult:FIRMessagingTopicOperationResultCancelled error:nil]; +} + +- (void)performSubscriptionChange { + + NSURL *url = [NSURL URLWithString:FIRMessagingSubscriptionsServer()]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + NSString *appIdentifier = FIRMessagingAppIdentifier(); + NSString *deviceAuthID = self.checkinService.deviceAuthID; + NSString *secretToken = self.checkinService.secretToken; + NSString *authString = [NSString stringWithFormat:@"AidLogin %@:%@", deviceAuthID, secretToken]; + [request setValue:authString forHTTPHeaderField:@"Authorization"]; + [request setValue:appIdentifier forHTTPHeaderField:@"app"]; + [request setValue:self.checkinService.versionInfo forHTTPHeaderField:@"info"]; + + NSMutableString *content = [NSMutableString stringWithFormat: + @"sender=%@&app=%@&device=%@&" + @"app_ver=%@&X-gcm.topic=%@&X-scope=%@", + self.token, + appIdentifier, + deviceAuthID, + FIRMessagingCurrentAppVersion(), + self.topic, + self.topic]; + + if (self.action == FIRMessagingTopicActionUnsubscribe) { + [content appendString:@"&delete=true"]; + } + + FIRMessagingLoggerInfo(kFIRMessagingMessageCodeTopicOption000, @"Topic subscription request: %@", + content); + + request.HTTPBody = [content dataUsingEncoding:NSUTF8StringEncoding]; + [request setHTTPMethod:@"POST"]; + +#if DEBUG_LOG_SUBSCRIPTION_OPERATION_DURATIONS + NSDate *start = [NSDate date]; +#endif + + FIRMessaging_WEAKIFY(self) + void(^requestHandler)(NSData *, NSURLResponse *, NSError *) = + ^(NSData *data, NSURLResponse *URLResponse, NSError *error) { + FIRMessaging_STRONGIFY(self) + if (error) { + // Our operation could have been cancelled, which would result in our data task's error being + // NSURLErrorCancelled + if (error.code == NSURLErrorCancelled) { + // We would only have been cancelled in the -cancel method, which will call finish for us + // so just return and do nothing. + return; + } + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTopicOption001, + @"Device registration HTTP fetch error. Error Code: %ld", + _FIRMessaging_L(error.code)); + [self finishWithResult:FIRMessagingTopicOperationResultError error:error]; + return; + } + NSString *response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (response.length == 0) { + [self finishWithResult:FIRMessagingTopicOperationResultError + error:[NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeUnknown]]; + return; + } + NSArray *parts = [response componentsSeparatedByString:@"="]; + _FIRMessagingDevAssert(parts.count, @"Invalid registration response"); + if (![parts[0] isEqualToString:@"token"] || parts.count <= 1) { + FIRMessagingLoggerDebug(kFIRMessagingMessageCodeTopicOption002, + @"Invalid registration request, response"); + [self finishWithResult:FIRMessagingTopicOperationResultError + error:[NSError errorWithFCMErrorCode:kFIRMessagingErrorCodeUnknown]]; + return; + } +#if DEBUG_LOG_SUBSCRIPTION_OPERATION_DURATIONS + NSTimeInterval duration = -[start timeIntervalSinceNow]; + FIRMessagingLoggerDebug(@"%@ change took %.2fs", self.topic, duration); +#endif + [self finishWithResult:FIRMessagingTopicOperationResultSucceeded error:nil]; + + }; + + NSURLSession *urlSession = [FIRMessagingTopicOperation sharedSession]; + + self.dataTask = [urlSession dataTaskWithRequest:request completionHandler:requestHandler]; + NSString *description; + if (_action == FIRMessagingTopicActionSubscribe) { + description = [NSString stringWithFormat:@"com.google.fcm.topics.subscribe: %@", _topic]; + } else { + description = [NSString stringWithFormat:@"com.google.fcm.topics.unsubscribe: %@", _topic]; + } + self.dataTask.taskDescription = description; + [self.dataTask resume]; +} + +@end diff --git a/Firebase/Messaging/FIRMessagingTopicsCommon.h b/Firebase/Messaging/FIRMessagingTopicsCommon.h new file mode 100644 index 0000000..50d8906 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingTopicsCommon.h @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +/** + * Represents the action taken on a subscription topic. + */ +typedef NS_ENUM(NSInteger, FIRMessagingTopicAction) { + FIRMessagingTopicActionSubscribe, + FIRMessagingTopicActionUnsubscribe +}; + +/** + * Represents the possible results of a topic operation. + */ +typedef NS_ENUM(NSInteger, FIRMessagingTopicOperationResult) { + FIRMessagingTopicOperationResultSucceeded, + FIRMessagingTopicOperationResultError, + FIRMessagingTopicOperationResultCancelled, +}; + +/** + * Callback to invoke once the HTTP call to FIRMessaging backend for updating + * subscription finishes. + * + * @param result The result of the operation. If the result is + * FIRMessagingTopicOperationResultError, the error parameter will be + * non-nil. + * @param error The error which occurred while updating the subscription topic + * on the FIRMessaging server. This will be nil in case the operation + * was successful, or if the operation was cancelled. + */ +typedef void(^FIRMessagingTopicOperationCompletion) + (FIRMessagingTopicOperationResult result, NSError * _Nullable error); + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Messaging/FIRMessagingUtilities.h b/Firebase/Messaging/FIRMessagingUtilities.h new file mode 100644 index 0000000..ed9dc83 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingUtilities.h @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +typedef NS_ENUM(int8_t, FIRMessagingProtoTag) { + kFIRMessagingProtoTagInvalid = -1, + kFIRMessagingProtoTagHeartbeatPing = 0, + kFIRMessagingProtoTagHeartbeatAck = 1, + kFIRMessagingProtoTagLoginRequest = 2, + kFIRMessagingProtoTagLoginResponse = 3, + kFIRMessagingProtoTagClose = 4, + kFIRMessagingProtoTagIqStanza = 7, + kFIRMessagingProtoTagDataMessageStanza = 8, +}; + +@class GPBMessage; + +#pragma mark - Protocol Buffers + +FOUNDATION_EXPORT FIRMessagingProtoTag FIRMessagingGetTagForProto(GPBMessage *protoClass); +FOUNDATION_EXPORT Class FIRMessagingGetClassForTag(FIRMessagingProtoTag tag); + +#pragma mark - MCS + +FOUNDATION_EXPORT NSString *FIRMessagingGetRmq2Id(GPBMessage *proto); +FOUNDATION_EXPORT void FIRMessagingSetRmq2Id(GPBMessage *proto, NSString *pID); +FOUNDATION_EXPORT int FIRMessagingGetLastStreamId(GPBMessage *proto); +FOUNDATION_EXPORT void FIRMessagingSetLastStreamId(GPBMessage *proto, int sid); + +#pragma mark - Time + +FOUNDATION_EXPORT int64_t FIRMessagingCurrentTimestampInSeconds(); +FOUNDATION_EXPORT int64_t FIRMessagingCurrentTimestampInMilliseconds(); + +#pragma mark - App Info + +FOUNDATION_EXPORT NSString *FIRMessagingCurrentAppVersion(); +FOUNDATION_EXPORT NSString *FIRMessagingAppIdentifier(); + +FOUNDATION_EXPORT uint64_t FIRMessagingGetFreeDiskSpaceInMB(); diff --git a/Firebase/Messaging/FIRMessagingUtilities.m b/Firebase/Messaging/FIRMessagingUtilities.m new file mode 100644 index 0000000..13c7a05 --- /dev/null +++ b/Firebase/Messaging/FIRMessagingUtilities.m @@ -0,0 +1,173 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingUtilities.h" + +#import "Protos/GtalkCore.pbobjc.h" + +#import "FIRMessagingLogger.h" + +// Convert the macro to a string +#define STR_EXPAND(x) #x +#define STR(x) STR_EXPAND(x) + +static const uint64_t kBytesToMegabytesDivisor = 1024 * 1024LL; + +#pragma mark - Protocol Buffers + +FIRMessagingProtoTag FIRMessagingGetTagForProto(GPBMessage *proto) { + if ([proto isKindOfClass:[GtalkHeartbeatPing class]]) { + return kFIRMessagingProtoTagHeartbeatPing; + } else if ([proto isKindOfClass:[GtalkHeartbeatAck class]]) { + return kFIRMessagingProtoTagHeartbeatAck; + } else if ([proto isKindOfClass:[GtalkLoginRequest class]]) { + return kFIRMessagingProtoTagLoginRequest; + } else if ([proto isKindOfClass:[GtalkLoginResponse class]]) { + return kFIRMessagingProtoTagLoginResponse; + } else if ([proto isKindOfClass:[GtalkClose class]]) { + return kFIRMessagingProtoTagClose; + } else if ([proto isKindOfClass:[GtalkIqStanza class]]) { + return kFIRMessagingProtoTagIqStanza; + } else if ([proto isKindOfClass:[GtalkDataMessageStanza class]]) { + return kFIRMessagingProtoTagDataMessageStanza; + } + return kFIRMessagingProtoTagInvalid; +} + +Class FIRMessagingGetClassForTag(FIRMessagingProtoTag tag) { + switch (tag) { + case kFIRMessagingProtoTagHeartbeatPing: + return GtalkHeartbeatPing.class; + case kFIRMessagingProtoTagHeartbeatAck: + return GtalkHeartbeatAck.class; + case kFIRMessagingProtoTagLoginRequest: + return GtalkLoginRequest.class; + case kFIRMessagingProtoTagLoginResponse: + return GtalkLoginResponse.class; + case kFIRMessagingProtoTagClose: + return GtalkClose.class; + case kFIRMessagingProtoTagIqStanza: + return GtalkIqStanza.class; + case kFIRMessagingProtoTagDataMessageStanza: + return GtalkDataMessageStanza.class; + case kFIRMessagingProtoTagInvalid: + return NSNull.class; + } + return NSNull.class; +} + +#pragma mark - MCS + +NSString *FIRMessagingGetRmq2Id(GPBMessage *proto) { + if ([proto isKindOfClass:[GtalkIqStanza class]]) { + if (((GtalkIqStanza *)proto).hasPersistentId) { + return ((GtalkIqStanza *)proto).persistentId; + } + } else if ([proto isKindOfClass:[GtalkDataMessageStanza class]]) { + if (((GtalkDataMessageStanza *)proto).hasPersistentId) { + return ((GtalkDataMessageStanza *)proto).persistentId; + } + } + return nil; +} + +void FIRMessagingSetRmq2Id(GPBMessage *proto, NSString *pID) { + if ([proto isKindOfClass:[GtalkIqStanza class]]) { + ((GtalkIqStanza *)proto).persistentId = pID; + } else if ([proto isKindOfClass:[GtalkDataMessageStanza class]]) { + ((GtalkDataMessageStanza *)proto).persistentId = pID; + } +} + +int FIRMessagingGetLastStreamId(GPBMessage *proto) { + if ([proto isKindOfClass:[GtalkIqStanza class]]) { + if (((GtalkIqStanza *)proto).hasLastStreamIdReceived) { + return ((GtalkIqStanza *)proto).lastStreamIdReceived; + } + } else if ([proto isKindOfClass:[GtalkDataMessageStanza class]]) { + if (((GtalkDataMessageStanza *)proto).hasLastStreamIdReceived) { + return ((GtalkDataMessageStanza *)proto).lastStreamIdReceived; + } + } else if ([proto isKindOfClass:[GtalkHeartbeatPing class]]) { + if (((GtalkHeartbeatPing *)proto).hasLastStreamIdReceived) { + return ((GtalkHeartbeatPing *)proto).lastStreamIdReceived; + } + } else if ([proto isKindOfClass:[GtalkHeartbeatAck class]]) { + if (((GtalkHeartbeatAck *)proto).hasLastStreamIdReceived) { + return ((GtalkHeartbeatAck *)proto).lastStreamIdReceived; + } + } + return -1; +} + +void FIRMessagingSetLastStreamId(GPBMessage *proto, int sid) { + if ([proto isKindOfClass:[GtalkIqStanza class]]) { + ((GtalkIqStanza *)proto).lastStreamIdReceived = sid; + } else if ([proto isKindOfClass:[GtalkDataMessageStanza class]]) { + ((GtalkDataMessageStanza *)proto).lastStreamIdReceived = sid; + } else if ([proto isKindOfClass:[GtalkHeartbeatPing class]]) { + ((GtalkHeartbeatPing *)proto).lastStreamIdReceived = sid; + } else if ([proto isKindOfClass:[GtalkHeartbeatAck class]]) { + ((GtalkHeartbeatAck *)proto).lastStreamIdReceived = sid; + } +} + +#pragma mark - Time + +int64_t FIRMessagingCurrentTimestampInSeconds() { + return (int64_t)[[NSDate date] timeIntervalSince1970]; +} + +int64_t FIRMessagingCurrentTimestampInMilliseconds() { + return (int64_t)(FIRMessagingCurrentTimestampInSeconds() * 1000.0); +} + +#pragma mark - App Info + +NSString *FIRMessagingCurrentAppVersion() { + NSString *version = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; + if (![version length]) { + FIRMessagingLoggerError(kFIRMessagingMessageCodeUtilities000, + @"Could not find current app version"); + return @""; + } + return version; +} + +NSString *FIRMessagingAppIdentifier() { + return [[NSBundle mainBundle] bundleIdentifier]; +} + +uint64_t FIRMessagingGetFreeDiskSpaceInMB() { + NSError *error; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + + NSDictionary *attributesMap = + [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] + error:&error]; + if (attributesMap) { + uint64_t totalSizeInBytes __unused = [attributesMap[NSFileSystemSize] longLongValue]; + uint64_t freeSizeInBytes = [attributesMap[NSFileSystemFreeSize] longLongValue]; + FIRMessagingLoggerDebug( + kFIRMessagingMessageCodeUtilities001, @"Device has capacity %llu MB with %llu MB free.", + totalSizeInBytes / kBytesToMegabytesDivisor, freeSizeInBytes / kBytesToMegabytesDivisor); + return ((double)freeSizeInBytes) / kBytesToMegabytesDivisor; + } else { + FIRMessagingLoggerError(kFIRMessagingMessageCodeUtilities002, + @"Error in retreiving device's free memory %@", error); + return 0; + } +} diff --git a/Firebase/Messaging/FIRMessagingVersionUtilities.h b/Firebase/Messaging/FIRMessagingVersionUtilities.h new file mode 100644 index 0000000..df7cebe --- /dev/null +++ b/Firebase/Messaging/FIRMessagingVersionUtilities.h @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +/** + * Parsing utility for FIRMessaging Library versions. FIRMessaging Library follows semantic versioning. + * This provides utilities to parse the library versions to enable features and do + * updates based on appropriate library versions. + * + * Some example semantic versions are 1.0.1, 2.1.0, 2.1.1, 2.2.0-alpha1, 2.2.1-beta1 + */ + +FOUNDATION_EXPORT NSString *FIRMessagingCurrentLibraryVersion(); +/// Returns the current Major version of FIRMessaging library. +FOUNDATION_EXPORT int FIRMessagingCurrentLibraryVersionMajor(); +/// Returns the current Minor version of FIRMessaging library. +FOUNDATION_EXPORT int FIRMessagingCurrentLibraryVersionMinor(); +/// Returns the current Patch version of FIRMessaging library. +FOUNDATION_EXPORT int FIRMessagingCurrentLibraryVersionPatch(); +/// Returns YES if current library version is `beta` else NO. +FOUNDATION_EXPORT BOOL FIRMessagingCurrentLibraryVersionIsBeta(); diff --git a/Firebase/Messaging/FIRMessagingVersionUtilities.m b/Firebase/Messaging/FIRMessagingVersionUtilities.m new file mode 100644 index 0000000..e0f922c --- /dev/null +++ b/Firebase/Messaging/FIRMessagingVersionUtilities.m @@ -0,0 +1,87 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessagingVersionUtilities.h" + +#import "FIRMessagingDefines.h" + +// Convert the macro to a string +#define STR_EXPAND(x) #x +#define STR(x) STR_EXPAND(x) + +static NSString *const kSemanticVersioningSeparator = @"."; +static NSString *const kBetaVersionPrefix = @"-beta"; + +static NSString *libraryVersion; +static int majorVersion; +static int minorVersion; +static int patchVersion; +static int betaVersion; + +void FIRMessagingParseCurrentLibraryVersion() { + static NSArray *allVersions; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSMutableString *daylightVersion = [NSMutableString stringWithUTF8String:STR(FIRMessaging_LIB_VERSION)]; + // Parse versions + // major, minor, patch[-beta#] + allVersions = [daylightVersion componentsSeparatedByString:kSemanticVersioningSeparator]; + _FIRMessagingDevAssert(allVersions.count == 3, @"Invalid versioning of FIRMessaging library"); + if (allVersions.count == 3) { + majorVersion = [allVersions[0] intValue]; + minorVersion = [allVersions[1] intValue]; + + // Parse patch and beta versions + NSArray *patchAndBetaVersion = + [allVersions[2] componentsSeparatedByString:kBetaVersionPrefix]; + _FIRMessagingDevAssert(patchAndBetaVersion.count <= 2, @"Invalid versioning of FIRMessaging library"); + if (patchAndBetaVersion.count == 2) { + patchVersion = [patchAndBetaVersion[0] intValue]; + betaVersion = [patchAndBetaVersion[1] intValue]; + } else if (patchAndBetaVersion.count == 1) { + patchVersion = [patchAndBetaVersion[0] intValue]; + } + } + + // Copy library version + libraryVersion = [daylightVersion copy]; + }); +} + +NSString *FIRMessagingCurrentLibraryVersion() { + FIRMessagingParseCurrentLibraryVersion(); + return libraryVersion; +} + +int FIRMessagingCurrentLibraryVersionMajor() { + FIRMessagingParseCurrentLibraryVersion(); + return majorVersion; +} + +int FIRMessagingCurrentLibraryVersionMinor() { + FIRMessagingParseCurrentLibraryVersion(); + return minorVersion; +} + +int FIRMessagingCurrentLibraryVersionPatch() { + FIRMessagingParseCurrentLibraryVersion(); + return patchVersion; +} + +BOOL FIRMessagingCurrentLibraryVersionIsBeta() { + FIRMessagingParseCurrentLibraryVersion(); + return betaVersion > 0; +} diff --git a/Firebase/Messaging/FIRMessaging_Private.h b/Firebase/Messaging/FIRMessaging_Private.h new file mode 100644 index 0000000..0c35179 --- /dev/null +++ b/Firebase/Messaging/FIRMessaging_Private.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessaging.h" + +@class FIRMessagingClient; +@class FIRMessagingPubSub; + +typedef NS_ENUM(int8_t, FIRMessagingNetworkStatus) { + kFIRMessagingReachabilityNotReachable = 0, + kFIRMessagingReachabilityReachableViaWiFi, + kFIRMessagingReachabilityReachableViaWWAN, +}; + +@interface FIRMessagingRemoteMessage () + +@property(nonatomic, strong) NSDictionary *appData; + +@end + +@interface FIRMessaging () + +#pragma mark - Private API + +- (NSString *)defaultFcmToken; +- (FIRMessagingClient *)client; +- (FIRMessagingPubSub *)pubsub; + +// Create a sample message to be sent over the wire using FIRMessaging. Look at +// FIRMessagingService.h to see what each param signifies. ++ (NSMutableDictionary *)createFIRMessagingMessageWithMessage:(NSDictionary *)message + to:(NSString *)to + withID:(NSString *)msgID + timeToLive:(int64_t)ttl + delay:(int)delay; + +- (BOOL)isNetworkAvailable; +- (FIRMessagingNetworkStatus)networkType; + +// Set the APNS token for FCM. +- (void)setAPNSToken:(NSData *)apnsToken error:(NSError *)error; + +@end diff --git a/Firebase/Messaging/FirebaseMessaging.h b/Firebase/Messaging/FirebaseMessaging.h new file mode 100644 index 0000000..ef081c9 --- /dev/null +++ b/Firebase/Messaging/FirebaseMessaging.h @@ -0,0 +1,17 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessaging.h" diff --git a/Firebase/Messaging/FirebaseMessaging.podspec b/Firebase/Messaging/FirebaseMessaging.podspec new file mode 100644 index 0000000..4bcbebb --- /dev/null +++ b/Firebase/Messaging/FirebaseMessaging.podspec @@ -0,0 +1,41 @@ +# This podspec is not intended to be deployed. It is solely for the static +# library framework build process at +# https://github.com/firebase/firebase-ios-sdk/tree/master/BuildFrameworks + +Pod::Spec.new do |s| + s.name = 'FirebaseMessaging' + s.version = '2.0.0' + s.summary = 'Firebase Open Source Libraries for iOS.' + + s.description = <<-DESC +Simplify your iOS development, grow your user base, and monetize more effectively with Firebase. + DESC + + s.homepage = 'https://firebase.google.com' + s.license = { :type => 'Apache', :file => '../../LICENSE' } + s.authors = 'Google, Inc.' + + # NOTE that the FirebaseDev pod is neither publicly deployed nor yet interchangeable with the + # Firebase pod + s.source = { :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => s.version.to_s } + s.social_media_url = 'https://twitter.com/Firebase' + s.ios.deployment_target = '7.0' + + s.source_files = '**/*.[mh]' + s.requires_arc = '*.m' + s.public_header_files = + 'Public/FirebaseMessaging.h', + 'Public/FIRMessaging.h' + + s.library = 'sqlite3' + s.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => + '$(inherited) ' + + 'GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1 ' + + 'FIRMessaging_LIB_VERSION=' + String(s.version) + } + s.framework = 'AddressBook' + s.framework = 'SystemConfiguration' +# s.dependency 'FirebaseDev/Core' + s.dependency 'GoogleToolboxForMac/Logger', '~> 2.1' + s.dependency 'Protobuf', '~> 3.1' +end diff --git a/Firebase/Messaging/InternalHeaders/FIRMessagingInternalUtilities.h b/Firebase/Messaging/InternalHeaders/FIRMessagingInternalUtilities.h new file mode 100644 index 0000000..d6a1639 --- /dev/null +++ b/Firebase/Messaging/InternalHeaders/FIRMessagingInternalUtilities.h @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Google + * + * 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. + */ + +/// @file FIRMessagingInternalUtilities.h +/// +/// Internal Class Names and Methods that other libs can query at runtime. + +/// FIRMessaging Class that responds to the FIRMessaging SDK version selector. +/// Verify at runtime if the class exists and implements the +/// required method. +static NSString *const kFIRMessagingSDKClassString = @"FIRMessaging"; + +/// FIRMessaging selector that returns the current FIRMessaging library version. +static NSString *const kFIRMessagingSDKVersionSelectorString = @"FIRMessagingSDKVersion"; + +/// FIRMessaging selector that returns the current device locale. +static NSString *const kFIRMessagingSDKLocaleSelectorString = @"FIRMessagingSDKCurrentLocale"; diff --git a/Firebase/Messaging/NSDictionary+FIRMessaging.h b/Firebase/Messaging/NSDictionary+FIRMessaging.h new file mode 100644 index 0000000..fe14451 --- /dev/null +++ b/Firebase/Messaging/NSDictionary+FIRMessaging.h @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +@interface NSDictionary (FIRMessaging) + +/** + * Returns a string representation for the given dictionary. Assumes that all + * keys and values are strings. + * + * @return A string representation of all keys and values in the dictionary. + * The returned string is not pretty-printed. + */ +- (NSString *)fcm_string; + +/** + * Check if the dictionary has any non-string keys or values. + * + * @return YES if the dictionary has any non-string keys or values else NO. + */ +- (BOOL)fcm_hasNonStringKeysOrValues; + +/** + * Trims all (key, value) pair in a dictionary that are not strings. + * + * @return A new copied dictionary with all the non-string keys or values + * removed from the original dictionary. + */ +- (NSDictionary *)fcm_trimNonStringValues; + +@end diff --git a/Firebase/Messaging/NSDictionary+FIRMessaging.m b/Firebase/Messaging/NSDictionary+FIRMessaging.m new file mode 100644 index 0000000..8df22ab --- /dev/null +++ b/Firebase/Messaging/NSDictionary+FIRMessaging.m @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Google + * + * 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 "NSDictionary+FIRMessaging.h" + +@implementation NSDictionary (FIRMessaging) + +- (NSString *)fcm_string { + NSMutableString *dictAsString = [NSMutableString string]; + NSString *separator = @"|"; + for (id key in self) { + id value = self[key]; + if ([key isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) { + [dictAsString appendFormat:@"%@:%@%@", key, value, separator]; + } + } + // remove the last separator + if ([dictAsString length]) { + [dictAsString deleteCharactersInRange:NSMakeRange(dictAsString.length - 1, 1)]; + } + return [dictAsString copy]; +} + +- (BOOL)fcm_hasNonStringKeysOrValues { + for (id key in self) { + id value = self[key]; + if (![key isKindOfClass:[NSString class]] || ![value isKindOfClass:[NSString class]]) { + return YES; + } + } + return NO; +} + +- (NSDictionary *)fcm_trimNonStringValues { + NSMutableDictionary *trimDictionary = + [NSMutableDictionary dictionaryWithCapacity:self.count]; + for (id key in self) { + id value = self[key]; + if ([key isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) { + trimDictionary[(NSString *)key] = value; + } + } + return trimDictionary; +} + +@end diff --git a/Firebase/Messaging/NSError+FIRMessaging.h b/Firebase/Messaging/NSError+FIRMessaging.h new file mode 100644 index 0000000..9b1e214 --- /dev/null +++ b/Firebase/Messaging/NSError+FIRMessaging.h @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + +FOUNDATION_EXPORT NSString *const kFIRMessagingDomain; + +typedef NS_ENUM(NSUInteger, FIRMessagingInternalErrorCode) { + // Unknown error. + kFIRMessagingErrorCodeUnknown = 0, + + // HTTP related errors. + kFIRMessagingErrorCodeAuthentication = 1, + kFIRMessagingErrorCodeNoAccess = 2, + kFIRMessagingErrorCodeTimeout = 3, + kFIRMessagingErrorCodeNetwork = 4, + + // Another operation is in progress. + kFIRMessagingErrorCodeOperationInProgress = 5, + + // Failed to perform device check in. + kFIRMessagingErrorCodeRegistrarFailedToCheckIn = 6, + + kFIRMessagingErrorCodeInvalidRequest = 7, + + // FIRMessaging generic errors + kFIRMessagingErrorCodeMissingDeviceID = 501, + + // upstream send errors + kFIRMessagingErrorServiceNotAvailable = 1001, + kFIRMessagingErrorInvalidParameters = 1002, + kFIRMessagingErrorMissingTo = 1003, + kFIRMessagingErrorSave = 1004, + kFIRMessagingErrorSizeExceeded = 1005, + // Future Send Errors + + // MCS errors + // Already connected with MCS + kFIRMessagingErrorCodeAlreadyConnected = 2001, + + // PubSub errors + kFIRMessagingErrorCodePubSubAlreadySubscribed = 3001, + kFIRMessagingErrorCodePubSubAlreadyUnsubscribed = 3002, + kFIRMessagingErrorCodePubSubInvalidTopic = 3003, + kFIRMessagingErrorCodePubSubFIRMessagingNotSetup = 3004, +}; + +@interface NSError (FIRMessaging) + +@property(nonatomic, readonly) FIRMessagingInternalErrorCode fcmErrorCode; + ++ (NSError *)errorWithFCMErrorCode:(FIRMessagingInternalErrorCode)fcmErrorCode; ++ (NSError *)fcm_errorWithCode:(NSInteger)code userInfo:(NSDictionary *)userInfo; + +@end diff --git a/Firebase/Messaging/NSError+FIRMessaging.m b/Firebase/Messaging/NSError+FIRMessaging.m new file mode 100644 index 0000000..e4b8736 --- /dev/null +++ b/Firebase/Messaging/NSError+FIRMessaging.m @@ -0,0 +1,35 @@ +/* + * Copyright 2017 Google + * + * 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 "NSError+FIRMessaging.h" + +NSString *const kFIRMessagingDomain = @"com.google.fcm"; + +@implementation NSError (FIRMessaging) + +- (FIRMessagingInternalErrorCode)fcmErrorCode { + return (FIRMessagingInternalErrorCode)self.code; +} + ++ (NSError *)errorWithFCMErrorCode:(FIRMessagingInternalErrorCode)fcmErrorCode { + return [NSError errorWithDomain:kFIRMessagingDomain code:fcmErrorCode userInfo:nil]; +} + ++ (NSError *)fcm_errorWithCode:(NSInteger)code userInfo:(NSDictionary *)userInfo { + return [NSError errorWithDomain:kFIRMessagingDomain code:code userInfo:userInfo]; +} + +@end diff --git a/Firebase/Messaging/Protos/GtalkCore.pbobjc.h b/Firebase/Messaging/Protos/GtalkCore.pbobjc.h new file mode 100644 index 0000000..d4c8c8c --- /dev/null +++ b/Firebase/Messaging/Protos/GtalkCore.pbobjc.h @@ -0,0 +1,1344 @@ +/* + * Copyright 2017 Google + * + * 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. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: buzz/mobile/proto/gtalk_core.proto + +// This CPP symbol can be defined to use imports that match up to the framework +// imports needed when using CocoaPods. +#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS) + #define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0 +#endif + +#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS + #import <Protobuf/GPBProtocolBuffers.h> +#else + #import "GPBProtocolBuffers.h" +#endif + +#if GOOGLE_PROTOBUF_OBJC_VERSION < 30002 +#error This file was generated by a newer version of protoc which is incompatible with your Protocol Buffer library sources. +#endif +#if 30002 < GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION +#error This file was generated by an older version of protoc which is incompatible with your Protocol Buffer library sources. +#endif + +// @@protoc_insertion_point(imports) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +CF_EXTERN_C_BEGIN + +@class GtalkAppData; +@class GtalkCellTower; +@class GtalkClientEvent; +@class GtalkErrorInfo; +@class GtalkExtension; +@class GtalkHeartbeatConfig; +@class GtalkHeartbeatStat; +@class GtalkPresenceStanza; +@class GtalkSetting; + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Enum GtalkLoginRequest_AuthService + +typedef GPB_ENUM(GtalkLoginRequest_AuthService) { + GtalkLoginRequest_AuthService_Mail = 0, + GtalkLoginRequest_AuthService_AndroidCloudToDeviceMessage = 1, + GtalkLoginRequest_AuthService_AndroidId = 2, +}; + +GPBEnumDescriptor *GtalkLoginRequest_AuthService_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkLoginRequest_AuthService_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkMessageStanza_MessageType + +typedef GPB_ENUM(GtalkMessageStanza_MessageType) { + GtalkMessageStanza_MessageType_Normal = 0, + GtalkMessageStanza_MessageType_Chat = 1, + GtalkMessageStanza_MessageType_Groupchat = 2, + GtalkMessageStanza_MessageType_Headline = 3, + GtalkMessageStanza_MessageType_Error = 4, +}; + +GPBEnumDescriptor *GtalkMessageStanza_MessageType_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkMessageStanza_MessageType_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkPresenceStanza_PresenceType + +typedef GPB_ENUM(GtalkPresenceStanza_PresenceType) { + GtalkPresenceStanza_PresenceType_Unavailable = 0, + GtalkPresenceStanza_PresenceType_Subscribe = 1, + GtalkPresenceStanza_PresenceType_Subscribed = 2, + GtalkPresenceStanza_PresenceType_Unsubscribe = 3, + GtalkPresenceStanza_PresenceType_Unsubscribed = 4, + GtalkPresenceStanza_PresenceType_Probe = 5, + GtalkPresenceStanza_PresenceType_Error = 6, +}; + +GPBEnumDescriptor *GtalkPresenceStanza_PresenceType_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkPresenceStanza_PresenceType_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkPresenceStanza_ShowType + +typedef GPB_ENUM(GtalkPresenceStanza_ShowType) { + GtalkPresenceStanza_ShowType_Away = 0, + GtalkPresenceStanza_ShowType_Chat = 1, + GtalkPresenceStanza_ShowType_Dnd = 2, + GtalkPresenceStanza_ShowType_Xa = 3, +}; + +GPBEnumDescriptor *GtalkPresenceStanza_ShowType_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkPresenceStanza_ShowType_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkPresenceStanza_ClientType + +typedef GPB_ENUM(GtalkPresenceStanza_ClientType) { + GtalkPresenceStanza_ClientType_Mobile = 0, + GtalkPresenceStanza_ClientType_Android = 1, +}; + +GPBEnumDescriptor *GtalkPresenceStanza_ClientType_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkPresenceStanza_ClientType_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkPresenceStanza_CapabilitiesFlags + +typedef GPB_ENUM(GtalkPresenceStanza_CapabilitiesFlags) { + GtalkPresenceStanza_CapabilitiesFlags_HasVoiceV1 = 1, + GtalkPresenceStanza_CapabilitiesFlags_HasVideoV1 = 2, + GtalkPresenceStanza_CapabilitiesFlags_HasCameraV1 = 4, + GtalkPresenceStanza_CapabilitiesFlags_HasPmucV1 = 8, +}; + +GPBEnumDescriptor *GtalkPresenceStanza_CapabilitiesFlags_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkPresenceStanza_CapabilitiesFlags_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkBatchPresenceStanza_Type + +typedef GPB_ENUM(GtalkBatchPresenceStanza_Type) { + GtalkBatchPresenceStanza_Type_Get = 0, + GtalkBatchPresenceStanza_Type_Set = 1, +}; + +GPBEnumDescriptor *GtalkBatchPresenceStanza_Type_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkBatchPresenceStanza_Type_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkIqStanza_IqType + +typedef GPB_ENUM(GtalkIqStanza_IqType) { + GtalkIqStanza_IqType_Get = 0, + GtalkIqStanza_IqType_Set = 1, + GtalkIqStanza_IqType_Result = 2, + GtalkIqStanza_IqType_Error = 3, +}; + +GPBEnumDescriptor *GtalkIqStanza_IqType_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkIqStanza_IqType_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkClientEvent_Type + +typedef GPB_ENUM(GtalkClientEvent_Type) { + GtalkClientEvent_Type_Unknown = 0, + GtalkClientEvent_Type_DiscardedEvents = 1, + GtalkClientEvent_Type_FailedConnection = 2, + GtalkClientEvent_Type_SuccessfulConnection = 3, +}; + +GPBEnumDescriptor *GtalkClientEvent_Type_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkClientEvent_Type_IsValidValue(int32_t value); + +#pragma mark - GtalkGtalkCoreRoot + +/** + * Exposes the extension registry for this file. + * + * The base class provides: + * @code + * + (GPBExtensionRegistry *)extensionRegistry; + * @endcode + * which is a @c GPBExtensionRegistry that includes all the extensions defined by + * this file and all files that it depends on. + **/ +@interface GtalkGtalkCoreRoot : GPBRootObject +@end + +#pragma mark - GtalkHeartbeatPing + +typedef GPB_ENUM(GtalkHeartbeatPing_FieldNumber) { + GtalkHeartbeatPing_FieldNumber_StreamId = 1, + GtalkHeartbeatPing_FieldNumber_LastStreamIdReceived = 2, + GtalkHeartbeatPing_FieldNumber_Status = 3, + GtalkHeartbeatPing_FieldNumber_CellTower = 4, + GtalkHeartbeatPing_FieldNumber_IntervalMs = 5, +}; + +@interface GtalkHeartbeatPing : GPBMessage + + +@property(nonatomic, readwrite) int32_t streamId; + +@property(nonatomic, readwrite) BOOL hasStreamId; + +@property(nonatomic, readwrite) int32_t lastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL hasLastStreamIdReceived; + +@property(nonatomic, readwrite) int64_t status; + +@property(nonatomic, readwrite) BOOL hasStatus; + +@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower; +/** Test to see if @c cellTower has been set. */ +@property(nonatomic, readwrite) BOOL hasCellTower; + + +@property(nonatomic, readwrite) int32_t intervalMs; + +@property(nonatomic, readwrite) BOOL hasIntervalMs; +@end + +#pragma mark - GtalkHeartbeatAck + +typedef GPB_ENUM(GtalkHeartbeatAck_FieldNumber) { + GtalkHeartbeatAck_FieldNumber_StreamId = 1, + GtalkHeartbeatAck_FieldNumber_LastStreamIdReceived = 2, + GtalkHeartbeatAck_FieldNumber_Status = 3, + GtalkHeartbeatAck_FieldNumber_CellTower = 4, + GtalkHeartbeatAck_FieldNumber_IntervalMs = 5, +}; + +@interface GtalkHeartbeatAck : GPBMessage + + +@property(nonatomic, readwrite) int32_t streamId; + +@property(nonatomic, readwrite) BOOL hasStreamId; + +@property(nonatomic, readwrite) int32_t lastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL hasLastStreamIdReceived; + +@property(nonatomic, readwrite) int64_t status; + +@property(nonatomic, readwrite) BOOL hasStatus; + +@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower; +/** Test to see if @c cellTower has been set. */ +@property(nonatomic, readwrite) BOOL hasCellTower; + + +@property(nonatomic, readwrite) int32_t intervalMs; + +@property(nonatomic, readwrite) BOOL hasIntervalMs; +@end + +#pragma mark - GtalkErrorInfo + +typedef GPB_ENUM(GtalkErrorInfo_FieldNumber) { + GtalkErrorInfo_FieldNumber_Code = 1, + GtalkErrorInfo_FieldNumber_Message = 2, + GtalkErrorInfo_FieldNumber_Type = 3, + GtalkErrorInfo_FieldNumber_Extension = 4, +}; + +@interface GtalkErrorInfo : GPBMessage + + +@property(nonatomic, readwrite) int32_t code; + +@property(nonatomic, readwrite) BOOL hasCode; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *message; +/** Test to see if @c message has been set. */ +@property(nonatomic, readwrite) BOOL hasMessage; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *type; +/** Test to see if @c type has been set. */ +@property(nonatomic, readwrite) BOOL hasType; + + +@property(nonatomic, readwrite, strong, null_resettable) GtalkExtension *extension; +/** Test to see if @c extension has been set. */ +@property(nonatomic, readwrite) BOOL hasExtension; + +@end + +#pragma mark - GtalkSetting + +typedef GPB_ENUM(GtalkSetting_FieldNumber) { + GtalkSetting_FieldNumber_Name = 1, + GtalkSetting_FieldNumber_Value = 2, +}; + +@interface GtalkSetting : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *name; +/** Test to see if @c name has been set. */ +@property(nonatomic, readwrite) BOOL hasName; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *value; +/** Test to see if @c value has been set. */ +@property(nonatomic, readwrite) BOOL hasValue; + +@end + +#pragma mark - GtalkHeartbeatStat + +typedef GPB_ENUM(GtalkHeartbeatStat_FieldNumber) { + GtalkHeartbeatStat_FieldNumber_Ip = 1, + GtalkHeartbeatStat_FieldNumber_Timeout = 2, + GtalkHeartbeatStat_FieldNumber_IntervalMs = 3, +}; + +@interface GtalkHeartbeatStat : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *ip; +/** Test to see if @c ip has been set. */ +@property(nonatomic, readwrite) BOOL hasIp; + + +@property(nonatomic, readwrite) BOOL timeout; + +@property(nonatomic, readwrite) BOOL hasTimeout; + +@property(nonatomic, readwrite) int32_t intervalMs; + +@property(nonatomic, readwrite) BOOL hasIntervalMs; +@end + +#pragma mark - GtalkHeartbeatConfig + +typedef GPB_ENUM(GtalkHeartbeatConfig_FieldNumber) { + GtalkHeartbeatConfig_FieldNumber_UploadStat = 1, + GtalkHeartbeatConfig_FieldNumber_Ip = 2, + GtalkHeartbeatConfig_FieldNumber_IntervalMs = 3, +}; + +@interface GtalkHeartbeatConfig : GPBMessage + + +@property(nonatomic, readwrite) BOOL uploadStat; + +@property(nonatomic, readwrite) BOOL hasUploadStat; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *ip; +/** Test to see if @c ip has been set. */ +@property(nonatomic, readwrite) BOOL hasIp; + + +@property(nonatomic, readwrite) int32_t intervalMs; + +@property(nonatomic, readwrite) BOOL hasIntervalMs; +@end + +#pragma mark - GtalkLoginRequest + +typedef GPB_ENUM(GtalkLoginRequest_FieldNumber) { + GtalkLoginRequest_FieldNumber_Id_p = 1, + GtalkLoginRequest_FieldNumber_Domain = 2, + GtalkLoginRequest_FieldNumber_User = 3, + GtalkLoginRequest_FieldNumber_Resource = 4, + GtalkLoginRequest_FieldNumber_AuthToken = 5, + GtalkLoginRequest_FieldNumber_DeviceId = 6, + GtalkLoginRequest_FieldNumber_LastRmqId = 7, + GtalkLoginRequest_FieldNumber_SettingArray = 8, + GtalkLoginRequest_FieldNumber_Compress = 9, + GtalkLoginRequest_FieldNumber_ReceivedPersistentIdArray = 10, + GtalkLoginRequest_FieldNumber_IncludeStreamIds = 11, + GtalkLoginRequest_FieldNumber_HeartbeatStat = 13, + GtalkLoginRequest_FieldNumber_UseRmq2 = 14, + GtalkLoginRequest_FieldNumber_AccountId = 15, + GtalkLoginRequest_FieldNumber_AuthService = 16, + GtalkLoginRequest_FieldNumber_NetworkType = 17, + GtalkLoginRequest_FieldNumber_Status = 18, + GtalkLoginRequest_FieldNumber_TokenVersionInfo = 19, + GtalkLoginRequest_FieldNumber_CellTower = 20, + GtalkLoginRequest_FieldNumber_GcmStartTimeMs = 21, + GtalkLoginRequest_FieldNumber_ClientEventArray = 22, + GtalkLoginRequest_FieldNumber_OnFallback = 23, +}; + +@interface GtalkLoginRequest : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *id_p; +/** Test to see if @c id_p has been set. */ +@property(nonatomic, readwrite) BOOL hasId_p; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *domain; +/** Test to see if @c domain has been set. */ +@property(nonatomic, readwrite) BOOL hasDomain; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *user; +/** Test to see if @c user has been set. */ +@property(nonatomic, readwrite) BOOL hasUser; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *resource; +/** Test to see if @c resource has been set. */ +@property(nonatomic, readwrite) BOOL hasResource; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *authToken; +/** Test to see if @c authToken has been set. */ +@property(nonatomic, readwrite) BOOL hasAuthToken; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *deviceId; +/** Test to see if @c deviceId has been set. */ +@property(nonatomic, readwrite) BOOL hasDeviceId; + + +@property(nonatomic, readwrite) int64_t lastRmqId; + +@property(nonatomic, readwrite) BOOL hasLastRmqId; + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GtalkSetting*> *settingArray; +/** The number of items in @c settingArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger settingArray_Count; + + +@property(nonatomic, readwrite) int32_t compress; + +@property(nonatomic, readwrite) BOOL hasCompress; + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<NSString*> *receivedPersistentIdArray; +/** The number of items in @c receivedPersistentIdArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger receivedPersistentIdArray_Count; + + +@property(nonatomic, readwrite) BOOL includeStreamIds; + +@property(nonatomic, readwrite) BOOL hasIncludeStreamIds; + +@property(nonatomic, readwrite, strong, null_resettable) GtalkHeartbeatStat *heartbeatStat; +/** Test to see if @c heartbeatStat has been set. */ +@property(nonatomic, readwrite) BOOL hasHeartbeatStat; + + +@property(nonatomic, readwrite) BOOL useRmq2; + +@property(nonatomic, readwrite) BOOL hasUseRmq2; + +@property(nonatomic, readwrite) int64_t accountId; + +@property(nonatomic, readwrite) BOOL hasAccountId; + +@property(nonatomic, readwrite) GtalkLoginRequest_AuthService authService; + +@property(nonatomic, readwrite) BOOL hasAuthService; + +@property(nonatomic, readwrite) int32_t networkType; + +@property(nonatomic, readwrite) BOOL hasNetworkType; + +@property(nonatomic, readwrite) int64_t status; + +@property(nonatomic, readwrite) BOOL hasStatus; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *tokenVersionInfo; +/** Test to see if @c tokenVersionInfo has been set. */ +@property(nonatomic, readwrite) BOOL hasTokenVersionInfo; + + +@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower; +/** Test to see if @c cellTower has been set. */ +@property(nonatomic, readwrite) BOOL hasCellTower; + + +@property(nonatomic, readwrite) uint64_t gcmStartTimeMs; + +@property(nonatomic, readwrite) BOOL hasGcmStartTimeMs; + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GtalkClientEvent*> *clientEventArray; +/** The number of items in @c clientEventArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger clientEventArray_Count; + + +@property(nonatomic, readwrite) BOOL onFallback; + +@property(nonatomic, readwrite) BOOL hasOnFallback; +@end + +#pragma mark - GtalkLoginResponse + +typedef GPB_ENUM(GtalkLoginResponse_FieldNumber) { + GtalkLoginResponse_FieldNumber_Id_p = 1, + GtalkLoginResponse_FieldNumber_Jid = 2, + GtalkLoginResponse_FieldNumber_Error = 3, + GtalkLoginResponse_FieldNumber_SettingArray = 4, + GtalkLoginResponse_FieldNumber_StreamId = 5, + GtalkLoginResponse_FieldNumber_LastStreamIdReceived = 6, + GtalkLoginResponse_FieldNumber_HeartbeatConfig = 7, + GtalkLoginResponse_FieldNumber_ServerTimestamp = 8, +}; + +@interface GtalkLoginResponse : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *id_p; +/** Test to see if @c id_p has been set. */ +@property(nonatomic, readwrite) BOOL hasId_p; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *jid; +/** Test to see if @c jid has been set. */ +@property(nonatomic, readwrite) BOOL hasJid; + + +@property(nonatomic, readwrite, strong, null_resettable) GtalkErrorInfo *error; +/** Test to see if @c error has been set. */ +@property(nonatomic, readwrite) BOOL hasError; + + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GtalkSetting*> *settingArray; +/** The number of items in @c settingArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger settingArray_Count; + + +@property(nonatomic, readwrite) int32_t streamId; + +@property(nonatomic, readwrite) BOOL hasStreamId; + +@property(nonatomic, readwrite) int32_t lastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL hasLastStreamIdReceived; + +@property(nonatomic, readwrite, strong, null_resettable) GtalkHeartbeatConfig *heartbeatConfig; +/** Test to see if @c heartbeatConfig has been set. */ +@property(nonatomic, readwrite) BOOL hasHeartbeatConfig; + + +@property(nonatomic, readwrite) int64_t serverTimestamp; + +@property(nonatomic, readwrite) BOOL hasServerTimestamp; +@end + +#pragma mark - GtalkBindAccountRequest + +typedef GPB_ENUM(GtalkBindAccountRequest_FieldNumber) { + GtalkBindAccountRequest_FieldNumber_Id_p = 1, + GtalkBindAccountRequest_FieldNumber_Domain = 2, + GtalkBindAccountRequest_FieldNumber_User = 3, + GtalkBindAccountRequest_FieldNumber_Resource = 4, + GtalkBindAccountRequest_FieldNumber_AuthToken = 5, + GtalkBindAccountRequest_FieldNumber_PersistentId = 6, + GtalkBindAccountRequest_FieldNumber_StreamId = 7, + GtalkBindAccountRequest_FieldNumber_LastStreamIdReceived = 8, + GtalkBindAccountRequest_FieldNumber_AccountId = 9, +}; + +@interface GtalkBindAccountRequest : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *id_p; +/** Test to see if @c id_p has been set. */ +@property(nonatomic, readwrite) BOOL hasId_p; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *domain; +/** Test to see if @c domain has been set. */ +@property(nonatomic, readwrite) BOOL hasDomain; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *user; +/** Test to see if @c user has been set. */ +@property(nonatomic, readwrite) BOOL hasUser; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *resource; +/** Test to see if @c resource has been set. */ +@property(nonatomic, readwrite) BOOL hasResource; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *authToken; +/** Test to see if @c authToken has been set. */ +@property(nonatomic, readwrite) BOOL hasAuthToken; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *persistentId; +/** Test to see if @c persistentId has been set. */ +@property(nonatomic, readwrite) BOOL hasPersistentId; + + +@property(nonatomic, readwrite) int32_t streamId; + +@property(nonatomic, readwrite) BOOL hasStreamId; + +@property(nonatomic, readwrite) int32_t lastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL hasLastStreamIdReceived; + +@property(nonatomic, readwrite) int64_t accountId; + +@property(nonatomic, readwrite) BOOL hasAccountId; +@end + +#pragma mark - GtalkBindAccountResponse + +typedef GPB_ENUM(GtalkBindAccountResponse_FieldNumber) { + GtalkBindAccountResponse_FieldNumber_Id_p = 1, + GtalkBindAccountResponse_FieldNumber_Jid = 2, + GtalkBindAccountResponse_FieldNumber_Error = 3, + GtalkBindAccountResponse_FieldNumber_StreamId = 4, + GtalkBindAccountResponse_FieldNumber_LastStreamIdReceived = 5, +}; + +@interface GtalkBindAccountResponse : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *id_p; +/** Test to see if @c id_p has been set. */ +@property(nonatomic, readwrite) BOOL hasId_p; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *jid; +/** Test to see if @c jid has been set. */ +@property(nonatomic, readwrite) BOOL hasJid; + + +@property(nonatomic, readwrite, strong, null_resettable) GtalkErrorInfo *error; +/** Test to see if @c error has been set. */ +@property(nonatomic, readwrite) BOOL hasError; + + +@property(nonatomic, readwrite) int32_t streamId; + +@property(nonatomic, readwrite) BOOL hasStreamId; + +@property(nonatomic, readwrite) int32_t lastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL hasLastStreamIdReceived; +@end + +#pragma mark - GtalkStreamErrorStanza + +typedef GPB_ENUM(GtalkStreamErrorStanza_FieldNumber) { + GtalkStreamErrorStanza_FieldNumber_Type = 1, + GtalkStreamErrorStanza_FieldNumber_Text = 2, +}; + +@interface GtalkStreamErrorStanza : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *type; +/** Test to see if @c type has been set. */ +@property(nonatomic, readwrite) BOOL hasType; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *text; +/** Test to see if @c text has been set. */ +@property(nonatomic, readwrite) BOOL hasText; + +@end + +#pragma mark - GtalkClose + +@interface GtalkClose : GPBMessage + +@end + +#pragma mark - GtalkExtension + +typedef GPB_ENUM(GtalkExtension_FieldNumber) { + GtalkExtension_FieldNumber_Id_p = 1, + GtalkExtension_FieldNumber_Data_p = 2, +}; + +@interface GtalkExtension : GPBMessage + + +@property(nonatomic, readwrite) int32_t id_p; + +@property(nonatomic, readwrite) BOOL hasId_p; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *data_p; +/** Test to see if @c data_p has been set. */ +@property(nonatomic, readwrite) BOOL hasData_p; + +@end + +#pragma mark - GtalkMessageStanza + +typedef GPB_ENUM(GtalkMessageStanza_FieldNumber) { + GtalkMessageStanza_FieldNumber_RmqId = 1, + GtalkMessageStanza_FieldNumber_Type = 2, + GtalkMessageStanza_FieldNumber_Id_p = 3, + GtalkMessageStanza_FieldNumber_From = 4, + GtalkMessageStanza_FieldNumber_To = 5, + GtalkMessageStanza_FieldNumber_Subject = 6, + GtalkMessageStanza_FieldNumber_Body = 7, + GtalkMessageStanza_FieldNumber_Thread = 8, + GtalkMessageStanza_FieldNumber_Error = 9, + GtalkMessageStanza_FieldNumber_ExtensionArray = 10, + GtalkMessageStanza_FieldNumber_Nosave = 11, + GtalkMessageStanza_FieldNumber_Timestamp = 12, + GtalkMessageStanza_FieldNumber_PersistentId = 13, + GtalkMessageStanza_FieldNumber_StreamId = 14, + GtalkMessageStanza_FieldNumber_LastStreamIdReceived = 15, + GtalkMessageStanza_FieldNumber_Read = 16, + GtalkMessageStanza_FieldNumber_AccountId = 17, +}; + +@interface GtalkMessageStanza : GPBMessage + + +@property(nonatomic, readwrite) int64_t rmqId; + +@property(nonatomic, readwrite) BOOL hasRmqId; + +@property(nonatomic, readwrite) GtalkMessageStanza_MessageType type; + +@property(nonatomic, readwrite) BOOL hasType; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *id_p; +/** Test to see if @c id_p has been set. */ +@property(nonatomic, readwrite) BOOL hasId_p; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *from; +/** Test to see if @c from has been set. */ +@property(nonatomic, readwrite) BOOL hasFrom; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *to; +/** Test to see if @c to has been set. */ +@property(nonatomic, readwrite) BOOL hasTo; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *subject; +/** Test to see if @c subject has been set. */ +@property(nonatomic, readwrite) BOOL hasSubject; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *body; +/** Test to see if @c body has been set. */ +@property(nonatomic, readwrite) BOOL hasBody; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *thread; +/** Test to see if @c thread has been set. */ +@property(nonatomic, readwrite) BOOL hasThread; + + +@property(nonatomic, readwrite, strong, null_resettable) GtalkErrorInfo *error; +/** Test to see if @c error has been set. */ +@property(nonatomic, readwrite) BOOL hasError; + + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GtalkExtension*> *extensionArray; +/** The number of items in @c extensionArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger extensionArray_Count; + + +@property(nonatomic, readwrite) BOOL nosave; + +@property(nonatomic, readwrite) BOOL hasNosave; + +@property(nonatomic, readwrite) int64_t timestamp; + +@property(nonatomic, readwrite) BOOL hasTimestamp; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *persistentId; +/** Test to see if @c persistentId has been set. */ +@property(nonatomic, readwrite) BOOL hasPersistentId; + + +@property(nonatomic, readwrite) int32_t streamId; + +@property(nonatomic, readwrite) BOOL hasStreamId; + +@property(nonatomic, readwrite) int32_t lastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL hasLastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL read; + +@property(nonatomic, readwrite) BOOL hasRead; + +@property(nonatomic, readwrite) int64_t accountId; + +@property(nonatomic, readwrite) BOOL hasAccountId; +@end + +#pragma mark - GtalkPresenceStanza + +typedef GPB_ENUM(GtalkPresenceStanza_FieldNumber) { + GtalkPresenceStanza_FieldNumber_RmqId = 1, + GtalkPresenceStanza_FieldNumber_Type = 2, + GtalkPresenceStanza_FieldNumber_Id_p = 3, + GtalkPresenceStanza_FieldNumber_From = 4, + GtalkPresenceStanza_FieldNumber_To = 5, + GtalkPresenceStanza_FieldNumber_Show = 6, + GtalkPresenceStanza_FieldNumber_Status = 7, + GtalkPresenceStanza_FieldNumber_Priority = 8, + GtalkPresenceStanza_FieldNumber_Error = 9, + GtalkPresenceStanza_FieldNumber_ExtensionArray = 10, + GtalkPresenceStanza_FieldNumber_Client = 11, + GtalkPresenceStanza_FieldNumber_AvatarHash = 12, + GtalkPresenceStanza_FieldNumber_PersistentId = 13, + GtalkPresenceStanza_FieldNumber_StreamId = 14, + GtalkPresenceStanza_FieldNumber_LastStreamIdReceived = 15, + GtalkPresenceStanza_FieldNumber_CapabilitiesFlags = 16, + GtalkPresenceStanza_FieldNumber_AccountId = 17, +}; + +@interface GtalkPresenceStanza : GPBMessage + + +@property(nonatomic, readwrite) int64_t rmqId; + +@property(nonatomic, readwrite) BOOL hasRmqId; + +@property(nonatomic, readwrite) GtalkPresenceStanza_PresenceType type; + +@property(nonatomic, readwrite) BOOL hasType; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *id_p; +/** Test to see if @c id_p has been set. */ +@property(nonatomic, readwrite) BOOL hasId_p; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *from; +/** Test to see if @c from has been set. */ +@property(nonatomic, readwrite) BOOL hasFrom; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *to; +/** Test to see if @c to has been set. */ +@property(nonatomic, readwrite) BOOL hasTo; + + +@property(nonatomic, readwrite) GtalkPresenceStanza_ShowType show; + +@property(nonatomic, readwrite) BOOL hasShow; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *status; +/** Test to see if @c status has been set. */ +@property(nonatomic, readwrite) BOOL hasStatus; + + +@property(nonatomic, readwrite) int32_t priority; + +@property(nonatomic, readwrite) BOOL hasPriority; + +@property(nonatomic, readwrite, strong, null_resettable) GtalkErrorInfo *error; +/** Test to see if @c error has been set. */ +@property(nonatomic, readwrite) BOOL hasError; + + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GtalkExtension*> *extensionArray; +/** The number of items in @c extensionArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger extensionArray_Count; + + +@property(nonatomic, readwrite) GtalkPresenceStanza_ClientType client; + +@property(nonatomic, readwrite) BOOL hasClient; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *avatarHash; +/** Test to see if @c avatarHash has been set. */ +@property(nonatomic, readwrite) BOOL hasAvatarHash; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *persistentId; +/** Test to see if @c persistentId has been set. */ +@property(nonatomic, readwrite) BOOL hasPersistentId; + + +@property(nonatomic, readwrite) int32_t streamId; + +@property(nonatomic, readwrite) BOOL hasStreamId; + +@property(nonatomic, readwrite) int32_t lastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL hasLastStreamIdReceived; + +@property(nonatomic, readwrite) int32_t capabilitiesFlags; + +@property(nonatomic, readwrite) BOOL hasCapabilitiesFlags; + +@property(nonatomic, readwrite) int64_t accountId; + +@property(nonatomic, readwrite) BOOL hasAccountId; +@end + +#pragma mark - GtalkBatchPresenceStanza + +typedef GPB_ENUM(GtalkBatchPresenceStanza_FieldNumber) { + GtalkBatchPresenceStanza_FieldNumber_Id_p = 1, + GtalkBatchPresenceStanza_FieldNumber_To = 2, + GtalkBatchPresenceStanza_FieldNumber_PresenceArray = 3, + GtalkBatchPresenceStanza_FieldNumber_PersistentId = 4, + GtalkBatchPresenceStanza_FieldNumber_StreamId = 5, + GtalkBatchPresenceStanza_FieldNumber_LastStreamIdReceived = 6, + GtalkBatchPresenceStanza_FieldNumber_AccountId = 7, + GtalkBatchPresenceStanza_FieldNumber_Type = 8, + GtalkBatchPresenceStanza_FieldNumber_Error = 9, +}; + +@interface GtalkBatchPresenceStanza : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *id_p; +/** Test to see if @c id_p has been set. */ +@property(nonatomic, readwrite) BOOL hasId_p; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *to; +/** Test to see if @c to has been set. */ +@property(nonatomic, readwrite) BOOL hasTo; + + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GtalkPresenceStanza*> *presenceArray; +/** The number of items in @c presenceArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger presenceArray_Count; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *persistentId; +/** Test to see if @c persistentId has been set. */ +@property(nonatomic, readwrite) BOOL hasPersistentId; + + +@property(nonatomic, readwrite) int32_t streamId; + +@property(nonatomic, readwrite) BOOL hasStreamId; + +@property(nonatomic, readwrite) int32_t lastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL hasLastStreamIdReceived; + +@property(nonatomic, readwrite) int64_t accountId; + +@property(nonatomic, readwrite) BOOL hasAccountId; + +@property(nonatomic, readwrite) GtalkBatchPresenceStanza_Type type; + +@property(nonatomic, readwrite) BOOL hasType; + +@property(nonatomic, readwrite, strong, null_resettable) GtalkErrorInfo *error; +/** Test to see if @c error has been set. */ +@property(nonatomic, readwrite) BOOL hasError; + +@end + +#pragma mark - GtalkIqStanza + +typedef GPB_ENUM(GtalkIqStanza_FieldNumber) { + GtalkIqStanza_FieldNumber_RmqId = 1, + GtalkIqStanza_FieldNumber_Type = 2, + GtalkIqStanza_FieldNumber_Id_p = 3, + GtalkIqStanza_FieldNumber_From = 4, + GtalkIqStanza_FieldNumber_To = 5, + GtalkIqStanza_FieldNumber_Error = 6, + GtalkIqStanza_FieldNumber_Extension = 7, + GtalkIqStanza_FieldNumber_PersistentId = 8, + GtalkIqStanza_FieldNumber_StreamId = 9, + GtalkIqStanza_FieldNumber_LastStreamIdReceived = 10, + GtalkIqStanza_FieldNumber_AccountId = 11, + GtalkIqStanza_FieldNumber_Status = 12, +}; + +@interface GtalkIqStanza : GPBMessage + + +@property(nonatomic, readwrite) int64_t rmqId; + +@property(nonatomic, readwrite) BOOL hasRmqId; + +@property(nonatomic, readwrite) GtalkIqStanza_IqType type; + +@property(nonatomic, readwrite) BOOL hasType; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *id_p; +/** Test to see if @c id_p has been set. */ +@property(nonatomic, readwrite) BOOL hasId_p; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *from; +/** Test to see if @c from has been set. */ +@property(nonatomic, readwrite) BOOL hasFrom; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *to; +/** Test to see if @c to has been set. */ +@property(nonatomic, readwrite) BOOL hasTo; + + +@property(nonatomic, readwrite, strong, null_resettable) GtalkErrorInfo *error; +/** Test to see if @c error has been set. */ +@property(nonatomic, readwrite) BOOL hasError; + + +@property(nonatomic, readwrite, strong, null_resettable) GtalkExtension *extension; +/** Test to see if @c extension has been set. */ +@property(nonatomic, readwrite) BOOL hasExtension; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *persistentId; +/** Test to see if @c persistentId has been set. */ +@property(nonatomic, readwrite) BOOL hasPersistentId; + + +@property(nonatomic, readwrite) int32_t streamId; + +@property(nonatomic, readwrite) BOOL hasStreamId; + +@property(nonatomic, readwrite) int32_t lastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL hasLastStreamIdReceived; + +@property(nonatomic, readwrite) int64_t accountId; + +@property(nonatomic, readwrite) BOOL hasAccountId; + +@property(nonatomic, readwrite) int64_t status; + +@property(nonatomic, readwrite) BOOL hasStatus; +@end + +#pragma mark - GtalkAppData + +typedef GPB_ENUM(GtalkAppData_FieldNumber) { + GtalkAppData_FieldNumber_Key = 1, + GtalkAppData_FieldNumber_Value = 2, +}; + +@interface GtalkAppData : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *key; +/** Test to see if @c key has been set. */ +@property(nonatomic, readwrite) BOOL hasKey; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *value; +/** Test to see if @c value has been set. */ +@property(nonatomic, readwrite) BOOL hasValue; + +@end + +#pragma mark - GtalkDataMessageStanza + +typedef GPB_ENUM(GtalkDataMessageStanza_FieldNumber) { + GtalkDataMessageStanza_FieldNumber_RmqId = 1, + GtalkDataMessageStanza_FieldNumber_Id_p = 2, + GtalkDataMessageStanza_FieldNumber_From = 3, + GtalkDataMessageStanza_FieldNumber_To = 4, + GtalkDataMessageStanza_FieldNumber_Category = 5, + GtalkDataMessageStanza_FieldNumber_Token = 6, + GtalkDataMessageStanza_FieldNumber_AppDataArray = 7, + GtalkDataMessageStanza_FieldNumber_FromTrustedServer = 8, + GtalkDataMessageStanza_FieldNumber_PersistentId = 9, + GtalkDataMessageStanza_FieldNumber_StreamId = 10, + GtalkDataMessageStanza_FieldNumber_LastStreamIdReceived = 11, + GtalkDataMessageStanza_FieldNumber_Permission = 12, + GtalkDataMessageStanza_FieldNumber_RegId = 13, + GtalkDataMessageStanza_FieldNumber_PkgSignature = 14, + GtalkDataMessageStanza_FieldNumber_ClientId = 15, + GtalkDataMessageStanza_FieldNumber_DeviceUserId = 16, + GtalkDataMessageStanza_FieldNumber_Ttl = 17, + GtalkDataMessageStanza_FieldNumber_Sent = 18, + GtalkDataMessageStanza_FieldNumber_Queued = 19, + GtalkDataMessageStanza_FieldNumber_Status = 20, + GtalkDataMessageStanza_FieldNumber_RawData = 21, + GtalkDataMessageStanza_FieldNumber_MaxDelay = 22, + GtalkDataMessageStanza_FieldNumber_ActualDelay = 23, + GtalkDataMessageStanza_FieldNumber_ImmediateAck = 24, + GtalkDataMessageStanza_FieldNumber_DeliveryReceiptRequested = 25, + GtalkDataMessageStanza_FieldNumber_ExternalMessageId = 26, + GtalkDataMessageStanza_FieldNumber_Flags = 27, + GtalkDataMessageStanza_FieldNumber_CellTower = 28, + GtalkDataMessageStanza_FieldNumber_Priority = 29, +}; + +@interface GtalkDataMessageStanza : GPBMessage + + +@property(nonatomic, readwrite) int64_t rmqId; + +@property(nonatomic, readwrite) BOOL hasRmqId; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *id_p; +/** Test to see if @c id_p has been set. */ +@property(nonatomic, readwrite) BOOL hasId_p; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *from; +/** Test to see if @c from has been set. */ +@property(nonatomic, readwrite) BOOL hasFrom; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *to; +/** Test to see if @c to has been set. */ +@property(nonatomic, readwrite) BOOL hasTo; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *category; +/** Test to see if @c category has been set. */ +@property(nonatomic, readwrite) BOOL hasCategory; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *token; +/** Test to see if @c token has been set. */ +@property(nonatomic, readwrite) BOOL hasToken; + + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GtalkAppData*> *appDataArray; +/** The number of items in @c appDataArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger appDataArray_Count; + + +@property(nonatomic, readwrite) BOOL fromTrustedServer; + +@property(nonatomic, readwrite) BOOL hasFromTrustedServer; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *persistentId; +/** Test to see if @c persistentId has been set. */ +@property(nonatomic, readwrite) BOOL hasPersistentId; + + +@property(nonatomic, readwrite) int32_t streamId; + +@property(nonatomic, readwrite) BOOL hasStreamId; + +@property(nonatomic, readwrite) int32_t lastStreamIdReceived; + +@property(nonatomic, readwrite) BOOL hasLastStreamIdReceived; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *permission; +/** Test to see if @c permission has been set. */ +@property(nonatomic, readwrite) BOOL hasPermission; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *regId; +/** Test to see if @c regId has been set. */ +@property(nonatomic, readwrite) BOOL hasRegId; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *pkgSignature; +/** Test to see if @c pkgSignature has been set. */ +@property(nonatomic, readwrite) BOOL hasPkgSignature; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *clientId; +/** Test to see if @c clientId has been set. */ +@property(nonatomic, readwrite) BOOL hasClientId; + + +@property(nonatomic, readwrite) int64_t deviceUserId; + +@property(nonatomic, readwrite) BOOL hasDeviceUserId; + +@property(nonatomic, readwrite) int32_t ttl; + +@property(nonatomic, readwrite) BOOL hasTtl; + +@property(nonatomic, readwrite) int64_t sent; + +@property(nonatomic, readwrite) BOOL hasSent; + +@property(nonatomic, readwrite) int32_t queued; + +@property(nonatomic, readwrite) BOOL hasQueued; + +@property(nonatomic, readwrite) int64_t status; + +@property(nonatomic, readwrite) BOOL hasStatus; + +@property(nonatomic, readwrite, copy, null_resettable) NSData *rawData; +/** Test to see if @c rawData has been set. */ +@property(nonatomic, readwrite) BOOL hasRawData; + + +@property(nonatomic, readwrite) int32_t maxDelay; + +@property(nonatomic, readwrite) BOOL hasMaxDelay; + +@property(nonatomic, readwrite) int32_t actualDelay; + +@property(nonatomic, readwrite) BOOL hasActualDelay; + +@property(nonatomic, readwrite) BOOL immediateAck; + +@property(nonatomic, readwrite) BOOL hasImmediateAck; + +@property(nonatomic, readwrite) BOOL deliveryReceiptRequested; + +@property(nonatomic, readwrite) BOOL hasDeliveryReceiptRequested; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *externalMessageId; +/** Test to see if @c externalMessageId has been set. */ +@property(nonatomic, readwrite) BOOL hasExternalMessageId; + + +@property(nonatomic, readwrite) int64_t flags; + +@property(nonatomic, readwrite) BOOL hasFlags; + +@property(nonatomic, readwrite, strong, null_resettable) GtalkCellTower *cellTower; +/** Test to see if @c cellTower has been set. */ +@property(nonatomic, readwrite) BOOL hasCellTower; + + +@property(nonatomic, readwrite) int32_t priority; + +@property(nonatomic, readwrite) BOOL hasPriority; +@end + +#pragma mark - GtalkTalkMetadata + +typedef GPB_ENUM(GtalkTalkMetadata_FieldNumber) { + GtalkTalkMetadata_FieldNumber_Foreground = 1, +}; + +@interface GtalkTalkMetadata : GPBMessage + + +@property(nonatomic, readwrite) BOOL foreground; + +@property(nonatomic, readwrite) BOOL hasForeground; +@end + +#pragma mark - GtalkCellTower + +typedef GPB_ENUM(GtalkCellTower_FieldNumber) { + GtalkCellTower_FieldNumber_Id_p = 1, + GtalkCellTower_FieldNumber_KnownCongestionStatus = 2, +}; + +@interface GtalkCellTower : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *id_p; +/** Test to see if @c id_p has been set. */ +@property(nonatomic, readwrite) BOOL hasId_p; + + +@property(nonatomic, readwrite) int32_t knownCongestionStatus; + +@property(nonatomic, readwrite) BOOL hasKnownCongestionStatus; +@end + +#pragma mark - GtalkClientEvent + +typedef GPB_ENUM(GtalkClientEvent_FieldNumber) { + GtalkClientEvent_FieldNumber_Type = 1, + GtalkClientEvent_FieldNumber_NumberDiscardedEvents = 100, + GtalkClientEvent_FieldNumber_NetworkType = 200, + GtalkClientEvent_FieldNumber_NetworkPort = 201, + GtalkClientEvent_FieldNumber_TimeConnectionStartedMs = 202, + GtalkClientEvent_FieldNumber_TimeConnectionEndedMs = 203, + GtalkClientEvent_FieldNumber_ErrorCode = 204, + GtalkClientEvent_FieldNumber_TimeConnectionEstablishedMs = 300, +}; + +@interface GtalkClientEvent : GPBMessage + + +@property(nonatomic, readwrite) GtalkClientEvent_Type type; + +@property(nonatomic, readwrite) BOOL hasType; + +@property(nonatomic, readwrite) uint32_t numberDiscardedEvents; + +@property(nonatomic, readwrite) BOOL hasNumberDiscardedEvents; + +@property(nonatomic, readwrite) int32_t networkType; + +@property(nonatomic, readwrite) BOOL hasNetworkType; + +@property(nonatomic, readwrite) int32_t networkPort; + +@property(nonatomic, readwrite) BOOL hasNetworkPort; + +@property(nonatomic, readwrite) uint64_t timeConnectionStartedMs; + +@property(nonatomic, readwrite) BOOL hasTimeConnectionStartedMs; + +@property(nonatomic, readwrite) uint64_t timeConnectionEndedMs; + +@property(nonatomic, readwrite) BOOL hasTimeConnectionEndedMs; + +@property(nonatomic, readwrite) int32_t errorCode; + +@property(nonatomic, readwrite) BOOL hasErrorCode; + +@property(nonatomic, readwrite) uint64_t timeConnectionEstablishedMs; + +@property(nonatomic, readwrite) BOOL hasTimeConnectionEstablishedMs; +@end + +NS_ASSUME_NONNULL_END + +CF_EXTERN_C_END + +#pragma clang diagnostic pop + +// @@protoc_insertion_point(global_scope) diff --git a/Firebase/Messaging/Protos/GtalkCore.pbobjc.m b/Firebase/Messaging/Protos/GtalkCore.pbobjc.m new file mode 100644 index 0000000..f4efe22 --- /dev/null +++ b/Firebase/Messaging/Protos/GtalkCore.pbobjc.m @@ -0,0 +1,2947 @@ +/* + * Copyright 2017 Google + * + * 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. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: buzz/mobile/proto/gtalk_core.proto + +// This CPP symbol can be defined to use imports that match up to the framework +// imports needed when using CocoaPods. +#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS) + #define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0 +#endif + +#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS + #import <Protobuf/GPBProtocolBuffers_RuntimeSupport.h> +#else + #import "GPBProtocolBuffers_RuntimeSupport.h" +#endif + + #import "GtalkCore.pbobjc.h" +// @@protoc_insertion_point(imports) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +#pragma mark - GtalkGtalkCoreRoot + +@implementation GtalkGtalkCoreRoot + +// No extensions in the file and no imports, so no need to generate +// +extensionRegistry. + +@end + +#pragma mark - GtalkGtalkCoreRoot_FileDescriptor + +static GPBFileDescriptor *GtalkGtalkCoreRoot_FileDescriptor(void) { + // This is called by +initialize so there is no need to worry + // about thread safety of the singleton. + static GPBFileDescriptor *descriptor = NULL; + if (!descriptor) { + GPB_DEBUG_CHECK_RUNTIME_VERSIONS(); + descriptor = [[GPBFileDescriptor alloc] initWithPackage:@"mobilegtalk" + objcPrefix:@"Gtalk" + syntax:GPBFileSyntaxProto2]; + } + return descriptor; +} + +#pragma mark - GtalkHeartbeatPing + +@implementation GtalkHeartbeatPing + +@dynamic hasStreamId, streamId; +@dynamic hasLastStreamIdReceived, lastStreamIdReceived; +@dynamic hasStatus, status; +@dynamic hasCellTower, cellTower; +@dynamic hasIntervalMs, intervalMs; + +typedef struct GtalkHeartbeatPing__storage_ { + uint32_t _has_storage_[1]; + int32_t streamId; + int32_t lastStreamIdReceived; + int32_t intervalMs; + GtalkCellTower *cellTower; + int64_t status; +} GtalkHeartbeatPing__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "streamId", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatPing_FieldNumber_StreamId, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkHeartbeatPing__storage_, streamId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "lastStreamIdReceived", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatPing_FieldNumber_LastStreamIdReceived, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkHeartbeatPing__storage_, lastStreamIdReceived), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "status", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatPing_FieldNumber_Status, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkHeartbeatPing__storage_, status), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "cellTower", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkCellTower), + .number = GtalkHeartbeatPing_FieldNumber_CellTower, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkHeartbeatPing__storage_, cellTower), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "intervalMs", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatPing_FieldNumber_IntervalMs, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkHeartbeatPing__storage_, intervalMs), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkHeartbeatPing class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkHeartbeatPing__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkHeartbeatAck + +@implementation GtalkHeartbeatAck + +@dynamic hasStreamId, streamId; +@dynamic hasLastStreamIdReceived, lastStreamIdReceived; +@dynamic hasStatus, status; +@dynamic hasCellTower, cellTower; +@dynamic hasIntervalMs, intervalMs; + +typedef struct GtalkHeartbeatAck__storage_ { + uint32_t _has_storage_[1]; + int32_t streamId; + int32_t lastStreamIdReceived; + int32_t intervalMs; + GtalkCellTower *cellTower; + int64_t status; +} GtalkHeartbeatAck__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "streamId", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatAck_FieldNumber_StreamId, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkHeartbeatAck__storage_, streamId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "lastStreamIdReceived", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatAck_FieldNumber_LastStreamIdReceived, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkHeartbeatAck__storage_, lastStreamIdReceived), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "status", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatAck_FieldNumber_Status, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkHeartbeatAck__storage_, status), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "cellTower", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkCellTower), + .number = GtalkHeartbeatAck_FieldNumber_CellTower, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkHeartbeatAck__storage_, cellTower), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "intervalMs", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatAck_FieldNumber_IntervalMs, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkHeartbeatAck__storage_, intervalMs), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkHeartbeatAck class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkHeartbeatAck__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkErrorInfo + +@implementation GtalkErrorInfo + +@dynamic hasCode, code; +@dynamic hasMessage, message; +@dynamic hasType, type; +@dynamic hasExtension, extension; + +typedef struct GtalkErrorInfo__storage_ { + uint32_t _has_storage_[1]; + int32_t code; + NSString *message; + NSString *type; + GtalkExtension *extension; +} GtalkErrorInfo__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "code", + .dataTypeSpecific.className = NULL, + .number = GtalkErrorInfo_FieldNumber_Code, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkErrorInfo__storage_, code), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeInt32, + }, + { + .name = "message", + .dataTypeSpecific.className = NULL, + .number = GtalkErrorInfo_FieldNumber_Message, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkErrorInfo__storage_, message), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "type", + .dataTypeSpecific.className = NULL, + .number = GtalkErrorInfo_FieldNumber_Type, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkErrorInfo__storage_, type), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "extension", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkExtension), + .number = GtalkErrorInfo_FieldNumber_Extension, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkErrorInfo__storage_, extension), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkErrorInfo class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkErrorInfo__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkSetting + +@implementation GtalkSetting + +@dynamic hasName, name; +@dynamic hasValue, value; + +typedef struct GtalkSetting__storage_ { + uint32_t _has_storage_[1]; + NSString *name; + NSString *value; +} GtalkSetting__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "name", + .dataTypeSpecific.className = NULL, + .number = GtalkSetting_FieldNumber_Name, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkSetting__storage_, name), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "value", + .dataTypeSpecific.className = NULL, + .number = GtalkSetting_FieldNumber_Value, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkSetting__storage_, value), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkSetting class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkSetting__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkHeartbeatStat + +@implementation GtalkHeartbeatStat + +@dynamic hasIp, ip; +@dynamic hasTimeout, timeout; +@dynamic hasIntervalMs, intervalMs; + +typedef struct GtalkHeartbeatStat__storage_ { + uint32_t _has_storage_[1]; + int32_t intervalMs; + NSString *ip; +} GtalkHeartbeatStat__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "ip", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatStat_FieldNumber_Ip, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkHeartbeatStat__storage_, ip), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "timeout", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatStat_FieldNumber_Timeout, + .hasIndex = 1, + .offset = 2, // Stored in _has_storage_ to save space. + .flags = GPBFieldRequired, + .dataType = GPBDataTypeBool, + }, + { + .name = "intervalMs", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatStat_FieldNumber_IntervalMs, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkHeartbeatStat__storage_, intervalMs), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkHeartbeatStat class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkHeartbeatStat__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkHeartbeatConfig + +@implementation GtalkHeartbeatConfig + +@dynamic hasUploadStat, uploadStat; +@dynamic hasIp, ip; +@dynamic hasIntervalMs, intervalMs; + +typedef struct GtalkHeartbeatConfig__storage_ { + uint32_t _has_storage_[1]; + int32_t intervalMs; + NSString *ip; +} GtalkHeartbeatConfig__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "uploadStat", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatConfig_FieldNumber_UploadStat, + .hasIndex = 0, + .offset = 1, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "ip", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatConfig_FieldNumber_Ip, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkHeartbeatConfig__storage_, ip), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "intervalMs", + .dataTypeSpecific.className = NULL, + .number = GtalkHeartbeatConfig_FieldNumber_IntervalMs, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkHeartbeatConfig__storage_, intervalMs), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkHeartbeatConfig class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkHeartbeatConfig__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkLoginRequest + +@implementation GtalkLoginRequest + +@dynamic hasId_p, id_p; +@dynamic hasDomain, domain; +@dynamic hasUser, user; +@dynamic hasResource, resource; +@dynamic hasAuthToken, authToken; +@dynamic hasDeviceId, deviceId; +@dynamic hasLastRmqId, lastRmqId; +@dynamic settingArray, settingArray_Count; +@dynamic hasCompress, compress; +@dynamic receivedPersistentIdArray, receivedPersistentIdArray_Count; +@dynamic hasIncludeStreamIds, includeStreamIds; +@dynamic hasHeartbeatStat, heartbeatStat; +@dynamic hasUseRmq2, useRmq2; +@dynamic hasAccountId, accountId; +@dynamic hasAuthService, authService; +@dynamic hasNetworkType, networkType; +@dynamic hasStatus, status; +@dynamic hasTokenVersionInfo, tokenVersionInfo; +@dynamic hasCellTower, cellTower; +@dynamic hasGcmStartTimeMs, gcmStartTimeMs; +@dynamic clientEventArray, clientEventArray_Count; +@dynamic hasOnFallback, onFallback; + +typedef struct GtalkLoginRequest__storage_ { + uint32_t _has_storage_[1]; + int32_t compress; + GtalkLoginRequest_AuthService authService; + int32_t networkType; + NSString *id_p; + NSString *domain; + NSString *user; + NSString *resource; + NSString *authToken; + NSString *deviceId; + NSMutableArray *settingArray; + NSMutableArray *receivedPersistentIdArray; + GtalkHeartbeatStat *heartbeatStat; + NSString *tokenVersionInfo; + GtalkCellTower *cellTower; + NSMutableArray *clientEventArray; + int64_t lastRmqId; + int64_t accountId; + int64_t status; + uint64_t gcmStartTimeMs; +} GtalkLoginRequest__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_Id_p, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, id_p), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "domain", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_Domain, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, domain), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "user", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_User, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, user), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "resource", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_Resource, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, resource), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "authToken", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_AuthToken, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, authToken), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "deviceId", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_DeviceId, + .hasIndex = 5, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, deviceId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "lastRmqId", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_LastRmqId, + .hasIndex = 6, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, lastRmqId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "settingArray", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkSetting), + .number = GtalkLoginRequest_FieldNumber_SettingArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, settingArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeMessage, + }, + { + .name = "compress", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_Compress, + .hasIndex = 7, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, compress), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "receivedPersistentIdArray", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_ReceivedPersistentIdArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, receivedPersistentIdArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeString, + }, + { + .name = "includeStreamIds", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_IncludeStreamIds, + .hasIndex = 8, + .offset = 9, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "heartbeatStat", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkHeartbeatStat), + .number = GtalkLoginRequest_FieldNumber_HeartbeatStat, + .hasIndex = 10, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, heartbeatStat), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "useRmq2", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_UseRmq2, + .hasIndex = 11, + .offset = 12, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "accountId", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_AccountId, + .hasIndex = 13, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, accountId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "authService", + .dataTypeSpecific.enumDescFunc = GtalkLoginRequest_AuthService_EnumDescriptor, + .number = GtalkLoginRequest_FieldNumber_AuthService, + .hasIndex = 14, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, authService), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "networkType", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_NetworkType, + .hasIndex = 15, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, networkType), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "status", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_Status, + .hasIndex = 16, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, status), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "tokenVersionInfo", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_TokenVersionInfo, + .hasIndex = 17, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, tokenVersionInfo), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "cellTower", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkCellTower), + .number = GtalkLoginRequest_FieldNumber_CellTower, + .hasIndex = 18, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, cellTower), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "gcmStartTimeMs", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_GcmStartTimeMs, + .hasIndex = 19, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, gcmStartTimeMs), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeUInt64, + }, + { + .name = "clientEventArray", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkClientEvent), + .number = GtalkLoginRequest_FieldNumber_ClientEventArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkLoginRequest__storage_, clientEventArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeMessage, + }, + { + .name = "onFallback", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginRequest_FieldNumber_OnFallback, + .hasIndex = 20, + .offset = 21, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkLoginRequest class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkLoginRequest__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - Enum GtalkLoginRequest_AuthService + +GPBEnumDescriptor *GtalkLoginRequest_AuthService_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Mail\000AndroidCloudToDeviceMessage\000Android" + "Id\000"; + static const int32_t values[] = { + GtalkLoginRequest_AuthService_Mail, + GtalkLoginRequest_AuthService_AndroidCloudToDeviceMessage, + GtalkLoginRequest_AuthService_AndroidId, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkLoginRequest_AuthService) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkLoginRequest_AuthService_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkLoginRequest_AuthService_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkLoginRequest_AuthService_Mail: + case GtalkLoginRequest_AuthService_AndroidCloudToDeviceMessage: + case GtalkLoginRequest_AuthService_AndroidId: + return YES; + default: + return NO; + } +} + +#pragma mark - GtalkLoginResponse + +@implementation GtalkLoginResponse + +@dynamic hasId_p, id_p; +@dynamic hasJid, jid; +@dynamic hasError, error; +@dynamic settingArray, settingArray_Count; +@dynamic hasStreamId, streamId; +@dynamic hasLastStreamIdReceived, lastStreamIdReceived; +@dynamic hasHeartbeatConfig, heartbeatConfig; +@dynamic hasServerTimestamp, serverTimestamp; + +typedef struct GtalkLoginResponse__storage_ { + uint32_t _has_storage_[1]; + int32_t streamId; + int32_t lastStreamIdReceived; + NSString *id_p; + NSString *jid; + GtalkErrorInfo *error; + NSMutableArray *settingArray; + GtalkHeartbeatConfig *heartbeatConfig; + int64_t serverTimestamp; +} GtalkLoginResponse__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginResponse_FieldNumber_Id_p, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkLoginResponse__storage_, id_p), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "jid", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginResponse_FieldNumber_Jid, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkLoginResponse__storage_, jid), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "error", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkErrorInfo), + .number = GtalkLoginResponse_FieldNumber_Error, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkLoginResponse__storage_, error), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "settingArray", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkSetting), + .number = GtalkLoginResponse_FieldNumber_SettingArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkLoginResponse__storage_, settingArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeMessage, + }, + { + .name = "streamId", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginResponse_FieldNumber_StreamId, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkLoginResponse__storage_, streamId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "lastStreamIdReceived", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginResponse_FieldNumber_LastStreamIdReceived, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkLoginResponse__storage_, lastStreamIdReceived), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "heartbeatConfig", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkHeartbeatConfig), + .number = GtalkLoginResponse_FieldNumber_HeartbeatConfig, + .hasIndex = 5, + .offset = (uint32_t)offsetof(GtalkLoginResponse__storage_, heartbeatConfig), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "serverTimestamp", + .dataTypeSpecific.className = NULL, + .number = GtalkLoginResponse_FieldNumber_ServerTimestamp, + .hasIndex = 6, + .offset = (uint32_t)offsetof(GtalkLoginResponse__storage_, serverTimestamp), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkLoginResponse class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkLoginResponse__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkBindAccountRequest + +@implementation GtalkBindAccountRequest + +@dynamic hasId_p, id_p; +@dynamic hasDomain, domain; +@dynamic hasUser, user; +@dynamic hasResource, resource; +@dynamic hasAuthToken, authToken; +@dynamic hasPersistentId, persistentId; +@dynamic hasStreamId, streamId; +@dynamic hasLastStreamIdReceived, lastStreamIdReceived; +@dynamic hasAccountId, accountId; + +typedef struct GtalkBindAccountRequest__storage_ { + uint32_t _has_storage_[1]; + int32_t streamId; + int32_t lastStreamIdReceived; + NSString *id_p; + NSString *domain; + NSString *user; + NSString *resource; + NSString *authToken; + NSString *persistentId; + int64_t accountId; +} GtalkBindAccountRequest__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountRequest_FieldNumber_Id_p, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkBindAccountRequest__storage_, id_p), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "domain", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountRequest_FieldNumber_Domain, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkBindAccountRequest__storage_, domain), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "user", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountRequest_FieldNumber_User, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkBindAccountRequest__storage_, user), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "resource", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountRequest_FieldNumber_Resource, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkBindAccountRequest__storage_, resource), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "authToken", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountRequest_FieldNumber_AuthToken, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkBindAccountRequest__storage_, authToken), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "persistentId", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountRequest_FieldNumber_PersistentId, + .hasIndex = 5, + .offset = (uint32_t)offsetof(GtalkBindAccountRequest__storage_, persistentId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "streamId", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountRequest_FieldNumber_StreamId, + .hasIndex = 6, + .offset = (uint32_t)offsetof(GtalkBindAccountRequest__storage_, streamId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "lastStreamIdReceived", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountRequest_FieldNumber_LastStreamIdReceived, + .hasIndex = 7, + .offset = (uint32_t)offsetof(GtalkBindAccountRequest__storage_, lastStreamIdReceived), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "accountId", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountRequest_FieldNumber_AccountId, + .hasIndex = 8, + .offset = (uint32_t)offsetof(GtalkBindAccountRequest__storage_, accountId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkBindAccountRequest class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkBindAccountRequest__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkBindAccountResponse + +@implementation GtalkBindAccountResponse + +@dynamic hasId_p, id_p; +@dynamic hasJid, jid; +@dynamic hasError, error; +@dynamic hasStreamId, streamId; +@dynamic hasLastStreamIdReceived, lastStreamIdReceived; + +typedef struct GtalkBindAccountResponse__storage_ { + uint32_t _has_storage_[1]; + int32_t streamId; + int32_t lastStreamIdReceived; + NSString *id_p; + NSString *jid; + GtalkErrorInfo *error; +} GtalkBindAccountResponse__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountResponse_FieldNumber_Id_p, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkBindAccountResponse__storage_, id_p), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "jid", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountResponse_FieldNumber_Jid, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkBindAccountResponse__storage_, jid), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "error", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkErrorInfo), + .number = GtalkBindAccountResponse_FieldNumber_Error, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkBindAccountResponse__storage_, error), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "streamId", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountResponse_FieldNumber_StreamId, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkBindAccountResponse__storage_, streamId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "lastStreamIdReceived", + .dataTypeSpecific.className = NULL, + .number = GtalkBindAccountResponse_FieldNumber_LastStreamIdReceived, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkBindAccountResponse__storage_, lastStreamIdReceived), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkBindAccountResponse class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkBindAccountResponse__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkStreamErrorStanza + +@implementation GtalkStreamErrorStanza + +@dynamic hasType, type; +@dynamic hasText, text; + +typedef struct GtalkStreamErrorStanza__storage_ { + uint32_t _has_storage_[1]; + NSString *type; + NSString *text; +} GtalkStreamErrorStanza__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "type", + .dataTypeSpecific.className = NULL, + .number = GtalkStreamErrorStanza_FieldNumber_Type, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkStreamErrorStanza__storage_, type), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "text", + .dataTypeSpecific.className = NULL, + .number = GtalkStreamErrorStanza_FieldNumber_Text, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkStreamErrorStanza__storage_, text), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkStreamErrorStanza class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkStreamErrorStanza__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkClose + +@implementation GtalkClose + + +typedef struct GtalkClose__storage_ { + uint32_t _has_storage_[1]; +} GtalkClose__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkClose class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:NULL + fieldCount:0 + storageSize:sizeof(GtalkClose__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkExtension + +@implementation GtalkExtension + +@dynamic hasId_p, id_p; +@dynamic hasData_p, data_p; + +typedef struct GtalkExtension__storage_ { + uint32_t _has_storage_[1]; + int32_t id_p; + NSString *data_p; +} GtalkExtension__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkExtension_FieldNumber_Id_p, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkExtension__storage_, id_p), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeInt32, + }, + { + .name = "data_p", + .dataTypeSpecific.className = NULL, + .number = GtalkExtension_FieldNumber_Data_p, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkExtension__storage_, data_p), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkExtension class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkExtension__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkMessageStanza + +@implementation GtalkMessageStanza + +@dynamic hasRmqId, rmqId; +@dynamic hasType, type; +@dynamic hasId_p, id_p; +@dynamic hasFrom, from; +@dynamic hasTo, to; +@dynamic hasSubject, subject; +@dynamic hasBody, body; +@dynamic hasThread, thread; +@dynamic hasError, error; +@dynamic extensionArray, extensionArray_Count; +@dynamic hasNosave, nosave; +@dynamic hasTimestamp, timestamp; +@dynamic hasPersistentId, persistentId; +@dynamic hasStreamId, streamId; +@dynamic hasLastStreamIdReceived, lastStreamIdReceived; +@dynamic hasRead, read; +@dynamic hasAccountId, accountId; + +typedef struct GtalkMessageStanza__storage_ { + uint32_t _has_storage_[1]; + GtalkMessageStanza_MessageType type; + int32_t streamId; + int32_t lastStreamIdReceived; + NSString *id_p; + NSString *from; + NSString *to; + NSString *subject; + NSString *body; + NSString *thread; + GtalkErrorInfo *error; + NSMutableArray *extensionArray; + NSString *persistentId; + int64_t rmqId; + int64_t timestamp; + int64_t accountId; +} GtalkMessageStanza__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "rmqId", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_RmqId, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, rmqId), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldTextFormatNameCustom), + .dataType = GPBDataTypeInt64, + }, + { + .name = "type", + .dataTypeSpecific.enumDescFunc = GtalkMessageStanza_MessageType_EnumDescriptor, + .number = GtalkMessageStanza_FieldNumber_Type, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, type), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_Id_p, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, id_p), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "from", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_From, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, from), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "to", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_To, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, to), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "subject", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_Subject, + .hasIndex = 5, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, subject), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "body", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_Body, + .hasIndex = 6, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, body), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "thread", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_Thread, + .hasIndex = 7, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, thread), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "error", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkErrorInfo), + .number = GtalkMessageStanza_FieldNumber_Error, + .hasIndex = 8, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, error), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "extensionArray", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkExtension), + .number = GtalkMessageStanza_FieldNumber_ExtensionArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, extensionArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeMessage, + }, + { + .name = "nosave", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_Nosave, + .hasIndex = 9, + .offset = 10, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "timestamp", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_Timestamp, + .hasIndex = 11, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, timestamp), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "persistentId", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_PersistentId, + .hasIndex = 12, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, persistentId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "streamId", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_StreamId, + .hasIndex = 13, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, streamId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "lastStreamIdReceived", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_LastStreamIdReceived, + .hasIndex = 14, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, lastStreamIdReceived), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "read", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_Read, + .hasIndex = 15, + .offset = 16, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "accountId", + .dataTypeSpecific.className = NULL, + .number = GtalkMessageStanza_FieldNumber_AccountId, + .hasIndex = 17, + .offset = (uint32_t)offsetof(GtalkMessageStanza__storage_, accountId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkMessageStanza class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkMessageStanza__storage_) + flags:GPBDescriptorInitializationFlag_None]; +#if !GPBOBJC_SKIP_MESSAGE_TEXTFORMAT_EXTRAS + static const char *extraTextFormatInfo = + "\001\001\005\000"; + [localDescriptor setupExtraTextInfo:extraTextFormatInfo]; +#endif // !GPBOBJC_SKIP_MESSAGE_TEXTFORMAT_EXTRAS + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - Enum GtalkMessageStanza_MessageType + +GPBEnumDescriptor *GtalkMessageStanza_MessageType_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Normal\000Chat\000Groupchat\000Headline\000Error\000"; + static const int32_t values[] = { + GtalkMessageStanza_MessageType_Normal, + GtalkMessageStanza_MessageType_Chat, + GtalkMessageStanza_MessageType_Groupchat, + GtalkMessageStanza_MessageType_Headline, + GtalkMessageStanza_MessageType_Error, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkMessageStanza_MessageType) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkMessageStanza_MessageType_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkMessageStanza_MessageType_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkMessageStanza_MessageType_Normal: + case GtalkMessageStanza_MessageType_Chat: + case GtalkMessageStanza_MessageType_Groupchat: + case GtalkMessageStanza_MessageType_Headline: + case GtalkMessageStanza_MessageType_Error: + return YES; + default: + return NO; + } +} + +#pragma mark - GtalkPresenceStanza + +@implementation GtalkPresenceStanza + +@dynamic hasRmqId, rmqId; +@dynamic hasType, type; +@dynamic hasId_p, id_p; +@dynamic hasFrom, from; +@dynamic hasTo, to; +@dynamic hasShow, show; +@dynamic hasStatus, status; +@dynamic hasPriority, priority; +@dynamic hasError, error; +@dynamic extensionArray, extensionArray_Count; +@dynamic hasClient, client; +@dynamic hasAvatarHash, avatarHash; +@dynamic hasPersistentId, persistentId; +@dynamic hasStreamId, streamId; +@dynamic hasLastStreamIdReceived, lastStreamIdReceived; +@dynamic hasCapabilitiesFlags, capabilitiesFlags; +@dynamic hasAccountId, accountId; + +typedef struct GtalkPresenceStanza__storage_ { + uint32_t _has_storage_[1]; + GtalkPresenceStanza_PresenceType type; + GtalkPresenceStanza_ShowType show; + int32_t priority; + GtalkPresenceStanza_ClientType client; + int32_t streamId; + int32_t lastStreamIdReceived; + int32_t capabilitiesFlags; + NSString *id_p; + NSString *from; + NSString *to; + NSString *status; + GtalkErrorInfo *error; + NSMutableArray *extensionArray; + NSString *avatarHash; + NSString *persistentId; + int64_t rmqId; + int64_t accountId; +} GtalkPresenceStanza__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "rmqId", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_RmqId, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, rmqId), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldTextFormatNameCustom), + .dataType = GPBDataTypeInt64, + }, + { + .name = "type", + .dataTypeSpecific.enumDescFunc = GtalkPresenceStanza_PresenceType_EnumDescriptor, + .number = GtalkPresenceStanza_FieldNumber_Type, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, type), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_Id_p, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, id_p), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "from", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_From, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, from), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "to", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_To, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, to), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "show", + .dataTypeSpecific.enumDescFunc = GtalkPresenceStanza_ShowType_EnumDescriptor, + .number = GtalkPresenceStanza_FieldNumber_Show, + .hasIndex = 5, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, show), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "status", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_Status, + .hasIndex = 6, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, status), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "priority", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_Priority, + .hasIndex = 7, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, priority), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "error", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkErrorInfo), + .number = GtalkPresenceStanza_FieldNumber_Error, + .hasIndex = 8, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, error), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "extensionArray", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkExtension), + .number = GtalkPresenceStanza_FieldNumber_ExtensionArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, extensionArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeMessage, + }, + { + .name = "client", + .dataTypeSpecific.enumDescFunc = GtalkPresenceStanza_ClientType_EnumDescriptor, + .number = GtalkPresenceStanza_FieldNumber_Client, + .hasIndex = 9, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, client), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "avatarHash", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_AvatarHash, + .hasIndex = 10, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, avatarHash), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "persistentId", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_PersistentId, + .hasIndex = 11, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, persistentId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "streamId", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_StreamId, + .hasIndex = 12, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, streamId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "lastStreamIdReceived", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_LastStreamIdReceived, + .hasIndex = 13, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, lastStreamIdReceived), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "capabilitiesFlags", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_CapabilitiesFlags, + .hasIndex = 14, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, capabilitiesFlags), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "accountId", + .dataTypeSpecific.className = NULL, + .number = GtalkPresenceStanza_FieldNumber_AccountId, + .hasIndex = 15, + .offset = (uint32_t)offsetof(GtalkPresenceStanza__storage_, accountId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkPresenceStanza class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkPresenceStanza__storage_) + flags:GPBDescriptorInitializationFlag_None]; +#if !GPBOBJC_SKIP_MESSAGE_TEXTFORMAT_EXTRAS + static const char *extraTextFormatInfo = + "\001\001\005\000"; + [localDescriptor setupExtraTextInfo:extraTextFormatInfo]; +#endif // !GPBOBJC_SKIP_MESSAGE_TEXTFORMAT_EXTRAS + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - Enum GtalkPresenceStanza_PresenceType + +GPBEnumDescriptor *GtalkPresenceStanza_PresenceType_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Unavailable\000Subscribe\000Subscribed\000Unsubsc" + "ribe\000Unsubscribed\000Probe\000Error\000"; + static const int32_t values[] = { + GtalkPresenceStanza_PresenceType_Unavailable, + GtalkPresenceStanza_PresenceType_Subscribe, + GtalkPresenceStanza_PresenceType_Subscribed, + GtalkPresenceStanza_PresenceType_Unsubscribe, + GtalkPresenceStanza_PresenceType_Unsubscribed, + GtalkPresenceStanza_PresenceType_Probe, + GtalkPresenceStanza_PresenceType_Error, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkPresenceStanza_PresenceType) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkPresenceStanza_PresenceType_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkPresenceStanza_PresenceType_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkPresenceStanza_PresenceType_Unavailable: + case GtalkPresenceStanza_PresenceType_Subscribe: + case GtalkPresenceStanza_PresenceType_Subscribed: + case GtalkPresenceStanza_PresenceType_Unsubscribe: + case GtalkPresenceStanza_PresenceType_Unsubscribed: + case GtalkPresenceStanza_PresenceType_Probe: + case GtalkPresenceStanza_PresenceType_Error: + return YES; + default: + return NO; + } +} + +#pragma mark - Enum GtalkPresenceStanza_ShowType + +GPBEnumDescriptor *GtalkPresenceStanza_ShowType_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Away\000Chat\000Dnd\000Xa\000"; + static const int32_t values[] = { + GtalkPresenceStanza_ShowType_Away, + GtalkPresenceStanza_ShowType_Chat, + GtalkPresenceStanza_ShowType_Dnd, + GtalkPresenceStanza_ShowType_Xa, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkPresenceStanza_ShowType) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkPresenceStanza_ShowType_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkPresenceStanza_ShowType_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkPresenceStanza_ShowType_Away: + case GtalkPresenceStanza_ShowType_Chat: + case GtalkPresenceStanza_ShowType_Dnd: + case GtalkPresenceStanza_ShowType_Xa: + return YES; + default: + return NO; + } +} + +#pragma mark - Enum GtalkPresenceStanza_ClientType + +GPBEnumDescriptor *GtalkPresenceStanza_ClientType_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Mobile\000Android\000"; + static const int32_t values[] = { + GtalkPresenceStanza_ClientType_Mobile, + GtalkPresenceStanza_ClientType_Android, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkPresenceStanza_ClientType) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkPresenceStanza_ClientType_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkPresenceStanza_ClientType_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkPresenceStanza_ClientType_Mobile: + case GtalkPresenceStanza_ClientType_Android: + return YES; + default: + return NO; + } +} + +#pragma mark - Enum GtalkPresenceStanza_CapabilitiesFlags + +GPBEnumDescriptor *GtalkPresenceStanza_CapabilitiesFlags_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "HasVoiceV1\000HasVideoV1\000HasCameraV1\000HasPmu" + "cV1\000"; + static const int32_t values[] = { + GtalkPresenceStanza_CapabilitiesFlags_HasVoiceV1, + GtalkPresenceStanza_CapabilitiesFlags_HasVideoV1, + GtalkPresenceStanza_CapabilitiesFlags_HasCameraV1, + GtalkPresenceStanza_CapabilitiesFlags_HasPmucV1, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkPresenceStanza_CapabilitiesFlags) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkPresenceStanza_CapabilitiesFlags_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkPresenceStanza_CapabilitiesFlags_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkPresenceStanza_CapabilitiesFlags_HasVoiceV1: + case GtalkPresenceStanza_CapabilitiesFlags_HasVideoV1: + case GtalkPresenceStanza_CapabilitiesFlags_HasCameraV1: + case GtalkPresenceStanza_CapabilitiesFlags_HasPmucV1: + return YES; + default: + return NO; + } +} + +#pragma mark - GtalkBatchPresenceStanza + +@implementation GtalkBatchPresenceStanza + +@dynamic hasId_p, id_p; +@dynamic hasTo, to; +@dynamic presenceArray, presenceArray_Count; +@dynamic hasPersistentId, persistentId; +@dynamic hasStreamId, streamId; +@dynamic hasLastStreamIdReceived, lastStreamIdReceived; +@dynamic hasAccountId, accountId; +@dynamic hasType, type; +@dynamic hasError, error; + +typedef struct GtalkBatchPresenceStanza__storage_ { + uint32_t _has_storage_[1]; + int32_t streamId; + int32_t lastStreamIdReceived; + GtalkBatchPresenceStanza_Type type; + NSString *id_p; + NSString *to; + NSMutableArray *presenceArray; + NSString *persistentId; + GtalkErrorInfo *error; + int64_t accountId; +} GtalkBatchPresenceStanza__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkBatchPresenceStanza_FieldNumber_Id_p, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkBatchPresenceStanza__storage_, id_p), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "to", + .dataTypeSpecific.className = NULL, + .number = GtalkBatchPresenceStanza_FieldNumber_To, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkBatchPresenceStanza__storage_, to), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "presenceArray", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkPresenceStanza), + .number = GtalkBatchPresenceStanza_FieldNumber_PresenceArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkBatchPresenceStanza__storage_, presenceArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeMessage, + }, + { + .name = "persistentId", + .dataTypeSpecific.className = NULL, + .number = GtalkBatchPresenceStanza_FieldNumber_PersistentId, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkBatchPresenceStanza__storage_, persistentId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "streamId", + .dataTypeSpecific.className = NULL, + .number = GtalkBatchPresenceStanza_FieldNumber_StreamId, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkBatchPresenceStanza__storage_, streamId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "lastStreamIdReceived", + .dataTypeSpecific.className = NULL, + .number = GtalkBatchPresenceStanza_FieldNumber_LastStreamIdReceived, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkBatchPresenceStanza__storage_, lastStreamIdReceived), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "accountId", + .dataTypeSpecific.className = NULL, + .number = GtalkBatchPresenceStanza_FieldNumber_AccountId, + .hasIndex = 5, + .offset = (uint32_t)offsetof(GtalkBatchPresenceStanza__storage_, accountId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "type", + .dataTypeSpecific.enumDescFunc = GtalkBatchPresenceStanza_Type_EnumDescriptor, + .number = GtalkBatchPresenceStanza_FieldNumber_Type, + .hasIndex = 6, + .offset = (uint32_t)offsetof(GtalkBatchPresenceStanza__storage_, type), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "error", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkErrorInfo), + .number = GtalkBatchPresenceStanza_FieldNumber_Error, + .hasIndex = 7, + .offset = (uint32_t)offsetof(GtalkBatchPresenceStanza__storage_, error), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkBatchPresenceStanza class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkBatchPresenceStanza__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - Enum GtalkBatchPresenceStanza_Type + +GPBEnumDescriptor *GtalkBatchPresenceStanza_Type_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Get\000Set\000"; + static const int32_t values[] = { + GtalkBatchPresenceStanza_Type_Get, + GtalkBatchPresenceStanza_Type_Set, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkBatchPresenceStanza_Type) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkBatchPresenceStanza_Type_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkBatchPresenceStanza_Type_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkBatchPresenceStanza_Type_Get: + case GtalkBatchPresenceStanza_Type_Set: + return YES; + default: + return NO; + } +} + +#pragma mark - GtalkIqStanza + +@implementation GtalkIqStanza + +@dynamic hasRmqId, rmqId; +@dynamic hasType, type; +@dynamic hasId_p, id_p; +@dynamic hasFrom, from; +@dynamic hasTo, to; +@dynamic hasError, error; +@dynamic hasExtension, extension; +@dynamic hasPersistentId, persistentId; +@dynamic hasStreamId, streamId; +@dynamic hasLastStreamIdReceived, lastStreamIdReceived; +@dynamic hasAccountId, accountId; +@dynamic hasStatus, status; + +typedef struct GtalkIqStanza__storage_ { + uint32_t _has_storage_[1]; + GtalkIqStanza_IqType type; + int32_t streamId; + int32_t lastStreamIdReceived; + NSString *id_p; + NSString *from; + NSString *to; + GtalkErrorInfo *error; + GtalkExtension *extension; + NSString *persistentId; + int64_t rmqId; + int64_t accountId; + int64_t status; +} GtalkIqStanza__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "rmqId", + .dataTypeSpecific.className = NULL, + .number = GtalkIqStanza_FieldNumber_RmqId, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, rmqId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "type", + .dataTypeSpecific.enumDescFunc = GtalkIqStanza_IqType_EnumDescriptor, + .number = GtalkIqStanza_FieldNumber_Type, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, type), + .flags = (GPBFieldFlags)(GPBFieldRequired | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkIqStanza_FieldNumber_Id_p, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, id_p), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "from", + .dataTypeSpecific.className = NULL, + .number = GtalkIqStanza_FieldNumber_From, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, from), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "to", + .dataTypeSpecific.className = NULL, + .number = GtalkIqStanza_FieldNumber_To, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, to), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "error", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkErrorInfo), + .number = GtalkIqStanza_FieldNumber_Error, + .hasIndex = 5, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, error), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "extension", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkExtension), + .number = GtalkIqStanza_FieldNumber_Extension, + .hasIndex = 6, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, extension), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "persistentId", + .dataTypeSpecific.className = NULL, + .number = GtalkIqStanza_FieldNumber_PersistentId, + .hasIndex = 7, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, persistentId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "streamId", + .dataTypeSpecific.className = NULL, + .number = GtalkIqStanza_FieldNumber_StreamId, + .hasIndex = 8, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, streamId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "lastStreamIdReceived", + .dataTypeSpecific.className = NULL, + .number = GtalkIqStanza_FieldNumber_LastStreamIdReceived, + .hasIndex = 9, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, lastStreamIdReceived), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "accountId", + .dataTypeSpecific.className = NULL, + .number = GtalkIqStanza_FieldNumber_AccountId, + .hasIndex = 10, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, accountId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "status", + .dataTypeSpecific.className = NULL, + .number = GtalkIqStanza_FieldNumber_Status, + .hasIndex = 11, + .offset = (uint32_t)offsetof(GtalkIqStanza__storage_, status), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkIqStanza class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkIqStanza__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - Enum GtalkIqStanza_IqType + +GPBEnumDescriptor *GtalkIqStanza_IqType_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Get\000Set\000Result\000Error\000"; + static const int32_t values[] = { + GtalkIqStanza_IqType_Get, + GtalkIqStanza_IqType_Set, + GtalkIqStanza_IqType_Result, + GtalkIqStanza_IqType_Error, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkIqStanza_IqType) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkIqStanza_IqType_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkIqStanza_IqType_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkIqStanza_IqType_Get: + case GtalkIqStanza_IqType_Set: + case GtalkIqStanza_IqType_Result: + case GtalkIqStanza_IqType_Error: + return YES; + default: + return NO; + } +} + +#pragma mark - GtalkAppData + +@implementation GtalkAppData + +@dynamic hasKey, key; +@dynamic hasValue, value; + +typedef struct GtalkAppData__storage_ { + uint32_t _has_storage_[1]; + NSString *key; + NSString *value; +} GtalkAppData__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "key", + .dataTypeSpecific.className = NULL, + .number = GtalkAppData_FieldNumber_Key, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkAppData__storage_, key), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "value", + .dataTypeSpecific.className = NULL, + .number = GtalkAppData_FieldNumber_Value, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkAppData__storage_, value), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkAppData class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkAppData__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkDataMessageStanza + +@implementation GtalkDataMessageStanza + +@dynamic hasRmqId, rmqId; +@dynamic hasId_p, id_p; +@dynamic hasFrom, from; +@dynamic hasTo, to; +@dynamic hasCategory, category; +@dynamic hasToken, token; +@dynamic appDataArray, appDataArray_Count; +@dynamic hasFromTrustedServer, fromTrustedServer; +@dynamic hasPersistentId, persistentId; +@dynamic hasStreamId, streamId; +@dynamic hasLastStreamIdReceived, lastStreamIdReceived; +@dynamic hasPermission, permission; +@dynamic hasRegId, regId; +@dynamic hasPkgSignature, pkgSignature; +@dynamic hasClientId, clientId; +@dynamic hasDeviceUserId, deviceUserId; +@dynamic hasTtl, ttl; +@dynamic hasSent, sent; +@dynamic hasQueued, queued; +@dynamic hasStatus, status; +@dynamic hasRawData, rawData; +@dynamic hasMaxDelay, maxDelay; +@dynamic hasActualDelay, actualDelay; +@dynamic hasImmediateAck, immediateAck; +@dynamic hasDeliveryReceiptRequested, deliveryReceiptRequested; +@dynamic hasExternalMessageId, externalMessageId; +@dynamic hasFlags, flags; +@dynamic hasCellTower, cellTower; +@dynamic hasPriority, priority; + +typedef struct GtalkDataMessageStanza__storage_ { + uint32_t _has_storage_[1]; + int32_t streamId; + int32_t lastStreamIdReceived; + int32_t ttl; + int32_t queued; + int32_t maxDelay; + int32_t actualDelay; + int32_t priority; + NSString *id_p; + NSString *from; + NSString *to; + NSString *category; + NSString *token; + NSMutableArray *appDataArray; + NSString *persistentId; + NSString *permission; + NSString *regId; + NSString *pkgSignature; + NSString *clientId; + NSData *rawData; + NSString *externalMessageId; + GtalkCellTower *cellTower; + int64_t rmqId; + int64_t deviceUserId; + int64_t sent; + int64_t status; + int64_t flags; +} GtalkDataMessageStanza__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "rmqId", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_RmqId, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, rmqId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_Id_p, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, id_p), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "from", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_From, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, from), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "to", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_To, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, to), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "category", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_Category, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, category), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "token", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_Token, + .hasIndex = 5, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, token), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "appDataArray", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkAppData), + .number = GtalkDataMessageStanza_FieldNumber_AppDataArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, appDataArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeMessage, + }, + { + .name = "fromTrustedServer", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_FromTrustedServer, + .hasIndex = 6, + .offset = 7, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "persistentId", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_PersistentId, + .hasIndex = 8, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, persistentId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "streamId", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_StreamId, + .hasIndex = 9, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, streamId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "lastStreamIdReceived", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_LastStreamIdReceived, + .hasIndex = 10, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, lastStreamIdReceived), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "permission", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_Permission, + .hasIndex = 11, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, permission), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "regId", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_RegId, + .hasIndex = 12, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, regId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "pkgSignature", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_PkgSignature, + .hasIndex = 13, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, pkgSignature), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "clientId", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_ClientId, + .hasIndex = 14, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, clientId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "deviceUserId", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_DeviceUserId, + .hasIndex = 15, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, deviceUserId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "ttl", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_Ttl, + .hasIndex = 16, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, ttl), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "sent", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_Sent, + .hasIndex = 17, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, sent), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "queued", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_Queued, + .hasIndex = 18, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, queued), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "status", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_Status, + .hasIndex = 19, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, status), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "rawData", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_RawData, + .hasIndex = 20, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, rawData), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBytes, + }, + { + .name = "maxDelay", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_MaxDelay, + .hasIndex = 21, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, maxDelay), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "actualDelay", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_ActualDelay, + .hasIndex = 22, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, actualDelay), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "immediateAck", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_ImmediateAck, + .hasIndex = 23, + .offset = 24, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "deliveryReceiptRequested", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_DeliveryReceiptRequested, + .hasIndex = 25, + .offset = 26, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "externalMessageId", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_ExternalMessageId, + .hasIndex = 27, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, externalMessageId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "flags", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_Flags, + .hasIndex = 28, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, flags), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt64, + }, + { + .name = "cellTower", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkCellTower), + .number = GtalkDataMessageStanza_FieldNumber_CellTower, + .hasIndex = 29, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, cellTower), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "priority", + .dataTypeSpecific.className = NULL, + .number = GtalkDataMessageStanza_FieldNumber_Priority, + .hasIndex = 30, + .offset = (uint32_t)offsetof(GtalkDataMessageStanza__storage_, priority), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkDataMessageStanza class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkDataMessageStanza__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkTalkMetadata + +@implementation GtalkTalkMetadata + +@dynamic hasForeground, foreground; + +typedef struct GtalkTalkMetadata__storage_ { + uint32_t _has_storage_[1]; +} GtalkTalkMetadata__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "foreground", + .dataTypeSpecific.className = NULL, + .number = GtalkTalkMetadata_FieldNumber_Foreground, + .hasIndex = 0, + .offset = 1, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkTalkMetadata class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkTalkMetadata__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkCellTower + +@implementation GtalkCellTower + +@dynamic hasId_p, id_p; +@dynamic hasKnownCongestionStatus, knownCongestionStatus; + +typedef struct GtalkCellTower__storage_ { + uint32_t _has_storage_[1]; + int32_t knownCongestionStatus; + NSString *id_p; +} GtalkCellTower__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkCellTower_FieldNumber_Id_p, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkCellTower__storage_, id_p), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "knownCongestionStatus", + .dataTypeSpecific.className = NULL, + .number = GtalkCellTower_FieldNumber_KnownCongestionStatus, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkCellTower__storage_, knownCongestionStatus), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkCellTower class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkCellTower__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkClientEvent + +@implementation GtalkClientEvent + +@dynamic hasType, type; +@dynamic hasNumberDiscardedEvents, numberDiscardedEvents; +@dynamic hasNetworkType, networkType; +@dynamic hasNetworkPort, networkPort; +@dynamic hasTimeConnectionStartedMs, timeConnectionStartedMs; +@dynamic hasTimeConnectionEndedMs, timeConnectionEndedMs; +@dynamic hasErrorCode, errorCode; +@dynamic hasTimeConnectionEstablishedMs, timeConnectionEstablishedMs; + +typedef struct GtalkClientEvent__storage_ { + uint32_t _has_storage_[1]; + GtalkClientEvent_Type type; + uint32_t numberDiscardedEvents; + int32_t networkType; + int32_t networkPort; + int32_t errorCode; + uint64_t timeConnectionStartedMs; + uint64_t timeConnectionEndedMs; + uint64_t timeConnectionEstablishedMs; +} GtalkClientEvent__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "type", + .dataTypeSpecific.enumDescFunc = GtalkClientEvent_Type_EnumDescriptor, + .number = GtalkClientEvent_FieldNumber_Type, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkClientEvent__storage_, type), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "numberDiscardedEvents", + .dataTypeSpecific.className = NULL, + .number = GtalkClientEvent_FieldNumber_NumberDiscardedEvents, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkClientEvent__storage_, numberDiscardedEvents), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeUInt32, + }, + { + .name = "networkType", + .dataTypeSpecific.className = NULL, + .number = GtalkClientEvent_FieldNumber_NetworkType, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkClientEvent__storage_, networkType), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "networkPort", + .dataTypeSpecific.className = NULL, + .number = GtalkClientEvent_FieldNumber_NetworkPort, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkClientEvent__storage_, networkPort), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "timeConnectionStartedMs", + .dataTypeSpecific.className = NULL, + .number = GtalkClientEvent_FieldNumber_TimeConnectionStartedMs, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkClientEvent__storage_, timeConnectionStartedMs), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeUInt64, + }, + { + .name = "timeConnectionEndedMs", + .dataTypeSpecific.className = NULL, + .number = GtalkClientEvent_FieldNumber_TimeConnectionEndedMs, + .hasIndex = 5, + .offset = (uint32_t)offsetof(GtalkClientEvent__storage_, timeConnectionEndedMs), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeUInt64, + }, + { + .name = "errorCode", + .dataTypeSpecific.className = NULL, + .number = GtalkClientEvent_FieldNumber_ErrorCode, + .hasIndex = 6, + .offset = (uint32_t)offsetof(GtalkClientEvent__storage_, errorCode), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "timeConnectionEstablishedMs", + .dataTypeSpecific.className = NULL, + .number = GtalkClientEvent_FieldNumber_TimeConnectionEstablishedMs, + .hasIndex = 7, + .offset = (uint32_t)offsetof(GtalkClientEvent__storage_, timeConnectionEstablishedMs), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeUInt64, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkClientEvent class] + rootClass:[GtalkGtalkCoreRoot class] + file:GtalkGtalkCoreRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkClientEvent__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - Enum GtalkClientEvent_Type + +GPBEnumDescriptor *GtalkClientEvent_Type_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Unknown\000DiscardedEvents\000FailedConnection" + "\000SuccessfulConnection\000"; + static const int32_t values[] = { + GtalkClientEvent_Type_Unknown, + GtalkClientEvent_Type_DiscardedEvents, + GtalkClientEvent_Type_FailedConnection, + GtalkClientEvent_Type_SuccessfulConnection, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkClientEvent_Type) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkClientEvent_Type_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkClientEvent_Type_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkClientEvent_Type_Unknown: + case GtalkClientEvent_Type_DiscardedEvents: + case GtalkClientEvent_Type_FailedConnection: + case GtalkClientEvent_Type_SuccessfulConnection: + return YES; + default: + return NO; + } +} + + +#pragma clang diagnostic pop + +// @@protoc_insertion_point(global_scope) diff --git a/Firebase/Messaging/Protos/GtalkExtensions.pbobjc.h b/Firebase/Messaging/Protos/GtalkExtensions.pbobjc.h new file mode 100644 index 0000000..f461884 --- /dev/null +++ b/Firebase/Messaging/Protos/GtalkExtensions.pbobjc.h @@ -0,0 +1,617 @@ +/* + * Copyright 2017 Google + * + * 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. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: buzz/mobile/proto/gtalk_extensions.proto + +// This CPP symbol can be defined to use imports that match up to the framework +// imports needed when using CocoaPods. +#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS) + #define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0 +#endif + +#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS + #import <Protobuf/GPBProtocolBuffers.h> +#else + #import "GPBProtocolBuffers.h" +#endif + +#if GOOGLE_PROTOBUF_OBJC_VERSION < 30002 +#error This file was generated by a newer version of protoc which is incompatible with your Protocol Buffer library sources. +#endif +#if 30002 < GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION +#error This file was generated by an older version of protoc which is incompatible with your Protocol Buffer library sources. +#endif + +// @@protoc_insertion_point(imports) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +CF_EXTERN_C_BEGIN + +@class GtalkOtrItem; +@class GtalkPhoto; +@class GtalkRosterItem; +@class GtalkSharedStatus_StatusList; + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Enum GtalkRosterItem_SubscriptionType + +typedef GPB_ENUM(GtalkRosterItem_SubscriptionType) { + GtalkRosterItem_SubscriptionType_None = 0, + GtalkRosterItem_SubscriptionType_To = 1, + GtalkRosterItem_SubscriptionType_From = 2, + GtalkRosterItem_SubscriptionType_Both = 3, + GtalkRosterItem_SubscriptionType_Remove = 4, +}; + +GPBEnumDescriptor *GtalkRosterItem_SubscriptionType_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkRosterItem_SubscriptionType_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkRosterItem_AskType + +typedef GPB_ENUM(GtalkRosterItem_AskType) { + GtalkRosterItem_AskType_Subscribe = 0, +}; + +GPBEnumDescriptor *GtalkRosterItem_AskType_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkRosterItem_AskType_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkRosterItem_DisplayType + +typedef GPB_ENUM(GtalkRosterItem_DisplayType) { + GtalkRosterItem_DisplayType_Blocked = 0, + GtalkRosterItem_DisplayType_Hidden = 1, + GtalkRosterItem_DisplayType_Pinned = 2, +}; + +GPBEnumDescriptor *GtalkRosterItem_DisplayType_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkRosterItem_DisplayType_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkSharedStatus_ShowType + +typedef GPB_ENUM(GtalkSharedStatus_ShowType) { + GtalkSharedStatus_ShowType_Default = 0, + GtalkSharedStatus_ShowType_Dnd = 1, +}; + +GPBEnumDescriptor *GtalkSharedStatus_ShowType_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkSharedStatus_ShowType_IsValidValue(int32_t value); + +#pragma mark - Enum GtalkPostAuthBatchQuery_CapabilitiesExtFlags + +typedef GPB_ENUM(GtalkPostAuthBatchQuery_CapabilitiesExtFlags) { + GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasVoiceV1 = 1, + GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasVideoV1 = 2, + GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasCameraV1 = 4, + GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasPmucV1 = 8, +}; + +GPBEnumDescriptor *GtalkPostAuthBatchQuery_CapabilitiesExtFlags_EnumDescriptor(void); + +/** + * Checks to see if the given value is defined by the enum or was not known at + * the time this source was generated. + **/ +BOOL GtalkPostAuthBatchQuery_CapabilitiesExtFlags_IsValidValue(int32_t value); + +#pragma mark - GtalkGtalkExtensionsRoot + +/** + * Exposes the extension registry for this file. + * + * The base class provides: + * @code + * + (GPBExtensionRegistry *)extensionRegistry; + * @endcode + * which is a @c GPBExtensionRegistry that includes all the extensions defined by + * this file and all files that it depends on. + **/ +@interface GtalkGtalkExtensionsRoot : GPBRootObject +@end + +#pragma mark - GtalkRosterQuery + +typedef GPB_ENUM(GtalkRosterQuery_FieldNumber) { + GtalkRosterQuery_FieldNumber_Etag = 1, + GtalkRosterQuery_FieldNumber_NotModified = 2, + GtalkRosterQuery_FieldNumber_ItemArray = 3, + GtalkRosterQuery_FieldNumber_AvatarWidth = 4, + GtalkRosterQuery_FieldNumber_AvatarHeight = 5, +}; + +@interface GtalkRosterQuery : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *etag; +/** Test to see if @c etag has been set. */ +@property(nonatomic, readwrite) BOOL hasEtag; + + +@property(nonatomic, readwrite) BOOL notModified; + +@property(nonatomic, readwrite) BOOL hasNotModified; + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GtalkRosterItem*> *itemArray; +/** The number of items in @c itemArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger itemArray_Count; + + +@property(nonatomic, readwrite) int32_t avatarWidth; + +@property(nonatomic, readwrite) BOOL hasAvatarWidth; + +@property(nonatomic, readwrite) int32_t avatarHeight; + +@property(nonatomic, readwrite) BOOL hasAvatarHeight; +@end + +#pragma mark - GtalkRosterItem + +typedef GPB_ENUM(GtalkRosterItem_FieldNumber) { + GtalkRosterItem_FieldNumber_Jid = 1, + GtalkRosterItem_FieldNumber_Name = 2, + GtalkRosterItem_FieldNumber_Subscription = 3, + GtalkRosterItem_FieldNumber_Ask = 4, + GtalkRosterItem_FieldNumber_GroupArray = 5, + GtalkRosterItem_FieldNumber_QuickContact = 6, + GtalkRosterItem_FieldNumber_Display = 7, + GtalkRosterItem_FieldNumber_Rejected = 8, +}; + +@interface GtalkRosterItem : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *jid; +/** Test to see if @c jid has been set. */ +@property(nonatomic, readwrite) BOOL hasJid; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *name; +/** Test to see if @c name has been set. */ +@property(nonatomic, readwrite) BOOL hasName; + + +@property(nonatomic, readwrite) GtalkRosterItem_SubscriptionType subscription; + +@property(nonatomic, readwrite) BOOL hasSubscription; + +@property(nonatomic, readwrite) GtalkRosterItem_AskType ask; + +@property(nonatomic, readwrite) BOOL hasAsk; + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<NSString*> *groupArray; +/** The number of items in @c groupArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger groupArray_Count; + + +@property(nonatomic, readwrite) BOOL quickContact; + +@property(nonatomic, readwrite) BOOL hasQuickContact; + +@property(nonatomic, readwrite) GtalkRosterItem_DisplayType display; + +@property(nonatomic, readwrite) BOOL hasDisplay; + +@property(nonatomic, readwrite) BOOL rejected; + +@property(nonatomic, readwrite) BOOL hasRejected; +@end + +#pragma mark - GtalkRmqLastId + +typedef GPB_ENUM(GtalkRmqLastId_FieldNumber) { + GtalkRmqLastId_FieldNumber_Id_p = 1, +}; + +@interface GtalkRmqLastId : GPBMessage + + +@property(nonatomic, readwrite) int64_t id_p; + +@property(nonatomic, readwrite) BOOL hasId_p; +@end + +#pragma mark - GtalkRmqAck + +typedef GPB_ENUM(GtalkRmqAck_FieldNumber) { + GtalkRmqAck_FieldNumber_Id_p = 1, +}; + +@interface GtalkRmqAck : GPBMessage + + +@property(nonatomic, readwrite) int64_t id_p; + +@property(nonatomic, readwrite) BOOL hasId_p; +@end + +#pragma mark - GtalkVCard + +typedef GPB_ENUM(GtalkVCard_FieldNumber) { + GtalkVCard_FieldNumber_Version = 1, + GtalkVCard_FieldNumber_FullName = 2, + GtalkVCard_FieldNumber_Photo = 3, + GtalkVCard_FieldNumber_AvatarHash = 4, + GtalkVCard_FieldNumber_Modified = 5, +}; + +@interface GtalkVCard : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *version; +/** Test to see if @c version has been set. */ +@property(nonatomic, readwrite) BOOL hasVersion; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *fullName; +/** Test to see if @c fullName has been set. */ +@property(nonatomic, readwrite) BOOL hasFullName; + + +@property(nonatomic, readwrite, strong, null_resettable) GtalkPhoto *photo; +/** Test to see if @c photo has been set. */ +@property(nonatomic, readwrite) BOOL hasPhoto; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *avatarHash; +/** Test to see if @c avatarHash has been set. */ +@property(nonatomic, readwrite) BOOL hasAvatarHash; + + +@property(nonatomic, readwrite) BOOL modified; + +@property(nonatomic, readwrite) BOOL hasModified; +@end + +#pragma mark - GtalkPhoto + +typedef GPB_ENUM(GtalkPhoto_FieldNumber) { + GtalkPhoto_FieldNumber_Type = 1, + GtalkPhoto_FieldNumber_Data_p = 2, +}; + +@interface GtalkPhoto : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *type; +/** Test to see if @c type has been set. */ +@property(nonatomic, readwrite) BOOL hasType; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *data_p; +/** Test to see if @c data_p has been set. */ +@property(nonatomic, readwrite) BOOL hasData_p; + +@end + +#pragma mark - GtalkChatRead + +typedef GPB_ENUM(GtalkChatRead_FieldNumber) { + GtalkChatRead_FieldNumber_User = 1, +}; + +@interface GtalkChatRead : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *user; +/** Test to see if @c user has been set. */ +@property(nonatomic, readwrite) BOOL hasUser; + +@end + +#pragma mark - GtalkChatClosed + +typedef GPB_ENUM(GtalkChatClosed_FieldNumber) { + GtalkChatClosed_FieldNumber_User = 1, +}; + +@interface GtalkChatClosed : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *user; +/** Test to see if @c user has been set. */ +@property(nonatomic, readwrite) BOOL hasUser; + +@end + +#pragma mark - GtalkCapabilities + +typedef GPB_ENUM(GtalkCapabilities_FieldNumber) { + GtalkCapabilities_FieldNumber_Node = 1, + GtalkCapabilities_FieldNumber_Ver = 2, + GtalkCapabilities_FieldNumber_Ext = 3, + GtalkCapabilities_FieldNumber_Hash_p = 4, +}; + +@interface GtalkCapabilities : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *node; +/** Test to see if @c node has been set. */ +@property(nonatomic, readwrite) BOOL hasNode; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *ver; +/** Test to see if @c ver has been set. */ +@property(nonatomic, readwrite) BOOL hasVer; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *ext; +/** Test to see if @c ext has been set. */ +@property(nonatomic, readwrite) BOOL hasExt; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *hash_p; +/** Test to see if @c hash_p has been set. */ +@property(nonatomic, readwrite) BOOL hasHash_p; + +@end + +#pragma mark - GtalkSharedStatus + +typedef GPB_ENUM(GtalkSharedStatus_FieldNumber) { + GtalkSharedStatus_FieldNumber_StatusMax = 1, + GtalkSharedStatus_FieldNumber_StatusListMax = 2, + GtalkSharedStatus_FieldNumber_StatusListContentsMax = 3, + GtalkSharedStatus_FieldNumber_Status = 4, + GtalkSharedStatus_FieldNumber_Show = 5, + GtalkSharedStatus_FieldNumber_StatusListArray = 6, + GtalkSharedStatus_FieldNumber_Invisible = 9, + GtalkSharedStatus_FieldNumber_StatusMinVersion = 10, +}; + +@interface GtalkSharedStatus : GPBMessage + + +@property(nonatomic, readwrite) int32_t statusMax; + +@property(nonatomic, readwrite) BOOL hasStatusMax; + +@property(nonatomic, readwrite) int32_t statusListMax; + +@property(nonatomic, readwrite) BOOL hasStatusListMax; + +@property(nonatomic, readwrite) int32_t statusListContentsMax; + +@property(nonatomic, readwrite) BOOL hasStatusListContentsMax; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *status; +/** Test to see if @c status has been set. */ +@property(nonatomic, readwrite) BOOL hasStatus; + + +@property(nonatomic, readwrite) GtalkSharedStatus_ShowType show; + +@property(nonatomic, readwrite) BOOL hasShow; + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GtalkSharedStatus_StatusList*> *statusListArray; +/** The number of items in @c statusListArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger statusListArray_Count; + + +@property(nonatomic, readwrite) BOOL invisible; + +@property(nonatomic, readwrite) BOOL hasInvisible; + +@property(nonatomic, readwrite) int32_t statusMinVersion; + +@property(nonatomic, readwrite) BOOL hasStatusMinVersion; +@end + +#pragma mark - GtalkSharedStatus_StatusList + +typedef GPB_ENUM(GtalkSharedStatus_StatusList_FieldNumber) { + GtalkSharedStatus_StatusList_FieldNumber_Show = 7, + GtalkSharedStatus_StatusList_FieldNumber_StatusArray = 8, +}; + +@interface GtalkSharedStatus_StatusList : GPBMessage + + +@property(nonatomic, readwrite) GtalkSharedStatus_ShowType show; + +@property(nonatomic, readwrite) BOOL hasShow; + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<NSString*> *statusArray; +/** The number of items in @c statusArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger statusArray_Count; + +@end + +#pragma mark - GtalkOtrQuery + +typedef GPB_ENUM(GtalkOtrQuery_FieldNumber) { + GtalkOtrQuery_FieldNumber_NosaveDefault = 1, + GtalkOtrQuery_FieldNumber_ItemArray = 2, + GtalkOtrQuery_FieldNumber_Etag = 3, + GtalkOtrQuery_FieldNumber_NotModified = 4, +}; + +@interface GtalkOtrQuery : GPBMessage + + +@property(nonatomic, readwrite) BOOL nosaveDefault; + +@property(nonatomic, readwrite) BOOL hasNosaveDefault; + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<GtalkOtrItem*> *itemArray; +/** The number of items in @c itemArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger itemArray_Count; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *etag; +/** Test to see if @c etag has been set. */ +@property(nonatomic, readwrite) BOOL hasEtag; + + +@property(nonatomic, readwrite) BOOL notModified; + +@property(nonatomic, readwrite) BOOL hasNotModified; +@end + +#pragma mark - GtalkOtrItem + +typedef GPB_ENUM(GtalkOtrItem_FieldNumber) { + GtalkOtrItem_FieldNumber_Jid = 1, + GtalkOtrItem_FieldNumber_Nosave = 2, + GtalkOtrItem_FieldNumber_ChangedByBuddy = 3, +}; + +@interface GtalkOtrItem : GPBMessage + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *jid; +/** Test to see if @c jid has been set. */ +@property(nonatomic, readwrite) BOOL hasJid; + + +@property(nonatomic, readwrite) BOOL nosave; + +@property(nonatomic, readwrite) BOOL hasNosave; + +@property(nonatomic, readwrite) BOOL changedByBuddy; + +@property(nonatomic, readwrite) BOOL hasChangedByBuddy; +@end + +#pragma mark - GtalkIdle + +typedef GPB_ENUM(GtalkIdle_FieldNumber) { + GtalkIdle_FieldNumber_Idle = 1, + GtalkIdle_FieldNumber_Away = 2, +}; + +@interface GtalkIdle : GPBMessage + + +@property(nonatomic, readwrite) BOOL idle; + +@property(nonatomic, readwrite) BOOL hasIdle; + +@property(nonatomic, readwrite) BOOL away; + +@property(nonatomic, readwrite) BOOL hasAway; +@end + +#pragma mark - GtalkPostAuthBatchQuery + +typedef GPB_ENUM(GtalkPostAuthBatchQuery_FieldNumber) { + GtalkPostAuthBatchQuery_FieldNumber_Available = 1, + GtalkPostAuthBatchQuery_FieldNumber_DeviceIdle = 2, + GtalkPostAuthBatchQuery_FieldNumber_MobileIndicator = 3, + GtalkPostAuthBatchQuery_FieldNumber_SharedStatusVersion = 4, + GtalkPostAuthBatchQuery_FieldNumber_RosterEtag = 5, + GtalkPostAuthBatchQuery_FieldNumber_OtrEtag = 6, + GtalkPostAuthBatchQuery_FieldNumber_AvatarHash = 7, + GtalkPostAuthBatchQuery_FieldNumber_VcardQueryStanzaId = 8, + GtalkPostAuthBatchQuery_FieldNumber_CapabilitiesExtFlags = 9, +}; + +@interface GtalkPostAuthBatchQuery : GPBMessage + + +@property(nonatomic, readwrite) BOOL available; + +@property(nonatomic, readwrite) BOOL hasAvailable; + +@property(nonatomic, readwrite) BOOL deviceIdle; + +@property(nonatomic, readwrite) BOOL hasDeviceIdle; + +@property(nonatomic, readwrite) BOOL mobileIndicator; + +@property(nonatomic, readwrite) BOOL hasMobileIndicator; + +@property(nonatomic, readwrite) int32_t sharedStatusVersion; + +@property(nonatomic, readwrite) BOOL hasSharedStatusVersion; + +@property(nonatomic, readwrite, copy, null_resettable) NSString *rosterEtag; +/** Test to see if @c rosterEtag has been set. */ +@property(nonatomic, readwrite) BOOL hasRosterEtag; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *otrEtag; +/** Test to see if @c otrEtag has been set. */ +@property(nonatomic, readwrite) BOOL hasOtrEtag; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *avatarHash; +/** Test to see if @c avatarHash has been set. */ +@property(nonatomic, readwrite) BOOL hasAvatarHash; + + +@property(nonatomic, readwrite, copy, null_resettable) NSString *vcardQueryStanzaId; +/** Test to see if @c vcardQueryStanzaId has been set. */ +@property(nonatomic, readwrite) BOOL hasVcardQueryStanzaId; + + +@property(nonatomic, readwrite) int32_t capabilitiesExtFlags; + +@property(nonatomic, readwrite) BOOL hasCapabilitiesExtFlags; +@end + +#pragma mark - GtalkStreamAck + +@interface GtalkStreamAck : GPBMessage + +@end + +#pragma mark - GtalkSelectiveAck + +typedef GPB_ENUM(GtalkSelectiveAck_FieldNumber) { + GtalkSelectiveAck_FieldNumber_IdArray = 1, +}; + +@interface GtalkSelectiveAck : GPBMessage + + +@property(nonatomic, readwrite, strong, null_resettable) NSMutableArray<NSString*> *idArray; +/** The number of items in @c idArray without causing the array to be created. */ +@property(nonatomic, readonly) NSUInteger idArray_Count; + +@end + +NS_ASSUME_NONNULL_END + +CF_EXTERN_C_END + +#pragma clang diagnostic pop + +// @@protoc_insertion_point(global_scope) diff --git a/Firebase/Messaging/Protos/GtalkExtensions.pbobjc.m b/Firebase/Messaging/Protos/GtalkExtensions.pbobjc.m new file mode 100644 index 0000000..e41d416 --- /dev/null +++ b/Firebase/Messaging/Protos/GtalkExtensions.pbobjc.m @@ -0,0 +1,1407 @@ +/* + * Copyright 2017 Google + * + * 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. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: buzz/mobile/proto/gtalk_extensions.proto + +// This CPP symbol can be defined to use imports that match up to the framework +// imports needed when using CocoaPods. +#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS) + #define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0 +#endif + +#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS + #import <Protobuf/GPBProtocolBuffers_RuntimeSupport.h> +#else + #import "GPBProtocolBuffers_RuntimeSupport.h" +#endif + + #import "GtalkExtensions.pbobjc.h" +// @@protoc_insertion_point(imports) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +#pragma mark - GtalkGtalkExtensionsRoot + +@implementation GtalkGtalkExtensionsRoot + +// No extensions in the file and no imports, so no need to generate +// +extensionRegistry. + +@end + +#pragma mark - GtalkGtalkExtensionsRoot_FileDescriptor + +static GPBFileDescriptor *GtalkGtalkExtensionsRoot_FileDescriptor(void) { + // This is called by +initialize so there is no need to worry + // about thread safety of the singleton. + static GPBFileDescriptor *descriptor = NULL; + if (!descriptor) { + GPB_DEBUG_CHECK_RUNTIME_VERSIONS(); + descriptor = [[GPBFileDescriptor alloc] initWithPackage:@"mobilegtalk" + objcPrefix:@"Gtalk" + syntax:GPBFileSyntaxProto2]; + } + return descriptor; +} + +#pragma mark - GtalkRosterQuery + +@implementation GtalkRosterQuery + +@dynamic hasEtag, etag; +@dynamic hasNotModified, notModified; +@dynamic itemArray, itemArray_Count; +@dynamic hasAvatarWidth, avatarWidth; +@dynamic hasAvatarHeight, avatarHeight; + +typedef struct GtalkRosterQuery__storage_ { + uint32_t _has_storage_[1]; + int32_t avatarWidth; + int32_t avatarHeight; + NSString *etag; + NSMutableArray *itemArray; +} GtalkRosterQuery__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "etag", + .dataTypeSpecific.className = NULL, + .number = GtalkRosterQuery_FieldNumber_Etag, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkRosterQuery__storage_, etag), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "notModified", + .dataTypeSpecific.className = NULL, + .number = GtalkRosterQuery_FieldNumber_NotModified, + .hasIndex = 1, + .offset = 2, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "itemArray", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkRosterItem), + .number = GtalkRosterQuery_FieldNumber_ItemArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkRosterQuery__storage_, itemArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeMessage, + }, + { + .name = "avatarWidth", + .dataTypeSpecific.className = NULL, + .number = GtalkRosterQuery_FieldNumber_AvatarWidth, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkRosterQuery__storage_, avatarWidth), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldTextFormatNameCustom), + .dataType = GPBDataTypeInt32, + }, + { + .name = "avatarHeight", + .dataTypeSpecific.className = NULL, + .number = GtalkRosterQuery_FieldNumber_AvatarHeight, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkRosterQuery__storage_, avatarHeight), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldTextFormatNameCustom), + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkRosterQuery class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkRosterQuery__storage_) + flags:GPBDescriptorInitializationFlag_None]; +#if !GPBOBJC_SKIP_MESSAGE_TEXTFORMAT_EXTRAS + static const char *extraTextFormatInfo = + "\002\004\013\000\005\014\000"; + [localDescriptor setupExtraTextInfo:extraTextFormatInfo]; +#endif // !GPBOBJC_SKIP_MESSAGE_TEXTFORMAT_EXTRAS + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkRosterItem + +@implementation GtalkRosterItem + +@dynamic hasJid, jid; +@dynamic hasName, name; +@dynamic hasSubscription, subscription; +@dynamic hasAsk, ask; +@dynamic groupArray, groupArray_Count; +@dynamic hasQuickContact, quickContact; +@dynamic hasDisplay, display; +@dynamic hasRejected, rejected; + +typedef struct GtalkRosterItem__storage_ { + uint32_t _has_storage_[1]; + GtalkRosterItem_SubscriptionType subscription; + GtalkRosterItem_AskType ask; + GtalkRosterItem_DisplayType display; + NSString *jid; + NSString *name; + NSMutableArray *groupArray; +} GtalkRosterItem__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "jid", + .dataTypeSpecific.className = NULL, + .number = GtalkRosterItem_FieldNumber_Jid, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkRosterItem__storage_, jid), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "name", + .dataTypeSpecific.className = NULL, + .number = GtalkRosterItem_FieldNumber_Name, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkRosterItem__storage_, name), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "subscription", + .dataTypeSpecific.enumDescFunc = GtalkRosterItem_SubscriptionType_EnumDescriptor, + .number = GtalkRosterItem_FieldNumber_Subscription, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkRosterItem__storage_, subscription), + .flags = (GPBFieldFlags)(GPBFieldRequired | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "ask", + .dataTypeSpecific.enumDescFunc = GtalkRosterItem_AskType_EnumDescriptor, + .number = GtalkRosterItem_FieldNumber_Ask, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkRosterItem__storage_, ask), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "groupArray", + .dataTypeSpecific.className = NULL, + .number = GtalkRosterItem_FieldNumber_GroupArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkRosterItem__storage_, groupArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeString, + }, + { + .name = "quickContact", + .dataTypeSpecific.className = NULL, + .number = GtalkRosterItem_FieldNumber_QuickContact, + .hasIndex = 4, + .offset = 5, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "display", + .dataTypeSpecific.enumDescFunc = GtalkRosterItem_DisplayType_EnumDescriptor, + .number = GtalkRosterItem_FieldNumber_Display, + .hasIndex = 6, + .offset = (uint32_t)offsetof(GtalkRosterItem__storage_, display), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "rejected", + .dataTypeSpecific.className = NULL, + .number = GtalkRosterItem_FieldNumber_Rejected, + .hasIndex = 7, + .offset = 8, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkRosterItem class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkRosterItem__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - Enum GtalkRosterItem_SubscriptionType + +GPBEnumDescriptor *GtalkRosterItem_SubscriptionType_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "None\000To\000From\000Both\000Remove\000"; + static const int32_t values[] = { + GtalkRosterItem_SubscriptionType_None, + GtalkRosterItem_SubscriptionType_To, + GtalkRosterItem_SubscriptionType_From, + GtalkRosterItem_SubscriptionType_Both, + GtalkRosterItem_SubscriptionType_Remove, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkRosterItem_SubscriptionType) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkRosterItem_SubscriptionType_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkRosterItem_SubscriptionType_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkRosterItem_SubscriptionType_None: + case GtalkRosterItem_SubscriptionType_To: + case GtalkRosterItem_SubscriptionType_From: + case GtalkRosterItem_SubscriptionType_Both: + case GtalkRosterItem_SubscriptionType_Remove: + return YES; + default: + return NO; + } +} + +#pragma mark - Enum GtalkRosterItem_AskType + +GPBEnumDescriptor *GtalkRosterItem_AskType_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Subscribe\000"; + static const int32_t values[] = { + GtalkRosterItem_AskType_Subscribe, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkRosterItem_AskType) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkRosterItem_AskType_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkRosterItem_AskType_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkRosterItem_AskType_Subscribe: + return YES; + default: + return NO; + } +} + +#pragma mark - Enum GtalkRosterItem_DisplayType + +GPBEnumDescriptor *GtalkRosterItem_DisplayType_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Blocked\000Hidden\000Pinned\000"; + static const int32_t values[] = { + GtalkRosterItem_DisplayType_Blocked, + GtalkRosterItem_DisplayType_Hidden, + GtalkRosterItem_DisplayType_Pinned, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkRosterItem_DisplayType) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkRosterItem_DisplayType_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkRosterItem_DisplayType_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkRosterItem_DisplayType_Blocked: + case GtalkRosterItem_DisplayType_Hidden: + case GtalkRosterItem_DisplayType_Pinned: + return YES; + default: + return NO; + } +} + +#pragma mark - GtalkRmqLastId + +@implementation GtalkRmqLastId + +@dynamic hasId_p, id_p; + +typedef struct GtalkRmqLastId__storage_ { + uint32_t _has_storage_[1]; + int64_t id_p; +} GtalkRmqLastId__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkRmqLastId_FieldNumber_Id_p, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkRmqLastId__storage_, id_p), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeInt64, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkRmqLastId class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkRmqLastId__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkRmqAck + +@implementation GtalkRmqAck + +@dynamic hasId_p, id_p; + +typedef struct GtalkRmqAck__storage_ { + uint32_t _has_storage_[1]; + int64_t id_p; +} GtalkRmqAck__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "id_p", + .dataTypeSpecific.className = NULL, + .number = GtalkRmqAck_FieldNumber_Id_p, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkRmqAck__storage_, id_p), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeInt64, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkRmqAck class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkRmqAck__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkVCard + +@implementation GtalkVCard + +@dynamic hasVersion, version; +@dynamic hasFullName, fullName; +@dynamic hasPhoto, photo; +@dynamic hasAvatarHash, avatarHash; +@dynamic hasModified, modified; + +typedef struct GtalkVCard__storage_ { + uint32_t _has_storage_[1]; + NSString *version; + NSString *fullName; + GtalkPhoto *photo; + NSString *avatarHash; +} GtalkVCard__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "version", + .dataTypeSpecific.className = NULL, + .number = GtalkVCard_FieldNumber_Version, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkVCard__storage_, version), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "fullName", + .dataTypeSpecific.className = NULL, + .number = GtalkVCard_FieldNumber_FullName, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkVCard__storage_, fullName), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "photo", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkPhoto), + .number = GtalkVCard_FieldNumber_Photo, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkVCard__storage_, photo), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeMessage, + }, + { + .name = "avatarHash", + .dataTypeSpecific.className = NULL, + .number = GtalkVCard_FieldNumber_AvatarHash, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkVCard__storage_, avatarHash), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "modified", + .dataTypeSpecific.className = NULL, + .number = GtalkVCard_FieldNumber_Modified, + .hasIndex = 4, + .offset = 5, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkVCard class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkVCard__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkPhoto + +@implementation GtalkPhoto + +@dynamic hasType, type; +@dynamic hasData_p, data_p; + +typedef struct GtalkPhoto__storage_ { + uint32_t _has_storage_[1]; + NSString *type; + NSString *data_p; +} GtalkPhoto__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "type", + .dataTypeSpecific.className = NULL, + .number = GtalkPhoto_FieldNumber_Type, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkPhoto__storage_, type), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "data_p", + .dataTypeSpecific.className = NULL, + .number = GtalkPhoto_FieldNumber_Data_p, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkPhoto__storage_, data_p), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkPhoto class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkPhoto__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkChatRead + +@implementation GtalkChatRead + +@dynamic hasUser, user; + +typedef struct GtalkChatRead__storage_ { + uint32_t _has_storage_[1]; + NSString *user; +} GtalkChatRead__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "user", + .dataTypeSpecific.className = NULL, + .number = GtalkChatRead_FieldNumber_User, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkChatRead__storage_, user), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkChatRead class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkChatRead__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkChatClosed + +@implementation GtalkChatClosed + +@dynamic hasUser, user; + +typedef struct GtalkChatClosed__storage_ { + uint32_t _has_storage_[1]; + NSString *user; +} GtalkChatClosed__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "user", + .dataTypeSpecific.className = NULL, + .number = GtalkChatClosed_FieldNumber_User, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkChatClosed__storage_, user), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkChatClosed class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkChatClosed__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkCapabilities + +@implementation GtalkCapabilities + +@dynamic hasNode, node; +@dynamic hasVer, ver; +@dynamic hasExt, ext; +@dynamic hasHash_p, hash_p; + +typedef struct GtalkCapabilities__storage_ { + uint32_t _has_storage_[1]; + NSString *node; + NSString *ver; + NSString *ext; + NSString *hash_p; +} GtalkCapabilities__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "node", + .dataTypeSpecific.className = NULL, + .number = GtalkCapabilities_FieldNumber_Node, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkCapabilities__storage_, node), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "ver", + .dataTypeSpecific.className = NULL, + .number = GtalkCapabilities_FieldNumber_Ver, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkCapabilities__storage_, ver), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "ext", + .dataTypeSpecific.className = NULL, + .number = GtalkCapabilities_FieldNumber_Ext, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkCapabilities__storage_, ext), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "hash_p", + .dataTypeSpecific.className = NULL, + .number = GtalkCapabilities_FieldNumber_Hash_p, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkCapabilities__storage_, hash_p), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkCapabilities class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkCapabilities__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkSharedStatus + +@implementation GtalkSharedStatus + +@dynamic hasStatusMax, statusMax; +@dynamic hasStatusListMax, statusListMax; +@dynamic hasStatusListContentsMax, statusListContentsMax; +@dynamic hasStatus, status; +@dynamic hasShow, show; +@dynamic statusListArray, statusListArray_Count; +@dynamic hasInvisible, invisible; +@dynamic hasStatusMinVersion, statusMinVersion; + +typedef struct GtalkSharedStatus__storage_ { + uint32_t _has_storage_[1]; + int32_t statusMax; + int32_t statusListMax; + int32_t statusListContentsMax; + GtalkSharedStatus_ShowType show; + int32_t statusMinVersion; + NSString *status; + NSMutableArray *statusListArray; +} GtalkSharedStatus__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "statusMax", + .dataTypeSpecific.className = NULL, + .number = GtalkSharedStatus_FieldNumber_StatusMax, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkSharedStatus__storage_, statusMax), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "statusListMax", + .dataTypeSpecific.className = NULL, + .number = GtalkSharedStatus_FieldNumber_StatusListMax, + .hasIndex = 1, + .offset = (uint32_t)offsetof(GtalkSharedStatus__storage_, statusListMax), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "statusListContentsMax", + .dataTypeSpecific.className = NULL, + .number = GtalkSharedStatus_FieldNumber_StatusListContentsMax, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkSharedStatus__storage_, statusListContentsMax), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "status", + .dataTypeSpecific.className = NULL, + .number = GtalkSharedStatus_FieldNumber_Status, + .hasIndex = 3, + .offset = (uint32_t)offsetof(GtalkSharedStatus__storage_, status), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "show", + .dataTypeSpecific.enumDescFunc = GtalkSharedStatus_ShowType_EnumDescriptor, + .number = GtalkSharedStatus_FieldNumber_Show, + .hasIndex = 4, + .offset = (uint32_t)offsetof(GtalkSharedStatus__storage_, show), + .flags = (GPBFieldFlags)(GPBFieldOptional | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "statusListArray", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkSharedStatus_StatusList), + .number = GtalkSharedStatus_FieldNumber_StatusListArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkSharedStatus__storage_, statusListArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeGroup, + }, + { + .name = "invisible", + .dataTypeSpecific.className = NULL, + .number = GtalkSharedStatus_FieldNumber_Invisible, + .hasIndex = 5, + .offset = 6, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "statusMinVersion", + .dataTypeSpecific.className = NULL, + .number = GtalkSharedStatus_FieldNumber_StatusMinVersion, + .hasIndex = 7, + .offset = (uint32_t)offsetof(GtalkSharedStatus__storage_, statusMinVersion), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkSharedStatus class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkSharedStatus__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - Enum GtalkSharedStatus_ShowType + +GPBEnumDescriptor *GtalkSharedStatus_ShowType_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "Default\000Dnd\000"; + static const int32_t values[] = { + GtalkSharedStatus_ShowType_Default, + GtalkSharedStatus_ShowType_Dnd, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkSharedStatus_ShowType) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkSharedStatus_ShowType_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkSharedStatus_ShowType_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkSharedStatus_ShowType_Default: + case GtalkSharedStatus_ShowType_Dnd: + return YES; + default: + return NO; + } +} + +#pragma mark - GtalkSharedStatus_StatusList + +@implementation GtalkSharedStatus_StatusList + +@dynamic hasShow, show; +@dynamic statusArray, statusArray_Count; + +typedef struct GtalkSharedStatus_StatusList__storage_ { + uint32_t _has_storage_[1]; + GtalkSharedStatus_ShowType show; + NSMutableArray *statusArray; +} GtalkSharedStatus_StatusList__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "show", + .dataTypeSpecific.enumDescFunc = GtalkSharedStatus_ShowType_EnumDescriptor, + .number = GtalkSharedStatus_StatusList_FieldNumber_Show, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkSharedStatus_StatusList__storage_, show), + .flags = (GPBFieldFlags)(GPBFieldRequired | GPBFieldHasEnumDescriptor), + .dataType = GPBDataTypeEnum, + }, + { + .name = "statusArray", + .dataTypeSpecific.className = NULL, + .number = GtalkSharedStatus_StatusList_FieldNumber_StatusArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkSharedStatus_StatusList__storage_, statusArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeString, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkSharedStatus_StatusList class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkSharedStatus_StatusList__storage_) + flags:GPBDescriptorInitializationFlag_None]; + [localDescriptor setupContainingMessageClassName:GPBStringifySymbol(GtalkSharedStatus)]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkOtrQuery + +@implementation GtalkOtrQuery + +@dynamic hasNosaveDefault, nosaveDefault; +@dynamic itemArray, itemArray_Count; +@dynamic hasEtag, etag; +@dynamic hasNotModified, notModified; + +typedef struct GtalkOtrQuery__storage_ { + uint32_t _has_storage_[1]; + NSMutableArray *itemArray; + NSString *etag; +} GtalkOtrQuery__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "nosaveDefault", + .dataTypeSpecific.className = NULL, + .number = GtalkOtrQuery_FieldNumber_NosaveDefault, + .hasIndex = 0, + .offset = 1, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "itemArray", + .dataTypeSpecific.className = GPBStringifySymbol(GtalkOtrItem), + .number = GtalkOtrQuery_FieldNumber_ItemArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkOtrQuery__storage_, itemArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeMessage, + }, + { + .name = "etag", + .dataTypeSpecific.className = NULL, + .number = GtalkOtrQuery_FieldNumber_Etag, + .hasIndex = 2, + .offset = (uint32_t)offsetof(GtalkOtrQuery__storage_, etag), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "notModified", + .dataTypeSpecific.className = NULL, + .number = GtalkOtrQuery_FieldNumber_NotModified, + .hasIndex = 3, + .offset = 4, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkOtrQuery class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkOtrQuery__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkOtrItem + +@implementation GtalkOtrItem + +@dynamic hasJid, jid; +@dynamic hasNosave, nosave; +@dynamic hasChangedByBuddy, changedByBuddy; + +typedef struct GtalkOtrItem__storage_ { + uint32_t _has_storage_[1]; + NSString *jid; +} GtalkOtrItem__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "jid", + .dataTypeSpecific.className = NULL, + .number = GtalkOtrItem_FieldNumber_Jid, + .hasIndex = 0, + .offset = (uint32_t)offsetof(GtalkOtrItem__storage_, jid), + .flags = GPBFieldRequired, + .dataType = GPBDataTypeString, + }, + { + .name = "nosave", + .dataTypeSpecific.className = NULL, + .number = GtalkOtrItem_FieldNumber_Nosave, + .hasIndex = 1, + .offset = 2, // Stored in _has_storage_ to save space. + .flags = GPBFieldRequired, + .dataType = GPBDataTypeBool, + }, + { + .name = "changedByBuddy", + .dataTypeSpecific.className = NULL, + .number = GtalkOtrItem_FieldNumber_ChangedByBuddy, + .hasIndex = 3, + .offset = 4, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkOtrItem class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkOtrItem__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkIdle + +@implementation GtalkIdle + +@dynamic hasIdle, idle; +@dynamic hasAway, away; + +typedef struct GtalkIdle__storage_ { + uint32_t _has_storage_[1]; +} GtalkIdle__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "idle", + .dataTypeSpecific.className = NULL, + .number = GtalkIdle_FieldNumber_Idle, + .hasIndex = 0, + .offset = 1, // Stored in _has_storage_ to save space. + .flags = GPBFieldRequired, + .dataType = GPBDataTypeBool, + }, + { + .name = "away", + .dataTypeSpecific.className = NULL, + .number = GtalkIdle_FieldNumber_Away, + .hasIndex = 2, + .offset = 3, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkIdle class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkIdle__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkPostAuthBatchQuery + +@implementation GtalkPostAuthBatchQuery + +@dynamic hasAvailable, available; +@dynamic hasDeviceIdle, deviceIdle; +@dynamic hasMobileIndicator, mobileIndicator; +@dynamic hasSharedStatusVersion, sharedStatusVersion; +@dynamic hasRosterEtag, rosterEtag; +@dynamic hasOtrEtag, otrEtag; +@dynamic hasAvatarHash, avatarHash; +@dynamic hasVcardQueryStanzaId, vcardQueryStanzaId; +@dynamic hasCapabilitiesExtFlags, capabilitiesExtFlags; + +typedef struct GtalkPostAuthBatchQuery__storage_ { + uint32_t _has_storage_[1]; + int32_t sharedStatusVersion; + int32_t capabilitiesExtFlags; + NSString *rosterEtag; + NSString *otrEtag; + NSString *avatarHash; + NSString *vcardQueryStanzaId; +} GtalkPostAuthBatchQuery__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "available", + .dataTypeSpecific.className = NULL, + .number = GtalkPostAuthBatchQuery_FieldNumber_Available, + .hasIndex = 0, + .offset = 1, // Stored in _has_storage_ to save space. + .flags = GPBFieldRequired, + .dataType = GPBDataTypeBool, + }, + { + .name = "deviceIdle", + .dataTypeSpecific.className = NULL, + .number = GtalkPostAuthBatchQuery_FieldNumber_DeviceIdle, + .hasIndex = 2, + .offset = 3, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "mobileIndicator", + .dataTypeSpecific.className = NULL, + .number = GtalkPostAuthBatchQuery_FieldNumber_MobileIndicator, + .hasIndex = 4, + .offset = 5, // Stored in _has_storage_ to save space. + .flags = GPBFieldOptional, + .dataType = GPBDataTypeBool, + }, + { + .name = "sharedStatusVersion", + .dataTypeSpecific.className = NULL, + .number = GtalkPostAuthBatchQuery_FieldNumber_SharedStatusVersion, + .hasIndex = 6, + .offset = (uint32_t)offsetof(GtalkPostAuthBatchQuery__storage_, sharedStatusVersion), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + { + .name = "rosterEtag", + .dataTypeSpecific.className = NULL, + .number = GtalkPostAuthBatchQuery_FieldNumber_RosterEtag, + .hasIndex = 7, + .offset = (uint32_t)offsetof(GtalkPostAuthBatchQuery__storage_, rosterEtag), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "otrEtag", + .dataTypeSpecific.className = NULL, + .number = GtalkPostAuthBatchQuery_FieldNumber_OtrEtag, + .hasIndex = 8, + .offset = (uint32_t)offsetof(GtalkPostAuthBatchQuery__storage_, otrEtag), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "avatarHash", + .dataTypeSpecific.className = NULL, + .number = GtalkPostAuthBatchQuery_FieldNumber_AvatarHash, + .hasIndex = 9, + .offset = (uint32_t)offsetof(GtalkPostAuthBatchQuery__storage_, avatarHash), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "vcardQueryStanzaId", + .dataTypeSpecific.className = NULL, + .number = GtalkPostAuthBatchQuery_FieldNumber_VcardQueryStanzaId, + .hasIndex = 10, + .offset = (uint32_t)offsetof(GtalkPostAuthBatchQuery__storage_, vcardQueryStanzaId), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeString, + }, + { + .name = "capabilitiesExtFlags", + .dataTypeSpecific.className = NULL, + .number = GtalkPostAuthBatchQuery_FieldNumber_CapabilitiesExtFlags, + .hasIndex = 11, + .offset = (uint32_t)offsetof(GtalkPostAuthBatchQuery__storage_, capabilitiesExtFlags), + .flags = GPBFieldOptional, + .dataType = GPBDataTypeInt32, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkPostAuthBatchQuery class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkPostAuthBatchQuery__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - Enum GtalkPostAuthBatchQuery_CapabilitiesExtFlags + +GPBEnumDescriptor *GtalkPostAuthBatchQuery_CapabilitiesExtFlags_EnumDescriptor(void) { + static GPBEnumDescriptor *descriptor = NULL; + if (!descriptor) { + static const char *valueNames = + "HasVoiceV1\000HasVideoV1\000HasCameraV1\000HasPmu" + "cV1\000"; + static const int32_t values[] = { + GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasVoiceV1, + GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasVideoV1, + GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasCameraV1, + GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasPmucV1, + }; + GPBEnumDescriptor *worker = + [GPBEnumDescriptor allocDescriptorForName:GPBNSStringifySymbol(GtalkPostAuthBatchQuery_CapabilitiesExtFlags) + valueNames:valueNames + values:values + count:(uint32_t)(sizeof(values) / sizeof(int32_t)) + enumVerifier:GtalkPostAuthBatchQuery_CapabilitiesExtFlags_IsValidValue]; + if (!OSAtomicCompareAndSwapPtrBarrier(nil, worker, (void * volatile *)&descriptor)) { + [worker release]; + } + } + return descriptor; +} + +BOOL GtalkPostAuthBatchQuery_CapabilitiesExtFlags_IsValidValue(int32_t value__) { + switch (value__) { + case GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasVoiceV1: + case GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasVideoV1: + case GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasCameraV1: + case GtalkPostAuthBatchQuery_CapabilitiesExtFlags_HasPmucV1: + return YES; + default: + return NO; + } +} + +#pragma mark - GtalkStreamAck + +@implementation GtalkStreamAck + + +typedef struct GtalkStreamAck__storage_ { + uint32_t _has_storage_[1]; +} GtalkStreamAck__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkStreamAck class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:NULL + fieldCount:0 + storageSize:sizeof(GtalkStreamAck__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + +#pragma mark - GtalkSelectiveAck + +@implementation GtalkSelectiveAck + +@dynamic idArray, idArray_Count; + +typedef struct GtalkSelectiveAck__storage_ { + uint32_t _has_storage_[1]; + NSMutableArray *idArray; +} GtalkSelectiveAck__storage_; + +// This method is threadsafe because it is initially called +// in +initialize for each subclass. ++ (GPBDescriptor *)descriptor { + static GPBDescriptor *descriptor = nil; + if (!descriptor) { + static GPBMessageFieldDescription fields[] = { + { + .name = "idArray", + .dataTypeSpecific.className = NULL, + .number = GtalkSelectiveAck_FieldNumber_IdArray, + .hasIndex = GPBNoHasBit, + .offset = (uint32_t)offsetof(GtalkSelectiveAck__storage_, idArray), + .flags = GPBFieldRepeated, + .dataType = GPBDataTypeString, + }, + }; + GPBDescriptor *localDescriptor = + [GPBDescriptor allocDescriptorForClass:[GtalkSelectiveAck class] + rootClass:[GtalkGtalkExtensionsRoot class] + file:GtalkGtalkExtensionsRoot_FileDescriptor() + fields:fields + fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription)) + storageSize:sizeof(GtalkSelectiveAck__storage_) + flags:GPBDescriptorInitializationFlag_None]; + NSAssert(descriptor == nil, @"Startup recursed!"); + descriptor = localDescriptor; + } + return descriptor; +} + +@end + + +#pragma clang diagnostic pop + +// @@protoc_insertion_point(global_scope) diff --git a/Firebase/Messaging/Public/FIRMessaging.h b/Firebase/Messaging/Public/FIRMessaging.h new file mode 100644 index 0000000..84d2526 --- /dev/null +++ b/Firebase/Messaging/Public/FIRMessaging.h @@ -0,0 +1,486 @@ +/* + * Copyright 2017 Google + * + * 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 <Foundation/Foundation.h> + + +// NS_SWIFT_NAME can only translate factory methods before the iOS 9.3 SDK. +// Wrap it in our own macro if it's a non-compatible SDK. +#ifndef FIR_SWIFT_NAME +#ifdef __IPHONE_9_3 +#define FIR_SWIFT_NAME(X) NS_SWIFT_NAME(X) +#else +#define FIR_SWIFT_NAME(X) // Intentionally blank. +#endif // #ifdef __IPHONE_9_3 +#endif // #ifndef FIR_SWIFT_NAME + +/** + * @related FIRMessaging + * + * The completion handler invoked when the registration token returns. + * If the call fails we return the appropriate `error code`, described by + * `FIRMessagingError`. + * + * @param FCMToken The valid registration token returned by FCM. + * @param error The error describing why a token request failed. The error code + * will match a value from the FIRMessagingError enumeration. + */ +typedef void(^FIRMessagingFCMTokenFetchCompletion)(NSString * _Nullable FCMToken, + NSError * _Nullable error) + FIR_SWIFT_NAME(MessagingFCMTokenFetchCompletion); + + +/** + * @related FIRMessaging + * + * The completion handler invoked when the registration token deletion request is + * completed. If the call fails we return the appropriate `error code`, described + * by `FIRMessagingError`. + * + * @param error The error describing why a token deletion failed. The error code + * will match a value from the FIRMessagingError enumeration. + */ +typedef void(^FIRMessagingDeleteFCMTokenCompletion)(NSError * _Nullable error) + FIR_SWIFT_NAME(MessagingDeleteFCMTokenCompletion); + +/** + * The completion handler invoked once the data connection with FIRMessaging is + * established. The data connection is used to send a continous stream of + * data and all the FIRMessaging data notifications arrive through this connection. + * Once the connection is established we invoke the callback with `nil` error. + * Correspondingly if we get an error while trying to establish a connection + * we invoke the handler with an appropriate error object and do an + * exponential backoff to try and connect again unless successful. + * + * @param error The error object if any describing why the data connection + * to FIRMessaging failed. + */ +typedef void(^FIRMessagingConnectCompletion)(NSError * __nullable error) + FIR_SWIFT_NAME(MessagingConnectCompletion) + __deprecated_msg("Please listen for the FIRMessagingConnectionStateChangedNotification " + "NSNotification instead."); + +#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +/** + * Notification sent when the upstream message has been delivered + * successfully to the server. The notification object will be the messageID + * of the successfully delivered message. + */ +FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingSendSuccessNotification + FIR_SWIFT_NAME(MessagingSendSuccess); + +/** + * Notification sent when the upstream message was failed to be sent to the + * server. The notification object will be the messageID of the failed + * message. The userInfo dictionary will contain the relevant error + * information for the failure. + */ +FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingSendErrorNotification + FIR_SWIFT_NAME(MessagingSendError); + +/** + * Notification sent when the Firebase messaging server deletes pending + * messages due to exceeded storage limits. This may occur, for example, when + * the device cannot be reached for an extended period of time. + * + * It is recommended to retrieve any missing messages directly from the + * server. + */ +FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingMessagesDeletedNotification + FIR_SWIFT_NAME(MessagingMessagesDeleted); + +/** + * Notification sent when Firebase Messaging establishes or disconnects from + * an FCM socket connection. You can query the connection state in this + * notification by checking the `isDirectChannelEstablished` property of FIRMessaging. + */ +FOUNDATION_EXPORT const NSNotificationName __nonnull FIRMessagingConnectionStateChangedNotification + FIR_SWIFT_NAME(MessagingConnectionStateChanged); + +/** + * Notification sent when the FCM registration token has been refreshed. You can also + * receive the FCM token via the FIRMessagingDelegate method + * `-messaging:didRefreshRegistrationToken:` + */ +FOUNDATION_EXPORT const NSNotificationName __nonnull + FIRMessagingRegistrationTokenRefreshedNotification + FIR_SWIFT_NAME(MessagingRegistrationTokenRefreshed); +#else +/** + * Notification sent when the upstream message has been delivered + * successfully to the server. The notification object will be the messageID + * of the successfully delivered message. + */ +FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingSendSuccessNotification + FIR_SWIFT_NAME(MessagingSendSuccessNotification); + +/** + * Notification sent when the upstream message was failed to be sent to the + * server. The notification object will be the messageID of the failed + * message. The userInfo dictionary will contain the relevant error + * information for the failure. + */ +FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingSendErrorNotification + FIR_SWIFT_NAME(MessagingSendErrorNotification); + +/** + * Notification sent when the Firebase messaging server deletes pending + * messages due to exceeded storage limits. This may occur, for example, when + * the device cannot be reached for an extended period of time. + * + * It is recommended to retrieve any missing messages directly from the + * server. + */ +FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingMessagesDeletedNotification + FIR_SWIFT_NAME(MessagingMessagesDeletedNotification); + +/** + * Notification sent when Firebase Messaging establishes or disconnects from + * an FCM socket connection. You can query the connection state in this + * notification by checking the `isDirectChannelEstablished` property of FIRMessaging. + */ +FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingConnectionStateChangedNotification + FIR_SWIFT_NAME(MessagingConnectionStateChangedNotification); + +/** + * Notification sent when the FCM registration token has been refreshed. You can also + * receive the FCM token via the FIRMessagingDelegate method + * `-messaging:didRefreshRegistrationToken:` + */ +FOUNDATION_EXPORT NSString * __nonnull const FIRMessagingRegistrationTokenRefreshedNotification + FIR_SWIFT_NAME(MessagingRegistrationTokenRefreshedNotification); +#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 + +/** + * @enum FIRMessagingError + */ +typedef NS_ENUM(NSUInteger, FIRMessagingError) { + /// Unknown error. + FIRMessagingErrorUnknown = 0, + + /// FIRMessaging couldn't validate request from this client. + FIRMessagingErrorAuthentication = 1, + + /// InstanceID service cannot be accessed. + FIRMessagingErrorNoAccess = 2, + + /// Request to InstanceID backend timed out. + FIRMessagingErrorTimeout = 3, + + /// No network available to reach the servers. + FIRMessagingErrorNetwork = 4, + + /// Another similar operation in progress, bailing this one. + FIRMessagingErrorOperationInProgress = 5, + + /// Some parameters of the request were invalid. + FIRMessagingErrorInvalidRequest = 7, +} FIR_SWIFT_NAME(MessagingError); + +/// Status for the downstream message received by the app. +typedef NS_ENUM(NSInteger, FIRMessagingMessageStatus) { + /// Unknown status. + FIRMessagingMessageStatusUnknown, + /// New downstream message received by the app. + FIRMessagingMessageStatusNew, +} FIR_SWIFT_NAME(MessagingMessageStatus); + +/** + * The APNS token type for the app. If the token type is set to `UNKNOWN` + * Firebase Messaging will implicitly try to figure out what the actual token type + * is from the provisioning profile. + * Unless you really need to specify the type, you should use the `APNSToken` + * property instead. + */ +typedef NS_ENUM(NSInteger, FIRMessagingAPNSTokenType) { + /// Unknown token type. + FIRMessagingAPNSTokenTypeUnknown, + /// Sandbox token type. + FIRMessagingAPNSTokenTypeSandbox, + /// Production token type. + FIRMessagingAPNSTokenTypeProd, +} FIR_SWIFT_NAME(MessagingAPNSTokenType); + +/// Information about a downstream message received by the app. +FIR_SWIFT_NAME(MessagingMessageInfo) +@interface FIRMessagingMessageInfo : NSObject + +/// The status of the downstream message +@property(nonatomic, readonly, assign) FIRMessagingMessageStatus status; + +@end + +/** + * A remote data message received by the app via FCM (not just the APNs interface). + * + * This is only for devices running iOS 10 or above. To support devices running iOS 9 or below, use + * the local and remote notifications handlers defined in UIApplicationDelegate protocol. + */ +FIR_SWIFT_NAME(MessagingRemoteMessage) +@interface FIRMessagingRemoteMessage : NSObject + +/// The downstream message received by the application. +@property(nonatomic, readonly, strong, nonnull) NSDictionary *appData; +@end + +@class FIRMessaging; +/** + * A protocol to handle events from FCM for devices running iOS 10 or above. + * + * To support devices running iOS 9 or below, use the local and remote notifications handlers + * defined in UIApplicationDelegate protocol. + */ +FIR_SWIFT_NAME(MessagingDelegate) +@protocol FIRMessagingDelegate <NSObject> + +/// This method will be called whenever FCM receives a new, default FCM token for your +/// Firebase project's Sender ID. +/// You can send this token to your application server to send notifications to this device. +- (void)messaging:(nonnull FIRMessaging *)messaging + didRefreshRegistrationToken:(nonnull NSString *)fcmToken + FIR_SWIFT_NAME(messaging(_:didRefreshRegistrationToken:)); + +@optional +/// This method is called on iOS 10 devices to handle data messages received via FCM through its +/// direct channel (not via APNS). For iOS 9 and below, the FCM data message is delivered via the +/// UIApplicationDelegate's -application:didReceiveRemoteNotification: method. +- (void)messaging:(nonnull FIRMessaging *)messaging + didReceiveMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage + FIR_SWIFT_NAME(messaging(_:didReceive:)) + __IOS_AVAILABLE(10.0); + +/// The callback to handle data message received via FCM for devices running iOS 10 or above. +- (void)applicationReceivedRemoteMessage:(nonnull FIRMessagingRemoteMessage *)remoteMessage + FIR_SWIFT_NAME(application(received:)) + __deprecated_msg("Use FIRMessagingDelegate’s -messaging:didReceiveMessage:"); + +@end + +/** + * Firebase Messaging lets you reliably deliver messages at no cost. + * + * To send or receive messages, the app must get a + * registration token from FIRInstanceID. This token authorizes an + * app server to send messages to an app instance. + * + * In order to receive FIRMessaging messages, declare `application:didReceiveRemoteNotification:`. + */ +FIR_SWIFT_NAME(Messaging) +@interface FIRMessaging : NSObject + +/** + * Delegate to handle FCM token refreshes, and remote data messages received via FCM for devices + * running iOS 10 or above. + */ +@property(nonatomic, weak, nullable) id<FIRMessagingDelegate> delegate; + + +/** + * Delegate to handle remote data messages received via FCM for devices running iOS 10 or above. + */ +@property(nonatomic, weak, nullable) id<FIRMessagingDelegate> remoteMessageDelegate + __deprecated_msg("Use 'delegate' property"); + +/** + * When set to YES, Firebase Messaging will automatically establish a socket-based, direct channel + * to the FCM server. You only need to enable this if you are sending upstream messages or + * receiving non-APNS, data-only messages in foregrounded apps. + * Default is NO. + */ +@property(nonatomic) BOOL shouldEstablishDirectChannel; + +/** + * Returns YES if the direct channel to the FCM server is active, NO otherwise. + */ +@property(nonatomic, readonly) BOOL isDirectChannelEstablished; + +/** + * FIRMessaging + * + * @return An instance of FIRMessaging. + */ ++ (nonnull instancetype)messaging FIR_SWIFT_NAME(messaging()); + +/** + * Unavailable. Use +messaging instead. + */ +- (nonnull instancetype)init __attribute__((unavailable("Use +messaging instead."))); + +#pragma mark - APNS + +/** + * This property is used to set the APNS Token received by the application delegate. + * + * FIRMessaging uses method swizzling to ensure the APNS token is set automatically. + * However, if you have disabled swizzling by setting `FirebaseAppDelegateProxyEnabled` + * to `NO` in your app's Info.plist, you should manually set the APNS token in your + * application delegate's -application:didRegisterForRemoteNotificationsWithDeviceToken: + * method. + * + * If you would like to set the type of the APNS token, rather than relying on automatic + * detection, see: -setAPNSToken:type:. + */ +@property(nonatomic, copy, nullable) NSData *APNSToken FIR_SWIFT_NAME(apnsToken); + +/** + * Set APNS token for the application. This APNS token will be used to register + * with Firebase Messaging using `FCMToken` or + * `tokenWithAuthorizedEntity:scope:options:handler`. + * + * @param apnsToken The APNS token for the application. + * @param type The type of APNS token. Debug builds should use + * FIRMessagingAPNSTokenTypeSandbox. Alternatively, you can supply + * FIRMessagingAPNSTokenTypeUnknown to have the type automatically + * detected based on your provisioning profile. + */ +- (void)setAPNSToken:(nonnull NSData *)apnsToken type:(FIRMessagingAPNSTokenType)type; + +#pragma mark - FCM Tokens + +/** + * The FCM token is used to identify this device so that FCM can send notifications to it. + * It is associated with your APNS token when the APNS token is supplied, so that sending + * messages to the FCM token will be delivered over APNS. + * + * The FCM token is sometimes refreshed automatically. You can be notified of these changes + * via the FIRMessagingDelegate method `-message:didRefreshRegistrationToken:`, or by + * listening for the `FIRMessagingRegistrationTokenRefreshedNotification` notification. + * + * Once you have an FCM token, you should send it to your application server, so it can use + * the FCM token to send notifications to your device. + */ +@property(nonatomic, readonly, nullable) NSString *FCMToken FIR_SWIFT_NAME(fcmToken); + + +/** + * Retrieves an FCM registration token for a particular Sender ID. This registration token is + * not cached by FIRMessaging. FIRMessaging should have an APNS token set before calling this + * to ensure that notifications can be delivered via APNS using this FCM token. You may + * re-retrieve the FCM token once you have the APNS token set, to associate it with the FCM + * token. The default FCM token is automatically associated with the APNS token, if the APNS + * token data is available. + * + * @param senderID The Sender ID for a particular Firebase project. + * @param completion The completion handler to handle the token request. + */ +- (void)retrieveFCMTokenForSenderID:(nonnull NSString *)senderID + completion:(nonnull FIRMessagingFCMTokenFetchCompletion)completion + FIR_SWIFT_NAME(retrieveFCMToken(forSenderID:completion:)); + + +/** + * Invalidates an FCM token for a particular Sender ID. That Sender ID cannot no longer send + * notifications to that FCM token. + * + * @param senderID The senderID for a particular Firebase project. + * @param completion The completion handler to handle the token deletion. + */ +- (void)deleteFCMTokenForSenderID:(nonnull NSString *)senderID + completion:(nonnull FIRMessagingDeleteFCMTokenCompletion)completion + FIR_SWIFT_NAME(deleteFCMToken(forSenderID:completion:)); + + +#pragma mark - Connect + +/** + * Create a FIRMessaging data connection which will be used to send the data notifications + * sent by your server. It will also be used to send ACKS and other messages based + * on the FIRMessaging ACKS and other messages based on the FIRMessaging protocol. + * + * + * @param handler The handler to be invoked once the connection is established. + * If the connection fails we invoke the handler with an + * appropriate error code letting you know why it failed. At + * the same time, FIRMessaging performs exponential backoff to retry + * establishing a connection and invoke the handler when successful. + */ +- (void)connectWithCompletion:(nonnull FIRMessagingConnectCompletion)handler + FIR_SWIFT_NAME(connect(handler:)) + __deprecated_msg("Please use the shouldEstablishDirectChannel property instead."); + +/** + * Disconnect the current FIRMessaging data connection. This stops any attempts to + * connect to FIRMessaging. Calling this on an already disconnected client is a no-op. + * + * Call this before `teardown` when your app is going to the background. + * Since the FIRMessaging connection won't be allowed to live when in background it is + * prudent to close the connection. + */ +- (void)disconnect + __deprecated_msg("Please use the shouldEstablishDirectChannel property instead."); + +#pragma mark - Topics + +/** + * Asynchronously subscribes to a topic. + * + * @param topic The name of the topic, for example, @"sports". + */ +- (void)subscribeToTopic:(nonnull NSString *)topic FIR_SWIFT_NAME(subscribe(toTopic:)); + +/** + * Asynchronously unsubscribe from a topic. + * + * @param topic The name of the topic, for example @"sports". + */ +- (void)unsubscribeFromTopic:(nonnull NSString *)topic FIR_SWIFT_NAME(unsubscribe(fromTopic:)); + +#pragma mark - Upstream + +/** + * Sends an upstream ("device to cloud") message. + * + * The message is queued if we don't have an active connection. + * You can only use the upstream feature if your FCM implementation + * uses the XMPP server protocol. + * + * @param message Key/Value pairs to be sent. Values must be String, any + * other type will be ignored. + * @param receiver A string identifying the receiver of the message. For FCM + * project IDs the value is `SENDER_ID@gcm.googleapis.com`. + * @param messageID The ID of the message. This is generated by the application. It + * must be unique for each message generated by this application. + * It allows error callbacks and debugging, to uniquely identify + * each message. + * @param ttl The time to live for the message. In case we aren't able to + * send the message before the TTL expires we will send you a + * callback. If 0, we'll attempt to send immediately and return + * an error if we're not connected. Otherwise, the message will + * be queued. As for server-side messages, we don't return an error + * if the message has been dropped because of TTL; this can happen + * on the server side, and it would require extra communication. + */ +- (void)sendMessage:(nonnull NSDictionary *)message + to:(nonnull NSString *)receiver + withMessageID:(nonnull NSString *)messageID + timeToLive:(int64_t)ttl; + +#pragma mark - Analytics + +/** + * Use this to track message delivery and analytics for messages, typically + * when you receive a notification in `application:didReceiveRemoteNotification:`. + * However, you only need to call this if you set the `FirebaseAppDelegateProxyEnabled` + * flag to NO in your Info.plist. If `FirebaseAppDelegateProxyEnabled` is either missing + * or set to YES in your Info.plist, the library will call this automatically. + * + * @param message The downstream message received by the application. + * + * @return Information about the downstream message. + */ +- (nonnull FIRMessagingMessageInfo *)appDidReceiveMessage:(nonnull NSDictionary *)message; + +@end diff --git a/Firebase/Messaging/Public/FirebaseMessaging.h b/Firebase/Messaging/Public/FirebaseMessaging.h new file mode 100755 index 0000000..ef081c9 --- /dev/null +++ b/Firebase/Messaging/Public/FirebaseMessaging.h @@ -0,0 +1,17 @@ +/* + * Copyright 2017 Google + * + * 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 "FIRMessaging.h" |