#protobuf #sql #asn-1 #compiler #uper #serialization

bin+lib asn1rs

ASN.1 到 Rust、Protobuf 和 SQL 编译器/代码生成器。支持 ASN.1 UPER

25 个版本

0.3.1 2023年7月27日
0.2.2 2021年5月3日
0.2.1 2021年3月22日
0.2.0-alpha32020年10月14日
0.1.2 2019年2月26日

#226 in 解析实现

Download history 7/week @ 2024-03-08 25/week @ 2024-03-15 4/week @ 2024-03-22 6/week @ 2024-03-29 3/week @ 2024-05-17 2/week @ 2024-05-24

147 次每月下载

MIT/Apache

410KB
10K SLoC

asn1rs - Rust ASN.1 编译器

此软件包从 ASN.1 定义生成 Rust 代码,可选地兼容 Protobuf 和 SQL 架构文件。支持与 serde 的集成。

此软件包可以作为独立的 CLI 二进制文件使用,也可以通过其 API 作为库使用(例如在您的 build.rs 脚本中)。

Build Status License Crates.io Coverage Status Documentation PRs Welcome

支持的功能

功能 解析 UPER Protobuf PSQL 异步 PSQL
...可扩展 ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
SEQUENCE OF ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是
...SIZE(A..B) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
...SIZE(A..B,...) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
SET ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是
...可扩展 ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
SET OF ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是
...SIZE(A..B) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
...SIZE(A..B,...) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
ENUMERATED ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是
...可扩展 ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
CHOICE ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是
...可扩展 ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
BIT STRING ✔️ 是 ✔️ 是 ✔️ 是¹ ✔️ 是¹ ✔️ 是¹
...SIZE(A..B) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
...SIZE(A..B,...) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
OCTET STRING ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是
...SIZE(A..B) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
...SIZE(A..B,...) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
UTF8String ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是
...SIZE(A..B) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
...SIZE(A..B,...) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
IA5String ✔️ 是 ✔️ 是 ✔️ 是¹ ✔️ 是¹ ✔️ 是¹
...SIZE(A..B) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
...SIZE(A..B,...) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
NumericString ✔️ 是 ✔️ 是 ✔️ 是¹ ✔️ 是¹ ✔️ 是¹
...SIZE(A..B) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
...SIZE(A..B,...) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
PrintableString ✔️ 是 ✔️ 是 ✔️ 是¹ ✔️ 是¹ ✔️ 是¹
...SIZE(A..B) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
...SIZE(A..B,...) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
VisibleString ✔️ 是 ✔️ 是 ✔️ 是¹ ✔️ 是¹ ✔️ 是¹
...SIZE(A..B) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
...SIZE(A..B,...) ✔️ 是 ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略
INTEGER ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是
...A..B ✔️ 是 ✔️ 是 ✔️ 是² ✔️ 是² ✔️ 是²
...A..B,... ✔️ 是 ✔️ 是 ✔️ 是² ✔️ 是² ✔️ 是²
BOOLEAN ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是
OPTIONAL ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是 ✔️ 是
DEFAULT ... ✔️ 是
...INTEGER ✔️ 是 ✔️ 是 ✔️ 是¹ ✔️ 是¹ ✔️ 是¹
...*String ✔️ 是 ✔️ 是 ✔️ 是¹ ✔️ 是¹ ✔️ 是¹
...BOOLEAN ✔️ 是 ✔️ 是 ✔️ 是¹ ✔️ 是¹ ✔️ 是¹
...ENUMERATED ✔️ 是 ✔️ 是 ✔️ 是¹ ✔️ 是¹ ✔️ 是¹
NULL ✔️ 是 ✔️ 是 ✔️ 是¹ ✔️ 是¹ ✔️ 是¹
IMPORTS..FROM..; ✔️ 是
ObjectIdentifiers ✔️ 是
Value References ✔️ 是
... in Range ✔️ 是
... in Size ✔️ 是
... in Default ✔️ 是
WITH COMPONENTS ✔️ 是 🆗 忽略 🆗 忽略 🆗 忽略 🆗 忽略
  • ✔️ 是:根据规范
  • ✔️ 是¹:不同的表示
  • ✔️ 是²:尽可能接近原始规范(有时是 yes,有时是 yes¹)
  • 🔶 未序列化:在这种情况下值未序列化或反序列化,可能会破坏兼容性
  • ⚠️ 忽略️:约束被忽略,这很可能会破坏兼容性
  • 🆗 忽略:约束被忽略,但不会破坏兼容性
  • ❌ ub:未定义行为 - 任何合理的方法都可以防止编译器错误并某种方式传递值
  • 🟥 错误:编译失败/翻译失败

旧的 UPER 读写器已弃用,将在版本 0.3.0 中移除

TLDR

  • 新的 (v0.2.0) UPER 读写器支持所有列出功能
  • Protobuf、同步/异步 PSQL 忽略大多数约束
  • 旧的 UPER 读写器不支持所有功能(版本 pre v0.2.0)

支持的标准

CLI 使用

在事先检查 asn1rs --help 总是很有帮助。基本用法如下所示

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

示例:build.rs

以下示例生成了工作区中 .asn1 目录中所有 .asn1 文件的 Rust、Protobuf 和 SQL 文件。虽然生成的 Rust 代码写入到 src/ 目录,但 Protobuf 文件写入到 proto/,SQL 文件写入到 sql/ 。此外,在这个示例中,每个生成的 Rust-Type 也接收到了 SerializeDeserialize derive 指令(#[derive(Serialize, Deserialize)])以实现 serde 集成。

sample build.rs 文件

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

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 and (async) psql 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);
    }

    // OPTIONAL: writing the .sql schema files to ../protocol/sql
    if let Err(e) = converter.to_sql_with(
        "../protocol/sql/",
        SqlDefGenerator::default() // optional parameter, alternatively see Converter::to_sql(&self, &str)
            .optimize_tables_for_write_performance() // optional
            .wrap_primary_key_on_overflow(), // optional
    ) {
        panic!("Conversion to sql failed: {:?}", e);
    }
}

