UNPKG

lwm2m

Version:

Library for developing servers and client of OMA Lightweight M2M

346 lines (292 loc) 7.63 kB
/* * Copyright 2017 Alexandre Moreno <alex_moreno@tutk.com> * * This file is part of node-lwm2m * * node-lwm2m is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * node-lwm2m is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with node-lwm2m. * If not, seehttp://www.gnu.org/licenses/. * * For those usages not covered by the GNU Affero General Public License * please contact with::[contacto@tid.es] * */ var debug = require('debug')('lwm2m'); /* * length in bytes of a number */ function byteLength(val) { if (val < 2) { return 1; } return Math.ceil(Math.log(val) * Math.LOG2E / 8); } 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; } } function readHeader(buf, offset, tlv) { var type, id, len; var header; header = buf.readUInt8(offset); offset += 1; type = header >>> 6; if (header & 0x20) { id = buf.readUInt16BE(offset); offset += 2; } else { id = buf.readUInt8(offset); offset += 1; } len = header & 0x7; if (!len) { switch ((header & 0x18) >>> 3) { case 1: len = buf.readUInt8(offset); offset += 1; break; case 2: len = buf.readUInt16BE(offset); offset += 2; break; case 3: len = buf.readUInt16BE(offset); len = buf.readUInt8(offset); offset += 3; break; } } tlv.len = len; tlv.id = id; tlv.type = type; return offset; } function writeHeader(buf, offset, idType, id, len) { 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; } var type = tlvType(idType, id, len); /* type (8-bits masked field) */ 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 serialize(obj, schema) { var buf = Buffer.alloc(16 * 1024 /*1024*/); var keys = Object.keys(obj); schema.validate(obj); function append(buf, offset, len, value, type) { var types = { 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; }, }; function skip () { debug('Skipping resource with invalid type %s', type); return offset; } return (types[type] || skip)(); } function writeRecord(buf, offset, key) { var res = schema.resources[key]; var val = obj[key]; var len; if (Array.isArray(res.type)) { var tmp = Buffer.alloc(1024); var arrLen = 0; val.forEach(function(elem, i) { len = length(elem); arrLen = writeHeader(tmp, arrLen, 1, i, len); arrLen = append(tmp, arrLen, len , elem, res.type[0]); }); offset = writeHeader(buf, offset, 2, res.id, arrLen); tmp.copy(buf, offset, 0, arrLen); offset += arrLen; } else { len = length(val); offset = writeHeader(buf, offset, 3, res.id, len); offset = append(buf, offset, len, val, res.type); } return offset; } var len = 0; for (var i = 0; i < keys.length; ++i) { var key = keys[i]; // skip resources not defined in schema if (!schema.resources[key]) { continue; } len = writeRecord(buf, len, key); } return buf.slice(0, len); } function parse(payload, schema) { function append(obj, key, type, payload, pos, len) { var types = { String: function() { obj[key] = payload.toString('utf8', pos, pos + len); pos += len; return pos; }, Integer: function() { obj[key] = payload.readIntBE(pos, len); pos += len; return pos; }, Float: function() { switch (len) { case 4: obj[key] = payload.readFloatBE(pos); break; case 8: obj[key] = payload.readDoubleBE(pos); break; } pos += len; return pos; }, Boolean: function() { obj[key] = payload.readUInt8(pos) ? true : false; pos += 1; return pos; }, Opaque: function() { var buf = Buffer.alloc(len); payload.copy(buf, 0, pos, pos + len); obj[key] = buf; pos += len; return pos; }, Time: function() { var timestamp = payload.readIntBE(pos, len); obj[key] = new Date(timestamp * 1e3); pos += len; return pos; }, }; return (types[type])(); } function readRecord(payload, pos, result) { var header = {}, key, type; pos = readHeader(payload, pos, header); key = schema.id[header.id]; // skip resources not defined in schema. if (!key) { debug('Resource not defined in schema: %s', key); return pos + header.len; } type = schema.resources[key].type; if (Array.isArray(type)) { var end = pos + header.len; var itemHeader = {}; result[key] = []; while (pos < end) { pos = readHeader(payload, pos, itemHeader); pos = append(result[key], itemHeader.id, type[0], payload, pos, itemHeader.len); } } else { pos = append(result, key, type, payload, pos, header.len); } return pos; } var result = {}; var pos = 0; while (pos < payload.length) { pos = readRecord(payload, pos, result); } return result; } exports.serialize = serialize; exports.parse = parse;