1use crate::llm::actions::{
4 client_trait::{Client, ClientActionResult},
5 protocol_trait::Protocol,
6 ActionDefinition, Parameter, ParameterDefinition,
7};
8use crate::protocol::EventType;
9use crate::state::app_state::AppState;
10use anyhow::{Context, Result};
11use serde_json::json;
12use std::sync::LazyLock;
13
14pub static ISIS_PDU_RECEIVED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
16 EventType::new(
17 "isis_pdu_received",
18 "IS-IS PDU captured from network interface",
19 json!({"type": "wait_for_more"}),
20 )
21 .with_parameters(vec![
22 Parameter {
23 name: "pdu_type".to_string(),
24 type_hint: "string".to_string(),
25 description: "Type of IS-IS PDU (e.g., 'L1 LAN Hello', 'L2 LSP')".to_string(),
26 required: true,
27 },
28 Parameter {
29 name: "pdu_type_code".to_string(),
30 type_hint: "number".to_string(),
31 description: "Numeric PDU type code".to_string(),
32 required: true,
33 },
34 Parameter {
35 name: "version".to_string(),
36 type_hint: "number".to_string(),
37 description: "IS-IS protocol version".to_string(),
38 required: true,
39 },
40 Parameter {
41 name: "pdu_length".to_string(),
42 type_hint: "number".to_string(),
43 description: "Length of the PDU in bytes".to_string(),
44 required: true,
45 },
46 Parameter {
47 name: "raw_pdu_hex".to_string(),
48 type_hint: "string".to_string(),
49 description: "Raw IS-IS PDU data as hex string".to_string(),
50 required: true,
51 },
52 Parameter {
53 name: "raw_frame_hex".to_string(),
54 type_hint: "string".to_string(),
55 description: "Complete Ethernet frame as hex string".to_string(),
56 required: true,
57 },
58 ])
59});
60
61pub struct IsisClientProtocol;
63
64impl IsisClientProtocol {
65 pub fn new() -> Self {
66 Self
67 }
68}
69
70impl Protocol for IsisClientProtocol {
72 fn get_async_actions(&self, _state: &AppState) -> Vec<ActionDefinition> {
73 vec![
74 ActionDefinition {
75 name: "analyze_topology".to_string(),
76 description: "Analyze IS-IS topology from captured PDUs".to_string(),
77 parameters: vec![],
78 example: json!({
79 "type": "analyze_topology"
80 }),
81 log_template: None,
82 },
83 ActionDefinition {
84 name: "stop_capture".to_string(),
85 description: "Stop capturing IS-IS PDUs".to_string(),
86 parameters: vec![],
87 example: json!({
88 "type": "stop_capture"
89 }),
90 log_template: None,
91 },
92 ]
93 }
94 fn get_sync_actions(&self) -> Vec<ActionDefinition> {
95 vec![ActionDefinition {
96 name: "wait_for_more".to_string(),
97 description: "Continue capturing IS-IS PDUs".to_string(),
98 parameters: vec![],
99 example: json!({
100 "type": "wait_for_more"
101 }),
102 log_template: None,
103 }]
104 }
105 fn protocol_name(&self) -> &'static str {
106 "IS-IS"
107 }
108 fn get_event_types(&self) -> Vec<EventType> {
109 vec![EventType::new(
110 "isis_pdu_received",
111 "Triggered when an IS-IS PDU is captured from the network",
112 json!({"type": "wait_for_more"}),
113 )]
114 }
115 fn stack_name(&self) -> &'static str {
116 "ETH>LLC/SNAP>IS-IS"
117 }
118 fn keywords(&self) -> Vec<&'static str> {
119 vec![
120 "isis",
121 "is-is",
122 "intermediate system",
123 "routing protocol",
124 "layer 2 routing",
125 ]
126 }
127 fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
128 use crate::protocol::metadata::{DevelopmentState, ProtocolMetadataV2};
129
130 ProtocolMetadataV2::builder()
131 .state(DevelopmentState::Experimental)
132 .implementation("pcap for raw packet capture, custom IS-IS PDU parsing")
133 .llm_control("Passive capture and analysis of IS-IS topology")
134 .e2e_testing("Requires IS-IS router or packet replay")
135 .build()
136 }
137 fn description(&self) -> &'static str {
138 "IS-IS client for capturing and analyzing IS-IS routing protocol PDUs"
139 }
140 fn example_prompt(&self) -> &'static str {
141 "Capture IS-IS PDUs on eth0 and analyze network topology"
142 }
143 fn group_name(&self) -> &'static str {
144 "Routing"
145 }
146 fn get_startup_parameters(&self) -> Vec<ParameterDefinition> {
147 vec![ParameterDefinition {
148 name: "interface".to_string(),
149 type_hint: "string".to_string(),
150 description: "Network interface to capture IS-IS traffic on (e.g., 'eth0', 'en0')"
151 .to_string(),
152 required: true,
153 example: json!("eth0"),
154 default: None,
155 }]
156 }
157 fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
158 use crate::llm::actions::StartupExamples;
159 use serde_json::json;
160
161 StartupExamples::new(
162 json!({
164 "type": "open_client",
165 "remote_addr": "0.0.0.0:0",
166 "base_stack": "isis",
167 "instruction": "Capture IS-IS PDUs on eth0 and analyze network topology",
168 "startup_params": {
169 "interface": "eth0"
170 }
171 }),
172 json!({
174 "type": "open_client",
175 "remote_addr": "0.0.0.0:0",
176 "base_stack": "isis",
177 "startup_params": {
178 "interface": "eth0"
179 },
180 "event_handlers": [{
181 "event_pattern": "isis_pdu_received",
182 "handler": {
183 "type": "script",
184 "language": "python",
185 "code": "<isis_client_handler>"
186 }
187 }]
188 }),
189 json!({
191 "type": "open_client",
192 "remote_addr": "0.0.0.0:0",
193 "base_stack": "isis",
194 "startup_params": {
195 "interface": "eth0"
196 },
197 "event_handlers": [{
198 "event_pattern": "isis_pdu_received",
199 "handler": {
200 "type": "static",
201 "actions": [{
202 "type": "wait_for_more"
203 }]
204 }
205 }]
206 }),
207 )
208 }
209}
210
211impl Client for IsisClientProtocol {
213 fn connect(
214 &self,
215 ctx: crate::protocol::ConnectContext,
216 ) -> std::pin::Pin<
217 Box<dyn std::future::Future<Output = anyhow::Result<std::net::SocketAddr>> + Send>,
218 > {
219 Box::pin(async move {
220 use crate::client::isis::IsisClient;
221
222 let interface = ctx
232 .startup_params
233 .as_ref()
234 .map(|p| p.get_optional_string("interface"))
235 .transpose()?
236 .flatten()
237 .filter(|s| !s.trim().is_empty())
238 .unwrap_or(ctx.remote_addr);
239
240 IsisClient::connect_with_llm_actions(
241 interface,
242 ctx.llm_client,
243 ctx.state,
244 ctx.status_tx,
245 ctx.client_id,
246 )
247 .await
248 })
249 }
250 fn execute_action(&self, action: serde_json::Value) -> Result<ClientActionResult> {
251 let action_type = action
252 .get("type")
253 .and_then(|v| v.as_str())
254 .context("Missing 'type' field in action")?;
255
256 match action_type {
257 "analyze_topology" => {
258 Ok(ClientActionResult::WaitForMore)
260 }
261 "stop_capture" => Ok(ClientActionResult::Disconnect),
262 "wait_for_more" => Ok(ClientActionResult::WaitForMore),
263 _ => Err(anyhow::anyhow!(
264 "Unknown IS-IS client action: {}",
265 action_type
266 )),
267 }
268 }
269}