saddle-framework 0.3.14

The single business-facing facade for Saddle applications
//! Framework-owned UUIDv7, RFC 9562 section 5.7. No raw issuer is public.
use ring::rand::{SecureRandom, SystemRandom};

#[derive(Debug)]
pub enum GeneratedParameterError {
    GenerationUnavailable,
    Construction(saddle_db::internal::ParameterConstructionError),
}

pub struct ParameterFactory<'a> {
    pub(crate) request: &'a mut crate::database_capability::DatabaseRequest,
}
impl ParameterFactory<'_> {
    #[doc(hidden)]
    pub async fn generated_uid(
        &mut self,
    ) -> Result<crate::database::Text, GeneratedParameterError> {
        let uid = generate().map_err(|_| GeneratedParameterError::GenerationUnavailable)?;
        let text = std::str::from_utf8(&uid).expect("hex is ASCII");
        self.request
            .parameter_text(text)
            .await
            .map_err(GeneratedParameterError::Construction)
    }
}

pub(crate) fn generate() -> Result<[u8; 32], ()> {
    let millis = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map_err(|_| ())?
        .as_millis();
    if millis >= 1u128 << 48 {
        return Err(());
    }
    let mut bytes = [0; 16];
    SystemRandom::new().fill(&mut bytes).map_err(|_| ())?;
    Ok(encode(millis as u64, bytes))
}

fn encode(millis: u64, mut bytes: [u8; 16]) -> [u8; 32] {
    bytes[..6].copy_from_slice(&millis.to_be_bytes()[2..]);
    bytes[6] = (bytes[6] & 0x0f) | 0x70;
    bytes[8] = (bytes[8] & 0x3f) | 0x80;
    const HEX: &[u8; 16] = b"0123456789abcdef";
    let mut text = [0; 32];
    for (i, byte) in bytes.iter().enumerate() {
        text[i * 2] = HEX[(byte >> 4) as usize];
        text[i * 2 + 1] = HEX[(byte & 15) as usize];
    }
    text
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn version_variant_time_and_lowercase_are_exact() {
        assert_eq!(
            &encode(0x0123456789ab, [0; 16]),
            b"0123456789ab70008000000000000000"
        );
    }
    #[test]
    fn production_entropy_produces_distinct_v7_values() {
        let mut values = std::collections::BTreeSet::new();
        for _ in 0..1024 {
            let id = generate().unwrap();
            assert_eq!(id[12], b'7');
            assert!(b"89ab".contains(&id[16]));
            assert!(values.insert(id));
        }
    }
}