Example #1
0
async def test_object_manager():
    expected_reply = {
        '/test/path/deeper': {
            'test.interface2': {
                'Bar': Variant('s', 'str'),
                'Foo': Variant('y', 42)
            }
        }
    }
    reply_ext = {
        '/test/path': {
            'test.interface1': {},
            'test.interface2': {
                'Bar': Variant('s', 'str'),
                'Foo': Variant('y', 42)
            }
        }
    }

    bus1 = await MessageBus().connect()
    bus2 = await MessageBus().connect()

    interface = ExampleInterface('test.interface1')
    interface2 = ExampleComplexInterface('test.interface2')

    export_path = '/test/path'
    bus1.export(export_path, interface)
    bus1.export(export_path, interface2)
    bus1.export(export_path + '/deeper', interface2)

    reply_root = await bus2.call(
        Message(destination=bus1.unique_name,
                path='/',
                interface='org.freedesktop.DBus.ObjectManager',
                member='GetManagedObjects'))

    reply_level1 = await bus2.call(
        Message(destination=bus1.unique_name,
                path=export_path,
                interface='org.freedesktop.DBus.ObjectManager',
                member='GetManagedObjects'))

    reply_level2 = await bus2.call(
        Message(destination=bus1.unique_name,
                path=export_path + '/deeper',
                interface='org.freedesktop.DBus.ObjectManager',
                member='GetManagedObjects'))

    assert reply_root.signature == 'a{oa{sa{sv}}}'
    assert reply_level1.signature == 'a{oa{sa{sv}}}'
    assert reply_level2.signature == 'a{oa{sa{sv}}}'

    assert reply_level2.body == [{}]
    assert reply_level1.body == [expected_reply]
    expected_reply.update(reply_ext)
    assert reply_root.body == [expected_reply]
Example #2
0
    async def write_gatt_descriptor(
            self, handle: int, data: Union[bytes, bytearray,
                                           memoryview]) -> None:
        """Perform a write operation on the specified GATT descriptor.

        Args:
            handle (int): The handle of the descriptor to read from.
            data (bytes or bytearray): The data to send.

        """
        if not self.is_connected:
            raise BleakError("Not connected")

        descriptor = self.services.get_descriptor(handle)
        if not descriptor:
            raise BleakError(
                "Descriptor with handle {0} was not found!".format(handle))

        reply = await self._bus.call(
            Message(
                destination=defs.BLUEZ_SERVICE,
                path=descriptor.path,
                interface=defs.GATT_DESCRIPTOR_INTERFACE,
                member="WriteValue",
                signature="aya{sv}",
                body=[bytes(data), {
                    "type": Variant("s", "command")
                }],
            ))
        assert_reply(reply)

        logger.debug("Write Descriptor {0} | {1}: {2}".format(
            handle, descriptor.path, data))
Example #3
0
    def set_scanning_filter(self, **kwargs):
        """Sets OS level scanning filters for the BleakScanner.

        For possible values for `filters`, see the parameters to the
        ``SetDiscoveryFilter`` method in the `BlueZ docs
        <https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt?h=5.48&id=0d1e3b9c5754022c779da129025d493a198d49cf>`_

        See variant types here: <https://python-dbus-next.readthedocs.io/en/latest/type-system/>

        Keyword Args:
            filters (dict): A dict of filters to be applied on discovery.

        """
        for k, v in kwargs.get("filters", {}).items():
            if k == "UUIDs":
                self._filters[k] = Variant("as", v)
            elif k == "RSSI":
                self._filters[k] = Variant("n", v)
            elif k == "DuplicateData":
                self._filters[k] = Variant("b", v)
            elif k == "Pathloss":
                self._filters[k] = Variant("n", v)
            elif k == "Transport":
                self._filters[k] = Variant("s", v)
            else:
                logger.warning("Filter '%s' is not currently supported." % k)

        if "Transport" not in self._filters:
            self._filters["Transport"] = Variant("s", "le")
