@iotize/device-client.js
Version:
IoTize Device client for Javascript
423 lines (422 loc) • 16.1 kB
JavaScript
"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 = {}));