La structure d'un paquet MQTT est volontairement compacte : chaque paquet commence par un en-tête fixe pouvant ne faire que deux octets, suivi d'un en-tête variable facultatif et d'un payload facultatif. Ce guide décode octet par octet de vrais paquets CONNECT, CONNACK et PUBLISH, pour que vous puissiez lire en confiance une capture Wireshark ou un dump hexadécimal et repérer les bugs d'encodage dans vos propres clients.
Les trois parties de chaque paquet MQTT
Chaque paquet de contrôle MQTT suit la même disposition :
| Partie | Présente dans | Contenu |
|---|---|---|
| En-tête fixe | Tous les paquets | Type de paquet, flags, remaining length |
| En-tête variable | La plupart des paquets | Champs propres au paquet comme le nom du topic, le packet identifier, le nom du protocole et, en MQTT 5, les propriétés |
| Payload | Certains paquets | Client ID et identifiants dans CONNECT, message applicatif dans PUBLISH, filtres de topics dans SUBSCRIBE |
Les plus petits paquets, PINGREQ, PINGRESP et le DISCONNECT de MQTT 3.1.1, se résument à l'en-tête fixe de deux octets. Cette sobriété explique en grande partie pourquoi MQTT fonctionne bien sur des liens lents, et c'est pourquoi comprendre la structure aide à estimer la consommation de bande passante MQTT.
L'en-tête fixe
Le premier octet se divise en deux moitiés :
Bit: 7 6 5 4 | 3 2 1 0
packet type | flags
Les quatre bits de poids fort contiennent le type de paquet, de 1 (CONNECT) à 15 (AUTH). Les quatre bits de poids faible sont des flags. Pour la plupart des paquets, ces flags ont des valeurs fixes et réservées ; seul PUBLISH les utilise réellement :
| Bit | Signification dans PUBLISH |
|---|---|
| 3 | DUP : il s'agit d'une nouvelle livraison d'un message QoS 1 ou 2 |
| 2-1 | Niveau de QoS : 00, 01 ou 10 |
| 0 | RETAIN : le broker doit stocker ce message |
Ainsi, un premier octet 0x30 correspond à un PUBLISH QoS 0, 0x32 à un QoS 1, 0x31 à un message QoS 0 retenu et 0x3A à une nouvelle livraison QoS 1 avec DUP activé. SUBSCRIBE, UNSUBSCRIBE et PUBREL doivent avoir les flags 0010, c'est pourquoi ils apparaissent sous la forme 0x82, 0xA2 et 0x62. La référence des paquets de contrôle MQTT liste chaque type avec son premier octet.
Encodage de la remaining length
Après le premier octet vient la remaining length : le nombre d'octets de l'en-tête variable plus le payload, sans compter l'en-tête fixe lui-même. Elle utilise un variable byte integer, de un à quatre octets. Chaque octet porte sept bits de valeur, et le bit de poids fort indique qu'un autre octet suit.
| Plage de valeurs | Octets utilisés |
|---|---|
| 0 à 127 | 1 |
| 128 à 16 383 | 2 |
| 16 384 à 2 097 151 | 3 |
| 2 097 152 à 268 435 455 | 4 |
Exemple : une remaining length de 321. Comme 321 = 2 x 128 + 65, le premier octet vaut 65 avec le bit de continuation activé (0xC1) et le second vaut 2 (0x02). Le maximum de quatre octets est à l'origine du plafond de 256 Mo par paquet souvent cité pour le protocole ; le guide de la taille maximale des messages MQTT explique pourquoi les brokers réels fixent des limites bien plus basses.
Voici un décodeur à utiliser pour lire des dumps hexadécimaux :
def decode_remaining_length(data: bytes, offset: int = 1):
multiplier, value = 1, 0
for i in range(4):
byte = data[offset + i]
value += (byte & 0x7F) * multiplier
if byte & 0x80 == 0:
return value, i + 1 # valeur, nombre d'octets utilisés
multiplier *= 128
raise ValueError("Malformed remaining length")
print(decode_remaining_length(bytes([0x30, 0xC1, 0x02]))) # (321, 2)
Décortiquer une vraie session
Lancez cette commande pour publier un message sur le serveur MQTT public gratuit en MQTT 3.1.1, la version utilisée par défaut par mosquitto_pub :
mosquitto_pub -h broker.connectmq.com -p 1883 -i demo-a1b2 \
-t "connectmq/demo/a1b2/t" -m "21.5" -d
Le port 1883 n'est pas chiffré : une capture de paquets montre donc chaque octet. Dans Wireshark, capturez sur votre interface réseau et appliquez le filtre d'affichage mqtt. Wireshark décode automatiquement le port 1883 comme du MQTT ; vous verrez CONNECT, CONNACK, PUBLISH et DISCONNECT. Utilisez votre propre segment de topic aléatoire, et n'oubliez pas que tout ce qui passe par le serveur public est lisible par d'autres.
CONNECT
Le premier paquet du client fait 23 octets :
10 15 Fixed header: type 1 (CONNECT), remaining length 21
00 04 4D 51 54 54 Protocol name: length 4, "MQTT"
04 Protocol level: 4 = MQTT 3.1.1
02 Connect flags: Clean Session
00 3C Keep alive: 60 seconds
00 09 64 65 6D 6F 2D 61 31 62 32 Payload: client ID length 9, "demo-a1b2"
L'octet des connect flags mérite qu'on s'y attarde. Le bit 1 correspond à Clean Session, le bit 2 à Will Flag, les bits 3-4 à Will QoS, le bit 5 à Will Retain, le bit 6 à Password Flag et le bit 7 à User Name Flag. Un client qui se connecte avec nom d'utilisateur, mot de passe et clean session enverrait 0xC2. Si ces flags sont activés, le nom d'utilisateur et le mot de passe suivent le client ID dans le payload, chacun précédé d'une longueur sur deux octets. C'est une raison de toujours utiliser TLS sur le port 8883 avec de vrais identifiants : sur le port 1883, ils circulent en clair.
CONNACK
Le broker répond avec quatre octets :
20 02 00 00
Type 2, remaining length 2, Session Present 0, code de retour 0 (accepté). Tout autre dernier octet est un refus ; le guide des codes de retour CONNACK MQTT décode chaque valeur.
PUBLISH
Le message lui-même fait 29 octets :
30 1B Fixed header: PUBLISH, QoS 0, no retain; remaining length 27
00 15 Topic length: 21
63 6F 6E 6E 65 63 74 6D 71 2F 64 65 6D 6F 2F 61 31 62 32 2F 74
Topic: "connectmq/demo/a1b2/t"
32 31 2E 35 Payload: "21.5"
Remarquez ce qui manque : il n'y a pas de packet identifier, car les messages QoS 0 ne sont jamais acquittés. Avec -q 1, le premier octet devient 0x32, deux octets de packet identifier apparaissent après le topic et le broker répond par un PUBACK de quatre octets (40 02 plus l'identifiant). Il n'y a pas non plus de champ de longueur du payload : le payload est simplement ce qui reste après l'en-tête variable, d'après la remaining length.
DISCONNECT
Enfin, le client envoie E0 00 : type 14, remaining length 0. En MQTT 3.1.1, c'est le paquet entier.
Ce qui change en MQTT 5
MQTT 5 conserve le même squelette mais ajoute des propriétés à l'en-tête variable de la plupart des paquets. Une section de propriétés commence par sa propre longueur en variable byte integer, suivie de paires identifiant-valeur. Même sans propriétés, l'octet de longueur 00 est présent.
Pour la même session en MQTT 5 :
- CONNECT a le niveau de protocole
05et une longueur de propriétés après le keep alive. - CONNACK s'enrichit d'un reason code et de propriétés, annonçant souvent des limites comme Maximum Packet Size, Topic Alias Maximum ou Maximum QoS.
- PUBLISH a une longueur de propriétés après le topic (et le packet identifier) : une publication QoS 0 sans propriétés est donc plus grande d'un octet.
- DISCONNECT peut porter un reason code et des propriétés, ce qui permet au broker d'expliquer pourquoi il a fermé une connexion.
Des propriétés comme Topic Alias, Message Expiry Interval et User Properties sont à la base de la plupart des fonctionnalités de MQTT 5.
Bugs d'encodage courants et comment les repérer
Si vous écrivez un client personnalisé ou analysez du MQTT sur une passerelle, vérifiez d'abord ces points :
- Remaining length écrite comme un champ fixe de deux octets. Cela fonctionne pour les petits paquets mais casse au-delà de 127 octets.
- Longueurs de chaînes comptées en caractères plutôt qu'en octets. Les noms de topics non ASCII comptent plus d'octets que de caractères, ce qui produit des paquets mal formés.
- Packet identifier manquant en QoS 1 ou 2. Le broker lit une partie de votre payload comme identifiant et le topic est correct : le bug ressemble donc à des données corrompues.
- Flags réservés incorrects sur SUBSCRIBE ou PUBREL, que les brokers doivent traiter comme mal formés en fermant la connexion.
- Oubli de la longueur de propriétés vide lors du passage d'un client à MQTT 5.
Wireshark signale clairement les paquets mal formés, et un broker MQTT 5 répond avec le reason code 0x81 Malformed Packet dans un DISCONNECT, ce qui est bien plus rapide à diagnostiquer qu'une fermeture silencieuse. Pour les définitions qui font foi, consultez la spécification OASIS MQTT.
Inspecter les paquets face à un vrai broker ConnectMQ
Lire des paquets est le moyen le plus rapide de comprendre MQTT, et le tutoriel MQTT s'appuie sur ces bases avec les sessions, la QoS et les messages retenus. ConnectMQ exploite le serveur MQTT public gratuit broker.connectmq.com précisément pour ce type d'expérimentation, et des brokers privés sur le port TLS 8883 dès que de vraies données entrent en jeu.
Passer des captures de paquets à la production avec ConnectMQ
Quand votre client est prêt, un broker MQTT managé vous fournit des endpoints chiffrés, des identifiants émis et un portail avec un client intégré au navigateur et un explorateur de topics en direct. Créez un broker Serverless gratuit sur app.connectmq.com, sans carte bancaire, ou apprenez-en davantage sur le choix d'un broker MQTT cloud.
Questions fréquentes
Quelle est la taille du plus petit paquet MQTT ?
Deux octets. PINGREQ, PINGRESP et le DISCONNECT de MQTT 3.1.1 ne sont constitués que de l'en-tête fixe : un octet pour le type et les flags, et un octet de remaining length à zéro.
Que comprend la remaining length ?
Elle compte les octets de l'en-tête variable et du payload, mais pas l'en-tête fixe lui-même. Elle est encodée sous forme de variable byte integer de un à quatre octets.
Pourquoi puis-je lire mon mot de passe dans Wireshark ?
Parce que le port 1883 n'est pas chiffré et que le CONNECT transporte le nom d'utilisateur et le mot de passe dans son payload. Utilisez TLS sur le port 8883 ou les WebSockets sécurisés sur le port 8084 pour tout identifiant réel.
Le paquet PUBLISH inclut-il la longueur du payload ?
Non. La longueur du payload est implicite : c'est la remaining length moins la taille de l'en-tête variable, qui comprend le nom du topic, le packet identifier en QoS 1 et 2, et les propriétés en MQTT 5.



