File: __init__.py

package info (click to toggle)
python-asusrouter 1.21.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,856 kB
  • sloc: python: 20,497; makefile: 3
file content (249 lines) | stat: -rw-r--r-- 6,901 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
"""Endpoint module for AsusRouter."""

from __future__ import annotations

from collections.abc import Awaitable, Callable
from concurrent.futures import ThreadPoolExecutor
from enum import Enum
import importlib
import logging
from types import ModuleType
from typing import Any, Final

from asusrouter.const import HTTPStatus, RequestType
from asusrouter.error import AsusRouter404Error, AsusRouterRequestFormatError
from asusrouter.modules.data import AsusData, AsusDataState
from asusrouter.modules.firmware import Firmware
from asusrouter.modules.wlan import Wlan

_LOGGER = logging.getLogger(__name__)


class Endpoint(str, Enum):
    """Endpoint enum.

    These endpoints are used to receive data from the device.
    """

    CERT_INFO = "ajax_certinfo.asp"
    DDNS_CODE = "ajax_ddnscode.asp"
    DEVICEMAP = "ajax_status.xml"
    DSL = "ajax_AdslStatus.asp"
    ETHERNET_PORTS = "ajax_ethernet_ports.asp"
    FIRMWARE = "detect_firmware.asp"
    FIRMWARE_NOTE = "release_note0.asp"
    FIRMWARE_NOTE_AIMESH = "release_note_amas.asp"
    HOOK = "appGet.cgi"
    NETWORKMAPD = "update_networkmapd.asp"
    ONBOARDING = "ajax_onboarding.asp"
    PORT_STATUS = "get_port_status.cgi"
    STATE = "state.js"
    SYSINFO = "ajax_sysinfo.asp"
    TEMPERATURE = "ajax_coretmp.asp"
    UPDATE_CLIENTS = "update_clients.asp"
    VPN = "ajax_vpn_status.asp"
    # Endpoints known but not used / not available on test devices
    # RGB = "light_effect.html"


class EndpointControl(str, Enum):
    """Control endpoint enum.

    These endpoints are used to set parameters to the device.
    """

    APPLY = "apply.cgi"
    COMMAND = "applyapp.cgi"


class EndpointService(str, Enum):
    """Service endpoints."""

    LOGIN = "login.cgi"
    LOGOUT = "Logout.asp"


class EndpointTools(str, Enum):
    """Tools endpoints."""

    # AURA control / RGB
    AURA = "set_ledg.cgi"
    # Network tools: ping, jitter, loss
    NETWORK = "netool.cgi"
    # Traffic analysis group
    TRAFFIC_BACKHAUL = "get_diag_sta_traffic.cgi"
    TRAFFIC_ETHERNET = "get_diag_eth_traffic.cgi"
    TRAFFIC_WIFI = "get_diag_wifi_traffic.cgi"


class EndpointNoCheck(str, Enum):
    """Endpoints that should not be checked for availability."""

    # AURA control / RGB
    AURA = "set_ledg.cgi"


# Typehint for the endpoint
EndpointType = Endpoint | EndpointControl | EndpointService | EndpointTools


# Force request type for the endpoint
ENDPOINT_FORCE_REQUEST = {
    Endpoint.PORT_STATUS: RequestType.GET,
    EndpointTools.NETWORK: RequestType.GET,
    EndpointTools.TRAFFIC_BACKHAUL: RequestType.GET,
    EndpointTools.TRAFFIC_ETHERNET: RequestType.GET,
    EndpointTools.TRAFFIC_WIFI: RequestType.GET,
}


SENSITIVE_ENDPOINTS: Final[frozenset[EndpointType]] = frozenset(
    {
        EndpointService.LOGIN,
        EndpointControl.APPLY,
    }
)


def get_request_type(endpoint: EndpointType) -> RequestType | None:
    """Get the request type for the endpoint."""

    return ENDPOINT_FORCE_REQUEST.get(endpoint, RequestType.POST)


def is_sensitive_endpoint(endpoint: EndpointType) -> bool:
    """Check if the endpoint is sensitive."""

    return endpoint in SENSITIVE_ENDPOINTS


def _get_module(
    endpoint: EndpointType,
) -> ModuleType | None:
    """Attempt to get the module for the endpoint."""

    try:
        # Get the module name from the endpoint
        module_name = f"asusrouter.modules.endpoint.{endpoint.name.lower()}"

        # Import the module in a separate thread
        # to avoid blocking the main thread
        with ThreadPoolExecutor(max_workers=1) as executor:
            future = executor.submit(importlib.import_module, module_name)
            # Return the module
            return future.result()

    except ModuleNotFoundError:
        _LOGGER.debug("No module found for endpoint %s", endpoint)
        return None


def read(
    endpoint: EndpointType,
    content: str,
    **kwargs: Any,
) -> dict[str, Any]:
    """Read the data from an endpoint."""

    _LOGGER.debug("Reading data from endpoint %s", endpoint)

    # Get the module
    submodule = _get_module(endpoint)

    # Read the data if module found
    if submodule:
        result = submodule.read(content, **kwargs)
        if isinstance(result, dict):
            return result
        return {}

    return {}


def process(
    endpoint: Endpoint,
    data: dict[str, Any],
    history: dict[AsusData, AsusDataState] | None = None,
    firmware: Firmware | None = None,
    wlan: list[Wlan] | None = None,
) -> dict[AsusData, Any]:
    """Process the data from an endpoint."""

    _LOGGER.debug("Processing data from endpoint %s", endpoint)

    # Get the module
    submodule = _get_module(endpoint)

    # Process the data if module found
    if submodule:
        # Check if the submodule requires history
        require_history = getattr(submodule, "REQUIRE_HISTORY", False)
        if require_history:
            data_set(data, history=history)
        # Check if the submodule requires identity
        require_firmware = getattr(submodule, "REQUIRE_FIRMWARE", False)
        if require_firmware:
            data_set(data, firmware=firmware)
        # Check if the submodule requires wlan
        require_wlan = getattr(submodule, "REQUIRE_WLAN", False)
        if require_wlan:
            data_set(data, wlan=wlan)

        # Process the data
        try:
            result = submodule.process(data)
            if isinstance(result, dict):
                return result
            return {}
        # Consider attribute and value errors to be possible
        # in case of unexpected data structure
        except (AttributeError, ValueError) as ex:
            _LOGGER.error(
                "Error processing data from endpoint %s: %s",
                endpoint,
                ex,
            )
            return {}

    return {}


def data_set(data: dict[str, Any], **kwargs: Any) -> dict[str, Any]:
    """Append the data to the data dict."""

    # Update the data dict with kwargs using dictionary comprehension
    data.update(dict(kwargs.items()))

    # Return the data
    return data


def data_get(data: dict[str, Any], key: str) -> Any | None:
    """Extract value from the data dict and update the data dict."""

    # Get the value
    value = data.get(key)

    # Remove the value from the data dict
    data.pop(key, None)

    # Return the value
    return value


async def check_available(
    endpoint: Endpoint | EndpointTools,
    api_query: Callable[..., Awaitable[Any]],
) -> tuple[bool, Any | None]:
    """Check whether the endpoint is available or returns 404."""

    try:
        status, _, content = await api_query(endpoint)
        if status == HTTPStatus.OK:
            return (True, content)
    except AsusRouterRequestFormatError:
        return (True, None)
    except AsusRouter404Error:
        return (False, None)

    return (False, None)