container_device_interface/
parser.rs1use anyhow::{anyhow, Result};
2
3#[allow(dead_code)]
16pub(crate) fn qualified_name(vendor: &str, class: &str, name: &str) -> String {
17 format!("{}/{}={}", vendor, class, name)
18}
19
20#[allow(dead_code)]
22pub(crate) fn is_qualified_name(name: &str) -> bool {
23 match parse_qualified_name(name) {
24 Ok(_) => {
25 println!("{} is a qualified name", name);
26 true
27 }
28 Err(e) => {
29 println!("{} is not a qualified name, {}", name, e);
30 false
31 }
32 }
33}
34
35pub(crate) fn parse_qualified_name(
41 device: &str,
42) -> Result<(String, String, String), anyhow::Error> {
43 let (vendor, class, name) = parse_device(device);
44 if vendor.is_empty() {
45 return Err(anyhow!("unqualified device {}, missing vendor", device));
46 }
47 if class.is_empty() {
48 return Err(anyhow!("unqualified device {}, missing class", device));
49 }
50 if name.is_empty() {
51 return Err(anyhow!("unqualified device {}, missing name", device));
52 }
53 if let Err(e) = validate_vendor_name(vendor) {
54 return Err(anyhow!("invalid vendor {}: {}", device, e));
55 }
56 if let Err(e) = validate_class_name(class) {
57 return Err(anyhow!("invalid class {}: {}", device, e));
58 }
59 if let Err(e) = validate_device_name(name) {
60 return Err(anyhow!("invalid device {}: {}", device, e));
61 }
62 Ok((vendor.to_string(), class.to_string(), name.to_string()))
63}
64
65pub(crate) fn parse_device(device: &str) -> (&str, &str, &str) {
70 if device.is_empty() || device.starts_with('/') {
71 return ("", "", device);
72 }
73
74 let parts: Vec<&str> = device.split('=').collect();
75 if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
76 return ("", "", device);
77 }
78
79 let name = parts[1];
80 let (vendor, class) = parse_qualifier(parts[0]);
81 if vendor.is_empty() {
82 return ("", "", device);
83 }
84 (vendor, class, name)
85}
86
87pub(crate) fn parse_qualifier(kind: &str) -> (&str, &str) {
95 let parts: Vec<&str> = kind.split('/').collect();
96 if parts.len() != 2 || parts[0].is_empty() || parts[1].is_empty() {
97 return ("", kind);
98 }
99 (parts[0], parts[1])
100}
101
102pub(crate) fn validate_vendor_name(vendor: &str) -> Result<()> {
108 if let Err(e) = validate_vendor_or_class_name(vendor) {
109 return Err(anyhow!("invalid vendor. {}", e));
110 }
111
112 Ok(())
113}
114
115pub(crate) fn validate_class_name(class: &str) -> Result<()> {
121 if let Err(e) = validate_vendor_or_class_name(class) {
122 return Err(anyhow!("invalid class. {}", e));
123 }
124
125 Ok(())
126}
127
128pub(crate) fn validate_vendor_or_class_name(name: &str) -> Result<()> {
134 if name.is_empty() {
135 return Err(anyhow!("empty name"));
136 }
137 if !name.chars().next().unwrap_or_default().is_alphabetic() {
138 return Err(anyhow!("name should start with a letter"));
139 }
140 if let Some(c) = name
141 .chars()
142 .find(|&c| !c.is_alphanumeric() && c != '-' && c != '_' && c != '.')
143 {
144 return Err(anyhow!("invalid character '{}' in name {}", c, name));
145 }
146 Ok(())
147}
148
149pub(crate) fn validate_device_name(name: &str) -> Result<()> {
155 if name.is_empty() {
156 return Err(anyhow!("empty name"));
157 }
158 if let Some(c) = name
159 .chars()
160 .find(|&c| !c.is_alphanumeric() && c != '-' && c != '_' && c != '.' && c != ':')
161 {
162 return Err(anyhow!("invalid character '{}' in device name {}", c, name));
163 }
164 Ok(())
165}
166
167#[cfg(test)]
168mod tests {
169
170 use crate::parser;
171
172 #[test]
173
174 fn qualified_name() {
175 let vendor = "nvidia.com";
176 let class = "gpu";
177 let name = "0";
178 let device = parser::qualified_name(vendor, class, name);
179 assert_eq!(device, "nvidia.com/gpu=0");
180 assert!(parser::is_qualified_name(&device));
181 }
182
183 #[test]
184 fn parse_qualified_name() {
185 let device = "nvidia.com/gpu=0";
186 match parser::parse_qualified_name(device) {
187 Ok((vendor, class, name)) => {
188 assert_eq!(vendor, "nvidia.com");
189 assert_eq!(class, "gpu");
190 assert_eq!(name, "0");
191 }
192 Err(e) => {
193 println!("error: {}", e);
194 }
195 }
196 }
197
198 #[test]
199 fn parse_device() {
200 let device = "nvidia.com/gpu=0";
201 let (vendor, class, name) = parser::parse_device(device);
202 assert_eq!(vendor, "nvidia.com");
203 assert_eq!(class, "gpu");
204 assert_eq!(name, "0");
205 }
206
207 #[test]
208 fn parse_qualifier() {
209 let qualifier = "nvidia.com/gpu";
210 let (vendor, class) = parser::parse_qualifier(qualifier);
211 assert_eq!(vendor, "nvidia.com");
212 assert_eq!(class, "gpu");
213 }
214
215 #[test]
216 fn validate_vendor_name() {
217 let vendor = "nvidia.com";
218 assert!(parser::validate_vendor_name(vendor).is_ok());
219
220 let vendor = "nvi((dia";
221 assert!(parser::validate_vendor_name(vendor).is_err());
222 }
223 #[test]
224 fn validate_class_name() {
225 let class = "gpu";
226 assert!(parser::validate_class_name(class).is_ok());
227
228 let class = "g(pu";
229 assert!(parser::validate_class_name(class).is_err());
230 }
231
232 #[test]
233 fn validate_device_name() {
234 let name = "0";
235 assert!(parser::validate_device_name(name).is_ok());
236
237 let name = "0(";
238 assert!(parser::validate_device_name(name).is_err());
239 }
240 #[test]
241 fn validate_vendor_or_class_name() {
242 let name = "nvidia.com";
243 assert!(parser::validate_vendor_or_class_name(name).is_ok());
244
245 let name = "nvi((dia.com";
246 assert!(parser::validate_vendor_or_class_name(name).is_err());
247 }
248}