@gandlaf21/bc-ur
Version:
A JS implementation of the Uniform Resources (UR) specification from Blockchain Commons
253 lines • 9.86 kB
JavaScript
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