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 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}