synta 0.2.2

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
//! Example: Serde serialization of a real X.509 certificate
//!
//! This example loads `tests/vectors/test_certificate.pem` from the repository root,
//! parses it with synta into an ASN.1 `Element` tree, serializes the tree
//! to JSON using serde, then deserializes the JSON back into a
//! `serde_json::Value` and prints both trees for comparison.
//!
//! Note: `Element<'a>` carries borrowed slices from the original DER buffer
//! and therefore cannot implement `Deserialize`. `serde_json::Value` is used
//! as the round-trip target instead.
//!
//! Run with:
//!   cargo run --example serde_cert --features serde

#[cfg(feature = "serde")]
fn main() {
    use synta::{Decoder, Element, Encoding};

    println!("=== Certificate Serde Round-trip Example ===\n");

    // -------------------------------------------------------------------------
    // 1. Load and decode the PEM file
    // -------------------------------------------------------------------------

    let pem = std::fs::read_to_string("tests/vectors/test_certificate.pem")
        .expect("Cannot read tests/vectors/test_certificate.pem — run from the repo root");

    let der = synta_certificate::pem_to_der(pem.as_bytes())
        .into_iter()
        .next()
        .expect("Failed to decode PEM data");
    println!("1. Loaded certificate: {} DER bytes\n", der.len());

    // -------------------------------------------------------------------------
    // 2. Parse DER → Element tree
    // -------------------------------------------------------------------------

    let mut decoder = Decoder::new(&der, Encoding::Der);
    let element: Element = decoder.decode().expect("Failed to decode ASN.1");

    println!("2. Original ASN.1 Element tree (from synta):");
    print_element_tree(&element, 1);
    let node_count_element = count_element_nodes(&element);
    println!("   [{} total nodes]\n", node_count_element);

    // -------------------------------------------------------------------------
    // 3. Serialize Element → JSON
    // -------------------------------------------------------------------------

    let json = serde_json::to_string_pretty(&element).expect("Failed to serialize to JSON");
    println!("3. Serialized to JSON ({} bytes)\n", json.len());

    // -------------------------------------------------------------------------
    // 4. Deserialize JSON → serde_json::Value
    // -------------------------------------------------------------------------

    let value: serde_json::Value = serde_json::from_str(&json).expect("Failed to deserialize JSON");

    println!("4. Deserialized serde_json::Value tree (interpreted):");
    print_json_tree(&value, 1);
    let node_count_json = count_json_nodes(&value);
    println!("   [{} total nodes]\n", node_count_json);

    // -------------------------------------------------------------------------
    // 5. Compare the two trees
    // -------------------------------------------------------------------------

    println!("5. Comparison:");
    if node_count_element == node_count_json {
        println!(
            "   Trees are structurally identical ({} nodes each).",
            node_count_element
        );
    } else {
        println!(
            "   Trees differ: Element tree has {} nodes, JSON tree has {} nodes.",
            node_count_element, node_count_json
        );
    }

    // Verify data integrity: re-serialize the Value and parse it back, then
    // compare as structured data (key ordering in maps may differ from the
    // original serialization, but the data must be equivalent).
    let re_json = serde_json::to_string_pretty(&value).expect("Failed to re-serialize");
    let re_value: serde_json::Value = serde_json::from_str(&re_json).expect("Failed to re-parse");
    if value == re_value {
        println!("   JSON round-trip verified: re-serialized data is equivalent.");
    } else {
        println!("   JSON round-trip MISMATCH: re-serialized data differs.");
    }
}

// ---- helpers ----------------------------------------------------------------

