synta 0.2.6

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# Library Comparison — Parse Only

```bash
cargo bench -p synta-bench --bench comparison --features bench-compare
```

(`library_comparison` Criterion group)

## Implementations

Each library takes a fundamentally different approach to the same problem:

| Implementation        | Parse strategy                                                                     |
| --------------------- | ---------------------------------------------------------------------------------- |
| **synta**             | Typed RFC 5280 parse; issuer/subject/extensions stored as `RawDer<'a>` (borrowed byte span — no DN traversal, no allocation at parse time) |
| **cryptography-x509** | PyCA Rust core; deferred-everything — raw DER byte offsets, decode only on first field access |
| **x509-parser**       | nom-based; fully eager typed parse — every DN, extension, and value decoded during parse |
| **x509-cert**         | RustCrypto; fully eager typed parse — same approach as x509-parser but using the `der` crate |
| **NSS**               | `CERT_NewTempCertificate`; formats issuer/subject Distinguished Names into C strings *at parse time*; arena allocation |
| **rust-openssl**      | OpenSSL `d2i_X509` via the `openssl` crate's safe Rust bindings                   |
| **ossl**              | OpenSSL `d2i_X509` via partial Rust FFI (Kryoptic project)                        |

The dominant cost in X.509 parsing is Distinguished Name traversal: a certificate's issuer
and subject each contain a SEQUENCE OF SET OF SEQUENCE with per-attribute OID lookup. synta
defers this entirely by storing the Name as a `RawDer<'a>` — a pointer+length into the
original input with no decoding. cryptography-x509 takes a similar deferred approach. The
nom-based and RustCrypto libraries decode Names eagerly. NSS goes further and formats them
into C strings, which is the dominant fraction of its 16× parse overhead.

## Traditional X.509 Certificates

Five PKITS end-entity certificates (AllCertificatesNoPoliciesTest2,
AllCertificatesSamePoliciesTest×2, AllCertificatesanyPolicyTest11, AnyPolicyTest14EE),
914–968 bytes each:

| Certificate              | synta       | cryptography-x509 | x509-parser | x509-cert   | NSS        |
| ------------------------ | ----------- | ----------------- | ----------- | ----------- | ---------- |
| cert_00 (NoPolicies)     | 483.55 ns   | 1426.4 ns         | 1826.3 ns   | 3032.1 ns   | 7883.6 ns  |
| cert_01 (SamePolicies-1) | 485.60 ns   | 1466.8 ns         | 2094.8 ns   | 3238.4 ns   | 7941.7 ns  |
| cert_02 (SamePolicies-2) | 484.66 ns   | 1448.2 ns         | 2135.0 ns   | 3183.5 ns   | 8017.1 ns  |
| cert_03 (anyPolicy)      | 480.88 ns   | 1441.0 ns         | 1981.2 ns   | 3180.3 ns   | 7908.4 ns  |
| cert_04 (AnyPolicyEE)    | 476.91 ns   | 1441.1 ns         | 1990.7 ns   | 3147.8 ns   | 7755.7 ns  |
| **Average**              | **482 ns**  | **1445 ns**       | **2006 ns** | **3156 ns** | **7901 ns**|

rust-openssl and ossl averaged **15.4 µs** and **16.1 µs** respectively across the five
certs (not shown per-cert to keep the table readable).

synta is **3.0× faster** than cryptography-x509, **4.2× faster** than x509-parser,
**6.6× faster** than x509-cert, and **16× faster** than NSS.

The variation across certs (476–486 ns for synta) reflects differences in extension lists:
certs with more policy extensions contain more bytes in the `extensions` `RawDer` field's
tag+length header, which is the only part synta reads at parse time.

## Field-Level String Formatting

Parse + format issuer DN, subject DN, signature OID, not-before, not-after as strings.
Average over 5 PyCA PKITS test certificates (914–968 bytes):

| Library | Time | vs synta |
|---------|------|----------|
| **synta** | **~1.55 µs** | baseline |
| x509-parser | ~3.95 µs | ~2.5× slower |
| NSS | ~8.45 µs | ~5.5× slower |
| x509-cert | ~11.35 µs | ~7.3× slower |
| rust-openssl | ~18.20 µs | ~11.7× slower |

synta uses `format_dn()` (single-pass DER walk), `ObjectIdentifier::Display` (zero-copy
dotted-decimal), and `UtcTime`/`GeneralizedTime::Display` for timestamps — no intermediate
allocations for the DN walk, no OID-to-string conversion.