1use crate::errors::XmlError;
13use alloc::format;
14use alloc::string::ToString;
15
16pub const LENGTH_UNLIMITED: i32 = -1;
21
22pub const DURATION_INFINITE_SEC: i32 = 0x7FFF_FFFF;
24
25pub const DURATION_INFINITE_NSEC: u32 = 0x7FFF_FFFF;
27
28pub const DURATION_ZERO_SEC: i32 = 0;
30
31pub const DURATION_ZERO_NSEC: u32 = 0;
33
34pub const TIME_INVALID_SEC: i32 = -1;
39
40pub const TIME_INVALID_NSEC: u32 = 0xFFFF_FFFF;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct Duration {
59 pub sec: i32,
63 pub nanosec: u32,
66}
67
68impl Duration {
69 pub const INFINITE: Self = Self {
71 sec: DURATION_INFINITE_SEC,
72 nanosec: DURATION_INFINITE_NSEC,
73 };
74
75 pub const ZERO: Self = Self {
77 sec: DURATION_ZERO_SEC,
78 nanosec: DURATION_ZERO_NSEC,
79 };
80
81 #[must_use]
83 pub fn is_infinite(&self) -> bool {
84 self.sec == DURATION_INFINITE_SEC && self.nanosec == DURATION_INFINITE_NSEC
85 }
86}
87
88pub fn parse_bool(s: &str) -> Result<bool, XmlError> {
100 let t = s.trim();
101 match t {
102 "1" | "true" | "TRUE" => Ok(true),
103 "0" | "false" | "FALSE" => Ok(false),
104 _ => Err(XmlError::ValueOutOfRange(format!(
105 "boolean expected (true/false/1/0), got `{t}`"
106 ))),
107 }
108}
109
110pub fn parse_long(s: &str) -> Result<i32, XmlError> {
122 let t = s.trim();
123 if t == "LENGTH_UNLIMITED" {
124 return Ok(LENGTH_UNLIMITED);
125 }
126 if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
127 let v = u32::from_str_radix(hex, 16)
128 .map_err(|e| XmlError::ValueOutOfRange(format!("hex long `{t}`: {e}")))?;
129 return Ok(i32::from_ne_bytes(v.to_ne_bytes()));
132 }
133 t.parse::<i32>()
134 .map_err(|e| XmlError::ValueOutOfRange(format!("long `{t}`: {e}")))
135}
136
137pub fn parse_ulong(s: &str) -> Result<u32, XmlError> {
144 let t = s.trim();
145 if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
146 return u32::from_str_radix(hex, 16)
147 .map_err(|e| XmlError::ValueOutOfRange(format!("hex ulong `{t}`: {e}")));
148 }
149 t.parse::<u32>()
150 .map_err(|e| XmlError::ValueOutOfRange(format!("ulong `{t}`: {e}")))
151}
152
153pub fn parse_duration_sec(s: &str) -> Result<i32, XmlError> {
164 let t = s.trim();
165 if t == "DURATION_INFINITY" || t == "DURATION_INFINITE_SEC" {
166 return Ok(DURATION_INFINITE_SEC);
167 }
168 let v = t
169 .parse::<i64>()
170 .map_err(|e| XmlError::ValueOutOfRange(format!("duration_sec `{t}`: {e}")))?;
171 if !(0..=i64::from(DURATION_INFINITE_SEC)).contains(&v) {
172 return Err(XmlError::ValueOutOfRange(format!(
173 "duration_sec `{t}` outside 0..=0x7FFFFFFF"
174 )));
175 }
176 Ok(i32::try_from(v).unwrap_or(0))
178}
179
180pub fn parse_duration_nsec(s: &str) -> Result<u32, XmlError> {
192 let t = s.trim();
193 if t == "DURATION_INFINITY" || t == "DURATION_INFINITE_NSEC" {
194 return Ok(DURATION_INFINITE_NSEC);
195 }
196 let v = t
197 .parse::<u32>()
198 .map_err(|e| XmlError::ValueOutOfRange(format!("duration_nsec `{t}`: {e}")))?;
199 if v > 999_999_999 {
202 return Err(XmlError::ValueOutOfRange(format!(
203 "duration_nsec `{t}` exceeds 999_999_999"
204 )));
205 }
206 Ok(v)
207}
208
209pub fn parse_positive_long_unlimited(s: &str) -> Result<i32, XmlError> {
230 let t = s.trim();
231 if t == "LENGTH_UNLIMITED" {
232 return Ok(LENGTH_UNLIMITED);
233 }
234 let mut chars = t.chars();
237 let first = chars.next().ok_or_else(|| {
238 XmlError::ValueOutOfRange("positiveInteger_UNLIMITED: empty input".to_string())
239 })?;
240 if !('1'..='9').contains(&first) {
241 return Err(XmlError::ValueOutOfRange(format!(
242 "positiveInteger_UNLIMITED `{t}`: first digit must be 1..9 (spec pattern)"
243 )));
244 }
245 if !chars.all(|c| c.is_ascii_digit()) {
246 return Err(XmlError::ValueOutOfRange(format!(
247 "positiveInteger_UNLIMITED `{t}`: only ASCII decimal digits allowed"
248 )));
249 }
250 t.parse::<i32>()
251 .map_err(|e| XmlError::ValueOutOfRange(format!("positiveInteger_UNLIMITED `{t}`: {e}")))
252}
253
254pub fn parse_octet_sequence(s: &str) -> Result<alloc::vec::Vec<u8>, XmlError> {
276 let trimmed = s.trim();
277 if trimmed.is_empty() {
278 return Ok(alloc::vec::Vec::new());
279 }
280 if trimmed.len() > MAX_STRING_BYTES * 16 {
282 return Err(XmlError::LimitExceeded(format!(
283 "octet sequence ({} bytes) exceeds cap",
284 trimmed.len()
285 )));
286 }
287 let mut out = alloc::vec::Vec::new();
288 for token in trimmed.split(',') {
289 let tok = token.trim();
290 if tok.is_empty() {
291 return Err(XmlError::ValueOutOfRange(
292 "octet sequence: empty element (e.g. `1,,2` or trailing comma)".to_string(),
293 ));
294 }
295 let v = if let Some(hex) = tok.strip_prefix("0x").or_else(|| tok.strip_prefix("0X")) {
296 u16::from_str_radix(hex, 16)
297 .map_err(|e| XmlError::ValueOutOfRange(format!("octet hex `{tok}`: {e}")))?
298 } else {
299 tok.parse::<u16>()
300 .map_err(|e| XmlError::ValueOutOfRange(format!("octet decimal `{tok}`: {e}")))?
301 };
302 let byte = u8::try_from(v)
303 .map_err(|_| XmlError::ValueOutOfRange(format!("octet `{tok}` outside 0..=255")))?;
304 out.push(byte);
305 }
306 Ok(out)
307}
308
309pub fn parse_enum<'a>(s: &str, whitelist: &[&'a str]) -> Result<&'a str, XmlError> {
315 let t = s.trim();
316 whitelist
317 .iter()
318 .find(|allowed| **allowed == t)
319 .copied()
320 .ok_or_else(|| XmlError::BadEnum(t.to_string()))
321}
322
323pub const MAX_STRING_BYTES: usize = 64 * 1024;
325
326pub fn parse_string(s: &str) -> Result<&str, XmlError> {
333 if s.len() > MAX_STRING_BYTES {
334 return Err(XmlError::LimitExceeded(format!(
335 "string ({} bytes) exceeds {MAX_STRING_BYTES}",
336 s.len()
337 )));
338 }
339 Ok(s)
340}
341
342#[cfg(test)]
343#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
344mod tests {
345 use super::*;
346
347 #[test]
350 fn bool_true_variants() {
351 assert!(parse_bool("true").expect("true"));
352 assert!(parse_bool("TRUE").expect("TRUE"));
353 assert!(parse_bool("1").expect("1"));
354 assert!(parse_bool(" true ").expect("trim"));
355 }
356
357 #[test]
358 fn bool_false_variants() {
359 assert!(!parse_bool("false").expect("false"));
360 assert!(!parse_bool("FALSE").expect("FALSE"));
361 assert!(!parse_bool("0").expect("0"));
362 }
363
364 #[test]
365 fn bool_invalid() {
366 let err = parse_bool("yes").expect_err("invalid");
367 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
368 }
369
370 #[test]
373 fn long_decimal() {
374 assert_eq!(parse_long("42").expect("dec"), 42);
375 assert_eq!(parse_long("-1").expect("neg"), -1);
376 assert_eq!(parse_long("2147483647").expect("max"), i32::MAX);
377 assert_eq!(parse_long("-2147483648").expect("min"), i32::MIN);
378 }
379
380 #[test]
381 fn long_hex() {
382 assert_eq!(parse_long("0xFF").expect("hex"), 0xFF);
383 assert_eq!(parse_long("0X80").expect("upper-X"), 0x80);
384 assert_eq!(parse_long("0x80000000").expect("hex-msb"), i32::MIN);
386 assert_eq!(parse_long("0x7FFFFFFF").expect("hex-max"), i32::MAX);
387 }
388
389 #[test]
390 fn long_length_unlimited_symbol() {
391 assert_eq!(parse_long("LENGTH_UNLIMITED").expect("symbol"), -1);
392 }
393
394 #[test]
395 fn long_invalid() {
396 assert!(parse_long("not-a-number").is_err());
397 assert!(parse_long("0xZZ").is_err());
398 }
399
400 #[test]
403 fn ulong_decimal_and_hex() {
404 assert_eq!(parse_ulong("0").expect("0"), 0);
405 assert_eq!(parse_ulong("4294967295").expect("max"), u32::MAX);
406 assert_eq!(parse_ulong("0xFFFFFFFF").expect("hex-max"), u32::MAX);
407 }
408
409 #[test]
410 fn ulong_invalid() {
411 assert!(parse_ulong("-1").is_err());
412 assert!(parse_ulong("4294967296").is_err());
413 }
414
415 #[test]
418 fn duration_sec_normal() {
419 assert_eq!(parse_duration_sec("0").expect("zero"), 0);
420 assert_eq!(parse_duration_sec("123").expect("normal"), 123);
421 }
422
423 #[test]
424 fn duration_sec_infinite_symbols() {
425 assert_eq!(
426 parse_duration_sec("DURATION_INFINITY").expect("infinity"),
427 DURATION_INFINITE_SEC
428 );
429 assert_eq!(
430 parse_duration_sec("DURATION_INFINITE_SEC").expect("infinite_sec"),
431 DURATION_INFINITE_SEC
432 );
433 }
434
435 #[test]
436 fn duration_sec_overflow() {
437 let err = parse_duration_sec("2147483648").expect_err("overflow");
439 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
440 }
441
442 #[test]
443 fn duration_nsec_normal() {
444 assert_eq!(
445 parse_duration_nsec("999999999").expect("max-nsec"),
446 999_999_999
447 );
448 assert_eq!(parse_duration_nsec("0").expect("zero"), 0);
449 }
450
451 #[test]
452 fn duration_nsec_infinite() {
453 assert_eq!(
454 parse_duration_nsec("DURATION_INFINITE_NSEC").expect("infinite"),
455 DURATION_INFINITE_NSEC
456 );
457 assert_eq!(
458 parse_duration_nsec("DURATION_INFINITY").expect("infinity"),
459 DURATION_INFINITE_NSEC
460 );
461 }
462
463 #[test]
464 fn duration_nsec_out_of_range() {
465 let err = parse_duration_nsec("1000000000").expect_err("oor");
466 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
467 }
468
469 #[test]
470 fn duration_constants() {
471 assert!(Duration::INFINITE.is_infinite());
472 assert!(!Duration::ZERO.is_infinite());
473 assert_eq!(Duration::ZERO.sec, 0);
474 assert_eq!(Duration::ZERO.nanosec, 0);
475 }
476
477 #[test]
480 fn enum_match() {
481 let wl = ["KEEP_LAST_HISTORY_QOS", "KEEP_ALL_HISTORY_QOS"];
482 assert_eq!(
483 parse_enum("KEEP_LAST_HISTORY_QOS", &wl).expect("match"),
484 "KEEP_LAST_HISTORY_QOS"
485 );
486 }
487
488 #[test]
489 fn enum_no_match() {
490 let wl = ["A", "B"];
491 let err = parse_enum("C", &wl).expect_err("no-match");
492 assert!(matches!(err, XmlError::BadEnum(_)));
493 }
494
495 #[test]
498 fn string_short_passes() {
499 assert_eq!(parse_string("hello").expect("ok"), "hello");
500 }
501
502 #[test]
503 fn string_too_long_rejected() {
504 let big = "x".repeat(MAX_STRING_BYTES + 1);
505 let err = parse_string(&big).expect_err("too-big");
506 assert!(matches!(err, XmlError::LimitExceeded(_)));
507 }
508
509 #[test]
512 fn spec_constants() {
513 assert_eq!(LENGTH_UNLIMITED, -1);
514 assert_eq!(DURATION_INFINITE_SEC, 0x7FFF_FFFF);
515 assert_eq!(DURATION_INFINITE_NSEC, 0x7FFF_FFFF);
516 }
517
518 #[test]
519 fn time_invalid_constants() {
520 assert_eq!(TIME_INVALID_SEC, -1);
522 assert_eq!(TIME_INVALID_NSEC, 0xFFFF_FFFF);
523 assert_ne!(TIME_INVALID_SEC, DURATION_INFINITE_SEC);
526 assert_ne!(TIME_INVALID_NSEC, DURATION_INFINITE_NSEC);
527 assert_ne!(TIME_INVALID_SEC, DURATION_ZERO_SEC);
528 }
529
530 #[test]
533 fn positive_unlimited_symbol_passes() {
534 assert_eq!(
535 parse_positive_long_unlimited("LENGTH_UNLIMITED").expect("symbol"),
536 LENGTH_UNLIMITED
537 );
538 }
539
540 #[test]
541 fn positive_unlimited_one_to_max_passes() {
542 assert_eq!(parse_positive_long_unlimited("1").expect("1"), 1);
543 assert_eq!(parse_positive_long_unlimited("42").expect("42"), 42);
544 assert_eq!(
545 parse_positive_long_unlimited("2147483647").expect("max"),
546 i32::MAX
547 );
548 }
549
550 #[test]
551 fn positive_unlimited_zero_rejected() {
552 let err = parse_positive_long_unlimited("0").expect_err("zero");
554 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
555 }
556
557 #[test]
558 fn positive_unlimited_negative_rejected() {
559 let err = parse_positive_long_unlimited("-1").expect_err("negative");
560 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
561 }
562
563 #[test]
564 fn positive_unlimited_leading_zero_rejected() {
565 let err = parse_positive_long_unlimited("01").expect_err("leading-zero");
567 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
568 }
569
570 #[test]
571 fn positive_unlimited_hex_rejected() {
572 let err = parse_positive_long_unlimited("0x10").expect_err("hex");
574 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
575 }
576
577 #[test]
578 fn positive_unlimited_overflow_rejected() {
579 let err = parse_positive_long_unlimited("2147483648").expect_err("overflow");
580 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
581 }
582
583 #[test]
584 fn positive_unlimited_empty_rejected() {
585 let err = parse_positive_long_unlimited("").expect_err("empty");
586 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
587 }
588
589 #[test]
592 fn octet_sequence_decimal_basic() {
593 let bytes = parse_octet_sequence("0,1,2,255").expect("ok");
594 assert_eq!(bytes, alloc::vec![0, 1, 2, 255]);
595 }
596
597 #[test]
598 fn octet_sequence_hex_basic() {
599 let bytes = parse_octet_sequence("0x00,0xFF,0x42").expect("ok");
600 assert_eq!(bytes, alloc::vec![0x00, 0xFF, 0x42]);
601 }
602
603 #[test]
604 fn octet_sequence_mixed_decimal_and_hex() {
605 let bytes = parse_octet_sequence("1,0x02,3,0x04").expect("mixed");
607 assert_eq!(bytes, alloc::vec![1, 2, 3, 4]);
608 }
609
610 #[test]
611 fn octet_sequence_whitespace_around_commas() {
612 let bytes = parse_octet_sequence(" 1 , 2 , 3 ").expect("trim");
613 assert_eq!(bytes, alloc::vec![1, 2, 3]);
614 }
615
616 #[test]
617 fn octet_sequence_empty_string_returns_empty_vec() {
618 let bytes = parse_octet_sequence("").expect("empty");
619 assert!(bytes.is_empty());
620 }
621
622 #[test]
623 fn octet_sequence_value_above_255_rejected() {
624 let err = parse_octet_sequence("0,256").expect_err("over");
625 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
626 }
627
628 #[test]
629 fn octet_sequence_negative_rejected() {
630 let err = parse_octet_sequence("0,-1").expect_err("negative");
631 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
632 }
633
634 #[test]
635 fn octet_sequence_trailing_comma_rejected() {
636 let err = parse_octet_sequence("1,2,").expect_err("trailing");
637 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
638 }
639
640 #[test]
641 fn octet_sequence_double_comma_rejected() {
642 let err = parse_octet_sequence("1,,2").expect_err("double");
643 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
644 }
645
646 #[test]
647 fn octet_sequence_non_numeric_token_rejected() {
648 let err = parse_octet_sequence("1,abc,3").expect_err("non-numeric");
649 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
650 }
651
652 #[test]
653 fn octet_sequence_hex_above_255_rejected() {
654 let err = parse_octet_sequence("0x100").expect_err("hex over");
655 assert!(matches!(err, XmlError::ValueOutOfRange(_)));
656 }
657}