Skip to content

Commit

Permalink
Add support for sending/receiving unix file descriptors.
Browse files Browse the repository at this point in the history
  • Loading branch information
robert-ancell committed Oct 19, 2021
1 parent 20bc7bf commit 52049f9
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 16 deletions.
69 changes: 69 additions & 0 deletions example/file_descriptor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'dart:convert';
import 'dart:io';

import 'package:dbus/dbus.dart';

class TestObject extends DBusObject {
TestObject() : super(DBusObjectPath('/com/canonical/DBusDart'));

@override
List<DBusIntrospectInterface> introspect() {
return [
DBusIntrospectInterface('com.canonical.DBusDart', methods: [
DBusIntrospectMethod('Open', args: [
DBusIntrospectArgument(DBusSignature('h'), DBusArgumentDirection.out,
name: 'fd')
])
])
];
}

@override
Future<DBusMethodResponse> handleMethodCall(DBusMethodCall methodCall) async {
if (methodCall.interface != 'com.canonical.DBusDart') {
return DBusMethodErrorResponse.unknownInterface();
}

if (methodCall.name == 'Open') {
// Write a file to use for testing.
await File('FD_TEST').writeAsString('Hello World!', flush: true);

print('Client opens file for reading');
var file = await File('FD_TEST').open();
return DBusMethodSuccessResponse(
[DBusUnixFd(ResourceHandle.fromFile(file))]);
} else {
return DBusMethodErrorResponse.unknownMethod();
}
}
}

