use super::address::{RpcAddress, RPC_ADDR_LEN};
use super::transport::MAX_FRAME_LEN;
use super::{RpcError, RpcResult};
pub(crate) const WIRE_HEADER_LEN: usize = 16;
pub(crate) const WIRE_TXN_FIXED_LEN: usize = RPC_ADDR_LEN + 4 + 4 + 8 + 16;
const CMD_TRANSACT: u32 = 0;
const CMD_REPLY: u32 = 1;
const CMD_DEC_STRONG: u32 = 2;
#[derive(Debug, Clone)]
pub struct WireTransaction {
pub address: RpcAddress,
pub code: u32,
pub flags: u32,
pub async_number: u64,
pub data: Vec<u8>,
pub object_positions: Vec<u32>,
}
#[derive(Debug, Clone, Default)]
pub struct WireReply {
pub status: i32,
pub data: Vec<u8>,
pub object_positions: Vec<u32>,
}
impl Default for WireTransaction {
fn default() -> Self {
Self {
address: RpcAddress::zero(),
code: 0,
flags: 0,
async_number: 0,
data: Vec::new(),
object_positions: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub enum WireMessage {
Transact(WireTransaction),
Reply(WireReply),
DecStrong(RpcAddress),
}
pub trait WireCodec: Send + Sync {
fn encode_transact(&self, txn: &WireTransaction) -> RpcResult<Vec<u8>>;
fn encode_reply(&self, reply: &WireReply) -> RpcResult<Vec<u8>>;
fn encode_dec_strong(&self, addr: &RpcAddress) -> Vec<u8>;
fn decode_message(&self, frame: &[u8]) -> RpcResult<WireMessage>;
fn encode_session_preamble(&self, session_id: i32) -> Vec<u8>;
fn decode_session_preamble(&self, buf: &[u8]) -> RpcResult<i32>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct R34Codec;
impl R34Codec {
fn header(command: u32, body_size: usize) -> RpcResult<[u8; WIRE_HEADER_LEN]> {
if body_size > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: body_size,
max: MAX_FRAME_LEN,
});
}
let mut h = [0u8; WIRE_HEADER_LEN];
h[0..4].copy_from_slice(&command.to_le_bytes());
h[4..8].copy_from_slice(&(body_size as u32).to_le_bytes());
Ok(h)
}
}
fn rd_u32(buf: &[u8], off: usize) -> RpcResult<u32> {
let end = off
.checked_add(4)
.ok_or(RpcError::Protocol("offset overflow"))?;
let slice = buf
.get(off..end)
.ok_or(RpcError::Protocol("truncated u32"))?;
Ok(u32::from_le_bytes(slice.try_into().unwrap()))
}
fn rd_i32(buf: &[u8], off: usize) -> RpcResult<i32> {
Ok(rd_u32(buf, off)? as i32)
}
fn rd_u64(buf: &[u8], off: usize) -> RpcResult<u64> {
let end = off
.checked_add(8)
.ok_or(RpcError::Protocol("offset overflow"))?;
let slice = buf
.get(off..end)
.ok_or(RpcError::Protocol("truncated u64"))?;
Ok(u64::from_le_bytes(slice.try_into().unwrap()))
}
fn rd_addr(buf: &[u8], off: usize) -> RpcResult<RpcAddress> {
let end = off
.checked_add(RPC_ADDR_LEN)
.ok_or(RpcError::Protocol("offset overflow"))?;
let slice = buf
.get(off..end)
.ok_or(RpcError::Protocol("truncated address"))?;
let mut bytes = [0u8; RPC_ADDR_LEN];
bytes.copy_from_slice(slice);
Ok(RpcAddress::from_wire_bytes(bytes))
}
impl WireCodec for R34Codec {
fn encode_transact(&self, txn: &WireTransaction) -> RpcResult<Vec<u8>> {
if !txn.object_positions.is_empty() {
return Err(RpcError::Protocol(
"r34 wire has no object table (object_positions must be empty)",
));
}
let body_len = WIRE_TXN_FIXED_LEN + txn.data.len();
let mut out = Vec::with_capacity(WIRE_HEADER_LEN + body_len);
out.extend_from_slice(&Self::header(CMD_TRANSACT, body_len)?);
out.extend_from_slice(txn.address.as_wire_bytes()); out.extend_from_slice(&txn.code.to_le_bytes()); out.extend_from_slice(&txn.flags.to_le_bytes()); out.extend_from_slice(&txn.async_number.to_le_bytes()); out.extend_from_slice(&[0u8; 16]); out.extend_from_slice(&txn.data); Ok(out)
}
fn encode_reply(&self, reply: &WireReply) -> RpcResult<Vec<u8>> {
if !reply.object_positions.is_empty() {
return Err(RpcError::Protocol(
"r34 wire has no object table (object_positions must be empty)",
));
}
let body_len = 4 + reply.data.len();
let mut out = Vec::with_capacity(WIRE_HEADER_LEN + body_len);
out.extend_from_slice(&Self::header(CMD_REPLY, body_len)?);
out.extend_from_slice(&reply.status.to_le_bytes());
out.extend_from_slice(&reply.data);
Ok(out)
}
fn encode_dec_strong(&self, addr: &RpcAddress) -> Vec<u8> {
let mut out = Vec::with_capacity(WIRE_HEADER_LEN + RPC_ADDR_LEN);
out.extend_from_slice(
&Self::header(CMD_DEC_STRONG, RPC_ADDR_LEN)
.expect("dec_strong header is within the frame bound"),
);
out.extend_from_slice(addr.as_wire_bytes());
out
}
fn decode_message(&self, frame: &[u8]) -> RpcResult<WireMessage> {
if frame.len() < WIRE_HEADER_LEN {
return Err(RpcError::Protocol("frame shorter than RpcWireHeader"));
}
let command = rd_u32(frame, 0)?;
let body_size = rd_u32(frame, 4)? as usize;
if body_size > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: body_size,
max: MAX_FRAME_LEN,
});
}
let expected = WIRE_HEADER_LEN
.checked_add(body_size)
.ok_or(RpcError::Protocol("body size overflow"))?;
if frame.len() != expected {
return Err(RpcError::Protocol("frame length != header + bodySize"));
}
let body = &frame[WIRE_HEADER_LEN..];
match command {
CMD_TRANSACT => {
if body.len() < WIRE_TXN_FIXED_LEN {
return Err(RpcError::Protocol("RpcWireTransaction truncated"));
}
let address = rd_addr(body, 0)?;
let code = rd_u32(body, RPC_ADDR_LEN)?;
let flags = rd_u32(body, RPC_ADDR_LEN + 4)?;
let async_number = rd_u64(body, RPC_ADDR_LEN + 8)?;
let data = body[WIRE_TXN_FIXED_LEN..].to_vec();
Ok(WireMessage::Transact(WireTransaction {
address,
code,
flags,
async_number,
data,
object_positions: Vec::new(),
}))
}
CMD_REPLY => {
if body.len() < 4 {
return Err(RpcError::Protocol("RpcWireReply truncated"));
}
let status = rd_i32(body, 0)?;
let data = body[4..].to_vec();
Ok(WireMessage::Reply(WireReply {
status,
data,
object_positions: Vec::new(),
}))
}
CMD_DEC_STRONG => {
if body.len() != RPC_ADDR_LEN {
return Err(RpcError::Protocol("DEC_STRONG body != 32 bytes"));
}
Ok(WireMessage::DecStrong(rd_addr(body, 0)?))
}
_ => Err(RpcError::Protocol("unknown RpcWireHeader.command")),
}
}
fn encode_session_preamble(&self, session_id: i32) -> Vec<u8> {
session_id.to_le_bytes().to_vec()
}
fn decode_session_preamble(&self, buf: &[u8]) -> RpcResult<i32> {
let arr: [u8; 4] = buf
.try_into()
.map_err(|_| RpcError::Protocol("session preamble must be exactly 4 bytes"))?;
Ok(i32::from_le_bytes(arr))
}
}
#[doc(hidden)]
pub fn __fuzz_decode_wire(input: &[u8]) {
let _ = R34Codec.decode_message(input);
}
#[doc(hidden)]
pub fn __fuzz_session_handshake(input: &[u8]) {
let c = R34Codec;
let (pre, rest) = input.split_at(input.len().min(4));
let _ = c.decode_session_preamble(pre);
let _ = c.decode_message(rest);
}
#[doc(hidden)]
pub fn __fuzz_decode_address(input: &[u8]) {
let Ok(header) = R34Codec::header(CMD_DEC_STRONG, input.len()) else {
return;
};
let mut frame = Vec::with_capacity(WIRE_HEADER_LEN + input.len());
frame.extend_from_slice(&header);
frame.extend_from_slice(input);
let _ = R34Codec.decode_message(&frame);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rpc::address::RPC_SESSION_ID_NEW;
fn rt_codec() -> R34Codec {
R34Codec
}
#[test]
fn header_rejects_oversize_body() {
assert!(matches!(
R34Codec::header(CMD_TRANSACT, MAX_FRAME_LEN + 1),
Err(RpcError::FrameTooLarge { .. })
));
assert!(R34Codec::header(CMD_TRANSACT, MAX_FRAME_LEN).is_ok());
}
#[test]
fn roundtrip_all_commands() {
let c = rt_codec();
for size in [0usize, 1, 4, 17, 4096, 1 << 20] {
let data: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let mut ctr = 7u64;
let addr = RpcAddress::unique(&mut ctr, crate::rpc::AddressSpace::Initiator);
let txn = WireTransaction {
address: addr,
code: 0xdead_beef,
flags: 1,
async_number: 0x0102_0304_0506_0708,
data: data.clone(),
..Default::default()
};
let enc = c.encode_transact(&txn).unwrap();
match c.decode_message(&enc).unwrap() {
WireMessage::Transact(t) => {
assert_eq!(t.address, addr);
assert_eq!(t.code, 0xdead_beef);
assert_eq!(t.flags, 1);
assert_eq!(t.async_number, 0x0102_0304_0506_0708);
assert_eq!(t.data, data);
}
other => panic!("expected Transact, got {other:?}"),
}
let reply = WireReply {
status: -42,
data: data.clone(),
..Default::default()
};
let enc = c.encode_reply(&reply).unwrap();
match c.decode_message(&enc).unwrap() {
WireMessage::Reply(r) => {
assert_eq!(r.status, -42);
assert_eq!(r.data, data);
}
other => panic!("expected Reply, got {other:?}"),
}
}
let mut ctr = 99u64;
let addr = RpcAddress::unique(&mut ctr, crate::rpc::AddressSpace::Initiator);
let enc = c.encode_dec_strong(&addr);
match c.decode_message(&enc).unwrap() {
WireMessage::DecStrong(a) => assert_eq!(a, addr),
other => panic!("expected DecStrong, got {other:?}"),
}
let pre = c.encode_session_preamble(RPC_SESSION_ID_NEW);
assert_eq!(c.decode_session_preamble(&pre).unwrap(), RPC_SESSION_ID_NEW);
}
#[test]
fn r34_spec_golden_vectors() {
let c = R34Codec;
let txn = WireTransaction {
address: RpcAddress::zero(),
code: 0, flags: 0,
async_number: 0,
data: vec![],
..Default::default()
};
let enc = c.encode_transact(&txn).unwrap();
let mut want = Vec::new();
want.extend_from_slice(&0u32.to_le_bytes()); want.extend_from_slice(&64u32.to_le_bytes()); want.extend_from_slice(&[0u8; 8]); want.extend_from_slice(&[0u8; 32]); want.extend_from_slice(&0u32.to_le_bytes()); want.extend_from_slice(&0u32.to_le_bytes()); want.extend_from_slice(&0u64.to_le_bytes()); want.extend_from_slice(&[0u8; 16]); assert_eq!(
enc, want,
"TRANSACT(zero,GET_ROOT) must be byte-identical to the r34 spec"
);
assert_eq!(enc.len(), WIRE_HEADER_LEN + WIRE_TXN_FIXED_LEN);
assert_eq!(WIRE_HEADER_LEN, 16);
assert_eq!(WIRE_TXN_FIXED_LEN, 64);
let reply = WireReply {
status: 0,
data: vec![0xAA, 0xBB],
..Default::default()
};
let enc = c.encode_reply(&reply).unwrap();
let mut want = Vec::new();
want.extend_from_slice(&1u32.to_le_bytes()); want.extend_from_slice(&6u32.to_le_bytes()); want.extend_from_slice(&[0u8; 8]); want.extend_from_slice(&0i32.to_le_bytes()); want.extend_from_slice(&[0xAA, 0xBB]); assert_eq!(enc, want, "REPLY must be byte-identical to the r34 spec");
let mut ctr = 0x4142_4344u64;
let addr = RpcAddress::unique(&mut ctr, crate::rpc::AddressSpace::Initiator);
let enc = c.encode_dec_strong(&addr);
assert_eq!(&enc[0..4], &2u32.to_le_bytes()); assert_eq!(&enc[4..8], &32u32.to_le_bytes()); assert_eq!(&enc[8..16], &[0u8; 8]); assert_eq!(&enc[16..48], addr.as_wire_bytes()); assert_eq!(enc.len(), 48);
assert_eq!(
c.encode_session_preamble(RPC_SESSION_ID_NEW),
(-1i32).to_le_bytes().to_vec()
);
}
#[test]
fn golden_is_not_close_enough() {
let c = R34Codec;
let mut enc = c
.encode_reply(&WireReply {
status: 0,
data: vec![],
..Default::default()
})
.unwrap();
let original = enc.clone();
enc[0] ^= 0x01; assert_ne!(enc, original);
assert!(matches!(c.decode_message(&enc), Err(RpcError::Protocol(_))));
}
#[test]
fn decoder_rejects_hostile_input_safely() {
let c = R34Codec;
assert!(c.decode_message(&[]).is_err());
assert!(c.decode_message(&[0u8; 15]).is_err());
let mut f = Vec::new();
f.extend_from_slice(&0u32.to_le_bytes()); f.extend_from_slice(&u32::MAX.to_le_bytes()); f.extend_from_slice(&[0u8; 8]);
assert!(matches!(
c.decode_message(&f),
Err(RpcError::FrameTooLarge { .. })
));
let mut f = Vec::new();
f.extend_from_slice(&0u32.to_le_bytes());
f.extend_from_slice(&10u32.to_le_bytes());
f.extend_from_slice(&[0u8; 8]);
f.extend_from_slice(&[0u8; 10]);
assert!(matches!(c.decode_message(&f), Err(RpcError::Protocol(_))));
let mut f = Vec::new();
f.extend_from_slice(&999u32.to_le_bytes());
f.extend_from_slice(&0u32.to_le_bytes());
f.extend_from_slice(&[0u8; 8]);
assert!(matches!(c.decode_message(&f), Err(RpcError::Protocol(_))));
let mut f = Vec::new();
f.extend_from_slice(&2u32.to_le_bytes());
f.extend_from_slice(&8u32.to_le_bytes());
f.extend_from_slice(&[0u8; 8]);
f.extend_from_slice(&[0u8; 8]);
assert!(matches!(c.decode_message(&f), Err(RpcError::Protocol(_))));
}
}