UNPKG

@homebridge/dbus-native

Version:

D-bus protocol implementation in native javascript

331 lines (321 loc) 9.89 kB
const Buffer = require('safe-buffer').Buffer; const align = require('./align').align; const parseSignature = require('../lib/signature'); const Long = require('long'); /** * MakeSimpleMarshaller * @param signature - the signature of the data you want to check * @returns a simple marshaller with the "check" method * * check returns nothing - it only raises errors if the data is * invalid for the signature */ var MakeSimpleMarshaller = function (signature) { var marshaller = {}; function checkValidString(data) { if (typeof data !== 'string') { throw new Error(`Data: ${data} was not of type string`); } else if (data.indexOf('\0') !== -1) { throw new Error('String contains null byte'); } } function checkValidSignature(data) { if (data.length > 0xff) { throw new Error( `Data: ${data} is too long for signature type (${data.length} > 255)` ); } var parenCount = 0; for (var ii = 0; ii < data.length; ++ii) { if (parenCount > 32) { throw new Error( `Maximum container type nesting exceeded in signature type:${data}` ); } switch (data[ii]) { case '(': ++parenCount; break; case ')': --parenCount; break; default: /* no-op */ break; } } parseSignature(data); } switch (signature) { case 'o': // object path // TODO: verify object path here? case 's': // eslint-disable-line no-fallthrough //STRING marshaller.check = function (data) { checkValidString(data); }; marshaller.marshall = function (ps, data) { this.check(data); // utf8 string align(ps, 4); const buff = Buffer.from(data, 'utf8'); ps.word32le(buff.length).put(buff).word8(0); ps._offset += 5 + buff.length; }; break; case 'g': //SIGNATURE marshaller.check = function (data) { checkValidString(data); checkValidSignature(data); }; marshaller.marshall = function (ps, data) { this.check(data); // signature const buff = Buffer.from(data, 'ascii'); ps.word8(data.length).put(buff).word8(0); ps._offset += 2 + buff.length; }; break; case 'y': //BYTE marshaller.check = function (data) { checkInteger(data); checkRange(0x00, 0xff, data); }; marshaller.marshall = function (ps, data) { this.check(data); ps.word8(data); ps._offset++; }; break; case 'b': //BOOLEAN marshaller.check = function (data) { checkBoolean(data); }; marshaller.marshall = function (ps, data) { this.check(data); // booleans serialised as 0/1 unsigned 32 bit int data = data ? 1 : 0; align(ps, 4); ps.word32le(data); ps._offset += 4; }; break; case 'n': //INT16 marshaller.check = function (data) { checkInteger(data); checkRange(-0x7fff - 1, 0x7fff, data); }; marshaller.marshall = function (ps, data) { this.check(data); align(ps, 2); const buff = Buffer.alloc(2); buff.writeInt16LE(parseInt(data), 0); ps.put(buff); ps._offset += 2; }; break; case 'q': //UINT16 marshaller.check = function (data) { checkInteger(data); checkRange(0, 0xffff, data); }; marshaller.marshall = function (ps, data) { this.check(data); align(ps, 2); ps.word16le(data); ps._offset += 2; }; break; case 'i': //INT32 marshaller.check = function (data) { checkInteger(data); checkRange(-0x7fffffff - 1, 0x7fffffff, data); }; marshaller.marshall = function (ps, data) { this.check(data); align(ps, 4); const buff = Buffer.alloc(4); buff.writeInt32LE(parseInt(data), 0); ps.put(buff); ps._offset += 4; }; break; case 'u': //UINT32 marshaller.check = function (data) { checkInteger(data); checkRange(0, 0xffffffff, data); }; marshaller.marshall = function (ps, data) { this.check(data); // 32 t unsigned int align(ps, 4); ps.word32le(data); ps._offset += 4; }; break; case 't': //UINT64 marshaller.check = function (data) { return checkLong(data, false); }; marshaller.marshall = function (ps, data) { data = this.check(data); align(ps, 8); ps.word32le(data.low); ps.word32le(data.high); ps._offset += 8; }; break; case 'x': //INT64 marshaller.check = function (data) { return checkLong(data, true); }; marshaller.marshall = function (ps, data) { data = this.check(data); align(ps, 8); ps.word32le(data.low); ps.word32le(data.high); ps._offset += 8; }; break; case 'd': //DOUBLE marshaller.check = function (data) { if (typeof data !== 'number') { throw new Error(`Data: ${data} was not of type number`); } else if (Number.isNaN(data)) { throw new Error(`Data: ${data} was not a number`); } else if (!Number.isFinite(data)) { throw new Error('Number outside range'); } }; marshaller.marshall = function (ps, data) { this.check(data); align(ps, 8); const buff = Buffer.alloc(8); buff.writeDoubleLE(parseFloat(data), 0); ps.put(buff); ps._offset += 8; }; break; default: throw new Error(`Unknown data type format: ${signature}`); } return marshaller; }; exports.MakeSimpleMarshaller = MakeSimpleMarshaller; var checkRange = function (minValue, maxValue, data) { if (data > maxValue || data < minValue) { throw new Error('Number outside range'); } }; var checkInteger = function (data) { if (typeof data !== 'number') { throw new Error(`Data: ${data} was not of type number`); } if (Math.floor(data) !== data) { throw new Error(`Data: ${data} was not an integer`); } }; var checkBoolean = function (data) { if (!(typeof data === 'boolean' || data === 0 || data === 1)) throw new Error(`Data: ${data} was not of type boolean`); }; // This is essentially a tweaked version of 'fromValue' from Long.js with error checking. // This can take number or string of decimal characters or 'Long' instance (or Long-style object with props low,high,unsigned). var makeLong = function (val, signed) { if (val instanceof Long) return val; if (val instanceof Number) val = val.valueOf(); if (typeof val === 'number') { try { // Long.js won't alert you to precision loss in passing more than 53 bit ints through a double number, so we check here checkInteger(val); if (signed) { checkRange(-0x1fffffffffffff, 0x1fffffffffffff, val); } else { checkRange(0, 0x1fffffffffffff, val); } } catch (e) { e.message += ' (Number type can only carry 53 bit integer)'; throw e; } try { return Long.fromNumber(val, !signed); } catch (e) { e.message = `Error converting number to 64bit integer "${e.message}"`; throw e; } } if (typeof val === 'string' || val instanceof String) { var radix = 10; val = val.trim().toUpperCase(); // remove extra whitespace and make uppercase (for hex) if (val.substring(0, 2) === '0X') { radix = 16; val = val.substring(2); } else if (val.substring(0, 3) === '-0X') { // unusual, but just in case? radix = 16; val = `-${val.substring(3)}`; } val = val.replace(/^0+(?=\d)/, ''); // dump leading zeroes var data; try { data = Long.fromString(val, !signed, radix); } catch (e) { e.message = `Error converting string to 64bit integer '${e.message}'`; throw e; } // If string represents a number outside of 64 bit range, it can quietly overflow. // We assume if things converted correctly the string coming out of Long should match what went into it. if (data.toString(radix).toUpperCase() !== val) throw new Error( `Data: '${val}' did not convert correctly to ${ signed ? 'signed' : 'unsigned' } 64 bit` ); return data; } // Throws for non-objects, converts non-instanceof Long: try { return Long.fromBits(val.low, val.high, val.unsigned); } catch (e) { e.message = `Error converting object to 64bit integer '${e.message}'`; throw e; } }; var checkLong = function (data, signed) { if (!Long.isLong(data)) { data = makeLong(data, signed); } // Do we enforce that Long.js object unsigned/signed match the field even if it is still in range? // Probably, might help users avoid unintended bugs? if (signed) { if (data.unsigned) throw new Error( 'Longjs object is unsigned, but marshalling into signed 64 bit field' ); if (data.gt(Long.MAX_VALUE) || data.lt(Long.MIN_VALUE)) { throw new Error(`Data: ${data} was out of range (64-bit signed)`); } } else { if (!data.unsigned) throw new Error( 'Longjs object is signed, but marshalling into unsigned 64 bit field' ); // NOTE: data.gt(Long.MAX_UNSIGNED_VALUE) will catch if Long.js object is a signed value but is still within unsigned range! // Since we are enforcing signed type matching between Long.js object and field, this note should not matter. if (data.gt(Long.MAX_UNSIGNED_VALUE) || data.lt(0)) { throw new Error(`Data: ${data} was out of range (64-bit unsigned)`); } } return data; };