fix: Audit-Findings behoben (Perf, Security, Quality)
- Sleep vor HID-Reads entfernt — read_timeout reicht als Synchronisation, spart ~300ms pro Aufruf - udev-Regel: MODE 0660 + GROUP plugdev statt world-writable 0666 - Eigener CorsairError::SidetoneNotFound für fehlende ALSA-Controls - Response-Validierung vorbereitet (parse_response_validated), Korrelation noch deaktiviert da Response-Format andere Endpoint-IDs nutzt als das Request-Format (0x00/0x01 vs 0x08/0x09) - Protokoll-Doku zum Response-Format korrigiert - 18 neue Tests für output.rs (Waybar-JSON Formatierung + Grenzwerte)
This commit is contained in:
@@ -124,6 +124,10 @@ 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(" "));
|
||||
}
|
||||
// Response-Korrelation ist noch nicht implementiert — das Response-Format
|
||||
// nutzt andere Endpoint-IDs (0x00/0x01) als das Request-Format (0x08/0x09)
|
||||
// und die Feld-Reihenfolge weicht ab. Error-Detection via 0xF0/0xF1
|
||||
// auf raw[1] funktioniert aber korrekt.
|
||||
protocol::parse_response(&raw)
|
||||
}
|
||||
|
||||
|
||||
+24
-2
@@ -100,7 +100,19 @@ impl BragiResponse {
|
||||
/// Parst eine rohe HID-Response (64 Bytes ohne Report-ID) in eine BragiResponse.
|
||||
///
|
||||
/// Response-Format: [marker, status, endpoint, command, ...data]
|
||||
///
|
||||
/// Validiert den Protokoll-Marker und 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)
|
||||
}
|
||||
|
||||
/// Wie `parse_response`, aber mit optionaler Endpoint/Command-Korrelation.
|
||||
pub fn parse_response_validated(
|
||||
raw: &[u8],
|
||||
expected_endpoint: Option<u8>,
|
||||
expected_command: Option<u8>,
|
||||
) -> Result<BragiResponse> {
|
||||
if raw.len() < 4 {
|
||||
return Err(CorsairError::ResponseTooShort {
|
||||
expected: 4,
|
||||
@@ -114,11 +126,21 @@ pub fn parse_response(raw: &[u8]) -> Result<BragiResponse> {
|
||||
|
||||
// Fehler-Status prüfen
|
||||
if status == STATUS_ERROR_F0 || status == STATUS_ERROR_F1 {
|
||||
// Property-ID steht im Command-Byte bei Fehlern — aber wir haben sie
|
||||
// nicht immer. Wir melden den Fehler generisch.
|
||||
return Err(CorsairError::PropertyNotSupported { property: raw[3] });
|
||||
}
|
||||
|
||||
// Response-Korrelation: Endpoint und Command gegen die Anfrage prüfen
|
||||
if let Some(exp) = expected_endpoint {
|
||||
if endpoint != exp {
|
||||
return Err(CorsairError::UnexpectedResponse { endpoint, command });
|
||||
}
|
||||
}
|
||||
if let Some(exp) = expected_command {
|
||||
if command != exp {
|
||||
return Err(CorsairError::UnexpectedResponse { endpoint, command });
|
||||
}
|
||||
}
|
||||
|
||||
let data = if raw.len() > 4 {
|
||||
raw[4..].to_vec()
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,9 @@ pub enum CorsairError {
|
||||
#[error("Kein Corsair-Gerät gefunden (VID 0x1B1C, Interface 3)")]
|
||||
DeviceNotFound,
|
||||
|
||||
#[error("ALSA Sidetone-Control nicht gefunden — kein Corsair USB-Audio-Interface erkannt")]
|
||||
SidetoneNotFound,
|
||||
|
||||
#[error("Headset antwortet nicht — möglicherweise ausgeschaltet")]
|
||||
HeadsetOffline,
|
||||
|
||||
@@ -17,6 +20,9 @@ pub enum CorsairError {
|
||||
#[error("Ungültige Antwort: erwartet mindestens {expected} Bytes, bekommen {got}")]
|
||||
ResponseTooShort { expected: usize, got: usize },
|
||||
|
||||
#[error("Unerwartete Antwort: Endpoint 0x{endpoint:02X}, Command 0x{command:02X}")]
|
||||
UnexpectedResponse { endpoint: u8, command: u8 },
|
||||
|
||||
#[error("HID-Fehler: {0}")]
|
||||
Hid(#[from] hidapi::HidError),
|
||||
|
||||
|
||||
+2
-8
@@ -1,8 +1,6 @@
|
||||
// ABOUTME: Dünner Wrapper um hidapi für Corsair-Geräte.
|
||||
// ABOUTME: Kapselt Device-Discovery, Send/Receive und Buffer-Flushing.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use hidapi::{HidApi, HidDevice};
|
||||
|
||||
use crate::bragi::protocol::{CORSAIR_VID, HID_INTERFACE, PACKET_SIZE, REPORT_SIZE};
|
||||
@@ -61,11 +59,8 @@ const KNOWN_BRAGI_PIDS: &[u16] = &[
|
||||
pub fn send_recv(device: &HidDevice, packet: &[u8; PACKET_SIZE]) -> Result<Vec<u8>> {
|
||||
device.write(packet)?;
|
||||
|
||||
// 50ms warten wie in der Python-Referenz
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
|
||||
let mut buf = [0u8; REPORT_SIZE];
|
||||
let bytes_read = device.read_timeout(&mut buf, 500)?;
|
||||
let bytes_read = device.read_timeout(&mut buf, 200)?;
|
||||
|
||||
if bytes_read == 0 {
|
||||
return Err(CorsairError::HeadsetOffline);
|
||||
@@ -77,10 +72,9 @@ pub fn send_recv(device: &HidDevice, packet: &[u8; PACKET_SIZE]) -> Result<Vec<u
|
||||
/// Sendet ein Paket und ignoriert die Antwort (für Init-Sequenz).
|
||||
pub fn send_recv_optional(device: &HidDevice, packet: &[u8; PACKET_SIZE]) -> Result<Option<Vec<u8>>> {
|
||||
device.write(packet)?;
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
|
||||
let mut buf = [0u8; REPORT_SIZE];
|
||||
let bytes_read = device.read_timeout(&mut buf, 500)?;
|
||||
let bytes_read = device.read_timeout(&mut buf, 200)?;
|
||||
|
||||
if bytes_read == 0 {
|
||||
Ok(None)
|
||||
|
||||
+3
-3
@@ -21,7 +21,7 @@ fn find_card() -> Result<String> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(crate::error::CorsairError::DeviceNotFound)
|
||||
Err(crate::error::CorsairError::SidetoneNotFound)
|
||||
}
|
||||
|
||||
/// Liest den aktuellen Sidetone-Level (0-23).
|
||||
@@ -32,7 +32,7 @@ pub fn get_level() -> Result<i64> {
|
||||
let selem_id = SelemId::new(SIDETONE_CONTROL, 0);
|
||||
let selem = mixer
|
||||
.find_selem(&selem_id)
|
||||
.ok_or(crate::error::CorsairError::DeviceNotFound)?;
|
||||
.ok_or(crate::error::CorsairError::SidetoneNotFound)?;
|
||||
|
||||
let value = selem.get_playback_volume(SelemChannelId::FrontLeft)?;
|
||||
Ok(value)
|
||||
@@ -47,7 +47,7 @@ pub fn set_level(level: i64) -> Result<()> {
|
||||
let volume_id = SelemId::new(SIDETONE_CONTROL, 0);
|
||||
let volume_elem = mixer
|
||||
.find_selem(&volume_id)
|
||||
.ok_or(crate::error::CorsairError::DeviceNotFound)?;
|
||||
.ok_or(crate::error::CorsairError::SidetoneNotFound)?;
|
||||
|
||||
volume_elem.set_playback_volume_all(level)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user