Skip to main content

android_usb_serial/
probe.rs

1//! Driver probing (ported from ProbeTable.java / UsbId.java).
2//!
3//! [`ProbeTable::default_table`] lists known VID/PID pairs. Unknown CDC composites fall
4//! back to [`DriverType::CdcAcm`] when communication/data interfaces are present.
5
6use crate::transport::InterfaceInfo;
7
8/// Vendor driver family selected for a USB product.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum DriverType {
11    CdcAcm,
12    Cp21xx,
13    Ftdi,
14    Prolific,
15    Ch34x,
16    GsmModem,
17    ChromeCcd,
18}
19
20/// One VID/PID → driver binding.
21#[derive(Debug, Clone)]
22pub struct ProbeEntry {
23    pub vendor_id: u16,
24    pub product_id: u16,
25    pub driver: DriverType,
26}
27
28/// Lookup table used by [`crate::describe_device`] / [`crate::open_port`].
29#[derive(Debug, Clone, Default)]
30pub struct ProbeTable {
31    entries: Vec<ProbeEntry>,
32}
33
34impl ProbeTable {
35    /// Built-in VID/PID map aligned with usb-serial-for-android defaults.
36    pub fn default_table() -> Self {
37        let mut table = Self::default();
38        // Order matches Java getDefaultProbeTable driver registration for probe-fn fallback.
39        table.add_product(0x0403, 0x6001, DriverType::Ftdi);
40        table.add_product(0x0403, 0x6010, DriverType::Ftdi);
41        table.add_product(0x0403, 0x6011, DriverType::Ftdi);
42        table.add_product(0x0403, 0x6014, DriverType::Ftdi);
43        table.add_product(0x0403, 0x6015, DriverType::Ftdi);
44        table.add_product(0x10C4, 0xEA60, DriverType::Cp21xx);
45        table.add_product(0x10C4, 0xEA70, DriverType::Cp21xx);
46        table.add_product(0x10C4, 0xEA71, DriverType::Cp21xx);
47        table.add_product(0x067B, 0x2303, DriverType::Prolific);
48        table.add_product(0x067B, 0x23A3, DriverType::Prolific);
49        table.add_product(0x067B, 0x23B3, DriverType::Prolific);
50        table.add_product(0x067B, 0x23C3, DriverType::Prolific);
51        table.add_product(0x067B, 0x23D3, DriverType::Prolific);
52        table.add_product(0x067B, 0x23E3, DriverType::Prolific);
53        table.add_product(0x067B, 0x23F3, DriverType::Prolific);
54        table.add_product(0x1A86, 0x7523, DriverType::Ch34x);
55        table.add_product(0x1A86, 0x5523, DriverType::Ch34x);
56        table.add_product(0x18D1, 0x5014, DriverType::ChromeCcd);
57        table.add_product(0x1782, 0x4D10, DriverType::GsmModem);
58        table.add_product(0x1782, 0x4D12, DriverType::GsmModem);
59        table
60    }
61
62    /// Register a product binding (last write wins on duplicates).
63    pub fn add_product(&mut self, vendor_id: u16, product_id: u16, driver: DriverType) {
64        self.entries.push(ProbeEntry {
65            vendor_id,
66            product_id,
67            driver,
68        });
69    }
70
71    /// Resolve driver for `(vid, pid)`; falls back to CDC-ACM when interfaces look CDC.
72    pub fn find(
73        &self,
74        vendor_id: u16,
75        product_id: u16,
76        interfaces: &[InterfaceInfo],
77    ) -> DriverType {
78        if let Some(entry) = self
79            .entries
80            .iter()
81            .find(|e| e.vendor_id == vendor_id && e.product_id == product_id)
82        {
83            return entry.driver;
84        }
85        if cdc_acm_port_count(interfaces) > 0 {
86            return DriverType::CdcAcm;
87        }
88        DriverType::CdcAcm // unreachable for unknown; caller checks port_count
89    }
90
91    /// How many serial ports this driver + interface layout exposes.
92    pub fn port_count(&self, driver: DriverType, interfaces: &[InterfaceInfo]) -> usize {
93        match driver {
94            DriverType::CdcAcm => cdc_acm_port_count(interfaces),
95            DriverType::Ftdi | DriverType::Cp21xx => interfaces.len().max(1),
96            DriverType::ChromeCcd => 3,
97            DriverType::Ch34x | DriverType::Prolific | DriverType::GsmModem => 1,
98        }
99    }
100
101    /// Port count when interface list is not yet known (enumeration / probe_table fixtures).
102    pub fn port_count_product(
103        &self,
104        vendor_id: u16,
105        product_id: u16,
106        driver: DriverType,
107        interfaces: &[InterfaceInfo],
108    ) -> usize {
109        if !interfaces.is_empty() {
110            return self.port_count(driver, interfaces);
111        }
112        match (vendor_id, product_id) {
113            (0x0403, 0x6010 | 0x6011 | 0x6014 | 0x6015) => 2,
114            (0x10C4, 0xEA70 | 0xEA71) => 2,
115            (0x18D1, 0x5014) => 3,
116            _ => self.port_count(driver, interfaces),
117        }
118    }
119
120    /// Immutable view of registered bindings.
121    pub fn entries(&self) -> &[ProbeEntry] {
122        &self.entries
123    }
124}
125
126/// Count CDC ACM data/comm interface pairs.
127pub fn cdc_acm_port_count(interfaces: &[InterfaceInfo]) -> usize {
128    let comm = interfaces
129        .iter()
130        .filter(|i| i.class == 2 && i.subclass == 2)
131        .count();
132    let data = interfaces.iter().filter(|i| i.class == 10).count();
133    comm.min(data)
134        .max(if comm == 0 && data == 0 { 0 } else { 1 })
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn default_table_finds_ftdi() {
143        let table = ProbeTable::default_table();
144        let driver = table.find(0x0403, 0x6001, &[]);
145        assert_eq!(driver, DriverType::Ftdi);
146    }
147
148    #[test]
149    fn cdc_probe_fn_fallback() {
150        let table = ProbeTable::default_table();
151        let ifaces = vec![
152            InterfaceInfo {
153                id: 0,
154                class: 2,
155                subclass: 2,
156                protocol: 0,
157            },
158            InterfaceInfo {
159                id: 1,
160                class: 10,
161                subclass: 0,
162                protocol: 0,
163            },
164        ];
165        let driver = table.find(0x9999, 0x0001, &ifaces);
166        assert_eq!(driver, DriverType::CdcAcm);
167        assert_eq!(table.port_count(driver, &ifaces), 1);
168    }
169}