use std::time::{Duration, SystemTime};
use thiserror::Error;
use crate::identity::KeyId;
use crate::proto::Did;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClaimNonce([u8; 16]);
impl ClaimNonce {
#[must_use]
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
ClaimNonce(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct JwtNonce([u8; 16]);
impl JwtNonce {
#[must_use]
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
JwtNonce(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 16] {
&self.0
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NonceKind {
CapabilityClaim,
Jwt,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum NoncePrincipal {
Service(Did),
UserJwt(Did),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NonceIssuerKey {
pub principal: NoncePrincipal,
pub key_id: KeyId,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NonceFreshness {
Fresh,
Replay {
first_seen_at: SystemTime,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum NonceTrackerError {
#[error("nonce tracker over capacity")]
OverCapacity,
#[error("nonce tracker backend unavailable")]
BackendUnavailable,
}
pub trait NonceTracker: Send + Sync {
fn record(
&self,
kind: NonceKind,
issuer: &NonceIssuerKey,
nonce_bytes: &[u8; 16],
observed_at: SystemTime,
) -> Result<NonceFreshness, NonceTrackerError>;
fn retention_window(&self) -> Duration;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nonce_issuer_key_is_principal_plus_key_id_round_4_reshape() {
let k = NonceIssuerKey {
principal: NoncePrincipal::Service(Did::new("did:plc:example").unwrap()),
key_id: KeyId::from_bytes([0; 32]),
};
match &k.principal {
NoncePrincipal::Service(_) | NoncePrincipal::UserJwt(_) => {}
}
let _ = k.key_id;
}
#[test]
fn nonce_kind_has_capability_claim_and_jwt() {
let _c = NonceKind::CapabilityClaim;
let _j = NonceKind::Jwt;
}
#[test]
fn nonce_freshness_has_fresh_and_replay() {
let _f = NonceFreshness::Fresh;
let _r = NonceFreshness::Replay {
first_seen_at: std::time::SystemTime::UNIX_EPOCH,
};
}
}