UNPKG

@gandlaf21/bc-ur

Version:

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

184 lines (138 loc) 4.46 kB
import FountainDecoder from './fountainDecoder'; import bytewords from './bytewords'; import { isURType, toUint32 } from './utils'; import { InvalidSchemeError, InvalidPathLengthError, InvalidTypeError, InvalidSequenceComponentError } from './errors'; import UR from './ur'; import { FountainEncoderPart } from './fountainEncoder'; import { Buffer } from "buffer"; export default class URDecoder { private expected_type: string; private result: UR | undefined; private error: Error | undefined; constructor( private fountainDecoder: FountainDecoder = new FountainDecoder(), public type: string = 'bytes' ) { if (!isURType(type)) { throw new Error("Invalid UR type"); } this.expected_type = ''; } private static decodeBody(type: string, message: string): UR { const cbor = bytewords.decode(message, bytewords.STYLES.MINIMAL); return new UR(Buffer.from(cbor, 'hex'), type); } private validatePart(type: string): boolean { if (this.expected_type) { return this.expected_type === type; } if (!isURType(type)) { return false; } this.expected_type = type; return true; } public static decode(message: string): UR { const [type, components] = this.parse(message); if (components.length === 0) { throw new InvalidPathLengthError(); } const body = components[0]; return URDecoder.decodeBody(type, body); } public static parse(message: string): [string, string[]] { const lowercase = message.toLowerCase(); const prefix = lowercase.slice(0, 3); if (prefix !== 'ur:') { throw new InvalidSchemeError(); } const components = lowercase.slice(3).split('/') const type = components[0]; if (components.length < 2) { throw new InvalidPathLengthError(); } if (!isURType(type)) { throw new InvalidTypeError(); } return [type, components.slice(1)] } public static parseSequenceComponent(s: string) { const components = s.split('-'); if (components.length !== 2) { throw new InvalidSequenceComponentError(); } const seqNum = toUint32(Number(components[0])); const seqLength = Number(components[1]); if (seqNum < 1 || seqLength < 1) { throw new InvalidSequenceComponentError(); } return [seqNum, seqLength]; } public receivePart(s: string): boolean { if (this.result !== undefined) { return false; } const [type, components] = URDecoder.parse(s) if (!this.validatePart(type)) { return false; } // If this is a single-part UR then we're done if (components.length === 1) { this.result = URDecoder.decodeBody(type, components[0]) return true; } if (components.length !== 2) { throw new InvalidPathLengthError(); } const [seq, fragment] = components; const [seqNum, seqLength] = URDecoder.parseSequenceComponent(seq); const cbor = bytewords.decode(fragment, bytewords.STYLES.MINIMAL); const part = FountainEncoderPart.fromCBOR(cbor); if (seqNum !== part.seqNum || seqLength !== part.seqLength) { return false; } if (!this.fountainDecoder.receivePart(part)) { return false; } if (this.fountainDecoder.isSuccess()) { this.result = new UR(this.fountainDecoder.resultMessage(), type); } else if (this.fountainDecoder.isFailure()) { this.error = new InvalidSchemeError(); } return true; } public resultUR(): UR { return this.result ? this.result : new UR(Buffer.from([])); } public isComplete(): boolean { return this.result && this.result.cbor.length > 0?true:false; } public isSuccess(): boolean { return !this.error && this.isComplete(); } public isError(): boolean { return this.error !== undefined; } public resultError() { return this.error ? this.error.message : ''; } public expectedPartCount() { return this.fountainDecoder.expectedPartCount(); } public expectedPartIndexes() { return this.fountainDecoder.getExpectedPartIndexes(); } public receivedPartIndexes() { return this.fountainDecoder.getReceivedPartIndexes(); } public lastPartIndexes() { return this.fountainDecoder.getLastPartIndexes(); } public estimatedPercentComplete() { return this.fountainDecoder.estimatedPercentComplete(); } public getProgress() { return this.fountainDecoder.getProgress(); } }