Skip to main content

flowscope/radius/
types.rs

1//! Public types for the RADIUS parser.
2
3use std::net::Ipv4Addr;
4
5/// One parsed RADIUS message.
6#[derive(Debug, Clone, PartialEq, Eq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[non_exhaustive]
9pub struct RadiusMessage {
10    /// Decoded RFC 2865 §3 code.
11    pub code: RadiusCodeKind,
12    /// The 1-byte identifier — matches requests with their
13    /// responses.
14    pub identifier: u8,
15    /// Authenticator length (always 16 octets — included
16    /// for round-trip completeness).
17    pub authenticator_len: usize,
18    /// User-Name attribute (RFC 2865 §5.1). Cleartext.
19    pub username: Option<String>,
20    /// Calling-Station-Id (RFC 2865 §5.31) — typically a
21    /// MAC address for wireless, or a calling phone number
22    /// for dial-up. Cleartext.
23    pub calling_station_id: Option<String>,
24    /// Called-Station-Id (RFC 2865 §5.30) — typically NAS
25    /// MAC + SSID for wireless.
26    pub called_station_id: Option<String>,
27    /// NAS-IP-Address (RFC 2865 §5.4) — the
28    /// network-access-server's IP.
29    pub nas_ip_address: Option<Ipv4Addr>,
30    /// Framed-IP-Address (RFC 2865 §5.8) — the IP
31    /// assigned to the user after authentication.
32    pub framed_ip_address: Option<Ipv4Addr>,
33    /// `true` when a User-Password attribute (RFC 2865
34    /// §5.2) was present. The bytes are not surfaced —
35    /// they're MD5(shared_secret || authenticator)-XOR'd
36    /// per the RFC; cleartext recovery requires the
37    /// shared secret which a passive observer doesn't
38    /// have.
39    pub has_user_password: bool,
40}
41
42/// RADIUS message code vocabulary per RFC 2865 §3.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
46#[non_exhaustive]
47pub enum RadiusCodeKind {
48    AccessRequest,
49    AccessAccept,
50    AccessReject,
51    AccessChallenge,
52    AccountingRequest,
53    AccountingResponse,
54    StatusServer,
55    StatusClient,
56    Other(u8),
57}
58
59impl RadiusCodeKind {
60    pub(crate) fn from_raw(code: u8) -> Self {
61        match code {
62            1 => Self::AccessRequest,
63            2 => Self::AccessAccept,
64            3 => Self::AccessReject,
65            4 => Self::AccountingRequest,
66            5 => Self::AccountingResponse,
67            11 => Self::AccessChallenge,
68            12 => Self::StatusServer,
69            13 => Self::StatusClient,
70            n => Self::Other(n),
71        }
72    }
73
74    /// Stable slug for metric labels / logs.
75    pub fn as_str(&self) -> &'static str {
76        match self {
77            Self::AccessRequest => "access_request",
78            Self::AccessAccept => "access_accept",
79            Self::AccessReject => "access_reject",
80            Self::AccessChallenge => "access_challenge",
81            Self::AccountingRequest => "accounting_request",
82            Self::AccountingResponse => "accounting_response",
83            Self::StatusServer => "status_server",
84            Self::StatusClient => "status_client",
85            Self::Other(_) => "other",
86        }
87    }
88}