C API リファレンス
wirespec はヒープアロケーションなしの C11 コードを生成します。メモリはすべてスタック上か、呼び出し元が提供するバッファを使います。
命名規則
モジュール mod.sub、型 FooBar の場合、生成シンボルのプレフィックスは mod_sub_foo_bar_:
| モジュール | 型 | C プレフィックス |
|---|---|---|
quic.frames | QuicFrame | quic_frames_quic_frame_ |
quic.varint | VarInt | quic_varint_var_int_ |
ble.att | AttPdu | ble_att_att_pdu_ |
mqtt | MqttPacket | mqtt_mqtt_packet_ |
モジュールパスと型名をそれぞれ snake_case に変換し、_ で結合します。
生成される関数
モジュール mod 内の packet、frame、capsule 型 Foo に対して:
/* パース: 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を超えた書き込みはしない
使用例:
#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 表現 |
|---|---|---|---|
| A | bytes[...] | ゼロコピー: 入力バッファへのポインタ + 長さ | 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 にマッピング:
typedef struct {
const uint8_t *ptr; /* 元の入力バッファを指す */
size_t len;
} wirespec_bytes_t;ptr はパース入力バッファへのスライスです。パース結果を使う間は入力バッファを保持してください。
段階 B — スカラー配列
[u16le; count] などのスカラー配列は、カウントフィールド付きの固定サイズ配列として実体化:
uint16_t items[WIRESPEC_MAX_ARRAY_ELEMENTS]; /* または @max_len のキャパシティ */
size_t items_count;バイトスワップはパース・シリアライズ時に自動で行われます。
段階 C — 複合配列
[AckRange; count] などの構造体配列は、要素型の固定サイズ配列として実体化:
quic_frames_ack_range_t ack_ranges[WIRESPEC_MAX_ARRAY_ELEMENTS];
size_t ack_ranges_count;配列キャパシティ
デフォルトキャパシティは WIRESPEC_MAX_ARRAY_ELEMENTS(デフォルト 64)。
グローバルに変更する場合:
gcc -DWIRESPEC_MAX_ARRAY_ELEMENTS=128 ...フィールド単位で変更するには .wspec で @max_len(N) を使います。
要素数がキャパシティを超えると WIRESPEC_ERR_CAPACITY が返ります。
オプションフィールド
if COND { T } は bool フラグ + 値に展開されます:
/* ワイヤソース: ecn_counts: if frame_type == 0x03 { EcnCounts } */
bool has_ecn_counts;
quic_frames_ecn_counts_t ecn_counts;has_ecn_counts が false のとき、ecn_counts はゼロ初期化されており、値として使うべきではありません。
wirespec の ?? 演算子は C では三項演算子に展開: offset_raw ?? 0 → (has_offset_raw ? offset_raw : 0)
フレームとカプセルのユニオン
タグ付きユニオン型(frame、capsule)はタグ enum + discriminated union を生成:
/* 生成元: 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;タグでディスパッチ:
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、ディスパッチ関数を生成:
/* 生成元: 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 行未満)。
カーソル
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);読み取り関数
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);書き込み関数
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 をインクルードします。ランタイムディレクトリをインクルードパスに追加してください:
gcc -I path/to/runtime -o my_app my_app.c quic_frames.c quic_varint.cランタイムヘッダは自己完結しており、.c ファイルは不要です。
Rust バックエンド
-t rust で .rs ファイルを生成できます:
wirespec compile examples/quic/varint.wspec -t rust -o build/生成コードは wirespec-rt クレート(runtime/rust/wirespec-rt/)の Cursor、Writer、Error 型を使います。詳細は Rust API リファレンスを参照してください。