@homebridge/dbus-native 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/bus.js ADDED
@@ -0,0 +1,384 @@
1
+ const EventEmitter = require('events').EventEmitter;
2
+ const constants = require('./constants');
3
+ const stdDbusIfaces = require('./stdifaces');
4
+ const introspect = require('./introspect').introspectBus;
5
+
6
+ module.exports = function bus(conn, opts) {
7
+ if (!(this instanceof bus)) {
8
+ return new bus(conn);
9
+ }
10
+ if (!opts) opts = {};
11
+
12
+ var self = this;
13
+ this.connection = conn;
14
+ this.serial = 1;
15
+ this.cookies = {}; // TODO: rename to methodReturnHandlers
16
+ this.methodCallHandlers = {};
17
+ this.signals = new EventEmitter();
18
+ this.exportedObjects = {};
19
+
20
+ this.invoke = function(msg, callback) {
21
+ if (!msg.type) msg.type = constants.messageType.methodCall;
22
+ msg.serial = self.serial++;
23
+ this.cookies[msg.serial] = callback;
24
+ self.connection.message(msg);
25
+ };
26
+
27
+ this.invokeDbus = function(msg, callback) {
28
+ if (!msg.path) msg.path = '/org/freedesktop/DBus';
29
+ if (!msg.destination) msg.destination = 'org.freedesktop.DBus';
30
+ if (!msg['interface']) msg['interface'] = 'org.freedesktop.DBus';
31
+ self.invoke(msg, callback);
32
+ };
33
+
34
+ this.mangle = function(path, iface, member) {
35
+ var obj = {};
36
+ if (typeof path === 'object') {
37
+ // handle one argumant case mangle(msg)
38
+ obj.path = path.path;
39
+ obj['interface'] = path['interface'];
40
+ obj.member = path.member;
41
+ } else {
42
+ obj.path = path;
43
+ obj['interface'] = iface;
44
+ obj.member = member;
45
+ }
46
+ return JSON.stringify(obj);
47
+ };
48
+
49
+ this.sendSignal = function(path, iface, name, signature, args) {
50
+ var signalMsg = {
51
+ type: constants.messageType.signal,
52
+ serial: self.serial++,
53
+ interface: iface,
54
+ path: path,
55
+ member: name
56
+ };
57
+ if (signature) {
58
+ signalMsg.signature = signature;
59
+ signalMsg.body = args;
60
+ }
61
+ self.connection.message(signalMsg);
62
+ };
63
+
64
+ // Warning: errorName must respect the same rules as interface names (must contain a dot)
65
+ this.sendError = function(msg, errorName, errorText) {
66
+ var reply = {
67
+ type: constants.messageType.error,
68
+ serial: self.serial++,
69
+ replySerial: msg.serial,
70
+ destination: msg.sender,
71
+ errorName: errorName,
72
+ signature: 's',
73
+ body: [errorText]
74
+ };
75
+ this.connection.message(reply);
76
+ };
77
+
78
+ this.sendReply = function(msg, signature, body) {
79
+ var reply = {
80
+ type: constants.messageType.methodReturn,
81
+ serial: self.serial++,
82
+ replySerial: msg.serial,
83
+ destination: msg.sender,
84
+ signature: signature,
85
+ body: body
86
+ };
87
+ this.connection.message(reply);
88
+ };
89
+
90
+ // route reply/error
91
+ this.connection.on('message', function(msg) {
92
+ function invoke(impl, func, resultSignature) {
93
+ Promise.resolve()
94
+ .then(function() {
95
+ return func.apply(impl, (msg.body || []).concat(msg));
96
+ })
97
+ .then(
98
+ function(methodReturnResult) {
99
+ var methodReturnReply = {
100
+ type: constants.messageType.methodReturn,
101
+ serial: self.serial++,
102
+ destination: msg.sender,
103
+ replySerial: msg.serial
104
+ };
105
+ if (methodReturnResult !== null) {
106
+ methodReturnReply.signature = resultSignature;
107
+ methodReturnReply.body = [methodReturnResult];
108
+ }
109
+ self.connection.message(methodReturnReply);
110
+ },
111
+ function(e) {
112
+ self.sendError(
113
+ msg,
114
+ e.dbusName || 'org.freedesktop.DBus.Error.Failed',
115
+ e.message || ''
116
+ );
117
+ }
118
+ );
119
+ }
120
+
121
+ var handler;
122
+ if (
123
+ msg.type === constants.messageType.methodReturn ||
124
+ msg.type === constants.messageType.error
125
+ ) {
126
+ handler = self.cookies[msg.replySerial];
127
+ if (handler) {
128
+ delete self.cookies[msg.replySerial];
129
+ var props = {
130
+ connection: self.connection,
131
+ bus: self,
132
+ message: msg,
133
+ signature: msg.signature
134
+ };
135
+ var args = msg.body || [];
136
+ if (msg.type === constants.messageType.methodReturn) {
137
+ args = [null].concat(args); // first argument - no errors, null
138
+ handler.apply(props, args); // body as array of arguments
139
+ } else {
140
+ handler.call(props, args); // body as first argument
141
+ }
142
+ }
143
+ } else if (msg.type === constants.messageType.signal) {
144
+ self.signals.emit(self.mangle(msg), msg.body, msg.signature);
145
+ } else {
146
+ // methodCall
147
+
148
+ if (stdDbusIfaces(msg, self)) return;
149
+
150
+ // exported interfaces handlers
151
+ var obj, iface, impl;
152
+ if ((obj = self.exportedObjects[msg.path])) {
153
+ if ((iface = obj[msg['interface']])) {
154
+ // now we are ready to serve msg.member
155
+ impl = iface[1];
156
+ var func = impl[msg.member];
157
+ if (!func) {
158
+ self.sendError(
159
+ msg,
160
+ 'org.freedesktop.DBus.Error.UnknownMethod',
161
+ `Method "${msg.member}" on interface "${
162
+ msg.interface
163
+ }" doesn't exist`
164
+ );
165
+ return;
166
+ }
167
+ // TODO safety check here
168
+ var resultSignature = iface[0].methods[msg.member][1];
169
+ invoke(impl, func, resultSignature);
170
+ return;
171
+ } else {
172
+ console.error(`Interface ${msg['interface']} is not supported`);
173
+ // TODO: respond with standard dbus error
174
+ }
175
+ }
176
+ // setMethodCall handlers
177
+ handler = self.methodCallHandlers[self.mangle(msg)];
178
+ if (handler) {
179
+ invoke(null, handler[0], handler[1]);
180
+ } else {
181
+ self.sendError(
182
+ msg,
183
+ 'org.freedesktop.DBus.Error.UnknownService',
184
+ 'Uh oh oh'
185
+ );
186
+ }
187
+ }
188
+ });
189
+
190
+ this.setMethodCallHandler = function(objectPath, iface, member, handler) {
191
+ var key = self.mangle(objectPath, iface, member);
192
+ self.methodCallHandlers[key] = handler;
193
+ };
194
+
195
+ this.exportInterface = function(obj, path, iface) {
196
+ var entry;
197
+ if (!self.exportedObjects[path]) {
198
+ entry = self.exportedObjects[path] = {};
199
+ } else {
200
+ entry = self.exportedObjects[path];
201
+ }
202
+ entry[iface.name] = [iface, obj];
203
+ // monkey-patch obj.emit()
204
+ if (typeof obj.emit === 'function') {
205
+ var oldEmit = obj.emit;
206
+ obj.emit = function() {
207
+ var args = Array.prototype.slice.apply(arguments);
208
+ var signalName = args[0];
209
+ if (!signalName) throw new Error('Trying to emit undefined signa');
210
+
211
+ //send signal to bus
212
+ var signal;
213
+ if (iface.signals && iface.signals[signalName]) {
214
+ signal = iface.signals[signalName];
215
+ var signalMsg = {
216
+ type: constants.messageType.signal,
217
+ serial: self.serial++,
218
+ interface: iface.name,
219
+ path: path,
220
+ member: signalName
221
+ };
222
+ if (signal[0]) {
223
+ signalMsg.signature = signal[0];
224
+ signalMsg.body = args.slice(1);
225
+ }
226
+ self.connection.message(signalMsg);
227
+ self.serial++;
228
+ }
229
+ // note that local emit is likely to be called before signal arrives
230
+ // to remote subscriber
231
+ oldEmit.apply(obj, args);
232
+ };
233
+ }
234
+ // TODO: emit ObjectManager's InterfaceAdded
235
+ };
236
+
237
+ // register name
238
+ if (opts.direct !== true) {
239
+ this.invokeDbus({ member: 'Hello' }, function(err, name) {
240
+ if (err) throw new Error(err);
241
+ self.name = name;
242
+ });
243
+ } else {
244
+ self.name = null;
245
+ }
246
+
247
+ function DBusObject(name, service) {
248
+ this.name = name;
249
+ this.service = service;
250
+ this.as = function(name) {
251
+ return this.proxy[name];
252
+ };
253
+ }
254
+
255
+ function DBusService(name, bus) {
256
+ this.name = name;
257
+ this.bus = bus;
258
+ this.getObject = function(name, callback) {
259
+ if (name === undefined)
260
+ return callback(new Error('Object name is null or undefined'));
261
+ var obj = new DBusObject(name, this);
262
+ introspect(obj, function(err, ifaces, nodes) {
263
+ if (err) return callback(err);
264
+ obj.proxy = ifaces;
265
+ obj.nodes = nodes;
266
+ callback(null, obj);
267
+ });
268
+ };
269
+
270
+ this.getInterface = function(objName, ifaceName, callback) {
271
+ this.getObject(objName, function(err, obj) {
272
+ if (err) return callback(err);
273
+ callback(null, obj.as(ifaceName));
274
+ });
275
+ };
276
+ }
277
+
278
+ this.getService = function(name) {
279
+ return new DBusService(name, this);
280
+ };
281
+
282
+ this.getObject = function(path, name, callback) {
283
+ var service = this.getService(path);
284
+ return service.getObject(name, callback);
285
+ };
286
+
287
+ this.getInterface = function(path, objname, name, callback) {
288
+ return this.getObject(path, objname, function(err, obj) {
289
+ if (err) return callback(err);
290
+ callback(null, obj.as(name));
291
+ });
292
+ };
293
+
294
+ // TODO: refactor
295
+
296
+ // bus meta functions
297
+ this.addMatch = function(match, callback) {
298
+ this.invokeDbus(
299
+ { member: 'AddMatch', signature: 's', body: [match] },
300
+ callback
301
+ );
302
+ };
303
+
304
+ this.removeMatch = function(match, callback) {
305
+ this.invokeDbus(
306
+ { member: 'RemoveMatch', signature: 's', body: [match] },
307
+ callback
308
+ );
309
+ };
310
+
311
+ this.getId = function(callback) {
312
+ this.invokeDbus({ member: 'GetId' }, callback);
313
+ };
314
+
315
+ this.requestName = function(name, flags, callback) {
316
+ this.invokeDbus(
317
+ { member: 'RequestName', signature: 'su', body: [name, flags] },
318
+ function(err, name) {
319
+ if (callback) callback(err, name);
320
+ }
321
+ );
322
+ };
323
+
324
+ this.releaseName = function(name, callback) {
325
+ this.invokeDbus(
326
+ { member: 'ReleaseName', signature: 's', body: [name] },
327
+ callback
328
+ );
329
+ };
330
+
331
+ this.listNames = function(callback) {
332
+ this.invokeDbus({ member: 'ListNames' }, callback);
333
+ };
334
+
335
+ this.listActivatableNames = function(callback) {
336
+ this.invokeDbus({ member: 'ListActivatableNames' }, callback);
337
+ };
338
+
339
+ this.updateActivationEnvironment = function(env, callback) {
340
+ this.invokeDbus(
341
+ {
342
+ member: 'UpdateActivationEnvironment',
343
+ signature: 'a{ss}',
344
+ body: [env]
345
+ },
346
+ callback
347
+ );
348
+ };
349
+
350
+ this.startServiceByName = function(name, flags, callback) {
351
+ this.invokeDbus(
352
+ { member: 'StartServiceByName', signature: 'su', body: [name, flags] },
353
+ callback
354
+ );
355
+ };
356
+
357
+ this.getConnectionUnixUser = function(name, callback) {
358
+ this.invokeDbus(
359
+ { member: 'GetConnectionUnixUser', signature: 's', body: [name] },
360
+ callback
361
+ );
362
+ };
363
+
364
+ this.getConnectionUnixProcessId = function(name, callback) {
365
+ this.invokeDbus(
366
+ { member: 'GetConnectionUnixProcessID', signature: 's', body: [name] },
367
+ callback
368
+ );
369
+ };
370
+
371
+ this.getNameOwner = function(name, callback) {
372
+ this.invokeDbus(
373
+ { member: 'GetNameOwner', signature: 's', body: [name] },
374
+ callback
375
+ );
376
+ };
377
+
378
+ this.nameHasOwner = function(name, callback) {
379
+ this.invokeDbus(
380
+ { member: 'NameHasOwner', signature: 's', body: [name] },
381
+ callback
382
+ );
383
+ };
384
+ };
@@ -0,0 +1,54 @@
1
+ module.exports = {
2
+ messageType: {
3
+ invalid: 0,
4
+ methodCall: 1,
5
+ methodReturn: 2,
6
+ error: 3,
7
+ signal: 4
8
+ },
9
+
10
+ headerTypeName: [
11
+ null,
12
+ 'path',
13
+ 'interface',
14
+ 'member',
15
+ 'errorName',
16
+ 'replySerial',
17
+ 'destination',
18
+ 'sender',
19
+ 'signature'
20
+ ],
21
+
22
+ // TODO: merge to single hash? e.g path -> [1, 'o']
23
+ fieldSignature: {
24
+ path: 'o',
25
+ interface: 's',
26
+ member: 's',
27
+ errorName: 's',
28
+ replySerial: 'u',
29
+ destination: 's',
30
+ sender: 's',
31
+ signature: 'g'
32
+ },
33
+ headerTypeId: {
34
+ path: 1,
35
+ interface: 2,
36
+ member: 3,
37
+ errorName: 4,
38
+ replySerial: 5,
39
+ destination: 6,
40
+ sender: 7,
41
+ signature: 8
42
+ },
43
+ protocolVersion: 1,
44
+ flags: {
45
+ noReplyExpected: 1,
46
+ noAutoStart: 2
47
+ },
48
+ endianness: {
49
+ le: 108,
50
+ be: 66
51
+ },
52
+ messageSignature: 'yyyyuua(yv)',
53
+ defaultAuthMethods: ['EXTERNAL', 'DBUS_COOKIE_SHA1', 'ANONYMOUS']
54
+ };
@@ -0,0 +1,182 @@
1
+ const Long = require('long');
2
+ const parseSignature = require('./signature');
3
+
4
+ // Buffer + position + global start position ( used in alignment )
5
+ function DBusBuffer(buffer, startPos, options) {
6
+ if (typeof options !== 'object') {
7
+ options = { ayBuffer: true, ReturnLongjs: false };
8
+ } else if (options.ayBuffer === undefined) {
9
+ // default settings object
10
+ options.ayBuffer = true; // enforce truthy default props
11
+ }
12
+ this.options = options;
13
+ this.buffer = buffer;
14
+ (this.startPos = startPos ? startPos : 0), (this.pos = 0);
15
+ }
16
+
17
+ DBusBuffer.prototype.align = function(power) {
18
+ var allbits = (1 << power) - 1;
19
+ var paddedOffset = ((this.pos + this.startPos + allbits) >> power) << power;
20
+ this.pos = paddedOffset - this.startPos;
21
+ };
22
+
23
+ DBusBuffer.prototype.readInt8 = function() {
24
+ this.pos++;
25
+ return this.buffer[this.pos - 1];
26
+ };
27
+
28
+ DBusBuffer.prototype.readSInt16 = function() {
29
+ this.align(1);
30
+ var res = this.buffer.readInt16LE(this.pos);
31
+ this.pos += 2;
32
+ return res;
33
+ };
34
+
35
+ DBusBuffer.prototype.readInt16 = function() {
36
+ this.align(1);
37
+ var res = this.buffer.readUInt16LE(this.pos);
38
+ this.pos += 2;
39
+ return res;
40
+ };
41
+
42
+ DBusBuffer.prototype.readSInt32 = function() {
43
+ this.align(2);
44
+ var res = this.buffer.readInt32LE(this.pos);
45
+ this.pos += 4;
46
+ return res;
47
+ };
48
+
49
+ DBusBuffer.prototype.readInt32 = function() {
50
+ this.align(2);
51
+ var res = this.buffer.readUInt32LE(this.pos);
52
+ this.pos += 4;
53
+ return res;
54
+ };
55
+
56
+ DBusBuffer.prototype.readDouble = function() {
57
+ this.align(3);
58
+ var res = this.buffer.readDoubleLE(this.pos);
59
+ this.pos += 8;
60
+ return res;
61
+ };
62
+
63
+ DBusBuffer.prototype.readString = function(len) {
64
+ if (len === 0) {
65
+ this.pos++;
66
+ return '';
67
+ }
68
+ var res = this.buffer.toString('utf8', this.pos, this.pos + len);
69
+ this.pos += len + 1; // dbus strings are always zero-terminated ('s' and 'g' types)
70
+ return res;
71
+ };
72
+
73
+ DBusBuffer.prototype.readTree = function readTree(tree) {
74
+ switch (tree.type) {
75
+ case '(':
76
+ case '{':
77
+ case 'r':
78
+ this.align(3);
79
+ return this.readStruct(tree.child);
80
+ case 'a':
81
+ if (!tree.child || tree.child.length !== 1)
82
+ throw new Error('Incorrect array element signature');
83
+ var arrayBlobLength = this.readInt32();
84
+ return this.readArray(tree.child[0], arrayBlobLength);
85
+ case 'v':
86
+ return this.readVariant();
87
+ default:
88
+ return this.readSimpleType(tree.type);
89
+ }
90
+ };
91
+
92
+ DBusBuffer.prototype.read = function read(signature) {
93
+ var tree = parseSignature(signature);
94
+ return this.readStruct(tree);
95
+ };
96
+
97
+ DBusBuffer.prototype.readVariant = function readVariant() {
98
+ var signature = this.readSimpleType('g');
99
+ var tree = parseSignature(signature);
100
+ return [tree, this.readStruct(tree)];
101
+ };
102
+
103
+ DBusBuffer.prototype.readStruct = function readStruct(struct) {
104
+ var result = [];
105
+ for (var i = 0; i < struct.length; ++i) {
106
+ result.push(this.readTree(struct[i]));
107
+ }
108
+ return result;
109
+ };
110
+
111
+ DBusBuffer.prototype.readArray = function readArray(eleType, arrayBlobSize) {
112
+ var result;
113
+ var start = this.pos;
114
+
115
+ // special case: treat ay as Buffer
116
+ if (eleType.type === 'y' && this.options.ayBuffer) {
117
+ this.pos += arrayBlobSize;
118
+ return this.buffer.slice(start, this.pos);
119
+ }
120
+
121
+ // end of array is start of first element + array size
122
+ // we need to add 4 bytes if not on 8-byte boundary
123
+ // and array element needs 8 byte alignment
124
+ if (['x', 't', 'd', '{', '(', 'r'].indexOf(eleType.type) !== -1)
125
+ this.align(3);
126
+ var end = this.pos + arrayBlobSize;
127
+ result = [];
128
+ while (this.pos < end) result.push(this.readTree(eleType));
129
+ return result;
130
+ };
131
+
132
+ DBusBuffer.prototype.readSimpleType = function readSimpleType(t) {
133
+ var data, len, word0, word1;
134
+ switch (t) {
135
+ case 'y':
136
+ return this.readInt8();
137
+ case 'b':
138
+ // TODO: spec says that true is strictly 1 and false is strictly 0
139
+ // shold we error (or warn?) when non 01 values?
140
+ return this.readInt32() ? true : false;
141
+ case 'n':
142
+ return this.readSInt16();
143
+ case 'q':
144
+ return this.readInt16();
145
+ case 'u':
146
+ return this.readInt32();
147
+ case 'i':
148
+ return this.readSInt32();
149
+ case 'g':
150
+ len = this.readInt8();
151
+ return this.readString(len);
152
+ case 's':
153
+ case 'o':
154
+ len = this.readInt32();
155
+ return this.readString(len);
156
+ // TODO: validate object path here
157
+ //if (t === 'o' && !isValidObjectPath(str))
158
+ // throw new Error('string is not a valid object path'));
159
+ case 'x':
160
+ //signed
161
+ this.align(3);
162
+ word0 = this.readInt32();
163
+ word1 = this.readInt32();
164
+ data = Long.fromBits(word0, word1, false);
165
+ if (this.options.ReturnLongjs) return data;
166
+ return data.toNumber(); // convert to number (good up to 53 bits)
167
+ case 't':
168
+ //unsigned
169
+ this.align(3);
170
+ word0 = this.readInt32();
171
+ word1 = this.readInt32();
172
+ data = Long.fromBits(word0, word1, true);
173
+ if (this.options.ReturnLongjs) return data;
174
+ return data.toNumber(); // convert to number (good up to 53 bits)
175
+ case 'd':
176
+ return this.readDouble();
177
+ default:
178
+ throw new Error(`Unsupported type: ${t}`);
179
+ }
180
+ };
181
+
182
+ module.exports = DBusBuffer;