Skip to content

Commit 906e676

Browse files
committed
Don't retry when MQTT response is unauthorized
1 parent ecfd228 commit 906e676

File tree

2 files changed

+60
-8
lines changed

2 files changed

+60
-8
lines changed

adafruit_minimqtt/adafruit_minimqtt.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,18 @@
7272
MQTT_PKT_TYPE_MASK = const(0xF0)
7373

7474

75+
CONNACK_ERROR_INCORRECT_PROTOCOL = const(0x01)
76+
CONNACK_ERROR_ID_REJECTED = const(0x02)
77+
CONNACK_ERROR_SERVER_UNAVAILABLE = const(0x03)
78+
CONNACK_ERROR_INCORECT_USERNAME_PASSWORD = const(0x04)
79+
CONNACK_ERROR_UNAUTHORIZED = const(0x05)
80+
7581
CONNACK_ERRORS = {
76-
const(0x01): "Connection Refused - Incorrect Protocol Version",
77-
const(0x02): "Connection Refused - ID Rejected",
78-
const(0x03): "Connection Refused - Server unavailable",
79-
const(0x04): "Connection Refused - Incorrect username/password",
80-
const(0x05): "Connection Refused - Unauthorized",
82+
CONNACK_ERROR_INCORRECT_PROTOCOL: "Connection Refused - Incorrect Protocol Version",
83+
CONNACK_ERROR_ID_REJECTED: "Connection Refused - ID Rejected",
84+
CONNACK_ERROR_SERVER_UNAVAILABLE: "Connection Refused - Server unavailable",
85+
CONNACK_ERROR_INCORECT_USERNAME_PASSWORD: "Connection Refused - Incorrect username/password",
86+
CONNACK_ERROR_UNAUTHORIZED: "Connection Refused - Unauthorized",
8187
}
8288

8389
_default_sock = None # pylint: disable=invalid-name
@@ -87,6 +93,10 @@
8793
class MMQTTException(Exception):
8894
"""MiniMQTT Exception class."""
8995

96+
def __init__(self, error, code=None):
97+
super().__init__(error, code)
98+
self.code = code
99+
90100

91101
class NullLogger:
92102
"""Fake logger class that does not do anything"""
@@ -428,8 +438,14 @@ def connect(
428438
self.logger.warning(f"Socket error when connecting: {e}")
429439
backoff = False
430440
except MMQTTException as e:
431-
last_exception = e
432441
self.logger.info(f"MMQT error: {e}")
442+
if e.code in [
443+
CONNACK_ERROR_INCORECT_USERNAME_PASSWORD,
444+
CONNACK_ERROR_UNAUTHORIZED,
445+
]:
446+
# No sense trying these again, re-raise
447+
raise
448+
last_exception = e
433449
backoff = True
434450

435451
if self._reconnect_attempts_max > 1:
@@ -535,7 +551,7 @@ def _connect(
535551
rc = self._sock_exact_recv(3)
536552
assert rc[0] == 0x02
537553
if rc[2] != 0x00:
538-
raise MMQTTException(CONNACK_ERRORS[rc[2]])
554+
raise MMQTTException(CONNACK_ERRORS[rc[2]], code=rc[2])
539555
self._is_connected = True
540556
result = rc[0] & 1
541557
if self.on_connect is not None:

tests/test_backoff.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,24 @@ class TestExpBackOff:
1818
"""basic exponential back-off test"""
1919

2020
connect_times = []
21+
raise_exception = None
2122

2223
# pylint: disable=unused-argument
2324
def fake_connect(self, arg):
2425
"""connect() replacement that records the call times and always raises OSError"""
2526
self.connect_times.append(time.monotonic())
26-
raise OSError("this connect failed")
27+
raise self.raise_exception
2728

2829
def test_failing_connect(self) -> None:
2930
"""test that exponential back-off is used when connect() always raises OSError"""
3031
# use RFC 1918 address to avoid dealing with IPv6 in the call list below
3132
host = "172.40.0.3"
3233
port = 1883
34+
self.connect_times = []
35+
error_code = MQTT.CONNACK_ERROR_SERVER_UNAVAILABLE
36+
self.raise_exception = MQTT.MMQTTException(
37+
MQTT.CONNACK_ERRORS[error_code], code=error_code
38+
)
3339

3440
with patch.object(socket.socket, "connect") as mock_method:
3541
mock_method.side_effect = self.fake_connect
@@ -54,3 +60,33 @@ def test_failing_connect(self) -> None:
5460
print(f"connect() call times: {self.connect_times}")
5561
for i in range(1, connect_retries):
5662
assert self.connect_times[i] >= 2**i
63+
64+
def test_unauthorized(self) -> None:
65+
"""test that exponential back-off is used when connect() always raises OSError"""
66+
# use RFC 1918 address to avoid dealing with IPv6 in the call list below
67+
host = "172.40.0.3"
68+
port = 1883
69+
self.connect_times = []
70+
error_code = MQTT.CONNACK_ERROR_UNAUTHORIZED
71+
self.raise_exception = MQTT.MMQTTException(
72+
MQTT.CONNACK_ERRORS[error_code], code=error_code
73+
)
74+
75+
with patch.object(socket.socket, "connect") as mock_method:
76+
mock_method.side_effect = self.fake_connect
77+
78+
connect_retries = 3
79+
mqtt_client = MQTT.MQTT(
80+
broker=host,
81+
port=port,
82+
socket_pool=socket,
83+
ssl_context=ssl.create_default_context(),
84+
connect_retries=connect_retries,
85+
)
86+
print("connecting")
87+
with pytest.raises(MQTT.MMQTTException) as context:
88+
mqtt_client.connect()
89+
assert "Connection Refused - Unauthorized" in str(context)
90+
91+
mock_method.assert_called()
92+
assert len(self.connect_times) == 1

0 commit comments

Comments
 (0)