diff --git a/src/lib/utils.ts b/src/lib/utils.ts index d7f8891..e46d8fe 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -12,6 +12,10 @@ export function getOwn( return Object.hasOwn(obj, key) ? obj[key] : undefined } +export function assert(condition: unknown, message: string): asserts condition { + if (!condition) throw new Error(message) +} + export class ExhaustivenessError extends Error { constructor(value: never) { super(`Exhaustiveness check failed. ${value} should be impossible`) @@ -46,6 +50,14 @@ export type VersionIdObject = { * @returns versionId string */ export function getVersionId({ coreDiscoveryKey, index }: VersionIdObject) { + assert( + coreDiscoveryKey.byteLength >= 32, + 'version ID core discovery key must be have at least 32 bytes' + ) + assert( + Number.isSafeInteger(index) && index >= 0, + 'version ID index must be a non-negative integer' + ) return coreDiscoveryKey.toString('hex') + '/' + index } diff --git a/test/lib/utils.test.js b/test/lib/utils.test.js index 8201cd3..6056f4f 100644 --- a/test/lib/utils.test.js +++ b/test/lib/utils.test.js @@ -1,7 +1,11 @@ // @ts-check import assert from 'node:assert/strict' import test from 'node:test' -import { ExhaustivenessError, getOwn } from '../../dist/lib/utils.js' +import { + ExhaustivenessError, + getOwn, + getVersionId, +} from '../../dist/lib/utils.js' test('getOwn', () => { class Foo { @@ -38,3 +42,48 @@ test('ExhaustivenessError', () => { } }) }) + +test('getVersionId', () => { + const coreDiscoveryKeyHex = + '1f1c774ac1041092c5d8334316919f43fc9e4db48b2074a2d9d7ecf6df7a1181' + const valid = { + coreDiscoveryKey: Buffer.from(coreDiscoveryKeyHex, 'hex'), + index: 123, + } + + assert.equal( + getVersionId(valid), + coreDiscoveryKeyHex + '/' + 123, + 'serializing version ID' + ) + + assert.throws( + () => getVersionId({ ...valid, index: -1 }), + 'throws when index is negative' + ) + assert.throws( + () => getVersionId({ ...valid, index: 1.2 }), + 'throws when index is not an integer' + ) + assert.throws( + () => getVersionId({ ...valid, index: NaN }), + 'throws when index is NaN' + ) + assert.throws( + () => getVersionId({ ...valid, index: Infinity }), + 'throws when index is Infinity' + ) + + assert.throws( + () => getVersionId({ ...valid, coreDiscoveryKey: Buffer.alloc(0) }), + 'throws when core discovery key is empty' + ) + assert.throws( + () => + getVersionId({ + ...valid, + coreDiscoveryKey: valid.coreDiscoveryKey.subarray(0, 31), + }), + 'throws when core discovery key is too short' + ) +})