Skip to content

Commit 91e854e

Browse files
committed
Move shared response processing code to ohttp.rs
The `process_res` methods across send and receive have a lot of shared code that can be extracted into reusable functions. `process_get_res` and `process_post_res` essentially do the same thing, and could conceivably be combined into one function. They were kept separate because "202 Accepted" is not a valid response for POST requests.
1 parent 1063687 commit 91e854e

File tree

5 files changed

+105
-64
lines changed

5 files changed

+105
-64
lines changed

payjoin/src/ohttp.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,47 @@ pub fn ohttp_encapsulate(
5252
Ok((buffer, ohttp_ctx))
5353
}
5454

55+
pub enum DirectoryResponseError {
56+
InvalidSize(usize),
57+
OhttpDecapsulation(OhttpEncapsulationError),
58+
UnexpectedStatusCode(http::StatusCode),
59+
}
60+
61+
pub fn process_get_res(
62+
res: &[u8],
63+
ohttp_context: ohttp::ClientResponse,
64+
) -> Result<Option<Vec<u8>>, DirectoryResponseError> {
65+
let response = process_ohttp_res(res, ohttp_context)?;
66+
match response.status() {
67+
http::StatusCode::OK => Ok(Some(response.body().to_vec())),
68+
http::StatusCode::ACCEPTED => Ok(None),
69+
status_code => Err(DirectoryResponseError::UnexpectedStatusCode(status_code)),
70+
}
71+
}
72+
73+
pub fn process_post_res(
74+
res: &[u8],
75+
ohttp_context: ohttp::ClientResponse,
76+
) -> Result<(), DirectoryResponseError> {
77+
let response = process_ohttp_res(res, ohttp_context)?;
78+
match response.status() {
79+
http::StatusCode::OK => Ok(()),
80+
status_code => Err(DirectoryResponseError::UnexpectedStatusCode(status_code)),
81+
}
82+
}
83+
84+
fn process_ohttp_res(
85+
res: &[u8],
86+
ohttp_context: ohttp::ClientResponse,
87+
) -> Result<http::Response<Vec<u8>>, DirectoryResponseError> {
88+
let response_array: &[u8; crate::directory::ENCAPSULATED_MESSAGE_BYTES] =
89+
res.try_into().map_err(|_| DirectoryResponseError::InvalidSize(res.len()))?;
90+
log::trace!("decapsulating directory response");
91+
let res = ohttp_decapsulate(ohttp_context, response_array)
92+
.map_err(DirectoryResponseError::OhttpDecapsulation)?;
93+
Ok(res)
94+
}
95+
5596
/// decapsulate ohttp, bhttp response and return http response body and status code
5697
pub fn ohttp_decapsulate(
5798
res_ctx: ohttp::ClientResponse,

payjoin/src/receive/v2/error.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::error;
33

44
use super::Error::V2;
55
use crate::hpke::HpkeError;
6-
use crate::ohttp::OhttpEncapsulationError;
6+
use crate::ohttp::{DirectoryResponseError, OhttpEncapsulationError};
77
use crate::receive::error::Error;
88

99
/// Error that may occur during a v2 session typestate change
@@ -21,6 +21,24 @@ impl From<InternalSessionError> for Error {
2121
fn from(e: InternalSessionError) -> Self { V2(e.into()) }
2222
}
2323

24+
impl From<DirectoryResponseError> for SessionError {
25+
fn from(value: DirectoryResponseError) -> Self {
26+
match value {
27+
DirectoryResponseError::InvalidSize(e) =>
28+
InternalSessionError::UnexpectedResponseSize(e),
29+
DirectoryResponseError::OhttpDecapsulation(e) =>
30+
InternalSessionError::OhttpEncapsulation(e),
31+
DirectoryResponseError::UnexpectedStatusCode(e) =>
32+
InternalSessionError::UnexpectedStatusCode(e),
33+
}
34+
.into()
35+
}
36+
}
37+
38+
impl From<DirectoryResponseError> for Error {
39+
fn from(value: DirectoryResponseError) -> Self { V2(value.into()) }
40+
}
41+
2442
#[derive(Debug)]
2543
pub(crate) enum InternalSessionError {
2644
/// Url parsing failed

payjoin/src/receive/v2/mod.rs

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ use super::{
1717
v1, InternalPayloadError, JsonReply, OutputSubstitutionError, ReplyableError, SelectionError,
1818
};
1919
use crate::hpke::{decrypt_message_a, encrypt_message_b, HpkeKeyPair, HpkePublicKey};
20-
use crate::ohttp::{ohttp_decapsulate, ohttp_encapsulate, OhttpEncapsulationError, OhttpKeys};
20+
use crate::ohttp::{
21+
ohttp_encapsulate, process_get_res, process_post_res, OhttpEncapsulationError, OhttpKeys,
22+
};
2123
use crate::output_substitution::OutputSubstitution;
2224
use crate::persist::Persister;
2325
use crate::receive::{parse_payload, InputPair};
@@ -180,23 +182,15 @@ impl Receiver<WithContext> {
180182
body: &[u8],
181183
context: ohttp::ClientResponse,
182184
) -> Result<Option<Receiver<UncheckedProposal>>, Error> {
183-
let response_array: &[u8; crate::directory::ENCAPSULATED_MESSAGE_BYTES] =
184-
body.try_into()
185-
.map_err(|_| InternalSessionError::UnexpectedResponseSize(body.len()))?;
186-
log::trace!("decapsulating directory response");
187-
let response = ohttp_decapsulate(context, response_array)
188-
.map_err(InternalSessionError::OhttpEncapsulation)?;
189-
if response.body().is_empty() {
190-
log::debug!("response is empty");
191-
return Ok(None);
192-
}
193-
match String::from_utf8(response.body().to_vec()) {
185+
let body = match process_get_res(body, context)? {
186+
Some(body) => body,
187+
None => return Ok(None),
188+
};
189+
match String::from_utf8(body.clone()) {
194190
// V1 response bodies are utf8 plaintext
195191
Ok(response) => Ok(Some(Receiver { state: self.extract_proposal_from_v1(response)? })),
196192
// V2 response bodies are encrypted binary
197-
Err(_) => Ok(Some(Receiver {
198-
state: self.extract_proposal_from_v2(response.body().to_vec())?,
199-
})),
193+
Err(_) => Ok(Some(Receiver { state: self.extract_proposal_from_v2(body)? })),
200194
}
201195
}
202196

@@ -350,16 +344,7 @@ impl Receiver<UncheckedProposal> {
350344
body: &[u8],
351345
context: ohttp::ClientResponse,
352346
) -> Result<(), SessionError> {
353-
let response_array: &[u8; crate::directory::ENCAPSULATED_MESSAGE_BYTES] =
354-
body.try_into()
355-
.map_err(|_| InternalSessionError::UnexpectedResponseSize(body.len()))?;
356-
let response = ohttp_decapsulate(context, response_array)
357-
.map_err(InternalSessionError::OhttpEncapsulation)?;
358-
359-
match response.status() {
360-
http::StatusCode::OK => Ok(()),
361-
_ => Err(InternalSessionError::UnexpectedStatusCode(response.status()).into()),
362-
}
347+
process_post_res(body, context).map_err(Into::into)
363348
}
364349
}
365350

@@ -648,15 +633,7 @@ impl Receiver<PayjoinProposal> {
648633
res: &[u8],
649634
ohttp_context: ohttp::ClientResponse,
650635
) -> Result<(), Error> {
651-
let response_array: &[u8; crate::directory::ENCAPSULATED_MESSAGE_BYTES] =
652-
res.try_into().map_err(|_| InternalSessionError::UnexpectedResponseSize(res.len()))?;
653-
let res = ohttp_decapsulate(ohttp_context, response_array)
654-
.map_err(InternalSessionError::OhttpEncapsulation)?;
655-
if res.status().is_success() {
656-
Ok(())
657-
} else {
658-
Err(InternalSessionError::UnexpectedStatusCode(res.status()).into())
659-
}
636+
process_post_res(res, ohttp_context).map_err(Into::into)
660637
}
661638
}
662639

payjoin/src/send/v2/error.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use core::fmt;
22

3+
use super::ResponseError;
4+
use crate::ohttp::DirectoryResponseError;
35
use crate::uri::url_ext::ParseReceiverPubkeyParamError;
46

57
/// Error returned when request could not be created.
@@ -120,3 +122,23 @@ impl From<InternalEncapsulationError> for super::ResponseError {
120122
)
121123
}
122124
}
125+
126+
impl From<DirectoryResponseError> for EncapsulationError {
127+
fn from(value: DirectoryResponseError) -> Self {
128+
match value {
129+
DirectoryResponseError::InvalidSize(e) => InternalEncapsulationError::InvalidSize(e),
130+
DirectoryResponseError::OhttpDecapsulation(e) => InternalEncapsulationError::Ohttp(e),
131+
DirectoryResponseError::UnexpectedStatusCode(e) =>
132+
InternalEncapsulationError::UnexpectedStatusCode(e),
133+
}
134+
.into()
135+
}
136+
}
137+
138+
impl From<DirectoryResponseError> for ResponseError {
139+
fn from(value: DirectoryResponseError) -> Self {
140+
ResponseError::Validation(
141+
super::InternalValidationError::V2Encapsulation(value.into()).into(),
142+
)
143+
}
144+
}

