Decodifica di protocolli personalizzati
Hai catturato un protocollo socket privato o interno che i formati integrati non riescono a riconoscere, lasciandoti con niente più di un mucchio di byte? La decodifica personalizzata ti permette di insegnare allo strumento come leggerlo con un breve script: suddividere un flusso di byte continuo in singoli messaggi, rimuovere l’intestazione del protocollo, decomprimere quando serve, e lasciare il resto al riconoscimento strutturato automatico dello strumento.
Questa pagina è per chi vuole scrivere un decodificatore da sé. Spiega come scrivere la funzione, quando viene chiamata e cosa significa il valore restituito.
1. Cos’è: un hook di decodifica per frame
Sezione intitolata “1. Cos’è: un hook di decodifica per frame”Scrivi un hook di decodifica, e lo strumento gli passa i byte di una connessione, ponendo ripetutamente una sola domanda: “A partire da qui, riesci a estrarre un messaggio completo?” Tu estrai un messaggio e segnali quanti byte hai usato; lo strumento richiede di nuovo con il resto, e così via finché il flusso non è completamente suddiviso.
Se hai mai scritto un decodificatore di byte ad accumulo in un framework di rete, il modello mentale è lo stesso: dato un flusso di byte continuo, sei tu a decidere i confini dei messaggi. Due convenzioni chiave:
- Non ti preoccupi della direzione: il flusso in uscita e il flusso in entrata di una connessione passano ciascuno separatamente attraverso il tuo hook, e lo strumento etichetta automaticamente ogni messaggio estratto con la sua direzione. Il tuo script si occupa solo di “come suddividere”.
- Una volta suddiviso, non ti occupi della resa visiva: ogni messaggio che invii passa di nuovo attraverso il riconoscimento automatico. Se al suo interno contiene protobuf, JSON o plist, viene analizzato ulteriormente in forma strutturata. Tu sei responsabile solo del framing, della rimozione delle intestazioni e della decompressione.
2. La funzione decode: decode(buf, out)
Sezione intitolata “2. La funzione decode: decode(buf, out)”L’intero decodificatore è un’unica funzione:
function decode(buf, out) { // buf: the byte stream currently pending parse; out: the output collector; return: the number of bytes consumed this time}Cosa sono gli input, gli output e il valore restituito
Sezione intitolata “Cosa sono gli input, gli output e il valore restituito”buf (input): il flusso di byte in attesa di analisi
- Tipo:
ArrayBuffer. Il suo contenuto è costituito da tutti i byte rimanenti “da dove hai terminato l’ultimo consumo, fino alla fine attuale”. - Proprietà
buf.byteLength: quanti byte rimangono in questo momento (può crescere a ogni chiamata; è così che valuti se ce ne sono abbastanza per estrarre un messaggio). - ⚠️ È un buffer di byte grezzo, non un
Uint8Array, quindi non puoi indicizzare i byte direttamente (buf[0]restituisceundefined). Per leggere i byte, usa le funzioni integrate della sezione 3 (u8/u16be/sub…), oppure avvolgi tu stesso una vista:new Uint8Array(buf),new DataView(buf).
out (output): il raccoglitore dell’output
- Tipo: un semplice array.
- Metodo
out.push(byte del messaggio): invia un messaggio; puoi inviarne diversi in una singola chiamata. - I “byte del messaggio” possono essere: un
ArrayBufferrestituito da una funzione integrata (comesub(...),gunzip(...)), oppure una stringa. Gli altri tipi vengono ignorati.
Valore restituito: quanti byte hai consumato
- Tipo: intero (
number), il numero di byte che hai usato dall’inizio dibuf. > 0: hai consumato questa quantità di byte dall’inizio ed estratto un messaggio → lo strumento li scarta e ti richiama con il resto.0/ nessunreturn/ numero negativo: significa “non c’è ancora un messaggio completo all’inizio” oppure “non si può estrarre altro” → lo strumento interrompe il ciclo e mostra i byte rimanenti come grezzi.- Il valore restituito viene automaticamente limitato alla lunghezza del buffer (per prevenire sforamenti), ma restituisci comunque il numero reale di byte che hai effettivamente usato.
Quando viene chiamata (ciclo di vita)
Sezione intitolata “Quando viene chiamata (ciclo di vita)”- Lo strumento esegue una passata sul flusso in uscita di una connessione e una sul suo flusso in entrata. In ogni passata,
decodeviene chiamata in un ciclo: ogni volta ti passa i byte rimanenti non consumati, tu estrai un messaggio e restituisci il numero consumato, e così via finché non restituisci 0. - Un messaggio compare solo quando il suo consumo è confermato: il messaggio che invii compare solo quando restituisci anche > 0. Se invii ma restituisci 0, il ciclo si interrompe e l’invio viene scartato, quindi inviare un messaggio deve sempre essere abbinato al restituire quanti byte ha richiesto.
- Al termine del ciclo, i byte finali non consumati non vanno persi; vengono mostrati come un unico blocco di dati grezzi (per esempio, la seconda metà di un messaggio incompleto).
Stato e idempotenza
Sezione intitolata “Stato e idempotenza”- Ogni flusso (direzione) riceve un ambiente di script separato e completamente nuovo; il flusso in uscita e il flusso in entrata non si mescolano mai tra loro.
- Per mantenere lo stato tra una chiamata e l’altra (un contatore, il tipo del messaggio precedente, e così via), dichiara le variabili all’esterno di
decode. Persistono tra le chiamate all’interno dello stesso flusso, e si azzerano quando la direzione cambia o quando ridecodifichi. - La decodifica opera su dati già catturati, e può essere rieseguita ripetutamente: modifica lo script, salva e fai clic di nuovo per ridecodificare la stessa connessione con le nuove regole.
Gli errori non rompono nulla
Sezione intitolata “Gli errori non rompono nulla”- Se lo script solleva un’eccezione o una singola esecuzione va in timeout, viene trattato come “questa volta non ho consumato nulla”, i byte rimanenti vengono mostrati come dati grezzi, e la cattura prosegue senza perdita di dati.
- In altre parole, il peggio che uno script difettoso può fare è non riuscire a decodificare, lasciandoti con byte grezzi; non farà crashare la connessione. Modifica pure liberamente.
- E gli errori non vengono nascosti: le eccezioni e i timeout vengono mostrati nel pannello di output di debug sopra i risultati (vedi sezione 5), etichettati a seconda che provengano dal flusso in uscita
↑o in entrata↓. Seguili direttamente fino alla soluzione.
3. Funzioni integrate (disponibili direttamente nello script)
Sezione intitolata “3. Funzioni integrate (disponibili direttamente nello script)”Le operazioni comuni, leggere byte, leggere interi, convertire in testo e decomprimere, sono tutte integrate, così non devi reinventarle:
| Categoria | Firma | Descrizione |
|---|---|---|
| Estrai sotto-porzione | sub(buf, off[, len]) |
Estrae a partire da off (ometti len per arrivare alla fine), restituisce ArrayBuffer |
| Leggi intero | u8(buf, off) / u16be / u16le / u32be / u32le (buf, off) |
Legge interi senza segno in big-endian / little-endian |
| Converti in testo | hex(buf) / ascii(buf) |
Converte in una stringa esadecimale / legge come testo |
| Rileva compressione | gzipMagic(buf) |
Se è gzip (restituisce un valore booleano) |
| Decomprimi | gunzip / inflate / unzstd / lz4dtx (buf) |
Decomprime; se non ci riesce, restituisce l’input invariato senza errori |
| Debug | log(...args) / console.log(...args) |
Stampa nel pannello di debug (vedi sezione 5) |
Anche le funzionalità standard di lettura/scrittura dei byte (DataView, Uint8Array, ecc.) sono utilizzabili direttamente.
4. Esempi
Sezione intitolata “4. Esempi”① Prefisso di lunghezza [lunghezza a 4 byte big-endian][payload], la forma più comune dei protocolli privati:
function decode(buf, out) { if (buf.byteLength < 4) return 0 // the length header hasn't fully arrived, wait const total = 4 + u32be(buf, 0) // whole message = 4-byte header + payload if (buf.byteLength < total) return 0 // the whole message hasn't fully arrived, wait out.push(sub(buf, 4, total - 4)) // strip the header, hand the payload to auto recognition return total // consume this message, go on to slice the next}② Per delimitatore / per riga, suddividendo i frame su un marcatore come un a capo:
function decode(buf, out) { const i = ascii(buf).indexOf('\n') if (i < 0) return 0 // no newline yet, wait out.push(sub(buf, 0, i)) // push this line (without the newline) return i + 1 // consume the newline along with it}③ Elabora tutto in una volta, trattando l’intero flusso come un unico messaggio, per esempio decomprimendolo tutto:
function decode(buf, out) { out.push(gzipMagic(buf) ? gunzip(buf) : buf) // if it's gzip, unpack it return buf.byteLength // consume everything, the loop ends right away}④ Smista per tipo, con un campo di tipo nell’intestazione e una gestione diversa per ciascun tipo:
function decode(buf, out) { if (buf.byteLength < 4) return 0 const total = 4 + u32be(buf, 0) if (buf.byteLength < total) return 0 const type = u8(buf, 4) // the first byte is the message type const body = sub(buf, 5, total - 5) out.push(type === 2 ? gunzip(body) : body) // type 2 is compressed return total}L’editor ha modelli già pronti per tutti questi schemi; inseriscine uno, ritocca qualche numero, e funziona.

5. Come usarlo, come fare il debug
Sezione intitolata “5. Come usarlo, come fare il debug”- Scrivi e salva: crea, assegna un nome e salva nell’editor del decodificatore; inserisci un modello per iniziare, con le funzioni integrate a portata di mano in una guida rapida.
- Applica: per una connessione che non riesci a interpretare, fai clic con il tasto destro e scegli “Decodifica come” → il tuo decodificatore, e tutto il traffico in uscita e in entrata della connessione viene immediatamente suddiviso e mostrato secondo le tue regole.
- Debug e ispezione delle variabili: all’interno di
decode, usalog(...)oconsole.log(...)per stampare qualsiasi valore: conteggi di byte, campi di tipo,hex(sub(buf, 0, 8)), e così via. L’output compare nel pannello “Output di debug” sopra i risultati di “Decodifica come”, ogni riga etichettata a seconda che provenga dal flusso in uscita↑o in entrata↓. Gli errori e i timeout dello script compaiono nello stesso pannello, anch’essi etichettati con la direzione. Stampa e individua i problemi così; è molto più veloce che modificare alla cieca.La sandbox non ha una
consolecompleta da browser / Node; soloconsole.loge l’equivalentelogsono collegati a questo pannello.TextDecoder,fetch,setTimeoute simili non sono disponibili. Per leggere i byte come testo, usaascii(buf). - Modifica e vedi il risultato dal vivo: non serve ricatturare. Salva il tuo script, fai di nuovo clic su “Decodifica come”, e la stessa connessione viene ridecodificata sul momento con le nuove regole. Itera finché non viene suddivisa come vuoi tu.
- Passa alla struttura: ogni blocco decodificato viene restituito al riconoscimento automatico, così protobuf, JSON e plist vengono analizzati ulteriormente in forma strutturata, visualizzabili attraverso le varie viste in Ispezione e decodifica dei dati.
- I decodificatori possono essere denominati e conservati in un elenco, per essere aggiunti, modificati o eliminati in qualsiasi momento.
6. Quando usarlo
Sezione intitolata “6. Quando usarlo”- Quando catturi un protocollo socket interno o privato (una forma comune è prefisso di lunghezza + protobuf / JSON / binario) che i formati integrati non riescono a riconoscere, scrivi un decodificatore per suddividerlo e riportarlo a una struttura leggibile.
- Quando vuoi riportare a un contenuto leggibile un blocco di byte avvolto in un’intestazione personalizzata, o passato attraverso un ciclo di compressione.
7. Riferimento API
Sezione intitolata “7. Riferimento API”Una guida rapida consolidata: il punto di ingresso decode, le funzioni integrate di supporto e l’ambiente di esecuzione. Ogni funzione che accetta buf accetta sia un ArrayBuffer sia una stringa.
Punto di ingresso decode
Sezione intitolata “Punto di ingresso decode”decode(buf, out) → number| Scopo | L’unico punto di ingresso del decodificatore, deve essere implementato. Estrae i messaggi dall’inizio di buf, li invia con push a out, e restituisce il numero di byte consumati questa volta. |
buf |
ArrayBuffer, il flusso di byte rimanente in attesa di analisi. Usa buf.byteLength per la sua lunghezza; non puoi indicizzare i byte, usa le funzioni sottostanti oppure new DataView(buf) / new Uint8Array(buf). |
out |
Array, il raccoglitore dell’output. out.push(bytes) invia un messaggio (bytes è un ArrayBuffer o una stringa; gli altri tipi vengono ignorati); puoi inviarne diversi in una volta. |
| Return | number, i byte consumati dall’inizio di buf. > 0 continua; 0 / negativo / nessun return → interrompe, e i byte rimanenti vengono mostrati come grezzi. |
| Convenzione di chiamata | Esegue una passata ciascuna sul flusso in uscita e sul flusso in entrata della connessione; ogni passata chiama ripetutamente, passando ogni volta i byte rimanenti non consumati, finché non restituisci 0. |
Funzioni integrate di supporto
Sezione intitolata “Funzioni integrate di supporto”Estrai sotto-porzione
| Firma | Restituisce | Descrizione |
|---|---|---|
sub(buf, off) |
ArrayBuffer |
Estrae da off fino alla fine |
sub(buf, off, len) |
ArrayBuffer |
Estrae len byte a partire da off; un off / len fuori intervallo viene automaticamente limitato, senza errori |
Leggi intero (senza segno; restituisce 0 quando off è fuori intervallo)
| Firma | Restituisce | Descrizione |
|---|---|---|
u8(buf, off) |
number |
Legge 1 byte |
u16be(buf, off) / u16le(buf, off) |
number |
Legge 2 byte, big-endian / little-endian |
u32be(buf, off) / u32le(buf, off) |
number |
Legge 4 byte, big-endian / little-endian |
Converti in testo
| Firma | Restituisce | Descrizione |
|---|---|---|
hex(buf) |
string |
Converte in esadecimale minuscolo |
ascii(buf) |
string |
Legge come testo (adatto a contenuti ASCII / testuali) |
Compressione / decompressione (restituisce l’input invariato quando non riesce a decomprimere; limite per singola decompressione intorno ai 16 MB)
| Firma | Restituisce | Descrizione |
|---|---|---|
gzipMagic(buf) |
boolean |
Se inizia con il numero magico di gzip |
gunzip(buf) |
ArrayBuffer |
Decompressione gzip |
inflate(buf) |
ArrayBuffer |
Decompressione zlib / deflate |
unzstd(buf) |
ArrayBuffer |
Decompressione zstd |
lz4dtx(buf) |
ArrayBuffer |
Decompressione di flusso LZ4 a blocchi |
Debug
| Firma | Restituisce | Descrizione |
|---|---|---|
log(...args) |
(nessuno) | Unisce gli argomenti in una riga e li stampa nel pannello “Output di debug”; un ArrayBuffer viene mostrato in esadecimale |
console.log(...args) |
(nessuno) | Come log (comodo per chi è abituato a console.log) |
Ambiente di esecuzione
Sezione intitolata “Ambiente di esecuzione”- JavaScript standard: gli oggetti integrati comuni come
ArrayBuffer, gli array tipizzati (Uint8Array, ecc.),DataView,JSON,Math,RegExp,Datesono tutti disponibili; quando ti serve la lettura/scrittura dei byte a più basso livello,new DataView(buf)/new Uint8Array(buf)sono a portata di mano. - Debug:
log(...)/console.log(...)stampano nel pannello “Output di debug”; anche gli errori e i timeout dello script vengono stampati lì, etichettati a seconda che provengano dal flusso in uscita↑o in entrata↓. - Non fornite sono le altre funzionalità specifiche di browser / Node: i metodi di
consolediversi dalog,TextDecoder/TextEncoder,fetch,setTimeout,require, ecc. non sono disponibili. Per leggere i byte come testo, usaascii(buf). - Ogni chiamata a
decodeha un limite di tempo di esecuzione: un ciclo infinito o un timeout viene interrotto e trattato come “non ha consumato nulla”, così non blocca lo strumento.
Torna a Cattura tramite proxy · Correlato: Ispezione e decodifica dei dati · Cattura locale dalla NIC