@iotile/iotile-device
Version:
A typescript library for interfacing with IOTile BLE devices
183 lines • 7.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const iotile_reports_1 = require("./iotile-reports");
const iotile_common_1 = require("@iotile/iotile-common");
class ReportReassembler {
constructor(report) {
this.currentReport = report;
this.header = iotile_reports_1.SignedListReport.extractHeader(report);
this.sigCalculator = new iotile_common_1.SHA256Calculator();
this.errors = [];
this.originalSignature = this.currentReport.slice(this.currentReport.byteLength - 16);
}
isValid() {
return this.checkSignature();
}
getTranspositions() {
return this.errors;
}
getFixedReport() {
if (this.checkSignature()) {
return this.currentReport;
}
else {
throw new iotile_common_1.InvalidOperationError("Report has invalid signature");
}
}
fixOutOfOrderChunks() {
let startI = 1;
let totalChunks = Math.floor(this.currentReport.byteLength / 20);
let endI = totalChunks - 1;
let offset = 0;
let lastStream = null;
let lastTS = null;
let lastID = null;
while (startI < endI) {
let candidates = this.findCandidates(startI, totalChunks, offset, lastStream, lastTS, lastID);
let bestCandidate = null;
if (candidates.length === 1)
bestCandidate = candidates[0];
else if (candidates.length > 1) {
this.sortCandidates(candidates);
bestCandidate = candidates[0];
}
if (bestCandidate == null)
return false;
if (bestCandidate.index !== startI) {
this.errors.push({ src: bestCandidate.index, dst: startI });
this.moveChunk(startI, bestCandidate.index);
}
[lastStream, lastID, lastTS] = this.extractLatest(bestCandidate);
offset = (offset + 20) % 16;
startI += 1;
}
return this.isValid();
}
sortCandidates(candidates) {
function extractID(candidate) {
if (candidate.offset === 4 || candidate.offset === 0)
return candidate.ids[0];
return candidate.ids[1];
}
function compareIDs(a, b) {
return extractID(a) - extractID(b);
}
candidates.sort(compareIDs);
}
moveChunk(destIndex, srcIndex) {
let tmp = new Uint8Array(20);
if (destIndex >= srcIndex)
throw new iotile_common_1.ArgumentError("Attempting to move chunk later rather than earlier in report.");
for (let curr = srcIndex; curr > destIndex; --curr) {
let swapDst = new Uint8Array(this.currentReport, (curr - 1) * 20, 20);
let swapSrc = new Uint8Array(this.currentReport, (curr) * 20, 20);
tmp.set(swapDst);
swapDst.set(swapSrc);
swapSrc.set(tmp);
}
}
extractLatest(chunk) {
let stream = chunk.streams[0];
let id = chunk.ids[0];
let ts = chunk.timestamps[0];
if (chunk.streams[1] !== null)
stream = chunk.streams[1];
if (chunk.ids[1] !== null)
id = chunk.ids[1];
if (chunk.timestamps[1] !== null)
ts = chunk.timestamps[1];
return [stream, id, ts];
}
fillChunk(chunk, lastStream, lastTS, lastID) {
if (chunk.streams[0] == null)
chunk.streams[0] = lastStream;
if (chunk.reserved[0] == null)
chunk.reserved[0] = 0;
if (chunk.timestamps[0] == null)
chunk.timestamps[0] = lastTS;
if (chunk.ids[0] == null)
chunk.ids[0] = lastID;
}
decodeChunk(startI, offset) {
let chunkData = this.currentReport.slice(startI * 20, startI * 20 + 20);
let [stream1, stream2] = [null, null];
let [id1, id2] = [null, null];
let [res1, res2] = [null, null];
let [ts1, ts2] = [null, null];
let [val1, val2] = [null, null];
if (offset === 0) {
[stream1, res1, id1, ts1, val1, stream2, res2] = iotile_common_1.unpackArrayBuffer("HHLLLHH", chunkData);
}
else if (offset === 4) {
[id1, ts1, val1, stream2, res2, id2] = iotile_common_1.unpackArrayBuffer("LLLHHL", chunkData);
}
else if (offset === 8) {
[ts1, val1, stream2, res2, id2, ts2] = iotile_common_1.unpackArrayBuffer("LLHHLL", chunkData);
}
else {
[val1, stream2, res2, id2, ts2, val2] = iotile_common_1.unpackArrayBuffer("LHHLLL", chunkData);
}
return { streams: [stream1, stream2], reserved: [res1, res2], ids: [id1, id2], timestamps: [ts1, ts2], values: [val1, val2], index: startI, offset: offset };
}
dumpChunk(index) {
let data = new Uint8Array(this.currentReport, index * 20, 20);
return Array.prototype.map.call(data, (x) => ('00' + x.toString(16)).slice(-2)).join(' ');
}
maskChunk(chunk) {
if (chunk.offset === 12)
return chunk;
chunk.streams[1] = null;
chunk.ids[1] = null;
chunk.reserved[1] = null;
chunk.timestamps[1] = null;
chunk.values[1] = null;
}
validateChunk(chunk, lastStream, lastTS, lastID) {
for (let stream of chunk.streams) {
if (stream !== null && !this.header.decodedSelector.matches(stream)) {
return false;
}
}
for (let res of chunk.reserved) {
if (res !== null && res !== 0) {
return false;
}
}
if (chunk.ids[1] !== null && chunk.ids[1] <= chunk.ids[0]) {
return false;
}
if (chunk.timestamps[1] !== null && chunk.timestamps[1] < chunk.timestamps[0] && chunk.streams[1] !== iotile_reports_1.StreamSelector.REBOOT_STREAM) {
return false;
}
if ((chunk.offset === 0 || chunk.offset === 4) && chunk.ids[0] <= lastID) {
return false;
}
return true;
}
findCandidates(startI, totalChunks, offset, lastStream, lastTS, lastID) {
let candidates = [];
for (let i = 0; i < 4; ++i) {
if (startI + i >= totalChunks)
continue;
let chunk = this.decodeChunk(startI + i, offset);
if (startI === totalChunks - 2)
this.maskChunk(chunk);
this.fillChunk(chunk, lastStream, lastTS, lastID);
if (this.validateChunk(chunk, lastStream, lastTS, lastID))
candidates.push(chunk);
}
return candidates;
}
calculateSignature() {
let signedData = this.currentReport.slice(0, this.currentReport.byteLength - 16);
return this.sigCalculator.calculateSignature(signedData);
}
checkSignature(prefix) {
if (prefix == null)
prefix = 16;
let actual = this.calculateSignature();
return this.sigCalculator.compareSignatures(this.originalSignature.slice(0, prefix), actual);
}
}
exports.ReportReassembler = ReportReassembler;
//# sourceMappingURL=report-reassembler.js.map