Example #4
0
async def test_standard_interface_properties():
    # standard interfaces have no properties, but should still behave correctly
    # when you try to call the methods anyway (#49)
    bus1 = await MessageBus().connect()
    bus2 = await MessageBus().connect()
    interface = ExampleInterface('test.interface1')
    export_path = '/test/path'
    bus1.export(export_path, interface)

    for iface in [
            'org.freedesktop.DBus.Properties', 'org.freedesktop.DBus.Introspectable',
            'org.freedesktop.DBus.Peer', 'org.freedesktop.DBus.ObjectManager'
    ]:

        result = await bus2.call(
            Message(destination=bus1.unique_name,
                    path=export_path,
                    interface='org.freedesktop.DBus.Properties',
                    member='Get',
                    signature='ss',
                    body=[iface, 'anything']))
        assert result.message_type is MessageType.ERROR
        assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value

        result = await bus2.call(
            Message(destination=bus1.unique_name,
                    path=export_path,
                    interface='org.freedesktop.DBus.Properties',
                    member='Set',
                    signature='ssv',
                    body=[iface, 'anything', Variant('s', 'new thing')]))
        assert result.message_type is MessageType.ERROR
        assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value

        result = await bus2.call(
            Message(destination=bus1.unique_name,
                    path=export_path,
                    interface='org.freedesktop.DBus.Properties',
                    member='GetAll',
                    signature='s',
                    body=[iface]))
        assert result.message_type is MessageType.METHOD_RETURN
        assert result.body == [{}]
Example #5
0
async def test_high_level_service_fd_passing(event_loop):
    bus1 = await MessageBus(negotiate_unix_fd=True).connect()
    bus2 = await MessageBus(negotiate_unix_fd=True).connect()

    interface_name = 'test.interface'
    interface = ExampleInterface(interface_name)
    export_path = '/test/path'

    async def call(member, signature='', body=[], unix_fds=[], iface=interface.name):
        return await bus2.call(
            Message(destination=bus1.unique_name,
                    path=export_path,
                    interface=iface,
                    member=member,
                    signature=signature,
                    body=body,
                    unix_fds=unix_fds))

    bus1.export(export_path, interface)

    # test that an fd can be returned by the service
    reply = await call('ReturnsFd')
    assert reply.message_type == MessageType.METHOD_RETURN, reply.body
    assert reply.signature == 'h'
    assert len(reply.unix_fds) == 1
    assert_fds_equal(interface.get_last_fd(), reply.unix_fds[0])
    interface.cleanup()
    os.close(reply.unix_fds[0])

    # test that an fd can be sent to the service
    fd = open_file()
    reply = await call('AcceptsFd', signature='h', body=[0], unix_fds=[fd])
    assert reply.message_type == MessageType.METHOD_RETURN, reply.body
    assert_fds_equal(interface.get_last_fd(), fd)

    interface.cleanup()
    os.close(fd)

    # signals
    fut = event_loop.create_future()

    def fd_listener(msg):
        if msg.sender == bus1.unique_name and msg.message_type == MessageType.SIGNAL:
            fut.set_result(msg)

    reply = await bus2.call(
        Message(destination='org.freedesktop.DBus',
                path='/org/freedesktop/DBus',
                member='AddMatch',
                signature='s',
                body=[f"sender='{bus1.unique_name}'"]))
    assert reply.message_type == MessageType.METHOD_RETURN

    bus2.add_message_handler(fd_listener)
    interface.SignalFd()
    reply = await fut

    assert len(reply.unix_fds) == 1
    assert reply.body == [0]
    assert_fds_equal(reply.unix_fds[0], interface.get_last_fd())

    interface.cleanup()
    os.close(reply.unix_fds[0])

    # properties
    reply = await call('Get',
                       'ss', [interface_name, 'PropFd'],
                       iface='org.freedesktop.DBus.Properties')
    assert reply.message_type == MessageType.METHOD_RETURN, reply.body
    assert reply.body[0].signature == 'h'
    assert reply.body[0].value == 0
    assert len(reply.unix_fds) == 1
    assert_fds_equal(interface.get_last_fd(), reply.unix_fds[0])
    interface.cleanup()
    os.close(reply.unix_fds[0])

    fd = open_file()
    reply = await call('Set',
                       'ssv', [interface_name, 'PropFd', Variant('h', 0)],
                       iface='org.freedesktop.DBus.Properties',
                       unix_fds=[fd])
    assert reply.message_type == MessageType.METHOD_RETURN, reply.body
    assert_fds_equal(interface.get_last_fd(), fd)
    interface.cleanup()
    os.close(fd)

    reply = await call('GetAll', 's', [interface_name], iface='org.freedesktop.DBus.Properties')
    assert reply.message_type == MessageType.METHOD_RETURN, reply.body
    assert reply.body[0]['PropFd'].signature == 'h'
    assert reply.body[0]['PropFd'].value == 0
    assert len(reply.unix_fds) == 1
    assert_fds_equal(interface.get_last_fd(), reply.unix_fds[0])
    interface.cleanup()
    os.close(reply.unix_fds[0])

    for bus in [bus1, bus2]:
        bus.disconnect()