payjoin/src/send/v2/mod.rs

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use url::Url;
3232
use super::error::BuildSenderError;
3333
use super::*;
3434
use crate::hpke::{decrypt_message_b, encrypt_message_a, HpkeSecretKey};
35-
use crate::ohttp::{ohttp_decapsulate, ohttp_encapsulate};
35+
use crate::ohttp::{ohttp_encapsulate, process_get_res, process_post_res};
3636
use crate::persist::Persister;
3737
use crate::send::v1;
3838
use crate::uri::{ShortId, UrlExt};
@@ -325,24 +325,14 @@ impl Sender<V2PostContext> {
325325
self,
326326
response: &[u8],
327327
) -> Result<Sender<V2GetContext>, EncapsulationError> {
328-
let response_array: &[u8; crate::directory::ENCAPSULATED_MESSAGE_BYTES] = response
329-
.try_into()
330-
.map_err(|_| InternalEncapsulationError::InvalidSize(response.len()))?;
331-
let response = ohttp_decapsulate(self.state.ohttp_ctx, response_array)
332-
.map_err(InternalEncapsulationError::Ohttp)?;
333-
match response.status() {
334-
http::StatusCode::OK => {
335-
// return OK with new Typestate
336-
Ok(Sender {
337-
state: V2GetContext {
338-
endpoint: self.state.endpoint,
339-
psbt_ctx: self.state.psbt_ctx,
340-
hpke_ctx: self.state.hpke_ctx,
341-
},
342-
})
343-
}
344-
_ => Err(InternalEncapsulationError::UnexpectedStatusCode(response.status()))?,
345-
}
328+
process_post_res(response, self.state.ohttp_ctx)?;
329+
Ok(Sender {
330+
state: V2GetContext {
331+
endpoint: self.state.endpoint,
332+
psbt_ctx: self.state.psbt_ctx,
333+
hpke_ctx: self.state.hpke_ctx,
334+
},
335+
})
346336
}
347337
}
348338

@@ -404,16 +394,9 @@ impl Sender<V2GetContext> {
404394
response: &[u8],
405395
ohttp_ctx: ohttp::ClientResponse,
406396
) -> Result<Option<Psbt>, ResponseError> {
407-
let response_array: &[u8; crate::directory::ENCAPSULATED_MESSAGE_BYTES] = response
408-
.try_into()
409-
.map_err(|_| InternalEncapsulationError::InvalidSize(response.len()))?;
410-
411-
let response = ohttp_decapsulate(ohttp_ctx, response_array)
412-
.map_err(InternalEncapsulationError::Ohttp)?;
413-
let body = match response.status() {
414-
http::StatusCode::OK => response.body().to_vec(),
415-
http::StatusCode::ACCEPTED => return Ok(None),
416-
_ => return Err(InternalEncapsulationError::UnexpectedStatusCode(response.status()))?,
397+
let body = match process_get_res(response, ohttp_ctx)? {
398+
Some(body) => body,
399+
None => return Ok(None),
417400
};
418401
let psbt = decrypt_message_b(
419402
&body,

0 commit comments

Comments
 (0)