pharos-plugins 0.1.0

CoreDNS-compatible Plugins for the Pharos DNS server
Documentation
//! Health endpoint configuration.

use std::time::Duration;

#[cfg(feature = "plugin-health")]
use pharos_corefile::{Cursor, Error};

#[cfg(feature = "plugin-health")]
#[derive(Debug)]
pub struct Health {
    pub address: String,
    pub lameduck: Duration,
}

#[cfg(feature = "plugin-health")]
pub(crate) fn parse(cursor: &mut Cursor<'_>) -> Result<Health, Error> {
    let mut address = ":8080".to_owned();
    let mut lameduck = Duration::ZERO;
    while cursor.next() {
        let args = cursor.remaining_args();
        if args.len() > 1 {
            return Err(cursor.arg_err());
        }
        if let Some(value) = args.first() {
            if !crate::valid_http_address(value) {
                return Err(cursor.error("invalid health address"));
            }
            address.clone_from(value);
        }
        while cursor.next_block() {
            if cursor.val() != "lameduck" {
                return Err(cursor.error(format!("unknown property: {}", cursor.val())));
            }
            let args = cursor.remaining_args();
            if args.len() != 1 {
                return Err(cursor.arg_err());
            }
            lameduck = parse_duration(&args[0]).ok_or_else(|| cursor.error("invalid duration"))?;
        }
    }
    Ok(Health { address, lameduck })
}

pub(crate) fn parse_duration(value: &str) -> Option<Duration> {
    let (negative, mut rest) = match value.as_bytes().first() {
        Some(b'-') => (true, &value[1..]),
        Some(b'+') => (false, &value[1..]),
        _ => (false, value),
    };
    if rest == "0" {
        return Some(Duration::ZERO);
    }
    if rest.is_empty() {
        return None;
    }
    let mut total = 0u128;
    while !rest.is_empty() {
        let digits = rest.bytes().take_while(u8::is_ascii_digit).count();
        let integer = if digits == 0 {
            0
        } else {
            rest[..digits].parse::<u128>().ok()?
        };
        rest = &rest[digits..];
        let mut fraction = 0u128;
        let mut scale = 1u128;
        let mut decimals = 0;
        if let Some(after_dot) = rest.strip_prefix('.') {
            decimals = after_dot.bytes().take_while(u8::is_ascii_digit).count();
            for digit in after_dot.bytes().take(decimals) {
                if scale < 1_000_000_000_000_000_000 {
                    fraction = fraction * 10 + u128::from(digit - b'0');
                    scale *= 10;
                }
            }
            rest = &after_dot[decimals..];
        }
        if digits == 0 && decimals == 0 {
            return None;
        }
        let end = rest
            .find(|ch: char| ch.is_ascii_digit() || ch == '.')
            .unwrap_or(rest.len());
        let unit = match &rest[..end] {
            "ns" => 1,
            "us" | "µs" | "μs" => 1_000,
            "ms" => 1_000_000,
            "s" => 1_000_000_000,
            "m" => 60_000_000_000,
            "h" => 3_600_000_000_000,
            _ => return None,
        };
        total = total.checked_add(integer.checked_mul(unit)?)?;
        total = total.checked_add(fraction * unit / scale)?;
        let limit = if negative {
            1u128 << 63
        } else {
            i64::MAX as u128
        };
        if total > limit {
            return None;
        }
        rest = &rest[end..];
    }
    Some(if negative {
        Duration::ZERO
    } else {
        Duration::from_nanos(total as u64)
    })
}