Example #6
0
     "foo": 0
 }], [3]), id='Signature: "a{sh}"'),
 pytest.param({
     "foo": 3,
     "bar": 6
 },
              'a{sh}', ([{
                  "foo": 0,
                  "bar": 1
              }], [3, 6]),
              id='Signature: "a{sh}"'),
 pytest.param(
     {"foo": [3, 8]}, 'a{sah}', ([{
         "foo": [0, 1]
     }], [3, 8]), id='Signature: "a{sah}"'),
 pytest.param({'foo': Variant('t', 100)},
              'a{sv}', ([{
                  'foo': Variant('t', 100)
              }], []),
              id='Signature: "a{sv}"'),
 pytest.param(['one', ['two', [Variant('s', 'three')]]],
              '(s(s(v)))', ([['one', ['two', [Variant('s', 'three')]]]], []),
              id='Signature: "(s(s(v)))"'),
 pytest.param(Variant('h', 2), 'v', ([Variant('h', 0)], [2]), id='Variant with: "h"'),
 pytest.param(Variant('(hh)', [2, 8]),
              'v', ([Variant('(hh)', [0, 1])], [2, 8]),
              id='Variant with: "(hh)"'),
 pytest.param(
     Variant('ah', [2, 4]), 'v', ([Variant('ah', [0, 1])], [2, 4]), id='Variant with: "ah"'),
 pytest.param(Variant('(ss)', ['hello', 'world']),
              'v', ([Variant('(ss)', ['hello', 'world'])], []),
Example #7
0
    async def connect(self, **kwargs) -> bool:
        """Connect to the specified GATT server.

        Keyword Args:
            timeout (float): Timeout for required ``BleakScanner.find_device_by_address`` call. Defaults to 10.0.

        Returns:
            Boolean representing connection status.

        Raises:
            BleakError: If the device is already connected or if the device could not be found.
            BleakDBusError: If there was a D-Bus error
            asyncio.TimeoutError: If the connection timed out
        """
        logger.debug(
            f"Connecting to device @ {self.address} with {self._adapter}")

        if self.is_connected:
            raise BleakError("Client is already connected")

        # A Discover must have been run before connecting to any devices.
        # Find the desired device before trying to connect.
        timeout = kwargs.get("timeout", self._timeout)
        if self._device_path is None:
            device = await BleakScannerBlueZDBus.find_device_by_address(
                self.address, timeout=timeout, adapter=self._adapter)

            if device:
                self._device_info = device.details.get("props")
                self._device_path = device.details["path"]
            else:
                raise BleakError(
                    "Device with address {0} was not found.".format(
                        self.address))

        # Create system bus
        self._bus = await MessageBus(bus_type=BusType.SYSTEM,
                                     negotiate_unix_fd=True).connect()

        try:
            # Add signal handlers. These monitor the device D-Bus object and
            # all of its descendats (services, characteristics, descriptors).
            # This we always have an up-to-date state for all of these that is
            # maintained automatically in the background.

            self._bus.add_message_handler(self._parse_msg)

            rules = MatchRules(
                interface=defs.OBJECT_MANAGER_INTERFACE,
                member="InterfacesAdded",
                arg0path=f"{self._device_path}/",
            )
            reply = await add_match(self._bus, rules)
            assert_reply(reply)

            rules = MatchRules(
                interface=defs.OBJECT_MANAGER_INTERFACE,
                member="InterfacesRemoved",
                arg0path=f"{self._device_path}/",
            )
            reply = await add_match(self._bus, rules)
            assert_reply(reply)

            rules = MatchRules(
                interface=defs.PROPERTIES_INTERFACE,
                member="PropertiesChanged",
                path_namespace=self._device_path,
            )
            reply = await add_match(self._bus, rules)
            assert_reply(reply)

            # Find the HCI device to use for scanning and get cached device properties
            reply = await self._bus.call(
                Message(
                    destination=defs.BLUEZ_SERVICE,
                    path="/",
                    member="GetManagedObjects",
                    interface=defs.OBJECT_MANAGER_INTERFACE,
                ))
            assert_reply(reply)

            interfaces_and_props: Dict[str, Dict[str, Variant]] = reply.body[0]

            # The device may have been removed from BlueZ since the time we stopped scanning
            if self._device_path not in interfaces_and_props:
                # Sometimes devices can be removed from the BlueZ object manager
                # before we connect to them. In this case we try using the
                # org.bluez.Adapter1.ConnectDevice method instead. This method
                # requires that bluetoothd is run with the --experimental flag
                # and is available since BlueZ 5.49.
                logger.debug(
                    f"org.bluez.Device1 object not found, trying org.bluez.Adapter1.ConnectDevice ({self._device_path})"
                )
                reply = await asyncio.wait_for(
                    self._bus.call(
                        Message(
                            destination=defs.BLUEZ_SERVICE,
                            interface=defs.ADAPTER_INTERFACE,
                            path=f"/org/bluez/{self._adapter}",
                            member="ConnectDevice",
                            signature="a{sv}",
                            body=[{
                                "Address":
                                Variant("s", self._device_info["Address"]),
                                "AddressType":
                                Variant("s", self._device_info["AddressType"]),
                            }],
                        )),
                    timeout,
                )

                # FIXME: how to cancel connection if timeout?

                if (reply.message_type == MessageType.ERROR and
                        reply.error_name == ErrorType.UNKNOWN_METHOD.value):
                    logger.debug(
                        f"org.bluez.Adapter1.ConnectDevice not found ({self._device_path}), try enabling bluetoothd --experimental"
                    )
                    raise BleakError(
                        "Device with address {0} could not be found. "
                        "Try increasing `timeout` value or moving the device closer."
                        .format(self.address))

                assert_reply(reply)
            else:
                # required interface
                self._properties = unpack_variants(interfaces_and_props[
                    self._device_path][defs.DEVICE_INTERFACE])

                # optional interfaces - services and characteristics may not
                # be populated yet
                for path, interfaces in interfaces_and_props.items():
                    if not path.startswith(self._device_path):
                        continue

                    if defs.GATT_SERVICE_INTERFACE in interfaces:
                        obj = unpack_variants(
                            interfaces[defs.GATT_SERVICE_INTERFACE])
                        self.services.add_service(
                            BleakGATTServiceBlueZDBus(obj, path))

                    if defs.GATT_CHARACTERISTIC_INTERFACE in interfaces:
                        obj = unpack_variants(
                            interfaces[defs.GATT_CHARACTERISTIC_INTERFACE])
                        service = interfaces_and_props[obj["Service"]][
                            defs.GATT_SERVICE_INTERFACE]
                        uuid = service["UUID"].value
                        handle = extract_service_handle_from_path(
                            obj["Service"])
                        self.services.add_characteristic(
                            BleakGATTCharacteristicBlueZDBus(
                                obj, path, uuid, handle))

                    if defs.GATT_DESCRIPTOR_INTERFACE in interfaces:
                        obj = unpack_variants(
                            interfaces[defs.GATT_DESCRIPTOR_INTERFACE])
                        characteristic = interfaces_and_props[
                            obj["Characteristic"]][
                                defs.GATT_CHARACTERISTIC_INTERFACE]
                        uuid = characteristic["UUID"].value
                        handle = extract_service_handle_from_path(
                            obj["Characteristic"])
                        self.services.add_descriptor(
                            BleakGATTDescriptorBlueZDBus(
                                obj, path, uuid, handle))

                try:
                    reply = await asyncio.wait_for(
                        self._bus.call(
                            Message(
                                destination=defs.BLUEZ_SERVICE,
                                interface=defs.DEVICE_INTERFACE,
                                path=self._device_path,
                                member="Connect",
                            )),
                        timeout,
                    )
                    assert_reply(reply)
                except BaseException:
                    # calling Disconnect cancels any pending connect request
                    try:
                        reply = await self._bus.call(
                            Message(
                                destination=defs.BLUEZ_SERVICE,
                                interface=defs.DEVICE_INTERFACE,
                                path=self._device_path,
                                member="Disconnect",
                            ))
                        try:
                            assert_reply(reply)
                        except BleakDBusError as e:
                            # if the object no longer exists, then we know we
                            # are disconnected for sure, so don't need to log a
                            # warning about it
                            if e.dbus_error != ErrorType.UNKNOWN_OBJECT.value:
                                raise
                    except Exception as e:
                        logger.warning(
                            f"Failed to cancel connection ({self._device_path}): {e}"
                        )

                    raise

            if self.is_connected:
                logger.debug(f"Connection successful ({self._device_path})")
            else:
                raise BleakError(
                    f"Connection was not successful! ({self._device_path})")

            # Create a task that runs until the device is disconnected.
            self._disconnect_monitor_event = asyncio.Event()
            asyncio.ensure_future(self._disconnect_monitor())

            # Get all services. This means making the actual connection.
            await self.get_services()

            return True
        except BaseException:
            self._cleanup_all()
            raise
Example #8
0
    async def write_gatt_char(
        self,
        char_specifier: Union[BleakGATTCharacteristicBlueZDBus, int, str,
                              UUID],
        data: Union[bytes, bytearray, memoryview],
        response: bool = False,
    ) -> None:
        """Perform a write operation on the specified GATT characteristic.

        .. note::

            The version check below is for the "type" option to the
            "Characteristic.WriteValue" method that was added to `Bluez in 5.51
            <https://git.kernel.org/pub/scm/bluetooth/bluez.git/commit?id=fa9473bcc48417d69cc9ef81d41a72b18e34a55a>`_
            Before that commit, ``Characteristic.WriteValue`` was only "Write with
            response". ``Characteristic.AcquireWrite`` was `added in Bluez 5.46
            <https://git.kernel.org/pub/scm/bluetooth/bluez.git/commit/doc/gatt-api.txt?id=f59f3dedb2c79a75e51a3a0d27e2ae06fefc603e>`_
            which can be used to "Write without response", but for older versions
            of Bluez, it is not possible to "Write without response".

        Args:
            char_specifier (BleakGATTCharacteristicBlueZDBus, int, str or UUID): The characteristic to write
                to, specified by either integer handle, UUID or directly by the
                BleakGATTCharacteristicBlueZDBus object representing it.
            data (bytes or bytearray): The data to send.
            response (bool): If write-with-response operation should be done. Defaults to `False`.

        """
        if not self.is_connected:
            raise BleakError("Not connected")

        if not isinstance(char_specifier, BleakGATTCharacteristicBlueZDBus):
            characteristic = self.services.get_characteristic(char_specifier)
        else:
            characteristic = char_specifier

        if not characteristic:
            raise BleakError(
                "Characteristic {0} was not found!".format(char_specifier))
        if ("write" not in characteristic.properties
                and "write-without-response" not in characteristic.properties):
            raise BleakError(
                "Characteristic %s does not support write operations!" %
                str(characteristic.uuid))
        if not response and "write-without-response" not in characteristic.properties:
            response = True
            # Force response here, since the device only supports that.
        if (response and "write" not in characteristic.properties
                and "write-without-response" in characteristic.properties):
            response = False
            logger.warning(
                "Characteristic %s does not support Write with response. Trying without..."
                % str(characteristic.uuid))

        # See docstring for details about this handling.
        if not response and not self._can_write_without_response:
            raise BleakError(
                "Write without response requires at least BlueZ 5.46")
        if response or not self._write_without_response_workaround_needed:
            # TODO: Add OnValueUpdated handler for response=True?
            reply = await self._bus.call(
                Message(
                    destination=defs.BLUEZ_SERVICE,
                    path=characteristic.path,
                    interface=defs.GATT_CHARACTERISTIC_INTERFACE,
                    member="WriteValue",
                    signature="aya{sv}",
                    body=[
                        bytes(data),
                        {
                            "type":
                            Variant("s", "request" if response else "command")
                        },
                    ],
                ))
            assert_reply(reply)
        else:
            # Older versions of BlueZ don't have the "type" option, so we have
            # to write the hard way. This isn't the most efficient way of doing
            # things, but it works.
            reply = await self._bus.call(
                Message(
                    destination=defs.BLUEZ_SERVICE,
                    path=characteristic.path,
                    interface=defs.GATT_CHARACTERISTIC_INTERFACE,
                    member="AcquireWrite",
                    signature="a{sv}",
                    body=[{}],
                ))
            assert_reply(reply)
            fd = reply.unix_fds[0]
            try:
                os.write(fd, data)
            finally:
                os.close(fd)

        logger.debug("Write Characteristic {0} | {1}: {2}".format(
            characteristic.uuid, characteristic.path, data))
Example #9
0
    async def pair(self, *args, **kwargs) -> bool:
        """Pair with the peripheral.

        You can use ConnectDevice method if you already know the MAC address of the device.
        Else you need to StartDiscovery, Trust, Pair and Connect in sequence.

        Returns:
            Boolean regarding success of pairing.

        """
        # See if it is already paired.
        reply = await self._bus.call(
            Message(
                destination=defs.BLUEZ_SERVICE,
                path=self._device_path,
                interface=defs.PROPERTIES_INTERFACE,
                member="Get",
                signature="ss",
                body=[defs.DEVICE_INTERFACE, "Paired"],
            ))
        assert_reply(reply)
        if reply.body[0].value:
            logger.debug(
                f"BLE device @ {self.address} already paired with {self._adapter}"
            )
            return True

        # Set device as trusted.
        reply = await self._bus.call(
            Message(
                destination=defs.BLUEZ_SERVICE,
                path=self._device_path,
                interface=defs.PROPERTIES_INTERFACE,
                member="Set",
                signature="ssv",
                body=[defs.DEVICE_INTERFACE, "Trusted",
                      Variant("b", True)],
            ))
        assert_reply(reply)

        logger.debug("Pairing to BLE device @ {0} with {1}".format(
            self.address, self._adapter))

        reply = await self._bus.call(
            Message(
                destination=defs.BLUEZ_SERVICE,
                path=self._device_path,
                interface=defs.DEVICE_INTERFACE,
                member="Pair",
            ))
        assert_reply(reply)

        reply = await self._bus.call(
            Message(
                destination=defs.BLUEZ_SERVICE,
                path=self._device_path,
                interface=defs.PROPERTIES_INTERFACE,
                member="Get",
                signature="ss",
                body=[defs.DEVICE_INTERFACE, "Paired"],
            ))
        assert_reply(reply)

        return reply.body[0].value
Example #10
0
async def test_interface_add_remove_signal():
    bus1 = await MessageBus().connect()
    bus2 = await MessageBus().connect()

    await bus2.call(
        Message(destination='org.freedesktop.DBus',
                path='/org/freedesktop/DBus',
                interface='org.freedesktop.DBus',
                member='AddMatch',
                signature='s',
                body=[f'sender={bus1.unique_name}']))

    first_interface = ExampleInterface('test.interface.first')
    second_interface = SecondExampleInterface('test.interface.second')
    export_path = '/test/path'

    # add first interface
    async with ExpectMessage(
        bus1, bus2, 'org.freedesktop.DBus.ObjectManager') as expected_signal:
        bus1.export(export_path, first_interface)
        assert_signal_ok(signal=await expected_signal,
                         export_path=export_path,
                         member='InterfacesAdded',
                         signature='oa{sa{sv}}',
                         body=[
                             export_path, {
                                 'test.interface.first': {
                                     'test_prop': Variant('i', 42)
                                 }
                             }
                         ])

    # add second interface
    async with ExpectMessage(
            bus1, bus2,
            'org.freedesktop.DBus.ObjectManager') as expected_signal:
        bus1.export(export_path, second_interface)
        assert_signal_ok(signal=await expected_signal,
                         export_path=export_path,
                         member='InterfacesAdded',
                         signature='oa{sa{sv}}',
                         body=[
                             export_path, {
                                 'test.interface.second': {
                                     'str_prop': Variant('s', "abc"),
                                     'list_prop': Variant('ai', [1, 2, 3])
                                 }
                             }
                         ])

    # remove single interface
    async with ExpectMessage(
            bus1, bus2,
            'org.freedesktop.DBus.ObjectManager') as expected_signal:
        bus1.unexport(export_path, second_interface)
        assert_signal_ok(signal=await expected_signal,
                         export_path=export_path,
                         member='InterfacesRemoved',
                         signature='oas',
                         body=[export_path, ['test.interface.second']])

    # add second interface again
    async with ExpectMessage(
            bus1, bus2,
            'org.freedesktop.DBus.ObjectManager') as expected_signal:
        bus1.export(export_path, second_interface)
        await expected_signal

    # remove multiple interfaces
    async with ExpectMessage(
            bus1, bus2,
            'org.freedesktop.DBus.ObjectManager') as expected_signal:
        bus1.unexport(export_path)
        assert_signal_ok(signal=await expected_signal,
                         export_path=export_path,
                         member='InterfacesRemoved',
                         signature='oas',
                         body=[
                             export_path,
                             ['test.interface.first', 'test.interface.second']
                         ])
Example #11
0
def get_connection_from_interface(interface: Interface,
                                  name: str | None = None,
                                  uuid: str | None = None) -> Any:
    """Generate message argument for network interface update."""

    # Generate/Update ID/name
    if not name or not name.startswith("Supervisor"):
        name = f"Supervisor {interface.name}"
    if interface.type == InterfaceType.VLAN:
        name = f"{name}.{interface.vlan.id}"

    if interface.type == InterfaceType.ETHERNET:
        iftype = "802-3-ethernet"
    elif interface.type == InterfaceType.WIRELESS:
        iftype = "802-11-wireless"
    else:
        iftype = interface.type.value

    # Generate UUID
    if not uuid:
        uuid = str(uuid4())

    connection = {
        "id": Variant("s", name),
        "type": Variant("s", iftype),
        "uuid": Variant("s", uuid),
        "llmnr": Variant("i", 2),
        "mdns": Variant("i", 2),
    }

    if interface.type != InterfaceType.VLAN:
        connection["interface-name"] = Variant("s", interface.name)

    conn = {}
    conn[CONF_ATTR_CONNECTION] = connection

    ipv4 = {}
    if not interface.ipv4 or interface.ipv4.method == InterfaceMethod.AUTO:
        ipv4["method"] = Variant("s", "auto")
    elif interface.ipv4.method == InterfaceMethod.DISABLED:
        ipv4["method"] = Variant("s", "disabled")
    else:
        ipv4["method"] = Variant("s", "manual")
        ipv4["dns"] = Variant(
            "au",
            [
                socket.htonl(int(ip_address))
                for ip_address in interface.ipv4.nameservers
            ],
        )

        adressdata = []
        for address in interface.ipv4.address:
            adressdata.append({
                "address":
                Variant("s", str(address.ip)),
                "prefix":
                Variant("u", int(address.with_prefixlen.split("/")[-1])),
            })

        ipv4["address-data"] = Variant("aa{sv}", adressdata)
        ipv4["gateway"] = Variant("s", str(interface.ipv4.gateway))

    conn[CONF_ATTR_IPV4] = ipv4

    ipv6 = {}
    if not interface.ipv6 or interface.ipv6.method == InterfaceMethod.AUTO:
        ipv6["method"] = Variant("s", "auto")
    elif interface.ipv6.method == InterfaceMethod.DISABLED:
        ipv6["method"] = Variant("s", "disabled")
    else:
        ipv6["method"] = Variant("s", "manual")
        ipv6["dns"] = Variant(
            "aay",
            [ip_address.packed for ip_address in interface.ipv6.nameservers])

        adressdata = []
        for address in interface.ipv6.address:
            adressdata.append({
                "address":
                Variant("s", str(address.ip)),
                "prefix":
                Variant("u", int(address.with_prefixlen.split("/")[-1])),
            })

        ipv6["address-data"] = Variant("aa{sv}", adressdata)
        ipv6["gateway"] = Variant("s", str(interface.ipv6.gateway))

    conn[CONF_ATTR_IPV6] = ipv6

    if interface.type == InterfaceType.ETHERNET:
        conn[CONF_ATTR_802_ETHERNET] = {
            ATTR_ASSIGNED_MAC: Variant("s", "preserve")
        }
    elif interface.type == "vlan":
        conn[CONF_ATTR_VLAN] = {
            "id": Variant("u", interface.vlan.id),
            "parent": Variant("s", interface.vlan.interface),
        }
    elif interface.type == InterfaceType.WIRELESS:
        wireless = {
            ATTR_ASSIGNED_MAC: Variant("s", "preserve"),
            "ssid": Variant("ay", interface.wifi.ssid.encode("UTF-8")),
            "mode": Variant("s", "infrastructure"),
            "powersave": Variant("i", 1),
        }
        conn[CONF_ATTR_802_WIRELESS] = wireless

        if interface.wifi.auth != "open":
            wireless["security"] = Variant("s",
                                           CONF_ATTR_802_WIRELESS_SECURITY)
            wireless_security = {}
            if interface.wifi.auth == "wep":
                wireless_security["auth-alg"] = Variant("s", "none")
                wireless_security["key-mgmt"] = Variant("s", "open")
            elif interface.wifi.auth == "wpa-psk":
                wireless_security["auth-alg"] = Variant("s", "open")
                wireless_security["key-mgmt"] = Variant("s", "wpa-psk")

            if interface.wifi.psk:
                wireless_security["psk"] = Variant("s", interface.wifi.psk)
            conn[CONF_ATTR_802_WIRELESS_SECURITY] = wireless_security

    return conn