mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-04-22 17:38:10 +01:00
Reuse recent CDN locators
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user