Skip to content

C API リファレンス

wirespec はヒープアロケーションなしの C11 コードを生成します。メモリはすべてスタック上か、呼び出し元が提供するバッファを使います。


命名規則

モジュール mod.sub、型 FooBar の場合、生成シンボルのプレフィックスは mod_sub_foo_bar_:

モジュールC プレフィックス
quic.framesQuicFramequic_frames_quic_frame_
quic.varintVarIntquic_varint_var_int_
ble.attAttPduble_att_att_pdu_
mqttMqttPacketmqtt_mqtt_packet_

モジュールパスと型名をそれぞれ snake_case に変換し、_ で結合します。


生成される関数

モジュール mod 内の packetframecapsuleFoo に対して:

c
/* パース: buf[0..len] を読み取り、*out にデコード、消費バイト数を *consumed に返す */
wirespec_result_t mod_foo_parse(
    const uint8_t *buf, size_t len,
    mod_foo_t     *out, size_t *consumed);

/* シリアライズ: buf[0..cap] に書き込み、書き込みバイト数を *written に返す */
wirespec_result_t mod_foo_serialize(
    const mod_foo_t *val,
    uint8_t *buf, size_t cap, size_t *written);

/* serialize() が書き込む正確なバイト数を返す */
size_t mod_foo_serialized_len(const mod_foo_t *val);

パースの契約:

  • 成功時は WIRESPEC_OK を返し、*consumed を更新
  • 失敗時はエラーコードを返す。*out の内容は不定
  • buf + len を超えた読み取りはしない

シリアライズの契約:

  • 成功時は WIRESPEC_OK を返し、*written を更新
  • cap < mod_foo_serialized_len(val) なら WIRESPEC_ERR_SHORT_BUFFER を返す
  • buf + cap を超えた書き込みはしない

使用例:

c
#include "quic_frames.h"
#include "wirespec_runtime.h"

uint8_t buf[4096];
size_t  len = read_packet(buf, sizeof(buf));

quic_frames_quic_frame_t frame;
size_t consumed;
wirespec_result_t rc = quic_frames_quic_frame_parse(buf, len, &frame, &consumed);
if (rc != WIRESPEC_OK) {
    handle_error(rc);
    return;
}

/* ラウンドトリップ: シリアライズして戻す */
uint8_t out[4096];
size_t  written;
rc = quic_frames_quic_frame_serialize(&frame, out, sizeof(out), &written);

メモリモデル — 3 段階方式

wirespec はヒープアロケーションを一切行いません。フィールドは以下の 3 段階のいずれかにマッピングされます:

段階対象方式C 表現
Abytes[...]ゼロコピー: 入力バッファへのポインタ + 長さwirespec_bytes_t
B[scalar; N]実体化: 要素ごとに memcpy + バイトスワップ固定サイズ配列
C[composite; N]実体化: 各構造体要素をパース構造体配列 + カウント

段階 A — ゼロコピーバイト

bytes[N]bytes[length: expr]bytes[remaining]bytes[length_or_remaining: expr] はすべて wirespec_bytes_t にマッピング:

c
typedef struct {
    const uint8_t *ptr; /* 元の入力バッファを指す */
    size_t         len;
} wirespec_bytes_t;

ptr はパース入力バッファへのスライスです。パース結果を使う間は入力バッファを保持してください。

段階 B — スカラー配列

[u16le; count] などのスカラー配列は、カウントフィールド付きの固定サイズ配列として実体化:

c
uint16_t items[WIRESPEC_MAX_ARRAY_ELEMENTS]; /* または @max_len のキャパシティ */
size_t   items_count;

バイトスワップはパース・シリアライズ時に自動で行われます。

段階 C — 複合配列

[AckRange; count] などの構造体配列は、要素型の固定サイズ配列として実体化:

c
quic_frames_ack_range_t ack_ranges[WIRESPEC_MAX_ARRAY_ELEMENTS];
size_t                  ack_ranges_count;

