1 คะแนน โดย GN⁺ 2025-10-25 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • นักพัฒนาคนหนึ่งได้แบ่งปันเส้นทางทั้งด้านเทคนิคและด้านจิตใจจากการลงมือสร้าง คอมไพเลอร์ ASN.1 (dasn1) ด้วยภาษา D ด้วยตนเอง
  • โปรเจ็กต์นี้มีเป้าหมายเพื่อ รองรับใบรับรอง x.509 และการใช้งาน TLS 1.3 โดยต้องจัดการกับ การเข้ารหัส DER ที่ซับซ้อนของ ASN.1
  • บทความอธิบายอย่างละเอียดถึงความเข้าใจยากของโครงสร้าง ASN.1, ความยากในการทำตามสเปก x.680~x.683, และวิธีใช้เมตาโปรแกรมมิงของภาษา D
  • มีการอธิบายอย่างเป็นรูปธรรมว่าความสามารถของ D อย่าง static import, mixin template, typeof(), alias this ช่วยงานออกแบบการสร้างโค้ดและ AST/IR ได้อย่างไร
  • ผู้เขียนสรุปว่า “ASN.1 นั้นทรมาน แต่ก็เป็นประสบการณ์ที่ได้เรียนรู้อย่างมาก” พร้อมถ่ายทอด ความยากในโลกจริงและความคุ้มค่าของการสร้างคอมไพเลอร์ อย่างตรงไปตรงมา

ภาพรวมและแรงจูงใจของโปรเจ็กต์

  • ผู้เขียนกำลังพัฒนาเฟรมเวิร์ก asynchronous I/O บน D ชื่อ Juptune และเพื่อทำ TLS จึงจำเป็นต้องจัดการการเข้ารหัส ASN.1 DER ด้วยตนเอง
    • การพาร์สโครงสร้างใบรับรอง x.509 ของ TLS จำเป็นต้องเข้าใจวิธีแทนข้อมูลที่ซับซ้อนของ ASN.1
  • โปรเจ็กต์นี้เริ่มจากความท้าทายส่วนตัวเพื่อ การเรียนรู้และความสนุก และได้ดำเนินไปถึงขั้นที่สามารถพาร์สใบรับรองบางรายการได้สำเร็จจริง
  • ASN.1 เป็นมาตรฐานเก่าตั้งแต่ยุค 1990 แต่ยังคง ถูกใช้อย่างแพร่หลายในระบบสมัยใหม่ เช่น TLS, SNMP, LDAP
  • ผู้เขียนกล่าวว่า “ASN.1 ถูกใช้แพร่หลายในโลก แต่ผู้พัฒนาส่วนใหญ่แทบไม่รู้ด้วยซ้ำว่ามันมีอยู่”

ASN.1 คืออะไร

  • ASN.1 (Abstract Syntax Notation One) คือ ภาษาสำหรับนิยามโครงสร้างข้อมูลและการเข้ารหัส เป็นเหมือน “บรรพบุรุษของ Protocol Buffers”
  • มาตรฐานประกอบด้วย รูปแบบไวยากรณ์ (x.680~x.683) และ กฎการเข้ารหัส (BER, CER, DER, PER, XER, JER เป็นต้น)
    • BER: รูปแบบ TLV พื้นฐาน รองรับความยาวแบบไม่สิ้นสุด
    • CER: รูปแบบจำกัดของ BER และใช้ความยาวแบบไม่สิ้นสุดเสมอ
    • DER: ส่วนย่อยแบบกำหนดแน่นอนของ BER, ใช้เป็นมาตรฐานในงานเข้ารหัสลับ
    • PER/OER: การเข้ารหัสแบบบีบอัดในระดับบิต
    • XER/JER: การเข้ารหัสบนพื้นฐาน XML·JSON
  • แม้จะซับซ้อนเพราะมีรูปแบบการเข้ารหัสหลายชนิด แต่ก็มี ความยืดหยุ่นและการขยายต่อได้สูง

