Skip to main content

netget/client/udp/
actions.rs

1//! UDP client protocol actions implementation
2
3use crate::llm::actions::{
4    client_trait::{Client, ClientActionResult},
5    protocol_trait::Protocol,
6    ActionDefinition, Parameter,
7};
8use crate::protocol::EventType;
9use crate::state::app_state::AppState;
10use anyhow::{Context, Result};
11use serde_json::json;
12use std::sync::LazyLock;
13
14/// UDP client connected event (socket bound and ready)
15pub static UDP_CLIENT_CONNECTED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
16    EventType::new(
17        "udp_connected",
18        "UDP client socket bound and ready to send/receive",
19        json!({
20            "type": "send_udp_datagram",
21            "data_hex": "48656c6c6f"
22        }),
23    )
24    .with_parameters(vec![
25        Parameter {
26            name: "remote_addr".to_string(),
27            type_hint: "string".to_string(),
28            description: "Default remote server address".to_string(),
29            required: true,
30        },
31        Parameter {
32            name: "local_addr".to_string(),
33            type_hint: "string".to_string(),
34            description: "Local socket address".to_string(),
35            required: true,
36        },
37    ])
38});
39
40/// UDP client datagram received event
41pub static UDP_CLIENT_DATAGRAM_RECEIVED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
42    EventType::new(
43        "udp_datagram_received",
44        "Datagram received from UDP server",
45        json!({"type": "placeholder", "event_id": "udp_datagram_received"}),
46    )
47    .with_parameters(vec![
48        Parameter {
49            name: "data_hex".to_string(),
50            type_hint: "string".to_string(),
51            description: "The datagram data (as hex string)".to_string(),
52            required: true,
53        },
54        Parameter {
55            name: "data_length".to_string(),
56            type_hint: "number".to_string(),
57            description: "Length of data in bytes".to_string(),
58            required: true,
59        },
60        Parameter {
61            name: "source_addr".to_string(),
62            type_hint: "string".to_string(),
63            description: "Source address of the datagram".to_string(),
64            required: true,
65        },
66    ])
67});
68
69/// UDP client protocol action handler
70pub struct UdpClientProtocol;
71
72impl Default for UdpClientProtocol {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl UdpClientProtocol {
79    pub fn new() -> Self {
80        Self
81    }
82}
83
84// Implement Protocol trait (common functionality)
85impl Protocol for UdpClientProtocol {
86    fn get_async_actions(&self, _state: &AppState) -> Vec<ActionDefinition> {
87        vec![
88            ActionDefinition {
89                name: "send_udp_datagram".to_string(),
90                description: "Send a UDP datagram to a specific address".to_string(),
91                parameters: vec![
92                    Parameter {
93                        name: "data_hex".to_string(),
94                        type_hint: "string".to_string(),
95                        description: "Hexadecimal encoded data to send".to_string(),
96                        required: true,
97                    },
98                    Parameter {
99                        name: "target_addr".to_string(),
100                        type_hint: "string".to_string(),
101                        description: "Optional target address (defaults to remote_addr)"
102                            .to_string(),
103                        required: false,
104                    },
105                ],
106                example: json!({
107                    "type": "send_udp_datagram",
108                    "data_hex": "48656c6c6f",
109                    "target_addr": "127.0.0.1:8080"
110                }),
111                log_template: None,
112            },
113            ActionDefinition {
114                name: "change_target".to_string(),
115                description: "Change the default target address for datagrams".to_string(),
116                parameters: vec![Parameter {
117                    name: "new_target".to_string(),
118                    type_hint: "string".to_string(),
119                    description: "New default target address".to_string(),
120                    required: true,
121                }],
122                example: json!({
123                    "type": "change_target",
124                    "new_target": "127.0.0.1:9090"
125                }),
126                log_template: None,
127            },
128            ActionDefinition {
129                name: "close_socket".to_string(),
130                description: "Close the UDP socket".to_string(),
131                parameters: vec![],
132                example: json!({
133                    "type": "close_socket"
134                }),
135                log_template: None,
136            },
137        ]
138    }
139    fn get_sync_actions(&self) -> Vec<ActionDefinition> {
140        vec![
141            ActionDefinition {
142                name: "send_udp_datagram".to_string(),
143                description: "Send UDP datagram in response to received datagram".to_string(),
144                parameters: vec![
145                    Parameter {
146                        name: "data_hex".to_string(),
147                        type_hint: "string".to_string(),
148                        description: "Hexadecimal encoded data to send".to_string(),
149                        required: true,
150                    },
151                    Parameter {
152                        name: "target_addr".to_string(),
153                        type_hint: "string".to_string(),
154                        description:
155                            "Optional target address (defaults to source of received datagram)"
156                                .to_string(),
157                        required: false,
158                    },
159                ],
160                example: json!({
161                    "type": "send_udp_datagram",
162                    "data_hex": "48656c6c6f"
163                }),
164                log_template: None,
165            },
166            ActionDefinition {
167                name: "wait_for_more".to_string(),
168                description: "Wait for more datagrams before responding".to_string(),
169                parameters: vec![],
170                example: json!({
171                    "type": "wait_for_more"
172                }),
173                log_template: None,
174            },
175        ]
176    }
177    fn protocol_name(&self) -> &'static str {
178        "UDP"
179    }
180    fn get_event_types(&self) -> Vec<EventType> {
181        vec![
182            EventType::new(
183                "udp_connected",
184                "Triggered when UDP client socket is bound and ready",
185                json!({"type": "placeholder", "event_id": "udp_connected"}),
186            ),
187            EventType::new(
188                "udp_datagram_received",
189                "Triggered when UDP client receives a datagram",
190                json!({"type": "placeholder", "event_id": "udp_datagram_received"}),
191            ),
192        ]
193    }
194    fn stack_name(&self) -> &'static str {
195        "ETH>IP>UDP"
196    }
197    fn keywords(&self) -> Vec<&'static str> {
198        vec!["udp", "udp client", "connect to udp", "datagram"]
199    }
200    fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
201        use crate::protocol::metadata::{DevelopmentState, ProtocolMetadataV2};
202
203        ProtocolMetadataV2::builder()
204            .state(DevelopmentState::Experimental)
205            .implementation("Tokio UdpSocket for connectionless datagram communication")
206            .llm_control("Full control over datagrams, target addresses, and responses")
207            .e2e_testing("nc (netcat) with -u flag as test server")
208            .build()
209    }
210    fn description(&self) -> &'static str {
211        "UDP client for sending and receiving datagrams"
212    }
213    fn example_prompt(&self) -> &'static str {
214        "Connect to UDP at localhost:8080 and send 'HELLO'"
215    }
216    fn group_name(&self) -> &'static str {
217        "Core"
218    }
219
220    fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
221        use crate::llm::actions::StartupExamples;
222        use serde_json::json;
223
224        StartupExamples::new(
225            // LLM mode: LLM handles UDP client
226            json!({
227                "type": "open_client",
228                "remote_addr": "127.0.0.1:8080",
229                "base_stack": "udp",
230                "instruction": "Send a UDP datagram containing 'HELLO' and echo any responses"
231            }),
232            // Script mode: Code-based UDP handling
233            json!({
234                "type": "open_client",
235                "remote_addr": "127.0.0.1:8080",
236                "base_stack": "udp",
237                "event_handlers": [{
238                    "event_pattern": "udp_datagram_received",
239                    "handler": {
240                        "type": "script",
241                        "language": "python",
242                        "code": "<udp_handler>"
243                    }
244                }]
245            }),
246            // Static mode: Fixed UDP response
247            json!({
248                "type": "open_client",
249                "remote_addr": "127.0.0.1:8080",
250                "base_stack": "udp",
251                "event_handlers": [{
252                    "event_pattern": "udp_connected",
253                    "handler": {
254                        "type": "static",
255                        "actions": [{
256                            "type": "send_udp_datagram",
257                            "data_hex": "48454c4c4f"
258                        }]
259                    }
260                }]
261            }),
262        )
263    }
264}
265
266// Implement Client trait (client-specific functionality)
267impl Client for UdpClientProtocol {
268    fn connect(
269        &self,
270        ctx: crate::protocol::ConnectContext,
271    ) -> std::pin::Pin<
272        Box<dyn std::future::Future<Output = anyhow::Result<std::net::SocketAddr>> + Send>,
273    > {
274        Box::pin(async move {
275            use crate::client::udp::UdpClient;
276            UdpClient::connect_with_llm_actions(
277                ctx.remote_addr,
278                ctx.llm_client,
279                ctx.state,
280                ctx.status_tx,
281                ctx.client_id,
282            )
283            .await
284        })
285    }
286    fn execute_action(&self, action: serde_json::Value) -> Result<ClientActionResult> {
287        let action_type = action
288            .get("type")
289            .and_then(|v| v.as_str())
290            .context("Missing 'type' field in action")?;
291
292        match action_type {
293            "send_udp_datagram" => {
294                let data_hex = action
295                    .get("data_hex")
296                    .and_then(|v| v.as_str())
297                    .context("Missing 'data_hex' field")?;
298
299                let data = hex::decode(data_hex).context("Invalid hex data")?;
300
301                // Target address is optional, handled in Custom action
302                let target_addr = action
303                    .get("target_addr")
304                    .and_then(|v| v.as_str())
305                    .map(|s| s.to_string());
306
307                Ok(ClientActionResult::Custom {
308                    name: "send_udp_datagram".to_string(),
309                    data: json!({
310                        "data": data,
311                        "target_addr": target_addr,
312                    }),
313                })
314            }
315            "change_target" => {
316                let new_target = action
317                    .get("new_target")
318                    .and_then(|v| v.as_str())
319                    .context("Missing 'new_target' field")?;
320
321                Ok(ClientActionResult::Custom {
322                    name: "change_target".to_string(),
323                    data: json!({
324                        "new_target": new_target,
325                    }),
326                })
327            }
328            "close_socket" => Ok(ClientActionResult::Disconnect),
329            "wait_for_more" => Ok(ClientActionResult::WaitForMore),
330            _ => Err(anyhow::anyhow!(
331                "Unknown UDP client action: {}",
332                action_type
333            )),
334        }
335    }
336}