kellerkindt / asn1rs

Generates Rust Code and optionally compatible Protobuf schema files from ASN.1 definitions.
http://asn1.rs
Apache License 2.0
54 stars 19 forks source link
asn asn1 asn1-decoder async-postgres postgres protobuf rust serde sql

asn1rs - ASN.1 Compiler for Rust

This crate generates Rust Code and optionally compatible Protobuf and SQL schema files from ASN.1 definitions. Integration with serde is supported.

The crate can be used as standalone CLI binary or used as library through its API (for example inside your build.rs script).

Build Status License Crates.io Coverage Status Documentation PRs Welcome

Supported Features

Feature Parses UPER Protobuf
...extensible โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
SEQUENCE OF โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yes
...SIZE(A..B) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
...SIZE(A..B,...) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
SET โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yes
...extensible โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
SET OF โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yes
...SIZE(A..B) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
...SIZE(A..B,...) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
ENUMERATED โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yes
...extensible โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
CHOICE โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yes
...extensible โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
BIT STRING โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยน
...SIZE(A..B) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
...SIZE(A..B,...) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
OCTET STRING โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yes
...SIZE(A..B) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
...SIZE(A..B,...) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
UTF8String โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yes
...SIZE(A..B) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
...SIZE(A..B,...) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
IA5String โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยน
...SIZE(A..B) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
...SIZE(A..B,...) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
NumericString โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยน
...SIZE(A..B) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
...SIZE(A..B,...) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
PrintableString โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยน
...SIZE(A..B) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
...SIZE(A..B,...) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
VisibleString โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยน
...SIZE(A..B) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
...SIZE(A..B,...) โœ”๏ธ yes โœ”๏ธ yes ๐Ÿ†— ignored
INTEGER โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yes
...A..B โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยฒ
...A..B,... โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยฒ
BOOLEAN โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yes
OPTIONAL โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yes
DEFAULT ... โœ”๏ธ yes
...INTEGER โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยน
...*String โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยน
...BOOLEAN โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยน
...ENUMERATED โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยน
NULL โœ”๏ธ yes โœ”๏ธ yes โœ”๏ธ yesยน
IMPORTS..FROM..; โœ”๏ธ yes
ObjectIdentifiers โœ”๏ธ yes
Value References โœ”๏ธ yes
... in Range โœ”๏ธ yes
... in Size โœ”๏ธ yes
... in Default โœ”๏ธ yes
WITH COMPONENTS โœ”๏ธ yes

Supported standards

CLI usage

It is always helpful to check asn1rs --help in advance. The basic usage can be seen blow:

asn1rs -t rust directory/for/rust/files some.asn1 messages.asn1
asn1rs -t proto directory/for/protobuf/files some.asn1 messages.asn1

Example: build.rs

