radius_server/
dictionary.rs1use std::collections::{HashMap, HashSet};
2use std::fs;
3use std::path::{Path, PathBuf};
4
5#[derive(Debug, Clone)]
6pub struct RadiusAttributeDef {
7 pub name: String,
8 pub code: u32,
9 pub vendor: Option<u32>,
10 pub data_type: String,
11}
12
13#[derive(Debug)]
14pub struct Dictionary {
15 pub attributes: HashMap<u32, RadiusAttributeDef>,
16 pub vendors: HashMap<String, u32>,
17}
18
19impl Dictionary {
20 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, String> {
21 let mut attributes = HashMap::new();
22 let mut vendors = HashMap::new();
23 let mut visited = HashSet::new();
24
25 fn parse_number(s: &str) -> Result<u32, String> {
26 let s = s.trim();
27 if s.starts_with("0x") || s.starts_with("0X") {
28 u32::from_str_radix(&s[2..], 16)
29 .map_err(|e| format!("Invalid hex '{}': {}", s, e))
30 } else {
31 s.parse::<u32>()
32 .map_err(|e| format!("Invalid number '{}': {}", s, e))
33 }
34 }
35
36 fn parse_file(
37 path: PathBuf,
38 attributes: &mut HashMap<u32, RadiusAttributeDef>,
39 vendors: &mut HashMap<String, u32>,
40 visited: &mut HashSet<PathBuf>,
41 ) -> Result<(), String> {
42 if !visited.insert(path.clone()) {
43 return Ok(()); }
45
46 let content = fs::read_to_string(&path)
47 .map_err(|e| format!("Failed to read {:?}: {}", path, e))?;
48
49 for (lineno, line) in content.lines().enumerate() {
50 let line = line.trim();
51 if line.is_empty() || line.starts_with('#') {
52 continue;
53 }
54
55 if line.starts_with("$INCLUDE") {
56 let parts: Vec<&str> = line.split_whitespace().collect();
57 if parts.len() == 2 {
58 let include_path = path.parent().unwrap().join(parts[1]);
59 parse_file(include_path, attributes, vendors, visited)?;
60 }
61 continue;
62 }
63
64 if line.starts_with("ATTRIBUTE") {
65 let parts: Vec<&str> = line.split_whitespace().collect();
66 if parts.len() >= 4 {
67 if parts[2].contains('.') {
68 continue;
73 }
74 let name = parts[1].to_string();
75 let code = match parse_number(parts[2]) {
76 Ok(code) => code,
77 Err(e) => {
78 eprintln!(
79 "❌ Error: {} in file {:?} at line {}",
80 e, path, lineno + 1
81 );
82 continue;
83 }
84 };
85 let data_type = parts[3].to_string();
86 attributes.insert(
87 code,
88 RadiusAttributeDef {
89 name,
90 code,
91 vendor: None,
92 data_type,
93 },
94 );
95 }
96 continue;
97 }
98
99 if line.starts_with("VENDOR") {
100 let parts: Vec<&str> = line.split_whitespace().collect();
101 if parts.len() >= 3 {
102 if parts[2].contains('.') {
103 continue;
108 }
109 let name = parts[1].to_string();
110 let id = match parse_number(parts[2]) {
111 Ok(id) => id,
112 Err(e) => {
113 eprintln!(
114 "❌ Error: {} in file {:?} at line {}",
115 e, path, lineno + 1
116 );
117 continue;
118 }
119 };
120 vendors.insert(name, id);
121 }
122 continue;
123 }
124
125 }
127
128 Ok(())
129 }
130
131 parse_file(path.as_ref().to_path_buf(), &mut attributes, &mut vendors, &mut visited)?;
132 Ok(Dictionary { attributes, vendors })
133 }
134}