Testes de brokers

Como testar os topic aliases do MQTT 5

Como testar topic aliases do MQTT 5: leia o Topic Alias Maximum, valide publicações com alias, meça a economia de banda e reproduza erros Topic Alias invalid.

Atualizado · 8 min de leitura

Como testar os topic aliases do MQTT 5

Os topic aliases do MQTT 5 substituem um nome de topic longo por um número de 2 bytes pelo resto da conexão. O primeiro PUBLISH leva o topic e o alias. Cada PUBLISH seguinte envia um topic vazio mais o alias. Para testar, leia o Topic Alias Maximum que o broker concede no CONNACK, publique com e sem o nome do topic, verifique se os subscribers recebem o topic completo e reproduza os dois casos de erro: um alias fora do limite e um alias que o broker não conhece.

O mecanismo em si está em topic aliases no MQTT. Este artigo é o plano de testes.

Como os aliases são negociados

DireçãoQuem define o limiteOndePadrão
Cliente → brokerBrokerTopic Alias Maximum no CONNACK0 (sem aliases)
Broker → clienteClienteTopic Alias Maximum no CONNECT0 (sem aliases)

Regras que uma implementação correta segue:

  • Valores de alias vão de 1 até o máximo negociado. 0 nunca é válido.
  • Quem envia escolhe o mapeamento. Enviar um topic novo com um número de alias existente remapeia esse alias.
  • Os mapeamentos são por conexão e por direção. Somem ao desconectar, mesmo com sessão persistente.
  • Um PUBLISH com topic vazio precisa levar um alias já mapeado nesta conexão.

Quanto um alias economiza?

Pegue um topic de telemetria típico, factory/line-3/press-07/telemetry/vibration (43 caracteres), um payload de 20 bytes e QoS 1:

CampoTopic completoCom alias
Cabeçalho fixo2 bytes2 bytes
Nome do topic (tamanho de 2 bytes + texto)45 bytes2 bytes (vazio)
Identificador do pacote2 bytes2 bytes
Propriedades (tamanho + Topic Alias)1 byte4 bytes
Payload20 bytes20 bytes
Total70 bytes30 bytes

São 40 bytes economizados por mensagem, mais da metade do pacote. A uma mensagem por segundo, um dispositivo economiza cerca de 3,5 MB por dia. A economia só importa quando os topics são longos em relação aos payloads e quando bytes custam dinheiro ou bateria, como em links celulares, satelitais ou LPWAN. Numa LAN com payloads de 2 KB, é ruído. A calculadora de tamanho de pacote mostra os números para os seus topics, e overhead de banda no MQTT traz outras formas de reduzir o tráfego. O TLS adiciona o próprio overhead por registro, então meça no fio se o orçamento for apertado.

Teste 1: ler o máximo negociado

Antes de tudo, registre o que o broker concede. Brokers variam: alguns permitem muitos aliases, outros só alguns, e outros desativam o recurso (máximo 0). Um cliente que usa aliases sem verificar será desconectado em brokers que não permitem nenhum.

Com a CLI do Mosquitto, -d imprime o CONNACK, mas a saída de propriedades muda entre versões. Um script é mais confiável. O teste abaixo imprime o máximo primeiro e para se ele for 0.

Teste 2: publicações com alias chegam intactas

Este script com paho-mqtt 2.x publica uma mensagem com o topic completo e o alias 1, mais quatro com topic vazio e alias 1, e verifica se um subscriber normal recebe as cinco com o nome completo do topic. Depois reconecta e envia o alias antigo para ver como o broker reage.

alias_test.py
import time, uuid
import paho.mqtt.client as mqtt
from paho.mqtt.properties import Properties
from paho.mqtt.packettypes import PacketTypes

HOST, PORT = "broker.connectmq.com", 1883
RUN = uuid.uuid4().hex[:8]
TOPIC = f"testmqtt/alias/{RUN}/factory/line-3/press-07/telemetry"
received = []

def client(cid, log):
    c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"{cid}-{RUN}", protocol=mqtt.MQTTv5)
    c.on_connect = lambda cl, u, f, rc, props: log.update(max=getattr(props, "TopicAliasMaximum", 0))
    c.on_disconnect = lambda cl, u, f, rc, props: log.update(disconnect=str(rc))
    return c

