Skip to main content

dfu_libusb/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::cell::RefCell;
4use std::marker;
5use thiserror::Error;
6
7pub type Dfu<C> = dfu_core::synchronous::DfuSync<DfuLibusb<C>, Error>;
8
9#[derive(Debug, Error)]
10pub enum Error {
11    #[error("Could not find device or an error occurred.")]
12    CouldNotOpenDevice,
13    #[error(transparent)]
14    Dfu(#[from] dfu_core::Error),
15    #[error(transparent)]
16    Io(#[from] std::io::Error),
17    #[error("rusb: {0}")]
18    LibUsb(#[from] rusb::Error),
19    #[error("The device has no languages.")]
20    MissingLanguage,
21    #[error("Could not find interface.")]
22    InvalidInterface,
23    #[error("Could not find alt interface.")]
24    InvalidAlt,
25    #[error("Could not parse functional descriptor: {0}")]
26    FunctionalDescriptor(#[from] dfu_core::functional_descriptor::Error),
27    #[error("No DFU capable device found.")]
28    NoDfuCapableDeviceFound,
29}
30
31pub struct DfuLibusb<C: rusb::UsbContext> {
32    usb: RefCell<rusb::DeviceHandle<C>>,
33    protocol: dfu_core::DfuProtocol<dfu_core::memory_layout::MemoryLayout>,
34    timeout: std::time::Duration,
35    iface: u16,
36    functional_descriptor: dfu_core::functional_descriptor::FunctionalDescriptor,
37    marker: marker::PhantomData<C>,
38}
39
40impl<C: rusb::UsbContext> dfu_core::DfuIo for DfuLibusb<C> {
41    type Read = usize;
42    type Write = usize;
43    type Reset = ();
44    type Error = Error;
45    type MemoryLayout = dfu_core::memory_layout::MemoryLayout;
46
47    #[allow(unused_variables)]
48    fn read_control(
49        &self,
50        request_type: u8,
51        request: u8,
52        value: u16,
53        buffer: &mut [u8],
54    ) -> Result<Self::Read, Self::Error> {
55        // TODO: do or do not? there is no try
56        let request_type = request_type | rusb::constants::LIBUSB_ENDPOINT_IN;
57        let res = self.usb.borrow().read_control(
58            request_type,
59            request,
60            value,
61            self.iface,
62            buffer,
63            self.timeout,
64        );
65        assert!(
66            !matches!(res, Err(rusb::Error::InvalidParam)),
67            "invalid param: {:08b} {:?}",
68            request_type,
69            res,
70        );
71        Ok(res?)
72    }
73
74    #[allow(unused_variables)]
75    fn write_control(
76        &self,
77        request_type: u8,
78        request: u8,
79        value: u16,
80        buffer: &[u8],
81    ) -> Result<Self::Write, Self::Error> {
82        let res = self.usb.borrow().write_control(
83            request_type,
84            request,
85            value,
86            self.iface,
87            buffer,
88            self.timeout,
89        );
90        assert!(
91            !matches!(res, Err(rusb::Error::InvalidParam)),
92            "invalid param: {:08b}",
93            request_type,
94        );
95        Ok(res?)
96    }
97
98    fn usb_reset(self) -> Result<Self::Reset, Self::Error> {
99        // Release the interface before resetting the device. On macOS,
100        // libusb's reset fails if an interface is still claimed. Explicitly
101        // releasing here (rather than relying on DeviceHandle's Drop) makes
102        // the intent clear and ensures the interface is released before the
103        // reset call.
104        let handle = self.usb.into_inner();
105        handle.release_interface(self.iface as u8)?;
106        Ok(handle.reset()?)
107    }
108
109    fn protocol(&self) -> &dfu_core::DfuProtocol<Self::MemoryLayout> {
110        &self.protocol
111    }
112
113    fn functional_descriptor(&self) -> &dfu_core::functional_descriptor::FunctionalDescriptor {
114        &self.functional_descriptor
115    }
116}
117
118impl<C: rusb::UsbContext> DfuLibusb<C> {
119    pub fn open(context: &C, vid: u16, pid: u16, iface: u8, alt: u8) -> Result<Dfu<C>, Error> {
120        let (device, handle) = Self::open_device(context, vid, pid)?;
121        Self::from_usb_device(device, handle, iface, alt)
122    }
123
124    pub fn from_usb_device(
125        device: rusb::Device<C>,
126        handle: rusb::DeviceHandle<C>,
127        iface: u8,
128        alt: u8,
129    ) -> Result<Dfu<C>, Error> {
130        let timeout = std::time::Duration::from_secs(3);
131        handle.claim_interface(iface)?;
132        handle.set_alternate_setting(iface, alt)?;
133        let device_descriptor = device.device_descriptor()?;
134        let languages = handle.read_languages(timeout)?;
135        let lang = languages.first().ok_or(Error::MissingLanguage)?;
136
137        for index in 0..device_descriptor.num_configurations() {
138            let config_descriptor = device.config_descriptor(index)?;
139
140            if let Some(functional_descriptor) =
141                Self::find_functional_descriptor(&handle, &config_descriptor, timeout)
142                    .transpose()?
143            {
144                let interface = config_descriptor
145                    .interfaces()
146                    .find(|x| x.number() == iface)
147                    .ok_or(Error::InvalidInterface)?;
148                let iface_desc = interface
149                    .descriptors()
150                    .find(|x| x.setting_number() == alt)
151                    .ok_or(Error::InvalidAlt)?;
152
153                let interface_string = match iface_desc.description_string_index() {
154                    None => String::new(),
155                    Some(_) => handle.read_interface_string(*lang, &iface_desc, timeout)?,
156                };
157                let protocol = dfu_core::DfuProtocol::new(
158                    &interface_string,
159                    functional_descriptor.dfu_version,
160                )?;
161
162                let io = DfuLibusb {
163                    usb: RefCell::new(handle),
164                    protocol,
165                    timeout,
166                    iface: iface as u16,
167                    functional_descriptor,
168                    marker: marker::PhantomData,
169                };
170
171                return Ok(Dfu::new(io));
172            }
173        }
174
175        Err(Error::NoDfuCapableDeviceFound)
176    }
177
178    fn open_device(
179        context: &C,
180        vid: u16,
181        pid: u16,
182    ) -> Result<(rusb::Device<C>, rusb::DeviceHandle<C>), Error> {
183        for device in context.devices()?.iter() {
184            let device_desc = match device.device_descriptor() {
185                Ok(x) => x,
186                Err(_) => continue,
187            };
188
189            if device_desc.vendor_id() == vid && device_desc.product_id() == pid {
190                let handle = device.open()?;
191                return Ok((device, handle));
192            }
193        }
194
195        Err(Error::CouldNotOpenDevice)
196    }
197
198    fn find_functional_descriptor(
199        handle: &rusb::DeviceHandle<C>,
200        config: &rusb::ConfigDescriptor,
201        timeout: std::time::Duration,
202    ) -> Option<Result<dfu_core::functional_descriptor::FunctionalDescriptor, Error>> {
203        macro_rules! find_func_desc {
204            ($data:expr) => {{
205                if let Some(func_desc) =
206                    dfu_core::functional_descriptor::FunctionalDescriptor::from_bytes($data)
207                {
208                    return Some(func_desc.map_err(Into::into));
209                }
210            }};
211        }
212
213        find_func_desc!(config.extra());
214
215        for if_desc in config.interfaces().flat_map(|x| x.descriptors()) {
216            find_func_desc!(if_desc.extra());
217        }
218
219        let mut buffer = [0x00; 9];
220        match handle.read_control(
221            rusb::constants::LIBUSB_ENDPOINT_IN,
222            rusb::constants::LIBUSB_REQUEST_GET_DESCRIPTOR,
223            0x2100,
224            0,
225            &mut buffer,
226            timeout,
227        ) {
228            Ok(n) => find_func_desc!(&buffer[..n]),
229            Err(err) => return Some(Err(err.into())),
230        }
231
232        None
233    }
234}