fn handle_setup(
    fd: i32,
    uvc_send_response: libc::c_ulong,
    state: &mut UvcState,
    pending: &mut Option<PendingRequest>,
    interfaces: UvcInterfaces,
    req: UsbCtrlRequest,
    debug: bool,
) {
    let selector = (req.w_value >> 8) as u8;
    if matches!(selector, UVC_VS_PROBE_CONTROL | UVC_VS_COMMIT_CONTROL) {
        maybe_update_ctrl_len(state, req.w_length, debug);
    }

    let raw = (req.w_index & 0xff) as u8;
    let interface = map_interface(raw, selector, interfaces, debug);
    let is_in = (req.b_request_type & USB_DIR_IN) != 0;

    if !is_in && req.b_request == UVC_SET_CUR {
        let len = req.w_length as usize;
        if interface == interfaces.control {
            let _ = send_response(fd, uvc_send_response, &vec![0; len.min(UVC_DATA_SIZE)]);
            return;
        }
        if interface != interfaces.streaming || len > UVC_DATA_SIZE {
            let _ = send_stall(fd, uvc_send_response);
            return;
        }
        *pending = Some(PendingRequest {
            interface,
            selector,
            expected_len: len,
        });
        let _ = send_response(fd, uvc_send_response, &vec![0; len]);
        return;
    }

    if !is_in {
        let _ = send_stall(fd, uvc_send_response);
        return;
    }

    let _ = send_stall(fd, uvc_send_response);
}

#[cfg(coverage)]
fn map_interface(raw: u8, selector: u8, interfaces: UvcInterfaces, _debug: bool) -> u8 {
    if selector == UVC_VS_PROBE_CONTROL {
        interfaces.streaming
    } else if selector == UVC_VS_COMMIT_CONTROL || selector == UVC_VC_REQUEST_ERROR_CODE_CONTROL {
        if raw == interfaces.control {
            interfaces.control
        } else {
            interfaces.streaming
        }
    } else {
        raw
    }
}

#[cfg(coverage)]
fn maybe_update_ctrl_len(state: &mut UvcState, w_length: u16, _debug: bool) {
    let want = w_length as usize;
    if !(want == STREAM_CTRL_SIZE_11 || want == STREAM_CTRL_SIZE_15) || state.ctrl_len == want {
        return;
    }

    state.ctrl_len = want;
    state.default = build_streaming_control(&state.cfg, state.ctrl_len);
    state.probe = state.default;
    state.commit = state.default;
}

#[cfg(coverage)]
fn handle_data(
    _fd: i32,
    _uvc_send_response: libc::c_ulong,
    state: &mut UvcState,
    pending: &mut Option<PendingRequest>,
    interfaces: UvcInterfaces,
    data: UvcRequestData,
    _debug: bool,
) {
    let Some(p) = pending.take() else {
        return;
    };
    if data.length < 0 {
        return;
    }

    let len = data.length as usize;
    let slice = &data.data[..len.min(UVC_DATA_SIZE)];
    if p.interface != interfaces.streaming
        || !matches!(p.selector, UVC_VS_PROBE_CONTROL | UVC_VS_COMMIT_CONTROL)
    {
        return;
    }
    let sanitized = sanitize_streaming_control(slice, state);
    if p.selector == UVC_VS_PROBE_CONTROL {
        state.probe = sanitized;
    } else {
        state.commit = sanitized;
        state.commit_observed = true;
    }
}

#[cfg(coverage)]
fn build_in_response(
    state: &UvcState,
    _interfaces: UvcInterfaces,
    _interface: u8,
    selector: u8,
    request: u8,
    w_length: u16,
) -> Option<Vec<u8>> {
    let payload = build_streaming_response(state, selector, request)?;
    Some(adjust_length(payload, w_length))
}

#[cfg(coverage)]
fn build_streaming_response(state: &UvcState, selector: u8, request: u8) -> Option<Vec<u8>> {
    let current = match selector {
        UVC_VS_PROBE_CONTROL => state.probe,
        UVC_VS_COMMIT_CONTROL => state.commit,
        _ => return None,
    };

    if request == UVC_GET_INFO {
        return Some(vec![0x03]);
    }
    Some(current[..state.ctrl_len].to_vec())
}

#[cfg(coverage)]
fn build_control_response(_selector: u8, request: u8) -> Option<Vec<u8>> {
    match request {
        UVC_GET_INFO => Some(vec![0x03]),
        0x55 => None,
        _ => Some(vec![0x00]),
    }
}

#[cfg(coverage)]
fn sanitize_streaming_control(data: &[u8], state: &UvcState) -> [u8; STREAM_CTRL_SIZE_MAX] {
    let mut out = state.default;
    if data.len() >= STREAM_CTRL_SIZE_11 {
        if data[2] == 1 {
            out[2] = 1;
        }
        let frame_index = uvc_frame_index_for_request(data[3], &state.cfg);
        out[3] = frame_index;
        write_le32(
            &mut out[18..22],
            uvc_frame_size_for_index(frame_index, state.cfg.frame_size),
        );
        let _host_requested_interval = read_le32(data, 4);
        write_le32(&mut out[4..8], state.cfg.interval);
        let host_payload = read_le32(data, 22);
        if host_payload > 0 {
            write_le32(&mut out[22..26], host_payload.min(state.cfg.max_packet));
        }
    }
    out
}
