feat(core): event queue, Ethernet, switch, ARP, IPv4, ICMP, routing and firewall

First slice of the TypeScript port of the filius-ipad runtime engine,
with 29 tests ported from the Swift suite. Parts not ported yet throw
NotPortedError instead of guessing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 19:22:57 +00:00
co-authored by Claude Opus 5.5
parent 7caee33b3d
commit 696334aa5a
29 changed files with 6199 additions and 11 deletions
+58
View File
@@ -0,0 +1,58 @@
// 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 RIP: 30 s · (f/3 + 0.84), truncated — somewhere between 25.2 s and 35.2 s. */
nextRipJitterMilliseconds(): number {
const javaFloat = this.nextFloat();
return Math.trunc(30_000.0 * (javaFloat / 3.0 + 0.84));
}
}