x690

Crates.iox690
lib.rsx690
version1.2.0
created_at2025-07-20 17:40:39.15085+00
updated_at2025-08-06 11:08:14.843032+00
descriptionBasic Encoding Rules (BER) and Distinguished Encoding Rules (DER) as specified in ITU-T Recommendation X.690
homepage
repositoryhttps://github.com/JonathanWilbur/asn1.rs/tree/master/x690
max_upload_size
id1761274
size521,482
Jonathan Wilbur (JonathanWilbur)

documentation

README

X.690 Encoding Rules

This library implements the encoding rules defined in ITU Recommendation X.690 (2021), namely:

  • The Basic Encoding Rules (BER)
  • The Distinguished Encoding Rules (DER)
  • The Canonical Encoding Rules (CER) (Currently unsupported)

Much of the functionality of this crate depends upon wildboar-asn1, which is a "core" crate for all aspects of ASN.1 that are mostly "codec-agnostic" so that it can be used by other crates for implementing the Packed Encoding Rules, Octet Encoding Rules, BACNet, etc.

Usage

The ITU-T Recommendation X.690 codecs use Tag-Length-Value (TLV) triplets to encode ASN.1 values. These triplets are encoded on an integral number of bytes (what the specifications call "octets" for disambiguation). In this library, this TLV triplet is referred to as an X690Element, and generally, throughout the library, the word "element" refers to one of these triplets.

The X690Element itself may be composed of constituent X690Elements, allowing for the representation of "structured types."

For your convenience, the X690Element is:

pub struct X690Element {
    /// The tag identifying the type and class of this element
    pub tag: Tag,
    /// The value content of this element
    pub value: X690Value,
}

The length is determined at encoding time by the value. It is not stored anywhere, but you can read it using X690Value::len().

The X690Value, which represents the value octets ("content octets") of the TLV triplet is defined like:

pub enum X690Value {
    /// A primitive value stored as raw bytes
    Primitive(Bytes),
    /// A constructed value containing child elements
    Constructed(Arc<Vec<X690Element>>),
    /// A value that has been serialized to bytes (for lazy decoding or faster encoding)
    Serialized(Bytes),
}

If the Serialized alternative is used, the tag, length, and value can be written in one single call to write() when used with x690_write_tlv(). This is a performance hack that exists only for when you are serializing trustworthy data.

You can serialize X690Elements to bytes using x690_write_tlv(). To read X690Elements from bytes, you must use one of the codec-specific methods: X690Codec::decode_from_slice() or or X690Codec::decode_from_bytes().

Codec API

The Basic Encoding Rules (BER) and Distinguished Encoding Rules (DER) are implemented as structs that implement the X690Codec trait. These structs currently do not have any fields, but they may, in the future, to support Encoding Control Notation (ECN), if that is ever supported.

This crate exports BER and DER, which are compile-time constructed instances of the BasicEncodingRules and DistinguishedEncodingRules respectively. You can import them and invoke them like so:

let b = true;
let el: X690Element = BER.encode_boolean(&b)?;
let decoded = BER.decode_boolean(&el)?;
assert_eq!(b, decoded);

Functional API

This library also provides some functionality independently of any codec, simply for ease-of-use. Most of these start with x690_read_* or x690_write_* for decoding and encoding, respectively. These functions try to encode in such a way that is compliant with DER, but no such guarantees are made.

Parsing API - for constructed types

For structured types:

| Use... | ...to parse | |===========================|=====================| | _parse_set() | SET | | _parse_sequence() | SEQUENCE | | _decode_sequence_of() | SEQUENCE OF | | _decode_set_of() | SET OF | | _encode_explicit() | EXPLICIT tagged | | _encode_implicit() | IMPLICIT tagged |

Feature Flags

  • der - Enable the Distinguished Encoding Rules
  • simdutf8 - Use SIMD instructions to accelerate UTF-8 operations
  • likely_stable - Use branch prediction hint macros so your CPU speculatively executes the branches more likely to be taken.

The feature flags have not been benchmarked thus far. It is unknown if they actually improve performance.

The above functions start with underscores just because they

Examples

Encoding, Decoding, and Validating AlgorithmIdentifier (a SEQUENCE)

This data structure is used in X.509 certificates and in many other cryptographic artifacts.

use wildboar_asn1::*;
use x690::*;

