use { solana_sdk::{ derivation_path::{DerivationPath, DerivationPathError}, pubkey::{ParsePubkeyError, Pubkey}, }, std::{ convert::{Infallible, TryFrom, TryInto}, str::FromStr, }, thiserror::Error, uriparse::{URIReference, URIReferenceBuilder, URIReferenceError}, }; #[derive(Clone, Copy, Debug, PartialEq)] pub enum Manufacturer { Unknown, Ledger, } impl Default for Manufacturer { fn default() -> Self { Self::Unknown } } const MANUFACTURER_UNKNOWN: &str = "unknown"; const MANUFACTURER_LEDGER: &str = "ledger"; #[derive(Clone, Debug, Error, PartialEq)] #[error("not a manufacturer")] pub struct ManufacturerError; impl From for ManufacturerError { fn from(_: Infallible) -> Self { ManufacturerError } } impl FromStr for Manufacturer { type Err = ManufacturerError; fn from_str(s: &str) -> Result { let s = s.to_ascii_lowercase(); match s.as_str() { MANUFACTURER_LEDGER => Ok(Self::Ledger), _ => Err(ManufacturerError), } } } impl TryFrom<&str> for Manufacturer { type Error = ManufacturerError; fn try_from(s: &str) -> Result { Manufacturer::from_str(s) } } impl AsRef for Manufacturer { fn as_ref(&self) -> &str { match self { Self::Unknown => MANUFACTURER_UNKNOWN, Self::Ledger => MANUFACTURER_LEDGER, } } } impl std::fmt::Display for Manufacturer { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let s: &str = self.as_ref(); write!(f, "{}", s) } } #[derive(Clone, Debug, Error, PartialEq)] pub enum LocatorError { #[error(transparent)] ManufacturerError(#[from] ManufacturerError), #[error(transparent)] PubkeyError(#[from] ParsePubkeyError), #[error(transparent)] DerivationPathError(#[from] DerivationPathError), #[error(transparent)] UriReferenceError(#[from] URIReferenceError), #[error("unimplemented scheme")] UnimplementedScheme, #[error("infallible")] Infallible, } impl From for LocatorError { fn from(_: Infallible) -> Self { Self::Infallible } } #[derive(Debug, PartialEq)] pub struct Locator { pub manufacturer: Manufacturer, pub pubkey: Option, pub derivation_path: Option, } impl std::fmt::Display for Locator { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let maybe_path = self.pubkey.map(|p| p.to_string()); let path = maybe_path.as_deref().unwrap_or("/"); let maybe_query = self.derivation_path.as_ref().map(|d| d.get_query()); let maybe_query2 = maybe_query.as_ref().map(|q| &q[1..]); let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(self.manufacturer.as_ref())) .unwrap() .try_path(path) .unwrap() .try_query(maybe_query2) .unwrap(); let uri = builder.build().unwrap(); write!(f, "{}", uri) } } impl Locator { pub fn new_from_path>(path: P) -> Result { let path = path.as_ref(); let uri = URIReference::try_from(path)?; Self::new_from_uri(&uri) } pub fn new_from_uri(uri: &URIReference<'_>) -> Result { let scheme = uri.scheme().map(|s| s.as_str().to_ascii_lowercase()); let host = uri.host().map(|h| h.to_string()); match (scheme, host) { (Some(scheme), Some(host)) if scheme == "usb" => { let path = uri.path().segments().get(0).and_then(|s| { if !s.is_empty() { Some(s.as_str()) } else { None } }); let key = if let Some(query) = uri.query() { let query_str = query.as_str(); let query = qstring::QString::from(query_str); if query.len() > 1 { return Err(DerivationPathError::InvalidDerivationPath( "invalid query string, extra fields not supported".to_string(), ) .into()); } let key = query.get("key"); if key.is_none() { return Err(DerivationPathError::InvalidDerivationPath(format!( "invalid query string `{}`, only `key` supported", query_str, )) .into()); } key.map(|v| v.to_string()) } else { None }; Self::new_from_parts(host.as_str(), path, key.as_deref()) } (Some(_scheme), Some(_host)) => Err(LocatorError::UnimplementedScheme), (None, Some(_host)) => Err(LocatorError::UnimplementedScheme), (_, None) => Err(LocatorError::ManufacturerError(ManufacturerError)), } } pub fn new_from_parts( manufacturer: V, pubkey: Option

, derivation_path: Option, ) -> Result where VE: Into, V: TryInto, PE: Into, P: TryInto, DE: Into, D: TryInto, { let manufacturer = manufacturer.try_into().map_err(|e| e.into())?; let pubkey = if let Some(pubkey) = pubkey { Some(pubkey.try_into().map_err(|e| e.into())?) } else { None }; let derivation_path = if let Some(derivation_path) = derivation_path { Some(derivation_path.try_into().map_err(|e| e.into())?) } else { None }; Ok(Self { manufacturer, pubkey, derivation_path, }) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_manufacturer() { assert_eq!(MANUFACTURER_LEDGER.try_into(), Ok(Manufacturer::Ledger)); assert!( matches!(Manufacturer::from_str(MANUFACTURER_LEDGER), Ok(v) if v == Manufacturer::Ledger) ); assert_eq!(Manufacturer::Ledger.as_ref(), MANUFACTURER_LEDGER); assert!( matches!(Manufacturer::from_str("bad-manufacturer"), Err(e) if e == ManufacturerError) ); } #[test] fn test_locator_new_from_parts() { let manufacturer = Manufacturer::Ledger; let manufacturer_str = "ledger"; let pubkey = Pubkey::new_unique(); let pubkey_str = pubkey.to_string(); let derivation_path = DerivationPath::new_bip44(Some(0), Some(0)); let derivation_path_str = "0/0"; let expect = Locator { manufacturer, pubkey: None, derivation_path: None, }; assert!(matches!( Locator::new_from_parts(manufacturer, None::, None::), Ok(e) if e == expect, )); assert!(matches!( Locator::new_from_parts(manufacturer_str, None::, None::), Ok(e) if e == expect, )); let expect = Locator { manufacturer, pubkey: Some(pubkey), derivation_path: None, }; assert!(matches!( Locator::new_from_parts(manufacturer, Some(pubkey), None::), Ok(e) if e == expect, )); assert!(matches!( Locator::new_from_parts(manufacturer_str, Some(pubkey_str.as_str()), None::), Ok(e) if e == expect, )); let expect = Locator { manufacturer, pubkey: None, derivation_path: Some(derivation_path.clone()), }; assert!(matches!( Locator::new_from_parts(manufacturer, None::, Some(derivation_path)), Ok(e) if e == expect, )); assert!(matches!( Locator::new_from_parts(manufacturer, None::, Some(derivation_path_str)), Ok(e) if e == expect, )); assert!(matches!( Locator::new_from_parts("bad-manufacturer", None::, None::), Err(LocatorError::ManufacturerError(e)) if e == ManufacturerError, )); assert!(matches!( Locator::new_from_parts(manufacturer, Some("bad-pubkey"), None::), Err(LocatorError::PubkeyError(e)) if e == ParsePubkeyError::Invalid, )); let bad_path = "bad-derivation-path".to_string(); assert!(matches!( Locator::new_from_parts(manufacturer, None::, Some(bad_path.as_str())), Err(LocatorError::DerivationPathError( DerivationPathError::InvalidDerivationPath(_) )), )); } #[test] fn test_locator_new_from_uri() { let derivation_path = DerivationPath::new_bip44(Some(0), Some(0)); let manufacturer = Manufacturer::Ledger; let pubkey = Pubkey::new_unique(); let pubkey_str = pubkey.to_string(); // usb://ledger/{PUBKEY}?key=0'/0' let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path(pubkey_str.as_str()) .unwrap() .try_query(Some("key=0/0")) .unwrap(); let uri = builder.build().unwrap(); let expect = Locator { manufacturer, pubkey: Some(pubkey), derivation_path: Some(derivation_path.clone()), }; assert_eq!(Locator::new_from_uri(&uri), Ok(expect)); // usb://ledger/{PUBKEY} let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path(pubkey_str.as_str()) .unwrap(); let uri = builder.build().unwrap(); let expect = Locator { manufacturer, pubkey: Some(pubkey), derivation_path: None, }; assert_eq!(Locator::new_from_uri(&uri), Ok(expect)); // usb://ledger let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("") .unwrap(); let uri = builder.build().unwrap(); let expect = Locator { manufacturer, pubkey: None, derivation_path: None, }; assert_eq!(Locator::new_from_uri(&uri), Ok(expect)); // usb://ledger/ let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("/") .unwrap(); let uri = builder.build().unwrap(); let expect = Locator { manufacturer, pubkey: None, derivation_path: None, }; assert_eq!(Locator::new_from_uri(&uri), Ok(expect)); // usb://ledger?key=0/0 let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("") .unwrap() .try_query(Some("key=0/0")) .unwrap(); let uri = builder.build().unwrap(); let expect = Locator { manufacturer, pubkey: None, derivation_path: Some(derivation_path.clone()), }; assert_eq!(Locator::new_from_uri(&uri), Ok(expect)); // usb://ledger?key=0'/0' let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("") .unwrap() .try_query(Some("key=0'/0'")) .unwrap(); let uri = builder.build().unwrap(); let expect = Locator { manufacturer, pubkey: None, derivation_path: Some(derivation_path.clone()), }; assert_eq!(Locator::new_from_uri(&uri), Ok(expect)); // usb://ledger/?key=0/0 let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("/") .unwrap() .try_query(Some("key=0/0")) .unwrap(); let uri = builder.build().unwrap(); let expect = Locator { manufacturer, pubkey: None, derivation_path: Some(derivation_path), }; assert_eq!(Locator::new_from_uri(&uri), Ok(expect)); // bad-scheme://ledger let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("bad-scheme")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("") .unwrap(); let uri = builder.build().unwrap(); assert_eq!( Locator::new_from_uri(&uri), Err(LocatorError::UnimplementedScheme) ); // usb://bad-manufacturer let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some("bad-manufacturer")) .unwrap() .try_path("") .unwrap(); let uri = builder.build().unwrap(); assert_eq!( Locator::new_from_uri(&uri), Err(LocatorError::ManufacturerError(ManufacturerError)) ); // usb://ledger/bad-pubkey let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("bad-pubkey") .unwrap(); let uri = builder.build().unwrap(); assert_eq!( Locator::new_from_uri(&uri), Err(LocatorError::PubkeyError(ParsePubkeyError::Invalid)) ); // usb://ledger?bad-key=0/0 let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("") .unwrap() .try_query(Some("bad-key=0/0")) .unwrap(); let uri = builder.build().unwrap(); assert!(matches!( Locator::new_from_uri(&uri), Err(LocatorError::DerivationPathError(_)) )); // usb://ledger?key=bad-value let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("") .unwrap() .try_query(Some("key=bad-value")) .unwrap(); let uri = builder.build().unwrap(); assert!(matches!( Locator::new_from_uri(&uri), Err(LocatorError::DerivationPathError(_)) )); // usb://ledger?key= let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("") .unwrap() .try_query(Some("key=")) .unwrap(); let uri = builder.build().unwrap(); assert!(matches!( Locator::new_from_uri(&uri), Err(LocatorError::DerivationPathError(_)) )); // usb://ledger?key let mut builder = URIReferenceBuilder::new(); builder .try_scheme(Some("usb")) .unwrap() .try_authority(Some(Manufacturer::Ledger.as_ref())) .unwrap() .try_path("") .unwrap() .try_query(Some("key")) .unwrap(); let uri = builder.build().unwrap(); assert!(matches!( Locator::new_from_uri(&uri), Err(LocatorError::DerivationPathError(_)) )); } #[test] fn test_locator_new_from_path() { let derivation_path = DerivationPath::new_bip44(Some(0), Some(0)); let manufacturer = Manufacturer::Ledger; let pubkey = Pubkey::new_unique(); let path = format!("usb://ledger/{}?key=0/0", pubkey); Locator::new_from_path(path).unwrap(); // usb://ledger/{PUBKEY}?key=0'/0' let path = format!("usb://ledger/{}?key=0'/0'", pubkey); let expect = Locator { manufacturer, pubkey: Some(pubkey), derivation_path: Some(derivation_path.clone()), }; assert_eq!(Locator::new_from_path(path), Ok(expect)); // usb://ledger/{PUBKEY} let path = format!("usb://ledger/{}", pubkey); let expect = Locator { manufacturer, pubkey: Some(pubkey), derivation_path: None, }; assert_eq!(Locator::new_from_path(path), Ok(expect)); // usb://ledger let path = "usb://ledger"; let expect = Locator { manufacturer, pubkey: None, derivation_path: None, }; assert_eq!(Locator::new_from_path(path), Ok(expect)); // usb://ledger/ let path = "usb://ledger/"; let expect = Locator { manufacturer, pubkey: None, derivation_path: None, }; assert_eq!(Locator::new_from_path(path), Ok(expect)); // usb://ledger?key=0'/0' let path = "usb://ledger?key=0'/0'"; let expect = Locator { manufacturer, pubkey: None, derivation_path: Some(derivation_path.clone()), }; assert_eq!(Locator::new_from_path(path), Ok(expect)); // usb://ledger/?key=0'/0' let path = "usb://ledger?key=0'/0'"; let expect = Locator { manufacturer, pubkey: None, derivation_path: Some(derivation_path), }; assert_eq!(Locator::new_from_path(path), Ok(expect)); // bad-scheme://ledger let path = "bad-scheme://ledger"; assert_eq!( Locator::new_from_path(path), Err(LocatorError::UnimplementedScheme) ); // usb://bad-manufacturer let path = "usb://bad-manufacturer"; assert_eq!( Locator::new_from_path(path), Err(LocatorError::ManufacturerError(ManufacturerError)) ); // usb://ledger/bad-pubkey let path = "usb://ledger/bad-pubkey"; assert_eq!( Locator::new_from_path(path), Err(LocatorError::PubkeyError(ParsePubkeyError::Invalid)) ); // usb://ledger?bad-key=0/0 let path = "usb://ledger?bad-key=0/0"; assert!(matches!( Locator::new_from_path(path), Err(LocatorError::DerivationPathError(_)) )); // usb://ledger?bad-key=0'/0' let path = "usb://ledger?bad-key=0'/0'"; assert!(matches!( Locator::new_from_path(path), Err(LocatorError::DerivationPathError(_)) )); // usb://ledger?key=bad-value let path = format!("usb://ledger/{}?key=bad-value", pubkey); assert!(matches!( Locator::new_from_path(path), Err(LocatorError::DerivationPathError(_)) )); // usb://ledger?key= let path = format!("usb://ledger/{}?key=", pubkey); assert!(matches!( Locator::new_from_path(path), Err(LocatorError::DerivationPathError(_)) )); // usb://ledger?key let path = format!("usb://ledger/{}?key", pubkey); assert!(matches!( Locator::new_from_path(path), Err(LocatorError::DerivationPathError(_)) )); } }