diff --git a/packages/node/src/riblt/decoder.ts b/packages/node/src/riblt/decoder.ts index bc78ac502..fd2d73672 100644 --- a/packages/node/src/riblt/decoder.ts +++ b/packages/node/src/riblt/decoder.ts @@ -1,5 +1,10 @@ -import type { SourceSymbol, CodedSymbol, SymbolFactory, HashedSymbol } from "./symbol.js"; import { CodingPrefix } from "./encoder.js"; +import type { + CodedSymbol, + HashedSymbol, + SourceSymbol, + SymbolFactory, +} from "./symbol.js"; export class Decoder extends CodingPrefix { @@ -32,7 +37,11 @@ export class Decoder extends CodingPrefix { } // called at most once for each index - addCodedSymbol(index: number, localSymbol: CodedSymbol, remoteSymbol: CodedSymbol): void { + addCodedSymbol( + index: number, + localSymbol: CodedSymbol, + remoteSymbol: CodedSymbol + ): void { this.extendPrefix(index + 1); this.codedSymbols[index].apply(localSymbol, localSymbol.count); this.codedSymbols[index].apply(remoteSymbol, -remoteSymbol.count); @@ -44,7 +53,10 @@ export class Decoder extends CodingPrefix { if (!this.isDecoded[index]) { // console.log(`+ map ${hashedSymbol}, dir=${direction} to index ${index}`) this.codedSymbols[index].apply(hashedSymbol, direction); - this.modifiedCodedSymbols.push(index); + if (!this.visited[index]) { + this.visited[index] = true; + this.modifiedCodedSymbols.push(index); + } } } @@ -52,7 +64,7 @@ export class Decoder extends CodingPrefix { while (this.modifiedCodedSymbols.length > 0) { const candidates: number[] = []; for (const index of this.modifiedCodedSymbols) { - if (this.isDecoded[index] || this.visited[index]) { + if (this.isDecoded[index]) { continue; } this.visited[index] = true; diff --git a/packages/node/src/riblt/encoder.ts b/packages/node/src/riblt/encoder.ts index 30867e29a..8b2dddd83 100644 --- a/packages/node/src/riblt/encoder.ts +++ b/packages/node/src/riblt/encoder.ts @@ -1,5 +1,10 @@ -import { type SourceSymbol, type SymbolFactory, type CodedSymbol, HashedSymbol } from "./symbol.js" import { RandomMapping } from "./mapping.js"; +import { + type SourceSymbol, + HashedSymbol, + type SymbolFactory, + type CodedSymbol +} from "./symbol.js" class SymbolMapping { @@ -92,13 +97,17 @@ export class CodingPrefix { } addSymbol(symbol: T, direction = 1): void { - const hashedSymbol = new HashedSymbol(this.symbolFactory.cloneSource(symbol)); + const hashedSymbol = new HashedSymbol( + this.symbolFactory.cloneSource(symbol) + ); const mapping = new RandomMapping(hashedSymbol.checksum, 0); this.sourceSymbols.push(hashedSymbol); this.sourceSymbolDirections.push(direction); this.mapGenerators.push(mapping); - this.queue.push(new SymbolMapping(this.sourceSymbols.length - 1, mapping.lastIdx)); + this.queue.push( + new SymbolMapping(this.sourceSymbols.length - 1, mapping.lastIdx) + ); } maps(index: number, hashedSymbol: HashedSymbol, direction: number): void { @@ -117,7 +126,11 @@ export class CodingPrefix { while (mapping.codedIdx < size) { const sourceIdx = mapping.sourceIdx; const codedIdx = mapping.codedIdx; - this.maps(codedIdx, this.sourceSymbols[sourceIdx], this.sourceSymbolDirections[sourceIdx]); + this.maps( + codedIdx, + this.sourceSymbols[sourceIdx], + this.sourceSymbolDirections[sourceIdx] + ); mapping.codedIdx = this.mapGenerators[sourceIdx].nextIndex(); } this.queue.push(mapping); diff --git a/packages/node/src/riblt/mapping.ts b/packages/node/src/riblt/mapping.ts index ae2b6b769..4692b6b73 100644 --- a/packages/node/src/riblt/mapping.ts +++ b/packages/node/src/riblt/mapping.ts @@ -1,29 +1,36 @@ import * as crypto from "node:crypto"; +function rotl(x: bigint, k: bigint) { + return BigInt.asUintN(64, ((x << k) | (x >> (64n - k)))); +} export class RandomMapping { - private state: Uint8Array; + private s: BigUint64Array; lastIdx: number; constructor(seed: Uint8Array, lastIdx = 0) { - this.state = crypto.createHash("sha1").update(seed).digest(); + this.s = new BigUint64Array(crypto.createHash("sha256").update(seed).digest().buffer); this.lastIdx = lastIdx; } nextIndex(): number { - let prng = 0n; - prng |= BigInt(this.state[0]) << 0n; - prng |= BigInt(this.state[1]) << 8n; - prng |= BigInt(this.state[2]) << 16n; - prng |= BigInt(this.state[3]) << 24n; - prng |= BigInt(this.state[4]) << 32n; - prng |= BigInt(this.state[5]) << 40n; - prng |= BigInt(this.state[6]) << 48n; - prng |= BigInt(this.state[7]) << 56n; + // https://prng.di.unimi.it/xoshiro256starstar.c + const result = BigInt.asUintN(64, rotl(BigInt.asUintN(64, this.s[1] * 5n), 7n) * 9n); + + const t = BigInt.asUintN(64, this.s[1] << 17n); + + this.s[2] ^= this.s[0]; + this.s[3] ^= this.s[1]; + this.s[1] ^= this.s[2]; + this.s[0] ^= this.s[3]; + + this.s[2] ^= t; + + this.s[3] = rotl(this.s[3], 45n); + this.lastIdx += Math.ceil( - (this.lastIdx + 1.5) * (2 ** 32 / Math.sqrt(Number(prng) + 1) - 1), + (this.lastIdx + 1.5) * (2 ** 32 / Math.sqrt(Number(result) + 1) - 1), ); - this.state = crypto.createHash("sha1").update(this.state).digest(); return this.lastIdx; } } diff --git a/packages/node/src/riblt/symbol.ts b/packages/node/src/riblt/symbol.ts index 25974849c..bd03d38d1 100644 --- a/packages/node/src/riblt/symbol.ts +++ b/packages/node/src/riblt/symbol.ts @@ -39,7 +39,7 @@ export class HashedSymbol { } toString(): string { - return `HashedSymbol(sum=${this.sum}, hash=[${this.checksum}])` + return `HashedSymbol(sum=${this.sum}, hash=[${this.checksum}])`; } } diff --git a/packages/node/tests/riblt.test.ts b/packages/node/tests/riblt.test.ts index ad50e45ea..7368f7a35 100644 --- a/packages/node/tests/riblt.test.ts +++ b/packages/node/tests/riblt.test.ts @@ -1,104 +1,111 @@ +import * as crypto from 'node:crypto'; import { beforeEach, describe, expect, test } from "vitest"; -import { Encoder } from "../src/riblt/encoder.js"; import { Decoder } from "../src/riblt/decoder.js"; -import { SymbolFactory, type SourceSymbol } from "../src/riblt/symbol.js"; -import * as crypto from 'node:crypto'; +import { Encoder } from "../src/riblt/encoder.js"; +import { type SourceSymbol, SymbolFactory } from "../src/riblt/symbol.js"; class VertexSymbol implements SourceSymbol { - data: number; - - constructor(data: number) { - this.data = data; - } - - xor(s: VertexSymbol): void { - this.data ^= s.data; - } - - hash(): Uint8Array { - return new Uint8Array(crypto.createHash('sha1').update(new Uint32Array([this.data])).digest()); - } - - equals(s: VertexSymbol): boolean { - return this.data === s.data; - } - - toString(): string { - return `${this.data}`; - } + data: number; + + constructor(data: number) { + this.data = data; + } + + xor(s: VertexSymbol): void { + this.data ^= s.data; + } + + hash(): Uint8Array { + return new Uint8Array( + crypto + .createHash('sha1') + .update(new Uint32Array([this.data])) + .digest(), + ); + } + + equals(s: VertexSymbol): boolean { + return this.data === s.data; + } + + toString(): string { + return `${this.data}`; + } } class VertexSymbolFactory extends SymbolFactory { - emptySource(): VertexSymbol { - return new VertexSymbol(0); - } + emptySource(): VertexSymbol { + return new VertexSymbol(0); + } - emptyHash(): Uint8Array { - return new Uint8Array(20); - } + emptyHash(): Uint8Array { + return new Uint8Array(20); + } - cloneSource(s: VertexSymbol): VertexSymbol { - return new VertexSymbol(s.data); - } + cloneSource(s: VertexSymbol): VertexSymbol { + return new VertexSymbol(s.data); + } - newTestSymbol(i): VertexSymbol { - return new VertexSymbol(i); - } + newTestSymbol(i): VertexSymbol { + return new VertexSymbol(i); + } } describe("RIBLT test", async () => { - const factory = new VertexSymbolFactory(); - - - test.each([10, 20, 40, 100, 1000, 10000, 50000, 100000])("d=%i", async (d) => { - const nlocal = d >> 1; - const nremote = d >> 1; - const ncommon = d; - - let symbolIndex = 0; - - const localEncoder = new Encoder(factory); - const remoteEncoder = new Encoder(factory); - const localDecoder = new Decoder(factory); - - const localSymbols: VertexSymbol[] = []; - const remoteSymbols: VertexSymbol[] = []; - - for (let i = 0; i < nlocal; i++) { - const localSymbol = factory.newTestSymbol(symbolIndex++); - localSymbols.push(localSymbol); - localEncoder.addSymbol(localSymbol); - } - for (let i = 0; i < nremote; i++) { - const remoteSymbol = factory.newTestSymbol(symbolIndex++); - remoteSymbols.push(remoteSymbol); - remoteEncoder.addSymbol(remoteSymbol); - } - for (let i = 0; i < ncommon; i++) { - const localSymbol = factory.newTestSymbol(symbolIndex++); - const remoteSymbol = factory.cloneSource(localSymbol); - localEncoder.addSymbol(localSymbol); - remoteEncoder.addSymbol(remoteSymbol); - } - - let sequenceSize = 0; - do { - sequenceSize++; - localEncoder.producePrefix(sequenceSize); - remoteEncoder.producePrefix(sequenceSize); - // console.log(`localEncoder[${sequenceSize - 1}]: ${localEncoder.codedSymbols[sequenceSize - 1]}`); - // console.log(`remoteEncoder[${sequenceSize - 1}]: ${remoteEncoder.codedSymbols[sequenceSize - 1]}`); - localDecoder.addCodedSymbol(sequenceSize - 1, localEncoder.codedSymbols[sequenceSize - 1], remoteEncoder.codedSymbols[sequenceSize - 1]); - // console.log(`localDecoder[${sequenceSize - 1}]: ${localDecoder.codedSymbols[sequenceSize - 1]}`); - } while (!localDecoder.tryDecode()); - - // console.log(localDecoder.decodedLocalSymbols); - // console.log(localDecoder.decodedRemoteSymbols); - // console.log(localDecoder.remaining); - - console.log(sequenceSize); - }); + const factory = new VertexSymbolFactory(); + + + test.each([10, 20, 40, 100, 1000, 10000, 50000, 100000, 300000])( + "d=%i", + async (d) => { + const nlocal = d >> 1; + const nremote = d >> 1; + const ncommon = d; + + let symbolIndex = 0; + + const localEncoder = new Encoder(factory); + const remoteEncoder = new Encoder(factory); + const localDecoder = new Decoder(factory); + + const localSymbols: VertexSymbol[] = []; + const remoteSymbols: VertexSymbol[] = []; + + for (let i = 0; i < nlocal; i++) { + const localSymbol = factory.newTestSymbol(symbolIndex++); + localSymbols.push(localSymbol); + localEncoder.addSymbol(localSymbol); + } + for (let i = 0; i < nremote; i++) { + const remoteSymbol = factory.newTestSymbol(symbolIndex++); + remoteSymbols.push(remoteSymbol); + remoteEncoder.addSymbol(remoteSymbol); + } + for (let i = 0; i < ncommon; i++) { + const localSymbol = factory.newTestSymbol(symbolIndex++); + const remoteSymbol = factory.cloneSource(localSymbol); + localEncoder.addSymbol(localSymbol); + remoteEncoder.addSymbol(remoteSymbol); + } + + let sequenceSize = 0; + do { + sequenceSize++; + localEncoder.producePrefix(sequenceSize); + remoteEncoder.producePrefix(sequenceSize); + // console.log(`localEncoder[${sequenceSize - 1}]: ${localEncoder.codedSymbols[sequenceSize - 1]}`); + // console.log(`remoteEncoder[${sequenceSize - 1}]: ${remoteEncoder.codedSymbols[sequenceSize - 1]}`); + localDecoder.addCodedSymbol(sequenceSize - 1, localEncoder.codedSymbols[sequenceSize - 1], remoteEncoder.codedSymbols[sequenceSize - 1]); + // console.log(`localDecoder[${sequenceSize - 1}]: ${localDecoder.codedSymbols[sequenceSize - 1]}`); + } while (!localDecoder.tryDecode()); + + // console.log(localDecoder.decodedLocalSymbols); + // console.log(localDecoder.decodedRemoteSymbols); + // console.log(localDecoder.remaining); + + console.log(`${sequenceSize/d} symbols/diff`); + }); });