From 076dc980e8e054bfcb3a0674e07bf0afd0006720 Mon Sep 17 00:00:00 2001 From: Sasha Weiss Date: Wed, 28 Jan 2026 13:39:33 -0800 Subject: [PATCH] Stricter rules for when we exclude an attachment from Backups --- .../BackupArchive+AccountDataContexts.swift | 15 +-- .../Chat/BackupArchive+ChatContexts.swift | 34 ++--- .../Chat/BackupArchiveChatStyleArchiver.swift | 10 +- .../BackupArchive+ChatItemContexts.swift | 9 +- .../BackupArchiveChatItemArchiver.swift | 2 +- ...ckupArchiveMessageAttachmentArchiver.swift | 121 +++++++++--------- ...ckupArchiveTSIncomingMessageArchiver.swift | 10 +- ...ckupArchiveTSOutgoingMessageArchiver.swift | 10 +- .../BackupArchive+RecipientContexts.swift | 20 +-- ...ackupArchiveContactRecipientArchiver.swift | 2 +- .../Archiving/BackupArchive+Contexts.swift | 33 +++-- .../Archiving/BackupArchiveManagerImpl.swift | 98 +++++++------- .../Archiving/BackupArchiveManagerMock.swift | 2 +- .../BackupArchiveProtoStreamProvider.swift | 4 +- SignalServiceKit/Upload/Upload.swift | 2 +- 15 files changed, 190 insertions(+), 182 deletions(-) diff --git a/SignalServiceKit/Backups/Archiving/Archivers/AccountData/BackupArchive+AccountDataContexts.swift b/SignalServiceKit/Backups/Archiving/Archivers/AccountData/BackupArchive+AccountDataContexts.swift index 7f7df9dcd3..7debf41837 100644 --- a/SignalServiceKit/Backups/Archiving/Archivers/AccountData/BackupArchive+AccountDataContexts.swift +++ b/SignalServiceKit/Backups/Archiving/Archivers/AccountData/BackupArchive+AccountDataContexts.swift @@ -9,31 +9,26 @@ import LibSignalClient extension BackupArchive { public class AccountDataRestoringContext: RestoringContext { - - let currentRemoteConfig: RemoteConfig - let backupPurpose: MessageBackupPurpose - /// Will only be nil if there was no earier AccountData frame to set it, which /// should be treated as an error at read time when processing all subsequent frames. var backupPlan: BackupPlan? - /// Will only be nil if there was no earier AccountData frame to set it, which /// should be treated as an error at read time when processing all subsequent frames. var uploadEra: String? init( - startTimestampMs: UInt64, + backupPurpose: MessageBackupPurpose, + startDate: Date, + remoteConfig: RemoteConfig, attachmentByteCounter: BackupArchiveAttachmentByteCounter, isPrimaryDevice: Bool, - currentRemoteConfig: RemoteConfig, - backupPurpose: MessageBackupPurpose, tx: DBWriteTransaction, ) { - self.currentRemoteConfig = currentRemoteConfig self.backupPurpose = backupPurpose super.init( - startTimestampMs: startTimestampMs, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, tx: tx, diff --git a/SignalServiceKit/Backups/Archiving/Archivers/Chat/BackupArchive+ChatContexts.swift b/SignalServiceKit/Backups/Archiving/Archivers/Chat/BackupArchive+ChatContexts.swift index 325653e8fc..a9095b2e09 100644 --- a/SignalServiceKit/Backups/Archiving/Archivers/Chat/BackupArchive+ChatContexts.swift +++ b/SignalServiceKit/Backups/Archiving/Archivers/Chat/BackupArchive+ChatContexts.swift @@ -105,23 +105,23 @@ extension BackupArchive { private let threadCache = SharedMap() init( + customChatColorContext: CustomChatColorArchivingContext, + recipientContext: RecipientArchivingContext, + startDate: Date, + remoteConfig: RemoteConfig, bencher: BackupArchive.ArchiveBencher, attachmentByteCounter: BackupArchiveAttachmentByteCounter, - currentBackupAttachmentUploadEra: String, - customChatColorContext: CustomChatColorArchivingContext, includedContentFilter: IncludedContentFilter, - recipientContext: RecipientArchivingContext, - startTimestampMs: UInt64, tx: DBReadTransaction, ) { self.customChatColorContext = customChatColorContext self.recipientContext = recipientContext super.init( + startDate: startDate, + remoteConfig: remoteConfig, bencher: bencher, attachmentByteCounter: attachmentByteCounter, - currentBackupAttachmentUploadEra: currentBackupAttachmentUploadEra, includedContentFilter: includedContentFilter, - startTimestampMs: startTimestampMs, tx: tx, ) } @@ -173,7 +173,8 @@ extension BackupArchive { init( customChatColorContext: CustomChatColorRestoringContext, recipientContext: RecipientRestoringContext, - startTimestampMs: UInt64, + startDate: Date, + remoteConfig: RemoteConfig, attachmentByteCounter: BackupArchiveAttachmentByteCounter, isPrimaryDevice: Bool, tx: DBWriteTransaction, @@ -181,7 +182,8 @@ extension BackupArchive { self.customChatColorContext = customChatColorContext self.recipientContext = recipientContext super.init( - startTimestampMs: startTimestampMs, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, tx: tx, @@ -343,19 +345,19 @@ extension BackupArchive { private let map = SharedMap() override init( + startDate: Date, + remoteConfig: RemoteConfig, bencher: BackupArchive.ArchiveBencher, attachmentByteCounter: BackupArchiveAttachmentByteCounter, - currentBackupAttachmentUploadEra: String, includedContentFilter: IncludedContentFilter, - startTimestampMs: UInt64, tx: DBReadTransaction, ) { super.init( + startDate: startDate, + remoteConfig: remoteConfig, bencher: bencher, attachmentByteCounter: attachmentByteCounter, - currentBackupAttachmentUploadEra: currentBackupAttachmentUploadEra, includedContentFilter: includedContentFilter, - startTimestampMs: startTimestampMs, tx: tx, ) } @@ -380,15 +382,17 @@ extension BackupArchive { let accountDataContext: AccountDataRestoringContext init( - startTimestampMs: UInt64, + accountDataContext: AccountDataRestoringContext, + startDate: Date, + remoteConfig: RemoteConfig, attachmentByteCounter: BackupArchiveAttachmentByteCounter, isPrimaryDevice: Bool, - accountDataContext: AccountDataRestoringContext, tx: DBWriteTransaction, ) { self.accountDataContext = accountDataContext super.init( - startTimestampMs: startTimestampMs, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, tx: tx, diff --git a/SignalServiceKit/Backups/Archiving/Archivers/Chat/BackupArchiveChatStyleArchiver.swift b/SignalServiceKit/Backups/Archiving/Archivers/Chat/BackupArchiveChatStyleArchiver.swift index ff5c749942..1e63ce8032 100644 --- a/SignalServiceKit/Backups/Archiving/Archivers/Chat/BackupArchiveChatStyleArchiver.swift +++ b/SignalServiceKit/Backups/Archiving/Archivers/Chat/BackupArchiveChatStyleArchiver.swift @@ -108,7 +108,7 @@ public class BackupArchiveChatStyleArchiver: BackupArchiveProtoStreamWriter { /// in the Backup, we can't use the same timestamp for all colors. To /// that end, we'll start with "now" and increment as we create more /// colors. - var chatColorCreationTimestamp = context.startTimestampMs + var chatColorCreationTimestamp = context.startDate.ows_millisecondsSince1970 for chatColorProto in chatColorProtos { let customChatColorId = BackupArchive.CustomChatColorId(value: chatColorProto.id) @@ -454,9 +454,7 @@ public class BackupArchiveChatStyleArchiver: BackupArchiveProtoStreamWriter { return .success(nil) } - return .success(referencedAttachment.asBackupFilePointer( - attachmentByteCounter: context.attachmentByteCounter, - )) + return .success(referencedAttachment.asBackupFilePointer(context: context)) } private func restoreWallpaperAttachment( @@ -522,9 +520,9 @@ public class BackupArchiveChatStyleArchiver: BackupArchiveProtoStreamWriter { for referencedAttachment in results { backupAttachmentDownloadScheduler.enqueueFromBackupIfNeeded( referencedAttachment, - restoreStartTimestampMs: context.startTimestampMs, + restoreStartTimestampMs: context.startDate.ows_millisecondsSince1970, backupPlan: backupPlan, - remoteConfig: context.accountDataContext.currentRemoteConfig, + remoteConfig: context.remoteConfig, isPrimaryDevice: context.isPrimaryDevice, tx: context.tx, ) diff --git a/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchive+ChatItemContexts.swift b/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchive+ChatItemContexts.swift index 35da68c33a..00d71b2afa 100644 --- a/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchive+ChatItemContexts.swift +++ b/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchive+ChatItemContexts.swift @@ -7,23 +7,28 @@ extension BackupArchive { public class ChatItemRestoringContext: RestoringContext { + let accountDataContext: AccountDataRestoringContext let chatContext: ChatRestoringContext let recipientContext: RecipientRestoringContext public var uploadEra: String? { chatContext.customChatColorContext.accountDataContext.uploadEra } init( + accountDataContext: AccountDataRestoringContext, chatContext: ChatRestoringContext, recipientContext: RecipientRestoringContext, - startTimestampMs: UInt64, + startDate: Date, + remoteConfig: RemoteConfig, attachmentByteCounter: BackupArchiveAttachmentByteCounter, isPrimaryDevice: Bool, tx: DBWriteTransaction, ) { + self.accountDataContext = accountDataContext self.recipientContext = recipientContext self.chatContext = chatContext super.init( - startTimestampMs: startTimestampMs, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, tx: tx, diff --git a/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveChatItemArchiver.swift b/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveChatItemArchiver.swift index ce1a662b49..5a13725ac3 100644 --- a/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveChatItemArchiver.swift +++ b/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveChatItemArchiver.swift @@ -304,7 +304,7 @@ public class BackupArchiveChatItemArchiver: BackupArchiveProtoStreamWriter { context.includedContentFilter.shouldSkipMessageBasedOnExpiration( expireStartDate: details.expireStartDate, expiresInMs: details.expiresInMs, - currentTimestamp: context.startTimestampMs, + currentTimestamp: context.startDate.ows_millisecondsSince1970, ) { // Skip, but treat as a success. diff --git a/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveMessageAttachmentArchiver.swift b/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveMessageAttachmentArchiver.swift index 30b1a03068..eb230d9046 100644 --- a/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveMessageAttachmentArchiver.swift +++ b/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveMessageAttachmentArchiver.swift @@ -33,7 +33,7 @@ class BackupArchiveMessageAttachmentArchiver: BackupArchiveProtoStreamWriter { for referencedAttachment in referencedAttachments { let pointerProto = referencedAttachment.asBackupFilePointer( - attachmentByteCounter: context.attachmentByteCounter, + context: context, ) var attachmentProto = BackupProto_MessageAttachment() @@ -59,27 +59,21 @@ class BackupArchiveMessageAttachmentArchiver: BackupArchiveProtoStreamWriter { referencedAttachment: ReferencedAttachment, context: BackupArchive.ArchivingContext, ) -> BackupProto_FilePointer { - return referencedAttachment.asBackupFilePointer( - attachmentByteCounter: context.attachmentByteCounter, - ) + return referencedAttachment.asBackupFilePointer(context: context) } func archiveLinkPreviewAttachment( referencedAttachment: ReferencedAttachment, context: BackupArchive.ArchivingContext, ) -> BackupProto_FilePointer { - return referencedAttachment.asBackupFilePointer( - attachmentByteCounter: context.attachmentByteCounter, - ) + return referencedAttachment.asBackupFilePointer(context: context) } func archiveQuotedReplyThumbnailAttachment( referencedAttachment: ReferencedAttachment, context: BackupArchive.ArchivingContext, ) -> BackupProto_MessageAttachment { - let pointerProto = referencedAttachment.asBackupFilePointer( - attachmentByteCounter: context.attachmentByteCounter, - ) + let pointerProto = referencedAttachment.asBackupFilePointer(context: context) var attachmentProto = BackupProto_MessageAttachment() attachmentProto.pointer = pointerProto @@ -94,18 +88,14 @@ class BackupArchiveMessageAttachmentArchiver: BackupArchiveProtoStreamWriter { referencedAttachment: ReferencedAttachment, context: BackupArchive.ArchivingContext, ) -> BackupProto_FilePointer { - return referencedAttachment.asBackupFilePointer( - attachmentByteCounter: context.attachmentByteCounter, - ) + return referencedAttachment.asBackupFilePointer(context: context) } func archiveStickerAttachment( referencedAttachment: ReferencedAttachment, context: BackupArchive.ArchivingContext, ) -> BackupProto_FilePointer { - return referencedAttachment.asBackupFilePointer( - attachmentByteCounter: context.attachmentByteCounter, - ) + return referencedAttachment.asBackupFilePointer(context: context) } // MARK: Restoring - @@ -374,12 +364,9 @@ class BackupArchiveMessageAttachmentArchiver: BackupArchiveProtoStreamWriter { )]) } - let accountDataContext = context.chatContext.customChatColorContext.accountDataContext - guard let backupPlan = accountDataContext.backupPlan else { + guard let backupPlan = context.accountDataContext.backupPlan else { return .messageFailure([.restoreFrameError( - .invalidProtoData( - .accountDataNotFound, - ), + .invalidProtoData(.accountDataNotFound), chatItemId, )]) } @@ -387,9 +374,9 @@ class BackupArchiveMessageAttachmentArchiver: BackupArchiveProtoStreamWriter { for referencedAttachment in results { backupAttachmentDownloadScheduler.enqueueFromBackupIfNeeded( referencedAttachment, - restoreStartTimestampMs: context.startTimestampMs, + restoreStartTimestampMs: context.startDate.ows_millisecondsSince1970, backupPlan: backupPlan, - remoteConfig: accountDataContext.currentRemoteConfig, + remoteConfig: context.remoteConfig, isPrimaryDevice: context.isPrimaryDevice, tx: context.tx, ) @@ -448,7 +435,7 @@ extension BackupArchive.RestoreFrameError.ErrorType { extension ReferencedAttachment { func asBackupFilePointer( - attachmentByteCounter: BackupArchiveAttachmentByteCounter, + context: BackupArchive.ArchivingContext, ) -> BackupProto_FilePointer { var proto = BackupProto_FilePointer() proto.contentType = attachment.mimeType @@ -478,7 +465,7 @@ extension ReferencedAttachment { } } - proto.locatorInfo = self.asBackupFilePointerLocatorInfo() + proto.locatorInfo = self.asBackupFilePointerLocatorInfo(context: context) if attachment.mediaName != nil, @@ -486,7 +473,7 @@ extension ReferencedAttachment { attachment.streamInfo?.unencryptedByteCount ?? attachment.mediaTierInfo?.unencryptedByteCount { - attachmentByteCounter.addToByteCount( + context.attachmentByteCounter.addToByteCount( attachmentID: attachment.id, byteCount: Cryptography.estimatedMediaTierCDNSize(unencryptedSize: UInt64(safeCast: unencryptedByteCount)) ?? UInt64(UInt32.max), ) @@ -497,7 +484,9 @@ extension ReferencedAttachment { return proto } - private func asBackupFilePointerLocatorInfo() -> BackupProto_FilePointer.LocatorInfo { + private func asBackupFilePointerLocatorInfo( + context: BackupArchive.ArchivingContext, + ) -> BackupProto_FilePointer.LocatorInfo { var locatorInfo = BackupProto_FilePointer.LocatorInfo() // Include the transit tier cdn info as a fallback, but only @@ -509,7 +498,7 @@ extension ReferencedAttachment { // When encryption keys don't match: if we reupload (e.g. forward) an // attachment after 3+ days, we rotate to a new encryption key; transit // tier info uses this new random key and can't be the fallback here. - let transitTierInfoToExport: Attachment.TransitTierInfo? + var transitTierInfoToExport: Attachment.TransitTierInfo? if let latestTransitTierInfo = attachment.latestTransitTierInfo, latestTransitTierInfo.encryptionKey == attachment.encryptionKey @@ -521,50 +510,56 @@ extension ReferencedAttachment { transitTierInfoToExport = nil } + if + let transitTierUploadDate = transitTierInfoToExport.map({ Date(millisecondsSince1970: $0.uploadTimestamp) }), + transitTierUploadDate.addingTimeInterval(context.remoteConfig.messageQueueTime) < context.startDate + { + // This transit tier info is expired, so there's no point in + // exporting it. + transitTierInfoToExport = nil + } + if let transitTierInfoToExport { locatorInfo.transitCdnKey = transitTierInfoToExport.cdnKey locatorInfo.transitCdnNumber = transitTierInfoToExport.cdnNumber locatorInfo.transitTierUploadTimestamp = transitTierInfoToExport.uploadTimestamp - // We may overwrite this below with plaintext hash integrity check, - // which is desired. We only use encrypted digest integrity check - // if we don't have a plaintext hash and DO have a transit tier upload. - switch transitTierInfoToExport.integrityCheck { - case .digestSHA256Ciphertext(let data): - locatorInfo.integrityCheck = .encryptedDigest(data) - case .sha256ContentHash(let data): - locatorInfo.integrityCheck = .plaintextHash(data) - } } - // If we have absolutely no present-time source of data - // for this attachment, even if we have a plaintext hash because - // we _previously_ had data, don't bother exporting it. Its unrecoverable. - let isTotallyMissingAttachment = - attachment.streamInfo == nil - && transitTierInfoToExport == nil - && attachment.mediaTierInfo == nil - - if !isTotallyMissingAttachment, let plaintextHash = attachment.sha256ContentHash { - locatorInfo.integrityCheck = .plaintextHash(plaintextHash) - if let mediaTierCdnNumber = attachment.mediaTierInfo?.cdnNumber { - locatorInfo.mediaTierCdnNumber = mediaTierCdnNumber - } - } - - // Set fields only if some cdn info is available. - switch locatorInfo.integrityCheck { - case .plaintextHash, .encryptedDigest: + if let mediaTierInfo = attachment.mediaTierInfo { locatorInfo.key = attachment.encryptionKey + locatorInfo.size = mediaTierInfo.unencryptedByteCount + locatorInfo.integrityCheck = .plaintextHash(mediaTierInfo.sha256ContentHash) - if - let unencryptedByteCount = attachment.streamInfo?.unencryptedByteCount - ?? attachment.mediaTierInfo?.unencryptedByteCount - ?? attachment.latestTransitTierInfo?.unencryptedByteCount - { - locatorInfo.size = unencryptedByteCount + if let cdnNumber = mediaTierInfo.cdnNumber { + locatorInfo.mediaTierCdnNumber = cdnNumber } - case .none: - break + } else if let streamInfo = attachment.streamInfo { + locatorInfo.key = attachment.encryptionKey + locatorInfo.size = streamInfo.unencryptedByteCount + locatorInfo.integrityCheck = .plaintextHash(streamInfo.sha256ContentHash) + } else if let transitTierInfoToExport { + locatorInfo.key = attachment.encryptionKey + locatorInfo.size = transitTierInfoToExport.unencryptedByteCount ?? 0 + + // At the time of writing, TransitTierInfo.integrityCheck prefers + // the encrypted digest even if both are present. (See comment in + // that type's init.) So, manually check for the plaintext hash + // here, falling back to the encrypted digest otherwise. + if let plaintextHash = attachment.sha256ContentHash { + locatorInfo.integrityCheck = .plaintextHash(plaintextHash) + } else { + switch transitTierInfoToExport.integrityCheck { + case .sha256ContentHash(let plaintextHash): + owsFailDebug("Missing Attachment plaintext hash, but had one on TransitTierInfo!") + locatorInfo.integrityCheck = .plaintextHash(plaintextHash) + case .digestSHA256Ciphertext(let encryptedDigest): + locatorInfo.integrityCheck = .encryptedDigest(encryptedDigest) + } + } + } else { + // This attachment isn't uploaded anywhere and this device won't be + // able to upload it in the future. So, we leave a bunch of fields + // unset so any restoring device knows it's unavailable. } return locatorInfo diff --git a/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveTSIncomingMessageArchiver.swift b/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveTSIncomingMessageArchiver.swift index ae11da93b3..87892e58a1 100644 --- a/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveTSIncomingMessageArchiver.swift +++ b/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveTSIncomingMessageArchiver.swift @@ -321,9 +321,9 @@ extension BackupArchiveTSIncomingMessageArchiver: BackupArchive.TSMessageEditHis // 0 == no expiration expiresInSeconds = 0 } - let expireStartDate: UInt64 + let expireStartedAt: UInt64 if chatItem.hasExpireStartDate { - expireStartDate = chatItem.expireStartDate + expireStartedAt = chatItem.expireStartDate } else if expiresInSeconds > 0, incomingDetails.read @@ -331,10 +331,10 @@ extension BackupArchiveTSIncomingMessageArchiver: BackupArchive.TSMessageEditHis // If marked as read but the chat timer hasn't started, // thats a bug on the export side but we can recover // from it now by starting the timer now. - expireStartDate = context.startTimestampMs + expireStartedAt = context.startDate.ows_millisecondsSince1970 } else { // 0 = hasn't started expiring. - expireStartDate = 0 + expireStartedAt = 0 } let editState: TSEditState @@ -385,7 +385,7 @@ extension BackupArchiveTSIncomingMessageArchiver: BackupArchive.TSMessageEditHis expiresInSeconds: expiresInSeconds, // Backed up messages don't set the chat timer; version is irrelevant. expireTimerVersion: nil, - expireStartedAt: expireStartDate, + expireStartedAt: expireStartedAt, read: wasReadForInteraction, serverTimestamp: incomingDetails.dateServerSent, serverDeliveryTimestamp: 0, diff --git a/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveTSOutgoingMessageArchiver.swift b/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveTSOutgoingMessageArchiver.swift index ec31f7210b..22e2016958 100644 --- a/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveTSOutgoingMessageArchiver.swift +++ b/SignalServiceKit/Backups/Archiving/Archivers/ChatItem/BackupArchiveTSOutgoingMessageArchiver.swift @@ -535,18 +535,18 @@ extension BackupArchiveTSOutgoingMessageArchiver: BackupArchive.TSMessageEditHis return .messageFailure(partialErrors) } - let expireStartDate: UInt64 + let expireStartedAt: UInt64 if chatItem.hasExpireStartDate { - expireStartDate = chatItem.expireStartDate + expireStartedAt = chatItem.expireStartDate } else if expiresInSeconds > 0, TSOutgoingMessage.isEligibleToStartExpireTimer(recipientStates: Array(recipientAddressStates.values)) { // If there is an expire timer and the message is eligible to start expiring, // set the expire start time to now even if unset in the proto. - expireStartDate = context.startTimestampMs + expireStartedAt = context.startDate.ows_millisecondsSince1970 } else { - expireStartDate = 0 + expireStartedAt = 0 } let outgoingMessageResult: BackupArchive.RestoreInteractionResult = { @@ -566,7 +566,7 @@ extension BackupArchiveTSOutgoingMessageArchiver: BackupArchive.TSMessageEditHis expiresInSeconds: expiresInSeconds, // Backed up messages don't set the chat timer; version is irrelevant. expireTimerVersion: nil, - expireStartedAt: expireStartDate, + expireStartedAt: expireStartedAt, isVoiceMessage: false, isSmsMessageRestoredFromBackup: chatItem.sms, isViewOnceMessage: false, diff --git a/SignalServiceKit/Backups/Archiving/Archivers/Recipient/BackupArchive+RecipientContexts.swift b/SignalServiceKit/Backups/Archiving/Archivers/Recipient/BackupArchive+RecipientContexts.swift index c06146235d..957e3fceb9 100644 --- a/SignalServiceKit/Backups/Archiving/Archivers/Recipient/BackupArchive+RecipientContexts.swift +++ b/SignalServiceKit/Backups/Archiving/Archivers/Recipient/BackupArchive+RecipientContexts.swift @@ -133,14 +133,14 @@ extension BackupArchive { private let callLinkIdMap = SharedMap() init( - bencher: BackupArchive.ArchiveBencher, - attachmentByteCounter: BackupArchiveAttachmentByteCounter, - currentBackupAttachmentUploadEra: String, - includedContentFilter: IncludedContentFilter, localIdentifiers: LocalIdentifiers, localRecipientId: RecipientId, localSignalRecipientRowId: SignalRecipient.RowId, - startTimestampMs: UInt64, + startDate: Date, + remoteConfig: RemoteConfig, + bencher: BackupArchive.ArchiveBencher, + attachmentByteCounter: BackupArchiveAttachmentByteCounter, + includedContentFilter: IncludedContentFilter, tx: DBReadTransaction, ) { self.localIdentifiers = localIdentifiers @@ -161,11 +161,11 @@ extension BackupArchive { } super.init( + startDate: startDate, + remoteConfig: remoteConfig, bencher: bencher, attachmentByteCounter: attachmentByteCounter, - currentBackupAttachmentUploadEra: currentBackupAttachmentUploadEra, includedContentFilter: includedContentFilter, - startTimestampMs: startTimestampMs, tx: tx, ) } @@ -284,14 +284,16 @@ extension BackupArchive { init( localIdentifiers: LocalIdentifiers, - startTimestampMs: UInt64, + startDate: Date, + remoteConfig: RemoteConfig, attachmentByteCounter: BackupArchiveAttachmentByteCounter, isPrimaryDevice: Bool, tx: DBWriteTransaction, ) { self.localIdentifiers = localIdentifiers super.init( - startTimestampMs: startTimestampMs, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, tx: tx, diff --git a/SignalServiceKit/Backups/Archiving/Archivers/Recipient/BackupArchiveContactRecipientArchiver.swift b/SignalServiceKit/Backups/Archiving/Archivers/Recipient/BackupArchiveContactRecipientArchiver.swift index 288473ad7b..2c701fc007 100644 --- a/SignalServiceKit/Backups/Archiving/Archivers/Recipient/BackupArchiveContactRecipientArchiver.swift +++ b/SignalServiceKit/Backups/Archiving/Archivers/Recipient/BackupArchiveContactRecipientArchiver.swift @@ -733,7 +733,7 @@ public class BackupArchiveContactRecipientArchiver: BackupArchiveProtoStreamWrit uniqueId: recipient.uniqueId, identityKey: identityKey, isFirstKnownKey: true, - createdAt: Date(millisecondsSince1970: context.startTimestampMs), + createdAt: context.startDate, verificationState: verificationState, ) do { diff --git a/SignalServiceKit/Backups/Archiving/BackupArchive+Contexts.swift b/SignalServiceKit/Backups/Archiving/BackupArchive+Contexts.swift index 98656847e5..534bf3ed51 100644 --- a/SignalServiceKit/Backups/Archiving/BackupArchive+Contexts.swift +++ b/SignalServiceKit/Backups/Archiving/BackupArchive+Contexts.swift @@ -17,51 +17,58 @@ extension BackupArchive { /// archiving to be updating the database, just reading from it. /// (The exception to this is enqueuing attachment uploads.) open class ArchivingContext { - + /// The timestamp at which the archiving process started. + let startDate: Date + /// The remote config at the start of archiving. + let remoteConfig: RemoteConfig /// For benchmarking archive steps. let bencher: BackupArchive.ArchiveBencher + /// Counts archived attachment bytes for future progress reporting. let attachmentByteCounter: BackupArchiveAttachmentByteCounter /// Parameters configuring what content is included in this archive. let includedContentFilter: IncludedContentFilter - /// The timestamp at which the archiving process started. - let startTimestampMs: UInt64 - /// Always set even if BackupPlan is free - let currentBackupAttachmentUploadEra: String + /// The single transaction used to create the archive. let tx: DBReadTransaction init( + startDate: Date, + remoteConfig: RemoteConfig, bencher: BackupArchive.ArchiveBencher, attachmentByteCounter: BackupArchiveAttachmentByteCounter, - currentBackupAttachmentUploadEra: String, includedContentFilter: IncludedContentFilter, - startTimestampMs: UInt64, tx: DBReadTransaction, ) { + self.startDate = startDate + self.remoteConfig = remoteConfig self.bencher = bencher self.attachmentByteCounter = attachmentByteCounter - self.currentBackupAttachmentUploadEra = currentBackupAttachmentUploadEra self.includedContentFilter = includedContentFilter - self.startTimestampMs = startTimestampMs self.tx = tx } } /// Base context class used for restoring from a backup. open class RestoringContext { - /// The timestamp at which we began restoring. - public let startTimestampMs: UInt64 + public let startDate: Date + /// The remote config at the start of restoring. + public let remoteConfig: RemoteConfig + /// Counts restored attachment bytes for future progress reporting. public let attachmentByteCounter: BackupArchiveAttachmentByteCounter + /// Are we restoring onto a primary? public let isPrimaryDevice: Bool + /// The single transaction used to restore the archive. public let tx: DBWriteTransaction init( - startTimestampMs: UInt64, + startDate: Date, + remoteConfig: RemoteConfig, attachmentByteCounter: BackupArchiveAttachmentByteCounter, isPrimaryDevice: Bool, tx: DBWriteTransaction, ) { - self.startTimestampMs = startTimestampMs + self.startDate = startDate + self.remoteConfig = remoteConfig self.attachmentByteCounter = attachmentByteCounter self.isPrimaryDevice = isPrimaryDevice self.tx = tx diff --git a/SignalServiceKit/Backups/Archiving/BackupArchiveManagerImpl.swift b/SignalServiceKit/Backups/Archiving/BackupArchiveManagerImpl.swift index bbcaf642aa..68ffe7cc7e 100644 --- a/SignalServiceKit/Backups/Archiving/BackupArchiveManagerImpl.swift +++ b/SignalServiceKit/Backups/Archiving/BackupArchiveManagerImpl.swift @@ -233,7 +233,7 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { } backupSettingsStore.setLastBackupDetails( - date: metadata.exportStartTimestamp, + date: metadata.exportStartDate, backupFileSizeBytes: backupFileSizeBytes, backupMediaSizeBytes: backupMediaSizeBytes, tx: tx, @@ -264,7 +264,7 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { progress progressSink: OWSProgressSink?, ) async throws -> Upload.EncryptedBackupUploadMetadata { let attachmentByteCounter = BackupArchiveAttachmentByteCounter() - let startTimestamp = dateProvider() + let startDate = dateProvider() // Filter included content according to the purpose of this backup. let includedContentFilter = BackupArchive.IncludedContentFilter( @@ -310,14 +310,14 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { let metadata = try await _exportBackup( localIdentifiers: localIdentifiers, backupPurpose: backupPurpose.libsignalPurpose, - startTimestamp: startTimestamp, + startDate: startDate, includedContentFilter: includedContentFilter, progressSink: progressSink, attachmentByteCounter: attachmentByteCounter, benchTitle: "Export encrypted Backup", openOutputStreamBlock: { exportProgress, tx in return encryptedStreamProvider.openEncryptedOutputFileStream( - startTimestamp: startTimestamp, + startDate: startDate, encryptionMetadata: encryptionMetadata, exportProgress: exportProgress, attachmentByteCounter: attachmentByteCounter, @@ -340,7 +340,7 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { localIdentifiers: LocalIdentifiers, ) async throws -> URL { let attachmentByteCounter = BackupArchiveAttachmentByteCounter() - let startTimestamp = dateProvider() + let startDate = dateProvider() // For the integration tests, don't filter out any content. The premise // of the tests is to verify that round-tripping a Backup file is @@ -352,7 +352,7 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { return try await _exportBackup( localIdentifiers: localIdentifiers, backupPurpose: .remoteBackup, - startTimestamp: startTimestamp, + startDate: startDate, includedContentFilter: includedContentFilter, progressSink: nil, attachmentByteCounter: attachmentByteCounter, @@ -369,7 +369,7 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { private func _exportBackup( localIdentifiers: LocalIdentifiers, backupPurpose: MessageBackupPurpose, - startTimestamp: Date, + startDate: Date, includedContentFilter: BackupArchive.IncludedContentFilter, progressSink: OWSProgressSink?, attachmentByteCounter: BackupArchiveAttachmentByteCounter, @@ -426,7 +426,7 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { outputStream: outputStream, localIdentifiers: localIdentifiers, backupPurpose: backupPurpose, - startTimestamp: startTimestamp, + startDate: startDate, attachmentByteCounter: attachmentByteCounter, includedContentFilter: includedContentFilter, currentAppVersion: appVersion.currentAppVersion, @@ -446,7 +446,7 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { outputStream stream: BackupArchiveProtoOutputStream, localIdentifiers: LocalIdentifiers, backupPurpose: MessageBackupPurpose, - startTimestamp: Date, + startDate: Date, attachmentByteCounter: BackupArchiveAttachmentByteCounter, includedContentFilter: BackupArchive.IncludedContentFilter, currentAppVersion: String, @@ -458,8 +458,7 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { dateProviderMonotonic: dateProviderMonotonic, memorySampler: memorySampler, ) - - let startTimestampMs = startTimestamp.ows_millisecondsSince1970 + let remoteConfig = remoteConfigManager.currentConfig() let backupVersion = Constants.supportedBackupVersion let purposeString: String = switch backupPurpose { case .deviceTransfer: "LinkNSync" @@ -475,13 +474,13 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { var errors = [LoggableErrorAndProto]() let result = Result(catching: { - logger.info("Exporting for \(purposeString) with version \(backupVersion), timestamp \(startTimestampMs)") + logger.info("Exporting for \(purposeString) with version \(backupVersion), timestamp \(startDate.ows_millisecondsSince1970)") try autoreleasepool { try writeHeader( stream: stream, backupVersion: backupVersion, - backupTimeMs: startTimestampMs, + startDate: startDate, currentAppVersion: currentAppVersion, firstAppVersion: firstAppVersion, mediaRootBackupKey: mediaRootBackupKey, @@ -490,14 +489,12 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { } try Task.checkCancellation() - let currentBackupAttachmentUploadEra = backupAttachmentUploadEraStore.currentUploadEra(tx: tx) - let customChatColorContext = BackupArchive.CustomChatColorArchivingContext( + startDate: startDate, + remoteConfig: remoteConfig, bencher: bencher, attachmentByteCounter: attachmentByteCounter, - currentBackupAttachmentUploadEra: currentBackupAttachmentUploadEra, includedContentFilter: includedContentFilter, - startTimestampMs: startTimestampMs, tx: tx, ) try autoreleasepool { @@ -543,14 +540,14 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { } let recipientArchivingContext = BackupArchive.RecipientArchivingContext( - bencher: bencher, - attachmentByteCounter: attachmentByteCounter, - currentBackupAttachmentUploadEra: currentBackupAttachmentUploadEra, - includedContentFilter: includedContentFilter, localIdentifiers: localIdentifiers, localRecipientId: localRecipientId, localSignalRecipientRowId: localSignalRecipientRowId, - startTimestampMs: startTimestampMs, + startDate: startDate, + remoteConfig: remoteConfig, + bencher: bencher, + attachmentByteCounter: attachmentByteCounter, + includedContentFilter: includedContentFilter, tx: tx, ) @@ -621,13 +618,13 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { } let chatArchivingContext = BackupArchive.ChatArchivingContext( + customChatColorContext: customChatColorContext, + recipientContext: recipientArchivingContext, + startDate: startDate, + remoteConfig: remoteConfig, bencher: bencher, attachmentByteCounter: attachmentByteCounter, - currentBackupAttachmentUploadEra: currentBackupAttachmentUploadEra, - customChatColorContext: customChatColorContext, includedContentFilter: includedContentFilter, - recipientContext: recipientArchivingContext, - startTimestampMs: startTimestampMs, tx: tx, ) let chatArchiveResult = try chatArchiver.archiveChats( @@ -659,11 +656,11 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { } let archivingContext = BackupArchive.ArchivingContext( + startDate: startDate, + remoteConfig: remoteConfig, bencher: bencher, attachmentByteCounter: attachmentByteCounter, - currentBackupAttachmentUploadEra: currentBackupAttachmentUploadEra, includedContentFilter: includedContentFilter, - startTimestampMs: startTimestampMs, tx: tx, ) let stickerPackArchiveResult = try stickerPackArchiver.archiveStickerPacks( @@ -706,7 +703,7 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { private func writeHeader( stream: BackupArchiveProtoOutputStream, backupVersion: UInt64, - backupTimeMs: UInt64, + startDate: Date, currentAppVersion: String, firstAppVersion: String, mediaRootBackupKey: MediaRootBackupKey, @@ -714,7 +711,7 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { ) throws { var backupInfo = BackupProto_BackupInfo() backupInfo.version = backupVersion - backupInfo.backupTimeMs = backupTimeMs + backupInfo.backupTimeMs = startDate.ows_millisecondsSince1970 backupInfo.currentAppVersion = currentAppVersion backupInfo.firstAppVersion = firstAppVersion @@ -930,11 +927,10 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { throw OWSAssertionError("Restoring from backup twice!") } - let startTimestampMs = dateProvider().ows_millisecondsSince1970 + let startDate = dateProvider() + let remoteConfig = remoteConfigManager.currentConfig() let attachmentByteCounter = BackupArchiveAttachmentByteCounter() - let currentRemoteConfig = remoteConfigManager.currentConfig() - // Drops all indexes on the `TSInteraction` table before doing the // import, which dramatically speeds up the import. We'll then recreate // all these indexes in bulk afterwards. @@ -1011,31 +1007,33 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { init( localIdentifiers: LocalIdentifiers, - startTimestampMs: UInt64, + backupPurpose: MessageBackupPurpose, + startDate: Date, + remoteConfig: RemoteConfig, attachmentByteCounter: BackupArchiveAttachmentByteCounter, isPrimaryDevice: Bool, - currentRemoteConfig: RemoteConfig, - backupPurpose: MessageBackupPurpose, tx: DBWriteTransaction, ) { accountData = BackupArchive.AccountDataRestoringContext( - startTimestampMs: startTimestampMs, + backupPurpose: backupPurpose, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, - currentRemoteConfig: currentRemoteConfig, - backupPurpose: backupPurpose, tx: tx, ) customChatColor = BackupArchive.CustomChatColorRestoringContext( - startTimestampMs: startTimestampMs, + accountDataContext: accountData, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, - accountDataContext: accountData, tx: tx, ) recipient = BackupArchive.RecipientRestoringContext( localIdentifiers: localIdentifiers, - startTimestampMs: startTimestampMs, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, tx: tx, @@ -1043,21 +1041,25 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { chat = BackupArchive.ChatRestoringContext( customChatColorContext: customChatColor, recipientContext: recipient, - startTimestampMs: startTimestampMs, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, tx: tx, ) chatItem = BackupArchive.ChatItemRestoringContext( + accountDataContext: accountData, chatContext: chat, recipientContext: recipient, - startTimestampMs: startTimestampMs, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, tx: tx, ) stickerPack = BackupArchive.RestoringContext( - startTimestampMs: startTimestampMs, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, tx: tx, @@ -1066,11 +1068,11 @@ public class BackupArchiveManagerImpl: BackupArchiveManager { } let contexts = Contexts( localIdentifiers: localIdentifiers, - startTimestampMs: startTimestampMs, + backupPurpose: backupPurpose, + startDate: startDate, + remoteConfig: remoteConfig, attachmentByteCounter: attachmentByteCounter, isPrimaryDevice: isPrimaryDevice, - currentRemoteConfig: currentRemoteConfig, - backupPurpose: backupPurpose, tx: tx, ) diff --git a/SignalServiceKit/Backups/Archiving/BackupArchiveManagerMock.swift b/SignalServiceKit/Backups/Archiving/BackupArchiveManagerMock.swift index afde45b963..7e548516b4 100644 --- a/SignalServiceKit/Backups/Archiving/BackupArchiveManagerMock.swift +++ b/SignalServiceKit/Backups/Archiving/BackupArchiveManagerMock.swift @@ -50,7 +50,7 @@ open class BackupArchiveManagerMock: BackupArchiveManager { let source = await progress?.addSource(withLabel: "", unitCount: 1) source?.incrementCompletedUnitCount(by: 1) return Upload.EncryptedBackupUploadMetadata( - exportStartTimestamp: Date(), + exportStartDate: Date(), fileUrl: URL(string: "file://")!, digest: Data(), encryptedDataLength: 0, diff --git a/SignalServiceKit/Backups/Archiving/FileStreams/BackupArchiveProtoStreamProvider.swift b/SignalServiceKit/Backups/Archiving/FileStreams/BackupArchiveProtoStreamProvider.swift index f9ec6861ee..1fbceed1d7 100644 --- a/SignalServiceKit/Backups/Archiving/FileStreams/BackupArchiveProtoStreamProvider.swift +++ b/SignalServiceKit/Backups/Archiving/FileStreams/BackupArchiveProtoStreamProvider.swift @@ -108,7 +108,7 @@ public class BackupArchiveEncryptedProtoStreamProvider { /// The caller owns the returned stream, and is responsible for closing it /// once finished. func openEncryptedOutputFileStream( - startTimestamp: Date, + startDate: Date, encryptionMetadata: BackupExportPurpose.EncryptionMetadata, exportProgress: BackupArchiveExportProgress?, attachmentByteCounter: BackupArchiveAttachmentByteCounter, @@ -147,7 +147,7 @@ public class BackupArchiveEncryptedProtoStreamProvider { outputStream, metadataProvider: { return Upload.EncryptedBackupUploadMetadata( - exportStartTimestamp: startTimestamp, + exportStartDate: startDate, fileUrl: fileUrl, digest: try outputTrackingTransform.digest(), encryptedDataLength: UInt32(clamping: outputTrackingTransform.count), diff --git a/SignalServiceKit/Upload/Upload.swift b/SignalServiceKit/Upload/Upload.swift index 5bf845d055..135626a471 100644 --- a/SignalServiceKit/Upload/Upload.swift +++ b/SignalServiceKit/Upload/Upload.swift @@ -104,7 +104,7 @@ public enum Upload { public struct EncryptedBackupUploadMetadata: UploadMetadata { /// When we started the export of this backup. - public let exportStartTimestamp: Date + public let exportStartDate: Date /// File URL of the data consisting of "iv + encrypted data + hmac" public let fileUrl: URL