配列キャパシティ

デフォルトキャパシティは WIRESPEC_MAX_ARRAY_ELEMENTS(デフォルト 64)。

グローバルに変更する場合:

bash
gcc -DWIRESPEC_MAX_ARRAY_ELEMENTS=128 ...

フィールド単位で変更するには .wspec@max_len(N) を使います。

要素数がキャパシティを超えると WIRESPEC_ERR_CAPACITY が返ります。


オプションフィールド

if COND { T } は bool フラグ + 値に展開されます:

c
/* ワイヤソース: ecn_counts: if frame_type == 0x03 { EcnCounts } */
bool                         has_ecn_counts;
quic_frames_ecn_counts_t     ecn_counts;

has_ecn_countsfalse のとき、ecn_counts はゼロ初期化されており、値として使うべきではありません。

wirespec の ?? 演算子は C では三項演算子に展開: offset_raw ?? 0(has_offset_raw ? offset_raw : 0)


フレームとカプセルのユニオン

タグ付きユニオン型(framecapsule)はタグ enum + discriminated union を生成:

c
/* 生成元: frame QuicFrame = match frame_type: VarInt { ... } */

typedef enum {
    QUIC_FRAMES_QUIC_FRAME_TAG_PADDING    = 0,
    QUIC_FRAMES_QUIC_FRAME_TAG_PING       = 1,
    QUIC_FRAMES_QUIC_FRAME_TAG_ACK        = 2,
    QUIC_FRAMES_QUIC_FRAME_TAG_CRYPTO     = 6,
    QUIC_FRAMES_QUIC_FRAME_TAG_STREAM     = 8,
    QUIC_FRAMES_QUIC_FRAME_TAG_UNKNOWN    = -1,
} quic_frames_quic_frame_tag_t;

typedef struct {
    quic_frames_quic_frame_tag_t tag;
    union {
        quic_frames_quic_frame_padding_t  padding;
        quic_frames_quic_frame_ping_t     ping;
        quic_frames_quic_frame_ack_t      ack;
        quic_frames_quic_frame_crypto_t   crypto;
        quic_frames_quic_frame_stream_t   stream;
        quic_frames_quic_frame_unknown_t  unknown;
    } data;
} quic_frames_quic_frame_t;

タグでディスパッチ:

c
switch (frame.tag) {
    case QUIC_FRAMES_QUIC_FRAME_TAG_ACK:
        process_ack(&frame.data.ack);
        break;
    case QUIC_FRAMES_QUIC_FRAME_TAG_STREAM:
        process_stream(&frame.data.stream);
        break;
    default:
        break;
}

ステートマシン

ステートマシンはタグ enum、データ union、ディスパッチ関数を生成:

c
/* 生成元: state machine PathState { ... } */

typedef enum {
    MPQUIC_PATH_PATH_STATE_TAG_INIT       = 0,
    MPQUIC_PATH_PATH_STATE_TAG_VALIDATING = 1,
    MPQUIC_PATH_PATH_STATE_TAG_ACTIVE     = 2,
    MPQUIC_PATH_PATH_STATE_TAG_STANDBY    = 3,
    MPQUIC_PATH_PATH_STATE_TAG_CLOSING    = 4,
    MPQUIC_PATH_PATH_STATE_TAG_CLOSED     = 5,
} mpquic_path_path_state_tag_t;

typedef struct {
    mpquic_path_path_state_tag_t tag;
    union {
        mpquic_path_path_state_init_t       init;
        mpquic_path_path_state_validating_t validating;
        mpquic_path_path_state_active_t     active;
        mpquic_path_path_state_standby_t    standby;
        mpquic_path_path_state_closing_t    closing;
        /* closed にデータなし */
    } data;
} mpquic_path_path_state_t;

