reed-solomon.ts

4.8 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    this.getUint8Memory().set(shards, shardsPointer);8081    const shardSize = shardsLength / (dataShards + parityShards);82    const result = exports.encode(83      shardsPointer,84      shardsLength,85      dataShards,86      parityShards,87    );88    if (result === ReedSolomonErasure.RESULT_OK) {89      shards.set(90        this.getUint8Memory().subarray(91          shardsPointer + shardSize * dataShards,92          shardsPointer + shardsLength,93        ),94        shardSize * dataShards,95      );96    }97    exports.__wbindgen_free(shardsPointer, shardsLength);98    return result;99  }100101  reconstruct(102    shards: Uint8Array,103    dataShards: number,104    parityShards: number,105    shardsAvailable: boolean[],106  ): number {107    const exports = this.exports;108    const shardsLength = shards.length;109    const shardsPointer = exports.__wbindgen_malloc(shardsLength);110    this.getUint8Memory().set(shards, shardsPointer);111112    const shardsAvailableBytes = new Uint8Array(shardsAvailable.length);113    shardsAvailableBytes.set(shardsAvailable.map((value) => (value ? 1 : 0)));114    const shardsAvailablePointer = exports.__wbindgen_malloc(115      shardsAvailableBytes.length,116    );117    this.getUint8Memory().set(shardsAvailableBytes, shardsAvailablePointer);118119    const shardSize = shardsLength / (dataShards + parityShards);120    const result = exports.reconstruct(121      shardsPointer,122      shardsLength,123      dataShards,124      parityShards,125      shardsAvailablePointer,126      shardsAvailableBytes.length,127    );128    if (result === ReedSolomonErasure.RESULT_OK) {129      shards.set(130        this.getUint8Memory().subarray(131          shardsPointer,132          shardsPointer + shardSize * dataShards,133        ),134      );135    }136    exports.__wbindgen_free(shardsPointer, shardsLength);137    exports.__wbindgen_free(138      shardsAvailablePointer,139      shardsAvailableBytes.length,140    );141    return result;142  }143144  private getUint8Memory(): Uint8Array {145    if (146      this.memoryCache === null ||147      this.memoryCache.buffer !== this.exports.memory.buffer148    ) {149      this.memoryCache = new Uint8Array(this.exports.memory.buffer);150    }151    return this.memoryCache;152  }153}