cloud-sdk 0.46.0

no_std-first provider-neutral cloud SDK foundations.
Documentation
use super::{HttpDate, RetryAfterError, WallClockTimestamp};

pub(super) fn parse_http_date(
    value: &[u8],
    now: WallClockTimestamp,
) -> Result<HttpDate, RetryAfterError> {
    let parts = if value.len() == 29 && value.get(3) == Some(&b',') {
        parse_imf_fixdate(value)?
    } else if value.contains(&b',') {
        parse_rfc850(value, now)?
    } else {
        parse_asctime(value)?
    };
    parts.into_http_date()
}

#[derive(Clone, Copy)]
struct DateParts {
    weekday: u8,
    year: i64,
    month: u8,
    day: u8,
    hour: u8,
    minute: u8,
    second: u8,
}

impl DateParts {
    fn into_http_date(self) -> Result<HttpDate, RetryAfterError> {
        if self.year < 1601
            || self.month == 0
            || self.month > 12
            || self.day == 0
            || self.day > days_in_month(self.year, self.month)
            || self.hour > 23
            || self.minute > 59
            || self.second > 60
        {
            return Err(RetryAfterError::InvalidDate);
        }
        let days = days_from_civil(self.year, self.month, self.day)?;
        let weekday_source = days.checked_add(4).ok_or(RetryAfterError::Overflow)?;
        let weekday =
            u8::try_from(weekday_source.rem_euclid(7)).map_err(|_| RetryAfterError::Overflow)?;
        if weekday != self.weekday {
            return Err(RetryAfterError::WeekdayMismatch);
        }
        let seconds = days
            .checked_mul(86_400)
            .and_then(|value| {
                i64::from(self.hour)
                    .checked_mul(3_600)
                    .and_then(|part| value.checked_add(part))
            })
            .and_then(|value| {
                i64::from(self.minute)
                    .checked_mul(60)
                    .and_then(|part| value.checked_add(part))
            })
            .and_then(|value| value.checked_add(i64::from(self.second)))
            .ok_or(RetryAfterError::Overflow)?;
        Ok(HttpDate::new(seconds))
    }
}

fn parse_imf_fixdate(value: &[u8]) -> Result<DateParts, RetryAfterError> {
    if value.get(4) != Some(&b' ')
        || value.get(7) != Some(&b' ')
        || value.get(11) != Some(&b' ')
        || value.get(16) != Some(&b' ')
        || value.get(19) != Some(&b':')
        || value.get(22) != Some(&b':')
        || value.get(25..29) != Some(b" GMT")
    {
        return Err(RetryAfterError::InvalidSyntax);
    }
    Ok(DateParts {
        weekday: short_weekday(slice(value, 0, 3)?)?,
        day: number_u8(slice(value, 5, 7)?)?,
        month: month(slice(value, 8, 11)?)?,
        year: i64::from(number_u16(slice(value, 12, 16)?)?),
        hour: number_u8(slice(value, 17, 19)?)?,
        minute: number_u8(slice(value, 20, 22)?)?,
        second: number_u8(slice(value, 23, 25)?)?,
    })
}

fn parse_rfc850(value: &[u8], now: WallClockTimestamp) -> Result<DateParts, RetryAfterError> {
    let comma = value
        .iter()
        .position(|byte| *byte == b',')
        .ok_or(RetryAfterError::InvalidSyntax)?;
    let weekday = long_weekday(slice(value, 0, comma)?)?;
    let rest = value
        .get(comma.checked_add(2).ok_or(RetryAfterError::Overflow)?..)
        .ok_or(RetryAfterError::InvalidSyntax)?;
    if rest.len() != 22
        || rest.get(2) != Some(&b'-')
        || rest.get(6) != Some(&b'-')
        || rest.get(9) != Some(&b' ')
        || rest.get(12) != Some(&b':')
        || rest.get(15) != Some(&b':')
        || rest.get(18..22) != Some(b" GMT")
    {
        return Err(RetryAfterError::InvalidSyntax);
    }
    let day = number_u8(slice(rest, 0, 2)?)?;
    let month = month(slice(rest, 3, 6)?)?;
    let short_year = i64::from(number_u8(slice(rest, 7, 9)?)?);
    let hour = number_u8(slice(rest, 10, 12)?)?;
    let minute = number_u8(slice(rest, 13, 15)?)?;
    let second = number_u8(slice(rest, 16, 18)?)?;
    let current = civil_time_at(now)?;
    let mut year = current
        .year
        .checked_div(100)
        .and_then(|century| century.checked_mul(100))
        .and_then(|century| century.checked_add(short_year))
        .ok_or(RetryAfterError::Overflow)?;
    let cutoff_year = current
        .year
        .checked_add(50)
        .ok_or(RetryAfterError::Overflow)?;
    if (year, month, day, hour, minute, second)
        > (
            cutoff_year,
            current.month,
            current.day,
            current.hour,
            current.minute,
            current.second,
        )
    {
        year = year.checked_sub(100).ok_or(RetryAfterError::Overflow)?;
    }
    Ok(DateParts {
        weekday,
        day,
        month,
        year,
        hour,
        minute,
        second,
    })
}

