1use crate::{
4 llm::actions::{client_trait::Client, protocol_trait::Server},
5 protocol::{ConnectContext, Event, SpawnContext},
6 server::connection::ConnectionId,
7 state::{AccessLogOwner, ClientStatus},
8};
9use anyhow::{ensure, Result};
10use async_trait::async_trait;
11use serde_json::Value;
12use std::{net::SocketAddr, sync::Arc, time::Duration};
13use tokio::{
14 io::{AsyncReadExt, AsyncWriteExt},
15 net::TcpStream,
16};
17
18pub const FIRST_BYTE_TIMEOUT: Duration = Duration::from_secs(30);
19pub const IDLE_TIMEOUT: Duration = Duration::from_secs(600);
20pub const RESPONSE_TIMEOUT: Duration = Duration::from_secs(10);
21
22pub fn parameter(
23 name: &str,
24 kind: &str,
25 description: &str,
26 required: bool,
27) -> crate::llm::actions::Parameter {
28 crate::llm::actions::Parameter {
29 name: name.into(),
30 type_hint: kind.into(),
31 description: description.into(),
32 required,
33 }
34}
35pub fn action(
36 name: &str,
37 description: &str,
38 parameters: Vec<crate::llm::actions::Parameter>,
39 example: Value,
40) -> crate::llm::actions::ActionDefinition {
41 crate::llm::actions::ActionDefinition {
42 name: name.into(),
43 description: description.into(),
44 parameters,
45 example,
46 log_template: Some(crate::protocol::LogTemplate::new().with_info(name)),
47 }
48}
49pub fn number(v: &Value, key: &str, max: u64) -> Result<u64> {
50 let n = v[key]
51 .as_u64()
52 .ok_or_else(|| anyhow::anyhow!("{key} must be an unsigned integer"))?;
53 ensure!(n <= max, "{key} exceeds {max}");
54 Ok(n)
55}
56pub async fn frame(
58 stream: &mut TcpStream,
59 header_len: usize,
60 max: usize,
61 length: fn(&[u8]) -> Result<usize>,
62) -> Result<Vec<u8>> {
63 let mut header = vec![0; header_len];
64 stream.read_exact(&mut header).await?;
65 let len = length(&header)?;
66 ensure!(len >= header_len && len <= max, "invalid frame length");
67 header.resize(len, 0);
68 stream.read_exact(&mut header[header_len..]).await?;
69 Ok(header)
70}
71#[async_trait]
72pub trait DeviceSession: Send + 'static {
73 async fn read(&mut self, stream: &mut TcpStream) -> Result<Vec<u8>>;
74 fn receive(&mut self, frame: &[u8]) -> Result<(Vec<u8>, Option<Value>)>;
76 fn answer(&mut self, request: &Value, action: Option<&Value>) -> Result<Vec<u8>>;
77}
78#[async_trait]
79pub trait ScannerSession: Send + 'static {
80 async fn open(&mut self, stream: &mut TcpStream) -> Result<()>;
81 async fn exchange(&mut self, stream: &mut TcpStream, action: &Value) -> Result<Value>;
82 async fn idle(&mut self, stream: &mut TcpStream) -> Result<Option<Value>> {
83 let mut b = [0];
84 stream.peek(&mut b).await?;
85 anyhow::bail!("unexpected data or peer closed")
86 }
87}
88pub async fn spawn<S: DeviceSession, F: Fn() -> S + Send + Sync + Clone + 'static>(
89 ctx: SpawnContext,
90 protocol: Arc<dyn Server>,
91 factory: F,
92 kinds: &'static [crate::protocol::EventType],
93) -> Result<SocketAddr> {
94 let listener =
95 crate::server::socket_helpers::create_reusable_tcp_listener(ctx.legacy_listen_addr())
96 .await?;
97 spawn_accept_bounded(ctx, protocol, factory, kinds, listener).await
98}
99pub async fn spawn_accept_bounded<
100 S: DeviceSession,
101 F: Fn() -> S + Send + Sync + Clone + 'static,
102>(
103 ctx: SpawnContext,
104 protocol: Arc<dyn Server>,
105 factory: F,
106 kinds: &'static [crate::protocol::EventType],
107 listener: tokio::net::TcpListener,
108) -> Result<SocketAddr> {
109 let local = listener.local_addr()?;
110 let state = ctx.state.clone();
111 let sid = ctx.server_id;
112 let task = tokio::spawn(async move {
113 let cap = crate::server::accept_bounded::ConnectionLimiter::new(
114 crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS,
115 );
116 loop {
117 let Ok((mut stream, peer, permit)) = crate::server::accept_bounded::accept_bounded(
118 &listener,
119 &cap,
120 b"",
121 protocol.protocol_name(),
122 Some(&ctx.status_tx),
123 )
124 .await
125 else {
126 break;
127 };
128 let id = ConnectionId::new(ctx.state.get_next_unified_id().await);
129 let now = crate::utils::clock::Instant::now();
130 ctx.state
131 .add_connection_to_server(
132 sid,
133 crate::state::server::ConnectionState {
134 id,
135 remote_addr: peer,
136 local_addr: local,
137 bytes_sent: 0,
138 bytes_received: 0,
139 packets_sent: 0,
140 packets_received: 0,
141 last_activity: now,
142 status: crate::state::server::ConnectionStatus::Active,
143 status_changed_at: now,
144 protocol_info: crate::state::server::ProtocolConnectionInfo::empty(),
145 },
146 )
147 .await;
148 let mut commands =
149 crate::server::peer_support::register_peer_channel(&ctx.state, sid, id.as_u32())
150 .await;
151 let factory = factory.clone();
152 let child = ctx.clone();
153 let p = protocol.clone();
154 ctx.state.spawn_server_task(sid, async move {
155 let _permit = permit; let mut session = factory(); let mut first = true;
156 let result: Result<()> = async {
157 loop {
158 let input = {
159 let read=tokio::time::timeout(if first {FIRST_BYTE_TIMEOUT} else {IDLE_TIMEOUT},session.read(&mut stream));
160 tokio::pin!(read);
161 loop {
162 tokio::select! {
163 v=&mut read=>break Some(v??),
164 Some(command)=commands.recv()=>{
165 if command.action["type"]=="disconnect" {crate::client::command_support::reply(command,Ok(crate::state::client_handles::ClientSendOutcome::Disconnected));break None;}
166 crate::client::command_support::reply(command,Ok(crate::state::client_handles::ClientSendOutcome::Rejected{error:"Only disconnect is valid outside a pending request".into()}));
167 }
168 }
169 }
170 };
171 let Some(input)=input else {break;};
172 first = false;
173 child.state.update_connection_stats(sid, id, Some(input.len() as u64), None, Some(1), None).await;
174 let (automatic, request) = session.receive(&input)?;
175 let mut reply = automatic;
176 if let Some(request) = request {
177 let event = Event::new(&kinds[0], request.clone());
178 let result = crate::llm::action_helper::call_llm(&child.llm_client, &child.state, sid, Some(id), &event, p.as_ref()).await;
179 let answer = result.as_ref().ok().and_then(|r| r.protocol_results.iter().find_map(|a| if let crate::llm::ActionResult::Custom {data,..}=a {Some(data)} else {None}));
180 if answer.is_none() { crate::logging::emit::Log::new(Some(&child.status_tx)).warn(format!("{} decision=fail_closed_no_action", p.protocol_name())); }
181 reply.extend(session.answer(&request, answer)?);
182 }
183 if !reply.is_empty() { stream.write_all(&reply).await?; child.state.update_connection_stats(sid, id, None, Some(reply.len() as u64), None, Some(1)).await; }
184 } Ok(())
185 }.await;
186 if let Err(e) = result { crate::logging::emit::Log::new(Some(&child.status_tx)).debug(format!("{} peer ended: {e}", p.protocol_name())); }
187 let _ = stream.shutdown().await;
188 child.state.remove_peer_handle(sid, id.as_u32()).await;
189 child.state.update_connection_status(sid, id, crate::state::server::ConnectionStatus::Closed).await;
190 }).await;
191 }
192 });
193 state.register_server_task(sid, task).await;
194 Ok(local)
195}
196pub async fn connect<S: ScannerSession>(
197 ctx: ConnectContext,
198 protocol: Arc<dyn Client>,
199 mut session: S,
200 kinds: &'static [crate::protocol::EventType],
201) -> Result<SocketAddr> {
202 ensure!(!ctx.remote_addr.is_empty(), "remote_addr is required");
203 let mut stream =
204 tokio::time::timeout(RESPONSE_TIMEOUT, TcpStream::connect(&ctx.remote_addr)).await??;
205 tokio::time::timeout(RESPONSE_TIMEOUT, session.open(&mut stream)).await??;
206 let local = stream.local_addr()?;
207 let mut commands =
208 crate::client::command_support::register_command_channel(&ctx.state, ctx.client_id).await;
209 ctx.state
210 .update_client_status(ctx.client_id, ClientStatus::Connected)
211 .await;
212 let state = ctx.state.clone();
213 let cid = ctx.client_id;
214 state
215 .spawn_client_task(cid, async move {
216 let (events_tx, mut events_rx) = tokio::sync::mpsc::channel::<Event>(32);
218 let (actions_tx, mut actions_rx) = tokio::sync::mpsc::channel::<Value>(32);
219 let event_ctx = ctx.clone();
220 let p = protocol.clone();
221 let turns = ctx
222 .state
223 .spawn_client_task(cid, async move {
224 while let Some(event) = events_rx.recv().await {
225 let instruction = event_ctx
226 .state
227 .get_instruction_for_client(cid)
228 .await
229 .unwrap_or_default();
230 let memory = event_ctx
231 .state
232 .get_memory_for_client(cid)
233 .await
234 .unwrap_or_default();
235 match crate::client::llm_budget::call_llm_for_client(
236 &event_ctx.llm_client,
237 &event_ctx.state,
238 cid.to_string(),
239 &instruction,
240 &memory,
241 Some(&event),
242 p.as_ref(),
243 &event_ctx.status_tx,
244 )
245 .await
246 {
247 Ok(result) => {
248 if let Some(memory) = result.memory_updates {
249 event_ctx.state.set_memory_for_client(cid, memory).await;
250 }
251 for a in result.actions {
252 if actions_tx.send(a).await.is_err() {
253 return;
254 }
255 }
256 }
257 Err(e) => crate::logging::emit::Log::new(Some(&event_ctx.status_tx))
258 .warn(format!("{} client handler: {e}", p.protocol_name())),
259 }
260 }
261 })
262 .await;
263 let _ = events_tx.try_send(Event::new(
264 &kinds[0],
265 serde_json::json!({"remote_addr":ctx.remote_addr}),
266 ));
267 loop {
268 let (action, command) = tokio::select! {
269 Some(c) = commands.recv() => (c.action.clone(),Some(c)),
270 Some(a) = actions_rx.recv() => (a,None),
271 incoming = session.idle(&mut stream) => {match incoming {Ok(Some(response))=>{let _=events_tx.try_send(Event::new(&kinds[1],response));},Ok(None)=>{},Err(_)=>break};continue;},
272 else => break,
273 };
274 if action["type"] == "disconnect" {
275 if let Some(c) = command {
276 crate::client::command_support::reply(
277 c,
278 Ok(crate::state::client_handles::ClientSendOutcome::Disconnected),
279 );
280 }
281 break;
282 }
283 if let Err(e) = protocol.execute_action(action.clone()) {
284 if let Some(c) = command {
285 crate::client::command_support::reply(
286 c,
287 Ok(crate::state::client_handles::ClientSendOutcome::Rejected {
288 error: e.to_string(),
289 }),
290 );
291 }
292 continue;
293 }
294 let result = match tokio::time::timeout(
295 RESPONSE_TIMEOUT,
296 session.exchange(&mut stream, &action),
297 )
298 .await
299 {
300 Ok(r) => r,
301 Err(_) => Err(anyhow::anyhow!("device response deadline exceeded")),
302 };
303 let terminal = result.is_err();
304 match result {
305 Ok(response) => {
306 ctx.state
307 .record_access_log(
308 AccessLogOwner::Client(cid.as_u32()),
309 protocol.protocol_name(),
310 None,
311 &kinds[1].id,
312 response.clone(),
313 vec![],
314 )
315 .await;
316 let _ = events_tx.try_send(Event::new(&kinds[1], response.clone()));
317 if let Some(c) = command {
318 crate::client::command_support::reply(
319 c,
320 Ok(crate::state::client_handles::ClientSendOutcome::Executed {
321 detail: response.to_string(),
322 }),
323 );
324 }
325 }
326 Err(e) => {
327 if let Some(c) = command {
328 crate::client::command_support::reply(c, Err(e));
329 }
330 }
331 }
332 if terminal {
333 break;
334 }
335 }
336 turns.abort();
337 let _ = stream.shutdown().await;
338 ctx.state.remove_client_handle(cid).await;
339 ctx.state
340 .update_client_status(cid, ClientStatus::Disconnected)
341 .await;
342 })
343 .await;
344 Ok(local)
345}
346
347#[async_trait]
348pub trait DatagramSession: Send + 'static {
349 async fn exchange(
350 &mut self,
351 socket: &mut tokio::net::UdpSocket,
352 action: &Value,
353 ) -> Result<Value>;
354 async fn idle(&mut self, socket: &mut tokio::net::UdpSocket) -> Result<Option<Value>> {
355 let mut b = [0];
356 socket.recv(&mut b).await?;
357 anyhow::bail!("unexpected datagram")
358 }
359}
360pub async fn connect_udp<S: DatagramSession>(
361 ctx: ConnectContext,
362 protocol: Arc<dyn Client>,
363 mut session: S,
364 kinds: &'static [crate::protocol::EventType],
365) -> Result<SocketAddr> {
366 ensure!(!ctx.remote_addr.is_empty(), "remote_addr is required");
367 let remote: SocketAddr = ctx.remote_addr.parse()?;
368 let mut stream = tokio::net::UdpSocket::bind(if remote.is_ipv4() {
369 "0.0.0.0:0"
370 } else {
371 "[::]:0"
372 })
373 .await?;
374 stream.connect(remote).await?;
375 let local = stream.local_addr()?;
376 let mut commands =
377 crate::client::command_support::register_command_channel(&ctx.state, ctx.client_id).await;
378 ctx.state
379 .update_client_status(ctx.client_id, ClientStatus::Connected)
380 .await;
381 let state = ctx.state.clone();
382 let cid = ctx.client_id;
383 state
384 .spawn_client_task(cid, async move {
385 let (events_tx, mut events_rx) = tokio::sync::mpsc::channel::<Event>(32);
387 let (actions_tx, mut actions_rx) = tokio::sync::mpsc::channel::<Value>(32);
388 let event_ctx = ctx.clone();
389 let p = protocol.clone();
390 let turns = ctx
391 .state
392 .spawn_client_task(cid, async move {
393 while let Some(event) = events_rx.recv().await {
394 let instruction = event_ctx
395 .state
396 .get_instruction_for_client(cid)
397 .await
398 .unwrap_or_default();
399 let memory = event_ctx
400 .state
401 .get_memory_for_client(cid)
402 .await
403 .unwrap_or_default();
404 match crate::client::llm_budget::call_llm_for_client(
405 &event_ctx.llm_client,
406 &event_ctx.state,
407 cid.to_string(),
408 &instruction,
409 &memory,
410 Some(&event),
411 p.as_ref(),
412 &event_ctx.status_tx,
413 )
414 .await
415 {
416 Ok(result) => {
417 if let Some(memory) = result.memory_updates {
418 event_ctx.state.set_memory_for_client(cid, memory).await;
419 }
420 for a in result.actions {
421 if actions_tx.send(a).await.is_err() {
422 return;
423 }
424 }
425 }
426 Err(e) => crate::logging::emit::Log::new(Some(&event_ctx.status_tx))
427 .warn(format!("{} client handler: {e}", p.protocol_name())),
428 }
429 }
430 })
431 .await;
432 let _ = events_tx.try_send(Event::new(
433 &kinds[0],
434 serde_json::json!({"remote_addr":ctx.remote_addr}),
435 ));
436 loop {
437 let (action, command) = tokio::select! {
438 Some(c) = commands.recv() => (c.action.clone(),Some(c)),
439 Some(a) = actions_rx.recv() => (a,None),
440 incoming = session.idle(&mut stream) => {match incoming {Ok(Some(response))=>{let _=events_tx.try_send(Event::new(&kinds[1],response));},Ok(None)=>{},Err(_)=>break};continue;},
441 else => break,
442 };
443 if action["type"] == "disconnect" {
444 if let Some(c) = command {
445 crate::client::command_support::reply(
446 c,
447 Ok(crate::state::client_handles::ClientSendOutcome::Disconnected),
448 );
449 }
450 break;
451 }
452 if let Err(e) = protocol.execute_action(action.clone()) {
453 if let Some(c) = command {
454 crate::client::command_support::reply(
455 c,
456 Ok(crate::state::client_handles::ClientSendOutcome::Rejected {
457 error: e.to_string(),
458 }),
459 );
460 }
461 continue;
462 }
463 let result = match tokio::time::timeout(
464 RESPONSE_TIMEOUT,
465 session.exchange(&mut stream, &action),
466 )
467 .await
468 {
469 Ok(r) => r,
470 Err(_) => Err(anyhow::anyhow!("device response deadline exceeded")),
471 };
472 let terminal = result.is_err();
473 match result {
474 Ok(response) => {
475 ctx.state
476 .record_access_log(
477 AccessLogOwner::Client(cid.as_u32()),
478 protocol.protocol_name(),
479 None,
480 &kinds[1].id,
481 response.clone(),
482 vec![],
483 )
484 .await;
485 let _ = events_tx.try_send(Event::new(&kinds[1], response.clone()));
486 if let Some(c) = command {
487 crate::client::command_support::reply(
488 c,
489 Ok(crate::state::client_handles::ClientSendOutcome::Executed {
490 detail: response.to_string(),
491 }),
492 );
493 }
494 }
495 Err(e) => {
496 if let Some(c) = command {
497 crate::client::command_support::reply(c, Err(e));
498 }
499 }
500 }
501 if terminal {
502 break;
503 }
504 }
505 turns.abort();
506
507 ctx.state.remove_client_handle(cid).await;
508 ctx.state
509 .update_client_status(cid, ClientStatus::Disconnected)
510 .await;
511 })
512 .await;
513 Ok(local)
514}