Skip to content

Commit 7a9db24

Browse files
committed
feat: implement ohttp gateway middleware using axum
- Added OHTTP middleware - Removed old directory handling feat: use axum for ohttp gateway middleware chore: removed ohttp handling from directory
1 parent 9bee4c7 commit 7a9db24

File tree

11 files changed

+396
-185
lines changed

11 files changed

+396
-185
lines changed

Cargo-minimal.lock

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2546,6 +2546,8 @@ dependencies = [
25462546
name = "ohttp-relay"
25472547
version = "0.0.11"
25482548
dependencies = [
2549+
"bhttp",
2550+
"bitcoin-ohttp",
25492551
"byteorder",
25502552
"bytes",
25512553
"futures",
@@ -2816,7 +2818,6 @@ name = "payjoin-directory"
28162818
version = "0.0.3"
28172819
dependencies = [
28182820
"anyhow",
2819-
"bhttp",
28202821
"bitcoin 0.32.8",
28212822
"bitcoin-ohttp",
28222823
"clap 4.5.46",
@@ -2878,6 +2879,7 @@ dependencies = [
28782879
"anyhow",
28792880
"axum",
28802881
"axum-server",
2882+
"bitcoin-ohttp",
28812883
"clap 4.5.46",
28822884
"config",
28832885
"flate2",

Cargo-recent.lock

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2546,6 +2546,8 @@ dependencies = [
25462546
name = "ohttp-relay"
25472547
version = "0.0.11"
25482548
dependencies = [
2549+
"bhttp",
2550+
"bitcoin-ohttp",
25492551
"byteorder",
25502552
"bytes",
25512553
"futures",
@@ -2816,7 +2818,6 @@ name = "payjoin-directory"
28162818
version = "0.0.3"
28172819
dependencies = [
28182820
"anyhow",
2819-
"bhttp",
28202821
"bitcoin 0.32.8",
28212822
"bitcoin-ohttp",
28222823
"clap 4.5.46",
@@ -2878,6 +2879,7 @@ dependencies = [
28782879
"anyhow",
28792880
"axum",
28802881
"axum-server",
2882+
"bitcoin-ohttp",
28812883
"clap 4.5.46",
28822884
"config",
28832885
"flate2",

ohttp-relay/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ ws-bootstrap = ["futures", "rustls", "tokio-tungstenite"]
2020
_test-util = []
2121

2222
[dependencies]
23+
bhttp = { version = "0.6.1", features = ["http"] }
2324
byteorder = "1.5.0"
2425
bytes = "1.10.1"
2526
futures = { version = "0.3.31", optional = true }
@@ -33,6 +34,7 @@ hyper-rustls = { version = "0.27.7", default-features = false, features = [
3334
"ring",
3435
] }
3536
hyper-util = { version = "0.1.16", features = ["client-legacy", "service"] }
37+
ohttp = { package = "bitcoin-ohttp", version = "0.6" }
3638
rustls = { version = "0.23.31", optional = true, default-features = false, features = [
3739
"ring",
3840
] }

ohttp-relay/src/gateway_helpers.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use std::io::Cursor;
2+
3+
pub const CHACHA20_POLY1305_NONCE_LEN: usize = 32;
4+
pub const POLY1305_TAG_SIZE: usize = 16;
5+
pub const OHTTP_OVERHEAD: usize = CHACHA20_POLY1305_NONCE_LEN + POLY1305_TAG_SIZE;
6+
pub const ENCAPSULATED_MESSAGE_BYTES: usize = 8192;
7+
pub const BHTTP_REQ_BYTES: usize = ENCAPSULATED_MESSAGE_BYTES - OHTTP_OVERHEAD;
8+
9+
#[derive(Debug)]
10+
pub enum GatewayError {
11+
BadRequest(String),
12+
OhttpKeyRejection(String),
13+
InternalServerError(String),
14+
}
15+
16+
impl std::fmt::Display for GatewayError {
17+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18+
match self {
19+
GatewayError::BadRequest(msg) => write!(f, "Bad request: {}", msg),
20+
GatewayError::OhttpKeyRejection(msg) => write!(f, "OHTTP key rejection: {}", msg),
21+
GatewayError::InternalServerError(msg) => write!(f, "Internal server error: {}", msg),
22+
}
23+
}
24+
}
25+
26+
impl std::error::Error for GatewayError {}
27+
28+
pub struct DecapsulatedRequest {
29+
pub method: String,
30+
pub uri: String,
31+
pub headers: Vec<(String, String)>,
32+
pub body: Vec<u8>,
33+
}
34+
35+
pub fn decapsulate_ohttp_request(
36+
ohttp_body: &[u8],
37+
ohttp_server: &ohttp::Server,
38+
) -> Result<(DecapsulatedRequest, ohttp::ServerResponse), GatewayError> {
39+
let (bhttp_req, res_ctx) = ohttp_server.decapsulate(ohttp_body).map_err(|e| {
40+
GatewayError::OhttpKeyRejection(format!("OHTTP decapsulation failed: {}", e))
41+
})?;
42+
43+
let mut cursor = Cursor::new(bhttp_req);
44+
let bhttp_msg = bhttp::Message::read_bhttp(&mut cursor)
45+
.map_err(|e| GatewayError::BadRequest(format!("Invalid BHTTP: {}", e)))?;
46+
47+
let method = String::from_utf8(bhttp_msg.control().method().unwrap_or_default().to_vec())
48+
.unwrap_or_else(|_| "GET".to_string());
49+
50+
let uri = format!(
51+
"{}://{}{}",
52+
std::str::from_utf8(bhttp_msg.control().scheme().unwrap_or_default()).unwrap_or("https"),
53+
std::str::from_utf8(bhttp_msg.control().authority().unwrap_or_default())
54+
.unwrap_or("localhost"),
55+
std::str::from_utf8(bhttp_msg.control().path().unwrap_or_default()).unwrap_or("/")
56+
);
57+
58+
let mut headers = Vec::new();
59+
for field in bhttp_msg.header().fields() {
60+
let name = String::from_utf8_lossy(field.name()).to_string();
61+
let value = String::from_utf8_lossy(field.value()).to_string();
62+
headers.push((name, value));
63+
}
64+
65+
let body = bhttp_msg.content().to_vec();
66+
67+
Ok((DecapsulatedRequest { method, uri, headers, body }, res_ctx))
68+
}
69+
70+
pub fn encapsulate_ohttp_response(
71+
status_code: u16,
72+
headers: Vec<(String, String)>,
73+
body: Vec<u8>,
74+
res_ctx: ohttp::ServerResponse,
75+
) -> Result<Vec<u8>, GatewayError> {
76+
let bhttp_status = bhttp::StatusCode::try_from(status_code)
77+
.map_err(|e| GatewayError::InternalServerError(format!("Invalid status code: {}", e)))?;
78+
79+
let mut bhttp_res = bhttp::Message::response(bhttp_status);
80+
81+
for (name, value) in &headers {
82+
bhttp_res.put_header(name.as_str(), value.as_str());
83+
}
84+
85+
bhttp_res.write_content(&body);
86+
87+
let mut bhttp_bytes = Vec::new();
88+
bhttp_res.write_bhttp(bhttp::Mode::KnownLength, &mut bhttp_bytes).map_err(|e| {
89+
GatewayError::InternalServerError(format!("BHTTP serialization failed: {}", e))
90+
})?;
91+
92+
if bhttp_bytes.len() > BHTTP_REQ_BYTES {
93+
return Err(GatewayError::InternalServerError(format!(
94+
"BHTTP response too large: {} > {}",
95+
bhttp_bytes.len(),
96+
BHTTP_REQ_BYTES
97+
)));
98+
}
99+
100+
bhttp_bytes.resize(BHTTP_REQ_BYTES, 0);
101+
102+
let ohttp_res = res_ctx.encapsulate(&bhttp_bytes).map_err(|e| {
103+
GatewayError::InternalServerError(format!("OHTTP encapsulation failed: {}", e))
104+
})?;
105+
106+
if ohttp_res.len() != ENCAPSULATED_MESSAGE_BYTES {
107+
return Err(GatewayError::InternalServerError(format!(
108+
"Unexpected OHTTP response size: {} != {}",
109+
ohttp_res.len(),
110+
ENCAPSULATED_MESSAGE_BYTES
111+
)));
112+
}
113+
114+
Ok(ohttp_res)
115+
}

ohttp-relay/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ pub mod gateway_prober;
4141
mod gateway_uri;
4242
pub mod sentinel;
4343
pub use sentinel::SentinelTag;
44+
pub mod gateway_helpers;
45+
46+
pub use gateway_helpers::{decapsulate_ohttp_request, encapsulate_ohttp_response};
4447

4548
use crate::error::{BoxError, Error};
4649

payjoin-directory/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ acme = ["tokio-rustls-acme"]
2020

2121
[dependencies]
2222
anyhow = "1.0.99"
23-
bhttp = { version = "0.6.1", features = ["http"] }
2423
bitcoin = { version = "0.32.7", features = ["base64", "rand-std"] }
2524
clap = { version = "4.5.45", features = ["derive", "env"] }
2625
config = "0.15.14"

0 commit comments

Comments
 (0)