canonical / dbus.dart

Native Dart client library to use DBus.
https://pub.dev/packages/dbus
Mozilla Public License 2.0
93 stars 33 forks source link

Invalid reply signature. #379

Closed jeremymreed closed 5 months ago

jeremymreed commented 5 months ago

Hi guys, I'm using your library to build an application.

I'm getting an exception thrown because my dbus method return invalid values. The method is com.thetechforest.WallsD.GetOutputsSettings. Here's the exact error:

Getting output settings
Unhandled exception:
com.thetechforest.WallsD.GetOutputsSettings returned invalid values: [DBusUint32(0), DBusString(''), DBusArray(DBusSignature('(susst)'), [DBusStruct([DBusString('eDP-1'), DBusUint32(1), DBusString('*-*-* *:0/15'), DBusString('/home/jeremyr/Pictures/Wallpapers/wallhaven-rddw7q-cc21588096c2843ac2220d1f403f20319c08f6127dd75eb2b8f5baafb06d27fb-1920x1080.jpeg'), DBusUint64(3363)]), DBusStruct([DBusString('HDMI-A-1'), DBusUint32(0), DBusString('*-*-* *:0/15'), DBusString('/home/jeremyr/Pictures/Wallpapers/Bk8Jq-7b172a5e91add9531ee71f8dfe114b0fafb74abeea3ad8f19b360961a6cd39a8-1920x1080.jpeg'), DBusUint64(1)])])]
#0      DBusClient._callMethod (package:dbus/src/dbus_client.dart:1110:9)
<asynchronous suspension>
#1      DBusClient.callMethod (package:dbus/src/dbus_client.dart:621:12)
<asynchronous suspension>
#2      ComThetechforestWallsD.callGetOutputsSettings (package:dart_dbus_spike/wallsd-interface.dart:12:18)
<asynchronous suspension>
#3      sendGetOutputsSettingsBroken (package:dart_dbus_spike/wallsd_commands.dart:9:18)
<asynchronous suspension>
#4      getOutputsSettings (package:dart_dbus_spike/ui.dart:44:3)
<asynchronous suspension>
#5      run (package:dart_dbus_spike/ui.dart:18:9)
<asynchronous suspension>

I generated a dbus interface with dart-dbus generate-remote-object dbus_interfaces/wallsd-interface.xml -o lib/wallsd-interface.dart. Here's the XML interface description:

<node name="/com/thetechforest/WallsD">
  <interface name="com.thetechforest.WallsD">
    <method name="GetOutputsSettings">
      <arg name="output" type="(usa(susst))" direction="out"/>
    </method>
  </interface>
</node>

That produced this dart code:

// This file was generated using the following command and may be overwritten.
// dart-dbus generate-remote-object dbus_interfaces/wallsd-interface.xml

import 'dart:io';
import 'package:dbus/dbus.dart';

class ComThetechforestWallsD extends DBusRemoteObject {
  ComThetechforestWallsD(DBusClient client, String destination,
      {DBusObjectPath path =
          const DBusObjectPath.unchecked('/com/thetechforest/WallsD')})
      : super(client, name: destination, path: path);

  /// Invokes com.thetechforest.WallsD.GetOutputsSettings()
  Future<List<DBusValue>> callGetOutputsSettings(
      {bool noAutoStart = false,
      bool allowInteractiveAuthorization = false}) async {
    var result = await callMethod(
        'com.thetechforest.WallsD', 'GetOutputsSettings', [],
        replySignature: DBusSignature('(usa(susst))'),
        noAutoStart: noAutoStart,
        allowInteractiveAuthorization: allowInteractiveAuthorization);
    return result.returnValues[0].asStruct();
  }
}

I ran busctl --user introspect on the desired interface, and I got this:

NAME                                TYPE      SIGNATURE RESULT/VALUE FLAGS
com.thetechforest.WallsD            interface -         -            -
.GetOutputsSettings                 method    -         (usa(susst)) -
.SetOutputImages                    method    (sas)     (uas)        -
.SetOutputMode                      method    (su)      (us)         -
.SetOutputOncalendar                method    (ss)      (us)         -
(Removed extra info)

From the busctl introspection output, the result signature of GetOutputsSettings is (usa(susst)), which matches the signature I specified in the XML interface description.

Here's my code. I've included the broken version, as well as the working version.

import 'package:dbus/dbus.dart';
import 'package:dart_dbus_spike/wallsd-interface.dart' as wallsd_interface;

