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:
+6
-3
@@ -4,7 +4,10 @@
|
||||
use hidapi::{HidApi, HidDevice};
|
||||
|
||||
use crate::bragi::properties::{self, BatteryStatus, Property};
|
||||
use crate::bragi::protocol::{self, BragiResponse, ENDPOINT_HEADSET, ENDPOINT_RECEIVER};
|
||||
use crate::bragi::protocol::{
|
||||
self, BragiResponse, ENDPOINT_HEADSET, ENDPOINT_RECEIVER,
|
||||
RESPONSE_ENDPOINT_HEADSET, CMD_GET, CMD_SET,
|
||||
};
|
||||
use crate::error::{CorsairError, Result};
|
||||
use crate::hid;
|
||||
|
||||
@@ -128,7 +131,7 @@ impl BragiDevice {
|
||||
let hex: Vec<String> = raw.iter().take(10).map(|b| format!("{b:02X}")).collect();
|
||||
eprintln!("[query] GET 0x{:02X} → {}", property.id(), hex.join(" "));
|
||||
}
|
||||
protocol::parse_response(&raw)
|
||||
protocol::parse_response_validated(&raw, Some(RESPONSE_ENDPOINT_HEADSET), Some(CMD_GET))
|
||||
}
|
||||
|
||||
/// Setzt eine Property auf dem Headset.
|
||||
@@ -138,7 +141,7 @@ impl BragiDevice {
|
||||
hid::flush(&self.device)?;
|
||||
let packet = protocol::build_set_packet(ENDPOINT_HEADSET, property.id(), data);
|
||||
let raw = hid::send_recv(&self.device, &packet)?;
|
||||
protocol::parse_response(&raw)
|
||||
protocol::parse_response_validated(&raw, Some(RESPONSE_ENDPOINT_HEADSET), Some(CMD_SET))
|
||||
}
|
||||
|
||||
/// Batterie-Level in Prozent (0.0 - 100.0).
|
||||
|
||||
@@ -9,4 +9,5 @@ pub use device::BragiDevice;
|
||||
pub use properties::{BatteryStatus, Property};
|
||||
pub use protocol::{
|
||||
BragiResponse, CORSAIR_VID, ENDPOINT_HEADSET, ENDPOINT_RECEIVER, HID_INTERFACE,
|
||||
RESPONSE_ENDPOINT_HEADSET, RESPONSE_ENDPOINT_RECEIVER,
|
||||
};
|
||||
|
||||
+19
-11
@@ -33,6 +33,10 @@ pub const STATUS_OK: u8 = 0x00;
|
||||
pub const STATUS_ERROR_F0: u8 = 0xF0;
|
||||
pub const STATUS_ERROR_F1: u8 = 0xF1;
|
||||
|
||||
// Response-Endpoints (weichen von Request-Endpoints ab!)
|
||||
pub const RESPONSE_ENDPOINT_RECEIVER: u8 = 0x00;
|
||||
pub const RESPONSE_ENDPOINT_HEADSET: u8 = 0x01;
|
||||
|
||||
/// Baut ein Bragi-Paket (65 Bytes) für den HID-Versand.
|
||||
///
|
||||
/// Format: [0x00, 0x02, endpoint, command, property, ...data, 0x00-padding]
|
||||
@@ -65,11 +69,14 @@ pub fn build_set_packet(endpoint: u8, property: u8, data: &[u8]) -> [u8; PACKET_
|
||||
}
|
||||
|
||||
/// Geparste Bragi-Antwort.
|
||||
///
|
||||
/// Byte-Zuordnung laut Protokoll-Doku:
|
||||
/// raw[0] = Report-Typ (0x01), raw[1] = Endpoint, raw[2] = Command, raw[3] = Status
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BragiResponse {
|
||||
pub status: u8,
|
||||
pub endpoint: u8,
|
||||
pub command: u8,
|
||||
pub status: u8,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
@@ -97,11 +104,11 @@ impl BragiResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parst eine rohe HID-Response (64 Bytes ohne Report-ID) in eine BragiResponse.
|
||||
/// Parst eine rohe HID-Response (64 Bytes) in eine BragiResponse.
|
||||
///
|
||||
/// Response-Format: [marker, status, endpoint, command, ...data]
|
||||
/// Response-Format: [report_type, endpoint, command, status, ...data]
|
||||
///
|
||||
/// Validiert den Protokoll-Marker und optional den erwarteten Endpoint/Command,
|
||||
/// Validiert optional den erwarteten Endpoint/Command,
|
||||
/// um veraltete oder fremde Antworten aus dem HID-Puffer zu erkennen.
|
||||
pub fn parse_response(raw: &[u8]) -> Result<BragiResponse> {
|
||||
parse_response_validated(raw, None, None)
|
||||
@@ -120,13 +127,14 @@ pub fn parse_response_validated(
|
||||
});
|
||||
}
|
||||
|
||||
let status = raw[1];
|
||||
let endpoint = raw[2];
|
||||
let command = raw[3];
|
||||
// raw[0] = Report-Typ (0x01)
|
||||
let endpoint = raw[1];
|
||||
let command = raw[2];
|
||||
let status = raw[3];
|
||||
|
||||
// Fehler-Status prüfen
|
||||
if status == STATUS_ERROR_F0 || status == STATUS_ERROR_F1 {
|
||||
return Err(CorsairError::PropertyNotSupported { property: raw[3] });
|
||||
// Fehler-Status prüfen: Endpoint-Byte 0xF0/0xF1 signalisiert Fehler
|
||||
if endpoint == STATUS_ERROR_F0 || endpoint == STATUS_ERROR_F1 {
|
||||
return Err(CorsairError::DeviceError { status: endpoint });
|
||||
}
|
||||
|
||||
// Response-Korrelation: Endpoint und Command gegen die Anfrage prüfen
|
||||
@@ -148,9 +156,9 @@ pub fn parse_response_validated(
|
||||
};
|
||||
|
||||
Ok(BragiResponse {
|
||||
status,
|
||||
endpoint,
|
||||
command,
|
||||
status,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
+2
-1
@@ -22,7 +22,8 @@ pub enum Command {
|
||||
/// Sidetone-Level lesen oder setzen (0-23, ALSA-Mixer)
|
||||
Sidetone {
|
||||
/// Sidetone-Level (0-23). Ohne Angabe wird der aktuelle Wert gelesen.
|
||||
level: Option<i64>,
|
||||
#[arg(value_parser = clap::value_parser!(u8).range(0..=23))]
|
||||
level: Option<u8>,
|
||||
},
|
||||
|
||||
/// LED-Helligkeit lesen oder setzen (0-1000)
|
||||
|
||||
+2
-2
@@ -14,8 +14,8 @@ pub enum CorsairError {
|
||||
#[error("Headset antwortet nicht — möglicherweise ausgeschaltet")]
|
||||
HeadsetOffline,
|
||||
|
||||
#[error("Gerät-Fehler: Property 0x{property:02X} nicht unterstützt")]
|
||||
PropertyNotSupported { property: u8 },
|
||||
#[error("Gerät-Fehler: Status 0x{status:02X} (Property nicht unterstützt)")]
|
||||
DeviceError { status: u8 },
|
||||
|
||||
#[error("Ungültige Antwort: erwartet mindestens {expected} Bytes, bekommen {got}")]
|
||||
ResponseTooShort { expected: usize, got: usize },
|
||||
|
||||
+4
-1
@@ -89,10 +89,13 @@ pub fn send_recv_optional(device: &HidDevice, packet: &[u8; PACKET_SIZE]) -> Res
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximale Iterationen beim Flushen, um Endlosschleifen zu verhindern.
|
||||
const MAX_FLUSH_ITERATIONS: usize = 64;
|
||||
|
||||
/// Leert den HID-Empfangspuffer (nonblocking reads bis leer).
|
||||
pub fn flush(device: &HidDevice) -> Result<()> {
|
||||
let mut buf = [0u8; REPORT_SIZE];
|
||||
loop {
|
||||
for _ in 0..MAX_FLUSH_ITERATIONS {
|
||||
// read_timeout mit 0ms = nonblocking
|
||||
let bytes_read = device.read_timeout(&mut buf, 0)?;
|
||||
if bytes_read == 0 {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// ABOUTME: Re-exportiert alle Module für externe Nutzung.
|
||||
|
||||
pub mod bragi;
|
||||
pub mod cli;
|
||||
pub mod error;
|
||||
pub mod hid;
|
||||
pub mod output;
|
||||
|
||||
+2
-3
@@ -25,9 +25,8 @@ fn run() -> error::Result<()> {
|
||||
|
||||
Command::Sidetone { level } => {
|
||||
if let Some(value) = level {
|
||||
let clamped = value.clamp(0, 23);
|
||||
sidetone::set_level(clamped)?;
|
||||
println!("{}", output::format_sidetone(clamped));
|
||||
sidetone::set_level(value as i64)?;
|
||||
println!("{}", output::format_sidetone(value as i64));
|
||||
} else {
|
||||
let current = sidetone::get_level()?;
|
||||
println!("{}", output::format_sidetone(current));
|
||||
|
||||
Reference in New Issue
Block a user