struct AlgorithmIdentifier {
    pub algorithm: OBJECT_IDENTIFIER,
    pub parameters: Option<ASN1Value>,
}

const _rctl1_components_for_AlgorithmIdentifier: &[ComponentSpec; 2] = &[
    ComponentSpec::new(
        "algorithm",
        false,
        TagSelector::tag((
            TagClass::UNIVERSAL,
            UNIV_TAG_OBJECT_IDENTIFIER,
        )),
        None,
        None,
    ),
    ComponentSpec::new("parameters", true, TagSelector::any, None, None),
];

const _eal_components_for_AlgorithmIdentifier: &[ComponentSpec; 0] = &[];
const _rctl2_components_for_AlgorithmIdentifier: &[ComponentSpec; 0] = &[];

fn decode_AlgorithmIdentifier(el: &X690Element) -> ASN1Result<AlgorithmIdentifier> {
    let elements = el.components()?;
    let seq_iter = X690StructureIterator::new(
        &elements,
        _rctl1_components_for_AlgorithmIdentifier,
        _eal_components_for_AlgorithmIdentifier,
        _rctl2_components_for_AlgorithmIdentifier,
    );
    let mut maybe_algorithm: Option<OBJECT_IDENTIFIER> = None;
    let mut parameters: Option<ASN1Value> = None;
    for (i, component) in seq_iter.enumerate() {
        match component.unwrap() {
            "algorithm" => {
                maybe_algorithm = Some(DER.decode_object_identifier(&elements[i])?);
            },
            "parameters" => {
                parameters = Some(DER.decode_any(&elements[i])?);
            }
            _ => {
                return Err(ASN1Error::new(ASN1ErrorCode::unrecognized_components_in_inextensible_type));
            }
        }
    }
    let algorithm: OBJECT_IDENTIFIER = maybe_algorithm.unwrap();
    let parameters: Option<ASN1Value> = parameters;
    Ok(AlgorithmIdentifier {
        algorithm,
        parameters,
    })
}

pub fn encode_AlgorithmIdentifier(value_: &AlgorithmIdentifier) -> ASN1Result<X690Element> {
    let mut components_: Vec<X690Element> = Vec::with_capacity(12);
    components_.push(DER.encode_object_identifier(&value_.algorithm)?);
    if let Some(v_) = &value_.parameters {
        components_.push(x690_identity(&v_)?);
    }
    Ok(X690Element::new(
        Tag::new(TagClass::UNIVERSAL, UNIV_TAG_SEQUENCE),
        X690Value::Constructed(Arc::new(
            [components_, value_._unrecognized.clone()].concat(),
        )),
    ))
}

pub fn validate_AlgorithmIdentifier(el: &X690Element) -> ASN1Result<()> {
    let _elements = match &el.value {
        X690Value::Constructed(children) => children,
        _ => {
            return Err(
                el.to_asn1_err_named(ASN1ErrorCode::invalid_construction, "AlgorithmIdentifier")
            )
        }
    };
    let _seq_iter = X690StructureIterator::new(
        _elements.as_slice(),
        _rctl1_components_for_AlgorithmIdentifier,
        _eal_components_for_AlgorithmIdentifier,
        _rctl2_components_for_AlgorithmIdentifier,
    );
    let mut _i: usize = 0;
    for _fallible_component_name in _seq_iter {
        let _component_name = _fallible_component_name?;
        let _maybe_el = _elements.get(_i);
        _i += 1;
        let _el = _maybe_el.unwrap();
        match _component_name {
            "algorithm" => DER.validate_object_identifier(_el)?,
            "parameters" => DER.validate_any(_el)?,
            _ => (),
        }
    }
    Ok(())
}

let alg = AlgorithmIdentifier{
  algorithm: OBJECT_IDENTIFIER::from_str("2.5.4.3").unwrap(),
  parameters: None,
};

let encoded = encode_AlgorithmIdentifier(&alg)?;
assert!(validate_AlgorithmIdentifier(&encoded).is_ok()); // For validating without fully decoding.
let decoded = decode_AlgorithmIdentifier(&encoded)?;
assert_eq(alg.algorithm, decoded.algorithm);

Encoding, Validating, and Decoding UnboundedDirectoryString (a CHOICE)

This type is used in X.500 directories.

use wildboar_asn1::*;
use x690::*;

pub enum UnboundedDirectoryString {
    teletexString(TeletexString),
    printableString(PrintableString),
    bmpString(BMPString),
    universalString(UniversalString),
    uTF8String(UTF8String),
}

