@quadratclown/dbus-next
Version:
The next great DBus library for Node
349 lines (288 loc) • 9.82 kB
JavaScript
const fs = require('fs');
const variant = require('../variant');
const Variant = variant.Variant;
const { Message } = require('../message-type');
const {
isObjectPathValid,
isInterfaceNameValid,
isMemberNameValid
} = require('../validators');
const {
ACCESS_READ,
ACCESS_WRITE,
ACCESS_READWRITE
} = require('./interface');
const constants = require('../constants');
const {
METHOD_RETURN
} = constants.MessageType;
const { DBusError } = require('../errors');
const INVALID_ARGS = 'org.freedesktop.DBus.Error.InvalidArgs';
function sendServiceError (bus, msg, errorMessage) {
bus.send(Message.newError(msg, 'com.github.dbus_next.ServiceError', `Service error: ${errorMessage}`));
return true;
}
function handleIntrospect (bus, msg, path) {
bus.send(Message.newMethodReturn(msg, 's', [bus._introspect(path)]));
}
function handleGetProperty (bus, msg, path) {
const [ifaceName, prop] = msg.body;
if (!bus._serviceObjects[path]) {
bus.send(Message.newError(msg, INVALID_ARGS, `Path not exported on bus: '${path}'`));
return;
}
const obj = bus._getServiceObject(path);
const iface = obj.interfaces[ifaceName];
// TODO An empty string may be provided for the interface name; in this case,
// if there are multiple properties on an object with the same name, the
// results are undefined (picking one by according to an arbitrary
// deterministic rule, or returning an error, are the reasonable
// possibilities).
if (!iface) {
bus.send(Message.newError(msg, INVALID_ARGS, `No such interface: '${ifaceName}'`));
return;
}
const properties = iface.$properties || {};
let options = null;
let propertyKey = null;
for (const k of Object.keys(properties)) {
if (properties[k].name === prop && !properties[k].disabled) {
options = properties[k];
propertyKey = k;
break;
}
}
if (options === null) {
bus.send(Message.newError(msg, INVALID_ARGS, `No such property: '${prop}'`));
return;
}
let propertyValue = null;
try {
propertyValue = iface[propertyKey];
} catch (e) {
if (e.name === 'DBusError') {
bus.send(Message.newError(msg, e.type, e.text));
} else {
sendServiceError(bus, msg, `The service threw an error.\n${e.stack}`);
}
return true;
}
if (propertyValue instanceof DBusError) {
bus.send(Message.newError(msg, propertyValue.type, propertyValue.text));
return true;
} else if (propertyValue === undefined) {
return sendServiceError(bus, msg, 'tried to get a property that is not set: ' + prop);
}
if (!(options.access === ACCESS_READWRITE ||
options.access === ACCESS_READ)) {
bus.send(Message.newError(msg, INVALID_ARGS, `Property does not have read access: '${prop}'`));
}
const body = new Variant(options.signature, propertyValue);
bus.send(Message.newMethodReturn(msg, 'v', [body]));
}
function handleGetAllProperties (bus, msg, path) {
const ifaceName = msg.body[0];
if (!bus._serviceObjects[path]) {
bus.send(Message.newError(msg, INVALID_ARGS, `Path not exported on bus: '${path}'`));
return;
}
const obj = bus._getServiceObject(path);
const iface = obj.interfaces[ifaceName];
const result = {};
if (iface) {
const properties = iface.$properties || {};
for (const k of Object.keys(properties)) {
const p = properties[k];
if (!(p.access === ACCESS_READ || p.access === ACCESS_READWRITE) || p.disabled) {
continue;
}
let value;
try {
value = iface[k];
} catch (e) {
if (e.name === 'DBusError') {
bus.send(Message.newError(msg, e.type, e.text));
} else {
sendServiceError(bus, msg, `The service threw an error.\n${e.stack}`);
}
return true;
}
if (value instanceof DBusError) {
bus.send(Message.newError(msg, value.type, value.text));
return true;
} else if (value === undefined) {
return sendServiceError(bus, msg, 'tried to get a property that is not set: ' + p);
}
result[p.name] = new Variant(p.signature, value);
}
}
bus.send(Message.newMethodReturn(msg, 'a{sv}', [result]));
}
function handleSetProperty (bus, msg, path) {
const [ifaceName, prop, value] = msg.body;
if (!bus._serviceObjects[path]) {
bus.send(Message.newError(msg, INVALID_ARGS, `Path not exported on bus: '${path}'`));
return;
}
const obj = bus._getServiceObject(path);
const iface = obj.interfaces[ifaceName];
if (!iface) {
bus.send(Message.newError(msg, INVALID_ARGS, `Interface not found: '${ifaceName}'`));
return;
}
const properties = iface.$properties || {};
let options = null;
let propertyKey = null;
for (const k of Object.keys(properties)) {
if (properties[k].name === prop && !properties[k].disabled) {
options = properties[k];
propertyKey = k;
break;
}
}
if (options === null) {
bus.send(Message.newError(msg, INVALID_ARGS, `No such property: '${prop}'`));
return;
}
if (!(options.access === ACCESS_WRITE || options.access === ACCESS_READWRITE)) {
bus.send(Message.newError(msg, INVALID_ARGS, `Property does not have write access: '${prop}'`));
}
if (value.signature !== options.signature) {
bus.send(Message.newError(msg, INVALID_ARGS, `Cannot set property '${prop}' with signature '${value.signature}' (expected '${options.signature}')`));
return;
}
try {
iface[propertyKey] = value.value;
} catch (e) {
if (e.name === 'DBusError') {
bus.send(Message.newError(msg, e.type, e.text));
} else {
sendServiceError(bus, msg, `The service threw an error.\n${e.stack}`);
}
return true;
}
bus.send(Message.newMethodReturn(msg, '', []));
}
function handleStdIfaces (bus, msg) {
const {
member,
path,
signature
} = msg;
const ifaceName = msg.interface;
if (!isInterfaceNameValid(ifaceName)) {
bus.send(Message.newError(msg, INVALID_ARGS, `Invalid interface name: '${ifaceName}'`));
return true;
}
if (!isMemberNameValid(member)) {
bus.send(Message.newError(msg, INVALID_ARGS, `Invalid member name: '${member}'`));
return true;
}
if (!isObjectPathValid(path)) {
bus.send(Message.newError(msg, INVALID_ARGS, `Invalid path name: '${path}'`));
return true;
}
if (ifaceName === 'org.freedesktop.DBus.Introspectable' &&
member === 'Introspect' &&
!signature) {
handleIntrospect(bus, msg, path);
return true;
} else if (ifaceName === 'org.freedesktop.DBus.Properties') {
if (member === 'Get' && signature === 'ss') {
handleGetProperty(bus, msg, path);
return true;
} else if (member === 'Set' && signature === 'ssv') {
handleSetProperty(bus, msg, path);
return true;
} else if (member === 'GetAll') {
handleGetAllProperties(bus, msg, path);
return true;
}
} else if (ifaceName === 'org.freedesktop.DBus.Peer') {
if (member === 'Ping' && !signature) {
bus._connection.message({
type: METHOD_RETURN,
serial: bus._serial++,
replySerial: msg.serial,
destination: msg.sender
});
return true;
} else if (member === 'GetMachineId' && !signature) {
const machineId = fs.readFileSync('/var/lib/dbus/machine-id').toString().trim();
bus._connection.message({
type: METHOD_RETURN,
serial: bus._serial++,
replySerial: msg.serial,
destination: msg.sender,
signature: 's',
body: [machineId]
});
return true;
}
}
return false;
}
function handleMessage (msg, bus) {
let {
path,
member,
signature
} = msg;
const ifaceName = msg.interface;
signature = signature || '';
if (handleStdIfaces(bus, msg)) {
return true;
}
if (!bus._serviceObjects[path]) {
return false;
}
const obj = bus._getServiceObject(path);
const iface = obj.interfaces[ifaceName];
if (!iface) {
return false;
}
const methods = iface.$methods || {};
for (const m of Object.keys(methods)) {
const method = methods[m];
let result = null;
const handleError = (e) => {
if (e.name === 'DBusError') {
bus.send(Message.newError(msg, e.type, e.text));
} else {
sendServiceError(bus, msg, `The service threw an error.\n${e.stack}`);
}
};
if (method.name === member && method.inSignature === signature) {
try {
result = method.fn.apply(iface, msg.body);
} catch (e) {
handleError(e);
return true;
}
const sendReply = (body) => {
if (method.noReply) return;
if (body === undefined) {
body = [];
} else if (method.outSignatureTree.length === 1) {
body = [body];
} else if (method.outSignatureTree.length === 0) {
return sendServiceError(bus, msg, `method ${iface.$name}.${method.name} was not expected to return a body.`);
} else if (!Array.isArray(body)) {
return sendServiceError(bus, msg, `method ${iface.$name}.${method.name} expected to return multiple arguments in an array (signature: '${method.outSignature}')`);
}
if (method.outSignatureTree.length !== body.length) {
return sendServiceError(bus, msg, `method ${iface.$name}.${m} returned the wrong number of arguments (got ${body.length} expected ${method.outSignatureTree.length}) for signature '${method.outSignature}'`);
}
bus.send(Message.newMethodReturn(msg, method.outSignature, body));
};
if (result && result.constructor === Promise) {
result.then(sendReply).catch(handleError);
} else {
sendReply(result);
}
return true;
}
}
return false;
}
module.exports = handleMessage;