reed-solomon.ts

5.2 KB · raw

1/**2 * Browser-safe wrapper around the Reed-Solomon WASM exports.3 *4 * The published JS wrapper for @subspace/reed-solomon-erasure.wasm imports5 * Node's `fs` module at top level, which breaks extension pages once bundled.6 * This file talks directly to the WASM exports we use and contains no Node7 * built-ins, so it is safe for popup chunks.8 */910interface ReedSolomonWasmExports extends WebAssembly.Exports {11  memory: WebAssembly.Memory;12  __wbindgen_malloc(size: number): number;13  __wbindgen_free(pointer: number, size: number): void;14  encode(15    shardsPointer: number,16    shardsLength: number,17    dataShards: number,18    parityShards: number,19  ): number;20  reconstruct(21    shardsPointer: number,22    shardsLength: number,23    dataShards: number,24    parityShards: number,25    shardsAvailablePointer: number,26    shardsAvailableLength: number,27  ): number;28}2930function isReedSolomonExports(31  exports: WebAssembly.Exports,32): exports is ReedSolomonWasmExports {33  return (34    exports.memory instanceof WebAssembly.Memory &&35    typeof exports.__wbindgen_malloc === "function" &&36    typeof exports.__wbindgen_free === "function" &&37    typeof exports.encode === "function" &&38    typeof exports.reconstruct === "function"39  );40}4142export class ReedSolomonErasure {43  static readonly RESULT_OK = 0;44  static readonly RESULT_ERROR_TOO_FEW_SHARDS = 1;45  static readonly RESULT_ERROR_TOO_MANY_SHARDS = 2;46  static readonly RESULT_ERROR_TOO_FEW_DATA_SHARDS = 3;47  static readonly RESULT_ERROR_TOO_MANY_DATA_SHARDS = 4;48  static readonly RESULT_ERROR_TOO_FEW_PARITY_SHARDS = 5;49  static readonly RESULT_ERROR_TOO_MANY_PARITY_SHARDS = 6;50  static readonly RESULT_ERROR_TOO_FEW_BUFFER_SHARDS = 7;51  static readonly RESULT_ERROR_TOO_MANY_BUFFER_SHARDS = 8;52  static readonly RESULT_ERROR_INCORRECT_SHARD_SIZE = 9;53  static readonly RESULT_ERROR_TOO_FEW_SHARDS_PRESENT = 10;54  static readonly RESULT_ERROR_EMPTY_SHARD = 11;55  static readonly RESULT_ERROR_INVALID_SHARD_FLAGS = 12;56  static readonly RESULT_ERROR_INVALID_INDEX = 13;5758  private memoryCache: Uint8Array | null = null;5960  private constructor(private readonly exports: ReedSolomonWasmExports) {}6162  static fromBytes(bytes: BufferSource): ReedSolomonErasure {63    const module = new WebAssembly.Module(bytes);64    const instance = new WebAssembly.Instance(module);65    if (!isReedSolomonExports(instance.exports)) {66      throw new Error("Reed-Solomon WASM exports are missing required functions");67    }68    return new ReedSolomonErasure(instance.exports);69  }7071  encode(72    shards: Uint8Array,73    dataShards: number,74    parityShards: number,75  ): number {76    const exports = this.exports;77    const shardsLength = shards.length;78    const shardsPointer = exports.__wbindgen_malloc(shardsLength);79    // PCH-01-026 #8: free the malloc'd region even if a step below traps.80    try {81      this.getUint8Memory().set(shards, shardsPointer);8283      const shardSize = shardsLength / (dataShards + parityShards);84      const result = exports.encode(85        shardsPointer,86        shardsLength,87        dataShards,88        parityShards,89      );90      if (result === ReedSolomonErasure.RESULT_OK) {91        shards.set(92          this.getUint8Memory().subarray(93            shardsPointer + shardSize * dataShards,94            shardsPointer + shardsLength,95          ),96          shardSize * dataShards,97        );98      }99      return result;100    } finally {101      exports.__wbindgen_free(shardsPointer, shardsLength);102    }103  }104105  reconstruct(106    shards: Uint8Array,107    dataShards: number,108    parityShards: number,109    shardsAvailable: boolean[],110  ): number {111    const exports = this.exports;112    const shardsLength = shards.length;113    const shardsPointer = exports.__wbindgen_malloc(shardsLength);114115    const shardsAvailableBytes = new Uint8Array(shardsAvailable.length);116    shardsAvailableBytes.set(shardsAvailable.map((value) => (value ? 1 : 0)));117    const shardsAvailablePointer = exports.__wbindgen_malloc(118      shardsAvailableBytes.length,119    );120    // PCH-01-026 #8: both allocations are freed in finally so a trap between the121    // malloc and the call below cannot leak either pointer.122    try {123      this.getUint8Memory().set(shards, shardsPointer);124      this.getUint8Memory().set(shardsAvailableBytes, shardsAvailablePointer);125126      const shardSize = shardsLength / (dataShards + parityShards);127      const result = exports.reconstruct(128        shardsPointer,129        shardsLength,130        dataShards,131        parityShards,132        shardsAvailablePointer,133        shardsAvailableBytes.length,134      );135      if (result === ReedSolomonErasure.RESULT_OK) {136        shards.set(137          this.getUint8Memory().subarray(138            shardsPointer,139            shardsPointer + shardSize * dataShards,140          ),141        );142      }143      return result;144    } finally {145      exports.__wbindgen_free(shardsPointer, shardsLength);146      exports.__wbindgen_free(147        shardsAvailablePointer,148        shardsAvailableBytes.length,149      );150    }151  }152153  private getUint8Memory(): Uint8Array {154    if (155      this.memoryCache === null ||156      this.memoryCache.buffer !== this.exports.memory.buffer157    ) {158      this.memoryCache = new Uint8Array(this.exports.memory.buffer);159    }160    return this.memoryCache;161  }162}