Skip to main content

grapsus_agent_protocol/
lib.rs

1// Allow large enum variants in generated protobuf code
2#![allow(clippy::large_enum_variant)]
3
4//! Agent protocol for Grapsus proxy
5//!
6//! This crate defines the protocol for communication between the proxy dataplane
7//! and external processing agents (WAF, auth, rate limiting, custom logic).
8//!
9//! The protocol is inspired by SPOE (Stream Processing Offload Engine) and Envoy's ext_proc,
10//! designed for bounded, predictable behavior with strong failure isolation.
11//!
12//! # Architecture
13//!
14//! - [`AgentHandlerV2`](v2::server::AgentHandlerV2): Trait for implementing agent logic
15//! - [`AgentResponse`]: Response from agent with decision and mutations
16//! - [`AgentClientV2`](v2::AgentClientV2): Client for sending events to agents from the proxy
17//! - [`GrpcAgentServerV2`](v2::server::GrpcAgentServerV2): gRPC server for agents
18//! - [`UdsAgentServerV2`](v2::uds_server::UdsAgentServerV2): UDS server for agents
19//!
20//! # Transports
21//!
22//! Two transport options are supported:
23//!
24//! ## Unix Domain Sockets (Default)
25//! Messages are length-prefixed with negotiated encoding (JSON or MessagePack):
26//! - 4-byte big-endian length prefix
27//! - Encoded payload (max 10MB)
28//!
29//! ## gRPC
30//! Binary protocol using Protocol Buffers over HTTP/2:
31//! - Better performance for high-throughput scenarios
32//! - Native support for TLS/mTLS
33//! - Language-agnostic (agents can be written in any language with gRPC support)
34
35#![allow(dead_code)]
36
37pub mod binary;
38pub mod buffer_pool;
39mod errors;
40pub mod headers;
41#[cfg(feature = "mmap-buffers")]
42pub mod mmap_buffer;
43mod protocol;
44
45/// Protocol v2 types with bidirectional streaming, capabilities, and flow control
46pub mod v2;
47
48/// gRPC v2 protocol definitions generated from proto/agent_v2.proto
49pub mod grpc_v2 {
50    tonic::include_proto!("grapsus.agent.v2");
51}
52
53// Re-export error types
54pub use errors::AgentProtocolError;
55
56// Re-export protocol types
57pub use protocol::{
58    AgentResponse, AuditMetadata, BinaryRequestBodyChunkEvent, BinaryResponseBodyChunkEvent,
59    BodyMutation, Decision, DetectionSeverity, EventType, GuardrailDetection,
60    GuardrailInspectEvent, GuardrailInspectionType, GuardrailResponse, HeaderOp,
61    RequestBodyChunkEvent, RequestCompleteEvent, RequestHeadersEvent, RequestMetadata,
62    ResponseBodyChunkEvent, ResponseHeadersEvent, TextSpan, WebSocketDecision, WebSocketFrameEvent,
63    WebSocketOpcode, MAX_MESSAGE_SIZE,
64};
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_body_mutation_types() {
72        // Test pass-through mutation
73        let pass_through = BodyMutation::pass_through(0);
74        assert!(pass_through.is_pass_through());
75        assert!(!pass_through.is_drop());
76        assert_eq!(pass_through.chunk_index, 0);
77
78        // Test drop mutation
79        let drop = BodyMutation::drop_chunk(1);
80        assert!(!drop.is_pass_through());
81        assert!(drop.is_drop());
82        assert_eq!(drop.chunk_index, 1);
83
84        // Test replace mutation
85        let replace = BodyMutation::replace(2, "modified content".to_string());
86        assert!(!replace.is_pass_through());
87        assert!(!replace.is_drop());
88        assert_eq!(replace.chunk_index, 2);
89        assert_eq!(replace.data, Some("modified content".to_string()));
90    }
91
92    #[test]
93    fn test_agent_response_streaming() {
94        // Test needs_more_data response
95        let response = AgentResponse::needs_more_data();
96        assert!(response.needs_more);
97        assert_eq!(response.decision, Decision::Allow);
98
99        // Test response with body mutation
100        let mutation = BodyMutation::replace(0, "new content".to_string());
101        let response = AgentResponse::default_allow().with_request_body_mutation(mutation.clone());
102        assert!(!response.needs_more);
103        assert!(response.request_body_mutation.is_some());
104        assert_eq!(
105            response.request_body_mutation.unwrap().data,
106            Some("new content".to_string())
107        );
108
109        // Test set_needs_more
110        let response = AgentResponse::default_allow().set_needs_more(true);
111        assert!(response.needs_more);
112    }
113}