इसे छोड़कर कंटेंट पर जाएं

कस्टम प्रोटोकॉल डिकोडिंग

प्राइवेट या इन-हाउस सॉकेट प्रोटोकॉल कैप्चर किया, जिसे बिल्ट-इन फ़ॉर्मेट पहचान नहीं पाते, और आपके हाथ में सिर्फ़ बाइट्स का ढेर रह जाता है? Custom Decoding से आप टूल को एक छोटी-सी स्क्रिप्ट के ज़रिए इसे पढ़ना सिखा सकते हैं: एक लगातार चलने वाली byte stream को अलग-अलग मैसेज में स्लाइस करें, प्रोटोकॉल हेडर हटाएं, ज़रूरत पड़ने पर डीकंप्रेस करें, और बाकी काम टूल की automatic structured recognition पर छोड़ दें।

यह पेज उन लोगों के लिए है जो खुद एक decoder लिखना चाहते हैं। इसमें बताया गया है कि function कैसे लिखें, यह कब कॉल होता है, और return value का क्या मतलब है।


1. यह क्या है: एक पर-फ़्रेम डिकोडिंग हुक

Section titled “1. यह क्या है: एक पर-फ़्रेम डिकोडिंग हुक”

आप एक डिकोडिंग हुक लिखते हैं, और टूल इसे किसी कनेक्शन के बाइट्स फ़ीड करता है, बार-बार एक ही सवाल पूछते हुए: “यहाँ से शुरू करके, क्या तुम एक पूरा मैसेज स्लाइस कर सकते हो?” आप एक मैसेज स्लाइस करते हैं और बताते हैं कि आपने कितने बाइट्स इस्तेमाल किए; यह बचे हुए हिस्से के साथ फिर पूछता है, और यह तब तक चलता रहता है जब तक पूरी stream स्लाइस नहीं हो जाती।

अगर आपने कभी किसी networking framework में accumulating byte decoder लिखा है, तो मेंटल मॉडल वही है: एक लगातार चलने वाली byte stream दी गई है, और मैसेज की सीमाएं आप खुद तय करते हैं। दो अहम कन्वेंशन:

  • आपको direction की चिंता नहीं करनी: किसी कनेक्शन की send stream और receive stream हर एक अलग-अलग आपके हुक से गुज़रती है, और टूल अपने आप हर स्लाइस किए गए मैसेज को उसकी direction के साथ लेबल कर देता है। आपकी स्क्रिप्ट सिर्फ़ “कैसे स्लाइस करें” संभालती है।
  • स्लाइस होने के बाद आपको rendering नहीं संभालनी: जो भी मैसेज आप पुश करते हैं वह फिर से automatic recognition से गुज़रता है। अगर उसमें protobuf, JSON, या plist है, तो वह आगे structured फ़ॉर्म में parse हो जाता है। आप सिर्फ़ framing, हेडर हटाने, और डीकंप्रेस करने के लिए ज़िम्मेदार हैं।

2. डिकोड function: decode(buf, out)

Section titled “2. डिकोड function: decode(buf, out)”

पूरा decoder एक ही function है:

function decode(buf, out) {
// buf: the byte stream currently pending parse; out: the output collector; return: the number of bytes consumed this time
}

इनपुट, आउटपुट, और return value क्या हैं

Section titled “इनपुट, आउटपुट, और return value क्या हैं”

buf (इनपुट): parse होने के इंतज़ार में मौजूद byte stream

  • टाइप: ArrayBuffer। इसमें बचे हुए वे सभी बाइट्स होते हैं “जहाँ आपने पिछली बार consume करना खत्म किया था, वहाँ से लेकर मौजूदा आखिर तक।”
  • प्रॉपर्टी buf.byteLength: अभी कितने बाइट्स बचे हैं (हर कॉल के साथ यह बढ़ सकता है; इसी से आप तय करते हैं कि एक मैसेज स्लाइस करने लायक पर्याप्त बाइट्स हैं या नहीं)।
  • ⚠️ यह एक raw byte buffer है, Uint8Array नहीं, इसलिए आप बाइट्स को सीधे इंडेक्स नहीं कर सकते (buf[0] undefined देता है)। बाइट्स पढ़ने के लिए सेक्शन 3 के बिल्ट-इन हेल्पर्स (u8 / u16be / sub …) इस्तेमाल करें, या खुद एक view बना लें: new Uint8Array(buf), new DataView(buf)

