@@ -175,6 +175,35 @@ def __enter__(self):
175
175
def __exit__ (self , exception_type , exception_value , traceback ):
176
176
self .deinit ()
177
177
178
+ def _sock_exact_recv (self , bufsize ):
179
+ """Reads _exact_ number of bytes from the connected socket. Will only return
180
+ string with the exact number of bytes requested.
181
+
182
+ The semantics of native socket receive is that it returns no more than the
183
+ specified number of bytes (i.e. max size). However, it makes no guarantees in
184
+ terms of the minimum size of the buffer, which could be 1 byte. This is a
185
+ wrapper for socket recv() to ensure that no less than the expected number of
186
+ bytes is returned or trigger a timeout exception.
187
+
188
+ :param int bufsize: number of bytes to receive
189
+ """
190
+ stamp = time .monotonic ()
191
+ rc = self ._sock .recv (bufsize )
192
+ to_read = bufsize - len (rc )
193
+ assert to_read >= 0
194
+ read_timeout = self .keep_alive
195
+ while to_read > 0 :
196
+ recv = self ._sock .recv (to_read )
197
+ to_read -= len (recv )
198
+ rc += recv
199
+ if time .monotonic () - stamp > read_timeout :
200
+ raise MMQTTException (
201
+ "Unable to receive {} bytes within {} seconds." .format (
202
+ to_read , read_timeout
203
+ )
204
+ )
205
+ return rc
206
+
178
207
def deinit (self ):
179
208
"""De-initializes the MQTT client and disconnects from the mqtt broker."""
180
209
self .disconnect ()
@@ -351,7 +380,7 @@ def connect(self, clean_session=True):
351
380
while True :
352
381
op = self ._wait_for_msg ()
353
382
if op == 32 :
354
- rc = self ._sock . recv (3 )
383
+ rc = self ._sock_exact_recv (3 )
355
384
assert rc [0 ] == 0x02
356
385
if rc [2 ] != 0x00 :
357
386
raise MMQTTException (CONNACK_ERRORS [rc [2 ]])
@@ -366,32 +395,38 @@ def disconnect(self):
366
395
self .is_connected ()
367
396
if self .logger is not None :
368
397
self .logger .debug ("Sending DISCONNECT packet to broker" )
369
- self ._sock .send (MQTT_DISCONNECT )
398
+ try :
399
+ self ._sock .send (MQTT_DISCONNECT )
400
+ except RuntimeError as e :
401
+ if self .logger :
402
+ self .logger .warning ("Unable to send DISCONNECT packet: {}" .format (e ))
370
403
if self .logger is not None :
371
404
self .logger .debug ("Closing socket" )
372
405
self ._sock .close ()
373
406
self ._is_connected = False
374
- self ._subscribed_topics = None
407
+ self ._subscribed_topics = []
375
408
if self .on_disconnect is not None :
376
409
self .on_disconnect (self , self .user_data , 0 )
377
410
378
411
def ping (self ):
379
412
"""Pings the MQTT Broker to confirm if the broker is alive or if
380
413
there is an active network connection.
414
+ Returns response codes of any messages received while waiting for PINGRESP.
381
415
"""
382
416
self .is_connected ()
383
- if self .logger is not None :
417
+ if self .logger :
384
418
self .logger .debug ("Sending PINGREQ" )
385
419
self ._sock .send (MQTT_PINGREQ )
386
- if self .logger is not None :
387
- self .logger .debug ("Checking PINGRESP" )
388
- while True :
389
- op = self ._wait_for_msg (0.5 )
390
- if op == 208 :
391
- ping_resp = self ._sock .recv (2 )
392
- if ping_resp [0 ] != 0x00 :
393
- raise MMQTTException ("PINGRESP not returned from broker." )
394
- return
420
+ ping_timeout = self .keep_alive
421
+ stamp = time .monotonic ()
422
+ rc , rcs = None , []
423
+ while rc != MQTT_PINGRESP :
424
+ rc = self ._wait_for_msg ()
425
+ if rc :
426
+ rcs .append (rc )
427
+ if time .monotonic () - stamp > ping_timeout :
428
+ raise MMQTTException ("PINGRESP not returned from broker." )
429
+ return rcs
395
430
396
431
# pylint: disable=too-many-branches, too-many-statements
397
432
def publish (self , topic , msg , retain = False , qos = 0 ):
@@ -486,9 +521,9 @@ def publish(self, topic, msg, retain=False, qos=0):
486
521
while True :
487
522
op = self ._wait_for_msg ()
488
523
if op == 0x40 :
489
- sz = self ._sock . recv (1 )
524
+ sz = self ._sock_exact_recv (1 )
490
525
assert sz == b"\x02 "
491
- rcv_pid = self ._sock . recv (2 )
526
+ rcv_pid = self ._sock_exact_recv (2 )
492
527
rcv_pid = rcv_pid [0 ] << 0x08 | rcv_pid [1 ]
493
528
if pid == rcv_pid :
494
529
if self .on_publish is not None :
@@ -571,7 +606,7 @@ def subscribe(self, topic, qos=0):
571
606
while True :
572
607
op = self ._wait_for_msg ()
573
608
if op == 0x90 :
574
- rc = self ._sock . recv (4 )
609
+ rc = self ._sock_exact_recv (4 )
575
610
assert rc [1 ] == packet [2 ] and rc [2 ] == packet [3 ]
576
611
if rc [3 ] == 0x80 :
577
612
raise MMQTTException ("SUBACK Failure!" )
@@ -634,7 +669,7 @@ def unsubscribe(self, topic):
634
669
while True :
635
670
op = self ._wait_for_msg ()
636
671
if op == 176 :
637
- return_code = self ._sock . recv (3 )
672
+ return_code = self ._sock_exact_recv (3 )
638
673
assert return_code [0 ] == 0x02
639
674
# [MQTT-3.32]
640
675
assert (
@@ -671,6 +706,7 @@ def reconnect(self, resub_topics=True):
671
706
def loop (self ):
672
707
"""Non-blocking message loop. Use this method to
673
708
check incoming subscription messages.
709
+ Returns response codes of any messages received.
674
710
"""
675
711
if self ._timestamp == 0 :
676
712
self ._timestamp = time .monotonic ()
@@ -682,10 +718,12 @@ def loop(self):
682
718
"KeepAlive period elapsed - \
683
719
requesting a PINGRESP from the server..."
684
720
)
685
- self .ping ()
721
+ rcs = self .ping ()
686
722
self ._timestamp = 0
723
+ return rcs
687
724
self ._sock .settimeout (0.1 )
688
- return self ._wait_for_msg ()
725
+ rc = self ._wait_for_msg ()
726
+ return [rc ] if rc else None
689
727
690
728
def _wait_for_msg (self , timeout = 30 ):
691
729
"""Reads and processes network events.
@@ -694,24 +732,30 @@ def _wait_for_msg(self, timeout=30):
694
732
res = self ._sock .recv (1 )
695
733
self ._sock .settimeout (timeout )
696
734
if res in [None , b"" ]:
735
+ # If we get here, it means that there is nothing to be received
697
736
return None
698
- if res == MQTT_PINGRESP :
699
- sz = self ._sock .recv (1 )[0 ]
700
- assert sz == 0
701
- return None
737
+ if res [0 ] == MQTT_PINGRESP :
738
+ if self .logger :
739
+ self .logger .debug ("Checking PINGRESP" )
740
+ sz = self ._sock_exact_recv (1 )[0 ]
741
+ if sz != 0x00 :
742
+ raise MMQTTException (
743
+ "Unexpected PINGRESP returned from broker: {}." .format (sz )
744
+ )
745
+ return MQTT_PINGRESP
702
746
if res [0 ] & 0xF0 != 0x30 :
703
747
return res [0 ]
704
748
sz = self ._recv_len ()
705
- topic_len = self ._sock . recv (2 )
749
+ topic_len = self ._sock_exact_recv (2 )
706
750
topic_len = (topic_len [0 ] << 8 ) | topic_len [1 ]
707
- topic = self ._sock . recv (topic_len )
751
+ topic = self ._sock_exact_recv (topic_len )
708
752
topic = str (topic , "utf-8" )
709
753
sz -= topic_len + 2
710
754
if res [0 ] & 0x06 :
711
- pid = self ._sock . recv (2 )
755
+ pid = self ._sock_exact_recv (2 )
712
756
pid = pid [0 ] << 0x08 | pid [1 ]
713
757
sz -= 0x02
714
- msg = self ._sock . recv (sz )
758
+ msg = self ._sock_exact_recv (sz )
715
759
self ._handle_on_message (self , topic , str (msg , "utf-8" ))
716
760
if res [0 ] & 0x06 == 0x02 :
717
761
pkt = bytearray (b"\x40 \x02 \0 \0 " )
@@ -725,7 +769,7 @@ def _recv_len(self):
725
769
n = 0
726
770
sh = 0
727
771
while True :
728
- b = self ._sock . recv (1 )[0 ]
772
+ b = self ._sock_exact_recv (1 )[0 ]
729
773
n |= (b & 0x7F ) << sh
730
774
if not b & 0x80 :
731
775
return n
0 commit comments