- एक डेवलपर ने D भाषा में ASN.1 compiler (dasn1) को सीधे implement करते हुए अपने तकनीकी और मानसिक सफर को साझा किया
- प्रोजेक्ट का लक्ष्य x.509 certificate और TLS 1.3 implementation है, और यह ASN.1 की जटिल DER encoding processing को support करता है
- लेख में ASN.1 की संरचनात्मक जटिलता, x.680~x.683 specifications को implement करने की कठिनाई, और D भाषा की metaprogramming के उपयोग को विस्तार से समझाया गया है
- D के static import, mixin template, typeof(), alias this जैसी सुविधाएँ code generation और AST/IR design में कैसे उपयोगी रहीं, यह ठोस उदाहरणों के साथ बताया गया है
- लेख ईमानदारी से बताता है कि “ASN.1 दर्दनाक है, लेकिन इससे बहुत सीखने को मिलता है”, और compiler बनाने की व्यावहारिक कठिनाइयों और संतोष को सामने रखता है
प्रोजेक्ट का अवलोकन और प्रेरणा
- लेखक Juptune नाम का D-आधारित asynchronous I/O framework विकसित कर रहे थे, और TLS implementation के लिए ASN.1 DER encoding को सीधे संभालने की जरूरत थी
- TLS के x.509 certificate structure को parse करने के लिए ASN.1 की जटिल data representation पद्धति को समझना जरूरी है
- यह प्रोजेक्ट सीखने और आनंद के लिए एक व्यक्तिगत चुनौती के रूप में शुरू हुआ, और वास्तव में कुछ certificates को सफलतापूर्वक parse करने के चरण तक पहुँचा
- ASN.1 भले 1990 के दशक का पुराना standard हो, लेकिन आज भी TLS, SNMP, LDAP जैसे आधुनिक सिस्टमों में व्यापक रूप से इस्तेमाल होता है
- लेखक कहते हैं, “ASN.1 दुनिया में बहुत इस्तेमाल होता है, लेकिन अधिकांश डेवलपर्स को इसके अस्तित्व का भी पता नहीं होता”
ASN.1 क्या है
- ASN.1 (Abstract Syntax Notation One) data structures को define और encode करने की भाषा है, यानी एक तरह से “Protocol Buffers का पूर्वज”
- standard में notation (x.680~x.683) और encoding rules (BER, CER, DER, PER, XER, JER आदि) शामिल हैं
- BER: मूल TLV format, infinite length support
- CER: BER का restricted रूप, हमेशा infinite length का उपयोग
- DER: BER का deterministic subset, cryptography में standard रूप से उपयोग
- PER/OER: bit-level compressed encoding
- XER/JER: XML·JSON आधारित encoding
- encoding के इतने प्रकार होने से जटिलता बढ़ती है, लेकिन flexibility और extensibility भी बहुत मिलती है
ASN.1 notation की जटिलता
- ASN.1 का मूल standard x.680 है, जबकि extension specifications (x.681~x.683) बहुत कठिन, लगभग अकादमिक शैली में लिखी गई हैं
- सिर्फ x.680 से implementation संभव है, लेकिन semantic transformation rules और syntax variations इतने अधिक हैं कि implementation कठिन हो जाता है
- x.681 Information Object Class system को define करता है, और अपना अलग initialization syntax support करता है
- उदाहरण:
CALLED &name [WHO IS &age YEARS OLD]
- x.682 Table Constraint और x.683 parameterized types को define करते हैं
- यह D भाषा के generics जैसा विचार है, जहाँ type और value दोनों को parameter के रूप में लिया जा सकता है
ASN.1 की रोचक विशेषताएँ
- Constraint system: type define करते समय value range या size को सीधे लिखा जा सकता है
- उदाहरण:
UInt8 ::= INTEGER (0..255)
SIZE, UNION(|), INTERSECTION(^) operators का समर्थन
- Version management system:
OBJECT IDENTIFIER के जरिए module versions को स्पष्ट रूप से अलग किया जा सकता है
- उदाहरण:
id-pkix1-implicit(19) vs id-mod-pkix1-implicit-02(59)
- बिना name collision के modules की स्पष्ट पहचान संभव
D भाषा code generation के लिए क्यों उपयुक्त है
- D का static import name collision को रोकता है और ASN.1 type names को ज्यों का त्यों बनाए रखने देता है
- module-local lookup (.Type1) सुविधा symbol lookup को स्पष्ट सीमा में रखती है
- typeof() से type inference अपने आप हो जाता है, इसलिए code generation में manual management की जरूरत नहीं पड़ती
- trailing comma की अनुमति code generation को सरल बनाती है
- compile-time constant concatenation की वजह से
@nogc functions में भी string composition संभव है
D भाषा की सुविधाओं का उपयोग करते हुए implementation के उदाहरण
mixin template-आधारित AST nodes
- D के mixin template feature का उपयोग करके ASN.1 syntax tree (AST) nodes define किए गए
- हर node type (
List, Container, OneOf) को template के रूप में reuse किया गया
- जटिल inheritance के बजाय compile-time code copying से सरलता मिली
template-आधारित API और compile-time validation
Container node कई child nodes को शामिल करता है, और compile time पर type validation करता है
node.getNode!Asn1TagDefaultNode जैसे रूप में safe access संभव है
OneOf node कई types में से एक को store करता है, और match function के जरिए pattern matching support करता है
- सभी type handlers को define करना अनिवार्य है, इसलिए compile-time safety मिलती है
D के experimental memory management package का उपयोग
std.experimental.allocator का उपयोग करके @nogc environment में object creation/deallocation implement किया गया
Region, StatsCollector जैसी चीजों को मिलाकर custom allocator बनाया गया
- हालांकि यह 10 साल से experimental स्थिति में ही है
alias this feature
alias this का उपयोग करके wrapper struct को internal field की तरह व्यवहार करने दिया गया
- उदाहरण:
cast(Asn1ValueReferenceIr)item जैसे concise casting संभव हुए
version(unittest)
version(unittest) keyword से सिर्फ test के लिए functions define किए गए, जो actual build में शामिल नहीं होते
template + with() का उपयोग करके test harness
- common test logic को template बनाया गया, और
with() statement से संक्षिप्त test code लिखा गया
Harness.T() की जगह T() के रूप में call करना संभव हुआ
implementation के दौरान आई मुख्य कठिनाइयाँ
Value Sequence Syntax
{} से शुरू होने वाले कई value syntaxes context के अनुसार ambiguous हैं
- parser comments में “यह मजेदार नहीं है” जैसा भाव दिखता है, जो इसकी जटिलता बताता है
- syntax analysis और semantic analysis को अलग रखने से इसे संभालना और कठिन हो गया
specification की अस्पष्टता
- कुछ स्थितियों में tag को
EXPLICIT की तरह handle करना चाहिए, जैसे नियमों के बारे में documentation में स्पष्ट रूप से नहीं लिखा गया
- module version management का तरीका भी स्पष्ट रूप से define नहीं है
constraints को तीन बार implement करने की जरूरत
- syntax validation के लिए
- value validity validation के लिए
- runtime code generation के लिए
- UNION, INTERSECTION को संभालते समय error messages बनाना भी जटिल हो जाता है
immutable IR nodes का भ्रम
- लेखक ने सोचा था कि AST को IR में बदलने के बाद उसे बदलने की जरूरत नहीं होगी,
लेकिन AUTOMATIC TAGS जैसी semantic transformation प्रक्रियाओं में data modification की जरूरत पड़ी
ASN.1 की सर्वव्यापी जटिलता
- x.509 सिर्फ पुराना syntax उपयोग करता है इसलिए अपेक्षाकृत सरल है, लेकिन नए specifications के लिए x.681~x.683 का implementation अनिवार्य है
- इसी कारण ASN.1 शैक्षणिक और commercial क्षेत्रों के बाहर लगभग इस्तेमाल नहीं होता
ANY DEFINED BY समस्या
ANY DEFINED BY ऐसी संरचना है जिसमें type किसी दूसरे field के value पर निर्भर करता है
- dasn1 इसे implement नहीं करता, और इसकी जगह custom intrinsic
Dasn1-Any का उपयोग करता है
- वास्तविक decoding के समय इसे manually handle करना पड़ता है
जानकारी का अत्यधिक बोझ
- ASN.1, x.68x, x.690, Juptune जैसे कई projects साथ चलने से codebase का context बनाए रखना मुश्किल हो गया
compiler निर्माण की वास्तविकता
- हजारों node visitors, repetitive code, और मामूली फर्क वाले implementations जैसे काम उबाऊ और बेहद मेहनत वाले थे
- फिर भी हर चरण पर बड़ी उपलब्धि और गहरी सीख का अनुभव हुआ
- लेखक ने याद करते हुए कहा, “शायद कोई इसका इस्तेमाल नहीं करेगा, लेकिन मुझे असली compiler बनाने का अनुभव मिला”
- अंत में उन्होंने मजाक में लिखा, “ASN.1 मत करना, जिंदगी बदल जाएगी”
निष्कर्ष
- एक साल के काम के बाद भी dasn1 अभी अधूरा है,
लेकिन यह D भाषा की क्षमता और ASN.1 की जटिलता को गहराई से समझने का अवसर बना
- कभी अपने resume में “ASN.1 compiler + TLS 1.3 implementation experience” लिखने का सपना देखते हुए,
यह लेख डेवलपर की growth और उद्योग की वास्तविकताओं पर हल्के हास्य के साथ समाप्त होता है
1 टिप्पणियां
Hacker News राय
संक्षेप में कहें तो मैं ASN.1, D भाषा, और खुद compiler के बारे में बात करना चाहता था
लेकिन कोई एकसमान फ़ॉर्मैट नहीं मिला, इसलिए जुड़े हुए विचारों को इकट्ठा करके ब्लॉग पोस्ट में बाँध दिया
यह बहुत परिष्कृत नहीं है, लेकिन विषय ऐसा है कि इसे छोटे में समेटना मुश्किल था, इसलिए कृपया समझें
गणितीय रूप से देखें तो
{0} ∪ ({2} ∩ {4,5,6,7,8}) = {0}है, इसलिए नतीजे में केवल एक ही मान स्वीकार होना चाहिएमुझे व्यक्तिगत रूप से D बहुत पसंद है, लेकिन व्यवहारिक रूप से Go और Rust कहीं ज़्यादा व्यापक रूप से इस्तेमाल होते हैं
लेखक की मेहनत और संघर्ष से गहरी सहानुभूति है
मैं D को पसंद करता हूँ, लेकिन लंबे समय से उससे दूर हूँ
पहले parser और protocol implementation पर काम कर चुका हूँ, इसलिए यह और भी दिलचस्प लगा
“OMG ASN.1” — क्या ही सुखद विषय है
मुझे वह दौर याद है जब इंटरनेट बढ़ रहा था और IETF protocols को आगे बढ़ा रहा था
उस समय कंपनियों की इंटरनेट में ज़्यादा रुचि नहीं थी, और अकादमिक जगत व IETF नेतृत्व कर रहे थे
लेकिन जैसे ही कंपनियों को समझ आया कि इसमें पैसा है, Protocol Wars शुरू हो गए
ASN.1 उसी युद्ध की उपज है और कॉर्पोरेट संस्कृति और अकादमिक संस्कृति की टक्कर का एक उदाहरण भी
कंपनियों को ‘recipe culture’ और अकादमिक जगत को ‘function culture’ के रूप में देखा जा सकता है
सोचने के इन तरीकों का अंतर आज की AI development culture पर भी संकेत देता है
यह सोचकर ही सिहरन होती है कि शायद हम इंटरनेट की जगह “CN=wikipedia, OU=org, C=US” जैसे address system की दिशा में चले गए होते
वास्तव में केंद्र में ITU और ISO थे
बाद में 90 के दशक के अंत में एक और ‘protocol war’ हुआ, और इस बार IETF हार गया
ISO पूर्णता के पीछे पड़ा रहा और धीमा हो गया, जबकि IETF “बाद में ठीक कर लेंगे” वाले रवैये से तेज़ी से आगे बढ़ा
नतीजतन protocols जड़ हो गए
और 1990 के दशक में C के लिए ASN.1 implementations का बहुत खराब होना भी एक समस्या थी
तुर्की की एक कहावत है: “यह इंसान के इस्तेमाल की चीज़ ही नहीं है!”
मैं इसे design philosophy का motto बनाना चाहूँगा
और Game of Thrones की उस पंक्ति की तरह, “जिसने फ़ैसला दिया, वही तलवार चलाए”,
जिसने spec बनाई है, उसी को parser भी implement करना चाहिए
अगर मंज़ूरी की प्रक्रिया ऐसी हो जाए कि काम करने वाला parser और tests साथ में जमा करना अनिवार्य हो, तो गुणवत्ता बहुत बेहतर हो सकती है
मुझे D भाषा बहुत पसंद है
मैं केवल Raylib पर निर्भर रहकर एक vim style text editor खुद implement कर रहा हूँ
D की खूबियाँ ये हैं
version(unittest)blocks से test-only code को संभालना आसान हैdocumentation देखने या ChatGPT से पूछने पर मुझे हमेशा elegant solutions मिले
design philosophy के स्तर पर यह लगभग परफेक्ट है, लेकिन अगर tooling और ecosystem Rust या Go के स्तर के होते, तो यह कहीं ज़्यादा सफल होता
Phobos standard library में छोटी-छोटी असुविधाएँ इतनी ज़्यादा थीं कि मैंने आख़िरकार उसे छोड़ दिया
नया Phobos V3 बन रहा है, लेकिन लोग कम हैं, इसलिए उम्मीद भी है और चिंता भी
“क्या मैंने कभी कहा था कि ASN.1 जटिल है?”
schema और data format दोनों जटिल हैं, लेकिन उनमें से ज़्यादातर जटिलता को अनदेखा किया जा सकता है
मैंने ASN.1 schema notation का उपयोग नहीं किया, बल्कि C में अपना DER implementation लिखा
मुझे लगता है DER standard encodings में अकेला सच में उपयोगी encoding है
मैंने DSER, SDSER, TER जैसे खुद के encoding formats भी बनाए
ANY DEFINED BYजैसी संरचनाएँ अब भी काफ़ी उपयोगी हैं,और efficient encoding के लिए मैंने OBJECT IDENTIFIER RELATIVE TO नाम का एक non-standard feature भी जोड़ा
मैंने भी ASN.1 compiler बनाया है
मैंने X.681~X.683 की केवल कुछ features implement कीं, लेकिन एक ही codec call से पूरे certificate को recursively decode करने लायक बना दिया
ASN.1 सिर्फ़ साधारण grammar नहीं, बल्कि एक शक्तिशाली type system है
इसे कम आंका जाता है, लेकिन यह सच में शानदार तकनीक है
मैंने पहले Swift के लिए ASN.1 compiler बनाया था
ASN1Codable प्रोजेक्ट में Heimdal के libasn1 का उपयोग करके
ASN.1 को JSON AST में बदला, ताकि parsing सरल हो सके
“इसे JSON में बदल देते हैं” सुनने में मानो किसी आहत developer की पुकार लगती है 😄
अजीब तरह से ASN.1 पर काम करना मज़ेदार लगता है
कभी न कभी मैं Rust के लिए अपना ASN.1 compiler बनाना चाहूँगा
इस समय Rust implementations ज़्यादातर derive macros या manual chaining पर आधारित हैं, जो थोड़ा खटकता है
आम तौर पर किसी standard को implement करते समय 80% features 20% समय में पूरी हो जाती हैं,
लेकिन ASN.1 का बाकी 20% शायद पूरी ज़िंदगी ले सकता है
मैंने पहले Netscape codebase के ASN.1 parser को बढ़ाकर PKCS#12 support जोड़ा था
RSA standard और ASN.1 definitions को ज़रूरत से ज़्यादा गहराई से जान लेने का अफ़सोस है,
लेकिन ब्लॉग लेखक की दृढ़ता और थोड़ी-सी masochism को मेरा सम्मान