// Ported from filius-ipad (GPLv2/GPLv3): // State/TopologyNetworkRuntimeEngine.swift (TopologyJavaCompatibleRandom) // A bit-exact java.util.Random: the 48-bit linear congruential generator // Java FILIUS draws its RIP beacon jitter and ephemeral ports from. BigInt // keeps the 48-bit arithmetic exact; a double would lose the low bits. const MULTIPLIER = 0x5deece66dn; const ADDEND = 0xbn; const MASK = (1n << 48n) - 1n; const INT32_MAX = 2147483647n; export class JavaCompatibleRandom { private state: bigint; constructor(seed: bigint | number) { this.state = (BigInt.asUintN(64, BigInt(seed)) ^ MULTIPLIER) & MASK; } /** The internal 48-bit state, for tests and for copying a generator. */ get seed(): bigint { return this.state; } clone(): JavaCompatibleRandom { const copy = new JavaCompatibleRandom(0); copy.state = this.state; return copy; } next(bits: number): number { if (!(bits > 0 && bits <= 32)) throw new RangeError(`bits out of range: ${bits}`); this.state = (this.state * MULTIPLIER + ADDEND) & MASK; return Number(this.state >> BigInt(48 - bits)); } nextInt(upperBound: number): number { if (!(upperBound > 0)) throw new RangeError(`upperBound must be positive: ${upperBound}`); const bound = BigInt(upperBound); if ((upperBound & -upperBound) === upperBound) { return Number((bound * BigInt(this.next(31))) >> 31n); } for (;;) { const bits = BigInt(this.next(31)); const value = bits % bound; if (bits - value + (bound - 1n) <= INT32_MAX) return Number(value); } } nextFloat(): number { return Math.fround(this.next(24) / (1 << 24)); } /** Java's nextDouble (what Math.random draws): 53 random bits over 2^53. */ nextDouble(): number { return (this.next(26) * 2 ** 27 + this.next(27)) / 2 ** 53; } /** * Java RIP: 30 s · (f/3 + 0.84), truncated — somewhere between 25.2 s and * 35.2 s. f/3 divides a float by an int, in float (JLS 5.6.2): one draw in * about 10,000 comes out a millisecond apart from dividing in double * (measured on OpenJDK 21). */ nextRipJitterMilliseconds(): number { return Math.trunc(30_000 * (Math.fround(this.nextFloat() / 3) + 0.84)); } }