UNPKG

@gandlaf21/bc-ur

Version:

A JS implementation of the Uniform Resources (UR) specification from Blockchain Commons

163 lines (131 loc) 4.5 kB
import { bufferXOR, getCRC, split, toUint32 } from "./utils"; import { chooseFragments } from "./fountainUtils"; import { cborEncode, cborDecode } from './cbor'; import { Buffer } from "buffer"; export class FountainEncoderPart { constructor( private _seqNum: number, private _seqLength: number, private _messageLength: number, private _checksum: number, private _fragment: Buffer, ) { } get messageLength() { return this._messageLength; } get fragment() { return this._fragment; } get seqNum() { return this._seqNum; } get seqLength() { return this._seqLength; } get checksum() { return this._checksum; } public cbor(): Buffer { const result = cborEncode([ this._seqNum, this._seqLength, this._messageLength, this._checksum, this._fragment ]) return Buffer.from(result); } public description(): string { return `seqNum:${this._seqNum}, seqLen:${this._seqLength}, messageLen:${this._messageLength}, checksum:${this._checksum}, data:${this._fragment.toString('hex')}` } public static fromCBOR(cborPayload: string | Buffer) { const [ seqNum, seqLength, messageLength, checksum, fragment, ] = cborDecode(cborPayload); if (typeof seqNum !== 'number'||typeof seqLength!== 'number'||typeof messageLength !== 'number'||typeof checksum !== 'number' || Buffer.isBuffer(fragment) && fragment.length <= 0) { throw new Error("type error"); } return new FountainEncoderPart( seqNum, seqLength, messageLength, checksum, Buffer.from(fragment), ) } } export default class FountainEncoder { private _messageLength: number; private _fragments: Buffer[]; private fragmentLength: number; private seqNum: number; private checksum: number; constructor( message: Buffer, maxFragmentLength: number = 100, firstSeqNum: number = 0, minFragmentLength: number = 10 ) { const fragmentLength = FountainEncoder.findNominalFragmentLength(message.length, minFragmentLength, maxFragmentLength); this._messageLength = message.length; this._fragments = FountainEncoder.partitionMessage(message, fragmentLength); this.fragmentLength = fragmentLength; this.seqNum = toUint32(firstSeqNum); this.checksum = getCRC(message) } public get fragmentsLength() { return this._fragments.length; } public get fragments() { return this._fragments; } public get messageLength() { return this._messageLength; } public isComplete(): boolean { return this.seqNum >= this._fragments.length; } public isSinglePart(): boolean { return this._fragments.length === 1; } public seqLength(): number { return this._fragments.length; } public mix(indexes: number[]) { return indexes.reduce( (result, index) => bufferXOR(this._fragments[index], result), Buffer.alloc(this.fragmentLength, 0) ) } public nextPart(): FountainEncoderPart { this.seqNum = toUint32(this.seqNum + 1); const indexes = chooseFragments(this.seqNum, this._fragments.length, this.checksum); const mixed = this.mix(indexes); return new FountainEncoderPart( this.seqNum, this._fragments.length, this._messageLength, this.checksum, mixed ) } public static findNominalFragmentLength( messageLength: number, minFragmentLength: number, maxFragmentLength: number ): number { if (messageLength <= 0 || minFragmentLength <= 0 || maxFragmentLength < minFragmentLength) { throw new Error("invalid fragment or message length"); } const maxFragmentCount = Math.ceil(messageLength / minFragmentLength); let fragmentLength = 0; for (let fragmentCount = 1; fragmentCount <= maxFragmentCount; fragmentCount++) { fragmentLength = Math.ceil(messageLength / fragmentCount); if (fragmentLength <= maxFragmentLength) { break; } } return fragmentLength; } public static partitionMessage(message: Buffer, fragmentLength: number): Buffer[] { let remaining = Buffer.from(message); let fragment; let _fragments: Buffer[] = []; while (remaining.length > 0) { [fragment, remaining] = split(remaining, -fragmentLength) fragment = Buffer .alloc(fragmentLength, 0) // initialize with 0's to achieve the padding .fill(fragment, 0, fragment.length) _fragments.push(fragment) } return _fragments; } }