12.5. Affidabilità – sequenze, ACK, ritrasmissioni

Il livello di framing rileva la corruzione tramite i suoi CRC. Il livello di affidabilità trasforma la «corruzione rilevata» in «l’applicazione non vede mai dati danneggiati» negoziando ritrasmissioni ogni volta che un pacchetto non arriva integro.

12.5.1. Numeri di sequenza

L’intestazione di ogni pacchetto contiene un numero di sequenza di un byte, distinto per ciascuna direzione di trasmissione. Il mittente incrementa il contatore prima di trasmettere; il ricevente verifica che la sequenza di ogni pacchetto ricevuto sia quella precedente più uno (modulo 256).

Al ricevente possono presentarsi tre situazioni invece di un pacchetto pulito e nell’ordine corretto:

  • Il numero di sequenza atteso, con un CRC valido. Il pacchetto viene consegnato al livello superiore.

  • Il numero di sequenza atteso, con un CRC errato. Il ricevente scarta il pacchetto e (se gli ACK sono negoziati) invia un NAK richiedendo una ritrasmissione.

  • Un numero di sequenza superiore di uno rispetto a quello atteso, con un CRC valido. Il ricevente sa che il pacchetto precedente è andato perso; invia un NAK che fa riferimento alla sequenza mancante e conserva il nuovo pacchetto.

Il caso del duplicato (una ritrasmissione che arriva dopo che l’originale è finalmente passato) viene gestito confrontando con il contatore atteso: se la sequenza è indietro rispetto a quella attesa, il pacchetto è un duplicato e il ricevente lo scarta dopo aver inviato l’ACK che evidentemente il mittente non aveva ricevuto la prima volta.

12.5.2. ACK e NAK

Due bit di flag nell’intestazione del pacchetto trasportano il traffico di affidabilità vero e proprio:

  • ACK_REQ impostato su un pacchetto in uscita significa «voglio un riscontro di ritorno.» I pacchetti di dati lo impostano normalmente; i ping di stato e gli eventi una tantum potrebbero non farlo.

  • ACK impostato su un pacchetto significa «questo pacchetto è il riscontro per il numero di sequenza indicato nell’intestazione.» Non trasporta alcun payload proprio.

  • NAK impostato significa «questo pacchetto rifiuta uno precedente» – di solito a causa di un CRC errato o di una lacuna nei numeri di sequenza. L’intestazione indica al mittente quale sequenza ritrasmettere.

Il mittente esegue un ciclo stop-and-wait: trasmette un pacchetto che richiede un riscontro, poi attende l’ACK (o il NAK) corrispondente prima di inviare il successivo. Il modello a singolo pacchetto in volo mantiene limitato lo stato del mittente – poche centinaia di byte sulle cam più piccole – e si adatta al ruolo del protocollo come canale di controllo tra due endpoint, anziché come un flusso ottimizzato per il throughput. In caso di NAK il mittente ritrasmette lo stesso pacchetto con il flag RTX impostato, così il ricevente sa che si tratta di un nuovo tentativo.

12.5.3. Tempistica delle ritrasmissioni

Se né l’ACK né il NAK arrivano entro il timeout di ritrasmissione, il mittente ritrasmette autonomamente il pacchetto in volo. Il timeout predefinito è 500 ms e raddoppia ad ogni tentativo consecutivo (1 s, 2 s, …). Dopo il numero configurato di tentativi – tre per impostazione predefinita – il mittente rinuncia e segnala un errore di trasporto all’applicazione.

Il raddoppio del timeout è il classico schema di backoff esponenziale. Un primo timeout breve intercetta rapidamente i pacchetti persi; il raddoppio fa sì che un host occupato per qualche centinaio di millisecondi non scateni una tempesta di duplicati che aumenterebbe ulteriormente il carico.

12.5.4. Configurare l’affidabilità

Entrambe le estremità possono disattivare, di comune accordo, parti del livello di affidabilità quando l’applicazione può permettersi di perdere dati:

  • protocol.init(ack=False) disabilita gli ACK per pacchetto. Il mittente invia e dimentica; il ricevente consegna ciò che arriva. Adatto allo streaming di dati dei sensori, dove un campione obsoleto è accettabile.

  • protocol.init(seq=False) disattiva il tracciamento dei numeri di sequenza, il che implica anche la disattivazione degli ACK. Utile solo su trasporti perfettamente affidabili.

  • protocol.init(crc=False) disattiva la convalida del CRC ma lascia intatto il resto del framing. Vale la pena farlo solo quando il trasporto stesso è abbastanza robusto da non causare errori di CRC.

I valori predefiniti – tutto attivo – sono il giusto punto di partenza per qualsiasi sessione di debug tra host e cam. Una volta che l’applicazione è in produzione, i compromessi diventano specifici per i suoi dati e il suo trasporto.

12.5.5. I codici di stato

Quando un errore di trasporto si propaga fino al codice dell’applicazione, arriva sotto forma di codice di stato. La libreria del protocollo ne definisce dieci:

  • SUCCESS – operazione completata.

  • FAILED – il comando è fallito per un motivo non specificato.

  • INVALID – il ricevente ha rifiutato il comando o uno dei suoi argomenti.

  • TIMEOUT – un timer di nuovo tentativo è scaduto.

  • BUSY – la cam è occupata (di solito un canale bloccato).

  • CHECKSUM – il CRC dell’intestazione o del payload non corrisponde.

  • SEQUENCE – il numero di sequenza era fuori ordine oltre quanto il livello è in grado di recuperare.

  • OVERFLOW – un payload ha superato il massimo negoziato.

  • FRAGMENT – un messaggio multi-frammento è arrivato con pezzi mancanti.

  • UNKNOWN – una rete di sicurezza difensiva per condizioni realmente impreviste.

Il codice host che chiama channel_read() li vede come eccezioni Python; il codice applicativo lato cam che ha scelto la gestione personalizzata degli errori li vede come valori di ritorno dai callback del backend. La maggior parte delle app per cam non ha bisogno di esaminare affatto i codici di stato – la libreria gestisce i nuovi tentativi, e solo i guasti realmente irrecuperabili (ad es. la scomparsa del trasporto stesso) raggiungono l’applicazione.

Con il framing in atto per rilevare la corruzione e l’affidabilità in atto per recuperare da essa, il lavoro a livello di wire è concluso. Il codice applicativo vede pacchetti incapsulati, ordinati e integri; i byte al loro interno sono liberi di significare qualunque cosa voglia il canale di livello superiore.