Reuse recent CDN locators

Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
automated-signal
2026-02-24 14:31:40 -06:00
committed by GitHub
parent d0ec8ae066
commit 4a9093091c
8 changed files with 398 additions and 120 deletions

View File

@@ -3,6 +3,7 @@
import Long from 'long';
import { createReadStream } from 'node:fs';
import type {
AttachmentType,
AttachmentWithHydratedData,
UploadedAttachmentType,
} from '../types/Attachment.std.js';
@@ -27,12 +28,20 @@ import {
} from '../AttachmentCrypto.node.js';
import { missingCaseError } from './missingCaseError.std.js';
import { uuidToBytes } from './uuidToBytes.std.js';
import { DAY } from './durations/index.std.js';
import { DAY, HOUR } from './durations/index.std.js';
import { isImageAttachment, isVideoAttachment } from './Attachment.std.js';
import { getAbsoluteAttachmentPath } from './migrations.preload.js';
import { isMoreRecentThan } from './timestamp.std.js';
import { DataReader } from '../sql/Client.preload.js';
import {
isValidAttachmentKey,
isValidDigest,
isValidPlaintextHash,
} from '../types/Crypto.std.js';
import type { ExistingAttachmentUploadData } from '../sql/Interface.std.js';
const CDNS_SUPPORTING_TUS = new Set([3]);
const MAX_DURATION_TO_REUSE_ATTACHMENT_CDN_POINTER = 3 * DAY;
const log = createLogger('uploadAttachment');
@@ -42,45 +51,40 @@ export async function uploadAttachment(
let keys: Uint8Array;
let cdnKey: string;
let cdnNumber: number;
let encrypted: Pick<
EncryptedAttachmentV2,
'digest' | 'plaintextHash' | 'incrementalMac' | 'chunkSize'
>;
let digest: Uint8Array;
let plaintextHash: string;
let incrementalMac: Uint8Array | undefined;
let chunkSize: number | undefined;
let uploadTimestamp: number;
// Recently uploaded attachment
if (
attachment.cdnKey &&
attachment.cdnNumber &&
attachment.key &&
attachment.size != null &&
attachment.digest &&
attachment.contentType != null &&
attachment.plaintextHash != null &&
attachment.uploadTimestamp != null &&
isMoreRecentThan(attachment.uploadTimestamp, 3 * DAY)
) {
log.info('reusing attachment uploaded at', attachment.uploadTimestamp);
const needIncrementalMac = supportsIncrementalMac(attachment.contentType);
const dataForReuse = await getAttachmentUploadDataForReuse(attachment);
({ cdnKey, cdnNumber, uploadTimestamp } = attachment);
if (dataForReuse && isValidPlaintextHash(attachment.plaintextHash)) {
log.info('reusing attachment uploaded at', dataForReuse.uploadTimestamp);
keys = Bytes.fromBase64(attachment.key);
({ cdnKey, cdnNumber, uploadTimestamp } = dataForReuse);
keys = Bytes.fromBase64(dataForReuse.key);
encrypted = {
digest: Bytes.fromBase64(attachment.digest),
plaintextHash: attachment.plaintextHash,
incrementalMac: attachment.incrementalMac
? Bytes.fromBase64(attachment.incrementalMac)
: undefined,
chunkSize: attachment.chunkSize,
};
digest = Bytes.fromBase64(dataForReuse.digest);
plaintextHash = attachment.plaintextHash;
incrementalMac =
needIncrementalMac && dataForReuse.incrementalMac
? Bytes.fromBase64(dataForReuse.incrementalMac)
: undefined;
chunkSize =
needIncrementalMac && dataForReuse.chunkSize
? dataForReuse.chunkSize
: undefined;
} else {
keys = getRandomBytes(64);
uploadTimestamp = Date.now();
const needIncrementalMac = supportsIncrementalMac(attachment.contentType);
({ cdnKey, cdnNumber, encrypted } = await encryptAndUploadAttachment({
({
cdnKey,
cdnNumber,
encrypted: { digest, plaintextHash, incrementalMac, chunkSize },
} = await encryptAndUploadAttachment({
keys,
needIncrementalMac,
plaintext: { data: attachment.data },
@@ -101,10 +105,10 @@ export async function uploadAttachment(
clientUuid: clientUuid ? uuidToBytes(clientUuid) : undefined,
key: keys,
size: attachment.data.byteLength,
digest: encrypted.digest,
plaintextHash: encrypted.plaintextHash,
incrementalMac: encrypted.incrementalMac,
chunkSize: encrypted.chunkSize,
digest,
plaintextHash,
incrementalMac,
chunkSize,
uploadTimestamp: Long.fromNumber(uploadTimestamp),
contentType: MIMETypeToString(attachment.contentType),
@@ -199,3 +203,66 @@ export async function uploadFile({
);
}
}
async function getAttachmentUploadDataForReuse(
attachment: AttachmentType
): Promise<ExistingAttachmentUploadData | null> {
if (!isValidPlaintextHash(attachment.plaintextHash)) {
return null;
}
if (isValidCdnDataForReuse(attachment)) {
return {
cdnKey: attachment.cdnKey,
cdnNumber: attachment.cdnNumber,
key: attachment.key,
digest: attachment.digest,
uploadTimestamp: attachment.uploadTimestamp,
incrementalMac: attachment.incrementalMac ?? null,
chunkSize: attachment.chunkSize ?? null,
};
}
const recentAttachmentUploadData =
await DataReader.getMostRecentAttachmentUploadData(
attachment.plaintextHash
);
if (
recentAttachmentUploadData &&
isValidCdnDataForReuse(recentAttachmentUploadData)
) {
return recentAttachmentUploadData;
}
return null;
}
function isValidCdnDataForReuse(attachment: {
cdnKey?: string;
cdnNumber?: number;
digest?: string;
key?: string;
uploadTimestamp?: number;
}): attachment is {
cdnKey: string;
cdnNumber: number;
digest: string;
key: string;
uploadTimestamp: number;
} {
return Boolean(
isValidDigest(attachment.digest) &&
isValidAttachmentKey(attachment.key) &&
attachment.cdnKey != null &&
attachment.cdnNumber != null &&
attachment.uploadTimestamp &&
isMoreRecentThan(
attachment.uploadTimestamp,
MAX_DURATION_TO_REUSE_ATTACHMENT_CDN_POINTER
) &&
// for extra safety, check to make sure we don't have an uploadTimestamp that's
// incorrectly in the future
attachment.uploadTimestamp < Date.now() + 12 * HOUR
);
}