Décodage de protocole personnalisé
Vous avez capturé un protocole socket privé ou maison que les formats intégrés ne reconnaissent pas, et il ne vous reste qu’un tas d’octets ? Le décodage personnalisé vous permet d’apprendre à l’outil comment le lire à l’aide d’un court script : découper un flux d’octets continu en messages individuels, retirer l’en-tête du protocole, décompresser au besoin, et confier le reste à la reconnaissance structurée automatique de l’outil.
Cette page s’adresse à quiconque veut écrire son propre décodeur. Elle explique comment écrire la fonction, quand elle est appelée, et ce que signifie la valeur de retour.
1. De quoi il s’agit : un hook de décodage par trame
Section intitulée « 1. De quoi il s’agit : un hook de décodage par trame »Vous écrivez un hook de décodage, et l’outil lui fournit les octets d’une connexion en posant sans cesse la même question : « À partir d’ici, peux-tu découper un message complet ? » Vous découpez un message et indiquez combien d’octets vous avez utilisés ; il pose de nouveau la question avec le reste, et ainsi de suite jusqu’à ce que le flux soit entièrement découpé.
Si vous avez déjà écrit un décodeur d’octets à accumulation dans un framework réseau, le modèle mental est le même : à partir d’un flux d’octets continu, vous décidez vous-même des frontières des messages. Deux conventions essentielles :
- Vous ne vous souciez pas du sens : le flux d’envoi et le flux de réception d’une connexion passent chacun séparément par votre hook, et l’outil étiquette automatiquement chaque message découpé avec son sens. Votre script ne gère que « comment découper ».
- Une fois découpé, vous ne gérez pas le rendu : chaque message que vous poussez repasse par la reconnaissance automatique. S’il contient du protobuf, du JSON ou du plist, cela est analysé plus loin sous forme structurée. Vous êtes seulement responsable du découpage en trames, du retrait des en-têtes et de la décompression.
2. La fonction decode : decode(buf, out)
Section intitulée « 2. La fonction decode : decode(buf, out) »Tout le décodeur tient dans une seule fonction :
function decode(buf, out) { // buf: the byte stream currently pending parse; out: the output collector; return: the number of bytes consumed this time}Ce que sont les entrées, les sorties et la valeur de retour
Section intitulée « Ce que sont les entrées, les sorties et la valeur de retour »buf (entrée) : le flux d’octets en attente d’analyse
- Type :
ArrayBuffer. Son contenu, ce sont tous les octets restants « depuis là où vous avez terminé votre dernière consommation, jusqu’à la fin actuelle ». - Propriété
buf.byteLength: combien d’octets restent en ce moment (elle peut croître à chaque appel ; c’est ainsi que vous jugez s’il y en a assez pour découper un message). - ⚠️ Il s’agit d’un tampon d’octets brut, pas d’un
Uint8Array, vous ne pouvez donc pas indexer les octets directement (buf[0]renvoieundefined). Pour lire des octets, utilisez les fonctions intégrées de la section 3 (u8/u16be/sub…), ou enveloppez vous-même une vue :new Uint8Array(buf),new DataView(buf).
out (sortie) : le collecteur de sortie
- Type : un tableau simple.
- Méthode
out.push(octets du message): pousse un message ; vous pouvez en pousser plusieurs en un seul appel. - Les « octets du message » peuvent être : un
ArrayBufferrenvoyé par une fonction intégrée (commesub(...),gunzip(...)), ou une chaîne de caractères. Les autres types sont ignorés.
Valeur de retour : combien d’octets vous avez consommés
- Type : entier (
number), le nombre d’octets que vous avez utilisés depuis le début debuf. > 0: vous avez consommé ce nombre d’octets en tête et découpé un message → l’outil les écarte et vous rappelle avec le reste.0/ aucunreturn/ nombre négatif : signifie « il n’y a pas encore de message complet en tête » ou « rien de plus à découper » → l’outil arrête la boucle et affiche les octets restants tels quels (bruts).- La valeur de retour est automatiquement bornée à la longueur du tampon (pour éviter les dépassements), mais veillez à renvoyer le nombre réel d’octets que vous avez effectivement utilisés.
Quand elle est appelée (cycle de vie)
Section intitulée « Quand elle est appelée (cycle de vie) »- L’outil effectue un passage sur le flux d’envoi d’une connexion et un sur son flux de réception. À chaque passage,
decodeest appelée en boucle : à chaque fois, elle vous remet les octets restants non consommés, vous découpez un message et renvoyez le nombre consommé, et ainsi de suite jusqu’à ce que vous renvoyiez 0. - Un message n’apparaît qu’une fois sa consommation confirmée : le message que vous poussez ne s’affiche que si vous renvoyez aussi une valeur > 0. Si vous poussez mais renvoyez 0, la boucle s’arrête et le push est écarté, donc pousser un message doit toujours aller de pair avec renvoyer le nombre d’octets qu’il a pris.
- Une fois la boucle terminée, les octets de fin non consommés ne sont pas perdus ; ils sont affichés comme un unique bloc de données brutes (par exemple, la seconde moitié d’un message incomplet).
État et idempotence
Section intitulée « État et idempotence »- Chaque flux (sens) reçoit un environnement de script tout neuf et distinct ; le flux d’envoi et le flux de réception ne se mélangent jamais.
- Pour conserver un état d’un appel à l’autre (un compteur, le type du message précédent, etc.), déclarez les variables en dehors de
decode. Elles persistent d’un appel à l’autre au sein d’un même flux, et se réinitialisent lorsque le sens change ou que vous redécodez. - Le décodage s’exécute sur des données déjà capturées, et peut être relancé à volonté : modifiez le script, enregistrez, puis cliquez de nouveau pour redécoder la même connexion avec les nouvelles règles.
Les erreurs ne cassent rien
Section intitulée « Les erreurs ne cassent rien »- Si le script lève une exception ou qu’une exécution unique dépasse le temps imparti, cela est traité comme « rien consommé cette fois », les octets restants sont affichés comme données brutes, et la capture continue sans aucune perte de données.
- Autrement dit, le pire qu’un script cassé puisse faire, c’est ne pas réussir à décoder, vous laissant des octets bruts ; il ne fera pas planter la connexion. Modifiez sans crainte.
- Et les erreurs ne sont pas escamotées : les exceptions et les dépassements de délai s’affichent dans le panneau de sortie de débogage au-dessus des résultats (voir section 5), étiquetés selon qu’ils proviennent du flux d’envoi
↑ou de réception↓. Suivez-les droit vers le correctif.
3. Fonctions intégrées (directement disponibles dans le script)
Section intitulée « 3. Fonctions intégrées (directement disponibles dans le script) »Les tâches courantes, lire des octets, lire des entiers, convertir en texte et décompresser, sont toutes intégrées, vous n’avez donc pas à les réinventer :
| Catégorie | Signature | Description |
|---|---|---|
| Extraire une sous-tranche | sub(buf, off[, len]) |
Découpe à partir de off (omettez len pour aller jusqu’à la fin), renvoie un ArrayBuffer |
| Lire un entier | u8(buf, off) / u16be / u16le / u32be / u32le (buf, off) |
Lit des entiers non signés en big-endian / little-endian |
| Convertir en texte | hex(buf) / ascii(buf) |
Convertit en chaîne hexadécimale / lit comme texte |
| Détecter la compression | gzipMagic(buf) |
S’il s’agit de gzip (renvoie un booléen) |
| Décompresser | gunzip / inflate / unzstd / lz4dtx (buf) |
Décompresse ; en cas d’échec, renvoie l’entrée inchangée sans erreur |
| Déboguer | log(...args) / console.log(...args) |
Affiche dans le panneau de débogage (voir section 5) |
Les capacités standard de lecture/écriture d’octets (DataView, Uint8Array, etc.) sont également utilisables directement.
4. Exemples
Section intitulée « 4. Exemples »① Préfixe de longueur [longueur sur 4 octets big-endian][charge utile], la forme la plus courante des protocoles privés :
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}② Par délimiteur / par ligne, en découpant les trames sur un marqueur tel qu’un retour à la ligne :
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}③ Traiter le tout d’un coup, en considérant l’ensemble du flux comme un seul message, par exemple pour tout décompresser :
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}④ Aiguiller par type, avec un champ de type dans l’en-tête et un traitement différent selon le 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) // 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’éditeur propose des modèles prêts à l’emploi pour tous ces schémas ; insérez-en un, ajustez quelques nombres, et ça tourne.

