Fundamentos de MQTT

Estrutura de Pacotes MQTT: Fixed Header, Variable Header e Payload

Um passo a passo em nível de byte dos pacotes MQTT: como o fixed header codifica tipo e flags, como funciona o remaining length e o que contêm o variable header e o payload, usando um CONNECT e um PUBLISH que você pode capturar com o Wireshark.

Hex dump de um PUBLISH MQTT dividido em fixed header, variable header e payload para explicar a estrutura de pacotes MQTT
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

A estrutura de pacotes MQTT é propositalmente compacta: todo pacote começa com um fixed header de apenas dois bytes, seguido de um variable header opcional e de um payload opcional. Este passo a passo decodifica pacotes CONNECT, CONNACK e PUBLISH reais byte a byte, para que você consiga ler uma captura do Wireshark ou um hex dump com segurança e identificar bugs de codificação nos seus próprios clientes.

As Três Partes de Todo Pacote MQTT

Todo pacote de controle MQTT segue o mesmo layout:

Parte Presente em Conteúdo
Fixed header Todos os pacotes Tipo do pacote, flags, remaining length
Variable header A maioria dos pacotes Campos específicos do pacote, como nome do topic, packet identifier, nome do protocolo e, no MQTT 5, properties
Payload Alguns pacotes Client ID e credenciais no CONNECT, mensagem da aplicação no PUBLISH, filtros de topic no SUBSCRIBE

Os menores pacotes, PINGREQ, PINGRESP e um DISCONNECT do MQTT 3.1.1, são apenas o fixed header de dois bytes. Essa economia é uma grande parte do motivo pelo qual o MQTT funciona bem em links lentos, e é por isso que entender a estrutura ajuda quando você estima o consumo de banda MQTT.

O Fixed Header

O primeiro byte se divide em duas metades:

Bit:     7  6  5  4 | 3  2  1  0
         packet type | flags

Os quatro bits altos guardam o tipo do pacote, de 1 (CONNECT) a 15 (AUTH). Os quatro bits baixos são flags. Na maioria dos pacotes as flags são valores fixos e reservados; só o PUBLISH as usa de forma significativa:

Bit Significado no PUBLISH
3 DUP: é uma reentrega de uma mensagem QoS 1 ou 2
2-1 Nível de QoS: 00, 01 ou 10
0 RETAIN: o broker deve armazenar esta mensagem

Assim, um primeiro byte 0x30 é um PUBLISH QoS 0, 0x32 é QoS 1, 0x31 é uma mensagem QoS 0 retida e 0x3A é uma reentrega QoS 1 com DUP ativado. SUBSCRIBE, UNSUBSCRIBE e PUBREL precisam ter as flags 0010, e é por isso que aparecem como 0x82, 0xA2 e 0x62. A referência de pacotes de controle MQTT lista cada tipo com seu primeiro byte.

Codificação do Remaining Length

Depois do primeiro byte vem o remaining length: o número de bytes do variable header mais o payload, sem contar o próprio fixed header. Ele usa um variable byte integer, de um a quatro bytes. Cada byte carrega sete bits de valor, e o bit mais alto indica "vem outro byte em seguida".

Faixa de valores Bytes usados
0 a 127 1
128 a 16.383 2
16.384 a 2.097.151 3
2.097.152 a 268.435.455 4

Exemplo: um remaining length de 321. Como 321 = 2 x 128 + 65, o primeiro byte é 65 com o bit de continuação ativado (0xC1) e o segundo byte é 2 (0x02). O máximo de quatro bytes é a origem do tão citado teto de 256 MB por pacote do protocolo; o guia sobre o limite de tamanho de mensagem MQTT explica por que os brokers reais definem limites bem menores.

Aqui está um decodificador que você pode usar ao ler hex dumps:

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  # valor, número de bytes usados
        multiplier *= 128
    raise ValueError("Malformed remaining length")

print(decode_remaining_length(bytes([0x30, 0xC1, 0x02])))  # (321, 2)

Analisando uma Sessão Real

Execute este comando para publicar uma mensagem no servidor MQTT público (gratuito) com MQTT 3.1.1, que o mosquitto_pub usa por padrão:

mosquitto_pub -h broker.connectmq.com -p 1883 -i demo-a1b2 \
  -t "connectmq/demo/a1b2/t" -m "21.5" -d

A porta 1883 não é criptografada, então uma captura de pacotes mostra todos os bytes. No Wireshark, capture na sua interface de rede e aplique o filtro de exibição mqtt. O Wireshark decodifica a porta 1883 como MQTT automaticamente; você verá CONNECT, CONNACK, PUBLISH e DISCONNECT. Use o seu próprio segmento de topic aleatório e lembre-se de que tudo no broker público pode ser lido por outras pessoas.

CONNECT

O primeiro pacote do cliente tem 23 bytes:

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"

O byte de connect flags merece uma análise mais atenta. O bit 1 é Clean Session, o bit 2 é Will Flag, os bits 3-4 são Will QoS, o bit 5 é Will Retain, o bit 6 é Password Flag e o bit 7 é User Name Flag. Um cliente que se conecta com usuário, senha e clean session enviaria 0xC2. Se essas flags estiverem ativadas, o usuário e a senha vêm depois do client ID no payload, cada um com um prefixo de tamanho de dois bytes. Esse é um dos motivos para sempre usar TLS na porta 8883 com credenciais reais: na porta 1883 elas trafegam em texto puro.

CONNACK

O broker responde com quatro bytes:

20 02 00 00

