Fix timestamp capping for storage service
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
		
					parent
					
						
							
								5fc53ee435
							
						
					
				
			
			
				commit
				
					
						f15d5049f8
					
				
			
		
					 5 changed files with 141 additions and 7 deletions
				
			
		| 
						 | 
					@ -888,7 +888,10 @@ export async function mergeGroupV1Record(
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  conversation.setMuteExpiration(
 | 
					  conversation.setMuteExpiration(
 | 
				
			||||||
    getTimestampFromLong(groupV1Record.mutedUntilTimestamp),
 | 
					    getTimestampFromLong(
 | 
				
			||||||
 | 
					      groupV1Record.mutedUntilTimestamp,
 | 
				
			||||||
 | 
					      Number.MAX_SAFE_INTEGER
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      viaStorageServiceSync: true,
 | 
					      viaStorageServiceSync: true,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -1025,7 +1028,10 @@ export async function mergeGroupV2Record(
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  conversation.setMuteExpiration(
 | 
					  conversation.setMuteExpiration(
 | 
				
			||||||
    getTimestampFromLong(groupV2Record.mutedUntilTimestamp),
 | 
					    getTimestampFromLong(
 | 
				
			||||||
 | 
					      groupV2Record.mutedUntilTimestamp,
 | 
				
			||||||
 | 
					      Number.MAX_SAFE_INTEGER
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      viaStorageServiceSync: true,
 | 
					      viaStorageServiceSync: true,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -1265,7 +1271,10 @@ export async function mergeContactRecord(
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  conversation.setMuteExpiration(
 | 
					  conversation.setMuteExpiration(
 | 
				
			||||||
    getTimestampFromLong(contactRecord.mutedUntilTimestamp),
 | 
					    getTimestampFromLong(
 | 
				
			||||||
 | 
					      contactRecord.mutedUntilTimestamp,
 | 
				
			||||||
 | 
					      Number.MAX_SAFE_INTEGER
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      viaStorageServiceSync: true,
 | 
					      viaStorageServiceSync: true,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										40
									
								
								ts/sql/migrations/1310-muted-fixup.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ts/sql/migrations/1310-muted-fixup.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					// Copyright 2025 Signal Messenger, LLC
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					import type { LoggerType } from '../../types/Logging';
 | 
				
			||||||
 | 
					import { sql } from '../util';
 | 
				
			||||||
 | 
					import type { WritableDB } from '../Interface';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const version = 1310;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Value from ts/util/timestamp.ts at the time of creation of this migration
 | 
				
			||||||
 | 
					const MAX_SAFE_DATE = 8640000000000000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function updateToSchemaVersion1310(
 | 
				
			||||||
 | 
					  currentVersion: number,
 | 
				
			||||||
 | 
					  db: WritableDB,
 | 
				
			||||||
 | 
					  logger: LoggerType
 | 
				
			||||||
 | 
					): void {
 | 
				
			||||||
 | 
					  if (currentVersion >= 1310) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  db.transaction(() => {
 | 
				
			||||||
 | 
					    const [query, params] = sql`
 | 
				
			||||||
 | 
					      UPDATE conversations
 | 
				
			||||||
 | 
					        SET json = json_replace(
 | 
				
			||||||
 | 
					          json,
 | 
				
			||||||
 | 
					          '$.muteExpiresAt',
 | 
				
			||||||
 | 
					          9007199254740991 -- max safe integer
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        WHERE json ->> '$.muteExpiresAt' IS ${MAX_SAFE_DATE};
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					    const { changes } = db.prepare(query).run(params);
 | 
				
			||||||
 | 
					    if (changes !== 0) {
 | 
				
			||||||
 | 
					      logger.warn(`updateToSchemaVersion1310: fixed ${changes} conversations`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    db.pragma('user_version = 1310');
 | 
				
			||||||
 | 
					  })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  logger.info('updateToSchemaVersion1310: success!');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -106,10 +106,11 @@ import { updateToSchemaVersion1260 } from './1260-sync-tasks-rowid';
 | 
				
			||||||
import { updateToSchemaVersion1270 } from './1270-normalize-messages';
 | 
					import { updateToSchemaVersion1270 } from './1270-normalize-messages';
 | 
				
			||||||
import { updateToSchemaVersion1280 } from './1280-blob-unprocessed';
 | 
					import { updateToSchemaVersion1280 } from './1280-blob-unprocessed';
 | 
				
			||||||
import { updateToSchemaVersion1290 } from './1290-int-unprocessed-source-device';
 | 
					import { updateToSchemaVersion1290 } from './1290-int-unprocessed-source-device';
 | 
				
			||||||
 | 
					import { updateToSchemaVersion1300 } from './1300-sticker-pack-refs';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  updateToSchemaVersion1300,
 | 
					  updateToSchemaVersion1310,
 | 
				
			||||||
  version as MAX_VERSION,
 | 
					  version as MAX_VERSION,
 | 
				
			||||||
} from './1300-sticker-pack-refs';
 | 
					} from './1310-muted-fixup';
 | 
				
			||||||
import { DataWriter } from '../Server';
 | 
					import { DataWriter } from '../Server';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function updateToSchemaVersion1(
 | 
					function updateToSchemaVersion1(
 | 
				
			||||||
| 
						 | 
					@ -2087,6 +2088,7 @@ export const SCHEMA_VERSIONS = [
 | 
				
			||||||
  updateToSchemaVersion1290,
 | 
					  updateToSchemaVersion1290,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  updateToSchemaVersion1300,
 | 
					  updateToSchemaVersion1300,
 | 
				
			||||||
 | 
					  updateToSchemaVersion1310,
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class DBVersionFromFutureError extends Error {
 | 
					export class DBVersionFromFutureError extends Error {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										80
									
								
								ts/test-node/sql/migration_1310_test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								ts/test-node/sql/migration_1310_test.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,80 @@
 | 
				
			||||||
 | 
					// Copyright 2025 Signal Messenger, LLC
 | 
				
			||||||
 | 
					// SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { assert } from 'chai';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { type WritableDB } from '../../sql/Interface';
 | 
				
			||||||
 | 
					import { createDB, updateToVersion, insertData, getTableData } from './helpers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('SQL/updateToSchemaVersion1310', () => {
 | 
				
			||||||
 | 
					  let db: WritableDB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(() => {
 | 
				
			||||||
 | 
					    db.close();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    db = createDB();
 | 
				
			||||||
 | 
					    updateToVersion(db, 1300);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('leaves absent muteExpiresAt untouched', () => {
 | 
				
			||||||
 | 
					    const convos = [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        id: 'convo',
 | 
				
			||||||
 | 
					        expireTimerVersion: 1,
 | 
				
			||||||
 | 
					        json: {},
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    insertData(db, 'conversations', convos);
 | 
				
			||||||
 | 
					    updateToVersion(db, 1310);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.deepStrictEqual(getTableData(db, 'conversations'), convos);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('leaves regular muteExpiresAt untouched', () => {
 | 
				
			||||||
 | 
					    const convos = [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        id: 'convo',
 | 
				
			||||||
 | 
					        expireTimerVersion: 1,
 | 
				
			||||||
 | 
					        json: {
 | 
				
			||||||
 | 
					          muteExpiresAt: 123,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        id: 'convo-2',
 | 
				
			||||||
 | 
					        expireTimerVersion: 1,
 | 
				
			||||||
 | 
					        json: {
 | 
				
			||||||
 | 
					          muteExpiresAt: 8640000000000000 - 1,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    insertData(db, 'conversations', convos);
 | 
				
			||||||
 | 
					    updateToVersion(db, 1310);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.deepStrictEqual(getTableData(db, 'conversations'), convos);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('promotes MAX_SAFE_DATE to MAX_SAFE_INTEGER', () => {
 | 
				
			||||||
 | 
					    insertData(db, 'conversations', [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        id: 'convo',
 | 
				
			||||||
 | 
					        expireTimerVersion: 1,
 | 
				
			||||||
 | 
					        json: {
 | 
				
			||||||
 | 
					          muteExpiresAt: 8640000000000000,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					    updateToVersion(db, 1310);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert.deepStrictEqual(getTableData(db, 'conversations'), [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        id: 'convo',
 | 
				
			||||||
 | 
					        expireTimerVersion: 1,
 | 
				
			||||||
 | 
					        json: {
 | 
				
			||||||
 | 
					          muteExpiresAt: Number.MAX_SAFE_INTEGER,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,10 @@ export function getSafeLongFromTimestamp(
 | 
				
			||||||
  return Long.fromNumber(timestamp);
 | 
					  return Long.fromNumber(timestamp);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getTimestampFromLong(value?: Long | null): number {
 | 
					export function getTimestampFromLong(
 | 
				
			||||||
 | 
					  value?: Long | null,
 | 
				
			||||||
 | 
					  maxValue = MAX_SAFE_DATE
 | 
				
			||||||
 | 
					): number {
 | 
				
			||||||
  if (!value || value.isNegative()) {
 | 
					  if (!value || value.isNegative()) {
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -27,7 +30,7 @@ export function getTimestampFromLong(value?: Long | null): number {
 | 
				
			||||||
  const num = value.toNumber();
 | 
					  const num = value.toNumber();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (num > MAX_SAFE_DATE) {
 | 
					  if (num > MAX_SAFE_DATE) {
 | 
				
			||||||
    return MAX_SAFE_DATE;
 | 
					    return maxValue;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return num;
 | 
					  return num;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue