use std::mem;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use generic_array::{self, ArrayLength, GenericArray};
use typenum::{Unsigned, U8};
use crate::base64::{self, Base64Sized, Base64SizedEncoder};
use crate::error::BadTimedSignature;
pub(crate) struct EncodedTimestamp<N: ArrayLength<u8>> {
array: GenericArray<u8, N>,
length: usize,
}
impl<N: ArrayLength<u8>> EncodedTimestamp<N> {
#[inline(always)]
pub(crate) fn as_slice(&self) -> &[u8] {
&self.array[..self.length]
}
#[inline(always)]
pub(crate) fn length(&self) -> usize {
self.length
}
#[inline(always)]
pub(crate) fn as_str(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(self.as_slice()) }
}
}
type TimestampEncoder = Base64SizedEncoder<U8>;
#[inline(always)]
pub(crate) fn encode(
timestamp: SystemTime,
) -> EncodedTimestamp<<TimestampEncoder as Base64Sized>::OutputSize> {
type InputSize = <TimestampEncoder as Base64Sized>::InputSize;
let epoch_delta: u64 = timestamp.duration_since(UNIX_EPOCH).unwrap().as_secs();
let timestamp_bytes: [u8; InputSize::USIZE] = unsafe { mem::transmute(epoch_delta.to_be()) };
let zero_index = timestamp_bytes.iter().take_while(|b| **b == 0u8).count();
let mut array = GenericArray::default();
let length = base64::encode_slice(×tamp_bytes[zero_index..], array.as_mut_slice());
EncodedTimestamp { array, length }
}
#[inline(always)]
pub(crate) fn decode(timestamp: &str) -> Result<SystemTime, BadTimedSignature> {
type InputSize = <TimestampEncoder as Base64Sized>::InputSize;
let timestamp_bytes = base64::decode::<InputSize, _>(timestamp)
.map_err(|_| BadTimedSignature::TimestampInvalid { timestamp })?;
let timestamp_bytes = timestamp_bytes.as_slice();
let mut input_array: GenericArray<u8, InputSize> = GenericArray::default();
input_array[InputSize::USIZE - timestamp_bytes.len()..].copy_from_slice(timestamp_bytes);
let timestamp_secs: u64 = unsafe { generic_array::transmute(input_array) };
let timestamp_duration = Duration::from_secs(timestamp_secs.to_be());
UNIX_EPOCH
.checked_add(timestamp_duration)
.ok_or_else(|| BadTimedSignature::TimestampInvalid { timestamp })
}