ความซับซ้อนของไวยากรณ์ ASN.1

  • มาตรฐานหลักของ ASN.1 คือ x.680 ส่วนสเปกขยาย (x.681~x.683) เขียนด้วย สำนวนเชิงวิชาการที่เข้าใจยากมาก
  • แม้จะสามารถพัฒนาได้ด้วย x.680 เพียงอย่างเดียว แต่ก็ยังยากมากเพราะมี กฎการแปลงความหมายและการแปลงรูปไวยากรณ์ จำนวนมาก
  • x.681 นิยาม ระบบ Information Object Class และรองรับไวยากรณ์เริ่มต้นแบบเฉพาะของตนเอง
    • ตัวอย่าง: CALLED &name [WHO IS &age YEARS OLD]
  • x.682 นิยาม Table Constraint และ x.683 นิยามชนิดแบบ Parameterized
    • เป็นแนวคิดคล้าย generic ของภาษา D โดยสามารถรับทั้งชนิดและค่าเป็นพารามิเตอร์ได้

ความสามารถที่น่าสนใจของ ASN.1

  • ระบบ Constraint: สามารถระบุช่วงค่าหรือขนาดของชนิดได้โดยตรงตอนนิยาม
    • ตัวอย่าง: UInt8 ::= INTEGER (0..255)
    • รองรับตัวดำเนินการ SIZE, UNION(|), INTERSECTION(^)
  • ระบบจัดการเวอร์ชัน: ใช้ OBJECT IDENTIFIER เพื่อแยกเวอร์ชันของโมดูลได้อย่างชัดเจน
    • ตัวอย่าง: id-pkix1-implicit(19) vs id-mod-pkix1-implicit-02(59)
    • ทำให้ระบุโมดูลได้อย่างชัดเจนโดยไม่เกิดการชนกันของชื่อ

เหตุใดภาษา D จึงเหมาะกับการสร้างโค้ด

  • static import ของ D ช่วยป้องกันการชนกันของชื่อ และทำให้สามารถคงชื่อชนิดของ ASN.1 ไว้ได้ตามเดิม
  • ความสามารถ module-local lookup (.Type1) ทำให้จำกัดขอบเขตการค้นหาสัญลักษณ์ได้อย่างชัดเจน
  • ใช้ typeof() เพื่ออนุมานชนิดอัตโนมัติ จึงไม่ต้องจัดการเองด้วยมือในขั้นสร้างโค้ด
  • การอนุญาต trailing comma ช่วยให้การสร้างโค้ดง่ายขึ้น
  • ด้วย การต่อค่าคงที่ตอนคอมไพล์ จึงสามารถประกอบสตริงได้แม้ภายในฟังก์ชัน @nogc

ตัวอย่างการใช้งานความสามารถของภาษา D ในการพัฒนา

AST node บนพื้นฐาน mixin template

  • ใช้ความสามารถ mixin template ของ D เพื่อกำหนดโหนดของต้นไม้ไวยากรณ์ ASN.1 (AST)
    • นำชนิดโหนดแต่ละแบบ (List, Container, OneOf) กลับมาใช้ซ้ำในรูปเทมเพลต
    • ลดความซับซ้อนด้วย การคัดลอกโค้ดในช่วงคอมไพล์ แทนการสืบทอดที่ยุ่งยาก

API แบบเทมเพลตและการตรวจสอบตอนคอมไพล์

  • โหนด Container สามารถมีโหนดย่อยหลายตัว และทำ การตรวจสอบชนิดตอนคอมไพล์
    • เข้าถึงได้อย่างปลอดภัยในรูปแบบ node.getNode!Asn1TagDefaultNode
  • โหนด OneOf ใช้เก็บค่าจากหลายชนิดได้หนึ่งชนิด และรองรับ pattern matching ผ่านฟังก์ชัน match
    • เพราะต้องนิยามตัวจัดการสำหรับทุกชนิด จึงได้ ความปลอดภัยในระดับคอมไพล์

การใช้แพ็กเกจทดลองด้านการจัดการหน่วยความจำของ D

  • ใช้ std.experimental.allocator เพื่อทำ การสร้าง/คืนหน่วยความจำของอ็อบเจ็กต์ในสภาพแวดล้อม @nogc
    • สร้าง allocator แบบกำหนดเองด้วยการผสาน Region, StatsCollector เป็นต้น
    • แต่แพ็กเกจนี้ก็ยังคงอยู่ในสถานะ experimental มานานถึง 10 ปี

