1 pontos por GN⁺ 2025-10-25 | 1 comentários | Compartilhar no WhatsApp
  • Um desenvolvedor compartilha a jornada técnica e mental de implementar por conta própria um compilador ASN.1 em D (dasn1)
  • O projeto tem como objetivo a implementação de x.509 e TLS 1.3, com suporte ao complexo processamento de codificação DER do ASN.1
  • O texto aborda em detalhes a dificuldade estrutural do ASN.1, a complexidade de implementar as especificações x.680~x.683 e formas de usar metaprogramação em D
  • Explica concretamente como recursos de D como static import, templates mixin, typeof() e alias this foram úteis na geração de código e no design de AST/IR
  • O autor descreve o ASN.1 como “doloroso, mas muito enriquecedor”, relatando com franqueza as dificuldades reais e as recompensas de criar um compilador

Visão geral e motivação do projeto

  • O autor está desenvolvendo um framework de I/O assíncrono em D chamado Juptune, e precisou lidar diretamente com a codificação ASN.1 DER para implementar TLS
    • Para fazer o parsing da estrutura de certificados x.509 no TLS, foi necessário entender a forma complexa como o ASN.1 representa dados
  • O projeto começou como um desafio pessoal voltado a aprendizado e diversão, e já avançou até o ponto de conseguir fazer o parsing de alguns certificados com sucesso
  • Embora o ASN.1 seja um padrão antigo, da década de 1990, ele ainda é usado amplamente em sistemas modernos como TLS, SNMP e LDAP
  • O autor comenta que “ASN.1 é amplamente usado no mundo, mas a maioria dos desenvolvedores nem sabe que ele existe”

O que é ASN.1

  • ASN.1 (Abstract Syntax Notation One) é uma linguagem para definir e codificar estruturas de dados, uma espécie de “ancestral do Protobuf”
  • O padrão é composto pela notação (x.680~x.683) e pelas regras de codificação (BER, CER, DER, PER, XER, JER etc.)
    • BER: formato TLV básico, com suporte a comprimento indefinido
    • CER: variante restrita de BER, usando sempre comprimento indefinido
    • DER: subconjunto determinístico de BER, usado como padrão em criptografia
    • PER/OER: codificação compactada em nível de bits
    • XER/JER: codificação baseada em XML e JSON
  • A grande variedade de codificações torna tudo complexo, mas também oferece alta flexibilidade e extensibilidade

A complexidade da notação ASN.1

  • O padrão-base do ASN.1 é o x.680, e as especificações de extensão (x.681~x.683) são escritas em um estilo acadêmico extremamente difícil
  • Mesmo só com x.680 já é possível implementar bastante coisa, mas a presença de muitas regras de transformação semântica e variações sintáticas eleva muito a dificuldade
  • O x.681 define o sistema de Information Object Class e oferece uma sintaxe própria de inicialização
    • Ex.: CALLED &name [WHO IS &age YEARS OLD]
  • O x.682 define Table Constraint, e o x.683 define tipos parametrizados (Parameterized)
    • É um conceito parecido com genéricos em D, aceitando tanto tipos quanto valores como parâmetros

Recursos interessantes do ASN.1

  • Sistema de restrições (Constraint): permite declarar diretamente o intervalo de valores ou o tamanho na definição do tipo
    • Ex.: UInt8 ::= INTEGER (0..255)
    • Suporte a operadores SIZE, UNION(|) e INTERSECTION(^)
  • Sistema de versionamento: usa OBJECT IDENTIFIER para distinguir claramente versões de módulos
    • Ex.: id-pkix1-implicit(19) vs id-mod-pkix1-implicit-02(59)
    • Permite identificar módulos de forma clara sem colisão de nomes

Por que D favorece a geração de código

  • O static import de D evita conflitos de nomes e permite preservar exatamente os nomes de tipos ASN.1
  • O recurso de busca local ao módulo (.Type1) limita com clareza a resolução de símbolos
  • Com typeof(), o tipo pode ser inferido automaticamente, dispensando gestão manual na geração de código
  • O suporte a vírgula final (trailing comma) simplifica a geração de código
  • Graças à concatenação de constantes em tempo de compilação, é possível montar strings até mesmo em funções @nogc

Exemplos de implementação com recursos da linguagem D

Nós de AST baseados em template mixin

  • O recurso de mixin template de D foi usado para definir nós da árvore sintática abstrata (AST) do ASN.1
    • Cada tipo de nó (List, Container, OneOf) é reutilizado como template
    • Em vez de uma herança complexa, a implementação foi simplificada com cópia de código em tempo de compilação

API baseada em templates e validação em tempo de compilação

  • O nó Container inclui vários nós filhos e realiza validação de tipos em tempo de compilação
    • Isso permite acesso seguro no formato node.getNode!Asn1TagDefaultNode
  • O nó OneOf armazena um entre vários tipos e oferece pattern matching por meio da função match
    • Como todos os handlers de tipo precisam ser definidos, isso garante segurança em tempo de compilação

