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
This commit is contained in:
parent
9dd5eee1ae
commit
7a6663ba80
|
@ -16,7 +16,6 @@ RUN \
|
|||
eudev \
|
||||
eudev-libs \
|
||||
git \
|
||||
glib \
|
||||
libffi \
|
||||
libpulse \
|
||||
musl \
|
||||
|
|
|
@ -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
|
||||
|
|
2
setup.py
2
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",
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
)
|
|
@ -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
|
|
@ -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]:
|
||||
|
|
|
@ -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]:
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
"""Init file for DBUS payloads."""
|
|
@ -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)
|
|
@ -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': <uint32 2>,
|
||||
'mdns': <uint32 2>
|
||||
}
|
||||
|
||||
{% 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': <uint32 {{ address.with_prefixlen.partition("/") | last | int }}>
|
||||
}]>,
|
||||
{% 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': <uint32 {{ address.with_prefixlen.partition("/") | last | int }}>
|
||||
}]>,
|
||||
{% 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': <uint32 {{ interface.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': <uint32 1>
|
||||
{% 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 %}
|
||||
}
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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}.*?)\]|<byte (.*?)>"
|
||||
)
|
||||
RE_GVARIANT_BINARY_STRING: re.Pattern[Any] = re.compile(
|
||||
r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|<?b\'(.*?)\'>?"
|
||||
)
|
||||
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<signal>[^\s].+?) (?P<data>.*)"
|
||||
)
|
||||
|
||||
# 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
|
|
@ -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"))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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"]
|
|
@ -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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
(<true>,)
|
|
@ -0,0 +1 @@
|
|||
[true]
|
|
@ -1 +0,0 @@
|
|||
(<true>,)
|
|
@ -0,0 +1 @@
|
|||
[true]
|
|
@ -1 +0,0 @@
|
|||
(<true>,)
|
|
@ -0,0 +1 @@
|
|||
[true]
|
|
@ -1 +0,0 @@
|
|||
(<true>,)
|
|
@ -0,0 +1 @@
|
|||
[true]
|
|
@ -1 +0,0 @@
|
|||
(<true>,)
|
|
@ -0,0 +1 @@
|
|||
[true]
|
|
@ -1 +0,0 @@
|
|||
(<true>,)
|
|
@ -0,0 +1 @@
|
|||
[true]
|
|
@ -1 +0,0 @@
|
|||
()
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -1 +0,0 @@
|
|||
()
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -1 +0,0 @@
|
|||
()
|
|
@ -7,6 +7,6 @@
|
|||
"HwAddress": "E4:57:40:A9:D7:DE",
|
||||
"Mode": 2,
|
||||
"MaxBitrate": 195000,
|
||||
"Strength": [47],
|
||||
"Strength": 47,
|
||||
"LastSeen": 1398776
|
||||
}
|
||||
|
|
|
@ -7,6 +7,6 @@
|
|||
"HwAddress": "18:4B:0D:23:A1:9C",
|
||||
"Mode": 2,
|
||||
"MaxBitrate": 540000,
|
||||
"Strength": [63],
|
||||
"Strength": 63,
|
||||
"LastSeen": 1398839
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
([objectpath '/org/freedesktop/NetworkManager/AccessPoint/43099', '/org/freedesktop/NetworkManager/AccessPoint/43100'],)
|
1
tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.json
vendored
Normal file
1
tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
[["/org/freedesktop/NetworkManager/AccessPoint/43099", "/org/freedesktop/NetworkManager/AccessPoint/43100"]]
|
|
@ -1 +0,0 @@
|
|||
()
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -1 +0,0 @@
|
|||
()
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -1 +0,0 @@
|
|||
({'connection': {'id': <'Wired connection 1'>, 'permissions': <@as []>, 'timestamp': <uint64 1598125548>, 'type': <'802-3-ethernet'>, 'uuid': <'0c23631e-2118-355c-bbb0-8943229cb0d6'>}, 'ipv4': {'address-data': <[{'address': <'192.168.2.148'>, 'prefix': <uint32 24>}]>, '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': <false>, 'mac-address-blacklist': <@as []>, 's390-options': <@a{ss} {}>}, '802-11-wireless': {'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}},)
|
|
@ -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]}}]
|
|
@ -1 +0,0 @@
|
|||
()
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -1 +0,0 @@
|
|||
()
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -1 +0,0 @@
|
|||
()
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -1 +0,0 @@
|
|||
()
|
|
@ -0,0 +1 @@
|
|||
[]
|
|
@ -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
|
||||
|
|
|
@ -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}"
|
|
@ -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': <false>}]>},)"
|
||||
|
||||
# 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': <uint64 1598526799>, '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': <uint64 1598526799>, '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': <b'*~_\\\\035\\\\311'>, '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': <uint64 1598526799>, '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': <uint32 1>, 'WpaFlags': <uint32 0>, 'RsnFlags': <uint32 392>, 'Ssid': <[byte 0x53, 0x59, 0x53, 0x48, 0x41, 0x43, 0x4b, 0x5f, 0x48, 0x6f, 0x6d, 0x65]>, 'Frequency': <uint32 5660>, 'HwAddress': <'18:4B:0D:A3:A1:9C'>, 'Mode': <uint32 2>, 'MaxBitrate': <uint32 540000>, 'Strength': <byte 0x2c>, '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,
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue