25 个版本
0.3.1 | 2023年7月27日 |
---|---|
0.2.2 | 2021年5月3日 |
0.2.1 | 2021年3月22日 |
0.2.0-alpha3 | 2020年10月14日 |
0.1.2 | 2019年2月26日 |
#226 in 解析实现
147 次每月下载
410KB
10K SLoC
asn1rs - Rust ASN.1 编译器
此软件包从 ASN.1 定义生成 Rust 代码,可选地兼容 Protobuf 和 SQL 架构文件。支持与 serde 的集成。
此软件包可以作为独立的 CLI 二进制文件使用,也可以通过其 API 作为库使用(例如在您的 build.rs
脚本中)。
支持的功能
功能 | 解析 | 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)
支持的标准
- 📜️ ETSI TS 102 894-2 (PDF) / 🧰 ITS-Container (GIT)
itu-t(0)identified-organization(4) etsi(0)itsDomain(5) wg1(1) ts(102894) cdd(2) version(2)
- 📜️ ETSI EN 302 637-2 (PDF) / 🧰 CAM-PDU-Description (GIT)
itu-t(0)identified-organization(4) etsi(0)itsDomain(5) wg1(1) en(302637) cam(2) version(2)
- 📜️ ETSI EN 302 637-3 (PDF) / 🧰 DENM-PDU-Descriptions (GIT)
itu-t(0)identified-organization(4) etsi(0)itsDomain(5) wg1(1) en(302637) denm(1) version(2)
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 也接收到了 Serialize
和 Deserialize
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.0或MIT许可证。除非您明确声明,否则任何提交的旨在包含在此软件包中的贡献,根据Apache-2.0许可证的定义,均应按上述方式双授权,不附加任何额外条款或条件。
来源
该软件包最初是在IT-Designers GmbH的研究项目中开发的(http://www.it-designers.de)。依赖项
~5–15MB
~190K SLoC