From 5ba8071aa7ce65af16cacd26c7dc96da2a2dd748 Mon Sep 17 00:00:00 2001 From: trevor-signal <131492920+trevor-signal@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:39:21 -0400 Subject: [PATCH] Ensure incrementalMac consistency when exporting --- ts/services/backups/export.ts | 22 ++++++++++----------- ts/services/backups/util/filePointers.ts | 22 ++++++++++----------- ts/test-electron/backup/attachments_test.ts | 16 +++++++++------ 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/ts/services/backups/export.ts b/ts/services/backups/export.ts index b882d1323c..367bd13876 100644 --- a/ts/services/backups/export.ts +++ b/ts/services/backups/export.ts @@ -249,10 +249,7 @@ export class BackupExportStream extends Readable { readonly #serviceIdToRecipientId = new Map(); readonly #e164ToRecipientId = new Map(); readonly #roomIdToRecipientId = new Map(); - readonly #mediaNamesToLocatorInfos = new Map< - string, - Backups.FilePointer.ILocatorInfo - >(); + readonly #mediaNamesToFilePointers = new Map(); readonly #stats: StatsType = { adHocCalls: 0, callLinks: 0, @@ -355,7 +352,7 @@ export class BackupExportStream extends Readable { } public getMediaNamesIterator(): MapIterator { - return this.#mediaNamesToLocatorInfos.keys(); + return this.#mediaNamesToFilePointers.keys(); } public getStats(): Readonly { @@ -2608,16 +2605,17 @@ export class BackupExportStream extends Readable { const mediaName = getMediaNameForAttachment(attachment); // Re-use existing locatorInfo and backup job if we've already seen this file - const existingLocatorInfo = this.#mediaNamesToLocatorInfos.get(mediaName); + const existingFilePointer = this.#mediaNamesToFilePointers.get(mediaName); - if (existingLocatorInfo) { - filePointer.locatorInfo = existingLocatorInfo; + if (existingFilePointer?.locatorInfo) { + filePointer.locatorInfo = existingFilePointer.locatorInfo; + // Also copy over incrementalMac, since that depends on the encryption key + filePointer.incrementalMac = existingFilePointer.incrementalMac; + filePointer.incrementalMacChunkSize = + existingFilePointer.incrementalMacChunkSize; } else { if (filePointer.locatorInfo) { - this.#mediaNamesToLocatorInfos.set( - mediaName, - filePointer.locatorInfo - ); + this.#mediaNamesToFilePointers.set(mediaName, filePointer); } if (backupJob) { diff --git a/ts/services/backups/util/filePointers.ts b/ts/services/backups/util/filePointers.ts index 7fbdec251b..0768a359ca 100644 --- a/ts/services/backups/util/filePointers.ts +++ b/ts/services/backups/util/filePointers.ts @@ -38,6 +38,7 @@ import { isValidAttachmentKey, isValidPlaintextHash, } from '../../../types/Crypto'; +import { isTestOrMockEnvironment } from '../../../environment'; const log = createLogger('filePointers'); @@ -227,19 +228,18 @@ export async function getFilePointerForAttachment({ height: attachment.height, caption: attachment.caption, blurHash: attachment.blurHash, - - // Resilience to invalid data in the database from internal testing - ...(typeof attachment.incrementalMac === 'string' && attachment.chunkSize - ? { - incrementalMac: Bytes.fromBase64(attachment.incrementalMac), - incrementalMacChunkSize: attachment.chunkSize, - } - : { - incrementalMac: undefined, - incrementalMacChunkSize: undefined, - }), }); + // TODO: DESKTOP-9112 + if (isTestOrMockEnvironment()) { + // Check for string type for resilience to invalid data in the database from internal + // testing + if (typeof attachment.incrementalMac === 'string' && attachment.chunkSize) { + filePointer.incrementalMac = Bytes.fromBase64(attachment.incrementalMac); + filePointer.incrementalMacChunkSize = attachment.chunkSize; + } + } + const locatorInfo = getLocatorInfoForAttachment({ attachment, isLocalBackup, diff --git a/ts/test-electron/backup/attachments_test.ts b/ts/test-electron/backup/attachments_test.ts index a023991e71..13af7b540a 100644 --- a/ts/test-electron/backup/attachments_test.ts +++ b/ts/test-electron/backup/attachments_test.ts @@ -100,12 +100,14 @@ describe('backup/attachments', () => { plaintextHash: Bytes.toHex(getRandomBytes(32)), key: Bytes.toBase64(generateKeys()), digest: Bytes.toBase64(getRandomBytes(32)), - size: 100, + size: 100 + index, contentType: IMAGE_JPEG, path: `/path/to/file${index}.png`, caption: `caption${index}`, localKey: Bytes.toBase64(generateAttachmentKeys()), uploadTimestamp: index, + incrementalMac: Bytes.toBase64(getRandomBytes(32)), + chunkSize: index * 128, thumbnail: { size: 1024, width: 150, @@ -405,11 +407,10 @@ describe('backup/attachments', () => { it('deduplicates attachments on export based on mediaName', async () => { const attachment1 = composeAttachment(1); const attachment2 = { - ...attachment1, - contentType: IMAGE_WEBP, - caption: 'attachment2caption', - cdnKey: 'attachment2cdnkey', - cdnNumber: 25, + ...composeAttachment(2), + plaintextHash: attachment1.plaintextHash, + key: attachment1.key, + size: attachment1.size, }; await asymmetricRoundtripHarness( @@ -431,6 +432,9 @@ describe('backup/attachments', () => { ...attachment2, cdnKey: attachment1.cdnKey, cdnNumber: attachment1.cdnNumber, + uploadTimestamp: attachment1.uploadTimestamp, + incrementalMac: attachment1.incrementalMac, + chunkSize: attachment1.chunkSize, }), ], }),