/* イベントをステートマシンに適用し、インプレースで遷移 */
wirespec_result_t mpquic_path_path_state_dispatch(
    mpquic_path_path_state_t  *sm,
    mpquic_path_path_event_tag_t event,
    mpquic_path_path_event_data_t *event_data);

成功時、*sm は新しい状態を保持します。該当する遷移がなければ WIRESPEC_ERR_INVALID_STATE を返します。


ランタイム API(wirespec_runtime.h

外部依存なしの単一ヘッダ(500 行未満)。

カーソル

c
typedef struct {
    const uint8_t *buf;
    size_t         pos;
    size_t         len;
} wirespec_cursor_t;

void wirespec_cursor_init(wirespec_cursor_t *c,
                          const uint8_t *buf, size_t len);

読み取り関数

c
wirespec_result_t wirespec_cursor_read_u8   (wirespec_cursor_t *c, uint8_t  *out);
wirespec_result_t wirespec_cursor_read_u16be(wirespec_cursor_t *c, uint16_t *out);
wirespec_result_t wirespec_cursor_read_u16le(wirespec_cursor_t *c, uint16_t *out);
wirespec_result_t wirespec_cursor_read_u32be(wirespec_cursor_t *c, uint32_t *out);
wirespec_result_t wirespec_cursor_read_u32le(wirespec_cursor_t *c, uint32_t *out);
wirespec_result_t wirespec_cursor_read_u64be(wirespec_cursor_t *c, uint64_t *out);
wirespec_result_t wirespec_cursor_read_u64le(wirespec_cursor_t *c, uint64_t *out);

/* ゼロコピー: out->ptr をカーソルのバッファにセットし、pos を len 分進める */
wirespec_result_t wirespec_cursor_read_bytes(wirespec_cursor_t *c,
                                             size_t len,
                                             wirespec_bytes_t *out);

/* within EXPR スコープ用: sub_len バイトのサブカーソルを切り出す */
wirespec_result_t wirespec_cursor_sub(wirespec_cursor_t *parent,
                                      size_t sub_len,
                                      wirespec_cursor_t *sub);

書き込み関数

c
wirespec_result_t wirespec_write_u8   (uint8_t  *buf, size_t cap, size_t *pos, uint8_t  val);
wirespec_result_t wirespec_write_u16be(uint8_t  *buf, size_t cap, size_t *pos, uint16_t val);
wirespec_result_t wirespec_write_u16le(uint8_t  *buf, size_t cap, size_t *pos, uint16_t val);
wirespec_result_t wirespec_write_u32be(uint8_t  *buf, size_t cap, size_t *pos, uint32_t val);
wirespec_result_t wirespec_write_u32le(uint8_t  *buf, size_t cap, size_t *pos, uint32_t val);
wirespec_result_t wirespec_write_u64be(uint8_t  *buf, size_t cap, size_t *pos, uint64_t val);
wirespec_result_t wirespec_write_u64le(uint8_t  *buf, size_t cap, size_t *pos, uint64_t val);

/* bytes->ptr から bytes->len バイトを書き込む */
wirespec_result_t wirespec_write_bytes(uint8_t *buf, size_t cap, size_t *pos,
                                       const wirespec_bytes_t *bytes);

読み取り/書き込み関数はいずれも、バッファが足りなければ WIRESPEC_ERR_SHORT_BUFFER を返します。


ヘッダのインクルード

生成された .h は相対パスで wirespec_runtime.h をインクルードします。ランタイムディレクトリをインクルードパスに追加してください:

bash
gcc -I path/to/runtime -o my_app my_app.c quic_frames.c quic_varint.c

ランタイムヘッダは自己完結しており、.c ファイルは不要です。


Rust バックエンド

-t rust.rs ファイルを生成できます:

bash
wirespec compile examples/quic/varint.wspec -t rust -o build/

生成コードは wirespec-rt クレート(runtime/rust/wirespec-rt/)の CursorWriterError 型を使います。詳細は Rust API リファレンスを参照してください。