fix: use crypto.getRandomValues for randId to eliminate collision risk (#1282)
* fix(randId): use crypto.getRandomValues for connection IDs Math.random() gives ~30 bits of entropy and no cryptographic guarantees. Switch to crypto.getRandomValues over a Uint32 buffer so device / saved- connection IDs get full 32-bit range and don't collide across concurrent transports. Closes #1273 * fix: assert Uint32Array index for strict TS7 compat Under strict + noUncheckedIndexedAccess (TS7 default), buf[0] is number | undefined. Uint32Array(1) guarantees length 1, so assert non-null to satisfy the number return type.
This commit is contained in:
@@ -1,44 +1,25 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { randId } from "./randId.ts";
|
import { randId } from "./randId.ts";
|
||||||
|
|
||||||
describe("randId", () => {
|
describe("randId", () => {
|
||||||
beforeEach(() => {
|
|
||||||
vi.restoreAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a number", () => {
|
|
||||||
const result = randId();
|
|
||||||
expect(typeof result).toBe("number");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns an integer", () => {
|
it("returns an integer", () => {
|
||||||
const result = randId();
|
const result = randId();
|
||||||
|
expect(typeof result).toBe("number");
|
||||||
expect(Number.isInteger(result)).toBe(true);
|
expect(Number.isInteger(result)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses Math.random to generate the number", () => {
|
it("returns a value within the uint32 range", () => {
|
||||||
const mockRandom = vi.spyOn(Math, "random").mockReturnValue(0.5);
|
|
||||||
const result = randId();
|
|
||||||
|
|
||||||
expect(mockRandom).toHaveBeenCalled();
|
|
||||||
expect(result).toBe(Math.floor(0.5 * 1e9));
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a value between 0 and 1e9 (exclusive)", () => {
|
|
||||||
const result = randId();
|
const result = randId();
|
||||||
expect(result).toBeGreaterThanOrEqual(0);
|
expect(result).toBeGreaterThanOrEqual(0);
|
||||||
expect(result).toBeLessThan(1e9);
|
expect(result).toBeLessThanOrEqual(0xffffffff);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns different values on subsequent calls", () => {
|
it("returns unique values across many calls", () => {
|
||||||
vi.spyOn(Math, "random").mockRestore();
|
const results = new Set<number>();
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
const results = new Set();
|
|
||||||
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
results.add(randId());
|
results.add(randId());
|
||||||
}
|
}
|
||||||
|
// With 32 bits of entropy over 1000 samples, collisions are vanishingly rare.
|
||||||
expect(results.size).toBeGreaterThan(95);
|
expect(results.size).toBe(1000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
export const randId = () => {
|
export const randId = (): number => {
|
||||||
return Math.floor(Math.random() * 1e9);
|
const buf = new Uint32Array(1);
|
||||||
|
crypto.getRandomValues(buf);
|
||||||
|
return buf[0]!;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user