@homebridge/dbus-native
Version:
D-bus protocol implementation in native javascript
331 lines (321 loc) • 9.89 kB
JavaScript
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;
};