5. Comment l’utiliser, comment le déboguer
Section intitulée « 5. Comment l’utiliser, comment le déboguer »- Écrire et enregistrer : créez, nommez et enregistrez dans l’éditeur de décodeur ; insérez un modèle pour démarrer, avec les fonctions intégrées à portée de main dans un aide-mémoire.
- Appliquer : pour une connexion que vous n’arrivez pas à comprendre, faites un clic droit et choisissez « Décoder comme » → votre décodeur, et tout le trafic d’envoi et de réception de la connexion est aussitôt découpé et affiché selon vos règles.
- Déboguer et inspecter les variables : à l’intérieur de
decode, utilisezlog(...)ouconsole.log(...)pour afficher n’importe quelle valeur : compteurs d’octets, champs de type,hex(sub(buf, 0, 8)), etc. La sortie apparaît dans le panneau « Sortie de débogage » au-dessus des résultats de « Décoder comme », chaque ligne étiquetée selon qu’elle provient du flux d’envoi↑ou de réception↓. Les erreurs de script et les dépassements de délai s’affichent dans le même panneau, également étiquetés avec le sens. Affichez et localisez ainsi ; c’est bien plus rapide que d’éditer à l’aveugle.Le bac à sable ne dispose pas d’un
consolecomplet façon navigateur / Node ; seulsconsole.loget son équivalentlogsont reliés à ce panneau.TextDecoder,fetch,setTimeout, et consorts ne sont pas disponibles. Pour lire des octets comme texte, utilisezascii(buf). - Éditer et voir le résultat en direct : inutile de recapturer. Enregistrez votre script, cliquez de nouveau sur « Décoder comme », et la même connexion est redécodée sur-le-champ avec les nouvelles règles. Itérez jusqu’à ce que le découpage vous convienne.
- Puis place à la structure : chaque bloc décodé est rendu à la reconnaissance automatique, de sorte que protobuf, JSON et plist sont analysés plus loin sous forme structurée, consultables via les multiples vues de Inspection et décodage des données.
- Les décodeurs peuvent être nommés et conservés sous forme de liste, à ajouter, modifier ou supprimer à tout moment.
6. Quand l’utiliser
Section intitulée « 6. Quand l’utiliser »- Lorsque vous capturez un protocole socket maison ou privé (une forme courante étant préfixe de longueur + protobuf / JSON / binaire) que les formats intégrés ne reconnaissent pas, écrivez un décodeur pour le découper et le restituer sous une structure lisible.
- Lorsque vous voulez retransformer un bloc d’octets enveloppé dans un en-tête personnalisé, ou passé par un tour de compression, en contenu lisible.
7. Référence de l’API
Section intitulée « 7. Référence de l’API »Un aide-mémoire consolidé : le point d’entrée de décodage, les fonctions intégrées et l’environnement d’exécution. Chaque fonction qui prend buf accepte soit un ArrayBuffer, soit une chaîne de caractères.
Point d’entrée de décodage
Section intitulée « Point d’entrée de décodage »decode(buf, out) → number| Objet | Le seul point d’entrée du décodeur, doit être implémenté. Découpez des messages depuis le début de buf, push-ez-les dans out, et renvoyez le nombre d’octets consommés cette fois. |
buf |
ArrayBuffer, le flux d’octets restant en attente d’analyse. Utilisez buf.byteLength pour sa longueur ; vous ne pouvez pas indexer les octets, utilisez les fonctions ci-dessous ou new DataView(buf) / new Uint8Array(buf). |
out |
Array, le collecteur de sortie. out.push(bytes) pousse un message (bytes est un ArrayBuffer ou une chaîne ; les autres types sont ignorés) ; vous pouvez en pousser plusieurs d’un coup. |
| Retour | number, les octets consommés depuis le début de buf. > 0 continue ; 0 / négatif / aucun retour → arrêt, et les octets restants sont affichés tels quels (bruts). |
| Convention d’appel | Un passage sur le flux d’envoi et un sur le flux de réception de la connexion ; chaque passage appelle de façon répétée, en passant à chaque fois les octets restants non consommés, jusqu’à ce que vous renvoyiez 0. |
Fonctions intégrées
Section intitulée « Fonctions intégrées »Extraire une sous-tranche
| Signature | Renvoie | Description |
|---|---|---|
sub(buf, off) |
ArrayBuffer |
Découpe de off jusqu’à la fin |
sub(buf, off, len) |
ArrayBuffer |
Découpe len octets à partir de off ; un off / len hors limites est automatiquement borné, sans erreur |
Lire un entier (non signé ; renvoie 0 lorsque off est hors limites)
| Signature | Renvoie | Description |
|---|---|---|
u8(buf, off) |
number |
Lit 1 octet |
u16be(buf, off) / u16le(buf, off) |
number |
Lit 2 octets, big-endian / little-endian |
u32be(buf, off) / u32le(buf, off) |
number |
Lit 4 octets, big-endian / little-endian |
Convertir en texte
| Signature | Renvoie | Description |
|---|---|---|
hex(buf) |
string |
Convertit en hexadécimal minuscule |
ascii(buf) |
string |
Lit comme texte (adapté au contenu ASCII / texte) |
Compression / décompression (renvoie l’entrée inchangée en cas d’échec de décompression ; plafond d’environ 16 Mo par décompression unique)
| Signature | Renvoie | Description |
|---|---|---|
gzipMagic(buf) |
boolean |
S’il commence par le nombre magique gzip |
gunzip(buf) |
ArrayBuffer |
Décompression gzip |
inflate(buf) |
ArrayBuffer |
Décompression zlib / deflate |
unzstd(buf) |
ArrayBuffer |
Décompression zstd |
lz4dtx(buf) |
ArrayBuffer |
Décompression de flux LZ4 par blocs |
Débogage
| Signature | Renvoie | Description |
|---|---|---|
log(...args) |
(aucun) | Assemble les arguments en une ligne et l’affiche dans le panneau « Sortie de débogage » ; un ArrayBuffer est affiché en hexadécimal |
console.log(...args) |
(aucun) | Identique à log (pratique pour ceux qui ont l’habitude de console.log) |
Environnement d’exécution
Section intitulée « Environnement d’exécution »- JavaScript standard : les objets intégrés courants tels que
ArrayBuffer, les tableaux typés (Uint8Array, etc.),DataView,JSON,Math,RegExp,Datesont tous disponibles ; quand vous avez besoin d’une lecture/écriture d’octets bas niveau,new DataView(buf)/new Uint8Array(buf)sont à portée de main. - Débogage :
log(...)/console.log(...)affichent dans le panneau « Sortie de débogage » ; les erreurs de script et les dépassements de délai y sont également affichés, étiquetés selon qu’ils proviennent du flux d’envoi↑ou de réception↓. - Ne sont pas fournies les autres capacités propres au navigateur / à Node : les méthodes de
consoleautres quelog,TextDecoder/TextEncoder,fetch,setTimeout,require, etc. sont toutes indisponibles. Pour lire des octets comme texte, utilisezascii(buf). - Chaque appel à
decodea un plafond de temps d’exécution : une boucle infinie ou un dépassement de délai est interrompu et traité comme « rien consommé », de sorte que l’outil ne se bloque pas.
Retour à Capture par proxy · À voir aussi : Inspection et décodage des données · Capture locale sur carte réseau