use crate::serial_core::codec::Codec; use crate::serial_core::error::CodecError; use crate::serial_core::utils::{calc_crc8_itu, elapsed_millis}; use std::time::Instant; const FRAME_HEADER: [u8; 2] = [0x55, 0xAA]; const RESPONSE_DATA_CMD: u8 = 0x81; const RESPONSE_DATA_LEGACY_FIXED_LENGTH: usize = 16; const RESPONSE_DATA_LEGACY_PREFIX_LENGTH: usize = 19; const RESPONSE_DATA_TEMP_FIXED_LENGTH: usize = 20; const RESPONSE_DATA_TEMP_PREFIX_LENGTH: usize = 23; const MIN_FRAME_LENGTH: usize = 7; const DEFAULT_MAX_FRAME_LENGTH: usize = 64 * 1024; #[derive(Debug, Clone, Copy)] struct HandGatewayResponseLayout { fixed_length: usize, prefix_length: usize, timestamp_len: usize, config_offset: usize, valid_config_offset: usize, block_count_offset: usize, } const TEMP_RESPONSE_LAYOUT: HandGatewayResponseLayout = HandGatewayResponseLayout { fixed_length: RESPONSE_DATA_TEMP_FIXED_LENGTH, prefix_length: RESPONSE_DATA_TEMP_PREFIX_LENGTH, timestamp_len: 8, config_offset: 14, valid_config_offset: 18, block_count_offset: 22, }; const LEGACY_RESPONSE_LAYOUT: HandGatewayResponseLayout = HandGatewayResponseLayout { fixed_length: RESPONSE_DATA_LEGACY_FIXED_LENGTH, prefix_length: RESPONSE_DATA_LEGACY_PREFIX_LENGTH, timestamp_len: 4, config_offset: 10, valid_config_offset: 14, block_count_offset: 18, }; #[derive(Debug, Clone, PartialEq, Eq)] pub struct HandGatewayNodeConfig { pub config_mask: u32, pub sample_count: usize, } impl HandGatewayNodeConfig { pub const fn new(config_mask: u32, sample_count: usize) -> Self { Self { config_mask, sample_count, } } fn payload_len(&self, bytes_per_sample: usize) -> Option { self.sample_count.checked_mul(bytes_per_sample) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct HandGatewayConfig { pub protocol_version: u8, pub bytes_per_sample: usize, pub max_frame_length: usize, pub nodes: Vec, } impl HandGatewayConfig { pub fn new(nodes: Vec) -> Self { Self { protocol_version: 0x01, bytes_per_sample: 2, max_frame_length: DEFAULT_MAX_FRAME_LENGTH, nodes, } } fn validate(&self) -> Result<(), CodecError> { if self.bytes_per_sample == 0 || self.max_frame_length < MIN_FRAME_LENGTH { return Err(CodecError::InvalidLength); } let mut used_masks = 0u32; for node in &self.nodes { if node.config_mask == 0 || node.config_mask.count_ones() != 1 || used_masks & node.config_mask != 0 || node.payload_len(self.bytes_per_sample).is_none() { return Err(CodecError::InvalidLength); } used_masks |= node.config_mask; } Ok(()) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct HandGatewayFrameNode { pub config_mask: u32, pub valid: bool, pub payload: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct HandGatewayDataRepFrame { pub timestamp: u64, pub config: u32, pub valid_config: u32, pub block_count: u8, pub raw: Vec, pub nodes: Vec, pub dts_ms: u64, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum HandGatewayFrame { DataRep(HandGatewayDataRepFrame), } pub struct HandGatewayCodec { buffer: Vec, config: HandGatewayConfig, } impl HandGatewayCodec { pub fn new(node_sample_counts: &[u16]) -> Self { let nodes = node_sample_counts .iter() .enumerate() .map(|(index, &sample_count)| { HandGatewayNodeConfig::new( 1u32.checked_shl(index as u32).unwrap_or(0), sample_count as usize, ) }) .collect(); Self { buffer: Vec::new(), config: HandGatewayConfig::new(nodes), } } pub fn parse_node_payload(data: &[u8]) -> Result, CodecError> { if data.len() % 2 != 0 { return Err(CodecError::InvalidLength); } Ok(data .chunks_exact(2) .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) .collect()) } fn retain_possible_header_prefix(&mut self) { if self.buffer.last() == Some(&FRAME_HEADER[0]) { self.buffer.drain(..self.buffer.len() - 1); } else { self.buffer.clear(); } } fn parse_data_response_with_layout( &self, frame: &[u8], length: u16, session_started_at: Instant, layout: HandGatewayResponseLayout, ) -> Result { let payload_len = usize::from(length) .checked_sub(layout.fixed_length) .ok_or(CodecError::InvalidLength)?; let payload_end = layout .prefix_length .checked_add(payload_len) .ok_or(CodecError::PayloadTooLarge)?; if payload_end + 1 != frame.len() { return Err(CodecError::InvalidLength); } let version = frame[5]; if version != self.config.protocol_version { return Err(CodecError::InvalidFrameType); } let timestamp = match layout.timestamp_len { 4 => u32::from_le_bytes(frame[6..10].try_into().unwrap()) as u64, 8 => u64::from_le_bytes(frame[6..14].try_into().unwrap()), _ => return Err(CodecError::InvalidLength), }; let config = u32::from_le_bytes( frame[layout.config_offset..layout.config_offset + 4] .try_into() .unwrap(), ); let valid_config = u32::from_le_bytes( frame[layout.valid_config_offset..layout.valid_config_offset + 4] .try_into() .unwrap(), ); let block_count = frame[layout.block_count_offset]; let active_nodes = self .config .nodes .iter() .filter(|node| config & node.config_mask != 0) .collect::>(); if active_nodes.len() != usize::from(block_count) { return Err(CodecError::InvalidLength); } let expected_payload_len = active_nodes.iter().try_fold(0usize, |total, node| { let node_len = node .payload_len(self.config.bytes_per_sample) .ok_or(CodecError::PayloadTooLarge)?; total .checked_add(node_len) .ok_or(CodecError::PayloadTooLarge) })?; if expected_payload_len != payload_len { return Err(CodecError::InvalidLength); } let mut cursor = layout.prefix_length; let mut nodes = Vec::with_capacity(active_nodes.len()); for node in active_nodes { let node_len = node .payload_len(self.config.bytes_per_sample) .ok_or(CodecError::PayloadTooLarge)?; let next = cursor + node_len; nodes.push(HandGatewayFrameNode { config_mask: node.config_mask, valid: valid_config & node.config_mask != 0, payload: frame[cursor..next].to_vec(), }); cursor = next; } Ok(HandGatewayFrame::DataRep(HandGatewayDataRepFrame { timestamp, config, valid_config, block_count, raw: frame.to_vec(), nodes, dts_ms: elapsed_millis(session_started_at), })) } fn parse_data_response( &self, frame: &[u8], length: u16, session_started_at: Instant, ) -> Result { self.parse_data_response_with_layout( frame, length, session_started_at, TEMP_RESPONSE_LAYOUT, ) .or_else(|_| { self.parse_data_response_with_layout( frame, length, session_started_at, LEGACY_RESPONSE_LAYOUT, ) }) } } impl Codec for HandGatewayCodec { fn decode( &mut self, input: &[u8], session_started_at: Instant, ) -> Result, CodecError> { self.config.validate()?; self.buffer.extend_from_slice(input); let mut frames = Vec::new(); loop { let Some(header_pos) = self .buffer .windows(FRAME_HEADER.len()) .position(|window| window == FRAME_HEADER) else { self.retain_possible_header_prefix(); break; }; if header_pos > 0 { self.buffer.drain(..header_pos); } if self.buffer.len() < 4 { break; } let length = u16::from_le_bytes([self.buffer[2], self.buffer[3]]); let frame_len = usize::from(length) .checked_add(4) .ok_or(CodecError::PayloadTooLarge)?; if frame_len < MIN_FRAME_LENGTH || frame_len > self.config.max_frame_length { log::debug!("invalid hand gateway frame length: {frame_len}"); self.buffer.drain(..1); continue; } if self.buffer.len() < frame_len { break; } let expected_checksum = calc_crc8_itu(&self.buffer[..frame_len - 1]); let received_checksum = self.buffer[frame_len - 1]; if expected_checksum != received_checksum { log::debug!( "hand gateway checksum mismatch: expected {expected_checksum:02X}, got {received_checksum:02X}" ); self.buffer.drain(..1); continue; } if self.buffer[4] == RESPONSE_DATA_CMD { match self.parse_data_response( &self.buffer[..frame_len], length, session_started_at, ) { Ok(frame) => frames.push(frame), Err(error) => log::debug!("invalid hand gateway data response: {error}"), } } self.buffer.drain(..frame_len); } Ok(frames) } fn encode(&self, frame: &HandGatewayFrame) -> Result, CodecError> { let _ = frame; Err(CodecError::InvalidFrameType) } } #[cfg(test)] mod tests { use super::*; fn data_response(config: u32, valid_config: u32, payload: &[u8]) -> Vec { let block_count = config.count_ones() as u8; let length = (RESPONSE_DATA_LEGACY_FIXED_LENGTH + payload.len()) as u16; let mut frame = Vec::new(); frame.extend_from_slice(&FRAME_HEADER); frame.extend_from_slice(&length.to_le_bytes()); frame.push(RESPONSE_DATA_CMD); frame.push(0x01); frame.extend_from_slice(&0x0102_0304u32.to_le_bytes()); frame.extend_from_slice(&config.to_le_bytes()); frame.extend_from_slice(&valid_config.to_le_bytes()); frame.push(block_count); frame.extend_from_slice(payload); let checksum = calc_crc8_itu(&frame); frame.push(checksum); frame } #[test] fn decodes_data_response_frame() { let payload = [0x10, 0x00, 0x34, 0x12, 0x08, 0x00]; let bytes = data_response(0b11, 0b01, &payload); let frames = HandGatewayCodec::new(&[2, 1]) .decode(&bytes, Instant::now()) .unwrap(); assert_eq!(frames.len(), 1); let HandGatewayFrame::DataRep(frame) = &frames[0]; assert_eq!(frame.timestamp, 0x0102_0304); assert_eq!(frame.config, 0b11); assert_eq!(frame.valid_config, 0b01); assert_eq!(frame.block_count, 2); assert_eq!(frame.raw, bytes); assert_eq!(frame.nodes.len(), 2); assert_eq!( HandGatewayCodec::parse_node_payload(&frame.nodes[0].payload).unwrap(), vec![16, 0x1234] ); assert_eq!( HandGatewayCodec::parse_node_payload(&frame.nodes[1].payload).unwrap(), vec![8] ); } }