ccxtechnologies / adbus

D-Bus Binding​ for Python that supports coroutines (asyncio)
MIT License
32 stars 5 forks source link

Failed to read value with signature s #58

Closed hamid-rostami closed 4 years ago

hamid-rostami commented 4 years ago

I have a dbus server with specific method with name New. Its Output signature is "is". When I call it using busctl, there is no error. But when I using adbus, my client program can't grab string part of output and below exception occurs. So I can't using proxy for client and have to use adbus.client by assign "i" to response_signature to drop "s" part.

adbus.sdbus.SdbusError: Failed to read value with signature s: ENXIO

Here is my test client with adbus:

import adbus
import asyncio

name2 = "ir.elecplus.iot"
path2 = "/ir/elecplus/iot/Daemon1/Tasks"
iface2 = "ir.elecplus.iot.Daemon1.Tasks"

async def main():
    service = adbus.Service(bus='session')
    proxy = adbus.client.Proxy(service, name2, path2,
        interface=iface2)
    await proxy.update()
    ret = await proxy.New("body")
    print(ret)

asyncio.run(main())

Output of busctl in monitor mode:

‣ Type=signal  Endian=l  Flags=1  Version=1  Priority=0 Cookie=2
  Sender=org.freedesktop.DBus  Destination=:1.146  Path=/org/freedesktop/DBus  Interface=org.freedesktop.DBus  Member=NameAcquired
  MESSAGE "s" {
          STRING ":1.146";
  };

‣ Type=signal  Endian=l  Flags=1  Version=1  Priority=0 Cookie=4
  Sender=org.freedesktop.DBus  Destination=:1.146  Path=/org/freedesktop/DBus  Interface=org.freedesktop.DBus  Member=NameLost
  MESSAGE "s" {
          STRING ":1.146";
  };

‣ Type=method_call  Endian=l  Flags=0  Version=1  Priority=0 Cookie=2
  Sender=:1.147  Destination=ir.elecplus.iot  Path=/ir/elecplus/iot/Daemon1/Tasks  Interface=org.freedesktop.DBus.Introspectable  Member=Introspect
  UniqueName=:1.147
  MESSAGE "" {
  };

‣ Type=method_return  Endian=l  Flags=1  Version=1  Priority=0 Cookie=46  ReplyCookie=2
  Sender=:1.111  Destination=:1.147
  UniqueName=:1.111
  MESSAGE "s" {
          STRING "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
 <interface name="org.freedesktop.DBus.Peer">
  <method name="Ping"/>
  <method name="GetMachineId">
   <arg type="s" name="machine_uuid" direction="out"/>
  </method>
 </interface>
 <interface name="org.freedesktop.DBus.Introspectable">
  <method name="Introspect">
   <arg name="data" type="s" direction="out"/>
  </method>
 </interface>
 <interface name="org.freedesktop.DBus.Properties">
  <method name="Get">
   <arg name="interface" direction="in" type="s"/>
   <arg name="property" direction="in" type="s"/>
   <arg name="value" direction="out" type="v"/>
  </method>
  <method name="GetAll">
   <arg name="interface" direction="in" type="s"/>
   <arg name="properties" direction="out" type="a{sv}"/>
  </method>
  <method name="Set">
   <arg name="interface" direction="in" type="s"/>
   <arg name="property" direction="in" type="s"/>
   <arg name="value" direction="in" type="v"/>
  </method>
  <signal name="PropertiesChanged">
   <arg type="s" name="interface"/>
   <arg type="a{sv}" name="changed_properties"/>
   <arg type="as" name="invalidated_properties"/>
  </signal>
 </interface>
 <interface name="ir.elecplus.iot.Daemon1.Tasks">
  <method name="New">
   <arg type="s" direction="in"/>
   <arg type="i" direction="out"/>
   <arg type="s" direction="out"/>
  </method>
  <method name="Tasks">
   <arg type="ai" direction="out"/>
  </method>
  <method name="Body">
   <arg type="ai" direction="in"/>
   <arg type="as" direction="out"/>
  </method>
  <method name="Cancel">
   <arg type="ai" direction="in"/>
   <arg type="ab" direction="out"/>
  </method>
  <method name="Status">
   <arg type="ai" direction="in"/>
   <arg type="as" direction="out"/>
  </method>
 </interface>
</node>
";
  };

‣ Type=method_call  Endian=l  Flags=0  Version=1  Priority=0 Cookie=3
  Sender=:1.147  Destination=ir.elecplus.iot  Path=/ir/elecplus/iot/Daemon1/Tasks  Interface=ir.elecplus.iot.Daemon1.Tasks  Member=New
  UniqueName=:1.147
  MESSAGE "s" {
          STRING "body";
  };

‣ Type=method_return  Endian=l  Flags=1  Version=1  Priority=0 Cookie=47  ReplyCookie=3
  Sender=:1.111  Destination=:1.147
  UniqueName=:1.111
  MESSAGE "is" {
          INT32 6;
          STRING "";
  };

Another point is, I implemented a mock-up server using adbus with same input/output signature, the client (written using adbus) runs successfully and no exception occurs. (My main server written in C using sd-bus library)

This is my mock-up server program:

