synta-codegen 0.1.4

ASN.1 schema parser and Rust code generator for the synta library
Documentation
  • Coverage
  • 78.7%
    170 out of 216 items documented3 out of 66 items with examples
  • Size
  • Source code size: 865.74 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 21.87 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 31s Average build duration of successful builds.
  • all releases: 33s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Repository
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • abbra

synta-codegen

Table of Contents generated with DocToc

ASN.1 schema parser and Rust/C code generator for the synta library with full X.680 constraint support.

Overview

synta-codegen parses ASN.1 module definitions (.asn1 files) and generates type-safe, validated Rust code or C code using the synta library. It supports ITU-T X.680 constraints and generates validated newtypes with runtime checks, eliminating the need to manually write encoding/decoding implementations.

Target Languages

  • Rust (default): Generates type-safe Rust code with derive macros and validation
  • C: Generates C header files with struct definitions and encoder/decoder functions using the libcsynta C API

Features

Language & output targets

  • Multi-Language Support - Generate Rust or C code from the same ASN.1 schema
  • Owned or Zero-Copy String Types - StringTypeMode::Borrowed emits OctetStringRef<'a>, BitStringRef<'a>, Utf8StringRef<'a>, etc. for zero-allocation parse-only workloads (Rust)
  • Idiomatic Code - PascalCase types, snake_case fields, proper conventions

Type system & constraints

  • X.680 Constraint Support - Full support for value ranges, size constraints, permitted alphabet, and more
  • ANY Type Support - Support for legacy ANY and ANY DEFINED BY types
  • Validated Newtypes - Generates type-safe wrappers with runtime validation (Rust)
  • DEFAULT Values - Automatic Default trait implementation (Rust)
  • Named Value Constants - Generate constants for INTEGER named values
  • Subtype Definitions - Support for type references with additional constraints
  • PATTERN Regex Validation - Optional regex validation with regex feature flag (Rust only)
  • CONTAINING Validation - Optional runtime validation of encoded types with validate_containing feature (Rust only)

Module system

  • IMPORTS/EXPORTS - Full module dependency support with topological ordering
  • Multi-File Projects - Automatic cross-module use statement generation (Rust) and multi-file output via --output-dir
  • Circular Import Detection - Detects and reports import cycles before code generation (--check-imports)

C build integration

  • Build System Integration - Generate CMakeLists.txt (--cmake) or meson.build (--meson) for C projects
  • C Helper Functions - _init, _validate, and _print helpers for every C type (--with-helpers)

Implementation quality

  • Zero Dependencies - No external crates required for code generation

See ../docs/asn1-support.md for the complete ASN.1 support matrix.

Quick Start

Installation

cargo install synta-codegen

Or add to your Cargo.toml:

[build-dependencies]
synta-codegen = "0.1"

Basic Usage

Command Line (Rust):

synta-codegen schema.asn1 -o generated.rs

Command Line (C):

# Single-file: generate header, then implementation
synta-codegen schema.asn1 --c -o generated.h
synta-codegen schema.asn1 --impl generated.h -o generated.c

# With helper functions (_init, _validate, _print)
synta-codegen schema.asn1 --c --with-helpers -o generated.h

# Multi-file: headers and implementations together
synta-codegen a.asn1 b.asn1 --c --emit both --output-dir ./generated/

# Generate CMake or Meson build files (single-file)
synta-codegen schema.asn1 --cmake -o CMakeLists.txt
synta-codegen schema.asn1 --meson -o meson.build

Library (owned types, default):

use synta_codegen::{parse, generate};

let schema = r#"
    MyModule DEFINITIONS ::= BEGIN
        Person ::= SEQUENCE {
            name UTF8String,
            age INTEGER (0..150)
        }
    END
"#;

let module = parse(schema)?;
let code = generate(&module)?;

Library (zero-copy borrowed types, parse-only workloads):

use synta_codegen::{parse, generate_with_config, CodeGenConfig, StringTypeMode};

let module = parse(schema)?;
let config = CodeGenConfig {
    string_type_mode: StringTypeMode::Borrowed,
    ..Default::default()
};
// Emits Utf8StringRef<'a> instead of Utf8String, struct gains <'a> lifetime
let code = generate_with_config(&module, config)?;

C Code Generation:

use synta_codegen::{parse, generate_c};

let schema = "...";
let module = parse(schema)?;
let c_code = generate_c(&module)?;

See ../docs/api-reference.md for the complete CLI and library API reference, ../docs/rust-generation.md for Rust code generation details, and ../docs/c-generation.md for C code generation and the libcsynta API.

Example

