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