UNPKG

@iotile/iotile-device

Version:

A typescript library for interfacing with IOTile BLE devices

183 lines 7.2 kB
"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