pub fn _decode_UnboundedDirectoryString(el: &X690Element) -> ASN1Result<UnboundedDirectoryString> {
    match (el.tag.tag_class, el.tag.tag_number) {
        (TagClass::UNIVERSAL, 20) => Ok(UnboundedDirectoryString::teletexString(
            BER.decode_t61_string(&el)?,
        )),
        (TagClass::UNIVERSAL, 19) => Ok(UnboundedDirectoryString::printableString(
            BER.decode_printable_string(&el)?,
        )),
        (TagClass::UNIVERSAL, 30) => Ok(UnboundedDirectoryString::bmpString(
            BER.decode_bmp_string(&el)?,
        )),
        (TagClass::UNIVERSAL, 28) => Ok(UnboundedDirectoryString::universalString(
            BER.decode_universal_string(&el)?,
        )),
        (TagClass::UNIVERSAL, 12) => Ok(UnboundedDirectoryString::uTF8String(
            BER.decode_utf8_string(&el)?,
        )),
        _ => {
            return Err(el.to_asn1_err_named(
                ASN1ErrorCode::unrecognized_alternative_in_inextensible_choice,
                "UnboundedDirectoryString",
            ))
        }
    }
}

pub fn _encode_UnboundedDirectoryString(
    value_: &UnboundedDirectoryString,
) -> ASN1Result<X690Element> {
    match value_ {
        UnboundedDirectoryString::teletexString(v) => BER.encode_t61_string(&v),
        UnboundedDirectoryString::printableString(v) => BER.encode_printable_string(&v),
        UnboundedDirectoryString::bmpString(v) => BER.encode_bmp_string(&v),
        UnboundedDirectoryString::universalString(v) => BER.encode_universal_string(&v),
        UnboundedDirectoryString::uTF8String(v) => BER.encode_utf8_string(&v),
    }
}

pub fn _validate_UnboundedDirectoryString(el: &X690Element) -> ASN1Result<()> {
    match (el.tag.tag_class, el.tag.tag_number) {
        (TagClass::UNIVERSAL, 20) => BER.validate_t61_string(&el),
        (TagClass::UNIVERSAL, 19) => BER.validate_printable_string(&el),
        (TagClass::UNIVERSAL, 30) => BER.validate_bmp_string(&el),
        (TagClass::UNIVERSAL, 28) => BER.validate_universal_string(&el),
        (TagClass::UNIVERSAL, 12) => BER.validate_utf8_string(&el),
        _ => {
            return Err(el.to_asn1_err_named(
                ASN1ErrorCode::unrecognized_alternative_in_inextensible_choice,
                "UnboundedDirectoryString",
            ))
        }
    }
}

let ds = UnboundedDirectoryString::UTF8String("hi mom".to_owned());
let encoded = _encode_UnboundedDirectoryString(&ds)?;
assert!(_validate_UnboundedDirectoryString(&encoded).is_ok()); // For validating without fully decoding.
let decoded = _decode_UnboundedDirectoryString(&encoded)?;
match (ds, decoded) {
  (UnboundedDirectoryString::UTF8String(a), UnboundedDirectoryString::UTF8String(b)) => assert_eq!(a, b),
  (_, _) => panic!(),
};

To Do

  • Canonical Encoding Rules (CER) on hold pending more field-testing
  • Convert OSString directly to BMPString on Windows?
  • More efficient BMPString and UniversalString methods

Notes

Why start with underscores?

Many of the functions in this library start with underscores because they are expected to be used in generated code. Valid ASN.1 identifiers are not allowed to start with hyphens, so code generation that converts hyphens to underscores are guaranteed to produce no symbols that conflict with those that start with underscores in this library. This library is already in use by the ASN.1 Compilation Service offered by Wildboar Software, which was used to compile the X.500 directory-related ASN.1 modules to what you see in x500.

Why not SmallVec?

I decided that there were not enough uses cases for SmallVec to justify it, even as a feature flag. Most things stored in a dynamically-allocated array in this library are too large to fit in a SmallVec.

AI / LLM Usage Statement

All but a small fraction of the code in this library was produced by a human; ChatGPT generated a very small part of the remainder.

The documentation, on the other hand, was generated largely by AI, but it was reviewed by the author of the code for correctness. Cursor's AI was used for this.

Commit count: 447

cargo fmt