wsprism_gateway/policy/
allowlist.rs

1//! Allowlist compilation and matching utilities.
2//!
3//! Supports simple wildcard matching for Ext lane (`svc:*`) and Hot lane
4//! (`svc_id:*`) entries.
5
6use wsprism_core::error::{Result, WsPrismError};
7
8/// Compiled allowlist rule for Ext Lane.
9#[derive(Debug, Clone)]
10pub struct ExtRule {
11    pub svc: String,
12    pub msg_type: Option<String>, // None => wildcard
13}
14
15/// Compiled allowlist rule for Hot Lane.
16#[derive(Debug, Clone)]
17pub struct HotRule {
18    pub svc_id: u8,
19    pub opcode: Option<u8>, // None => wildcard
20}
21
22pub fn compile_ext_rules(raw: &[String]) -> Result<Vec<ExtRule>> {
23    let mut out = Vec::with_capacity(raw.len());
24    for s in raw {
25        // format: "svc:type" or "svc:*"
26        let (svc, ty) = s.split_once(':').ok_or_else(|| {
27            WsPrismError::BadRequest(format!("invalid ext_allowlist entry: {s} (expected svc:type)"))
28        })?;
29        let ty = if ty == "*" { None } else { Some(ty.to_string()) };
30        out.push(ExtRule { svc: svc.to_string(), msg_type: ty });
31    }
32    Ok(out)
33}
34
35pub fn compile_hot_rules(raw: &[String]) -> Result<Vec<HotRule>> {
36    let mut out = Vec::with_capacity(raw.len());
37    for s in raw {
38        // format: "svc_id:opcode" where opcode may be "*"
39        let (svc_id_s, op_s) = s.split_once(':').ok_or_else(|| {
40            WsPrismError::BadRequest(format!("invalid hot_allowlist entry: {s} (expected svc_id:opcode)"))
41        })?;
42
43        let svc_id: u8 = svc_id_s.parse().map_err(|_| {
44            WsPrismError::BadRequest(format!("invalid hot_allowlist svc_id: {svc_id_s}"))
45        })?;
46
47        let opcode = if op_s == "*" {
48            None
49        } else {
50            Some(op_s.parse().map_err(|_| {
51                WsPrismError::BadRequest(format!("invalid hot_allowlist opcode: {op_s}"))
52            })?)
53        };
54
55        out.push(HotRule { svc_id, opcode });
56    }
57    Ok(out)
58}
59
60pub fn is_ext_allowed(rules: &[ExtRule], svc: &str, msg_type: &str) -> bool {
61    rules.iter().any(|r| {
62        if r.svc != svc { return false; }
63        match &r.msg_type {
64            None => true,
65            Some(t) => t == msg_type,
66        }
67    })
68}
69
70pub fn is_hot_allowed(rules: &[HotRule], svc_id: u8, opcode: u8) -> bool {
71    rules.iter().any(|r| {
72        if r.svc_id != svc_id { return false; }
73        match r.opcode {
74            None => true,
75            Some(op) => op == opcode,
76        }
77    })
78}