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 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 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 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 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 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 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 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 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 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 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 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 let path = "bad-scheme://ledger";
384 assert_eq!(
385 Locator::new_from_path(path),
386 Err(LocatorError::UnimplementedScheme)
387 );
388
389 let path = "usb://bad-manufacturer";
391 assert_eq!(
392 Locator::new_from_path(path),
393 Err(LocatorError::ManufacturerError(ManufacturerError))
394 );
395
396 let path = "usb://ledger/bad-pubkey";
398 assert_eq!(
399 Locator::new_from_path(path),
400 Err(LocatorError::PubkeyError(ParsePubkeyError::Invalid))
401 );
402 }
403}