use std::collections::HashSet;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use async_trait::async_trait;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::task::JoinHandle;
use super::mesh_forward::{InferPeer, ServeGate, serve_infer};
use super::table::PlacementTable;
use super::types::{MeshDialer, ProxyAudit, ProxyError, ProxyLimits};
use super::upstream::Upstream;
use crate::mesh::MeshStream;
use crate::mesh_admit::{Grant, PeerClass, PeerLimits};
use crate::mesh_test_support::connected_pair;
#[derive(Debug, Clone)]
pub struct Seen {
pub request_line: String,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
impl Seen {
pub fn header(&self, n: &str) -> Option<&str> {
self.headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(n))
.map(|(_, v)| v.as_str())
}
}
#[derive(Clone)]
pub struct Reply {
pub status: u16,
pub content_type: &'static str,
pub chunks: Vec<Vec<u8>>,
pub gap: Duration,
pub hold: Duration,
}
impl Reply {
pub fn ok(body: &str) -> Self {
Self {
status: 200,
content_type: "application/json",
chunks: vec![body.as_bytes().to_vec()],
gap: Duration::ZERO,
hold: Duration::ZERO,
}
}
}
pub struct Fake {
pub addr: SocketAddr,
pub seen: Arc<Mutex<Vec<Seen>>>,
task: JoinHandle<()>,
}
impl Fake {
pub fn base(&self) -> String {
format!("http://{}", self.addr)
}
pub fn count(&self) -> usize {
self.seen.lock().unwrap().len()
}
pub fn last(&self) -> Seen {
self.seen.lock().unwrap().last().cloned().expect("no request seen")
}
}
impl Drop for Fake {
fn drop(&mut self) {
self.task.abort();
}
}
async fn read_req(s: &mut TcpStream) -> Option<Seen> {
let mut buf = Vec::new();
let mut tmp = [0u8; 4096];
let end = loop {
if let Some(i) = buf.windows(4).position(|w| w == b"\r\n\r\n") {
break i;
}
let n = s.read(&mut tmp).await.ok()?;
if n == 0 {
return None;
}
buf.extend_from_slice(&tmp[..n]);
};
let head = String::from_utf8_lossy(&buf[..end]).into_owned();
let mut lines = head.split("\r\n");
let request_line = lines.next()?.to_string();
let headers: Vec<(String, String)> = lines
.filter_map(|l| l.split_once(':').map(|(a, b)| (a.to_string(), b.trim().to_string())))
.collect();
let want: usize = headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case("content-length"))
.and_then(|(_, v)| v.parse().ok())
.unwrap_or(0);
let mut body = buf[end + 4..].to_vec();
while body.len() < want {
let n = s.read(&mut tmp).await.ok()?;
if n == 0 {
break;
}
body.extend_from_slice(&tmp[..n]);
}
Some(Seen {
request_line,
headers,
body,
})
}
pub async fn fake(reply: Reply) -> Fake {
let l = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = l.local_addr().unwrap();
let seen = Arc::new(Mutex::new(Vec::new()));
let sn = seen.clone();
let task = tokio::spawn(async move {
loop {
let Ok((mut s, _)) = l.accept().await else { return };
let (sn, reply) = (sn.clone(), reply.clone());
tokio::spawn(async move {
let Some(r) = read_req(&mut s).await else { return };
sn.lock().unwrap().push(r);
let head = format!(
"HTTP/1.1 {} X\r\nContent-Type: {}\r\nConnection: close\r\n\r\n",
reply.status, reply.content_type
);
let _ = s.write_all(head.as_bytes()).await;
for c in &reply.chunks {
tokio::time::sleep(reply.gap).await;
if s.write_all(c).await.is_err() {
return;
}
let _ = s.flush().await;
}
tokio::time::sleep(reply.hold).await;
let _ = s.shutdown().await;
});
}
});
Fake { addr, seen, task }
}
pub async fn raw(addr: SocketAddr, req: &[u8]) -> String {
let mut s = TcpStream::connect(addr).await.unwrap();
s.write_all(req).await.unwrap();
let mut out = Vec::new();
let _ = tokio::time::timeout(Duration::from_secs(10), s.read_to_end(&mut out)).await;
String::from_utf8_lossy(&out).into_owned()
}
pub fn post(addr: SocketAddr, path: &str, body: &str) -> Vec<u8> {
format!(
"POST {path} HTTP/1.1\r\nHost: {addr}\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{body}",
body.len()
)
.into_bytes()
}
pub fn get(addr: SocketAddr, path: &str) -> Vec<u8> {
format!("GET {path} HTTP/1.1\r\nHost: {addr}\r\n\r\n").into_bytes()
}
pub fn status(resp: &str) -> u16 {
resp.split(' ').nth(1).and_then(|s| s.parse().ok()).unwrap_or(0)
}
pub fn body(resp: &str) -> &str {
resp.split_once("\r\n\r\n").map_or("", |(_, b)| b)
}
pub fn small_limits() -> ProxyLimits {
ProxyLimits {
stall_timeout: Duration::from_millis(800),
request_timeout: Duration::from_secs(10),
head_timeout: Duration::from_secs(2),
body_timeout: Duration::from_secs(2),
connect_timeout: Duration::from_secs(1),
..ProxyLimits::default()
}
}
#[derive(Default)]
pub struct Audit(pub Mutex<Vec<(String, serde_json::Value)>>);
impl ProxyAudit for Audit {
fn record(&self, kind: &str, payload: serde_json::Value) {
self.0.lock().unwrap().push((kind.to_string(), payload));
}
}
impl Audit {
pub fn kinds(&self) -> Vec<String> {
self.0.lock().unwrap().iter().map(|(k, _)| k.clone()).collect()
}
}
pub struct FakeMesh {
pub admitted: Mutex<HashSet<String>>,
pub overrides: Mutex<std::collections::HashMap<String, Grant>>,
pub gate: Arc<ServeGate>,
pub nodes: Mutex<std::collections::HashMap<String, Arc<PlacementTable>>>,
pub serve_grant: Mutex<Option<Grant>>,
pub caller: String,
pub dials: Mutex<Vec<String>>,
pub audit: Option<Arc<dyn ProxyAudit>>,
}
impl FakeMesh {
pub fn new(caller: &str) -> Arc<Self> {
Arc::new(Self {
admitted: Mutex::new(HashSet::new()),
overrides: Mutex::new(Default::default()),
gate: Arc::new(ServeGate::new(4, 16)),
nodes: Mutex::new(Default::default()),
serve_grant: Mutex::new(Some(Grant {
limits: PeerLimits::None,
class: PeerClass::Node,
admitted: true,
trust_scope: true,
observed: None,
})),
caller: caller.to_string(),
dials: Mutex::new(Vec::new()),
audit: None,
})
}
pub fn add_node(&self, id: &str, t: Arc<PlacementTable>, admitted: bool) {
self.nodes.lock().unwrap().insert(id.to_string(), t);
if admitted {
self.admitted.lock().unwrap().insert(id.to_string());
}
}
}
#[async_trait]
impl MeshDialer for FakeMesh {
fn standing(&self, node_id: &str) -> Option<Grant> {
if let Some(g) = self.overrides.lock().unwrap().get(node_id) {
return Some(g.clone());
}
if !self.admitted.lock().unwrap().contains(node_id) {
return None;
}
Some(Grant {
limits: PeerLimits::None,
class: PeerClass::Node,
admitted: true,
trust_scope: true,
observed: None,
})
}
async fn dial(&self, node_id: &str) -> Result<Box<dyn MeshStream>, ProxyError> {
self.dials.lock().unwrap().push(node_id.to_string());
let table = self
.nodes
.lock()
.unwrap()
.get(node_id)
.cloned()
.ok_or_else(|| ProxyError::Mesh("no route".into()))?;
let (client, mut server) = connected_pair().await.map_err(|e| ProxyError::Mesh(e.to_string()))?;
let peer = InferPeer {
node_id: self.caller.clone(),
grant: self.serve_grant.lock().unwrap().clone(),
};
let audit = self.audit.clone();
let gate = self.gate.clone();
tokio::spawn(async move {
let up = Upstream::new(small_limits()).unwrap();
let _ = serve_infer(&mut server, &peer, table.as_ref(), &up, &gate, audit.as_deref()).await;
});
Ok(Box::new(client))
}
}
pub fn enforced_node() -> Grant {
Grant {
limits: PeerLimits::None,
class: PeerClass::Node,
admitted: true,
trust_scope: true,
observed: None,
}
}
pub fn trusted_peer(id: &str) -> InferPeer {
InferPeer {
node_id: id.into(),
grant: Some(enforced_node()),
}
}