Skip to content

Commit 7e039cb

Browse files
authored
Fix nonce, add auto retry on auth exception, fix session id int, improve code quality (#336)
This pull request includes several changes to improve error handling, enhance functionality, and update configurations. The most important changes include adding new exception handling for invalid sessions, updating the `postCreateCommand` in the devcontainer, and adding custom funding options. ### Error Handling Improvements: * Added `InvalidSessionException` to handle invalid session errors and updated relevant methods to raise this exception when needed (`sagemcom_api/client.py`, `sagemcom_api/const.py`, `sagemcom_api/exceptions.py`). [[1]](diffhunk://#diff-052218051094ff1cc2c8352cd555cfb9eb02bf261eb5e635cf73b798809306a5R32) [[2]](diffhunk://#diff-052218051094ff1cc2c8352cd555cfb9eb02bf261eb5e635cf73b798809306a5R46) [[3]](diffhunk://#diff-052218051094ff1cc2c8352cd555cfb9eb02bf261eb5e635cf73b798809306a5R220-R225) [[4]](diffhunk://#diff-57cf505b5e01fc69f170c5a1b839fb40b45a97a9c0ebc1fe53f4bd524ae9ac04R10) [[5]](diffhunk://#diff-63c847110513149341a9f7e94d412bfb7c67d79df07a321774a9a2f3329ca34fR4-L43) * Introduced `retry_login` function to retry login on specific exceptions using backoff (`sagemcom_api/client.py`). [[1]](diffhunk://#diff-052218051094ff1cc2c8352cd555cfb9eb02bf261eb5e635cf73b798809306a5R58-R62) [[2]](diffhunk://#diff-052218051094ff1cc2c8352cd555cfb9eb02bf261eb5e635cf73b798809306a5R260-R270) ### Functional Enhancements: * Modified `get_hosts` method to include `capability-flags` in the options for retrieving hosts (`sagemcom_api/client.py`). * Corrected the nonce generation logic to use the correct range (`sagemcom_api/client.py`). ### Configuration Updates: * Updated `postCreateCommand` in `.devcontainer/devcontainer.json` to include `pre-commit install-hooks` for better pre-commit setup. * Added a custom funding option in `.github/FUNDING.yml` to include a PayPal link.
1 parent 3da2376 commit 7e039cb

File tree

5 files changed

+63
-24
lines changed

5 files changed

+63
-24
lines changed

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// Use 'forwardPorts' to make a list of ports inside the container available locally.
1313
// "forwardPorts": [],
1414
// Use 'postCreateCommand' to run commands after the container is created.
15-
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install --with dev --no-interaction && . .venv/bin/activate && pre-commit install",
15+
"postCreateCommand": "poetry config virtualenvs.in-project true && poetry install --with dev --no-interaction && . .venv/bin/activate && pre-commit install && pre-commit install-hooks",
1616
// Configure tool-specific properties.
1717
"customizations": {
1818
"vscode": {

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
custom: ["https://paypal.me/imick"]

sagemcom_api/client.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
from __future__ import annotations
44

55
import asyncio
6+
from collections.abc import Mapping
67
import hashlib
78
import json
89
import math
910
import random
1011
from types import TracebackType
12+
from typing import Any
1113
import urllib.parse
1214

1315
from aiohttp import (
@@ -27,6 +29,7 @@
2729
DEFAULT_USER_AGENT,
2830
XMO_ACCESS_RESTRICTION_ERR,
2931
XMO_AUTHENTICATION_ERR,
32+
XMO_INVALID_SESSION_ERR,
3033
XMO_LOGIN_RETRY_ERR,
3134
XMO_MAX_SESSION_COUNT_ERR,
3235
XMO_NO_ERR,
@@ -40,6 +43,7 @@
4043
AccessRestrictionException,
4144
AuthenticationException,
4245
BadRequestException,
46+
InvalidSessionException,
4347
LoginRetryErrorException,
4448
LoginTimeoutException,
4549
MaximumSessionCountException,
@@ -51,6 +55,11 @@
5155
from .models import Device, DeviceInfo, PortMapping
5256

5357

58+
async def retry_login(invocation: Mapping[str, Any]) -> None:
59+
"""Retry login via backoff if an exception occurs."""
60+
await invocation["args"][0].login()
61+
62+
5463
# pylint: disable=too-many-instance-attributes
5564
class SagemcomClient:
5665
"""Client to communicate with the Sagemcom API."""
@@ -118,7 +127,7 @@ async def close(self) -> None:
118127

119128
def __generate_nonce(self):
120129
"""Generate pseudo random number (nonce) to avoid replay attacks."""
121-
self._current_nonce = math.floor(random.randrange(0, 1) * 500000)
130+
self._current_nonce = math.floor(random.randrange(0, 500000))
122131

123132
def __generate_request_id(self):
124133
"""Generate sequential request ID."""
@@ -187,6 +196,7 @@ def __get_response_value(self, response, index=0):
187196
(ClientConnectorError, ClientOSError, ServerDisconnectedError),
188197
max_tries=5,
189198
)
199+
# pylint: disable=too-many-branches
190200
async def __post(self, url, data):
191201
async with self.session.post(url, data=data) as response:
192202
if response.status == 400:
@@ -207,6 +217,12 @@ async def __post(self, url, data):
207217
):
208218
return result
209219

220+
if error["description"] == XMO_INVALID_SESSION_ERR:
221+
self._session_id = 0
222+
self._server_nonce = ""
223+
self._request_id = -1
224+
raise InvalidSessionException(error)
225+
210226
# Error in one of the actions
211227
if error["description"] == XMO_REQUEST_ACTION_ERR:
212228
# pylint:disable=fixme
@@ -241,6 +257,17 @@ async def __post(self, url, data):
241257

242258
return result
243259

260+
@backoff.on_exception(
261+
backoff.expo,
262+
(
263+
AuthenticationException,
264+
LoginRetryErrorException,
265+
LoginTimeoutException,
266+
InvalidSessionException,
267+
),
268+
max_tries=2,
269+
on_backoff=retry_login,
270+
)
244271
async def __api_request_async(self, actions, priority=False):
245272
"""Build request to the internal JSON-req API."""
246273
self.__generate_request_id()
@@ -252,7 +279,7 @@ async def __api_request_async(self, actions, priority=False):
252279
payload = {
253280
"request": {
254281
"id": self._request_id,
255-
"session-id": str(self._session_id),
282+
"session-id": int(self._session_id),
256283
"priority": priority,
257284
"actions": actions,
258285
"cnonce": self._current_nonce,
@@ -435,7 +462,9 @@ async def get_device_info(self) -> DeviceInfo:
435462

436463
async def get_hosts(self, only_active: bool | None = False) -> list[Device]:
437464
"""Retrieve hosts connected to Sagemcom F@st device."""
438-
data = await self.get_value_by_xpath("Device/Hosts/Hosts")
465+
data = await self.get_value_by_xpath(
466+
"Device/Hosts/Hosts", options={"capability-flags": {"interface": True}}
467+
)
439468
devices = [Device(**d) for d in data]
440469

441470
if only_active:

sagemcom_api/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
XMO_ACCESS_RESTRICTION_ERR = "XMO_ACCESS_RESTRICTION_ERR"
99
XMO_AUTHENTICATION_ERR = "XMO_AUTHENTICATION_ERR"
10+
XMO_INVALID_SESSION_ERR = "XMO_INVALID_SESSION_ERR"
1011
XMO_NON_WRITABLE_PARAMETER_ERR = "XMO_NON_WRITABLE_PARAMETER_ERR"
1112
XMO_NO_ERR = "XMO_NO_ERR"
1213
XMO_REQUEST_ACTION_ERR = "XMO_REQUEST_ACTION_ERR"

sagemcom_api/exceptions.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,51 @@
11
"""Exceptions for the Sagemcom F@st client."""
22

33

4+
class BaseSagemcomException(Exception):
5+
"""Base exception for Sagemcom F@st client."""
6+
7+
8+
# Broad exceptions provided by this library
9+
class BadRequestException(BaseSagemcomException):
10+
"""Bad request."""
11+
12+
13+
class UnauthorizedException(BaseSagemcomException):
14+
"""Unauthorized."""
15+
16+
17+
class UnknownException(BaseSagemcomException):
18+
"""Unknown exception."""
19+
20+
421
# Exceptions provided by SagemCom API
5-
class AccessRestrictionException(Exception):
22+
class AccessRestrictionException(BaseSagemcomException):
623
"""Raised when current user has access restrictions."""
724

825

9-
class AuthenticationException(Exception):
26+
class AuthenticationException(UnauthorizedException):
1027
"""Raised when authentication is not correct."""
1128

1229

13-
class LoginRetryErrorException(Exception):
30+
class InvalidSessionException(UnauthorizedException):
31+
"""Raised when session is invalid."""
32+
33+
34+
class LoginRetryErrorException(BaseSagemcomException):
1435
"""Raised when too many login retries are attempted."""
1536

1637

17-
class LoginTimeoutException(Exception):
38+
class LoginTimeoutException(BaseSagemcomException):
1839
"""Raised when a timeout is encountered during login."""
1940

2041

21-
class NonWritableParameterException(Exception):
42+
class NonWritableParameterException(BaseSagemcomException):
2243
"""Raised when provided parameter is not writable."""
2344

2445

25-
class UnknownPathException(Exception):
46+
class UnknownPathException(BaseSagemcomException):
2647
"""Raised when provided path does not exist."""
2748

2849

29-
class MaximumSessionCountException(Exception):
50+
class MaximumSessionCountException(BaseSagemcomException):
3051
"""Raised when the maximum session count is reached."""
31-
32-
33-
# Broad exceptions provided by this library
34-
class BadRequestException(Exception):
35-
"""TODO."""
36-
37-
38-
class UnauthorizedException(Exception):
39-
"""TODO."""
40-
41-
42-
class UnknownException(Exception):
43-
"""TODO."""

0 commit comments

Comments
 (0)