Uso do pacote experimental de gerenciamento de memória de D

  • Com std.experimental.allocator, foi possível implementar criação e liberação de objetos em ambiente @nogc
    • Um alocador customizado foi montado combinando elementos como Region e StatsCollector
    • Ainda assim, o pacote continua em estado experimental há 10 anos

Recurso alias this

  • Com alias this, o autor fez com que structs wrapper se comportassem como seus campos internos
    • Ex.: é possível fazer casts concisos como cast(Asn1ValueReferenceIr)item

version(unittest)

  • A palavra-chave version(unittest) foi usada para definir funções exclusivas para testes, que não entram no build real

Harness de testes com templates + with()

  • A lógica comum de testes foi transformada em template, e a instrução with() permitiu escrever testes de forma mais concisa
    • Em vez de Harness.T(), é possível chamar simplesmente T()

Principais dificuldades enfrentadas na implementação

Sintaxe de sequência de valores (Value Sequence Syntax)

  • As várias formas de sintaxe de valor que começam com {} são ambíguas dependendo do contexto
    • A complexidade é tanta que um comentário no parser chega a dizer “isso não é divertido”
  • Como a análise sintática e a análise semântica foram separadas, a dificuldade de tratamento aumentou

Falta de clareza na especificação

  • Existem comportamentos não explicitamente descritos na documentação, como regras que exigem tratar certas tags como EXPLICIT em condições específicas
  • O próprio método de versionamento de módulos também não é definido com clareza

Necessidade de implementar restrições em triplo

  1. Para validação sintática
  2. Para validação de valores
  3. Para geração de código em tempo de execução
  • Ao lidar com UNION e INTERSECTION, até mesmo montar mensagens de erro fica complexo

A ilusão de nós IR imutáveis

  • O autor imaginava que, depois de converter a AST em IR, não seria mais necessário modificar nada,
    mas transformações semânticas como AUTOMATIC TAGS exigiram alterações nos dados

A complexidade total do ASN.1

  • O x.509 usa apenas uma sintaxe antiga e por isso é relativamente simples, mas as especificações mais novas exigem implementar x.681~x.683
    • Por isso, o ASN.1 quase não é usado fora de contextos acadêmicos ou comerciais específicos

O problema de ANY DEFINED BY

  • ANY DEFINED BY é uma estrutura cujo tipo muda conforme o valor de outro campo
    • O dasn1 não implementa isso e usa em seu lugar um intrinsic customizado Dasn1-Any
    • Na decodificação real, isso precisa ser tratado manualmente

Sobrecarga de informação

  • Ao tocar simultaneamente em ASN.1, x.68x, x.690, Juptune e outros projetos, tornou-se difícil manter o contexto do codebase

A realidade de fazer um compilador

  • O trabalho envolve milhares de visitantes de nós, código repetitivo e implementações com diferenças mínimas, sendo por isso cansativo e árduo
  • Ainda assim, cada etapa trouxe grande sensação de realização e aprendizado
  • O autor relembra que “ninguém vai usar isso, mas eu ganhei uma experiência real de compilador”
  • No fim, fecha o texto com a piada: “não mexa com ASN.1, isso muda a sua vida”

Conclusão

  • Mesmo após um ano de trabalho, o dasn1 ainda está incompleto,
    mas a experiência serviu para compreender profundamente o potencial de D e a complexidade do ASN.1
  • O texto termina com humor, sonhando com o dia em que o autor poderá colocar no currículo a experiência de “compilador ASN.1 + implementação de TLS 1.3”,
    revisitando ao mesmo tempo o crescimento do desenvolvedor e a realidade da indústria

