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))
}
const fn make_encode_table() -> [[u8; 2]; 256] {
let hex = b"0123456789abcdef";
let mut table = [[0u8; 2]; 256];
let mut i = 0usize;
while i < 256 {
table[i][0] = hex[i >> 4];
table[i][1] = hex[i & 0xF];
i += 1;
}
table
}
const fn make_decode_table() -> [u8; 256] {
let mut table = [0xFFu8; 256];
let mut i = 0u8;
loop {
table[i as usize] = match i {
b'0'..=b'9' => i - b'0',
b'a'..=b'f' => i - b'a' + 10,
b'A'..=b'F' => i - b'A' + 10,
_ => 0xFF,
};
if i == 255 { break; }
i += 1;
}
table
}
static ENCODE_TABLE: [[u8; 2]; 256] = make_encode_table();
static DECODE_TABLE: [u8; 256] = make_decode_table();
pub fn to_hex(frame: &[u8]) -> String {
let mut buf = Vec::with_capacity(frame.len() * 2);
for &byte in frame {
let pair = ENCODE_TABLE[byte as usize];
buf.push(pair[0]);
buf.push(pair[1]);
}
unsafe { String::from_utf8_unchecked(buf) }
}
pub fn from_hex(hex: &str) -> Option<Vec<u8>> {
let bytes = hex.as_bytes();
if bytes.len() % 2 != 0 {
return None;
}
let mut out = Vec::with_capacity(bytes.len() / 2);
let mut i = 0;
while i < bytes.len() {
let hi = DECODE_TABLE[bytes[i] as usize];
let lo = DECODE_TABLE[bytes[i + 1] as usize];
if hi == 0xFF || lo == 0xFF {
return None;
}
out.push((hi << 4) | lo);
i += 2;
}
Some(out)
}
#[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());
}
#[test]
fn hex_all_bytes() {
let all: Vec<u8> = (0u8..=255).collect();
let hex = to_hex(&all);
assert_eq!(hex.len(), 512);
assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
assert_eq!(from_hex(&hex).unwrap(), all);
}
#[test]
fn hex_case_insensitive_decode() {
assert_eq!(from_hex("FC60"), from_hex("fc60"));
assert_eq!(from_hex("Fc60"), from_hex("fc60"));
}
#[test]
fn hex_invalid_chars() {
assert!(from_hex("gg").is_none());
assert!(from_hex("zz").is_none());
assert!(from_hex("0").is_none()); }
}