wip(blaze-proto): retained Blaze frame/codec refinement WIP

RETAINED PRE-EXISTING WIP (brought forward after verification, not authored
here, not production-ready). Blaze wire frame/packet layout work (Fire2
header still flagged as an unvalidated FIFA23 guess). No secrets/captures
staged (captures/ + certs/ gitignored). Preserved off detached base f4f33969f2.
This commit is contained in:
funman300
2026-08-20 16:08:36 +00:00
parent f4f33969f2
commit b7126b255f
9 changed files with 227 additions and 144 deletions
+24 -11
View File
@@ -12,7 +12,7 @@ use std::io;
use tokio_util::codec::{Decoder, Encoder};
use super::{
frame::{FireFrame, FIRE2_MIN_HEADER, FIRE2_JUMBO_EXT, PacketOptions},
frame::{FireFrame, PacketOptions, FIRE2_JUMBO_EXT, FIRE2_MIN_HEADER},
packet::Packet,
};
@@ -31,8 +31,8 @@ pub enum FramingVariant {
/// Tracks partial-decode state between `decode()` calls.
struct PartialFire2 {
frame : FireFrame,
body_len : usize,
frame: FireFrame,
body_len: usize,
}
/// Tokio codec for encoding and decoding Blaze packets.
@@ -44,18 +44,23 @@ pub struct PacketCodec {
impl PacketCodec {
pub fn new(variant: FramingVariant) -> Self {
PacketCodec { variant, partial: None }
PacketCodec {
variant,
partial: None,
}
}
}
impl Decoder for PacketCodec {
type Item = Packet;
type Item = Packet;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
match self.variant {
FramingVariant::Raw => {
if src.is_empty() { return Ok(None); }
if src.is_empty() {
return Ok(None);
}
let body = src.copy_to_bytes(src.len());
Ok(Some(Packet::raw(body)))
}
@@ -63,21 +68,29 @@ impl Decoder for PacketCodec {
FramingVariant::Fire2 => {
// ── Resume waiting for the body ───────────────────────────
if let Some(ref p) = self.partial {
if src.len() < p.body_len { return Ok(None); }
let PartialFire2 { frame, body_len, .. } = self.partial.take().unwrap();
if src.len() < p.body_len {
return Ok(None);
}
let PartialFire2 {
frame, body_len, ..
} = self.partial.take().unwrap();
let body = src.copy_to_bytes(body_len);
return Ok(Some(Packet { frame, body }));
}
// ── Need at least the minimum header ─────────────────────
if src.len() < FIRE2_MIN_HEADER { return Ok(None); }
if src.len() < FIRE2_MIN_HEADER {
return Ok(None);
}
// Peek at options to see if we need a jumbo ext too.
let opt_nibble = (src[9] >> 4) & 0xF;
let has_jumbo = (opt_nibble & PacketOptions::JUMBO_FRAME.0) != 0;
let has_jumbo = (opt_nibble & PacketOptions::JUMBO_FRAME.0) != 0;
let header_len = FIRE2_MIN_HEADER + if has_jumbo { FIRE2_JUMBO_EXT } else { 0 };
if src.len() < header_len { return Ok(None); }
if src.len() < header_len {
return Ok(None);
}
let (frame, body_len) = FireFrame::read(src);
+51 -30
View File
@@ -52,10 +52,10 @@ pub const FIRE2_JUMBO_EXT: usize = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum FrameType {
Request = 0x0,
Request = 0x0,
Response = 0x1,
Notify = 0x2,
Error = 0x3,
Notify = 0x2,
Error = 0x3,
}
impl From<u8> for FrameType {
@@ -64,7 +64,7 @@ impl From<u8> for FrameType {
0x1 => FrameType::Response,
0x2 => FrameType::Notify,
0x3 => FrameType::Error,
_ => FrameType::Request,
_ => FrameType::Request,
}
}
}
@@ -72,10 +72,10 @@ impl From<u8> for FrameType {
impl std::fmt::Display for FrameType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrameType::Request => write!(f, "REQUEST"),
FrameType::Request => write!(f, "REQUEST"),
FrameType::Response => write!(f, "RESPONSE"),
FrameType::Notify => write!(f, "NOTIFY"),
FrameType::Error => write!(f, "ERROR"),
FrameType::Notify => write!(f, "NOTIFY"),
FrameType::Error => write!(f, "ERROR"),
}
}
}
@@ -85,22 +85,24 @@ impl std::fmt::Display for FrameType {
pub struct PacketOptions(pub u8);
impl PacketOptions {
pub const NONE : Self = Self(0x0);
pub const NONE: Self = Self(0x0);
/// Body length exceeds 16 bits; an extra u16 follows the header.
pub const JUMBO_FRAME: Self = Self(0x1);
pub fn contains(self, flag: Self) -> bool { (self.0 & flag.0) != 0 }
pub fn contains(self, flag: Self) -> bool {
(self.0 & flag.0) != 0
}
}
/// Fire2 packet header.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FireFrame {
pub component : u16,
pub command : u16,
pub error : u16,
pub ty : FrameType,
pub options : PacketOptions,
pub seq : u16,
pub component: u16,
pub command: u16,
pub error: u16,
pub ty: FrameType,
pub options: PacketOptions,
pub seq: u16,
}
impl FireFrame {
@@ -108,23 +110,32 @@ impl FireFrame {
pub fn response(&self) -> Self {
Self {
component: self.component,
command: self.command,
error: 0,
ty: FrameType::Response,
options: PacketOptions::NONE,
seq: self.seq,
command: self.command,
error: 0,
ty: FrameType::Response,
options: PacketOptions::NONE,
seq: self.seq,
}
}
/// Build a notify frame.
pub fn notify(component: u16, command: u16) -> Self {
Self { component, command, error: 0, ty: FrameType::Notify, options: PacketOptions::NONE, seq: 0 }
Self {
component,
command,
error: 0,
ty: FrameType::Notify,
options: PacketOptions::NONE,
seq: 0,
}
}
/// Encode this header into `dst`, prepending `body_len` at the front.
pub fn write(&self, dst: &mut BytesMut, body_len: usize) {
let mut options = self.options;
if body_len > u16::MAX as usize { options = PacketOptions(options.0 | PacketOptions::JUMBO_FRAME.0); }
if body_len > u16::MAX as usize {
options = PacketOptions(options.0 | PacketOptions::JUMBO_FRAME.0);
}
dst.put_u16(body_len as u16);
dst.put_u16(self.component);
@@ -142,15 +153,15 @@ impl FireFrame {
/// Parse a Fire2 header from `src`, which must already have `>= FIRE2_MIN_HEADER` bytes.
/// Returns `(frame, body_length)`. Advances `src` past the header (including jumbo ext).
pub fn read(src: &mut BytesMut) -> (FireFrame, usize) {
let len_lo = src.get_u16() as usize;
let len_lo = src.get_u16() as usize;
let component = src.get_u16();
let command = src.get_u16();
let error = src.get_u16();
let ty_byte = src.get_u8();
let opt_byte = src.get_u8();
let seq = src.get_u16();
let command = src.get_u16();
let error = src.get_u16();
let ty_byte = src.get_u8();
let opt_byte = src.get_u8();
let seq = src.get_u16();
let ty = FrameType::from(ty_byte >> 4);
let ty = FrameType::from(ty_byte >> 4);
let options = PacketOptions(opt_byte >> 4);
let body_len = if options.contains(PacketOptions::JUMBO_FRAME) {
@@ -160,6 +171,16 @@ impl FireFrame {
len_lo
};
(FireFrame { component, command, error, ty, options, seq }, body_len)
(
FireFrame {
component,
command,
error,
ty,
options,
seq,
},
body_len,
)
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
pub mod frame;
pub mod codec;
pub mod frame;
pub mod packet;
pub use codec::{FramingVariant, PacketCodec};
pub use frame::{FireFrame, FrameType, PacketOptions};
pub use codec::{PacketCodec, FramingVariant};
pub use packet::Packet;
+25 -22
View File
@@ -2,8 +2,8 @@ use bytes::Bytes;
use serde::Serialize;
use super::frame::{FireFrame, FrameType, PacketOptions};
use tdf::stringify::TdfStringifier;
use tdf::reader::TdfDeserializer;
use tdf::stringify::TdfStringifier;
/// A fully framed Blaze packet.
#[derive(Debug, Clone)]
@@ -20,11 +20,11 @@ impl Packet {
Packet {
frame: FireFrame {
component: 0,
command: 0,
error: 0,
ty: FrameType::Request,
options: PacketOptions::NONE,
seq: 0,
command: 0,
error: 0,
ty: FrameType::Request,
options: PacketOptions::NONE,
seq: 0,
},
body,
}
@@ -32,14 +32,17 @@ impl Packet {
/// 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() }
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)),
body: Bytes::from(tdf::serialize_vec(value)),
}
}
@@ -62,13 +65,13 @@ impl Packet {
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(),
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(),
}
}
}
@@ -76,11 +79,11 @@ impl Packet {
#[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,
pub command: u16,
pub error: u16,
pub ty: String,
pub seq: u16,
pub body_len: usize,
pub raw_hex: String,
pub tdf: String,
}