LEW21 / pydbus

Pythonic DBus library
GNU Lesser General Public License v2.1
326 stars 76 forks source link

Not receiving UNIX_FD property #54

Open ukBaz opened 7 years ago

ukBaz commented 7 years ago

This looks similar to #42 but the suggested modifications by @LEW21 do not seem to be having an effect. However I am right at the edge of my knowledge so apologies if this is a duplicate.

I am looking to implement the BlueZ Profile DBus APi https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/profile-api.txt#n104

I have implemented, published and registered the NewConnection method

class Profile(object):
    """
      <node>
        <interface name='org.bluez.Profile1'>
          <method name='Release'>
          </method>
          <method name='NewConnection'>
            <arg type='o' name='path' direction='in'/>
            <arg type='h' name='fd' direction='in'/>
            <arg type='a{sv}' name='properties' direction='in'/>
          </method>
          <method name='RequestDisconnection'>
            <arg type='o' name='path' direction='in'/>
          </method>
        </interface>
      </node>
    """

    def NewConnection(self, path, fd, properties):
        print('New connection', path, type(fd), properties)
        print('fd: ', fd)
        print('Is a tty? ', os.isatty(fd))
        print('tty name: ', os.ttyname(fd))

When a new connection is made the the Bluetooth daemon calls the NewConnection method but the value of fd is wrong:

New connection /org/bluez/hci0/dev_64_BC_0C_F6_22_F8 <class 'int'> {}
fd:  0
Is a tty?  True
tty name:  /dev/pts/0

I say wrong because it gives the tty of the terminal I'm running in and If I monitor the bus then I can see the bluetoothd sending the command with UNIX_FD = 4

linaro@linaro-alip:~/python/pydbus/pydbus$ sudo busctl monitor org.bluez
‣ Type=method_call  Endian=l  Flags=0  Version=1  Priority=0 Cookie=67
  Sender=:1.120  Destination=:1.132  Path=/ukBaz/bluezero/spp  Interface=org.bluez.Profile1  Member=NewConnection
  UniqueName=:1.120
  MESSAGE "oha{sv}" {
          OBJECT_PATH "/org/bluez/hci0/dev_64_BC_0C_F6_22_F8";
          UNIX_FD 4;
          ARRAY "{sv}" {
          };
  };

To confirm what the sender is

linaro@linaro-alip:~/python/pydbus/pydbus$ sudo busctl list
NAME                                       PID PROCESS         USER             CONNECTION    UNIT                      SESSION    DESCRIPTION        
:1.0                                      1696 systemd-logind  root             :1.0          systemd-logind.service    -          -                  
:1.1                                         1 systemd         root             :1.1          init.scope                -          -                  
:1.10                                     1844 systemd-resolve systemd-resolve  :1.10         systemd-resolved.service  -          -                  
:1.11                                     1860 sddm            root             :1.11         sddm.service              -          -                  
:1.12                                     1904 Xorg            root             :1.12         sddm.service              -          -                  
:1.120                                    7147 bluetoothd      root             :1.120        bluetooth.service         -          -     
...

Versions I am running

$ pip3 freeze | grep pydbus
pydbus==0.6.0
$python3 -V
Python 3.5.3
$ uname -a
Linux linaro-alip 4.9.0-linaro-lt-qcom #1 SMP PREEMPT Mon Apr 10 16:08:49 UTC 2017 aarch64 GNU/Linux

As I say, I am right on the edge of my knowledge so I am not sure how to fix this. If someone can give me some guidance then I am willing to do more on this.

molobrakos commented 5 years ago

Just stumbled upon this bug as well, is there any workaround?

molobrakos commented 5 years ago

Implemented support here: https://github.com/LEW21/pydbus/compare/master...molobrakos:unixfd Both passing and receiving unix file descriptors should work, but I have mainly tested receiving, and verified that receiving file descriptors for org.bluez.Profile1:NewConnection works, which is my main use case right now. @ukBaz, maybe you are interested in testing this out as well? @LEW21, feel free to steal/modify the code, or please let me know it you want it as a PR as-is (thank you for your beautiful and very useful library btw)?

ukBaz commented 5 years ago

Thanks @molobrakos for taking a look at this. I've had time to do some testing tonight.

It is still not coming out as <type 'dbus.UnixFd'> in my tests . When I run with your unixfd branch I still see <type 'int'>

I've been using org.bluez.Profile1:NewConnection to create a Serial Port Profile. Do you have some example code?

molobrakos commented 5 years ago

@ukBaz, thanks for testing! The file descriptor will be received as int (not dbus.UnixFd), have you tried reading from it using os.read?

molobrakos commented 5 years ago

I could add a proper test case to the tests-directory (but it wasn't obvious anyone, including the maintainer, cared). Anyway, here is how I use it:

UUID_SPP = "00001101-0000-1000-8000-00805f9b34fb"

class Profile:

    def __init__(self, read_callback):
        _LOGGER.debug("Init profile")
        self.read_callback = read_callback
        self.fd = None

    def Release(self):
        _LOGGER.debug("Release")

    def NewConnection(self, path, fd, properties):

        _LOGGER.error("New connection %s %s %s",
                      path, fd, properties)

        if self.fd != None:
            _LOGGER.error("Should not happen")

        self.fd = os.dup(fd)

        _LOGGER.debug("Got fd %s, dup as %d", fd, self.fd)

        def fd_read_callback(fd, conditions):
            _LOGGER.debug("io cb on fd %d (self.fd: %d)", fd, self.fd)
            assert(self.fd == fd)
            data = os.read(fd, 1024)
            _LOGGER.debug("Read %d bytes: %s: %s", len(data), 
                 binascii.hexlify(data), data.decode("ascii"))
            self.read_callback(path, data.decode("ascii"))

            # We are done
            # os.close(self.fd)
            # gobject.source_remove(io_id)
            # self.fd = None

            return True

        io_id = GObject.io_add_watch(self.fd,
                                     GObject.PRIORITY_DEFAULT,
                                     GObject.IO_IN | GObject.IO_PRI,
                                     fd_read_callback)

    def RequestDisconnection(self, path):
        _LOGGER.debug("RequestDisconnection: %s", path)
        if self.fd:
            os.close(self.fd)
            # gobject.source_remove(io_id)
            self.fd = None

def register_spp_profile():
    profile_path = "/foo/bar/profile"
    opts = dict(
        AutoConnect=pydbus.Variant("b", True),
        Role=pydbus.Variant("s", "server"),
        Channel=pydbus.Variant("q", 1),
        RequireAuthorization=pydbus.Variant("b", False),
        RequireAuthentication=pydbus.Variant("b", False),
        Name=pydbus.Variant("s", "Foobar")
    )

    _LOGGER.info("Creating Serial Port Profile")

    def read_cb(path, value):
        _LOGGER.debug("Read value %s", value)

    pydbus.SystemBus().register_object(
        profile_path,
        Profile(read_cb),
        pathlib.Path(__file__).with_name("btspp.xml").read_text())

    blus.profile_manager().RegisterProfile(
        profile_path,
        UUID_SPP, opts)

    _LOGGER.info("Registered profile on %s", profile_path)

(Btw, I don't think I have seen Release or RequestDisconnection being called, but I guess that is a Bluez-issue)

molobrakos commented 5 years ago

Actually there seems to be a problem returning fd:s with the current code (as in direction="out"). Sending and receiving should work. Fixed in latest commit. Also added a test case.

penguin359 commented 1 month ago

Any chance this has been merged in yet? I am looking to use something like this for accepting incoming RFCOMM connections passed in from BlueZ.