/** * Browser-safe wrapper around the Reed-Solomon WASM exports. * * The published JS wrapper for @subspace/reed-solomon-erasure.wasm imports * Node's `fs` module at top level, which breaks extension pages once bundled. * This file talks directly to the WASM exports we use and contains no Node * built-ins, so it is safe for popup chunks. */ interface ReedSolomonWasmExports extends WebAssembly.Exports { memory: WebAssembly.Memory; __wbindgen_malloc(size: number): number; __wbindgen_free(pointer: number, size: number): void; encode( shardsPointer: number, shardsLength: number, dataShards: number, parityShards: number, ): number; reconstruct( shardsPointer: number, shardsLength: number, dataShards: number, parityShards: number, shardsAvailablePointer: number, shardsAvailableLength: number, ): number; } function isReedSolomonExports( exports: WebAssembly.Exports, ): exports is ReedSolomonWasmExports { return ( exports.memory instanceof WebAssembly.Memory && typeof exports.__wbindgen_malloc === "function" && typeof exports.__wbindgen_free === "function" && typeof exports.encode === "function" && typeof exports.reconstruct === "function" ); } export class ReedSolomonErasure { static readonly RESULT_OK = 0; static readonly RESULT_ERROR_TOO_FEW_SHARDS = 1; static readonly RESULT_ERROR_TOO_MANY_SHARDS = 2; static readonly RESULT_ERROR_TOO_FEW_DATA_SHARDS = 3; static readonly RESULT_ERROR_TOO_MANY_DATA_SHARDS = 4; static readonly RESULT_ERROR_TOO_FEW_PARITY_SHARDS = 5; static readonly RESULT_ERROR_TOO_MANY_PARITY_SHARDS = 6; static readonly RESULT_ERROR_TOO_FEW_BUFFER_SHARDS = 7; static readonly RESULT_ERROR_TOO_MANY_BUFFER_SHARDS = 8; static readonly RESULT_ERROR_INCORRECT_SHARD_SIZE = 9; static readonly RESULT_ERROR_TOO_FEW_SHARDS_PRESENT = 10; static readonly RESULT_ERROR_EMPTY_SHARD = 11; static readonly RESULT_ERROR_INVALID_SHARD_FLAGS = 12; static readonly RESULT_ERROR_INVALID_INDEX = 13; private memoryCache: Uint8Array | null = null; private constructor(private readonly exports: ReedSolomonWasmExports) {} static fromBytes(bytes: BufferSource): ReedSolomonErasure { const module = new WebAssembly.Module(bytes); const instance = new WebAssembly.Instance(module); if (!isReedSolomonExports(instance.exports)) { throw new Error("Reed-Solomon WASM exports are missing required functions"); } return new ReedSolomonErasure(instance.exports); } encode( shards: Uint8Array, dataShards: number, parityShards: number, ): number { const exports = this.exports; const shardsLength = shards.length; const shardsPointer = exports.__wbindgen_malloc(shardsLength); // PCH-01-026 #8: free the malloc'd region even if a step below traps. try { this.getUint8Memory().set(shards, shardsPointer); const shardSize = shardsLength / (dataShards + parityShards); const result = exports.encode( shardsPointer, shardsLength, dataShards, parityShards, ); if (result === ReedSolomonErasure.RESULT_OK) { shards.set( this.getUint8Memory().subarray( shardsPointer + shardSize * dataShards, shardsPointer + shardsLength, ), shardSize * dataShards, ); } return result; } finally { exports.__wbindgen_free(shardsPointer, shardsLength); } } reconstruct( shards: Uint8Array, dataShards: number, parityShards: number, shardsAvailable: boolean[], ): number { const exports = this.exports; const shardsLength = shards.length; const shardsPointer = exports.__wbindgen_malloc(shardsLength); const shardsAvailableBytes = new Uint8Array(shardsAvailable.length); shardsAvailableBytes.set(shardsAvailable.map((value) => (value ? 1 : 0))); const shardsAvailablePointer = exports.__wbindgen_malloc( shardsAvailableBytes.length, ); // PCH-01-026 #8: both allocations are freed in finally so a trap between the // malloc and the call below cannot leak either pointer. try { this.getUint8Memory().set(shards, shardsPointer); this.getUint8Memory().set(shardsAvailableBytes, shardsAvailablePointer); const shardSize = shardsLength / (dataShards + parityShards); const result = exports.reconstruct( shardsPointer, shardsLength, dataShards, parityShards, shardsAvailablePointer, shardsAvailableBytes.length, ); if (result === ReedSolomonErasure.RESULT_OK) { shards.set( this.getUint8Memory().subarray( shardsPointer, shardsPointer + shardSize * dataShards, ), ); } return result; } finally { exports.__wbindgen_free(shardsPointer, shardsLength); exports.__wbindgen_free( shardsAvailablePointer, shardsAvailableBytes.length, ); } } private getUint8Memory(): Uint8Array { if ( this.memoryCache === null || this.memoryCache.buffer !== this.exports.memory.buffer ) { this.memoryCache = new Uint8Array(this.exports.memory.buffer); } return this.memoryCache; } }