From 7a6663ba8049a66fdbb94db53a06a5b5e402b1dc Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 18 Oct 2021 23:06:44 +0200 Subject: [PATCH] Use Python dbus-next D-Bus library (#3234) * Use the correct interface name to get properties of systemd It seems that gdbus (or systemd) automatically pick the correct interface and return the properties. However, dbussy requires the correct interface name to get all properties. * Don't expect array from Strength property The property returns a type "y" which equates to "guchar": https://developer-old.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.AccessPoint.html#gdbus-property-org-freedesktop-NetworkManager-AccessPoint.Strength It seems that the old D-Bus implementation returned an array. With dbus-next a integer is returned, so no list indexing required. * Support signals and remove no longer used tests and code * Pass rauc update file path as string That is what the interface is expecting, otherwise the new lib chocks on the Pathlib type. * Support Network configuration with dbus-next Assemble Python native objects and pass them to dbus-next. Use dbus-next specific Variant class where necessary. * Use org.freedesktop.NetworkManager.Connection.Active.StateChanged org.freedesktop.NetworkManager.Connection.Active.PropertyChanged is depricated. Also it seems that StateChanged leads to fewer and more accurate signals. * Pass correct data type to RequestScan. RequestScan expects an option dictionary. Pass an empty option dictionary to it. * Update unit tests Replace gdbus specific fixtures with json files representing the return values. Those can be easily converted into native Python objects. * Rename D-Bus utils module gdbus to dbus --- Dockerfile | 1 - requirements.txt | 1 + setup.py | 2 +- supervisor/bootstrap.py | 5 - supervisor/dbus/agent/__init__.py | 2 +- supervisor/dbus/agent/apparmor.py | 2 +- supervisor/dbus/agent/cgroup.py | 2 +- supervisor/dbus/agent/datadisk.py | 2 +- supervisor/dbus/agent/system.py | 2 +- supervisor/dbus/const.py | 4 +- supervisor/dbus/hostname.py | 2 +- supervisor/dbus/interface.py | 2 +- supervisor/dbus/logind.py | 2 +- supervisor/dbus/network/__init__.py | 12 +- supervisor/dbus/network/accesspoint.py | 4 +- supervisor/dbus/network/connection.py | 2 +- supervisor/dbus/network/dns.py | 2 +- supervisor/dbus/network/interface.py | 2 +- .../{setting.py => setting/__init__.py} | 23 +- supervisor/dbus/network/setting/generate.py | 146 +++++ supervisor/dbus/network/settings.py | 6 +- supervisor/dbus/network/wireless.py | 4 +- supervisor/dbus/payloads/__init__.py | 1 - supervisor/dbus/payloads/generate.py | 58 -- .../dbus/payloads/interface_update.tmpl | 106 ---- supervisor/dbus/rauc.py | 4 +- supervisor/dbus/systemd.py | 6 +- supervisor/dbus/timedate.py | 2 +- supervisor/exceptions.py | 18 +- supervisor/host/network.py | 20 +- supervisor/utils/dbus.py | 249 +++++++++ supervisor/utils/gdbus.py | 383 -------------- tests/common.py | 3 +- tests/conftest.py | 45 +- tests/dbus/network/test_setting.py | 36 ++ .../payloads/test_interface_update_payload.py | 271 ---------- tests/dbus/test_systemd.py | 2 +- .../io_hass_os_AppArmor-LoadProfile.fixture | 1 - .../io_hass_os_AppArmor-LoadProfile.json | 1 + .../io_hass_os_AppArmor-UnloadProfile.fixture | 1 - .../io_hass_os_AppArmor-UnloadProfile.json | 1 + ...o_hass_os_CGroup-AddDevicesAllowed.fixture | 1 - .../io_hass_os_CGroup-AddDevicesAllowed.json | 1 + .../io_hass_os_DataDisk-ChangeDevice.fixture | 1 - .../io_hass_os_DataDisk-ChangeDevice.json | 1 + .../io_hass_os_DataDisk-ReloadDevice.fixture | 1 - .../io_hass_os_DataDisk-ReloadDevice.json | 1 + ..._hass_os_System-ScheduleWipeDevice.fixture | 1 - .../io_hass_os_System-ScheduleWipeDevice.json | 1 + ..._NetworkManager-ActivateConnection.fixture | 1 - ...top_NetworkManager-ActivateConnection.json | 1 + ...rkManager-AddAndActivateConnection.fixture | 1 - ...tworkManager-AddAndActivateConnection.json | 1 + ...top_NetworkManager-CheckConnectivity.json} | 0 .../org_freedesktop_NetworkManager.fixture | 1 - ...ktop_NetworkManager_AccessPoint_43099.json | 2 +- ...ktop_NetworkManager_AccessPoint_43100.json | 2 +- ...nager_Devices_3-GetAllAccessPoints.fixture | 1 - ...kManager_Devices_3-GetAllAccessPoints.json | 1 + ...tworkManager_Devices_3-RequestScan.fixture | 1 - ..._NetworkManager_Devices_3-RequestScan.json | 1 + ...p_NetworkManager_Settings_1-Delete.fixture | 1 - ...ktop_NetworkManager_Settings_1-Delete.json | 1 + ...workManager_Settings_1-GetSettings.fixture | 1 - ...NetworkManager_Settings_1-GetSettings.json | 1 + ...p_NetworkManager_Settings_1-Update.fixture | 1 - ...ktop_NetworkManager_Settings_1-Update.json | 1 + ...esktop_hostname1-SetStaticHostname.fixture | 1 - ...eedesktop_hostname1-SetStaticHostname.json | 1 + .../org_freedesktop_timedate1-SetNTP.fixture | 1 - .../org_freedesktop_timedate1-SetNTP.json | 1 + .../org_freedesktop_timedate1-SetTime.fixture | 1 - .../org_freedesktop_timedate1-SetTime.json | 1 + tests/host/test_connectivity.py | 4 +- tests/utils/test_dbus.py | 29 + tests/utils/test_gvariant_parser.py | 497 ------------------ 76 files changed, 574 insertions(+), 1427 deletions(-) rename supervisor/dbus/network/{setting.py => setting/__init__.py} (90%) create mode 100644 supervisor/dbus/network/setting/generate.py delete mode 100644 supervisor/dbus/payloads/__init__.py delete mode 100644 supervisor/dbus/payloads/generate.py delete mode 100644 supervisor/dbus/payloads/interface_update.tmpl create mode 100644 supervisor/utils/dbus.py delete mode 100644 supervisor/utils/gdbus.py create mode 100644 tests/dbus/network/test_setting.py delete mode 100644 tests/dbus/payloads/test_interface_update_payload.py delete mode 100644 tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture create mode 100644 tests/fixtures/io_hass_os_AppArmor-LoadProfile.json delete mode 100644 tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture create mode 100644 tests/fixtures/io_hass_os_AppArmor-UnloadProfile.json delete mode 100644 tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture create mode 100644 tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.json delete mode 100644 tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture create mode 100644 tests/fixtures/io_hass_os_DataDisk-ChangeDevice.json delete mode 100644 tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture create mode 100644 tests/fixtures/io_hass_os_DataDisk-ReloadDevice.json delete mode 100644 tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture create mode 100644 tests/fixtures/io_hass_os_System-ScheduleWipeDevice.json delete mode 100644 tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.fixture create mode 100644 tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.json delete mode 100644 tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.fixture create mode 100644 tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.json rename tests/fixtures/{org_freedesktop_NetworkManager-CheckConnectivity.fixture => org_freedesktop_NetworkManager-CheckConnectivity.json} (100%) delete mode 100644 tests/fixtures/org_freedesktop_NetworkManager.fixture delete mode 100644 tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.fixture create mode 100644 tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.json delete mode 100644 tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.fixture create mode 100644 tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.json delete mode 100644 tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.fixture create mode 100644 tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.json delete mode 100644 tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.fixture create mode 100644 tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.json delete mode 100644 tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.fixture create mode 100644 tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.json delete mode 100644 tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.fixture create mode 100644 tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.json delete mode 100644 tests/fixtures/org_freedesktop_timedate1-SetNTP.fixture create mode 100644 tests/fixtures/org_freedesktop_timedate1-SetNTP.json delete mode 100644 tests/fixtures/org_freedesktop_timedate1-SetTime.fixture create mode 100644 tests/fixtures/org_freedesktop_timedate1-SetTime.json create mode 100644 tests/utils/test_dbus.py delete mode 100644 tests/utils/test_gvariant_parser.py diff --git a/Dockerfile b/Dockerfile index 172a9ce18..95578a589 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,6 @@ RUN \ eudev \ eudev-libs \ git \ - glib \ libffi \ libpulse \ musl \ diff --git a/requirements.txt b/requirements.txt index 63d4610f2..a740e7b76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ pyudev==0.22.0 ruamel.yaml==0.15.100 sentry-sdk==1.4.3 voluptuous==0.12.2 +dbus-next==0.2.3 diff --git a/setup.py b/setup.py index f0a8d8b75..9f2bd2431 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ setup( "supervisor.api", "supervisor.backups", "supervisor.dbus.network", - "supervisor.dbus.payloads", + "supervisor.dbus.network.setting", "supervisor.dbus", "supervisor.discovery.services", "supervisor.discovery", diff --git a/supervisor/bootstrap.py b/supervisor/bootstrap.py index bb08b2d82..b31849234 100644 --- a/supervisor/bootstrap.py +++ b/supervisor/bootstrap.py @@ -2,7 +2,6 @@ import logging import os from pathlib import Path -import shutil import signal from colorlog import ColoredFormatter @@ -258,10 +257,6 @@ def check_environment() -> None: if not SOCKET_DOCKER.is_socket(): _LOGGER.critical("Can't find Docker socket!") - # check socat exec - if not shutil.which("gdbus"): - _LOGGER.critical("Can't find gdbus!") - def reg_signal(loop, coresys: CoreSys) -> None: """Register SIGTERM and SIGKILL to stop system.""" diff --git a/supervisor/dbus/agent/__init__.py b/supervisor/dbus/agent/__init__.py index b5694359f..81558922f 100644 --- a/supervisor/dbus/agent/__init__.py +++ b/supervisor/dbus/agent/__init__.py @@ -6,7 +6,7 @@ from typing import Any from awesomeversion import AwesomeVersion from ...exceptions import DBusError, DBusInterfaceError -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_DIAGNOSTICS, DBUS_ATTR_VERSION, diff --git a/supervisor/dbus/agent/apparmor.py b/supervisor/dbus/agent/apparmor.py index 749493cc0..90f862020 100644 --- a/supervisor/dbus/agent/apparmor.py +++ b/supervisor/dbus/agent/apparmor.py @@ -4,7 +4,7 @@ from typing import Any from awesomeversion import AwesomeVersion -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_PARSER_VERSION, DBUS_NAME_HAOS, diff --git a/supervisor/dbus/agent/cgroup.py b/supervisor/dbus/agent/cgroup.py index e51f225ad..b35e2a1c5 100644 --- a/supervisor/dbus/agent/cgroup.py +++ b/supervisor/dbus/agent/cgroup.py @@ -1,6 +1,6 @@ """CGroup object for OS-Agent.""" -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_CGROUP from ..interface import DBusInterface from ..utils import dbus_connected diff --git a/supervisor/dbus/agent/datadisk.py b/supervisor/dbus/agent/datadisk.py index 90ff3abf3..5991920b1 100644 --- a/supervisor/dbus/agent/datadisk.py +++ b/supervisor/dbus/agent/datadisk.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Any -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_CURRENT_DEVICE, DBUS_NAME_HAOS, diff --git a/supervisor/dbus/agent/system.py b/supervisor/dbus/agent/system.py index beddd5e00..5ed3600a7 100644 --- a/supervisor/dbus/agent/system.py +++ b/supervisor/dbus/agent/system.py @@ -1,6 +1,6 @@ """System object for OS-Agent.""" -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_SYSTEM from ..interface import DBusInterface from ..utils import dbus_connected diff --git a/supervisor/dbus/const.py b/supervisor/dbus/const.py index 301df0bb3..51ea28eb9 100644 --- a/supervisor/dbus/const.py +++ b/supervisor/dbus/const.py @@ -17,14 +17,14 @@ DBUS_NAME_IP6CONFIG = "org.freedesktop.NetworkManager.IP6Config" DBUS_NAME_LOGIND = "org.freedesktop.login1" DBUS_NAME_NM = "org.freedesktop.NetworkManager" DBUS_NAME_NM_CONNECTION_ACTIVE_CHANGED = ( - "org.freedesktop.NetworkManager.Connection.Active.PropertiesChanged" + "org.freedesktop.NetworkManager.Connection.Active.StateChanged" ) DBUS_NAME_RAUC = "de.pengutronix.rauc" DBUS_NAME_RAUC_INSTALLER = "de.pengutronix.rauc.Installer" DBUS_NAME_RAUC_INSTALLER_COMPLETED = "de.pengutronix.rauc.Installer.Completed" -DBUS_NAME_ROOT = "" DBUS_NAME_SETTINGS_CONNECTION = "org.freedesktop.NetworkManager.Settings.Connection" DBUS_NAME_SYSTEMD = "org.freedesktop.systemd1" +DBUS_NAME_SYSTEMD_MANAGER = "org.freedesktop.systemd1.Manager" DBUS_NAME_TIMEDATE = "org.freedesktop.timedate1" diff --git a/supervisor/dbus/hostname.py b/supervisor/dbus/hostname.py index b489d8251..0952db33c 100644 --- a/supervisor/dbus/hostname.py +++ b/supervisor/dbus/hostname.py @@ -3,7 +3,7 @@ import logging from typing import Any, Optional from ..exceptions import DBusError, DBusInterfaceError -from ..utils.gdbus import DBus +from ..utils.dbus import DBus from .const import ( DBUS_ATTR_CHASSIS, DBUS_ATTR_DEPLOYMENT, diff --git a/supervisor/dbus/interface.py b/supervisor/dbus/interface.py index 2af1dafdb..e22075cbb 100644 --- a/supervisor/dbus/interface.py +++ b/supervisor/dbus/interface.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from functools import wraps from typing import Any, Optional -from ..utils.gdbus import DBus +from ..utils.dbus import DBus def dbus_property(func): diff --git a/supervisor/dbus/logind.py b/supervisor/dbus/logind.py index 7e1bad92d..2053f8bc8 100644 --- a/supervisor/dbus/logind.py +++ b/supervisor/dbus/logind.py @@ -2,7 +2,7 @@ import logging from ..exceptions import DBusError, DBusInterfaceError -from ..utils.gdbus import DBus +from ..utils.dbus import DBus from .const import DBUS_NAME_LOGIND, DBUS_OBJECT_LOGIND from .interface import DBusInterface from .utils import dbus_connected diff --git a/supervisor/dbus/network/__init__.py b/supervisor/dbus/network/__init__.py index 3a58f79c0..dbfc3367c 100644 --- a/supervisor/dbus/network/__init__.py +++ b/supervisor/dbus/network/__init__.py @@ -7,11 +7,11 @@ import sentry_sdk from ...exceptions import ( DBusError, + DBusFatalError, DBusInterfaceError, - DBusProgramError, HostNotSupportedError, ) -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_CONNECTION_ENABLED, DBUS_ATTR_DEVICES, @@ -77,16 +77,16 @@ class NetworkManager(DBusInterface): ) -> Awaitable[Any]: """Activate a connction on a device.""" return self.dbus.ActivateConnection( - connection_object, device_object, DBUS_OBJECT_BASE + ("o", connection_object), ("o", device_object), ("o", DBUS_OBJECT_BASE) ) @dbus_connected def add_and_activate_connection( - self, settings: str, device_object: str + self, settings: Any, device_object: str ) -> Awaitable[Any]: """Activate a connction on a device.""" return self.dbus.AddAndActivateConnection( - settings, device_object, DBUS_OBJECT_BASE + ("a{sa{sv}}", settings), ("o", device_object), ("o", DBUS_OBJECT_BASE) ) @dbus_connected @@ -145,7 +145,7 @@ class NetworkManager(DBusInterface): # Connect to interface try: await interface.connect() - except DBusProgramError as err: + except DBusFatalError as err: _LOGGER.warning("Can't process %s: %s", device, err) continue except Exception as err: # pylint: disable=broad-except diff --git a/supervisor/dbus/network/accesspoint.py b/supervisor/dbus/network/accesspoint.py index 3e1338929..80fca7e91 100644 --- a/supervisor/dbus/network/accesspoint.py +++ b/supervisor/dbus/network/accesspoint.py @@ -1,6 +1,6 @@ """Connection object for Network Manager.""" -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_FREQUENCY, DBUS_ATTR_HWADDRESS, @@ -44,7 +44,7 @@ class NetworkWirelessAP(DBusInterfaceProxy): @property def strength(self) -> int: """Return details about mac address.""" - return int(self.properties[DBUS_ATTR_STRENGTH][0]) + return int(self.properties[DBUS_ATTR_STRENGTH]) async def connect(self) -> None: """Get connection information.""" diff --git a/supervisor/dbus/network/connection.py b/supervisor/dbus/network/connection.py index d2ed017d8..2a273cb31 100644 --- a/supervisor/dbus/network/connection.py +++ b/supervisor/dbus/network/connection.py @@ -3,7 +3,7 @@ from ipaddress import ip_address, ip_interface from typing import Optional from ...const import ATTR_ADDRESS, ATTR_PREFIX -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_ADDRESS_DATA, DBUS_ATTR_CONNECTION, diff --git a/supervisor/dbus/network/dns.py b/supervisor/dbus/network/dns.py index 8dbb9ba09..843b2992e 100644 --- a/supervisor/dbus/network/dns.py +++ b/supervisor/dbus/network/dns.py @@ -11,7 +11,7 @@ from ...const import ( ATTR_VPN, ) from ...exceptions import DBusError, DBusInterfaceError -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_CONFIGURATION, DBUS_ATTR_MODE, diff --git a/supervisor/dbus/network/interface.py b/supervisor/dbus/network/interface.py index 875ceca04..2aaff5411 100644 --- a/supervisor/dbus/network/interface.py +++ b/supervisor/dbus/network/interface.py @@ -1,7 +1,7 @@ """NetworkInterface object for Network Manager.""" from typing import Optional -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_ACTIVE_CONNECTION, DBUS_ATTR_DEVICE_INTERFACE, diff --git a/supervisor/dbus/network/setting.py b/supervisor/dbus/network/setting/__init__.py similarity index 90% rename from supervisor/dbus/network/setting.py rename to supervisor/dbus/network/setting/__init__.py index 83d1ff3df..0457c6c04 100644 --- a/supervisor/dbus/network/setting.py +++ b/supervisor/dbus/network/setting/__init__.py @@ -1,12 +1,13 @@ """Connection object for Network Manager.""" +import logging from typing import Any, Awaitable, Optional -from ...const import ATTR_METHOD, ATTR_MODE, ATTR_PSK, ATTR_SSID -from ...utils.gdbus import DBus -from ..const import DBUS_NAME_NM -from ..interface import DBusInterfaceProxy -from ..utils import dbus_connected -from .configuration import ( +from ....const import ATTR_METHOD, ATTR_MODE, ATTR_PSK, ATTR_SSID +from ....utils.dbus import DBus +from ...const import DBUS_NAME_NM +from ...interface import DBusInterfaceProxy +from ...utils import dbus_connected +from ..configuration import ( ConnectionProperties, EthernetProperties, IpProperties, @@ -29,10 +30,12 @@ ATTR_TYPE = "type" ATTR_PARENT = "parent" ATTR_ASSIGNED_MAC = "assigned-mac-address" ATTR_POWERSAVE = "powersave" -ATTR_AUTH_ALGO = "auth-algo" +ATTR_AUTH_ALG = "auth-alg" ATTR_KEY_MGMT = "key-mgmt" ATTR_INTERFACE_NAME = "interface-name" +_LOGGER: logging.Logger = logging.getLogger(__name__) + class NetworkSetting(DBusInterfaceProxy): """NetworkConnection object for Network Manager.""" @@ -91,9 +94,9 @@ class NetworkSetting(DBusInterfaceProxy): return self.dbus.Settings.Connection.GetSettings() @dbus_connected - def update(self, settings: str) -> Awaitable[None]: + def update(self, settings: Any) -> Awaitable[None]: """Update connection settings.""" - return self.dbus.Settings.Connection.Update(settings) + return self.dbus.Settings.Connection.Update(("a{sa{sv}}", settings)) @dbus_connected def delete(self) -> Awaitable[None]: @@ -128,7 +131,7 @@ class NetworkSetting(DBusInterfaceProxy): if CONF_ATTR_802_WIRELESS_SECURITY in data: self._wireless_security = WirelessSecurityProperties( - data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_AUTH_ALGO), + data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_AUTH_ALG), data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_KEY_MGMT), data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_PSK), ) diff --git a/supervisor/dbus/network/setting/generate.py b/supervisor/dbus/network/setting/generate.py new file mode 100644 index 000000000..2e62fd372 --- /dev/null +++ b/supervisor/dbus/network/setting/generate.py @@ -0,0 +1,146 @@ +"""Payload generators for DBUS communication.""" +from __future__ import annotations + +import socket +from typing import TYPE_CHECKING, Any +from uuid import uuid4 + +from dbus_next.signature import Variant + +from . import ( + ATTR_ASSIGNED_MAC, + CONF_ATTR_802_ETHERNET, + CONF_ATTR_802_WIRELESS, + CONF_ATTR_802_WIRELESS_SECURITY, + CONF_ATTR_CONNECTION, + CONF_ATTR_IPV4, + CONF_ATTR_IPV6, + CONF_ATTR_VLAN, +) +from ....host.const import InterfaceMethod, InterfaceType + +if TYPE_CHECKING: + from ....host.network import Interface + + +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), + "interface-name": Variant("s", interface.name), + "type": Variant("s", iftype), + "uuid": Variant("s", uuid), + "llmnr": Variant("i", 2), + "mdns": Variant("i", 2), + } + + conn = {} + conn[CONF_ATTR_CONNECTION] = connection + + ipv4 = {} + if 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 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: + if address.with_prefixlen.startswith("fe80::"): + continue + adressdata.append( + { + "address": Variant("s", str(address.ip)), + "prefix": Variant("u", int(address.with_prefixlen.split("/")[-1])), + } + ) + + ipv6["address-data"] = Variant("(a{sv})", adressdata) + ipv4["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 diff --git a/supervisor/dbus/network/settings.py b/supervisor/dbus/network/settings.py index 457f3d771..758f32a0b 100644 --- a/supervisor/dbus/network/settings.py +++ b/supervisor/dbus/network/settings.py @@ -3,7 +3,7 @@ import logging from typing import Any, Awaitable from ...exceptions import DBusError, DBusInterfaceError -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import DBUS_NAME_NM, DBUS_OBJECT_SETTINGS from ..interface import DBusInterface from ..utils import dbus_connected @@ -26,9 +26,9 @@ class NetworkManagerSettings(DBusInterface): ) @dbus_connected - def add_connection(self, settings: str) -> Awaitable[Any]: + def add_connection(self, settings: Any) -> Awaitable[Any]: """Add new connection.""" - return self.dbus.Settings.AddConnection(settings) + return self.dbus.Settings.AddConnection(("a{sa{sv}}", settings)) @dbus_connected def reload_connections(self) -> Awaitable[Any]: diff --git a/supervisor/dbus/network/wireless.py b/supervisor/dbus/network/wireless.py index 998321447..cf60ed93b 100644 --- a/supervisor/dbus/network/wireless.py +++ b/supervisor/dbus/network/wireless.py @@ -1,7 +1,7 @@ """Connection object for Network Manager.""" from typing import Any, Awaitable, Optional -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_ACTIVE_ACCESSPOINT, DBUS_NAME_DEVICE_WIRELESS, @@ -31,7 +31,7 @@ class NetworkWireless(DBusInterfaceProxy): @dbus_connected def request_scan(self) -> Awaitable[None]: """Request a new AP scan.""" - return self.dbus.Device.Wireless.RequestScan("[]") + return self.dbus.Device.Wireless.RequestScan(("a{sv}", {})) @dbus_connected def get_all_accesspoints(self) -> Awaitable[Any]: diff --git a/supervisor/dbus/payloads/__init__.py b/supervisor/dbus/payloads/__init__.py deleted file mode 100644 index 5ae0b813c..000000000 --- a/supervisor/dbus/payloads/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Init file for DBUS payloads.""" diff --git a/supervisor/dbus/payloads/generate.py b/supervisor/dbus/payloads/generate.py deleted file mode 100644 index 500f2d93d..000000000 --- a/supervisor/dbus/payloads/generate.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Payload generators for DBUS communication.""" -from __future__ import annotations - -from ipaddress import IPv4Address, IPv6Address -from pathlib import Path -import socket -from typing import TYPE_CHECKING -from uuid import uuid4 - -import jinja2 - -from ...host.const import InterfaceType - -if TYPE_CHECKING: - from ...host.network import Interface - - -INTERFACE_UPDATE_TEMPLATE: Path = ( - Path(__file__).parents[2].joinpath("dbus/payloads/interface_update.tmpl") -) - - -def interface_update_payload( - interface: Interface, name: str | None = None, uuid: str | None = None -) -> str: - """Generate a payload for network interface update.""" - env = jinja2.Environment() - - def ipv4_to_int(ip_address: IPv4Address) -> int: - """Convert an ipv4 to an int.""" - return socket.htonl(int(ip_address)) - - def ipv6_to_byte(ip_address: IPv6Address) -> str: - """Convert an ipv6 to an byte array.""" - return f'[byte {", ".join(f"0x{val:02x}" for val in ip_address.packed)}]' - - # Init template - env.filters["ipv4_to_int"] = ipv4_to_int - env.filters["ipv6_to_byte"] = ipv6_to_byte - template: jinja2.Template = env.from_string(INTERFACE_UPDATE_TEMPLATE.read_text()) - - # Generate UUID - if not uuid: - uuid = str(uuid4()) - - # 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}" - - # Fix SSID - if interface.wifi: - interface.wifi.ssid = ", ".join( - [f"0x{x}" for x in interface.wifi.ssid.encode().hex(",").split(",")] - ) - - return template.render(interface=interface, name=name, uuid=uuid) diff --git a/supervisor/dbus/payloads/interface_update.tmpl b/supervisor/dbus/payloads/interface_update.tmpl deleted file mode 100644 index a2af64ede..000000000 --- a/supervisor/dbus/payloads/interface_update.tmpl +++ /dev/null @@ -1,106 +0,0 @@ -{ - 'connection': - { - 'id': <'{{ name }}'>, -{% if interface.type != "vlan" %} - 'interface-name': <'{{ interface.name }}'>, -{% endif %} - 'type': <'{% if interface.type == "ethernet" %}802-3-ethernet{% elif interface.type == "wireless" %}802-11-wireless{% else %}{{ interface.type.value }}{% endif %}'>, - 'uuid': <'{{ uuid }}'>, - 'llmnr': , - 'mdns': - } - -{% if interface.ipv4 %} - , - 'ipv4': - { -{% if interface.ipv4.method == "auto" %} - 'method': <'auto'> -{% elif interface.ipv4.method == "disabled" %} - 'method': <'disabled'> -{% else %} - 'method': <'manual'>, - 'dns': <[uint32 {{ interface.ipv4.nameservers | map("ipv4_to_int") | join(",") }}]>, - 'address-data': <[ - {% for address in interface.ipv4.address %} - { - 'address': <'{{ address.ip | string }}'>, - 'prefix': - }]>, - {% endfor %} - 'gateway': <'{{ interface.ipv4.gateway | string }}'> -{% endif %} - } -{% endif %} - -{% if interface.ipv6 %} - , - 'ipv6': - { -{% if interface.ipv6.method == "auto" %} - 'method': <'auto'> -{% elif interface.ipv6.method == "disabled" %} - 'method': <'disabled'> -{% else %} - 'method': <'manual'>, - 'dns': <[{{ interface.ipv6.nameservers | map("ipv6_to_byte") | join(",") }}]>, - 'address-data': <[ - {% for address in interface.ipv6.address if not address.with_prefixlen.startswith("fe80::") %} - { - 'address': <'{{ address.ip | string }}'>, - 'prefix': - }]>, - {% endfor %} - 'gateway': <'{{ interface.ipv6.gateway | string }}'> -{% endif %} - } -{% endif %} - -{% if interface.type == "ethernet" %} - , - '802-3-ethernet': - { - 'assigned-mac-address': <'preserve'> - } -{% endif %} - -{% if interface.type == "vlan" %} - , - 'vlan': - { - 'id': , - 'parent': <'{{ interface.vlan.interface }}'> - } -{% endif %} - -{% if interface.type == "wireless" %} - , - '802-11-wireless': - { - 'assigned-mac-address': <'preserve'>, - 'ssid': <[byte {{ interface.wifi.ssid }}]>, - 'mode': <'{{ interface.wifi.mode.value }}'>, - 'powersave': -{% if interface.wifi.auth != "open" %} - , - 'security': <'802-11-wireless-security'> - }, - - '802-11-wireless-security': - { - {% if interface.wifi.auth == "wep" %} - 'auth-alg': <'none'>, - 'key-mgmt': <'none'> - {% elif interface.wifi.auth == "wpa-psk" %} - 'auth-alg': <'open'>, - 'key-mgmt': <'wpa-psk'> - {% endif %} - {% if interface.wifi.psk %} - , - 'psk': <'{{ interface.wifi.psk }}'> - {% endif %} -{% endif %} - } -{% endif %} -} \ No newline at end of file diff --git a/supervisor/dbus/rauc.py b/supervisor/dbus/rauc.py index 7eb1d18c6..d29beed39 100644 --- a/supervisor/dbus/rauc.py +++ b/supervisor/dbus/rauc.py @@ -3,7 +3,7 @@ import logging from typing import Optional from ..exceptions import DBusError, DBusInterfaceError -from ..utils.gdbus import DBus +from ..utils.dbus import DBus from .const import ( DBUS_ATTR_BOOT_SLOT, DBUS_ATTR_COMPATIBLE, @@ -75,7 +75,7 @@ class Rauc(DBusInterface): Return a coroutine. """ - return self.dbus.Installer.Install(raucb_file) + return self.dbus.Installer.Install(str(raucb_file)) @dbus_connected def get_slot_status(self): diff --git a/supervisor/dbus/systemd.py b/supervisor/dbus/systemd.py index 246dab8ac..1ec3ca44f 100644 --- a/supervisor/dbus/systemd.py +++ b/supervisor/dbus/systemd.py @@ -3,15 +3,15 @@ import logging from typing import Any from ..exceptions import DBusError, DBusInterfaceError -from ..utils.gdbus import DBus +from ..utils.dbus import DBus from .const import ( DBUS_ATTR_FINISH_TIMESTAMP, DBUS_ATTR_FIRMWARE_TIMESTAMP_MONOTONIC, DBUS_ATTR_KERNEL_TIMESTAMP_MONOTONIC, DBUS_ATTR_LOADER_TIMESTAMP_MONOTONIC, DBUS_ATTR_USERSPACE_TIMESTAMP_MONOTONIC, - DBUS_NAME_ROOT, DBUS_NAME_SYSTEMD, + DBUS_NAME_SYSTEMD_MANAGER, DBUS_OBJECT_SYSTEMD, ) from .interface import DBusInterface, dbus_property @@ -116,4 +116,4 @@ class Systemd(DBusInterface): @dbus_connected async def update(self): """Update Properties.""" - self.properties = await self.dbus.get_properties(DBUS_NAME_ROOT) + self.properties = await self.dbus.get_properties(DBUS_NAME_SYSTEMD_MANAGER) diff --git a/supervisor/dbus/timedate.py b/supervisor/dbus/timedate.py index 24c4a8634..a857de8cf 100644 --- a/supervisor/dbus/timedate.py +++ b/supervisor/dbus/timedate.py @@ -4,8 +4,8 @@ import logging from typing import Any from ..exceptions import DBusError, DBusInterfaceError +from ..utils.dbus import DBus from ..utils.dt import utc_from_timestamp -from ..utils.gdbus import DBus from .const import ( DBUS_ATTR_LOCALRTC, DBUS_ATTR_NTP, diff --git a/supervisor/exceptions.py b/supervisor/exceptions.py index 53e51b98f..626500258 100644 --- a/supervisor/exceptions.py +++ b/supervisor/exceptions.py @@ -263,35 +263,31 @@ class ServicesError(HassioError): """Services Errors.""" -# utils/gdbus +# utils/dbus class DBusError(HassioError): - """DBus generic error.""" + """D-Bus generic error.""" class DBusNotConnectedError(HostNotSupportedError): - """DBus is not connected and call a method.""" + """D-Bus is not connected and call a method.""" class DBusInterfaceError(HassioNotSupportedError): - """DBus interface not connected.""" + """D-Bus interface not connected.""" class DBusFatalError(DBusError): - """DBus call going wrong.""" + """D-Bus call going wrong.""" class DBusInterfaceMethodError(DBusInterfaceError): - """Dbus method was not definied.""" + """D-Bus method was not defined.""" class DBusParseError(DBusError): - """DBus parse error.""" - - -class DBusProgramError(DBusError): - """DBus application error.""" + """D-Bus parse error.""" # util/apparmor diff --git a/supervisor/host/network.py b/supervisor/host/network.py index 76a203447..9151346d8 100644 --- a/supervisor/host/network.py +++ b/supervisor/host/network.py @@ -19,11 +19,10 @@ from ..dbus.const import ( from ..dbus.network.accesspoint import NetworkWirelessAP from ..dbus.network.connection import NetworkConnection from ..dbus.network.interface import NetworkInterface -from ..dbus.payloads.generate import interface_update_payload +from ..dbus.network.setting.generate import get_connection_from_interface from ..exceptions import ( DBusError, DBusNotConnectedError, - DBusProgramError, HostNetworkError, HostNetworkNotFound, HostNotSupportedError, @@ -128,7 +127,8 @@ class NetworkManager(CoreSysAttributes): and inet.settings.connection.interface_name == interface.name and interface.enabled ): - settings = interface_update_payload( + _LOGGER.debug("Updating existing configuration for %s", interface.name) + settings = get_connection_from_interface( interface, name=inet.settings.connection.id, uuid=inet.settings.connection.uuid, @@ -146,12 +146,14 @@ class NetworkManager(CoreSysAttributes): # Create new configuration and activate interface elif inet and interface.enabled: - settings = interface_update_payload(interface) + _LOGGER.debug("Create new configuration for %s", interface.name) + settings = get_connection_from_interface(interface) try: - await self.sys_dbus.network.add_and_activate_connection( + new_con = await self.sys_dbus.network.add_and_activate_connection( settings, inet.object_path ) + _LOGGER.debug("add_and_activate_connection returns %s", new_con) except DBusError as err: raise HostNetworkError( f"Can't create config and activate {interface.name}: {err}", @@ -169,7 +171,7 @@ class NetworkManager(CoreSysAttributes): # Create new interface (like vlan) elif not inet: - settings = interface_update_payload(interface) + settings = get_connection_from_interface(interface) try: await self.sys_dbus.network.settings.add_connection(settings) @@ -182,9 +184,12 @@ class NetworkManager(CoreSysAttributes): "Requested Network interface update is not possible", _LOGGER.warning ) + # This signal is fired twice: Activating -> Activated. It seems we miss the first + # "usually"... We should filter by state and explicitly wait for the second. await self.sys_dbus.network.dbus.wait_signal( DBUS_NAME_NM_CONNECTION_ACTIVE_CHANGED ) + await self.update() async def scan_wifi(self, interface: Interface) -> list[AccessPoint]: @@ -199,9 +204,8 @@ class NetworkManager(CoreSysAttributes): # Request Scan try: await inet.wireless.request_scan() - except DBusProgramError as err: - _LOGGER.debug("Can't request a new scan: %s", err) except DBusError as err: + _LOGGER.warning("Can't request a new scan: %s", err) raise HostNetworkError() from err else: await asyncio.sleep(5) diff --git a/supervisor/utils/dbus.py b/supervisor/utils/dbus.py new file mode 100644 index 000000000..fab5950d7 --- /dev/null +++ b/supervisor/utils/dbus.py @@ -0,0 +1,249 @@ +"""DBus implementation with glib.""" +from __future__ import annotations + +import asyncio +import logging +from typing import Any + +from dbus_next import BusType, InvalidIntrospectionError, Message, MessageType +from dbus_next.aio import MessageBus +from dbus_next.signature import Variant + +from ..exceptions import ( + DBusFatalError, + DBusInterfaceError, + DBusInterfaceMethodError, + DBusNotConnectedError, + DBusParseError, +) + + +def _remove_dbus_signature(data: Any) -> Any: + if isinstance(data, Variant): + return _remove_dbus_signature(data.value) + elif isinstance(data, dict): + for k in data: + data[k] = _remove_dbus_signature(data[k]) + return data + elif isinstance(data, list): + new_list = [] + for item in data: + new_list.append(_remove_dbus_signature(item)) + return new_list + else: + return data + + +_LOGGER: logging.Logger = logging.getLogger(__name__) + +DBUS_METHOD_GETALL: str = "org.freedesktop.DBus.Properties.GetAll" +DBUS_METHOD_SET: str = "org.freedesktop.DBus.Properties.Set" + + +class DBus: + """DBus handler.""" + + def __init__(self, bus_name: str, object_path: str) -> None: + """Initialize dbus object.""" + self.bus_name: str = bus_name + self.object_path: str = object_path + self.methods: set[str] = set() + self.signals: set[str] = set() + self._bus: MessageBus = None + + @staticmethod + async def connect(bus_name: str, object_path: str) -> DBus: + """Read object data.""" + self = DBus(bus_name, object_path) + + # pylint: disable=protected-access + await self._init_proxy() + + _LOGGER.debug("Connect to D-Bus: %s - %s", bus_name, object_path) + return self + + def _add_interfaces(self, introspection: Any): + # Read available methods + for interface in introspection.interfaces: + interface_name = interface.name + + # Methods + for method in interface.methods: + method_name = method.name + self.methods.add(f"{interface_name}.{method_name}") + + # Signals + for signal in interface.signals: + signal_name = signal.name + self.signals.add(f"{interface_name}.{signal_name}") + + async def _init_proxy(self) -> None: + """Read interface data.""" + # Wait for dbus object to be available after restart + try: + self._bus = await MessageBus(bus_type=BusType.SYSTEM).connect() + except Exception as err: + raise DBusFatalError() from err + + try: + introspection = await self._bus.introspect( + self.bus_name, self.object_path, timeout=10 + ) + except InvalidIntrospectionError as err: + _LOGGER.error("Can't parse introspect data: %s", err) + raise DBusParseError() from err + + self._add_interfaces(introspection) + + def _prepare_args(self, *args: list[Any]) -> tuple[str, list[Any]]: + signature = "" + arg_list = [] + + for arg in args: + _LOGGER.debug("...arg %s (type %s)", str(arg), type(arg)) + if isinstance(arg, bool): + signature += "b" + arg_list.append(arg) + elif isinstance(arg, int): + signature += "i" + arg_list.append(arg) + elif isinstance(arg, float): + signature += "d" + arg_list.append(arg) + elif isinstance(arg, str): + signature += "s" + arg_list.append(arg) + elif isinstance(arg, tuple): + signature += arg[0] + arg_list.append(arg[1]) + else: + raise DBusFatalError(f"Type {type(arg)} not supported") + + return signature, arg_list + + async def call_dbus(self, method: str, *args: list[Any]) -> str: + """Call a dbus method.""" + method_parts = method.split(".") + + signature, arg_list = self._prepare_args(*args) + + _LOGGER.debug("Call %s on %s", method, self.object_path) + reply = await self._bus.call( + Message( + destination=self.bus_name, + path=self.object_path, + interface=".".join(method_parts[:-1]), + member=method_parts[-1], + signature=signature, + body=arg_list, + ) + ) + + if reply.message_type == MessageType.ERROR: + if reply.error_name in ( + "org.freedesktop.DBus.Error.ServiceUnknown", + "org.freedesktop.DBus.Error.UnknownMethod", + ): + raise DBusInterfaceError(reply.body[0]) + if reply.error_name == "org.freedesktop.DBus.Error.Disconnected": + raise DBusNotConnectedError() + if reply.body and len(reply.body) > 0: + raise DBusFatalError(reply.body[0]) + raise DBusFatalError() + + return _remove_dbus_signature(reply.body) + + async def get_properties(self, interface: str) -> dict[str, Any]: + """Read all properties from interface.""" + try: + return (await self.call_dbus(DBUS_METHOD_GETALL, interface))[0] + except IndexError as err: + _LOGGER.error("No attributes returned for %s", interface) + raise DBusFatalError() from err + + async def set_property( + self, interface: str, name: str, value: Any + ) -> dict[str, Any]: + """Set a property from interface.""" + try: + return (await self.call_dbus(DBUS_METHOD_SET, interface, name, value))[0] + except IndexError as err: + _LOGGER.error("No Set attribute %s for %s", name, interface) + raise DBusFatalError() from err + + async def wait_signal(self, signal): + """Wait for single event.""" + signal_parts = signal.split(".") + interface = ".".join(signal_parts[:-1]) + member = signal_parts[-1] + + _LOGGER.debug("Wait for signal %s", signal) + await self._bus.call( + Message( + destination="org.freedesktop.DBus", + interface="org.freedesktop.DBus", + path="/org/freedesktop/DBus", + member="AddMatch", + signature="s", + body=[f"type='signal',interface={interface},member={member}"], + ) + ) + + loop = asyncio.get_event_loop() + future = loop.create_future() + + def message_handler(msg: Message): + if msg.message_type != MessageType.SIGNAL: + return + + _LOGGER.debug( + "Signal message received %s, %s %s", msg, msg.interface, msg.member + ) + if msg.interface != interface or msg.member != member: + return + + # Avoid race condition: We already received signal but handler not yet removed. + if future.done(): + return + + future.set_result(_remove_dbus_signature(msg.body)) + + self._bus.add_message_handler(message_handler) + result = await future + self._bus.remove_message_handler(message_handler) + + return result + + def __getattr__(self, name: str) -> DBusCallWrapper: + """Map to dbus method.""" + return getattr(DBusCallWrapper(self, self.bus_name), name) + + +class DBusCallWrapper: + """Wrapper a DBus interface for a call.""" + + def __init__(self, dbus: DBus, interface: str) -> None: + """Initialize wrapper.""" + self.dbus: DBus = dbus + self.interface: str = interface + + def __call__(self) -> None: + """Catch this method from being called.""" + _LOGGER.error("D-Bus method %s not exists!", self.interface) + raise DBusInterfaceMethodError() + + def __getattr__(self, name: str): + """Map to dbus method.""" + interface = f"{self.interface}.{name}" + + if interface not in self.dbus.methods: + return DBusCallWrapper(self.dbus, interface) + + def _method_wrapper(*args): + """Wrap method. + + Return a coroutine + """ + return self.dbus.call_dbus(interface, *args) + + return _method_wrapper diff --git a/supervisor/utils/gdbus.py b/supervisor/utils/gdbus.py deleted file mode 100644 index 1c07e93ea..000000000 --- a/supervisor/utils/gdbus.py +++ /dev/null @@ -1,383 +0,0 @@ -"""DBus implementation with glib.""" -from __future__ import annotations - -import asyncio -import json -import logging -import re -import shlex -from signal import SIGINT -from typing import Any -import xml.etree.ElementTree as ET - -import sentry_sdk - -from . import clean_env -from ..exceptions import ( - DBusFatalError, - DBusInterfaceError, - DBusInterfaceMethodError, - DBusNotConnectedError, - DBusParseError, - DBusProgramError, -) - -_LOGGER: logging.Logger = logging.getLogger(__name__) - -# Use to convert GVariant into json -RE_GVARIANT_TYPE: re.Pattern[Any] = re.compile( - r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(boolean|byte|int16|uint16|int32|uint32|handle|int64|uint64|double|" - r"string|objectpath|signature|@[asviumodfy\{\}\(\)]+) " -) -RE_GVARIANT_VARIANT: re.Pattern[Any] = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(<|>)") -RE_GVARIANT_STRING_ESC: re.Pattern[Any] = re.compile( - r"(?<=(?: |{|\[|\(|<))'[^']*?\"[^']*?'(?=(?:|]|}|,|\)|>))" -) -RE_GVARIANT_STRING: re.Pattern[Any] = re.compile( - r"(?<=(?: |{|\[|\(|<))'(.*?)'(?=(?:|]|}|,|\)|>))" -) -RE_GVARIANT_BINARY: re.Pattern[Any] = re.compile( - r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|\[byte (.*?)\]|\[(0x[0-9A-Za-z]{2}.*?)\]|" -) -RE_GVARIANT_BINARY_STRING: re.Pattern[Any] = re.compile( - r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|?" -) -RE_GVARIANT_TUPLE_O: re.Pattern[Any] = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(\()") -RE_GVARIANT_TUPLE_C: re.Pattern[Any] = re.compile( - r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(,?\))" -) - -RE_BIN_STRING_OCT: re.Pattern[Any] = re.compile(r"\\\\(\d{3})") -RE_BIN_STRING_HEX: re.Pattern[Any] = re.compile(r"\\\\x([0-9A-Za-z]{2})") - -RE_MONITOR_OUTPUT: re.Pattern[Any] = re.compile( - r".+?: (?P[^\s].+?) (?P.*)" -) - -# Map GDBus to errors -MAP_GDBUS_ERROR: dict[str, Any] = { - "GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown": DBusInterfaceError, - "GDBus.Error:org.freedesktop.DBus.Error.Spawn.ChildExited": DBusFatalError, - "No such file or directory": DBusNotConnectedError, -} - -# Commands for dbus -INTROSPECT: str = "gdbus introspect --system --dest {bus} --object-path {object} --xml" -CALL: str = "gdbus call --system --dest {bus} --object-path {object} --timeout 10 --method {method} {args}" -MONITOR: str = "gdbus monitor --system --dest {bus}" -WAIT: str = "gdbus wait --system --activate {bus} --timeout 5 {bus}" - -DBUS_METHOD_GETALL: str = "org.freedesktop.DBus.Properties.GetAll" -DBUS_METHOD_SET: str = "org.freedesktop.DBus.Properties.Set" - - -def _convert_bytes(value: str) -> str: - """Convert bytes to string or byte-array.""" - data: bytes = bytes(int(char, 0) for char in value.split(", ")) - return f"[{', '.join(str(char) for char in data)}]" - - -def _convert_bytes_string(value: str) -> str: - """Convert bytes to string or byte-array.""" - data = RE_BIN_STRING_OCT.sub(lambda x: chr(int(x.group(1), 8)), value) - data = RE_BIN_STRING_HEX.sub(lambda x: chr(int(f"0x{x.group(1)}", 0)), data) - return f"[{', '.join(str(char) for char in list(char for char in data.encode()))}]" - - -class DBus: - """DBus handler.""" - - def __init__(self, bus_name: str, object_path: str) -> None: - """Initialize dbus object.""" - self.bus_name: str = bus_name - self.object_path: str = object_path - self.methods: set[str] = set() - self.signals: set[str] = set() - - @staticmethod - async def connect(bus_name: str, object_path: str) -> DBus: - """Read object data.""" - self = DBus(bus_name, object_path) - - # pylint: disable=protected-access - await self._init_proxy() - - _LOGGER.debug("Connect to D-Bus: %s - %s", bus_name, object_path) - return self - - async def _init_proxy(self) -> None: - """Read interface data.""" - # Wait for dbus object to be available after restart - command_wait = shlex.split(WAIT.format(bus=self.bus_name)) - await self._send(command_wait, silent=True) - - # Introspect object & Parse XML - command_introspect = shlex.split( - INTROSPECT.format(bus=self.bus_name, object=self.object_path) - ) - data = await self._send(command_introspect) - try: - xml = ET.fromstring(data) - except ET.ParseError as err: - _LOGGER.error("Can't parse introspect data: %s", err) - _LOGGER.debug("Introspect %s on %s", self.bus_name, self.object_path) - raise DBusParseError() from err - - # Read available methods - for interface in xml.findall("./interface"): - interface_name = interface.get("name") - - # Methods - for method in interface.findall("./method"): - method_name = method.get("name") - self.methods.add(f"{interface_name}.{method_name}") - - # Signals - for signal in interface.findall("./signal"): - signal_name = signal.get("name") - self.signals.add(f"{interface_name}.{signal_name}") - - @staticmethod - def parse_gvariant(raw: str) -> Any: - """Parse GVariant input to python.""" - # Process first string - json_raw = RE_GVARIANT_STRING_ESC.sub( - lambda x: x.group(0).replace('"', '\\"'), raw - ) - json_raw = RE_GVARIANT_STRING.sub(r'"\1"', json_raw) - - # Handle Bytes - json_raw = RE_GVARIANT_BINARY.sub( - lambda x: x.group(0) - if not (x.group(1) or x.group(2) or x.group(3)) - else _convert_bytes(x.group(1) or x.group(2) or x.group(3)), - json_raw, - ) - json_raw = RE_GVARIANT_BINARY_STRING.sub( - lambda x: x.group(0) - if not x.group(1) - else _convert_bytes_string(x.group(1)), - json_raw, - ) - - # Remove complex type handling - json_raw: str = RE_GVARIANT_TYPE.sub( - lambda x: x.group(0) if not x.group(1) else "", json_raw - ) - json_raw = RE_GVARIANT_VARIANT.sub( - lambda x: x.group(0) if not x.group(1) else "", json_raw - ) - json_raw = RE_GVARIANT_TUPLE_O.sub( - lambda x: x.group(0) if not x.group(1) else "[", json_raw - ) - json_raw = RE_GVARIANT_TUPLE_C.sub( - lambda x: x.group(0) if not x.group(1) else "]", json_raw - ) - - # No data - if json_raw.startswith("[]"): - return [] - - try: - return json.loads(json_raw) - except json.JSONDecodeError as err: - _LOGGER.error("Can't parse '%s': '%s' - %s", json_raw, raw, err) - sentry_sdk.capture_exception(err) - raise DBusParseError() from err - - @staticmethod - def gvariant_args(args: list[Any]) -> str: - """Convert args into gvariant.""" - gvariant = "" - for arg in args: - if isinstance(arg, bool): - gvariant += f" {str(arg).lower()}" - elif isinstance(arg, (int, float)): - gvariant += f" {arg}" - elif isinstance(arg, str): - gvariant += f' "{arg}"' - else: - gvariant += f" {arg!s}" - - return gvariant.lstrip() - - async def call_dbus(self, method: str, *args: list[Any]) -> str: - """Call a dbus method.""" - command = shlex.split( - CALL.format( - bus=self.bus_name, - object=self.object_path, - method=method, - args=self.gvariant_args(args), - ) - ) - - # Run command - _LOGGER.debug("Call %s on %s", method, self.object_path) - data = await self._send(command) - - # Parse and return data - return self.parse_gvariant(data) - - async def get_properties(self, interface: str) -> dict[str, Any]: - """Read all properties from interface.""" - try: - return (await self.call_dbus(DBUS_METHOD_GETALL, interface))[0] - except IndexError as err: - _LOGGER.error("No attributes returned for %s", interface) - raise DBusFatalError() from err - - async def set_property( - self, interface: str, name: str, value: Any - ) -> dict[str, Any]: - """Set a property from interface.""" - try: - return (await self.call_dbus(DBUS_METHOD_SET, interface, name, value))[0] - except IndexError as err: - _LOGGER.error("No Set attribute %s for %s", name, interface) - raise DBusFatalError() from err - - async def _send(self, command: list[str], silent=False) -> str: - """Send command over dbus.""" - # Run command - _LOGGER.debug("Send D-Bus command: %s", command) - try: - proc = await asyncio.create_subprocess_exec( - *command, - stdin=asyncio.subprocess.DEVNULL, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - env=clean_env(), - ) - - data, error = await proc.communicate() - except OSError as err: - _LOGGER.critical("D-Bus fatal error: %s", err) - raise DBusFatalError() from err - - # Success? - if proc.returncode == 0 or silent: - return data.decode() - - # Filter error - error = error.decode() - for msg, exception in MAP_GDBUS_ERROR.items(): - if msg not in error: - continue - raise exception() - - # General - _LOGGER.debug("D-Bus return: %s", error.strip()) - raise DBusProgramError(error.strip()) - - def attach_signals(self, filters=None): - """Generate a signals wrapper.""" - return DBusSignalWrapper(self, filters) - - async def wait_signal(self, signal): - """Wait for single event.""" - monitor = DBusSignalWrapper(self, [signal]) - async with monitor as signals: - async for signal in signals: - return signal - - def __getattr__(self, name: str) -> DBusCallWrapper: - """Map to dbus method.""" - return getattr(DBusCallWrapper(self, self.bus_name), name) - - -class DBusCallWrapper: - """Wrapper a DBus interface for a call.""" - - def __init__(self, dbus: DBus, interface: str) -> None: - """Initialize wrapper.""" - self.dbus: DBus = dbus - self.interface: str = interface - - def __call__(self) -> None: - """Catch this method from being called.""" - _LOGGER.error("D-Bus method %s not exists!", self.interface) - raise DBusInterfaceMethodError() - - def __getattr__(self, name: str): - """Map to dbus method.""" - interface = f"{self.interface}.{name}" - - if interface not in self.dbus.methods: - return DBusCallWrapper(self.dbus, interface) - - def _method_wrapper(*args): - """Wrap method. - - Return a coroutine - """ - return self.dbus.call_dbus(interface, *args) - - return _method_wrapper - - -class DBusSignalWrapper: - """Process Signals.""" - - def __init__(self, dbus: DBus, signals: str | None = None): - """Initialize dbus signal wrapper.""" - self.dbus: DBus = dbus - self._signals: str | None = signals - self._proc: asyncio.Process | None = None - - async def __aenter__(self): - """Start monitor events.""" - _LOGGER.info("Starting dbus monitor on %s", self.dbus.bus_name) - command = shlex.split(MONITOR.format(bus=self.dbus.bus_name)) - self._proc = await asyncio.create_subprocess_exec( - *command, - stdin=asyncio.subprocess.DEVNULL, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - env=clean_env(), - ) - - return self - - async def __aexit__(self, exception_type, exception_value, traceback): - """Stop monitor events.""" - _LOGGER.info("Stopping dbus monitor on %s", self.dbus.bus_name) - self._proc.send_signal(SIGINT) - await self._proc.communicate() - - def __aiter__(self): - """Start Iteratation.""" - return self - - async def __anext__(self): - """Get next data.""" - if not self._proc: - raise StopAsyncIteration() from None - - # Read signals - while True: - try: - data = await self._proc.stdout.readline() - except asyncio.TimeoutError: - raise StopAsyncIteration() from None - - # Program close - if not data: - raise StopAsyncIteration() from None - - # Extract metadata - match = RE_MONITOR_OUTPUT.match(data.decode()) - if not match: - continue - signal = match.group("signal") - data = match.group("data") - - # Filter signals? - if self._signals and signal not in self._signals: - _LOGGER.debug("Skiping event %s - %s", signal, data) - continue - - try: - return self.dbus.parse_gvariant(data) - except DBusParseError as err: - raise StopAsyncIteration() from err diff --git a/tests/common.py b/tests/common.py index 2f0149cf3..4d7d86684 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,9 +1,10 @@ """Common test functions.""" import json from pathlib import Path +from typing import Any -def load_json_fixture(filename: str) -> dict: +def load_json_fixture(filename: str) -> Any: """Load a json fixture.""" path = Path(Path(__file__).parent.joinpath("fixtures"), filename) return json.loads(path.read_text(encoding="utf-8")) diff --git a/tests/conftest.py b/tests/conftest.py index 88c808509..d6705410c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,11 +3,13 @@ from functools import partial from inspect import unwrap from pathlib import Path import re +from typing import Any from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch from uuid import uuid4 from aiohttp import web from awesomeversion import AwesomeVersion +from dbus_next import introspection as intr import pytest from supervisor import config as su_config @@ -20,7 +22,7 @@ from supervisor.dbus.network import NetworkManager from supervisor.docker import DockerAPI from supervisor.store.addon import AddonStore from supervisor.store.repository import Repository -from supervisor.utils.gdbus import DBus +from supervisor.utils.dbus import DBus from .common import exists_fixture, load_fixture, load_json_fixture from .const import TEST_ADDON_SLUG @@ -80,35 +82,38 @@ def dbus() -> DBus: async def mock_wait_signal(_, __): pass - async def mock_send(_, command, silent=False): - if silent: - return "" + async def mock_init_proxy(self): - fixture = command[6].replace("/", "_")[1:] - if command[1] == "introspect": - filetype = "xml" + filetype = "xml" + fixture = self.object_path.replace("/", "_")[1:] + if not exists_fixture(f"{fixture}.{filetype}"): + fixture = re.sub(r"_[0-9]+$", "", fixture) - if not exists_fixture(f"{fixture}.{filetype}"): - fixture = re.sub(r"_[0-9]+$", "", fixture) + # special case + if exists_fixture(f"{fixture}_~.{filetype}"): + fixture = f"{fixture}_~" - # special case - if exists_fixture(f"{fixture}_~.{filetype}"): - fixture = f"{fixture}_~" - else: - fixture = f"{fixture}-{command[10].split('.')[-1]}" - filetype = "fixture" + # Use dbus-next infrastructure to parse introspection xml + node = intr.Node.parse(load_fixture(f"{fixture}.{filetype}")) + self._add_interfaces(node) - dbus_commands.append(fixture) + async def mock_call_dbus(self, method: str, *args: list[Any]): - return load_fixture(f"{fixture}.{filetype}") + fixture = self.object_path.replace("/", "_")[1:] + fixture = f"{fixture}-{method.split('.')[-1]}" + dbus_commands.append(fixture) - with patch("supervisor.utils.gdbus.DBus._send", new=mock_send), patch( - "supervisor.utils.gdbus.DBus.wait_signal", new=mock_wait_signal + return load_json_fixture(f"{fixture}.json") + + with patch("supervisor.utils.dbus.DBus.call_dbus", new=mock_call_dbus), patch( + "supervisor.utils.dbus.DBus.wait_signal", new=mock_wait_signal ), patch( "supervisor.dbus.interface.DBusInterface.is_connected", return_value=True, ), patch( - "supervisor.utils.gdbus.DBus.get_properties", new=mock_get_properties + "supervisor.utils.dbus.DBus.get_properties", new=mock_get_properties + ), patch( + "supervisor.utils.dbus.DBus._init_proxy", new=mock_init_proxy ): yield dbus_commands diff --git a/tests/dbus/network/test_setting.py b/tests/dbus/network/test_setting.py new file mode 100644 index 000000000..1ba3a9e79 --- /dev/null +++ b/tests/dbus/network/test_setting.py @@ -0,0 +1,36 @@ +"""Test NetwrokInterface.""" +import pytest + +from supervisor.dbus.const import DeviceType +from supervisor.dbus.network import NetworkManager +from supervisor.dbus.network.setting.generate import get_connection_from_interface +from supervisor.host.network import Interface + +from tests.const import TEST_INTERFACE, TEST_INTERFACE_WLAN + + +@pytest.mark.asyncio +async def test_get_connection_from_interface(network_manager: NetworkManager): + """Test network interface.""" + dbus_interface = network_manager.interfaces[TEST_INTERFACE] + interface = Interface.from_dbus_interface(dbus_interface) + connection_payload = get_connection_from_interface(interface) + + assert "connection" in connection_payload + + assert connection_payload["connection"]["interface-name"].value == TEST_INTERFACE + assert connection_payload["connection"]["type"].value == "802-3-ethernet" + + assert connection_payload["ipv4"]["method"].value == "auto" + assert "address-data" not in connection_payload["ipv4"] + + assert connection_payload["ipv6"]["method"].value == "auto" + assert "address-data" not in connection_payload["ipv6"] + + +@pytest.mark.asyncio +async def test_network_interface_wlan(network_manager: NetworkManager): + """Test network interface.""" + interface = network_manager.interfaces[TEST_INTERFACE_WLAN] + assert interface.name == TEST_INTERFACE_WLAN + assert interface.type == DeviceType.WIRELESS diff --git a/tests/dbus/payloads/test_interface_update_payload.py b/tests/dbus/payloads/test_interface_update_payload.py deleted file mode 100644 index 69945fc37..000000000 --- a/tests/dbus/payloads/test_interface_update_payload.py +++ /dev/null @@ -1,271 +0,0 @@ -"""Test interface update payload.""" -from ipaddress import ip_address, ip_interface - -import pytest - -from supervisor.dbus.payloads.generate import interface_update_payload -from supervisor.host.const import AuthMethod, InterfaceMethod, InterfaceType, WifiMode -from supervisor.host.network import VlanConfig, WifiConfig -from supervisor.utils.gdbus import DBus - -from tests.const import TEST_INTERFACE - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - - data = interface_update_payload(interface) - assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto" - assert DBus.parse_gvariant(data)["ipv6"]["method"] == "auto" - - assert ( - DBus.parse_gvariant(data)["802-3-ethernet"]["assigned-mac-address"] - == "preserve" - ) - - assert DBus.parse_gvariant(data)["connection"]["mdns"] == 2 - assert DBus.parse_gvariant(data)["connection"]["llmnr"] == 2 - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv4(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv4.method = InterfaceMethod.STATIC - interface.ipv4.address = [ip_interface("192.168.1.1/24")] - interface.ipv4.nameservers = [ip_address("1.1.1.1"), ip_address("1.0.1.1")] - interface.ipv4.gateway = ip_address("192.168.1.1") - - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv4"]["method"] == "manual" - assert ( - DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["address"] == "192.168.1.1" - ) - assert DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["prefix"] == 24 - assert DBus.parse_gvariant(data)["ipv4"]["dns"] == [16843009, 16842753] - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - assert DBus.parse_gvariant(data)["ipv4"]["gateway"] == "192.168.1.1" - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv4_disabled(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv4.method = InterfaceMethod.DISABLED - - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv4"]["method"] == "disabled" - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv4_auto(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv4.method = InterfaceMethod.AUTO - - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto" - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv6(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv6.method = InterfaceMethod.STATIC - interface.ipv6.address = [ip_interface("2a03:169:3df5:0:6be9:2588:b26a:a679/64")] - interface.ipv6.nameservers = [ - ip_address("2606:4700:4700::64"), - ip_address("2606:4700:4700::6400"), - ip_address("2606:4700:4700::1111"), - ] - interface.ipv6.gateway = ip_address("fe80::da58:d7ff:fe00:9c69") - - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv6"]["method"] == "manual" - assert ( - DBus.parse_gvariant(data)["ipv6"]["address-data"][0]["address"] - == "2a03:169:3df5:0:6be9:2588:b26a:a679" - ) - assert DBus.parse_gvariant(data)["ipv6"]["address-data"][0]["prefix"] == 64 - assert DBus.parse_gvariant(data)["ipv6"]["dns"] == [ - [38, 6, 71, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100], - [38, 6, 71, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0], - [38, 6, 71, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17], - ] - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - assert DBus.parse_gvariant(data)["ipv6"]["gateway"] == "fe80::da58:d7ff:fe00:9c69" - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv6_disabled(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv6.method = InterfaceMethod.DISABLED - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv6"]["method"] == "disabled" - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv6_auto(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv6.method = InterfaceMethod.AUTO - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv6"]["method"] == "auto" - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - - -@pytest.mark.asyncio -async def test_interface_update_payload_wireless_wpa_psk(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - - interface.type = InterfaceType.WIRELESS - interface.wifi = WifiConfig( - WifiMode.INFRASTRUCTURE, "Test", AuthMethod.WPA_PSK, "password", 0 - ) - - data = interface_update_payload(interface) - - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-11-wireless" - assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == [84, 101, 115, 116] - assert DBus.parse_gvariant(data)["802-11-wireless"]["mode"] == "infrastructure" - - assert DBus.parse_gvariant(data)["802-11-wireless-security"]["auth-alg"] == "open" - assert ( - DBus.parse_gvariant(data)["802-11-wireless-security"]["key-mgmt"] == "wpa-psk" - ) - assert DBus.parse_gvariant(data)["802-11-wireless-security"]["psk"] == "password" - - -@pytest.mark.asyncio -async def test_interface_update_payload_wireless_web(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - - interface.type = InterfaceType.WIRELESS - interface.wifi = WifiConfig( - WifiMode.INFRASTRUCTURE, "Test", AuthMethod.WEP, "password", 0 - ) - - data = interface_update_payload(interface) - - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-11-wireless" - assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == [84, 101, 115, 116] - assert DBus.parse_gvariant(data)["802-11-wireless"]["mode"] == "infrastructure" - - assert DBus.parse_gvariant(data)["802-11-wireless-security"]["auth-alg"] == "none" - assert DBus.parse_gvariant(data)["802-11-wireless-security"]["key-mgmt"] == "none" - assert DBus.parse_gvariant(data)["802-11-wireless-security"]["psk"] == "password" - - -@pytest.mark.asyncio -async def test_interface_update_payload_wireless_open(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - - interface.type = InterfaceType.WIRELESS - interface.wifi = WifiConfig( - WifiMode.INFRASTRUCTURE, "Test", AuthMethod.OPEN, None, 0 - ) - - data = interface_update_payload(interface) - - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-11-wireless" - assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == [84, 101, 115, 116] - assert DBus.parse_gvariant(data)["802-11-wireless"]["mode"] == "infrastructure" - assert ( - DBus.parse_gvariant(data)["802-11-wireless"]["assigned-mac-address"] - == "preserve" - ) - assert "802-11-wireless-security" not in DBus.parse_gvariant(data) - - -@pytest.mark.asyncio -async def test_interface_update_payload_vlan(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - - interface.type = InterfaceType.VLAN - interface.vlan = VlanConfig(10, interface.name) - - data = interface_update_payload(interface) - assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto" - assert DBus.parse_gvariant(data)["ipv6"]["method"] == "auto" - - assert DBus.parse_gvariant(data)["vlan"]["id"] == 10 - assert DBus.parse_gvariant(data)["vlan"]["parent"] == interface.name - assert DBus.parse_gvariant(data)["connection"]["type"] == "vlan" - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0.10" - assert "interface-name" not in DBus.parse_gvariant(data)["connection"] diff --git a/tests/dbus/test_systemd.py b/tests/dbus/test_systemd.py index 776d2a2ce..d0c5f66c7 100644 --- a/tests/dbus/test_systemd.py +++ b/tests/dbus/test_systemd.py @@ -20,7 +20,7 @@ async def test_dbus_systemd_info(coresys: CoreSys): f"{DBUS_NAME_SYSTEMD.replace('.', '_')}_properties.json" ) - with patch("supervisor.utils.gdbus.DBus.get_properties", new=mock_get_properties): + with patch("supervisor.utils.dbus.DBus.get_properties", new=mock_get_properties): await coresys.dbus.systemd.update() assert coresys.dbus.systemd.boot_timestamp == 1632236713344227 diff --git a/tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture b/tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_AppArmor-LoadProfile.json b/tests/fixtures/io_hass_os_AppArmor-LoadProfile.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_AppArmor-LoadProfile.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture b/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.json b/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture b/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.json b/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture b/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.json b/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture b/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.json b/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture b/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.json b/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.fixture b/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.json b/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.fixture b/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.json b/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager-CheckConnectivity.fixture b/tests/fixtures/org_freedesktop_NetworkManager-CheckConnectivity.json similarity index 100% rename from tests/fixtures/org_freedesktop_NetworkManager-CheckConnectivity.fixture rename to tests/fixtures/org_freedesktop_NetworkManager-CheckConnectivity.json diff --git a/tests/fixtures/org_freedesktop_NetworkManager.fixture b/tests/fixtures/org_freedesktop_NetworkManager.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43099.json b/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43099.json index 7b4e7b6b1..5f738e36e 100644 --- a/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43099.json +++ b/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43099.json @@ -7,6 +7,6 @@ "HwAddress": "E4:57:40:A9:D7:DE", "Mode": 2, "MaxBitrate": 195000, - "Strength": [47], + "Strength": 47, "LastSeen": 1398776 } diff --git a/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43100.json b/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43100.json index 15e1efdf6..161c3edfb 100644 --- a/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43100.json +++ b/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43100.json @@ -7,6 +7,6 @@ "HwAddress": "18:4B:0D:23:A1:9C", "Mode": 2, "MaxBitrate": 540000, - "Strength": [63], + "Strength": 63, "LastSeen": 1398839 } diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.fixture deleted file mode 100644 index 4758cdca3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.fixture +++ /dev/null @@ -1 +0,0 @@ -([objectpath '/org/freedesktop/NetworkManager/AccessPoint/43099', '/org/freedesktop/NetworkManager/AccessPoint/43100'],) \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.json b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.json new file mode 100644 index 000000000..d1aabbfbc --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.json @@ -0,0 +1 @@ +[["/org/freedesktop/NetworkManager/AccessPoint/43099", "/org/freedesktop/NetworkManager/AccessPoint/43100"]] diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.json b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.fixture deleted file mode 100644 index 6a452c185..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.fixture +++ /dev/null @@ -1 +0,0 @@ -() diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.json b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.fixture deleted file mode 100644 index 233343596..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.fixture +++ /dev/null @@ -1 +0,0 @@ -({'connection': {'id': <'Wired connection 1'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-3-ethernet'>, 'uuid': <'0c23631e-2118-355c-bbb0-8943229cb0d6'>}, 'ipv4': {'address-data': <[{'address': <'192.168.2.148'>, 'prefix': }]>, 'addresses': <[[uint32 2483202240, 24, 16951488]]>, 'dns': <[uint32 16951488]>, 'dns-search': <@as []>, 'gateway': <'192.168.2.1'>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, 'proxy': {}, '802-3-ethernet': {'auto-negotiate': , 'mac-address-blacklist': <@as []>, 's390-options': <@a{ss} {}>}, '802-11-wireless': {'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}},) \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.json b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.json new file mode 100644 index 000000000..1ada5fab7 --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.json @@ -0,0 +1 @@ +[{"connection": {"id": "Wired connection 1", "permissions": [], "timestamp": 1598125548, "type": "802-3-ethernet", "uuid": "0c23631e-2118-355c-bbb0-8943229cb0d6"}, "ipv4": {"address-data": [{"address": "192.168.2.148", "prefix": 24}], "addresses": [[2483202240, 24, 16951488]], "dns": [16951488], "dns-search": [], "gateway": "192.168.2.1", "method": "auto", "route-data": [], "routes": []}, "ipv6": {"address-data": [], "addresses": [], "dns": [], "dns-search": [], "method": "auto", "route-data": [], "routes": []}, "proxy": {}, "802-3-ethernet": {"auto-negotiate": false, "mac-address-blacklist": [], "s390-options": {}}, "802-11-wireless": {"ssid": [78, 69, 84, 84]}}] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.json b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.fixture b/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.json b/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_timedate1-SetNTP.fixture b/tests/fixtures/org_freedesktop_timedate1-SetNTP.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_timedate1-SetNTP.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_timedate1-SetNTP.json b/tests/fixtures/org_freedesktop_timedate1-SetNTP.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_timedate1-SetNTP.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_timedate1-SetTime.fixture b/tests/fixtures/org_freedesktop_timedate1-SetTime.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_timedate1-SetTime.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_timedate1-SetTime.json b/tests/fixtures/org_freedesktop_timedate1-SetTime.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_timedate1-SetTime.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/host/test_connectivity.py b/tests/host/test_connectivity.py index f252a7462..321871e7f 100644 --- a/tests/host/test_connectivity.py +++ b/tests/host/test_connectivity.py @@ -7,13 +7,13 @@ from supervisor.coresys import CoreSys async def test_connectivity_not_connected(coresys: CoreSys): """Test host unknown connectivity.""" - with patch("supervisor.utils.gdbus.DBus._send", return_value="[0]"): + with patch("supervisor.utils.dbus.DBus.call_dbus", return_value=[0]): await coresys.host.network.check_connectivity() assert not coresys.host.network.connectivity async def test_connectivity_connected(coresys: CoreSys): """Test host full connectivity.""" - with patch("supervisor.utils.gdbus.DBus._send", return_value="[4]"): + with patch("supervisor.utils.dbus.DBus.call_dbus", return_value=[4]): await coresys.host.network.check_connectivity() assert coresys.host.network.connectivity diff --git a/tests/utils/test_dbus.py b/tests/utils/test_dbus.py new file mode 100644 index 000000000..5e4a8936e --- /dev/null +++ b/tests/utils/test_dbus.py @@ -0,0 +1,29 @@ +"""Check dbus-next implementation.""" +from dbus_next.signature import Variant + +from supervisor.coresys import CoreSys +from supervisor.utils.dbus import DBus, _remove_dbus_signature + + +def test_remove_dbus_signature(): + """Check D-Bus signature clean-up.""" + test = _remove_dbus_signature(Variant("s", "Value")) + assert isinstance(test, str) + assert test == "Value" + + test_dict = _remove_dbus_signature({"Key": Variant("s", "Value")}) + assert isinstance(test_dict["Key"], str) + assert test_dict["Key"] == "Value" + + test_dict = _remove_dbus_signature([Variant("s", "Value")]) + assert isinstance(test_dict[0], str) + assert test_dict[0] == "Value" + + +async def test_dbus_prepare_args(coresys: CoreSys): + """Check D-Bus dynamic argument builder.""" + dbus = DBus("org.freedesktop.systemd1", "/org/freedesktop/systemd1") + signature, args = dbus._prepare_args( + True, 1, 1.0, "Value", ("a{sv}", {"Key": "Value"}) + ) + assert signature == "bidsa{sv}" diff --git a/tests/utils/test_gvariant_parser.py b/tests/utils/test_gvariant_parser.py deleted file mode 100644 index 3d705114f..000000000 --- a/tests/utils/test_gvariant_parser.py +++ /dev/null @@ -1,497 +0,0 @@ -"""Test gdbus gvariant parser.""" -from supervisor.utils.gdbus import DBus - - -def test_simple_return(): - """Test Simple return value.""" - raw = "(objectpath '/org/freedesktop/systemd1/job/35383',)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data == ["/org/freedesktop/systemd1/job/35383"] - - -def test_get_property(): - """Test Property parsing.""" - raw = "({'Hostname': <'hassio'>, 'StaticHostname': <'hassio'>, 'PrettyHostname': <''>, 'IconName': <'computer-embedded'>, 'Chassis': <'embedded'>, 'Deployment': <'production'>, 'Location': <''>, 'KernelName': <'Linux'>, 'KernelRelease': <'4.14.98-v7'>, 'KernelVersion': <'#1 SMP Sat May 11 02:17:06 UTC 2019'>, 'OperatingSystemPrettyName': <'HassOS 2.12'>, 'OperatingSystemCPEName': <'cpe:2.3:o:home_assistant:hassos:2.12:*:production:*:*:*:rpi3:*'>, 'HomeURL': <'https://hass.io/'>},)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data[0] == { - "Hostname": "hassio", - "StaticHostname": "hassio", - "PrettyHostname": "", - "IconName": "computer-embedded", - "Chassis": "embedded", - "Deployment": "production", - "Location": "", - "KernelName": "Linux", - "KernelRelease": "4.14.98-v7", - "KernelVersion": "#1 SMP Sat May 11 02:17:06 UTC 2019", - "OperatingSystemPrettyName": "HassOS 2.12", - "OperatingSystemCPEName": "cpe:2.3:o:home_assistant:hassos:2.12:*:production:*:*:*:rpi3:*", - "HomeURL": "https://hass.io/", - } - - -def test_systemd_unitlist_simple(): - """Test Systemd Unit list simple.""" - raw = "([('systemd-remount-fs.service', 'Remount Root and Kernel File Systems', 'loaded', 'active', 'exited', '', objectpath '/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice', uint32 0, '', objectpath '/'), ('sys-subsystem-net-devices-veth5714b4e.device', '/sys/subsystem/net/devices/veth5714b4e', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice', 0, '', '/'), ('rauc.service', 'Rauc Update Service', 'loaded', 'active', 'running', '', '/org/freedesktop/systemd1/unit/rauc_2eservice', 0, '', '/'), ('mnt-data-docker-overlay2-7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2-merged.mount', '/mnt/data/docker/overlay2/7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2/merged', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/mnt_2ddata_2ddocker_2doverlay2_2d7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2_2dmerged_2emount', 0, '', '/'), ('hassos-hardware.target', 'HassOS hardware targets', 'loaded', 'active', 'active', '', '/org/freedesktop/systemd1/unit/hassos_2dhardware_2etarget', 0, '', '/'), ('dev-zram1.device', '/dev/zram1', 'loaded', 'active', 'plugged', 'sys-devices-virtual-block-zram1.device', '/org/freedesktop/systemd1/unit/dev_2dzram1_2edevice', 0, '', '/'), ('sys-subsystem-net-devices-hassio.device', '/sys/subsystem/net/devices/hassio', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dhassio_2edevice', 0, '', '/'), ('cryptsetup.target', 'cryptsetup.target', 'not-found', 'inactive', 'dead', '', '/org/freedesktop/systemd1/unit/cryptsetup_2etarget', 0, '', '/'), ('sys-devices-virtual-net-vethd256dfa.device', '/sys/devices/virtual/net/vethd256dfa', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dvethd256dfa_2edevice', 0, '', '/'), ('network-pre.target', 'Network (Pre)', 'loaded', 'inactive', 'dead', '', '/org/freedesktop/systemd1/unit/network_2dpre_2etarget', 0, '', '/'), ('sys-devices-virtual-net-veth5714b4e.device', '/sys/devices/virtual/net/veth5714b4e', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dveth5714b4e_2edevice', 0, '', '/'), ('sys-kernel-debug.mount', 'Kernel Debug File System', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/sys_2dkernel_2ddebug_2emount', 0, '', '/'), ('slices.target', 'Slices', 'loaded', 'active', 'active', '', '/org/freedesktop/systemd1/unit/slices_2etarget', 0, '', '/'), ('etc-NetworkManager-system\x2dconnections.mount', 'NetworkManager persistent system connections', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/etc_2dNetworkManager_2dsystem_5cx2dconnections_2emount', 0, '', '/'), ('run-docker-netns-26ede3178729.mount', '/run/docker/netns/26ede3178729', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/run_2ddocker_2dnetns_2d26ede3178729_2emount', 0, '', '/'), ('dev-disk-by\x2dpath-platform\x2d3f202000.mmc\x2dpart2.device', '/dev/disk/by-path/platform-3f202000.mmc-part2', 'loaded', 'active', 'plugged', 'sys-devices-platform-soc-3f202000.mmc-mmc_host-mmc0-mmc0:e624-block-mmcblk0-mmcblk0p2.device', '/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dplatform_5cx2d3f202000_2emmc_5cx2dpart2_2edevice', 0, '', '/')],)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data == [ - [ - [ - "systemd-remount-fs.service", - "Remount Root and Kernel File Systems", - "loaded", - "active", - "exited", - "", - "/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice", - 0, - "", - "/", - ], - [ - "sys-subsystem-net-devices-veth5714b4e.device", - "/sys/subsystem/net/devices/veth5714b4e", - "loaded", - "active", - "plugged", - "", - "/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice", - 0, - "", - "/", - ], - [ - "rauc.service", - "Rauc Update Service", - "loaded", - "active", - "running", - "", - "/org/freedesktop/systemd1/unit/rauc_2eservice", - 0, - "", - "/", - ], - [ - "mnt-data-docker-overlay2-7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2-merged.mount", - "/mnt/data/docker/overlay2/7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2/merged", - "loaded", - "active", - "mounted", - "", - "/org/freedesktop/systemd1/unit/mnt_2ddata_2ddocker_2doverlay2_2d7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2_2dmerged_2emount", - 0, - "", - "/", - ], - [ - "hassos-hardware.target", - "HassOS hardware targets", - "loaded", - "active", - "active", - "", - "/org/freedesktop/systemd1/unit/hassos_2dhardware_2etarget", - 0, - "", - "/", - ], - [ - "dev-zram1.device", - "/dev/zram1", - "loaded", - "active", - "plugged", - "sys-devices-virtual-block-zram1.device", - "/org/freedesktop/systemd1/unit/dev_2dzram1_2edevice", - 0, - "", - "/", - ], - [ - "sys-subsystem-net-devices-hassio.device", - "/sys/subsystem/net/devices/hassio", - "loaded", - "active", - "plugged", - "", - "/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dhassio_2edevice", - 0, - "", - "/", - ], - [ - "cryptsetup.target", - "cryptsetup.target", - "not-found", - "inactive", - "dead", - "", - "/org/freedesktop/systemd1/unit/cryptsetup_2etarget", - 0, - "", - "/", - ], - [ - "sys-devices-virtual-net-vethd256dfa.device", - "/sys/devices/virtual/net/vethd256dfa", - "loaded", - "active", - "plugged", - "", - "/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dvethd256dfa_2edevice", - 0, - "", - "/", - ], - [ - "network-pre.target", - "Network (Pre)", - "loaded", - "inactive", - "dead", - "", - "/org/freedesktop/systemd1/unit/network_2dpre_2etarget", - 0, - "", - "/", - ], - [ - "sys-devices-virtual-net-veth5714b4e.device", - "/sys/devices/virtual/net/veth5714b4e", - "loaded", - "active", - "plugged", - "", - "/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dveth5714b4e_2edevice", - 0, - "", - "/", - ], - [ - "sys-kernel-debug.mount", - "Kernel Debug File System", - "loaded", - "active", - "mounted", - "", - "/org/freedesktop/systemd1/unit/sys_2dkernel_2ddebug_2emount", - 0, - "", - "/", - ], - [ - "slices.target", - "Slices", - "loaded", - "active", - "active", - "", - "/org/freedesktop/systemd1/unit/slices_2etarget", - 0, - "", - "/", - ], - [ - "etc-NetworkManager-system-connections.mount", - "NetworkManager persistent system connections", - "loaded", - "active", - "mounted", - "", - "/org/freedesktop/systemd1/unit/etc_2dNetworkManager_2dsystem_5cx2dconnections_2emount", - 0, - "", - "/", - ], - [ - "run-docker-netns-26ede3178729.mount", - "/run/docker/netns/26ede3178729", - "loaded", - "active", - "mounted", - "", - "/org/freedesktop/systemd1/unit/run_2ddocker_2dnetns_2d26ede3178729_2emount", - 0, - "", - "/", - ], - [ - "dev-disk-by-path-platform-3f202000.mmc-part2.device", - "/dev/disk/by-path/platform-3f202000.mmc-part2", - "loaded", - "active", - "plugged", - "sys-devices-platform-soc-3f202000.mmc-mmc_host-mmc0-mmc0:e624-block-mmcblk0-mmcblk0p2.device", - "/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dplatform_5cx2d3f202000_2emmc_5cx2dpart2_2edevice", - 0, - "", - "/", - ], - ] - ] - - -def test_systemd_unitlist_complex(): - """Test Systemd Unit list simple.""" - raw = "([('systemd-remount-fs.service', 'Remount Root and \"Kernel File Systems\"', 'loaded', 'active', 'exited', '', objectpath '/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice', uint32 0, '', objectpath '/'), ('sys-subsystem-net-devices-veth5714b4e.device', '/sys/subsystem/net/devices/veth5714b4e for \" is', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice', 0, '', '/')],)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data == [ - [ - [ - "systemd-remount-fs.service", - 'Remount Root and "Kernel File Systems"', - "loaded", - "active", - "exited", - "", - "/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice", - 0, - "", - "/", - ], - [ - "sys-subsystem-net-devices-veth5714b4e.device", - '/sys/subsystem/net/devices/veth5714b4e for " is', - "loaded", - "active", - "plugged", - "", - "/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice", - 0, - "", - "/", - ], - ] - ] - - -def test_networkmanager_dns_properties(): - """Test NetworkManager DNS properties.""" - raw = "({'Mode': <'default'>, 'RcManager': <'file'>, 'Configuration': <[{'nameservers': <['192.168.23.30']>, 'domains': <['syshack.local']>, 'interface': <'eth0'>, 'priority': <100>, 'vpn': }]>},)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "Mode": "default", - "RcManager": "file", - "Configuration": [ - { - "nameservers": ["192.168.23.30"], - "domains": ["syshack.local"], - "interface": "eth0", - "priority": 100, - "vpn": False, - } - ], - } - ] - - -def test_networkmanager_dns_properties_empty(): - """Test NetworkManager DNS properties.""" - raw = "({'Mode': <'default'>, 'RcManager': <'resolvconf'>, 'Configuration': <@aa{sv} []>},)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data == [{"Mode": "default", "RcManager": "resolvconf", "Configuration": []}] - - -def test_networkmanager_binary_data(): - """Test NetworkManager Binary datastrings.""" - raw = "({'802-11-wireless': {'mac-address-blacklist': <@as []>, 'mode': <'infrastructure'>, 'security': <'802-11-wireless-security'>, 'seen-bssids': <['7C:2E:BD:98:1B:06']>, 'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}, 'connection': {'id': <'NETT'>, 'interface-name': <'wlan0'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-11-wireless'>, 'uuid': <'13f9af79-a6e9-4e07-9353-165ad57bf1a8'>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, '802-11-wireless-security': {'auth-alg': <'open'>, 'key-mgmt': <'wpa-psk'>}, 'ipv4': {'address-data': <@aa{sv} []>, 'addresses': <@aau []>, 'dns': <@au []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'proxy': {}},)" - - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "802-11-wireless": { - "mac-address-blacklist": [], - "mode": "infrastructure", - "security": "802-11-wireless-security", - "seen-bssids": ["7C:2E:BD:98:1B:06"], - "ssid": [78, 69, 84, 84], - }, - "connection": { - "id": "NETT", - "interface-name": "wlan0", - "permissions": [], - "timestamp": 1598526799, - "type": "802-11-wireless", - "uuid": "13f9af79-a6e9-4e07-9353-165ad57bf1a8", - }, - "ipv6": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "802-11-wireless-security": {"auth-alg": "open", "key-mgmt": "wpa-psk"}, - "ipv4": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "proxy": {}, - } - ] - - raw = "({'802-11-wireless': {'mac-address-blacklist': <@as []>, 'mac-address': <[byte 0xca, 0x0b, 0x61, 0x00, 0xd8, 0xbd]>, 'mode': <'infrastructure'>, 'security': <'802-11-wireless-security'>, 'seen-bssids': <['7C:2E:BD:98:1B:06']>, 'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}, 'connection': {'id': <'NETT'>, 'interface-name': <'wlan0'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-11-wireless'>, 'uuid': <'13f9af79-a6e9-4e07-9353-165ad57bf1a8'>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, '802-11-wireless-security': {'auth-alg': <'open'>, 'key-mgmt': <'wpa-psk'>}, 'ipv4': {'address-data': <@aa{sv} []>, 'addresses': <@aau []>, 'dns': <@au []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'proxy': {}},)" - - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "802-11-wireless": { - "mac-address": [202, 11, 97, 0, 216, 189], - "mac-address-blacklist": [], - "mode": "infrastructure", - "security": "802-11-wireless-security", - "seen-bssids": ["7C:2E:BD:98:1B:06"], - "ssid": [78, 69, 84, 84], - }, - "802-11-wireless-security": {"auth-alg": "open", "key-mgmt": "wpa-psk"}, - "connection": { - "id": "NETT", - "interface-name": "wlan0", - "permissions": [], - "timestamp": 1598526799, - "type": "802-11-wireless", - "uuid": "13f9af79-a6e9-4e07-9353-165ad57bf1a8", - }, - "ipv4": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "ipv6": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "proxy": {}, - } - ] - - -def test_networkmanager_binary_string_data(): - """Test NetworkManager Binary string datastrings.""" - raw = "({'802-11-wireless': {'mac-address-blacklist': <@as []>, 'mac-address': , 'mode': <'infrastructure'>, 'security': <'802-11-wireless-security'>, 'seen-bssids': <['7C:2E:BD:98:1B:06']>, 'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}, 'connection': {'id': <'NETT'>, 'interface-name': <'wlan0'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-11-wireless'>, 'uuid': <'13f9af79-a6e9-4e07-9353-165ad57bf1a8'>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, '802-11-wireless-security': {'auth-alg': <'open'>, 'key-mgmt': <'wpa-psk'>}, 'ipv4': {'address-data': <@aa{sv} []>, 'addresses': <@aau []>, 'dns': <@au []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'proxy': {}},)" - - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "802-11-wireless": { - "mac-address": [42, 126, 95, 29, 195, 137], - "mac-address-blacklist": [], - "mode": "infrastructure", - "security": "802-11-wireless-security", - "seen-bssids": ["7C:2E:BD:98:1B:06"], - "ssid": [78, 69, 84, 84], - }, - "802-11-wireless-security": {"auth-alg": "open", "key-mgmt": "wpa-psk"}, - "connection": { - "id": "NETT", - "interface-name": "wlan0", - "permissions": [], - "timestamp": 1598526799, - "type": "802-11-wireless", - "uuid": "13f9af79-a6e9-4e07-9353-165ad57bf1a8", - }, - "ipv4": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "ipv6": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "proxy": {}, - } - ] - - -def test_v6(): - """Test IPv6 Property.""" - raw = "({'addresses': <[([byte 0x20, 0x01, 0x04, 0x70, 0x79, 0x2d, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10], uint32 64, [byte 0x20, 0x01, 0x04, 0x70, 0x79, 0x2d, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])]>, 'dns': <[[byte 0x20, 0x01, 0x04, 0x70, 0x79, 0x2d, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05], [0x20, 0x01, 0x04, 0x70, 0x79, 0x2d, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05]]>})" - - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "addresses": [ - [ - [32, 1, 4, 112, 121, 45, 0, 1, 0, 18, 0, 0, 0, 0, 0, 16], - 64, - [32, 1, 4, 112, 121, 45, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], - ] - ], - "dns": [ - [32, 1, 4, 112, 121, 45, 0, 1, 0, 18, 0, 0, 0, 0, 0, 5], - [32, 1, 4, 112, 121, 45, 0, 1, 0, 18, 0, 0, 0, 0, 0, 5], - ], - } - ] - - -def test_single_byte(): - """Test a singlebyte response.""" - raw = "({'Flags': , 'WpaFlags': , 'RsnFlags': , 'Ssid': <[byte 0x53, 0x59, 0x53, 0x48, 0x41, 0x43, 0x4b, 0x5f, 0x48, 0x6f, 0x6d, 0x65]>, 'Frequency': , 'HwAddress': <'18:4B:0D:A3:A1:9C'>, 'Mode': , 'MaxBitrate': , 'Strength': , 'LastSeen': <1646569>},)" - - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "Flags": 1, - "Frequency": 5660, - "HwAddress": "18:4B:0D:A3:A1:9C", - "LastSeen": 1646569, - "MaxBitrate": 540000, - "Mode": 2, - "RsnFlags": 392, - "Ssid": [83, 89, 83, 72, 65, 67, 75, 95, 72, 111, 109, 101], - "Strength": [44], - "WpaFlags": 0, - } - ]