def alias_props(alias):
    p = Properties(PacketTypes.PUBLISH)
    p.TopicAlias = alias
    return p

# Subscriber: a normal client that does not accept aliases from the broker
sub_log = {}
sub = client("alias-sub", sub_log)
sub.on_message = lambda cl, u, msg: received.append((msg.topic, msg.payload.decode()))
sub.connect(HOST, PORT)
sub.loop_start()
sub.subscribe(TOPIC, qos=1)

# Test 1: negotiation + valid alias use
log = {}
pub = client("alias-pub", log)
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1)
print("broker Topic Alias Maximum:", log.get("max"))
if not log.get("max"):
    raise SystemExit("broker does not accept topic aliases from clients (maximum 0)")

pub.publish(TOPIC, "m0", qos=1, properties=alias_props(1))      # set alias 1 = TOPIC
for i in range(1, 5):
    pub.publish("", f"m{i}", qos=1, properties=alias_props(1))  # topic omitted, alias only
time.sleep(2)
ok = received == [(TOPIC, f"m{i}") for i in range(5)]
print("PASS" if ok else "FAIL", "aliased messages arrive with the full topic:", len(received), "of 5")
pub.disconnect(); pub.loop_stop()

# Test 2: alias mappings do not survive a reconnect
received.clear()
log = {}
pub = client("alias-pub", log)
pub.connect(HOST, PORT)
pub.loop_start()
time.sleep(1)
pub.publish("", "after-reconnect", qos=1, properties=alias_props(1))  # unknown on this connection
time.sleep(2)
print("PASS" if not received else "FAIL", "stale alias after reconnect is not delivered;",
      "broker reaction:", log.get("disconnect", "no DISCONNECT, message dropped"))
pub.disconnect(); pub.loop_stop()

# Test 3: alias above the negotiated maximum (only possible if the maximum is below 65535)
if log.get("max", 0) < 65535:
    log2 = {}
    bad = client("alias-bad", log2)
    bad.connect(HOST, PORT)
    bad.loop_start()
    time.sleep(1)
    bad.publish(TOPIC, "too-high", qos=1, properties=alias_props(log2["max"] + 1))
    time.sleep(2)
    print("broker reaction to alias > maximum:", log2.get("disconnect", "none"))
    bad.loop_stop()

sub.disconnect(); sub.loop_stop()

Uma execução típica imprime:

Saída
broker Topic Alias Maximum: 65535
PASS aliased messages arrive with the full topic: 5 of 5
PASS stale alias after reconnect is not delivered; broker reaction: no DISCONNECT, message dropped

O paho-mqtt não gerencia aliases para você. Ele envia exatamente o alias que você define e não resolve aliases vindos do broker, então nunca anuncie um Topic Alias Maximum num subscriber paho. Isso também faz dele uma boa ferramenta para testes negativos. O paho recusa o alias 0 localmente (TopicAlias property value must be in the range 1-65535), então para testar como o broker trata o alias 0 você precisa de uma ferramenta de pacotes brutos.

Aliases automáticos com MQTT.js

O MQTT.js 5 pode atribuir aliases por você. Com autoAssignTopicAlias, ele mapeia topics para aliases pelo critério de uso menos recente (LRU), dentro do máximo do broker. Registre os pacotes para verificar o que vai pelo fio:

alias.mjs
import mqtt from "mqtt";

const topic = "testmqtt/alias/js-demo/factory/line-3/press-07/telemetry";
const client = mqtt.connect("mqtt://broker.connectmq.com:1883", {
  protocolVersion: 5,
  clientId: `alias-js-${Math.random().toString(16).slice(2, 10)}`,
  autoAssignTopicAlias: true,             // let MQTT.js pick and reuse aliases
  properties: { topicAliasMaximum: 10 },  // accept up to 10 aliases from the broker
});

// Log what actually goes over the wire
client.on("packetsend", (p) => {
  if (p.cmd === "publish") console.log(`sent topic="${p.topic}" alias=${p.properties?.topicAlias}`);
});