out (आउटपुट): output collector

  • टाइप: एक साधारण array
  • मेथड out.push(message bytes): एक मैसेज पुश करें; एक ही कॉल में आप कई मैसेज पुश कर सकते हैं।
  • “Message bytes” ये हो सकते हैं: किसी बिल्ट-इन हेल्पर (जैसे sub(...), gunzip(...)) से मिला एक ArrayBuffer, या एक string। बाकी टाइप को इग्नोर कर दिया जाता है।

Return value: आपने कितने बाइट्स consume किए

  • टाइप: integer (number), buf के शुरू से आपने जितने बाइट्स इस्तेमाल किए उतनी संख्या।
  • > 0: आपने आगे से इतने बाइट्स खा लिए और एक मैसेज स्लाइस कर लिया → टूल इन्हें डिस्कार्ड कर देता है और आपको बचे हुए हिस्से के साथ फिर कॉल करता है।
  • 0 / कोई return नहीं / negative number: इसका मतलब है “अभी आगे कोई पूरा मैसेज नहीं है” या “अब और कुछ स्लाइस नहीं किया जा सकता” → टूल लूप रोक देता है और बचे हुए बाइट्स को raw के रूप में दिखाता है।
  • Return value अपने आप buffer की लंबाई के अंदर क्लैंप हो जाती है (overrun रोकने के लिए), लेकिन कृपया वही असली संख्या return करें जितने बाइट्स आपने वाकई इस्तेमाल किए।

यह कब कॉल होता है (lifecycle)

Section titled “यह कब कॉल होता है (lifecycle)”
  • टूल किसी कनेक्शन की send stream पर एक पास और उसकी receive stream पर एक पास चलाता है। हर पास में, decode को लूप में कॉल किया जाता है: हर बार यह आपको बचे हुए unconsumed बाइट्स देता है, आप एक मैसेज स्लाइस करते हैं और consumed संख्या return करते हैं, और यह तब तक चलता है जब तक आप 0 return नहीं करते।
  • कोई मैसेज तभी दिखता है जब उसका consumption confirm हो: जो मैसेज आप पुश करते हैं वह तभी दिखता है जब आप भी > 0 return करते हैं। अगर आप पुश करते हैं लेकिन 0 return करते हैं, तो लूप रुक जाता है और पुश डिस्कार्ड हो जाता है, इसलिए किसी मैसेज को पुश करने के साथ हमेशा यह return करना ज़रूरी है कि उसमें कितने बाइट्स लगे।
  • लूप खत्म होने के बाद, बचे हुए unconsumed बाइट्स खोते नहीं हैं; इन्हें एक raw डेटा के टुकड़े के रूप में दिखाया जाता है (उदाहरण के लिए, किसी अधूरे मैसेज का दूसरा आधा हिस्सा)।
  • हर stream (direction) को एक अलग, बिल्कुल नया script environment मिलता है; send stream और receive stream कभी एक-दूसरे में मिक्स नहीं होतीं।
  • कॉल्स के बीच state बनाए रखने के लिए (जैसे कोई counter, पिछले मैसेज का type, वगैरह), वेरिएबल्स को decode के बाहर डिक्लेयर करें। ये उसी stream के भीतर कॉल्स के बीच बने रहते हैं, और direction बदलने या दोबारा-डिकोड करने पर रीसेट हो जाते हैं।
  • डिकोडिंग पहले से कैप्चर किए गए डेटा पर चलती है, और बार-बार दोबारा चलाई जा सकती है: स्क्रिप्ट एडिट करें, सेव करें, और नए rules के साथ उसी कनेक्शन को दोबारा-डिकोड करने के लिए फिर से क्लिक करें।

