bose_dfu/
device_ids.rs

1use std::fmt::Display;
2
3const BOSE_VID: u16 = 0x05a7;
4const BOSE_HID_USAGE_PAGE: u16 = 0xff00;
5
6const COMPATIBLE_DEVICES: &[DeviceIds] = &[
7    // Bose Color II SoundLink
8    bose_dev(0x40fe, 0x400d),
9];
10
11// Use UsbId instead of DeviceIds since some incompatible devices don't have a concept of DFU mode.
12const INCOMPATIBLE_DEVICES: &[UsbId] = &[
13    // Bose Noise Cancelling Headphones 700
14    bose_pid(0x40fc),
15];
16
17const fn bose_dev(normal_pid: u16, dfu_pid: u16) -> DeviceIds {
18    DeviceIds {
19        normal_mode: UsbId {
20            vid: BOSE_VID,
21            pid: normal_pid,
22        },
23        dfu_mode: UsbId {
24            vid: BOSE_VID,
25            pid: dfu_pid,
26        },
27    }
28}
29
30const fn bose_pid(pid: u16) -> UsbId {
31    UsbId { vid: BOSE_VID, pid }
32}
33
34/// Find a device's compatibility and mode based on its USB ID.
35pub fn identify_device(id: UsbId, usage_page: u16) -> DeviceCompat {
36    // On macOS, Windows, and Linux/hidraw, each usage page is exposed as a separate device and we
37    // only want the DFU one. On Linux/libusb, all pages are one device and usage_page() is 0.
38    if ![0, BOSE_HID_USAGE_PAGE].contains(&usage_page) {
39        return DeviceCompat::Incompatible;
40    }
41
42    // See if the device is known to us.
43    for candidate in COMPATIBLE_DEVICES {
44        if let Some(mode) = candidate.match_id(id) {
45            return DeviceCompat::Compatible(mode);
46        }
47    }
48
49    // Next, see if it's known to be incompatible.
50    if INCOMPATIBLE_DEVICES.contains(&id) {
51        return DeviceCompat::Incompatible;
52    }
53
54    // If not, mark it as untested if it has Bose's VID.
55    if id.vid == BOSE_VID {
56        DeviceCompat::Untested(DeviceMode::Unknown)
57    } else {
58        DeviceCompat::Incompatible
59    }
60}
61
62/// Compatibility of a device, with detected mode if applicable.
63pub enum DeviceCompat {
64    /// Known to speak the Bose DFU protocol. Usable by default.
65    Compatible(DeviceMode),
66    /// May speak the Bose DFU protocol but has not been tested. Usable with `--force` flag. Mode
67    /// currently always [DeviceMode::Unknown], but that may change if we find a non-PID way to
68    /// identify different modes (e.g. parsing the HID descriptor).
69    Untested(DeviceMode),
70    /// Definitely does not speak the Bose DFU protocol. Treated as if it doesn't exist.
71    Incompatible,
72}
73
74impl Display for DeviceCompat {
75    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
76        match self {
77            DeviceCompat::Compatible(mode) => write!(f, "compatible device in {} mode", mode),
78            DeviceCompat::Untested(mode) => write!(f, "UNTESTED device in {} mode", mode),
79            DeviceCompat::Incompatible => write!(f, "incompatible device"),
80        }
81    }
82}
83
84#[derive(Copy, Clone, Debug)]
85struct DeviceIds {
86    normal_mode: UsbId,
87    dfu_mode: UsbId,
88}
89
90impl DeviceIds {
91    /// If one of our modes uses with the given ID, return it. Otherwise, return [None].
92    fn match_id(&self, id: UsbId) -> Option<DeviceMode> {
93        if id == self.normal_mode {
94            Some(DeviceMode::Normal)
95        } else if id == self.dfu_mode {
96            Some(DeviceMode::Dfu)
97        } else {
98            None
99        }
100    }
101}
102
103/// Modes a device can be in. Can be unknown.
104#[derive(Copy, Clone, Debug, Eq, PartialEq)]
105pub enum DeviceMode {
106    Normal,
107    Dfu,
108    Unknown,
109}
110
111impl Display for DeviceMode {
112    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
113        match self {
114            DeviceMode::Normal => write!(f, "normal"),
115            DeviceMode::Dfu => write!(f, "DFU"),
116            DeviceMode::Unknown => write!(f, "unknown"),
117        }
118    }
119}
120
121/// A USB vendor ID and product ID pair.
122#[derive(Copy, Clone, Debug, Eq, PartialEq)]
123pub struct UsbId {
124    pub vid: u16,
125    pub pid: u16,
126}
127
128impl Display for UsbId {
129    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
130        write!(f, "{:04x}:{:04x}", self.vid, self.pid)
131    }
132}