clawft_kernel/workload_ctl/
transport.rs1use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8
9use async_trait::async_trait;
10
11use crate::mesh::{MeshError, MeshStream, MeshTransport, TransportListener};
12use crate::mesh_noise::{NoiseChannel, NoiseConfig};
13use crate::mesh_tcp::TcpTransport;
14use crate::mesh_test_support::connected_pair;
15
16use super::host_service::WorkloadHostService;
17use super::session::{NoiseStream, serve_connection};
18
19pub const MEM_SCHEME: &str = "mem://";
21
22#[async_trait]
24pub trait CtlConnector: Send + Sync {
25 async fn connect(&self, addr: &str) -> Result<Box<dyn MeshStream>, MeshError>;
27}
28
29fn noise_config() -> NoiseConfig {
30 NoiseConfig {
31 pattern: crate::mesh_noise::NoisePattern::XX,
32 local_private_key: rand::random(),
33 remote_static_key: None,
34 }
35}
36
37#[derive(Default)]
40pub struct MeshConnector {
41 noise: bool,
42 local: RwLock<HashMap<String, Arc<WorkloadHostService>>>,
43}
44
45impl MeshConnector {
46 pub fn new(noise: bool) -> Self {
48 Self {
49 noise,
50 local: RwLock::new(HashMap::new()),
51 }
52 }
53
54 pub fn register_local(&self, name: &str, svc: Arc<WorkloadHostService>) -> String {
58 let addr = format!("{MEM_SCHEME}{name}");
59 if let Ok(mut m) = self.local.write() {
60 m.insert(addr.clone(), svc);
61 }
62 addr
63 }
64}
65
66#[async_trait]
67impl CtlConnector for MeshConnector {
68 async fn connect(&self, addr: &str) -> Result<Box<dyn MeshStream>, MeshError> {
69 if addr.starts_with(MEM_SCHEME) {
70 let svc = self
71 .local
72 .read()
73 .ok()
74 .and_then(|m| m.get(addr).cloned())
75 .ok_or_else(|| MeshError::PeerNotConnected(addr.to_string()))?;
76 let (client, server) = connected_pair().await?;
77 tokio::spawn(async move {
78 if let Err(e) = serve_connection(Box::new(server), svc).await {
79 tracing::debug!(error = %e, "in-process workload-host session ended");
80 }
81 });
82 return Ok(Box::new(client));
83 }
84 let stream = TcpTransport.connect(addr).await?;
85 if !self.noise {
86 return Ok(stream);
87 }
88 let ch = NoiseChannel::initiate(stream, &noise_config()).await?;
89 Ok(Box::new(NoiseStream::new(Box::new(ch))))
90 }
91}
92
93pub const MAX_SESSIONS: usize = 32;
95
96pub async fn serve_listener(
100 listener: Box<dyn TransportListener>,
101 svc: Arc<WorkloadHostService>,
102 noise: bool,
103) -> Result<(), MeshError> {
104 serve_listener_with(listener, svc, noise, MAX_SESSIONS).await
105}
106
107pub async fn serve_listener_with(
109 mut listener: Box<dyn TransportListener>,
110 svc: Arc<WorkloadHostService>,
111 noise: bool,
112 max_sessions: usize,
113) -> Result<(), MeshError> {
114 let slots = Arc::new(tokio::sync::Semaphore::new(max_sessions));
115 loop {
116 let (mut stream, peer) = listener.accept().await?;
117 let Ok(slot) = slots.clone().try_acquire_owned() else {
118 tracing::debug!(%peer, "workload-host session limit reached; connection closed");
119 let _ = stream.close().await;
120 continue;
121 };
122 let svc = svc.clone();
123 tokio::spawn(async move {
124 let _slot = slot;
125 let stream: Box<dyn MeshStream> = if noise {
126 match NoiseChannel::respond(stream, &noise_config()).await {
127 Ok(ch) => Box::new(NoiseStream::new(Box::new(ch))),
128 Err(e) => {
129 tracing::warn!(%peer, error = %e, "workload-host noise handshake failed");
130 return;
131 }
132 }
133 } else {
134 stream
135 };
136 if let Err(e) = serve_connection(stream, svc).await {
137 tracing::debug!(%peer, error = %e, "workload-host session ended");
138 }
139 });
140 }
141}
142
143pub async fn listen_tcp(addr: &str) -> Result<Box<dyn TransportListener>, MeshError> {
145 TcpTransport.listen(addr).await
146}