12345678910111213141516import { ReedSolomonErasure } from "@digitaldefiance/reed-solomon-erasure.wasm";17import {18 CHUNK_SIZE,19 CodexError,20 PARITY_SHARDS,21} from "../../shared/codex-types";22import { getReedSolomon } from "./rs-loader";232425262728293031323334353637let cachedWasmBytes: Uint8Array | null = null;3839async function freshReedSolomon(): Promise<ReedSolomonErasure> {40 if (cachedWasmBytes) {41 return ReedSolomonErasure.fromBytes(cachedWasmBytes);42 }43 44 45 46 47 await getReedSolomon();48 if (!cachedWasmBytes) {49 cachedWasmBytes = await loadWasmBytes();50 }51 return ReedSolomonErasure.fromBytes(cachedWasmBytes);52}5354555657585960616263async function loadWasmBytes(): Promise<Uint8Array> {64 const inPopup =65 typeof chrome !== "undefined" &&66 typeof chrome.runtime !== "undefined" &&67 typeof chrome.runtime.getURL === "function";68 if (inPopup) {69 const url = chrome.runtime.getURL("codex/reed_solomon_erasure_bg.wasm");70 const response = await fetch(url);71 if (!response.ok) {72 throw new CodexError(73 "ERASURE_CODING_VERIFICATION_FAILED",74 `Failed to fetch WASM: ${response.status}`,75 );76 }77 return new Uint8Array(await response.arrayBuffer());78 }79 const fs = await import("node:fs");80 const path = await import("node:path");81 const wasmPath = path.resolve(82 "node_modules/@digitaldefiance/reed-solomon-erasure.wasm/dist/reed_solomon_erasure_bg.wasm",83 );84 return new Uint8Array(fs.readFileSync(wasmPath));85}8687export interface EncodeResult {88 dataChunks: Uint8Array[];89 parityChunks: Uint8Array[];90 totalChunks: number;91 dataShards: number;92}93949596979899100export async function encodeWithErasure(data: Uint8Array): Promise<EncodeResult> {101 const rs = await freshReedSolomon();102103 const dataShards = Math.max(1, Math.ceil(data.length / CHUNK_SIZE));104 const totalShards = dataShards + PARITY_SHARDS;105 const buffer = new Uint8Array(totalShards * CHUNK_SIZE);106 buffer.set(data, 0); 107108 const code = rs.encode(buffer, dataShards, PARITY_SHARDS);109 if (code !== ReedSolomonErasure.RESULT_OK) {110 throw new CodexError(111 "ERASURE_CODING_VERIFICATION_FAILED",112 `Reed-Solomon encode failed: ${ReedSolomonErasure.getResultMessage(code)}`,113 );114 }115116 117 118 const dataChunks: Uint8Array[] = [];119 for (let i = 0; i < dataShards; i++) {120 dataChunks.push(buffer.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE));121 }122 const parityChunks: Uint8Array[] = [];123 for (let i = 0; i < PARITY_SHARDS; i++) {124 const base = (dataShards + i) * CHUNK_SIZE;125 parityChunks.push(buffer.slice(base, base + CHUNK_SIZE));126 }127128 return { dataChunks, parityChunks, totalChunks: totalShards, dataShards };129}130131132133134135136137export async function decodeWithErasure(138 chunks: Map<number, Uint8Array>,139 dataShards: number,140 totalShards: number,141): Promise<Uint8Array> {142 const parityShards = totalShards - dataShards;143 if (parityShards < 0) {144 throw new CodexError(145 "ERASURE_CODING_VERIFICATION_FAILED",146 `Invalid shard layout: dataShards=${dataShards} totalShards=${totalShards}`,147 );148 }149150 if (chunks.size < dataShards) {151 throw new CodexError(152 "INSUFFICIENT_CHUNKS",153 `Need at least ${dataShards} chunks, have ${chunks.size}`,154 );155 }156157 const rs = await freshReedSolomon();158 const buffer = new Uint8Array(totalShards * CHUNK_SIZE);159 const available: boolean[] = new Array(totalShards).fill(false);160161 for (const [index, chunk] of chunks) {162 if (index < 0 || index >= totalShards) {163 throw new CodexError(164 "ERASURE_CODING_VERIFICATION_FAILED",165 `Chunk index ${index} out of range [0, ${totalShards})`,166 );167 }168 if (chunk.length !== CHUNK_SIZE) {169 throw new CodexError(170 "ERASURE_CODING_VERIFICATION_FAILED",171 `Chunk ${index} has length ${chunk.length}, expected ${CHUNK_SIZE}`,172 );173 }174 buffer.set(chunk, index * CHUNK_SIZE);175 available[index] = true;176 }177178 const code = rs.reconstruct(buffer, dataShards, parityShards, available);179 if (code !== ReedSolomonErasure.RESULT_OK) {180 181 182 183 if (code === ReedSolomonErasure.RESULT_ERROR_TOO_FEW_SHARDS_PRESENT) {184 throw new CodexError(185 "INSUFFICIENT_CHUNKS",186 `Need at least ${dataShards} valid chunks for reconstruction`,187 );188 }189 throw new CodexError(190 "ERASURE_CODING_VERIFICATION_FAILED",191 `Reed-Solomon reconstruct failed: ${ReedSolomonErasure.getResultMessage(code)}`,192 );193 }194195 return buffer.slice(0, dataShards * CHUNK_SIZE);196}