Skip to content

Commit 954c0b1

Browse files
author
Tony Crisci
committed
use BigInt by default for 'x' and 't'
(Breaking change) Always use BigInt to represent dbus int64 and uint64 types. Remove Long.js dependency.
1 parent 54d0bdb commit 954c0b1

File tree

8 files changed

+139
-103
lines changed

8 files changed

+139
-103
lines changed

.babelrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"plugins": [
33
[ "@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true, "legacy": false } ],
4-
"@babel/plugin-proposal-class-properties"
4+
"@babel/plugin-proposal-class-properties",
5+
"@babel/plugin-syntax-bigint"
56
]
67
}

examples/new-service/.babelrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"plugins": [
33
[ "@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true, "legacy": false } ],
4-
"@babel/plugin-proposal-class-properties"
4+
"@babel/plugin-proposal-class-properties",
5+
"@babel/plugin-syntax-bigint"
56
]
67
}

examples/new-service/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
"@babel/plugin-proposal-decorators": "^7.1.2"
77
},
88
"scripts": {
9-
"start": "rm -r build && mkdir build && babel ./index.js -o ./build/index.js && node ./build"
9+
"start": "rm -rf build && mkdir build && babel ./index.js -o ./build/index.js && node ./build"
1010
}
1111
}

lib/constants.js

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
module.exports = {
2+
MAX_INT64: 9223372036854775807n,
3+
MIN_INT64: -9223372036854775807n,
4+
MAX_UINT64: 18446744073709551615n,
5+
MIN_UINT64: 0n,
6+
27
messageType: {
38
invalid: 0,
49
methodCall: 1,

lib/dbus-buffer.js

+16-12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
const Long = require('long');
21
const parseSignature = require('./signature');
32

43
// Buffer + position + global start position ( used in alignment )
54
function DBusBuffer(buffer, startPos, options) {
65
if (typeof options !== 'object') {
7-
options = { ayBuffer: true, ReturnLongjs: false };
6+
options = { ayBuffer: true };
87
} else if (options.ayBuffer === undefined) {
98
// default settings object
109
options.ayBuffer = true; // enforce truthy default props
@@ -159,19 +158,24 @@ DBusBuffer.prototype.readSimpleType = function readSimpleType(t) {
159158
case 'x':
160159
//signed
161160
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)
161+
word0 = BigInt(this.readInt32());
162+
word1 = BigInt(this.readInt32());
163+
164+
let word1Stripped = word1 & ~(1n << 31n);
165+
166+
let data = (word1Stripped << 32n) + word0;
167+
168+
if (word1Stripped !== word1) {
169+
data *= -1n;
170+
}
171+
172+
return data;
167173
case 't':
168174
//unsigned
169175
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)
176+
word0 = BigInt(this.readInt32());
177+
word1 = BigInt(this.readInt32());
178+
return (word1 << BigInt(32)) + word0;
175179
case 'd':
176180
return this.readDouble();
177181
default:

lib/marshallers.js

+34-87
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
const Buffer = require('safe-buffer').Buffer;
22
const align = require('./align').align;
33
const parseSignature = require('../lib/signature');
4-
const Long = require('long');
4+
const {
5+
MAX_INT64, MIN_INT64,
6+
MAX_UINT64, MIN_UINT64
7+
} = require('./constants');
8+
59
/**
610
* MakeSimpleMarshaller
711
* @param signature - the signature of the data you want to check
@@ -178,8 +182,10 @@ var MakeSimpleMarshaller = function(signature) {
178182
marshaller.marshall = function(ps, data) {
179183
data = this.check(data);
180184
align(ps, 8);
181-
ps.word32le(data.low);
182-
ps.word32le(data.high);
185+
let word0 = data - ((data >> 32n) << 32n);
186+
let word1 = data >> 32n;
187+
ps.word32le(Number(word0));
188+
ps.word32le(Number(word1));
183189
ps._offset += 8;
184190
};
185191
break;
@@ -190,9 +196,22 @@ var MakeSimpleMarshaller = function(signature) {
190196
};
191197
marshaller.marshall = function(ps, data) {
192198
data = this.check(data);
199+
let isNegative = (data < 0n);
200+
if (isNegative) {
201+
data *= -1n;
202+
}
203+
204+
let word0 = data - ((data >> 32n) << 32n);
205+
let word1 = data >> 32n;
206+
207+
if (isNegative) {
208+
word1 |= (1n << 31n);
209+
}
210+
193211
align(ps, 8);
194-
ps.word32le(data.low);
195-
ps.word32le(data.high);
212+
ps.word32le(Number(word0));
213+
ps.word32le(Number(word1));
214+
196215
ps._offset += 8;
197216
};
198217
break;
@@ -243,94 +262,22 @@ var checkBoolean = function(data) {
243262
throw new Error(`Data: ${data} was not of type boolean`);
244263
};
245264

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-
309265
var checkLong = function(data, signed) {
310-
if (!Long.isLong(data)) {
311-
data = makeLong(data, signed);
312-
}
266+
data = BigInt(data);
313267

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?
316268
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)`);
269+
if (data > MAX_INT64) {
270+
throw new Error('data was out of range (greater than max int64)');
271+
} else if (data < MIN_INT64) {
272+
throw new Error('data was out of range (less than min int64)');
323273
}
324274
} 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)`);
275+
if (data > MAX_UINT64) {
276+
throw new Error('data was out of range (greater than max uint64)');
277+
} else if (data < MIN_UINT64) {
278+
throw new Error('data was out of range (less than min uint64)');
333279
}
334280
}
281+
335282
return data;
336283
};

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
"dependencies": {
3636
"event-stream": "3.3.4",
3737
"hexy": "^0.2.10",
38-
"long": "^4.0.0",
3938
"optimist": "^0.6.1",
4039
"put": "0.0.6",
4140
"safe-buffer": "^5.1.1",
@@ -48,6 +47,7 @@
4847
"@babel/core": "^7.1.5",
4948
"@babel/plugin-proposal-class-properties": "^7.1.0",
5049
"@babel/plugin-proposal-decorators": "^7.1.2",
50+
"@babel/plugin-syntax-bigint": "^7.2.0",
5151
"babel-core": "^7.0.0-bridge.0",
5252
"babel-jest": "^23.6.0",
5353
"commander": "^2.19.0",

test/integration/long.test.js

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Test that methods and properties of type 'x' (long int) work correctly
2+
3+
let dbus = require('../../');
4+
5+
let {
6+
Interface, property, method, signal,
7+
ACCESS_READ, ACCESS_WRITE, ACCESS_READWRITE
8+
} = dbus.interface;
9+
10+
let {
11+
MAX_INT64, MIN_INT64,
12+
MAX_UINT64, MIN_UINT64
13+
} = require('../../lib/constants');
14+
15+
const TEST_NAME = 'org.test.name';
16+
const TEST_PATH = '/org/test/path';
17+
const TEST_IFACE = 'org.test.iface';
18+
19+
let bus = dbus.sessionBus();
20+
21+
class LongInterface extends Interface {
22+
@method({inSignature: 'x', outSignature: 'x'})
23+
EchoSigned(what) {
24+
return what;
25+
}
26+
27+
@method({inSignature: 't', outSignature: 't'})
28+
EchoUnsigned(what) {
29+
return what;
30+
}
31+
}
32+
33+
let testIface = new LongInterface(TEST_IFACE);
34+
35+
beforeAll(async () => {
36+
await bus.export(TEST_NAME, TEST_PATH, testIface);
37+
});
38+
39+
afterAll(() => {
40+
bus.connection.stream.end();
41+
});
42+
43+
test('test long type works correctly', async () => {
44+
let object = await bus.getProxyObject(TEST_NAME, TEST_PATH);
45+
let test = object.getInterface(TEST_IFACE);
46+
47+
// small numbers
48+
let what = -30n;
49+
let result = await test.EchoSigned(what);
50+
// XXX jest does not support bigint yet
51+
expect(result === what).toEqual(true);
52+
53+
what = 30n;
54+
result = await test.EchoUnsigned(what);
55+
expect(result === what).toEqual(true);
56+
result = await test.EchoSigned(what);
57+
expect(result === what).toEqual(true);
58+
59+
// int64 max
60+
what = MAX_INT64;
61+
result = await test.EchoSigned(what);
62+
expect(result === what).toEqual(true);
63+
64+
// int64 min
65+
what = MIN_INT64;
66+
result = await test.EchoSigned(what);
67+
expect(result === what).toEqual(true);
68+
69+
// uint64 max
70+
what = MAX_UINT64;
71+
result = await test.EchoUnsigned(what);
72+
expect(result === what).toEqual(true);
73+
74+
// uint64 min
75+
what = MIN_UINT64;
76+
result = await test.EchoUnsigned(what);
77+
expect(result === what).toEqual(true);
78+
});

0 commit comments

Comments
 (0)