Input ASN.1:

Types DEFINITIONS ::= BEGIN

Percentage ::= INTEGER (0..100)

Protocol ::= INTEGER {tcp(6), udp(17), sctp(132)}

User ::= SEQUENCE {
    name IA5String (SIZE (1..64)),
    age Percentage,
    email IA5String OPTIONAL
}

END

Generated Rust:

/// INTEGER (0..100)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Percentage(u8);

impl Percentage {
    pub fn new(value: u8) -> Result<Self, &'static str> {
        let val: i64 = value as i64;
        if (0..=100).contains(&val) {
            Ok(Percentage(value))
        } else {
            Err("must be in range 0..100")
        }
    }
    pub const fn new_unchecked(value: u8) -> Self { Percentage(value) }
    pub const fn get(&self) -> u8 { self.0 }
    pub fn into_inner(self) -> u8 { self.0 }
}

pub type Protocol = Integer;

impl Protocol {
    pub const TCP: i64 = 6;
    pub const UDP: i64 = 17;
    pub const SCTP: i64 = 132;
}

#[derive(Debug, Clone, PartialEq)]
pub struct User {
    pub name: Name,
    pub age: Percentage,
    pub email: Option<IA5String>,
}

Usage:

let user = User {
    name: Name::new(IA5String::from("Alice"))?,
    age: Percentage::new(25)?,
    email: None,
};

// Validation at compile time and runtime
let invalid = Percentage::new(150); // Err("must be in range 0..100")

Multi-File Projects

synta-codegen supports automatic cross-module use statement generation and multi-file output:

# Rust: generate with crate-relative imports
synta-codegen base_types.asn1 --crate-imports -o src/base_types.rs
synta-codegen user_module.asn1 --crate-imports -o src/user_module.rs

# C: generate headers only into a directory (default)
synta-codegen a.asn1 b.asn1 c.asn1 --c --output-dir ./generated

# C: generate both headers and implementations
synta-codegen a.asn1 b.asn1 c.asn1 --c --emit both --output-dir ./generated

# C: generate build file + all C sources in one invocation
synta-codegen a.asn1 b.asn1 c.asn1 --cmake --output-dir ./generated
synta-codegen a.asn1 b.asn1 c.asn1 --meson --output-dir ./generated

# Validate imports without generating code
synta-codegen a.asn1 b.asn1 c.asn1 --check-imports

Generated Rust use statements:

use crate::base_types::{Percentage, ShortString};

See ../docs/rust-generation.md for the complete multi-module build.rs guide.

Documentation

ASN.1 Reference

  • ASN.1 Support - What ASN.1 syntax is supported and what is not; complete support matrix

Language-Specific Generation

  • Rust Generation - Type mappings, naming, constraint validation, build.rs integration
  • C Generation - Type mappings, libcsynta API reference, memory contract, arena mode

Getting Started

  • Tutorial - Step-by-step guide from basics to real-world examples
  • API Reference - Complete CLI options, examples, and library API

Reference

Supported ASN.1 Constructs

Type Definitions

  • SEQUENCE, SET, CHOICE
  • SEQUENCE OF, SET OF
  • All primitive types (INTEGER, BOOLEAN, OCTET STRING, etc.)

X.680 Constraints

  • Value ranges: INTEGER (0..100)
  • Size constraints: IA5String (SIZE (1..64))
  • Permitted alphabet: IA5String (FROM ("0".."9"))
  • PATTERN constraints: IA5String (PATTERN "[0-9]+") (with optional regex feature)
  • CONTAINING constraints: OCTET STRING (CONTAINING Certificate) (with optional validate_containing feature)
  • Combined constraints: IA5String (SIZE (4..6) FROM ("0".."9"))
  • Union, intersection, complement
  • Inner type constraints

Field Modifiers

  • OPTIONAL fields
  • DEFAULT values
  • Tagged fields ([0] EXPLICIT, [1] IMPLICIT)

Module Features

  • IMPORTS and EXPORTS
  • Type references
  • Nested definitions

See ../docs/asn1-support.md for the complete support matrix.

synta-codegen vs. Manual Implementation

Aspect Manual synta-codegen
Type definitions Manual struct definitions Auto-generated
Constraint validation Manual checks in constructors Auto-generated from schema
OPTIONAL fields Manual Option handling Auto-generated
DEFAULT values Manual Default impl Auto-generated
Type references Manual imports Auto-generated use statements
Maintenance Update code when schema changes Regenerate

Real-World Support

