2424//! Note: Even fresh requests may be linkable via metadata (e.g. client IP, request timing),
2525//! but request reuse makes correlation trivial for the relay.
2626
27- use std:: str:: FromStr ;
27+ use alloc:: boxed:: Box ;
28+ use alloc:: string:: { String , ToString } ;
29+ use alloc:: vec:: Vec ;
30+ #[ cfg( not( feature = "std" ) ) ]
31+ use alloc:: { format, vec} ;
32+ use core:: str:: FromStr ;
2833#[ cfg( not( target_arch = "wasm32" ) ) ]
29- use std :: time:: Duration ;
34+ use core :: time:: Duration ;
3035
3136use bitcoin:: hashes:: { sha256, Hash } ;
3237use bitcoin:: psbt:: Psbt ;
@@ -35,10 +40,16 @@ pub use error::{CreateRequestError, SessionError};
3540pub ( crate ) use error:: { InternalCreateRequestError , InternalSessionError } ;
3641use serde:: de:: Deserializer ;
3742use serde:: { Deserialize , Serialize } ;
38- pub use session:: {
39- replay_event_log, replay_event_log_async, SessionEvent , SessionHistory , SessionOutcome ,
40- SessionStatus ,
41- } ;
43+ #[ cfg( feature = "std" ) ]
44+ pub use session:: replay_event_log_async;
45+ pub use session:: { replay_event_log, SessionEvent , SessionHistory , SessionOutcome , SessionStatus } ;
46+
47+ #[ cfg( feature = "std" ) ]
48+ pub use super :: JsonReply as ErrorReply ;
49+
50+ #[ cfg( not( feature = "std" ) ) ]
51+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
52+ pub struct ErrorReply ;
4253#[ cfg( target_arch = "wasm32" ) ]
4354use web_time:: Duration ;
4455
@@ -51,9 +62,9 @@ use super::{
5162use crate :: core:: Url ;
5263use crate :: error:: { InternalReplayError , ReplayError } ;
5364use crate :: hpke:: { decrypt_message_a, encrypt_message_b, HpkeKeyPair , HpkePublicKey } ;
54- use crate :: ohttp:: {
55- ohttp_encapsulate , process_get_res, process_post_res , OhttpEncapsulationError , OhttpKeys ,
56- } ;
65+ # [ cfg ( all ( feature = "std" , feature = "v2- ohttp" ) ) ]
66+ use crate :: ohttp :: process_get_res;
67+ use crate :: ohttp :: { ohttp_encapsulate , process_post_res , OhttpEncapsulationError , OhttpKeys } ;
5768use crate :: output_substitution:: OutputSubstitution ;
5869use crate :: persist:: {
5970 MaybeFatalOrSuccessTransition , MaybeFatalTransition , MaybeFatalTransitionWithNoResults ,
@@ -64,7 +75,6 @@ use crate::receive::{parse_payload, InputPair, OriginalPayload, PsbtContext};
6475use crate :: time:: Time ;
6576use crate :: uri:: ShortId ;
6677use crate :: { ImplementationError , IntoUrl , IntoUrlError , Request , Version } ;
67-
6878mod error;
6979mod session;
7080
@@ -143,6 +153,7 @@ pub enum ReceiveSession {
143153 WantsFeeRange ( Receiver < WantsFeeRange > ) ,
144154 ProvisionalProposal ( Receiver < ProvisionalProposal > ) ,
145155 PayjoinProposal ( Receiver < PayjoinProposal > ) ,
156+ #[ cfg( feature = "std" ) ]
146157 HasReplyableError ( Receiver < HasReplyableError > ) ,
147158 Monitor ( Receiver < Monitor > ) ,
148159 PendingFallback ( Receiver < PendingFallback > ) ,
@@ -220,6 +231,7 @@ impl ReceiveSession {
220231 ( _, SessionEvent :: Closed ( session_outcome) ) =>
221232 Ok ( ReceiveSession :: Closed ( session_outcome) ) ,
222233
234+ #[ cfg( feature = "std" ) ]
223235 ( session, SessionEvent :: GotReplyableError ( error) ) => {
224236 let ( session_context, fallback_tx) = match session {
225237 ReceiveSession :: Initialized ( r) => ( r. session_context , None ) ,
@@ -240,8 +252,7 @@ impl ReceiveSession {
240252 ( r. session_context , Some ( r. state . fallback_tx ( ) ) ) ,
241253 ReceiveSession :: PayjoinProposal ( r) =>
242254 ( r. session_context , Some ( r. state . fallback_tx ( ) ) ) ,
243- ReceiveSession :: HasReplyableError ( r) =>
244- ( r. session_context , r. state . fallback_tx . clone ( ) ) ,
255+ ReceiveSession :: HasReplyableError ( r) => ( r. session_context , None ) ,
245256 ReceiveSession :: Monitor ( r) => ( r. session_context , Some ( r. state . fallback_tx ( ) ) ) ,
246257 ReceiveSession :: PendingFallback ( r) => {
247258 let fallback_tx = r. fallback_tx ( ) . clone ( ) ;
@@ -319,6 +330,7 @@ mod sealed {
319330 impl State for super :: WantsFeeRange { }
320331 impl State for super :: ProvisionalProposal { }
321332 impl State for super :: PayjoinProposal { }
333+ #[ cfg( feature = "std" ) ]
322334 impl State for super :: HasReplyableError { }
323335 impl State for super :: Monitor { }
324336 impl State for super :: PendingFallback { }
@@ -610,21 +622,34 @@ impl Receiver<Initialized> {
610622 body : & [ u8 ] ,
611623 context : ohttp:: ClientResponse ,
612624 ) -> Result < Option < ( OriginalPayload , Option < HpkePublicKey > ) > , ProtocolError > {
613- let body = match process_get_res ( body, context)
614- . map_err ( |e| ProtocolError :: V2 ( InternalSessionError :: DirectoryResponse ( e) . into ( ) ) ) ?
625+ #[ cfg( all( feature = "std" , feature = "v2-ohttp" ) ) ]
615626 {
616- Some ( body) => body,
617- None => return Ok ( None ) ,
618- } ;
619- match std:: str:: from_utf8 ( & body) {
620- // V1 response bodies are utf8 plaintext
621- Ok ( response) =>
622- Ok ( Some ( self . extract_proposal_from_v1 ( response) . map ( |original| ( original, None ) ) ?) ) ,
623- // V2 response bodies are encrypted binary
624- Err ( _) => Ok ( Some (
625- self . extract_proposal_from_v2 ( body)
626- . map ( |( original, reply_key) | ( original, Some ( reply_key) ) ) ?,
627- ) ) ,
627+ let body: Vec < u8 > = match process_get_res ( body, context)
628+ . map_err ( |e| ProtocolError :: V2 ( InternalSessionError :: DirectoryResponse ( e) . into ( ) ) ) ?
629+ {
630+ Some ( body) => body,
631+ None => return Ok ( None ) ,
632+ } ;
633+
634+ match core:: str:: from_utf8 ( & body) {
635+ // V1 response bodies are utf8 plaintext
636+ Ok ( response) => Ok ( Some (
637+ self . extract_proposal_from_v1 ( response) . map ( |original| ( original, None ) ) ?,
638+ ) ) ,
639+ // V2 response bodies are encrypted binary
640+ Err ( _) => Ok ( Some (
641+ self . extract_proposal_from_v2 ( body)
642+ . map ( |( original, reply_key) | ( original, Some ( reply_key) ) ) ?,
643+ ) ) ,
644+ }
645+ }
646+
647+ #[ cfg( not( all( feature = "std" , feature = "v2-ohttp" ) ) ) ]
648+ {
649+ let _ = ( body, context) ;
650+ Err ( ProtocolError :: V2 (
651+ InternalSessionError :: Implementation ( ImplementationError :: std_required ( ) ) . into ( ) ,
652+ ) )
628653 }
629654 }
630655
@@ -652,7 +677,7 @@ impl Receiver<Initialized> {
652677 let ( payload_bytes, reply_key) =
653678 decrypt_message_a ( & response, self . session_context . receiver_key . secret_key ( ) )
654679 . map_err ( |e| ProtocolError :: V2 ( InternalSessionError :: Hpke ( e) . into ( ) ) ) ?;
655- let payload = std :: str:: from_utf8 ( & payload_bytes)
680+ let payload = core :: str:: from_utf8 ( & payload_bytes)
656681 . map_err ( |e| ProtocolError :: OriginalPayload ( InternalPayloadError :: Utf8 ( e) . into ( ) ) ) ?;
657682 self . unchecked_from_payload ( payload) . map ( |p| ( p, reply_key) )
658683 }
@@ -745,6 +770,7 @@ impl Receiver<UncheckedOriginalPayload> {
745770 /// This can be used to further prevent probing attacks since the attacker would now need to probe the receiver
746771 /// with transactions which are both broadcastable and pay high fee. Unrelated to the probing attack scenario,
747772 /// this parameter also makes operating in a high fee environment easier for the receiver.
773+ #[ cfg( feature = "std" ) ]
748774 pub fn check_broadcast_suitability (
749775 self ,
750776 min_fee_rate : Option < FeeRate > ,
@@ -776,6 +802,24 @@ impl Receiver<UncheckedOriginalPayload> {
776802 }
777803 }
778804
805+ #[ cfg( not( feature = "std" ) ) ]
806+ pub fn check_broadcast_suitability (
807+ self ,
808+ min_fee_rate : Option < FeeRate > ,
809+ can_broadcast : impl Fn ( & bitcoin:: Transaction ) -> Result < bool , ImplementationError > ,
810+ ) -> MaybeFatalTransition < SessionEvent , Receiver < MaybeInputsOwned > , Error > {
811+ match self . state . original . check_broadcast_suitability ( min_fee_rate, can_broadcast) {
812+ Ok ( ( ) ) => MaybeFatalTransition :: success (
813+ SessionEvent :: CheckedBroadcastSuitability ( ) ,
814+ Receiver {
815+ state : MaybeInputsOwned { original : self . original . clone ( ) } ,
816+ session_context : self . session_context ,
817+ } ,
818+ ) ,
819+ Err ( e) => MaybeFatalTransition :: transient ( e) ,
820+ }
821+ }
822+
779823 /// Moves on to the next typestate without any of the current typestate's validations.
780824 ///
781825 /// Use this for interactive payment receivers, where there is no risk of a probing attack since the
@@ -1362,7 +1406,15 @@ impl Receiver<PayjoinProposal> {
13621406 }
13631407}
13641408
1409+ #[ cfg( feature = "std" ) ]
13651410#[ derive( Debug , Clone , PartialEq ) ]
1411+ pub struct HasReplyableError {
1412+ error_reply : super :: JsonReply ,
1413+ fallback_tx : Option < bitcoin:: Transaction > ,
1414+ }
1415+
1416+ #[ cfg( not( feature = "std" ) ) ]
1417+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
13661418pub struct HasReplyableError {
13671419 error_reply : JsonReply ,
13681420 fallback_tx : Option < bitcoin:: Transaction > ,
@@ -1371,14 +1423,17 @@ pub struct HasReplyableError {
13711423impl Receiver < HasReplyableError > {
13721424 /// Cancel without sending the error response.
13731425 pub fn cancel ( self ) -> MaybeTerminalTransition < SessionEvent , Receiver < PendingFallback > > {
1374- let Receiver { state : HasReplyableError { fallback_tx, .. } , session_context } = self ;
1375- match fallback_tx {
1376- Some ( fallback_tx) => MaybeTerminalTransition :: advance (
1377- SessionEvent :: Cancelled ,
1378- Receiver { state : PendingFallback { fallback_tx } , session_context } ,
1379- ) ,
1380- None =>
1381- MaybeTerminalTransition :: terminate ( SessionEvent :: Closed ( SessionOutcome :: Aborted ) ) ,
1426+ {
1427+ let Receiver { state : HasReplyableError { fallback_tx, .. } , session_context } = self ;
1428+ match fallback_tx {
1429+ Some ( fallback_tx) => MaybeTerminalTransition :: advance (
1430+ SessionEvent :: Cancelled ,
1431+ Receiver { state : PendingFallback { fallback_tx } , session_context } ,
1432+ ) ,
1433+ None => MaybeTerminalTransition :: terminate ( SessionEvent :: Closed (
1434+ SessionOutcome :: Aborted ,
1435+ ) ) ,
1436+ }
13821437 }
13831438 }
13841439
@@ -1389,6 +1444,7 @@ impl Receiver<HasReplyableError> {
13891444 ohttp_relay : impl IntoUrl ,
13901445 ) -> Result < ( Request , ohttp:: ClientResponse ) , SessionError > {
13911446 let session_context = & self . session_context ;
1447+ #[ cfg( feature = "std" ) ]
13921448 if session_context. expiration . elapsed ( ) {
13931449 return Err ( InternalSessionError :: Expired ( session_context. expiration ) . into ( ) ) ;
13941450 }
@@ -1836,6 +1892,7 @@ pub mod test {
18361892 Ok ( ( ) )
18371893 }
18381894
1895+ #[ cfg( feature = "v1" ) ]
18391896 #[ test]
18401897 fn test_unchecked_proposal_fatal_error ( ) -> Result < ( ) , BoxError > {
18411898 let persister = InMemoryPersister :: default ( ) ;
@@ -1947,6 +2004,7 @@ pub mod test {
19472004 Ok ( ( ) )
19482005 }
19492006
2007+ #[ cfg( all( feature = "v1" , not( feature = "std" ) ) ) ]
19502008 #[ test]
19512009 fn test_create_error_request ( ) -> Result < ( ) , BoxError > {
19522010 let mock_err = mock_err ( ) ;
@@ -1970,6 +2028,8 @@ pub mod test {
19702028 Ok ( ( ) )
19712029 }
19722030
2031+ #[ cfg( feature = "v1" ) ]
2032+ #[ cfg( not( feature = "std" ) ) ]
19732033 #[ test]
19742034 fn test_create_error_request_expiration ( ) -> Result < ( ) , BoxError > {
19752035 let now = crate :: time:: Time :: now ( ) ;
@@ -1994,6 +2054,7 @@ pub mod test {
19942054 Ok ( ( ) )
19952055 }
19962056
2057+ #[ cfg( not( feature = "std" ) ) ]
19972058 #[ test]
19982059 fn process_error_response_success_with_fallback_enters_pending_fallback ( ) -> Result < ( ) , BoxError >
19992060 {
@@ -2015,7 +2076,7 @@ pub mod test {
20152076 assert_events ( & persister, & [ SessionEvent :: ProtocolFailed ] , false ) ;
20162077 Ok ( ( ) )
20172078 }
2018-
2079+ # [ cfg ( not ( feature = "std" ) ) ]
20192080 #[ test]
20202081 fn process_error_response_success_without_fallback_closes_session ( ) -> Result < ( ) , BoxError > {
20212082 let receiver = receiver ( HasReplyableError { error_reply : mock_err ( ) , fallback_tx : None } ) ;
@@ -2030,6 +2091,7 @@ pub mod test {
20302091 Ok ( ( ) )
20312092 }
20322093
2094+ #[ cfg( not( feature = "std" ) ) ]
20332095 #[ test]
20342096 fn process_error_response_fatal_with_fallback_enters_pending_fallback ( ) -> Result < ( ) , BoxError >
20352097 {
@@ -2053,7 +2115,7 @@ pub mod test {
20532115 assert_events ( & persister, & [ SessionEvent :: ProtocolFailed ] , false ) ;
20542116 Ok ( ( ) )
20552117 }
2056-
2118+ # [ cfg ( not ( feature = "std" ) ) ]
20572119 #[ test]
20582120 fn process_error_response_fatal_without_fallback_closes_session ( ) -> Result < ( ) , BoxError > {
20592121 let receiver = receiver ( HasReplyableError { error_reply : mock_err ( ) , fallback_tx : None } ) ;
@@ -2071,6 +2133,7 @@ pub mod test {
20712133 Ok ( ( ) )
20722134 }
20732135
2136+ #[ cfg( not( feature = "std" ) ) ]
20742137 #[ test]
20752138 fn process_error_response_transient_leaves_session_open ( ) -> Result < ( ) , BoxError > {
20762139 let receiver = receiver ( HasReplyableError {
@@ -2271,6 +2334,7 @@ pub mod test {
22712334 assert_events ( & persister, & [ SessionEvent :: Cancelled ] , false ) ;
22722335 }
22732336
2337+ #[ cfg( not( feature = "std" ) ) ]
22742338 #[ test]
22752339 fn cancel_replyable_error_with_fallback_enters_pending_fallback ( ) {
22762340 let expected_tx = mock_fallback_tx ( ) ;
@@ -2288,6 +2352,7 @@ pub mod test {
22882352 assert_events ( & persister, & [ SessionEvent :: Cancelled ] , false ) ;
22892353 }
22902354
2355+ #[ cfg( not( feature = "std" ) ) ]
22912356 #[ test]
22922357 fn cancel_replyable_error_without_fallback_closes_session ( ) {
22932358 let persister = InMemoryPersister :: < SessionEvent > :: default ( ) ;
@@ -2436,6 +2501,7 @@ pub mod test {
24362501 }
24372502 }
24382503
2504+ #[ cfg( not( feature = "std" ) ) ]
24392505 #[ test]
24402506 fn replaying_replyable_error_from_unchecked_captures_no_fallback ( ) {
24412507 let state = unchecked_proposal_v2_from_test_vector ( ) ;
@@ -2458,6 +2524,7 @@ pub mod test {
24582524 }
24592525 }
24602526
2527+ #[ cfg( not( feature = "std" ) ) ]
24612528 #[ test]
24622529 fn replaying_replyable_error_from_initialized_captures_no_fallback ( ) {
24632530 let error = mock_err ( ) ;
@@ -2479,6 +2546,7 @@ pub mod test {
24792546 }
24802547 }
24812548
2549+ #[ cfg( not( feature = "std" ) ) ]
24822550 #[ test]
24832551 fn replaying_replyable_error_from_replyable_error_carries_some_fallback ( ) {
24842552 let expected_fallback = mock_fallback_tx ( ) ;
@@ -2504,6 +2572,7 @@ pub mod test {
25042572 }
25052573 }
25062574
2575+ #[ cfg( not( feature = "std" ) ) ]
25072576 #[ test]
25082577 fn replaying_replyable_error_from_replyable_error_carries_no_fallback ( ) {
25092578 let error = mock_err ( ) ;
@@ -2524,4 +2593,24 @@ pub mod test {
25242593 other => panic ! ( "Expected HasReplyableError, got {other:?}" ) ,
25252594 }
25262595 }
2596+
2597+ #[ cfg( not( feature = "std" ) ) ]
2598+ #[ cfg( test) ]
2599+ mod json_reply_placeholder_tests {
2600+ use super :: json_reply_placeholder:: JsonReply ;
2601+ use crate :: error_codes:: ErrorCode ;
2602+
2603+ #[ test]
2604+ fn test_json_reply_new ( ) {
2605+ let reply = JsonReply :: new ( ErrorCode :: Unavailable , "test" ) ;
2606+ assert_eq ! ( reply. to_json( ) , "{}" ) ;
2607+ }
2608+
2609+ #[ test]
2610+ fn test_json_reply_from ( ) {
2611+ let val = 42u32 ;
2612+ let reply = JsonReply :: from ( & val) ;
2613+ assert_eq ! ( reply. to_json( ) , "{}" ) ;
2614+ }
2615+ }
25272616}
0 commit comments