UNPKG

@gandlaf21/bc-ur

Version:

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

253 lines 9.86 kB
var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; import { arrayContains, arraysEqual, bufferXOR, getCRC, setDifference } from "./utils"; import { chooseFragments } from "./fountainUtils"; import { InvalidChecksumError } from "./errors"; import { Buffer } from "buffer"; var FountainDecoderPart = /** @class */ (function () { function FountainDecoderPart(_indexes, _fragment) { this._indexes = _indexes; this._fragment = _fragment; } Object.defineProperty(FountainDecoderPart.prototype, "indexes", { get: function () { return this._indexes; }, enumerable: false, configurable: true }); Object.defineProperty(FountainDecoderPart.prototype, "fragment", { get: function () { return this._fragment; }, enumerable: false, configurable: true }); FountainDecoderPart.fromEncoderPart = function (encoderPart) { var indexes = chooseFragments(encoderPart.seqNum, encoderPart.seqLength, encoderPart.checksum); var fragment = encoderPart.fragment; return new FountainDecoderPart(indexes, fragment); }; FountainDecoderPart.prototype.isSimple = function () { return this.indexes.length === 1; }; return FountainDecoderPart; }()); export { FountainDecoderPart }; var FountainDecoder = /** @class */ (function () { function FountainDecoder() { this.result = undefined; this.expectedMessageLength = 0; this.expectedChecksum = 0; this.expectedFragmentLength = 0; this.processedPartsCount = 0; this.expectedPartIndexes = []; this.lastPartIndexes = []; this.queuedParts = []; this.receivedPartIndexes = []; this.mixedParts = []; this.simpleParts = []; } FountainDecoder.prototype.validatePart = function (part) { var _this = this; // If this is the first part we've seen if (this.expectedPartIndexes.length === 0) { // Record the things that all the other parts we see will have to match to be valid. __spreadArrays(new Array(part.seqLength)).forEach(function (_, index) { return _this.expectedPartIndexes.push(index); }); this.expectedMessageLength = part.messageLength; this.expectedChecksum = part.checksum; this.expectedFragmentLength = part.fragment.length; } else { // If this part's values don't match the first part's values, throw away the part if (this.expectedPartIndexes.length !== part.seqLength) { return false; } if (this.expectedMessageLength !== part.messageLength) { return false; } if (this.expectedChecksum !== part.checksum) { return false; } if (this.expectedFragmentLength !== part.fragment.length) { return false; } } // This part should be processed return true; }; FountainDecoder.prototype.reducePartByPart = function (a, b) { // If the fragments mixed into `b` are a strict (proper) subset of those in `a`... if (arrayContains(a.indexes, b.indexes)) { var newIndexes = setDifference(a.indexes, b.indexes); var newFragment = bufferXOR(a.fragment, b.fragment); return new FountainDecoderPart(newIndexes, newFragment); } else { // `a` is not reducable by `b`, so return a return a; } }; FountainDecoder.prototype.reduceMixedBy = function (part) { var _this = this; var newMixed = []; this.mixedParts .map(function (_a) { var mixedPart = _a.value; return _this.reducePartByPart(mixedPart, part); }) .forEach(function (reducedPart) { if (reducedPart.isSimple()) { _this.queuedParts.push(reducedPart); } else { newMixed.push({ key: reducedPart.indexes, value: reducedPart }); } }); this.mixedParts = newMixed; }; FountainDecoder.prototype.processSimplePart = function (part) { // Don't process duplicate parts var fragmentIndex = part.indexes[0]; if (this.receivedPartIndexes.includes(fragmentIndex)) { return; } this.simpleParts.push({ key: part.indexes, value: part }); this.receivedPartIndexes.push(fragmentIndex); // If we've received all the parts if (arraysEqual(this.receivedPartIndexes, this.expectedPartIndexes)) { // Reassemble the message from its fragments var sortedParts = this.simpleParts .map(function (_a) { var value = _a.value; return value; }) .sort(function (a, b) { return (a.indexes[0] - b.indexes[0]); }); var message = FountainDecoder.joinFragments(sortedParts.map(function (part) { return part.fragment; }), this.expectedMessageLength); var checksum = getCRC(message); if (checksum === this.expectedChecksum) { this.result = message; } else { this.error = new InvalidChecksumError(); } } else { this.reduceMixedBy(part); } }; FountainDecoder.prototype.processMixedPart = function (part) { var _this = this; // Don't process duplicate parts if (this.mixedParts.some(function (_a) { var indexes = _a.key; return arraysEqual(indexes, part.indexes); })) { return; } // Reduce this part by all the others var p2 = this.simpleParts.reduce(function (acc, _a) { var p = _a.value; return _this.reducePartByPart(acc, p); }, part); p2 = this.mixedParts.reduce(function (acc, _a) { var p = _a.value; return _this.reducePartByPart(acc, p); }, p2); // If the part is now simple if (p2.isSimple()) { // Add it to the queue this.queuedParts.push(p2); } else { this.reduceMixedBy(p2); this.mixedParts.push({ key: p2.indexes, value: p2 }); } }; FountainDecoder.prototype.processQueuedItem = function () { if (this.queuedParts.length === 0) { return; } var part = this.queuedParts.shift(); if (part.isSimple()) { this.processSimplePart(part); } else { this.processMixedPart(part); } }; FountainDecoder.prototype.receivePart = function (encoderPart) { if (this.isComplete()) { return false; } if (!this.validatePart(encoderPart)) { return false; } var decoderPart = FountainDecoderPart.fromEncoderPart(encoderPart); this.lastPartIndexes = decoderPart.indexes; this.queuedParts.push(decoderPart); while (!this.isComplete() && this.queuedParts.length > 0) { this.processQueuedItem(); } ; this.processedPartsCount += 1; return true; }; FountainDecoder.prototype.isComplete = function () { return Boolean(this.result !== undefined && this.result.length > 0); }; FountainDecoder.prototype.isSuccess = function () { return Boolean(this.error === undefined && this.isComplete()); }; FountainDecoder.prototype.resultMessage = function () { return this.isSuccess() ? this.result : Buffer.from([]); }; FountainDecoder.prototype.isFailure = function () { return this.error !== undefined; }; FountainDecoder.prototype.resultError = function () { return this.error ? this.error.message : ''; }; FountainDecoder.prototype.expectedPartCount = function () { return this.expectedPartIndexes.length; }; FountainDecoder.prototype.getExpectedPartIndexes = function () { return __spreadArrays(this.expectedPartIndexes); }; FountainDecoder.prototype.getReceivedPartIndexes = function () { return __spreadArrays(this.receivedPartIndexes); }; FountainDecoder.prototype.getLastPartIndexes = function () { return __spreadArrays(this.lastPartIndexes); }; FountainDecoder.prototype.estimatedPercentComplete = function () { if (this.isComplete()) { return 1; } var expectedPartCount = this.expectedPartCount(); if (expectedPartCount === 0) { return 0; } // We multiply the expectedPartCount by `1.75` as a way to compensate for the facet // that `this.processedPartsCount` also tracks the duplicate parts that have been // processeed. return Math.min(0.99, this.processedPartsCount / (expectedPartCount * 1.75)); }; FountainDecoder.prototype.getProgress = function () { if (this.isComplete()) { return 1; } var expectedPartCount = this.expectedPartCount(); if (expectedPartCount === 0) { return 0; } return this.receivedPartIndexes.length / expectedPartCount; }; FountainDecoder.joinFragments = function (fragments, messageLength) { return Buffer.concat(fragments).slice(0, messageLength); }; return FountainDecoder; }()); export default FountainDecoder; //# sourceMappingURL=fountainDecoder.js.map