import {
instantiate,
putString,
putBytes,
takePacket,
validateDecimalInput,
} from "./abi.js";
let ex = null;
let session = 0;
let generation = 0;
let baseTarget = 0;
let extractionTarget = 0;
let lastRelations = 0;
self.onmessage = async ({ data }) => {
const messageGeneration = Number.isSafeInteger(data?.gen) ? data.gen : 0;
try {
if (!data || typeof data !== "object") throw new Error("invalid coordinator command");
if (data.cmd === "init") {
if (ex) throw new Error("coordinator is already initialized");
ex = await instantiate(data.module);
const abi = ex.qs_abi_version();
if (abi !== 2) throw new Error(`unsupported rusqsieve wasm ABI ${abi}`);
self.postMessage({ type: "ready", abi });
return;
}
if (data.cmd === "new") {
if (!ex) throw new Error("coordinator is not initialized");
if (!Number.isSafeInteger(data.gen) || data.gen <= 0 || typeof data.n !== "string") {
throw new Error("invalid session command");
}
if (session) ex.qs_coord_free(session);
generation = data.gen;
session = 0;
baseTarget = 0;
extractionTarget = 0;
lastRelations = 0;
const decimal = validateDecimalInput(data.n);
const input = putString(ex, decimal);
try {
session = ex.qs_coord_new(input.ptr, input.len);
} finally {
ex.qs_dealloc(input.ptr, input.len, 1);
}
if (!session) throw new Error("could not build a sieve for this number");
baseTarget = ex.qs_coord_target(session);
if (!baseTarget) throw new Error("coordinator returned an invalid relation target");
extractionTarget = baseTarget;
self.postMessage({
type: "session",
gen: generation,
target: extractionTarget,
});
return;
}
if (data.cmd === "submit") {
if (data.gen !== generation) return;
if (!session) throw new Error("coordinator has no active session");
if (
!(data.payload instanceof Uint8Array) ||
!Number.isInteger(data.worker) ||
data.worker < 0
) {
throw new Error("invalid relation submission");
}
validateRelationBatch(data.payload);
const bytes = putBytes(ex, data.payload);
let relations;
try {
relations = ex.qs_coord_submit(session, bytes.ptr, bytes.len);
} finally {
ex.qs_dealloc(bytes.ptr, bytes.len, 1);
}
if (relations < lastRelations) throw new Error("coordinator relation count regressed");
lastRelations = relations;
if (relations < extractionTarget) {
self.postMessage({
type: "submitted",
gen: generation,
relations,
target: extractionTarget,
worker: data.worker,
});
return;
}
self.postMessage({
type: "linalg",
gen: generation,
relations,
target: extractionTarget,
});
const handle = ex.qs_coord_extract(session);
const factor = takePacket(ex, handle, 11);
if (factor) {
if (factor.byteLength !== 128) throw new Error("coordinator returned a malformed factor");
ex.qs_coord_free(session);
session = 0;
self.postMessage({ type: "factor", gen: generation, factor }, [factor.buffer]);
return;
}
const additional = Math.max(64, Math.ceil(baseTarget / 20));
extractionTarget = relations + additional;
self.postMessage({
type: "submitted",
gen: generation,
relations,
target: extractionTarget,
worker: data.worker,
retry: true,
});
return;
}
throw new Error(`unknown coordinator command: ${String(data.cmd)}`);
} catch (error) {
self.postMessage({
type: "error",
gen: messageGeneration,
phase: data?.cmd === "init" ? "boot" : "run",
error: String(error?.message || error),
});
}
};
function validateRelationBatch(payload) {
if (payload.byteLength < 4) throw new Error("truncated relation batch");
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
const count = view.getUint32(0, true);
if (count > 4096) throw new Error("relation batch count exceeds the worker limit");
let offset = 4;
for (let index = 0; index < count; index++) {
if (offset > payload.byteLength - 4) throw new Error("truncated relation length");
const length = view.getUint32(offset, true);
offset += 4;
if (length < 20 || length > payload.byteLength - offset) {
throw new Error("invalid serialized family length");
}
offset += length;
}
if (offset !== payload.byteLength) throw new Error("trailing bytes in relation batch");
}