ledger_lib/
info.rs

1//! Device information types and connection filters
2
3use std::collections::BTreeMap;
4
5use strum::{Display, EnumString};
6use uuid::{uuid, Uuid};
7
8use crate::Filters;
9
10use super::transport;
11
12/// Ledger device information
13#[derive(Clone, PartialEq, Debug)]
14pub struct LedgerInfo {
15    /// Device Model
16    pub model: Model,
17
18    /// Device connection information
19    pub conn: ConnInfo,
20}
21
22impl std::fmt::Display for LedgerInfo {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        write!(f, "{} ({})", self.model, self.conn)
25    }
26}
27
28impl LedgerInfo {
29    /// Fetch connection kind enumeration
30    pub fn kind(&self) -> ConnType {
31        match &self.conn {
32            #[cfg(feature = "transport_usb")]
33            ConnInfo::Usb(_) => ConnType::Usb,
34            #[cfg(feature = "transport_tcp")]
35            ConnInfo::Tcp(_) => ConnType::Tcp,
36            #[cfg(feature = "transport_ble")]
37            ConnInfo::Ble(_) => ConnType::Ble,
38        }
39    }
40}
41
42/// Ledger device models
43#[derive(Copy, Clone, PartialEq, Debug, Display, EnumString)]
44pub enum Model {
45    /// Nano S
46    NanoS,
47    /// Nano S Plus
48    NanoSPlus,
49    /// Nano X
50    NanoX,
51    /// Stax
52    Stax,
53    /// Flex
54    Flex,
55    /// Nano Gen5
56    NanoGen5,
57    /// Unknown model
58    Unknown { usb_pid: Option<u16> },
59}
60
61impl Model {
62    /// Convert a USB PID to a [Model] kind
63    pub fn from_usb_pid(usb_pid: u16) -> Model {
64        INTERNAL_DEVICE_INFOS
65            .iter()
66            .find_map(|device_info| {
67                device_info
68                    .matches_usb_pid(usb_pid)
69                    .then_some(device_info.model)
70            })
71            .unwrap_or(Model::Unknown {
72                usb_pid: Some(usb_pid),
73            })
74    }
75}
76
77#[derive(Clone, PartialEq, Debug)]
78pub struct BleSpec {
79    pub service_uuid: Uuid,
80    pub notify_uuid: Uuid,
81    pub write_uuid: Uuid,
82    pub write_cmd_uuid: Uuid,
83}
84
85struct InternalDeviceInfo {
86    model: Model,
87    legacy_usb_product_id: u16,
88    product_id_mm: u16,
89    ble_specs: Vec<BleSpec>,
90}
91
92impl InternalDeviceInfo {
93    fn matches_usb_pid(&self, usb_pid: u16) -> bool {
94        // First compare the passed pid with the legacy product id, if that doesn't match, use product_id_mm.
95        // The logic was taken from here:
96        // https://github.com/LedgerHQ/ledger-live/blob/b870b8018319b7489c39c92e743adcfd4e33e948/libs/ledgerjs/packages/devices/src/index.ts#L190
97        // (the legacy product id match will probably only work for some early variants of NanoS, but it's
98        // still better to be consistent with Ledger Live, just in case).
99        usb_pid == self.legacy_usb_product_id || usb_pid >> 8 == self.product_id_mm
100    }
101}
102
103// The table was taken from here:
104// https://github.com/LedgerHQ/ledger-live/blob/b870b8018319b7489c39c92e743adcfd4e33e948/libs/ledgerjs/packages/devices/src/index.ts#L41
105lazy_static::lazy_static! {
106    static ref INTERNAL_DEVICE_INFOS: Vec<InternalDeviceInfo> = {
107        vec![
108            InternalDeviceInfo {
109                model: Model::NanoS,
110                legacy_usb_product_id: 0x0001,
111                product_id_mm: 0x10,
112                ble_specs: vec![],
113            },
114            InternalDeviceInfo {
115                model: Model::NanoX,
116                legacy_usb_product_id: 0x0004,
117                product_id_mm: 0x40,
118                ble_specs: vec![
119                    BleSpec {
120                        service_uuid: uuid!("13d63400-2c97-0004-0000-4c6564676572"),
121                        notify_uuid: uuid!("13d63400-2c97-0004-0001-4c6564676572"),
122                        write_uuid: uuid!("13d63400-2c97-0004-0002-4c6564676572"),
123                        write_cmd_uuid: uuid!("13d63400-2c97-0004-0003-4c6564676572"),
124                    },
125                ],
126            },
127            InternalDeviceInfo {
128                model: Model::NanoSPlus,
129                legacy_usb_product_id: 0x0005,
130                product_id_mm: 0x50,
131                ble_specs: vec![],
132            },
133            InternalDeviceInfo {
134                model: Model::Stax,
135                legacy_usb_product_id: 0x0006,
136                product_id_mm: 0x60,
137                ble_specs: vec![
138                    BleSpec {
139                        service_uuid: uuid!("13d63400-2c97-6004-0000-4c6564676572"),
140                        notify_uuid: uuid!("13d63400-2c97-6004-0001-4c6564676572"),
141                        write_uuid: uuid!("13d63400-2c97-6004-0002-4c6564676572"),
142                        write_cmd_uuid: uuid!("13d63400-2c97-6004-0003-4c6564676572"),
143                    },
144                ],
145            },
146            InternalDeviceInfo {
147                model: Model::Flex,
148                legacy_usb_product_id: 0x0007,
149                product_id_mm: 0x70,
150                ble_specs: vec![
151                    BleSpec {
152                        service_uuid: uuid!("13d63400-2c97-3004-0000-4c6564676572"),
153                        notify_uuid: uuid!("13d63400-2c97-3004-0001-4c6564676572"),
154                        write_uuid: uuid!("13d63400-2c97-3004-0002-4c6564676572"),
155                        write_cmd_uuid: uuid!("13d63400-2c97-3004-0003-4c6564676572"),
156                    },
157                ],
158            },
159            InternalDeviceInfo {
160                model: Model::NanoGen5,
161                legacy_usb_product_id: 0x0008,
162                product_id_mm: 0x80,
163                ble_specs: vec![
164                    BleSpec {
165                        service_uuid: uuid!("13d63400-2c97-8004-0000-4c6564676572"),
166                        notify_uuid: uuid!("13d63400-2c97-8004-0001-4c6564676572"),
167                        write_uuid: uuid!("13d63400-2c97-8004-0002-4c6564676572"),
168                        write_cmd_uuid: uuid!("13d63400-2c97-8004-0003-4c6564676572"),
169                    },
170                ],
171            },
172        ]
173    };
174
175    static ref BLE_SPECS_BY_SERVICE_UUID: BTreeMap<Uuid, &'static BleSpec> = {
176        INTERNAL_DEVICE_INFOS
177            .iter()
178            .flat_map(|dev_info| dev_info.ble_specs.iter())
179            .map(|ble_spec| (ble_spec.service_uuid, ble_spec))
180            .collect()
181    };
182
183    static ref INTERNAL_DEVICE_INFOS_BY_BLE_SERVICE_UUID: BTreeMap<Uuid, &'static InternalDeviceInfo> = {
184        INTERNAL_DEVICE_INFOS
185            .iter()
186            .flat_map(|dev_info| {
187                dev_info.ble_specs.iter().map(move |ble_spec| (ble_spec.service_uuid, dev_info))
188            })
189            .collect()
190    };
191}
192
193pub fn model_by_ble_service_uuid(service_uuid: &Uuid) -> Option<Model> {
194    INTERNAL_DEVICE_INFOS_BY_BLE_SERVICE_UUID
195        .get(service_uuid)
196        .map(|dev_info| dev_info.model)
197}
198
199pub fn ble_spec_by_service_uuid(service_uuid: &Uuid) -> Option<&'static BleSpec> {
200    BLE_SPECS_BY_SERVICE_UUID.get(service_uuid).copied()
201}
202
203/// Ledger connection information
204#[derive(Clone, PartialEq, Debug)]
205pub enum ConnInfo {
206    #[cfg(feature = "transport_usb")]
207    Usb(transport::UsbInfo),
208    #[cfg(feature = "transport_tcp")]
209    Tcp(transport::TcpInfo),
210    #[cfg(feature = "transport_ble")]
211    Ble(transport::BleInfo),
212}
213
214/// Ledger connection types
215#[derive(Copy, Clone, PartialEq, Debug)]
216pub enum ConnType {
217    Usb,
218    Tcp,
219    Ble,
220}
221
222impl From<ConnType> for Filters {
223    /// Convert a connection type to a discovery filter
224    fn from(value: ConnType) -> Self {
225        match value {
226            ConnType::Usb => Filters::Hid,
227            ConnType::Tcp => Filters::Tcp,
228            ConnType::Ble => Filters::Ble,
229        }
230    }
231}
232
233impl std::fmt::Display for ConnInfo {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        match self {
236            #[cfg(feature = "transport_usb")]
237            Self::Usb(i) => write!(f, "HID {}", i),
238            #[cfg(feature = "transport_tcp")]
239            Self::Tcp(i) => write!(f, "TCP {}", i),
240            #[cfg(feature = "transport_ble")]
241            Self::Ble(i) => write!(f, "BLE {}", i),
242        }
243    }
244}
245
246#[cfg(feature = "transport_usb")]
247impl From<transport::UsbInfo> for ConnInfo {
248    fn from(value: transport::UsbInfo) -> Self {
249        Self::Usb(value)
250    }
251}
252
253#[cfg(feature = "transport_tcp")]
254impl From<transport::TcpInfo> for ConnInfo {
255    fn from(value: transport::TcpInfo) -> Self {
256        Self::Tcp(value)
257    }
258}
259
260#[cfg(feature = "transport_ble")]
261impl From<transport::BleInfo> for ConnInfo {
262    fn from(value: transport::BleInfo) -> Self {
263        Self::Ble(value)
264    }
265}
266
267/// Application info object
268#[derive(Debug, Clone, PartialEq)]
269pub struct AppInfo {
270    pub name: String,
271    pub version: String,
272    pub flags: ledger_proto::apdus::AppFlags,
273}
274
275/// Device info object
276#[derive(Debug, Clone, PartialEq)]
277pub struct DeviceInfo {
278    pub target_id: [u8; 4],
279    pub se_version: String,
280    pub mcu_version: String,
281    pub flags: Vec<u8>,
282}