solana_remote_wallet/
locator.rs

1use {
2    solana_pubkey::{ParsePubkeyError, Pubkey},
3    std::{
4        convert::{Infallible, TryFrom, TryInto},
5        str::FromStr,
6    },
7    thiserror::Error,
8    uriparse::{URIReference, URIReferenceBuilder, URIReferenceError},
9};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Manufacturer {
13    Unknown,
14    Ledger,
15}
16
17impl Default for Manufacturer {
18    fn default() -> Self {
19        Self::Unknown
20    }
21}
22
23const MANUFACTURER_UNKNOWN: &str = "unknown";
24const MANUFACTURER_LEDGER: &str = "ledger";
25
26#[derive(Clone, Debug, Error, PartialEq, Eq)]
27#[error("not a manufacturer")]
28pub struct ManufacturerError;
29
30impl From<Infallible> for ManufacturerError {
31    fn from(_: Infallible) -> Self {
32        ManufacturerError
33    }
34}
35
36impl FromStr for Manufacturer {
37    type Err = ManufacturerError;
38    fn from_str(s: &str) -> Result<Self, Self::Err> {
39        let s = s.to_ascii_lowercase();
40        match s.as_str() {
41            MANUFACTURER_LEDGER => Ok(Self::Ledger),
42            _ => Err(ManufacturerError),
43        }
44    }
45}
46
47impl TryFrom<&str> for Manufacturer {
48    type Error = ManufacturerError;
49    fn try_from(s: &str) -> Result<Self, Self::Error> {
50        Manufacturer::from_str(s)
51    }
52}
53
54impl AsRef<str> for Manufacturer {
55    fn as_ref(&self) -> &str {
56        match self {
57            Self::Unknown => MANUFACTURER_UNKNOWN,
58            Self::Ledger => MANUFACTURER_LEDGER,
59        }
60    }
61}
62
63impl std::fmt::Display for Manufacturer {
64    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
65        let s: &str = self.as_ref();
66        write!(f, "{s}")
67    }
68}
69
70#[derive(Clone, Debug, Error, PartialEq, Eq)]
71pub enum LocatorError {
72    #[error(transparent)]
73    ManufacturerError(#[from] ManufacturerError),
74    #[error(transparent)]
75    PubkeyError(#[from] ParsePubkeyError),
76    #[error(transparent)]
77    UriReferenceError(#[from] URIReferenceError),
78    #[error("unimplemented scheme")]
79    UnimplementedScheme,
80    #[error("infallible")]
81    Infallible,
82}
83
84impl From<Infallible> for LocatorError {
85    fn from(_: Infallible) -> Self {
86        Self::Infallible
87    }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
91pub struct Locator {
92    pub manufacturer: Manufacturer,
93    pub pubkey: Option<Pubkey>,
94}
95
96impl std::fmt::Display for Locator {
97    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
98        let maybe_path = self.pubkey.map(|p| p.to_string());
99        let path = maybe_path.as_deref().unwrap_or("/");
100
101        let mut builder = URIReferenceBuilder::new();
102        builder
103            .try_scheme(Some("usb"))
104            .unwrap()
105            .try_authority(Some(self.manufacturer.as_ref()))
106            .unwrap()
107            .try_path(path)
108            .unwrap();
109
110        let uri = builder.build().unwrap();
111        write!(f, "{uri}")
112    }
113}
114
115impl Locator {
116    pub fn new_from_path<P: AsRef<str>>(path: P) -> Result<Self, LocatorError> {
117        let path = path.as_ref();
118        let uri = URIReference::try_from(path)?;
119        Self::new_from_uri(&uri)
120    }
121
122    pub fn new_from_uri(uri: &URIReference<'_>) -> Result<Self, LocatorError> {
123        let scheme = uri.scheme().map(|s| s.as_str().to_ascii_lowercase());
124        let host = uri.host().map(|h| h.to_string());
125        match (scheme, host) {
126            (Some(scheme), Some(host)) if scheme == "usb" => {
127                let path = uri.path().segments().first().and_then(|s| {
128                    if !s.is_empty() {
129                        Some(s.as_str())
130                    } else {
131                        None
132                    }
133                });
134                Self::new_from_parts(host.as_str(), path)
135            }
136            (Some(_scheme), Some(_host)) => Err(LocatorError::UnimplementedScheme),
137            (None, Some(_host)) => Err(LocatorError::UnimplementedScheme),
138            (_, None) => Err(LocatorError::ManufacturerError(ManufacturerError)),
139        }
140    }
141
142    pub fn new_from_parts<V, VE, P, PE>(
143        manufacturer: V,
144        pubkey: Option<P>,
145    ) -> Result<Self, LocatorError>
146    where
147        VE: Into<LocatorError>,
148        V: TryInto<Manufacturer, Error = VE>,
149        PE: Into<LocatorError>,
150        P: TryInto<Pubkey, Error = PE>,
151    {
152        let manufacturer = manufacturer.try_into().map_err(|e| e.into())?;
153        let pubkey = if let Some(pubkey) = pubkey {
154            Some(pubkey.try_into().map_err(|e| e.into())?)
155        } else {
156            None
157        };
158        Ok(Self {
159            manufacturer,
160            pubkey,
161        })
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use {super::*, assert_matches::assert_matches};
168
169    #[test]
170    fn test_manufacturer() {
171        assert_eq!(MANUFACTURER_LEDGER.try_into(), Ok(Manufacturer::Ledger));
172        assert!(
173            matches!(Manufacturer::from_str(MANUFACTURER_LEDGER), Ok(v) if v == Manufacturer::Ledger)
174        );
175        assert_eq!(Manufacturer::Ledger.as_ref(), MANUFACTURER_LEDGER);
176
177        assert!(
178            matches!(Manufacturer::from_str("bad-manufacturer"), Err(e) if e == ManufacturerError)
179        );
180    }
181
182    #[test]
183    fn test_locator_new_from_parts() {
184        let manufacturer = Manufacturer::Ledger;
185        let manufacturer_str = "ledger";
186        let pubkey = Pubkey::new_unique();
187        let pubkey_str = pubkey.to_string();
188
189        let expect = Locator {
190            manufacturer,
191            pubkey: None,
192        };
193        assert_matches!(
194            Locator::new_from_parts(manufacturer, None::<Pubkey>),
195            Ok(e) if e == expect
196        );
197        assert_matches!(
198            Locator::new_from_parts(manufacturer_str, None::<Pubkey>),
199            Ok(e) if e == expect
200        );
201
202        let expect = Locator {
203            manufacturer,
204            pubkey: Some(pubkey),
205        };
206        assert_matches!(
207            Locator::new_from_parts(manufacturer, Some(pubkey)),
208            Ok(e) if e == expect
209        );
210        assert_matches!(
211            Locator::new_from_parts(manufacturer_str, Some(pubkey_str.as_str())),
212            Ok(e) if e == expect
213        );
214
215        assert_matches!(
216            Locator::new_from_parts("bad-manufacturer", None::<Pubkey>),
217            Err(LocatorError::ManufacturerError(e)) if e == ManufacturerError
218        );
219        assert_matches!(
220            Locator::new_from_parts(manufacturer, Some("bad-pubkey")),
221            Err(LocatorError::PubkeyError(e)) if e == ParsePubkeyError::Invalid
222        );
223    }
224
225    #[test]
226    fn test_locator_new_from_uri() {
227        let manufacturer = Manufacturer::Ledger;
228        let pubkey = Pubkey::new_unique();
229        let pubkey_str = pubkey.to_string();
230
231        // usb://ledger/{PUBKEY}?key=0/0
232        let mut builder = URIReferenceBuilder::new();
233        builder
234            .try_scheme(Some("usb"))
235            .unwrap()
236            .try_authority(Some(Manufacturer::Ledger.as_ref()))
237            .unwrap()
238            .try_path(pubkey_str.as_str())
239            .unwrap()
240            .try_query(Some("key=0/0"))
241            .unwrap();
242        let uri = builder.build().unwrap();
243        let expect = Locator {
244            manufacturer,
245            pubkey: Some(pubkey),
246        };
247        assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
248
249        // usb://ledger/{PUBKEY}
250        let mut builder = URIReferenceBuilder::new();
251        builder
252            .try_scheme(Some("usb"))
253            .unwrap()
254            .try_authority(Some(Manufacturer::Ledger.as_ref()))
255            .unwrap()
256            .try_path(pubkey_str.as_str())
257            .unwrap();
258        let uri = builder.build().unwrap();
259        let expect = Locator {
260            manufacturer,
261            pubkey: Some(pubkey),
262        };
263        assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
264
265        // usb://ledger
266        let mut builder = URIReferenceBuilder::new();
267        builder
268            .try_scheme(Some("usb"))
269            .unwrap()
270            .try_authority(Some(Manufacturer::Ledger.as_ref()))
271            .unwrap()
272            .try_path("")
273            .unwrap();
274        let uri = builder.build().unwrap();
275        let expect = Locator {
276            manufacturer,
277            pubkey: None,
278        };
279        assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
280
281        // usb://ledger/
282        let mut builder = URIReferenceBuilder::new();
283        builder
284            .try_scheme(Some("usb"))
285            .unwrap()
286            .try_authority(Some(Manufacturer::Ledger.as_ref()))
287            .unwrap()
288            .try_path("/")
289            .unwrap();
290        let uri = builder.build().unwrap();
291        let expect = Locator {
292            manufacturer,
293            pubkey: None,
294        };
295        assert_eq!(Locator::new_from_uri(&uri), Ok(expect));
296
297        // bad-scheme://ledger
298        let mut builder = URIReferenceBuilder::new();
299        builder
300            .try_scheme(Some("bad-scheme"))
301            .unwrap()
302            .try_authority(Some(Manufacturer::Ledger.as_ref()))
303            .unwrap()
304            .try_path("")
305            .unwrap();
306        let uri = builder.build().unwrap();
307        assert_eq!(
308            Locator::new_from_uri(&uri),
309            Err(LocatorError::UnimplementedScheme)
310        );
311
312        // usb://bad-manufacturer
313        let mut builder = URIReferenceBuilder::new();
314        builder
315            .try_scheme(Some("usb"))
316            .unwrap()
317            .try_authority(Some("bad-manufacturer"))
318            .unwrap()
319            .try_path("")
320            .unwrap();
321        let uri = builder.build().unwrap();
322        assert_eq!(
323            Locator::new_from_uri(&uri),
324            Err(LocatorError::ManufacturerError(ManufacturerError))
325        );
326
327        // usb://ledger/bad-pubkey
328        let mut builder = URIReferenceBuilder::new();
329        builder
330            .try_scheme(Some("usb"))
331            .unwrap()
332            .try_authority(Some(Manufacturer::Ledger.as_ref()))
333            .unwrap()
334            .try_path("bad-pubkey")
335            .unwrap();
336        let uri = builder.build().unwrap();
337        assert_eq!(
338            Locator::new_from_uri(&uri),
339            Err(LocatorError::PubkeyError(ParsePubkeyError::Invalid))
340        );
341    }
342
343    #[test]
344    fn test_locator_new_from_path() {
345        let manufacturer = Manufacturer::Ledger;
346        let pubkey = Pubkey::new_unique();
347        let path = format!("usb://ledger/{pubkey}?key=0/0");
348        Locator::new_from_path(path).unwrap();
349
350        // usb://ledger/{PUBKEY}?key=0'/0'
351        let path = format!("usb://ledger/{pubkey}?key=0'/0'");
352        let expect = Locator {
353            manufacturer,
354            pubkey: Some(pubkey),
355        };
356        assert_eq!(Locator::new_from_path(path), Ok(expect));
357
358        // usb://ledger/{PUBKEY}
359        let path = format!("usb://ledger/{pubkey}");
360        let expect = Locator {
361            manufacturer,
362            pubkey: Some(pubkey),
363        };
364        assert_eq!(Locator::new_from_path(path), Ok(expect));
365
366        // usb://ledger
367        let path = "usb://ledger";
368        let expect = Locator {
369            manufacturer,
370            pubkey: None,
371        };
372        assert_eq!(Locator::new_from_path(path), Ok(expect));
373
374        // usb://ledger/
375        let path = "usb://ledger/";
376        let expect = Locator {
377            manufacturer,
378            pubkey: None,
379        };
380        assert_eq!(Locator::new_from_path(path), Ok(expect));
381
382        // bad-scheme://ledger
383        let path = "bad-scheme://ledger";
384        assert_eq!(
385            Locator::new_from_path(path),
386            Err(LocatorError::UnimplementedScheme)
387        );
388
389        // usb://bad-manufacturer
390        let path = "usb://bad-manufacturer";
391        assert_eq!(
392            Locator::new_from_path(path),
393            Err(LocatorError::ManufacturerError(ManufacturerError))
394        );
395
396        // usb://ledger/bad-pubkey
397        let path = "usb://ledger/bad-pubkey";
398        assert_eq!(
399            Locator::new_from_path(path),
400            Err(LocatorError::PubkeyError(ParsePubkeyError::Invalid))
401        );
402    }
403}