1pub mod actions;
3
4pub use actions::UdpClientProtocol;
5
6use anyhow::{Context, Result};
7use std::net::SocketAddr;
8use std::sync::Arc;
9use tokio::net::UdpSocket;
10use tokio::sync::{mpsc, Mutex};
11use tracing::{error, info, trace, warn};
12
13use crate::client::llm_budget::call_llm_for_client;
14use crate::client::udp::actions::{UDP_CLIENT_CONNECTED_EVENT, UDP_CLIENT_DATAGRAM_RECEIVED_EVENT};
15use crate::llm::actions::client_trait::{Client, ClientActionResult};
16use crate::llm::ollama_client::OllamaClient;
17use crate::llm::ClientLlmResult;
18use crate::protocol::Event;
19use crate::state::app_state::AppState;
20use crate::state::client_handles::{ClientCommand, ClientSendOutcome};
21use crate::state::{AccessLogOwner, ClientId, ClientStatus};
22
23#[derive(Debug, Clone, PartialEq)]
25enum ConnectionState {
26 Idle,
27 Processing,
28 Accumulating,
29}
30
31struct ClientData {
33 state: ConnectionState,
34 queued_datagrams: Vec<(Vec<u8>, SocketAddr)>, memory: String,
36 default_target: SocketAddr,
37}
38
39pub struct UdpClient;
41
42impl UdpClient {
43 pub async fn connect_with_llm_actions(
45 remote_addr: String,
46 llm_client: OllamaClient,
47 app_state: Arc<AppState>,
48 status_tx: mpsc::UnboundedSender<String>,
49 client_id: ClientId,
50 ) -> Result<SocketAddr> {
51 let default_target: SocketAddr = remote_addr
53 .parse()
54 .context(format!("Failed to parse remote address: {}", remote_addr))?;
55
56 let socket = UdpSocket::bind("0.0.0.0:0")
58 .await
59 .context("Failed to bind UDP socket")?;
60
61 let local_addr = socket.local_addr()?;
62
63 info!(
64 "UDP client {} bound to {} (default target: {})",
65 client_id, local_addr, default_target
66 );
67
68 app_state
70 .update_client_status(client_id, ClientStatus::Connected)
71 .await;
72 let _ = status_tx.send(format!("[CLIENT] UDP client {} ready", client_id));
73 let _ = status_tx.send("__UPDATE_UI__".to_string());
74
75 let client_data = Arc::new(Mutex::new(ClientData {
77 state: ConnectionState::Idle,
78 queued_datagrams: Vec::new(),
79 memory: String::new(),
80 default_target,
81 }));
82
83 let socket_arc = Arc::new(socket);
84
85 let command_rx =
91 crate::client::command_support::register_command_channel(&app_state, client_id).await;
92
93 let cmd_state = app_state.clone();
98 let cmd_status_tx = status_tx.clone();
99 let cmd_socket = socket_arc.clone();
100 let cmd_data = client_data.clone();
101 let cmd_task = tokio::spawn(async move {
102 Self::command_loop(
103 command_rx,
104 cmd_socket,
105 cmd_data,
106 client_id,
107 cmd_state,
108 cmd_status_tx,
109 )
110 .await;
111 });
112 app_state.register_client_task(client_id, cmd_task).await;
113
114 let task_registrar = app_state.clone();
118 let task_handle = tokio::spawn({
119 let socket_arc = socket_arc.clone();
120 let client_data = client_data.clone();
121 let llm_client = llm_client.clone();
122 let app_state = app_state.clone();
123 let status_tx = status_tx.clone();
124
125 async move {
126 if let Some(instruction) = app_state.get_instruction_for_client(client_id).await {
128 let protocol = Arc::new(UdpClientProtocol::new());
129 let connected_event = Event::new(
130 &UDP_CLIENT_CONNECTED_EVENT,
131 serde_json::json!({
132 "remote_addr": default_target.to_string(),
133 "local_addr": local_addr.to_string(),
134 }),
135 );
136
137 match call_llm_for_client(
139 &llm_client,
140 &app_state,
141 client_id.to_string(),
142 &instruction,
143 "",
144 Some(&connected_event),
145 protocol.as_ref(),
146 &status_tx,
147 )
148 .await
149 {
150 Ok(llm_result) => {
151 if let Err(e) = Self::handle_llm_result(
152 llm_result,
153 &socket_arc,
154 &client_data,
155 client_id,
156 &app_state,
157 &status_tx,
158 )
159 .await
160 {
161 error!(
162 "Error handling LLM result for UDP client {}: {}",
163 client_id, e
164 );
165 }
166 }
167 Err(e) => {
168 error!(
169 "Failed to call LLM for UDP client {} connected event: {}",
170 client_id, e
171 );
172 }
173 }
174 }
175 }
176 });
177 task_registrar
178 .register_client_task(client_id, task_handle)
179 .await;
180
181 let task_registrar = app_state.clone();
185 let task_handle = tokio::spawn({
186 let socket_arc = socket_arc.clone();
187 let client_data = client_data.clone();
188
189 async move {
190 let mut buffer = vec![0u8; 65536]; loop {
193 match socket_arc.recv_from(&mut buffer).await {
194 Ok((n, source_addr)) => {
195 let data = buffer[..n].to_vec();
196 trace!(
197 "UDP client {} received {} bytes from {}",
198 client_id,
199 n,
200 source_addr
201 );
202
203 let mut client_data_lock = client_data.lock().await;
205
206 match client_data_lock.state {
207 ConnectionState::Idle => {
208 client_data_lock.state = ConnectionState::Processing;
210 drop(client_data_lock);
211
212 if let Err(e) = Self::process_datagram(
214 data,
215 source_addr,
216 client_id,
217 &llm_client,
218 &app_state,
219 &status_tx,
220 &socket_arc,
221 &client_data,
222 )
223 .await
224 {
225 error!(
226 "Error processing UDP datagram for client {}: {}",
227 client_id, e
228 );
229
230 let mut client_data_lock = client_data.lock().await;
232 client_data_lock.state = ConnectionState::Idle;
233 }
234 }
235 ConnectionState::Processing => {
236 trace!(
238 "UDP client {} is processing, queuing datagram",
239 client_id
240 );
241 client_data_lock.queued_datagrams.push((data, source_addr));
242 drop(client_data_lock);
243 }
244 ConnectionState::Accumulating => {
245 trace!(
247 "UDP client {} is accumulating, adding datagram",
248 client_id
249 );
250 client_data_lock.queued_datagrams.push((data, source_addr));
251 drop(client_data_lock);
252 }
253 }
254 }
255 Err(e) => {
256 error!("UDP client {} receive error: {}", client_id, e);
257 app_state
258 .update_client_status(client_id, ClientStatus::Error(e.to_string()))
259 .await;
260 let _ = status_tx
261 .send(format!("[CLIENT] UDP client {} error: {}", client_id, e));
262 let _ = status_tx.send("__UPDATE_UI__".to_string());
263 break;
264 }
265 }
266 }
267 app_state.remove_client_handle(client_id).await;
271 let _ = status_tx.send("__UPDATE_UI__".to_string());
272 }
273 });
274 task_registrar
275 .register_client_task(client_id, task_handle)
276 .await;
277
278 Ok(local_addr)
279 }
280
281 async fn process_datagram(
283 data: Vec<u8>,
284 source_addr: SocketAddr,
285 client_id: ClientId,
286 llm_client: &OllamaClient,
287 app_state: &Arc<AppState>,
288 status_tx: &mpsc::UnboundedSender<String>,
289 socket: &Arc<UdpSocket>,
290 client_data: &Arc<Mutex<ClientData>>,
291 ) -> Result<()> {
292 let data_hex = hex::encode(&data);
293 let data_len = data.len();
294
295 if let Some(instruction) = app_state.get_instruction_for_client(client_id).await {
297 let protocol = Arc::new(UdpClientProtocol::new());
298 let event = Event::new(
299 &UDP_CLIENT_DATAGRAM_RECEIVED_EVENT,
300 serde_json::json!({
301 "data_hex": data_hex,
302 "data_length": data_len,
303 "source_addr": source_addr.to_string(),
304 }),
305 );
306
307 let memory = {
309 let client_data_lock = client_data.lock().await;
310 client_data_lock.memory.clone()
311 };
312
313 let llm_result = call_llm_for_client(
315 llm_client,
316 app_state,
317 client_id.to_string(),
318 &instruction,
319 &memory,
320 Some(&event),
321 protocol.as_ref(),
322 status_tx,
323 )
324 .await?;
325
326 Self::handle_llm_result(
328 llm_result,
329 socket,
330 client_data,
331 client_id,
332 app_state,
333 status_tx,
334 )
335 .await?;
336 }
337
338 Ok(())
339 }
340
341 async fn handle_llm_result(
343 llm_result: ClientLlmResult,
344 socket: &Arc<UdpSocket>,
345 client_data: &Arc<Mutex<ClientData>>,
346 client_id: ClientId,
347 app_state: &Arc<AppState>,
348 status_tx: &mpsc::UnboundedSender<String>,
349 ) -> Result<()> {
350 if let Some(new_memory) = llm_result.memory_updates {
352 let mut client_data_lock = client_data.lock().await;
353 client_data_lock.memory = new_memory;
354 }
355
356 let protocol = Arc::new(UdpClientProtocol::new());
358 for action in llm_result.actions {
359 let action_result = protocol.as_ref().execute_action(action)?;
360 let waits = matches!(action_result, ClientActionResult::WaitForMore);
361
362 match Self::apply_action_result(action_result, socket, client_data, client_id).await? {
363 Applied::Disconnect => {
364 app_state
365 .update_client_status(client_id, ClientStatus::Disconnected)
366 .await;
367 let _ = status_tx.send(format!("[CLIENT] UDP client {} closed", client_id));
368 let _ = status_tx.send("__UPDATE_UI__".to_string());
369 return Ok(());
370 }
371 _ if waits => return Ok(()),
374 _ => {}
375 }
376 }
377
378 let mut client_data_lock = client_data.lock().await;
381 if !client_data_lock.queued_datagrams.is_empty() {
382 trace!(
383 "UDP client {} clearing {} queued datagrams",
384 client_id,
385 client_data_lock.queued_datagrams.len()
386 );
387 client_data_lock.queued_datagrams.clear();
388 }
389 client_data_lock.state = ConnectionState::Idle;
390
391 Ok(())
392 }
393
394 async fn apply_action_result(
400 action_result: ClientActionResult,
401 socket: &Arc<UdpSocket>,
402 client_data: &Arc<Mutex<ClientData>>,
403 client_id: ClientId,
404 ) -> Result<Applied> {
405 match action_result {
406 ClientActionResult::SendData(_) => {
407 warn!("SendData action not supported for UDP client, use send_udp_datagram");
409 Ok(Applied::Executed(
410 "send_data is not a UDP client verb; use send_udp_datagram".to_string(),
411 ))
412 }
413 ClientActionResult::Custom { name, data } => {
414 if name == "send_udp_datagram" {
415 let data_bytes = crate::client::wire_values::bytes(
416 data["data"]
417 .as_array()
418 .context("Missing 'data' array in send_udp_datagram")?,
419 )?;
420
421 let target_addr = if let Some(target) = data["target_addr"].as_str() {
422 target
423 .parse::<SocketAddr>()
424 .context(format!("Invalid target address: {}", target))?
425 } else {
426 let client_data_lock = client_data.lock().await;
428 client_data_lock.default_target
429 };
430
431 let sent = socket.send_to(&data_bytes, target_addr).await?;
434 trace!(
435 "UDP client {} sent {} bytes to {}",
436 client_id,
437 sent,
438 target_addr
439 );
440 Ok(Applied::Sent(sent))
441 } else if name == "change_target" {
442 let new_target_str = data["new_target"]
443 .as_str()
444 .context("Missing 'new_target' in change_target action")?;
445
446 let new_target = new_target_str
447 .parse::<SocketAddr>()
448 .context(format!("Invalid target address: {}", new_target_str))?;
449
450 let mut client_data_lock = client_data.lock().await;
451 client_data_lock.default_target = new_target;
452 info!(
453 "UDP client {} changed default target to {}",
454 client_id, new_target
455 );
456 Ok(Applied::Executed(format!(
457 "default target changed to {new_target}; nothing written to the wire"
458 )))
459 } else {
460 Ok(Applied::Executed(format!(
461 "custom result '{name}' is not a UDP client verb"
462 )))
463 }
464 }
465 ClientActionResult::Disconnect => {
466 info!("UDP client {} closing socket", client_id);
467 Ok(Applied::Disconnect)
468 }
469 ClientActionResult::WaitForMore => {
470 let mut client_data_lock = client_data.lock().await;
472 client_data_lock.state = ConnectionState::Accumulating;
473 trace!("UDP client {} waiting for more datagrams", client_id);
474 Ok(Applied::Executed(
475 "wait_for_more: now accumulating, nothing written to the wire".to_string(),
476 ))
477 }
478 ClientActionResult::NoAction => Ok(Applied::Executed(
479 "no_action: nothing written to the wire".to_string(),
480 )),
481 ClientActionResult::Multiple(_) => {
482 warn!("Multiple actions not yet supported in UDP client");
483 Ok(Applied::Executed(
484 "multiple actions are not yet supported by the UDP client".to_string(),
485 ))
486 }
487 }
488 }
489
490 async fn command_loop(
499 mut command_rx: mpsc::Receiver<ClientCommand>,
500 socket: Arc<UdpSocket>,
501 client_data: Arc<Mutex<ClientData>>,
502 client_id: ClientId,
503 app_state: Arc<AppState>,
504 status_tx: mpsc::UnboundedSender<String>,
505 ) {
506 use crate::llm::actions::protocol_trait::Protocol;
507
508 let protocol = UdpClientProtocol::new();
509
510 while let Some(command) = command_rx.recv().await {
511 let action = command.action.clone();
512 let outcome = match protocol.execute_action(action.clone()) {
513 Err(e) => Ok(ClientSendOutcome::Rejected {
514 error: e.to_string(),
515 }),
516 Ok(result) => {
517 match Self::apply_action_result(result, &socket, &client_data, client_id).await
518 {
519 Ok(Applied::Sent(bytes_sent)) => Ok(ClientSendOutcome::Sent { bytes_sent }),
522 Ok(Applied::Executed(detail)) => Ok(ClientSendOutcome::Executed { detail }),
523 Ok(Applied::Disconnect) => Ok(ClientSendOutcome::Disconnected),
524 Err(e) => Err(e),
525 }
526 }
527 };
528
529 let outcome_json = match &outcome {
530 Ok(outcome) => serde_json::to_value(outcome).unwrap_or(serde_json::Value::Null),
531 Err(e) => serde_json::json!({"error": e.to_string()}),
532 };
533 app_state
534 .record_access_log(
535 AccessLogOwner::Client(client_id.as_u32()),
536 protocol.protocol_name(),
537 None,
538 "injected_action",
539 action,
540 vec![outcome_json],
541 )
542 .await;
543
544 let disconnect = matches!(outcome, Ok(ClientSendOutcome::Disconnected));
545 if let Err(e) = &outcome {
546 error!("UDP client {} injected action failed: {}", client_id, e);
547 let _ = status_tx.send(format!(
548 "[WARN] Client {} injected action failed: {}",
549 client_id, e
550 ));
551 }
552 let _ = status_tx.send("__UPDATE_UI__".to_string());
553 crate::client::command_support::reply(command, outcome);
554
555 if disconnect {
556 app_state
557 .update_client_status(client_id, ClientStatus::Disconnected)
558 .await;
559 let _ = status_tx.send(format!(
560 "[CLIENT] UDP client {} closed (injected action)",
561 client_id
562 ));
563 break;
564 }
565 }
566
567 app_state.remove_client_handle(client_id).await;
568 let _ = status_tx.send("__UPDATE_UI__".to_string());
569 }
570}
571
572enum Applied {
574 Sent(usize),
576 Executed(String),
578 Disconnect,
580}