Ga naar inhoud

Eigen protocoldecodering

Een privaat of intern socketprotocol vastgelegd dat de ingebouwde formaten niet herkennen, zodat je met niets anders dan een berg bytes blijft zitten? Met eigen decodering leer je de tool hoe die het moet lezen met een kort script: deel een doorlopende bytestroom op in afzonderlijke berichten, strip de protocolheader, decomprimeer waar nodig, en laat de rest over aan de automatische structurele herkenning van de tool.

Deze pagina is voor iedereen die zelf een decoder wil schrijven. Ze legt uit hoe je de functie schrijft, wanneer die wordt aangeroepen en wat de returnwaarde betekent.


Je schrijft een decodeerhook, en de tool voedt hem de bytes van een verbinding en stelt daarbij steeds opnieuw dezelfde vraag: “Kun je, vanaf hier, één compleet bericht uitsnijden?” Je snijdt een bericht uit en meldt hoeveel bytes je hebt gebruikt; vervolgens vraagt de tool het opnieuw met de rest, en zo verder tot de stroom volledig is opgedeeld.

Als je ooit een accumulerende bytedecoder hebt geschreven in een netwerkframework, is het mentale model hetzelfde: gegeven een doorlopende bytestroom, bepaal je zelf de berichtgrenzen. Twee belangrijke afspraken:

  • Je hoeft je niet druk te maken om de richting: de verzendstroom en de ontvangststroom van een verbinding lopen elk apart door je hook, en de tool labelt elk uitgesneden bericht automatisch met zijn richting. Je script hoeft alleen te bepalen “hoe te snijden.”
  • Zodra iets is uitgesneden, hoef je het niet zelf weer te geven: elk bericht dat je naar buiten duwt, loopt opnieuw door de automatische herkenning. Als het protobuf, JSON of plist bevat, wordt dat verder ontleed tot een gestructureerde vorm. Jij bent alleen verantwoordelijk voor het framen, het strippen van headers en het decomprimeren.

De hele decoder is één functie:

function decode(buf, out) {
// buf: de bytestroom die momenteel wacht op ontleding; out: de outputverzamelaar; return: het aantal bytes dat deze keer is verbruikt
}

Wat de inputs, outputs en returnwaarde zijn

Section titled “Wat de inputs, outputs en returnwaarde zijn”

buf (input): de bytestroom die wacht op ontleding

  • Type: ArrayBuffer. De inhoud bestaat uit alle resterende bytes “vanaf waar je de vorige keer bent gestopt met verbruiken, tot het huidige einde.”
  • Eigenschap buf.byteLength: hoeveel bytes er op dit moment nog over zijn (dit kan bij elke aanroep groeien; hiermee bepaal je of er genoeg is om een bericht uit te snijden).
  • ⚠️ Het is een ruwe bytebuffer, geen Uint8Array, dus je kunt bytes niet direct indexeren (buf[0] geeft undefined). Om bytes te lezen gebruik je de ingebouwde hulpfuncties uit sectie 3 (u8 / u16be / sub …), of maak je zelf een view: new Uint8Array(buf), new DataView(buf).

out (output): de outputverzamelaar

  • Type: een gewone array.
  • Methode out.push(berichtbytes): duw één bericht naar buiten; je kunt er meerdere pushen in één aanroep.
  • “Berichtbytes” kunnen zijn: een ArrayBuffer teruggegeven door een ingebouwde hulpfunctie (zoals sub(...), gunzip(...)), of een string. Andere types worden genegeerd.

Returnwaarde: hoeveel bytes je hebt verbruikt

  • Type: geheel getal (number), het aantal bytes dat je hebt gebruikt vanaf het begin van buf.
  • > 0: je hebt zoveel bytes van de voorkant opgegeten en er een bericht uit gesneden → de tool verwijdert ze en roept je opnieuw aan met de rest.
  • 0 / geen return / negatief getal: betekent “er zit nog geen compleet bericht aan de voorkant” of “er kan niets meer worden uitgesneden” → de tool stopt de lus en toont de resterende bytes als ruwe data.
  • De returnwaarde wordt automatisch beperkt tot de bufferlengte (om overschrijdingen te voorkomen), maar geef alsjeblieft het werkelijke aantal bytes terug dat je daadwerkelijk hebt gebruikt.

