Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo-minimal.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2441,6 +2441,8 @@ dependencies = [
name = "ohttp-relay"
version = "0.0.11"
dependencies = [
"bhttp",
"bitcoin-ohttp",
"byteorder",
"bytes",
"futures",
Expand Down Expand Up @@ -2631,7 +2633,6 @@ name = "payjoin-directory"
version = "0.0.3"
dependencies = [
"anyhow",
"bhttp",
"bitcoin 0.32.8",
"bitcoin-ohttp",
"clap",
Expand Down Expand Up @@ -2691,6 +2692,7 @@ dependencies = [
"anyhow",
"axum",
"axum-server",
"bitcoin-ohttp",
"clap",
"config",
"ohttp-relay",
Expand Down
4 changes: 3 additions & 1 deletion Cargo-recent.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2441,6 +2441,8 @@ dependencies = [
name = "ohttp-relay"
version = "0.0.11"
dependencies = [
"bhttp",
"bitcoin-ohttp",
"byteorder",
"bytes",
"futures",
Expand Down Expand Up @@ -2631,7 +2633,6 @@ name = "payjoin-directory"
version = "0.0.3"
dependencies = [
"anyhow",
"bhttp",
"bitcoin 0.32.8",
"bitcoin-ohttp",
"clap",
Expand Down Expand Up @@ -2691,6 +2692,7 @@ dependencies = [
"anyhow",
"axum",
"axum-server",
"bitcoin-ohttp",
"clap",
"config",
"ohttp-relay",
Expand Down
2 changes: 2 additions & 0 deletions ohttp-relay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ws-bootstrap = ["futures", "rustls", "tokio-tungstenite"]
_test-util = []

[dependencies]
bhttp = { version = "0.6.1", features = ["http"] }
byteorder = "1.5.0"
bytes = "1.10.1"
futures = { version = "0.3.31", optional = true }
Expand All @@ -33,6 +34,7 @@ hyper-rustls = { version = "0.27.7", default-features = false, features = [
"ring",
] }
hyper-util = { version = "0.1.16", features = ["client-legacy", "service"] }
ohttp = { package = "bitcoin-ohttp", version = "0.6" }
rustls = { version = "0.23.31", optional = true, default-features = false, features = [
"ring",
] }
Expand Down
115 changes: 115 additions & 0 deletions ohttp-relay/src/gateway_helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::io::Cursor;

pub const CHACHA20_POLY1305_NONCE_LEN: usize = 32;
pub const POLY1305_TAG_SIZE: usize = 16;
pub const OHTTP_OVERHEAD: usize = CHACHA20_POLY1305_NONCE_LEN + POLY1305_TAG_SIZE;
pub const ENCAPSULATED_MESSAGE_BYTES: usize = 8192;
pub const BHTTP_REQ_BYTES: usize = ENCAPSULATED_MESSAGE_BYTES - OHTTP_OVERHEAD;

#[derive(Debug)]
pub enum GatewayError {
BadRequest(String),
OhttpKeyRejection(String),
InternalServerError(String),
}

impl std::fmt::Display for GatewayError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GatewayError::BadRequest(msg) => write!(f, "Bad request: {}", msg),
GatewayError::OhttpKeyRejection(msg) => write!(f, "OHTTP key rejection: {}", msg),
GatewayError::InternalServerError(msg) => write!(f, "Internal server error: {}", msg),
}
}
}

impl std::error::Error for GatewayError {}

pub struct DecapsulatedRequest {
pub method: String,
pub uri: String,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}

pub fn decapsulate_ohttp_request(
ohttp_body: &[u8],
ohttp_server: &ohttp::Server,
) -> Result<(DecapsulatedRequest, ohttp::ServerResponse), GatewayError> {
let (bhttp_req, res_ctx) = ohttp_server.decapsulate(ohttp_body).map_err(|e| {
GatewayError::OhttpKeyRejection(format!("OHTTP decapsulation failed: {}", e))
})?;

let mut cursor = Cursor::new(bhttp_req);
let bhttp_msg = bhttp::Message::read_bhttp(&mut cursor)
.map_err(|e| GatewayError::BadRequest(format!("Invalid BHTTP: {}", e)))?;

let method = String::from_utf8(bhttp_msg.control().method().unwrap_or_default().to_vec())
.unwrap_or_else(|_| "GET".to_string());

let uri = format!(
"{}://{}{}",
std::str::from_utf8(bhttp_msg.control().scheme().unwrap_or_default()).unwrap_or("https"),
std::str::from_utf8(bhttp_msg.control().authority().unwrap_or_default())
.unwrap_or("localhost"),
std::str::from_utf8(bhttp_msg.control().path().unwrap_or_default()).unwrap_or("/")
);

let mut headers = Vec::new();
for field in bhttp_msg.header().fields() {
let name = String::from_utf8_lossy(field.name()).to_string();
let value = String::from_utf8_lossy(field.value()).to_string();
headers.push((name, value));
}

let body = bhttp_msg.content().to_vec();

Ok((DecapsulatedRequest { method, uri, headers, body }, res_ctx))
}

pub fn encapsulate_ohttp_response(
status_code: u16,
headers: Vec<(String, String)>,
body: Vec<u8>,
res_ctx: ohttp::ServerResponse,
) -> Result<Vec<u8>, GatewayError> {
let bhttp_status = bhttp::StatusCode::try_from(status_code)
.map_err(|e| GatewayError::InternalServerError(format!("Invalid status code: {}", e)))?;

let mut bhttp_res = bhttp::Message::response(bhttp_status);

for (name, value) in &headers {
bhttp_res.put_header(name.as_str(), value.as_str());
}

bhttp_res.write_content(&body);

let mut bhttp_bytes = Vec::new();
bhttp_res.write_bhttp(bhttp::Mode::KnownLength, &mut bhttp_bytes).map_err(|e| {
GatewayError::InternalServerError(format!("BHTTP serialization failed: {}", e))
})?;

if bhttp_bytes.len() > BHTTP_REQ_BYTES {
return Err(GatewayError::InternalServerError(format!(
"BHTTP response too large: {} > {}",
bhttp_bytes.len(),
BHTTP_REQ_BYTES
)));
}

bhttp_bytes.resize(BHTTP_REQ_BYTES, 0);

let ohttp_res = res_ctx.encapsulate(&bhttp_bytes).map_err(|e| {
GatewayError::InternalServerError(format!("OHTTP encapsulation failed: {}", e))
})?;

if ohttp_res.len() != ENCAPSULATED_MESSAGE_BYTES {
return Err(GatewayError::InternalServerError(format!(
"Unexpected OHTTP response size: {} != {}",
ohttp_res.len(),
ENCAPSULATED_MESSAGE_BYTES
)));
}

Ok(ohttp_res)
}
3 changes: 3 additions & 0 deletions ohttp-relay/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub mod gateway_prober;
mod gateway_uri;
pub mod sentinel;
pub use sentinel::SentinelTag;
pub mod gateway_helpers;

pub use gateway_helpers::{decapsulate_ohttp_request, encapsulate_ohttp_response};

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

Expand Down
1 change: 0 additions & 1 deletion payjoin-directory/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ acme = ["tokio-rustls-acme"]

[dependencies]
anyhow = "1.0.99"
bhttp = { version = "0.6.1", features = ["http"] }
bitcoin = { version = "0.32.7", features = ["base64", "rand-std"] }
clap = { version = "4.5.45", features = ["derive", "env"] }
config = "0.15.14"
Expand Down
Loading
Loading