void main(List<String> args) async {
var client = DBusClient.session();

String? mode;
if (args.isNotEmpty) {
mode = args[0];
}
if (mode == 'client') {
var object = DBusRemoteObject(client,
name: 'com.canonical.DBusDart',
path: DBusObjectPath('/com/canonical/DBusDart'));

var result = await object.callMethod('com.canonical.DBusDart', 'Open', [],
replySignature: DBusSignature('h'));
var fd = result.returnValues[0] as DBusUnixFd;
var file = fd.handle.toFile();

print('Contents of file:');
print(utf8.decode(await file.read(1024)));
} else if (mode == 'server') {
await client.requestName('com.canonical.DBusDart');
var object = TestObject();
await client.registerObject(object);
} else {
print('Usage:');
print('file_descriptor.dart server - Run as a server');
print('file_descriptor.dart client - Run as a client');
}
}
16 changes: 12 additions & 4 deletions lib/src/dbus_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -886,11 +886,14 @@ class DBusClient {

/// Read incoming data from the D-Bus server.
void _readData() {
var data = _socket?.read();
if (data == null) {
var message = _socket?.readMessage();
if (message == null) {
return;
}
_readBuffer.writeBytes(data);
_readBuffer.writeBytes(message.data);
for (var message in message.controlMessages) {
_readBuffer.addResourceHandles(message.extractHandles());
}

var complete = false;
while (!complete) {
Expand Down Expand Up @@ -1196,8 +1199,13 @@ class DBusClient {

var buffer = DBusWriteBuffer();
buffer.writeMessage(message);
var controlMessages = <SocketControlMessage>[];
if (buffer.resourceHandles.isNotEmpty) {
controlMessages
.add(SocketControlMessage.fromHandles(buffer.resourceHandles));
}

_socket?.write(buffer.data);
_socket?.sendMessage(controlMessages, buffer.data);
}

@override
Expand Down
40 changes: 35 additions & 5 deletions lib/src/dbus_read_buffer.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'dbus_buffer.dart';
Expand All @@ -14,6 +15,9 @@ class DBusReadBuffer extends DBusBuffer {
/// Data in the buffer.
var _data = Uint8List(0);

/// Unix file descriptors available.
final _resourceHandles = <ResourceHandle>[];

/// View of the buffer to allow accessing fixed width integers and floats.
late ByteData _view;

Expand All @@ -34,6 +38,11 @@ class DBusReadBuffer extends DBusBuffer {
_view = ByteData.view(_data.buffer);
}

/// Add received resource handles (file descriptors).
void addResourceHandles(List<ResourceHandle> handles) {
_resourceHandles.addAll(handles);
}

/// Read a single byte from the buffer.
int _readByte() {
readOffset++;
Expand Down Expand Up @@ -152,9 +161,6 @@ class DBusReadBuffer extends DBusBuffer {
signature = value as DBusSignature;
} else if (code == 9) {
fdCount = (value as DBusUint32).value;
if (fdCount != 0) {
throw 'Message contains file descriptors, which are not supported';
}
}
}
if (!align(8)) {
Expand All @@ -170,7 +176,7 @@ class DBusReadBuffer extends DBusBuffer {
if (signature != null) {
var signatures = signature.split();
for (var s in signatures) {
var value = readDBusValue(s, endian);
var value = readDBusValue(s, endian, fdCount);
if (value == null) {
return null;
}
Expand All @@ -188,6 +194,13 @@ class DBusReadBuffer extends DBusBuffer {
}
}

// Remove file descriptors that were part of this message.
// Note: This could remove descriptors added after the end of the message.
if (_resourceHandles.length < fdCount) {
throw 'Insufficient file descriptors received';
}
_resourceHandles.removeRange(0, fdCount);

return DBusMessage(type,
flags: flags,
serial: serial,
Expand Down Expand Up @@ -392,6 +405,21 @@ class DBusReadBuffer extends DBusBuffer {
return DBusVariant(childValue);
}

/// Reads a [DBusUnixFd] from the buffer or returns null if not enough data.
DBusUnixFd? readDBusUnixFd(int fdCount, [Endian endian = Endian.little]) {
var index = readDBusUint32()?.value;
if (index == null) {
return null;
}
if (index > fdCount) {
throw 'Unix fd index out of bounds';
}
if (index > _resourceHandles.length) {
throw 'Unix fd $index not yet received';
}
return DBusUnixFd(_resourceHandles[index]);
}

/// Reads a [DBusStruct] from the buffer or returns null if not enough data.
DBusStruct? readDBusStruct(Iterable<DBusSignature> childSignatures,
[Endian endian = Endian.little]) {
Expand Down Expand Up @@ -458,7 +486,7 @@ class DBusReadBuffer extends DBusBuffer {

/// Reads a [DBusValue] with [signature].
DBusValue? readDBusValue(DBusSignature signature,
[Endian endian = Endian.little]) {
[Endian endian = Endian.little, int fdCount = 0]) {
var s = signature.value;
if (s == 'y') {
return readDBusByte();
Expand Down Expand Up @@ -488,6 +516,8 @@ class DBusReadBuffer extends DBusBuffer {
return readDBusVariant(endian);
} else if (s == 'm') {
throw 'D-Bus reserved maybe type not valid';
} else if (s == 'h') {
return readDBusUnixFd(fdCount, endian);
} else if (s.startsWith('a{') && s.endsWith('}')) {
var childSignature = DBusSignature(s.substring(2, s.length - 1));
var signatures = childSignature.split();
Expand Down
19 changes: 14 additions & 5 deletions lib/src/dbus_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class _DBusRemoteClient {
final matchRules = <DBusMatchRule>[];

_DBusRemoteClient(this.serverSocket, this._socket, this.uniqueName)
: _authServer = DBusAuthServer(serverSocket.uuid) {
: _authServer = DBusAuthServer(serverSocket.uuid, unixFdSupported: true) {
_authServer.responses
.listen((message) => _socket.write(utf8.encode(message + '\r\n')));
_socket.listen((event) {
Expand Down Expand Up @@ -114,7 +114,13 @@ class _DBusRemoteClient {
void sendMessage(DBusMessage message) {
var buffer = DBusWriteBuffer();
buffer.writeMessage(message);
_socket.write(buffer.data);
var controlMessages = <SocketControlMessage>[];
if (buffer.resourceHandles.isNotEmpty) {
controlMessages
.add(SocketControlMessage.fromHandles(buffer.resourceHandles));
}

_socket.sendMessage(controlMessages, buffer.data);
}

Future<void> close() async {
Expand All @@ -123,11 +129,14 @@ class _DBusRemoteClient {

/// Reads incoming data from this D-Bus client.
void _readData() {
var data = _socket.read();
if (data == null) {
var message = _socket.readMessage();
if (message == null) {
return;
}
_readBuffer.writeBytes(data);
_readBuffer.writeBytes(message.data);
for (var message in message.controlMessages) {
_readBuffer.addResourceHandles(message.extractHandles());
}

var complete = false;
while (!complete) {
Expand Down
35 changes: 33 additions & 2 deletions lib/src/dbus_value.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:io';

bool _listsEqual<T>(List<T> a, List<T> b) {
if (a.length != b.length) {
return false;
Expand Down Expand Up @@ -430,11 +432,10 @@ class DBusObjectPath extends DBusString {
/// * `g`[DBusSignature]
/// * `v`[DBusVariant]
/// * `m`[DBusMaybe]
/// * 'h' → [DBusUnixFd]
/// * `(xyz...)`[DBusStruct] (`x`, `y`, `z` represent the child value signatures).
/// * `av`[DBusArray] (v represents the array value signature).
/// * `a{kv}`[DBusDict] (`k` and `v` represent the key and value signatures).
///
/// There is also a Unix file descriptor `h` which may be in a signature, but is not supported in Dart.
class DBusSignature extends DBusValue {
/// A D-Bus signature string.
final String value;
Expand Down Expand Up @@ -657,6 +658,36 @@ class DBusMaybe extends DBusValue {
String toString() => 'DBusMaybe($valueSignature, ${value?.toString()})';
}

/// D-Bus value that contains a Unix file descriptor.
class DBusUnixFd extends DBusValue {
/// The resource handle containing this file descriptor.
final ResourceHandle handle;

/// Creates a new file descriptor containing [handle].
const DBusUnixFd(this.handle);

@override
DBusSignature get signature {
return DBusSignature('h');
}

@override
dynamic toNative() {
return this;
}

@override
bool operator ==(other) => other is DBusUnixFd && other.handle == handle;

@override
int get hashCode => handle.hashCode;

@override
String toString() {
return 'DBusUnixFd()';
}
}

/// D-Bus value that contains a fixed set of other values.
class DBusStruct extends DBusValue {
/// Child values in this structure.
Expand Down
13 changes: 13 additions & 0 deletions lib/src/dbus_write_buffer.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'dbus_buffer.dart';
Expand All @@ -10,6 +11,10 @@ class DBusWriteBuffer extends DBusBuffer {
/// Data generated.
var data = <int>[];

// File descriptors to send with data.
List<ResourceHandle> get resourceHandles => _resourceHandles;
final _resourceHandles = <ResourceHandle>[];

/// Writes a [DBusMessage] to the buffer.
void writeMessage(DBusMessage message) {
var valueBuffer = DBusWriteBuffer();
Expand Down Expand Up @@ -67,9 +72,14 @@ class DBusWriteBuffer extends DBusBuffer {
}
headers.add(_makeHeader(8, DBusSignature(signature)));
}
if (valueBuffer.resourceHandles.isNotEmpty) {
headers
.add(_makeHeader(9, DBusUint32(valueBuffer.resourceHandles.length)));
}
writeValue(DBusArray(DBusSignature('(yv)'), headers));
align(8);
writeBytes(valueBuffer.data);
_resourceHandles.addAll(valueBuffer.resourceHandles);
}

/// Makes a new message header.
Expand Down Expand Up @@ -188,6 +198,9 @@ class DBusWriteBuffer extends DBusBuffer {
writeValue(childValue);
} else if (value is DBusMaybe) {
throw UnsupportedError("D-Bus doesn't support reserved maybe type");
} else if (value is DBusUnixFd) {
writeUint32(_resourceHandles.length);
_resourceHandles.add(value.handle);
} else if (value is DBusStruct) {
align(STRUCT_ALIGNMENT);
var children = value.children;
Expand Down
11 changes: 11 additions & 0 deletions test/dbus_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,17 @@ void main() {
equals("DBusMaybe(DBusSignature('s'), DBusString('one'))"));
});

test('value - unix fd', () async {
expect(() => DBusUnixFd(-1), throwsArgumentError);
expect(DBusUnixFd(0).fd, equals(0));
expect(DBusUnixFd(4294967295).fd, equals(4294967295));
expect(() => DBusUnixFd(4294967296), throwsArgumentError);
expect(DBusUnixFd(0).signature, equals(DBusSignature('h')));
expect(DBusUnixFd(42).toNative(), equals(DBusUnixFd(42)));
expect(DBusUnixFd(42) == DBusUnixFd(42), isTrue);
expect(DBusUnixFd(42) == DBusUnixFd(99), isFalse);
});

test('value - struct', () async {
expect(DBusStruct([]).children, equals([]));
expect(
Expand Down

0 comments on commit 52049f9

Please sign in to comment.