एरर से कुछ नहीं टूटता

Section titled “एरर से कुछ नहीं टूटता”
  • अगर स्क्रिप्ट कोई exception थ्रो करती है या एक ही रन टाइमआउट हो जाता है, तो इसे “इस बार कुछ भी consume नहीं हुआ” माना जाता है, बचे हुए बाइट्स raw डेटा के रूप में दिखते हैं, और कैप्चर बिना किसी डेटा लॉस के चलता रहता है
  • यानी, कोई खराब स्क्रिप्ट ज़्यादा से ज़्यादा डिकोड करने में नाकाम हो सकती है, जिससे आपके पास raw बाइट्स रह जाएंगे; यह कनेक्शन को क्रैश नहीं करेगी। बेझिझक एडिट करें।
  • और एरर छिपाए नहीं जाते: exceptions और timeouts नतीजों के ऊपर मौजूद debug output पैनल में दिखते हैं (सेक्शन 5 देखें), यह लेबल के साथ कि ये send या receive stream से आए हैं। इन्हें फ़ॉलो करके सीधे फ़िक्स तक पहुँचें।

3. बिल्ट-इन हेल्पर्स (स्क्रिप्ट में सीधे उपलब्ध)

Section titled “3. बिल्ट-इन हेल्पर्स (स्क्रिप्ट में सीधे उपलब्ध)”

आम काम, बाइट्स पढ़ना, इंटीजर पढ़ना, टेक्स्ट में बदलना, और डीकंप्रेस करना, ये सब बिल्ट-इन हैं, ताकि आपको इन्हें दोबारा बनाना न पड़े:

Category Signature Description
Sub-slice लेना sub(buf, off[, len]) off से स्लाइस करें (आखिर तक जाने के लिए len छोड़ दें), ArrayBuffer return करता है
इंटीजर पढ़ना u8(buf, off) / u16be / u16le / u32be / u32le (buf, off) big-endian / little-endian में unsigned integers पढ़ें
टेक्स्ट में बदलना hex(buf) / ascii(buf) hex string में बदलें / टेक्स्ट के रूप में पढ़ें
कंप्रेशन डिटेक्ट करना gzipMagic(buf) क्या यह gzip है (boolean return करता है)
डीकंप्रेस करना gunzip / inflate / unzstd / lz4dtx (buf) डीकंप्रेस करें; अगर नहीं हो पाता तो बिना एरर के इनपुट ज्यों का त्यों return करता है
Debug log(...args) / console.log(...args) debug पैनल में प्रिंट करें (सेक्शन 5 देखें)

मानक byte read/write क्षमताएं (DataView, Uint8Array, आदि) भी सीधे इस्तेमाल की जा सकती हैं।


① Length prefix [4-byte big-endian length][payload], सबसे सामान्य प्राइवेट प्रोटोकॉल शेप:

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
}

② By delimiter / by line, स्लाइस करना ऐसे मार्कर पर जैसे newline:

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
}

③ पूरा एक साथ प्रोसेस करना, पूरी stream को एक मैसेज मानते हुए, उदाहरण के लिए पूरे को डीकंप्रेस करना:

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
}

④ Type के हिसाब से dispatch करना, हेडर में एक type field के साथ और हर 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
}

एडिटर में इन सभी पैटर्न के लिए तैयार टेम्पलेट्स मौजूद हैं; एक इंसर्ट करें, कुछ नंबर बदलें, और यह चल पड़ता है।

Custom decoder editor: एक decode(buf, out) स्क्रिप्ट लिखें; “Insert template” length prefix / magic signature / delimiter / fixed-length / gzip / dispatch-by-type के लिए तैयार skeletons देता है, साथ ही एक function quick reference भी


5. इसे कैसे इस्तेमाल करें, कैसे डीबग करें

