Skip to content

ASN.1 統合

wirespec はバイナリプロトコル記述に ASN.1 エンコーディングされたフィールドを埋め込むことができます。SUPL、V2X ITS、3GPP LPP など、バイナリワイヤヘッダと ASN.1 ペイロードを組み合わせるプロトコルに有用です。

基本的な使い方

extern asn1 で外部 ASN.1 型を宣言し、フィールド型として使用します:

wire
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 呼び出しを生成します:

rust
pub struct ItsCamPacket {
    pub version: u8,
    pub length: u16,
    pub cam: CAM,  // デコード済み ASN.1 型(&[u8] ではない)
}

cam フィールドは完全にデコードされた ASN.1 型です。パース時に自動デコードし、シリアライズ時に再エンコードして length フィールドを再計算します。

C バックエンド

C バックエンドでは ASN.1 フィールドは生バイトとして扱われます:

c
typedef struct {
    uint8_t version;
    uint16_t length;
    const uint8_t *cam;  // 生バイト
} its_cam_packet_t;

C ユーザーは asn1c 等の C ASN.1 ライブラリで手動デコードします。

対応エンコーディング

エンコーディングwirespec 名用途
UPERuper3GPP、V2X、LTE
BERberLDAP、SNMP
DERderX.509、CMS
APERaperS1AP、NGAP
OERoerV2X IEEE 1609.2
COERcoerCanonical OER

長さの指定

2 つの形式があります:

wire
// 長さプレフィックス: `length` バイト分を読み取る
payload: asn1(Type, encoding: uper, length: length_field)

// 残り全部: 残りのバイトをすべて消費
payload: asn1(Type, encoding: uper, remaining)

自動コンパイル (asn1 フィーチャー)

asn1 Cargo フィーチャーを有効にすると、wirespec は rasn-compiler 経由で .asn1 ファイルを自動コンパイルします:

bash
cargo run --features asn1 -- compile its.wspec -t rust -o build/

rasn 生成型と wirespec コーデックの両方が出力されます。extern asn1use 句は自動解決されます。

フィーチャーなしの場合、use 句を手動で指定する必要があります:

wire
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 パケットを定義:

asn1
-- messages.asn1
Messages DEFINITIONS AUTOMATIC TAGS ::= BEGIN
  SimpleMessage ::= SEQUENCE {
    id INTEGER (0..255),
    active BOOLEAN
  }
END
wire
module example

@endian big

packet MessageWrapper {
    version: u8,
    payload_length: u16,
    payload: bytes[length: payload_length],
}

2. 両方をコンパイル:

bash
# 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 でデコード:

c
#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 でシリアライズ:

c
// 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 の生成ファイルを一緒にコンパイルします:

bash
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 ライブラリで使える