ความสามารถ alias this

  • ใช้ alias this เพื่อทำให้ struct wrapper ทำงานเสมือนเป็นฟิลด์ภายในโดยตรง
    • ตัวอย่าง: สามารถแคสต์อย่างกระชับในรูป cast(Asn1ValueReferenceIr)item

version(unittest)

  • ใช้คีย์เวิร์ด version(unittest) เพื่อกำหนด ฟังก์ชันสำหรับการทดสอบโดยเฉพาะ ซึ่งจะไม่ถูกนำไปรวมในบิลด์จริง

test harness ด้วย template + with()

  • ทำเทมเพลตให้กับ logic การทดสอบร่วม และใช้คำสั่ง with() เพื่อ เขียนโค้ดทดสอบให้กระชับ
    • สามารถเรียก T() ได้แทน Harness.T()

ปัญหาหลักที่พบระหว่างการพัฒนา

Value Sequence Syntax

  • ไวยากรณ์ของค่าหลายรูปแบบที่เริ่มด้วย {} มี ความกำกวมตามบริบท
    • ซับซ้อนถึงขั้นมีคอมเมนต์ใน parser ว่า “นี่ไม่สนุกเลย”
  • เพราะแยกการวิเคราะห์ไวยากรณ์ออกจากการวิเคราะห์ความหมาย จึงทำให้ยิ่งจัดการยากขึ้น

ความไม่ชัดเจนของสเปก

  • มีพฤติกรรมบางอย่างที่ เอกสารไม่ได้ระบุไว้อย่างชัดเจน เช่น กฎที่ระบุว่าในบางเงื่อนไขแท็กควรถูกจัดการเป็น EXPLICIT
  • วิธีจัดการเวอร์ชันของโมดูลเองก็ไม่ได้ถูกกำหนดไว้อย่างชัดเจน

ความจำเป็นต้องทำ constraint ซ้ำ 3 ชั้น

  1. สำหรับตรวจสอบไวยากรณ์
  2. สำหรับตรวจสอบความถูกต้องของค่า
  3. สำหรับสร้างโค้ดที่รันไทม์
  • เมื่อต้องจัดการ UNION และ INTERSECTION ก็ทำให้ การประกอบข้อความ error ซับซ้อนขึ้นด้วย

ภาพฝันเรื่อง IR node ที่ไม่เปลี่ยนแปลง

  • เดิมคิดว่าเมื่อแปลง AST ไปเป็น IR แล้วจะไม่ต้องแก้ไขอีก
    แต่ในกระบวนการแปลงความหมาย เช่น AUTOMATIC TAGS ยังจำเป็นต้องเปลี่ยนข้อมูลอยู่

ความซับซ้อนรอบด้านของ ASN.1

  • x.509 ใช้เพียงไวยากรณ์แบบเก่าจึงค่อนข้างง่าย แต่สเปกสมัยใหม่ จำเป็นต้องรองรับ x.681~x.683
    • เพราะเหตุนี้ ASN.1 จึงแทบไม่ถูกใช้นอกเหนือจากวงการวิชาการและเชิงพาณิชย์บางส่วน

ปัญหา ANY DEFINED BY

  • ANY DEFINED BY เป็นโครงสร้างที่ชนิดจะเปลี่ยนไปตามค่าของฟิลด์อื่น
    • dasn1 ไม่ได้รองรับสิ่งนี้โดยตรง แต่ใช้ intrinsic แบบกำหนดเอง Dasn1-Any แทน
    • ทำให้เวลาถอดรหัสจริงยังต้องจัดการด้วยมือ

ภาระข้อมูลที่ล้นเกิน

  • เนื่องจากทำหลายโปรเจ็กต์พร้อมกัน ทั้ง ASN.1, x.68x, x.690, Juptune จึงทำให้ รักษาความเข้าใจบริบทของ codebase ได้ยาก

