1use std::time::Duration;
4
5#[cfg(feature = "plugin-health")]
6use pharos_corefile::{Cursor, Error};
7
8#[cfg(feature = "plugin-health")]
9#[derive(Debug)]
10pub struct Health {
11 pub address: String,
12 pub lameduck: Duration,
13}
14
15#[cfg(feature = "plugin-health")]
16pub(crate) fn parse(cursor: &mut Cursor<'_>) -> Result<Health, Error> {
17 let mut address = ":8080".to_owned();
18 let mut lameduck = Duration::ZERO;
19 while cursor.next() {
20 let args = cursor.remaining_args();
21 if args.len() > 1 {
22 return Err(cursor.arg_err());
23 }
24 if let Some(value) = args.first() {
25 if !crate::valid_http_address(value) {
26 return Err(cursor.error("invalid health address"));
27 }
28 address.clone_from(value);
29 }
30 while cursor.next_block() {
31 if cursor.val() != "lameduck" {
32 return Err(cursor.error(format!("unknown property: {}", cursor.val())));
33 }
34 let args = cursor.remaining_args();
35 if args.len() != 1 {
36 return Err(cursor.arg_err());
37 }
38 lameduck = parse_duration(&args[0]).ok_or_else(|| cursor.error("invalid duration"))?;
39 }
40 }
41 Ok(Health { address, lameduck })
42}
43
44pub(crate) fn parse_duration(value: &str) -> Option<Duration> {
45 let (negative, mut rest) = match value.as_bytes().first() {
46 Some(b'-') => (true, &value[1..]),
47 Some(b'+') => (false, &value[1..]),
48 _ => (false, value),
49 };
50 if rest == "0" {
51 return Some(Duration::ZERO);
52 }
53 if rest.is_empty() {
54 return None;
55 }
56 let mut total = 0u128;
57 while !rest.is_empty() {
58 let digits = rest.bytes().take_while(u8::is_ascii_digit).count();
59 let integer = if digits == 0 {
60 0
61 } else {
62 rest[..digits].parse::<u128>().ok()?
63 };
64 rest = &rest[digits..];
65 let mut fraction = 0u128;
66 let mut scale = 1u128;
67 let mut decimals = 0;
68 if let Some(after_dot) = rest.strip_prefix('.') {
69 decimals = after_dot.bytes().take_while(u8::is_ascii_digit).count();
70 for digit in after_dot.bytes().take(decimals) {
71 if scale < 1_000_000_000_000_000_000 {
72 fraction = fraction * 10 + u128::from(digit - b'0');
73 scale *= 10;
74 }
75 }
76 rest = &after_dot[decimals..];
77 }
78 if digits == 0 && decimals == 0 {
79 return None;
80 }
81 let end = rest
82 .find(|ch: char| ch.is_ascii_digit() || ch == '.')
83 .unwrap_or(rest.len());
84 let unit = match &rest[..end] {
85 "ns" => 1,
86 "us" | "µs" | "μs" => 1_000,
87 "ms" => 1_000_000,
88 "s" => 1_000_000_000,
89 "m" => 60_000_000_000,
90 "h" => 3_600_000_000_000,
91 _ => return None,
92 };
93 total = total.checked_add(integer.checked_mul(unit)?)?;
94 total = total.checked_add(fraction * unit / scale)?;
95 let limit = if negative {
96 1u128 << 63
97 } else {
98 i64::MAX as u128
99 };
100 if total > limit {
101 return None;
102 }
103 rest = &rest[end..];
104 }
105 Some(if negative {
106 Duration::ZERO
107 } else {
108 Duration::from_nanos(total as u64)
109 })
110}