blob: 5720546d4616760d5bb84e7c139f868d9127ef78 [file] [log] [blame]
// Copyright 2025 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use log::info;
use rusb::{Direction, TransferType, UsbContext};
use crate::error::Error;
use crate::error::Result;
pub(crate) fn is_ippusb_interface(descriptor: &rusb::InterfaceDescriptor) -> bool {
descriptor.class_code() == 0x07
&& descriptor.sub_class_code() == 0x01
&& descriptor.protocol_code() == 0x04
}
/// Check if the given device supports IPP-USB.
///
/// Given a rusb Device, search through the device's configurations to see if there is a
/// particular configuration that supports IPP-over-USB (aka IPP-USB). If such a configuration
/// is found, return `Ok(true)`. If there were no errors, but the device does not have such a
/// configuration, return `Ok(false)`. If there was an error reading descriptors, return `Err`.
///
/// A device is considered to support IPP-USB if it has a configuration with at least two
/// IPP-USB interfaces.
///
/// An interface is considered an IPP-USB interface if all of the following are true:
///
/// * The USB class is Printer (7).
/// * The USB subclass is Printer (1).
/// * The USB protocol is IPP-USB (4).
/// * The interface contains a bulk-in and a bulk-out endpoint.
///
/// The device's configuration is not changed by this function.
pub fn device_supports_ippusb<T: UsbContext>(device: &rusb::Device<T>) -> Result<bool> {
match IppusbDeviceInfo::new(device) {
Ok(_) => Ok(true),
Err(Error::NotIppUsb) => Ok(false),
Err(e) => Err(e),
}
}
/// The information for an interface descriptor that supports IPP-USB.
///
/// Bulk transfers can be read/written to the in/out endpoints, respectively.
#[derive(Copy, Clone)]
pub(crate) struct IppusbDescriptor {
pub interface_number: u8,
pub alternate_setting: u8,
pub in_endpoint: u8,
pub out_endpoint: u8,
}
/// The configuration and interfaces that support IPP-USB for a USB device.
///
/// A valid IPP-USB device will have at least two interfaces.
pub(crate) struct IppusbDeviceInfo {
pub config: u8,
pub interfaces: Vec<IppusbDescriptor>,
}
impl IppusbDeviceInfo {
/// Given a rusb Device, search through the device's configurations to see if there is a
/// particular configuration that supports IPP-over-USB (aka IPP-USB). If such a configuration
/// is found, return an `IppusbDeviceInfo`, which specifies the configuration as well as the
/// IPP-USB interfaces within that configuration.
///
/// See the documentation for `device_supports_ippusb()` for the details of what IPP-USB
/// support requires.
///
/// If the given device does not support IPP-USB or the descriptors cannot be read, return
/// `Err`.
pub(crate) fn new<T: UsbContext>(device: &rusb::Device<T>) -> Result<Self> {
let desc = device
.device_descriptor()
.map_err(Error::ReadDeviceDescriptor)?;
for i in 0..desc.num_configurations() {
let config = device
.config_descriptor(i)
.map_err(Error::ReadConfigDescriptor)?;
let mut interfaces = Vec::new();
for interface in config.interfaces() {
'alternates: for alternate in interface.descriptors() {
if !is_ippusb_interface(&alternate) {
continue;
}
info!(
concat!(
"Device {}:{} - Found IPP-USB interface. ",
"config {}, interface {}, alternate {}"
),
device.bus_number(),
device.address(),
config.number(),
interface.number(),
alternate.setting_number()
);
// Find the bulk in and out endpoints for this interface.
let mut in_endpoint: Option<u8> = None;
let mut out_endpoint: Option<u8> = None;
for endpoint in alternate.endpoint_descriptors() {
match (endpoint.direction(), endpoint.transfer_type()) {
(Direction::In, TransferType::Bulk) => {
in_endpoint.get_or_insert(endpoint.address());
}
(Direction::Out, TransferType::Bulk) => {
out_endpoint.get_or_insert(endpoint.address());
}
_ => {}
};
if in_endpoint.is_some() && out_endpoint.is_some() {
break;
}
}
if let (Some(in_endpoint), Some(out_endpoint)) = (in_endpoint, out_endpoint) {
interfaces.push(IppusbDescriptor {
interface_number: interface.number(),
alternate_setting: alternate.setting_number(),
in_endpoint,
out_endpoint,
});
// We must consider at most one alternate setting when detecting IPP-USB
// interfaces.
break 'alternates;
}
}
}
// A device must have at least two IPP-USB interfaces in order to be considered an
// IPP-USB device.
if interfaces.len() >= 2 {
return Ok(Self {
config: config.number(),
interfaces,
});
}
}
Err(Error::NotIppUsb)
}
}