Data Model
Event, ProtocolData and protocol helpers
Data Model
The Event is modular by design: fields common to any source live at
the top level; protocol-specific fields live in ProtocolData (an extensible
enum). Today Http covers nginx; tomorrow Tcp, Udp, Tls etc. can be
added without changing the core — the source just populates the corresponding
variant. Heuristics and the scorer operate on the Event and pattern-match
on protocol, ignoring absent fields.
// sentry-core/src/event.rs
pub struct Event {
// --- common to any protocol ---
pub id: Uuid,
pub timestamp: DateTime<Utc>,
pub source: SourceKind, // Nginx, Tcp, HttpProxy, CloudflareLogs...
pub transport: Transport, // Tcp | Udp | Tls | Internal
pub client_ip: IpAddr,
pub client_port: Option<u16>,
pub server_port: Option<u16>, // exposed port observed
pub geo: Option<GeoInfo>,
pub asn: Option<u32>,
pub direction: Direction, // Inbound | Outbound
pub bytes_in: Option<u64>,
pub bytes_out: Option<u64>,
pub duration_ms: Option<u64>,
pub raw: Option<String>, // original record for audit
// --- protocol-specific ---
pub protocol: ProtocolData,
}
pub enum ProtocolData {
Http(HttpData),
Tcp(TcpData),
Udp(UdpData),
TlsHandshake(TlsData),
Raw(RawData), // fallback: bytes + note
// future variants land here without breaking consumers
}
pub struct HttpData {
pub method: HttpMethod,
pub scheme: Option<String>, // http | https
pub host: Option<String>,
pub path: String,
pub query: Option<String>,
pub fragment: Option<String>,
pub status: Option<u16>,
pub user_agent: Option<String>,
pub referer: Option<String>,
pub headers: HashMap<String, String>,
pub body: Option<Vec<u8>>, // when available (proxy/middleware)
pub cookies: Option<HashMap<String, String>>,
}
pub struct TcpData {
pub flags: TcpFlags, // syn/fin/rst/ack...
pub payload: Option<Vec<u8>>, // reconstructed stream bytes (when capturable)
pub stream_id: Option<u64>, // to correlate segments
pub stage: TcpStage, // Syn | SynAck | Data | Fin | Reset
}
pub struct UdpData {
pub payload: Option<Vec<u8>>,
pub dns_query: Option<String>, // if recognized as DNS
}
pub struct TlsData {
pub sni: Option<String>,
pub ja3: Option<String>, // TLS fingerprint
pub ja4: Option<String>,
pub cipher: Option<String>,
pub version: Option<String>,
}
pub struct RawData {
pub note: String,
pub bytes: Vec<u8>,
}
// Ergonomic helpers: e.kind_http() -> Option<&HttpData> etc.
impl Event {
pub fn http(&self) -> Option<&HttpData> { match &self.protocol { ProtocolData::Http(d) => Some(d), _ => None } }
pub fn tcp(&self) -> Option<&TcpData> { match &self.protocol { ProtocolData::Tcp(d) => Some(d), _ => None } }
pub fn tls(&self) -> Option<&TlsData> { match &self.protocol { ProtocolData::TlsHandshake(d) => Some(d), _ => None } }
pub fn is_http(&self) -> bool { matches!(self.protocol, ProtocolData::Http(_)) }
}
Rule: no pipeline stage may assume
ProtocolData::Http. HTTP heuristics checkevt.http()and returnNonefor other variants; TCP heuristics do the analog. This way the same pipeline runs for nginx today and TCP capture tomorrow.
AnalysisResult
pub struct AnalysisResult {
pub risk_score: u8, // 0..=100
pub risk_level: RiskLevel, // Info|Low|Medium|High|Critical
pub signals: Vec<Signal>, // what fired
pub verdict: Verdict, // Allow|Challenge|Block|Quarantine
}
pub enum Signal {
PathTraversal, SqlInjection, Xss, CmdInjection,
UnknownRoute, ScanBehavior, AbnormalRate,
SuspiciousUA, TorExitNode, KnownBadIp,
AnomalousPayload(/* model */),
Custom(String),
}
Adding a new protocol
When adding a new protocol:
- Add a variant to
ProtocolData. - Add a helper on
impl Event(e.g.pub fn my_proto(&self) -> Option<&MyData>). - Update
protocol_kind(). - Do not add loose fields to the top-level
Event— put them in the enum.
Encoding normalization (heuristics)
Heuristics (SQLi, XSS, path traversal, etc.) run on the URL-decoded form
of the path/query (heuristics::http_text), so that encoded payloads
(%27 = ', + or %20 = space) cannot bypass them. When writing new
heuristics, always use http_text(http) instead of reading http.path /
http.query directly.