示例:使用过程宏内联 ASN.1

最小示例通过内联 ASN.1 定义。更多示例请参阅 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,
    );
}

示例:将 ASN.1-Definition 转换为 Rust、Protobuf 和 SQL

展示从 ASN.1 定义生成内容的简化示例

MyMessages DEFINITIONS AUTOMATIC TAGS ::=
BEGIN

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

END

生成的 Rust 文件

use asn1rs::prelude::*;

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

// only with the feature "async-psql": Insert and query functions for async PostgreSQL
impl Header {
    pub async fn apsql_retrieve_many(context: &apsql::Context<'_>, ids: &[i32]) -> Result<Vec<Self>, apsql::Error> { /*..*/ }
    pub async fn apsql_retrieve(context: &apsql::Context<'_>, id: i32) -> Result<Self, apsql::Error> { /*..*/ }
    pub async fn apsql_load(context: &apsql::Context<'_>, row: &apsql::Row) -> Result<Self, apsql::Error> { /*..*/ }
    pub async fn apsql_insert(&self, context: &apsql::Context<'_>) -> Result<i32, apsql::PsqlError> { /*..*/ }
}

// only with the feature "psql": Insert and query functions for non-async PostgreSQL
impl PsqlRepresentable for Header { /*..*/ }
impl PsqlInsertable for Header { /*..*/ }
impl PsqlQueryable for Header { /*..*/ }

生成的 protobuf 文件(可选)

syntax = 'proto3';
package my.messages;

message Header {
    uint32 timestamp = 1;
}

生成的 SQL 文件(可选)

DROP TABLE IF EXISTS Header CASCADE;

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

示例:异步 postgres 的使用

注意:这需要 async-psql 功能。

使用异步 postgres 允许消息或批处理消息利用 pipelining。这可以显著提高深层次消息类型(个人经验这大约是 26%)的速度,与同步/阻塞 postgres 实现相比。

use asn1rs::io::async_psql::*;
use tokio_postgres::NoTls;

#[tokio::main]
async fn main() {
    let transactional = true;
    let (mut client, connection) = tokio_postgres::connect(
        "host=localhost user=postgres application_name=psql_async_demo",
        NoTls,
    )
        .await
        .expect("Failed to connect");

    tokio::spawn(connection);
  

    let context = if transactional {
        let transaction = client
            .transaction()
            .await
            .expect("Failed to open a new transaction");
        Cache::default().into_transaction_context(transaction)
    } else {
        Cache::default().into_client_context(client)
    };

    // using sample message from above
    let message = Header {
        timestamp: 1234,
    };
   
    // This issues all necessary insert statements on the given Context and
    // because it does not require exclusive access to the context, you can
    // issue multiple inserts and await them concurrently with for example
    // tokio::try_join, futures::try_join_all or the like. 
    let id = message.apsql_insert(&context).await.expect("Insert failed");
    
    // This disassembles the context, allowing the Transaction to be committed
    // or rolled back. This operation also optimizes the read access to
    // prepared statements of the Cache. If you do not want to do that, then call
    // Context::split_unoptimized instead.
    // You can also call `Cache::optimize()` manually to optimize the read access
    // to the cached prepared statements.
    // See the doc for more information about the usage of cached prepared statements
    let (mut cache, transaction) = context.split();
   
    // this is (logically) a nop on a non-transactional context
    transaction.commit().await.expect("failed to commit");

    let context = if transactional {
        let transaction = client
            .transaction()
            .await
            .expect("Failed to open a new transaction");
        Cache::default().into_transaction_context(transaction)
    } else {
        Cache::default().into_client_context(client)
    };

    let message_from_db = Header::apsql_retrieve(&context, id).await.expect("Failed to load");
    assert_eq!(message, message_from_db);
}

示例:原始 uPER 使用

模块 asn1rs::io 提供了(反)序列化和助手,可以直接使用而不需要 ASN.1 定义

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>>()):

示例:原始 Protobuf 使用

模块 asn1rs::io::protobuf 提供了用于 Protobuf 使用的(反)序列化器

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):

查找反序列化错误来源

要获取关于反序列化错误的更详细报告,请启用 descriptive-deserialize-errors 功能。使用此功能标志时,在反序列化数据时将记住更多详细信息(见 ScopeDescription)——这会带来性能惩罚——但它会在显示错误时列出中间结果,包括错误起源和类型层次结构中的当前位置(println!("{e}"));

待办事项

在某个时间点要完成的事情(欢迎提交PR)

  • 从模块的对象标识符生成适当的Rust模块层次结构
  • 移除旧版rust+uper代码生成器(v0.3.0)
  • 支持 #![no_std]
  • 重构/清理(Rust)代码生成器(大多数将在v0.3.0版本中删除)
  • 支持更多ASN.1编码格式(欢迎提供帮助!)

许可证

根据您的选择,许可证为Apache License, Version 2.0MIT许可证
除非您明确声明,否则任何提交的旨在包含在此软件包中的贡献,根据Apache-2.0许可证的定义,均应按上述方式双授权,不附加任何额外条款或条件。
来源
该软件包最初是在IT-Designers GmbH的研究项目中开发的(http://www.it-designers.de)。

依赖项

~5–15MB
~190K SLoC