You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

264 lines
8.2 KiB

6 years ago
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FIRMessagingRmqManager.h"
  17. #import <sqlite3.h>
  18. #import "FIRMessagingDefines.h"
  19. #import "FIRMessagingLogger.h"
  20. #import "FIRMessagingRmq2PersistentStore.h"
  21. #import "FIRMessagingUtilities.h"
  22. #import "Protos/GtalkCore.pbobjc.h"
  23. #ifndef _FIRMessagingRmqLogAndExit
  24. #define _FIRMessagingRmqLogAndExit(stmt, return_value) \
  25. do { \
  26. [self logErrorAndFinalizeStatement:stmt]; \
  27. return return_value; \
  28. } while(0)
  29. #endif
  30. static NSString *const kFCMRmqTag = @"FIRMessagingRmq:";
  31. @interface FIRMessagingRmqManager ()
  32. @property(nonatomic, readwrite, strong) FIRMessagingRmq2PersistentStore *rmq2Store;
  33. // map the category of an outgoing message with the number of messages for that category
  34. // should always have two keys -- the app, gcm
  35. @property(nonatomic, readwrite, strong) NSMutableDictionary *outstandingMessages;
  36. // Outgoing RMQ persistent id
  37. @property(nonatomic, readwrite, assign) int64_t rmqId;
  38. @end
  39. @implementation FIRMessagingRmqManager
  40. - (instancetype)initWithDatabaseName:(NSString *)databaseName {
  41. self = [super init];
  42. if (self) {
  43. _FIRMessagingDevAssert([databaseName length] > 0, @"RMQ: Invalid rmq db name");
  44. _rmq2Store = [[FIRMessagingRmq2PersistentStore alloc] initWithDatabaseName:databaseName];
  45. _outstandingMessages = [NSMutableDictionary dictionaryWithCapacity:2];
  46. _rmqId = -1;
  47. }
  48. return self;
  49. }
  50. - (void)loadRmqId {
  51. if (self.rmqId >= 0) {
  52. return; // already done
  53. }
  54. [self loadInitialOutgoingPersistentId];
  55. if (self.outstandingMessages.count) {
  56. FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRmqManager000,
  57. @"%@: outstanding categories %ld", kFCMRmqTag,
  58. _FIRMessaging_UL(self.outstandingMessages.count));
  59. }
  60. }
  61. /**
  62. * Initialize the 'initial RMQ':
  63. * - max ID of any message in the queue
  64. * - if the queue is empty, stored value in separate DB.
  65. *
  66. * Stream acks will remove from RMQ, when we remove the highest message we keep track
  67. * of its ID.
  68. */
  69. - (void)loadInitialOutgoingPersistentId {
  70. // we shouldn't always trust the lastRmqId stored in the LastRmqId table, because
  71. // we only save to the LastRmqId table once in a while (after getting the lastRmqId sent
  72. // by the server after reconnect, and after getting a rmq ack from the server). The
  73. // rmq message with the highest rmq id tells the real story, so check against that first.
  74. int64_t rmqId = [self queryHighestRmqId];
  75. if (rmqId == 0) {
  76. rmqId = [self querylastRmqId];
  77. }
  78. self.rmqId = rmqId + 1;
  79. }
  80. #pragma mark - Save
  81. /**
  82. * Save a message to RMQ2. Will populate the rmq2 persistent ID.
  83. */
  84. - (BOOL)saveRmqMessage:(GPBMessage *)message
  85. error:(NSError **)error {
  86. // send using rmq2manager
  87. // the wire format of rmq2 id is a string. However, we keep it as a long internally
  88. // in the database. So only convert the id to string when preparing for sending over
  89. // the wire.
  90. NSString *rmq2Id = FIRMessagingGetRmq2Id(message);
  91. if (![rmq2Id length]) {
  92. int64_t rmqId = [self nextRmqId];
  93. rmq2Id = [NSString stringWithFormat:@"%lld", rmqId];
  94. FIRMessagingSetRmq2Id(message, rmq2Id);
  95. }
  96. FIRMessagingProtoTag tag = FIRMessagingGetTagForProto(message);
  97. return [self saveMessage:message withRmqId:[rmq2Id integerValue] tag:tag error:error];
  98. }
  99. - (BOOL)saveMessage:(GPBMessage *)message
  100. withRmqId:(int64_t)rmqId
  101. tag:(int8_t)tag
  102. error:(NSError **)error {
  103. NSData *data = [message data];
  104. return [self.rmq2Store saveMessageWithRmqId:rmqId tag:tag data:data error:error];
  105. }
  106. /**
  107. * This is called when we delete the largest outgoing message from queue.
  108. */
  109. - (void)saveLastOutgoingRmqId:(int64_t)rmqID {
  110. [self.rmq2Store updateLastOutgoingRmqId:rmqID];
  111. }
  112. - (BOOL)saveS2dMessageWithRmqId:(NSString *)rmqID {
  113. return [self.rmq2Store saveUnackedS2dMessageWithRmqId:rmqID];
  114. }
  115. #pragma mark - Query
  116. - (int64_t)queryHighestRmqId {
  117. return [self.rmq2Store queryHighestRmqId];
  118. }
  119. - (int64_t)querylastRmqId {
  120. return [self.rmq2Store queryLastRmqId];
  121. }
  122. - (NSArray *)unackedS2dRmqIds {
  123. return [self.rmq2Store unackedS2dRmqIds];
  124. }
  125. #pragma mark - FIRMessagingRMQScanner protocol
  126. /**
  127. * We don't have a 'getMessages' method - it would require loading in memory
  128. * the entire content body of all messages.
  129. *
  130. * Instead we iterate and call 'resend' for each message.
  131. *
  132. * This is called:
  133. * - on connect MCS, to resend any outstanding messages
  134. * - init
  135. */
  136. - (void)scanWithRmqMessageHandler:(FIRMessagingRmqMessageHandler)rmqMessageHandler
  137. dataMessageHandler:(FIRMessagingDataMessageHandler)dataMessageHandler {
  138. // no need to scan database with no callbacks
  139. if (rmqMessageHandler || dataMessageHandler) {
  140. [self.rmq2Store scanOutgoingRmqMessagesWithHandler:^(int64_t rmqId, int8_t tag, NSData *data) {
  141. if (rmqMessageHandler != nil) {
  142. rmqMessageHandler(rmqId, tag, data);
  143. }
  144. if (dataMessageHandler != nil && kFIRMessagingProtoTagDataMessageStanza == tag) {
  145. GPBMessage *proto =
  146. [FIRMessagingGetClassForTag((FIRMessagingProtoTag)tag) parseFromData:data error:NULL];
  147. GtalkDataMessageStanza *stanza = (GtalkDataMessageStanza *)proto;
  148. dataMessageHandler(rmqId, stanza);
  149. }
  150. }];
  151. }
  152. }
  153. #pragma mark - Remove
  154. - (void)ackReceivedForRmqId:(NSString *)rmqId {
  155. // TODO: Optional book-keeping
  156. }
  157. - (int)removeRmqMessagesWithRmqId:(NSString *)rmqId {
  158. return [self removeRmqMessagesWithRmqIds:@[rmqId]];
  159. }
  160. - (int)removeRmqMessagesWithRmqIds:(NSArray *)rmqIds {
  161. if (![rmqIds count]) {
  162. return 0;
  163. }
  164. for (NSString *rmqId in rmqIds) {
  165. [self ackReceivedForRmqId:rmqId];
  166. }
  167. int64_t maxRmqId = -1;
  168. for (NSString *rmqId in rmqIds) {
  169. int64_t rmqIdValue = [rmqId longLongValue];
  170. if (rmqIdValue > maxRmqId) {
  171. maxRmqId = rmqIdValue;
  172. }
  173. }
  174. maxRmqId++;
  175. if (maxRmqId >= self.rmqId) {
  176. [self saveLastOutgoingRmqId:maxRmqId];
  177. }
  178. return [self.rmq2Store deleteMessagesFromTable:kTableOutgoingRmqMessages withRmqIds:rmqIds];
  179. }
  180. - (void)removeS2dIds:(NSArray *)s2dIds {
  181. [self.rmq2Store deleteMessagesFromTable:kTableS2DRmqIds withRmqIds:s2dIds];
  182. }
  183. #pragma mark - Sync Messages
  184. // TODO: RMQManager should also have a cache for all the sync messages
  185. // so we don't hit the DB each time.
  186. - (FIRMessagingPersistentSyncMessage *)querySyncMessageWithRmqID:(NSString *)rmqID {
  187. return [self.rmq2Store querySyncMessageWithRmqID:rmqID];
  188. }
  189. - (BOOL)deleteSyncMessageWithRmqID:(NSString *)rmqID {
  190. return [self.rmq2Store deleteSyncMessageWithRmqID:rmqID];
  191. }
  192. - (int)deleteExpiredOrFinishedSyncMessages:(NSError **)error {
  193. return [self.rmq2Store deleteExpiredOrFinishedSyncMessages:error];
  194. }
  195. - (BOOL)saveSyncMessageWithRmqID:(NSString *)rmqID
  196. expirationTime:(int64_t)expirationTime
  197. apnsReceived:(BOOL)apnsReceived
  198. mcsReceived:(BOOL)mcsReceived
  199. error:(NSError *__autoreleasing *)error {
  200. return [self.rmq2Store saveSyncMessageWithRmqID:rmqID
  201. expirationTime:expirationTime
  202. apnsReceived:apnsReceived
  203. mcsReceived:mcsReceived
  204. error:error];
  205. }
  206. - (BOOL)updateSyncMessageViaAPNSWithRmqID:(NSString *)rmqID error:(NSError **)error {
  207. return [self.rmq2Store updateSyncMessageViaAPNSWithRmqID:rmqID error:error];
  208. }
  209. - (BOOL)updateSyncMessageViaMCSWithRmqID:(NSString *)rmqID error:(NSError **)error {
  210. return [self.rmq2Store updateSyncMessageViaMCSWithRmqID:rmqID error:error];
  211. }
  212. #pragma mark - Testing
  213. + (void)removeDatabaseWithName:(NSString *)dbName {
  214. [FIRMessagingRmq2PersistentStore removeDatabase:dbName];
  215. }
  216. #pragma mark - Private
  217. - (int64_t)nextRmqId {
  218. return ++self.rmqId;
  219. }
  220. @end