CBOR (Concise Binary Object Representation): semântica binária não é framing
O CBOR é frequentemente mal interpretado em sistemas embarcados. Muitos engenheiros o adotam esperando que ele “resolva a comunicação serial” — e é aqui que surgem projetos frágeis. CBOR não é um protocolo de transporte, não é um mecanismo de framing e não é, por si só, auto-sincronizável. Ele é uma serialização binária semântica, pensada para representar dados estruturados de forma compacta, eficiente e extensível.
Diferente de SLIP e COBS, que lidam com fluxo de bytes, o CBOR lida com significado: mapas, arrays, inteiros, floats, strings, tags. Ele resolve o problema de “como representar dados”, não o de “como saber onde eles começam e terminam no fio”. Quando essa distinção não é feita, surgem parsers bloqueantes, buffers gigantes e dependência de timeouts — todos sinais de um design errado.
O que o CBOR realmente define
O CBOR é baseado em tipos maiores (major types) codificados nos bits mais significativos do primeiro byte:
| Major Type | Função |
|---|---|
| 0 | Inteiros positivos |
| 1 | Inteiros negativos |
| 2 | Byte strings |
| 3 | Text strings |
| 4 | Arrays |
| 5 | Mapas (chave–valor) |
| 6 | Tags semânticas |
| 7 | Tipos simples e floats |
Cada item carrega informação de tipo + comprimento, o que permite parsing incremental desde que o framing já exista.
Exemplo conceitual: CBOR vs JSON
Estrutura lógica:
{
"cmd": 3,
"value": 1234
}
CBOR (hex):
A2 63 63 6D 64 03 65 76 61 6C 75 65 19 04 D2
Esse payload é compacto, eficiente e auto-descritivo — mas não diz onde começa nem onde termina em um stream UART.
Erro clássico: “vou ler CBOR direto da UART”
// ❌ exemplo de má escolha
read(uart, buffer, sizeof(buffer));
cbor_parse(buffer);
Esse padrão falha porque:
- UART não garante alinhamento
- Um byte perdido invalida toda a estrutura
- O parser não sabe onde começar
- Não há ponto de re-sincronização
CBOR assume que o transporte já entregou um bloco íntegro.
Uso correto: CBOR como payload, COBS como transporte
Arquitetura correta:
UART → COBS → [ payload CBOR ] → aplicação
- COBS garante framing e auto-sincronização
- CBOR garante semântica e extensibilidade
- CRC pode ser aplicado antes do COBS
Isso separa responsabilidades de forma limpa.
Exemplo didático — gerando CBOR (C)
Usando uma biblioteca CBOR mínima (pseudo-API):
uint8_t payload[64];
cbor_encoder_t enc;
cbor_init(&enc, payload, sizeof(payload));
cbor_map_start(&enc, 2);
cbor_put_text(&enc, "cmd");
cbor_put_uint(&enc, 3);
cbor_put_text(&enc, "value");
cbor_put_uint(&enc, 1234);
cbor_map_end(&enc);
size_t payload_len = cbor_length(&enc);
Esse payload não deve ir direto para a UART.
Encapsulando CBOR com COBS
uint8_t frame[128];
size_t frame_len;
frame_len = cobs_encode(payload, payload_len, frame);
uart_send(frame, frame_len);
No receptor:
- Recebe bytes até
0x00 - Decodifica COBS
- Valida CRC (se houver)
- Faz parsing CBOR
Esse pipeline sobrevive a ruído, perda de bytes e reinícios assíncronos.
Boas e más escolhas com CBOR
Boas escolhas
- Protocolos extensíveis
- RPC embarcado
- Gateways MCU ↔ Linux
- IoT, Zephyr, FreeRTOS, Linux embarcado
- Dados versionados
Más escolhas
- Transporte direto em UART sem framing
- Debug humano em terminal
- Mensagens ultra-críticas de tempo fixo
- Sistemas com memória extremamente limitada
CBOR brilha quando o significado importa mais que o byte — mas ele não substitui engenharia de transporte.
Comparação direta: COBS × CBOR
| Critério | COBS | CBOR |
|---|---|---|
| Framing | Sim | Não |
| Auto-sincronização | Forte | Parcial |
| Semântica | Não | Sim |
| Overhead previsível | Sim | Sim |
| Uso isolado em UART | Correto | Errado |
Conclusão parcial
CBOR não compete com COBS — eles se complementam. Um cuida do fio, o outro do significado. Quando usados juntos, formam uma base extremamente sólida para protocolos binários modernos em sistemas embarcados.
Na próxima seção, faremos:
- Comparação geral SLIP × COBS × CBOR
- Checklist de boas práticas
- Decisões certas e erradas em projetos reais