fix(hid): 兼容 CH9329/CH9329F 串口响应 (#300)

支持 CH9329/CH9329F 扩展参数响应、粘包与乱序处理,保留严格校验和及有界接收缓冲。
This commit is contained in:
TXIuTnVsbA
2026-08-26 15:19:41 +08:00
committed by GitHub
parent 6e1194fe4c
commit d613739b06
2 changed files with 138 additions and 20 deletions

View File

@@ -10,6 +10,7 @@ pub const DEFAULT_ADDR: u8 = 0x00;
pub const DEFAULT_BAUD_RATE: u32 = 9600;
pub const MAX_DATA_LEN: usize = 64;
pub const MAX_PACKET_SIZE: usize = 70;
const EXTENDED_PARAMETER_RESPONSE_SIZES: [usize; 2] = [72, 88];
pub mod cmd {
pub const GET_INFO: u8 = 0x01;
@@ -130,7 +131,8 @@ impl Response {
let cmd = bytes[3];
let len = bytes[4] as usize;
if bytes.len() < 5 + len + 1 {
let expected_frame_len = 6 + len;
if bytes.len() < expected_frame_len {
return None;
}
@@ -139,7 +141,7 @@ impl Response {
.iter()
.fold(0u8, |acc, &x| acc.wrapping_add(x));
if expected_checksum != calculated_checksum {
tracing::warn!(
tracing::debug!(
"CH9329 checksum mismatch: expected {:02X}, got {:02X}",
expected_checksum,
calculated_checksum
@@ -215,6 +217,11 @@ pub fn try_extract_response(buffer: &[u8]) -> Option<(Response, usize)> {
}
let len = buffer[offset + 4] as usize;
if len > MAX_DATA_LEN {
offset += 1;
continue;
}
let frame_len = 6 + len;
if offset + frame_len > buffer.len() {
return None;
@@ -225,8 +232,103 @@ pub fn try_extract_response(buffer: &[u8]) -> Option<(Response, usize)> {
return Some((response, offset + frame_len));
}
// Some CH9329F firmware appends reserved bytes to GET_PARA_CFG while
// retaining the protocol LEN value of 50. Locate and validate the real
// checksum, return the documented 50-byte payload, and consume the
// complete extended frame. Other commands keep strict framing.
let cmd = buffer[offset + 3];
let data_start = offset + 5;
let parameter_payload_is_plausible = cmd == expected_response_cmd(cmd::GET_PARA_CFG, false)
&& len == 50
&& matches!(buffer[data_start], 0x00..=0x03 | 0x80..=0x83)
&& matches!(buffer[data_start + 1], 0x00..=0x02 | 0x80..=0x82);
if parameter_payload_is_plausible {
for extended_size in EXTENDED_PARAMETER_RESPONSE_SIZES {
let extended_end = offset + extended_size;
if buffer.len() >= extended_end {
let checksum_index = extended_end - 1;
if calculate_checksum(&buffer[offset..checksum_index]) != buffer[checksum_index]
{
continue;
}
return Some((
Response {
cmd,
data: buffer[data_start..data_start + len].to_vec(),
is_error: false,
error_code: None,
},
extended_end,
));
}
}
if buffer.len() < offset + EXTENDED_PARAMETER_RESPONSE_SIZES[1] {
return None;
}
}
offset += 1;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_standard_response_and_checksum() {
let frame = build_packet(DEFAULT_ADDR, 0x81, &[0x30, 0x01, 0x00, 0, 0, 0, 0, 0]);
let response = Response::parse(&frame).expect("valid response");
assert_eq!(response.cmd, 0x81);
assert_eq!(response.data, vec![0x30, 0x01, 0x00, 0, 0, 0, 0, 0]);
assert!(!response.is_error);
}
#[test]
fn extracts_extended_parameter_response_with_valid_trailing_checksum() {
let payload = [
0x80, 0x80, 0x00, 0x00, 0x00, 0x25, 0x80, 0x08, 0x00, 0x00, 0x03, 0x86, 0x1A, 0x2A,
0xE1, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
for reserved_len in [16, 32] {
let mut frame = vec![0x57, 0xAB, DEFAULT_ADDR, 0x88, 50];
frame.extend_from_slice(&payload);
frame.extend_from_slice(&vec![0; reserved_len]);
frame.push(calculate_checksum(&frame));
assert!(Response::parse(&frame[..56]).is_none());
let (response, consumed) = try_extract_response(&frame).expect("extended response");
assert_eq!(response.cmd, 0x88);
assert_eq!(response.data, payload);
assert!(!response.is_error);
assert_eq!(consumed, frame.len());
}
}
#[test]
fn rejects_bad_checksum_for_other_commands() {
let mut frame = build_packet(DEFAULT_ADDR, 0x89, &[0x00; 50]);
*frame.last_mut().unwrap() ^= 0xFF;
assert!(Response::parse(&frame).is_none());
assert!(try_extract_response(&frame).is_none());
}
#[test]
fn extracts_noise_and_adjacent_packets() {
let first = build_packet(DEFAULT_ADDR, 0x81, &[0x30, 0x01, 0, 0, 0, 0, 0, 0]);
let second = build_packet(DEFAULT_ADDR, 0x82, &[0x00]);
let mut buffer = vec![0x00, 0xFF];
buffer.extend_from_slice(&first);
buffer.extend_from_slice(&second);
let (_, consumed) = try_extract_response(&buffer).expect("first response");
assert_eq!(consumed, 2 + first.len());
let (response, _) = try_extract_response(&buffer[consumed..]).expect("second response");
assert_eq!(response.cmd, 0x82);
}
}