Section titled “5. इसे कैसे इस्तेमाल करें, कैसे डीबग करें”
  • लिखें और सेव करें: decoder editor में बनाएं, नाम दें, और सेव करें; शुरुआत के लिए एक टेम्पलेट इंसर्ट करें, बिल्ट-इन functions का quick reference पास ही मौजूद है।
  • लागू करें: किसी ऐसे कनेक्शन के लिए जिसे आप समझ नहीं पा रहे, राइट-क्लिक करें और “Decode as” → अपना decoder चुनें, और पूरे कनेक्शन का send और receive ट्रैफ़िक तुरंत आपके rules के हिसाब से स्लाइस होकर दिखने लगता है।
  • डीबग करें और वेरिएबल्स इंस्पेक्ट करें: decode के अंदर, log(...) या console.log(...) से कोई भी वैल्यू प्रिंट करें: byte counts, type fields, hex(sub(buf, 0, 8)), वगैरह। आउटपुट “Decode as” के नतीजों के ऊपरDebug output” पैनल में दिखता है, हर लाइन के साथ यह लेबल कि यह send या receive stream से आई है। स्क्रिप्ट की एरर और timeouts भी इसी पैनल में दिखते हैं, साथ ही direction का लेबल भी। इस तरह प्रिंट करके पता लगाना, बिना देखे एडिट करने से कहीं तेज़ है।

    sandbox में पूरा browser / Node console मौजूद नहीं है; सिर्फ़ console.log और उसके बराबर log इस पैनल से जुड़े हैं। TextDecoder, fetch, setTimeout, जैसी चीज़ें उपलब्ध नहीं हैं। बाइट्स को टेक्स्ट के रूप में पढ़ने के लिए ascii(buf) इस्तेमाल करें।

  • एडिट करें और तुरंत असर देखें: दोबारा कैप्चर करने की ज़रूरत नहीं। अपनी स्क्रिप्ट सेव करें, फिर से “Decode as” पर क्लिक करें, और वही कनेक्शन नए rules के साथ तुरंत दोबारा-डिकोड हो जाता है। तब तक iterate करें जब तक यह आपके मन-मुताबिक स्लाइस न करने लगे।
  • structure तक आगे: हर डिकोड किया गया टुकड़ा वापस automatic recognition को सौंपा जाता है, इसलिए protobuf, JSON, और plist आगे structured फ़ॉर्म में parse होते हैं, जिन्हें Inspecting and decoding data के कई views के ज़रिए देखा जा सकता है।
  • Decoders को नाम देकर एक लिस्ट में रखा जा सकता है, जिन्हें कभी भी जोड़ा, एडिट किया, या डिलीट किया जा सकता है।

6. इसका इस्तेमाल कब करें

Section titled “6. इसका इस्तेमाल कब करें”
  • जब आप कोई इन-हाउस या प्राइवेट सॉकेट प्रोटोकॉल कैप्चर करते हैं (एक आम शेप है length prefix + protobuf / JSON / binary) जिसे बिल्ट-इन फ़ॉर्मेट पहचान नहीं पाते, तो एक decoder लिखकर उसे स्लाइस करें और पढ़ने लायक structure में वापस लाएं।
  • जब आप किसी कस्टम हेडर में लिपटे हुए, या कंप्रेशन के एक राउंड से गुज़रे हुए बाइट्स के टुकड़े को वापस पढ़ने लायक कंटेंट में बदलना चाहते हैं।

एक कंसोलिडेटेड quick reference: decode entry point, बिल्ट-इन हेल्पर functions, और runtime environment। जो भी function buf लेता है वह ArrayBuffer या string दोनों को स्वीकार करता है।