Wanneer het wordt aangeroepen (levenscyclus)

Section titled “Wanneer het wordt aangeroepen (levenscyclus)”
  • De tool doorloopt één keer de verzendstroom en één keer de ontvangststroom van een verbinding. Bij elke doorloop wordt decode in een lus aangeroepen: elke keer krijg je de resterende onverbruikte bytes, snijd je één bericht uit en geef je terug hoeveel er zijn verbruikt, enzovoort tot je 0 teruggeeft.
  • Een bericht verschijnt pas zodra het verbruik ervan is bevestigd: het bericht dat je pusht, verschijnt pas zodra je ook > 0 teruggeeft. Als je pusht maar 0 teruggeeft, stopt de lus en wordt de push weggegooid, dus het pushen van een bericht moet altijd gepaard gaan met het teruggeven van hoeveel bytes het kostte.
  • Nadat de lus is beëindigd, gaan resterende onverbruikte bytes aan het einde niet verloren; ze worden getoond als één stuk ruwe data (bijvoorbeeld de tweede helft van een onvolledig bericht).
  • Elke stroom (richting) krijgt een aparte, gloednieuwe scriptomgeving; de verzendstroom en de ontvangststroom lekken nooit in elkaar over.
  • Om status tussen aanroepen te bewaren (een teller, het type van het vorige bericht, enzovoort), declareer je de variabelen buiten decode. Ze blijven bestaan tussen aanroepen binnen dezelfde stroom, en worden gereset wanneer de richting verandert of je opnieuw decodeert.
  • Decoderen werkt op al vastgelegde data, en kan herhaaldelijk opnieuw worden uitgevoerd: bewerk het script, sla op en klik opnieuw om dezelfde verbinding met de nieuwe regels opnieuw te decoderen.
  • Als het script een uitzondering gooit of een enkele run te lang duurt, wordt dat behandeld als “deze keer niets verbruikt,” worden de resterende bytes getoond als ruwe data, en blijft het vastleggen gewoon doorlopen zonder dataverlies.
  • Met andere woorden, het ergste dat een kapot script kan doen is niet decoderen en je met ruwe bytes achterlaten; het laat de verbinding niet crashen. Bewerk vrijuit.
  • En fouten worden niet weggeslikt: uitzonderingen en time-outs worden getoond in het debugoutput-paneel boven de resultaten (zie sectie 5), gelabeld met of ze afkomstig zijn van de verzendstroom of de ontvangststroom . Volg ze recht naar de oplossing.

3. Ingebouwde hulpfuncties (direct beschikbaar in het script)

Section titled “3. Ingebouwde hulpfuncties (direct beschikbaar in het script)”

De veelvoorkomende taken, bytes lezen, gehele getallen lezen, omzetten naar tekst en decomprimeren, zijn allemaal ingebouwd, zodat je ze niet opnieuw hoeft uit te vinden:

Categorie Signatuur Beschrijving
Sub-slice nemen sub(buf, off[, len]) Snijd vanaf off (laat len weg om tot het einde te gaan), geeft ArrayBuffer terug
Geheel getal lezen u8(buf, off) / u16be / u16le / u32be / u32le (buf, off) Lees unsigned gehele getallen in big-endian / little-endian
Omzetten naar tekst hex(buf) / ascii(buf) Omzetten naar een hexstring / lezen als tekst
Compressie detecteren gzipMagic(buf) Of het gzip is (geeft een boolean terug)
Decomprimeren gunzip / inflate / unzstd / lz4dtx (buf) Decomprimeren; als het niet lukt, wordt de input ongewijzigd teruggegeven zonder foutmelding
Debug log(...args) / console.log(...args) Printen naar het debugpaneel (zie sectie 5)

De standaard byte-lees-/schrijfmogelijkheden (DataView, Uint8Array, enz.) zijn ook direct bruikbaar.


① Lengteprefix [4-byte big-endian lengte][payload], de meest voorkomende vorm van een privaat protocol:

