qrloop
Version:
Envelop big blob of data into frames that can be displayed in series of QR Codes
116 lines • 4.43 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.dataToFrames = exports.wrapData = exports.makeDataFrame = exports.makeFountainFrame = void 0;
const md5_1 = __importDefault(require("md5"));
const buffer_1 = require("buffer");
const Buffer_1 = require("./Buffer");
const constants_1 = require("./constants");
function makeFountainFrame(dataChunks, selectedFrameIndexes) {
const k = selectedFrameIndexes.length;
const head = buffer_1.Buffer.alloc(3 + 2 * k);
head.writeUInt8(constants_1.FOUNTAIN_V1, 0);
head.writeUInt16BE(k, 1);
const selectedFramesData = [];
for (let j = 0; j < k; j++) {
const frameIndex = selectedFrameIndexes[j];
selectedFramesData.push(dataChunks[frameIndex]);
head.writeUInt16BE(frameIndex, 3 + 2 * j);
}
const data = (0, Buffer_1.xor)(selectedFramesData);
return buffer_1.Buffer.concat([head, data]).toString("base64");
}
exports.makeFountainFrame = makeFountainFrame;
function makeDataFrame({ data, nonce, totalFrames, frameIndex, }) {
const head = buffer_1.Buffer.alloc(5);
head.writeUInt8(nonce, 0);
head.writeUInt16BE(totalFrames, 1);
head.writeUInt16BE(frameIndex, 3);
return buffer_1.Buffer.concat([head, data]).toString("base64");
}
exports.makeDataFrame = makeDataFrame;
function wrapData(data) {
const lengthBuffer = buffer_1.Buffer.alloc(4);
lengthBuffer.writeUInt32BE(data.length, 0);
const md5Buffer = buffer_1.Buffer.from((0, md5_1.default)(data), "hex");
return buffer_1.Buffer.concat([lengthBuffer, md5Buffer, data]);
}
exports.wrapData = wrapData;
/**
* in one loop:
* the data is prepend in the frames with this head:
* 4 bytes: uint, data length
* 16 bytes: md5 of data
*
* each frame is a base64 of:
* 1 byte: nonce
* 2 bytes: uint, total number of frames
* 2 bytes: uint, index of frame
* variable data
*
* each "fountain" frame is base64 of:
* 1 byte: fountain version
* 2 bytes: number of K frames associated
* K times 2 bytes: the index of each frame
* variable data: the XOR of the frames data
*
* It inspires idea from https://en.wikipedia.org/wiki/Luby_transform_code
*/
function makeLoop(wrappedData, dataSize, index, random) {
const nonce = index % constants_1.MAX_NONCE;
const dataChunks = (0, Buffer_1.cutAndPad)(wrappedData, dataSize);
const fountains = [];
if (dataChunks.length > 2) {
// TODO optimal number fcount and k still need to be determined
const fcount = Math.floor(dataChunks.length / 6);
const k = Math.ceil(dataChunks.length / 2);
for (let i = 0; i < fcount; i++) {
const distribution = Array(dataChunks.length)
.fill(null)
.map((_, i) => ({ i, n: random() }))
.sort((a, b) => a.n - b.n)
.slice(0, k)
.map((o) => o.i);
fountains.push(makeFountainFrame(dataChunks, distribution));
}
}
const result = [];
let j = 0;
const fountainEach = Math.floor(dataChunks.length / fountains.length);
for (let i = 0; i < dataChunks.length; i++) {
result.push(makeDataFrame({
data: dataChunks[i],
nonce,
totalFrames: dataChunks.length,
frameIndex: i,
}));
if (i % fountainEach === 0 && fountains[j]) {
result.push(fountains[j++]);
}
}
return result;
}
/**
* Export data into one series of chunk of string that you can generate a QR with
* @param dataOrStr the complete data to encode in a series of QR code frames
* @param dataSize the number of bytes to use from data for each frame
* @param loops number of loops to generate. more loops increase chance for readers to read frames
*/
function dataToFrames(dataOrStr, dataSize = 120, loops = 1) {
// Simple deterministic RNG
let seed = 1;
function random() {
let x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
const wrappedData = wrapData(buffer_1.Buffer.from(dataOrStr));
let r = [];
for (let i = 0; i < loops; i++) {
r = r.concat(makeLoop(wrappedData, dataSize, i, random));
}
return r;
}
exports.dataToFrames = dataToFrames;
//# sourceMappingURL=exporter.js.map