SLIP (Serial Line Internet Protocol): simplicidade que cobra seu preço
O SLIP é, historicamente, uma das primeiras tentativas de organizar dados em um fluxo serial contínuo. Ele nasceu em um contexto onde simplicidade era mais importante que robustez, e isso aparece claramente em seu desenho. O SLIP não define campos, não define tipos, não define verificação de integridade — ele apenas delimita pacotes.
A ideia central é direta: um byte especial (0xC0, chamado END) indica o fim de um pacote. Se esse byte aparecer dentro dos dados, ele é “escapado”. Com isso, o receptor consegue identificar onde termina uma mensagem, mesmo em um fluxo contínuo.
Essa abordagem funciona, mas carrega limitações estruturais que ficam evidentes assim que o sistema cresce.

Como o SLIP funciona (byte a byte)
O protocolo define apenas três valores relevantes:
| Byte | Nome | Função |
|---|---|---|
0xC0 | END | Delimitador de fim de pacote |
0xDB | ESC | Escape |
0xDC | ESC_END | Representa 0xC0 dentro dos dados |
0xDD | ESC_ESC | Representa 0xDB dentro dos dados |
Regras:
- Todo pacote termina com
END - Se
ENDaparecer nos dados, ele viraESC + ESC_END - Se
ESCaparecer nos dados, ele viraESC + ESC_ESC
Isso garante que o byte END nunca aparece dentro do payload, apenas como delimitador.
Exemplo conceitual de framing SLIP
Payload original:
41 C0 42 DB 43
Após codificação SLIP:
41 DB DC 42 DB DD 43 C0
O receptor lê bytes até encontrar 0xC0, desfaz os escapes e reconstrói o payload.
Implementação didática — Encoder SLIP em C
#include <stdint.h>
#include <stddef.h>
#define SLIP_END 0xC0
#define SLIP_ESC 0xDB
#define SLIP_ESC_END 0xDC
#define SLIP_ESC_ESC 0xDD
size_t slip_encode(const uint8_t *in, size_t len, uint8_t *out)
{
size_t j = 0;
for (size_t i = 0; i < len; i++) {
if (in[i] == SLIP_END) {
out[j++] = SLIP_ESC;
out[j++] = SLIP_ESC_END;
} else if (in[i] == SLIP_ESC) {
out[j++] = SLIP_ESC;
out[j++] = SLIP_ESC_ESC;
} else {
out[j++] = in[i];
}
}
out[j++] = SLIP_END; // delimitador de pacote
return j;
}
Esse código é típico de exemplos encontrados em projetos embarcados simples. Ele é fácil de entender, fácil de portar e fácil de depurar — e exatamente por isso é tão sedutor.
Decoder SLIP (lado crítico)
int slip_decode_byte(uint8_t byte, uint8_t *out, size_t *idx)
{
static uint8_t esc = 0;
if (byte == SLIP_END) {
size_t size = *idx;
*idx = 0;
esc = 0;
return (int)size; // pacote completo
}
if (esc) {
if (byte == SLIP_ESC_END) out[(*idx)++] = SLIP_END;
else if (byte == SLIP_ESC_ESC) out[(*idx)++] = SLIP_ESC;
esc = 0;
return -1;
}
if (byte == SLIP_ESC) {
esc = 1;
return -1;
}
out[(*idx)++] = byte;
return -1;
}
Note o ponto crítico: qualquer erro de byte pode invalidar todo o pacote atual, e não há mecanismo interno para detectar isso.
Onde o SLIP é uma boa escolha
Boas escolhas
- Debug simples via UART
- Bootloaders muito pequenos
- Prototipação rápida
- Sistemas com link confiável (USB CDC, por exemplo)
Nesses cenários, a previsibilidade do meio compensa a fragilidade do protocolo.
Onde o SLIP é uma má escolha (e por quê)
Más escolhas
- Ambientes industriais
- Links com ruído elétrico
- Sistemas com múltiplos nós
- Protocolos binários estruturados
- Comunicação crítica ou contínua
O SLIP não detecta erro, não valida comprimento, não permite recuperação rápida. Um byte perdido desloca todo o fluxo até o próximo END, e o sistema sequer sabe que recebeu lixo.
Em firmware de produção, isso normalmente se manifesta como:
- pacotes “fantasmas”
- comandos truncados
- estados inválidos difíceis de reproduzir
Conclusão parcial
O SLIP ensina uma lição importante: framing é melhor do que nada, mas framing frágil vira dívida técnica. Ele é didático, histórico e ainda útil em nichos, mas não escala.
Na próxima seção, entraremos no COBS, um protocolo que resolve exatamente os problemas estruturais do SLIP — mantendo simplicidade, mas com auto-sincronização forte e previsível.