Skip to content

Commit

Permalink
Add support for sending/receiving unix file descriptors.
Browse files Browse the repository at this point in the history
Fixes #245.
  • Loading branch information
robert-ancell committed Jan 14, 2022
1 parent 0b223d8 commit a5c4e66
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 29 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 @@ -890,11 +890,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 @@ -1202,8 +1205,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
66 changes: 49 additions & 17 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 @@ -378,30 +391,46 @@ class DBusReadBuffer extends DBusBuffer {
}

/// Reads a [DBusVariant] from the buffer or returns null if not enough data.
DBusVariant? readDBusVariant([Endian endian = Endian.little]) {
DBusVariant? readDBusVariant(
[Endian endian = Endian.little, int fdCount = 0]) {
var signature = readDBusSignature();
if (signature == null) {
return null;
}

var childValue = readDBusValue(signature, endian);
var childValue = readDBusValue(signature, endian, fdCount);
if (childValue == null) {
return null;
}

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(endian)?.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]) {
[Endian endian = Endian.little, int fdCount = 0]) {
if (!align(structAlignment)) {
return null;
}

var children = <DBusValue>[];
for (var signature in childSignatures) {
var child = readDBusValue(signature, endian);
var child = readDBusValue(signature, endian, fdCount);
if (child == null) {
return null;
}
Expand All @@ -413,7 +442,7 @@ class DBusReadBuffer extends DBusBuffer {

/// Reads a [DBusArray] from the buffer or returns null if not enough data.
DBusArray? readDBusArray(DBusSignature childSignature,
[Endian endian = Endian.little]) {
[Endian endian = Endian.little, int fdCount = 0]) {
var length = readDBusUint32(endian);
if (length == null || !align(getAlignment(childSignature))) {
return null;
Expand All @@ -422,7 +451,7 @@ class DBusReadBuffer extends DBusBuffer {
var end = readOffset + length.value;
var children = <DBusValue>[];
while (readOffset < end) {
var child = readDBusValue(childSignature, endian);
var child = readDBusValue(childSignature, endian, fdCount);
if (child == null) {
return null;
}
Expand All @@ -434,7 +463,7 @@ class DBusReadBuffer extends DBusBuffer {

DBusDict? readDBusDict(
DBusSignature keySignature, DBusSignature valueSignature,
[Endian endian = Endian.little]) {
[Endian endian = Endian.little, int fdCount = 0]) {
var length = readDBusUint32(endian);
if (length == null || !align(dictEntryAlignment)) {
return null;
Expand All @@ -444,7 +473,7 @@ class DBusReadBuffer extends DBusBuffer {
var childSignatures = [keySignature, valueSignature];
var children = <DBusValue, DBusValue>{};
while (readOffset < end) {
var child = readDBusStruct(childSignatures);
var child = readDBusStruct(childSignatures, endian, fdCount);
if (child == null) {
return null;
}
Expand All @@ -458,7 +487,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 All @@ -485,9 +514,11 @@ class DBusReadBuffer extends DBusBuffer {
} else if (s == 'g') {
return readDBusSignature();
} else if (s == 'v') {
return readDBusVariant(endian);
return readDBusVariant(endian, fdCount);
} 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 All @@ -499,12 +530,13 @@ class DBusReadBuffer extends DBusBuffer {
if (!keySignature.isBasic) {
throw 'Invalid dict key signature ${keySignature.value}';
}
return readDBusDict(keySignature, valueSignature, endian);
return readDBusDict(keySignature, valueSignature, endian, fdCount);
} else if (s.startsWith('a')) {
return readDBusArray(DBusSignature(s.substring(1, s.length)), endian);
return readDBusArray(
DBusSignature(s.substring(1, s.length)), endian, fdCount);
} else if (s.startsWith('(') && s.endsWith(')')) {
return readDBusStruct(
DBusSignature(s.substring(1, s.length - 1)).split(), endian);
DBusSignature(s.substring(1, s.length - 1)).split(), endian, fdCount);
} else {
throw "Unknown D-Bus data type '$s'";
}
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 @@ -75,7 +75,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 @@ -113,7 +113,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 @@ -122,11 +128,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 @@ -656,6 +657,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
Loading

0 comments on commit a5c4e66

Please sign in to comment.