Integridade e parsing defensivo: confiar em bytes é ingenuidade
Quando o framing está definido, muitos engenheiros acreditam que o problema está resolvido. Não está. Um frame bem delimitado ainda pode estar corrompido, incompleto, injetado por ruído ou gerado por um nó defeituoso. UART não oferece nenhuma garantia de integridade — se um bit muda de estado no fio, o receptor recebe outro valor e segue a vida.
É aqui que sistemas imaturos começam a tomar decisões perigosas baseadas em dados inválidos.
O erro clássico: “se chegou, é válido”
// ❌ Confiança cega no payload
void handle_packet(uint8_t *data, uint8_t len) {
uint8_t cmd = data[0];
if (cmd == CMD_START_MOTOR) {
motor_start(data[1]);
}
}
Esse código assume coisas que não são verdadeiras:
- Que
lené válido - Que
data[0]é um comando conhecido - Que
data[1]existe - Que o frame não sofreu corrupção
- Que o remetente está bem comportado
Em sistemas reais, isso leva a comportamentos perigosos, especialmente quando UART controla atuadores, válvulas, motores ou sistemas de segurança.
Integridade não é luxo, é requisito
Todo protocolo sério define um mecanismo de verificação de integridade, no mínimo:
- Checksum simples (fraco, mas melhor que nada)
- CRC (Cyclic Redundancy Check) — o padrão de fato
Um CRC transforma um conjunto de bytes em uma assinatura matemática extremamente sensível a erro. Um único bit errado invalida o pacote.
Exemplo correto: CRC explícito no frame
Estrutura típica:
[ SOF ][ LEN ][ PAYLOAD ][ CRC16 ]
Implementação conceitual:
uint16_t crc16(const uint8_t *data, uint8_t len);
case WAIT_CRC:
{
uint16_t rx_crc = (crc_high << 8) | crc_low;
uint16_t calc_crc = crc16(payload, length);
if (rx_crc == calc_crc) {
handle_packet(payload, length);
} else {
// frame descartado silenciosamente
}
state = WAIT_SOF;
}
Aqui surgem propriedades críticas:
- Frames inválidos não produzem efeitos
- Ruído vira descarte, não ação
- O sistema se mantém previsível
- Debug se torna rastreável
Parsing defensivo: nunca confie no emissor
Mesmo com CRC, o conteúdo pode ser semanticamente inválido. Parsing defensivo significa nunca assumir que dados estão corretos, mesmo quando passam no CRC.
// ✅ Parsing defensivo
void handle_packet(uint8_t *data, uint8_t len) {
if (len < 1) return;
switch (data[0]) {
case CMD_START_MOTOR:
if (len != 2) return;
if (data[1] > MAX_SPEED) return;
motor_start(data[1]);
break;
case CMD_STOP_MOTOR:
if (len != 1) return;
motor_stop();
break;
default:
// comando desconhecido
break;
}
}
Isso impede:
- Buffer overflow
- Execução de comandos incompletos
- Estados ilegais
- Exploração acidental ou maliciosa
UART como superfície de ataque (mesmo sem rede)
Um ponto pouco discutido: UART é uma interface externa. Qualquer coisa conectada a ela pode injetar dados. Em ambientes industriais, médicos ou robóticos, isso não é hipotético.
Parsing defensivo é a diferença entre:
- Um sistema que ignora lixo
- Um sistema que entra em estado perigoso
Regra de ouro
Nenhum dado externo deve causar efeito colateral antes de ser validado em três níveis:
- Framing correto
- Integridade verificada
- Semântica válida
Se um desses níveis falhar, o dado deve ser descartado sem drama.