Skip to content

Commit

Permalink
Rename SessionError to v2::RequestError
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
DanGould committed Jan 14, 2025
1 parent 4549b38 commit 81418e7
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 92 deletions.
101 changes: 31 additions & 70 deletions payjoin/src/receive/v2/error.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand All @@ -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<InternalRequestError> 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<InternalRequestError> 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<std::time::SystemTime> for Error {
fn from(e: std::time::SystemTime) -> Self { InternalRequestError::Expired(e).into() }
}

impl From<OhttpEncapsulationError> for Error {
fn from(e: OhttpEncapsulationError) -> Self {
InternalRequestError::OhttpEncapsulation(e).into()
}
}

impl From<HpkeError> 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<InternalSessionError> for SessionError {
fn from(e: InternalSessionError) -> Self { SessionError(e) }
}

impl From<OhttpEncapsulationError> for SessionError {
fn from(e: OhttpEncapsulationError) -> Self {
SessionError(InternalSessionError::OhttpEncapsulation(e))
}
}

impl From<crate::hpke::HpkeError> for super::Error {
fn from(e: crate::hpke::HpkeError) -> Self { super::Error::External(Box::new(e)) }
}

impl From<crate::ohttp::OhttpEncapsulationError> for super::Error {
fn from(e: crate::ohttp::OhttpEncapsulationError) -> Self {
super::Error::External(Box::new(e))
}
}
38 changes: 16 additions & 22 deletions payjoin/src/receive/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand All @@ -116,9 +116,7 @@ impl Receiver {
) -> Result<Option<UncheckedProposal>, 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)?;
Expand Down Expand Up @@ -258,15 +256,15 @@ 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,
"POST",
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))
Expand All @@ -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()),
}
}
}
Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit 81418e7

Please sign in to comment.