Future<void> sendGetOutputsSettingsBroken() async {
  var client = DBusClient.session();
  var object = wallsd_interface.ComThetechforestWallsD(
      client, 'com.thetechforest.WallsD');

  var response = await object.callGetOutputsSettings();

  print('response: $response');

  await client.close();
}

Future<void> sendGetOutputsSettings() async {
  var client = DBusClient.session();
  var object = DBusRemoteObject(
    client,
    name: 'com.thetechforest.WallsD',
    path: DBusObjectPath('/com/thetechforest/WallsD'),
  );

  var response = await object
      .callMethod('com.thetechforest.WallsD', 'GetOutputsSettings', []);

  print('response: $response');

  print('response.signature: ${response.signature}');

  await client.close();
}

The first method is the broken one, and this one tries to validate the signature of the response. This one throws an exception. But the second method does not do any validation of the signature of the response, and this one succeeds. I get a DBusMethodSuccessResponse as expected.

If I modify the second method to include replySignature='(usa(susst))' in the call to object.callMethod, I will get an exception. Remove this argument makes the call successful.

Calling the method manually via busctl results in the expected response.

It looks like either I'm not specifying the replySignature properly, or there's a bug in the validation code for the reply signature.

What should I be looking for? Let me know if you need any extra data or code.

For now, I'll skip the reply signature validation, and use a mapper to transform the DBusMethodSuccessResponse data into something I can use inside my application.

Thanks!

robert-ancell commented 5 months ago

Confirmed here building a server and client generated from the interface causes the reported exception.

robert-ancell commented 5 months ago

Actually, I made a mistake in my server. I was reproducing by returning this:

class TestObject extends ComThetechforestWallsD {
  @override
  Future<DBusMethodResponse> doGetOutputsSettings() async {
    return DBusMethodSuccessResponse([
      DBusUint32(0),
      DBusString(''),
      DBusArray(DBusSignature('(susst)'), [
        DBusStruct([
          DBusString('eDP-1'),
          DBusUint32(1),
          DBusString('*-*-* *:0/15'),
          DBusString(
              '/home/jeremyr/Pictures/Wallpapers/wallhaven-rddw7q-cc21588096c2843ac2220d1f403f20319c08f6127dd75eb2b8f5baafb06d27fb-1920x1080.jpeg'),
          DBusUint64(3363)
        ]),
        DBusStruct([
          DBusString('HDMI-A-1'),
          DBusUint32(0),
          DBusString('*-*-* *:0/15'),
          DBusString(
              '/home/jeremyr/Pictures/Wallpapers/Bk8Jq-7b172a5e91add9531ee71f8dfe114b0fafb74abeea3ad8f19b360961a6cd39a8-1920x1080.jpeg'),
          DBusUint64(1)
        ])
      ])
    ]);
  }
}

Which returns a method reply with the signature usa(susst), but it was missing being wrapped in a struct to return (usa(susst)):

class TestObject extends ComThetechforestWallsD {
  @override
  Future<DBusMethodResponse> doGetOutputsSettings() async {
    return DBusMethodSuccessResponse([
      DBusStruct([
        DBusUint32(0),
        DBusString(''),
        DBusArray(DBusSignature('(susst)'), [
          DBusStruct([
            DBusString('eDP-1'),
            DBusUint32(1),
            DBusString('*-*-* *:0/15'),
            DBusString(
                '/home/jeremyr/Pictures/Wallpapers/wallhaven-rddw7q-cc21588096c2843ac2220d1f403f20319c08f6127dd75eb2b8f5baafb06d27fb-1920x1080.jpeg'),
            DBusUint64(3363)
          ]),
          DBusStruct([
            DBusString('HDMI-A-1'),
            DBusUint32(0),
            DBusString('*-*-* *:0/15'),
            DBusString(
                '/home/jeremyr/Pictures/Wallpapers/Bk8Jq-7b172a5e91add9531ee71f8dfe114b0fafb74abeea3ad8f19b360961a6cd39a8-1920x1080.jpeg'),
            DBusUint64(1)
          ])
        ])
      ])
    ]);
  }
}

The first case the method was returning three values, when it's supposed to return one value (which is a struct of three values). So I suspect you've made a similar mistake in your server code.

Please reopen if this is not the case!

jeremymreed commented 2 months ago

Thanks for taking a look at this! My server side code is written in Rust, using the zbus crate. If you want, I can show you the Rust code that I'm using. What I have now seems to be working for now.