@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.
@@ -0,0 +1,336 @@
1
+ const Buffer = require('safe-buffer').Buffer;
2
+ const align = require('./align').align;
3
+ const parseSignature = require('../lib/signature');
4
+ const Long = require('long');
5
+ /**
6
+ * MakeSimpleMarshaller
7
+ * @param signature - the signature of the data you want to check
8
+ * @returns a simple marshaller with the "check" method
9
+ *
10
+ * check returns nothing - it only raises errors if the data is
11
+ * invalid for the signature
12
+ */
13
+ var MakeSimpleMarshaller = function(signature) {
14
+ var marshaller = {};
15
+ function checkValidString(data) {
16
+ if (typeof data !== 'string') {
17
+ throw new Error(`Data: ${data} was not of type string`);
18
+ } else if (data.indexOf('\0') !== -1) {
19
+ throw new Error('String contains null byte');
20
+ }
21
+ }
22
+
23
+ function checkValidSignature(data) {
24
+ if (data.length > 0xff) {
25
+ throw new Error(
26
+ `Data: ${data} is too long for signature type (${data.length} > 255)`
27
+ );
28
+ }
29
+
30
+ var parenCount = 0;
31
+ for (var ii = 0; ii < data.length; ++ii) {
32
+ if (parenCount > 32) {
33
+ throw new Error(
34
+ `Maximum container type nesting exceeded in signature type:${data}`
35
+ );
36
+ }
37
+ switch (data[ii]) {
38
+ case '(':
39
+ ++parenCount;
40
+ break;
41
+ case ')':
42
+ --parenCount;
43
+ break;
44
+ default:
45
+ /* no-op */
46
+ break;
47
+ }
48
+ }
49
+ parseSignature(data);
50
+ }
51
+
52
+ switch (signature) {
53
+ case 'o':
54
+ // object path
55
+ // TODO: verify object path here?
56
+ case 's': // eslint-disable-line no-fallthrough
57
+ //STRING
58
+ marshaller.check = function(data) {
59
+ checkValidString(data);
60
+ };
61
+ marshaller.marshall = function(ps, data) {
62
+ this.check(data);
63
+ // utf8 string
64
+ align(ps, 4);
65
+ const buff = Buffer.from(data, 'utf8');
66
+ ps
67
+ .word32le(buff.length)
68
+ .put(buff)
69
+ .word8(0);
70
+ ps._offset += 5 + buff.length;
71
+ };
72
+ break;
73
+ case 'g':
74
+ //SIGNATURE
75
+ marshaller.check = function(data) {
76
+ checkValidString(data);
77
+ checkValidSignature(data);
78
+ };
79
+ marshaller.marshall = function(ps, data) {
80
+ this.check(data);
81
+ // signature
82
+ const buff = Buffer.from(data, 'ascii');
83
+ ps
84
+ .word8(data.length)
85
+ .put(buff)
86
+ .word8(0);
87
+ ps._offset += 2 + buff.length;
88
+ };
89
+ break;
90
+ case 'y':
91
+ //BYTE
92
+ marshaller.check = function(data) {
93
+ checkInteger(data);
94
+ checkRange(0x00, 0xff, data);
95
+ };
96
+ marshaller.marshall = function(ps, data) {
97
+ this.check(data);
98
+ ps.word8(data);
99
+ ps._offset++;
100
+ };
101
+ break;
102
+ case 'b':
103
+ //BOOLEAN
104
+ marshaller.check = function(data) {
105
+ checkBoolean(data);
106
+ };
107
+ marshaller.marshall = function(ps, data) {
108
+ this.check(data);
109
+ // booleans serialised as 0/1 unsigned 32 bit int
110
+ data = data ? 1 : 0;
111
+ align(ps, 4);
112
+ ps.word32le(data);
113
+ ps._offset += 4;
114
+ };
115
+ break;
116
+ case 'n':
117
+ //INT16
118
+ marshaller.check = function(data) {
119
+ checkInteger(data);
120
+ checkRange(-0x7fff - 1, 0x7fff, data);
121
+ };
122
+ marshaller.marshall = function(ps, data) {
123
+ this.check(data);
124
+ align(ps, 2);
125
+ const buff = Buffer.alloc(2);
126
+ buff.writeInt16LE(parseInt(data), 0);
127
+ ps.put(buff);
128
+ ps._offset += 2;
129
+ };
130
+ break;
131
+ case 'q':
132
+ //UINT16
133
+ marshaller.check = function(data) {
134
+ checkInteger(data);
135
+ checkRange(0, 0xffff, data);
136
+ };
137
+ marshaller.marshall = function(ps, data) {
138
+ this.check(data);
139
+ align(ps, 2);
140
+ ps.word16le(data);
141
+ ps._offset += 2;
142
+ };
143
+ break;
144
+ case 'i':
145
+ //INT32
146
+ marshaller.check = function(data) {
147
+ checkInteger(data);
148
+ checkRange(-0x7fffffff - 1, 0x7fffffff, data);
149
+ };
150
+ marshaller.marshall = function(ps, data) {
151
+ this.check(data);
152
+ align(ps, 4);
153
+ const buff = Buffer.alloc(4);
154
+ buff.writeInt32LE(parseInt(data), 0);
155
+ ps.put(buff);
156
+ ps._offset += 4;
157
+ };
158
+ break;
159
+ case 'u':
160
+ //UINT32
161
+ marshaller.check = function(data) {
162
+ checkInteger(data);
163
+ checkRange(0, 0xffffffff, data);
164
+ };
165
+ marshaller.marshall = function(ps, data) {
166
+ this.check(data);
167
+ // 32 t unsigned int
168
+ align(ps, 4);
169
+ ps.word32le(data);
170
+ ps._offset += 4;
171
+ };
172
+ break;
173
+ case 't':
174
+ //UINT64
175
+ marshaller.check = function(data) {
176
+ return checkLong(data, false);
177
+ };
178
+ marshaller.marshall = function(ps, data) {
179
+ data = this.check(data);
180
+ align(ps, 8);
181
+ ps.word32le(data.low);
182
+ ps.word32le(data.high);
183
+ ps._offset += 8;
184
+ };
185
+ break;
186
+ case 'x':
187
+ //INT64
188
+ marshaller.check = function(data) {
189
+ return checkLong(data, true);
190
+ };
191
+ marshaller.marshall = function(ps, data) {
192
+ data = this.check(data);
193
+ align(ps, 8);
194
+ ps.word32le(data.low);
195
+ ps.word32le(data.high);
196
+ ps._offset += 8;
197
+ };
198
+ break;
199
+ case 'd':
200
+ //DOUBLE
201
+ marshaller.check = function(data) {
202
+ if (typeof data !== 'number') {
203
+ throw new Error(`Data: ${data} was not of type number`);
204
+ } else if (Number.isNaN(data)) {
205
+ throw new Error(`Data: ${data} was not a number`);
206
+ } else if (!Number.isFinite(data)) {
207
+ throw new Error('Number outside range');
208
+ }
209
+ };
210
+ marshaller.marshall = function(ps, data) {
211
+ this.check(data);
212
+ align(ps, 8);
213
+ const buff = Buffer.alloc(8);
214
+ buff.writeDoubleLE(parseFloat(data), 0);
215
+ ps.put(buff);
216
+ ps._offset += 8;
217
+ };
218
+ break;
219
+ default:
220
+ throw new Error(`Unknown data type format: ${signature}`);
221
+ }
222
+ return marshaller;
223
+ };
224
+ exports.MakeSimpleMarshaller = MakeSimpleMarshaller;
225
+
226
+ var checkRange = function(minValue, maxValue, data) {
227
+ if (data > maxValue || data < minValue) {
228
+ throw new Error('Number outside range');
229
+ }
230
+ };
231
+
232
+ var checkInteger = function(data) {
233
+ if (typeof data !== 'number') {
234
+ throw new Error(`Data: ${data} was not of type number`);
235
+ }
236
+ if (Math.floor(data) !== data) {
237
+ throw new Error(`Data: ${data} was not an integer`);
238
+ }
239
+ };
240
+
241
+ var checkBoolean = function(data) {
242
+ if (!(typeof data === 'boolean' || data === 0 || data === 1))
243
+ throw new Error(`Data: ${data} was not of type boolean`);
244
+ };
245
+
246
+ // This is essentially a tweaked version of 'fromValue' from Long.js with error checking.
247
+ // This can take number or string of decimal characters or 'Long' instance (or Long-style object with props low,high,unsigned).
248
+ var makeLong = function(val, signed) {
249
+ if (val instanceof Long) return val;
250
+ if (val instanceof Number) val = val.valueOf();
251
+ if (typeof val === 'number') {
252
+ try {
253
+ // Long.js won't alert you to precision loss in passing more than 53 bit ints through a double number, so we check here
254
+ checkInteger(val);
255
+ if (signed) {
256
+ checkRange(-0x1fffffffffffff, 0x1fffffffffffff, val);
257
+ } else {
258
+ checkRange(0, 0x1fffffffffffff, val);
259
+ }
260
+ } catch (e) {
261
+ e.message += ' (Number type can only carry 53 bit integer)';
262
+ throw e;
263
+ }
264
+ try {
265
+ return Long.fromNumber(val, !signed);
266
+ } catch (e) {
267
+ e.message = `Error converting number to 64bit integer "${e.message}"`;
268
+ throw e;
269
+ }
270
+ }
271
+ if (typeof val === 'string' || val instanceof String) {
272
+ var radix = 10;
273
+ val = val.trim().toUpperCase(); // remove extra whitespace and make uppercase (for hex)
274
+ if (val.substring(0, 2) === '0X') {
275
+ radix = 16;
276
+ val = val.substring(2);
277
+ } else if (val.substring(0, 3) === '-0X') {
278
+ // unusual, but just in case?
279
+ radix = 16;
280
+ val = `-${val.substring(3)}`;
281
+ }
282
+ val = val.replace(/^0+(?=\d)/, ''); // dump leading zeroes
283
+ var data;
284
+ try {
285
+ data = Long.fromString(val, !signed, radix);
286
+ } catch (e) {
287
+ e.message = `Error converting string to 64bit integer '${e.message}'`;
288
+ throw e;
289
+ }
290
+ // If string represents a number outside of 64 bit range, it can quietly overflow.
291
+ // We assume if things converted correctly the string coming out of Long should match what went into it.
292
+ if (data.toString(radix).toUpperCase() !== val)
293
+ throw new Error(
294
+ `Data: '${val}' did not convert correctly to ${
295
+ signed ? 'signed' : 'unsigned'
296
+ } 64 bit`
297
+ );
298
+ return data;
299
+ }
300
+ // Throws for non-objects, converts non-instanceof Long:
301
+ try {
302
+ return Long.fromBits(val.low, val.high, val.unsigned);
303
+ } catch (e) {
304
+ e.message = `Error converting object to 64bit integer '${e.message}'`;
305
+ throw e;
306
+ }
307
+ };
308
+
309
+ var checkLong = function(data, signed) {
310
+ if (!Long.isLong(data)) {
311
+ data = makeLong(data, signed);
312
+ }
313
+
314
+ // Do we enforce that Long.js object unsigned/signed match the field even if it is still in range?
315
+ // Probably, might help users avoid unintended bugs?
316
+ if (signed) {
317
+ if (data.unsigned)
318
+ throw new Error(
319
+ 'Longjs object is unsigned, but marshalling into signed 64 bit field'
320
+ );
321
+ if (data.gt(Long.MAX_VALUE) || data.lt(Long.MIN_VALUE)) {
322
+ throw new Error(`Data: ${data} was out of range (64-bit signed)`);
323
+ }
324
+ } else {
325
+ if (!data.unsigned)
326
+ throw new Error(
327
+ 'Longjs object is signed, but marshalling into unsigned 64 bit field'
328
+ );
329
+ // NOTE: data.gt(Long.MAX_UNSIGNED_VALUE) will catch if Long.js object is a signed value but is still within unsigned range!
330
+ // Since we are enforcing signed type matching between Long.js object and field, this note should not matter.
331
+ if (data.gt(Long.MAX_UNSIGNED_VALUE) || data.lt(0)) {
332
+ throw new Error(`Data: ${data} was out of range (64-bit unsigned)`);
333
+ }
334
+ }
335
+ return data;
336
+ };
package/lib/message.js ADDED
@@ -0,0 +1,118 @@
1
+ const Buffer = require('safe-buffer').Buffer;
2
+ const marshall = require('./marshall');
3
+ const constants = require('./constants');
4
+ const DBusBuffer = require('./dbus-buffer');
5
+
6
+ const headerSignature = require('./header-signature.json');
7
+
8
+ module.exports.unmarshalMessages = function messageParser(
9
+ stream,
10
+ onMessage,
11
+ opts
12
+ ) {
13
+ var state = 0; // 0: header, 1: fields + body
14
+ var header, fieldsAndBody;
15
+ var fieldsLength, fieldsLengthPadded;
16
+ var fieldsAndBodyLength = 0;
17
+ var bodyLength = 0;
18
+ stream.on('readable', function() {
19
+ while (1) {
20
+ if (state === 0) {
21
+ header = stream.read(16);
22
+ if (!header) break;
23
+ state = 1;
24
+
25
+ fieldsLength = header.readUInt32LE(12);
26
+ fieldsLengthPadded = ((fieldsLength + 7) >> 3) << 3;
27
+ bodyLength = header.readUInt32LE(4);
28
+ fieldsAndBodyLength = fieldsLengthPadded + bodyLength;
29
+ } else {
30
+ fieldsAndBody = stream.read(fieldsAndBodyLength);
31
+ if (!fieldsAndBody) break;
32
+ state = 0;
33
+
34
+ var messageBuffer = new DBusBuffer(fieldsAndBody, undefined, opts);
35
+ var unmarshalledHeader = messageBuffer.readArray(
36
+ headerSignature[0].child[0],
37
+ fieldsLength
38
+ );
39
+ messageBuffer.align(3);
40
+ var headerName;
41
+ var message = {};
42
+ message.serial = header.readUInt32LE(8);
43
+
44
+ for (var i = 0; i < unmarshalledHeader.length; ++i) {
45
+ headerName = constants.headerTypeName[unmarshalledHeader[i][0]];
46
+ message[headerName] = unmarshalledHeader[i][1][1][0];
47
+ }
48
+
49
+ message.type = header[1];
50
+ message.flags = header[2];
51
+
52
+ if (bodyLength > 0 && message.signature) {
53
+ message.body = messageBuffer.read(message.signature);
54
+ }
55
+ onMessage(message);
56
+ }
57
+ }
58
+ });
59
+ };
60
+
61
+ // given buffer which contains entire message deserialise it
62
+ // TODO: factor out common code
63
+ module.exports.unmarshall = function unmarshall(buff, opts) {
64
+ var msgBuf = new DBusBuffer(buff, undefined, opts);
65
+ var headers = msgBuf.read('yyyyuua(yv)');
66
+ var message = {};
67
+ for (var i = 0; i < headers[6].length; ++i) {
68
+ var headerName = constants.headerTypeName[headers[6][i][0]];
69
+ message[headerName] = headers[6][i][1][1][0];
70
+ }
71
+ message.type = headers[1];
72
+ message.flags = headers[2];
73
+ message.serial = headers[5];
74
+ msgBuf.align(3);
75
+ message.body = msgBuf.read(message.signature);
76
+ return message;
77
+ };
78
+
79
+ module.exports.marshall = function marshallMessage(message) {
80
+ if (!message.serial) throw new Error('Missing or invalid serial');
81
+ var flags = message.flags || 0;
82
+ var type = message.type || constants.messageType.methodCall;
83
+ var bodyLength = 0;
84
+ var bodyBuff;
85
+ if (message.signature && message.body) {
86
+ bodyBuff = marshall(message.signature, message.body);
87
+ bodyLength = bodyBuff.length;
88
+ }
89
+ var header = [
90
+ constants.endianness.le,
91
+ type,
92
+ flags,
93
+ constants.protocolVersion,
94
+ bodyLength,
95
+ message.serial
96
+ ];
97
+ var headerBuff = marshall('yyyyuu', header);
98
+ var fields = [];
99
+ constants.headerTypeName.forEach(function(fieldName) {
100
+ var fieldVal = message[fieldName];
101
+ if (fieldVal) {
102
+ fields.push([
103
+ constants.headerTypeId[fieldName],
104
+ [constants.fieldSignature[fieldName], fieldVal]
105
+ ]);
106
+ }
107
+ });
108
+ var fieldsBuff = marshall('a(yv)', [fields], 12);
109
+ var headerLenAligned =
110
+ ((headerBuff.length + fieldsBuff.length + 7) >> 3) << 3;
111
+ var messageLen = headerLenAligned + bodyLength;
112
+ var messageBuff = Buffer.alloc(messageLen);
113
+ headerBuff.copy(messageBuff);
114
+ fieldsBuff.copy(messageBuff, headerBuff.length);
115
+ if (bodyLength > 0) bodyBuff.copy(messageBuff, headerLenAligned);
116
+
117
+ return messageBuff;
118
+ };
@@ -0,0 +1,38 @@
1
+ const net = require('net');
2
+ const abs = require('abstract-socket');
3
+ const hexy = require('hexy').hexy;
4
+
5
+ var address = process.env.DBUS_SESSION_BUS_ADDRESS;
6
+ var m = address.match(/abstract=([^,]+)/);
7
+
8
+ net
9
+ .createServer(function(s) {
10
+ var buff = '';
11
+ var connected = false;
12
+ var cli = abs.createConnection(`\0${m[1]}`);
13
+ s.on('data', function(d) {
14
+ if (connected) {
15
+ cli.write(d);
16
+ } else {
17
+ buff += d.toString();
18
+ }
19
+ });
20
+ setTimeout(function() {
21
+ console.log('CONNECTED!');
22
+ connected = true;
23
+ cli.write(buff);
24
+ }, 100);
25
+ cli.pipe(s);
26
+
27
+ cli.on('data', function(b) {
28
+ console.log(hexy(b, { prefix: 'from client ' }));
29
+ });
30
+ s.on('data', function(b) {
31
+ console.log(hexy(b, { prefix: 'from server ' }));
32
+ });
33
+ })
34
+ .listen(3334, function() {
35
+ console.log(
36
+ 'Server started. connect with DBUS_SESSION_BUS_ADDRESS=tcp:host=127.0.0.1,port=3334'
37
+ );
38
+ });
@@ -0,0 +1,23 @@
1
+ const Buffer = require('safe-buffer').Buffer;
2
+
3
+ module.exports = function readOneLine(stream, cb) {
4
+ var bytes = [];
5
+ function readable() {
6
+ while (1) {
7
+ var buf = stream.read(1);
8
+ if (!buf) return;
9
+ var b = buf[0];
10
+ if (b === 0x0a) {
11
+ try {
12
+ cb(Buffer.from(bytes));
13
+ } catch (error) {
14
+ stream.emit('error', error);
15
+ }
16
+ stream.removeListener('readable', readable);
17
+ return;
18
+ }
19
+ bytes.push(b);
20
+ }
21
+ }
22
+ stream.on('readable', readable);
23
+ };
@@ -0,0 +1,37 @@
1
+ const Buffer = require('safe-buffer').Buffer;
2
+ const readLine = require('./readline');
3
+
4
+ module.exports = function serverHandshake(stream, opts, cb) {
5
+ stream.name = 'SERVER SERVER';
6
+ readLine(stream, function(hello) {
7
+ console.log(['hello string: ', hello.toString(), hello]);
8
+ stream.write('REJECTED EXTERNAL DBUS_COOKIE_SHA1 ANONYMOUS\r\n');
9
+ readLine(stream, function() {
10
+ stream.write(
11
+ `DATA ${Buffer.from(
12
+ 'org_freedesktop_general 642038150 b9ce247a275f427c8586e4c9de9bb951'
13
+ ).toString('hex')}\r\n`
14
+ );
15
+ readLine(stream, function() {
16
+ stream.write(
17
+ 'OK 6f72675f667265656465736b746f705f67656e6572616c20353631303331333937206239636532343761323735663432376338353836653463396465396262393531\r\n'
18
+ );
19
+ readLine(stream, function(begin) {
20
+ console.log(['AFTER begin: ', begin.toString()]);
21
+ cb(null);
22
+ });
23
+ });
24
+ });
25
+ });
26
+ };
27
+
28
+ // cookie: 561031397 1410749774 3a83c8200f930e7af4de135e8abd299b681a1f44dbb85399
29
+
30
+ // 1539856202
31
+
32
+ // server: org_freedesktop_general 561031397 b9ce247a275f427c8586e4c9de9bb951
33
+ // client: bwFSDjS0TJerqb0l 82986a987194788803d7da2a4b00e801cff9bdfd
34
+ // 82986a987194788803d7da2a4b00e801cff9bdfd = sha1(b9ce247a275f427c8586e4c9de9bb951:bwFSDjS0TJerqb0l:3a83c8200f930e7af4de135e8abd299b681a1f44dbb85399)
35
+ // server: OK e12a29dd7ffe3effac5eb95054123f80
36
+
37
+ //dbus.write('DATA 6f72675f667265656465736b746f705f67656e6572616c203636383430 31303032 203733653733313762383630356537323937623438303233376336353234343533\r\n');
package/lib/server.js ADDED
@@ -0,0 +1,18 @@
1
+ const dbus = require('../index');
2
+ const net = require('net');
3
+
4
+ module.exports.createServer = function(handler) {
5
+ function Server() {
6
+ var id = 123;
7
+ this.server = net.createServer(function(socket) {
8
+ socket.idd = id;
9
+ id++;
10
+
11
+ var dbusConn = dbus.createConnection({ stream: socket, server: true });
12
+ if (handler) handler(dbusConn);
13
+ // TODO: inherit from EE this.emit('connect', dbusConn);
14
+ });
15
+ this.listen = this.server.listen.bind(this.server);
16
+ }
17
+ return new Server();
18
+ };
@@ -0,0 +1,61 @@
1
+ // parse signature from string to tree
2
+
3
+ var match = {
4
+ '{': '}',
5
+ '(': ')'
6
+ };
7
+
8
+ var knownTypes = {};
9
+ '(){}ybnqiuxtdsogarvehm*?@&^'.split('').forEach(function(c) {
10
+ knownTypes[c] = true;
11
+ });
12
+
13
+ module.exports = function parseSignature(signature) {
14
+ var index = 0;
15
+ function next() {
16
+ if (index < signature.length) {
17
+ var c = signature[index];
18
+ ++index;
19
+ return c;
20
+ }
21
+ return null;
22
+ }
23
+
24
+ function parseOne(c) {
25
+ function checkNotEnd(c) {
26
+ if (!c) throw new Error('Bad signature: unexpected end');
27
+ return c;
28
+ }
29
+
30
+ if (!knownTypes[c])
31
+ throw new Error(`Unknown type: "${c}" in signature "${signature}"`);
32
+
33
+ var ele;
34
+ var res = { type: c, child: [] };
35
+ switch (c) {
36
+ case 'a': // array
37
+ ele = next();
38
+ checkNotEnd(ele);
39
+ res.child.push(parseOne(ele));
40
+ return res;
41
+ case '{': // dict entry
42
+ case '(': // struct
43
+ while ((ele = next()) !== null && ele !== match[c])
44
+ res.child.push(parseOne(ele));
45
+ checkNotEnd(ele);
46
+ return res;
47
+ }
48
+ return res;
49
+ }
50
+
51
+ var ret = [];
52
+ var c;
53
+ while ((c = next()) !== null) ret.push(parseOne(c));
54
+ return ret;
55
+ };
56
+
57
+ // command-line test
58
+ //console.log(JSON.stringify(module.exports(process.argv[2]), null, 4));
59
+ //var tree = module.exports('a(ssssbbbbbbbbuasa{ss}sa{sv})a(ssssssbbssa{ss}sa{sv})a(ssssssbsassa{sv})');
60
+ //console.log(tree);
61
+ //console.log(fromTree(tree))