UNPKG

@iotile/iotile-device

Version:

A typescript library for interfacing with IOTile BLE devices

280 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const iotile_common_1 = require("@iotile/iotile-common"); const report_reassembler_1 = require("./report-reassembler"); const config_1 = require("../config"); class RawReading { constructor(stream, value, timestamp, timebase, id) { this._stream = stream; this._value = value; this._raw_timestamp = timestamp; this._time = new Date(timebase.valueOf() + timestamp * 1000); if (id !== undefined) { this._id = id; } else { this._id = 0; } } get timestamp() { return this._raw_timestamp; } get value() { return this._value; } get stream() { return this._stream; } get id() { return this._id; } get time() { return this._time; } get variable() { return iotile_common_1.numberToHexString(this.stream, 4); } } exports.RawReading = RawReading; class IOTileReport { } exports.IOTileReport = IOTileReport; class IndividualReport extends IOTileReport { constructor(uuid, sentTime, reading) { super(); this._uuid = uuid; this._reading = reading; this._sentTimestamp = sentTime; } decodeUsingFormat(fmt) { if (fmt == '<l') { let signedValue; let rawData = iotile_common_1.packArrayBuffer('L', this._reading.value); [signedValue] = iotile_common_1.unpackArrayBuffer('l', rawData); this._reading = new RawReading(this._reading.stream, signedValue, this._reading.timestamp, this._reading.time, this._reading.id); } } get deviceID() { return this._uuid; } get reading() { return this._reading; } get sentTimestamp() { return this._sentTimestamp; } } exports.IndividualReport = IndividualReport; const REPORT_HEADER_SIZE = 20; exports.USER_REPORT_STREAMER = 0; exports.SYSTEM_REPORT_STREAMER = 1; exports.COMBINED_REPORT_STREAMER = 0xFF; var SignedReportSelectors; (function (SignedReportSelectors) { SignedReportSelectors[SignedReportSelectors["UserOutputs"] = 22527] = "UserOutputs"; SignedReportSelectors[SignedReportSelectors["SystemOutputs"] = 24575] = "SystemOutputs"; SignedReportSelectors[SignedReportSelectors["CombinedOutputs"] = 55295] = "CombinedOutputs"; })(SignedReportSelectors = exports.SignedReportSelectors || (exports.SignedReportSelectors = {})); var SignatureFlags; (function (SignatureFlags) { SignatureFlags[SignatureFlags["HashOnly"] = 0] = "HashOnly"; SignatureFlags[SignatureFlags["SignedUserKey"] = 1] = "SignedUserKey"; SignatureFlags[SignatureFlags["SignedDeviceKey"] = 2] = "SignedDeviceKey"; })(SignatureFlags = exports.SignatureFlags || (exports.SignatureFlags = {})); var SignatureStatus; (function (SignatureStatus) { SignatureStatus[SignatureStatus["Valid"] = 0] = "Valid"; SignatureStatus[SignatureStatus["Invalid"] = 1] = "Invalid"; SignatureStatus[SignatureStatus["Unknown"] = 2] = "Unknown"; })(SignatureStatus = exports.SignatureStatus || (exports.SignatureStatus = {})); var StreamMatchOperator; (function (StreamMatchOperator) { StreamMatchOperator[StreamMatchOperator["UserOnly"] = 0] = "UserOnly"; StreamMatchOperator[StreamMatchOperator["SystemOnly"] = 1] = "SystemOnly"; StreamMatchOperator[StreamMatchOperator["UserAndBreaks"] = 2] = "UserAndBreaks"; StreamMatchOperator[StreamMatchOperator["UserAndSystem"] = 3] = "UserAndSystem"; })(StreamMatchOperator = exports.StreamMatchOperator || (exports.StreamMatchOperator = {})); var StreamType; (function (StreamType) { StreamType[StreamType["Storage"] = 0] = "Storage"; StreamType[StreamType["Unbuffered"] = 1] = "Unbuffered"; StreamType[StreamType["Constant"] = 2] = "Constant"; StreamType[StreamType["Input"] = 3] = "Input"; StreamType[StreamType["Count"] = 4] = "Count"; StreamType[StreamType["Output"] = 5] = "Output"; StreamType[StreamType["Realtime"] = 6] = "Realtime"; })(StreamType = exports.StreamType || (exports.StreamType = {})); class StreamSelector { constructor(encodedSelector) { [this.type, this.code, this.match_op] = StreamSelector.decode(encodedSelector); this.isWildcard = (this.code === StreamSelector.WILDCARD); } matches(streamID) { let [type, code, op] = StreamSelector.decode(streamID); if (op === StreamMatchOperator.UserAndSystem || op === StreamMatchOperator.UserAndBreaks) return false; if (!this.isWildcard && this.code !== code) return false; if (this.type !== type) return false; if (this.match_op === StreamMatchOperator.SystemOnly && op == StreamMatchOperator.UserOnly) return false; if (this.match_op === StreamMatchOperator.UserOnly && op == StreamMatchOperator.SystemOnly) return false; if (this.match_op === StreamMatchOperator.UserAndBreaks && op == StreamMatchOperator.SystemOnly && streamID !== StreamSelector.REBOOT_STREAM) return false; return true; } static decode(encodedSelector) { if (encodedSelector < 0 || encodedSelector > 0xFFFF) throw new iotile_common_1.ArgumentError(`invalid number in StreamSelector that is not in [0, 65535]: ${encodedSelector}`); if (encodedSelector !== Math.floor(encodedSelector)) throw new iotile_common_1.ArgumentError(`You must create a StreamSelector with a whole number: ${encodedSelector}`); let isSystem = !!(encodedSelector & (1 << 11)); let includeBreaks = !!(encodedSelector & (1 << 15)); let match_op = StreamSelector.getOperator(isSystem, includeBreaks); let code = encodedSelector & ((1 << 11) - 1); let type = (encodedSelector >> 12) & 0b111; return [type, code, match_op]; } static getOperator(isSystem, includeBreaks) { if (isSystem && !includeBreaks) return StreamMatchOperator.SystemOnly; if (!isSystem && !includeBreaks) return StreamMatchOperator.UserOnly; if (isSystem && includeBreaks) return StreamMatchOperator.UserAndSystem; return StreamMatchOperator.UserAndBreaks; } } StreamSelector.WILDCARD = (1 << 11) - 1; StreamSelector.REBOOT_STREAM = 0x5C00; exports.StreamSelector = StreamSelector; class SignedListReport extends IOTileReport { static extractHeader(data) { if (data.byteLength < REPORT_HEADER_SIZE) { throw new iotile_common_1.ArgumentError("Invalid report that was not long enough to contain a valid header"); } let header = iotile_common_1.unpackArrayBuffer('BBHLLLBBH', data.slice(0, REPORT_HEADER_SIZE)); return { format: header[0], lengthLow: header[1], lengthHigh: header[2], uuid: header[3], reportID: header[4], sentTime: header[5], signatureFlags: header[6], streamer: header[7], selector: header[8], decodedSelector: new StreamSelector(header[8]) }; } constructor(uuid, streamer, rawData, receivedTime) { super(); this._highestID = 0; this._lowestID = 0; this._uuid = uuid; this._readings = []; this._rawData = rawData; this._streamer = streamer; this._receivedTime = receivedTime; this._header = SignedListReport.extractHeader(rawData); this._valid = this.validateSignature(); if (this._valid === SignatureStatus.Valid) { this.updateReadings(this._rawData); this.updateReadingRange(this._readings); } } get deviceID() { return this._uuid; } get readings() { return this._readings; } get validity() { return this._valid; } get rawData() { return this._rawData; } get streamer() { return this._streamer; } get receivedTime() { return this._receivedTime; } get readingIDRange() { return [this._lowestID, this._highestID]; } get header() { return this._header; } updateReadingRange(readings) { if (readings.length == 0) { this._lowestID = 0; this._highestID = 0; } else { this._lowestID = readings[0].id; this._highestID = readings[0].id; for (let i = 1; i < readings.length; ++i) { let id = readings[i].id; if (id < this._lowestID) { this._lowestID = id; } if (id > this._highestID) { this._highestID = id; } } } } updateReadings(rawData) { let readings = []; let onTime = new Date(this._receivedTime.valueOf() - (this._header.sentTime * 1000)); let allReadingsData = rawData.slice(20, rawData.byteLength - 24); for (let i = 0; i < allReadingsData.byteLength; i += 16) { let readingData = allReadingsData.slice(i, i + 16); let reading = iotile_common_1.unpackArrayBuffer("HHLLL", readingData); let stream = reading[0]; let readingID = reading[2]; let readingTimestamp = reading[3]; let readingValue = reading[4]; let parsedReading = new RawReading(stream, readingValue, readingTimestamp, onTime, readingID); readings.push(parsedReading); } this._readings = readings; this.updateReadingRange(readings); } validateSignature() { let calc = new iotile_common_1.SHA256Calculator(); if (this._header.signatureFlags != SignatureFlags.HashOnly) { return SignatureStatus.Unknown; } let signedData = this._rawData.slice(0, this._rawData.byteLength - 16); let embeddedSig = this._rawData.slice(this._rawData.byteLength - 16); let signature = calc.calculateSignature(signedData); if (calc.compareSignatures(embeddedSig, signature)) { return SignatureStatus.Valid; } config_1.catReports.warn("Report signature invalid, attempting to reassemble"); let reassembler = new report_reassembler_1.ReportReassembler(this.rawData); if (reassembler.fixOutOfOrderChunks()) { config_1.catReports.info("Report successfully fixed"); let errors = reassembler.getTranspositions(); let errorStrings = []; for (let error of errors) { let errorString = error.src + " -> " + error.dst; errorStrings.push(errorString); } let errorString = errorStrings.join(", "); config_1.catReports.info("Report transpositions were: " + errorString); let newReport = reassembler.getFixedReport(); this._rawData = newReport; return SignatureStatus.Valid; } config_1.catReports.error("Unable to correct report signature", null); return SignatureStatus.Invalid; } } exports.SignedListReport = SignedListReport; //# sourceMappingURL=iotile-reports.js.map