diff --git a/SignalMessaging/environment/AppSetup.m b/SignalMessaging/environment/AppSetup.m index 1757dc84a4..c582e7494d 100644 --- a/SignalMessaging/environment/AppSetup.m +++ b/SignalMessaging/environment/AppSetup.m @@ -116,6 +116,7 @@ NS_ASSUME_NONNULL_BEGIN BulkProfileFetch *bulkProfileFetch = [BulkProfileFetch new]; BulkUUIDLookup *bulkUUIDLookup = [BulkUUIDLookup new]; id versionedProfiles = [VersionedProfilesImpl new]; + EarlyMessageManager *earlyMessageManager = [EarlyMessageManager new]; [Environment setShared:[[Environment alloc] initWithAudioSession:audioSession incomingContactSyncJobQueue:incomingContactSyncJobQueue @@ -173,7 +174,8 @@ NS_ASSUME_NONNULL_BEGIN messageFetcherJob:messageFetcherJob bulkProfileFetch:bulkProfileFetch bulkUUIDLookup:bulkUUIDLookup - versionedProfiles:versionedProfiles]]; + versionedProfiles:versionedProfiles + earlyMessageManager:earlyMessageManager]]; appSpecificSingletonBlock(); diff --git a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m index 42d63a7a6e..f207be7b2d 100644 --- a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m +++ b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m @@ -67,6 +67,11 @@ NS_ASSUME_NONNULL_BEGIN return SSKEnvironment.shared.tsAccountManager; } ++ (EarlyMessageManager *)earlyMessageManager +{ + return SSKEnvironment.shared.earlyMessageManager; +} + #pragma mark - + (void)processIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)transcript @@ -234,7 +239,7 @@ NS_ASSUME_NONNULL_BEGIN expirationStartedAt:transcript.expirationStartedAt transaction:transaction]; - [OWSEarlyMessageManager applyPendingMessagesFor:outgoingMessage transaction:transaction]; + [self.earlyMessageManager applyPendingMessagesFor:outgoingMessage transaction:transaction]; if (outgoingMessage.isViewOnceMessage) { // To be extra-conservative, always mark diff --git a/SignalServiceKit/src/Messages/EarlyMessageManager.swift b/SignalServiceKit/src/Messages/EarlyMessageManager.swift index 1d57cc3382..63a36fbd84 100644 --- a/SignalServiceKit/src/Messages/EarlyMessageManager.swift +++ b/SignalServiceKit/src/Messages/EarlyMessageManager.swift @@ -4,7 +4,7 @@ import Foundation -@objc(OWSEarlyMessageManager) +@objc public class EarlyMessageManager: NSObject { private struct MessageIdentifier: Hashable { let timestamp: UInt64 @@ -34,19 +34,42 @@ public class EarlyMessageManager: NSObject { private static let maxQueuedMessages = 100 private static let maxEarlyEnvelopeSize = 1024 - private static let serialQueue = DispatchQueue(label: "EarlyMessageManager") - private static var pendingEnvelopes = OrderedDictionary() - private static var pendingReceipts = OrderedDictionary() + private let serialQueue = DispatchQueue(label: "EarlyMessageManager") + private var pendingEnvelopes = OrderedDictionary() + private var pendingReceipts = OrderedDictionary() + + public override init() { + super.init() + + SwiftSingletons.register(self) + + // Listen for memory warnings to evacuate the caches + NotificationCenter.default.addObserver( + self, + selector: #selector(didReceiveMemoryWarning), + name: UIApplication.didReceiveMemoryWarningNotification, + object: nil + ) + } @objc - public static func recordEarlyEnvelope( + func didReceiveMemoryWarning() { + Logger.error("Dropping all early messages due to memory warning.") + serialQueue.sync { + pendingEnvelopes = OrderedDictionary() + pendingReceipts = OrderedDictionary() + } + } + + @objc + public func recordEarlyEnvelope( _ envelope: SSKProtoEnvelope, plainTextData: Data?, wasReceivedByUD: Bool, associatedMessageTimestamp: UInt64, associatedMessageAuthor: SignalServiceAddress ) { - guard plainTextData?.count ?? 0 <= maxEarlyEnvelopeSize else { + guard plainTextData?.count ?? 0 <= Self.maxEarlyEnvelopeSize else { return owsFailDebug("unexpectedly tried to record an excessively large early envelope") } @@ -54,7 +77,7 @@ public class EarlyMessageManager: NSObject { serialQueue.sync { var envelopes = pendingEnvelopes[identifier] ?? [] - while envelopes.count >= maxQueuedPerMessage, let droppedEarlyEnvelope = envelopes.first { + while envelopes.count >= Self.maxQueuedPerMessage, let droppedEarlyEnvelope = envelopes.first { envelopes.remove(at: 0) owsFailDebug("Dropping early envelope \(droppedEarlyEnvelope.envelope.timestamp) for message \(identifier) due to excessive early envelopes.") } @@ -62,7 +85,7 @@ public class EarlyMessageManager: NSObject { envelopes.append(EarlyEnvelope(envelope: envelope, plainTextData: plainTextData, wasReceivedByUD: wasReceivedByUD)) pendingEnvelopes[identifier] = envelopes - while pendingEnvelopes.count >= maxQueuedMessages, let droppedEarlyIdentifier = pendingEnvelopes.orderedKeys.first { + while pendingEnvelopes.count >= Self.maxQueuedMessages, let droppedEarlyIdentifier = pendingEnvelopes.orderedKeys.first { pendingEnvelopes.remove(key: droppedEarlyIdentifier) owsFailDebug("Dropping all early envelopes for message \(droppedEarlyIdentifier) due to excessive early messages.") } @@ -70,7 +93,7 @@ public class EarlyMessageManager: NSObject { } @objc - public static func recordEarlyReceiptForOutgoingMessage( + public func recordEarlyReceiptForOutgoingMessage( type: SSKProtoReceiptMessageType, sender: SignalServiceAddress, timestamp: UInt64, @@ -88,7 +111,7 @@ public class EarlyMessageManager: NSObject { } @objc - public static func recordEarlyReadReceiptFromLinkedDevice( + public func recordEarlyReadReceiptFromLinkedDevice( timestamp: UInt64, associatedMessageTimestamp: UInt64, associatedMessageAuthor: SignalServiceAddress @@ -100,7 +123,7 @@ public class EarlyMessageManager: NSObject { ) } - private static func recordEarlyReceipt( + private func recordEarlyReceipt( _ earlyReceipt: EarlyReceipt, associatedMessageTimestamp: UInt64, associatedMessageAuthor: SignalServiceAddress @@ -109,7 +132,7 @@ public class EarlyMessageManager: NSObject { serialQueue.sync { var receipts = pendingReceipts[identifier] ?? [] - while receipts.count >= maxQueuedPerMessage, let droppedEarlyReceipt = receipts.first { + while receipts.count >= Self.maxQueuedPerMessage, let droppedEarlyReceipt = receipts.first { receipts.remove(at: 0) owsFailDebug("Dropping early receipt \(droppedEarlyReceipt) for message \(identifier) due to excessive early receipts.") } @@ -117,7 +140,7 @@ public class EarlyMessageManager: NSObject { receipts.append(earlyReceipt) pendingReceipts[identifier] = receipts - while pendingReceipts.count >= maxQueuedMessages, let droppedEarlyIdentifier = pendingReceipts.orderedKeys.first { + while pendingReceipts.count >= Self.maxQueuedMessages, let droppedEarlyIdentifier = pendingReceipts.orderedKeys.first { pendingReceipts.remove(key: droppedEarlyIdentifier) owsFailDebug("Dropping all early envelopes for message \(droppedEarlyIdentifier) due to excessive early messages.") } @@ -125,7 +148,7 @@ public class EarlyMessageManager: NSObject { } @objc - public static func applyPendingMessages(for message: TSMessage, transaction: SDSAnyWriteTransaction) { + public func applyPendingMessages(for message: TSMessage, transaction: SDSAnyWriteTransaction) { var earlyReceipts: [EarlyReceipt]? var earlyEnvelopes: [EarlyEnvelope]? diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index cfcc76f04f..f144120790 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -172,6 +172,11 @@ NS_ASSUME_NONNULL_BEGIN return SSKEnvironment.shared.groupsV2; } +- (EarlyMessageManager *)earlyMessageManager +{ + return SSKEnvironment.shared.earlyMessageManager; +} + #pragma mark - - (void)startObserving @@ -942,10 +947,10 @@ NS_ASSUME_NONNULL_BEGIN for (NSNumber *nsEarlyTimestamp in earlyTimestamps) { UInt64 earlyTimestamp = [nsEarlyTimestamp unsignedLongLongValue]; - [OWSEarlyMessageManager recordEarlyReceiptForOutgoingMessageWithType:receiptMessage.unwrappedType - sender:envelope.sourceAddress - timestamp:envelope.timestamp - associatedMessageTimestamp:earlyTimestamp]; + [self.earlyMessageManager recordEarlyReceiptForOutgoingMessageWithType:receiptMessage.unwrappedType + sender:envelope.sourceAddress + timestamp:envelope.timestamp + associatedMessageTimestamp:earlyTimestamp]; } } @@ -1480,11 +1485,11 @@ NS_ASSUME_NONNULL_BEGIN case OWSReactionProcessingResultInvalidReaction: break; case OWSReactionProcessingResultAssociatedMessageMissing: - [OWSEarlyMessageManager recordEarlyEnvelope:envelope - plainTextData:plaintextData - wasReceivedByUD:wasReceivedByUD - associatedMessageTimestamp:dataMessage.reaction.timestamp - associatedMessageAuthor:dataMessage.reaction.authorAddress]; + [self.earlyMessageManager recordEarlyEnvelope:envelope + plainTextData:plaintextData + wasReceivedByUD:wasReceivedByUD + associatedMessageTimestamp:dataMessage.reaction.timestamp + associatedMessageAuthor:dataMessage.reaction.authorAddress]; break; } } else if (dataMessage.delete != nil) { @@ -1515,11 +1520,11 @@ NS_ASSUME_NONNULL_BEGIN OWSLogError(@"Failed to remotely delete message: %llu", dataMessage.delete.targetSentTimestamp); break; case OWSRemoteDeleteProcessingResultDeletedMessageMissing: - [OWSEarlyMessageManager recordEarlyEnvelope:envelope - plainTextData:plaintextData - wasReceivedByUD:wasReceivedByUD - associatedMessageTimestamp:dataMessage.delete.targetSentTimestamp - associatedMessageAuthor:envelope.sourceAddress]; + [self.earlyMessageManager recordEarlyEnvelope:envelope + plainTextData:plaintextData + wasReceivedByUD:wasReceivedByUD + associatedMessageTimestamp:dataMessage.delete.targetSentTimestamp + associatedMessageAuthor:envelope.sourceAddress]; break; } } else { @@ -1577,9 +1582,10 @@ NS_ASSUME_NONNULL_BEGIN readTimestamp:envelope.timestamp transaction:transaction]; for (SSKProtoSyncMessageRead *readReceiptProto in earlyReceipts) { - [OWSEarlyMessageManager recordEarlyReadReceiptFromLinkedDeviceWithTimestamp:envelope.timestamp - associatedMessageTimestamp:readReceiptProto.timestamp - associatedMessageAuthor:readReceiptProto.senderAddress]; + [self.earlyMessageManager + recordEarlyReadReceiptFromLinkedDeviceWithTimestamp:envelope.timestamp + associatedMessageTimestamp:readReceiptProto.timestamp + associatedMessageAuthor:readReceiptProto.senderAddress]; } } else if (syncMessage.verified) { OWSLogInfo(@"Received verification state for %@", syncMessage.verified.destinationAddress); @@ -1603,11 +1609,11 @@ NS_ASSUME_NONNULL_BEGIN case OWSViewOnceSyncMessageProcessingResultInvalidSyncMessage: break; case OWSViewOnceSyncMessageProcessingResultAssociatedMessageMissing: - [OWSEarlyMessageManager recordEarlyEnvelope:envelope - plainTextData:plaintextData - wasReceivedByUD:wasReceivedByUD - associatedMessageTimestamp:syncMessage.viewOnceOpen.timestamp - associatedMessageAuthor:syncMessage.viewOnceOpen.senderAddress]; + [self.earlyMessageManager recordEarlyEnvelope:envelope + plainTextData:plaintextData + wasReceivedByUD:wasReceivedByUD + associatedMessageTimestamp:syncMessage.viewOnceOpen.timestamp + associatedMessageAuthor:syncMessage.viewOnceOpen.senderAddress]; break; } } else if (syncMessage.configuration) { @@ -1903,11 +1909,11 @@ NS_ASSUME_NONNULL_BEGIN case OWSReactionProcessingResultInvalidReaction: break; case OWSReactionProcessingResultAssociatedMessageMissing: - [OWSEarlyMessageManager recordEarlyEnvelope:envelope - plainTextData:plaintextData - wasReceivedByUD:wasReceivedByUD - associatedMessageTimestamp:dataMessage.reaction.timestamp - associatedMessageAuthor:dataMessage.reaction.authorAddress]; + [self.earlyMessageManager recordEarlyEnvelope:envelope + plainTextData:plaintextData + wasReceivedByUD:wasReceivedByUD + associatedMessageTimestamp:dataMessage.reaction.timestamp + associatedMessageAuthor:dataMessage.reaction.authorAddress]; break; } @@ -1929,11 +1935,11 @@ NS_ASSUME_NONNULL_BEGIN OWSLogError(@"Failed to remotely delete message: %llu", dataMessage.delete.targetSentTimestamp); break; case OWSRemoteDeleteProcessingResultDeletedMessageMissing: - [OWSEarlyMessageManager recordEarlyEnvelope:envelope - plainTextData:plaintextData - wasReceivedByUD:wasReceivedByUD - associatedMessageTimestamp:dataMessage.delete.targetSentTimestamp - associatedMessageAuthor:envelope.sourceAddress]; + [self.earlyMessageManager recordEarlyEnvelope:envelope + plainTextData:plaintextData + wasReceivedByUD:wasReceivedByUD + associatedMessageTimestamp:dataMessage.delete.targetSentTimestamp + associatedMessageAuthor:envelope.sourceAddress]; break; } return nil; @@ -2021,7 +2027,7 @@ NS_ASSUME_NONNULL_BEGIN [incomingMessage anyInsertWithTransaction:transaction]; - [OWSEarlyMessageManager applyPendingMessagesFor:incomingMessage transaction:transaction]; + [self.earlyMessageManager applyPendingMessagesFor:incomingMessage transaction:transaction]; // Any messages sent from the current user - from this device or another - should be automatically marked as read. if (envelope.sourceAddress.isLocalAddress) { diff --git a/SignalServiceKit/src/SSKEnvironment.h b/SignalServiceKit/src/SSKEnvironment.h index 7f8be52d06..20353f47af 100644 --- a/SignalServiceKit/src/SSKEnvironment.h +++ b/SignalServiceKit/src/SSKEnvironment.h @@ -8,6 +8,7 @@ NS_ASSUME_NONNULL_BEGIN @class BulkProfileFetch; @class BulkUUIDLookup; @class ContactsUpdater; +@class EarlyMessageManager; @class GroupsV2MessageProcessor; @class MessageFetcherJob; @class MessageProcessing; @@ -105,7 +106,8 @@ NS_ASSUME_NONNULL_BEGIN messageFetcherJob:(MessageFetcherJob *)messageFetcherJob bulkProfileFetch:(BulkProfileFetch *)bulkProfileFetch bulkUUIDLookup:(BulkUUIDLookup *)bulkUUIDLookup - versionedProfiles:(id)versionedProfiles NS_DESIGNATED_INITIALIZER; + versionedProfiles:(id)versionedProfiles + earlyMessageManager:(EarlyMessageManager *)earlyMessageManager NS_DESIGNATED_INITIALIZER; @property (nonatomic, readonly, class) SSKEnvironment *shared; @@ -163,6 +165,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) BulkProfileFetch *bulkProfileFetch; @property (nonatomic, readonly) BulkUUIDLookup *bulkUUIDLookup; @property (nonatomic, readonly) id versionedProfiles; +@property (nonatomic, readonly) EarlyMessageManager *earlyMessageManager; @property (nonatomic, readonly, nullable) OWSPrimaryStorage *primaryStorage; diff --git a/SignalServiceKit/src/SSKEnvironment.m b/SignalServiceKit/src/SSKEnvironment.m index 8d0df16a57..fbcdd85add 100644 --- a/SignalServiceKit/src/SSKEnvironment.m +++ b/SignalServiceKit/src/SSKEnvironment.m @@ -52,6 +52,7 @@ static SSKEnvironment *sharedSSKEnvironment; @property (nonatomic) BulkProfileFetch *bulkProfileFetch; @property (nonatomic) BulkUUIDLookup *bulkUUIDLookup; @property (nonatomic) id versionedProfiles; +@property (nonatomic) EarlyMessageManager *earlyMessageManager; @end @@ -109,6 +110,7 @@ static SSKEnvironment *sharedSSKEnvironment; bulkProfileFetch:(BulkProfileFetch *)bulkProfileFetch bulkUUIDLookup:(BulkUUIDLookup *)bulkUUIDLookup versionedProfiles:(id)versionedProfiles + earlyMessageManager:(EarlyMessageManager *)earlyMessageManager { self = [super init]; if (!self) { @@ -160,6 +162,7 @@ static SSKEnvironment *sharedSSKEnvironment; OWSAssertDebug(bulkProfileFetch); OWSAssertDebug(versionedProfiles); OWSAssertDebug(bulkUUIDLookup); + OWSAssertDebug(earlyMessageManager); _contactsManager = contactsManager; _linkPreviewManager = linkPreviewManager; @@ -207,6 +210,7 @@ static SSKEnvironment *sharedSSKEnvironment; _bulkProfileFetch = bulkProfileFetch; _versionedProfiles = versionedProfiles; _bulkUUIDLookup = bulkUUIDLookup; + _earlyMessageManager = earlyMessageManager; return self; } diff --git a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m b/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m index 9a5a7c1bf5..e8a76d2c24 100644 --- a/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m +++ b/SignalServiceKit/src/TestUtils/MockSSKEnvironment.m @@ -108,6 +108,7 @@ NS_ASSUME_NONNULL_BEGIN BulkProfileFetch *bulkProfileFetch = [BulkProfileFetch new]; BulkUUIDLookup *bulkUUIDLookup = [BulkUUIDLookup new]; id versionedProfiles = [MockVersionedProfiles new]; + EarlyMessageManager *earlyMessageManager = [EarlyMessageManager new]; self = [super initWithContactsManager:contactsManager linkPreviewManager:linkPreviewManager @@ -154,7 +155,8 @@ NS_ASSUME_NONNULL_BEGIN messageFetcherJob:messageFetcherJob bulkProfileFetch:bulkProfileFetch bulkUUIDLookup:bulkUUIDLookup - versionedProfiles:versionedProfiles]; + versionedProfiles:versionedProfiles + earlyMessageManager:earlyMessageManager]; if (!self) { return nil;