aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Messaging
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
committerGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
commit98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch)
tree131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Firebase/Messaging
parent32461366c9e204a527ca05e6e9b9404a2454ac51 (diff)
Initial
Diffstat (limited to 'Firebase/Messaging')
-rw-r--r--Firebase/Messaging/FIRMMessageCode.h169
-rw-r--r--Firebase/Messaging/FIRMessaging+FIRApp.h24
-rw-r--r--Firebase/Messaging/FIRMessaging+FIRApp.m111
-rw-r--r--Firebase/Messaging/FIRMessaging.m1071
-rw-r--r--Firebase/Messaging/FIRMessagingCheckinService.h53
-rw-r--r--Firebase/Messaging/FIRMessagingCheckinService.m132
-rw-r--r--Firebase/Messaging/FIRMessagingClient.h156
-rw-r--r--Firebase/Messaging/FIRMessagingClient.m490
-rw-r--r--Firebase/Messaging/FIRMessagingCodedInputStream.h28
-rw-r--r--Firebase/Messaging/FIRMessagingCodedInputStream.m142
-rw-r--r--Firebase/Messaging/FIRMessagingConfig.h46
-rw-r--r--Firebase/Messaging/FIRMessagingConfig.m38
-rw-r--r--Firebase/Messaging/FIRMessagingConnection.h107
-rw-r--r--Firebase/Messaging/FIRMessagingConnection.m711
-rw-r--r--Firebase/Messaging/FIRMessagingConstants.h58
-rw-r--r--Firebase/Messaging/FIRMessagingConstants.m51
-rw-r--r--Firebase/Messaging/FIRMessagingContextManagerService.h44
-rw-r--r--Firebase/Messaging/FIRMessagingContextManagerService.m189
-rw-r--r--Firebase/Messaging/FIRMessagingDataMessageManager.h101
-rw-r--r--Firebase/Messaging/FIRMessagingDataMessageManager.m545
-rw-r--r--Firebase/Messaging/FIRMessagingDefines.h96
-rw-r--r--Firebase/Messaging/FIRMessagingDelayedMessageQueue.h35
-rw-r--r--Firebase/Messaging/FIRMessagingDelayedMessageQueue.m146
-rw-r--r--Firebase/Messaging/FIRMessagingFileLogger.h31
-rw-r--r--Firebase/Messaging/FIRMessagingFileLogger.m108
-rw-r--r--Firebase/Messaging/FIRMessagingInstanceIDProxy.h56
-rw-r--r--Firebase/Messaging/FIRMessagingInstanceIDProxy.m123
-rw-r--r--Firebase/Messaging/FIRMessagingLogger.h97
-rw-r--r--Firebase/Messaging/FIRMessagingLogger.m305
-rw-r--r--Firebase/Messaging/FIRMessagingPacketQueue.h43
-rw-r--r--Firebase/Messaging/FIRMessagingPacketQueue.m103
-rw-r--r--Firebase/Messaging/FIRMessagingPendingTopicsList.h118
-rw-r--r--Firebase/Messaging/FIRMessagingPendingTopicsList.m261
-rw-r--r--Firebase/Messaging/FIRMessagingPersistentSyncMessage.h28
-rw-r--r--Firebase/Messaging/FIRMessagingPersistentSyncMessage.m54
-rw-r--r--Firebase/Messaging/FIRMessagingPubSub.h148
-rw-r--r--Firebase/Messaging/FIRMessagingPubSub.m278
-rw-r--r--Firebase/Messaging/FIRMessagingPubSubRegistrar.h56
-rw-r--r--Firebase/Messaging/FIRMessagingPubSubRegistrar.m78
-rw-r--r--Firebase/Messaging/FIRMessagingReceiver.h31
-rw-r--r--Firebase/Messaging/FIRMessagingReceiver.m141
-rw-r--r--Firebase/Messaging/FIRMessagingRegistrar.h87
-rw-r--r--Firebase/Messaging/FIRMessagingRegistrar.m112
-rw-r--r--Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.h40
-rw-r--r--Firebase/Messaging/FIRMessagingRemoteNotificationsProxy.m613
-rw-r--r--Firebase/Messaging/FIRMessagingRmq2PersistentStore.h201
-rw-r--r--Firebase/Messaging/FIRMessagingRmq2PersistentStore.m770
-rw-r--r--Firebase/Messaging/FIRMessagingRmqManager.h190
-rw-r--r--Firebase/Messaging/FIRMessagingRmqManager.m264
-rw-r--r--Firebase/Messaging/FIRMessagingSecureSocket.h56
-rw-r--r--Firebase/Messaging/FIRMessagingSecureSocket.m448
-rw-r--r--Firebase/Messaging/FIRMessagingSyncMessageManager.h59
-rw-r--r--Firebase/Messaging/FIRMessagingSyncMessageManager.m147
-rw-r--r--Firebase/Messaging/FIRMessagingTopicOperation.h45
-rw-r--r--Firebase/Messaging/FIRMessagingTopicOperation.m246
-rw-r--r--Firebase/Messaging/FIRMessagingTopicsCommon.h52
-rw-r--r--Firebase/Messaging/FIRMessagingUtilities.h54
-rw-r--r--Firebase/Messaging/FIRMessagingUtilities.m173
-rw-r--r--Firebase/Messaging/FIRMessagingVersionUtilities.h35
-rw-r--r--Firebase/Messaging/FIRMessagingVersionUtilities.m87
-rw-r--r--Firebase/Messaging/FIRMessaging_Private.h56
-rw-r--r--Firebase/Messaging/FirebaseMessaging.h17
-rw-r--r--Firebase/Messaging/FirebaseMessaging.podspec41
-rw-r--r--Firebase/Messaging/InternalHeaders/FIRMessagingInternalUtilities.h30
-rw-r--r--Firebase/Messaging/NSDictionary+FIRMessaging.h45
-rw-r--r--Firebase/Messaging/NSDictionary+FIRMessaging.m59
-rw-r--r--Firebase/Messaging/NSError+FIRMessaging.h68
-rw-r--r--Firebase/Messaging/NSError+FIRMessaging.m35
-rw-r--r--Firebase/Messaging/Protos/GtalkCore.pbobjc.h1344
-rw-r--r--Firebase/Messaging/Protos/GtalkCore.pbobjc.m2947
-rw-r--r--Firebase/Messaging/Protos/GtalkExtensions.pbobjc.h617
-rw-r--r--Firebase/Messaging/Protos/GtalkExtensions.pbobjc.m1407
-rw-r--r--Firebase/Messaging/Public/FIRMessaging.h486
-rwxr-xr-xFirebase/Messaging/Public/FirebaseMessaging.h17
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&mdash;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"