use serde_json::Value;
use crate::error::{Result, WireBandError};
pub const FRAME_PREFIX_LEN: usize = 2;
pub fn encode(symbol: u16, topic: &str, data: &Value) -> Vec<u8> {
let body = serde_json::to_vec(&serde_json::json!({ "t": topic, "d": data }))
.unwrap_or_default();
let mut frame = Vec::with_capacity(FRAME_PREFIX_LEN + body.len());
frame.push((symbol >> 8) as u8);
frame.push((symbol & 0xFF) as u8);
frame.extend_from_slice(&body);
frame
}
pub fn decode(raw: &[u8]) -> Result<(u16, Value)> {
if raw.len() < FRAME_PREFIX_LEN {
return Err(WireBandError::FrameTooShort(raw.len()));
}
let symbol = ((raw[0] as u16) << 8) | raw[1] as u16;
let payload: Value = serde_json::from_slice(&raw[FRAME_PREFIX_LEN..])?;
Ok((symbol, payload))
}
#[inline]
pub fn to_hex(frame: &[u8]) -> String {
frame.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn from_hex(hex: &str) -> Option<Vec<u8>> {
if hex.len() % 2 != 0 {
return None;
}
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).ok())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::symbols::SENSOR_TEMP;
#[test]
fn round_trip() {
let data = serde_json::json!({ "v": 22.5 });
let frame = encode(SENSOR_TEMP, "sensors/temp", &data);
let (sym, payload) = decode(&frame).unwrap();
assert_eq!(sym, SENSOR_TEMP);
assert_eq!(payload["t"], "sensors/temp");
assert_eq!(payload["d"]["v"], 22.5);
}
#[test]
fn prefix_bytes() {
let frame = encode(0xFC60, "t", &serde_json::json!({}));
assert_eq!(frame[0], 0xFC);
assert_eq!(frame[1], 0x60);
}
#[test]
fn compact_no_whitespace() {
let frame = encode(SENSOR_TEMP, "t", &serde_json::json!({ "v": 1 }));
let body = std::str::from_utf8(&frame[2..]).unwrap();
assert!(!body.contains(' '));
}
#[test]
fn hex_round_trip() {
let frame = encode(SENSOR_TEMP, "t", &serde_json::json!({ "v": 1 }));
let hex = to_hex(&frame);
let decoded = from_hex(&hex).unwrap();
assert_eq!(frame, decoded);
}
#[test]
fn decode_too_short() {
assert!(decode(&[0xFC]).is_err());
assert!(decode(&[]).is_err());
}
}