qrloop
Version:
Envelop big blob of data into frames that can be displayed in series of QR Codes
175 lines • 6.62 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.framesToData = exports.areFramesComplete = exports.progressOfFrames = exports.currentNumberOfFrames = exports.totalNumberOfFrames = exports.parseFramesReducer = void 0;
const md5_1 = __importDefault(require("md5"));
const buffer_1 = require("buffer");
const Buffer_1 = require("./Buffer");
const constants_1 = require("./constants");
const initialState = {
frames: [],
fountainsQueue: [],
exploredFountains: [],
};
function resolveFountains(state) {
if (!state)
return state;
const fountainsQueue = state.fountainsQueue.slice(0);
const frames = state.frames.slice(0);
if (fountainsQueue.length === 0 || frames.length === 0)
return state;
const { framesCount } = frames[0];
const framesByIndex = {};
for (let i = 0; i < frames.length; ++i) {
const frame = frames[i];
framesByIndex[frame.index] = frame;
}
let i = 0;
while (i < fountainsQueue.length) {
const fountain = fountainsQueue[i];
const existingFramesData = [];
const missing = [];
for (let j = 0; j < fountain.frameIndexes.length; ++j) {
const index = fountain.frameIndexes[j];
const f = framesByIndex[index];
if (f) {
existingFramesData.push(f.data);
}
else {
missing.push(index);
}
}
if (existingFramesData.length > 0 &&
fountain.data.length !==
Math.min(...existingFramesData.map((f) => f.length))) {
// drop the fountain that no longer match the frames data length
fountainsQueue.splice(i, 1);
}
else if (missing.length === 0) {
// fountain useless, simply eat it and continue on same index
// TODO we could assert the data is equal to xor to do a checksum. not sure to do if does not match
fountainsQueue.splice(i, 1);
}
else if (missing.length === 1) {
// found a frame to recover. rebuild it
const [index] = missing;
const recoveredData = (0, Buffer_1.xor)(existingFramesData.concat([fountain.data]));
const head = buffer_1.Buffer.alloc(5);
head.writeUInt8(0, 0);
head.writeUInt16BE(framesCount, 1);
head.writeUInt16BE(index, 3);
const frame = {
index,
framesCount,
data: recoveredData,
};
frames.push(frame);
framesByIndex[index] = frame;
fountainsQueue.splice(i, 1);
// we start over to see if there is no impacted fountains
i = 0;
}
else {
i++;
}
}
return Object.assign(Object.assign({}, state), { frames,
fountainsQueue });
}
/**
* reduce frames data array to add on more chunk to it.
* As a user of this function, consider the frames to be a black box and use the available functions to extract things.
*/
function parseFramesReducer(_state, chunkStr) {
const state = _state || initialState;
const chunk = buffer_1.Buffer.from(chunkStr, "base64");
const head = chunk.slice(0, 5);
const version = head.readUInt8(0);
if (version === constants_1.FOUNTAIN_V1) {
if (state.exploredFountains.includes(chunkStr))
return state; // no need to address again
const exploredFountains = state.exploredFountains.concat(chunkStr);
const k = chunk.readUInt16BE(1);
const frameIndexes = [];
for (let i = 0; i < k; ++i) {
frameIndexes.push(chunk.readUInt16BE(3 + 2 * i));
}
const data = chunk.slice(3 + 2 * k);
const frames = state.frames;
const fountain = {
frameIndexes,
data,
};
const fountainsQueue = state.fountainsQueue.concat(fountain);
return resolveFountains({
frames,
fountainsQueue,
exploredFountains,
});
}
if (version >= constants_1.MAX_NONCE) {
throw new Error("version " + version + " not supported");
}
const framesCount = head.readUInt16BE(1);
const index = head.readUInt16BE(3);
const data = chunk.slice(5);
if (framesCount <= 0) {
throw new Error("invalid framesCount");
}
if (index < 0 || index >= framesCount) {
throw new Error("invalid index");
}
return resolveFountains(Object.assign(Object.assign({}, state), { frames: state.frames
// override frame by index and also make sure all frames have same framesCount. this allows to not be stucked and recover any scenario.
.filter((c) => c.index !== index && c.framesCount === framesCount)
.concat({ framesCount, index, data }) }));
}
exports.parseFramesReducer = parseFramesReducer;
/**
* retrieve the total number of frames
*/
const totalNumberOfFrames = (s) => s && s.frames.length > 0 ? s.frames[0].framesCount : null;
exports.totalNumberOfFrames = totalNumberOfFrames;
/**
* get the currently captured number of frames
*/
const currentNumberOfFrames = (s) => s ? s.frames.length : 0;
exports.currentNumberOfFrames = currentNumberOfFrames;
/**
* get a progress value from 0 to 1
*/
const progressOfFrames = (s) => {
const total = (0, exports.totalNumberOfFrames)(s);
if (!total)
return 0;
return (0, exports.currentNumberOfFrames)(s) / total;
};
exports.progressOfFrames = progressOfFrames;
/**
* check if the frames have all been retrieved
*/
const areFramesComplete = (s) => (0, exports.totalNumberOfFrames)(s) === (0, exports.currentNumberOfFrames)(s);
exports.areFramesComplete = areFramesComplete;
/**
* return final result of the frames. assuming you have checked `areFramesComplete`
*/
function framesToData(s) {
if (!s) {
throw new Error("invalid date: frames is undefined");
}
const all = buffer_1.Buffer.concat(s.frames
.slice(0)
.sort((a, b) => a.index - b.index)
.map((frame) => frame.data));
const length = all.readUInt32BE(0);
const expectedMD5 = all.slice(4, 20).toString("hex");
const data = all.slice(20).slice(0, length);
if ((0, md5_1.default)(data) !== expectedMD5) {
throw new Error("invalid data: md5 doesn't match");
}
return data;
}
exports.framesToData = framesToData;
//# sourceMappingURL=importer.js.map