Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ If you are not sure which encryption method to use, you can leave it empty or pa

This function will throw a `LoginTimeoutException` when no match is found, since this is still a HTTP Time Out. This could caused by the wrong encryption method, but also by trying to connect to an inaccessible host.

It will throw a `LoginRetryErrorException` when the router has locked logins after too many attempts. Wait for the lock to expire before trying again.

### Handle exceptions

Some functions may cause an error when an attempt is made to execute it. These exceptions are thrown by the client and need to be [handled in your Python program](https://docs.python.org/3/tutorial/errors.html#handling-exceptions). Best practice is to catch some specific exceptions and handle them gracefully.
Expand Down
7 changes: 5 additions & 2 deletions sagemcom_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,11 @@ async def logout(self):
self._request_id = -1

async def get_encryption_method(self):
"""Determine which encryption method to use for authentication and set it directly."""
"""Determine which encryption method to use for authentication and set it directly.

:raises LoginRetryErrorException: the router has locked logins after too many attempts.
Probing further would only extend the lock, so detection stops here.
"""
for encryption_method in EncryptionMethod:
try:
self.authentication_method = encryption_method
Expand All @@ -380,7 +384,6 @@ async def get_encryption_method(self):
except (
LoginTimeoutException,
AuthenticationException,
LoginRetryErrorException,
):
pass

Expand Down
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ def login_auth_error_response() -> dict[str, Any]:
return load_fixture("login_auth_error.json")


@pytest.fixture
def login_retry_error_response() -> dict[str, Any]:
"""Mock response for a login locked after too many attempts."""
return load_fixture("login_retry_error.json")


@pytest.fixture
def login_invalid_session_response() -> dict[str, Any]:
"""Mock response for invalid session error."""
Expand Down
22 changes: 22 additions & 0 deletions tests/fixtures/login_retry_error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"reply": {
"uid": 0,
"id": 0,
"error": {
"code": 16777236,
"description": "XMO_REQUEST_ACTION_ERR"
},
"actions": [
{
"uid": 1,
"id": 0,
"error": {
"code": 16777224,
"description": "XMO_LOGIN_RETRY_ERR"
},
"callbacks": []
}
],
"events": []
}
}
31 changes: 31 additions & 0 deletions tests/unit/test_encryption_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Tests for automatic encryption method detection."""

import pytest

from sagemcom_api.client import SagemcomClient
from sagemcom_api.enums import EncryptionMethod
from sagemcom_api.exceptions import LoginRetryErrorException


@pytest.mark.asyncio
async def test_detection_stops_when_router_locks_login(mock_session_factory, login_retry_error_response):
"""XMO_LOGIN_RETRY_ERR means the router has locked logins; probing further only extends the lock."""
mock_session = mock_session_factory([login_retry_error_response] * 3)
client = SagemcomClient("192.168.1.1", "admin", "admin", session=mock_session)

with pytest.raises(LoginRetryErrorException):
await client.get_encryption_method()

assert mock_session.post.call_count == 1


@pytest.mark.asyncio
async def test_detection_moves_on_after_authentication_error(mock_session_factory, login_auth_error_response, login_success_response):
"""A plain authentication error still means "wrong method", so the next method is tried."""
mock_session = mock_session_factory([login_auth_error_response, login_success_response])
client = SagemcomClient("192.168.1.1", "admin", "admin", session=mock_session)

method = await client.get_encryption_method()

assert method == EncryptionMethod.MD5_NONCE
assert mock_session.post.call_count == 2
Loading