fn parse_asctime(value: &[u8]) -> Result<DateParts, RetryAfterError> {
    if value.len() != 24
        || value.get(3) != Some(&b' ')
        || value.get(7) != Some(&b' ')
        || value.get(10) != Some(&b' ')
        || value.get(13) != Some(&b':')
        || value.get(16) != Some(&b':')
        || value.get(19) != Some(&b' ')
    {
        return Err(RetryAfterError::InvalidSyntax);
    }
    let day = match value.get(8) {
        Some(b' ') => number_u8(slice(value, 9, 10)?)?,
        Some(_) => number_u8(slice(value, 8, 10)?)?,
        None => return Err(RetryAfterError::InvalidSyntax),
    };
    Ok(DateParts {
        weekday: short_weekday(slice(value, 0, 3)?)?,
        month: month(slice(value, 4, 7)?)?,
        day,
        hour: number_u8(slice(value, 11, 13)?)?,
        minute: number_u8(slice(value, 14, 16)?)?,
        second: number_u8(slice(value, 17, 19)?)?,
        year: i64::from(number_u16(slice(value, 20, 24)?)?),
    })
}

fn slice(value: &[u8], start: usize, end: usize) -> Result<&[u8], RetryAfterError> {
    value.get(start..end).ok_or(RetryAfterError::InvalidSyntax)
}

fn number_u8(value: &[u8]) -> Result<u8, RetryAfterError> {
    u8::try_from(number_u16(value)?).map_err(|_| RetryAfterError::Overflow)
}

fn number_u16(value: &[u8]) -> Result<u16, RetryAfterError> {
    if value.is_empty() || !value.iter().all(u8::is_ascii_digit) {
        return Err(RetryAfterError::InvalidSyntax);
    }
    let mut parsed = 0_u16;
    for byte in value {
        parsed = parsed
            .checked_mul(10)
            .and_then(|current| current.checked_add(u16::from(*byte & 0x0f)))
            .ok_or(RetryAfterError::Overflow)?;
    }
    Ok(parsed)
}

fn month(value: &[u8]) -> Result<u8, RetryAfterError> {
    match value {
        b"Jan" => Ok(1),
        b"Feb" => Ok(2),
        b"Mar" => Ok(3),
        b"Apr" => Ok(4),
        b"May" => Ok(5),
        b"Jun" => Ok(6),
        b"Jul" => Ok(7),
        b"Aug" => Ok(8),
        b"Sep" => Ok(9),
        b"Oct" => Ok(10),
        b"Nov" => Ok(11),
        b"Dec" => Ok(12),
        _ => Err(RetryAfterError::InvalidSyntax),
    }
}

fn short_weekday(value: &[u8]) -> Result<u8, RetryAfterError> {
    match value {
        b"Sun" => Ok(0),
        b"Mon" => Ok(1),
        b"Tue" => Ok(2),
        b"Wed" => Ok(3),
        b"Thu" => Ok(4),
        b"Fri" => Ok(5),
        b"Sat" => Ok(6),
        _ => Err(RetryAfterError::InvalidSyntax),
    }
}

fn long_weekday(value: &[u8]) -> Result<u8, RetryAfterError> {
    match value {
        b"Sunday" => Ok(0),
        b"Monday" => Ok(1),
        b"Tuesday" => Ok(2),
        b"Wednesday" => Ok(3),
        b"Thursday" => Ok(4),
        b"Friday" => Ok(5),
        b"Saturday" => Ok(6),
        _ => Err(RetryAfterError::InvalidSyntax),
    }
}

fn is_leap_year(year: i64) -> bool {
    year.rem_euclid(4) == 0 && (year.rem_euclid(100) != 0 || year.rem_euclid(400) == 0)
}

