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_embedded() -> Result<Self, String> {
21 let embedded = include_str!("../dictionaries/dictionary");
22 Self::parse_from_str(embedded)
23 }
24 pub fn parse_from_str(content: &str) -> Result<Self, String> {
25 let mut attributes = HashMap::new();
26 let mut vendors = HashMap::new();
27
28 for (lineno, line) in content.lines().enumerate() {
29 let line = line.trim();
30 if line.is_empty() || line.starts_with('#') {
31 continue;
32 }
33
34 if line.starts_with("ATTRIBUTE") {
35 let parts: Vec<&str> = line.split_whitespace().collect();
36 if parts.len() >= 4 {
37 let name = parts[1].to_string();
38 let code = parts[2].parse::<u32>()
39 .map_err(|e| format!("Invalid code on line {}: {}", lineno + 1, e))?;
40 let data_type = parts[3].to_string();
41
42 attributes.insert(code, RadiusAttributeDef {
43 name,
44 code,
45 vendor: None,
46 data_type,
47 });
48 }
49 } else if line.starts_with("VENDOR") {
50 let parts: Vec<&str> = line.split_whitespace().collect();
51 if parts.len() >= 3 {
52 let name = parts[1].to_string();
53 let id = parts[2].parse::<u32>()
54 .map_err(|e| format!("Invalid vendor ID on line {}: {}", lineno + 1, e))?;
55 vendors.insert(name, id);
56 }
57 }
58 }
59
60 Ok(Dictionary { attributes, vendors })
61 }
62 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self, String> {
63 let mut attributes = HashMap::new();
64 let mut vendors = HashMap::new();
65 let mut visited = HashSet::new();
66
67 fn parse_number(s: &str) -> Result<u32, String> {
68 let s = s.trim();
69 if s.starts_with("0x") || s.starts_with("0X") {
70 u32::from_str_radix(&s[2..], 16)
71 .map_err(|e| format!("Invalid hex '{}': {}", s, e))
72 } else {
73 s.parse::<u32>()
74 .map_err(|e| format!("Invalid number '{}': {}", s, e))
75 }
76 }
77
78 fn parse_file(
79 path: PathBuf,
80 attributes: &mut HashMap<u32, RadiusAttributeDef>,
81 vendors: &mut HashMap<String, u32>,
82 visited: &mut HashSet<PathBuf>,
83 ) -> Result<(), String> {
84 if !visited.insert(path.clone()) {
85 return Ok(()); }
87
88 let content = fs::read_to_string(&path)
89 .map_err(|e| format!("Failed to read {:?}: {}", path, e))?;
90
91 for (lineno, line) in content.lines().enumerate() {
92 let line = line.trim();
93 if line.is_empty() || line.starts_with('#') {
94 continue;
95 }
96
97 if line.starts_with("$INCLUDE") {
98 let parts: Vec<&str> = line.split_whitespace().collect();
99 if parts.len() == 2 {
100 let include_path = path.parent().unwrap().join(parts[1]);
101 parse_file(include_path, attributes, vendors, visited)?;
102 }
103 continue;
104 }
105
106 if line.starts_with("ATTRIBUTE") {
107 let parts: Vec<&str> = line.split_whitespace().collect();
108 if parts.len() >= 4 {
109 if parts[2].contains('.') {
110 continue;
115 }
116 let name = parts[1].to_string();
117 let code = match parse_number(parts[2]) {
118 Ok(code) => code,
119 Err(e) => {
120 eprintln!(
121 "❌ Error: {} in file {:?} at line {}",
122 e, path, lineno + 1
123 );
124 continue;
125 }
126 };
127 let data_type = parts[3].to_string();
128 attributes.insert(
129 code,
130 RadiusAttributeDef {
131 name,
132 code,
133 vendor: None,
134 data_type,
135 },
136 );
137 }
138 continue;
139 }
140
141 if line.starts_with("VENDOR") {
142 let parts: Vec<&str> = line.split_whitespace().collect();
143 if parts.len() >= 3 {
144 if parts[2].contains('.') {
145 continue;
150 }
151 let name = parts[1].to_string();
152 let id = match parse_number(parts[2]) {
153 Ok(id) => id,
154 Err(e) => {
155 eprintln!(
156 "❌ Error: {} in file {:?} at line {}",
157 e, path, lineno + 1
158 );
159 continue;
160 }
161 };
162 vendors.insert(name, id);
163 }
164 continue;
165 }
166
167 }
169
170 Ok(())
171 }
172
173 parse_file(path.as_ref().to_path_buf(), &mut attributes, &mut vendors, &mut visited)?;
174 Ok(Dictionary { attributes, vendors })
175 }
176}