- Bir geliştirici, D diliyle bir ASN.1 derleyicisini (
dasn1) bizzat geliştirirken yaşadığı teknik ve zihinsel yolculuğu paylaşıyor
- Proje, x.509 sertifikaları ve TLS 1.3 uygulamasını hedefliyor ve ASN.1’in karmaşık DER kodlama işleme yapısını destekliyor
- Yazı; ASN.1’in yapısal olarak ne kadar zor olduğunu, x.680~x.683 standartlarını uygulamanın güçlüğünü ve D dilinin metaprogramlama olanaklarının nasıl kullanıldığını ayrıntılı biçimde ele alıyor
- D’nin static import, mixin template, typeof(), alias this gibi özelliklerinin kod üretimi ile AST/IR tasarımında nasıl faydalı olduğuna somut örneklerle değiniliyor
- Yazı, “ASN.1 acı verici ama çok şey öğreten bir deneyim” diyerek derleyici geliştirmenin gerçek zorluklarını ve tatminini dürüstçe aktarıyor
Projenin genel görünümü ve motivasyonu
- Yazar, Juptune adlı D tabanlı asenkron I/O çerçevesi üzerinde çalışıyor ve TLS uygulaması için ASN.1 DER kodlamasını doğrudan işlemesi gerektiğini söylüyor
- TLS’deki x.509 sertifika yapısını ayrıştırmak için ASN.1’in karmaşık veri gösterim biçimini anlamak gerekiyor
- Bu proje, öğrenme ve eğlence amacıyla kişisel bir meydan okuma olarak başlamış ve gerçekten de bazı sertifikaları başarıyla ayrıştırma aşamasına kadar ilerlemiş
- ASN.1, 1990’lardan kalma eski bir standart olsa da bugün hâlâ TLS, SNMP, LDAP gibi modern sistemlerde yaygın biçimde kullanılıyor
- Yazar, “ASN.1 dünyada çok yaygın ama çoğu geliştirici onun varlığından bile habersiz” diyor
ASN.1 nedir
- ASN.1 (Abstract Syntax Notation One), veri yapılarını tanımlamak ve kodlamak için kullanılan bir dil; bir bakıma “Protocol Buffers’ın atası”
- Standart, gösterim kuralları (x.680~x.683) ve kodlama kuralları (BER, CER, DER, PER, XER, JER vb.) olarak ikiye ayrılıyor
- BER: temel TLV biçimi, sonsuz uzunluğu destekler
- CER: BER’in kısıtlı biçimi, her zaman sonsuz uzunluk kullanır
- DER: BER’in deterministik alt kümesi, kriptografide standart olarak kullanılır
- PER/OER: bit düzeyinde sıkıştırılmış kodlama
- XER/JER: XML ve JSON tabanlı kodlama
- Kodlama türlerinin çokluğu sistemi karmaşıklaştırsa da esneklik ve genişletilebilirlik sağlıyor
ASN.1 gösteriminin karmaşıklığı
- ASN.1’in temel standardı x.680’dir; genişletme standartları olan x.681~x.683 ise oldukça ağır ve akademik bir üslupla yazılmıştır
- Yalnızca x.680 ile uygulama yapmak mümkün olsa da anlamsal dönüşüm kuralları ve sözdizimsel varyasyonlar çok olduğu için uygulama zorluğu yüksektir
- x.681, Information Object Class sistemini tanımlar ve kendine özgü bir başlatma sözdizimini destekler
- Örnek:
CALLED &name [WHO IS &age YEARS OLD]
- x.682, Table Constraint; x.683 ise şablonlu (Parameterized) tipleri tanımlar
- Bu, D dilindeki generics’e benzer bir kavramdır; hem tipleri hem de değerleri parametre olarak alabilir
ASN.1’in ilginç özellikleri
- Constraint sistemi: Tip tanımlarken değer aralığı veya boyut doğrudan belirtilebilir
- Örnek:
UInt8 ::= INTEGER (0..255)
SIZE, UNION(|), INTERSECTION(^) işleçlerini destekler
- Sürüm yönetim sistemi:
OBJECT IDENTIFIER ile modül sürümleri açık biçimde ayrıştırılabilir
- Örnek:
id-pkix1-implicit(19) vs id-mod-pkix1-implicit-02(59)
- Böylece ad çakışması olmadan modüller net biçimde tanımlanabilir
D dilinin kod üretimi için avantajları
- D’deki static import, ad çakışmalarını önler ve ASN.1 tip adlarını olduğu gibi korumayı mümkün kılar
- Modül yerel arama (
.Type1) özelliği, sembol aramasını açık şekilde sınırlar
typeof() sayesinde tipler otomatik çıkarılır; kod üretiminde elle yönetim gerekmez
- Sondaki virgül (trailing comma) desteği kod üretimini basitleştirir
- Derleme zamanı sabit birleştirme sayesinde
@nogc işlevlerinde bile string birleştirme yapılabilir
D dili özellikleriyle yapılan uygulama örnekleri
Mixin template tabanlı AST düğümleri
- D’nin mixin template özelliği kullanılarak ASN.1 sözdizim ağacı (AST) düğümleri tanımlanmış
- Her düğüm tipi (
List, Container, OneOf) şablon olarak yeniden kullanılıyor
- Karmaşık kalıtım yerine derleme zamanında kod kopyalama ile yapı sadeleştiriliyor
Template tabanlı API ve derleme zamanı doğrulama
Container düğümü birden çok alt düğüm içeriyor ve derleme zamanında tip doğrulaması yapıyor
node.getNode!Asn1TagDefaultNode biçiminde güvenli erişim mümkün
OneOf düğümü birden fazla tipten birini tutuyor ve match işleviyle örüntü eşleme destekliyor
- Tüm tip işleyicilerinin tanımlanması zorunlu olduğu için derleme zamanı güvenliği sağlanıyor
D’nin deneysel bellek yönetimi paketinin kullanımı
std.experimental.allocator kullanılarak @nogc ortamında nesne oluşturma ve serbest bırakma gerçekleştirilmiş
Region, StatsCollector gibi bileşenlerle özel ayırıcılar oluşturulmuş
- Ancak bu yapı 10 yıldır hâlâ deneysel durumda
alias this özelliği
alias this kullanılarak sarmalayıcı struct’ların iç alan gibi davranması sağlanmış
- Örnek:
cast(Asn1ValueReferenceIr)item biçiminde daha sade cast kullanımı mümkün
version(unittest)
version(unittest) anahtar sözcüğüyle yalnızca test için işlevler tanımlanıyor; bunlar gerçek build’e dahil edilmiyor
Template + with() kullanarak test harness’i kurma
- Ortak test mantığı template hâline getirilmiş ve
with() deyimiyle daha kısa test kodu yazılmış
Harness.T() yerine T() çağrısı yapılabiliyor
Uygulama sırasında yaşanan başlıca zorluklar
Değer dizisi sözdizimi (Value Sequence Syntax)
{} ile başlayan çeşitli değer sözdizimleri bağlama göre belirsizlik taşıyor
- Ayrıştırıcı yorumlarında “bu eğlenceli değil” denecek kadar karmaşık olduğu belirtiliyor
- Sözdizimsel analiz ile anlamsal analiz ayrıldığı için işleme zorluğu daha da artmış
Belirtimin açık olmaması
- Belirli koşullarda etiketin
EXPLICIT olarak ele alınması gibi belgede açıkça yazmayan davranışlar bulunuyor
- Modül sürümleme yöntemi de net biçimde tanımlanmış değil
Constraint’leri üç kez uygulama gereği
- Sözdizimi doğrulaması için
- Değer geçerliliği doğrulaması için
- Çalışma zamanı kod üretimi için
UNION, INTERSECTION işlenirken hata mesajlarını oluşturmak da karmaşıklaşıyor
Değişmez IR düğümleri yanılsaması
- AST’yi IR’ye dönüştürdükten sonra artık değişiklik gerekmeyeceği düşünülmüş
ancak AUTOMATIC TAGS gibi anlamsal dönüşümlerde veri değişikliği gerekmiş
ASN.1’in baştan sona karmaşıklığı
- x.509 eski sözdizimini kullandığı için görece basit; ancak modern standartlarda x.681~x.683 uygulaması zorunlu
- Bu yüzden ASN.1, akademik ve ticari alanlar dışında neredeyse hiç kullanılmıyor
ANY DEFINED BY sorunu
ANY DEFINED BY, başka bir alanın değerine göre tipi değişen bir yapı
dasn1 bunu uygulamıyor; yerine özel intrinsic Dasn1-Any kullanılıyor
- Gerçek çözümleme sırasında bunun elle işlenmesi gerekiyor
Bilgi yüklenmesi
- ASN.1, x.68x, x.690, Juptune gibi birden fazla projeyle aynı anda uğraşmak kod tabanı bağlamını korumayı zorlaştırmış
Derleyici geliştirmenin gerçeği
- Binlerce düğüm ziyaretçisi, tekrar eden kodlar ve küçük farklarla ayrılan uygulamalar nedeniyle bu iş sıkıcı ve yorucu bir emek gerektiriyor
- Buna rağmen her aşamada büyük bir tatmin ve öğrenme etkisi var
- Yazar, “Muhtemelen kimse kullanmayacak ama gerçek bir derleyici deneyimi kazandım” diye anlatıyor
- Yazı, “ASN.1 ile uğraşmayın, hayatınızı değiştirir” esprisiyle kapanıyor
Sonuç
- Bir yıllık çalışmaya rağmen
dasn1 hâlâ tamamlanmış değil, ancak bu süreç D dilinin potansiyelini ve ASN.1’in karmaşıklığını derinlemesine anlamayı sağlamış
- Yazar, bir gün özgeçmişine “ASN.1 derleyicisi + TLS 1.3 uygulama deneyimi” yazmayı umarken,
yazıyı geliştiricinin büyümesi ve sektörün gerçekleri üzerine mizahi bir geri bakışla bitiriyor
1 yorum
Hacker News görüşleri
Özetle ASN.1, D dili ve derleyicinin kendisi hakkında konuşmak istemiş
Ama tutarlı bir biçim bulamayınca ilgili düşüncelerini toplayıp bir blog yazısı haline getirmiş
Ortaya çıkan şey çok cilalı değil ama kısa geçilmesi zor bir konu olduğu için anlayış bekliyor
intersection example) amaçlandığı gibi çalışmıyor gibi görünüyorMatematiksel olarak bakınca
{0} ∪ ({2} ∩ {4,5,6,7,8}) = {0}olduğundan sonuçta yalnızca tek bir değere izin veriliyorBen de D'yi gerçekten seviyorum ama pratikte Go ve Rust çok daha yaygın kullanılıyor
Yazarın çektiği sıkıntıyla derinden empati kuruyorum
D'yi seviyorum ama uzun zamandır elimi sürmemiştim
Geçmişte parser ve protokol implementasyonu yaptığım için daha da ilgi çekiciydi
“OMG ASN.1” gerçekten görmekten memnun olduğum bir konu
İnternetin büyüdüğü, IETF'nin protokolleri geliştirdiği dönemi hatırlıyorum
O zamanlar şirketler internetle ilgilenmiyordu; işi akademi ve IETF sürüklüyordu
Ama şirketler bunun para getirdiğini fark edince Protocol Wars başladı
ASN.1, o savaşın bir ürünü ve şirket kültürü ile akademik kültürün çatışmasının bir örneği
Şirket tarafını “tarif kültürü”, akademiyi ise “işlev kültürü” diye düşünebilirsiniz
Bu düşünme biçimi farkı bugün yapay zeka geliştirme kültürü için de çıkarımlar sunuyor
İşlerin internet yerine “CN=wikipedia, OU=org, C=US” gibi bir adresleme düzenine evrilebileceğini düşünmek bile ürkütücüydü
Aslında merkezde ITU ve ISO vardı
Sonra 90'ların sonunda bir başka “protokol savaşı” daha yaşandı ve bu kez IETF kaybetti
en-shittification) süreciydiISO mükemmelliği kovalamaktan yavaş kaldı, IETF ise “sonra düzeltiriz” yaklaşımıyla hızlı hareket etti
Bunun sonucunda protokollerin taşlaşması gibi sorunlar ortaya çıktı
Ayrıca 1990'larda C için ASN.1 implementasyonlarının berbat olması da ayrı bir problemdi
Türkçede “Bu insan için yapılmış bir şey değil!” diye bir ifade var
Bunu tasarım felsefesinin mottosu yapmak isterdim
Ayrıca Game of Thrones'taki “Hükmü veren kişi kılıcı da kendisi sallamalıdır” sözü gibi,
spesifikasyonu yazan kişi parser'ı da kendisi implemente etmeli
Gerçekten çalışan bir parser ve testler de birlikte teslim edilmeden spesifikasyon onaylanmasa kalite muhtemelen çok daha yüksek olurdu
D dilini gerçekten seviyorum
Yalnızca Raylib'e bağlı bir vim tarzı metin editörü yazıyorum
D'nin güçlü yanları şunlar
version(unittest)bloklarıyla yalnızca test amaçlı kodu yönetmek kolayBelgeleri okuyunca ya da ChatGPT'ye sorunca hep zarif bir çözüm bulabildim
Tasarım felsefesi açısından neredeyse kusursuz ama araçları ve ekosistemi Rust veya Go düzeyinde olsaydı çok daha başarılı olabilirdi
noisy) hale geliyorPhobos standart kütüphanesinde çok fazla küçük can sıkıntısı vardı, sonunda bıraktım
Yeni sürüm Phobos V3 üzerinde çalışılıyor ama insan kaynağı az olduğu için hem umutluyum hem tedirginim
“ASN.1'in karmaşık olduğunu söylediğim oldu mu?”
Hem şema hem veri biçimi karmaşık ama bunun büyük kısmı göz ardı edilebilir bir karmaşıklık
Ben ASN.1 şema gösterimini kullanmadım; onun yerine doğrudan C ile bir DER implementasyonu yazdım
Standart kodlamalar içinde işe yarar olanın yalnızca DER olduğunu düşünüyorum
Ayrıca DSER, SDSER ve TER gibi kendi kodlama biçimlerimi de yaptım
ANY DEFINED BYgibi yapıları hâlâ faydalı biçimde kullanıyorumVerimli kodlama için OBJECT IDENTIFIER RELATIVE TO adında standart dışı bir özellik bile ekledim
Ben de bir ASN.1 derleyicisi yazdım
X.681~X.683'ün yalnızca bazı özelliklerini implemente ettim ama tek bir codec çağrısıyla tüm sertifikayı özyinelemeli olarak decode edebiliyordu
ASN.1 basit bir sözdiziminden ibaret değil, güçlü bir tür sistemi
Hak ettiği değeri görmüyor ama gerçekten çok havalı bir teknoloji
Bir zamanlar Swift için bir ASN.1 derleyicisi yapmıştım
ASN1Codable projesiydi; Heimdal'in libasn1 kütüphanesini kullanarak
ASN.1'i JSON AST'ye dönüştürüp parse etmeyi basitleştirmiştim
“Bunu JSON'a çevirelim” demek, incinmiş bir geliştiricinin çığlığı gibi geliyor 😄
Garip şekilde ASN.1 ile uğraşmak eğlenceli geliyor
Bir gün Rust için kendi ASN.1 derleyicimi yazmak istiyorum
Şu anki Rust implementasyonlarının çoğu derive makroları ya da elle zincirleme yaklaşımına dayanıyor; bu biraz hayal kırıklığı yaratıyor
Genel olarak bir standardı implemente ederken işin %80'ini zamanın %20'sinde tamamlarsınız ama
ASN.1'in kalan %20'si bir ömür sürebilir
Bir zamanlar Netscape kod tabanındaki ASN.1 parser'ını genişletip PKCS#12 desteği eklemiştim
RSA standardını ve ASN.1 tanımlarını gereğinden fazla iyi öğrenmiş olmaktan pişmanım ama
blog yazarının azmine ve biraz da mazoşizmine saygı duyuyorum