function decode(buf, out) {
if (buf.byteLength < 4) return 0 // de lengteheader is nog niet volledig binnen, wachten
const total = 4 + u32be(buf, 0) // heel bericht = 4-byte header + payload
if (buf.byteLength < total) return 0 // het hele bericht is nog niet volledig binnen, wachten
out.push(sub(buf, 4, total - 4)) // strip de header, geef de payload door aan automatische herkenning
return total // verbruik dit bericht, ga verder met het volgende uitsnijden
}

② Op scheidingsteken / per regel, frames uitsnijden op een markering zoals een newline:

function decode(buf, out) {
const i = ascii(buf).indexOf('\n')
if (i < 0) return 0 // nog geen newline, wachten
out.push(sub(buf, 0, i)) // duw deze regel naar buiten (zonder de newline)
return i + 1 // verbruik de newline ook mee
}

③ Alles in één keer verwerken, waarbij de hele stroom als één bericht wordt behandeld, bijvoorbeeld door het geheel te decomprimeren:

function decode(buf, out) {
out.push(gzipMagic(buf) ? gunzip(buf) : buf) // als het gzip is, pak het dan uit
return buf.byteLength // verbruik alles, de lus eindigt meteen
}

④ Verdelen naar type, met een typeveld in de header en verschillende afhandeling per type:

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) // het eerste byte is het berichttype
const body = sub(buf, 5, total - 5)
out.push(type === 2 ? gunzip(body) : body) // type 2 is gecomprimeerd
return total
}

De editor heeft kant-en-klare sjablonen voor al deze patronen; voeg er een in, pas een paar getallen aan, en het draait.

Eigen decoder-editor: schrijf een decode(buf, out)-script; “Sjabloon invoegen” biedt kant-en-klare skeletten voor lengteprefix / magic signature / scheidingsteken / vaste lengte / gzip / verdelen naar type, plus een snelle functiereferentie


  • Schrijven en opslaan: maak, benoem en sla op in de decoder-editor; voeg een sjabloon in om te beginnen, met de ingebouwde functies in een snelle referentie binnen handbereik.
  • Toepassen: klik voor een verbinding waar je niets van begrijpt met rechts en kies “Decoderen als” → je decoder, en het verzend- en ontvangstverkeer van de hele verbinding wordt meteen opgedeeld en getoond volgens jouw regels.
  • Debuggen en variabelen inspecteren: gebruik binnen decode log(...) of console.log(...) om elke waarde te printen: aantallen bytes, typevelden, hex(sub(buf, 0, 8)), enzovoort. De output verschijnt in het paneel “Debugoutputboven de resultaten van “Decoderen als”, elke regel gelabeld met of hij afkomstig is van de verzendstroom of de ontvangststroom . Scriptfouten en time-outs verschijnen in hetzelfde paneel, ook gelabeld met richting. Print en lokaliseer op deze manier; het is veel sneller dan blindelings bewerken.

    De sandbox heeft geen volledige browser-/Node-console; alleen console.log en het equivalente log zijn aangesloten op dit paneel. TextDecoder, fetch, setTimeout en dergelijke zijn niet beschikbaar. Om bytes als tekst te lezen, gebruik ascii(buf).

  • Bewerken en het live zien: geen nieuwe capture nodig. Sla je script op, klik opnieuw op “Decoderen als”, en dezelfde verbinding wordt ter plekke opnieuw gedecodeerd met de nieuwe regels. Itereer tot het precies zo snijdt als je wilt.
  • Verder naar de structuur: elk gedecodeerd stuk wordt teruggegeven aan de automatische herkenning, zodat protobuf, JSON en plist verder worden ontleed tot een gestructureerde vorm, te bekijken via de meerdere weergaven in Inspecteren en decoderen.
  • Decoders kunnen worden benoemd en bewaard als een lijst, op elk moment toe te voegen, te bewerken of te verwijderen.

  • Wanneer je een intern of privaat socketprotocol vastlegt (een veelvoorkomende vorm is lengteprefix + protobuf / JSON / binair) dat de ingebouwde formaten niet herkennen, schrijf je een decoder om het op te delen en te herstellen tot een leesbare structuur.
  • Wanneer je een stuk bytes, verpakt in een eigen header of gecomprimeerd, weer wilt terugbrengen naar leesbare inhoud.

Een geconsolideerde snelle referentie: het decode-toegangspunt, de ingebouwde hulpfuncties en de runtimeomgeving. Elke functie die buf accepteert, accepteert zowel een ArrayBuffer als een string.

