12.5. Confiabilidade – sequências, ACKs, retransmissões

A camada de enquadramento detecta corrupção com seus CRCs. A camada de confiabilidade transforma “corrupção detectada” em “a aplicação nunca vê dados quebrados” ao negociar retransmissões sempre que um pacote não chega intacto.

12.5.1. Números de sequência

Cada cabeçalho de pacote carrega um número de sequência de um byte, separado para cada direção de tráfego. O emissor incrementa o contador antes de transmitir; o receptor verifica se a sequência de cada pacote recebido é a anterior mais um (módulo 256).

Três coisas podem aparecer no receptor em vez de um pacote limpo e em ordem:

  • O número de sequência esperado, com um CRC válido. O pacote é entregue à camada seguinte.

  • O número de sequência esperado, com um CRC inválido. O receptor descarta o pacote e (se os ACKs forem negociados) envia um NAK pedindo uma retransmissão.

  • Um número de sequência que está um acima do esperado, com um CRC válido. O receptor sabe que o pacote anterior se perdeu; ele envia um NAK referenciando a sequência perdida e guarda o novo.

O caso de duplicata (uma retransmissão chegando depois que o original finalmente passou) é tratado verificando o contador esperado: se a sequência está atrás da esperada, o pacote é uma duplicata e o receptor o descarta depois de enviar o ACK que o emissor claramente não recebeu da primeira vez.

12.5.2. ACK e NAK

Dois bits de flag no cabeçalho do pacote carregam o próprio tráfego de confiabilidade:

  • ACK_REQ ativado em um pacote de saída significa “quero uma confirmação de volta.” Pacotes de dados normalmente ativam isso; pings de status e eventos pontuais podem não ativar.

  • ACK ativado em um pacote significa “este pacote é a confirmação do número de sequência indicado no cabeçalho.” Ele não carrega payload próprio.

  • NAK ativado significa “este pacote rejeita um anterior” – geralmente por causa de um CRC inválido ou uma lacuna no número de sequência. O cabeçalho indica ao emissor qual sequência retransmitir.

O emissor executa um laço stop-and-wait: ele transmite um pacote que requer confirmação, depois espera pelo ACK (ou NAK) correspondente antes de enviar o próximo. O modelo de um único pacote em trânsito mantém o estado do emissor limitado – algumas centenas de bytes nas câmeras menores – e corresponde ao papel do protocolo como um canal de controle entre dois endpoints, em vez de um canal otimizado para throughput. Em um NAK, o emissor retransmite o mesmo pacote com a flag RTX ativada para que o receptor saiba que é uma nova tentativa.

12.5.3. Tempo de retransmissão

Se nem o ACK nem o NAK chegarem dentro do tempo limite de retransmissão, o emissor retransmite por conta própria o pacote em trânsito. O tempo limite tem o valor padrão de 500 ms e dobra a cada nova tentativa consecutiva (1 s, 2 s, …). Após o número configurado de tentativas – três por padrão – o emissor desiste e reporta um erro de transporte à aplicação.

Dobrar o tempo limite é o padrão clássico de backoff exponencial. Um primeiro tempo limite curto captura pacotes perdidos rapidamente; o fato de dobrar significa que um host que fica ocupado por algumas centenas de milissegundos não dispara uma tempestade de duplicatas que agravem a carga.

12.5.4. Configurando a confiabilidade

Ambas as pontas podem desligar partes da camada de confiabilidade, mediante acordo, quando a aplicação pode se dar ao luxo de perder dados:

  • protocol.init(ack=False) desabilita os ACKs por pacote. O emissor dispara e esquece; o receptor entrega o que quer que chegue. Bom para o streaming de dados de sensor em que uma amostra desatualizada é aceitável.

  • protocol.init(seq=False) desativa o rastreamento de números de sequência, o que implica também desligar os ACKs. Útil apenas em transportes perfeitamente confiáveis.

  • protocol.init(crc=False) desativa a validação de CRC, mas deixa o resto do enquadramento intacto. Vale a pena fazer apenas quando o próprio transporte é robusto o suficiente para que erros de CRC não aconteçam.

Os padrões – tudo ligado – são o ponto de partida certo para qualquer sessão de depuração entre host e câmera. Uma vez que a aplicação está em produção, os compromissos se tornam específicos aos seus dados e ao seu transporte.

12.5.5. Os códigos de status

Quando um erro de transporte se propaga até o código da aplicação, ele chega como um código de status. A biblioteca do protocolo define dez:

  • SUCCESS – operação concluída.

  • FAILED – o comando falhou por um motivo não especificado.

  • INVALID – o receptor rejeitou o comando ou um de seus argumentos.

  • TIMEOUT – um temporizador de nova tentativa esgotou.

  • BUSY – a câmera está ocupada (tipicamente um canal bloqueado).

  • CHECKSUM – o CRC do cabeçalho ou do payload não correspondeu.

  • SEQUENCE – o número de sequência estava fora de ordem além do que a camada consegue recuperar.

  • OVERFLOW – um payload excedeu o máximo negociado.

  • FRAGMENT – uma mensagem de múltiplos fragmentos chegou com partes faltando.

  • UNKNOWN – um capturador defensivo para condições genuinamente inesperadas.

O código do host que chama channel_read() vê esses códigos como exceções Python; o código de aplicação no lado da câmera que optou por tratamento de erros personalizado os vê como valores de retorno dos callbacks do backend. A maioria dos apps de câmera não precisa olhar para os códigos de status – a biblioteca trata da nova tentativa, e apenas falhas genuinamente irrecuperáveis (por exemplo, o próprio transporte sumiu) chegam à aplicação.

Com o enquadramento no lugar para detectar corrupção e a confiabilidade no lugar para se recuperar dela, o trabalho a nível de fio está pronto. O código da aplicação vê pacotes enquadrados, ordenados e intactos; os bytes dentro deles são livres para significar o que quer que a camada acima queira que signifiquem.