Ship a working HexPigeon: typed binary parsing in Rust/WASM, a three-pane frontend, and unit tests covering the field types. Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
3.0 KiB
Rust
111 lines
3.0 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
/// A full schema describing a binary frame layout.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Schema {
|
|
/// Human-readable name for this schema.
|
|
pub name: Option<String>,
|
|
/// Endianness applied globally unless overridden per field.
|
|
#[serde(default = "default_endian")]
|
|
pub endian: Endian,
|
|
/// Ordered list of fields to parse.
|
|
pub fields: Vec<FieldDef>,
|
|
}
|
|
|
|
fn default_endian() -> Endian {
|
|
Endian::Big
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum Endian {
|
|
Big,
|
|
Little,
|
|
}
|
|
|
|
/// A single field definition.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FieldDef {
|
|
/// Display name.
|
|
pub name: String,
|
|
/// Field type.
|
|
#[serde(rename = "type")]
|
|
pub kind: FieldKind,
|
|
/// Override global endianness for this field.
|
|
pub endian: Option<Endian>,
|
|
/// Description / comment shown in the tree.
|
|
pub description: Option<String>,
|
|
/// For bitflags: the flag definitions.
|
|
pub flags: Option<Vec<FlagDef>>,
|
|
/// For `bytes` type: number of bytes (required).
|
|
/// For `string_lp8` / `string_lp16`: omit (length is read from data).
|
|
/// For other types: derived from type width.
|
|
pub length: Option<usize>,
|
|
/// For `crc16` / `crc32`: byte range to checksum [start, end) relative to frame start.
|
|
pub checksum_range: Option<[usize; 2]>,
|
|
/// For `repeated`: how many repetitions (constant or from a previously parsed field name).
|
|
pub count: Option<CountSpec>,
|
|
/// For `repeated`: the inner fields to repeat.
|
|
pub fields: Option<Vec<FieldDef>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum FieldKind {
|
|
/// 1 byte unsigned.
|
|
Uint8,
|
|
/// 2 bytes unsigned.
|
|
Uint16,
|
|
/// 4 bytes unsigned.
|
|
Uint32,
|
|
/// 8 bytes unsigned.
|
|
Uint64,
|
|
/// 1 byte signed.
|
|
Int8,
|
|
/// 2 bytes signed.
|
|
Int16,
|
|
/// 4 bytes signed.
|
|
Int32,
|
|
/// 4 bytes float.
|
|
Float32,
|
|
/// 8 bytes float.
|
|
Float64,
|
|
/// 1-byte bitfield with named flags.
|
|
Bitflags8,
|
|
/// 2-byte bitfield with named flags.
|
|
Bitflags16,
|
|
/// Raw bytes of `length`.
|
|
Bytes,
|
|
/// Null-terminated string of `length` bytes.
|
|
StringFixed,
|
|
/// Pascal-style: 1-byte length prefix, then UTF-8 bytes.
|
|
StringLp8,
|
|
/// Pascal-style: 2-byte length prefix (respects endian), then UTF-8 bytes.
|
|
StringLp16,
|
|
/// CRC-16/CCITT checksum (2 bytes).
|
|
Crc16,
|
|
/// CRC-32 checksum (4 bytes).
|
|
Crc32,
|
|
/// Repeat inner `fields` `count` times.
|
|
Repeated,
|
|
/// Skip / padding bytes.
|
|
Padding,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FlagDef {
|
|
/// Bit index (0 = LSB).
|
|
pub bit: u8,
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(untagged)]
|
|
pub enum CountSpec {
|
|
/// Literal count.
|
|
Fixed(usize),
|
|
/// Name of a previously parsed uint field holding the count.
|
|
Field(String),
|
|
}
|