// SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2026 Bryan Joshua Pedini /** * Small deterministic PRNG (sfc32 seeded through a string hash) for reproducible fuzzing. * The run seed comes from $DPSAPI_E2E_SEED (default DEFAULT_SEED); each test derives its own * stream from the run seed and its title path, so results do not depend on scheduling. */ import crypto from 'node:crypto'; export class Rng { private a: number; private b: number; private c: number; private d: number; /** `seed` is any string; equal seeds produce equal streams. */ constructor(readonly seed: string) { const h = crypto.createHash('sha256').update(seed, 'utf8').digest(); this.a = h.readUInt32LE(0); this.b = h.readUInt32LE(4); this.c = h.readUInt32LE(8); this.d = h.readUInt32LE(12); for (let i = 0; i < 12; i++) this.nextUint32(); } /** Independent child stream, e.g. rng.fork('descriptions'). */ fork(label: string): Rng { return new Rng(`${this.seed}/${label}`); } nextUint32(): number { const t = (((this.a + this.b) >>> 0) + this.d) >>> 0; this.d = (this.d + 1) >>> 0; this.a = this.b ^ (this.b >>> 9); this.b = (this.c + (this.c << 3)) >>> 0; this.c = ((this.c << 21) | (this.c >>> 11)) >>> 0; this.c = (this.c + t) >>> 0; return t; } /** Uniform float in [0, 1). */ next(): number { return this.nextUint32() / 4294967296; } /** Uniform integer in [min, max] (inclusive). */ int(min: number, max: number): number { return min + Math.floor(this.next() * (max - min + 1)); } bool(pTrue = 0.5): boolean { return this.next() < pTrue; } pick(items: readonly T[]): T { if (items.length === 0) throw new Error('pick from empty array'); return items[this.int(0, items.length - 1)]; } /** Fisher–Yates shuffle (returns a new array). */ shuffle(items: readonly T[]): T[] { const a = [...items]; for (let i = a.length - 1; i > 0; i--) { const j = this.int(0, i); [a[i], a[j]] = [a[j], a[i]]; } return a; } bytes(n: number): Buffer { const b = Buffer.alloc(n); for (let i = 0; i < n; i++) b[i] = this.nextUint32() & 0xff; return b; } /** Random string of `length` characters drawn from `alphabet` (code points). */ string(length: number, alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): string { const chars = [...alphabet]; let s = ''; for (let i = 0; i < length; i++) s += chars[this.int(0, chars.length - 1)]; return s; } } /** Seed of a test: `:: '>`. */ export function testSeed(runSeed: string, titlePath: readonly string[]): string { return `${runSeed}::${titlePath.join(' > ')}`; }