60
60
MQTT_PINGREQ = b"\xc0 \0 "
61
61
MQTT_PINGRESP = const (0xD0 )
62
62
MQTT_PUBLISH = const (0x30 )
63
- MQTT_SUB = b" \x82 "
64
- MQTT_UNSUB = b" \xA2 "
63
+ MQTT_SUB = const ( 0x82 )
64
+ MQTT_UNSUB = const ( 0xA2 )
65
65
MQTT_DISCONNECT = b"\xe0 \0 "
66
66
67
67
MQTT_PKT_TYPE_MASK = const (0xF0 )
@@ -597,13 +597,12 @@ def _connect(
597
597
self .broker , self .port , timeout = self ._socket_timeout
598
598
)
599
599
600
- # Fixed Header
601
600
fixed_header = bytearray ([0x10 ])
602
601
603
602
# Variable CONNECT header [MQTT 3.1.2]
604
603
# The byte array is used as a template.
605
- var_header = bytearray (b"\x04 MQTT\x04 \x02 \0 \0 " )
606
- var_header [6 ] = clean_session << 1
604
+ var_header = bytearray (b"\x00 \ x04 MQTT\x04 \x02 \0 \0 " )
605
+ var_header [7 ] = clean_session << 1
607
606
608
607
# Set up variable header and remaining_length
609
608
remaining_length = 12 + len (self .client_id .encode ("utf-8" ))
@@ -614,36 +613,19 @@ def _connect(
614
613
+ 2
615
614
+ len (self ._password .encode ("utf-8" ))
616
615
)
617
- var_header [6 ] |= 0xC0
616
+ var_header [7 ] |= 0xC0
618
617
if self .keep_alive :
619
618
assert self .keep_alive < MQTT_TOPIC_LENGTH_LIMIT
620
- var_header [7 ] |= self .keep_alive >> 8
621
- var_header [8 ] |= self .keep_alive & 0x00FF
619
+ var_header [8 ] |= self .keep_alive >> 8
620
+ var_header [9 ] |= self .keep_alive & 0x00FF
622
621
if self ._lw_topic :
623
622
remaining_length += (
624
623
2 + len (self ._lw_topic .encode ("utf-8" )) + 2 + len (self ._lw_msg )
625
624
)
626
- var_header [6 ] |= 0x4 | (self ._lw_qos & 0x1 ) << 3 | (self ._lw_qos & 0x2 ) << 3
627
- var_header [6 ] |= self ._lw_retain << 5
628
-
629
- # Remaining length calculation
630
- large_rel_length = False
631
- if remaining_length > 0x7F :
632
- large_rel_length = True
633
- # Calculate Remaining Length [2.2.3]
634
- while remaining_length > 0 :
635
- encoded_byte = remaining_length % 0x80
636
- remaining_length = remaining_length // 0x80
637
- # if there is more data to encode, set the top bit of the byte
638
- if remaining_length > 0 :
639
- encoded_byte |= 0x80
640
- fixed_header .append (encoded_byte )
641
- if large_rel_length :
642
- fixed_header .append (0x00 )
643
- else :
644
- fixed_header .append (remaining_length )
645
- fixed_header .append (0x00 )
625
+ var_header [7 ] |= 0x4 | (self ._lw_qos & 0x1 ) << 3 | (self ._lw_qos & 0x2 ) << 3
626
+ var_header [7 ] |= self ._lw_retain << 5
646
627
628
+ self ._encode_remaining_length (fixed_header , remaining_length )
647
629
self .logger .debug ("Sending CONNECT to broker..." )
648
630
self .logger .debug (f"Fixed Header: { fixed_header } " )
649
631
self .logger .debug (f"Variable Header: { var_header } " )
@@ -680,6 +662,26 @@ def _connect(
680
662
f"No data received from broker for { self ._recv_timeout } seconds."
681
663
)
682
664
665
+ # pylint: disable=no-self-use
666
+ def _encode_remaining_length (
667
+ self , fixed_header : bytearray , remaining_length : int
668
+ ) -> None :
669
+ """Encode Remaining Length [2.2.3]"""
670
+ if remaining_length > 268_435_455 :
671
+ raise MMQTTException ("invalid remaining length" )
672
+
673
+ # Remaining length calculation
674
+ if remaining_length > 0x7F :
675
+ while remaining_length > 0 :
676
+ encoded_byte = remaining_length % 0x80
677
+ remaining_length = remaining_length // 0x80
678
+ # if there is more data to encode, set the top bit of the byte
679
+ if remaining_length > 0 :
680
+ encoded_byte |= 0x80
681
+ fixed_header .append (encoded_byte )
682
+ else :
683
+ fixed_header .append (remaining_length )
684
+
683
685
def disconnect (self ) -> None :
684
686
"""Disconnects the MiniMQTT client from the MQTT broker."""
685
687
self ._connected ()
@@ -766,16 +768,7 @@ def publish(
766
768
pub_hdr_var .append (self ._pid >> 8 )
767
769
pub_hdr_var .append (self ._pid & 0xFF )
768
770
769
- # Calculate remaining length [2.2.3]
770
- if remaining_length > 0x7F :
771
- while remaining_length > 0 :
772
- encoded_byte = remaining_length % 0x80
773
- remaining_length = remaining_length // 0x80
774
- if remaining_length > 0 :
775
- encoded_byte |= 0x80
776
- pub_hdr_fixed .append (encoded_byte )
777
- else :
778
- pub_hdr_fixed .append (remaining_length )
771
+ self ._encode_remaining_length (pub_hdr_fixed , remaining_length )
779
772
780
773
self .logger .debug (
781
774
"Sending PUBLISH\n Topic: %s\n Msg: %s\
@@ -810,9 +803,9 @@ def publish(
810
803
f"No data received from broker for { self ._recv_timeout } seconds."
811
804
)
812
805
813
- def subscribe (self , topic : str , qos : int = 0 ) -> None :
806
+ def subscribe (self , topic : Optional [ Union [ tuple , str , list ]] , qos : int = 0 ) -> None :
814
807
"""Subscribes to a topic on the MQTT Broker.
815
- This method can subscribe to one topics or multiple topics.
808
+ This method can subscribe to one topic or multiple topics.
816
809
817
810
:param str|tuple|list topic: Unique MQTT topic identifier string. If
818
811
this is a `tuple`, then the tuple should
@@ -842,21 +835,28 @@ def subscribe(self, topic: str, qos: int = 0) -> None:
842
835
self ._valid_topic (t )
843
836
topics .append ((t , q ))
844
837
# Assemble packet
838
+ self .logger .debug ("Sending SUBSCRIBE to broker..." )
839
+ fixed_header = bytearray ([MQTT_SUB ])
845
840
packet_length = 2 + (2 * len (topics )) + (1 * len (topics ))
846
841
packet_length += sum (len (topic .encode ("utf-8" )) for topic , qos in topics )
847
- packet_length_byte = packet_length .to_bytes (1 , "big" )
842
+ self ._encode_remaining_length (fixed_header , remaining_length = packet_length )
843
+ self .logger .debug (f"Fixed Header: { fixed_header } " )
844
+ self ._sock .send (fixed_header )
848
845
self ._pid = self ._pid + 1 if self ._pid < 0xFFFF else 1
849
846
packet_id_bytes = self ._pid .to_bytes (2 , "big" )
850
- # Packet with variable and fixed headers
851
- packet = MQTT_SUB + packet_length_byte + packet_id_bytes
847
+ var_header = packet_id_bytes
848
+ self .logger .debug (f"Variable Header: { var_header } " )
849
+ self ._sock .send (var_header )
852
850
# attaching topic and QOS level to the packet
851
+ payload = bytes ()
853
852
for t , q in topics :
854
853
topic_size = len (t .encode ("utf-8" )).to_bytes (2 , "big" )
855
854
qos_byte = q .to_bytes (1 , "big" )
856
- packet += topic_size + t .encode () + qos_byte
855
+ payload += topic_size + t .encode () + qos_byte
857
856
for t , q in topics :
858
- self .logger .debug ("SUBSCRIBING to topic %s with QoS %d" , t , q )
859
- self ._sock .send (packet )
857
+ self .logger .debug (f"SUBSCRIBING to topic { t } with QoS { q } " )
858
+ self .logger .debug (f"payload: { payload } " )
859
+ self ._sock .send (payload )
860
860
stamp = self .get_monotonic_time ()
861
861
while True :
862
862
op = self ._wait_for_msg ()
@@ -867,13 +867,13 @@ def subscribe(self, topic: str, qos: int = 0) -> None:
867
867
)
868
868
else :
869
869
if op == 0x90 :
870
- rc = self ._sock_exact_recv (3 )
871
- # Check packet identifier.
872
- assert rc [1 ] == packet [2 ] and rc [2 ] == packet [3 ]
873
- remaining_len = rc [0 ] - 2
870
+ remaining_len = self ._decode_remaining_length ()
874
871
assert remaining_len > 0
875
- rc = self ._sock_exact_recv (remaining_len )
876
- for i in range (0 , remaining_len ):
872
+ rc = self ._sock_exact_recv (2 )
873
+ # Check packet identifier.
874
+ assert rc [0 ] == var_header [0 ] and rc [1 ] == var_header [1 ]
875
+ rc = self ._sock_exact_recv (remaining_len - 2 )
876
+ for i in range (0 , remaining_len - 2 ):
877
877
if rc [i ] not in [0 , 1 , 2 ]:
878
878
raise MMQTTException (
879
879
f"SUBACK Failure for topic { topics [i ][0 ]} : { hex (rc [i ])} "
@@ -883,13 +883,17 @@ def subscribe(self, topic: str, qos: int = 0) -> None:
883
883
if self .on_subscribe is not None :
884
884
self .on_subscribe (self , self .user_data , t , q )
885
885
self ._subscribed_topics .append (t )
886
+
886
887
return
887
888
888
- raise MMQTTException (
889
- f"invalid message received as response to SUBSCRIBE: { hex (op )} "
890
- )
889
+ if op != MQTT_PUBLISH :
890
+ # [3.8.4] The Server is permitted to start sending PUBLISH packets
891
+ # matching the Subscription before the Server sends the SUBACK Packet.
892
+ raise MMQTTException (
893
+ f"invalid message received as response to SUBSCRIBE: { hex (op )} "
894
+ )
891
895
892
- def unsubscribe (self , topic : str ) -> None :
896
+ def unsubscribe (self , topic : Optional [ Union [ str , list ]] ) -> None :
893
897
"""Unsubscribes from a MQTT topic.
894
898
895
899
:param str|list topic: Unique MQTT topic identifier string or list.
@@ -910,18 +914,25 @@ def unsubscribe(self, topic: str) -> None:
910
914
"Topic must be subscribed to before attempting unsubscribe."
911
915
)
912
916
# Assemble packet
917
+ self .logger .debug ("Sending UNSUBSCRIBE to broker..." )
918
+ fixed_header = bytearray ([MQTT_UNSUB ])
913
919
packet_length = 2 + (2 * len (topics ))
914
920
packet_length += sum (len (topic .encode ("utf-8" )) for topic in topics )
915
- packet_length_byte = packet_length .to_bytes (1 , "big" )
921
+ self ._encode_remaining_length (fixed_header , remaining_length = packet_length )
922
+ self .logger .debug (f"Fixed Header: { fixed_header } " )
923
+ self ._sock .send (fixed_header )
916
924
self ._pid = self ._pid + 1 if self ._pid < 0xFFFF else 1
917
925
packet_id_bytes = self ._pid .to_bytes (2 , "big" )
918
- packet = MQTT_UNSUB + packet_length_byte + packet_id_bytes
926
+ var_header = packet_id_bytes
927
+ self .logger .debug (f"Variable Header: { var_header } " )
928
+ self ._sock .send (var_header )
929
+ payload = bytes ()
919
930
for t in topics :
920
931
topic_size = len (t .encode ("utf-8" )).to_bytes (2 , "big" )
921
- packet += topic_size + t .encode ()
932
+ payload += topic_size + t .encode ()
922
933
for t in topics :
923
- self .logger .debug ("UNSUBSCRIBING from topic %s" , t )
924
- self ._sock .send (packet )
934
+ self .logger .debug (f "UNSUBSCRIBING from topic { t } " )
935
+ self ._sock .send (payload )
925
936
self .logger .debug ("Waiting for UNSUBACK..." )
926
937
while True :
927
938
stamp = self .get_monotonic_time ()
@@ -1082,7 +1093,7 @@ def _wait_for_msg(self) -> Optional[int]:
1082
1093
return pkt_type
1083
1094
1084
1095
# Handle only the PUBLISH packet type from now on.
1085
- sz = self ._recv_len ()
1096
+ sz = self ._decode_remaining_length ()
1086
1097
# topic length MSB & LSB
1087
1098
topic_len_buf = self ._sock_exact_recv (2 )
1088
1099
topic_len = int ((topic_len_buf [0 ] << 8 ) | topic_len_buf [1 ])
@@ -1115,11 +1126,13 @@ def _wait_for_msg(self) -> Optional[int]:
1115
1126
1116
1127
return pkt_type
1117
1128
1118
- def _recv_len (self ) -> int :
1119
- """Unpack MQTT message length. """
1129
+ def _decode_remaining_length (self ) -> int :
1130
+ """Decode Remaining Length [2.2.3] """
1120
1131
n = 0
1121
1132
sh = 0
1122
1133
while True :
1134
+ if sh > 28 :
1135
+ raise MMQTTException ("invalid remaining length encoding" )
1123
1136
b = self ._sock_exact_recv (1 )[0 ]
1124
1137
n |= (b & 0x7F ) << sh
1125
1138
if not b & 0x80 :
0 commit comments