1#[cfg(feature = "bitbox")]
12pub mod bitbox;
13#[cfg(feature = "coldcard")]
15pub mod coldcard;
16pub mod descriptors;
18pub mod device;
20pub mod display_address;
22#[cfg(feature = "jade")]
24pub mod jade;
25#[cfg(feature = "keepkey")]
27pub mod keepkey;
28#[cfg(feature = "ledger")]
30pub mod ledger;
31#[cfg(any(feature = "bitbox", feature = "keepkey", feature = "trezor"))]
33pub mod management;
34pub mod psbt;
36pub mod signing;
38#[cfg(feature = "specter")]
39pub mod specter;
40pub mod transport;
42#[cfg(feature = "trezor")]
44pub mod trezor;
45
46use std::{error::Error as StdError, fmt::Debug, str::FromStr};
47
48use async_trait::async_trait;
49pub use bhwi::common::DeviceBackup;
50pub use bhwi::common::DeviceContext;
51pub use bhwi::common::DisplayAddress;
52pub use bhwi::common::ErrorKind;
53pub use bhwi::common::Info;
54pub use bhwi::common::RestoreOptions;
55pub use bhwi::common::SetupOptions;
56pub use bhwi::common::WalletRegistration;
57pub use bhwi::common::{Error as HostError, HostRequest, HostResponse, PinMatrixRequestKind};
58pub use bhwi::common::{MultisigAddressType, MultisigDisplayAddress};
59use bhwi::miniscript::descriptor::WalletPolicy;
60#[cfg(any(feature = "trezor", feature = "keepkey"))]
61pub use bhwi::passphrase::HostPassphrase;
62use bhwi::{
63 Interpreter,
64 bitcoin::{
65 Network,
66 bip32::{DerivationPath, Fingerprint, Xpub},
67 psbt::Psbt,
68 secp256k1::ecdsa::Signature,
69 },
70 common::{self},
71};
72#[cfg(feature = "jade")]
74pub use jade::Jade;
75#[cfg(feature = "keepkey")]
77pub use keepkey::KeepKey;
78#[cfg(feature = "ledger")]
80pub use ledger::Ledger;
81#[cfg(feature = "specter")]
83pub use specter::Specter;
84#[cfg(feature = "trezor")]
86pub use trezor::Trezor;
87
88#[async_trait(?Send)]
93pub trait Transport {
94 type Error: Debug;
96 async fn exchange(&mut self, command: &[u8], encrypted: bool) -> Result<Vec<u8>, Self::Error>;
101
102 fn is_post_write_disconnect(&self, _error: &Self::Error) -> bool {
108 false
109 }
110
111 fn error_kind(&self, _error: &Self::Error) -> ErrorKind {
113 ErrorKind::Transport
114 }
115}
116
117#[async_trait(?Send)]
121pub trait HttpClient {
122 type Error: Debug;
124 async fn request(&self, url: &str, request: &[u8]) -> Result<Vec<u8>, Self::Error>;
126
127 fn error_kind(&self, _error: &Self::Error) -> ErrorKind {
129 ErrorKind::Transport
130 }
131}
132
133#[async_trait(?Send)]
138pub trait HostInteraction {
139 async fn respond(
141 &mut self,
142 request: &common::HostRequest,
143 ) -> Result<common::HostResponse, common::Error>;
144}
145
146#[async_trait(?Send)]
152pub trait HWI {
153 type Error: Debug;
155 async fn backup_device(&mut self) -> Result<DeviceBackup, Self::Error>;
157 async fn setup_device(
159 &mut self,
160 options: SetupOptions,
161 context: Option<DeviceContext>,
162 ) -> Result<bool, Self::Error>;
163 async fn wipe_device(&mut self) -> Result<bool, Self::Error>;
169 async fn restore_device(
171 &mut self,
172 options: RestoreOptions,
173 context: Option<DeviceContext>,
174 ) -> Result<bool, Self::Error>;
175 async fn toggle_passphrase(&mut self) -> Result<bool, Self::Error>;
179 async fn prompt_pin(&mut self) -> Result<bool, Self::Error>;
181 async fn send_pin(&mut self, context: Option<DeviceContext>) -> Result<bool, Self::Error>;
183 async fn unlock(&mut self, network: Network) -> Result<(), Self::Error>;
189 async fn get_info(&mut self) -> Result<Info, Self::Error>;
191 async fn get_master_fingerprint(&mut self) -> Result<Fingerprint, Self::Error>;
193 async fn get_extended_pubkey(
195 &mut self,
196 path: DerivationPath,
197 display: bool,
198 ) -> Result<Xpub, Self::Error>;
199 async fn sign_message(
201 &mut self,
202 message: &[u8],
203 path: DerivationPath,
204 ) -> Result<(u8, Signature), Self::Error>;
205 async fn display_address(
207 &mut self,
208 address: common::DisplayAddress,
209 context: Option<common::DeviceContext>,
210 ) -> Result<String, Self::Error>;
211 async fn register_wallet(
213 &mut self,
214 name: &str,
215 policy: &str,
216 ) -> Result<WalletRegistration, Self::Error>;
217 async fn sign_tx(
221 &mut self,
222 psbt: Psbt,
223 context: Option<common::DeviceContext>,
224 ) -> Result<Psbt, Self::Error>;
225}
226
227#[async_trait(?Send)]
235pub trait HWIDevice {
236 async fn backup_device(&mut self) -> Result<DeviceBackup, HWIDeviceError>;
238 async fn setup_device(
240 &mut self,
241 options: SetupOptions,
242 context: Option<DeviceContext>,
243 ) -> Result<bool, HWIDeviceError>;
244 async fn wipe_device(&mut self) -> Result<bool, HWIDeviceError>;
250 async fn restore_device(
252 &mut self,
253 options: RestoreOptions,
254 context: Option<DeviceContext>,
255 ) -> Result<bool, HWIDeviceError>;
256 async fn toggle_passphrase(&mut self) -> Result<bool, HWIDeviceError>;
260 async fn prompt_pin(&mut self) -> Result<bool, HWIDeviceError>;
262 async fn send_pin(&mut self, context: Option<DeviceContext>) -> Result<bool, HWIDeviceError>;
264 async fn unlock(&mut self, network: Network) -> Result<(), HWIDeviceError>;
270 async fn get_info(&mut self) -> Result<Info, HWIDeviceError>;
272 async fn get_master_fingerprint(&mut self) -> Result<Fingerprint, HWIDeviceError>;
274 async fn get_extended_pubkey(
276 &mut self,
277 path: DerivationPath,
278 display: bool,
279 ) -> Result<Xpub, HWIDeviceError>;
280 async fn sign_message(
282 &mut self,
283 message: &[u8],
284 path: DerivationPath,
285 ) -> Result<(u8, Signature), HWIDeviceError>;
286 async fn display_address(
288 &mut self,
289 address: common::DisplayAddress,
290 context: Option<common::DeviceContext>,
291 ) -> Result<String, HWIDeviceError>;
292 async fn register_wallet(
294 &mut self,
295 name: &str,
296 policy: &str,
297 ) -> Result<WalletRegistration, HWIDeviceError>;
298 async fn sign_tx(
302 &mut self,
303 psbt: Psbt,
304 context: Option<common::DeviceContext>,
305 ) -> Result<Psbt, HWIDeviceError>;
306}
307
308#[derive(Debug, thiserror::Error)]
310#[error("{error}")]
311pub struct HWIDeviceError {
312 #[source]
313 error: Box<dyn StdError + Send + Sync + 'static>,
314 kind: Option<ErrorKind>,
315}
316
317impl HWIDeviceError {
318 pub fn new(error: impl StdError + Send + Sync + 'static) -> Self {
320 Self {
321 error: Box::new(error),
322 kind: None,
323 }
324 }
325
326 pub fn with_kind(error: impl StdError + Send + Sync + 'static, kind: ErrorKind) -> Self {
328 Self {
329 kind: Some(kind),
330 ..Self::new(error)
331 }
332 }
333
334 pub fn kind(&self) -> Option<ErrorKind> {
336 if self.kind.is_some() {
337 return self.kind;
338 }
339 let mut source: Option<&(dyn StdError + 'static)> = Some(self.error.as_ref());
340 while let Some(current) = source {
341 if let Some(error) = current.downcast_ref::<common::Error>() {
342 return Some(error.kind());
343 }
344 if let Some(kind) = current
345 .downcast_ref::<HWIDeviceError>()
346 .and_then(Self::kind)
347 {
348 return Some(kind);
349 }
350 source = current.source();
351 }
352 None
353 }
354}
355
356impl From<Box<dyn StdError + Send + Sync + 'static>> for HWIDeviceError {
357 fn from(error: Box<dyn StdError + Send + Sync + 'static>) -> Self {
358 Self { error, kind: None }
359 }
360}
361
362pub trait ErrorKindOf {
364 fn error_kind(&self) -> ErrorKind;
366}
367
368impl ErrorKindOf for common::Error {
369 fn error_kind(&self) -> ErrorKind {
370 self.kind()
371 }
372}
373
374#[derive(Debug, thiserror::Error)]
376pub enum Error<E, F> {
377 #[error("transport error: {error}")]
379 Transport {
380 kind: ErrorKind,
382 error: E,
384 },
385
386 #[error("http client error: {error}")]
388 HttpClient {
389 kind: ErrorKind,
391 error: F,
393 },
394
395 #[error("{0}")]
397 Interpreter(
398 #[from]
400 common::Error,
401 ),
402}
403
404impl<E, F> Error<E, F> {
405 pub fn kind(&self) -> ErrorKind {
407 match self {
408 Self::Transport { kind, .. } | Self::HttpClient { kind, .. } => *kind,
409 Self::Interpreter(error) => error.kind(),
410 }
411 }
412}
413
414impl<E, F> ErrorKindOf for Error<E, F> {
415 fn error_kind(&self) -> ErrorKind {
416 self.kind()
417 }
418}
419
420fn kinded<E>(error: E) -> HWIDeviceError
421where
422 E: ErrorKindOf + StdError + Send + Sync + 'static,
423{
424 let kind = error.error_kind();
425 HWIDeviceError::with_kind(error, kind)
426}
427
428#[async_trait(?Send)]
429impl<D> HWI for D
430where
431 D: CommonInterface<common::Command, common::Transmit, common::Response, common::Error>
432 + OnUnlock,
433{
434 type Error = Error<D::TransportError, D::HttpClientError>;
435 async fn backup_device(&mut self) -> Result<DeviceBackup, Self::Error> {
436 if let common::Response::Backup(backup) = run_command(self, common::Command::Backup).await?
437 {
438 Ok(backup)
439 } else {
440 Err(
441 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
442 .into(),
443 )
444 }
445 }
446
447 async fn setup_device(
448 &mut self,
449 options: SetupOptions,
450 context: Option<DeviceContext>,
451 ) -> Result<bool, Self::Error> {
452 if let common::Response::DeviceAction(success) =
453 run_command(self, common::Command::Setup(options, context)).await?
454 {
455 Ok(success)
456 } else {
457 Err(
458 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
459 .into(),
460 )
461 }
462 }
463
464 async fn wipe_device(&mut self) -> Result<bool, Self::Error> {
465 if let common::Response::DeviceAction(success) =
466 run_command_allowing_final_disconnect(self, common::Command::Wipe).await?
467 {
468 Ok(success)
469 } else {
470 Err(
471 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
472 .into(),
473 )
474 }
475 }
476
477 async fn restore_device(
478 &mut self,
479 options: RestoreOptions,
480 context: Option<DeviceContext>,
481 ) -> Result<bool, Self::Error> {
482 if let common::Response::DeviceAction(success) =
483 run_command(self, common::Command::Restore(options, context)).await?
484 {
485 Ok(success)
486 } else {
487 Err(
488 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
489 .into(),
490 )
491 }
492 }
493
494 async fn toggle_passphrase(&mut self) -> Result<bool, Self::Error> {
495 if let common::Response::DeviceAction(success) =
496 run_command(self, common::Command::TogglePassphrase).await?
497 {
498 Ok(success)
499 } else {
500 Err(
501 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
502 .into(),
503 )
504 }
505 }
506
507 async fn prompt_pin(&mut self) -> Result<bool, Self::Error> {
508 if let common::Response::DeviceAction(success) =
509 run_command(self, common::Command::PromptPin).await?
510 {
511 Ok(success)
512 } else {
513 Err(
514 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
515 .into(),
516 )
517 }
518 }
519
520 async fn send_pin(&mut self, context: Option<DeviceContext>) -> Result<bool, Self::Error> {
521 if let common::Response::DeviceAction(success) =
522 run_command(self, common::Command::SendPin(context)).await?
523 {
524 Ok(success)
525 } else {
526 Err(
527 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
528 .into(),
529 )
530 }
531 }
532
533 async fn unlock(&mut self, network: Network) -> Result<(), Self::Error> {
534 let res = run_command(
535 self,
536 common::Command::Unlock {
537 options: common::UnlockOptions {
538 network: Some(network),
539 },
540 },
541 )
542 .await?;
543 self.on_unlock(res)?;
544 Ok(())
545 }
546
547 async fn get_info(&mut self) -> Result<Info, Self::Error> {
548 if let common::Response::Info(version) =
549 run_command(self, common::Command::GetVersion).await?
550 {
551 Ok(version)
552 } else {
553 Err(
554 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
555 .into(),
556 )
557 }
558 }
559
560 async fn get_master_fingerprint(&mut self) -> Result<Fingerprint, Self::Error> {
561 if let common::Response::MasterFingerprint(fg) =
562 run_command(self, common::Command::GetMasterFingerprint).await?
563 {
564 Ok(fg)
565 } else {
566 Err(
567 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
568 .into(),
569 )
570 }
571 }
572
573 async fn get_extended_pubkey(
574 &mut self,
575 path: DerivationPath,
576 display: bool,
577 ) -> Result<Xpub, Self::Error> {
578 if let common::Response::Xpub(xpub) =
579 run_command(self, common::Command::GetXpub { path, display }).await?
580 {
581 Ok(xpub)
582 } else {
583 Err(
584 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
585 .into(),
586 )
587 }
588 }
589
590 async fn sign_message(
591 &mut self,
592 message: &[u8],
593 path: DerivationPath,
594 ) -> Result<(u8, Signature), Self::Error> {
595 if let common::Response::Signature(header, signature) = run_command(
596 self,
597 common::Command::SignMessage {
598 message: message.to_vec(),
599 path,
600 },
601 )
602 .await?
603 {
604 Ok((header, signature))
605 } else {
606 Err(
607 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
608 .into(),
609 )
610 }
611 }
612
613 async fn display_address(
614 &mut self,
615 address: common::DisplayAddress,
616 context: Option<common::DeviceContext>,
617 ) -> Result<String, Self::Error> {
618 if let common::Response::Address(addr) =
619 run_command(self, common::Command::DisplayAddress(address, context)).await?
620 {
621 Ok(addr)
622 } else {
623 Err(
624 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
625 .into(),
626 )
627 }
628 }
629
630 async fn register_wallet(
631 &mut self,
632 name: &str,
633 policy: &str,
634 ) -> Result<WalletRegistration, Self::Error> {
635 let wallet_policy = WalletPolicy::from_str(policy)
636 .map_err(|e| common::Error::new(ErrorKind::Serialization, e.to_string()))?;
637 if let common::Response::WalletRegistration(registration) = run_command(
638 self,
639 common::Command::RegisterWallet {
640 name: name.to_string(),
641 policy: wallet_policy,
642 },
643 )
644 .await?
645 {
646 Ok(registration)
647 } else {
648 Err(
649 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
650 .into(),
651 )
652 }
653 }
654
655 async fn sign_tx(
656 &mut self,
657 psbt: Psbt,
658 context: Option<common::DeviceContext>,
659 ) -> Result<Psbt, Self::Error> {
660 if let common::Response::SignedPsbt(psbt) =
661 run_command(self, common::Command::SignTx(psbt, context)).await?
662 {
663 Ok(psbt)
664 } else {
665 Err(
666 common::Error::new(ErrorKind::UnexpectedResponse, "no error or result returned")
667 .into(),
668 )
669 }
670 }
671}
672
673#[async_trait(?Send)]
674impl<T> HWIDevice for T
675where
676 T: HWI,
677 T::Error: ErrorKindOf + StdError + Send + Sync + 'static,
678{
679 async fn backup_device(&mut self) -> Result<DeviceBackup, HWIDeviceError> {
680 HWI::backup_device(self).await.map_err(kinded)
681 }
682
683 async fn setup_device(
684 &mut self,
685 options: SetupOptions,
686 context: Option<DeviceContext>,
687 ) -> Result<bool, HWIDeviceError> {
688 HWI::setup_device(self, options, context)
689 .await
690 .map_err(kinded)
691 }
692
693 async fn wipe_device(&mut self) -> Result<bool, HWIDeviceError> {
694 HWI::wipe_device(self).await.map_err(kinded)
695 }
696
697 async fn restore_device(
698 &mut self,
699 options: RestoreOptions,
700 context: Option<DeviceContext>,
701 ) -> Result<bool, HWIDeviceError> {
702 HWI::restore_device(self, options, context)
703 .await
704 .map_err(kinded)
705 }
706
707 async fn toggle_passphrase(&mut self) -> Result<bool, HWIDeviceError> {
708 HWI::toggle_passphrase(self).await.map_err(kinded)
709 }
710
711 async fn prompt_pin(&mut self) -> Result<bool, HWIDeviceError> {
712 HWI::prompt_pin(self).await.map_err(kinded)
713 }
714
715 async fn send_pin(&mut self, context: Option<DeviceContext>) -> Result<bool, HWIDeviceError> {
716 HWI::send_pin(self, context).await.map_err(kinded)
717 }
718
719 async fn unlock(&mut self, network: Network) -> Result<(), HWIDeviceError> {
720 HWI::unlock(self, network).await.map_err(kinded)
721 }
722
723 async fn get_info(&mut self) -> Result<Info, HWIDeviceError> {
724 HWI::get_info(self).await.map_err(kinded)
725 }
726
727 async fn get_master_fingerprint(&mut self) -> Result<Fingerprint, HWIDeviceError> {
728 HWI::get_master_fingerprint(self).await.map_err(kinded)
729 }
730
731 async fn get_extended_pubkey(
732 &mut self,
733 path: DerivationPath,
734 display: bool,
735 ) -> Result<Xpub, HWIDeviceError> {
736 HWI::get_extended_pubkey(self, path, display)
737 .await
738 .map_err(kinded)
739 }
740
741 async fn sign_message(
742 &mut self,
743 message: &[u8],
744 path: DerivationPath,
745 ) -> Result<(u8, Signature), HWIDeviceError> {
746 HWI::sign_message(self, message, path).await.map_err(kinded)
747 }
748
749 async fn display_address(
750 &mut self,
751 address: DisplayAddress,
752 context: Option<DeviceContext>,
753 ) -> Result<String, HWIDeviceError> {
754 HWI::display_address(self, address, context)
755 .await
756 .map_err(kinded)
757 }
758
759 async fn register_wallet(
760 &mut self,
761 name: &str,
762 policy: &str,
763 ) -> Result<WalletRegistration, HWIDeviceError> {
764 HWI::register_wallet(self, name, policy)
765 .await
766 .map_err(kinded)
767 }
768
769 async fn sign_tx(
770 &mut self,
771 psbt: Psbt,
772 context: Option<common::DeviceContext>,
773 ) -> Result<Psbt, HWIDeviceError> {
774 HWI::sign_tx(self, psbt, context).await.map_err(kinded)
775 }
776}
777
778pub trait OnUnlock {
780 fn on_unlock(&mut self, _response: common::Response) -> Result<(), common::Error>;
782}
783
784pub trait CommonInterface<C, T, R, E> {
786 type TransportError: Debug;
788 type HttpClientError: Debug;
790
791 #[allow(clippy::type_complexity)]
795 fn components(
796 &mut self,
797 ) -> (
798 &mut (dyn Transport<Error = Self::TransportError> + '_),
799 &(dyn HttpClient<Error = Self::HttpClientError> + '_),
800 Option<&mut (dyn HostInteraction + 'static)>,
801 impl Interpreter<Command = C, Transmit = T, Response = R, Error = E>,
802 );
803}
804
805async fn run_command<D, C, E, F>(
806 device: &mut D,
807 command: C,
808) -> Result<common::Response, Error<E, F>>
809where
810 E: Debug,
811 F: Debug,
812 D: CommonInterface<
813 common::Command,
814 common::Transmit,
815 common::Response,
816 common::Error,
817 TransportError = E,
818 HttpClientError = F,
819 >,
820 C: Into<common::Command>,
821{
822 run_command_inner(device, command, false).await
823}
824
825async fn run_command_allowing_final_disconnect<D, C, E, F>(
826 device: &mut D,
827 command: C,
828) -> Result<common::Response, Error<E, F>>
829where
830 E: Debug,
831 F: Debug,
832 D: CommonInterface<
833 common::Command,
834 common::Transmit,
835 common::Response,
836 common::Error,
837 TransportError = E,
838 HttpClientError = F,
839 >,
840 C: Into<common::Command>,
841{
842 run_command_inner(device, command, true).await
843}
844
845async fn run_command_inner<D, C, E, F>(
846 device: &mut D,
847 command: C,
848 allow_final_disconnect: bool,
849) -> Result<common::Response, Error<E, F>>
850where
851 E: Debug,
852 F: Debug,
853 D: CommonInterface<
854 common::Command,
855 common::Transmit,
856 common::Response,
857 common::Error,
858 TransportError = E,
859 HttpClientError = F,
860 >,
861 C: Into<common::Command>,
862{
863 let (transport, http_client, mut host_interaction, mut intpr) = device.components();
864 let mut transmit = Some(intpr.start(command.into())?);
865 while let Some(t) = &transmit {
866 match &t.recipient {
867 common::Recipient::PinServer { url } => {
868 let res = http_client
869 .request(url, &t.payload)
870 .await
871 .map_err(|error| Error::HttpClient {
872 kind: http_client.error_kind(&error),
873 error,
874 })?;
875 transmit = intpr.exchange(res)?;
876 }
877 common::Recipient::Host(request) => {
878 let interaction = host_interaction.as_deref_mut().ok_or_else(|| {
879 common::Error::new(ErrorKind::Unsupported, "host interaction required")
880 })?;
881 let response = interaction.respond(request).await?;
882 transmit = intpr.exchange(response.into_bytes_for(request)?)?;
883 }
884 common::Recipient::Device => {
885 let exchange = match transport.exchange(&t.payload, t.encrypted).await {
886 Ok(exchange) => exchange,
887 Err(error)
888 if allow_final_disconnect && transport.is_post_write_disconnect(&error) =>
889 {
890 return Ok(common::Response::DeviceAction(true));
891 }
892 Err(error) => {
893 return Err(Error::Transport {
894 kind: transport.error_kind(&error),
895 error,
896 });
897 }
898 };
899 transmit = intpr.exchange(exchange)?;
900 }
901 }
902 }
903 intpr.end().map_err(|e| e.into())
904}
905
906#[cfg(test)]
907mod tests {
908 use super::*;
909 use futures::executor::block_on;
910 use std::{
911 cell::{Cell, RefCell},
912 convert::Infallible,
913 rc::Rc,
914 };
915
916 struct ScriptedTransport {
917 calls: usize,
918 events: Rc<RefCell<Vec<&'static str>>>,
919 }
920
921 #[async_trait(?Send)]
922 impl Transport for ScriptedTransport {
923 type Error = Infallible;
924
925 async fn exchange(
926 &mut self,
927 command: &[u8],
928 encrypted: bool,
929 ) -> Result<Vec<u8>, Self::Error> {
930 self.events.borrow_mut().push("device");
931 self.calls += 1;
932 assert_eq!(command, &[0x01]);
933 assert!(!encrypted);
934 Ok(vec![0x02])
935 }
936 }
937
938 struct NoHttp;
939
940 #[async_trait(?Send)]
941 impl HttpClient for NoHttp {
942 type Error = Infallible;
943
944 async fn request(&self, _url: &str, _request: &[u8]) -> Result<Vec<u8>, Self::Error> {
945 panic!("host interaction must not use HTTP")
946 }
947 }
948
949 struct ScriptedHost {
950 response: Option<common::HostResponse>,
951 calls: Rc<Cell<usize>>,
952 events: Rc<RefCell<Vec<&'static str>>>,
953 }
954
955 #[async_trait(?Send)]
956 impl HostInteraction for ScriptedHost {
957 async fn respond(
958 &mut self,
959 request: &common::HostRequest,
960 ) -> Result<common::HostResponse, common::Error> {
961 assert_eq!(
962 request,
963 &common::HostRequest::PinMatrix {
964 kind: common::PinMatrixRequestKind::Current,
965 }
966 );
967 self.events.borrow_mut().push("host");
968 self.calls.set(self.calls.get() + 1);
969 Ok(self.response.take().expect("one scripted host response"))
970 }
971 }
972
973 #[derive(Default)]
974 struct ScriptedInterpreter {
975 state: u8,
976 host_first: bool,
977 }
978
979 impl Interpreter for ScriptedInterpreter {
980 type Command = common::Command;
981 type Transmit = common::Transmit;
982 type Response = common::Response;
983 type Error = common::Error;
984
985 fn start(&mut self, command: Self::Command) -> Result<Self::Transmit, Self::Error> {
986 assert!(matches!(command, common::Command::GetVersion));
987 assert_eq!(self.state, 0);
988 self.state = 1;
989 if self.host_first {
990 Ok(common::HostRequest::PinMatrix {
991 kind: common::PinMatrixRequestKind::Current,
992 }
993 .into())
994 } else {
995 Ok(vec![0x01].into())
996 }
997 }
998
999 fn exchange(&mut self, data: Vec<u8>) -> Result<Option<Self::Transmit>, Self::Error> {
1000 match (self.host_first, self.state) {
1001 (false, 1) => {
1002 assert_eq!(data.as_slice(), &[0x02]);
1003 self.state = 2;
1004 Ok(Some(
1005 common::HostRequest::PinMatrix {
1006 kind: common::PinMatrixRequestKind::Current,
1007 }
1008 .into(),
1009 ))
1010 }
1011 (false, 2) => {
1012 assert_eq!(data.as_slice(), b"123");
1013 self.state = 3;
1014 Ok(None)
1015 }
1016 (true, 1) => {
1017 assert_eq!(data.as_slice(), b"123");
1018 self.state = 2;
1019 Ok(Some(vec![0x01].into()))
1020 }
1021 (true, 2) => {
1022 assert_eq!(data.as_slice(), &[0x02]);
1023 self.state = 3;
1024 Ok(None)
1025 }
1026 _ => panic!("unexpected scripted interpreter state"),
1027 }
1028 }
1029
1030 fn end(self) -> Result<Self::Response, Self::Error> {
1031 assert_eq!(self.state, 3);
1032 Ok(common::Response::DeviceAction(true))
1033 }
1034 }
1035
1036 struct ScriptedDevice {
1037 transport: ScriptedTransport,
1038 host_interaction: Option<Box<dyn HostInteraction>>,
1039 host_first: bool,
1040 }
1041
1042 impl CommonInterface<common::Command, common::Transmit, common::Response, common::Error>
1043 for ScriptedDevice
1044 {
1045 type TransportError = Infallible;
1046 type HttpClientError = Infallible;
1047
1048 fn components(
1049 &mut self,
1050 ) -> (
1051 &mut (dyn Transport<Error = Self::TransportError> + '_),
1052 &(dyn HttpClient<Error = Self::HttpClientError> + '_),
1053 Option<&mut (dyn HostInteraction + 'static)>,
1054 impl Interpreter<
1055 Command = common::Command,
1056 Transmit = common::Transmit,
1057 Response = common::Response,
1058 Error = common::Error,
1059 >,
1060 ) {
1061 (
1062 &mut self.transport,
1063 &NoHttp,
1064 self.host_interaction.as_deref_mut(),
1065 ScriptedInterpreter {
1066 host_first: self.host_first,
1067 ..Default::default()
1068 },
1069 )
1070 }
1071 }
1072
1073 fn scripted_device(
1074 response: Option<common::HostResponse>,
1075 ) -> (ScriptedDevice, Rc<Cell<usize>>) {
1076 scripted_device_with_sequence(response, false)
1077 }
1078
1079 fn scripted_device_with_sequence(
1080 response: Option<common::HostResponse>,
1081 host_first: bool,
1082 ) -> (ScriptedDevice, Rc<Cell<usize>>) {
1083 let calls = Rc::new(Cell::new(0));
1084 let events = Rc::new(RefCell::new(Vec::new()));
1085 let host_interaction = response.map(|response| {
1086 Box::new(ScriptedHost {
1087 response: Some(response),
1088 calls: Rc::clone(&calls),
1089 events: Rc::clone(&events),
1090 }) as Box<dyn HostInteraction>
1091 });
1092 (
1093 ScriptedDevice {
1094 transport: ScriptedTransport { calls: 0, events },
1095 host_interaction,
1096 host_first,
1097 },
1098 calls,
1099 )
1100 }
1101
1102 #[test]
1103 fn initial_host_recipient_uses_provider_before_device_exchange() {
1104 let (mut device, host_calls) = scripted_device_with_sequence(
1105 Some(common::HostResponse::PinPositions("123".into())),
1106 true,
1107 );
1108
1109 let response = block_on(run_command_inner(
1110 &mut device,
1111 common::Command::GetVersion,
1112 false,
1113 ))
1114 .expect("scripted command succeeds");
1115
1116 assert!(matches!(response, common::Response::DeviceAction(true)));
1117 assert_eq!(host_calls.get(), 1);
1118 assert_eq!(device.transport.calls, 1);
1119 assert_eq!(
1120 device.transport.events.borrow().as_slice(),
1121 &["host", "device"]
1122 );
1123 }
1124
1125 #[test]
1126 fn host_recipient_uses_provider_without_second_device_exchange() {
1127 let (mut device, host_calls) =
1128 scripted_device(Some(common::HostResponse::PinPositions("123".into())));
1129
1130 let response = block_on(run_command_inner(
1131 &mut device,
1132 common::Command::GetVersion,
1133 false,
1134 ))
1135 .expect("scripted command succeeds");
1136
1137 assert!(matches!(response, common::Response::DeviceAction(true)));
1138 assert_eq!(device.transport.calls, 1);
1139 assert_eq!(host_calls.get(), 1);
1140 }
1141
1142 #[test]
1143 fn host_recipient_requires_provider_without_second_device_exchange() {
1144 let (mut device, host_calls) = scripted_device(None);
1145
1146 let error = match block_on(run_command_inner(
1147 &mut device,
1148 common::Command::GetVersion,
1149 false,
1150 )) {
1151 Err(error) => error,
1152 Ok(_) => panic!("missing host provider succeeds"),
1153 };
1154
1155 assert!(matches!(
1156 error,
1157 Error::Interpreter(e) if e.kind() == common::ErrorKind::Unsupported
1158 && e.message() == "host interaction required"
1159 ));
1160 assert_eq!(device.transport.calls, 1);
1161 assert_eq!(host_calls.get(), 0);
1162 }
1163
1164 #[test]
1165 fn host_recipient_validates_response_without_second_device_exchange() {
1166 let (mut device, host_calls) =
1167 scripted_device(Some(common::HostResponse::RecoveryCharacter('a')));
1168
1169 let error = match block_on(run_command_inner(
1170 &mut device,
1171 common::Command::GetVersion,
1172 false,
1173 )) {
1174 Err(error) => error,
1175 Ok(_) => panic!("mismatched host response succeeds"),
1176 };
1177
1178 assert!(matches!(
1179 error,
1180 Error::Interpreter(e) if e.kind() == common::ErrorKind::InvalidInput
1181 && e.message() == "host response does not match request"
1182 ));
1183 assert_eq!(device.transport.calls, 1);
1184 assert_eq!(host_calls.get(), 1);
1185 }
1186}