Files
OpenFUT/openfut-protocol-blaze/src/error.rs
T
funman300 a9a816e0ed openfut-protocol-blaze: generic Blaze protocol layer, oracle-tested
First Rust component of the Python -> Rust migration. Chosen first because
it is the lowest genuinely game-independent layer, it has an executable
oracle, and both existing Rust implementations of it are wrong.

Contents:
  * fire2   -- the proven 16-byte frame header, frame/stream splitting
  * heat2   -- tag packing, varints, all 11 TDF value types
  * message -- frame + decoded body, routed by NUMERIC component/command
  * diagnostics -- dumps for capture review

No FIFA 17 command tables, response schemas or notification IDs: this layer
knows 0x0009/0x0007 is component 9, command 7, not that it means
Util::preAuth. That mapping belongs to a game adapter, which is what lets a
future FIFA 18/23 adapter reuse this.

Parity is tested, not asserted. fixtures/generate.py drives the proven
Python responders (heat2.py, blaze_responder_v3b.py) and freezes 56 vectors
-- 31 of them real payloads from the responder's own builders, including
the 11.8 KB preAuth reply. tests/oracle_parity.rs replays every one
byte-for-byte. 54 tests green; clippy clean.

Supersedes two wrong framings, neither of which is removed yet:
  * fifa-blaze/crates/blaze-proto/frame.rs -- a 12-byte header with a u16
    length, nibble-packed type/options, an error field and a JUMBO flag.
    A documented guess at FIFA 23 predating the FIFA 17 recon.
  * heat2.py::build_fire2_frame -- packs >IHHHHB3s, msgId at [10:12] and
    msgType at [12]. Dead code, but its docstring still states that layout.

Confidence is carried in the types: TypeId::is_verified() reports which
layouts are capture-backed (int/string/blob/struct) and which the oracle
marks UNVERIFIED (list/map/union/varlist/objtype/objid/float), with a test
asserting the unverified ones stay flagged.

Cargo.lock is deliberately NOT included: it re-resolves ~240 lines against
the current registry even without this crate, so that churn is pre-existing
and does not belong in a foundation commit.

The Python backend remains the live runtime and is untouched. Nothing
consumes this crate yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 00:53:59 +00:00

47 lines
1.5 KiB
Rust

//! Decode errors.
//!
//! Encoding cannot fail: every `Value` is representable on the wire. Decoding
//! is fallible because the input is attacker-shaped bytes from a socket.
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
/// Ran off the end of the buffer while reading `what`.
Truncated {
what: &'static str,
need: usize,
have: usize,
},
/// A type byte that is not one of the eleven Heat2 types.
UnknownType { type_byte: u8, at: usize },
/// A varint whose continuation bits never terminated within 64 bits.
VarintOverflow { at: usize },
/// A Fire2 header whose declared lengths cannot be satisfied.
ShortFrame { need: usize, have: usize },
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Truncated { what, need, have } => write!(
f,
"truncated while reading {what}: need {need} bytes, have {have}"
),
Error::UnknownType { type_byte, at } => {
write!(f, "unknown TDF type byte 0x{type_byte:02x} at offset {at}")
}
Error::VarintOverflow { at } => {
write!(f, "varint at offset {at} exceeds 64 bits")
}
Error::ShortFrame { need, have } => {
write!(f, "short Fire2 frame: need {need} bytes, have {have}")
}
}
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;