Skip to main content

relay_core_api/
rule.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// A self-contained unit of logic with explicit stage, priority, and flow control.
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Rule {
7    /// Unique identifier
8    pub id: String,
9    /// Human-readable name
10    pub name: String,
11    /// Whether the rule is enabled
12    pub active: bool,
13
14    /// [P0] Execution Stage: Determines *when* this rule is evaluated.
15    pub stage: RuleStage,
16
17    /// Execution priority (Higher value = Earlier execution)
18    /// Default: 0
19    #[serde(default)]
20    pub priority: i32,
21
22    /// [P0] Flow Control: Replaces `stop_on_match`.
23    /// Determines if subsequent rules in the same stage should be skipped.
24    pub termination: RuleTermination,
25
26    /// The condition to match traffic
27    pub filter: Filter,
28
29    /// The actions to execute when matched
30    pub actions: Vec<Action>,
31
32    /// [P1] Performance Constraints
33    pub constraints: Option<RuleConstraints>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct RuleGroup {
38    pub id: String,
39    pub name: String,
40    pub active: bool,
41    pub rules: Vec<Rule>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45pub enum RuleStage {
46    /// L3/L4: Connection establishment (IP/Port filtering)
47    Connect,
48    /// L7: HTTP Request Headers available
49    RequestHeaders,
50    /// L7: HTTP Request Body available (Streaming or Buffered)
51    RequestBody,
52    /// L7: HTTP Response Headers available
53    ResponseHeaders,
54    /// L7: HTTP Response Body available (Streaming or Buffered)
55    ResponseBody,
56    /// WebSocket Message frame
57    WebSocketMessage,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub enum RuleTermination {
62    /// Continue to the next rule in this stage (Default)
63    Continue,
64    /// Stop processing subsequent rules in this stage
65    Stop,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct RuleConstraints {
70    /// Max execution time in ms (soft limit)
71    pub timeout_ms: Option<u64>,
72}
73
74// --- Filters ---
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(tag = "type", content = "config")]
78pub enum Filter {
79    /// Matches everything (useful for global rules)
80    All,
81
82    // --- L3/L4 Network Filters (Valid in Connect stage) ---
83    /// Matches by Source IP (CIDR support)
84    SrcIp(String), // e.g., "192.168.1.0/24"
85    /// Matches by Destination Port
86    DstPort(u16),
87    /// Matches by Protocol (TCP/UDP)
88    Protocol(String),
89    /// Matches if connection is in transparent proxy mode
90    TransparentMode(bool),
91
92    // --- L7 HTTP Filters (Valid in Request/Response stages) ---
93    /// Matches by Full URL
94    Url(StringMatcher),
95    /// Matches by Host/Domain
96    Host(StringMatcher),
97    /// Matches by URL Path
98    Path(StringMatcher),
99    /// Matches by HTTP Method (GET, POST, etc.)
100    Method(StringMatcher),
101    /// Matches by Request Header presence or value
102    RequestHeader {
103        name: String,
104        value: Option<StringMatcher>,
105    },
106    /// Matches by Response Header presence or value
107    ResponseHeader {
108        name: String,
109        value: Option<StringMatcher>,
110    },
111    /// Matches by Response Status Code
112    StatusCode(u16),
113    /// Matches by Response Body Content (Valid only in ResponseBody stage)
114    ResponseBody(StringMatcher),
115    /// Matches by WebSocket Message Content (Valid only in WebSocketMessage stage)
116    WebSocketMessage(StringMatcher),
117
118    // --- Logical Compositors ---
119    And(Vec<Filter>),
120    Or(Vec<Filter>),
121    Not(Box<Filter>),
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(tag = "mode", content = "value")]
126pub enum StringMatcher {
127    Exact(String),
128    Contains(String),
129    Prefix(String),
130    Suffix(String),
131    Regex(String),
132    Glob(String),
133}
134
135// --- Actions ---
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
138#[serde(tag = "type", content = "config")]
139pub enum Action {
140    // === Tier 1: Universal Control Actions ===
141    /// Drop the connection immediately
142    Drop,
143    /// Abort the connection (RST)
144    Abort,
145    /// Delay execution (Latency Simulation)
146    Delay {
147        ms: u64,
148    },
149    /// Throttle bandwidth
150    Throttle {
151        kbps: u64,
152    },
153    /// Tag for subsequent processing/stats
154    Tag {
155        key: String,
156        value: String,
157    },
158    /// Pause the flow for manual inspection/intervention (e.g., in GUI)
159    Inspect,
160    /// Set a variable for use in subsequent actions (Fluxzy-style)
161    SetVariable {
162        name: String,
163        value: String,
164    },
165    /// Rate limit traffic based on a key
166    RateLimit {
167        /// The key to limit by (e.g., "ip", "host", "path", or a template like "{{ip}}:{{path}}")
168        key: String,
169        /// Maximum number of requests allowed in the window
170        limit: u32,
171        /// Time window in milliseconds
172        window_ms: u64,
173    },
174
175    // === Tier 2: L3/L4 Network Actions (Future) ===
176    RedirectIp {
177        target: String,
178    },
179    SetTtl {
180        ttl: u8,
181    },
182    ForwardPort {
183        target_host: String,
184        target_port: u16,
185    },
186
187    // === Tier 3: L7 HTTP Actions ===
188    // --- Terminal Actions ---
189    MockResponse {
190        status: u16,
191        headers: HashMap<String, String>,
192        body: Option<BodySource>,
193    },
194    MapLocal {
195        path: String,
196        content_type: Option<String>,
197    },
198    MapRemote {
199        url: String,
200        preserve_host: bool,
201    },
202    Redirect {
203        location: String,
204        status: u16,
205    },
206
207    // --- Modification Actions (Headers) ---
208    /// Add a header (Appends if exists, useful for multi-value headers like Set-Cookie)
209    AddRequestHeader {
210        name: String,
211        value: String,
212    },
213    /// Update an existing header (Supports {{previous}} variable)
214    /// If add_if_missing is true, creates it if not found.
215    UpdateRequestHeader {
216        name: String,
217        value: String,
218        add_if_missing: bool,
219    },
220    /// Delete a header
221    DeleteRequestHeader {
222        name: String,
223    },
224
225    AddResponseHeader {
226        name: String,
227        value: String,
228    },
229    UpdateResponseHeader {
230        name: String,
231        value: String,
232        add_if_missing: bool,
233    },
234    DeleteResponseHeader {
235        name: String,
236    },
237
238    // --- Modification Actions (Request/Response) ---
239    SetRequestMethod {
240        method: String,
241    },
242    SetRequestUrl {
243        url: String,
244    },
245    SetRequestBody {
246        body: BodySource,
247    },
248    SetResponseStatus {
249        status: u16,
250    },
251    SetResponseBody {
252        body: BodySource,
253    },
254
255    // --- Transformation Actions ---
256    TransformRequestBody {
257        transform: BodyTransform,
258    },
259    TransformResponseBody {
260        transform: BodyTransform,
261    },
262
263    // === Tier 3: WebSocket Actions ===
264    MockWebSocketMessage {
265        direction: WebSocketDirection,
266        message: String,
267    },
268    DropWebSocketMessage,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
272#[serde(tag = "type", content = "value")]
273pub enum BodySource {
274    Text(String),
275    File(String),
276    Base64(String),
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
280#[serde(tag = "type", content = "config")]
281pub enum BodyTransform {
282    RegexReplace {
283        pattern: String,
284        replacement: String,
285    },
286    JsonPathSet {
287        path: String,
288        value: String,
289    },
290    JsonPathDelete {
291        path: String,
292    },
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub enum WebSocketDirection {
297    Incoming,
298    Outgoing,
299}
300
301// --- Tracing ---
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct RuleTrace {
305    pub flow_id: String,
306    /// Summary for list view icons
307    pub summary: RuleTraceSummary,
308    /// Detailed execution log (Lazy loaded)
309    pub events: Vec<RuleExecutionEvent>,
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
313pub enum RuleTraceSummary {
314    NoMatch,
315    /// Modified by one or more rules
316    Modified {
317        rule_ids: Vec<String>,
318    },
319    /// Terminated by a rule (Drop/Mock)
320    Terminated {
321        rule_id: String,
322        reason: TerminalReason,
323    },
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
327pub enum TerminalReason {
328    Drop,
329    Abort,
330    Mock,
331    Redirect,
332    Inspect,
333    RateLimited,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct RuleExecutionEvent {
338    pub rule_id: String,
339    pub stage: RuleStage,
340    pub matched: bool,
341    pub duration_us: u64,
342    pub outcome: RuleOutcome,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
346pub enum RuleOutcome {
347    Skipped,
348    MatchedAndExecuted,
349    MatchedAndTerminated,
350    Failed(String),
351}