synta-codegen can parse and generate code for:

  • X.509 certificates (PKIX schemas), including SET OF RDN fields with correct 0x31 tag
  • X.509 Certificate Revocation Lists (CRL, RFC 5280)
  • PKCS#10 Certificate Signing Requests (CSR, RFC 2986)
  • OCSP request/response (RFC 6960)
  • RFC 3279 algorithm parameter types (DSA/DH domain params, ECDSA signature values, EC domain params)
  • RFC 5755 Attribute Certificates (AC v2: AttributeCertificate, Holder, AttCertIssuer, etc.)
  • RFC 4211 Certificate Request Message Format (CRMF: CertTemplate, ProofOfPossession, PKIArchiveOptions, etc.)
  • RFC 9810 Certificate Management Protocol v3 (CMP: PKIMessage, PKIHeader, PKIBody, etc.)
  • PKCS#7 / CMS (RFC 5652), PKCS#8 (RFC 5958), PKCS#9 (RFC 2985), PKCS#12 (RFC 7292)
  • Kerberos V5 (RFC 4120), SPNEGO (RFC 4178), FAST (RFC 6113)
  • Merkle Tree Certificates (draft-ietf-tls-merkle-tree-certs)
  • LDAP directory schemas
  • SNMP management information bases
  • 5G/LTE protocol specifications
  • Custom ASN.1 schemas

IMPLICIT-tagged CHOICE variants

When a CHOICE variant is annotated with #[asn1(tag(N, implicit))], the codegen selects the correct decode path based on whether the inner type has a lifetime parameter:

  • Owned types (no lifetime): uses ImplicitTag<T> as before
  • Borrowed types (lifetime 'a): reads the context tag + length and calls <T as DecodeImplicit>::decode_implicit(content_bytes, encoding) directly, avoiding the for<'b> Decode<'b> HRTB that borrowed types cannot satisfy

This is transparent to schema authors: write [N] IMPLICIT SomeType in the schema and the codegen picks the right path automatically.

Note: IMPLICIT tagging of a CHOICE inner type is prohibited by ASN.1 (CHOICE has no single tag to replace). If the inner type of a variant is itself a CHOICE, use EXPLICIT tagging: [N] EXPLICIT ChoiceType. The codegen emits EXPLICIT when the inner type is a CHOICE or a type alias for one.

Development

Run tests:

cargo test -p synta-codegen

Build the CLI:

cargo build --release -p synta-codegen

Contributing

Contributions are welcome! Please see the main synta repository for contribution guidelines.

Current Limitations

  • Table constraints and information objects not yet implemented
  • Some X.680 advanced features not yet supported

See ../docs/limitations.md for details and workarounds.

Optional Features

Regex Validation for PATTERN Constraints

To enable runtime regex validation for PATTERN constraints, add the regex feature when using the generated code:

[dependencies]
synta = { version = "0.1", features = ["regex"] }
regex = "1.10"
once_cell = "1.19"

Example:

Email ::= IA5String (PATTERN "[a-z]+@[a-z]+\\.com")

Generated code (with regex feature):

// Static regex pattern compiled once
static PATTERN_0: Lazy<Regex> = Lazy::new(|| Regex::new(r"[a-z]+@[a-z]+\.com").unwrap());

pub fn new(value: IA5String) -> Result<Self, String> {
    if !PATTERN_0.is_match(value.as_ref()) {
        return Err(format!("value does not match pattern: [a-z]+@[a-z]+\\.com"));
    }
    Ok(Email(value))
}

Without the regex feature, the generated code includes a comment noting that pattern validation is disabled.

Runtime Validation for CONTAINING Constraints

To enable runtime validation for CONTAINING constraints, add the validate_containing feature when using the generated code:

[dependencies]
synta = { version = "0.1", features = ["validate_containing"] }

Example:

EncodedCertificate ::= OCTET STRING (CONTAINING Certificate)

Generated code (with validate_containing feature):

pub fn new(value: OctetString) -> Result<Self, String> {
    // Validate that value contains a valid DER-encoded Certificate
    use synta::der::Decoder;
    let bytes = value.as_ref();
    let mut decoder = Decoder::new(bytes, synta::Encoding::Der)?;
    let _decoded: Certificate = decoder.decode().map_err(|e| {
        format!("invalid Certificate in CONTAINING constraint: {}", e)
    })?;
    // Verify complete consumption
    if !decoder.is_empty() {
        return Err("trailing bytes after Certificate in CONTAINING constraint".into());
    }
    Ok(EncodedCertificate(value))
}

Without the validate_containing feature, the generated code includes a comment noting that CONTAINING validation is disabled. This allows for zero-overhead when validation is not needed.

See Also

License

Licensed under either of:

at your option.