@iotile/iotile-device
Version:
A typescript library for interfacing with IOTile BLE devices
280 lines • 11.3 kB
JavaScript
"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