asn1rs 0.1.1

ASN.1 to Rust, Protobuf and SQL compiler/code generator. Supports ASN.1 UPER
Documentation

asn1rs - ASN.1 Compiler for Rust

This crate allows one to generate Rust, Protobuf and SQL code from ASN.1 definitions, providing also support for basic serde integration. The crate can be used as standalone binary using its command line interface or included invoked through its API (for example inside the build.rs script).

The generated Rust code has serializers and deserializers for ASN.1 UPER, protobuf and PostgreSQL and can therefore communicate with other applications supporting these formats (like Java-classes generated by the Google protobuf compiler).

Currently only UPER is supported for ASN.1.

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
asn1rs -t sql directory/for/sql/schema/files some.asn1 messages.asn1

API usage

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

File build.rs:

extern crate asn1rs;

use std::fs;

use asn1rs::converter::convert_to_proto;
use asn1rs::converter::convert_to_rust;
use asn1rs::converter::convert_to_sql;
use asn1rs::gen::rust::RustCodeGenerator;

pub fn main() {
    for entry in fs::read_dir("asn").unwrap().into_iter() {
        let entry = entry.unwrap();
        let file_name = entry.file_name().into_string().unwrap();
        if file_name.ends_with(".asn1") {
            if let Err(e) = convert_to_rust(
                entry.path().to_str().unwrap(),
                "src/",
                |generator: &mut RustCodeGenerator| {
                    generator.add_global_derive("Serialize");
                    generator.add_global_derive("Deserialize");
                },
            ) {
                panic!("Conversion to rust failed for {}: {:?}", file_name, e);
            }
            if let Err(e) = convert_to_proto(entry.path().to_str().unwrap(), "proto/") {
                panic!("Conversion to proto failed for {}: {:?}", file_name, e);
            }
            if let Err(e) = convert_to_sql(entry.path().to_str().unwrap(), "sql/") {
                panic!("Conversion to sql failed for {}: {:?}", file_name, e);
            }
        }
    }
}

Example Input / Output

Input input.asn1

MyMessages DEFINITIONS AUTOMATIC TAGS ::=
BEGIN

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

END

Output my_messages.rs:

// use ...

#[derive(Default, Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
pub struct Header {
    pub timestamp: u32,
}

impl Header {
    pub fn timestamp_min() -> u32 {
        0
    }

    pub fn timestamp_max() -> u32 {
        1_209_600_000
    }
}

// Serialize and deserialize functions for ASN.1 UPER
impl Uper for Header { /*..*/ }

// Serialize and deserialize functions for protobuf
impl ProtobufEq for Header { /*..*/ }
impl Protobuf for Header { /*..*/ }

// Insert and query functions for PostgreSQL
impl PsqlRepresentable for Header { /*..*/ }
impl PsqlInsertable for Header { /*..*/ }
impl PsqlQueryable for Header { /*..*/ }

Output my_messages.proto:

syntax = 'proto3';
package my.messages;

message Header {
    uint32 timestamp = 1;
}

Output my_messages.sql:

DROP TABLE IF EXISTS Header CASCADE;

CREATE TABLE Header (
    id SERIAL PRIMARY KEY,
    timestamp INTEGER NOT NULL
);

Good to know

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

use asn1rs::io::uper::*;
use asn1rs::io::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>>()):
use asn1rs::io::protobuf::*;

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

send_to_another_host(buffer):

What works

  • Generating Rust Code with serializtion support for
    • UPER
    • Protobuf
    • PostgreSQL
  • Generating Protobuf Definitions
  • Generating PostgreSQL Schema files
  • Support for the following ASN.1 datatypes:
    • SEQUENCE, SEQUENCE OF, CHOICE and ENUMERATED
    • inline SEQUENCE OF and CHOICE
    • OPTIONAL
    • INTEGER with range value only (numbers or MIN/MAX)
    • UTF8String
    • OCTET STRING
    • BOOLEAN
    • using previously declared message types
    • IMPORTS .. FROM ..;

What doesn't work

  • most of the (not mentioned) remaining ASN.1 data-types
  • probably most non-trivial ASN.1 declarations
  • comments
  • let me know

TODO

Things to do at some point in time

  • support #![no_std]
  • refactor / clean-up (rust) code-generators
  • async postgres

LICENSE

MIT/Apache-2.0

About

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