fn days_in_month(year: i64, month: u8) -> u8 {
    match month {
        2 if is_leap_year(year) => 29,
        2 => 28,
        4 | 6 | 9 | 11 => 30,
        _ => 31,
    }
}

fn days_from_civil(year: i64, month: u8, day: u8) -> Result<i64, RetryAfterError> {
    let adjusted_year = year
        .checked_sub(if month <= 2 { 1 } else { 0 })
        .ok_or(RetryAfterError::Overflow)?;
    let era = adjusted_year.div_euclid(400);
    let year_of_era = adjusted_year.rem_euclid(400);
    let month = i64::from(month);
    let shifted_month = month
        .checked_add(if month > 2 { -3 } else { 9 })
        .ok_or(RetryAfterError::Overflow)?;
    let day_of_year = 153_i64
        .checked_mul(shifted_month)
        .and_then(|value| value.checked_add(2))
        .map(|value| value / 5)
        .and_then(|value| value.checked_add(i64::from(day)))
        .and_then(|value| value.checked_sub(1))
        .ok_or(RetryAfterError::Overflow)?;
    let day_of_era = year_of_era
        .checked_mul(365)
        .and_then(|value| value.checked_add(year_of_era / 4))
        .and_then(|value| value.checked_sub(year_of_era / 100))
        .and_then(|value| value.checked_add(day_of_year))
        .ok_or(RetryAfterError::Overflow)?;
    era.checked_mul(146_097)
        .and_then(|value| value.checked_add(day_of_era))
        .and_then(|value| value.checked_sub(719_468))
        .ok_or(RetryAfterError::Overflow)
}

struct CivilTime {
    year: i64,
    month: u8,
    day: u8,
    hour: u8,
    minute: u8,
    second: u8,
}

fn civil_time_at(now: WallClockTimestamp) -> Result<CivilTime, RetryAfterError> {
    let seconds = i64::try_from(now.get()).map_err(|_| RetryAfterError::Overflow)?;
    let days = seconds.div_euclid(86_400);
    let seconds_of_day = seconds.rem_euclid(86_400);
    let shifted = days.checked_add(719_468).ok_or(RetryAfterError::Overflow)?;
    let era = shifted.div_euclid(146_097);
    let day_of_era = shifted.rem_euclid(146_097);
    let year_of_era = day_of_era
        .checked_sub(day_of_era / 1_460)
        .and_then(|value| value.checked_add(day_of_era / 36_524))
        .and_then(|value| value.checked_sub(day_of_era / 146_096))
        .map(|value| value / 365)
        .ok_or(RetryAfterError::Overflow)?;
    let mut year = era
        .checked_mul(400)
        .and_then(|value| value.checked_add(year_of_era))
        .ok_or(RetryAfterError::Overflow)?;
    let elapsed = year_of_era
        .checked_mul(365)
        .and_then(|value| value.checked_add(year_of_era / 4))
        .and_then(|value| value.checked_sub(year_of_era / 100))
        .ok_or(RetryAfterError::Overflow)?;
    let day_of_year = day_of_era
        .checked_sub(elapsed)
        .ok_or(RetryAfterError::Overflow)?;
    let month_prime = day_of_year
        .checked_mul(5)
        .and_then(|value| value.checked_add(2))
        .map(|value| value / 153)
        .ok_or(RetryAfterError::Overflow)?;
    let month = month_prime
        .checked_add(if month_prime < 10 { 3 } else { -9 })
        .ok_or(RetryAfterError::Overflow)?;
    if month <= 2 {
        year = year.checked_add(1).ok_or(RetryAfterError::Overflow)?;
    }
    let day = day_of_year
        .checked_sub(
            153_i64
                .checked_mul(month_prime)
                .and_then(|value| value.checked_add(2))
                .map(|value| value / 5)
                .ok_or(RetryAfterError::Overflow)?,
        )
        .and_then(|value| value.checked_add(1))
        .ok_or(RetryAfterError::Overflow)?;
    let hour = seconds_of_day / 3_600;
    let minute = (seconds_of_day % 3_600) / 60;
    let second = seconds_of_day % 60;
    Ok(CivilTime {
        year,
        month: u8::try_from(month).map_err(|_| RetryAfterError::Overflow)?,
        day: u8::try_from(day).map_err(|_| RetryAfterError::Overflow)?,
        hour: u8::try_from(hour).map_err(|_| RetryAfterError::Overflow)?,
        minute: u8::try_from(minute).map_err(|_| RetryAfterError::Overflow)?,
        second: u8::try_from(second).map_err(|_| RetryAfterError::Overflow)?,
    })
}