Skip to content

Commit 8dc54b0

Browse files
author
Tony Crisci
committed
cache name owners and discriminate signals on owner
Create a name cache. Cache the name owner (the unique name) when the user creates a proxy object client and keep the cache up to date when the name owner changes on the NameOwnerChanged standard DBus signal. Then when a signal comes in, make sure that the sender for the signal actually owns the name based on the `sender` field in the message and only emit the signal on the proxy interface if it matches. This fixes a bug where if two external connections take different names but export the same objects with the same interface names (common for DBus interface standards), the signal would be emitted on all proxy objects that emit for that path/interface combination regardless of whether it was for the name they are listening on. See the new test for a precise description of the error case.
1 parent f7f11c0 commit 8dc54b0

File tree

3 files changed

+91
-7
lines changed

3 files changed

+91
-7
lines changed

lib/bus.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ module.exports = function bus(conn, opts) {
2626
this.signals = new EventEmitter();
2727
this.exportedObjects = {};
2828

29+
this.nameOwners = {};
30+
31+
this._handleNameOwnerChanged = function(msg) {
32+
let {sender, path, iface, member} = msg;
33+
if (sender !== 'org.freedesktop.DBus' ||
34+
path !== '/org/freedesktop/DBus' ||
35+
iface !== 'org.freedesktop.DBus' ||
36+
member !== 'NameOwnerChanged') {
37+
return;
38+
}
39+
let [name, oldOwner, newOwner] = msg.body;
40+
if (name.startsWith(':')) {
41+
return;
42+
}
43+
this.nameOwners[name] = newOwner;
44+
};
45+
2946
this.invoke = function(msg, callback) {
3047
if (!msg.type) msg.type = constants.messageType.methodCall;
3148
msg.serial = self.serial++;
@@ -150,7 +167,8 @@ module.exports = function bus(conn, opts) {
150167
}
151168
}
152169
} else if (msg.type === constants.messageType.signal) {
153-
self.signals.emit(self.mangle(msg), msg.body, msg.signature);
170+
self._handleNameOwnerChanged(msg);
171+
self.signals.emit(self.mangle(msg), msg);
154172
} else {
155173
// methodCall
156174
if (handleMethod(msg, self)) {
@@ -397,6 +415,19 @@ module.exports = function bus(conn, opts) {
397415
);
398416
};
399417

418+
this.cacheNameOwner = function(name) {
419+
let that = this;
420+
return new Promise((resolve, reject) => {
421+
this.getNameOwner(name, function(err, owner) {
422+
if (err) {
423+
return reject(err);
424+
}
425+
that.nameOwners[name] = owner;
426+
return resolve(owner);
427+
});
428+
});
429+
}
430+
400431
this.nameHasOwner = function(name, callback) {
401432
this.invokeDbus(
402433
{ member: 'NameHasOwner', signature: 's', body: [name] },

lib/client/proxy-object.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ class ProxyObject {
5151
interface: iface.$name,
5252
member: signal.name
5353
});
54-
this.bus.signals.on(event, (body, signature) => {
54+
this.bus.signals.on(event, (msg) => {
55+
let {body, signature, sender} = msg;
56+
if (this.bus.nameOwners[this.name] !== sender) {
57+
return;
58+
}
5559
if (signature !== signal.signature) {
5660
console.error(`warning: got signature ${signature} for signal ${iface.$name}.${signal.name} (expected ${signal.signature})`);
5761
return;
@@ -72,7 +76,7 @@ class ProxyObject {
7276
}
7377

7478
_init() {
75-
return new Promise((resolve, reject) => {
79+
let introspect = new Promise((resolve, reject) => {
7680
this.bus.invoke(
7781
{
7882
destination: this.name,
@@ -99,6 +103,16 @@ class ProxyObject {
99103
}
100104
);
101105
});
106+
107+
return new Promise((resolve, reject) => {
108+
Promise.all([introspect, this.bus.cacheNameOwner(this.name)])
109+
.then((result) => {
110+
return resolve(result[0]);
111+
})
112+
.catch((err) => {
113+
return reject(err);
114+
});
115+
});
102116
}
103117

104118
getInterface(name) {

test/integration/signals.test.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ let {
1010
} = dbus.interface;
1111

1212
const TEST_NAME = 'org.test.name';
13+
const TEST_NAME2 = 'org.test.name2';
1314
const TEST_PATH = '/org/test/path';
1415
const TEST_IFACE = 'org.test.iface';
1516

1617
let bus = dbus.sessionBus();
18+
let bus2 = dbus.sessionBus();
1719

1820
class SignalsInterface extends Interface {
1921
@signal({signature: 's'})
@@ -64,23 +66,29 @@ class SignalsInterface extends Interface {
6466
}
6567

6668
let testIface = new SignalsInterface(TEST_IFACE);
69+
let testIface2 = new SignalsInterface(TEST_IFACE);
6770

6871
beforeAll(async () => {
69-
let name = await bus.requestName(TEST_NAME);
72+
let [name, name2] = await Promise.all([
73+
bus.requestName(TEST_NAME),
74+
bus2.requestName(TEST_NAME2)
75+
]);
7076
name.export(TEST_PATH, testIface);
77+
name2.export(TEST_PATH, testIface2);
7178
});
7279

7380
afterAll(() => {
7481
bus.connection.stream.end();
82+
bus2.connection.stream.end();
7583
});
7684

7785
test('test that signals work correctly', async () => {
7886
let object = await bus.getProxyObject(TEST_NAME, TEST_PATH);
7987
let test = object.getInterface(TEST_IFACE);
8088

81-
let onHelloWorld = jest.fn(() => {});
82-
let onSignalMultiple = jest.fn(() => {});
83-
let onSignalComplicated = jest.fn(() => {});
89+
let onHelloWorld = jest.fn();
90+
let onSignalMultiple = jest.fn();
91+
let onSignalComplicated = jest.fn();
8492

8593
test.on('HelloWorld', onHelloWorld);
8694
test.on('SignalMultiple', onSignalMultiple);
@@ -92,3 +100,34 @@ test('test that signals work correctly', async () => {
92100
expect(onSignalMultiple).toHaveBeenCalledWith('hello', 'world');
93101
expect(onSignalComplicated).toHaveBeenCalledWith(testIface.complicated);
94102
});
103+
104+
test('signals dont get mixed up between names that define objects on the same path and interface', async () => {
105+
// Note that there is a really bad case where a single connection takes two
106+
// names and exports the same interfaces and paths on them. Then there is no
107+
// way to tell the signals apart from the names because the messages look
108+
// identical to us. All we get is the unique name of the sender and not the
109+
// well known name, and the well known name is what will be different. For
110+
// this reason, I am going to recommend that people only use one name per bus
111+
// connection until we can figure that out.
112+
let object = await bus.getProxyObject(TEST_NAME, TEST_PATH);
113+
let object2 = await bus.getProxyObject(TEST_NAME2, TEST_PATH);
114+
115+
let test = object.getInterface(TEST_IFACE);
116+
let test2 = object2.getInterface(TEST_IFACE);
117+
118+
let cb = jest.fn();
119+
let cb2 = jest.fn();
120+
121+
test.on('HelloWorld', cb);
122+
test.on('SignalMultiple', cb);
123+
test.on('SignalComplicated', cb);
124+
125+
test2.on('HelloWorld', cb2);
126+
test2.on('SignalMultiple', cb2);
127+
test2.on('SignalComplicated', cb2);
128+
129+
await test.EmitSignals();
130+
131+
expect(cb).toHaveBeenCalledTimes(3);
132+
expect(cb2).toHaveBeenCalledTimes(0);
133+
});

0 commit comments

Comments
 (0)