use crate::{ common::errors::ProtocolError, dsp408::{ protocol::{ constants::{METERS_HIGH_STEP, METERS_LOW_STEP, METERS_SPLIT, METERS_SPLIT_LEVEL_DB}, frame::Frame, }, types::{ Acknowledgement, AuthenticationResult, ConfigChunk, CurrentPreset, DeviceFlags, DeviceInfo, DeviceResponseType, HandshakeAck, Meters, ModifiedPreset, NotImplemented, PresetName, UnknownResponse, }, }, }; use super::encoding::{decode_ascii, decode_float16}; pub fn map_response(frame: &Frame) -> Result { let cmd = frame.command; let payload = &frame.payload; // Command `0x01` if cmd == 0x01 { return Ok(DeviceResponseType::Acknowledgement(Acknowledgement { success: true, })); } // Command `0x02` if cmd == 0x02 { return Ok(DeviceResponseType::NotImplemented(NotImplemented)); } // Command `0x10` (Start) if cmd == 0x10 { if payload.len() != 1 { return Err(ProtocolError::new(format!( "Invalid Start response payload length: {}", payload.len() ))); } return Ok(DeviceResponseType::HandshakeAck(HandshakeAck { ack: payload.first().copied(), })); } // Command `0x13` (Device Name) if cmd == 0x13 { if payload.is_empty() { return Err(ProtocolError::new("Missing device name payload")); } let name = decode_ascii(payload, false).map_err(|e| ProtocolError::new(e.0))?; return Ok(DeviceResponseType::DeviceInfo(DeviceInfo { name })); } // Command `0x14` (Current Preset Index) if cmd == 0x14 { if payload.len() != 1 { return Err(ProtocolError::new(format!( "Invalid Current Preset payload length: {}", payload.len() ))); } return Ok(DeviceResponseType::CurrentPreset(CurrentPreset { index: payload[0], })); } // Command `0x22` (Get Preset Modification Status) if cmd == 0x22 { if payload.len() != 20 { return Err(ProtocolError::new(format!( "Invalid Modified Preset payload length: {}", payload.len() ))); } let flags: Vec = payload.iter().map(|&b| b == 0xFF).collect(); return Ok(DeviceResponseType::ModifiedPreset(ModifiedPreset { modified: flags, })); } // Command `0x24` (Get Config Chunk) if cmd == 0x24 { // Mirrors Python's `int.from_bytes(payload[:1], "little")`, which // is `0` for an empty payload rather than an error — Python slices // never panic. `payload.get(1..)` below mirrors the same // forgiving-slice behavior for the remainder. let index = payload.first().copied().unwrap_or(0); if index > 0x1c { return Err(ProtocolError::new(format!( "Invalid Config Chunk Index: {}", index ))); } let data = payload.get(1..).unwrap_or(&[]).to_vec(); return Ok(DeviceResponseType::ConfigChunk(ConfigChunk { sub_index: index, data, })); } // Command `0x29` (Preset Name) if cmd == 0x29 { if payload.is_empty() { return Err(ProtocolError::new("Missing preset index")); } let name = decode_ascii(&payload[1..], false).map_err(|e| ProtocolError::new(e.0))?; return Ok(DeviceResponseType::PresetName(PresetName { index: payload[0], name, })); } // Command `0x2C` (Device Flags) if cmd == 0x2C { if payload.len() < 6 { return Err(ProtocolError::new(format!( "Invalid Device Flags payload length: {}", payload.len() ))); } if payload[5] != 0x00 && payload[5] != 0x01 { return Err(ProtocolError::new(format!( "Invalid lock flag: {:#04x}", payload[5] ))); } return Ok(DeviceResponseType::DeviceFlags(DeviceFlags { is_locked: payload[5] == 0x01, })); } // Command `0x2D` (Unlock Device) if cmd == 0x2D { if payload.len() < 2 { return Err(ProtocolError::new("Missing authentication result")); } if payload[1] != 0x00 && payload[1] != 0x01 { return Err(ProtocolError::new(format!( "Invalid authentication flag: {:#04x}", payload[1] ))); } return Ok(DeviceResponseType::AuthenticationResult( AuthenticationResult { success: payload[1] == 0x01, }, )); } // Command `0x40` (Get Level Meters) if cmd == 0x40 { if payload.len() != 38 { return Err(ProtocolError::new("Not enough bytes for meter levels")); } let floats: Vec = (0..36) .step_by(3) .map(|i| decode_float16(payload[i], payload[i + 1]).unwrap_or_default()) .collect(); let meters: Vec = floats .into_iter() .map(|f| { if f < METERS_SPLIT { METERS_SPLIT_LEVEL_DB + (f - METERS_SPLIT) / METERS_LOW_STEP } else { METERS_SPLIT_LEVEL_DB + (f - METERS_SPLIT) / METERS_HIGH_STEP } }) .collect(); return Ok(DeviceResponseType::Meters(Meters { levels: meters })); } Ok(DeviceResponseType::UnknownResponse(UnknownResponse { cmd: format!("{:#04x}", cmd), raw: payload.clone(), })) } #[cfg(test)] mod tests { use super::*; fn frame(command: u8, payload: Vec) -> Frame { Frame { command, payload } } #[test] fn acknowledgement() { let result = map_response(&frame(0x01, vec![])).unwrap(); assert_eq!( result, DeviceResponseType::Acknowledgement(Acknowledgement { success: true }) ); } #[test] fn not_implemented() { let result = map_response(&frame(0x02, vec![])).unwrap(); assert_eq!(result, DeviceResponseType::NotImplemented(NotImplemented)); } #[test] fn handshake_ack() { let result = map_response(&frame(0x10, vec![0x01])).unwrap(); assert_eq!( result, DeviceResponseType::HandshakeAck(HandshakeAck { ack: Some(1) }) ); } #[test] fn handshake_ack_invalid_payload_length() { let result = map_response(&frame(0x10, vec![1, 2])); assert!(result.is_err()); } #[test] fn device_name() { let result = map_response(&frame(0x13, b"TestDevice".to_vec())).unwrap(); assert_eq!( result, DeviceResponseType::DeviceInfo(DeviceInfo { name: "TestDevice".into() }) ); } #[test] fn device_name_missing_payload() { assert!(map_response(&frame(0x13, vec![])).is_err()); } #[test] fn device_name_invalid_ascii() { assert!(map_response(&frame(0x13, vec![0xff])).is_err()); } #[test] fn current_preset() { let result = map_response(&frame(0x14, vec![5])).unwrap(); assert_eq!( result, DeviceResponseType::CurrentPreset(CurrentPreset { index: 5 }) ); } #[test] fn current_preset_wrong_length_errors() { assert!(map_response(&frame(0x14, vec![1, 2])).is_err()); } #[test] fn modified_preset() { let payload = vec![0xff; 20]; let result = map_response(&frame(0x22, payload)).unwrap(); match result { DeviceResponseType::ModifiedPreset(p) => { assert_eq!(p.modified.len(), 20); assert!(p.modified.iter().all(|v| *v)); } other => panic!("unexpected {:?}", other), } } #[test] fn modified_preset_invalid_length() { assert!(map_response(&frame(0x22, vec![0; 19])).is_err()); } #[test] fn config_chunk() { let result = map_response(&frame(0x24, vec![3, 0xaa, 0xbb])).unwrap(); assert_eq!( result, DeviceResponseType::ConfigChunk(ConfigChunk { sub_index: 3, data: vec![0xaa, 0xbb], }) ); } #[test] fn config_chunk_empty_payload_defaults_to_zero() { let result = map_response(&frame(0x24, vec![])).unwrap(); assert_eq!( result, DeviceResponseType::ConfigChunk(ConfigChunk { sub_index: 0, data: vec![], }) ); } #[test] fn config_chunk_invalid_index() { assert!(map_response(&frame(0x24, vec![0x1d])).is_err()); } #[test] fn preset_name() { let result = map_response(&frame(0x29, vec![2, b'A', b'm', b'p'])).unwrap(); assert_eq!( result, DeviceResponseType::PresetName(PresetName { index: 2, name: "Amp".into(), }) ); } #[test] fn preset_name_missing_index() { assert!(map_response(&frame(0x29, vec![])).is_err()); } #[test] fn preset_name_invalid_ascii() { assert!(map_response(&frame(0x29, vec![1, 0xff])).is_err()); } #[test] fn device_flags_locked() { let mut payload = vec![0; 6]; payload[5] = 1; let result = map_response(&frame(0x2c, payload)).unwrap(); assert_eq!( result, DeviceResponseType::DeviceFlags(DeviceFlags { is_locked: true }) ); } #[test] fn device_flags_invalid_length() { assert!(map_response(&frame(0x2c, vec![0; 5])).is_err()); } #[test] fn device_flags_invalid_lock_flag() { let mut payload = vec![0; 6]; payload[5] = 2; assert!(map_response(&frame(0x2c, payload)).is_err()); } #[test] fn authentication_success() { let result = map_response(&frame(0x2d, vec![0, 1])).unwrap(); assert_eq!( result, DeviceResponseType::AuthenticationResult(AuthenticationResult { success: true }) ); } #[test] fn authentication_missing_result() { assert!(map_response(&frame(0x2d, vec![0])).is_err()); } #[test] fn authentication_invalid_flag() { assert!(map_response(&frame(0x2d, vec![0, 2])).is_err()); } #[test] fn meters_invalid_length() { assert!(map_response(&frame(0x40, vec![0; 10])).is_err()); } #[test] fn meters_decode() { // 38 bytes required. // First 36 bytes are meter values, remaining 2 bytes ignored. let mut payload = vec![0; 38]; // half float 1.0 = 0x3c00 for i in (0..36).step_by(3) { payload[i] = 0x00; payload[i + 1] = 0x3c; } let result = map_response(&frame(0x40, payload)).unwrap(); match result { DeviceResponseType::Meters(m) => { assert_eq!(m.levels.len(), 12); for level in m.levels { assert!(level.is_finite()); } } other => panic!("unexpected {:?}", other), } } #[test] fn unknown_command_falls_through() { let result = map_response(&frame(0x99, vec![0xaa])).unwrap(); match result { DeviceResponseType::UnknownResponse(r) => { assert_eq!(r.cmd, "0x99"); assert_eq!(r.raw, vec![0xaa]); } other => panic!("expected UnknownResponse, got {:?}", other), } } }