diff options
| author | Dawid Rycerz <dawid@rycerz.xyz> | 2026-02-08 12:44:10 +0100 |
|---|---|---|
| committer | Dawid Rycerz <dawid@rycerz.xyz> | 2026-02-08 12:44:10 +0100 |
| commit | 0c20fb86633104744dbccf30ad732296694fff1b (patch) | |
| tree | 02ffb8494086960b4a84decf3bdc2c8c61bfc4f6 /src/protocol/parser.rs | |
Initial pipewiremain
Diffstat (limited to 'src/protocol/parser.rs')
| -rw-r--r-- | src/protocol/parser.rs | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/src/protocol/parser.rs b/src/protocol/parser.rs new file mode 100644 index 0000000..7db824e --- /dev/null +++ b/src/protocol/parser.rs @@ -0,0 +1,418 @@ +//! UPP protocol parser for decoding USB frames + +use super::frame::{UPPFlags, UPPFrame, UPPFrameHeader, UPPUsbFrame}; +use crate::error::{ProtocolError, Result}; +use std::mem; +use tracing::{debug, trace, warn}; + +/// UPP protocol parser +pub struct UPPParser { + enable_debug: bool, +} + +impl UPPParser { + /// Create a new UPP parser + pub fn new() -> Self { + Self { + enable_debug: false, + } + } + + /// Create a new UPP parser with debug enabled + pub fn new_with_debug(enable_debug: bool) -> Self { + Self { enable_debug } + } + + /// Parse a raw USB frame into a UPP frame + pub fn parse_frame(&self, data: &[u8]) -> Result<UPPFrame> { + trace!("Parsing UPP frame: {} bytes", data.len()); + + // Parse USB frame header + let usb_frame = self.parse_usb_header(data)?; + + // Validate frame length - based on C++ POC analysis + let expected_total_size = usb_frame.total_size(); + let actual_size = data.len(); + + if self.enable_debug { + trace!( + "Frame size validation: expected={}, actual={}, difference={}", + expected_total_size, actual_size, actual_size as i32 - expected_total_size as i32 + ); + } + + // Based on C++ POC: the length field represents total payload size + // If we have less data than expected, this is a partial frame (chunked) + if actual_size < expected_total_size { + return Err(ProtocolError::FrameTooSmall { + expected: expected_total_size, + actual: actual_size, + } + .into()); + } + + // Parse camera frame header + let camera_header = self.parse_camera_header(data, &usb_frame)?; + + // Extract frame data - use the expected data size + let data_start = mem::size_of::<UPPUsbFrame>() + 7; // Manual header size: 7 bytes + let frame_data = if data_start < data.len() { + let end_index = std::cmp::min(data_start + usb_frame.expected_data_size(), data.len()); + data[data_start..end_index].to_vec() + } else { + Vec::new() + }; + + if self.enable_debug { + trace!( + "Parsed UPP frame: ID={}, Camera={}, Data={} bytes, Total={} bytes, Expected={} bytes", + camera_header.frame_id, + camera_header.camera_number, + frame_data.len(), + data.len(), + expected_total_size + ); + } + + Ok(UPPFrame { + header: camera_header, + data: frame_data, + }) + } + + /// Parse a raw USB frame with better error handling and diagnostics + pub fn parse_frame_robust(&self, data: &[u8]) -> Result<UPPFrame> { + trace!("Parsing UPP frame robustly: {} bytes", data.len()); + + // First, try to find a valid frame start + let frame_start = self.find_frame_start(data)?; + let frame_data = &data[frame_start..]; + + if self.enable_debug { + debug!("Found frame start at offset {}, processing {} bytes", frame_start, frame_data.len()); + } + + // Parse the frame from the found start position + self.parse_frame(frame_data) + } + + /// Find the start of a valid UPP frame in potentially misaligned data + fn find_frame_start(&self, data: &[u8]) -> Result<usize> { + if data.len() < 12 { // Minimum frame size + return Err(ProtocolError::FrameTooSmall { + expected: 12, + actual: data.len(), + } + .into()); + } + + // Look for the magic number (0xBBAA) in the data + for i in 0..=data.len().saturating_sub(12) { + if data.len() >= i + 2 { + let magic_bytes = [data[i], data[i + 1]]; + let magic = u16::from_le_bytes(magic_bytes); + + if magic == super::UPP_USB_MAGIC { + if self.enable_debug { + debug!("Found magic number 0x{:04X} at offset {}", magic, i); + } + return Ok(i); + } + } + } + + Err(ProtocolError::InvalidMagic { + expected: super::UPP_USB_MAGIC, + actual: 0, // Unknown + } + .into()) + } + + /// Parse USB frame header + pub(crate) fn parse_usb_header(&self, data: &[u8]) -> Result<UPPUsbFrame> { + if data.len() < mem::size_of::<UPPUsbFrame>() { + return Err(ProtocolError::FrameTooSmall { + expected: mem::size_of::<UPPUsbFrame>(), + actual: data.len(), + } + .into()); + } + + // Safe to transmute since we've checked the size + let usb_frame = unsafe { *(data.as_ptr() as *const UPPUsbFrame) }; + + // Validate magic number + if !usb_frame.is_valid() { + return Err(ProtocolError::InvalidMagic { + expected: super::UPP_USB_MAGIC, + actual: usb_frame.magic, + } + .into()); + } + + // Validate camera ID + if usb_frame.cid != super::UPP_CAMID_7 { + return Err(ProtocolError::UnknownCameraId(usb_frame.cid).into()); + } + + if self.enable_debug { + let magic = usb_frame.magic; + let cid = usb_frame.cid; + let length = usb_frame.length; + trace!( + "USB frame: magic=0x{:04X}, cid={}, length={}", + magic, cid, length + ); + } + + Ok(usb_frame) + } + + /// Parse camera frame header + pub(crate) fn parse_camera_header(&self, data: &[u8], _usb_frame: &UPPUsbFrame) -> Result<UPPFrameHeader> { + let header_offset = mem::size_of::<UPPUsbFrame>(); + // Manual header size: frame_id(1) + camera_number(1) + flags(1) + g_sensor(4) = 7 bytes + let header_end = header_offset + 7; + + if data.len() < header_end { + return Err(ProtocolError::FrameTooSmall { + expected: header_end, + actual: data.len(), + } + .into()); + } + + // Manually parse the header to avoid packed struct issues + let frame_id = data[header_offset]; + let camera_number = data[header_offset + 1]; + + // Read flags byte and extract individual bits + let flags_byte = data[header_offset + 2]; + let has_g = (flags_byte & 0x01) != 0; // Bit 0 + let button_press = (flags_byte & 0x02) != 0; // Bit 1 + let other = (flags_byte >> 2) & 0x3F; // Bits 2-7 (6 bits) + + if self.enable_debug { + trace!( + "Raw flags byte: 0x{:02X}, has_g={}, button_press={}, other=0x{:02X}", + flags_byte, has_g, button_press, other + ); + } + + // Read G-sensor data (4 bytes, little-endian) + let g_sensor_bytes = [ + data[header_offset + 3], + data[header_offset + 4], + data[header_offset + 5], + data[header_offset + 6], + ]; + + if self.enable_debug { + trace!( + "G-sensor bytes: {:02X} {:02X} {:02X} {:02X}", + g_sensor_bytes[0], g_sensor_bytes[1], g_sensor_bytes[2], g_sensor_bytes[3] + ); + } + + let g_sensor = u32::from_le_bytes(g_sensor_bytes); + + let camera_header = UPPFrameHeader { + frame_id, + camera_number, + flags: UPPFlags { + has_g, + button_press, + other, + }, + g_sensor, + }; + + // Validate frame header + self.validate_camera_header(&camera_header)?; + + if self.enable_debug { + trace!( + "Camera header: frame_id={}, camera={}, has_g={}, button={}", + camera_header.frame_id, + camera_header.camera_number, + camera_header.flags.has_g, + camera_header.flags.button_press + ); + } + + Ok(camera_header) + } + + /// Validate camera frame header + fn validate_camera_header(&self, header: &UPPFrameHeader) -> Result<()> { + // Validate camera number (should be 0 or 1) + if header.camera_number >= 2 { + return Err(ProtocolError::InvalidFrameFormat(format!( + "Invalid camera number: {}", + header.camera_number + )) + .into()); + } + + // Validate flags (G-sensor and other flags should be 0 for now) + if header.flags.has_g { + warn!("G-sensor data not yet supported"); + } + + if header.flags.other != 0 { + warn!("Unknown flags set: 0x{:02X}", header.flags.other); + } + + Ok(()) + } + + /// Get frame statistics + pub fn get_stats(&self) -> UPPParserStats { + UPPParserStats { + enable_debug: self.enable_debug, + } + } +} + +impl Default for UPPParser { + fn default() -> Self { + Self::new() + } +} + +/// UPP parser statistics +#[derive(Debug, Clone)] +pub struct UPPParserStats { + pub enable_debug: bool, +} + +#[cfg(test)] +mod tests { + use super::super::UPP_USB_MAGIC; + use super::*; + use crate::error::Error; + + // Create test data for a valid UPP frame + fn create_test_frame(frame_id: u8, camera_number: u8, data_size: usize) -> Vec<u8> { + let mut frame = Vec::new(); + + // USB header (5 bytes) + frame.extend_from_slice(&UPP_USB_MAGIC.to_le_bytes()); + frame.push(7); // Camera ID + frame.extend_from_slice(&(data_size as u16 + 7).to_le_bytes()); // Length (camera header + data) + + // Camera header (7 bytes) + frame.push(frame_id); + frame.push(camera_number); + // Flags struct (1 byte - packed bit fields) + let flags_byte = 0u8; // has_g: false, button_press: false, other: 0 + frame.push(flags_byte); + frame.extend_from_slice(&0u32.to_le_bytes()); // G-sensor data (4 bytes) + + // Frame data + frame.extend(std::iter::repeat(0xAA).take(data_size)); + + frame + } + + #[test] + fn test_upp_parser_creation() { + let parser = UPPParser::new(); + assert!(!parser.enable_debug); + + let parser = UPPParser::new_with_debug(true); + assert!(parser.enable_debug); + } + + #[test] + fn test_upp_parser_parse_valid_frame() { + let parser = UPPParser::new(); + let test_frame = create_test_frame(1, 0, 10); + + let result = parser.parse_frame(&test_frame); + assert!(result.is_ok()); + + let frame = result.unwrap(); + assert_eq!(frame.frame_id(), 1); + assert_eq!(frame.camera_number(), 0); + assert_eq!(frame.data.len(), 10); + assert!(!frame.button_pressed()); + assert!(frame.g_sensor_data().is_none()); + } + + #[test] + fn test_upp_parser_frame_too_small() { + let parser = UPPParser::new(); + let short_data = &[0xFF, 0xAA]; // Too short for any header + + let result = parser.parse_frame(short_data); + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + Error::Protocol(ProtocolError::FrameTooSmall { .. }) + )); + } + + #[test] + fn test_upp_parser_invalid_magic() { + let parser = UPPParser::new(); + let mut test_frame = create_test_frame(1, 0, 5); + test_frame[0] = 0x12; // Corrupt magic + + let result = parser.parse_frame(&test_frame); + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + Error::Protocol(ProtocolError::InvalidMagic { .. }) + )); + } + + #[test] + fn test_upp_parser_unknown_camera_id() { + let parser = UPPParser::new(); + let mut test_frame = create_test_frame(1, 0, 5); + test_frame[2] = 5; // Unknown camera ID + + let result = parser.parse_frame(&test_frame); + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + Error::Protocol(ProtocolError::UnknownCameraId(5)) + )); + } + + #[test] + fn test_upp_parser_button_press() { + let parser = UPPParser::new(); + let mut test_frame = create_test_frame(1, 0, 5); + test_frame[7] = 0x02; // Set button press flag (bit 1 of flags byte at index 7) + + let result = parser.parse_frame(&test_frame); + assert!(result.is_ok()); + + let frame = result.unwrap(); + assert!(frame.button_pressed()); + } + + #[test] + fn test_upp_parser_g_sensor() { + let parser = UPPParser::new(); + let mut test_frame = create_test_frame(1, 0, 5); + test_frame[7] = 0x01; // Set G-sensor flag (bit 0 of flags byte at index 7) + test_frame[8..12].copy_from_slice(&0x12345678u32.to_le_bytes()); // G-sensor data (indices 8-11) + + let result = parser.parse_frame(&test_frame); + assert!(result.is_ok()); + + let frame = result.unwrap(); + assert!(frame.g_sensor_data().is_some()); + assert_eq!(frame.g_sensor_data().unwrap(), 0x12345678); + } + + #[test] + fn test_upp_parser_stats() { + let parser = UPPParser::new(); + let stats = parser.get_stats(); + assert!(!stats.enable_debug); + } +} |
