-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
encode/decode remaining length properly for {,UN}SUBSCRIBE/SUBACK #187
Conversation
I attempted to test this version on a FunHouse 9.0.0-alpha.5 using the native networking simpletest from this repo. It seems like it is having trouble subscribing to the onoff feed. It keeps printing this over and over: I'm attaching two log files from the more verbose logging output. One with the version from this PR and one with current main. The particulars of the MQTT spec and connection are beyond my knowledge so I'm not sure what could cause this. Let me know if there is any additional testing or info I could provide to try to help figure out what could cause this issue. test script:
|
it seems the previous code does not properly work with Adafruit logging. This should fix it.
a04fe49
to
973df68
Compare
Thanks for the testing. I found the extra zero byte in the CONNECT packet strange and did not investigate. As a result I missed the logic for short remaining length. This should work now. |
As I've started testing on QtPy with code that basically calls
and upon realizing this is valid behavior according to the spec, added a fix. |
Further testing revealed that UNSUBSCRIBE needs to be fixed (sic!) as well. Here's the test code used on QtPy running CircuitPython 8.2.6 on 2023-09-12: #!/usr/bin/env python3
import adafruit_logging as logging
import random
import socketpool
import ssl
import sys
import time
import wifi
from secrets import secrets
import adafruit_minimqtt.adafruit_minimqtt as MQTT
def main():
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.info("Connecting to wifi")
wifi.radio.connect(secrets["SSID"], secrets["password"], timeout=10)
logger.info(f"Connected to {secrets['SSID']}")
logger.debug(f"IP: {wifi.radio.ipv4_address}")
pool = socketpool.SocketPool(wifi.radio)
host = "172.40.0.3"
port = 1883
mqtt_client = MQTT.MQTT(
broker=host,
port=port,
socket_pool=pool,
ssl_context=ssl.create_default_context(),
connect_retries=1,
recv_timeout=5,
)
mqtt_client.logger = logger
logger.debug(f"connecting")
mqtt_client.connect()
topic = "devices"
# topic length should not exceed the maximum given by spec (65535 bytes).
for i in range(128):
logger.info(f"### iteration {i}")
topic += "/" + ''.join(random.choice('abcdefghijklmnopqrstuvwxyz')
for _ in range(random.randrange(3, 16)))
mqtt_client.subscribe(topic)
mqtt_client.unsubscribe(topic)
logger.debug("loop")
mqtt_client.loop(1)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
sys.exit(0)
It went through all of the iterations, although at times it seemed that the Mu editor will choke over the long input emitted to the serial console however it always recovered. |
Added protocol level tests for the SUBSCRIBE packet. Firstly, I captured short and long (with remaining length encoded as 1 byte and 2 bytes, respectively) SUBSCRIBE/SUBACK packets using Mosquitto client and server, then added an assert that verifies that the SUBSCRIBE packet sent by MiniMQTT is the same as the one sent by Mosquitto client and that MiniMQTT processes the reply as sent from the Mosquitto server successfully. There is of course potential for more testing, like generating SUBSCRIBE packets with remaining length encoded as 3 and 4 bytes (maximum) while perhaps at the same time using the topics specified with tuple and list. Similarly for UNSUBSCRIBE. |
Also, the change that covers the PUBLISH-before-SUBACK case can be tested this way. Let me know. |
Never mind. I implemented some of the tests described above. |
After adding the test case for long lists of topics in SUBSCRIBE, I noticed that the remaining length parsing for SUBACK also assumes 2 bytes so added a fix. |
I believe all these remaining length encoding/decoding implementation problems stem from the insufficiently worded spec. I wrote an e-mail to the IBM guys who were the editors of the 3.1.1 spec to fix it, however one of them does not seem to be working for IBM any longer as the e-mail bounced and the other one did not reply. |
The changes are done unless someone raises review concern. Next, I'd like to retest the changes on my trusty QtPy, using both local MQTT broker as well as the public Mosquitto test bed which experiences pretty wild traffic. |
Completed all the 128 iterations of the above subscribe/unsubscribe test with randomized topics on the QtPy just fine. |
Used this code on the QtPy to follow the wild traffic on test.mosquitto.org: #!/usr/bin/env python3
import adafruit_logging as logging
import random
import socketpool
import ssl
import sys
import time
import wifi
from secrets import secrets
import adafruit_minimqtt.adafruit_minimqtt as MQTT
def on_message(client, topic, msg):
#logger = logging.getLogger(__name__)
#logger.info(f"Got msg on '{topic}' ({len(msg)} bytes)")
client.user_data[0] += 1
client.user_data[1] += len(msg)
def main():
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.info("Connecting to wifi")
wifi.radio.connect(secrets["SSID"], secrets["password"], timeout=10)
logger.info(f"Connected to {secrets['SSID']}")
logger.debug(f"IP: {wifi.radio.ipv4_address}")
pool = socketpool.SocketPool(wifi.radio)
host = "test.mosquitto.org"
port = 1883
stats = [0, 0]
mqtt_client = MQTT.MQTT(
broker=host,
port=port,
socket_pool=pool,
ssl_context=ssl.create_default_context(),
connect_retries=1,
recv_timeout=5,
use_binary_mode=True, # test.mosquitto.org has messages with UnicodeError
user_data=stats,
)
# mqtt_client.logger = logger
mqtt_client.on_message = on_message
logger.debug(f"connecting")
mqtt_client.connect()
topic = "#"
logger.debug(f"subscribing")
mqtt_client.subscribe(topic)
while True:
mqtt_client.loop(1)
logger.info(f"Messages: {stats[0]}, bytes: {stats[1]}")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
sys.exit(0) It survived several thousands of messages until it got down with the
and this with the MQTT logger left at default, just the callback was used to do the logging:
Also, I got the timeout once:
None is related to the changes, I'd say. |
this uncovered a bug in SUBACK processing
527fa5f
to
279387e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me.
I tested the latest version successfully with the test script posted above using Adafruit.io server on a Fun House 8.2.8
.
Updating https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer to 4.5.0 from 4.4.5: > Merge pull request adafruit/Adafruit_CircuitPython_HTTPServer#74 from michalpokusa/optional-hashlib-for-websockets Updating https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT to 7.5.6 from 7.5.4: > Merge pull request adafruit/Adafruit_CircuitPython_MiniMQTT#187 from vladak/subscribe_vs_remaining_len Updating https://github.com/adafruit/Adafruit_CircuitPython_Bundle/circuitpython_library_list.md to NA from NA: > Updated download stats for the libraries
Hi, i get this error message on unsubscribe: I use the Mosquitto broker in Homeassistant. Is there something that i can do about this error? Bernd |
This is a closed issue so it's better to open a new one, esp. if running the latest gratest MiniMQTT. |
This change fixes remaining length encoding for SUBSCRIBE packets, allowing subscribe to pass for remaning length bigger than 127. Tested on CPython with:
which produces the following output: