diff --git a/remote-wallet/src/ledger.rs b/remote-wallet/src/ledger.rs index 44b21f0287..1aa5d1c2e2 100644 --- a/remote-wallet/src/ledger.rs +++ b/remote-wallet/src/ledger.rs @@ -1,9 +1,8 @@ use { crate::{ ledger_error::LedgerError, - remote_wallet::{ - Manufacturer, RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager, - }, + locator::Manufacturer, + remote_wallet::{RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager}, }, console::Emoji, dialoguer::{theme::ColorfulTheme, Select}, diff --git a/remote-wallet/src/lib.rs b/remote-wallet/src/lib.rs index ee2e2232a0..2e58e2a2f2 100644 --- a/remote-wallet/src/lib.rs +++ b/remote-wallet/src/lib.rs @@ -1,5 +1,6 @@ #![allow(clippy::integer_arithmetic)] pub mod ledger; pub mod ledger_error; +pub mod locator; pub mod remote_keypair; pub mod remote_wallet; diff --git a/remote-wallet/src/locator.rs b/remote-wallet/src/locator.rs new file mode 100644 index 0000000000..3b11628c00 --- /dev/null +++ b/remote-wallet/src/locator.rs @@ -0,0 +1,656 @@ +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(_)) + )); + } +} diff --git a/remote-wallet/src/remote_keypair.rs b/remote-wallet/src/remote_keypair.rs index e0fb945a27..350bf063d6 100644 --- a/remote-wallet/src/remote_keypair.rs +++ b/remote-wallet/src/remote_keypair.rs @@ -1,8 +1,9 @@ use { crate::{ ledger::get_ledger_from_info, + locator::Manufacturer, remote_wallet::{ - Manufacturer, RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager, + RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager, RemoteWalletType, }, }, diff --git a/remote-wallet/src/remote_wallet.rs b/remote-wallet/src/remote_wallet.rs index fdcd971c7c..afd2ef38bd 100644 --- a/remote-wallet/src/remote_wallet.rs +++ b/remote-wallet/src/remote_wallet.rs @@ -2,22 +2,20 @@ use { crate::{ ledger::{is_valid_ledger, LedgerWallet}, ledger_error::LedgerError, + locator::{Locator, LocatorError, Manufacturer}, }, log::*, parking_lot::{Mutex, RwLock}, solana_sdk::{ derivation_path::{DerivationPath, DerivationPathError}, - pubkey::{ParsePubkeyError, Pubkey}, + pubkey::Pubkey, signature::{Signature, SignerError}, }, std::{ - convert::{Infallible, TryFrom, TryInto}, - str::FromStr, sync::Arc, time::{Duration, Instant}, }, thiserror::Error, - uriparse::{URIReference, URIReferenceBuilder, URIReferenceError}, }; const HID_GLOBAL_USAGE_PAGE: u16 = 0xFF00; @@ -254,197 +252,6 @@ pub struct RemoteWalletInfo { pub error: Option, } -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Manufacturer { - Unknown, - Ledger, -} - -impl Default for Manufacturer { - fn default() -> Self { - Self::Unknown - } -} - -const MFR_UNKNOWN: &str = "unknown"; -const MFR_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() { - MFR_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 => MFR_UNKNOWN, - Self::Ledger => MFR_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 { - manufacturer: Manufacturer, - pubkey: Option, - 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(pubkeyable) = pubkey { - Some(pubkeyable.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, - }) - } -} - impl RemoteWalletInfo { pub fn parse_path(path: String) -> Result<(Self, DerivationPath), RemoteWalletError> { let Locator { @@ -625,424 +432,4 @@ mod tests { format!("usb://ledger/{}", pubkey_str) ); } - - #[test] - fn test_manufacturer() { - assert_eq!(MFR_LEDGER.try_into(), Ok(Manufacturer::Ledger)); - assert!(matches!(Manufacturer::from_str(MFR_LEDGER), Ok(v) if v == Manufacturer::Ledger)); - assert_eq!(Manufacturer::Ledger.as_ref(), MFR_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), - }; - 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?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(_)) - )); - } }