Files
funman300 eccd46f52b feat: scaffold fifa-blaze Blaze protocol emulator (Milestone 1)
Two-crate workspace: blaze-proto (Fire2 framing via tokio_util codec,
tdf=0.1 for TDF decode/stringify) and server (TLS listeners for
redirector + Blaze, JSONL capture with TdfStringifier, pluggable
FramingVariant enum).

Both listeners bind on startup and write a capture JSONL for every
packet. Component/command IDs are unknown — captures reveal them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 15:24:50 -07:00

87 lines
2.5 KiB
Rust

use bytes::Bytes;
use serde::Serialize;
use super::frame::{FireFrame, FrameType, PacketOptions};
use tdf::stringify::TdfStringifier;
use tdf::reader::TdfDeserializer;
/// A fully framed Blaze packet.
#[derive(Debug, Clone)]
pub struct Packet {
/// Decoded Fire2 header.
pub frame: FireFrame,
/// Raw body bytes (unparsed TDF).
pub body: Bytes,
}
impl Packet {
/// Create a raw-mode packet (no frame header — body only).
pub fn raw(body: Bytes) -> Self {
Packet {
frame: FireFrame {
component: 0,
command: 0,
error: 0,
ty: FrameType::Request,
options: PacketOptions::NONE,
seq: 0,
},
body,
}
}
/// Build an empty response that mirrors `req`'s routing fields.
pub fn response_empty(req: &Packet) -> Self {
Packet { frame: req.frame.response(), body: Bytes::new() }
}
/// Build a response with the given TDF body.
pub fn response<V: tdf::TdfSerialize>(req: &Packet, value: &V) -> Self {
Packet {
frame: req.frame.response(),
body: Bytes::from(tdf::serialize_vec(value)),
}
}
/// Try to decode the body as TDF and return a human-readable string.
/// On any decode error, return the raw hex.
pub fn tdf_string(&self) -> String {
if self.body.is_empty() {
return String::from("(empty)");
}
let r = TdfDeserializer::new(&self.body);
let (s, ok) = TdfStringifier::<String>::new_string(r);
if ok {
s
} else {
format!("(decode error) raw={}", hex::encode(&self.body))
}
}
/// Serialize the packet into a JSON-friendly struct for capture logs.
pub fn to_capture(&self) -> CaptureRecord {
CaptureRecord {
component: self.frame.component,
command: self.frame.command,
error: self.frame.error,
ty: self.frame.ty.to_string(),
seq: self.frame.seq,
body_len: self.body.len(),
raw_hex: hex::encode(&self.body),
tdf: self.tdf_string(),
}
}
}
#[derive(Debug, Serialize)]
pub struct CaptureRecord {
pub component: u16,
pub command: u16,
pub error: u16,
pub ty: String,
pub seq: u16,
pub body_len: usize,
pub raw_hex: String,
pub tdf: String,
}