Tipo 2, remaining length 2, Session Present 0, código de retorno 0 (aceito). Qualquer outro byte final é uma recusa; o guia de códigos de retorno do CONNACK MQTT decodifica cada valor.

PUBLISH

A mensagem em si tem 29 bytes:

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"

Repare no que está faltando: não há packet identifier, porque mensagens QoS 0 nunca são confirmadas. Com -q 1, o primeiro byte passa a ser 0x32, dois bytes de packet identifier aparecem depois do topic e o broker responde com um PUBACK de quatro bytes (40 02 mais o identificador). Também não existe campo de tamanho do payload; o payload é simplesmente o que sobra depois do variable header, de acordo com o remaining length.

DISCONNECT

Por fim, o cliente envia E0 00: tipo 14, remaining length 0. No MQTT 3.1.1 esse é o pacote inteiro.

O Que Muda no MQTT 5

O MQTT 5 mantém o mesmo esqueleto, mas adiciona properties ao variable header da maioria dos pacotes. Uma seção de properties começa com o seu próprio tamanho em variable byte integer, seguido de pares identificador-valor. Mesmo sem properties, o byte de tamanho 00 está presente.

Para a mesma sessão sobre MQTT 5:

  • O CONNECT tem protocol level 05 e um tamanho de properties depois do keep alive.
  • O CONNACK cresce para incluir um reason code e properties, muitas vezes anunciando limites como Maximum Packet Size, Topic Alias Maximum ou Maximum QoS.
  • O PUBLISH tem um tamanho de properties depois do topic (e do packet identifier), então um publish QoS 0 sem properties fica um byte maior.
  • O DISCONNECT pode carregar um reason code e properties, que é como o broker explica por que fechou uma conexão.

Properties como Topic Alias, Message Expiry Interval e User Properties são o que viabilizam a maioria dos recursos do MQTT 5.

Bugs de Codificação Comuns e Como Identificá-los

Se você escreve um cliente próprio ou faz parsing de MQTT em um gateway, verifique estes pontos primeiro:

  1. Remaining length escrito como um campo fixo de dois bytes. Funciona para pacotes pequenos e quebra acima de 127 bytes.
  2. Tamanho de strings contado em caracteres em vez de bytes. Nomes de topic com caracteres não ASCII têm mais bytes do que caracteres, o que gera pacotes malformados.
  3. Packet identifier ausente em QoS 1 ou 2. O broker lê parte do seu payload como identificador e o topic fica correto, então o bug parece dado corrompido.
  4. Flags reservadas erradas em SUBSCRIBE ou PUBREL, que os brokers precisam tratar como malformadas, fechando a conexão.
  5. Esquecer o tamanho de properties vazio ao migrar um cliente para o MQTT 5.

O Wireshark marca pacotes malformados com clareza, e um broker MQTT 5 responde com o reason code 0x81 Malformed Packet no DISCONNECT, o que é muito mais rápido de diagnosticar do que um fechamento silencioso. Para as definições oficiais, consulte a especificação MQTT da OASIS.

Inspecione Pacotes em um Broker ConnectMQ Real

Ler pacotes é a forma mais rápida de entender o MQTT, e o tutorial de MQTT parte dessa base para abordar sessões, QoS e retained messages. A ConnectMQ mantém o servidor MQTT público em broker.connectmq.com justamente para esse tipo de experimento, que não deve envolver dados de produção, e brokers privados na porta TLS 8883 quando dados reais entram em cena.

Das Capturas de Pacotes para a Produção com a ConnectMQ

Quando o seu cliente estiver pronto, um broker MQTT gerenciado oferece endpoints criptografados, credenciais emitidas e um portal com cliente no navegador e explorador de topics ao vivo. Crie um broker Serverless gratuito em app.connectmq.com, sem cartão de crédito, ou saiba mais sobre como escolher um broker MQTT na nuvem.

Perguntas frequentes

Qual é o tamanho do menor pacote MQTT?

Dois bytes. PINGREQ, PINGRESP e um DISCONNECT do MQTT 3.1.1 consistem apenas no fixed header: um byte para tipo e flags e um byte de remaining length igual a zero.

O que o remaining length inclui?

Ele conta os bytes do variable header e do payload, mas não o próprio fixed header. É codificado como um variable byte integer de um a quatro bytes.

Por que consigo ler minha senha no Wireshark?

Porque a porta 1883 não é criptografada, e o CONNECT carrega o usuário e a senha no payload. Use TLS na porta 8883 ou WebSockets seguros na 8084 para qualquer credencial real.

O pacote PUBLISH inclui o tamanho do payload?

Não. O tamanho do payload é implícito: é o remaining length menos o tamanho do variable header, que inclui o nome do topic, o packet identifier em QoS 1 e 2 e as properties no MQTT 5.

Artigos relacionados

Por que Seu Cliente MQTT Fica Desconectando (e Como Corrigir)

Loops de reconexão estão entre os problemas mais comuns do MQTT. Conheça as cinco causas habituais, de client IDs duplicados a timeouts de keep alive e pacotes grandes demais, e siga um fluxo de diagnóstico que identifica a sua.

9 min de leitura

Quem Inventou o MQTT? A História dos Oleodutos à IoT

O MQTT foi inventado em 1999 por Andy Stanford-Clark, da IBM, e Arlen Nipper, da Arcom, para monitorar oleodutos via satélite. Acompanhe sua trajetória até o open source, a OASIS, a ISO e os brokers MQTT gerenciados na nuvem de hoje.

8 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.