Decodificação de Protocolo Personalizado
Capturou um protocolo socket privado ou interno que os formatos embutidos não conseguem reconhecer, deixando você apenas com um monte de bytes? A decodificação personalizada permite que você ensine a ferramenta a lê-lo com um script curto: fatiar um fluxo contínuo de bytes em mensagens individuais, remover o cabeçalho do protocolo, descomprimir quando necessário e deixar o resto para o reconhecimento estruturado automático da ferramenta.
Esta página é para quem quer escrever um decodificador por conta própria. Ela explica como escrever a função, quando ela é chamada e o que o valor de retorno significa.
1. O que é: um gancho de decodificação por quadro
Seção intitulada “1. O que é: um gancho de decodificação por quadro”Você escreve um gancho de decodificação, e a ferramenta alimenta-o com os bytes de uma conexão, perguntando repetidamente uma única coisa: “A partir daqui, você consegue fatiar uma mensagem completa?” Você fatia uma mensagem e informa quantos bytes usou; ela pergunta de novo com o restante, e assim por diante até que o fluxo esteja totalmente fatiado.
Se você já escreveu um decodificador de bytes acumulativo em algum framework de rede, o modelo mental é o mesmo: dado um fluxo contínuo de bytes, você mesmo decide os limites das mensagens. Duas convenções-chave:
- Você não se preocupa com a direção: o fluxo de envio e o fluxo de recebimento de uma conexão passam separadamente pelo seu gancho, e a ferramenta rotula automaticamente cada mensagem fatiada com sua direção. Seu script só cuida de “como fatiar”.
- Depois de fatiar, você não cuida da renderização: toda mensagem que você emite passa por reconhecimento automático novamente. Se ela contiver protobuf, JSON ou plist por dentro, isso é analisado mais a fundo em forma estruturada. Você é responsável apenas pelo enquadramento, pela remoção de cabeçalhos e pela descompressão.
2. A função de decodificação: decode(buf, out)
Seção intitulada “2. A função de decodificação: decode(buf, out)”O decodificador inteiro é uma única função:
function decode(buf, out) { // buf: o fluxo de bytes atualmente pendente de análise; out: o coletor de saída; return: o número de bytes consumidos desta vez}O que são as entradas, saídas e o valor de retorno
Seção intitulada “O que são as entradas, saídas e o valor de retorno”buf (entrada): o fluxo de bytes pendente de análise
- Tipo:
ArrayBuffer. Seu conteúdo são todos os bytes restantes “de onde você terminou de consumir por último até o fim atual”. - Propriedade
buf.byteLength: quantos bytes restam neste momento (pode crescer a cada chamada; é assim que você julga se há o suficiente para fatiar uma mensagem). - ⚠️ É um buffer de bytes bruto, não um
Uint8Array, então você não consegue indexar bytes diretamente (buf[0]retornaundefined). Para ler bytes, use os auxiliares embutidos na seção 3 (u8/u16be/sub…), ou construa você mesmo uma visão:new Uint8Array(buf),new DataView(buf).
out (saída): o coletor de saída
- Tipo: um array comum.
- Método
out.push(bytes da mensagem): emite uma mensagem; você pode emitir várias em uma única chamada. - “Bytes da mensagem” podem ser: um
ArrayBufferretornado por um auxiliar embutido (comosub(...),gunzip(...)), ou uma string. Outros tipos são ignorados.
Valor de retorno: quantos bytes você consumiu
- Tipo: inteiro (
number), o número de bytes que você usou a partir do início debuf. > 0: você consumiu esta quantidade de bytes do começo e fatiou uma mensagem → a ferramenta descarta-os e chama você de novo com o restante.0/ semreturn/ número negativo: significa “ainda não há uma mensagem completa no começo” ou “nada mais pode ser fatiado” → a ferramenta para o laço e exibe os bytes restantes como brutos.- O valor de retorno é automaticamente limitado ao comprimento do buffer (para evitar estouros), mas informe o número real de bytes que você de fato usou.
Quando ela é chamada (ciclo de vida)
Seção intitulada “Quando ela é chamada (ciclo de vida)”- A ferramenta executa uma passada sobre o fluxo de envio de uma conexão e uma sobre seu fluxo de recebimento. Em cada passada,
decodeé chamada em laço: a cada vez, ela entrega os bytes restantes ainda não consumidos, você fatia uma mensagem e retorna o número consumido, e assim por diante até você retornar 0. - Uma mensagem só aparece quando seu consumo é confirmado: a mensagem que você emite só aparece quando você também retorna > 0. Se você emitir mas retornar 0, o laço para e a emissão é descartada, então emitir uma mensagem deve sempre ser acompanhado de retornar quantos bytes ela ocupou.
- Depois que o laço termina, os bytes finais não consumidos não são perdidos; eles são mostrados como um único bloco de dados brutos (por exemplo, a segunda metade de uma mensagem incompleta).
Estado e idempotência
Seção intitulada “Estado e idempotência”- Cada fluxo (direção) recebe um ambiente de script separado e totalmente novo; o fluxo de envio e o fluxo de recebimento nunca se misturam.
- Para manter estado entre chamadas (um contador, o tipo da mensagem anterior e assim por diante), declare as variáveis fora de
decode. Elas persistem entre chamadas dentro do mesmo fluxo, e são reiniciadas quando a direção muda ou quando você redecodifica. - A decodificação roda sobre dados já capturados e pode ser reexecutada repetidamente: edite o script, salve e clique de novo para redecodificar a mesma conexão com as novas regras.
Erros não quebram nada
Seção intitulada “Erros não quebram nada”- Se o script lançar uma exceção ou uma única execução exceder o tempo, isso é tratado como “não consumiu nada desta vez”, os bytes restantes são mostrados como dados brutos, e a captura continua rodando sem perda de dados.
- Em outras palavras, o pior que um script defeituoso pode fazer é falhar em decodificar, deixando você com bytes brutos; ele não vai derrubar a conexão. Edite à vontade.
- E os erros não são engolidos: exceções e estouros de tempo são mostrados no painel de saída de depuração acima dos resultados (veja a seção 5), rotulados conforme vieram do fluxo de envio
↑ou de recebimento↓. Siga-os direto até a correção.
3. Auxiliares embutidos (disponíveis diretamente no script)
Seção intitulada “3. Auxiliares embutidos (disponíveis diretamente no script)”As tarefas comuns, ler bytes, ler inteiros, converter para texto e descomprimir, são todas embutidas, para que você não precise reinventá-las:
| Categoria | Assinatura | Descrição |
|---|---|---|
| Extrair subfatia | sub(buf, off[, len]) |
Fatia a partir de off (omita len para ir até o fim), retorna ArrayBuffer |
| Ler inteiro | u8(buf, off) / u16be / u16le / u32be / u32le (buf, off) |
Lê inteiros sem sinal em big-endian / little-endian |
| Converter para texto | hex(buf) / ascii(buf) |
Converte para uma string hexadecimal / lê como texto |
| Detectar compressão | gzipMagic(buf) |
Se é gzip (retorna um booleano) |
| Descomprimir | gunzip / inflate / unzstd / lz4dtx (buf) |
Descomprime; se não conseguir, retorna a entrada inalterada sem erro |
| Depuração | log(...args) / console.log(...args) |
Imprime no painel de depuração (veja a seção 5) |
As capacidades padrão de leitura/escrita de bytes (DataView, Uint8Array, etc.) também podem ser usadas diretamente.
4. Exemplos
Seção intitulada “4. Exemplos”① Prefixo de comprimento [comprimento big-endian de 4 bytes][payload], o formato de protocolo privado mais comum:
function decode(buf, out) { if (buf.byteLength < 4) return 0 // o cabeçalho de comprimento ainda não chegou por completo, aguardar const total = 4 + u32be(buf, 0) // mensagem inteira = cabeçalho de 4 bytes + payload if (buf.byteLength < total) return 0 // a mensagem inteira ainda não chegou por completo, aguardar out.push(sub(buf, 4, total - 4)) // remove o cabeçalho, entrega o payload ao reconhecimento automático return total // consome esta mensagem, segue para fatiar a próxima}② Por delimitador / por linha, fatiando quadros em um marcador como uma quebra de linha:
function decode(buf, out) { const i = ascii(buf).indexOf('\n') if (i < 0) return 0 // ainda sem quebra de linha, aguardar out.push(sub(buf, 0, i)) // emite esta linha (sem a quebra de linha) return i + 1 // consome a quebra de linha junto}③ Processar tudo de uma vez, tratando o fluxo inteiro como uma mensagem, por exemplo descomprimindo tudo:
function decode(buf, out) { out.push(gzipMagic(buf) ? gunzip(buf) : buf) // se for gzip, descompacta return buf.byteLength // consome tudo, o laço termina imediatamente}④ Despachar por tipo, com um campo de tipo no cabeçalho e tratamento diferente por 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) // o primeiro byte é o tipo da mensagem const body = sub(buf, 5, total - 5) out.push(type === 2 ? gunzip(body) : body) // o tipo 2 é comprimido return total}O editor tem modelos prontos para todos esses padrões; insira um, ajuste alguns números e ele roda.

5. Como usá-lo, como depurá-lo
Seção intitulada “5. Como usá-lo, como depurá-lo”- Escreva e salve: crie, nomeie e salve no editor de decodificador; insira um modelo para começar, com as funções embutidas em uma referência rápida à mão.
- Aplique: para uma conexão que você não consegue entender, clique com o botão direito e escolha “Decodificar como” → seu decodificador, e todo o tráfego de envio e recebimento da conexão é imediatamente fatiado e mostrado conforme suas regras.
- Depure e inspecione variáveis: dentro de
decode, uselog(...)ouconsole.log(...)para imprimir qualquer valor: contagens de bytes, campos de tipo,hex(sub(buf, 0, 8))e assim por diante. A saída aparece no painel “Saída de depuração” acima dos resultados de “Decodificar como”, cada linha rotulada conforme veio do fluxo de envio↑ou de recebimento↓. Erros e estouros de tempo do script aparecem no mesmo painel, também rotulados com a direção. Imprima e localize dessa forma; é muito mais rápido do que editar às cegas.O sandbox não tem um
consolecompleto de navegador / Node; apenasconsole.loge o equivalentelogestão ligados a este painel.TextDecoder,fetch,setTimeoute afins não estão disponíveis. Para ler bytes como texto, useascii(buf). - Edite e veja ao vivo: não é preciso recapturar. Salve seu script, clique em “Decodificar como” de novo, e a mesma conexão é redecodificada na hora com as novas regras. Itere até fatiar do jeito que você quer.
- Rumo à estrutura: cada trecho decodificado é devolvido ao reconhecimento automático, então protobuf, JSON e plist são analisados mais a fundo em forma estruturada, visualizáveis pelas múltiplas visões em Inspecionar e decodificar dados.
- Os decodificadores podem ser nomeados e mantidos como uma lista, para serem adicionados, editados ou removidos a qualquer momento.
6. Quando usá-lo
Seção intitulada “6. Quando usá-lo”- Quando você captura um protocolo socket interno ou privado (um formato comum é prefixo de comprimento + protobuf / JSON / binário) que os formatos embutidos não conseguem reconhecer, escreva um decodificador para fatiá-lo e restaurá-lo a uma estrutura legível.
- Quando você quer transformar um bloco de bytes envolto em um cabeçalho personalizado, ou submetido a uma rodada de compressão, de volta em conteúdo legível.
7. Referência da API
Seção intitulada “7. Referência da API”Uma referência rápida consolidada: o ponto de entrada de decodificação, as funções auxiliares embutidas e o ambiente de execução. Toda função que recebe buf aceita tanto um ArrayBuffer quanto uma string.
Ponto de entrada de decodificação
Seção intitulada “Ponto de entrada de decodificação”decode(buf, out) → number| Propósito | O único ponto de entrada do decodificador, deve ser implementado. Fatie mensagens do começo de buf, push-e-as para out e retorne o número de bytes consumidos desta vez. |
buf |
ArrayBuffer, o fluxo de bytes restante pendente de análise. Use buf.byteLength para seu comprimento; você não consegue indexar bytes, use os auxiliares abaixo ou new DataView(buf) / new Uint8Array(buf). |
out |
Array, o coletor de saída. out.push(bytes) emite uma mensagem (bytes é um ArrayBuffer ou uma string; outros tipos são ignorados); você pode emitir várias de uma vez. |
| Retorno | number, os bytes consumidos do começo de buf. > 0 continua; 0 / negativo / sem retorno → para, e os bytes restantes são mostrados como brutos. |
| Convenção de chamada | Executa uma passada em cada um dos fluxos de envio e de recebimento da conexão; cada passada chama repetidamente, passando os bytes restantes não consumidos a cada vez, até você retornar 0. |
Funções auxiliares embutidas
Seção intitulada “Funções auxiliares embutidas”Extrair subfatia
| Assinatura | Retorna | Descrição |
|---|---|---|
sub(buf, off) |
ArrayBuffer |
Fatia de off até o fim |
sub(buf, off, len) |
ArrayBuffer |
Fatia len bytes a partir de off; um off / len fora do intervalo é automaticamente limitado, sem erro |
Ler inteiro (sem sinal; retorna 0 quando off está fora do intervalo)
| Assinatura | Retorna | Descrição |
|---|---|---|
u8(buf, off) |
number |
Lê 1 byte |
u16be(buf, off) / u16le(buf, off) |
number |
Lê 2 bytes, big-endian / little-endian |
u32be(buf, off) / u32le(buf, off) |
number |
Lê 4 bytes, big-endian / little-endian |
Converter para texto
| Assinatura | Retorna | Descrição |
|---|---|---|
hex(buf) |
string |
Converte para hexadecimal minúsculo |
ascii(buf) |
string |
Lê como texto (adequado para conteúdo ASCII / texto) |
Compressão / descompressão (retorna a entrada inalterada quando não consegue descomprimir; limite por descompressão em torno de 16 MB)
| Assinatura | Retorna | Descrição |
|---|---|---|
gzipMagic(buf) |
boolean |
Se começa com o número mágico gzip |
gunzip(buf) |
ArrayBuffer |
Descompressão gzip |
inflate(buf) |
ArrayBuffer |
Descompressão zlib / deflate |
unzstd(buf) |
ArrayBuffer |
Descompressão zstd |
lz4dtx(buf) |
ArrayBuffer |
Descompressão de fluxo LZ4 por bloco |
Depuração
| Assinatura | Retorna | Descrição |
|---|---|---|
log(...args) |
(nenhum) | Junta os argumentos em uma linha e imprime no painel “Saída de depuração”; um ArrayBuffer é mostrado em hexadecimal |
console.log(...args) |
(nenhum) | Igual a log (conveniente para quem está acostumado com console.log) |
Ambiente de execução
Seção intitulada “Ambiente de execução”- JavaScript padrão: objetos embutidos comuns como
ArrayBuffer, arrays tipados (Uint8Array, etc.),DataView,JSON,Math,RegExp,Dateestão todos disponíveis; quando você precisar de leitura/escrita de bytes de mais baixo nível,new DataView(buf)/new Uint8Array(buf)estão à mão. - Depuração:
log(...)/console.log(...)imprimem no painel “Saída de depuração”; erros e estouros de tempo do script também são impressos lá, rotulados conforme vieram do fluxo de envio↑ou de recebimento↓. - Não são fornecidas as demais capacidades específicas de navegador / Node: métodos de
consolealém delog,TextDecoder/TextEncoder,fetch,setTimeout,require, etc. estão todos indisponíveis. Para ler bytes como texto, useascii(buf). - Cada chamada de
decodetem um limite de tempo de execução: um laço infinito ou um estouro de tempo é interrompido e tratado como “não consumiu nada”, então não trava a ferramenta.
Voltar a Captura por proxy · Relacionado: Inspecionar e decodificar dados · Captura local na placa de rede