ความเป็นจริงของการสร้างคอมไพเลอร์

  • มีทั้งการเขียน visitor ของโหนดนับพันตัว โค้ดซ้ำ ๆ และการทำงานที่ต่างกันเพียงเล็กน้อย ซึ่งเป็น งานที่น่าเบื่อและหนักหนา
  • แต่ในแต่ละขั้นตอนก็มี ความรู้สึกสำเร็จและผลลัพธ์ด้านการเรียนรู้สูงมาก
  • ผู้เขียนย้อนมองว่า “คงไม่มีใครใช้มัน แต่ฉันก็ได้ประสบการณ์คอมไพเลอร์ของจริง”
  • ตอนท้ายยังปิดบทความด้วยมุกว่า “อย่าไปยุ่งกับ ASN.1 เลย ชีวิตคุณจะเปลี่ยน”

บทสรุป

  • แม้ทำงานมานาน 1 ปี dasn1 ก็ยังไม่เสร็จสมบูรณ์
    แต่ก็เป็นจุดเปลี่ยนที่ทำให้เข้าใจอย่างลึกซึ้งถึงศักยภาพของภาษา D และความซับซ้อนของ ASN.1
  • ผู้เขียนปิดท้ายอย่างมีอารมณ์ขัน โดยฝันว่าสักวันจะได้เขียนในเรซูเม่ว่า “มีประสบการณ์ทำ ASN.1 compiler + TLS 1.3 implementation” พร้อมย้อนมอง การเติบโตของนักพัฒนาและความจริงของอุตสาหกรรม