decode(buf, out) → number
Doel Het enige toegangspunt van de decoder, moet worden geïmplementeerd. Snijd berichten uit vanaf het begin van buf, push ze naar out, en geef het aantal deze keer verbruikte bytes terug.
buf ArrayBuffer, de resterende bytestroom die wacht op ontleding. Gebruik buf.byteLength voor de lengte; je kunt bytes niet indexeren, gebruik de hulpfuncties hieronder of new DataView(buf) / new Uint8Array(buf).
out Array, de outputverzamelaar. out.push(bytes) duwt één bericht naar buiten (bytes is een ArrayBuffer of een string; andere types worden genegeerd); je kunt er meerdere tegelijk pushen.
Return number, de vanaf het begin van buf verbruikte bytes. > 0 gaat door; 0 / negatief / geen return → stoppen, en de resterende bytes worden getoond als ruwe data.
Aanroepconventie Doorloop één keer de verzendstroom en één keer de ontvangststroom van de verbinding; elke doorloop roept herhaaldelijk aan, waarbij elke keer de resterende onverbruikte bytes worden meegegeven, tot je 0 teruggeeft.

Sub-slice nemen

Signatuur Retourneert Beschrijving
sub(buf, off) ArrayBuffer Snijd vanaf off tot het einde
sub(buf, off, len) ArrayBuffer Snijd len bytes vanaf off; een off / len buiten bereik wordt automatisch beperkt, geen fout

Geheel getal lezen (unsigned; geeft 0 terug wanneer off buiten bereik ligt)

Signatuur Retourneert Beschrijving
u8(buf, off) number Lees 1 byte
u16be(buf, off) / u16le(buf, off) number Lees 2 bytes, big-endian / little-endian
u32be(buf, off) / u32le(buf, off) number Lees 4 bytes, big-endian / little-endian

Omzetten naar tekst

Signatuur Retourneert Beschrijving
hex(buf) string Omzetten naar hexadecimaal in kleine letters
ascii(buf) string Uitlezen als tekst (geschikt voor ASCII-/tekstinhoud)

Compressie / decompressie (geeft de input ongewijzigd terug als decomprimeren niet lukt; enkele decompressie is begrensd op ongeveer 16 MB)

Signatuur Retourneert Beschrijving
gzipMagic(buf) boolean Of het begint met het gzip magic number
gunzip(buf) ArrayBuffer gzip-decompressie
inflate(buf) ArrayBuffer zlib-/deflate-decompressie
unzstd(buf) ArrayBuffer zstd-decompressie
lz4dtx(buf) ArrayBuffer Blok-LZ4-streamdecompressie

Debug

Signatuur Retourneert Beschrijving
log(...args) (geen) Voeg de argumenten samen tot één regel en print naar het paneel “Debugoutput”; een ArrayBuffer wordt getoond in hexadecimaal
console.log(...args) (geen) Hetzelfde als log (handig voor wie gewend is aan console.log)
  • Standaard JavaScript: gangbare ingebouwde objecten zoals ArrayBuffer, typed arrays (Uint8Array, enz.), DataView, JSON, Math, RegExp, Date zijn allemaal beschikbaar; wanneer je lager-niveau byte-lees-/schrijftoegang nodig hebt, staan new DataView(buf) / new Uint8Array(buf) klaar.
  • Debug: log(...) / console.log(...) printen naar het paneel “Debugoutput”; scriptfouten en time-outs worden daar ook geprint, gelabeld met of ze afkomstig zijn van de verzendstroom of de ontvangststroom .
  • Niet beschikbaar zijn de overige browser-/Node-specifieke mogelijkheden: console-methoden anders dan log, TextDecoder / TextEncoder, fetch, setTimeout, require, enz. zijn allemaal niet beschikbaar. Om bytes als tekst te lezen, gebruik ascii(buf).
  • Elke decode-aanroep heeft een uitvoeringstijdlimiet: een oneindige lus of een time-out wordt onderbroken en behandeld als “niets verbruikt,” zodat de tool nooit vastloopt.

Terug naar Proxy-capture · Gerelateerd: Inspecteren en decoderen · Lokale NIC-capture