client.on("connect", (connack) => {
  console.log("broker Topic Alias Maximum:", connack.properties?.topicAliasMaximum ?? 0);
  let n = 0;
  const timer = setInterval(() => {
    client.publish(topic, JSON.stringify({ n, vibration: Math.random() }), { qos: 1 });
    if (++n === 5) {
      clearInterval(timer);
      setTimeout(() => client.end(), 500);
    }
  }, 300);
});

client.on("error", (err) => console.error("error:", err.message));

Saída esperada: a primeira linha mostra o topic completo com alias=1, e as quatro seguintes mostram topic="" com alias=1. Se todas as linhas mostrarem o topic completo, o broker concedeu máximo 0 e o MQTT.js voltou silenciosamente para publicações normais. Esse é o comportamento correto, mas significa que você não está economizando nada. autoUseTopicAlias é a opção mais conservadora: só reutiliza aliases que você mesmo registrou.

Teste 3: os casos de erro

O que você enviaO que a especificação dizErro que as pessoas procuram
Alias 0Erro de protocoloDISCONNECT 0x94 Topic Alias invalid
Alias acima do máximo negociadoErro de protocoloDISCONNECT 0x94 Topic Alias invalid
Topic vazio, alias nunca mapeadoErro de protocoloDISCONNECT 0x82 Protocol Error
Qualquer alias quando o máximo é 0Erro de protocolo0x94, ou no cliente Sending Topic Alias out of range no MQTT.js
Alias antigo após reconectarIgual a "nunca mapeado"0x82, ou a mensagem some em silêncio

Nem todo broker reage do mesmo jeito. Alguns enviam o DISCONNECT com o reason code, outros fecham a conexão ou descartam aquela mensagem sem avisar. A execução acima mostra um broker que descartou a mensagem com alias antigo sem desconectar. Por isso seu teste deve validar o resultado que importa, a mensagem inválida não foi entregue com o topic errado, e registrar a reação do broker como informação. Decifre qualquer reason code com a consulta de reason codes. As correções para cada caso estão em erros Topic Alias invalid e erro de protocolo MQTT 0x82.

Verificar o lado do subscriber

Subscribers nunca deveriam perceber aliases, a não ser que tenham pedido. Abra o cliente MQTT online, assine testmqtt/alias/# e rode qualquer um dos scripts: toda mensagem deve mostrar o topic completo. Se um subscriber que não anunciou Topic Alias Maximum receber algum topic vazio, o broker está com defeito. É um bug raro, mas sério, que vale reportar.

Ver mensagens com alias chegando com o topic completo

Checklist

  1. Registre o Topic Alias Maximum de todo CONNACK e nunca o ultrapasse.
  2. Envie primeiro o topic completo com o alias, depois publicações só com alias, e valide que os subscribers veem topics completos.
  3. Reconecte e confirme que aliases antigos não são usados.
  4. Teste um alias acima do máximo e um alias não mapeado, e registre a reação do broker.
  5. Meça a economia real de bytes para os seus topics antes de tornar aliases um requisito.

Topic aliases são um ganho barato em links limitados e inúteis no resto. Teste a negociação e o caso de reconexão, e eles vão economizar banda sem virar o motivo de um dispositivo ficar mudo.

Perguntas frequentes

Qual é o valor padrão do Topic Alias Maximum?

0. Se o CONNECT ou o CONNACK não trouxer a propriedade Topic Alias Maximum, o receptor não aceita nenhum alias. Cada direção é negociada separadamente: o valor do cliente limita o broker e o valor do broker limita o cliente.

Topic aliases sobrevivem a uma reconexão?

Não. Os mapeamentos de alias existem só durante uma conexão de rede, mesmo com sessão persistente. Depois de cada reconexão, o cliente precisa enviar o nome completo do topic de novo antes de poder usar o alias.

Os subscribers enxergam o alias?

Só se pedirem. Aliases são por salto: o broker resolve o alias do publisher para o topic completo e só usa aliases com um subscriber que anunciou o próprio Topic Alias Maximum. A maioria dos subscribers simplesmente recebe o nome completo do topic.