1 ความคิดเห็น

 
GN⁺ 2025-10-25
ความคิดเห็นจาก Hacker News
  • สรุปคืออยากพูดถึง ASN.1, ภาษา D และตัวคอมไพเลอร์เอง
    แต่เพราะหาฟอร์แมตที่สม่ำเสมอไม่ได้ เลยรวบรวมความคิดที่เกี่ยวข้องมาเป็นบล็อกโพสต์
    งานยังไม่ถึงกับสมบูรณ์นัก แต่เป็นหัวข้อที่ยากจะเล่าให้สั้น ขออภัยไว้ก่อน

    • ดูเหมือนตัวอย่าง intersection จะไม่ทำงานตามที่ตั้งใจไว้
      ถ้ามองทางคณิตศาสตร์ {0} ∪ ({2} ∩ {4,5,6,7,8}) = {0} ดังนั้นสุดท้ายจึงอนุญาตได้แค่ค่าเดียว
    • พอพูดถึงภาษา D ก็เหมือนเป็นการอัญเชิญ Walter Bright ขึ้นมา
      ส่วนตัวชอบ D มาก แต่ในโลกความเป็นจริง Go กับ Rust ถูกใช้งานแพร่หลายกว่ามาก
    • ผมเองก็เคยทำงานกับข้อมูล ASN.1 โดยเฉพาะงานเกี่ยวกับใบรับรอง ซึ่งทรมานมาก
      เลยเข้าใจความลำบากของผู้เขียนอย่างลึกซึ้ง
    • อ่านแล้วสนุกมากจริงๆ
      ผมรัก D แต่ไม่ได้จับมานานแล้ว
      ยิ่งเคยมีประสบการณ์ทำพาร์เซอร์และการ implement โปรโตคอลมาก่อน ก็ยิ่งรู้สึกน่าสนใจ
    • บล็อกก็เป็นพื้นที่ของตัวเองในท้ายที่สุด ขอให้เขียนต่อในแบบของคุณเอง
  • พอเห็นคำว่า “OMG ASN.1” ก็รู้สึกยินดีมากที่เจอหัวข้อนี้
    ผมยังจำยุคที่อินเทอร์เน็ตกำลังเติบโต และ IETF กำลังพัฒนาโปรโตคอลต่างๆ ได้
    ตอนนั้นภาคธุรกิจยังไม่สนใจอินเทอร์เน็ต และฝั่งวิชาการกับ IETF เป็นคนขับเคลื่อน
    แต่พอบริษัทต่างๆ รู้ว่ามันทำเงินได้ Protocol Wars ก็เริ่มขึ้น
    ASN.1 เป็นผลผลิตของสงครามนั้น และเป็นตัวอย่างของการปะทะกันระหว่างวัฒนธรรมองค์กรกับวัฒนธรรมวิชาการ
    ฝั่งองค์กรอาจเปรียบได้กับ ‘วัฒนธรรมสูตรสำเร็จ’ ส่วนฝั่งวิชาการคือ ‘วัฒนธรรมเชิงฟังก์ชัน’
    ความต่างของวิธีคิดนี้ยังให้แง่มุมกับวัฒนธรรมการพัฒนา AIในปัจจุบันด้วย

    • เมื่อก่อนตอนดูหนัง Father of the Bride แล้วมีบทพูดเรื่องเครือข่าย X.25 โผล่มา ทำให้ผมตกใจมาก
      แค่คิดว่าเราอาจลงเอยด้วยระบบที่อยู่แบบ “CN=wikipedia, OU=org, C=US” แทนอินเทอร์เน็ตทุกวันนี้ก็ขนลุกแล้ว
    • ผมเริ่มคิดว่า “OMG ASN.1” น่าจะเป็นชื่อวงดนตรีวงต่อไปของผม
    • บางส่วนของเรื่องนี้ก็จริง แต่การเรียกผู้เล่นหลักว่า ‘ภาคธุรกิจ’ อาจไม่แม่นนัก
      ที่จริงแล้ว ITU และ ISO ต่างหากที่เป็นศูนย์กลาง
      หลังจากนั้นช่วงปลายยุค 90 ก็ยังมี ‘สงครามโปรโตคอล’ อีกครั้ง และคราวนี้ IETF เป็นฝ่ายแพ้
    • สงครามนี้ยังเป็นส่วนหนึ่งของ**การทำให้อินเทอร์เน็ตเป็นสินค้าเชิงพาณิชย์ระยะแรก (en-shittification)**ด้วย
      ISO มุ่งสู่ความสมบูรณ์แบบจนช้าเกินไป ขณะที่ IETF เดินเร็วด้วยแนวคิดว่า “ค่อยแก้ทีหลัง”
      ผลก็คือเกิดปัญหาโปรโตคอลแข็งตัวเปลี่ยนแปลงยาก
      อีกทั้ง implementation ของ ASN.1 สำหรับ C ในยุค 1990 ก็แย่มากเช่นกัน
    • แก่นสำคัญคือมุมมองขององค์กรในที่นี้ จริงๆ แล้วคือมุมมองแบบเมนเฟรม
  • มีสุภาษิตตุรกีที่พูดว่า “นี่ไม่ใช่ของที่มนุษย์จะใช้กัน!”
    ผมอยากยึดประโยคนี้เป็นคติประจำใจด้านปรัชญาการออกแบบ
    อีกทั้งเหมือนประโยคใน Game of Thrones ที่ว่า “ผู้ตัดสินต้องเป็นคนลงดาบเอง”
    คนที่เขียนสเปกควรต้องลงมือ implement พาร์เซอร์เองด้วย
    ถ้าเปลี่ยนให้สเปกจะผ่านการอนุมัติได้ก็ต่อเมื่อมีพาร์เซอร์ที่ใช้งานได้จริงและมีชุดทดสอบแนบมาด้วย คุณภาพน่าจะดีขึ้นมาก

  • ผมชอบภาษา D มากจริงๆ
    ตอนนี้กำลังเขียนโปรแกรมแก้ไขข้อความสไตล์ vimเอง โดยพึ่งพาแค่ Raylib
    ข้อดีของ D มีดังนี้

    • เขียน unit test ได้จากทุกที่
    • ใช้บล็อก version(unittest) เพื่อจัดการโค้ดเฉพาะสำหรับการทดสอบได้ง่าย
    • มีการรองรับในระดับภาษาที่ยอดเยี่ยม ทั้ง enum, union, assert, การเขียนโปรแกรมเชิงสัญญา เป็นต้น
      ไม่ว่าจะเปิดเอกสารอ่านหรือถาม ChatGPT ผมก็หาทางออกที่สวยงามได้เสมอ
    • D สำหรับผมคือภาษาที่หวานปนขม
      ในเชิงปรัชญาการออกแบบมันเกือบสมบูรณ์แบบ แต่ถ้าเครื่องมือและ ecosystem ไปถึงระดับ Rust หรือ Go ได้ มันคงประสบความสำเร็จกว่านี้มาก
    • ฟีเจอร์ของ D นั้นดี แต่พักหลังมีแนวโน้มว่าภาษาจะรกขึ้นเรื่อยๆ (noisy)
      ส่วนไลบรารีมาตรฐาน Phobos ก็มีจุดจุกจิกเล็กๆ น้อยๆ มากเกินไปจนสุดท้ายผมเลิกใช้
      ตอนนี้มี Phobos V3 รุ่นใหม่กำลังพัฒนาอยู่ แต่เพราะคนทำน้อยก็เลยทั้งหวังทั้งกังวล
  • “ผมเคยพูดหรือว่า ASN.1 มันซับซ้อน?”
    ทั้งสคีมาและฟอร์แมตข้อมูลนั้นซับซ้อน แต่ส่วนใหญ่เป็นความซับซ้อนที่มองข้ามได้
    ผมไม่ได้ใช้สัญกรณ์สคีมาของ ASN.1 และเขียนimplementation ของ DERขึ้นมาเองด้วย C
    ผมคิดว่า DER เป็น encoding มาตรฐานเพียงแบบเดียวที่ใช้งานได้จริง
    นอกจากนี้ยังเคยสร้างฟอร์แมต encoding ของตัวเองอย่าง DSER, SDSER, TER ด้วย
    โครงสร้างอย่าง ANY DEFINED BY ก็ยังใช้อยู่และยังมีประโยชน์มาก
    และเพื่อให้ encoding มีประสิทธิภาพขึ้น ผมยังเพิ่มฟีเจอร์นอกมาตรฐานชื่อ OBJECT IDENTIFIER RELATIVE TO เข้าไปด้วย

  • ผมเองก็เคยสร้าง ASN.1 compiler มาก่อน
    แม้จะ implement แค่บางส่วนของ X.681~X.683 แต่ก็ทำให้สามารถถอดรหัสใบรับรองทั้งใบแบบ recursive ได้ด้วยการเรียก codec แค่ครั้งเดียว
    ASN.1 ไม่ใช่แค่ไวยากรณ์ธรรมดา แต่เป็นระบบชนิดข้อมูลที่ทรงพลัง
    มันถูกประเมินค่าต่ำไป แต่เป็นเทคโนโลยีที่เจ๋งมากจริงๆ

  • ผมเคยสร้าง ASN.1 compiler สำหรับ Swift มาก่อน
    ในโปรเจกต์ ASN1Codable โดยใช้ libasn1 ของ Heimdal
    เพื่อแปลง ASN.1 เป็นJSON ASTและทำให้การพาร์เซอร์ง่ายขึ้น

    • ใน README ของ libasn1 มีความรู้สึกแบบเกลียด ASN.1 แบบเก็บอาการแทรกอยู่
      คำว่า “แปลงเป็น JSON กันเถอะ” ฟังดูเหมือนเสียงร้องของนักพัฒนาที่บอบช้ำมาแล้ว 😄
  • แปลกดีที่การทำงานกับ ASN.1 ให้ความรู้สึกสนุก
    สักวันหนึ่งผมก็อยากลองสร้าง ASN.1 compiler สำหรับ Rust เองบ้าง
    implementation ของ Rust ตอนนี้ส่วนใหญ่ยังเป็นแบบ derive macro หรือเชื่อมต่อกันด้วยมือ ซึ่งน่าเสียดาย

  • โดยทั่วไปเวลา implement มาตรฐาน เรามักทำฟีเจอร์ได้ 80% ในเวลา 20%
    แต่ 20% ที่เหลือของ ASN.1 อาจใช้เวลาทั้งชีวิตก็ได้

  • เมื่อก่อนผมเคยขยาย ASN.1 parser ในโค้ดเบสของ Netscape เพื่อรองรับPKCS#12
    แม้จะรู้ลึกเรื่องมาตรฐาน RSA และนิยาม ASN.1 จนแอบเสียดายอยู่บ้าง
    แต่ก็ขอคารวะต่อความอึดและความมาโซนิดๆของคนเขียนบล็อก

    • จากประสบการณ์แบบนั้น คงมีวีรกรรมพัฒนาแบบสมรภูมิรบเยอะแน่ๆ