Support for global.messageQueueTimeInSeconds

Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
automated-signal 2024-10-15 18:12:03 -05:00 committed by GitHub
parent 23e3a847d1
commit 6cc07a4d17
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 57 additions and 31 deletions

View file

@ -36,6 +36,7 @@ export type ConfigKeyType =
| 'global.calling.maxGroupCallRingSize' | 'global.calling.maxGroupCallRingSize'
| 'global.groupsv2.groupSizeHardLimit' | 'global.groupsv2.groupSizeHardLimit'
| 'global.groupsv2.maxGroupSize' | 'global.groupsv2.maxGroupSize'
| 'global.messageQueueTimeInSeconds'
| 'global.nicknames.max' | 'global.nicknames.max'
| 'global.nicknames.min' | 'global.nicknames.min'
| 'global.textAttachmentLimitBytes'; | 'global.textAttachmentLimitBytes';

View file

@ -44,6 +44,7 @@ import { storageJobQueue } from '../util/JobQueue';
import { sleep } from '../util/sleep'; import { sleep } from '../util/sleep';
import { isMoreRecentThan, isOlderThan } from '../util/timestamp'; import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
import { map, filter } from '../util/iterables'; import { map, filter } from '../util/iterables';
import { getMessageQueueTime } from '../util/getMessageQueueTime';
import { ourProfileKeyService } from './ourProfileKey'; import { ourProfileKeyService } from './ourProfileKey';
import { import {
ConversationTypes, ConversationTypes,
@ -350,7 +351,10 @@ async function generateManifest(
if ( if (
storyDistributionList.deletedAtTimestamp != null && storyDistributionList.deletedAtTimestamp != null &&
isOlderThan(storyDistributionList.deletedAtTimestamp, durations.MONTH) isOlderThan(
storyDistributionList.deletedAtTimestamp,
getMessageQueueTime()
)
) { ) {
const droppedID = storyDistributionList.storageID; const droppedID = storyDistributionList.storageID;
const droppedVersion = storyDistributionList.storageVersion; const droppedVersion = storyDistributionList.storageVersion;
@ -1316,7 +1320,7 @@ async function processManifest(
'unregistered and not in remote manifest' 'unregistered and not in remote manifest'
); );
conversation.setUnregistered({ conversation.setUnregistered({
timestamp: Date.now() - durations.MONTH, timestamp: Date.now() - getMessageQueueTime(),
fromStorageService: true, fromStorageService: true,
// Saving below // Saving below

View file

@ -72,11 +72,9 @@ import {
fromRootKeyBytes, fromRootKeyBytes,
getRoomIdFromRootKey, getRoomIdFromRootKey,
} from '../util/callLinksRingrtc'; } from '../util/callLinksRingrtc';
import { import { fromAdminKeyBytes } from '../util/callLinks';
CALL_LINK_DELETED_STORAGE_RECORD_TTL,
fromAdminKeyBytes,
} from '../util/callLinks';
import { isOlderThan } from '../util/timestamp'; import { isOlderThan } from '../util/timestamp';
import { getMessageQueueTime } from '../util/getMessageQueueTime';
import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue'; import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue';
const MY_STORY_BYTES = uuidToBytes(MY_STORY_ID); const MY_STORY_BYTES = uuidToBytes(MY_STORY_ID);
@ -1981,8 +1979,7 @@ export async function mergeCallLinkRecord(
? getTimestampFromLong(callLinkRecord.deletedAtTimestampMs) ? getTimestampFromLong(callLinkRecord.deletedAtTimestampMs)
: null; : null;
const shouldDrop = const shouldDrop =
deletedAt != null && deletedAt != null && isOlderThan(deletedAt, getMessageQueueTime());
isOlderThan(deletedAt, CALL_LINK_DELETED_STORAGE_RECORD_TTL);
if (shouldDrop) { if (shouldDrop) {
details.push('expired deleted call link; scheduling for removal'); details.push('expired deleted call link; scheduling for removal');
} }

View file

@ -4,7 +4,7 @@
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { DataReader } from '../sql/Client'; import { DataReader } from '../sql/Client';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { DAY } from '../util/durations'; import { getMessageQueueTime } from '../util/getMessageQueueTime';
import * as Errors from '../types/errors'; import * as Errors from '../types/errors';
async function eraseTapToViewMessages() { async function eraseTapToViewMessages() {
@ -12,7 +12,9 @@ async function eraseTapToViewMessages() {
window.SignalContext.log.info( window.SignalContext.log.info(
'eraseTapToViewMessages: Loading messages...' 'eraseTapToViewMessages: Loading messages...'
); );
const messages = await DataReader.getTapToViewMessagesNeedingErase(); const maxTimestamp = Date.now() - getMessageQueueTime();
const messages =
await DataReader.getTapToViewMessagesNeedingErase(maxTimestamp);
await Promise.all( await Promise.all(
messages.map(async fromDB => { messages.map(async fromDB => {
const message = window.MessageCache.__DEPRECATED$register( const message = window.MessageCache.__DEPRECATED$register(
@ -59,7 +61,7 @@ class TapToViewMessagesDeletionService {
return; return;
} }
const nextCheck = receivedAt + 30 * DAY; const nextCheck = receivedAt + getMessageQueueTime();
window.SignalContext.log.info( window.SignalContext.log.info(
'checkTapToViewMessages: next check at', 'checkTapToViewMessages: next check at',
new Date(nextCheck).toISOString() new Date(nextCheck).toISOString()

View file

@ -541,7 +541,9 @@ type ReadableInterface = {
getMessagesUnexpectedlyMissingExpirationStartTimestamp: () => Array<MessageType>; getMessagesUnexpectedlyMissingExpirationStartTimestamp: () => Array<MessageType>;
getSoonestMessageExpiry: () => undefined | number; getSoonestMessageExpiry: () => undefined | number;
getNextTapToViewMessageTimestampToAgeOut: () => undefined | number; getNextTapToViewMessageTimestampToAgeOut: () => undefined | number;
getTapToViewMessagesNeedingErase: () => Array<MessageType>; getTapToViewMessagesNeedingErase: (
maxTimestamp: number
) => Array<MessageType>;
// getOlderMessagesByConversation is JSON on server, full message on Client // getOlderMessagesByConversation is JSON on server, full message on Client
getAllStories: (options: { getAllStories: (options: {
conversationId?: string; conversationId?: string;

View file

@ -4524,9 +4524,10 @@ function getNextTapToViewMessageTimestampToAgeOut(
return isNormalNumber(result) ? result : undefined; return isNormalNumber(result) ? result : undefined;
} }
function getTapToViewMessagesNeedingErase(db: ReadableDB): Array<MessageType> { function getTapToViewMessagesNeedingErase(
const THIRTY_DAYS_AGO = Date.now() - 30 * 24 * 60 * 60 * 1000; db: ReadableDB,
maxTimestamp: number
): Array<MessageType> {
const rows: JSONRows = db const rows: JSONRows = db
.prepare<Query>( .prepare<Query>(
` `
@ -4535,12 +4536,12 @@ function getTapToViewMessagesNeedingErase(db: ReadableDB): Array<MessageType> {
WHERE WHERE
isViewOnce = 1 isViewOnce = 1
AND (isErased IS NULL OR isErased != 1) AND (isErased IS NULL OR isErased != 1)
AND received_at <= $THIRTY_DAYS_AGO AND received_at <= $maxTimestamp
ORDER BY received_at ASC, sent_at ASC; ORDER BY received_at ASC, sent_at ASC;
` `
) )
.all({ .all({
THIRTY_DAYS_AGO, maxTimestamp,
}); });
return rows.map(row => jsonToObject(row.json)); return rows.map(row => jsonToObject(row.json));

View file

@ -59,6 +59,7 @@ import * as log from '../logging/log';
import type { StorageAccessType } from '../types/Storage'; import type { StorageAccessType } from '../types/Storage';
import { getRelativePath, createName } from '../util/attachmentPath'; import { getRelativePath, createName } from '../util/attachmentPath';
import { isBackupEnabled } from '../util/isBackupEnabled'; import { isBackupEnabled } from '../util/isBackupEnabled';
import { getMessageQueueTime } from '../util/getMessageQueueTime';
type StorageKeyByServiceIdKind = { type StorageKeyByServiceIdKind = {
[kind in ServiceIdKind]: keyof StorageAccessType; [kind in ServiceIdKind]: keyof StorageAccessType;
@ -77,7 +78,6 @@ export const KYBER_KEY_ID_KEY: StorageKeyByServiceIdKind = {
[ServiceIdKind.PNI]: 'maxKyberPreKeyIdPNI', [ServiceIdKind.PNI]: 'maxKyberPreKeyIdPNI',
}; };
const LAST_RESORT_KEY_ARCHIVE_AGE = 30 * DAY;
const LAST_RESORT_KEY_ROTATION_AGE = DAY * 1.5; const LAST_RESORT_KEY_ROTATION_AGE = DAY * 1.5;
const LAST_RESORT_KEY_MINIMUM = 5; const LAST_RESORT_KEY_MINIMUM = 5;
const LAST_RESORT_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = { const LAST_RESORT_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = {
@ -96,7 +96,6 @@ const PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = {
}; };
const PRE_KEY_MINIMUM = 10; const PRE_KEY_MINIMUM = 10;
const SIGNED_PRE_KEY_ARCHIVE_AGE = 30 * DAY;
export const SIGNED_PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = { export const SIGNED_PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = {
[ServiceIdKind.ACI]: 'signedKeyId', [ServiceIdKind.ACI]: 'signedKeyId',
[ServiceIdKind.Unknown]: 'signedKeyId', [ServiceIdKind.Unknown]: 'signedKeyId',
@ -756,7 +755,7 @@ export default class AccountManager extends EventTarget {
'confirmed' 'confirmed'
); );
// Keep SIGNED_PRE_KEY_MINIMUM keys, drop if older than SIGNED_PRE_KEY_ARCHIVE_AGE // Keep SIGNED_PRE_KEY_MINIMUM keys, drop if older than message queue time
const toDelete: Array<number> = []; const toDelete: Array<number> = [];
sortedKeys.forEach((key, index) => { sortedKeys.forEach((key, index) => {
@ -765,7 +764,7 @@ export default class AccountManager extends EventTarget {
} }
const createdAt = key.created_at || 0; const createdAt = key.created_at || 0;
if (isOlderThan(createdAt, SIGNED_PRE_KEY_ARCHIVE_AGE)) { if (isOlderThan(createdAt, getMessageQueueTime())) {
const timestamp = new Date(createdAt).toJSON(); const timestamp = new Date(createdAt).toJSON();
const confirmedText = key.confirmed ? ' (confirmed)' : ''; const confirmedText = key.confirmed ? ' (confirmed)' : '';
log.info( log.info(
@ -813,7 +812,7 @@ export default class AccountManager extends EventTarget {
'confirmed' 'confirmed'
); );
// Keep LAST_RESORT_KEY_MINIMUM keys, drop if older than LAST_RESORT_KEY_ARCHIVE_AGE // Keep LAST_RESORT_KEY_MINIMUM keys, drop if older than message queue time
const toDelete: Array<number> = []; const toDelete: Array<number> = [];
sortedKeys.forEach((key, index) => { sortedKeys.forEach((key, index) => {
@ -822,7 +821,7 @@ export default class AccountManager extends EventTarget {
} }
const createdAt = key.createdAt || 0; const createdAt = key.createdAt || 0;
if (isOlderThan(createdAt, LAST_RESORT_KEY_ARCHIVE_AGE)) { if (isOlderThan(createdAt, getMessageQueueTime())) {
const timestamp = new Date(createdAt).toJSON(); const timestamp = new Date(createdAt).toJSON();
const confirmedText = key.isConfirmed ? ' (confirmed)' : ''; const confirmedText = key.isConfirmed ? ' (confirmed)' : '';
log.info( log.info(

View file

@ -30,6 +30,7 @@ import { strictAssert } from '../util/assert';
import type { SignalService as Proto } from '../protobuf'; import type { SignalService as Proto } from '../protobuf';
import { isMoreRecentThan } from '../util/timestamp'; import { isMoreRecentThan } from '../util/timestamp';
import { DAY } from '../util/durations'; import { DAY } from '../util/durations';
import { getMessageQueueTime } from '../util/getMessageQueueTime';
import { getLocalAttachmentUrl } from '../util/getLocalAttachmentUrl'; import { getLocalAttachmentUrl } from '../util/getLocalAttachmentUrl';
import type { ReencryptionInfo } from '../AttachmentCrypto'; import type { ReencryptionInfo } from '../AttachmentCrypto';
@ -1156,10 +1157,9 @@ export function isReencryptableWithNewEncryptionInfo(
); );
} }
const TIME_ON_TRANSIT_TIER = 30 * DAY;
// Extend range in case the attachment is actually still there (this function is meant to // Extend range in case the attachment is actually still there (this function is meant to
// be optimistic) // be optimistic)
const BUFFERED_TIME_ON_TRANSIT_TIER = TIME_ON_TRANSIT_TIER + 5 * DAY; const BUFFER_TIME_ON_TRANSIT_TIER = 5 * DAY;
export function mightStillBeOnTransitTier( export function mightStillBeOnTransitTier(
attachment: Pick<AttachmentType, 'cdnKey' | 'cdnNumber' | 'uploadTimestamp'> attachment: Pick<AttachmentType, 'cdnKey' | 'cdnNumber' | 'uploadTimestamp'>
@ -1177,7 +1177,10 @@ export function mightStillBeOnTransitTier(
} }
if ( if (
isMoreRecentThan(attachment.uploadTimestamp, BUFFERED_TIME_ON_TRANSIT_TIER) isMoreRecentThan(
attachment.uploadTimestamp,
getMessageQueueTime() + BUFFER_TIME_ON_TRANSIT_TIER
)
) { ) {
return true; return true;
} }

View file

@ -15,7 +15,6 @@ import {
type CallHistoryDetails, type CallHistoryDetails,
CallMode, CallMode,
} from '../types/CallDisposition'; } from '../types/CallDisposition';
import { DAY } from './durations';
export const CALL_LINK_DEFAULT_STATE: Pick< export const CALL_LINK_DEFAULT_STATE: Pick<
CallLinkType, CallLinkType,
@ -28,8 +27,6 @@ export const CALL_LINK_DEFAULT_STATE: Pick<
storageNeedsSync: false, storageNeedsSync: false,
}; };
export const CALL_LINK_DELETED_STORAGE_RECORD_TTL = 30 * DAY;
export function getKeyFromCallLink(callLink: string): string { export function getKeyFromCallLink(callLink: string): string {
const url = new URL(callLink); const url = new URL(callLink);
if (url == null) { if (url == null) {

View file

@ -0,0 +1,18 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as RemoteConfig from '../RemoteConfig';
import { MONTH, SECOND } from './durations';
import { parseIntWithFallback } from './parseIntWithFallback';
export function getMessageQueueTime(): number {
return (
Math.max(
parseIntWithFallback(
RemoteConfig.getValue('global.messageQueueTimeInSeconds'),
MONTH / SECOND
),
MONTH / SECOND
) * SECOND
);
}

View file

@ -3,7 +3,8 @@
import type { ServiceIdString } from '../types/ServiceId'; import type { ServiceIdString } from '../types/ServiceId';
import { isMoreRecentThan, isOlderThan } from './timestamp'; import { isMoreRecentThan, isOlderThan } from './timestamp';
import { HOUR, MONTH } from './durations'; import { HOUR } from './durations';
import { getMessageQueueTime } from './getMessageQueueTime';
const SIX_HOURS = 6 * HOUR; const SIX_HOURS = 6 * HOUR;
@ -45,6 +46,7 @@ export function isConversationUnregisteredAndStale({
} }
return Boolean( return Boolean(
firstUnregisteredAt && isOlderThan(firstUnregisteredAt, MONTH) firstUnregisteredAt &&
isOlderThan(firstUnregisteredAt, getMessageQueueTime())
); );
} }