@homebridge/dbus-native
Version:
D-bus protocol implementation in native javascript
202 lines (192 loc) • 7.79 kB
JavaScript
const constants = require('./constants');
const parseSignature = require('./signature');
// TODO: use xmlbuilder
var xmlHeader =
'<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"\n' +
' "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">';
var stdIfaces;
module.exports = function (msg, bus) {
if (
msg['interface'] === 'org.freedesktop.DBus.Introspectable' &&
msg.member === 'Introspect'
) {
if (msg.path === '/') msg.path = '';
var resultXml = [xmlHeader];
var nodes = {};
// TODO: this is not very efficiant for large number of exported objects
// need to build objects tree as they are exported and walk this tree on introspect request
for (var path in bus.exportedObjects) {
if (path.indexOf(msg.path) === 0) {
// objects path starts with requested
var introspectableObj = bus.exportedObjects[msg.path];
if (introspectableObj) {
nodes[msg.path] = introspectableObj;
} else {
if (path[msg.path.length] !== '/') continue;
var localPath = path.substr(msg.path.length);
var pathParts = localPath.split('/');
var localName = pathParts[1];
nodes[localName] = null;
}
}
}
var length = Object.keys(nodes).length;
if (length === 0) {
resultXml.push('<node/>');
} else if (length === 1) {
var obj = nodes[Object.keys(nodes)[0]];
if (obj) {
resultXml.push('<node>');
for (var ifaceNode in obj) {
resultXml.push(interfaceToXML(obj[ifaceNode][0]));
}
resultXml.push(stdIfaces);
resultXml.push('</node>');
} else {
resultXml.push(
`<node>\n <node name="${Object.keys(nodes)[0]}"/>\n </node>`
);
}
} else {
resultXml.push('<node>');
for (var name in nodes) {
if (nodes[name] === null) {
resultXml.push(` <node name="${name}" />`);
} else {
obj = nodes[name];
resultXml.push(` <node name="${name}" >`);
for (var ifaceName in obj) {
resultXml.push(interfaceToXML(obj[ifaceName][0]));
}
resultXml.push(stdIfaces);
resultXml.push(' </node>');
}
}
resultXml.push('</node>');
}
const introspectableReply = {
type: constants.messageType.methodReturn,
serial: bus.serial++,
replySerial: msg.serial,
destination: msg.sender,
signature: 's',
body: [resultXml.join('\n')]
};
bus.connection.message(introspectableReply);
return 1;
} else if (msg['interface'] === 'org.freedesktop.DBus.Properties') {
var interfaceName = msg.body[0];
var propertiesObj = bus.exportedObjects[msg.path];
// TODO: !propertiesObj -> UnknownObject http://www.freedesktop.org/wiki/Software/DBusBindingErrors
if (!propertiesObj || !propertiesObj[interfaceName]) {
// TODO:
bus.sendError(
msg,
'org.freedesktop.DBus.Error.UnknownMethod',
'Uh oh oh'
);
return 1;
}
var impl = propertiesObj[interfaceName][1];
const propertiesReply = {
type: constants.messageType.methodReturn,
serial: bus.serial++,
replySerial: msg.serial,
destination: msg.sender
};
if (msg.member === 'Get' || msg.member === 'Set') {
var propertyName = msg.body[1];
var propType = propertiesObj[interfaceName][0].properties[propertyName];
if (msg.member === 'Get') {
var propValue = impl[propertyName];
propertiesReply.signature = 'v';
propertiesReply.body = [[propType, propValue]];
} else {
impl[propertyName] = 1234; // TODO: read variant and set property value
}
} else if (msg.member === 'GetAll') {
propertiesReply.signature = 'a{sv}';
var props = [];
for (var p in propertiesObj[interfaceName][0].properties) {
var propertySignature = propertiesObj[interfaceName][0].properties[p];
props.push([p, [propertySignature, impl[p]]]);
}
propertiesReply.body = [props];
}
bus.connection.message(propertiesReply);
return 1;
} else if (msg['interface'] === 'org.freedesktop.DBus.Peer') {
// TODO: implement bus.replyTo(srcMsg, signature, body) method
const peerReply = {
type: constants.messageType.methodReturn,
serial: bus.serial++,
replySerial: msg.serial,
destination: msg.sender
};
if (msg.member === 'Ping') {
// empty body
} else if (msg.member === 'GetMachineId') {
peerReply.signature = 's';
peerReply.body = ['This is a machine id. TODO: implement'];
}
bus.connection.message(peerReply);
return 1;
}
return 0;
};
// TODO: move to introspect.js
function interfaceToXML(iface) {
var result = [];
var dumpArgs = function (argsSignature, argsNames, direction) {
if (!argsSignature) return;
var args = parseSignature(argsSignature);
args.forEach(function (arg, num) {
var argName = argsNames ? argsNames[num] : direction + num;
var dirStr = direction === 'signal' ? '' : `" direction="${direction}`;
result.push(
` <arg type="${dumpSignature([
arg
])}" name="${argName}${dirStr}" />`
);
});
};
result.push(` <interface name="${iface.name}">`);
if (iface.methods) {
for (var methodName in iface.methods) {
var method = iface.methods[methodName];
result.push(` <method name="${methodName}">`);
dumpArgs(method[0], method[2], 'in');
dumpArgs(method[1], method[3], 'out');
result.push(' </method>');
}
}
if (iface.signals) {
for (var signalName in iface.signals) {
var signal = iface.signals[signalName];
result.push(` <signal name="${signalName}">`);
dumpArgs(signal[0], signal.slice(1), 'signal');
result.push(' </signal>');
}
}
if (iface.properties) {
for (const propertyName in iface.properties) {
// TODO: decide how to encode access
result.push(
` <property name="${propertyName}" type="${iface.properties[propertyName]}" access="readwrite"/>`
);
}
}
result.push(' </interface>');
return result.join('\n');
}
function dumpSignature(s) {
var result = [];
s.forEach(function (sig) {
result.push(sig.type + dumpSignature(sig.child));
if (sig.type === '{') result.push('}');
if (sig.type === '(') result.push(')');
});
return result.join('');
}
stdIfaces =
' <interface name="org.freedesktop.DBus.Properties">\n <method name="Get">\n <arg type="s" name="interface_name" direction="in"/>\n <arg type="s" name="property_name" direction="in"/>\n <arg type="v" name="value" direction="out"/>\n </method>\n <method name="GetAll">\n <arg type="s" name="interface_name" direction="in"/>\n <arg type="a{sv}" name="properties" direction="out"/>\n </method>\n <method name="Set">\n <arg type="s" name="interface_name" direction="in"/>\n <arg type="s" name="property_name" direction="in"/>\n <arg type="v" name="value" direction="in"/>\n </method>\n <signal name="PropertiesChanged">\n <arg type="s" name="interface_name"/>\n <arg type="a{sv}" name="changed_properties"/>\n <arg type="as" name="invalidated_properties"/>\n </signal>\n </interface>\n <interface name="org.freedesktop.DBus.Introspectable">\n <method name="Introspect">\n <arg type="s" name="xml_data" direction="out"/>\n </method>\n </interface>\n <interface name="org.freedesktop.DBus.Peer">\n <method name="Ping"/>\n <method name="GetMachineId">\n <arg type="s" name="machine_uuid" direction="out"/>\n </method>\n </interface>';