use std::time::Duration;
use async_trait::async_trait;
use crate::mesh::MeshStream;
use crate::mesh_admit::{Grant, PeerClass};
#[derive(Debug, Clone)]
pub struct ProxyLimits {
pub max_head_bytes: usize,
pub max_headers: usize,
pub max_request_body: usize,
pub max_mesh_request_body: usize,
pub max_response_body: u64,
pub head_timeout: Duration,
pub body_timeout: Duration,
pub connect_timeout: Duration,
pub stall_timeout: Duration,
pub request_timeout: Duration,
pub max_connections: usize,
pub max_connections_per_ip: usize,
}
impl Default for ProxyLimits {
fn default() -> Self {
Self {
max_head_bytes: 16 * 1024,
max_headers: 64,
max_request_body: 8 * 1024 * 1024,
max_mesh_request_body: 2 * 1024 * 1024,
max_response_body: 64 * 1024 * 1024,
head_timeout: Duration::from_secs(10),
body_timeout: Duration::from_secs(30),
connect_timeout: Duration::from_secs(5),
stall_timeout: Duration::from_secs(120),
request_timeout: Duration::from_secs(900),
max_connections: 32,
max_connections_per_ip: 8,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Method {
Get,
Post,
}
impl Method {
pub fn as_str(self) -> &'static str {
match self {
Self::Get => "GET",
Self::Post => "POST",
}
}
}
#[derive(Debug, Clone)]
pub struct ProxyRequest {
pub role: String,
pub method: Method,
pub path: String,
pub content_type: Option<String>,
pub accept: Option<String>,
pub authorization: Option<String>,
pub body: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Target {
Local {
base: String,
},
Remote {
node_id: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ProxyError {
#[error("bad request: {0}")]
BadRequest(String),
#[error("unauthorized")]
Unauthorized,
#[error("forbidden: {0}")]
Forbidden(String),
#[error("method not allowed")]
MethodNotAllowed,
#[error("too large: {0}")]
TooLarge(String),
#[error("not implemented: {0}")]
Unsupported(String),
#[error("no instance serves role '{0}'")]
NoInstance(String),
#[error("upstream: {0}")]
Upstream(String),
#[error("mesh: {0}")]
Mesh(String),
#[error("timeout: {0}")]
Timeout(String),
#[error("refused: {0}")]
Refused(String),
#[error("not loopback: {0}")]
NotLoopback(String),
#[error("port {0} is in use")]
Occupied(u16),
#[error("io: {0}")]
Io(String),
}
impl ProxyError {
pub fn status(&self) -> u16 {
match self {
Self::BadRequest(_) => 400,
Self::Unauthorized => 401,
Self::Forbidden(_) | Self::Refused(_) => 403,
Self::MethodNotAllowed => 405,
Self::TooLarge(_) => 413,
Self::Unsupported(_) => 501,
Self::NoInstance(_) => 503,
Self::Upstream(_) | Self::Mesh(_) => 502,
Self::Timeout(_) => 504,
Self::NotLoopback(_) | Self::Occupied(_) | Self::Io(_) => 500,
}
}
}
#[async_trait]
pub trait ResponseSink: Send {
async fn head(&mut self, status: u16, content_type: Option<&str>) -> Result<(), ProxyError>;
async fn chunk(&mut self, data: &[u8]) -> Result<(), ProxyError>;
}
pub fn qualifies(g: &Grant) -> bool {
g.admitted && g.class == PeerClass::Node && g.trust_scope && g.observed.is_none()
}
#[async_trait]
pub trait MeshDialer: Send + Sync + 'static {
fn standing(&self, node_id: &str) -> Option<Grant>;
fn is_admitted(&self, node_id: &str) -> bool {
self.standing(node_id).is_some_and(|g| qualifies(&g))
}
async fn dial(&self, node_id: &str) -> Result<Box<dyn MeshStream>, ProxyError>;
}
pub trait ProxyAudit: Send + Sync + 'static {
fn record(&self, kind: &str, payload: serde_json::Value);
}
#[cfg(feature = "exochain")]
pub struct ChainAudit(pub std::sync::Arc<crate::chain::ChainManager>);
#[cfg(feature = "exochain")]
impl ProxyAudit for ChainAudit {
fn record(&self, kind: &str, payload: serde_json::Value) {
self.0.append("infer_proxy", kind, Some(payload));
}
}