dfu_rs/
lib.rs

1// Copyright (C) 2025 Piers Finlayson <piers@piers.rocks>
2//
3// MIT License
4
5//! Implements DFU operations for USB devices.
6//! 
7//! Based on [`rusb`](https://docs.rs/rusb/latest/rusb/) for USB communication,
8//! this crate provides a simple interface for discovering DFU-capable devices
9//! and performing DFU operations.
10//! 
11//! It is designed for use in host applications that need to update firmware
12//! on embedded devices via USB DFU.
13//! 
14//! It is intended to work on Windows, Linux, and macOS.
15//! 
16//! # Features
17//! 
18//! - Upload (read) data from DFU devices
19//! - Download (write) data to DFU devices
20//! - Erase flash memory (page-wise and mass erase)
21//! - Device discovery with filtering by DFU type (e.g. internal flash)
22//! - Error handling with detailed DFU and USB errors
23//! - Uses blocking calls via `rusb` - wrap in `tokio::task::spawn_blocking`
24//!   for async runtimes
25//! - Customizable USB timeout
26//! 
27//! # Usage
28//! 
29//! ```no_run
30//! use dfu_rs::{Device, DfuType};
31//! 
32//! if let Some(device) = Device::search(None).unwrap().first() {
33//!    let mut buffer = vec![0u32; 4096]; // 16KB
34//!   device.upload(0x08000000, &mut buffer).unwrap();
35//!   println!("Uploaded data: {:X?}", &buffer[..16]); // Print first 16 words
36//! }
37//! ```
38//! 
39//! See [`Device`] for more examples.
40
41#[allow(unused_imports)]
42use log::{debug, error, info, trace, warn};
43use rusb::{Context, Device as RusbDevice, Error as RusbError, UsbContext};
44use std::time::Duration;
45
46// Timeout
47const DEFAULT_USB_TIMEOUT: Duration = Duration::from_secs(30);
48
49// USB class/subclass codes for DFU
50const USB_CLASS_APPLICATION_SPECIFIC: u8 = 0xFE;
51const USB_SUBCLASS_DFU: u8 = 0x01;
52
53// DFU block size
54const DFU_BLOCK_SIZE: usize = 2048;
55
56// USB request types (bmRequestType)
57const USB_CLASS_INTERFACE_OUT: u8 = 0x21; // Class, Interface, Host-to-Device
58const USB_CLASS_INTERFACE_IN: u8 = 0xA1;  // Class, Interface, Device-to-Host
59
60// STM32 DFU commands (vendor-specific)
61const STM32_DFU_CMD_SET_ADDRESS: u8 = 0x21;
62const STM32_DFU_CMD_ERASE: u8 = 0x41;
63#[allow(dead_code)]
64const STM32_DFU_CMD_READ_UNPROTECT: u8 = 0x92;
65
66// DFU request types
67#[derive(Debug, Clone, PartialEq)]
68#[repr(u8)]
69#[allow(dead_code)]
70enum Request {
71    Detach = 0,
72    Download = 1,
73    Upload = 2,
74    GetStatus = 3,
75    ClearStatus = 4,
76    GetState = 5,
77    Abort = 6,
78}
79
80impl Into<u8> for Request {
81    fn into(self) -> u8 {
82        self as u8
83    }
84}
85
86impl Request {
87    // Returns expected length of the request's data phase where known
88    const fn fixed_length(&self) -> usize {
89        match self {
90            Request::Detach => 0,
91            Request::Download => 0,
92            Request::Upload => 0,
93            Request::GetStatus => 6,
94            Request::ClearStatus => 0,
95            Request::GetState => 1,
96            Request::Abort => 0,
97        }
98    }
99}
100
101/// DFU Status codes
102#[derive(Debug, Clone, PartialEq)]
103#[repr(u8)]
104pub enum Status {
105    Ok = 0,
106    ErrTarget = 1,
107    ErrFile = 2,
108    ErrWrite = 3,
109    ErrErase = 4,
110    ErrCheckErased = 5,
111    ErrProg = 6,
112    ErrVerify = 7,
113    ErrAddress = 8,
114    ErrNotDone = 9,
115    ErrFirmware = 10,
116    ErrVendor = 11,
117    ErrUsbReset = 12,
118    ErrPowerOnReset = 13,
119    ErrUnknown = 14,
120    ErrStalledPkt = 15,
121}
122
123impl std::fmt::Display for Status {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        match self {
126            Status::Ok => write!(f, "OK"),
127            Status::ErrTarget => write!(f, "Error: Target"),
128            Status::ErrFile => write!(f, "Error: File"),
129            Status::ErrWrite => write!(f, "Error: Write"),
130            Status::ErrErase => write!(f, "Error: Erase"),
131            Status::ErrCheckErased => write!(f, "Error: Check Erased"),
132            Status::ErrProg => write!(f, "Error: Program"),
133            Status::ErrVerify => write!(f, "Error: Verify"),
134            Status::ErrAddress => write!(f, "Error: Address"),
135            Status::ErrNotDone => write!(f, "Error: Not Done"),
136            Status::ErrFirmware => write!(f, "Error: Firmware"),
137            Status::ErrVendor => write!(f, "Error: Vendor"),
138            Status::ErrUsbReset => write!(f, "Error: USB Reset"),
139            Status::ErrPowerOnReset => write!(f, "Error: Power On Reset"),
140            Status::ErrUnknown => write!(f, "Error: Unknown"),
141            Status::ErrStalledPkt => write!(f, "Error: Stalled Packet"),
142        }
143    }
144}
145
146/// DFU Device State codes
147#[derive(Debug, Clone, PartialEq)]
148#[repr(u8)]
149pub enum State {
150    AppIdle = 0,
151    AppDetach = 1,
152    DfuIdle = 2,
153    DfuDnloadSync = 3,
154    DfuDnloadBusy = 4,
155    DfuDnloadIdle = 5,
156    DfuManifestSync = 6,
157    DfuManifest = 7,
158    DfuManifestWaitReset = 8,
159    DfuUploadIdle = 9,
160    DfuError = 10,
161}
162
163impl std::fmt::Display for State {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        match self {
166            State::AppIdle => write!(f, "App Idle"),
167            State::AppDetach => write!(f, "App Detach"),
168            State::DfuIdle => write!(f, "DFU Idle"),
169            State::DfuDnloadSync => write!(f, "DFU Download Sync"),
170            State::DfuDnloadBusy => write!(f, "DFU Download Busy"),
171            State::DfuDnloadIdle => write!(f, "DFU Download Idle"),
172            State::DfuManifestSync => write!(f, "DFU Manifest Sync"),
173            State::DfuManifest => write!(f, "DFU Manifest"),
174            State::DfuManifestWaitReset => write!(f, "DFU Manifest Wait Reset"),
175            State::DfuUploadIdle => write!(f, "DFU Upload Idle"),
176            State::DfuError => write!(f, "DFU Error"),
177        }
178    }
179}
180
181// Status returned by the DFU device
182struct DeviceStatus {
183    status: Status,
184    state: State,
185    poll_time: u32,
186    string: u8,
187}
188
189#[allow(dead_code)]
190impl DeviceStatus {
191    fn status(&self) -> Status {
192        self.status.clone()
193    }
194
195    fn state(&self) -> State {
196        self.state.clone()
197    }
198
199    fn poll_time(&self) -> u32 {
200        self.poll_time
201    }
202
203    fn string_index(&self) -> u8 {
204        self.string
205    }
206
207    fn is_error(&self) -> bool {
208        self.is_status_error() || self.is_state_error()
209    }
210
211    fn is_status_error(&self) -> bool {
212        self.status != Status::Ok
213    }
214
215    fn is_state_error(&self) -> bool {
216        self.state == State::DfuError
217    }
218
219    fn is_state_dfu_idle(&self) -> bool {
220        self.state == State::DfuIdle
221    }
222
223    fn is_download_busy(&self) -> bool {
224        self.state == State::DfuDnloadBusy
225    }
226
227    fn is_download_manifest(&self) -> bool {
228        self.state == State::DfuManifest || self.state == State::DfuManifestSync
229    }
230
231    fn from_packet(data: &[u8]) -> Result<Self, Error> {
232        if data.len() < Request::GetStatus.fixed_length() {
233            warn!("Invalid DFU device status packet length: {}", data.len());
234            return Err(Error::DfuInvalidDeviceStatus);
235        }
236
237        let status = match data[0] {
238            0 => Status::Ok,
239            1 => Status::ErrTarget,
240            2 => Status::ErrFile,
241            3 => Status::ErrWrite,
242            4 => Status::ErrErase,
243            5 => Status::ErrCheckErased,
244            6 => Status::ErrProg,
245            7 => Status::ErrVerify,
246            8 => Status::ErrAddress,
247            9 => Status::ErrNotDone,
248            10 => Status::ErrFirmware,
249            11 => Status::ErrVendor,
250            12 => Status::ErrUsbReset,
251            13 => Status::ErrPowerOnReset,
252            14 => Status::ErrUnknown,
253            15 => Status::ErrStalledPkt,
254            _ => {
255                warn!("Unknown DFU status code: {}", data[0]);
256                return Err(Error::DfuInvalidDeviceStatus)
257            }
258        };
259
260        let state = match data[4] {
261            0 => State::AppIdle,
262            1 => State::AppDetach,
263            2 => State::DfuIdle,
264            3 => State::DfuDnloadSync,
265            4 => State::DfuDnloadBusy,
266            5 => State::DfuDnloadIdle,
267            6 => State::DfuManifestSync,
268            7 => State::DfuManifest,
269            8 => State::DfuManifestWaitReset,
270            9 => State::DfuUploadIdle,
271            10 => State::DfuError,
272            _ => {
273                warn!("Unknown DFU state code: {}", data[4]);
274                return Err(Error::DfuInvalidDeviceStatus);
275            }
276        };
277
278        let poll_time = u32::from_le_bytes([data[1], data[2], data[3], 0]);
279        let string = data[5];
280
281        trace!("Device Status: status={}, state={}, poll_time={}ms, string={}", status, state, poll_time, string);
282
283        Ok(DeviceStatus {
284            status,
285            state,
286            poll_time,
287            string,
288        })
289    }
290}
291
292/// DFU Type enumeration - each USB DFU device can represent different memory
293/// regions, represented by this object
294#[derive(Debug, Clone, PartialEq)]
295pub enum DfuType {
296    InternalFlash,
297    OptionBytes,
298    SystemMemory,
299    Unknown(String),
300}
301
302/// USB device information
303#[derive(Debug, Clone, PartialEq)]
304pub struct DeviceInfo {
305    /// USB device vendor ID
306    pub vid: u16,
307    /// USB device product ID
308    pub pid: u16,
309    /// USB interface number
310    pub interface: u8,
311    /// USB bus number
312    pub bus: u8,
313    /// USB device address
314    pub address: u8,
315    /// USB alternate setting
316    pub alt: u8,
317    /// Device description string
318    pub desc: String,
319    /// DFU Type, decoded via USB description string
320    pub dfu_type: DfuType,
321}
322
323impl std::fmt::Display for DeviceInfo {
324    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325        write!(
326            f,
327            "{:0>4X}:{:0>4X}",
328            self.vid, self.pid,
329        )
330    }
331}
332
333/// Error type
334/// 
335/// Many of the errors are wrappers around `rusb::Error`, and are often
336/// somewhat esoteric.  [`Error::usb_stack_error()`] can be used to retrieve
337/// the underlying USB stack error for further analysis, or test whether the
338/// failure was a USB stack one.
339#[derive(Debug, Clone, PartialEq)]
340pub enum Error {
341    /// DFU Device not found
342    DeviceNotFound,
343    /// DFU status error returned by device
344    DfuStatus{
345        status: Status,
346        state: State,
347    },
348    /// Invalid DFU device status response
349    DfuInvalidDeviceStatus,
350    /// DFU set address pointer failed
351    DfuSetAddressFailed(Status, State),
352    UsbContext(RusbError),
353    UsbDeviceEnumeration(RusbError),
354    UsbDeviceOpen(RusbError),
355    UsbKernelDriverDetach(RusbError),
356    UsbClaimInterface(RusbError),
357    UsbSetAltSetting(RusbError),
358    UsbControlTransfer(RusbError),
359}
360
361impl std::fmt::Display for Error {
362    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363        match self {
364            Error::DeviceNotFound => write!(f, "DFU Device Not Found"),
365            Error::DfuStatus{ status, state } => write!(f, "DFU Status Error: status code {}, state {}", status, state),
366            Error::DfuInvalidDeviceStatus => write!(f, "DFU Invalid Device Status"),
367            Error::DfuSetAddressFailed(status, state) => write!(f, "DFU Set Address Failed: status code {}, state {}", status, state),
368            Error::UsbContext(e) => write!(f, "USB Context Error: {}", e),
369            Error::UsbDeviceEnumeration(e) => write!(f, "USB Device Enumeration Error: {}", e),
370            Error::UsbDeviceOpen(e) => write!(f, "Device Open Error: {}", e),
371            Error::UsbKernelDriverDetach(e) => write!(f, "Kernel Driver Detach Error: {}", e),
372            Error::UsbClaimInterface(e) => write!(f, "Claim Interface Error: {}", e),
373            Error::UsbSetAltSetting(e) => write!(f, "Set Alternate Setting Error: {}", e),
374            Error::UsbControlTransfer(e) => write!(f, "Control Transfer Error: {}", e),
375        }
376    }
377}
378
379impl Error {
380    /// Returns the underlying USB stack error if applicable.
381    pub fn usb_stack_error(&self) -> Option<&RusbError> {
382        match self {
383            Error::UsbContext(e) => Some(e),
384            Error::UsbDeviceEnumeration(e) => Some(e),
385            Error::UsbDeviceOpen(e) => Some(e),
386            Error::UsbKernelDriverDetach(e) => Some(e),
387            Error::UsbClaimInterface(e) => Some(e),
388            Error::UsbSetAltSetting(e) => Some(e),
389            Error::UsbControlTransfer(e) => Some(e),
390            _ => None,
391        }
392    }
393}
394
395/// DFU Device representation
396/// 
397/// Used to search for DFU-capable devices, hold their information, and perform
398/// DFU operations.
399/// 
400/// Create a DFU device by calling `Device::search()`, which returns a list
401/// of found DFU devices.
402/// 
403/// Example:
404/// ```no_run
405/// use dfu_rs::{Device, DfuType};
406/// 
407/// // Search for all DFU devices
408/// let devices = Device::search(None).unwrap();
409/// for device in devices {
410///    println!("Found DFU Device: {}", device);
411/// }
412/// 
413/// // Search for only Internal Flash DFU devices
414/// let flash_devices = Device::search(Some(DfuType::InternalFlash)).unwrap();
415/// for device in flash_devices {
416///    println!("Found Flash DFU Device: {}", device);
417/// }
418/// 
419/// // Retrieve the first 16KB from the first found DFU device
420/// if let Some(device) = Device::search(None).unwrap().first() {
421///    let mut buffer = vec![0u32; 4096]; // 16KB
422///   device.upload(0x08000000, &mut buffer).unwrap();
423///   println!("Uploaded data: {:X?}", &buffer[..16]); // Print first 16 words
424/// }
425/// ```
426#[derive(Debug, Clone, PartialEq)]
427pub struct Device {
428    info: DeviceInfo,
429    timeout: Duration,
430}
431
432impl std::fmt::Display for Device {
433    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
434        write!(
435            f,
436            "{} ({:04X}:{:04X})",
437            self.info.desc,
438            self.info.vid,
439            self.info.pid,
440        )
441    }
442}
443
444impl Device {
445    /// Enumerates the USB bus and searches for DFU-capable devices.
446    /// 
447    /// Arguments:
448    /// - `filter`: Optional filter to only return devices of a specific DFU type.
449    ///   (such as flash)
450    /// 
451    /// Returns:
452    /// - `Ok(Vec<DeviceInfo>)`: A vector of found DFU devices.
453    /// - `Err(Error)`: An error occurred during USB enumeration.
454    pub fn search(filter: Option<DfuType>) -> Result<Vec<Self>, Error> {
455        let context = Context::new()
456            .map_err(|e| Error::UsbContext(e))?;
457        
458        let devices = context.devices()
459            .map_err(|e| Error::UsbDeviceEnumeration(e))?;
460
461        let mut dfu_devices = Vec::new();
462        
463        for device in devices.iter() {
464            dfu_devices.extend(check_device_for_dfu(&device));
465        }
466
467        let dfu_devices = if let Some(filter) = filter {
468            check_device_for_dfu_type(filter, dfu_devices)
469        } else {
470            dfu_devices
471        };
472
473        let dfu_devices: Vec<Self> = dfu_devices.into_iter()
474            .map(|info| Device::new(info))
475            .collect();
476        
477        Ok(dfu_devices)
478    }
479
480    /// Creates a new DFU Device instance from the provided DeviceInfo.
481    /// 
482    /// Users should primarily use [`Device::search()`] to find and create DFU
483    /// devices.  However, this constructor is provided for cases where
484    /// [`Device::search()`] doesn't return the desired device, or is
485    /// considered inefficient or otherwise insufficient.
486    /// 
487    /// Arguments:
488    /// - `info`: The DeviceInfo struct representing the DFU device.
489    /// 
490    /// Returns:
491    /// - `Device`: The created DFU Device instance.
492    pub fn new(info: DeviceInfo) -> Self {
493        Device { 
494            info,
495            timeout: DEFAULT_USB_TIMEOUT,
496        }
497    }
498
499    /// Sets the USB timeout duration for operations.
500    pub fn set_timeout(&mut self, timeout: Duration) {
501        self.timeout = timeout;
502    }
503
504    /// Returns the device information.
505    pub fn info(&self) -> &DeviceInfo {
506        &self.info
507    }
508
509    /// Upload (retrieve) data from the device via DFU.
510    /// 
511    /// Arguments:
512    /// - `address`: The starting address to read from.
513    /// - `buf`: The buffer to fill with read data (in words).
514    /// 
515    /// Returns:
516    /// - `Ok(())`: The upload was successful, and `buf` is filled
517    /// - `Err(Error)`: An error occurred during the upload process.
518    pub fn upload(&self, address: u32, buf: &mut [u32]) -> Result<(), Error> {
519        trace!("Starting DFU upload from address 0x{:08X} for {} words", address, buf.len());
520        let byte_count = buf.len() * 4;
521        let mut bytes = vec![0u8; byte_count];
522        
523        // Open and setup device
524        let (_context, handle) = self.open_device()?;
525        trace!("DFU device opened successfully");
526        
527        // Set address pointer
528        self.set_address(&handle, address)?;
529        trace!("DFU address set successfully");
530        
531        // Abort to return to dfuIDLE before upload
532        self.abort(&handle, false)?;
533        trace!("DFU aborted to enter dfuIDLE state");
534        
535        // Calculate blocks needed
536        let total_blocks = (byte_count + DFU_BLOCK_SIZE - 1) / DFU_BLOCK_SIZE;
537        
538        // Read each block
539        for block in 0..total_blocks {
540            let block_data = self.read_block(&handle, block)?;
541            let offset = block * DFU_BLOCK_SIZE;
542            let end = (offset + block_data.len()).min(byte_count);
543            bytes[offset..end].copy_from_slice(&block_data[..end - offset]);
544        }
545        trace!("DFU upload completed successfully");
546        
547        // Final abort
548        self.abort(&handle, true)?;
549        trace!("DFU session aborted successfully");
550        
551        // Convert bytes to words and write to caller's buffer
552        for (ii, chunk) in bytes.chunks_exact(4).enumerate() {
553            buf[ii] = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
554        }
555        
556        Ok(())
557    }
558
559    /// Erases flash memory by page/sector
560    /// 
561    /// Arguments:
562    /// - `address`: Starting address to erase from
563    /// - `length`: Number of bytes to erase (rounded up to page boundary)
564    /// - `page_size`: Size of each page/sector in bytes (device-specific)
565    /// 
566    /// STM32F4 devices have variable sector sizes. Common values:
567    /// - STM32F0/F1: 1024-2048 bytes
568    /// - STM32F4: 16384 bytes for sectors 0-3, then 65536, then 131072
569    /// 
570    /// For STM32F4 with mixed sector sizes, use the smallest sector size
571    /// and this will erase more than requested (safe but potentially slower).
572    /// Alternatively, call erase_page() directly for each specific sector.
573    pub fn erase(&self, address: u32, length: usize, page_size: usize) -> Result<(), Error> {
574        trace!("Starting DFU erase at address 0x{:08X} for {} bytes with page size {}", 
575               address, length, page_size);
576        
577        let (_context, handle) = self.open_device()?;
578        trace!("DFU device opened successfully");
579        
580        // Calculate number of pages to erase
581        let total_pages = (length + page_size - 1) / page_size;
582        
583        // Erase each page
584        for page in 0..total_pages {
585            let page_address = address + (page * page_size) as u32;
586            trace!("Erasing page {} at address 0x{:08X}", page, page_address);
587            self.erase_page(&handle, page_address)?;
588        }
589        
590        trace!("DFU erase completed successfully");
591        Ok(())
592    }
593
594    /// Erases the entire flash memory
595    /// 
596    /// More efficient than page-by-page erase when erasing the complete flash.
597    /// Uses the STM32 mass erase command (0x41 with 0xFF parameter).
598    pub fn mass_erase(&self) -> Result<(), Error> {
599        trace!("Starting DFU mass erase");
600        
601        let (_context, handle) = self.open_device()?;
602        trace!("DFU device opened successfully");
603        
604        // Mass erase command: Just hte command byte
605        let cmd = vec![STM32_DFU_CMD_ERASE];
606        
607        handle.write_control(
608            USB_CLASS_INTERFACE_OUT,
609            Request::Download.into(),
610            0,
611            self.info.interface as u16,
612            &cmd,
613            self.timeout
614        ).map_err(|e| Error::UsbControlTransfer(e))?;
615        
616        // Wait for erase to complete
617        self.get_status_check_download_busy(&handle)?;
618        self.get_status(&handle)?;
619        
620        trace!("DFU mass erase completed successfully");
621        Ok(())
622    }
623
624    /// Downloads (writes) data to flash memory
625    /// 
626    /// Arguments:
627    /// - `address`: Starting address to write to
628    /// - `data`: Slice of u32 words to write
629    /// 
630    /// Note: Flash must be erased before writing. Use erase() or mass_erase() first.
631    /// Data is written in 2KB blocks using the DFU download protocol.
632    pub fn download(&self, address: u32, data: &[u32]) -> Result<(), Error> {
633        trace!("Starting DFU download to address 0x{:08X} for {} words", address, data.len());
634        
635        // Convert words to bytes
636        let mut bytes = Vec::with_capacity(data.len() * 4);
637        for word in data {
638            bytes.extend_from_slice(&word.to_le_bytes());
639        }
640        
641        let (_context, handle) = self.open_device()?;
642        trace!("DFU device opened successfully");
643        
644        // Set address pointer
645        self.set_address(&handle, address)?;
646        trace!("DFU address set successfully");
647        
648        // Calculate blocks needed
649        let total_blocks = (bytes.len() + DFU_BLOCK_SIZE - 1) / DFU_BLOCK_SIZE;
650        
651        // Write each block
652        for block in 0..total_blocks {
653            let start = block * DFU_BLOCK_SIZE;
654            let end = (start + DFU_BLOCK_SIZE).min(bytes.len());
655            
656            trace!("Writing block {} of {}", block + 1, total_blocks);
657            self.write_block(&handle, block, &bytes[start..end])?;
658        }
659        trace!("DFU download completed successfully");
660        
661        // Send zero-length download to complete the transfer
662        handle.write_control(
663            USB_CLASS_INTERFACE_OUT,
664            Request::Download.into(),
665            0,
666            self.info.interface as u16,
667            &[],
668            self.timeout
669        ).map_err(|e| Error::UsbControlTransfer(e))?;
670        
671        // Final status check to trigger manifest
672        self.get_status(&handle)?;
673        
674        trace!("DFU download session completed successfully");
675        Ok(())
676    }
677}
678
679// Private helper methods
680impl Device {
681    fn clear_status(&self, handle: &rusb::DeviceHandle<Context>) -> Result<(), Error> {
682        trace!("Clearing DFU status");
683        handle.write_control(
684            USB_CLASS_INTERFACE_OUT,
685            Request::ClearStatus.into(),
686            0,
687            self.info.interface as u16,
688            &[],
689            self.timeout
690        ).map_err(|e| Error::UsbControlTransfer(e))?;
691        
692        Ok(())
693    }
694    
695    fn open_device(&self) -> Result<(Context, rusb::DeviceHandle<Context>), Error> {
696        trace!("Opening DFU device: {:?}", self.info);
697        let context = Context::new()
698            .map_err(|e| Error::UsbContext(e))?;
699        
700        let devices = context.devices()
701            .map_err(|e| Error::UsbDeviceEnumeration(e))?;
702        
703        let device = devices.iter()
704            .find(|d| d.bus_number() == self.info.bus && d.address() == self.info.address)
705            .ok_or_else(|| Error::DeviceNotFound)?;
706        
707        #[allow(unused_mut)] // Required mutable on Windows
708        let mut handle = device.open()
709            .map_err(|e| Error::UsbDeviceOpen(e))?;
710
711        // Select configuration 1 (required on Windows)
712        handle.set_active_configuration(1)
713            .map_err(|e| Error::UsbDeviceOpen(e))?;
714        
715        // Detach kernel driver if needed
716        if let Ok(true) = handle.kernel_driver_active(self.info.interface) {
717            handle.detach_kernel_driver(self.info.interface)
718                .map_err(|e| Error::UsbKernelDriverDetach(e))?;
719        }
720        
721        handle.claim_interface(self.info.interface)
722            .map_err(|e| Error::UsbClaimInterface(e))?;
723        
724        handle.set_alternate_setting(self.info.interface, self.info.alt)
725            .map_err(|e| Error::UsbSetAltSetting(e))?;
726
727        // Initialize the DFU state machine
728        let needs_clear = match self.get_status(&handle) {
729            Ok(status) => status.is_state_error(),
730            Err(_) => true,  // getStatus failed, needs initialization
731        };
732
733        if needs_clear {
734            self.clear_status(&handle)?;
735            self.get_status(&handle)?;
736        }
737        
738        // Ensure device is in dfuIDLE state for subsequent operations
739        self.abort(&handle, false)?;
740
741        Ok((context, handle))
742    }
743    
744    fn set_address(&self, handle: &rusb::DeviceHandle<Context>, address: u32) -> Result<(), Error> {
745        trace!("Setting DFU address to 0x{:08X}", address);
746        trace!("DFU info {:?}", self.info);
747
748        // Command: 0x21 followed by address in little-endian
749        let mut cmd = vec![STM32_DFU_CMD_SET_ADDRESS];
750        cmd.extend_from_slice(&address.to_le_bytes());
751        
752        handle.write_control(
753            USB_CLASS_INTERFACE_OUT,
754            Request::Download.into(),
755            0,    // wValue: 0 for command mode
756            self.info.interface as u16,
757            &cmd,
758            self.timeout
759        )
760            .inspect_err(|e| warn!("Control transfer error during set_address: {e}"))
761            .map_err(|e| Error::UsbControlTransfer(e))?;
762        
763        // First get status after write address should return download busy
764        self.get_status_check_download_busy(handle)?;
765        self.get_status(handle)?;
766        
767        Ok(())
768    }
769
770    fn erase_page(&self, handle: &rusb::DeviceHandle<Context>, address: u32) -> Result<(), Error> {
771        trace!("Erasing page at address 0x{:08X}", address);
772        
773        // Command: 0x41 followed by address in little-endian
774        let mut cmd = vec![STM32_DFU_CMD_ERASE];
775        cmd.extend_from_slice(&address.to_le_bytes());
776        
777        handle.write_control(
778            USB_CLASS_INTERFACE_OUT,
779            Request::Download.into(),
780            0,    // wValue: 0 for command mode
781            self.info.interface as u16,
782            &cmd,
783            self.timeout
784        ).map_err(|e| Error::UsbControlTransfer(e))?;
785        
786        // Wait for erase to complete - can take significant time
787        self.get_status_check_download_busy(handle)?;
788        self.get_status(handle)?;
789        
790        Ok(())
791    }
792
793    fn abort(&self, handle: &rusb::DeviceHandle<Context>, get_status: bool) -> Result<(), Error> {
794        trace!("Sending DFU abort");
795        handle.write_control(
796            USB_CLASS_INTERFACE_OUT,
797            Request::Abort.into(),
798            0,
799            self.info.interface as u16,
800            &[],
801            self.timeout
802        ).map_err(|e| Error::UsbControlTransfer(e))?;
803        
804        if get_status {
805            self.get_status(handle)?;
806        }
807        
808        Ok(())
809    }
810    
811    fn read_block(&self, handle: &rusb::DeviceHandle<Context>, block: usize) -> Result<Vec<u8>, Error> {
812        trace!("Reading DFU block {}", block);
813        let mut buf = vec![0u8; DFU_BLOCK_SIZE];
814        
815        let bytes_read = handle.read_control(
816            USB_CLASS_INTERFACE_IN,
817            Request::Upload.into(),
818            (2 + block) as u16, // wValue: block number + 2
819            self.info.interface as u16,
820            &mut buf,
821            self.timeout
822        ).map_err(|e| Error::UsbControlTransfer(e))?;
823        
824        buf.truncate(bytes_read);
825        
826        self.get_status(handle)?;
827        self.get_status(handle)?;
828        
829        Ok(buf)
830    }
831    
832    fn write_block(&self, handle: &rusb::DeviceHandle<Context>, block: usize, data: &[u8]) -> Result<(), Error> {
833        trace!("Writing DFU block {} ({} bytes)", block, data.len());
834        
835        // Prepare 2KB block, padding with 0xFF if needed
836        let mut block_data = vec![0xFF; DFU_BLOCK_SIZE];
837        block_data[..data.len()].copy_from_slice(data);
838        
839        handle.write_control(
840            USB_CLASS_INTERFACE_OUT,
841            Request::Download.into(),
842            (2 + block) as u16, // wValue: block number + 2
843            self.info.interface as u16,
844            &block_data,
845            self.timeout
846        ).map_err(|e| Error::UsbControlTransfer(e))?;
847        
848        // Wait for write to complete
849        self.get_status_check_download_busy(handle)?;
850        self.get_status(handle)?;
851
852        Ok(())
853    }
854
855    // Returns Err(Error) if device is not in download busy state
856    fn get_status_check_download_busy(&self, handle: &rusb::DeviceHandle<Context>) -> Result<(), Error> {
857        let status = self.get_status_error_flag(handle, true)?;
858        if !status.is_download_busy() {
859            return Err(Error::DfuStatus{ status: status.status(), state: status.state() })
860        }
861        Ok(())
862    }
863
864    // Returns Err(Error) if any error status/state is reported
865    fn get_status(&self, handle: &rusb::DeviceHandle<Context>) -> Result<DeviceStatus, Error> {
866        self.get_status_error_flag(handle, false)
867    }
868
869    fn get_status_error_flag(&self, handle: &rusb::DeviceHandle<Context>, error_ok: bool) -> Result<DeviceStatus, Error> {
870        trace!("Getting DFU status");
871        let mut data = [0u8; Request::GetStatus.fixed_length()];
872        handle.read_control(
873            USB_CLASS_INTERFACE_IN,
874            Request::GetStatus.into(),
875            0,
876            self.info.interface as u16,
877            &mut data,
878            self.timeout
879        ).map_err(|e| Error::UsbControlTransfer(e))?;
880
881        let status = DeviceStatus::from_packet(&data)?;
882        
883        // Wait for poll time
884        std::thread::sleep(std::time::Duration::from_millis(status.poll_time() as u64));
885        
886        if !error_ok && status.is_error() {
887            return Err(Error::DfuStatus{ status: status.status(), state: status.state() });
888        }
889        
890        Ok(status)
891    }
892}
893
894fn check_device_for_dfu_type(filter: DfuType, device_info: Vec<DeviceInfo>) -> Vec<DeviceInfo> {
895    device_info.into_iter()
896        .filter(|info| info.dfu_type == filter)
897        .collect()
898}
899
900fn check_device_for_dfu(device: &RusbDevice<Context>) -> Vec<DeviceInfo> {
901    let mut results = Vec::new();
902    
903    let desc = match device.device_descriptor() {
904        Ok(d) => d,
905        Err(_) => return results,
906    };
907    
908    let vid = desc.vendor_id();
909    let pid = desc.product_id();
910    let bus = device.bus_number();
911    let address = device.address();
912    
913    for config_idx in 0..desc.num_configurations() {
914        let config = match device.config_descriptor(config_idx) {
915            Ok(c) => c,
916            Err(_) => continue,
917        };
918        
919        for interface in config.interfaces() {
920            for iface_desc in interface.descriptors() {
921                if iface_desc.class_code() == USB_CLASS_APPLICATION_SPECIFIC && iface_desc.sub_class_code() == USB_SUBCLASS_DFU {
922                    let alt = iface_desc.setting_number();
923                    let desc_string = get_interface_string(device, &iface_desc);
924                    let dfu_type = parse_dfu_type(&desc_string);
925                    
926                    results.push(DeviceInfo {
927                        vid,
928                        pid,
929                        interface: interface.number(),
930                        bus,
931                        address,
932                        alt,
933                        desc: desc_string,
934                        dfu_type,
935                    });
936                }
937            }
938        }
939    }
940    
941    results
942}
943
944fn get_interface_string(
945    device: &RusbDevice<Context>,
946    iface_desc: &rusb::InterfaceDescriptor,
947) -> String {
948    device.open()
949        .and_then(|handle| {
950            handle.read_string_descriptor_ascii(iface_desc.description_string_index().unwrap_or(0))
951        })
952        .unwrap_or_else(|_| "Unknown".to_string())
953}
954
955fn parse_dfu_type(desc: &str) -> DfuType {
956    // Look for @Region Name / pattern
957    if let Some(at_pos) = desc.find('@') {
958        if let Some(slash_pos) = desc[at_pos..].find('/') {
959            let region = desc[at_pos + 1..at_pos + slash_pos].trim();
960            
961            return match region {
962                s if s.contains("Internal Flash") => DfuType::InternalFlash,
963                s if s.contains("Option Bytes") => DfuType::OptionBytes,
964                s if s.contains("System Memory") || s.contains("Bootloader") => DfuType::SystemMemory,
965                _ => DfuType::Unknown(region.to_string()),
966            };
967        }
968    }
969    
970    DfuType::Unknown(desc.to_string())
971}