From f043634ce21738244380d34e077de1c03ac987b7 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 6 Jan 2026 14:28:00 -0300 Subject: [PATCH] server: fix UVC control length --- server/src/bin/lesavka-uvc.rs | 86 ++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/server/src/bin/lesavka-uvc.rs b/server/src/bin/lesavka-uvc.rs index 9da8326..0121dcd 100644 --- a/server/src/bin/lesavka-uvc.rs +++ b/server/src/bin/lesavka-uvc.rs @@ -8,7 +8,9 @@ use std::os::unix::io::AsRawFd; use std::thread; use std::time::Duration; -const STREAM_CTRL_SIZE: usize = 34; +const STREAM_CTRL_SIZE_11: usize = 26; +const STREAM_CTRL_SIZE_15: usize = 34; +const STREAM_CTRL_SIZE_MAX: usize = STREAM_CTRL_SIZE_15; const UVC_DATA_SIZE: usize = 60; const V4L2_EVENT_PRIVATE_START: u32 = 0x0800_0000; @@ -90,9 +92,10 @@ struct UvcConfig { struct UvcState { cfg: UvcConfig, - default: [u8; STREAM_CTRL_SIZE], - probe: [u8; STREAM_CTRL_SIZE], - commit: [u8; STREAM_CTRL_SIZE], + ctrl_len: usize, + default: [u8; STREAM_CTRL_SIZE_MAX], + probe: [u8; STREAM_CTRL_SIZE_MAX], + commit: [u8; STREAM_CTRL_SIZE_MAX], } #[derive(Clone, Copy)] @@ -270,9 +273,11 @@ impl UvcConfig { impl UvcState { fn new(cfg: UvcConfig) -> Self { - let default = build_streaming_control(&cfg); + let ctrl_len = stream_ctrl_len(); + let default = build_streaming_control(&cfg, ctrl_len); Self { cfg, + ctrl_len, default, probe: default, commit: default, @@ -342,7 +347,15 @@ fn handle_setup( req: UsbCtrlRequest, debug: bool, ) { - let interface = (req.w_index & 0xff) as u8; + let interface_lo = (req.w_index & 0xff) as u8; + let interface_hi = (req.w_index >> 8) as u8; + let interface = if interface_lo == interfaces.streaming || interface_lo == interfaces.control { + interface_lo + } else if interface_hi == interfaces.streaming || interface_hi == interfaces.control { + interface_hi + } else { + interface_lo + }; let selector = (req.w_value >> 8) as u8; let is_in = (req.b_request_type & USB_DIR_IN) != 0; @@ -369,7 +382,8 @@ fn handle_setup( return; } - let payload = build_in_response(state, interfaces, interface, selector, req.b_request); + let payload = + build_in_response(state, interfaces, interface, selector, req.b_request, req.w_length); match payload { Some(bytes) => { let _ = send_response(fd, uvc_send_response, &bytes); @@ -397,7 +411,7 @@ fn handle_data( let len = data.length as usize; let slice = &data.data[..len.min(data.data.len())]; - if debug && slice.len() >= STREAM_CTRL_SIZE { + if debug && slice.len() >= STREAM_CTRL_SIZE_11 { let interval = read_le32(slice, 4); let payload = read_le32(slice, 22); eprintln!( @@ -445,14 +459,17 @@ fn build_in_response( interface: u8, selector: u8, request: u8, + w_length: u16, ) -> Option> { - match interface { + let payload = match interface { _ if interface == interfaces.streaming => { build_streaming_response(state, selector, request) } _ if interface == interfaces.control => build_control_response(selector, request), _ => None, - } + }?; + + Some(adjust_length(payload, w_length)) } fn build_streaming_response( @@ -468,9 +485,11 @@ fn build_streaming_response( match request { UVC_GET_INFO => Some(vec![0x03]), - UVC_GET_LEN => Some((STREAM_CTRL_SIZE as u16).to_le_bytes().to_vec()), - UVC_GET_CUR => Some(current.to_vec()), - UVC_GET_MIN | UVC_GET_MAX | UVC_GET_DEF | UVC_GET_RES => Some(state.default.to_vec()), + UVC_GET_LEN => Some((state.ctrl_len as u16).to_le_bytes().to_vec()), + UVC_GET_CUR => Some(current[..state.ctrl_len].to_vec()), + UVC_GET_MIN | UVC_GET_MAX | UVC_GET_DEF | UVC_GET_RES => { + Some(state.default[..state.ctrl_len].to_vec()) + } _ => None, } } @@ -490,9 +509,12 @@ fn build_control_response(selector: u8, request: u8) -> Option> { } } -fn sanitize_streaming_control(data: &[u8], state: &UvcState) -> [u8; STREAM_CTRL_SIZE] { +fn sanitize_streaming_control( + data: &[u8], + state: &UvcState, +) -> [u8; STREAM_CTRL_SIZE_MAX] { let mut out = state.default; - if data.len() >= STREAM_CTRL_SIZE { + if data.len() >= STREAM_CTRL_SIZE_11 { let format_index = data[2]; let frame_index = data[3]; let interval = read_le32(data, 4); @@ -546,8 +568,8 @@ fn send_stall(fd: i32, req: libc::c_ulong) -> Result<()> { Ok(()) } -fn build_streaming_control(cfg: &UvcConfig) -> [u8; STREAM_CTRL_SIZE] { - let mut buf = [0u8; STREAM_CTRL_SIZE]; +fn build_streaming_control(cfg: &UvcConfig, ctrl_len: usize) -> [u8; STREAM_CTRL_SIZE_MAX] { + let mut buf = [0u8; STREAM_CTRL_SIZE_MAX]; let frame_size = cfg.width * cfg.height * 2; write_le16(&mut buf[0..2], 1); // bmHint: dwFrameInterval @@ -561,11 +583,13 @@ fn build_streaming_control(cfg: &UvcConfig) -> [u8; STREAM_CTRL_SIZE] { write_le16(&mut buf[16..18], 0); write_le32(&mut buf[18..22], frame_size); write_le32(&mut buf[22..26], cfg.max_packet); - write_le32(&mut buf[26..30], 48_000_000); - buf[30] = 0; - buf[31] = 0; - buf[32] = 0; - buf[33] = 0; + if ctrl_len >= STREAM_CTRL_SIZE_15 { + write_le32(&mut buf[26..30], 48_000_000); + buf[30] = 0; + buf[31] = 0; + buf[32] = 0; + buf[33] = 0; + } buf } @@ -591,6 +615,14 @@ fn parse_request_data(data: [u8; 64]) -> UvcRequestData { UvcRequestData { length, data: out } } +fn stream_ctrl_len() -> usize { + let value = env_u32("LESAVKA_UVC_CTRL_LEN", STREAM_CTRL_SIZE_11 as u32) as usize; + match value { + STREAM_CTRL_SIZE_11 | STREAM_CTRL_SIZE_15 => value, + _ => STREAM_CTRL_SIZE_11, + } +} + fn env_u32(name: &str, default: u32) -> u32 { env::var(name) .ok() @@ -602,6 +634,16 @@ fn env_u8(name: &str) -> Option { env::var(name).ok().and_then(|v| v.parse::().ok()) } +fn adjust_length(mut bytes: Vec, w_length: u16) -> Vec { + let want = (w_length as usize).min(UVC_DATA_SIZE); + if bytes.len() > want { + bytes.truncate(want); + } else if bytes.len() < want { + bytes.resize(want, 0); + } + bytes +} + fn write_le16(dst: &mut [u8], val: u16) { let bytes = val.to_le_bytes(); dst[0] = bytes[0];