ASN.1 統合
wirespec はバイナリプロトコル記述に ASN.1 エンコーディングされたフィールドを埋め込むことができます。SUPL、V2X ITS、3GPP LPP など、バイナリワイヤヘッダと ASN.1 ペイロードを組み合わせるプロトコルに有用です。
基本的な使い方
extern asn1 で外部 ASN.1 型を宣言し、フィールド型として使用します:
extern asn1 "etsi_its_cdd.asn1" use crate::etsi_its_cdd { CAM }
packet ItsCamPacket {
version: u8,
length: u16,
cam: asn1(CAM, encoding: uper, length: length),
}asn1() フィールド型は、このフィールドが ASN.1 エンコーディングされていることを wirespec に伝えます。ワイヤ上では bytes[length: length] として扱われ、ASN.1 エンコーディングは上位レイヤの関心事です。
生成コード
Rust バックエンド
Rust バックエンドは rasn::uper::decode/encode 呼び出しを生成します:
pub struct ItsCamPacket {
pub version: u8,
pub length: u16,
pub cam: CAM, // デコード済み ASN.1 型(&[u8] ではない)
}cam フィールドは完全にデコードされた ASN.1 型です。パース時に自動デコードし、シリアライズ時に再エンコードして length フィールドを再計算します。
C バックエンド
C バックエンドでは ASN.1 フィールドは生バイトとして扱われます:
typedef struct {
uint8_t version;
uint16_t length;
const uint8_t *cam; // 生バイト
} its_cam_packet_t;C ユーザーは asn1c 等の C ASN.1 ライブラリで手動デコードします。
対応エンコーディング
| エンコーディング | wirespec 名 | 用途 |
|---|---|---|
| UPER | uper | 3GPP、V2X、LTE |
| BER | ber | LDAP、SNMP |
| DER | der | X.509、CMS |
| APER | aper | S1AP、NGAP |
| OER | oer | V2X IEEE 1609.2 |
| COER | coer | Canonical OER |
長さの指定
2 つの形式があります:
// 長さプレフィックス: `length` バイト分を読み取る
payload: asn1(Type, encoding: uper, length: length_field)
// 残り全部: 残りのバイトをすべて消費
payload: asn1(Type, encoding: uper, remaining)自動コンパイル (asn1 フィーチャー)
asn1 Cargo フィーチャーを有効にすると、wirespec は rasn-compiler 経由で .asn1 ファイルを自動コンパイルします:
cargo run --features asn1 -- compile its.wspec -t rust -o build/rasn 生成型と wirespec コーデックの両方が出力されます。extern asn1 の use 句は自動解決されます。
フィーチャーなしの場合、use 句を手動で指定する必要があります:
extern asn1 "schema.asn1" use crate::my_types { MyType }C バックエンドで asn1c を使う
C バックエンドでは ASN.1 フィールドは wirespec_bytes_t(ポインタと長さ)として出力されます。実際の ASN.1 データをデコードするには、asn1c などの ASN.1 ライブラリと組み合わせます。
ワークフロー
1. ASN.1 スキーマと wirespec パケットを定義:
-- messages.asn1
Messages DEFINITIONS AUTOMATIC TAGS ::= BEGIN
SimpleMessage ::= SEQUENCE {
id INTEGER (0..255),
active BOOLEAN
}
ENDmodule example
@endian big
packet MessageWrapper {
version: u8,
payload_length: u16,
payload: bytes[length: payload_length],
}2. 両方をコンパイル:
# ASN.1 を UPER 対応でコンパイル
asn1c -fcompound-names -gen-PER -pdu=SimpleMessage messages.asn1
# wirespec パケットを C にコンパイル
wirespec compile wrapper.wspec -t c -o build/3. wirespec でパース → asn1c でデコード:
#include "SimpleMessage.h" // asn1c 生成
#include "example.h" // wirespec 生成
#include "wirespec_runtime.h"
// wirespec でバイナリフレームをパース
example_message_wrapper_t wrapper;
size_t consumed;
wirespec_result_t rc = example_message_wrapper_parse(
buf, buf_len, &wrapper, &consumed);
if (rc != WIRESPEC_OK) { /* エラー処理 */ }
// asn1c で ASN.1 ペイロードをデコード
SimpleMessage_t *msg = NULL;
asn_dec_rval_t dec = uper_decode(
0, &asn_DEF_SimpleMessage,
(void **)&msg,
wrapper.payload.ptr,
wrapper.payload.len,
0, 0);
if (dec.code != RC_OK) { /* エラー処理 */ }
printf("id=%ld active=%d\n", msg->id, msg->active);
ASN_STRUCT_FREE(asn_DEF_SimpleMessage, msg);4. asn1c でエンコード → wirespec でシリアライズ:
// asn1c で ASN.1 をエンコード
SimpleMessage_t msg = { .id = 42, .active = 1 };
uint8_t asn1_buf[128];
asn_enc_rval_t enc = uper_encode_to_buffer(
&asn_DEF_SimpleMessage, 0, &msg,
asn1_buf, sizeof(asn1_buf));
size_t asn1_len = (enc.encoded + 7) / 8; // ビットをバイトに変換
// wirespec パケットにラップ
example_message_wrapper_t wrapper = {
.version = 1,
.payload_length = (uint16_t)asn1_len,
.payload = { .ptr = asn1_buf, .len = asn1_len },
};
uint8_t wire_buf[256];
size_t written;
wirespec_result_t rc = example_message_wrapper_serialize(
&wrapper, wire_buf, sizeof(wire_buf), &written);
// wire_buf に完全なバイナリフレームが格納されるビルド
asn1c と wirespec の生成ファイルを一緒にコンパイルします:
gcc -Wall -Wextra -std=c11 \
-I asn1c_output/ \
-I wirespec_output/ \
-I path/to/wirespec/runtime/ \
-o my_app \
my_app.c \
wirespec_output/example.c \
asn1c_output/*.c \
-lm-lm フラグは asn1c が内部で数学関数を使用するために必要です。
ポイント
- wirespec がバイナリフレーミング(ヘッダフィールド、長さプレフィックス、エンディアン)を担当
- asn1c が ASN.1 エンコーディング/デコーディング(UPER, BER, DER など)を担当
- 2 つのライブラリは生のバイトバッファで連携し、密結合はない
- このパターンは asn1c に限らず、任意の ASN.1 ライブラリで使える