From 81418e73f1f779a27ca9605cdfe9a60aff636ff8 Mon Sep 17 00:00:00 2001 From: DanGould Date: Tue, 14 Jan 2025 14:29:15 -0500 Subject: [PATCH] Rename SessionError to v2::RequestError The v2-specific error types in the receive module deserve their own type refleting those types in v1. I do not believe they are yet fully handled in payjoin-cli so its error handling will need to be audited. --- payjoin/src/receive/v2/error.rs | 101 ++++++++++---------------------- payjoin/src/receive/v2/mod.rs | 38 +++++------- 2 files changed, 47 insertions(+), 92 deletions(-) diff --git a/payjoin/src/receive/v2/error.rs b/payjoin/src/receive/v2/error.rs index 2eb20377..74674135 100644 --- a/payjoin/src/receive/v2/error.rs +++ b/payjoin/src/receive/v2/error.rs @@ -1,7 +1,9 @@ use core::fmt; use std::error; +use crate::hpke::HpkeError; use crate::ohttp::OhttpEncapsulationError; +use crate::receive::Error; /// Error that may occur when the v2 request from sender is malformed. /// @@ -10,110 +12,69 @@ use crate::ohttp::OhttpEncapsulationError; #[derive(Debug)] pub struct RequestError(InternalRequestError); -#[derive(Debug)] -pub(crate) enum InternalRequestError { - /// Serde deserialization failed - ParsePsbt(bitcoin::psbt::PsbtParseError), - Utf8(std::string::FromUtf8Error), -} - impl From for RequestError { fn from(value: InternalRequestError) -> Self { RequestError(value) } } -impl fmt::Display for RequestError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use InternalRequestError::*; - fn write_error( - f: &mut fmt::Formatter, - code: &str, - message: impl fmt::Display, - ) -> fmt::Result { - write!(f, r#"{{ "errorCode": "{}", "message": "{}" }}"#, code, message) - } - - match &self.0 { - ParsePsbt(e) => write_error(f, "Error parsing PSBT:", e), - Utf8(e) => write_error(f, "Error parsing PSBT:", e), - } - } -} - -impl std::error::Error for RequestError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use InternalRequestError::*; - - match &self.0 { - ParsePsbt(e) => Some(e), - Utf8(e) => Some(e), - } - } -} - impl From for crate::receive::Error { fn from(e: InternalRequestError) -> Self { crate::receive::Error::Validation(e.into()) } } #[derive(Debug)] -pub struct SessionError(InternalSessionError); - -#[derive(Debug)] -pub(crate) enum InternalSessionError { +pub(crate) enum InternalRequestError { /// The session has expired Expired(std::time::SystemTime), /// OHTTP Encapsulation failed OhttpEncapsulation(OhttpEncapsulationError), + /// Hybrid Public Key Encryption failed + Hpke(HpkeError), /// Unexpected response size UnexpectedResponseSize(usize), /// Unexpected status code UnexpectedStatusCode(http::StatusCode), } -impl fmt::Display for SessionError { +impl From for Error { + fn from(e: std::time::SystemTime) -> Self { InternalRequestError::Expired(e).into() } +} + +impl From for Error { + fn from(e: OhttpEncapsulationError) -> Self { + InternalRequestError::OhttpEncapsulation(e).into() + } +} + +impl From for Error { + fn from(e: HpkeError) -> Self { InternalRequestError::Hpke(e).into() } +} + +impl fmt::Display for RequestError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.0 { - InternalSessionError::Expired(expiry) => write!(f, "Session expired at {:?}", expiry), - InternalSessionError::OhttpEncapsulation(e) => + InternalRequestError::Expired(expiry) => write!(f, "Session expired at {:?}", expiry), + InternalRequestError::OhttpEncapsulation(e) => write!(f, "OHTTP Encapsulation Error: {}", e), - InternalSessionError::UnexpectedResponseSize(size) => write!( + InternalRequestError::Hpke(e) => write!(f, "Hpke decryption failed: {}", e), + InternalRequestError::UnexpectedResponseSize(size) => write!( f, "Unexpected response size {}, expected {} bytes", size, crate::ohttp::ENCAPSULATED_MESSAGE_BYTES ), - InternalSessionError::UnexpectedStatusCode(status) => + InternalRequestError::UnexpectedStatusCode(status) => write!(f, "Unexpected status code: {}", status), } } } -impl error::Error for SessionError { +impl error::Error for RequestError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match &self.0 { - InternalSessionError::Expired(_) => None, - InternalSessionError::OhttpEncapsulation(e) => Some(e), - InternalSessionError::UnexpectedResponseSize(_) => None, - InternalSessionError::UnexpectedStatusCode(_) => None, + InternalRequestError::Expired(_) => None, + InternalRequestError::OhttpEncapsulation(e) => Some(e), + InternalRequestError::Hpke(e) => Some(e), + InternalRequestError::UnexpectedResponseSize(_) => None, + InternalRequestError::UnexpectedStatusCode(_) => None, } } } - -impl From for SessionError { - fn from(e: InternalSessionError) -> Self { SessionError(e) } -} - -impl From for SessionError { - fn from(e: OhttpEncapsulationError) -> Self { - SessionError(InternalSessionError::OhttpEncapsulation(e)) - } -} - -impl From for super::Error { - fn from(e: crate::hpke::HpkeError) -> Self { super::Error::External(Box::new(e)) } -} - -impl From for super::Error { - fn from(e: crate::ohttp::OhttpEncapsulationError) -> Self { - super::Error::External(Box::new(e)) - } -} diff --git a/payjoin/src/receive/v2/mod.rs b/payjoin/src/receive/v2/mod.rs index 7ca8eb55..ebcd965b 100644 --- a/payjoin/src/receive/v2/mod.rs +++ b/payjoin/src/receive/v2/mod.rs @@ -4,8 +4,8 @@ use std::time::{Duration, SystemTime}; use bitcoin::hashes::{sha256, Hash}; use bitcoin::psbt::Psbt; use bitcoin::{Address, FeeRate, OutPoint, Script, TxOut}; -pub(crate) use error::{InternalRequestError, InternalSessionError}; -pub use error::{RequestError, SessionError}; +pub(crate) use error::InternalRequestError; +pub use error::RequestError; use serde::de::Deserializer; use serde::{Deserialize, Serialize}; use url::Url; @@ -96,12 +96,12 @@ impl Receiver { pub fn extract_req( &mut self, ohttp_relay: &Url, - ) -> Result<(Request, ohttp::ClientResponse), SessionError> { + ) -> Result<(Request, ohttp::ClientResponse), Error> { if SystemTime::now() > self.context.expiry { - return Err(InternalSessionError::Expired(self.context.expiry).into()); + return Err(InternalRequestError::Expired(self.context.expiry).into()); } let (body, ohttp_ctx) = - self.fallback_req_body().map_err(InternalSessionError::OhttpEncapsulation)?; + self.fallback_req_body().map_err(InternalRequestError::OhttpEncapsulation)?; let url = ohttp_relay.clone(); let req = Request::new_v2(url, body); Ok((req, ohttp_ctx)) @@ -116,9 +116,7 @@ impl Receiver { ) -> Result, Error> { let response_array: &[u8; crate::ohttp::ENCAPSULATED_MESSAGE_BYTES] = body.try_into().map_err(|_| { - Error::External(Box::new(SessionError::from( - InternalSessionError::UnexpectedResponseSize(body.len()), - ))) + Error::Validation(InternalRequestError::UnexpectedResponseSize(body.len()).into()) })?; log::trace!("decapsulating directory response"); let response = ohttp_decapsulate(context, response_array)?; @@ -258,7 +256,7 @@ impl UncheckedProposal { &mut self, err: &Error, ohttp_relay: &Url, - ) -> Result<(Request, ohttp::ClientResponse), SessionError> { + ) -> Result<(Request, ohttp::ClientResponse), RequestError> { let subdir = subdir(&self.context.directory, &id(&self.context.s)); let (body, ohttp_ctx) = ohttp_encapsulate( &mut self.context.ohttp_keys, @@ -266,7 +264,7 @@ impl UncheckedProposal { subdir.as_str(), Some(err.to_json().as_bytes()), ) - .map_err(InternalSessionError::OhttpEncapsulation)?; + .map_err(InternalRequestError::OhttpEncapsulation)?; let req = Request::new_v2(ohttp_relay.clone(), body); Ok((req, ohttp_ctx)) @@ -278,18 +276,16 @@ impl UncheckedProposal { &mut self, body: &[u8], context: ohttp::ClientResponse, - ) -> Result<(), SessionError> { - let response_array: &[u8; crate::ohttp::ENCAPSULATED_MESSAGE_BYTES] = - body.try_into().map_err(|_| { - SessionError::from(InternalSessionError::UnexpectedResponseSize(body.len())) - })?; - let response = ohttp_decapsulate(context, response_array)?; + ) -> Result<(), RequestError> { + let response_array: &[u8; crate::ohttp::ENCAPSULATED_MESSAGE_BYTES] = body + .try_into() + .map_err(|_| InternalRequestError::UnexpectedResponseSize(body.len()))?; + let response = ohttp_decapsulate(context, response_array) + .map_err(InternalRequestError::OhttpEncapsulation)?; match response.status() { http::StatusCode::OK => Ok(()), - _ => Err(SessionError::from(InternalSessionError::UnexpectedStatusCode( - response.status(), - ))), + _ => Err(InternalRequestError::UnexpectedStatusCode(response.status()).into()), } } } @@ -546,9 +542,7 @@ impl PayjoinProposal { ) -> Result<(), Error> { let response_array: &[u8; crate::ohttp::ENCAPSULATED_MESSAGE_BYTES] = res.try_into().map_err(|_| { - Error::External(Box::new(SessionError::from( - InternalSessionError::UnexpectedResponseSize(res.len()), - ))) + Error::Validation(InternalRequestError::UnexpectedResponseSize(res.len()).into()) })?; let res = ohttp_decapsulate(ohttp_context, response_array)?; if res.status().is_success() {