Fondamentaux MQTT

Structure d'un paquet MQTT : en-tête fixe, en-tête variable et payload

Une visite au niveau de l'octet des paquets MQTT : comment l'en-tête fixe encode le type et les flags, comment fonctionne la remaining length, et ce que contiennent l'en-tête variable et le payload, à partir d'un CONNECT et d'un PUBLISH que vous pouvez capturer avec Wireshark.

Dump hexadécimal d'un PUBLISH MQTT découpé en en-tête fixe, en-tête variable et payload pour expliquer la structure d'un paquet MQTT
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

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 05 et 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 :

  1. Remaining length écrite comme un champ fixe de deux octets. Cela fonctionne pour les petits paquets mais casse au-delà de 127 octets.
  2. 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.
  3. 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.
  4. Flags réservés incorrects sur SUBSCRIBE ou PUBREL, que les brokers doivent traiter comme mal formés en fermant la connexion.
  5. 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.

Articles liés

Qui a inventé MQTT ? L'histoire, des oléoducs à l'IoT

MQTT a été inventé en 1999 par Andy Stanford-Clark (IBM) et Arlen Nipper (Arcom) pour surveiller des oléoducs par satellite. Suivez son parcours vers l'open source, OASIS, l'ISO et les brokers MQTT managés d'aujourd'hui.

8 min de lecture

Pourquoi votre client MQTT se déconnecte sans cesse (et comment y remédier)

Les boucles de reconnexion sont l'un des problèmes MQTT les plus courants. Découvrez les cinq causes habituelles, des client ID en double aux timeouts de keep alive et aux paquets trop volumineux, et suivez une méthode de diagnostic qui identifie la vôtre.

9 min de lecture

Taille maximale d'un message MQTT : limites et stratégies pour les gros payloads

MQTT peut techniquement transporter un paquet de 256 Mo, mais les brokers, les bibliothèques clientes et les appareils imposent des limites bien plus basses. Découvrez où se trouvent les vraies limites, ce qui se passe quand on les dépasse et comment envoyer de gros payloads.

8 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.