1 comentários

 
GN⁺ 2025-10-25
Comentários do Hacker News
  • Resumindo, eu queria falar sobre ASN.1, a linguagem D e compiladores em si
    Mas não consegui encontrar um formato consistente, então juntei essas ideias em um post de blog
    O nível de acabamento não é alto, mas peço compreensão, porque é um tema difícil de tratar brevemente

    • Parece que o exemplo de interseção (intersection example) não está funcionando como pretendido
      Matematicamente, {0} ∪ ({2} ∩ {4,5,6,7,8}) = {0}, então no fim só um único valor é permitido
    • Falar da linguagem D é praticamente invocar Walter Bright
      Pessoalmente eu gosto muito de D, mas, na prática, Go e Rust são muito mais usados
    • Eu também já trabalhei com dados ASN.1 e, especialmente em tarefas relacionadas a certificados, foi bem doloroso
      Tenho muita empatia pelo sofrimento do autor
    • Gostei muito de ler o texto
      Adoro D, mas faz muito tempo que não mexo com ela
      Já tive experiência com parsers e implementação de protocolos, então achei ainda mais interessante
    • No fim das contas, o blog é o seu espaço, então espero que você continue do seu jeito
  • “OMG ASN.1”, que tema bom de rever
    Lembro da época em que a internet estava crescendo e a IETF fazia os protocolos evoluírem
    Naquele tempo, as empresas não tinham interesse na internet, e a academia e a IETF puxavam isso
    Mas quando as empresas perceberam que dava dinheiro, começaram as Protocol Wars
    ASN.1 é um produto dessa guerra e um exemplo do choque entre cultura corporativa e cultura acadêmica
    Dá para comparar empresas a uma “cultura de receita” e a academia a uma “cultura de funcionalidade”
    Essa diferença de mentalidade também traz reflexões para a cultura de desenvolvimento de IA hoje

    • Uma vez eu estava vendo o filme Father of the Bride e tomei um susto quando apareceu uma conversa sobre redes X.25
      Pensar que poderíamos ter seguido por um sistema de endereços como “CN=wikipedia, OU=org, C=US”, em vez da internet, dá arrepios
    • Fiquei pensando que “OMG ASN.1” devia ser o nome da minha próxima banda
    • Parte da história está certa, mas chamar o ator principal de “empresas” é um pouco impreciso
      Na prática, ITU e ISO estavam no centro disso
      Depois, no fim dos anos 90, houve outra “guerra de protocolos”, e dessa vez a IETF perdeu
    • Essa guerra também foi parte do processo de comercialização inicial (en-shittification) da internet
      A ISO buscava a perfeição e ficou lenta, enquanto a IETF se movia rápido com a atitude de “a gente corrige depois”
      Como resultado, enfrentaram o problema de protocolos que acabavam se cristalizando
      Outro problema foi que as implementações de ASN.1 em C nos anos 1990 eram péssimas
    • O ponto central é que a perspectiva corporativa era, no fundo, uma perspectiva de mainframe
  • Há um ditado turco que diz: “Isto não é uma coisa para seres humanos usarem!”
    Eu gostaria de adotar isso como lema de filosofia de design
    E, assim como a fala de Game of Thrones de que “quem profere a sentença deve empunhar a espada”,
    quem cria a especificação deveria implementar o parser pessoalmente
    Se as coisas mudassem para que uma especificação só fosse aprovada com um parser funcional e testes junto, a qualidade provavelmente melhoraria muito

  • Eu gosto muito da linguagem D
    Estou implementando por conta própria um editor de texto estilo vim dependendo apenas de Raylib
    As vantagens de D são as seguintes

    • Dá para escrever unit tests em qualquer lugar
    • O bloco version(unittest) facilita muito gerenciar código exclusivo de teste
    • O suporte da linguagem para enum, union, assert, programação por contrato etc. é excelente
      Consultando a documentação ou perguntando ao ChatGPT, eu sempre conseguia encontrar uma solução elegante
    • D é uma linguagem agridoce para mim
      Em termos de filosofia de design ela chega perto da perfeição, mas, se as ferramentas e o ecossistema estivessem no nível de Rust ou Go, teria feito muito mais sucesso
    • Os recursos de D são bons, mas há uma tendência de a linguagem ficar mais verbosa (noisy) com o tempo
      A biblioteca padrão Phobos tem tantos pequenos incômodos que eu acabei desistindo dela
      A nova versão, Phobos V3, está em andamento, mas como há pouca gente envolvida, fico meio esperançoso e meio preocupado
  • “Eu já disse alguma vez que ASN.1 é complexa?”
    Tanto a notação de schema quanto o formato de dados são complexos, mas grande parte disso é uma complexidade que dá para ignorar
    Eu não uso a notação de schema ASN.1 e escrevi uma implementação de DER em C por conta própria
    Acho que DER é a única codificação padrão que realmente presta
    Também criei formatos de codificação próprios como DSER, SDSER e TER
    Estruturas como ANY DEFINED BY ainda continuam sendo úteis para mim,
    e, para uma codificação eficiente, também adicionei um recurso não padronizado chamado OBJECT IDENTIFIER RELATIVE TO

  • Eu também já fiz um compilador ASN.1
    Implementei apenas parte dos recursos de X.681~X.683, mas fiz com que fosse possível decodificar um certificado inteiro recursivamente com uma única chamada de codec
    ASN.1 não é só uma gramática simples, e sim um sistema de tipos poderoso
    É subestimada, mas é uma tecnologia realmente muito boa

  • No passado eu fiz um compilador ASN.1 para Swift
    No projeto ASN1Codable, usando o libasn1 do Heimdal,
    converti ASN.1 para uma AST JSON para simplificar o parsing

    • No README do libasn1 dá para sentir um certo desgosto velado por ASN.1
      Falar “vamos converter para JSON” soa, no fundo, como o grito de um desenvolvedor ferido 😄
  • Estranhamente, trabalhar com ASN.1 parece divertido
    Um dia eu gostaria de fazer meu próprio compilador ASN.1 para Rust
    As implementações atuais em Rust em geral dependem de macros derive ou de encadeamento manual, o que é uma pena

  • Em geral, ao implementar um padrão, você consegue concluir 80% dos recursos em 20% do tempo,
    mas os 20% restantes de ASN.1 podem levar uma vida inteira

  • No passado, eu estendi o parser ASN.1 do código-base do Netscape para suportar PKCS#12
    Acabei conhecendo os padrões da RSA e as definições de ASN.1 profundamente demais para o meu gosto,
    mas deixo meu respeito à persistência e a um leve masoquismo do autor do blog

    • Com uma experiência dessas, parece que você deve ter mesmo muitas histórias de guerra de desenvolvimento