UNPKG

@iotize/device-client.js

Version:

IoTize Device client for Javascript

423 lines (422 loc) 16.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var buffer_1 = require("buffer"); var tap_stream_1 = require("../client/impl/frame/tap-stream"); var logger_1 = require("../logger"); var format_1 = require("./format"); var kaitai_stream_writer_1 = require("./kaitai/kaitai-stream-writer"); var logger = logger_1.default('TLV'); var TLV; (function (TLV) { var decoders = { String: function (payload, offset, len) { return payload.toString('utf8', offset, offset + len); }, Integer: function (payload, offset, len) { return payload.readIntBE(offset, len); }, Float: function (payload, offset, len) { switch (len) { case 4: return payload.readFloatBE(offset); case 8: return payload.readDoubleBE(offset); default: throw new Error("Invalid length for float: " + len); } }, Boolean: function (payload, offset, len) { return payload.readUInt8(offset) ? true : false; }, Opaque: function (payload, offset, len) { if (payload.length < offset + len) { throw new Error("Buffer too small. Expecting " + len + " bytes but only " + (payload.length - offset) + " avaiable(s). Offset of " + offset); } var buf = buffer_1.Buffer.alloc(len); payload.copy(buf, 0, offset, offset + len); return buf; }, Time: function (payload, offset, len) { var timestamp = payload.readIntBE(offset, len); return new Date(timestamp * 1e3); }, }; var DataType; (function (DataType) { DataType["String"] = "String"; DataType["Integer"] = "Integer"; DataType["Float"] = "Float"; DataType["Boolean"] = "Boolean"; DataType["Opaque"] = "Opaque"; DataType["Time"] = "Time"; DataType["Array"] = "Array"; })(DataType = TLV.DataType || (TLV.DataType = {})); var IdentifierType; (function (IdentifierType) { IdentifierType[IdentifierType["OBJECT_INSTANCE"] = 0] = "OBJECT_INSTANCE"; IdentifierType[IdentifierType["RESOURCE_INSTANCE"] = 1] = "RESOURCE_INSTANCE"; IdentifierType[IdentifierType["MULTIPLE_RESOURCE"] = 2] = "MULTIPLE_RESOURCE"; IdentifierType[IdentifierType["RESOURCE_VALUE"] = 3] = "RESOURCE_VALUE"; })(IdentifierType = TLV.IdentifierType || (TLV.IdentifierType = {})); function tlvType(type, id, len) { var byte = type << 6; if (id > 0xff) { byte |= 0x1 << 5; } if (len < 8) { byte |= len; } else { byte |= length(len) << 3; } return byte; } TLV.tlvType = tlvType; var Parser = /** @class */ (function () { function Parser(schema) { this.position = 0; this.schema = schema; if (this.schema) { throw new Error("Schema are not supported yet"); } } /** * * @param buf * @param offset * @param tlv */ Parser.prototype.readHeader = function () { var type, id, len; var header; header = this.buffer.readUInt8(this.position); this.position += 1; type = header >>> 6; if (header & 0x20) { id = this.buffer.readUInt16BE(this.position); this.position += 2; } else { id = this.buffer.readUInt8(this.position); this.position += 1; } len = header & 0x7; if (!len) { switch ((header & 0x18) >>> 3) { case 1: len = this.buffer.readUInt8(this.position); this.position += 1; break; case 2: len = this.buffer.readUInt16BE(this.position); this.position += 2; break; case 3: len = this.buffer.readUInt16BE(this.position); len = this.buffer.readUInt8(this.position); this.position += 3; break; } } var result = { id: id, type: type, len: len }; this.debug("\t-Found header: ", result); return result; }; Parser.prototype.readNextRecord = function () { var header = this.readHeader(); var key = header.id.toString(); if (header.type == IdentifierType.RESOURCE_VALUE || header.type == IdentifierType.RESOURCE_INSTANCE) { this.debug("\t-Resource value readRecords with", header); var payload = buffer_1.Buffer.alloc(header.len); this.buffer.copy(payload, 0, this.position, this.position + header.len); this.position += header.len; return { header: header, payload: payload }; } else { this.debug("\t-Multiple resources found! readRecords with key " + key); var children = this.readAllRecords(this.position + header.len); return { header: header, children: children }; } }; // readNextRecord() { // this.debug(`Parsing record`); // var key: string, type: DataType | Array<any>; // let header = this.readHeader(); // key = this.schema.id[header.id]; // // skip resources not defined in schema. // if (!key) { // this.debug('\t-Resource not defined in schema: %s', key); // this.position += header.len; // return; // } // this.debug(`\t-Key=${key}`); // type = this.schema.resources[key].type; // if (Array.isArray(type)) { // this.debug(`\t-Reading array`); // var end = this.position + header.len; // var itemHeader: Header = {} as any; // let itemValue: any; // result[key] = []; // while (this.position < end) { // itemHeader = this.readHeader(); // itemValue = this.append(itemHeader, type[0]); // result[key].push(itemValue); // TODO // } // } else { // result[key] = this.append(header, type); // } // } Parser.prototype.append = function (header, type) { this.debug("\t-Append " + type + " header=", header); if (!(type in decoders)) { throw new Error("Decoder type " + type + " not found"); } var result = (decoders[type])(this.buffer, this.position, header.len); this.debug("\t-Appending value", result); this.position += header.len; return result; }; Parser.prototype.readAllRecords = function (endPosition) { var result = []; this.debug("Parsing records until position " + endPosition); while (this.position < endPosition) { result.push(this.readNextRecord()); } return result; }; Parser.prototype.parse = function (data) { this.position = 0; this.buffer = buffer_1.Buffer.from(tap_stream_1.typedArrayToBuffer(data)); return this.readNextRecord(); }; Parser.prototype.debug = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } logger.debug.apply(logger, args.concat(["[pos=" + this.position + "/" + (this.buffer.length - 1) + ", remaining buffer=" + format_1.FormatHelper.toHexString(this.buffer.slice(this.position)) + "]"])); }; return Parser; }()); TLV.Parser = Parser; var Serializer = /** @class */ (function () { function Serializer() { } Serializer.prototype.serialize = function (obj, stream) { stream = stream || new kaitai_stream_writer_1.KaitaiStreamWriter(); this.writeHeader(stream, obj.header.type, obj.header.id, obj.header.len); if (obj.payload) { stream.writeBytes(obj.payload); } if (obj.children) { for (var _i = 0, _a = obj.children; _i < _a.length; _i++) { var node = _a[_i]; this.serialize(node, stream); } } return stream.toBytes; }; Serializer.prototype.writeHeader = function (stream, idType, id, len) { var type = tlvType(idType, id, len); /* type (8-bits masked field) */ stream.writeU(type, 1); /* identifier (8-bit or 16-bit UInt) */ if (type & 0x20) { stream.writeU(id, 2); } else { stream.writeU(id, 1); } /* length (0-24-bit UInt) */ if (type & 0x18) { switch (length(len)) { case 3: stream.writeU(len >>> 0x10 & 0xff, 1); /* falls through */ case 2: stream.writeU(len >>> 0x08 & 0xff, 1); /* falls through */ case 1: stream.writeU(len & 0xff, 1); break; default: throw new Error('Invalid resource `' + id + '`'); } } }; Serializer.prototype.serializeObj = function (obj, schema) { var buf = buffer_1.Buffer.alloc(16 * 1024 /*1024*/); var keys = Object.keys(obj); if (schema) { schema.validate(obj); } var len = 0; for (var i = 0; i < keys.length; ++i) { var key = keys[i]; // skip resources not defined in schema if (!schema || !schema.resources[key]) { continue; } len = writeRecordWithSchema(obj, buf, len, key); } console.info(buf, len); return buf.slice(0, len); }; return Serializer; }()); TLV.Serializer = Serializer; function writeHeader(buf, offset, idType, id, len) { var type = tlvType(idType, id, len); /* type (8-bits masked fielsd) */ buf.writeUInt8(type, offset); offset += 1; /* identifier (8-bit or 16-bit UInt) */ if (type & 0x20) { buf.writeUInt16BE(id, offset); offset += 2; } else { buf.writeUInt8(id, offset); offset += 1; } /* length (0-24-bit UInt) */ if (type & 0x18) { switch (length(len)) { case 3: buf.writeUInt8(len >>> 0x10 & 0xff, offset); offset += 1; /* falls through */ case 2: buf.writeUInt8(len >>> 0x08 & 0xff, offset); offset += 1; /* falls through */ case 1: buf.writeUInt8(len & 0xff, offset); offset += 1; break; default: throw new Error('Invalid resource `' + id + '`'); } } return offset; } function writeData(buf, offset, len, value, type) { // if (type){ // switch (type){ // case DataType.String: // stream.write(); // case DataType.Boolean: // stream.writeB // } // } var ValueSerializerMap = { String: function () { buf.write(value, offset, buf.length - offset, 'utf8'); return offset += len; }, Integer: function () { buf.writeIntBE(value, offset, len); return offset += len; }, Float: function () { buf.writeDoubleBE(value, offset); return offset += len; }, Boolean: function () { buf.writeInt8(value, offset); return offset += len; }, Opaque: function () { value.copy(buf, offset); return offset += len; }, Time: function () { // convert date to timestamp in seconds var timestamp = value.getTime() / 1e3 >> 0; buf.writeIntBE(timestamp, offset, len); return offset += len; }, Array: function () { return offset; } }; if (!type) { return offset; } if (type in ValueSerializerMap) { return ValueSerializerMap[type](); } return offset; } function writeRecordWithSchema(obj, buf, offset, key, schema) { var res; // TODO check res if (schema) { res = schema.resources[key]; } var val = obj[key]; var len; if (res && Array.isArray(res.type)) { var tmp_1 = buffer_1.Buffer.alloc(1024); var arrLen_1 = 0; val.forEach(function (elem, i) { len = length(elem); arrLen_1 = writeHeader(tmp_1, arrLen_1, IdentifierType.RESOURCE_INSTANCE, i, len); arrLen_1 = writeData(tmp_1, arrLen_1, len, elem, res.type[0]); }); offset = writeHeader(buf, offset, IdentifierType.MULTIPLE_RESOURCE, parseInt(res.id), arrLen_1); tmp_1.copy(buf, offset, 0, arrLen_1); offset += arrLen_1; } else { len = length(val); offset = writeHeader(buf, offset, IdentifierType.RESOURCE_VALUE, parseInt(res.id), len); offset = writeData(buf, offset, len, val, res.type); } return offset; } /* * length in bytes of a number */ function byteLength(val) { if (val < 2) { return 1; } return Math.ceil(Math.log(val) * Math.LOG2E / 8); // TODO can be shorter } function length(val) { // integer size: 1, 2, 4 or 8 bytes. function size(val) { var v = byteLength(val); v--; v |= v >>> 1; v |= v >>> 2; v++; return v; } var type = Object.prototype.toString.call(val).slice(8, -1); switch (type) { case 'Number': return (val % 1 === 0 ? size(val) : 8); case 'String': case 'Uint8Array': case 'Buffer': return val.length; case 'Boolean': return 1; case 'Date': return size(val.getTime() / 1e3 >> 0); default: return 0; } } })(TLV = exports.TLV || (exports.TLV = {}));