Skip to content

Commit f8fcdfe

Browse files
committed
feat: use axum for ohttp gateway middleware
1 parent db8f835 commit f8fcdfe

File tree

8 files changed

+296
-352
lines changed

8 files changed

+296
-352
lines changed

Cargo-minimal.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2441,6 +2441,8 @@ dependencies = [
24412441
name = "ohttp-relay"
24422442
version = "0.0.11"
24432443
dependencies = [
2444+
"bhttp",
2445+
"bitcoin-ohttp",
24442446
"byteorder",
24452447
"bytes",
24462448
"futures",

Cargo-recent.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2441,6 +2441,8 @@ dependencies = [
24412441
name = "ohttp-relay"
24422442
version = "0.0.11"
24432443
dependencies = [
2444+
"bhttp",
2445+
"bitcoin-ohttp",
24442446
"byteorder",
24452447
"bytes",
24462448
"futures",

ohttp-relay/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ tokio-util = { version = "0.7.16", features = ["net", "codec"] }
4747
tower = "0.5"
4848
tracing = "0.1.41"
4949
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
50+
ohttp = { package = "bitcoin-ohttp", version = "0.6" }
51+
bhttp = { version = "0.6.1", features = ["http"] }
5052

5153
[dev-dependencies]
5254
mockito = "1.7.0"

ohttp-relay/src/gateway_helpers.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use http_body_util::combinators::BoxBody;
2+
use http_body_util::{BodyExt, Full};
3+
use hyper::body::Bytes;
4+
use hyper::{Request, Response, Uri};
5+
6+
pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
7+
8+
pub const CHACHA20_POLY1305_NONCE_LEN: usize = 32;
9+
pub const POLY1305_TAG_SIZE: usize = 16;
10+
pub const ENCAPSULATED_MESSAGE_BYTES: usize = 65536;
11+
pub const BHTTP_REQ_BYTES: usize =
12+
ENCAPSULATED_MESSAGE_BYTES - (CHACHA20_POLY1305_NONCE_LEN + POLY1305_TAG_SIZE);
13+
14+
#[derive(Debug)]
15+
pub enum GatewayError {
16+
BadRequest(String),
17+
OhttpKeyRejection(String),
18+
InternalServerError(String),
19+
}
20+
21+
impl std::fmt::Display for GatewayError {
22+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23+
match self {
24+
GatewayError::BadRequest(msg) => write!(f, "Bad request: {}", msg),
25+
GatewayError::OhttpKeyRejection(msg) => write!(f, "OHTTP key rejection: {}", msg),
26+
GatewayError::InternalServerError(msg) => write!(f, "Internal server error: {}", msg),
27+
}
28+
}
29+
}
30+
31+
impl std::error::Error for GatewayError {}
32+
33+
pub async fn decapsulate_ohttp_request<B>(
34+
req: Request<B>,
35+
ohttp_server: &ohttp::Server,
36+
) -> Result<(Request<BoxBody<Bytes, hyper::Error>>, ohttp::ServerResponse), GatewayError>
37+
where
38+
B: hyper::body::Body<Data = Bytes> + Send + 'static,
39+
B::Error: Into<BoxError>,
40+
{
41+
// Collect OHTTP body
42+
let ohttp_body = req
43+
.into_body()
44+
.collect()
45+
.await
46+
.map_err(|e| GatewayError::BadRequest(format!("Failed to read body: {}", e.into())))?
47+
.to_bytes();
48+
49+
// Decapsulate using OHTTP server
50+
let (bhttp_req, res_ctx) = ohttp_server.decapsulate(&ohttp_body).map_err(|e| {
51+
GatewayError::OhttpKeyRejection(format!("OHTTP decapsulation failed: {}", e))
52+
})?;
53+
54+
// Parse BHTTP message
55+
let mut cursor = std::io::Cursor::new(bhttp_req);
56+
let bhttp_msg = bhttp::Message::read_bhttp(&mut cursor)
57+
.map_err(|e| GatewayError::BadRequest(format!("Invalid BHTTP: {}", e)))?;
58+
59+
let uri = Uri::builder()
60+
.scheme(bhttp_msg.control().scheme().unwrap_or_default())
61+
.authority(bhttp_msg.control().authority().unwrap_or_default())
62+
.path_and_query(bhttp_msg.control().path().unwrap_or_default())
63+
.build()
64+
.map_err(|e| GatewayError::BadRequest(format!("Invalid URI: {}", e)))?;
65+
66+
let body = bhttp_msg.content().to_vec();
67+
let mut http_req =
68+
Request::builder().uri(uri).method(bhttp_msg.control().method().unwrap_or_default());
69+
70+
for header in bhttp_msg.header().fields() {
71+
http_req = http_req.header(header.name(), header.value());
72+
}
73+
74+
let request = http_req.body(full(body)).map_err(|e| {
75+
GatewayError::InternalServerError(format!("Failed to build request: {}", e))
76+
})?;
77+
78+
Ok((request, res_ctx))
79+
}
80+
81+
pub async fn encapsulate_ohttp_response(
82+
response: Response<BoxBody<Bytes, hyper::Error>>,
83+
res_ctx: ohttp::ServerResponse,
84+
) -> Result<Response<BoxBody<Bytes, hyper::Error>>, GatewayError> {
85+
let (parts, body) = response.into_parts();
86+
87+
let mut bhttp_res =
88+
bhttp::Message::response(bhttp::StatusCode::try_from(parts.status.as_u16()).map_err(
89+
|e| GatewayError::InternalServerError(format!("Invalid status code: {}", e)),
90+
)?);
91+
92+
for (name, value) in parts.headers.iter() {
93+
bhttp_res.put_header(name.as_str(), value.to_str().unwrap_or_default());
94+
}
95+
96+
let full_body = body
97+
.collect()
98+
.await
99+
.map_err(|e| GatewayError::InternalServerError(format!("Failed to collect body: {}", e)))?
100+
.to_bytes();
101+
bhttp_res.write_content(&full_body);
102+
103+
let mut bhttp_bytes = Vec::new();
104+
bhttp_res.write_bhttp(bhttp::Mode::KnownLength, &mut bhttp_bytes).map_err(|e| {
105+
GatewayError::InternalServerError(format!("BHTTP serialization failed: {}", e))
106+
})?;
107+
108+
bhttp_bytes.resize(BHTTP_REQ_BYTES, 0);
109+
110+
let ohttp_res = res_ctx.encapsulate(&bhttp_bytes).map_err(|e| {
111+
GatewayError::InternalServerError(format!("OHTTP encapsulation failed: {}", e))
112+
})?;
113+
114+
assert!(
115+
ohttp_res.len() == ENCAPSULATED_MESSAGE_BYTES,
116+
"Unexpected OHTTP response size: {} != {}",
117+
ohttp_res.len(),
118+
ENCAPSULATED_MESSAGE_BYTES
119+
);
120+
121+
Ok(Response::new(full(ohttp_res)))
122+
}
123+
124+
fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, hyper::Error> {
125+
Full::new(chunk.into()).map_err(|never| match never {}).boxed()
126+
}

ohttp-relay/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ pub mod gateway_prober;
3636
mod gateway_uri;
3737
pub mod sentinel;
3838
pub use sentinel::SentinelTag;
39+
pub mod gateway_helpers;
40+
41+
pub use gateway_helpers::{
42+
decapsulate_ohttp_request, encapsulate_ohttp_response, BHTTP_REQ_BYTES,
43+
ENCAPSULATED_MESSAGE_BYTES,
44+
};
3945

4046
use crate::error::{BoxError, Error};
4147

0 commit comments

Comments
 (0)