2026-04-12 19:18:18 -03:00
|
|
|
//! Integration coverage for the `lesavka-uvc` binary control-path contracts.
|
|
|
|
|
//!
|
|
|
|
|
//! Scope: include the production UVC binary source in the centralized testing
|
|
|
|
|
//! crate and exercise selector routing, payload shaping, env parsing, and I/O
|
|
|
|
|
//! helper behavior.
|
|
|
|
|
//! Targets: `server/src/bin/lesavka-uvc.rs`.
|
|
|
|
|
//! Why: this improves coverage for the operational UVC binary while keeping
|
|
|
|
|
//! source-file hygiene baselines stable.
|
|
|
|
|
|
|
|
|
|
mod uvc_binary {
|
|
|
|
|
#![allow(warnings)]
|
|
|
|
|
#![allow(clippy::all)]
|
|
|
|
|
#![allow(dead_code)]
|
|
|
|
|
#![allow(unused_imports)]
|
|
|
|
|
#![allow(unused_variables)]
|
|
|
|
|
|
|
|
|
|
include!(env!("LESAVKA_SERVER_UVC_BIN_SRC"));
|
|
|
|
|
|
|
|
|
|
use serial_test::serial;
|
|
|
|
|
use std::fs;
|
|
|
|
|
use temp_env::with_var;
|
|
|
|
|
use tempfile::NamedTempFile;
|
|
|
|
|
|
|
|
|
|
fn sample_cfg() -> UvcConfig {
|
|
|
|
|
UvcConfig {
|
|
|
|
|
width: 1280,
|
|
|
|
|
height: 720,
|
|
|
|
|
fps: 25,
|
|
|
|
|
interval: 400_000,
|
|
|
|
|
max_packet: 1024,
|
|
|
|
|
frame_size: 1_843_200,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn sample_interfaces() -> UvcInterfaces {
|
|
|
|
|
UvcInterfaces {
|
|
|
|
|
control: UVC_STRING_CONTROL_IDX,
|
|
|
|
|
streaming: UVC_STRING_STREAMING_IDX,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 02:52:32 -03:00
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn uvc_config_from_env_applies_payload_caps_and_interval_defaults() {
|
|
|
|
|
with_var("LESAVKA_UVC_WIDTH", Some("640"), || {
|
|
|
|
|
with_var("LESAVKA_UVC_HEIGHT", Some("480"), || {
|
|
|
|
|
with_var("LESAVKA_UVC_FPS", Some("30"), || {
|
|
|
|
|
with_var("LESAVKA_UVC_INTERVAL", Some("0"), || {
|
|
|
|
|
with_var("LESAVKA_UVC_MAXPAYLOAD_LIMIT", Some("300"), || {
|
|
|
|
|
with_var("LESAVKA_UVC_MAXPACKET", Some("4096"), || {
|
|
|
|
|
with_var("LESAVKA_UVC_BULK", Some("1"), || {
|
|
|
|
|
let cfg = UvcConfig::from_env();
|
|
|
|
|
assert_eq!(cfg.width, 640);
|
|
|
|
|
assert_eq!(cfg.height, 480);
|
|
|
|
|
assert_eq!(cfg.fps, 30);
|
|
|
|
|
assert_eq!(cfg.interval, 10_000_000 / 30);
|
|
|
|
|
assert!(cfg.max_packet <= 300);
|
|
|
|
|
assert!(cfg.max_packet <= 512);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn uvc_config_from_env_keeps_explicit_interval_and_non_bulk_cap() {
|
|
|
|
|
with_var("LESAVKA_UVC_INTERVAL", Some("200000"), || {
|
|
|
|
|
with_var("LESAVKA_UVC_MAXPAYLOAD_LIMIT", Some("1500"), || {
|
|
|
|
|
with_var("LESAVKA_UVC_MAXPACKET", Some("1200"), || {
|
|
|
|
|
with_var("LESAVKA_UVC_BULK", None::<&str>, || {
|
|
|
|
|
let cfg = UvcConfig::from_env();
|
|
|
|
|
assert_eq!(cfg.interval, 200_000);
|
|
|
|
|
assert_eq!(cfg.max_packet, 1024);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 19:18:18 -03:00
|
|
|
#[test]
|
|
|
|
|
fn build_streaming_control_populates_core_fields_for_11_and_15_byte_profiles() {
|
|
|
|
|
let cfg = sample_cfg();
|
|
|
|
|
let ctrl_11 = build_streaming_control(&cfg, STREAM_CTRL_SIZE_11);
|
|
|
|
|
assert_eq!(read_le32(&ctrl_11, 4), cfg.interval);
|
|
|
|
|
assert_eq!(read_le32(&ctrl_11, 18), cfg.frame_size);
|
|
|
|
|
assert_eq!(read_le32(&ctrl_11, 22), cfg.max_packet);
|
|
|
|
|
assert_eq!(ctrl_11[30], 0);
|
|
|
|
|
|
|
|
|
|
let ctrl_15 = build_streaming_control(&cfg, STREAM_CTRL_SIZE_15);
|
|
|
|
|
assert_eq!(read_le32(&ctrl_15, 4), cfg.interval);
|
|
|
|
|
assert_eq!(read_le32(&ctrl_15, 18), cfg.frame_size);
|
|
|
|
|
assert_eq!(read_le32(&ctrl_15, 22), cfg.max_packet);
|
|
|
|
|
assert_eq!(read_le32(&ctrl_15, 26), 48_000_000);
|
|
|
|
|
assert_eq!(ctrl_15[30], 0x03);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn stream_ctrl_len_accepts_only_supported_sizes() {
|
|
|
|
|
with_var("LESAVKA_UVC_CTRL_LEN", Some("26"), || {
|
|
|
|
|
assert_eq!(stream_ctrl_len(), STREAM_CTRL_SIZE_11);
|
|
|
|
|
});
|
|
|
|
|
with_var("LESAVKA_UVC_CTRL_LEN", Some("34"), || {
|
|
|
|
|
assert_eq!(stream_ctrl_len(), STREAM_CTRL_SIZE_15);
|
|
|
|
|
});
|
|
|
|
|
with_var("LESAVKA_UVC_CTRL_LEN", Some("99"), || {
|
|
|
|
|
assert_eq!(stream_ctrl_len(), STREAM_CTRL_SIZE_11);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn maybe_update_ctrl_len_rebuilds_state_profiles() {
|
|
|
|
|
let mut state = UvcState::new(sample_cfg());
|
|
|
|
|
assert_eq!(state.ctrl_len, STREAM_CTRL_SIZE_15);
|
|
|
|
|
maybe_update_ctrl_len(&mut state, STREAM_CTRL_SIZE_11 as u16, false);
|
|
|
|
|
assert_eq!(state.ctrl_len, STREAM_CTRL_SIZE_11);
|
|
|
|
|
assert_eq!(state.probe[2], 1);
|
|
|
|
|
assert_eq!(state.commit[3], 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn sanitize_streaming_control_applies_supported_fields() {
|
|
|
|
|
let state = UvcState::new(sample_cfg());
|
|
|
|
|
let mut data = [0u8; STREAM_CTRL_SIZE_MAX];
|
|
|
|
|
data[2] = 1;
|
|
|
|
|
data[3] = 1;
|
|
|
|
|
write_le32(&mut data[4..8], 333_333);
|
|
|
|
|
write_le32(&mut data[22..26], 4096);
|
|
|
|
|
let out = sanitize_streaming_control(&data, &state);
|
|
|
|
|
assert_eq!(out[2], 1);
|
|
|
|
|
assert_eq!(out[3], 1);
|
|
|
|
|
assert_eq!(read_le32(&out, 4), 333_333);
|
|
|
|
|
assert_eq!(read_le32(&out, 22), state.cfg.max_packet);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn handle_data_updates_probe_and_commit_requests() {
|
|
|
|
|
let interfaces = sample_interfaces();
|
|
|
|
|
let mut state = UvcState::new(sample_cfg());
|
|
|
|
|
let mut pending = Some(PendingRequest {
|
|
|
|
|
interface: interfaces.streaming,
|
|
|
|
|
selector: UVC_VS_PROBE_CONTROL,
|
|
|
|
|
expected_len: STREAM_CTRL_SIZE_11,
|
|
|
|
|
});
|
|
|
|
|
let mut payload = [0u8; UVC_DATA_SIZE];
|
|
|
|
|
payload[2] = 1;
|
|
|
|
|
payload[3] = 1;
|
|
|
|
|
write_le32(&mut payload[4..8], 250_000);
|
|
|
|
|
write_le32(&mut payload[22..26], 2048);
|
|
|
|
|
handle_data(
|
|
|
|
|
-1,
|
|
|
|
|
0,
|
|
|
|
|
&mut state,
|
|
|
|
|
&mut pending,
|
|
|
|
|
interfaces,
|
|
|
|
|
UvcRequestData {
|
|
|
|
|
length: STREAM_CTRL_SIZE_11 as i32,
|
|
|
|
|
data: payload,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
assert!(pending.is_none());
|
|
|
|
|
assert_eq!(read_le32(&state.probe, 4), 250_000);
|
|
|
|
|
assert_eq!(read_le32(&state.probe, 22), state.cfg.max_packet);
|
|
|
|
|
|
|
|
|
|
let mut pending = Some(PendingRequest {
|
|
|
|
|
interface: interfaces.streaming,
|
|
|
|
|
selector: UVC_VS_COMMIT_CONTROL,
|
|
|
|
|
expected_len: STREAM_CTRL_SIZE_11,
|
|
|
|
|
});
|
|
|
|
|
handle_data(
|
|
|
|
|
-1,
|
|
|
|
|
0,
|
|
|
|
|
&mut state,
|
|
|
|
|
&mut pending,
|
|
|
|
|
interfaces,
|
|
|
|
|
UvcRequestData {
|
|
|
|
|
length: STREAM_CTRL_SIZE_11 as i32,
|
|
|
|
|
data: payload,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(read_le32(&state.commit, 4), 250_000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn map_interface_prefers_selector_specific_routes() {
|
|
|
|
|
let interfaces = sample_interfaces();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
map_interface(interfaces.control, UVC_VS_PROBE_CONTROL, interfaces, false),
|
|
|
|
|
interfaces.streaming
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
map_interface(
|
|
|
|
|
interfaces.control,
|
|
|
|
|
UVC_VC_REQUEST_ERROR_CODE_CONTROL,
|
|
|
|
|
interfaces,
|
|
|
|
|
false
|
|
|
|
|
),
|
|
|
|
|
interfaces.control
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
map_interface(
|
|
|
|
|
interfaces.streaming,
|
|
|
|
|
UVC_VC_REQUEST_ERROR_CODE_CONTROL,
|
|
|
|
|
interfaces,
|
|
|
|
|
false
|
|
|
|
|
),
|
|
|
|
|
interfaces.streaming
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
map_interface(interfaces.streaming, 0xff, interfaces, false),
|
|
|
|
|
interfaces.streaming
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn build_in_response_and_streaming_response_return_expected_shapes() {
|
|
|
|
|
let state = UvcState::new(sample_cfg());
|
|
|
|
|
let interfaces = sample_interfaces();
|
|
|
|
|
let cur = build_streaming_response(&state, UVC_VS_PROBE_CONTROL, UVC_GET_CUR)
|
|
|
|
|
.expect("streaming GET_CUR");
|
|
|
|
|
assert_eq!(cur.len(), state.ctrl_len);
|
|
|
|
|
let info = build_streaming_response(&state, UVC_VS_COMMIT_CONTROL, UVC_GET_INFO)
|
|
|
|
|
.expect("streaming GET_INFO");
|
|
|
|
|
assert_eq!(info, vec![0x03]);
|
|
|
|
|
let none = build_streaming_response(&state, 0xff, UVC_GET_CUR);
|
|
|
|
|
assert!(none.is_none());
|
|
|
|
|
|
|
|
|
|
let built = build_in_response(
|
|
|
|
|
&state,
|
|
|
|
|
interfaces,
|
|
|
|
|
interfaces.streaming,
|
|
|
|
|
UVC_VS_PROBE_CONTROL,
|
|
|
|
|
UVC_GET_CUR,
|
|
|
|
|
8,
|
|
|
|
|
)
|
|
|
|
|
.expect("build in response");
|
|
|
|
|
assert_eq!(built.len(), 8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn build_control_response_covers_supported_and_unknown_selectors() {
|
|
|
|
|
let info = build_control_response(UVC_VC_REQUEST_ERROR_CODE_CONTROL, UVC_GET_INFO)
|
|
|
|
|
.expect("control info");
|
|
|
|
|
assert_eq!(info, vec![0x03]);
|
|
|
|
|
let cur = build_control_response(UVC_VC_REQUEST_ERROR_CODE_CONTROL, UVC_GET_CUR)
|
|
|
|
|
.expect("control cur");
|
|
|
|
|
assert_eq!(cur, vec![0x00]);
|
|
|
|
|
let unk = build_control_response(0xff, UVC_GET_CUR).expect("unknown selector placeholder");
|
|
|
|
|
assert_eq!(unk, vec![0x00]);
|
|
|
|
|
assert!(build_control_response(0xff, 0x55).is_none());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn adjust_length_truncates_and_pads_payloads() {
|
|
|
|
|
assert_eq!(adjust_length(vec![1, 2, 3, 4], 2), vec![1, 2]);
|
|
|
|
|
assert_eq!(adjust_length(vec![1, 2], 4), vec![1, 2, 0, 0]);
|
|
|
|
|
assert_eq!(adjust_length(vec![1], 0), Vec::<u8>::new());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_helpers_decode_usb_structures() {
|
|
|
|
|
let mut raw = [0u8; 64];
|
|
|
|
|
raw[0] = 0x81;
|
|
|
|
|
raw[1] = UVC_GET_CUR;
|
|
|
|
|
raw[2] = 0x34;
|
|
|
|
|
raw[3] = 0x12;
|
|
|
|
|
raw[4] = 0x78;
|
|
|
|
|
raw[5] = 0x56;
|
|
|
|
|
raw[6] = 0xBC;
|
|
|
|
|
raw[7] = 0x9A;
|
|
|
|
|
let req = parse_ctrl_request(raw);
|
|
|
|
|
assert_eq!(req.b_request_type, 0x81);
|
|
|
|
|
assert_eq!(req.b_request, UVC_GET_CUR);
|
|
|
|
|
assert_eq!(req.w_value, 0x1234);
|
|
|
|
|
assert_eq!(req.w_index, 0x5678);
|
|
|
|
|
assert_eq!(req.w_length, 0x9ABC);
|
|
|
|
|
|
|
|
|
|
let mut raw = [0u8; 64];
|
|
|
|
|
raw[0..4].copy_from_slice(&(26_i32.to_le_bytes()));
|
|
|
|
|
raw[4] = 0xAA;
|
|
|
|
|
raw[63] = 0xBB;
|
|
|
|
|
let data = parse_request_data(raw);
|
|
|
|
|
assert_eq!(data.length, 26);
|
|
|
|
|
assert_eq!(data.data[0], 0xAA);
|
|
|
|
|
assert_eq!(data.data[59], 0xBB);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn env_helpers_and_payload_cap_env_override_behave() {
|
|
|
|
|
with_var("LESAVKA_UVC_MAXPAYLOAD_LIMIT", Some("777"), || {
|
|
|
|
|
let cap = compute_payload_cap(false).expect("payload cap from env");
|
|
|
|
|
assert_eq!(cap.limit, 777);
|
|
|
|
|
assert_eq!(cap.source, "env");
|
|
|
|
|
assert_eq!(cap.pct, 100);
|
|
|
|
|
});
|
|
|
|
|
with_var("LESAVKA_TEST_U32", Some("42"), || {
|
|
|
|
|
assert_eq!(env_u32("LESAVKA_TEST_U32", 1), 42);
|
|
|
|
|
});
|
|
|
|
|
with_var("LESAVKA_TEST_U8", Some("9"), || {
|
|
|
|
|
assert_eq!(env_u8("LESAVKA_TEST_U8"), Some(9));
|
|
|
|
|
});
|
|
|
|
|
with_var("LESAVKA_TEST_U32_OPT", Some("77"), || {
|
|
|
|
|
assert_eq!(env_u32_opt("LESAVKA_TEST_U32_OPT"), Some(77));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 21:00:56 -03:00
|
|
|
#[test]
|
|
|
|
|
#[serial]
|
|
|
|
|
fn uvc_control_open_mode_defaults_read_only_with_escape_hatch() {
|
|
|
|
|
with_var("LESAVKA_UVC_CONTROL_READ_ONLY", None::<&str>, || {
|
|
|
|
|
assert!(uvc_control_read_only());
|
|
|
|
|
});
|
|
|
|
|
for disabled in ["0", "false", "no", "off"] {
|
|
|
|
|
with_var("LESAVKA_UVC_CONTROL_READ_ONLY", Some(disabled), || {
|
|
|
|
|
assert!(!uvc_control_read_only());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
with_var("LESAVKA_UVC_CONTROL_READ_ONLY", Some("1"), || {
|
|
|
|
|
assert!(uvc_control_read_only());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 02:52:32 -03:00
|
|
|
#[test]
|
|
|
|
|
fn interface_helpers_and_configfs_snapshot_are_stable_without_sysfs() {
|
|
|
|
|
let tmp = NamedTempFile::new().expect("tmp");
|
|
|
|
|
fs::write(tmp.path(), "7\n").expect("write");
|
|
|
|
|
assert_eq!(read_interface(tmp.path().to_str().expect("path")), Some(7));
|
|
|
|
|
fs::write(tmp.path(), "bad\n").expect("write bad");
|
|
|
|
|
assert_eq!(read_interface(tmp.path().to_str().expect("path")), None);
|
|
|
|
|
|
|
|
|
|
let interfaces = load_interfaces();
|
|
|
|
|
assert_eq!(interfaces.control, UVC_STRING_CONTROL_IDX);
|
|
|
|
|
assert_eq!(interfaces.streaming, UVC_STRING_STREAMING_IDX);
|
|
|
|
|
|
|
|
|
|
assert!(read_configfs_snapshot().is_none());
|
|
|
|
|
|
|
|
|
|
let mut state = UvcState::new(sample_cfg());
|
|
|
|
|
state.cfg_snapshot = Some(ConfigfsSnapshot {
|
|
|
|
|
width: 640,
|
|
|
|
|
height: 480,
|
|
|
|
|
default_interval: 333_333,
|
|
|
|
|
frame_interval: 333_333,
|
|
|
|
|
maxpacket: 1024,
|
|
|
|
|
maxburst: 0,
|
|
|
|
|
});
|
|
|
|
|
log_configfs_snapshot(&mut state, "contract");
|
|
|
|
|
assert!(state.cfg_snapshot.is_some());
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 19:18:18 -03:00
|
|
|
#[test]
|
|
|
|
|
fn io_helpers_read_values_and_fifo_minimums() {
|
|
|
|
|
let tmp = NamedTempFile::new().expect("tmp");
|
|
|
|
|
fs::write(tmp.path(), "123\n").expect("write");
|
|
|
|
|
assert_eq!(read_u32_file(tmp.path().to_str().unwrap()), Some(123));
|
|
|
|
|
|
|
|
|
|
let tmp = NamedTempFile::new().expect("tmp");
|
|
|
|
|
fs::write(tmp.path(), "400000 500000 600000\n").expect("write");
|
|
|
|
|
assert_eq!(read_u32_first(tmp.path().to_str().unwrap()), Some(400000));
|
|
|
|
|
|
|
|
|
|
let tmp = NamedTempFile::new().expect("tmp");
|
|
|
|
|
fs::write(tmp.path(), "512, 256 1024\n").expect("write");
|
|
|
|
|
assert_eq!(read_fifo_min(tmp.path().to_str().unwrap()), Some(256));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn ioctl_helpers_produce_non_zero_numbers() {
|
|
|
|
|
let r = ioctl_read::<V4l2Event>(b'V', 89);
|
|
|
|
|
let w = ioctl_write::<UvcRequestData>(b'U', 1);
|
|
|
|
|
assert_ne!(r, 0);
|
|
|
|
|
assert_ne!(w, 0);
|
|
|
|
|
assert_ne!(r, w);
|
|
|
|
|
assert_ne!(ioc(IOC_READ, b'V', 1, 64), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn open_with_retry_works_for_existing_temp_file() {
|
|
|
|
|
let tmp = NamedTempFile::new().expect("tmp");
|
|
|
|
|
let file = open_with_retry(tmp.path().to_str().unwrap()).expect("open with retry");
|
|
|
|
|
assert!(file.metadata().is_ok());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn send_response_and_stall_fail_with_invalid_fd() {
|
|
|
|
|
assert!(send_response(-1, 0, &[1, 2, 3]).is_err());
|
|
|
|
|
assert!(send_stall(-1, 0).is_err());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn handle_setup_tracks_streaming_set_cur_pending_request() {
|
|
|
|
|
let interfaces = sample_interfaces();
|
|
|
|
|
let mut state = UvcState::new(sample_cfg());
|
|
|
|
|
let mut pending = None;
|
|
|
|
|
|
|
|
|
|
let req = UsbCtrlRequest {
|
|
|
|
|
b_request_type: 0x00,
|
|
|
|
|
b_request: UVC_SET_CUR,
|
|
|
|
|
w_value: (UVC_VS_PROBE_CONTROL as u16) << 8,
|
|
|
|
|
w_index: interfaces.streaming as u16,
|
|
|
|
|
w_length: STREAM_CTRL_SIZE_11 as u16,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
handle_setup(-1, 0, &mut state, &mut pending, interfaces, req, false);
|
|
|
|
|
let pending = pending.expect("expected pending request");
|
|
|
|
|
assert_eq!(pending.interface, interfaces.streaming);
|
|
|
|
|
assert_eq!(pending.selector, UVC_VS_PROBE_CONTROL);
|
|
|
|
|
assert_eq!(pending.expected_len, STREAM_CTRL_SIZE_11);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn handle_setup_accepts_control_set_cur_without_pending() {
|
|
|
|
|
let interfaces = sample_interfaces();
|
|
|
|
|
let mut state = UvcState::new(sample_cfg());
|
|
|
|
|
let mut pending = None;
|
|
|
|
|
let req = UsbCtrlRequest {
|
|
|
|
|
b_request_type: 0x00,
|
|
|
|
|
b_request: UVC_SET_CUR,
|
|
|
|
|
w_value: (UVC_VC_REQUEST_ERROR_CODE_CONTROL as u16) << 8,
|
|
|
|
|
w_index: interfaces.control as u16,
|
|
|
|
|
w_length: 1,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
handle_setup(-1, 0, &mut state, &mut pending, interfaces, req, false);
|
|
|
|
|
assert!(pending.is_none());
|
|
|
|
|
}
|
|
|
|
|
}
|