Coverage for amqtt/mqtt/connack.py: 93%
69 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-08-12 14:35 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-08-12 14:35 +0000
1from typing_extensions import Self
3from amqtt.adapters import ReaderAdapter
4from amqtt.codecs_amqtt import bytes_to_int, read_or_raise
5from amqtt.errors import AMQTTError
6from amqtt.mqtt.packet import CONNACK, MQTTFixedHeader, MQTTPacket, MQTTPayload, MQTTVariableHeader
8CONNECTION_ACCEPTED = 0x00
9UNACCEPTABLE_PROTOCOL_VERSION = 0x01
10IDENTIFIER_REJECTED = 0x02
11SERVER_UNAVAILABLE = 0x03
12BAD_USERNAME_PASSWORD = 0x04
13NOT_AUTHORIZED = 0x05
16class ConnackVariableHeader(MQTTVariableHeader):
17 __slots__ = ("return_code", "session_parent")
19 def __init__(self, session_parent: int | None = None, return_code: int | None = None) -> None:
20 super().__init__()
21 self.session_parent = session_parent
22 self.return_code = return_code
24 @classmethod
25 async def from_stream(cls, reader: ReaderAdapter, _: MQTTFixedHeader | None) -> Self:
26 data = await read_or_raise(reader, 2)
27 session_parent = data[0] & 0x01
28 return_code = bytes_to_int(data[1])
29 return cls(session_parent, return_code)
31 def to_bytes(self) -> bytes | bytearray:
32 out = bytearray(2)
33 # Connect acknowledge flags
34 out[0] = 1 if self.session_parent else 0
35 # Return code
36 out[1] = self.return_code or 0
37 return out
39 def __repr__(self) -> str:
40 """Return a string representation of the ConnackVariableHeader object."""
41 return f"{type(self).__name__}(session_parent={hex(self.session_parent or 0)}, return_code={hex(self.return_code or 0)})"
44class ConnackPacket(MQTTPacket[ConnackVariableHeader, MQTTPayload[MQTTVariableHeader], MQTTFixedHeader]):
45 VARIABLE_HEADER = ConnackVariableHeader
46 PAYLOAD = MQTTPayload[MQTTVariableHeader]
48 def __init__(
49 self,
50 fixed: MQTTFixedHeader | None = None,
51 variable_header: ConnackVariableHeader | None = None,
52 payload: MQTTPayload[MQTTVariableHeader] | None = None,
53 ) -> None:
54 if fixed is None:
55 header = MQTTFixedHeader(CONNACK, 0x00)
56 elif fixed.packet_type != CONNACK:
57 msg = f"Invalid fixed packet type {fixed.packet_type} for ConnackPacket init"
58 raise AMQTTError(msg) from None
59 else:
60 header = fixed
62 super().__init__(header, variable_header, payload)
64 @classmethod
65 def build(cls, session_parent: int | None = None, return_code: int | None = None) -> Self:
66 v_header = ConnackVariableHeader(session_parent, return_code)
67 return cls(variable_header=v_header)
69 @property
70 def return_code(self) -> int | None:
71 if self.variable_header is None:
72 msg = "Variable header is not set"
73 raise ValueError(msg)
74 return self.variable_header.return_code
76 @return_code.setter
77 def return_code(self, return_code: int | None) -> None:
78 if self.variable_header is None: 78 ↛ 81line 78 didn't jump to line 81 because the condition on line 78 was always true
79 msg = "Variable header is not set"
80 raise ValueError(msg)
81 self.variable_header.return_code = return_code
83 @property
84 def session_parent(self) -> int | None:
85 if self.variable_header is None: 85 ↛ 88line 85 didn't jump to line 88 because the condition on line 85 was always true
86 msg = "Variable header is not set"
87 raise ValueError(msg)
88 return self.variable_header.session_parent
90 @session_parent.setter
91 def session_parent(self, session_parent: int | None) -> None:
92 if self.variable_header is None: 92 ↛ 95line 92 didn't jump to line 95 because the condition on line 92 was always true
93 msg = "Variable header is not set"
94 raise ValueError(msg)
95 self.variable_header.session_parent = session_parent