diff --git a/kasa/exceptions.py b/kasa/exceptions.py index 1c764ad7..98e893f9 100644 --- a/kasa/exceptions.py +++ b/kasa/exceptions.py @@ -135,6 +135,9 @@ class SmartErrorCode(IntEnum): # Camera error codes SESSION_EXPIRED = -40401 BAD_USERNAME = -40411 # determined from testing + # determined from testing, returned by secure-login devices (C200, H200) + # for an unaccepted username + UNKNOWN_USERNAME = -60502 HOMEKIT_LOGIN_FAIL = -40412 DEVICE_BLOCKED = -40404 DEVICE_FACTORY = -40405 diff --git a/tests/transports/test_sslaestransport.py b/tests/transports/test_sslaestransport.py index cc11ccb3..eeaebfea 100644 --- a/tests/transports/test_sslaestransport.py +++ b/tests/transports/test_sslaestransport.py @@ -345,6 +345,53 @@ async def test_unencrypted_passthrough_errors( await transport.send(json_dumps(request)) +@pytest.mark.xdist_group(name="caplog") +async def test_handshake_unaccepted_username_no_unknown_error_warning( + mocker: MockerFixture, caplog: pytest.LogCaptureFixture +) -> None: + """Secure-login devices answer -60502 for an unaccepted username.""" + host = "127.0.0.1" + mock_ssl_aes_device = MockSslAesDevice(host, want_default_username=True) + mocker.patch.object( + aiohttp.ClientSession, "post", side_effect=mock_ssl_aes_device.post + ) + transport = SslAesTransport( + config=DeviceConfig(host, credentials=Credentials(MOCK_USER, MOCK_PWD)) + ) + + caplog.set_level(logging.WARNING, logger="kasa.transports.sslaestransport") + await transport.perform_handshake() + + assert transport._state is TransportState.ESTABLISHED + assert "received unknown error code" not in caplog.text + assert SmartErrorCode.from_int(-60502) is SmartErrorCode.UNKNOWN_USERNAME + + +@pytest.mark.xdist_group(name="caplog") +async def test_handshake_unknown_inner_error_code( + mocker: MockerFixture, caplog: pytest.LogCaptureFixture +) -> None: + host = "127.0.0.1" + mock_ssl_aes_device = MockSslAesDevice(host) + mocker.patch.object( + aiohttp.ClientSession, "post", side_effect=mock_ssl_aes_device.post + ) + mocker.patch.object( + MockSslAesDevice, + "BAD_USER_RESP", + {**MockSslAesDevice.BAD_USER_RESP, "result": {"data": {"code": -99999}}}, + ) + transport = SslAesTransport( + config=DeviceConfig(host, credentials=Credentials("foobar", MOCK_PWD)) + ) + + caplog.set_level(logging.WARNING, logger="kasa.transports.sslaestransport") + with pytest.raises(AuthenticationError): + await transport.perform_handshake() + + assert f"Device {host} received unknown error code: -99999" in caplog.text + + async def test_device_blocked_response(mocker: MockerFixture) -> None: host = "127.0.0.1" mock_ssl_aes_device = MockSslAesDevice(host, device_blocked=True) @@ -465,11 +512,16 @@ async def test_login_version_default_credentials( class MockSslAesDevice: + # Response observed on secure-login devices (C200, H200) when the username + # is not the one accepted for secure login. BAD_USER_RESP = { "error_code": SmartErrorCode.SESSION_EXPIRED.value, "result": { "data": { "code": -60502, + "encrypt_type": ["1", "2"], + "key": "Someb64keyWithUnknownPurpose", + "nonce": "MixedCaseAlphaNumericWithUnknownPurpose", } }, }