1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9pub const PROTOCOL_VERSION: u32 = 1;
11
12pub const MAX_MESSAGE_SIZE: usize = 10 * 1024 * 1024;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum EventType {
19 RequestHeaders,
21 RequestBodyChunk,
23 ResponseHeaders,
25 ResponseBodyChunk,
27 RequestComplete,
29 WebSocketFrame,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
35#[serde(rename_all = "snake_case")]
36pub enum Decision {
37 #[default]
39 Allow,
40 Block {
42 status: u16,
44 body: Option<String>,
46 headers: Option<HashMap<String, String>>,
48 },
49 Redirect {
51 url: String,
53 status: u16,
55 },
56 Challenge {
58 challenge_type: String,
60 params: HashMap<String, String>,
62 },
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(rename_all = "snake_case")]
68pub enum HeaderOp {
69 Set { name: String, value: String },
71 Add { name: String, value: String },
73 Remove { name: String },
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct BodyMutation {
89 pub data: Option<String>,
95
96 #[serde(default)]
100 pub chunk_index: u32,
101}
102
103impl BodyMutation {
104 pub fn pass_through(chunk_index: u32) -> Self {
106 Self {
107 data: None,
108 chunk_index,
109 }
110 }
111
112 pub fn drop_chunk(chunk_index: u32) -> Self {
114 Self {
115 data: Some(String::new()),
116 chunk_index,
117 }
118 }
119
120 pub fn replace(chunk_index: u32, data: String) -> Self {
122 Self {
123 data: Some(data),
124 chunk_index,
125 }
126 }
127
128 pub fn is_pass_through(&self) -> bool {
130 self.data.is_none()
131 }
132
133 pub fn is_drop(&self) -> bool {
135 matches!(&self.data, Some(d) if d.is_empty())
136 }
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct RequestMetadata {
142 pub correlation_id: String,
144 pub request_id: String,
146 pub client_ip: String,
148 pub client_port: u16,
150 pub server_name: Option<String>,
152 pub protocol: String,
154 pub tls_version: Option<String>,
156 pub tls_cipher: Option<String>,
158 pub route_id: Option<String>,
160 pub upstream_id: Option<String>,
162 pub timestamp: String,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct RequestHeadersEvent {
169 pub metadata: RequestMetadata,
171 pub method: String,
173 pub uri: String,
175 pub headers: HashMap<String, Vec<String>>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct RequestBodyChunkEvent {
182 pub correlation_id: String,
184 pub data: String,
186 pub is_last: bool,
188 pub total_size: Option<usize>,
190 #[serde(default)]
194 pub chunk_index: u32,
195 #[serde(default)]
197 pub bytes_received: usize,
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct ResponseHeadersEvent {
203 pub correlation_id: String,
205 pub status: u16,
207 pub headers: HashMap<String, Vec<String>>,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct ResponseBodyChunkEvent {
214 pub correlation_id: String,
216 pub data: String,
218 pub is_last: bool,
220 pub total_size: Option<usize>,
222 #[serde(default)]
224 pub chunk_index: u32,
225 #[serde(default)]
227 pub bytes_sent: usize,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct RequestCompleteEvent {
233 pub correlation_id: String,
235 pub status: u16,
237 pub duration_ms: u64,
239 pub request_body_size: usize,
241 pub response_body_size: usize,
243 pub upstream_attempts: u32,
245 pub error: Option<String>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct WebSocketFrameEvent {
259 pub correlation_id: String,
261 pub opcode: String,
263 pub data: String,
265 pub client_to_server: bool,
267 pub frame_index: u64,
269 pub fin: bool,
271 pub route_id: Option<String>,
273 pub client_ip: String,
275}
276
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
279#[serde(rename_all = "snake_case")]
280pub enum WebSocketOpcode {
281 Continuation,
283 Text,
285 Binary,
287 Close,
289 Ping,
291 Pong,
293}
294
295impl WebSocketOpcode {
296 pub fn as_str(&self) -> &'static str {
298 match self {
299 Self::Continuation => "continuation",
300 Self::Text => "text",
301 Self::Binary => "binary",
302 Self::Close => "close",
303 Self::Ping => "ping",
304 Self::Pong => "pong",
305 }
306 }
307
308 pub fn from_u8(value: u8) -> Option<Self> {
310 match value {
311 0x0 => Some(Self::Continuation),
312 0x1 => Some(Self::Text),
313 0x2 => Some(Self::Binary),
314 0x8 => Some(Self::Close),
315 0x9 => Some(Self::Ping),
316 0xA => Some(Self::Pong),
317 _ => None,
318 }
319 }
320
321 pub fn as_u8(&self) -> u8 {
323 match self {
324 Self::Continuation => 0x0,
325 Self::Text => 0x1,
326 Self::Binary => 0x2,
327 Self::Close => 0x8,
328 Self::Ping => 0x9,
329 Self::Pong => 0xA,
330 }
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
338#[serde(rename_all = "snake_case")]
339pub enum WebSocketDecision {
340 #[default]
342 Allow,
343 Drop,
345 Close {
347 code: u16,
349 reason: String,
351 },
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct AgentRequest {
357 pub version: u32,
359 pub event_type: EventType,
361 pub payload: serde_json::Value,
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
367pub struct AgentResponse {
368 pub version: u32,
370 pub decision: Decision,
372 #[serde(default)]
374 pub request_headers: Vec<HeaderOp>,
375 #[serde(default)]
377 pub response_headers: Vec<HeaderOp>,
378 #[serde(default)]
380 pub routing_metadata: HashMap<String, String>,
381 #[serde(default)]
383 pub audit: AuditMetadata,
384
385 #[serde(default)]
396 pub needs_more: bool,
397
398 #[serde(default)]
403 pub request_body_mutation: Option<BodyMutation>,
404
405 #[serde(default)]
410 pub response_body_mutation: Option<BodyMutation>,
411
412 #[serde(default)]
416 pub websocket_decision: Option<WebSocketDecision>,
417}
418
419impl AgentResponse {
420 pub fn default_allow() -> Self {
422 Self {
423 version: PROTOCOL_VERSION,
424 decision: Decision::Allow,
425 request_headers: vec![],
426 response_headers: vec![],
427 routing_metadata: HashMap::new(),
428 audit: AuditMetadata::default(),
429 needs_more: false,
430 request_body_mutation: None,
431 response_body_mutation: None,
432 websocket_decision: None,
433 }
434 }
435
436 pub fn block(status: u16, body: Option<String>) -> Self {
438 Self {
439 version: PROTOCOL_VERSION,
440 decision: Decision::Block {
441 status,
442 body,
443 headers: None,
444 },
445 request_headers: vec![],
446 response_headers: vec![],
447 routing_metadata: HashMap::new(),
448 audit: AuditMetadata::default(),
449 needs_more: false,
450 request_body_mutation: None,
451 response_body_mutation: None,
452 websocket_decision: None,
453 }
454 }
455
456 pub fn redirect(url: String, status: u16) -> Self {
458 Self {
459 version: PROTOCOL_VERSION,
460 decision: Decision::Redirect { url, status },
461 request_headers: vec![],
462 response_headers: vec![],
463 routing_metadata: HashMap::new(),
464 audit: AuditMetadata::default(),
465 needs_more: false,
466 request_body_mutation: None,
467 response_body_mutation: None,
468 websocket_decision: None,
469 }
470 }
471
472 pub fn needs_more_data() -> Self {
474 Self {
475 version: PROTOCOL_VERSION,
476 decision: Decision::Allow,
477 request_headers: vec![],
478 response_headers: vec![],
479 routing_metadata: HashMap::new(),
480 audit: AuditMetadata::default(),
481 needs_more: true,
482 request_body_mutation: None,
483 response_body_mutation: None,
484 websocket_decision: None,
485 }
486 }
487
488 pub fn websocket_allow() -> Self {
490 Self {
491 websocket_decision: Some(WebSocketDecision::Allow),
492 ..Self::default_allow()
493 }
494 }
495
496 pub fn websocket_drop() -> Self {
498 Self {
499 websocket_decision: Some(WebSocketDecision::Drop),
500 ..Self::default_allow()
501 }
502 }
503
504 pub fn websocket_close(code: u16, reason: String) -> Self {
506 Self {
507 websocket_decision: Some(WebSocketDecision::Close { code, reason }),
508 ..Self::default_allow()
509 }
510 }
511
512 pub fn with_websocket_decision(mut self, decision: WebSocketDecision) -> Self {
514 self.websocket_decision = Some(decision);
515 self
516 }
517
518 pub fn with_request_body_mutation(mut self, mutation: BodyMutation) -> Self {
520 self.request_body_mutation = Some(mutation);
521 self
522 }
523
524 pub fn with_response_body_mutation(mut self, mutation: BodyMutation) -> Self {
526 self.response_body_mutation = Some(mutation);
527 self
528 }
529
530 pub fn set_needs_more(mut self, needs_more: bool) -> Self {
532 self.needs_more = needs_more;
533 self
534 }
535
536 pub fn add_request_header(mut self, op: HeaderOp) -> Self {
538 self.request_headers.push(op);
539 self
540 }
541
542 pub fn add_response_header(mut self, op: HeaderOp) -> Self {
544 self.response_headers.push(op);
545 self
546 }
547
548 pub fn with_audit(mut self, audit: AuditMetadata) -> Self {
550 self.audit = audit;
551 self
552 }
553}
554
555#[derive(Debug, Clone, Default, Serialize, Deserialize)]
557pub struct AuditMetadata {
558 #[serde(default)]
560 pub tags: Vec<String>,
561 #[serde(default)]
563 pub rule_ids: Vec<String>,
564 pub confidence: Option<f32>,
566 #[serde(default)]
568 pub reason_codes: Vec<String>,
569 #[serde(default)]
571 pub custom: HashMap<String, serde_json::Value>,
572}