UNPKG

@homebridge/dbus-native

Version:

D-bus protocol implementation in native javascript

202 lines (192 loc) 7.79 kB
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>';