abol_core/
lib.rs

1pub mod attribute;
2pub mod packet;
3use crate::packet::{Packet, PacketParseError};
4use core::fmt;
5use std::{
6    convert::TryFrom,
7    fmt::{Display, Formatter},
8    net::IpAddr,
9};
10/// Represents a RADIUS request received by the server.
11///
12/// Contains metadata about the client connection and the parsed RADIUS packet.
13pub struct Request {
14    /// Local socket address (IP:port) of the server that received the request.
15    pub local_addr: String,
16    /// Remote socket address (IP:port) of the client sending the request.
17    pub remote_addr: String,
18    /// Parsed RADIUS packet for this request.
19    pub packet: Packet,
20}
21/// Represents a RADIUS response to be sent back to the client.
22///
23/// Wraps a `Packet` that will be transmitted as the response.
24pub struct Response {
25    /// RADIUS packet that will be sent as a response.
26    pub packet: Packet,
27}
28
29/// RADIUS packet codes as defined in [RFC 2865](https://datatracker.ietf.org/doc/html/rfc2865).
30///
31/// Each variant corresponds to the `Code` field in the RADIUS header, indicating
32/// the type of request or response.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Code {
35    /// Access-Request (1): Used by a client to request authentication.
36    AccessRequest = 1,
37    /// Access-Accept (2): Server response granting access.
38    AccessAccept = 2,
39    /// Access-Reject (3): Server response denying access.
40    AccessReject = 3,
41    /// Accounting-Request (4): Used by a client to send accounting data.
42    AccountingRequest = 4,
43    /// Accounting-Response (5): Server acknowledgment of an Accounting-Request.
44    AccountingResponse = 5,
45    /// Access-Challenge (11): Server requests additional information.
46    AccessChallenge = 11,
47    /// Status-Server (12): Experimental code for server status.
48    StatusServer = 12,
49    /// Status-Client (13): Experimental code for client status.
50    StatusClient = 13,
51    /// Disconnect-Request (40): Used in CoA to terminate a session.
52    DisconnectRequest = 40,
53    /// Disconnect-ACK (41): Acknowledgment of Disconnect-Request.
54    DisconnectAck = 41,
55    /// Disconnect-NAK (42): Negative acknowledgment of Disconnect-Request.
56    DisconnectNak = 42,
57    /// CoA-Request (43): Change-of-Authorization request.
58    CoARequest = 43,
59    /// CoA-ACK (44): Acknowledgment of CoA request.
60    CoAACK = 44,
61    /// CoA-NAK (45): Negative acknowledgment of CoA request.
62    CoANAK = 45,
63    /// Reserved (255): Reserved/experimental codes not standardized.
64    Reserved = 255,
65}
66
67impl Display for Code {
68    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69        let text = match self {
70            Code::AccessRequest => "Access-Request",
71            Code::AccessAccept => "Access-Accept",
72            Code::AccessReject => "Access-Reject",
73            Code::AccountingRequest => "Accounting-Request",
74            Code::AccountingResponse => "Accounting-Response",
75            Code::AccessChallenge => "Access-Challenge",
76            Code::StatusServer => "Status-Server",
77            Code::StatusClient => "Status-Client",
78            Code::DisconnectRequest => "Disconnect-Request",
79            Code::DisconnectAck => "Disconnect-ACK",
80            Code::DisconnectNak => "Disconnect-NAK",
81            Code::CoARequest => "CoA-Request",
82            Code::CoAACK => "CoA-ACK",
83            Code::CoANAK => "CoA-NAK",
84            Code::Reserved => "Reserved",
85        };
86
87        write!(f, "{}", text)
88    }
89}
90
91impl TryFrom<u8> for Code {
92    type Error = PacketParseError;
93    fn try_from(value: u8) -> Result<Self, Self::Error> {
94        match value {
95            1 => Ok(Code::AccessRequest),
96            2 => Ok(Code::AccessAccept),
97            3 => Ok(Code::AccessReject),
98            4 => Ok(Code::AccountingRequest),
99            5 => Ok(Code::AccountingResponse),
100            11 => Ok(Code::AccessChallenge),
101            12 => Ok(Code::StatusServer),
102            13 => Ok(Code::StatusClient),
103            40 => Ok(Code::DisconnectRequest),
104            41 => Ok(Code::DisconnectAck),
105            42 => Ok(Code::DisconnectNak),
106            43 => Ok(Code::CoARequest),
107            44 => Ok(Code::CoAACK),
108            45 => Ok(Code::CoANAK),
109            255 => Ok(Code::Reserved),
110            _ => Err(PacketParseError::InvalidLength(value as usize)),
111        }
112    }
113}
114/// Represents an IP network prefix using Classless Inter-Domain Routing (CIDR) notation.
115///
116/// This struct is used to define a range of IP addresses (a subnet) by combining
117/// a base IP address with a routing prefix length.
118///
119/// # Example
120/// ```rust
121/// # use std::error::Error;
122/// # use std::net::IpAddr;
123/// # use abol_core::Cidr;
124/// # fn main() -> Result<(), Box<dyn Error>> {
125/// let network = Cidr {
126///     ip: "192.168.1.0".parse()?,
127///     prefix: 24,
128/// };
129/// # Ok(())
130/// # }
131/// ```
132#[derive(Debug, Clone, PartialEq, Eq, Hash)]
133pub struct Cidr {
134    /// The base IP address of the network.
135    pub ip: IpAddr,
136
137    /// The routing prefix length (the number of leading bits in the subnet mask).
138    ///
139    /// For IPv4, this should be in the range 0..=32.
140    /// For IPv6, this should be in the range 0..=128.
141    pub prefix: u8,
142}
143
144impl Cidr {
145    pub fn contains(&self, other: &IpAddr) -> bool {
146        match (self.ip, other) {
147            (IpAddr::V4(net), IpAddr::V4(ip)) => {
148                let mask = u32::MAX.checked_shl(32 - self.prefix as u32).unwrap_or(0);
149                u32::from(net) & mask == u32::from(*ip) & mask
150            }
151            (IpAddr::V6(net), IpAddr::V6(ip)) => {
152                let mask = u128::MAX.checked_shl(128 - self.prefix as u32).unwrap_or(0);
153                u128::from(net) & mask == u128::from(*ip) & mask
154            }
155            _ => false,
156        }
157    }
158}