言語ツアー
wirespec のワイヤフォーマット機能を、実際に使う順序で一通り解説します。各セクションで機能の説明、実例、生成される C コードを示します。
初めての方は、先にはじめにでコンパイラをセットアップしてください。ステートマシンとモジュールは別ガイドで扱います。
1. プリミティブ型
標準的な符号あり・符号なし整数型を備えています。u8 = 1 バイト、u16 = 2 バイト、以下同様。マルチバイトフィールドのエンディアンはモジュールの @endian 設定に従います(未指定時はビッグエンディアン)。
packet Example {
a: u8, # 1 byte, unsigned
b: u16, # 2 bytes, unsigned
c: u32, # 4 bytes, unsigned
d: u64, # 8 bytes, unsigned
e: i8, # 1 byte, signed
f: i16, # 2 bytes, signed
g: i32, # 4 bytes, signed
h: i64, # 8 bytes, signed
}エンディアンネスを明示的に指定するには、サフィックス付きの型を使います:
packet MixedEndian {
big_val: u32be, # 4 bytes, big-endian
little_val: u32le, # 4 bytes, little-endian
also_big: u16be,
also_le: u16le,
}u24 / u24be / u24le は 24 ビット(3 バイト)符号なし整数です。TLS のメッセージ長など 3 バイト幅のフィールドに使います:
# From examples/tls/tls13.wspec
packet CertificateEntry {
cert_data_length: u24,
cert_data: bytes[cert_data_length],
extensions_length: u16,
extensions: bytes[extensions_length],
}C への対応: u8 → uint8_t、u16 → uint16_t、u32 → uint32_t、u64 → uint64_t。符号あり型は int8_t、int16_t 等。u24 は uint32_t にマップされ、上位バイトはマスクで除去されます。パース関数は適切なバイト数を読み、必要に応じてバイトスワップします。
2. バイト型
固定長、長さプレフィックス付き、スコープ消費の 4 バリアントがあります。
固定長バイト
bytes[N] はちょうど N バイトを読み書きします。コピーではなくゼロコピーのビューを返します。
# From examples/net/ethernet.wspec
packet EthernetFrame {
dst_mac: bytes[6],
src_mac: bytes[6],
ether_type: u16,
payload: bytes[remaining],
}# From examples/tls/tls13.wspec — 32-byte TLS random nonce
packet ClientHello {
legacy_version: u16,
random: bytes[32],
...
}長さプレフィックス付きバイト
bytes[length: EXPR] は EXPR(先行フィールドや式)で指定されたバイト数だけ読み込みます。
# From examples/mqtt/mqtt.wspec
packet MqttString {
length: u16,
data: bytes[length],
}先行フィールドを使った算術式も書けます:
# From examples/net/udp.wspec
packet UdpDatagram {
src_port: u16,
dst_port: u16,
length: u16,
checksum: u16,
require length >= 8,
data: bytes[length: length - 8], # payload = total - 8-byte header
}残りバイト
bytes[remaining] は現在のスコープの残りバイトをすべて消費します。スコープ内の最後のワイヤフィールドである必要があります。
# From examples/ble/att.wspec
frame AttPdu = match opcode: u8 {
0x0b => ReadRsp { value: bytes[remaining] },
0x12 => WriteReq { handle: AttHandle, value: bytes[remaining] },
...
}オプション長バイト
bytes[length_or_remaining: EXPR] は長さフィールドがオプショナルな場合に使います。EXPR が Some なら指定バイト数だけ読み込み、null ならスコープの残り全バイトを消費します。EXPR の型は Option[T](T は整数型)でなければなりません。
# From examples/quic/frames.wspec — Stream frame
0x08..=0x0f => Stream {
stream_id: VarInt,
offset_raw: if frame_type & 0x04 { VarInt },
length_raw: if frame_type & 0x02 { VarInt },
data: bytes[length_or_remaining: length_raw],
...
},C への対応: すべてのバイト型バリアントは wirespec_bytes_t になります。const uint8_t *ptr と size_t len を持つゼロコピー構造体で、メモリ割り当てはなく、ポインタは元の入力バッファを指します。
typedef struct { const uint8_t *ptr; size_t len; } wirespec_bytes_t;3. エンディアンネス
マルチバイト整数フィールドにはエンディアン指定が必要です。モジュールレベルのデフォルトを設定し、フィールド単位でオーバーライドできます。
モジュールレベルのデフォルト
.wspec ファイルの先頭で @endian big または @endian little を指定します:
# From examples/ble/att.wspec — BLE uses little-endian
@endian little
module ble.att
type AttHandle = u16le
type Uuid16 = u16le# QUIC and most network protocols use big-endian
@endian big
module quic.framesフィールドごとのオーバーライド
明示的なエンディアン型(u16le、u32be 等)のフィールドは、モジュールデフォルトに関わらず常にその型のエンディアンを使います。1 つの構造体内でエンディアンを混在させられます。
packet MixedEndian {
net_field: u32, # big-endian (from @endian big)
le_field: u32le, # always little-endian
another_net: u16, # big-endian again
}優先ルール: フィールドの明示的な型指定は常にモジュールデフォルトより優先されます。
4. 定数、列挙型、フラグ
定数
const でコンパイル時定数を定義します。型はプリミティブ整数型に限ります。
# From examples/quic/frames.wspec
const MAX_CID_LENGTH: u8 = 20
packet LengthPrefixedCid {
length: u8,
value: bytes[length],
require length <= MAX_CID_LENGTH,
}列挙型
enum は名前と整数値の対応を定義します。: の後の型(基底型)がワイヤ上のタグ型になります。
# From examples/tls/tls13.wspec
enum ContentType: u8 {
ChangeCipherSpec = 20,
Alert = 21,
Handshake = 22,
ApplicationData = 23,
}
enum HandshakeType: u8 {
ClientHello = 1,
ServerHello = 2,
Certificate = 11,
CertificateVerify = 15,
Finished = 20,
}enum はカプセルの match タグとして直接使えます:
# From examples/tls/tls13.wspec
capsule TlsRecord {
content_type: ContentType,
legacy_version: u16,
length: u16,
payload: match content_type within length {
22 => Handshake { data: bytes[remaining] },
21 => Alert { level: u8, description: u8 },
...
},
}フラグ(ビットマスク)
flags は enum に似ていますが、値をビット OR で組み合わせる前提のビットマスクです。基底型は符号なし整数でなければなりません。
flags PacketFlags: u8 {
KeyPhase = 0x04,
SpinBit = 0x20,
FixedBit = 0x40,
}C への対応: enum も flags も C の enum typedef を生成します。const は #define マクロになります。
5. パケット(構造体)
packet は固定構造を定義します。名前付きフィールドが宣言順にパースされます。プロトコルヘッダの基本的な構成要素です。
# From examples/net/udp.wspec
module net.udp
@endian big
packet UdpDatagram {
src_port: u16,
dst_port: u16,
length: u16,
checksum: u16,
require length >= 8,
data: bytes[length: length - 8],
}# From examples/net/tcp.wspec
packet TcpSegment {
src_port: u16,
dst_port: u16,
seq_num: u32,
ack_num: u32,
data_offset: bits[4],
reserved: bits[4],
cwr: bit, ece: bit, urg: bit, ack: bit,
psh: bit, rst: bit, syn: bit, fin: bit,
window: u16,
checksum: u16,
urgent_pointer: u16,
require data_offset >= 5,
options: bytes[length: data_offset * 4 - 20],
}C への対応: packet は typedef struct になり、3 つの関数が生成されます:
wirespec_result_t udp_datagram_parse(
const uint8_t *buf, size_t len,
udp_datagram_t *out, size_t *consumed);
wirespec_result_t udp_datagram_serialize(
const udp_datagram_t *in,
uint8_t *buf, size_t cap, size_t *written);
size_t udp_datagram_serialized_len(const udp_datagram_t *in);構造体にはスタック割り当ての値のみが入ります。wirespec は malloc を呼びません。
6. フレーム(タグ付きユニオン)
frame はタグ付きユニオンです。先頭フィールドがディスクリミネータとなり、その値で本体の構造が決まります。1 つのオペコード/タイプバイトから複数のレイアウトに分岐する場合に使います。
# From examples/quic/frames.wspec (abbreviated)
frame QuicFrame = match frame_type: VarInt {
0x00 => Padding {},
0x01 => Ping {},
0x06 => Crypto {
offset: VarInt,
length: VarInt,
data: bytes[length],
},
0x08..=0x0f => Stream {
stream_id: VarInt,
offset_raw: if frame_type & 0x04 { VarInt },
length_raw: if frame_type & 0x02 { VarInt },
data: bytes[length_or_remaining: length_raw],
let offset: u64 = offset_raw ?? 0,
let fin: bool = (frame_type & 0x01) != 0,
},
_ => Unknown { data: bytes[remaining] },
}パターン範囲
..= で閉区間のパターン範囲を書けます:
0x02..=0x03— タグ値 2 または 3 にマッチ0x08..=0x0f— 8 から 15(両端含む)にマッチ_— キャッチオール(網羅性のために必須)
具体値 > パターン範囲 > _ の優先順位です。
ブランチフィールドでのタグ参照
タグフィールド(上の例では frame_type)はすべてのブランチ内で参照可能です。if frame_type == 0x03 { EcnCounts } のように正確な値で分岐できます。
C への対応: frame は tag フィールド付きの構造体でラップされた C union になります。各バリアントはネストされた構造体です。パース関数はタグを読み取り、対応するブランチへディスパッチしてフィールドを埋めます。
7. カプセル(within を使った TLV)
capsule は Type-Length-Value コンテナです。固定ヘッダ(長さフィールド含む)の後、ペイロードが長さで区切られたサブスコープ内でパースされます。不正・未知のペイロードが割り当て範囲を超えて読み込むことはありません。
# From examples/mqtt/mqtt.wspec
capsule MqttPacket {
type_and_flags: u8,
remaining_length: MqttLength,
payload: match (type_and_flags >> 4) within remaining_length {
1 => Connect {
protocol_name: MqttString,
protocol_level: u8,
connect_flags: u8,
keep_alive: u16,
client_id: MqttString,
will_topic: if connect_flags & 0x04 { MqttString },
will_message: if connect_flags & 0x04 { MqttBytes },
username: if connect_flags & 0x80 { MqttString },
password: if connect_flags & 0x40 { MqttBytes },
},
3 => Publish {
topic: MqttString,
let qos: u8 = (type_and_flags & 0x06) >> 1,
packet_id: if qos > 0 { u16 },
payload: bytes[remaining],
},
_ => Unknown { data: bytes[remaining] },
},
}within remaining_length は remaining_length バイトに制限されたサブカーソルを作ります。サブスコープ内で:
- 消費不足(バイトが余った)→
WIRESPEC_ERR_TRAILING_DATA - 消費超過(範囲外を読もうとした)→
WIRESPEC_ERR_SHORT_BUFFER
式タグ
capsule(や frame)のタグにはフィールド名だけでなく任意の式も使えます:
payload: match (type_and_flags >> 4) within remaining_length { ... }(type_and_flags >> 4) でフラグバイトの上位ニブルからパケット型を抽出しています。タグ式で参照するフィールドはすべて within より前に宣言されている必要があります。
C への対応: capsule は frame と同じ構造体/union パターンを生成し、長さ境界を強制するサブカーソルが追加されます。
TLS 拡張の例
# From examples/tls/tls13.wspec
capsule Extension {
extension_type: u16,
length: u16,
payload: match extension_type within length {
0x002b => SupportedVersions { data: bytes[remaining] },
0x000d => SignatureAlgorithms { data: bytes[remaining] },
0x0033 => KeyShare { data: bytes[remaining] },
0x0000 => ServerName { data: bytes[remaining] },
_ => Unknown { data: bytes[remaining] },
},
}8. 式言語
式はフィールド条件、導出フィールド、制約、配列サイズで使います。演算子の優先順位(低い順):
| レベル | 演算子 | 備考 |
|---|---|---|
| コアレス | ?? | Option[T] ?? T — デフォルト値でアンラップ |
| 論理 OR | or | |
| 論理 AND | and | |
| 比較 | == != < <= > >= | |
| ビット OR | | | |
| ビット XOR | ^ | |
| ビット AND | & | 比較より強く結合 |
| シフト | << >> | |
| 加減算 | + - | |
| 乗除算 | * / % | |
| 単項 | ! - | |
| 後置 | .field、[idx]、[start..end] |
重要: wirespec ではビット演算子が比較演算子より強く結合します(C とは逆)。a & mask == 0 は常に (a & mask) == 0 と解釈されます。C だと a & (mask == 0) になる定番のバグを回避できます。
?? コアレス演算子
?? でオプショナル値をデフォルト値付きでアンラップします:
0x08..=0x0f => Stream {
offset_raw: if frame_type & 0x04 { VarInt },
...
let offset: u64 = offset_raw ?? 0,
}offset_raw が存在する(0x04 ビットがセット)場合はその値を、存在しない場合は 0 を返します。
フィールド値の算術演算
数値が期待される場所なら、先行フィールドを使った式を自由に書けます:
data: bytes[length: length - 8], # subtract header overhead
cipher_suites: [u16; cipher_suites_length / 2], # count in elements, not bytes
options: bytes[length: data_offset * 4 - 20], # multiply and subtract9. オプションフィールド
if COND { T } で条件付きフィールドを定義します。COND が真のときだけワイヤ上に存在し、型システムでは Option[T] として扱われます。
# From examples/quic/frames.wspec — ECN counts only in ACK type 0x03
0x02..=0x03 => Ack {
largest_ack: VarInt,
ack_range_count: VarInt,
first_ack_range: VarInt,
ack_ranges: [AckRange; ack_range_count],
ecn_counts: if frame_type == 0x03 { EcnCounts },
},# From examples/mqtt/mqtt.wspec — QoS-dependent packet ID
3 => Publish {
topic: MqttString,
let qos: u8 = (type_and_flags & 0x06) >> 1,
packet_id: if qos > 0 { u16 },
payload: bytes[remaining],
},条件にはビットマスクテストなど先行フィールドの任意の式を使えます:
# Present when a specific flag bit is set
offset_raw: if frame_type & 0x04 { VarInt },C への対応: 存在フラグと値のペアになります:
bool has_ecn_counts;
ecn_counts_t ecn_counts;オプションフィールドの値を式中で使うには、同じ条件でガードするか ?? でデフォルト値を与える必要があります。
10. 導出フィールド(let)
let フィールドは他のフィールドから値を計算します。ワイヤ上には存在せずバイトも消費しませんが、C 構造体のメンバになり、後続の式や制約で参照できます。
# From examples/quic/frames.wspec
0x08..=0x0f => Stream {
stream_id: VarInt,
offset_raw: if frame_type & 0x04 { VarInt },
length_raw: if frame_type & 0x02 { VarInt },
data: bytes[length_or_remaining: length_raw],
let offset: u64 = offset_raw ?? 0,
let fin: bool = (frame_type & 0x01) != 0,
},# From examples/mqtt/mqtt.wspec
let qos: u8 = (type_and_flags & 0x06) >> 1,bool はセマンティック型で、ワイヤフィールドにはなれませんが let の型として使えます。導出フィールドは先行するワイヤフィールドと let フィールドを参照できます。
C への対応: let フィールドはパース時に値が設定される通常の構造体メンバになります。シリアライズ時は格納値を使わず式を再計算します。
11. 制約(require と static_assert)
require による実行時制約
require EXPR は実行時チェックです。パース中にこの式が偽なら WIRESPEC_ERR_CONSTRAINT を返します。
# From examples/net/udp.wspec
require length >= 8,
# From examples/quic/frames.wspec
require length <= MAX_CID_LENGTH,
# From examples/net/tcp.wspec
require data_offset >= 5,
# From examples/mqtt/mqtt.wspec — check flag bits
require type_and_flags & 0x0F == 0x02,require は packet、frame ブランチ、capsule 本体のどこにでも配置可能です。先行するワイヤフィールドや let フィールドを参照できます。
static_assert によるコンパイル時アサーション
static_assert EXPR はコンパイル時チェックです。定数間の関係を保証するのに便利です:
const MAX_CID_LENGTH: u8 = 20
static_assert MAX_CID_LENGTH <= 255失敗するとコード生成前にコンパイルエラーになります。
12. 配列
個数指定配列
[T; count] は T 型の要素をちょうど count 個読み込みます。count は先行する整数フィールドで指定します:
# From examples/quic/frames.wspec
ack_ranges: [AckRange; ack_range_count],
# From examples/tls/tls13.wspec — count derived from byte length / element size
cipher_suites: [u16; cipher_suites_length / 2],fill 配列
[T; fill] はスコープの残りを消費し、T を読めるだけ読みます:
# A list that fills the rest of the packet
entries: [Entry; fill],長さ境界付き fill
[T; fill] within EXPR はちょうど EXPR バイトを消費するまで要素を読みます。TLS の拡張リストなどで典型的なパターンです:
# From examples/tls/tls13.wspec
extensions: [Extension; fill] within extensions_length,
# From examples/tls/tls13.wspec
certificate_list: [CertificateEntry; fill] within certificate_list_length,extensions_length バイトのサブスコープを作り、使い切るまで Extension レコードを読みます。
配列のキャパシティ
C への対応: 配列はスタック上に固定キャパシティで確保されます。デフォルトは wirespec_runtime.h の WIRESPEC_MAX_ARRAY_ELEMENTS(64 要素)で、-DWIRESPEC_MAX_ARRAY_ELEMENTS=N でグローバルに変更可能です。
フィールド単位でキャパシティを変えるには @max_len を使います:
packet Foo {
count: u16,
items: [Item; count], # uses default capacity (64)
@max_len(1024)
large_items: [Item; count], # uses capacity 1024
}ワイヤ上の実際のカウントがキャパシティを超えると WIRESPEC_ERR_CAPACITY が返ります。
生成される構造体:
typedef struct {
uint16_t count;
item_t items[64];
item_t large_items[1024];
} foo_t;13. 型エイリアスと計算型
シンプルなエイリアス
type Name = TypeExpr は型エイリアスです。新しいワイヤレイアウトは生まれず、純粋に名前を付けるだけです。
# From examples/ble/att.wspec
type AttHandle = u16le
type Uuid16 = u16le計算型(依存レコード)
右辺が { ... } ブロックなら計算型になります。先行フィールドの値によってフィールドの型が決まる依存レコードです。
# From examples/quic/varint.wspec — QUIC Variable-Length Integer
@strict
type VarInt = {
prefix: bits[2],
value: match prefix {
0b00 => bits[6],
0b01 => bits[14],
0b10 => bits[30],
0b11 => bits[62],
},
}まず prefix(2 ビット)を読み、その値に応じて value のビット幅が決まります。計算型は通常の型として扱えるので、型が求められる場所ならどこでも使えます。
@strict を付けると、パース時に非正規エンコードを拒否します。たとえば 1 バイトで表現できる値を 2 バイト形式でエンコードした場合に WIRESPEC_ERR_NONCANONICAL を返します。
14. 継続ビット VarInt
各バイトの MSB(または LSB)を継続フラグとし、残りのビットがデータを運ぶ可変長整数方式です。MQTT、Protocol Buffers、LEB128 がこのパターンを使います。
# From examples/mqtt/mqtt.wspec — MQTT Remaining Length
type MqttLength = varint {
continuation_bit: msb, # MSB=1 means more bytes follow
value_bits: 7, # 7 data bits per byte
max_bytes: 4, # maximum encoded length: 4 bytes (268,435,455)
byte_order: little, # lower-order groups come first
}varint キーワードで継続ビット VarInt 型を定義します。パラメータ:
| パラメータ | 値 | 説明 |
|---|---|---|
continuation_bit | msb または lsb | 継続フラグのビット位置 |
value_bits | 整数 | バイトあたりのデータビット数 |
max_bytes | 整数 | オーバーフロー前の最大エンコードバイト数 |
byte_order | little または big | 下位グループが先かどうか |
max_bytes バイト読んでもまだ継続ビットがセットされていれば WIRESPEC_ERR_OVERFLOW を返します。
C への対応: 最大値に収まる最小の uintN_t にデコードされます。MqttLength(最大 4 バイト、28 有効ビット)なら uint32_t です。
15. bits[N] と BitGroup パッキング
bits[N] は N ビットを符号なし整数として読みます。bit は bits[1] の省略形です。複数の小さなフィールドを 1 バイトや 1 ワードにパックするヘッダで使います。
# From examples/ip/ipv4.wspec
packet IPv4Header {
version: bits[4],
ihl: bits[4],
dscp: bits[6],
ecn: bits[2],
total_length: u16,
identification: u16,
flags: bits[3],
fragment_offset: bits[13],
ttl: u8,
protocol: u8,
...
}# From examples/net/tcp.wspec
packet TcpSegment {
...
data_offset: bits[4],
reserved: bits[4],
cwr: bit,
ece: bit,
urg: bit,
ack: bit,
psh: bit,
rst: bit,
syn: bit,
fin: bit,
window: u16,
...
}BitGroup 自動グループ化
連続する bits[N] / bit フィールドは自動的にグループ化され、1 回の読み込みになります。コンパイラは:
- ビット幅の合計から読み込むバイト数を決定(整数バイトでなければならない)
- そのバイトを 1 回で読む
- シフト+マスクで各フィールドを抽出
ビッグエンディアンでは最初に宣言したフィールドが MSB 側、リトルエンディアン(@endian little)では LSB 側に配置されます。
C への対応: 各フィールドは収まる最小の型(uint8_t、uint16_t、uint32_t)になります。パース関数のコード例:
uint8_t _byte0 = buf[pos];
out->version = (_byte0 >> 4) & 0x0f;
out->ihl = (_byte0 >> 0) & 0x0f;ビットグループの合計がバイト境界に揃っていない場合はコンパイルエラーになります。
16. @checksum アノテーション
@checksum を付けたフィールドは、パース時に自動検証、シリアライズ時に自動計算されます。
# From examples/ip/ipv4.wspec
packet IPv4Header {
version: bits[4],
ihl: bits[4],
...
@checksum(internet)
header_checksum: u16,
src_addr: u32,
dst_addr: u32,
}パース時: 構造体全体のバイトに対してチェックサムを計算・検証します。不一致なら WIRESPEC_ERR_CHECKSUM を返します。
シリアライズ時: チェックサムフィールドをゼロクリアしてからシリアライズし、計算結果を書き戻します。
対応アルゴリズム
| アルゴリズム | フィールド型 | 規格 |
|---|---|---|
internet | u16 | RFC 1071 1 の補数和(IPv4、UDP、TCP) |
crc32 | u32 | IEEE 802.3 CRC-32 |
crc32c | u32 | CRC-32C(Castagnoli)、SCTP、iSCSI で使用 |
fletcher16 | u16 | RFC 1146 Fletcher-16 |
# From examples/checksum/crc32_test.wspec
packet Crc32Packet {
id: u16,
length: u16,
require length >= 8,
data: bytes[length: length - 8],
@checksum(crc32)
checksum: u32,
}1 つの packet または frame ブランチに付けられる @checksum は最大 1 つです。フィールド型はアルゴリズムが要求する型と一致しなければなりません。
まとめ
ここまでの機能をほぼ網羅した実例として、examples/quic/frames.wspec の QUIC フレーム定義を示します:
module quic.frames
@endian big
import quic.varint.VarInt
const MAX_CID_LENGTH: u8 = 20
packet AckRange { gap: VarInt, ack_range: VarInt }
packet EcnCounts { ect0: VarInt, ect1: VarInt, ecn_ce: VarInt }
packet LengthPrefixedCid {
length: u8,
value: bytes[length],
require length <= MAX_CID_LENGTH,
}
frame QuicFrame = match frame_type: VarInt {
0x00 => Padding {},
0x01 => Ping {},
0x02..=0x03 => Ack {
largest_ack: VarInt,
ack_delay: VarInt,
ack_range_count: VarInt,
first_ack_range: VarInt,
ack_ranges: [AckRange; ack_range_count],
ecn_counts: if frame_type == 0x03 { EcnCounts },
},
0x06 => Crypto {
offset: VarInt,
length: VarInt,
data: bytes[length],
},
0x08..=0x0f => Stream {
stream_id: VarInt,
offset_raw: if frame_type & 0x04 { VarInt },
length_raw: if frame_type & 0x02 { VarInt },
data: bytes[length_or_remaining: length_raw],
let offset: u64 = offset_raw ?? 0,
let fin: bool = (frame_type & 0x01) != 0,
},
0x18 => NewConnectionId {
sequence: VarInt,
retire_prior: VarInt,
cid_length: u8,
cid: bytes[cid_length],
reset_token: bytes[16],
},
0x1c..=0x1d => ConnectionClose {
error_code: VarInt,
offending_frame_type: if frame_type == 0x1c { VarInt },
reason_length: VarInt,
reason_phrase: bytes[reason_length],
},
0x30..=0x31 => Datagram {
length: if frame_type & 0x01 { VarInt },
data: bytes[length_or_remaining: length],
},
_ => Unknown { data: bytes[remaining] },
}この定義だけで import、const、packet、frame、パターン範囲、オプションフィールド、導出フィールド、配列、bytes[length]、bytes[remaining]、bytes[length_or_remaining]、ビットマスク条件、??、require をカバーしています。