The following example generates Rust and Protobuf files for all .asn1-files in the asn/ directory of a workspace. While the generated Rust code is written to the src/ directory, the Protobuf files are written to proto/. Additionally, in this example each generated Rust-Type also receives Serialize and Deserialize derive directives (#[derive(Serialize, Deserialize)]) for serde integration.

Sample build.rs file:

use asn1rs::converter::Converter;
use asn1rs::gen::rust::RustCodeGenerator;

pub fn main() {
    let mut converter = Converter::default();

    // collecting all relevant .asn1 files
    std::fs::read_dir("../protocol/asn")
        .into_iter()
        .flat_map(|read_dir| {
            read_dir
                .into_iter()
                .flat_map(|dir_entry| dir_entry.into_iter())
                .flat_map(|entry| {
                    entry
                        .path()
                        .as_os_str()
                        .to_os_string()
                        .into_string()
                        .into_iter()
                })
                .filter(|entry| entry.ends_with(".asn1"))
        })
        .for_each(|path| {
            println!("cargo:rerun-if-changed={}", path);
            if let Err(e) = converter.load_file(&path) {
                panic!("Loading of .asn1 file failed {}: {:?}", path, e);
            }
        });

    // writing the .rs files into src with serde_derive support
    // feature flags decide whether additional code for protobuf is generated
    if let Err(e) = converter.to_rust("src/", |generator: &mut RustCodeGenerator| {
        generator.add_global_derive("Serialize"); // Adds serde_derive support: #[derive(Serialize)]
        generator.add_global_derive("Deserialize"); // Adds serde_derive support: #[derive(Deserialize)]
    }) {
        panic!("Conversion to rust failed: {:?}", e);
    }

    // OPTIONAL: writing the .proto representation to ../protocol/proto
    if let Err(e) = converter.to_protobuf("../protocol/proto/") {
        panic!("Conversion to proto failed: {:?}", e);
    }
}

Example: Inlining ASN.1 with procedural macros

Minimal example by inlining the ASN.1 definition. For more examples see tests/.

use asn1rs::prelude::*;

asn_to_rust!(
    r"BasicInteger DEFINITIONS AUTOMATIC TAGS ::=
    BEGIN

    RangedMax ::= Integer (0..MAX)

    NotRanged ::= Integer

    END"
);

#[test]
fn test_write_read() {
    // inner INTEGER identified as u64
    let value = NotRanged(123_u64);

    let mut writer = UperWriter::default();
    writer.write(&value).expect("Failed to serialize");

    let mut reader = writer.into_reader();
    let value2 = reader.read::<NotRanged>().expect("Failed to deserialize");

    assert_eq!(value, value2);
}

#[test]
fn test_constraint_eq() {
    // these types should normally not be accessed, but in this exampled they show
    // the way the ASN.1 constraints are encoded with the Rust type system.
    use asn1rs::syn::numbers::Constraint;
    assert_eq!(
        ___asn1rs_RangedMaxField0Constraint::MIN,
        ___asn1rs_NotRangedField0Constraint::MIN,
    );
    assert_eq!(
        ___asn1rs_RangedMaxField0Constraint::MAX,
        ___asn1rs_NotRangedField0Constraint::MAX,
    );
}

Example: ASN.1-Definition converted to Rust and Protobuf

Minimal example showcasing what is being generated from an ASN.1 definition:

MyMessages DEFINITIONS AUTOMATIC TAGS ::=
BEGIN

Header ::= SEQUENCE {
    timestamp    INTEGER (0..1209600000)
}

END

The generated Rust file:

use asn1rs::prelude::*;

#[asn(sequence)]
#[derive(Default, Debug, Clone, PartialEq, Hash)]
pub struct Header {
    #[asn(integer(0..1209600000))] pub timestamp: u32,
}

The generated protobuf file (optional):

syntax = 'proto3';
package my.messages;

message Header {
    uint32 timestamp = 1;
}

Example: Raw uPER usage

The module asn1rs::io exposes (de-)serializers and helpers for direct usage without ASN.1 definition:

use asn1rs::prelude::*;
use asn1rs::io::per::unaligned::buffer::BitBuffer;

let mut buffer = BitBuffer::default();
buffer.write_bit(true).unwrap();
buffer.write_utf8_string("My UTF8 Text").unwrap();

send_to_another_host(buffer.into::<Vec<u8>>()):

Example: Raw Protobuf usage

The module asn1rs::io::protobuf exposes (de-)serializers for protobuf usage:

use asn1rs::io::protobuf::*;

let mut buffer = Vec::default();
buffer.write_varint(1337).unwrap();
buffer.write_string("Still UTF8 Text").unwrap();

send_to_another_host(buffer):

Extending Rust Codegen

use asn1rs::model::model::gen::rust::RustCodeGenerator;
use asn1rs::model::model::gen::rust::GeneratorSupplement;

impl GeneratorSupplement<Rust> for MyRustCodeGeneratorExtension {
    // .. implement the trait
}

fn main() {
    let model: Model<Rust> = ... ;
    let (_file_name, file_content) = RustCodeGenerator::from(model)
        .to_string_with_generators(&[&MyRustCodeGeneratorExtension])
        .into_iter()
        .next()
        .unwrap();
}

Finding deserialization error origins

For a more detailed report on deserialization errors, enable the descriptive-deserialize-errors feature. With this feature flag more details will be memorized while deserializing your data (see ScopeDescription) - thus causing a performance penalty - but it will list intermediate results with the error origin and the current location in the type hierarchy when displaying the error ( println!("{e}"));

TODO

Things to do at some point in time (PRs are welcome)

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.


Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Origin

This crate was initially developed during a research project at IT-Designers GmbH (http://www.it-designers.de).