import adbus
import asyncio

name2 = "ir.elecplus.iot"
path2 = "/ir/elecplus/iot/Daemon1/Tasks"
iface2 = "ir.elecplus.iot.Daemon1.Tasks"

async def main():
    service = adbus.Service(bus='session')
    proxy = adbus.client.Proxy(service, name2, path2,
        interface=iface2)
    await proxy.update()
    ret = await proxy.New("body")
    print(ret)

asyncio.run(main())
charleseidsness commented 4 years ago

This is strange, the output signature looks like it can be either 'i' or 's'?

  <method name="New">
   <arg type="s" direction="in"/>
   <arg type="i" direction="out"/>
   <arg type="s" direction="out"/>
  </method>

It's been a while since I worked with D-Bus introspect xml, I'm not sure what that means. Looking it up now.

charleseidsness commented 4 years ago

That is a legitimate way to define multiple output arguments, but it isn't supported by the proxy. It only supports a single return type.

https://github.com/ccxtechnologies/adbus/blob/f976d5046c7305db6f00188815c909e737f2f1b2/adbus/client/proxy.py#L172-L182

In your mocked test that worked what did the signature look like?

There is a chance that if you change line 180 of proxy.py: https://github.com/ccxtechnologies/adbus/blob/f976d5046c7305db6f00188815c909e737f2f1b2/adbus/client/proxy.py#L180

To:

self.return_signature += x.attrib['type']

it may work.

It'll be easier for you to test since you have all of your tooling setup.

hamid-rostami commented 4 years ago

Hi @charleseidsness, thanks for your response.

You're right, output signature of mock-up is different from main program. Return type of mock-up:

‣ Type=method_return  Endian=l  Flags=1  Version=1  Priority=0 Cookie=4  ReplyCookie=3
  Sender=:1.169  Destination=:1.172
  UniqueName=:1.169                                   
  MESSAGE "(is)" {
          STRUCT "is" { 
                  INT32 10;                            
                  STRING "success";
          };                                  
  }; 

Return type of main program:

‣ Type=method_return  Endian=l  Flags=1  Version=1  Priority=0 Cookie=47  ReplyCookie=3
  Sender=:1.111  Destination=:1.147
  UniqueName=:1.111
  MESSAGE "is" {
          INT32 6;
          STRING "";
  };

About changing in proxy.py source line 180. The result is no exception occurs anymore, but still just int part returned from proxy. If I should test something else, I'll be appreciate if you tell me.

charleseidsness commented 4 years ago

D-Bus supports multiple in and out values for method calls. The multiple ins map nicely to multiple arguments in Python, but there isn't a straight-forward equivalent to multiple outs.

We could convert it to a tuple if there are multiple outs, it may make sense with the Python unpacking syntax.

I don't have an easy way to test it, but it may be as simple as fudging the return signature into a struct. You'd have to count how many out definitions there are if you have more than 1 out enclose it in "()".

Something like this:


 self.arg_signatures = [] 
 self.return_signature = '' 
 num_outs = 0

 for x in etree.iter(): 
     if x.tag == 'method': 
         self.name = x.attrib['name'] 
     if x.tag == 'arg': 
         if x.attrib['direction'] == 'out': 
             self.return_signature += x.attrib['type']
             num_outs += 1
         elif x.attrib['direction'] == 'in': 
             self.arg_signatures.append(x.attrib['type']) 

  if num_outs > 1:
    self.return_signature = "(" + self.return_signature + ")"
hamid-rostami commented 4 years ago

Nice idea, but unfortunately didn't work. The reason of failure, I guess, is that the program is asking for (is), and there's no such a thing.

Here is occurred exception:

  File "client.py", line 18, in <module>
    asyncio.run(main())
  File "/usr/local/lib/python3.7/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/local/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
    return future.result()
  File "client.py", line 15, in main
    ret = await proxy.New("body")
  File "/usr/local/lib/python3.7/site-packages/adbus/client/proxy.py", line 202, in __call__
    timeout_ms=self.timeout_ms
  File "/usr/local/lib/python3.7/site-packages/adbus/client/call.py", line 48, in call
    raise call.response
  File "adbus/sdbus/call.pyx", line 13, in adbus.sdbus.call_callback
  File "adbus/sdbus/message.pyx", line 221, in adbus.sdbus.Message.read
  File "adbus/sdbus/message.pyx", line 170, in adbus.sdbus.Message._read_struct
adbus.sdbus.SdbusError: Failed to enter structure b'is'
charleseidsness commented 4 years ago

Thanks for trying.

I'll have to add this as a new feature, it may be a few days before I have some time to look at it.

hamid-rostami commented 4 years ago

I hope you get free time soon, feel free to ask me if I can help.

charleseidsness commented 4 years ago

I created a branch with a few updates for this new feature. I don't have time to test it (or even build it right now) but it should get things started. If you get a chance to test it that would be a big help.

feature-multi-return-proxy-call

hamid-rostami commented 4 years ago

Great work @charleseidsness, Problem fixed, now it's working for both services (mock-up and my C program) Do I need to provide log or something ?

charleseidsness commented 4 years ago

That's great, I'll issue a PR.