Skip to content

Commit

Permalink
Allow clients and servers to operate without names, for example if us…
Browse files Browse the repository at this point in the history
…ing D-Bus

for a simple client-server relationship and not in the more common
client-client.

This only supports method calls from the client to the server at present.
  • Loading branch information
robert-ancell committed Sep 21, 2023
1 parent f7dbc76 commit e6c4c17
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 15 deletions.
33 changes: 23 additions & 10 deletions lib/src/dbus_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -226,15 +226,24 @@ class DBusClient {
// Names owned by this client. e.g. [ 'com.example.Foo', 'com.example.Bar' ].
final _ownedNames = <DBusBusName>{};

// True if this client registers a name on this bus.
final bool _acquireName;

// Unique name of this client, e.g. ':42'.
DBusBusName? _uniqueName;

/// True if this client allows other clients to introspect it.
final bool introspectable;

/// Creates a new DBus client to connect on [address].
DBusClient(DBusAddress address, {this.introspectable = true})
: _address = address;
/// If [acquireName] is false, then no name is acquired from the server.
/// This is useful if the server is not a standard bus with multiple clients
/// but rather using D-Bus for client to server communication and not client
/// to client.
DBusClient(DBusAddress address,
{this.introspectable = true, bool acquireName = true})
: _address = address,
_acquireName = acquireName;

/// Creates a new DBus client to communicate with the system bus.
factory DBusClient.system({bool introspectable = true}) {
Expand Down Expand Up @@ -797,21 +806,25 @@ class DBusClient {
// The first message to the bus must be this call, note requireConnect is
// false as the _connect call hasn't yet completed and would otherwise have
// been called again.
var result = await _callMethod(
destination: DBusBusName('org.freedesktop.DBus'),
path: DBusObjectPath('/org/freedesktop/DBus'),
interface: DBusInterfaceName('org.freedesktop.DBus'),
name: DBusMemberName('Hello'),
replySignature: DBusSignature('s'));
_uniqueName = DBusBusName(result.returnValues[0].asString());
if (_acquireName) {
var result = await _callMethod(
destination: DBusBusName('org.freedesktop.DBus'),
path: DBusObjectPath('/org/freedesktop/DBus'),
interface: DBusInterfaceName('org.freedesktop.DBus'),
name: DBusMemberName('Hello'),
replySignature: DBusSignature('s'));
_uniqueName = DBusBusName(result.returnValues[0].asString());
}

// Notify anyone else awaiting connection.
_connectCompleter?.complete();

// Monitor name ownership so we know what names we have, and can match incoming signals from other clients.
_nameAcquiredSubscription = nameAcquired.listen((name) {
var busName = DBusBusName(name);
_nameOwners[busName] = _uniqueName!;
if (_acquireName) {
_nameOwners[busName] = _uniqueName!;
}
_ownedNames.add(busName);
});
_nameLostSubscription = nameLost.listen((name) {
Expand Down
4 changes: 2 additions & 2 deletions lib/src/dbus_method_call.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'dbus_value.dart';
/// A D-Bus method call.
class DBusMethodCall {
/// Client that called the method.
final String sender;
final String? sender;

/// Interface method is on.
final String? interface;
Expand All @@ -29,7 +29,7 @@ class DBusMethodCall {
.fold(DBusSignature(''), (a, b) => a + b);

const DBusMethodCall(
{required this.sender,
{this.sender,
this.interface,
required this.name,
this.values = const [],
Expand Down
75 changes: 72 additions & 3 deletions lib/src/dbus_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import 'dbus_introspectable.dart';
import 'dbus_match_rule.dart';
import 'dbus_member_name.dart';
import 'dbus_message.dart';
import 'dbus_method_call.dart';
import 'dbus_method_response.dart';
import 'dbus_object.dart';
import 'dbus_object_tree.dart';
import 'dbus_peer.dart';
import 'dbus_properties.dart';
import 'dbus_read_buffer.dart';
Expand Down Expand Up @@ -326,6 +329,9 @@ class DBusServer {
/// Next serial number to use for messages from the server.
int _nextSerial = 1;

/// True if clients are required to use names on this server.
final bool _requireNames;

/// Queues for name ownership.
final _nameQueues = <DBusBusName, _DBusNameQueue>{};

Expand All @@ -335,8 +341,15 @@ class DBusServer {
/// Interfaces supported by the server.
final _interfaces = <String>[];

/// Objects provided by the server.
final _objectTree = DBusObjectTree();

/// Creates a new DBus server.
DBusServer();
/// If [requireNames] is false, clients don't need to acquire a names.
/// This is useful if the server is not a standard bus with multiple clients
/// but rather using D-Bus for client to server communication and not client
/// to client.
DBusServer({bool requireNames = true}) : _requireNames = requireNames;

/// Start a service that uses [name].
/// Override this method to enable this feature.
Expand Down Expand Up @@ -486,6 +499,29 @@ class DBusServer {
}
}

/// Registers a server [object] on the bus.
/// This is useful if this server is being used for .
void registerObject(DBusObject object) async {
if (object.client != null) {
throw Exception('Object already registered');

Check warning on line 506 in lib/src/dbus_server.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/dbus_server.dart#L506

Added line #L506 was not covered by tests
}

_objectTree.add(object.path, object);
}

/// Unregisters an [object] on the bus.
void unregisterObject(DBusObject object) async {
if (object.client != null) {
throw 'Object registered on other client';
}

var node = _objectTree.lookup(object.path);
if (node == null) {
return;
}
_objectTree.remove(object.path);
}

/// Get the client that is currently owning [name].
_DBusRemoteClient? _getClientByName(DBusBusName name) {
for (var client in _clients) {
Expand All @@ -511,7 +547,9 @@ class DBusServer {

// Process requests for the server.
DBusMethodResponse? response;
if (client != null &&
_DBusRemoteClient? responseClient;
if (_requireNames &&
client != null &&
!client.receivedHello &&
!(message.destination?.value == 'org.freedesktop.DBus' &&
message.interface?.value == 'org.freedesktop.DBus' &&
Expand All @@ -523,6 +561,11 @@ class DBusServer {
if (client != null && message.type == DBusMessageType.methodCall) {
response = await _processServerMethodCall(client, message);
}
} else if (message.destination == null) {
if (client != null && message.type == DBusMessageType.methodCall) {
response = await _processServerObjectMethodCall(message);
responseClient = client;
}
} else {
// No-one is going to handle this message.
if (message.destination != null &&
Expand Down Expand Up @@ -555,7 +598,7 @@ class DBusServer {
values: values);
_nextSerial++;
// ignore: unawaited_futures
_processMessage(null, responseMessage);
_processMessage(responseClient, responseMessage);
}
}

Expand Down Expand Up @@ -1230,6 +1273,32 @@ class DBusServer {
return DBusGetAllPropertiesResponse(properties);
}

/// Process a method call requested on the D-Bus server.
Future<DBusMethodResponse> _processServerObjectMethodCall(
DBusMessage message) async {
if (message.path == null || message.member == null) {
return DBusMethodErrorResponse.failed();

Check warning on line 1280 in lib/src/dbus_server.dart

View check run for this annotation

Codecov / codecov/patch

lib/src/dbus_server.dart#L1280

Added line #L1280 was not covered by tests
}

var node = _objectTree.lookup(message.path!);
if (node == null || node.object == null) {
return DBusMethodErrorResponse.unknownObject();
}
var object = node.object!;

var methodCall = DBusMethodCall(
sender: message.sender?.value,
interface: message.interface?.value,
name: message.member!.value,
values: message.values,
noReplyExpected:
message.flags.contains(DBusMessageFlag.noReplyExpected),
noAutoStart: message.flags.contains(DBusMessageFlag.noAutoStart),
allowInteractiveAuthorization: message.flags
.contains(DBusMessageFlag.allowInteractiveAuthorization));
return await object.handleMethodCall(methodCall);
}

/// Emits org.freedesktop.DBus.NameOwnerChanged.
void _emitNameOwnerChanged(
DBusBusName name, DBusBusName? oldOwner, DBusBusName? newOwner) {
Expand Down
32 changes: 32 additions & 0 deletions test/dbus_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,38 @@ void main() {
() => client2.ping(name1), throwsA(isA<DBusServiceUnknownException>()));
});

test('no acquire name', () async {
var server = DBusServer(requireNames: false);
var address =
await server.listenAddress(DBusAddress.unix(abstract: 'abstract'));
var client = DBusClient(address, acquireName: false);
addTearDown(() async {
await client.close();
await server.close();
});

var object = TestObject(
path: DBusObjectPath('/com/example/Object1'),
expectedMethodName: 'Foo',
methodResponses: {
'Foo': DBusMethodSuccessResponse([DBusString('Hello World')])
});
server.registerObject(object);

// Call method on server.
var response = await client.callMethod(
path: DBusObjectPath('/com/example/Object1'), name: 'Foo');
expect(response.values, equals([DBusString('Hello World')]));

// Try and access an unknown object.
expect(
() => client.callMethod(
path: DBusObjectPath('/no/such/object'), name: 'Test'),
throwsA(isA<DBusUnknownObjectException>()));

server.unregisterObject(object);
});

test('list names', () async {
var server = DBusServer();
var address =
Expand Down

0 comments on commit e6c4c17

Please sign in to comment.