use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use async_trait::async_trait;
use crate::mesh::{MeshError, MeshStream, MeshTransport, TransportListener};
use crate::mesh_noise::{NoiseChannel, NoiseConfig};
use crate::mesh_tcp::TcpTransport;
use crate::mesh_test_support::connected_pair;
use super::host_service::WorkloadHostService;
use super::session::{NoiseStream, serve_connection};
pub const MEM_SCHEME: &str = "mem://";
#[async_trait]
pub trait CtlConnector: Send + Sync {
async fn connect(&self, addr: &str) -> Result<Box<dyn MeshStream>, MeshError>;
}
fn noise_config() -> NoiseConfig {
NoiseConfig {
pattern: crate::mesh_noise::NoisePattern::XX,
local_private_key: rand::random(),
remote_static_key: None,
}
}
#[derive(Default)]
pub struct MeshConnector {
noise: bool,
local: RwLock<HashMap<String, Arc<WorkloadHostService>>>,
}
impl MeshConnector {
pub fn new(noise: bool) -> Self {
Self {
noise,
local: RwLock::new(HashMap::new()),
}
}
pub fn register_local(&self, name: &str, svc: Arc<WorkloadHostService>) -> String {
let addr = format!("{MEM_SCHEME}{name}");
if let Ok(mut m) = self.local.write() {
m.insert(addr.clone(), svc);
}
addr
}
}
#[async_trait]
impl CtlConnector for MeshConnector {
async fn connect(&self, addr: &str) -> Result<Box<dyn MeshStream>, MeshError> {
if addr.starts_with(MEM_SCHEME) {
let svc = self
.local
.read()
.ok()
.and_then(|m| m.get(addr).cloned())
.ok_or_else(|| MeshError::PeerNotConnected(addr.to_string()))?;
let (client, server) = connected_pair().await?;
tokio::spawn(async move {
if let Err(e) = serve_connection(Box::new(server), svc).await {
tracing::debug!(error = %e, "in-process workload-host session ended");
}
});
return Ok(Box::new(client));
}
let stream = TcpTransport.connect(addr).await?;
if !self.noise {
return Ok(stream);
}
let ch = NoiseChannel::initiate(stream, &noise_config()).await?;
Ok(Box::new(NoiseStream::new(Box::new(ch))))
}
}
pub const MAX_SESSIONS: usize = 32;
pub async fn serve_listener(
listener: Box<dyn TransportListener>,
svc: Arc<WorkloadHostService>,
noise: bool,
) -> Result<(), MeshError> {
serve_listener_with(listener, svc, noise, MAX_SESSIONS).await
}
pub async fn serve_listener_with(
mut listener: Box<dyn TransportListener>,
svc: Arc<WorkloadHostService>,
noise: bool,
max_sessions: usize,
) -> Result<(), MeshError> {
let slots = Arc::new(tokio::sync::Semaphore::new(max_sessions));
loop {
let (mut stream, peer) = listener.accept().await?;
let Ok(slot) = slots.clone().try_acquire_owned() else {
tracing::debug!(%peer, "workload-host session limit reached; connection closed");
let _ = stream.close().await;
continue;
};
let svc = svc.clone();
tokio::spawn(async move {
let _slot = slot;
let stream: Box<dyn MeshStream> = if noise {
match NoiseChannel::respond(stream, &noise_config()).await {
Ok(ch) => Box::new(NoiseStream::new(Box::new(ch))),
Err(e) => {
tracing::warn!(%peer, error = %e, "workload-host noise handshake failed");
return;
}
}
} else {
stream
};
if let Err(e) = serve_connection(stream, svc).await {
tracing::debug!(%peer, error = %e, "workload-host session ended");
}
});
}
}
pub async fn listen_tcp(addr: &str) -> Result<Box<dyn TransportListener>, MeshError> {
TcpTransport.listen(addr).await
}