aqara 0.1.1

Aqara SDK for Rust.
Documentation
use std::cmp;
use std::time::{Duration, SystemTime};

use http::HeaderMap;

use crate::types::RetryConfig;
use rand::Rng;

pub(crate) fn compute_backoff_with_jitter(attempt: u32, retry: RetryConfig) -> Duration {
    let base_ms = duration_to_millis_u64(retry.base_delay);
    if base_ms == 0 {
        return Duration::from_millis(0);
    }

    let exp = cmp::min(attempt.saturating_sub(1), 30);
    let factor = 1_u64 << exp;
    let exp_ms = base_ms.saturating_mul(factor);

    let capped = cmp::min(exp_ms, duration_to_millis_u64(retry.max_delay));
    if capped == 0 {
        return Duration::from_millis(0);
    }

    let mut rng = rand::rng();
    let jitter_ms = rng.random_range(0..=capped);
    Duration::from_millis(jitter_ms)
}

pub(crate) fn parse_retry_after(headers: &HeaderMap) -> Option<Duration> {
    let value = headers.get(http::header::RETRY_AFTER)?;
    let value = value.to_str().ok()?.trim();

    if let Ok(seconds) = value.parse::<u64>() {
        return Some(Duration::from_secs(seconds));
    }

    let retry_at = httpdate::parse_http_date(value).ok()?;
    let now = SystemTime::now();
    match retry_at.duration_since(now) {
        Ok(d) => Some(d),
        Err(_) => Some(Duration::from_secs(0)),
    }
}

fn duration_to_millis_u64(d: Duration) -> u64 {
    cmp::min(d.as_millis(), u128::from(u64::MAX)) as u64
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn retry_after_seconds_parses() {
        let mut headers = HeaderMap::new();
        headers.insert(
            http::header::RETRY_AFTER,
            http::HeaderValue::from_static("5"),
        );
        assert_eq!(parse_retry_after(&headers), Some(Duration::from_secs(5)));
    }

    #[test]
    fn backoff_respects_max_delay() {
        let cfg = RetryConfig {
            max_retries: 3,
            base_delay: Duration::from_millis(100),
            max_delay: Duration::from_millis(150),
        };
        let delay = compute_backoff_with_jitter(10, cfg);
        assert!(delay <= cfg.max_delay);
    }
}