decode(buf, out) → number
उद्देश्य decoder का इकलौता entry point, इसे ज़रूर implement करना होगाbuf के आगे से मैसेज स्लाइस करें, उन्हें out में push करें, और इस बार consume किए गए बाइट्स की संख्या return करें।
buf ArrayBuffer, parse होने के इंतज़ार में बची हुई byte stream। इसकी लंबाई के लिए buf.byteLength इस्तेमाल करें; आप बाइट्स को इंडेक्स नहीं कर सकते, नीचे दिए हेल्पर्स या new DataView(buf) / new Uint8Array(buf) इस्तेमाल करें।
out Array, output collector। out.push(bytes) एक मैसेज पुश करता है (bytes एक ArrayBuffer या string होता है; बाकी टाइप इग्नोर होते हैं); आप एक साथ कई पुश कर सकते हैं।
Return number, buf के आगे से consume किए गए बाइट्स। > 0 जारी रहता है; 0 / negative / कोई return नहीं → रुक जाता है, और बचे हुए बाइट्स raw के रूप में दिखते हैं।
Call convention कनेक्शन की send stream और receive stream, दोनों पर एक-एक पास चलाएं; हर पास बार-बार कॉल करता है, हर बार बचे हुए unconsumed बाइट्स पास करते हुए, जब तक आप 0 return नहीं करते।

बिल्ट-इन हेल्पर functions

Section titled “बिल्ट-इन हेल्पर functions”

Sub-slice लेना

Signature Returns Description
sub(buf, off) ArrayBuffer off से आखिर तक स्लाइस करें
sub(buf, off, len) ArrayBuffer off से len बाइट्स स्लाइस करें; रेंज से बाहर का off / len अपने आप क्लैंप हो जाता है, कोई एरर नहीं

इंटीजर पढ़ना (unsigned; off रेंज से बाहर होने पर 0 return करता है)

Signature Returns Description
u8(buf, off) number 1 बाइट पढ़ें
u16be(buf, off) / u16le(buf, off) number 2 बाइट्स पढ़ें, big-endian / little-endian
u32be(buf, off) / u32le(buf, off) number 4 बाइट्स पढ़ें, big-endian / little-endian

टेक्स्ट में बदलना

Signature Returns Description
hex(buf) string lowercase hexadecimal में बदलें
ascii(buf) string टेक्स्ट के रूप में पढ़ें (ASCII / text कंटेंट के लिए उपयुक्त)

Compression / decompression (डीकंप्रेस न हो पाने पर इनपुट ज्यों का त्यों return करता है; एक बार में decompression की सीमा लगभग 16 MB है)

Signature Returns Description
gzipMagic(buf) boolean क्या यह gzip magic number से शुरू होता है
gunzip(buf) ArrayBuffer gzip decompression
inflate(buf) ArrayBuffer zlib / deflate decompression
unzstd(buf) ArrayBuffer zstd decompression
lz4dtx(buf) ArrayBuffer Block LZ4 stream decompression

Debug

Signature Returns Description
log(...args) (none) arguments को एक लाइन में जोड़कर “Debug output” पैनल में प्रिंट करें; एक ArrayBuffer को hexadecimal में दिखाया जाता है
console.log(...args) (none) log जैसा ही (जो console.log के आदी हैं उनके लिए सुविधाजनक)
  • मानक JavaScript: आम बिल्ट-इन ऑब्जेक्ट्स जैसे ArrayBuffer, typed arrays (Uint8Array, आदि), DataView, JSON, Math, RegExp, Date सभी उपलब्ध हैं; जब आपको लो-लेवल byte read/write चाहिए, तो new DataView(buf) / new Uint8Array(buf) हाथ में हैं।
  • Debug: log(...) / console.log(...) “Debug output” पैनल में प्रिंट करते हैं; स्क्रिप्ट की एरर और timeouts भी वहीं प्रिंट होते हैं, साथ ही यह लेबल कि ये send या receive stream से आए हैं।
  • उपलब्ध नहीं हैं बाकी browser / Node-specific क्षमताएं: console के log के अलावा बाकी मेथड, TextDecoder / TextEncoder, fetch, setTimeout, require, वगैरह सभी अनुपलब्ध हैं। बाइट्स को टेक्स्ट के रूप में पढ़ने के लिए ascii(buf) इस्तेमाल करें।
  • हर decode कॉल की एक execution time सीमा होती है: कोई infinite loop या timeout को इंटरप्ट करके “कुछ भी consume नहीं हुआ” माना जाता है, ताकि यह टूल को हैंग न कर दे।

वापस जाएं Proxy capture · संबंधित: Inspecting and decoding data · Local NIC capture