freewili_finder_rs/
lib.rs

1//! # FreeWili Finder - Rust Bindings
2//!
3//! This library provides safe Rust bindings for the FreeWili Finder C/C++ library,
4//! making it easy to discover and interface with FreeWili devices from Rust applications.
5//!
6mod ffi;
7
8use ffi::fw_error_t;
9use ffi::fw_freewili_device_t;
10use std::cell::RefCell;
11use std::collections::HashMap;
12use std::ffi::{CStr, c_char};
13use std::fmt;
14use std::rc::Rc;
15use thiserror::Error;
16
17use ffi::_fw_devicetype_t::*;
18use ffi::_fw_inttype_t::*;
19use ffi::_fw_stringtype_t::*;
20
21use crate::ffi::fw_stringtype_t;
22
23thread_local! {
24    static DEVICE_REGISTRY: RefCell<HashMap<*mut fw_freewili_device_t, Rc<FreeWiliDeviceHandle>>> = RefCell::new(HashMap::new());
25}
26
27#[derive(Error, Debug)]
28pub enum FreeWiliError {
29    /// Invalid parameter passed to a function
30    #[error("Invalid Parameter")]
31    InvalidParameter,
32    /// Invalid device handle (device may have been disconnected)
33    #[error("Invalid device handle")]
34    InvalidDevice,
35    /// Internal error in the underlying C library
36    #[error("Internal error{}", .0.as_ref().map(|s| format!(": {s}")).unwrap_or_default())]
37    InternalError(Option<String>),
38    /// Memory allocation error
39    #[error("Memory error")]
40    MemoryError,
41    /// No more devices found during enumeration
42    #[error("No more devices found")]
43    NoMoreDevices,
44    /// Success or no error (used internally)
45    #[error("None")]
46    None,
47}
48
49pub type Result<T> = std::result::Result<T, FreeWiliError>;
50
51impl From<ffi::_fw_error_t> for FreeWiliError {
52    fn from(error: ffi::_fw_error_t) -> Self {
53        match error {
54            ffi::_fw_error_t::fw_error_success => FreeWiliError::None,
55            ffi::_fw_error_t::fw_error_invalid_parameter => FreeWiliError::InvalidParameter,
56            ffi::_fw_error_t::fw_error_invalid_device => FreeWiliError::InvalidDevice,
57            ffi::_fw_error_t::fw_error_internal_error => FreeWiliError::InternalError(None),
58            ffi::_fw_error_t::fw_error_memory => FreeWiliError::MemoryError,
59            ffi::_fw_error_t::fw_error_no_more_devices => FreeWiliError::NoMoreDevices,
60            ffi::_fw_error_t::fw_error_none => FreeWiliError::None,
61            ffi::_fw_error_t::fw_error__maxvalue => FreeWiliError::InternalError(None),
62        }
63    }
64}
65
66impl From<fw_error_t> for FreeWiliError {
67    fn from(error_code: fw_error_t) -> Self {
68        match error_code {
69            x if x == ffi::_fw_error_t::fw_error_success as u32 => FreeWiliError::None,
70            x if x == ffi::_fw_error_t::fw_error_invalid_parameter as u32 => {
71                FreeWiliError::InvalidParameter
72            }
73            x if x == ffi::_fw_error_t::fw_error_invalid_device as u32 => {
74                FreeWiliError::InvalidDevice
75            }
76            x if x == ffi::_fw_error_t::fw_error_internal_error as u32 => {
77                FreeWiliError::InternalError(None)
78            }
79            x if x == ffi::_fw_error_t::fw_error_memory as u32 => FreeWiliError::MemoryError,
80            x if x == ffi::_fw_error_t::fw_error_no_more_devices as u32 => {
81                FreeWiliError::NoMoreDevices
82            }
83            x if x == ffi::_fw_error_t::fw_error_none as u32 => FreeWiliError::None,
84            x if x == ffi::_fw_error_t::fw_error__maxvalue as u32 => {
85                FreeWiliError::InternalError(None)
86            }
87            _ => FreeWiliError::InternalError(Some(format!("Unknown error code: {error_code}"))),
88        }
89    }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum UsbDeviceType {
94    /// USB Hub
95    Hub,
96    /// Legacy Serial device
97    Serial,
98    /// Main CPU Serial
99    SerialMain,
100    /// Display CPU Serial
101    SerialDisplay,
102    /// Mass Storage Device
103    MassStorage,
104    /// ESP32 device
105    Esp32,
106    /// FTDI device (typically connected to FPGA)
107    Ftdi,
108    /// Other/Unknown USB device
109    Other,
110    /// Maximum value marker (internal use)
111    _MaxValue,
112}
113
114impl From<ffi::_fw_usbdevicetype_t> for UsbDeviceType {
115    fn from(device_type: ffi::_fw_usbdevicetype_t) -> Self {
116        match device_type {
117            ffi::_fw_usbdevicetype_t::fw_usbdevicetype_hub => UsbDeviceType::Hub,
118            ffi::_fw_usbdevicetype_t::fw_usbdevicetype_serial => UsbDeviceType::Serial,
119            ffi::_fw_usbdevicetype_t::fw_usbdevicetype_serialmain => UsbDeviceType::SerialMain,
120            ffi::_fw_usbdevicetype_t::fw_usbdevicetype_serialdisplay => {
121                UsbDeviceType::SerialDisplay
122            }
123            ffi::_fw_usbdevicetype_t::fw_usbdevicetype_massstorage => UsbDeviceType::MassStorage,
124            ffi::_fw_usbdevicetype_t::fw_usbdevicetype_esp32 => UsbDeviceType::Esp32,
125            ffi::_fw_usbdevicetype_t::fw_usbdevicetype_ftdi => UsbDeviceType::Ftdi,
126            ffi::_fw_usbdevicetype_t::fw_usbdevicetype_other => UsbDeviceType::Other,
127            ffi::_fw_usbdevicetype_t::fw_usbdevicetype__maxvalue => UsbDeviceType::_MaxValue,
128        }
129    }
130}
131
132impl From<ffi::fw_usbdevicetype_t> for UsbDeviceType {
133    fn from(device_type: ffi::fw_usbdevicetype_t) -> Self {
134        match device_type {
135            x if x == ffi::_fw_usbdevicetype_t::fw_usbdevicetype_hub as u32 => UsbDeviceType::Hub,
136            x if x == ffi::_fw_usbdevicetype_t::fw_usbdevicetype_serial as u32 => {
137                UsbDeviceType::Serial
138            }
139            x if x == ffi::_fw_usbdevicetype_t::fw_usbdevicetype_serialmain as u32 => {
140                UsbDeviceType::SerialMain
141            }
142            x if x == ffi::_fw_usbdevicetype_t::fw_usbdevicetype_serialdisplay as u32 => {
143                UsbDeviceType::SerialDisplay
144            }
145            x if x == ffi::_fw_usbdevicetype_t::fw_usbdevicetype_massstorage as u32 => {
146                UsbDeviceType::MassStorage
147            }
148            x if x == ffi::_fw_usbdevicetype_t::fw_usbdevicetype_esp32 as u32 => {
149                UsbDeviceType::Esp32
150            }
151            x if x == ffi::_fw_usbdevicetype_t::fw_usbdevicetype_ftdi as u32 => UsbDeviceType::Ftdi,
152            x if x == ffi::_fw_usbdevicetype_t::fw_usbdevicetype_other as u32 => {
153                UsbDeviceType::Other
154            }
155            x if x == ffi::_fw_usbdevicetype_t::fw_usbdevicetype__maxvalue as u32 => {
156                UsbDeviceType::_MaxValue
157            }
158            _ => UsbDeviceType::Other, // Default fallback
159        }
160    }
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum DeviceType {
165    /// USB Hub (parent device)
166    Unknown,
167    /// Generic Serial Port device
168    Freewili,
169    /// Main CPU Serial Port
170    Defcon2024Badge,
171    /// Display CPU Serial Port
172    Defcon2025FwBadge,
173    /// Mass Storage Device
174    Uf2,
175    /// ESP32 USB device (JTAG/RTT)
176    Winky,
177}
178
179impl From<ffi::_fw_devicetype_t> for DeviceType {
180    fn from(device_type: ffi::_fw_devicetype_t) -> Self {
181        match device_type {
182            ffi::_fw_devicetype_t::fw_devicetype_unknown => DeviceType::Unknown,
183            ffi::_fw_devicetype_t::fw_devicetype_freewili => DeviceType::Freewili,
184            ffi::_fw_devicetype_t::fw_devicetype_defcon2024badge => DeviceType::Defcon2024Badge,
185            ffi::_fw_devicetype_t::fw_devicetype_defcon2025fwbadge => DeviceType::Defcon2025FwBadge,
186            ffi::_fw_devicetype_t::fw_devicetype_uf2 => DeviceType::Uf2,
187            ffi::_fw_devicetype_t::fw_devicetype_winky => DeviceType::Winky,
188        }
189    }
190}
191
192impl From<ffi::fw_devicetype_t> for DeviceType {
193    fn from(device_type: ffi::fw_devicetype_t) -> Self {
194        match device_type {
195            x if x == ffi::_fw_devicetype_t::fw_devicetype_unknown as u32 => DeviceType::Unknown,
196            x if x == ffi::_fw_devicetype_t::fw_devicetype_freewili as u32 => DeviceType::Freewili,
197            x if x == ffi::_fw_devicetype_t::fw_devicetype_defcon2024badge as u32 => {
198                DeviceType::Defcon2024Badge
199            }
200            x if x == ffi::_fw_devicetype_t::fw_devicetype_defcon2025fwbadge as u32 => {
201                DeviceType::Defcon2025FwBadge
202            }
203            x if x == ffi::_fw_devicetype_t::fw_devicetype_uf2 as u32 => DeviceType::Uf2,
204            x if x == ffi::_fw_devicetype_t::fw_devicetype_winky as u32 => DeviceType::Winky,
205            _ => DeviceType::Unknown, // Default fallback
206        }
207    }
208}
209
210#[derive(Debug, Clone)]
211pub struct USBDevice {
212    /// The type of USB device
213    pub kind: UsbDeviceType,
214    pub kind_name: String,
215
216    /// USB Vendor ID
217    pub vid: u16,
218    /// USB Product ID
219    pub pid: u16,
220    /// Human-readable device name
221    pub name: String,
222    /// Device serial number
223    pub serial: String,
224    /// USB location identifier
225    pub location: u32,
226    /// USB Port chain
227    pub port_chain: Vec<u32>,
228    /// Serial port path (for serial devices like /dev/ttyUSB0, COM1)
229    pub port: Option<String>,
230    /// File system path
231    pub path: Option<String>,
232}
233
234impl USBDevice {
235    /// # Safety
236    ///
237    /// The `device` pointer must be a valid pointer to a `fw_freewili_device_t` that is properly initialized
238    /// and has not been freed. The caller must ensure the device remains valid for the duration of this call.
239    pub unsafe fn from_device(device: *mut ffi::fw_freewili_device_t) -> Result<Self> {
240        let mut usb_device_type: ffi::fw_usbdevicetype_t =
241            fw_devicetype_unknown as ffi::fw_usbdevicetype_t;
242        let res = unsafe { ffi::fw_usb_device_get_type(device, &mut usb_device_type) };
243        if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t {
244            return Err(res.into());
245        }
246
247        let mut usb_device_type_name = vec![0i8; 1024];
248        let mut usb_device_type_name_size: u32 = usb_device_type_name.len() as u32;
249        let res = unsafe {
250            ffi::fw_usb_device_get_type_name(
251                usb_device_type as ffi::fw_usbdevicetype_t,
252                usb_device_type_name.as_mut_ptr(),
253                &mut usb_device_type_name_size,
254            )
255        };
256        if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t {
257            return Err(res.into());
258        }
259        let usb_device_type_name =
260            unsafe { CStr::from_ptr(usb_device_type_name.as_ptr() as *const c_char) }
261                .to_string_lossy()
262                .into_owned();
263
264        let mut vid: u32 = 0;
265        let res = unsafe {
266            ffi::fw_usb_device_get_int(device, fw_inttype_vid as u32, &mut vid as *mut u32)
267        };
268        if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t {
269            return Err(res.into());
270        }
271
272        let mut pid: u32 = 0;
273        let res = unsafe {
274            ffi::fw_usb_device_get_int(device, fw_inttype_pid as u32, &mut pid as *mut u32)
275        };
276        if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t {
277            return Err(res.into());
278        }
279
280        let mut location: u32 = 0;
281        let res = unsafe {
282            ffi::fw_usb_device_get_int(
283                device,
284                fw_inttype_location as u32,
285                &mut location as *mut u32,
286            )
287        };
288        if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t {
289            return Err(res.into());
290        }
291
292        let mut name = vec![0i8; 1024];
293        let mut name_size: u32 = name.len() as u32;
294        let res = unsafe {
295            ffi::fw_usb_device_get_str(
296                device,
297                fw_stringtype_name as fw_stringtype_t,
298                name.as_mut_ptr(),
299                &mut name_size,
300            )
301        };
302        if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t {
303            return Err(res.into());
304        }
305        let name = unsafe { CStr::from_ptr(name.as_ptr() as *const c_char) }
306            .to_string_lossy()
307            .into_owned();
308
309        let mut serial = vec![0i8; 1024];
310        let mut serial_size: u32 = serial.len() as u32;
311        let res = unsafe {
312            ffi::fw_usb_device_get_str(
313                device,
314                fw_stringtype_serial as fw_stringtype_t,
315                serial.as_mut_ptr(),
316                &mut serial_size,
317            )
318        };
319        if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t {
320            return Err(res.into());
321        }
322        let serial = unsafe { CStr::from_ptr(serial.as_ptr() as *const c_char) }
323            .to_string_lossy()
324            .into_owned();
325
326        let mut port = vec![0i8; 1024];
327        let mut port_size: u32 = port.len() as u32;
328        let res = unsafe {
329            ffi::fw_usb_device_get_str(
330                device,
331                fw_stringtype_port as fw_stringtype_t,
332                port.as_mut_ptr(),
333                &mut port_size,
334            )
335        };
336        if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t
337            && res != ffi::_fw_error_t::fw_error_none as ffi::fw_error_t
338        {
339            return Err(res.into());
340        }
341        let port = unsafe { CStr::from_ptr(port.as_ptr() as *const c_char) }
342            .to_string_lossy()
343            .into_owned();
344
345        let mut path = vec![0i8; 1024];
346        let mut path_size: u32 = path.len() as u32;
347        let res = unsafe {
348            ffi::fw_usb_device_get_str(
349                device,
350                fw_stringtype_path as fw_stringtype_t,
351                path.as_mut_ptr(),
352                &mut path_size,
353            )
354        };
355        if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t
356            && res != ffi::_fw_error_t::fw_error_none as ffi::fw_error_t
357        {
358            return Err(res.into());
359        }
360        let path = unsafe { CStr::from_ptr(path.as_ptr() as *const c_char) }
361            .to_string_lossy()
362            .into_owned();
363
364        let mut port_chain: Vec<u32> = vec![0u32; 10];
365        let mut port_chain_size: u32 = port_chain.len() as u32;
366        let res = unsafe {
367            ffi::fw_usb_device_get_port_chain(device, port_chain.as_mut_ptr(), &mut port_chain_size)
368        };
369        if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t {
370            return Err(res.into());
371        }
372        port_chain.resize(port_chain_size as usize, 0);
373
374        let usb_device = USBDevice {
375            kind: usb_device_type.into(),
376            kind_name: usb_device_type_name,
377            vid: vid as u16,
378            pid: pid as u16,
379            name,
380            serial,
381            location,
382            port_chain,
383            port: if port.is_empty() { None } else { Some(port) },
384            path: if path.is_empty() { None } else { Some(path) },
385        };
386
387        Ok(usb_device)
388    }
389}
390
391impl fmt::Display for USBDevice {
392    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393        // Get the device type name
394        let type_name = match usb_device_get_type_name(self.kind) {
395            Ok(name) => name,
396            Err(_) => "Unknown".to_string(),
397        };
398
399        // Format: "Type: Device Name: port/path"
400        write!(f, "{}: {}", type_name, self.name)?;
401
402        if let Some(port) = &self.port {
403            write!(f, ": {port}")?;
404        } else if let Some(path) = &self.path {
405            write!(f, ": {path}")?;
406        }
407
408        Ok(())
409    }
410}
411
412#[derive(Debug, Clone)]
413pub struct FreeWiliDevice {
414    /// Raw handle to the C library device structure
415    handle: Rc<FreeWiliDeviceHandle>,
416}
417
418#[derive(Debug)]
419struct FreeWiliDeviceHandle {
420    ptr: *mut fw_freewili_device_t,
421}
422
423impl Drop for FreeWiliDeviceHandle {
424    fn drop(&mut self) {
425        let _res: ffi::fw_error_t = unsafe { ffi::fw_device_free(&mut self.ptr, 1) };
426        if _res != ffi::_fw_error_t::fw_error_success as u32 {
427            eprintln!("Failed to free FreeWili device handle: {_res:?}");
428        }
429    }
430}
431
432impl Default for FreeWiliDevice {
433    fn default() -> Self {
434        FreeWiliDevice {
435            handle: Rc::new(FreeWiliDeviceHandle {
436                ptr: std::ptr::null_mut(),
437            }),
438        }
439    }
440}
441
442impl FreeWiliDevice {
443    /// Get or create a device handle for the given pointer
444    /// This ensures that the same physical device always gets the same Rc<FreeWiliDeviceHandle>
445    fn get_or_create_handle(ptr: *mut fw_freewili_device_t) -> Rc<FreeWiliDeviceHandle> {
446        DEVICE_REGISTRY.with(|registry| {
447            let mut registry = registry.borrow_mut();
448
449            // Check if we already have a handle for this device pointer
450            if let Some(existing_handle) = registry.get(&ptr) {
451                return existing_handle.clone();
452            }
453
454            // Create a new handle and store it in the registry
455            let new_handle = Rc::new(FreeWiliDeviceHandle { ptr });
456            registry.insert(ptr, new_handle.clone());
457            new_handle
458        })
459    }
460
461    /// Find all connected FreeWili devices.
462    pub fn find_all() -> Result<Vec<FreeWiliDevice>> {
463        const MAX_DEVICE_COUNT: u32 = 255;
464        let mut device_count: u32 = MAX_DEVICE_COUNT;
465        let mut devices: [*mut fw_freewili_device_t; MAX_DEVICE_COUNT as usize] =
466            [std::ptr::null_mut(); MAX_DEVICE_COUNT as usize];
467        let mut error_msg = vec![0u8; 1024];
468        let mut error_size: u32 = error_msg.len() as u32;
469
470        let res: fw_error_t = unsafe {
471            ffi::fw_device_find_all(
472                devices.as_mut_ptr(),
473                &mut device_count,
474                error_msg.as_mut_ptr() as *mut i8,
475                &mut error_size,
476            )
477        };
478        match res {
479            x if x == ffi::_fw_error_t::fw_error_success as ffi::fw_error_t => {}
480            x if x == ffi::_fw_error_t::fw_error_internal_error as ffi::fw_error_t => {
481                let error_str = unsafe { CStr::from_ptr(error_msg.as_ptr() as *const c_char) }
482                    .to_string_lossy()
483                    .into_owned();
484                return Err(FreeWiliError::InternalError(Some(error_str)));
485            }
486            _ => return Err(res.into()),
487        }
488
489        let mut device_handles = Vec::with_capacity(device_count as usize);
490        for i in 0..device_count {
491            let device_ptr = devices[i as usize];
492
493            // Get or create a shared handle for this device
494            let handle = Self::get_or_create_handle(device_ptr);
495
496            device_handles.push(FreeWiliDevice { handle });
497        }
498        Ok(device_handles)
499    }
500
501    pub fn device_type(&self) -> Result<DeviceType> {
502        let mut device_type: ffi::fw_devicetype_t = 0;
503        let res = unsafe { ffi::fw_device_get_type(self.handle.ptr, &mut device_type) };
504        if res != ffi::_fw_error_t::fw_error_success as u32 {
505            return Err(res.into());
506        }
507
508        Ok(device_type.into())
509    }
510
511    pub fn device_type_name(&self) -> Result<String> {
512        let device_type = self.device_type()? as ffi::fw_devicetype_t;
513
514        device_type_name(device_type)
515    }
516
517    fn get_device_string(&self, string_type: ffi::_fw_stringtype_t) -> Result<String> {
518        let mut buffer = vec![0u8; 1024];
519        let mut buffer_size = buffer.len() as u32;
520
521        let res: fw_error_t = unsafe {
522            ffi::fw_device_get_str(
523                self.handle.ptr,
524                string_type as u32,
525                buffer.as_mut_ptr() as *mut c_char,
526                &mut buffer_size,
527            )
528        };
529        if res != ffi::_fw_error_t::fw_error_success as u32 {
530            return Err(res.into());
531        }
532
533        let cstr = unsafe { CStr::from_ptr(buffer.as_ptr() as *const c_char) };
534        Ok(cstr.to_string_lossy().into_owned())
535    }
536
537    pub fn name(&self) -> Result<String> {
538        self.get_device_string(ffi::_fw_stringtype_t::fw_stringtype_name)
539    }
540
541    pub fn serial(&self) -> Result<String> {
542        self.get_device_string(ffi::_fw_stringtype_t::fw_stringtype_serial)
543    }
544
545    pub fn unique_id(&self) -> Result<u64> {
546        let mut unique_id: u64 = 0;
547        let res = unsafe { ffi::fw_device_unique_id(self.handle.ptr, &mut unique_id as *mut u64) };
548        if res != ffi::_fw_error_t::fw_error_success as u32 {
549            return Err(res.into());
550        }
551        Ok(unique_id)
552    }
553
554    pub fn standalone(&self) -> Result<bool> {
555        let mut is_standalone: bool = false;
556        let res = unsafe {
557            ffi::fw_device_is_standalone(self.handle.ptr, &mut is_standalone as *mut bool)
558        };
559        if res != ffi::_fw_error_t::fw_error_success as u32 {
560            return Err(res.into());
561        }
562        Ok(is_standalone)
563    }
564
565    pub fn usb_device_get_string(&self, string_type: ffi::_fw_stringtype_t) -> Result<String> {
566        let mut buffer = vec![0u8; 1024];
567        let mut buffer_size = buffer.len() as u32;
568
569        let res: fw_error_t = unsafe {
570            ffi::fw_usb_device_get_str(
571                self.handle.ptr,
572                string_type as u32,
573                buffer.as_mut_ptr() as *mut c_char,
574                &mut buffer_size,
575            )
576        };
577        if res != ffi::_fw_error_t::fw_error_success as u32 {
578            return Err(res.into());
579        }
580
581        let cstr = unsafe { CStr::from_ptr(buffer.as_ptr() as *const c_char) };
582        Ok(cstr.to_string_lossy().into_owned())
583    }
584
585    pub fn get_usb_devices(&self) -> Result<Vec<USBDevice>> {
586        let res = unsafe { ffi::fw_usb_device_begin(self.handle.ptr) };
587        if res != ffi::_fw_error_t::fw_error_success as u32 {
588            return Err(res.into());
589        }
590
591        let mut devices = Vec::new();
592        loop {
593            let usb_device = unsafe { USBDevice::from_device(self.handle.ptr)? };
594            devices.push(usb_device);
595
596            let res = unsafe { ffi::fw_usb_device_next(self.handle.ptr) };
597            if res != ffi::_fw_error_t::fw_error_success as u32 {
598                break;
599            }
600        }
601        Ok(devices)
602    }
603
604    pub fn get_main_usb_device(&self) -> Result<USBDevice> {
605        let mut error_msg = vec![0u8; 1024];
606        let mut error_size: u32 = error_msg.len() as u32;
607        let res = unsafe {
608            ffi::fw_usb_device_set(
609                self.handle.ptr,
610                ffi::_fw_usbdevice_iter_set_t::fw_usbdevice_iter_main
611                    as ffi::fw_usbdevice_iter_set_t,
612                error_msg.as_mut_ptr() as *mut i8,
613                &mut error_size,
614            )
615        };
616        if res == ffi::_fw_error_t::fw_error_internal_error as fw_error_t {
617            let error_str = unsafe { CStr::from_ptr(error_msg.as_ptr() as *const c_char) }
618                .to_string_lossy()
619                .into_owned();
620            return Err(FreeWiliError::InternalError(Some(error_str)));
621        }
622        if res != ffi::_fw_error_t::fw_error_success as fw_error_t {
623            return Err(res.into());
624        }
625        unsafe { USBDevice::from_device(self.handle.ptr) }
626    }
627
628    pub fn get_display_usb_device(&self) -> Result<USBDevice> {
629        let mut error_msg = vec![0u8; 1024];
630        let mut error_size: u32 = error_msg.len() as u32;
631        let res = unsafe {
632            ffi::fw_usb_device_set(
633                self.handle.ptr,
634                ffi::_fw_usbdevice_iter_set_t::fw_usbdevice_iter_display
635                    as ffi::fw_usbdevice_iter_set_t,
636                error_msg.as_mut_ptr() as *mut i8,
637                &mut error_size,
638            )
639        };
640        if res == ffi::_fw_error_t::fw_error_internal_error as fw_error_t {
641            let error_str = unsafe { CStr::from_ptr(error_msg.as_ptr() as *const c_char) }
642                .to_string_lossy()
643                .into_owned();
644            return Err(FreeWiliError::InternalError(Some(error_str)));
645        }
646        if res != ffi::_fw_error_t::fw_error_success as fw_error_t {
647            return Err(res.into());
648        }
649        unsafe { USBDevice::from_device(self.handle.ptr) }
650    }
651
652    pub fn get_fpga_usb_device(&self) -> Result<USBDevice> {
653        let mut error_msg = vec![0u8; 1024];
654        let mut error_size: u32 = error_msg.len() as u32;
655        let res = unsafe {
656            ffi::fw_usb_device_set(
657                self.handle.ptr,
658                ffi::_fw_usbdevice_iter_set_t::fw_usbdevice_iter_fpga
659                    as ffi::fw_usbdevice_iter_set_t,
660                error_msg.as_mut_ptr() as *mut i8,
661                &mut error_size,
662            )
663        };
664        if res == ffi::_fw_error_t::fw_error_internal_error as fw_error_t {
665            let error_str = unsafe { CStr::from_ptr(error_msg.as_ptr() as *const c_char) }
666                .to_string_lossy()
667                .into_owned();
668            return Err(FreeWiliError::InternalError(Some(error_str)));
669        }
670        if res != ffi::_fw_error_t::fw_error_success as fw_error_t {
671            return Err(res.into());
672        }
673        unsafe { USBDevice::from_device(self.handle.ptr) }
674    }
675
676    pub fn get_hub_usb_device(&self) -> Result<USBDevice> {
677        let mut error_msg = vec![0u8; 1024];
678        let mut error_size: u32 = error_msg.len() as u32;
679        let res = unsafe {
680            ffi::fw_usb_device_set(
681                self.handle.ptr,
682                ffi::_fw_usbdevice_iter_set_t::fw_usbdevice_iter_hub
683                    as ffi::fw_usbdevice_iter_set_t,
684                error_msg.as_mut_ptr() as *mut i8,
685                &mut error_size,
686            )
687        };
688        if res == ffi::_fw_error_t::fw_error_internal_error as fw_error_t {
689            let error_str = unsafe { CStr::from_ptr(error_msg.as_ptr() as *const c_char) }
690                .to_string_lossy()
691                .into_owned();
692            return Err(FreeWiliError::InternalError(Some(error_str)));
693        }
694        if res != ffi::_fw_error_t::fw_error_success as fw_error_t {
695            return Err(res.into());
696        }
697        unsafe { USBDevice::from_device(self.handle.ptr) }
698    }
699}
700
701pub fn device_type_name(device_type: impl Into<DeviceType>) -> Result<String> {
702    let device_type = device_type.into() as ffi::fw_devicetype_t;
703
704    let mut device_type_name = vec![0i8; 1024];
705    let mut device_type_name_size: u32 = device_type_name.len() as u32;
706    let res = unsafe {
707        ffi::fw_device_get_type_name(
708            device_type,
709            device_type_name.as_mut_ptr(),
710            &mut device_type_name_size,
711        )
712    };
713    if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t {
714        return Err(res.into());
715    }
716
717    let cstr = unsafe { CStr::from_ptr(device_type_name.as_ptr() as *const c_char) };
718    Ok(cstr.to_string_lossy().into_owned())
719}
720
721pub fn usb_device_get_type_name(usb_device_type: impl Into<UsbDeviceType>) -> Result<String> {
722    let device_type = usb_device_type.into() as ffi::fw_devicetype_t;
723
724    let mut usb_device_type_name = vec![0i8; 1024];
725    let mut usb_device_type_name_size: u32 = usb_device_type_name.len() as u32;
726    let res = unsafe {
727        ffi::fw_usb_device_get_type_name(
728            device_type,
729            usb_device_type_name.as_mut_ptr(),
730            &mut usb_device_type_name_size,
731        )
732    };
733    if res != ffi::_fw_error_t::fw_error_success as ffi::fw_error_t {
734        return Err(res.into());
735    }
736
737    let cstr = unsafe { CStr::from_ptr(usb_device_type_name.as_ptr() as *const c_char) };
738    Ok(cstr.to_string_lossy().into_owned())
739}
740
741impl fmt::Display for FreeWiliDevice {
742    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743        write!(
744            f,
745            "{} {}",
746            self.name().unwrap_or("Unknown".to_string()),
747            self.serial().unwrap_or("Unknown".to_string())
748        )
749    }
750}
751
752#[cfg(test)]
753mod tests {
754    use super::*;
755
756    #[test]
757    fn test_basic_functionality() -> Result<()> {
758        let devices = FreeWiliDevice::find_all()?;
759
760        for (i, device) in devices.iter().enumerate() {
761            let dev_type = device.device_type()?;
762            let type_name = device.device_type_name()?;
763            let name = device.name()?;
764            let serial = device.serial()?;
765            let unique_id = device.unique_id()?;
766            let standalone = device.standalone()?;
767            let usb_devices = device.get_usb_devices()?;
768
769            println!("{}. Found device: {}", i + 1, device);
770            println!("\ttype: {:?}", dev_type);
771            println!("\ttype name: {}", type_name);
772            println!("\tname: {}", name);
773            println!("\tserial: {}", serial);
774            println!("\tunique ID: {}", unique_id);
775            println!("\tstandalone: {}", standalone);
776            println!("\tUSB devices ({}):", usb_devices.len());
777
778            for (count, usb_device) in usb_devices.iter().enumerate() {
779                println!("\t\t{}: {}", count + 1, usb_device.kind_name);
780                println!("\t\t\tname: {}", usb_device.name);
781                println!("\t\t\tserial: {}", usb_device.serial);
782                println!("\t\t\tVID: {} PID: {}", usb_device.vid, usb_device.pid);
783                println!("\t\t\tlocation: {}", usb_device.location);
784                println!("\t\t\tport chain: {:?}", usb_device.port_chain);
785                if let Some(path) = &usb_device.path {
786                    println!("\t\t\tpath: {}", path);
787                }
788                if let Some(port) = &usb_device.port {
789                    println!("\t\t\tport: {}", port);
790                }
791            }
792
793            // Try to get specific USB devices - these may fail, so handle gracefully
794            if let Ok(main_usb_device) = device.get_main_usb_device() {
795                println!("\tMain USB device: {}", main_usb_device.kind_name);
796            } else {
797                println!("\tNo main USB device found");
798            }
799
800            if let Ok(display_usb_device) = device.get_display_usb_device() {
801                println!("\tDisplay USB device: {}", display_usb_device.kind_name);
802            } else {
803                println!("\tNo display USB device found");
804            }
805
806            if let Ok(fpga_usb_device) = device.get_fpga_usb_device() {
807                println!("\tFPGA USB device: {}", fpga_usb_device.kind_name);
808            } else {
809                println!("\tNo FPGA USB device found");
810            }
811
812            if let Ok(hub_usb_device) = device.get_hub_usb_device() {
813                println!("\tHUB USB device: {}", hub_usb_device.kind_name);
814            } else {
815                println!("\tNo HUB USB device found");
816            }
817        }
818
819        Ok(())
820    }
821
822    #[test]
823    fn test_handle_copy() -> Result<()> {
824        let devices = FreeWiliDevice::find_all()?;
825
826        if devices.is_empty() {
827            println!("No devices found, skipping handle copy test.");
828            return Ok(());
829        }
830
831        let first_device = devices[0].clone();
832        let devices = FreeWiliDevice::find_all()?;
833
834        first_device.device_type()?;
835        let first_device = first_device;
836        for device in devices.clone() {
837            if device.unique_id()? == first_device.unique_id()? {
838                let _ = device;
839            }
840        }
841        for device in devices.clone() {
842            device.device_type()?;
843        }
844
845        let mut device = &devices[0];
846
847        device.device_type()?;
848
849        let devices = FreeWiliDevice::find_all()?;
850
851        device = &devices[0];
852
853        device.device_type()?;
854
855        Ok(())
856    }
857}