moon-boofuzz

    Deterministic protocol fuzzing core with native TCP/UDP execution and portable JSONL replay

    fuzzing
    protocol
    boofuzz
    testing
    Download zip
    Author
    Version
    0.1.3
    License
    GPL-2.0-only
    Last updated
    8 hours ago
    Downloads
    8

    #moon-boofuzz

    MoonBit 协议模糊测试核心:定义协议、生成单字段变异、执行前置会话、通过 TCP/UDP 发送、记录结果并重放。

    采用 boofuzz 固定版本 518c13904fc32e7f2cc88c9dec934e509062953e 的明确功能子集;核心支持 Wasm 与 Native,网络和文件 I/O 支持 Windows/Linux Native。已发布到 mooncakes.io(页面显示最新版本);支持范围与兼容边界见 UPSTREAM.md。

    #前置环境

    • 安装 MoonBit。纯逻辑核心(协议模型与变异枚举)在 Wasm 下即可使用,无需 C 环境。
    • 网络与文件 I/O 仅支持 Native,需要系统 C 编译器:Windows 推荐 llvm-mingw(把 bin 加入 PATH)或 Visual Studio MSVC,Linux 使用 GCC。当前验证工具链为 moon 0.1.20260920、moonc v0.10.14。

    #使用示例

    #1. 直接安装 CLI 使用

    无需克隆仓库(需本机 C 编译器),从 mooncakes 全局安装:

    moon install GuoXBQ-Q/moon-boofuzz/cmd/boofuzz # 主 CLI,装到 ~/.moon/bin moon install GuoXBQ-Q/moon-boofuzz/cmd/httpd # 可选:自带 HTTP fuzz 靶子

    httpd 是一个严格解析的回环 HTTP 服务器,对畸形输入返回明确的拒绝状态码(431/413/405 等),让 fuzzer 能区分"被拒绝"和"连接被丢弃"。

    保存协议定义 http.json(与仓库 examples/http.json 相同——只有 uri 字段开启变异,其余请求行/头部字节全部冻结,完整枚举共 1954 个用例):

    { "schema_version": 1, "requests": [{ "name": "request", "children": [ {"type": "text", "name": "method", "value": "GET", "fuzzable": false}, {"type": "static", "name": "sp1", "value_hex": "20"}, {"type": "text", "name": "uri", "value": "/index.html", "fuzzable": true}, {"type": "static", "name": "sp2", "value_hex": "20"}, {"type": "text", "name": "version", "value": "HTTP/1.1", "fuzzable": false}, {"type": "static", "name": "crlf0", "value_hex": "0d0a"}, {"type": "static", "name": "name_0", "value_hex": "486f73743a20"}, {"type": "text", "name": "hdr_0", "value": "127.0.0.1:9000", "fuzzable": false}, {"type": "static", "name": "crlf1", "value_hex": "0d0a"}, {"type": "static", "name": "name_1", "value_hex": "557365722d4167656e743a20"}, {"type": "text", "name": "hdr_1", "value": "moon-boofuzz", "fuzzable": false}, {"type": "static", "name": "crlf2", "value_hex": "0d0a"}, {"type": "static", "name": "name_2", "value_hex": "436f6e6e656374696f6e3a20"}, {"type": "text", "name": "hdr_2", "value": "close", "fuzzable": false}, {"type": "static", "name": "crlf3", "value_hex": "0d0a"}, {"type": "static", "name": "head_end", "value_hex": "0d0a"}, {"type": "text", "name": "body", "value": "", "fuzzable": false} ] }], "execution": { "transport": "tcp", "endpoint": {"host": "127.0.0.1", "port": 9000, "receive_timeout_ms": 500}, "policies": {"request": {"kind": "until", "delimiter_hex": "0d0a0d0a"}} } }

    靶子有两种启动方式,任选其一:

    方式 A · 手动启动:另开一个终端单独运行靶子,fuzz 命令只负责发送。

    # 终端 1:启动靶子 httpd --port 9000 # httpd listening on 127.0.0.1:9000 # 终端 2:执行 fuzz boofuzz run http.json --output cases.jsonl

    方式 B · --target-cmd 一条命令:boofuzz 自己孵化靶子并开启进程监视——开跑前自动拉起(含约 1 秒启动延迟),运行期内靶子的任何退出(包括正常退出 0)都会把该用例记为 MonitorFailed 故障、自动重启靶子后续跑,结束后子进程自动回收,全程无需另开终端。命令含空格时整体加引号。

    boofuzz run http.json --output cases.jsonl --target-cmd "httpd --port 9000"

    崩溃监视的实战效果可用仓库的 examples/httpd_lab.json 体验(模拟 RCE 与缓冲区溢出的崩溃注入场景;注意其 execution 自带 "case_limit": 6)。

    两种方式下 run 的行为相同:逐例变异发送,把真实收发字节写入 JSONL 和 boofuzz-results/ 下的 SQLite 结果库。启动时会打印实时 Web 界面地址,fuzz 过程中用浏览器打开它即可查看进度:

    Web interface can be found at http://localhost:26000

    页面提供进度条(当前用例 / 总数)、运行速率、崩溃列表和暂停/恢复按钮(暂停会挂起用例循环),失败或崩溃条目可点进 /test-case/<n> 查看逐例日志。--web-port N 可改端口(被占用时自动顺延,0 为随机空闲端口)。对本地 httpd 全速约 80 例/秒,此定义完整跑约 25 秒,结束时输出汇总:

    {"kind":"run_summary","executed":1954,"state":"exhausted","records":"cases.jsonl","database":"boofuzz-results/run-<UTC时间戳>.db"}

    分析结果(每条记录包含完整的收发字节,1954 例约 117 MB;report 默认可读约 2 GiB 以内的文件,超过时按报错提示处理):

    boofuzz report cases.jsonl

    {"kind":"report","summary":{"total":1954,"outcomes":{"passed":1947,"connection_ignored":7}, "failures":[{"line":240,"case_id":"[\"request\"]/v1:request.uri:239","outcome":"connection_ignored", "failed_step":0,"detail":"ConnectionIgnored(\"send reset/aborted at step 0\")"}, ...]},"error":null}

    从源码使用(想阅读/修改工具本身时):克隆仓库后先 moon update 初始化依赖索引,moon check --deny-warn 做全量类型检查,moon build --target native 构建全部可执行(CLI、httpd 靶子等),moon test --target native --deny-warn 跑完整测试(含临时文件、回环地址和临时端口、无需另启服务的自动验收场景);此后所有子命令的等价写法是 moon run --target native cmd/boofuzz -- <子命令> ...。

    #2. 作为库使用(MoonBit API)

    以 examples/http_get_full(头部丰富的 HTTP GET 变异,与 examples/http_get_full.json 等价)为蓝本的精简版。在自己的模块 moon add GuoXBQ-Q/moon-boofuzz 后,新建可执行包 cmd/main:

    cmd/main/moon.pkg:

    import { "GuoXBQ-Q/moon-boofuzz" @boofuzz, "GuoXBQ-Q/moon-boofuzz/runner", "GuoXBQ-Q/moon-boofuzz/transport", } supported_targets = "native" pkgtype(kind: "executable")

    cmd/main/main.mbt:

    ///|
    /// 冻结的结构字节:空格、CRLF、头名,永不变异。
    fn fixed(bytes : Bytes) -> @boofuzz.Field {
    @boofuzz.Field::simple(bytes, [], fuzzable=false)
    }

    ///|
    /// 请求行 + Host/Accept/X-Fuzz 头。变异点:动词组(GET/HEAD)、
    /// URI 显式候选、两个字符串库字段。
    fn http_get() -> @boofuzz.CompiledRequest raise {
    @boofuzz.CompiledRequest::compile(
    @boofuzz.Block::new("http_get", [
    @boofuzz.Leaf("verb", @boofuzz.Field::group([b"GET", b"HEAD"])),
    @boofuzz.Leaf("sp1", fixed(b" ")),
    @boofuzz.Leaf(
    "uri",
    @boofuzz.Field::simple(b"/", [b"/echo", b"/headers", b"/nope"]),
    ),
    @boofuzz.Leaf("sp2", fixed(b" ")),
    @boofuzz.Leaf("version", fixed(b"HTTP/1.1")),
    @boofuzz.Leaf("crlf0", fixed(b"\r\n")),
    @boofuzz.Leaf("host", fixed(b"Host: 127.0.0.1:9000\r\n")),
    @boofuzz.Leaf("accept_name", fixed(b"Accept: ")),
    @boofuzz.Leaf("accept", @boofuzz.Field::text("application/json")),
    @boofuzz.Leaf("crlf1", fixed(b"\r\n")),
    @boofuzz.Leaf("xfuzz_name", fixed(b"X-Fuzz: ")),
    @boofuzz.Leaf("xfuzz", @boofuzz.Field::text("seed")),
    @boofuzz.Leaf("crlf2", fixed(b"\r\n")),
    @boofuzz.Leaf("head_end", fixed(b"\r\n")),
    ]),
    )
    }

    ///|
    fn main raise {
    let request = http_get()
    let graph = @boofuzz.SessionGraph::new()
    graph.add(request)
    let paths = graph.paths(targets=["http_get"])
    let endpoint = @transport.Endpoint::new(
    "127.0.0.1",
    9000,
    connect_timeout_ms=750,
    receive_timeout_ms=750,
    )
    // 读到 HTTP 头结束为止;每例新建连接。
    let policies : Map[String, @runner.ReadPolicy] = Map([
    ("http_get", @runner.Until(b"\r\n\r\n")),
    ])
    let runner = @runner.Runner::new(
    paths,
    endpoint,
    policies~,
    limit=@runner.NO_CASE_LIMIT,
    restart_threshold=Some(1),
    restart_sleep_ms=0,
    )
    for ;; {
    match runner.next() {
    Some(result) =>
    println(
    "\{result.case.field_path}:\{result.case.mutation_index} -> \{result.outcome.name()}",
    )
    None => break
    }
    }
    }

    对着 httpd 靶子运行(先按上面方式启动 httpd --port 9000):

    moon run --target native cmd/main

    输出形如 uri:1 -> timeout、verb:0 -> ... 的逐例结果。完整版(参数化 host/port、离线生成模式、Web UI 实时面板与暂停)见 examples/http_get_full/main.mbt;从零编写自己的 fuzz 程序见 CODE.md 教程,模型与字段原语见 MODEL.md,会话与执行器细节见 SESSION.md、RUNNER.md 与 MONITORS.md。

    #boofuzz 命令简介

    boofuzz 的子命令围绕"定义 → 执行 → 分析/重放"组织(源码仓库内的等价写法是 moon run --target native cmd/boofuzz -- <子命令> ...):

    命令用途
    boofuzz generate DEFINITION.jsonJSON 协议定义 → 变异载荷 JSONL,不连接目标
    boofuzz run DEFINITION.json --output CASES.jsonl逐例执行并保存实际流量;--target-cmd CMD 可让 boofuzz 自己孵化并监视靶子(崩溃记为 fault 并自动重启),--web-port N 控制实时界面端口
    boofuzz report CASES.jsonl分类计数、失败身份和行号
    boofuzz replay CASES.jsonl --id CASE_ID按身份重放保存的字节,可用 --host/--port 覆盖目标
    boofuzz open FILE结果库/JSONL → 本地只读 Web 视图
    boofuzz convertWeb 页面:粘贴原始 HTTP 报文 → 自动生成协议定义 JSON

    帮助:boofuzz --help。全部选项、示例文件说明、运行行为与退出码见 CLI.md;JSON 协议定义写法见 DEFINITIONS.md。

    #文档导航

    需求文档
    CLI 全部选项、示例文件与退出码CLI.md
    编写 JSON 协议、字段与读取策略DEFINITIONS.md
    用 Web 页面把 HTTP 报文转成定义CONVERT.md
    运行本地 HTTP fuzz 靶子(httpd)HTTPD.md
    用 MoonBit 代码编写 fuzz 脚本原生 MoonBit 完整示例、CODE.md、可执行 API 示例、MODEL.md
    配置前置路径和执行器SESSION.md、RUNNER.md
    响应检查、故障通知和恢复回调MONITORS.md
    理解日志、重放和退出码RECORDS.md、REPLAY.md、REPORT.md
    支持范围、兼容边界、验收与许可UPSTREAM.md、ACCEPTANCE.md

    #许可与来源

    本项目保持 GPL-2.0-only,见 LICENSE。移植来源、差分样本生成方式、已知差异及工具链许可注意事项见 UPSTREAM.md。

    ModelError

    pub suberror ModelError {
    Invalid(String)
    Limit(String)
    } derive(
    Debug
    )

    Block

    pub struct Block {
    name : String
    children : Array[Node]
    condition : Condition?
    alignment : (Int, Bytes)?
    group : String?
    }

    Block::aligned

    fn Block::aligned(self : Block, modulus : Int, pattern? : Bytes) -> Block raise ModelError

    Upstream intentionally appends a full modulus even when already aligned.

    Block::new

    fn Block::new(name : String, children : Array[Node]) -> Block

    Block::when

    fn Block::when(self : Block, condition : Condition) -> Block

    Block::with_group

    fn Block::with_group(self : Block, target : String) -> Block

    Link the block to a Group field; its candidates multiply with the block's own mutation cases like upstream Block(group=...) (boofuzz/blocks/block.py mutations, 518c139). The target is a full field path.

    CaseSeq

    pub enum CaseSeq {
    Plain(Int)
    Sequence(Array[CaseSeq])
    Product(Int, CaseSeq)
    }

    Lazy case sequence tree. Upstream enumerates a block as its plain child mutations followed by group products replaying that same sequence once per group candidate (boofuzz/blocks/block.py mutations, 518c139).

    CaseStream

    pub struct CaseStream {
    request : CompiledRequest
    limit : Int
    skip : Int
    ordinal : Int
    emitted : Int
    state : EnumerationState
    stack : Array[StreamFrame]
    combinatorial : Bool
    units : Array[StreamUnit]
    max_depth : Int?
    depth : Int
    depth_emitted : Int
    frames : Array[CombFrame]
    vars : Map[String, Bytes]?
    }

    CaseStream::next

    fn CaseStream::next(self : CaseStream) -> MutationCase? raise ModelError

    CaseStream::position

    fn CaseStream::position(self : CaseStream) -> Int

    The next raw candidate position, including oversized skipped candidates.

    CaseStream::remaining_skip

    fn CaseStream::remaining_skip(self : CaseStream) -> Int

    Cases not yet consumed from the start offset; nonzero after a stream exhausts before reaching it.

    CaseStream::state

    CaseStream::stop

    fn CaseStream::stop(self : CaseStream) -> Unit

    ChecksumAlgorithm

    pub(all) enum ChecksumAlgorithm {
    Crc32
    Crc32c
    Adler32
    Md5
    Sha1
    } derive(Eq,
    Debug
    )

    Checksum algorithms supported by checksum nodes. MD5/SHA-1 render with upstream's 32-bit word swap when the node endianness is big (boofuzz/blocks/checksum.py:171-189, 518c139).

    ChecksumAlgorithm::equal

    ChecksumAlgorithm::not_equal

    fn ChecksumAlgorithm::not_equal(x : ChecksumAlgorithm, y : ChecksumAlgorithm) -> Bool

    ChecksumSpec

    pub struct ChecksumSpec {
    target : String
    endian : Endian
    mutations : Array[Bytes]
    algorithm : ChecksumAlgorithm
    }

    CombFrame

    pub struct CombFrame {
    unit_index : Int
    incoming : Map[Int, Bool]
    acc : Map[Int, Bool]
    parts : Array[(Int, Int, Bool)]
    }

    CompiledRequest

    pub struct CompiledRequest {
    name : String
    entries : Array[Entry]
    paths : Map[String, Int]
    max_bytes : Int
    seq : CaseSeq
    }

    CompiledRequest::cases

    fn CompiledRequest::cases(self : CompiledRequest, start? : Int, limit? : Int, vars? : Map[String, Bytes]?) -> CaseStream raise ModelError

    CompiledRequest::cases_with_variables

    fn CompiledRequest::cases_with_variables(self : CompiledRequest, vars : Map[String, Bytes]?, start? : Int, limit? : Int) -> CaseStream raise ModelError

    Enumerate combinatorial cases: depth 1..max_depth (indefinite when max_depth is absent, stopping at the first depth without a valid case), combining that many units of the base sequence, deduplicated exactly like upstream. Group products take part as whole units.

    CompiledRequest::combinatorial_cases

    fn CompiledRequest::combinatorial_cases(self : CompiledRequest, start? : Int, limit? : Int, max_depth? : Int) -> CaseStream raise ModelError

    Enumerate combinatorial cases: depth 1..max_depth (indefinite when max_depth is absent, stopping at the first depth without a valid case), combining that many units of the base sequence, deduplicated exactly like upstream. Group products take part as whole units.

    CompiledRequest::combinatorial_cases_with_variables

    fn CompiledRequest::combinatorial_cases_with_variables(self : CompiledRequest, vars : Map[String, Bytes]?, start? : Int, limit? : Int, max_depth? : Int) -> CaseStream raise ModelError

    CompiledRequest::compile

    fn CompiledRequest::compile(root : Block, max_bytes? : Int) -> CompiledRequest raise ModelError

    Validate and snapshot a named protocol tree. Limits count encoded wire bytes.

    CompiledRequest::count_at

    fn CompiledRequest::count_at(self : CompiledRequest, path : String) -> Int raise ModelError

    Number of sequential cases contributed by the entry at path (the entry's own candidate count; group product replays are excluded).

    CompiledRequest::from_request

    fn CompiledRequest::from_request(name : String, request : Request, max_bytes? : Int) -> CompiledRequest raise ModelError

    Legacy flat fields receive stable names field0, field1, ... under name.

    CompiledRequest::name

    fn CompiledRequest::name(self : CompiledRequest) -> String

    CompiledRequest::parent_path

    fn CompiledRequest::parent_path(self : CompiledRequest, path : String) -> String? raise ModelError

    CompiledRequest::paths

    fn CompiledRequest::paths(self : CompiledRequest) -> Array[String]

    CompiledRequest::raw_mutation_count

    fn CompiledRequest::raw_mutation_count(self : CompiledRequest) -> Int64

    Case positions of the sequential stream, including positions inside group product replays.

    CompiledRequest::raw_prefix_before

    fn CompiledRequest::raw_prefix_before(self : CompiledRequest, path : String) -> Int64 raise ModelError

    Raw sequential case count emitted before the entry at path renders its first plain case, walking the same tree the stream enumerates: unlike count_at this includes group product replays of preceding grouped blocks, so the result is a real stream position. Returns -1 when the entry emits no plain cases at all (disabled or non-mutating entry).

    CompiledRequest::render

    fn CompiledRequest::render(self : CompiledRequest) -> Bytes raise ModelError

    CompiledRequest::render_case

    fn CompiledRequest::render_case(self : CompiledRequest, parts : Array[(String, Int)], vars : Map[String, Bytes]?) -> Bytes raise ModelError

    Render a mutation case (parts from MutationCase::parts) with session variables, so edge-callback variable writes are visible at send time.

    CompiledRequest::render_path

    fn CompiledRequest::render_path(self : CompiledRequest, path : String) -> Bytes raise ModelError

    CompiledRequest::render_with

    fn CompiledRequest::render_with(self : CompiledRequest, vars : Map[String, Bytes]?) -> Bytes raise ModelError

    Render with session variables; inside a running case dynamic fields resolve strictly, matching upstream node.render(mutation_context).

    Condition

    pub(all) enum Condition {
    Equal(String, Bytes)
    NotEqual(String, Bytes)
    OneOf(String, Array[Bytes])
    NotOneOf(String, Array[Bytes])
    Greater(String, Bytes)
    GreaterEqual(String, Bytes)
    Less(String, Bytes)
    LessEqual(String, Bytes)
    }

    Block visibility conditions, following boofuzz/blocks/block.py _do_dependencies_allow_render at 518c13904fc32e7f2cc88c9dec934e509062953e. The ordering variants keep upstream's operand order: upstream evaluates dep_value OP dependent_value (the configured threshold on the left), so Greater renders when the current field value is LESS than the configured bytes, not greater. Preserved for definition parity.

    Endian

    pub(all) enum Endian {
    Little
    Big
    } derive(Eq,
    Debug
    )

    Endian::equal

    fn Endian::equal(Endian, Endian) -> Bool

    Endian::not_equal

    fn Endian::not_equal(x : Endian, y : Endian) -> Bool

    Endian::to_repr

    Entry

    type Entry

    EnumerationState

    pub(all) enum EnumerationState {
    Running
    Exhausted
    Limited
    Stopped
    } derive(Eq,
    Debug
    )

    EnumerationState::equal

    EnumerationState::not_equal

    fn EnumerationState::not_equal(x : EnumerationState, y : EnumerationState) -> Bool

    Field

    pub struct Field {
    value : Bytes
    count : Int
    candidate : (Int) -> Bytes
    candidate_length : (Int) -> Int64
    }

    An immutable field definition with indexed, on-demand mutation generation.

    Field::binary

    fn Field::binary(value : Bytes, size? : Int, max_len? : Int, padding? : Bytes, fuzzable? : Bool, fuzz_values? : Array[Bytes]) -> Field raise ModelError

    Binary mutation candidates are materialized individually on indexed access. Size/max_len adjust mutations only, matching upstream; default stays verbatim. Padding is restricted to exactly one byte.

    Field::default_value

    fn Field::default_value(self : Field) -> Bytes

    Field::delimiter

    fn Field::delimiter(value : String, fuzzable? : Bool, fuzz_values? : Array[Bytes]) -> Field

    Field::float

    fn Field::float(default_value? : Double, s_format? : String, f_min? : Double, f_max? : Double, max_mutations? : Int, seed? : Int64, encode_as_ieee_754? : Bool, endian? : Endian, fuzzable? : Bool, fuzz_values? : Array[Bytes]) -> Field raise ModelError

    Float candidates: the first mutation is the default value formatted with s_format, the rest draw random.uniform(f_min, f_max) from the seeded MT19937 stream; adjacent duplicates are dropped exactly like upstream, where the uniform draw is consumed before the dedup check so the remaining sequence matches upstream's generator. The candidate count is the number of formatted values actually produced, whereas upstream num_mutations keeps claiming max_mutations.

    Field::from_lines

    fn Field::from_lines(default_value? : Bytes, lines? : Array[Bytes], max_len? : Int, fuzzable? : Bool, fuzz_values? : Array[Bytes]) -> Field raise ModelError

    Cycle through bad values, one per provided line. Empty lines are dropped like upstream's filter(None, ...); max_len > 0 keeps only shorter lines. Upstream collapses the filtered library through a Python set ONLY when at least one line exceeds max_len (from_file.py:43-47), so the port applies dedup under the same condition — keeping first-occurrence order where the set would scramble it (documented deviation).

    Field::group

    fn Field::group(values : Array[Bytes], default_value? : Bytes, fuzzable? : Bool, fuzz_values? : Array[Bytes]) -> Field raise ModelError

    Group removes only the first occurrence of its default from candidates.

    Field::integer

    fn Field::integer(value : UInt64, width? : Int, endian? : Endian, fuzzable? : Bool, fuzz_values? : Array[Bytes]) -> Field raise ModelError

    Unsigned binary integers with upstream's sorted, deduplicated +/-10 boundaries. Width is in bits. ASCII, full_range and custom max_num are not supported.

    Field::mutation

    fn Field::mutation(self : Field, index : Int) -> Bytes?

    Field::num_mutations

    fn Field::num_mutations(self : Field) -> Int

    Field::random_data

    fn Field::random_data(default_value? : Bytes, min_length? : Int, max_length? : Int, max_mutations? : Int, step? : Int, fuzzable? : Bool, fuzz_values? : Array[Bytes]) -> Field raise ModelError

    Random data candidates from upstream's deterministic Random(0) stream. Step mode fixes each length at min_length + i*step without consuming the generator; otherwise each candidate draws one randint for its length and one randint(0, 255) per byte. Candidate identity is stable, so the upstream count quirk (mutations loop over get_num_mutations, which also counts fuzz_values) is intentionally not reproduced: the random sequence has exactly max_mutations entries and user fuzz_values are appended after.

    Field::simple

    fn Field::simple(value : Bytes, values : Array[Bytes], fuzzable? : Bool, fuzz_values? : Array[Bytes]) -> Field

    Field::text

    fn Field::text(value : String, fuzzable? : Bool, fuzz_values? : Array[Bytes]) -> Field

    UTF-8 bad-string library, variable repeats and deterministic long strings. Only dynamic size is supported. Enforce wire-byte limits at request rendering.

    MutationCase

    pub(all) struct MutationCase {
    id : String
    request_name : String
    field_path : String
    mutation_index : Int
    ordinal : Int
    payload : Bytes
    extra : Array[(String, Int)]
    } derive(Eq,
    Debug
    )

    MutationCase::equal

    MutationCase::not_equal

    fn MutationCase::not_equal(x : MutationCase, y : MutationCase) -> Bool

    MutationCase::parts

    fn MutationCase::parts(self : MutationCase) -> Array[(String, Int)]

    All mutated (path, mutation index) pairs of this case, outermost first.

    Node

    pub(all) enum Node {
    Leaf(String, Field)
    Nested(Block)
    Repeat(String, String, Int, Int, Int, String?)
    Sized(String, SizeSpec)
    Checksummed(String, ChecksumSpec)
    Mirrored(String, String)
    Dynamic(String, String, Field)
    }

    Named protocol items. Names are one path segment and cannot contain dots.

    Node::checksum

    fn Node::checksum(name : String, target : String, endian? : Endian, mutations? : Array[UInt], fuzzable? : Bool, algorithm? : ChecksumAlgorithm) -> Node raise ModelError

    Node::dynamic

    fn Node::dynamic(name : String, variable : String, field : Field) -> Node

    Bind a field's default value to a session variable. Inside a test case the variable must exist (upstream raises on a missing ProtocolSession variable); outside, the field's own value renders. Mutations derive from the fallback value, matching upstream's mutation seeding.

    Node::mirror

    fn Node::mirror(name : String, target : String) -> Node

    Node::repeat

    fn Node::repeat(name : String, target : String, min? : Int, max? : Int, step? : Int, variable? : String) -> Node

    Repeat appends copies of a preceding named block; normal output is empty.

    Node::size

    fn Node::size(name : String, target : String, length? : Int, endian? : Endian, offset? : Int, inclusive? : Bool, ascii? : Bool, mutations? : Array[UInt64], fuzzable? : Bool) -> Node raise ModelError

    Primitive

    pub(all) enum Primitive {
    Static(Bytes)
    Choice(Bytes, Array[Bytes])
    } derive(
    Debug
    )

    Explicit byte fields. Automatic boundary mutations are planned.

    Primitive::default_value

    fn Primitive::default_value(self : Primitive) -> Bytes

    Return the normal wire value.

    Primitive::mutations

    fn Primitive::mutations(self : Primitive) -> Array[Bytes]

    Explicit alternatives in caller order; duplicates are preserved.

    Request

    pub struct Request {
    fields : Array[Primitive]
    }

    A flat request. Nested blocks and computed fields are planned.

    Request::mutations

    fn Request::mutations(self : Request) -> Array[Bytes]

    Generate one-field-at-a-time cases in field and alternative order. Materializes all cases; intended for small explicit mutation sets.

    Request::new

    fn Request::new(fields : Array[Primitive]) -> Request

    Snapshot caller-owned field definitions.

    Request::render

    fn Request::render(self : Request) -> Bytes

    Render the normal request without text decoding.

    SessionGraph

    pub struct SessionGraph {
    requests : Array[CompiledRequest]
    edges : Array[Array[Int]]
    names : Map[String, Int]
    edge_callbacks : Map[(Int, Int), (StepContext) -> Bytes?]
    }

    SessionGraph::add

    fn SessionGraph::add(self : SessionGraph, request : CompiledRequest) -> Unit raise ModelError

    SessionGraph::connect

    fn SessionGraph::connect(self : SessionGraph, from : String, to : String, callback? : (StepContext) -> Bytes?) -> Unit raise ModelError

    SessionGraph::new

    SessionGraph::paths

    fn SessionGraph::paths(self : SessionGraph, targets? : Array[String], max_paths? : Int) -> Array[SessionPath] raise ModelError

    Roots follow node insertion order; outgoing edges follow connection order.

    SessionPath

    pub struct SessionPath {
    requests : Array[CompiledRequest]
    transitions : Array[(StepContext) -> Bytes??]
    }

    SessionPath::cases

    fn SessionPath::cases(self : SessionPath, start? : Int, limit? : Int, variables? : Map[String, Bytes]?) -> CaseStream raise ModelError

    Only the terminal request mutates; prefixes are rendered from their defaults.

    SessionPath::cases_with_variables

    fn SessionPath::cases_with_variables(self : SessionPath, vars : Map[String, Bytes]?, start? : Int, limit? : Int) -> CaseStream raise ModelError

    SessionPath::combinatorial_cases_with_variables

    fn SessionPath::combinatorial_cases_with_variables(self : SessionPath, vars : Map[String, Bytes]?, start? : Int, limit? : Int, max_depth? : Int) -> CaseStream raise ModelError

    SessionPath::names

    fn SessionPath::names(self : SessionPath) -> Array[String]

    SessionPath::prefix

    fn SessionPath::prefix(self : SessionPath) -> Array[Bytes] raise ModelError

    SessionPath::requests

    fn SessionPath::requests(self : SessionPath) -> Array[CompiledRequest]

    SessionPath::target

    SizeSpec

    pub struct SizeSpec {
    target : String
    length : Int
    endian : Endian
    offset : Int
    inclusive : Bool
    ascii : Bool
    mutations : Array[Bytes]
    }

    StepContext

    pub(all) struct StepContext {
    variables : Map[String, Bytes]
    received : Array[Bytes]
    request : String
    }

    Context handed to an edge callback: the mutable session-variable map for the running case, bytes received from earlier steps, and the destination request name. Mirrors upstream's ProtocolSession (protocol_session.py, 518c139); callbacks write variables and may return replacement data.

    StreamFrame

    pub struct StreamFrame {
    seq : CaseSeq
    child : Int
    pass : Int
    walking : Bool
    }

    One stack frame of the lazy case-sequence walker. Sequence frames track the next child, plain frames the next mutation index, product frames the current group pass.

    StreamUnit

    pub struct StreamUnit {
    parts : Array[(Int, Int, Bool)]
    child : Int
    }

    A pre-enumerated unit of the base case sequence for combinatorial enumeration. is_group marks parts contributed by a product's group, which upstream adds without consulting the skip set. child is the availability-checked leaf entry.

    TextCandidate

    type TextCandidate derive(Eq)

    TextCandidate::equal

    TextCandidate::not_equal

    fn TextCandidate::not_equal(x : TextCandidate, y : TextCandidate) -> Bool

    adler32

    fn adler32(bytes : Bytes) -> UInt

    Adler-32 (RFC 1950): two 16-bit sums modulo 65521, matching zlib.

    crc32

    fn crc32(bytes : Bytes) -> UInt

    Reflected IEEE CRC32, initial/final XOR 0xffffffff, polynomial 0xedb88320.

    crc32c

    fn crc32c(bytes : Bytes) -> UInt

    CRC-32C (Castagnoli, poly 0x82F63B78 reflected), initial/final XOR 0xffffffff — the algorithm behind upstream's optional crc32c dependency.

    ipv4_checksum

    fn ipv4_checksum(header : Bytes) -> UInt

    IPv4 header checksum over the header bytes with the checksum field zero; the result is already complemented (0xb861 for the RFC 1071 example).

    md5

    fn md5(message : Bytes) -> Bytes

    MD5 digest (16 bytes) of the input.

    sha1

    fn sha1(message : Bytes) -> Bytes

    SHA-1 digest (20 bytes) of the input.

    udp_checksum

    fn udp_checksum(source : Bytes, destination : Bytes, udp_bytes : Bytes) -> UInt

    UDP checksum over the pseudo-header plus the UDP header and payload. source/destination are the 4-byte IPv4 addresses of the pseudo header; length is the UDP length field (header + payload). The complemented sum is computed over the same layout as upstream helpers.udp_checksum; an all-zero result is transmitted as 0xffff.