fix: Audit-Befunde in Protokoll-Parsing, Error-Handling und Eingabe-Validierung

BragiResponse-Felder korrekt zugeordnet (endpoint=raw[1], command=raw[2],
status=raw[3]) gemäß Protokoll-Doku. PropertyNotSupported durch DeviceError
ersetzt, parse_response_validated in device.rs aktiviert, flush() mit
Iterationslimit gegen Endlosschleifen, Sidetone-Range per clap validiert
statt clamp, JSON-Escaping im hidpp-battery-waybar.sh, udev auf uaccess
umgestellt. 52 Tests grün.
This commit is contained in:
2026-03-28 00:37:36 +01:00
parent 05d138e922
commit 25eacfc02d
15 changed files with 208 additions and 48 deletions
+66 -20
View File
@@ -46,22 +46,23 @@ fn build_packet_total_size() {
#[test]
fn parse_response_extracts_fields() {
// Simulierte Response: [marker, status, endpoint, command, data0, data1, ...]
let mut raw = vec![0x02, 0x00, 0x09, 0x02, 0xE8, 0x03];
// Simulierte Response: [report_type, endpoint, command, status, data0, data1, ...]
// Laut Protokoll-Doku: raw[0]=0x01 Report-Typ, raw[1]=Endpoint, raw[2]=Command, raw[3]=Status
let mut raw = vec![0x01, RESPONSE_ENDPOINT_HEADSET, CMD_GET, STATUS_OK, 0xE8, 0x03];
raw.resize(REPORT_SIZE, 0x00);
let resp = parse_response(&raw).unwrap();
assert_eq!(resp.status, STATUS_OK);
assert_eq!(resp.endpoint, ENDPOINT_HEADSET);
assert_eq!(resp.endpoint, RESPONSE_ENDPOINT_HEADSET);
assert_eq!(resp.command, CMD_GET);
assert_eq!(resp.status, STATUS_OK);
assert_eq!(resp.data[0], 0xE8);
assert_eq!(resp.data[1], 0x03);
}
#[test]
fn parse_response_value_u16_little_endian() {
let mut raw = vec![0x02, 0x00, 0x09, 0x02, 0xE8, 0x03];
let mut raw = vec![0x01, RESPONSE_ENDPOINT_HEADSET, CMD_GET, STATUS_OK, 0xE8, 0x03];
raw.resize(REPORT_SIZE, 0x00);
let resp = parse_response(&raw).unwrap();
@@ -73,7 +74,7 @@ fn parse_response_value_u16_little_endian() {
#[test]
fn parse_response_value_u8() {
let mut raw = vec![0x02, 0x00, 0x09, 0x02, 0x03];
let mut raw = vec![0x01, RESPONSE_ENDPOINT_HEADSET, CMD_GET, STATUS_OK, 0x03];
raw.resize(REPORT_SIZE, 0x00);
let resp = parse_response(&raw).unwrap();
@@ -82,31 +83,31 @@ fn parse_response_value_u8() {
#[test]
fn parse_response_error_f0() {
let raw = vec![0x02, 0xF0, 0x09, 0x02];
let raw = vec![0x01, 0xF0, CMD_GET, STATUS_OK];
let result = parse_response(&raw);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
CorsairError::PropertyNotSupported { .. }
));
match result.unwrap_err() {
CorsairError::DeviceError { status } => assert_eq!(status, 0xF0),
other => panic!("Erwarteter DeviceError, bekommen: {other:?}"),
}
}
#[test]
fn parse_response_error_f1() {
let raw = vec![0x02, 0xF1, 0x09, 0x02];
let raw = vec![0x01, 0xF1, CMD_GET, STATUS_OK];
let result = parse_response(&raw);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
CorsairError::PropertyNotSupported { .. }
));
match result.unwrap_err() {
CorsairError::DeviceError { status } => assert_eq!(status, 0xF1),
other => panic!("Erwarteter DeviceError, bekommen: {other:?}"),
}
}
#[test]
fn parse_response_too_short() {
let raw = vec![0x02, 0x00];
let raw = vec![0x01, 0x00];
let result = parse_response(&raw);
assert!(result.is_err());
@@ -118,7 +119,7 @@ fn parse_response_too_short() {
#[test]
fn value_u16_on_empty_data_returns_error() {
let raw = vec![0x02, 0x00, 0x09, 0x02]; // Keine Daten-Bytes
let raw = vec![0x01, RESPONSE_ENDPOINT_HEADSET, CMD_GET, STATUS_OK]; // Keine Daten-Bytes
let resp = parse_response(&raw).unwrap();
let result = resp.value_u16();
@@ -132,7 +133,7 @@ fn value_u16_on_empty_data_returns_error() {
#[test]
fn battery_level_full_is_1000_promille() {
// Battery Level = 1000 (voll) → 0xE8, 0x03 LE
let mut raw = vec![0x02, 0x00, 0x09, 0x02, 0xE8, 0x03];
let mut raw = vec![0x01, RESPONSE_ENDPOINT_HEADSET, CMD_GET, STATUS_OK, 0xE8, 0x03];
raw.resize(REPORT_SIZE, 0x00);
let resp = parse_response(&raw).unwrap();
@@ -143,10 +144,55 @@ fn battery_level_full_is_1000_promille() {
#[test]
fn battery_level_half_is_500_promille() {
// Battery Level = 500 (50%) → 0xF4, 0x01 LE
let mut raw = vec![0x02, 0x00, 0x09, 0x02, 0xF4, 0x01];
let mut raw = vec![0x01, RESPONSE_ENDPOINT_HEADSET, CMD_GET, STATUS_OK, 0xF4, 0x01];
raw.resize(REPORT_SIZE, 0x00);
let resp = parse_response(&raw).unwrap();
let promille = resp.value_u16().unwrap();
assert_eq!(promille, 500);
}
// --- Schritt 4: parse_response_validated Tests ---
#[test]
fn parse_response_validated_rejects_wrong_endpoint() {
let mut raw = vec![0x01, RESPONSE_ENDPOINT_RECEIVER, CMD_GET, STATUS_OK, 0x00];
raw.resize(REPORT_SIZE, 0x00);
let result = parse_response_validated(&raw, Some(RESPONSE_ENDPOINT_HEADSET), None);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
CorsairError::UnexpectedResponse { .. }
));
}
#[test]
fn parse_response_validated_rejects_wrong_command() {
let mut raw = vec![0x01, RESPONSE_ENDPOINT_HEADSET, CMD_SET, STATUS_OK, 0x00];
raw.resize(REPORT_SIZE, 0x00);
let result = parse_response_validated(&raw, None, Some(CMD_GET));
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
CorsairError::UnexpectedResponse { .. }
));
}
#[test]
fn parse_response_validated_accepts_matching() {
let mut raw = vec![0x01, RESPONSE_ENDPOINT_HEADSET, CMD_GET, STATUS_OK, 0xE8, 0x03];
raw.resize(REPORT_SIZE, 0x00);
let resp = parse_response_validated(
&raw,
Some(RESPONSE_ENDPOINT_HEADSET),
Some(CMD_GET),
)
.unwrap();
assert_eq!(resp.endpoint, RESPONSE_ENDPOINT_HEADSET);
assert_eq!(resp.command, CMD_GET);
assert_eq!(resp.status, STATUS_OK);
}