Skip to main content

wireforge_app/radius/
parser.rs

1use core::net::Ipv4Addr;
2use super::types::RadiusCode;
3
4pub const RADIUS_MIN_LEN: usize = 20;
5
6#[derive(Debug, Clone)]
7pub struct RadiusPacket<'a> { buf: &'a [u8] }
8impl<'a> RadiusPacket<'a> {
9    pub fn new(buf: &'a [u8]) -> Option<Self> { if buf.len()<RADIUS_MIN_LEN{return None;} Some(Self{buf}) }
10    pub fn code(&self)->RadiusCode { RadiusCode::from(self.buf[0]) }
11    pub fn identifier(&self)->u8 { self.buf[1] }
12    pub fn length(&self)->u16 { u16::from_be_bytes([self.buf[2],self.buf[3]]) }
13    /// 16-byte authenticator at offset 4. `new()` guarantees buf.len() >= 20.
14    pub fn authenticator(&self) -> &[u8; 16] { self.buf[4..20].try_into().unwrap() }
15    pub fn attributes(&self)->RadiusAttributeIter<'a> { RadiusAttributeIter{data:&self.buf[20..]} }
16}
17
18#[derive(Debug,Clone)]
19pub enum RadiusAttribute { UserName(String),UserPassword(Vec<u8>),NasIpAddress(Ipv4Addr),NasPort(u32),ServiceType(u32),FramedProtocol(u32),CallingStationId(String),NasIdentifier(String),AcctStatusType(u32),AcctSessionId(String),Unknown{type_:u8,data:Vec<u8>} }
20
21pub struct RadiusAttributeIter<'a> { data: &'a [u8] }
22impl<'a> Iterator for RadiusAttributeIter<'a> {
23    type Item = RadiusAttribute;
24    fn next(&mut self)->Option<Self::Item> {
25        if self.data.len()<2{return None;}
26        let t=self.data[0];let len=self.data[1] as usize;
27        if len<2||self.data.len()<len{return None;}
28        let val=&self.data[2..len];self.data=&self.data[len..];
29        Some(match t{
30            1=>RadiusAttribute::UserName(String::from_utf8_lossy(val).into_owned()),
31            2=>RadiusAttribute::UserPassword(val.to_vec()),
32            4=>RadiusAttribute::NasIpAddress(Ipv4Addr::new(val[0],val[1],val[2],val[3])),
33            5=>RadiusAttribute::NasPort(u32::from_be_bytes([val[0],val[1],val[2],val[3]])),
34            6=>RadiusAttribute::ServiceType(u32::from_be_bytes([val[0],val[1],val[2],val[3]])),
35            7=>RadiusAttribute::FramedProtocol(u32::from_be_bytes([val[0],val[1],val[2],val[3]])),
36            31=>RadiusAttribute::CallingStationId(String::from_utf8_lossy(val).into_owned()),
37            32=>RadiusAttribute::NasIdentifier(String::from_utf8_lossy(val).into_owned()),
38            40=>RadiusAttribute::AcctStatusType(u32::from_be_bytes([val[0],val[1],val[2],val[3]])),
39            44=>RadiusAttribute::AcctSessionId(String::from_utf8_lossy(val).into_owned()),
40            _=>RadiusAttribute::Unknown{type_:t,data:val.to_vec()},
41        })
42    }
43}
44
45#[cfg(test)]mod tests{use super::*;
46    fn build_pkt(code:u8,id:u8,attrs:&[u8])->Vec<u8>{
47        let mut v=vec![code,id,0,20];v.extend_from_slice(&[0u8;16]);v.extend_from_slice(attrs);
48        let l=v.len()as u16;v[2]=(l>>8)as u8;v[3]=l as u8;v
49    }
50    #[test]fn parse_access_request(){
51        let attrs=[1u8,8,b't',b'e',b's',b't',b'e',b'r',2,4,0xAA,0xBB,0xCC,0xDD];
52        let pkt=build_pkt(1,0,&attrs);
53        let p=RadiusPacket::new(&pkt).unwrap();
54        assert_eq!(p.code(),RadiusCode::AccessRequest);
55        let avps:Vec<_>=p.attributes().collect();
56        assert_eq!(avps.len(),2);
57        match &avps[0]{RadiusAttribute::UserName(n)=>assert_eq!(n,"tester"),_=>panic!()}
58    }
59    #[test]fn parse_access_accept(){
60        let attrs=[6u8,6,0,0,0,2,7,6,0,0,0,1];
61        let b=build_pkt(2,1,&attrs);let p=RadiusPacket::new(&b).unwrap();
62        assert_eq!(p.code(),RadiusCode::AccessAccept);
63    }
64    #[test]fn parse_accounting_request(){
65        let attrs=[40u8,6,0,0,0,1,44,5,b'a',b'b',b'c'];
66        let b=build_pkt(4,2,&attrs);let p=RadiusPacket::new(&b).unwrap();
67        assert_eq!(p.code(),RadiusCode::AccountingRequest);
68    }
69    #[test]fn parse_too_short(){assert!(RadiusPacket::new(&[]).is_none());assert!(RadiusPacket::new(&[0u8;19]).is_none());}
70}