/// Recursively print an `Element` tree in an ASN.1-style indented format.
#[cfg(feature = "serde")]
fn print_element_tree(el: &synta::Element<'_>, depth: usize) {
    use synta::{bytes_to_hex, Element};
    let pad = "  ".repeat(depth);
    match el {
        Element::Boolean(v) => println!("{}Boolean: {}", pad, v.value()),
        Element::Integer(v) => println!("{}Integer: {}", pad, bytes_to_hex(v.as_bytes())),
        Element::BitString(v) => println!(
            "{}BitString: {} bytes ({} unused bits)",
            pad,
            v.as_bytes().len(),
            v.unused_bits()
        ),
        Element::OctetString(v) => {
            println!("{}OctetString: {}", pad, bytes_to_hex(v.as_bytes()))
        }
        Element::Null(_) => println!("{}Null", pad),
        Element::Real(v) => println!("{}Real: {}", pad, v.value()),
        Element::ObjectIdentifier(v) => println!("{}OID: {}", pad, v),
        Element::Utf8String(v) => println!("{}Utf8String: {:?}", pad, v.as_str()),
        Element::PrintableString(v) => println!("{}PrintableString: {:?}", pad, v.as_str()),
        Element::IA5String(v) => println!("{}IA5String: {:?}", pad, v.as_str()),
        Element::UtcTime(v) => println!(
            "{}UtcTime: {:02}{:02}{:02}{:02}{:02}{:02}Z",
            pad,
            v.year % 100,
            v.month,
            v.day,
            v.hour,
            v.minute,
            v.second
        ),
        Element::GeneralizedTime(v) => println!(
            "{}GeneralizedTime: {:04}{:02}{:02}{:02}{:02}{:02}Z",
            pad, v.year, v.month, v.day, v.hour, v.minute, v.second
        ),
        Element::NumericString(v) => println!("{}NumericString: {:?}", pad, v.as_str()),
        Element::TeletexString(v) => {
            println!("{}TeletexString: {}", pad, bytes_to_hex(v.as_bytes()))
        }
        Element::VisibleString(v) => println!("{}VisibleString: {:?}", pad, v.as_str()),
        Element::GeneralString(v) => {
            println!("{}GeneralString: {}", pad, bytes_to_hex(v.as_bytes()))
        }
        Element::UniversalString(v) => println!("{}UniversalString: {:?}", pad, v.as_str()),
        Element::BmpString(v) => println!("{}BmpString: {:?}", pad, v.as_str()),
        Element::Sequence(seq) => {
            let children: Vec<_> = seq.iter().flatten().collect();
            println!("{}Sequence ({} elements):", pad, children.len());
            for child in children {
                print_element_tree(&child, depth + 1);
            }
        }
        Element::Set(set) => {
            let children: Vec<_> = set.iter().flatten().collect();
            println!("{}Set ({} elements):", pad, children.len());
            for child in children {
                print_element_tree(&child, depth + 1);
            }
        }
        Element::Tagged(tag, inner) => {
            println!("{}Tagged [{:?} {}]:", pad, tag.class(), tag.number());
            print_element_tree(inner, depth + 1);
        }
        Element::Raw(tag, bytes) => {
            println!(
                "{}Raw [{:?} {}]: {}",
                pad,
                tag.class(),
                tag.number(),
                bytes_to_hex(bytes)
            );
        }
    }
}

/// Count total nodes in an `Element` tree (each element counts as one node).
#[cfg(feature = "serde")]
fn count_element_nodes(el: &synta::Element<'_>) -> usize {
    use synta::Element;
    match el {
        Element::Sequence(seq) => {
            1 + seq
                .iter()
                .flatten()
                .map(|el| count_element_nodes(&el))
                .sum::<usize>()
        }
        Element::Set(set) => {
            1 + set
                .iter()
                .flatten()
                .map(|el| count_element_nodes(&el))
                .sum::<usize>()
        }
        Element::Tagged(_, inner) => 1 + count_element_nodes(inner),
        _ => 1,
    }
}

/// Recursively print a `serde_json::Value` tree, interpreting the
/// `{"type": "...", "value": ...}` format produced by `Element`'s `Serialize`
/// impl.
#[cfg(feature = "serde")]
fn print_json_tree(val: &serde_json::Value, depth: usize) {
    use serde_json::Value;
    let pad = "  ".repeat(depth);
    let type_name = val.get("type").and_then(Value::as_str).unwrap_or("Unknown");

    match type_name {
        "Sequence" | "Set" => {
            if let Some(arr) = val.get("value").and_then(Value::as_array) {
                println!("{}{} ({} elements):", pad, type_name, arr.len());
                for child in arr {
                    print_json_tree(child, depth + 1);
                }
            } else {
                println!("{}{}: (empty)", pad, type_name);
            }
        }
        "Tagged" => {
            let tag = val.get("tag").cloned().unwrap_or(Value::Null);
            let class = tag.get("class").and_then(Value::as_str).unwrap_or("?");
            let number = tag.get("number").and_then(Value::as_u64).unwrap_or(0);
            println!("{}Tagged [{} {}]:", pad, class, number);
            if let Some(inner) = val.get("value") {
                print_json_tree(inner, depth + 1);
            }
        }
        _ => {
            // Leaf node: print type and its JSON value
            let leaf = val.get("value").unwrap_or(&Value::Null);
            println!("{}{}: {}", pad, type_name, leaf);
        }
    }
}

/// Count total nodes in the JSON tree, following the same structure as
/// `count_element_nodes`.
#[cfg(feature = "serde")]
fn count_json_nodes(val: &serde_json::Value) -> usize {
    use serde_json::Value;
    let type_name = val.get("type").and_then(Value::as_str).unwrap_or("Unknown");

    match type_name {
        "Sequence" | "Set" => {
            let children = val
                .get("value")
                .and_then(Value::as_array)
                .map(|a| a.iter().map(count_json_nodes).sum::<usize>())
                .unwrap_or(0);
            1 + children
        }
        "Tagged" => {
            let inner_count = val.get("value").map(count_json_nodes).unwrap_or(0);
            1 + inner_count
        }
        _ => 1,
    }
}

#[cfg(not(feature = "serde"))]
fn main() {
    println!("This example requires the 'serde' feature.");
    println!("Run with: cargo run --example serde_cert --features serde");
}