MCU & FPGA UART (Serial) UART não é Porta Serial: Como Projetar Protocolos Robustos em Sistemas Embarcados

UART não é Porta Serial: Como Projetar Protocolos Robustos em Sistemas Embarcados


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:

  1. Framing correto
  2. Integridade verificada
  3. Semântica válida

Se um desses níveis falhar, o dado deve ser descartado sem drama.


0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x