use std::fmt;
use crate::{PolicyFingerprint, SubjectKey};
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CounterKey {
fingerprint: PolicyFingerprint,
subject: SubjectKey,
}
impl CounterKey {
pub const fn new(fingerprint: PolicyFingerprint, subject: SubjectKey) -> Self {
Self {
fingerprint,
subject,
}
}
pub const fn fingerprint(self) -> PolicyFingerprint {
self.fingerprint
}
pub const fn subject(self) -> SubjectKey {
self.subject
}
pub fn to_bytes(self) -> [u8; 64] {
let mut bytes = [0_u8; 64];
bytes[..32].copy_from_slice(self.fingerprint.as_bytes());
bytes[32..].copy_from_slice(self.subject.as_bytes());
bytes
}
}
impl fmt::Debug for CounterKey {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CounterKey")
.field("fingerprint", &self.fingerprint)
.field("subject", &self.subject)
.finish()
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use crate::{Check, FixedWindowPolicy, PolicyId, ScopeId, SubjectKey};
#[test]
fn fixed_width_encoding_is_fingerprint_then_subject() {
let policy = FixedWindowPolicy::new(
PolicyId::new("auth.login").unwrap(),
ScopeId::new("client").unwrap(),
8,
Duration::from_secs(60),
)
.unwrap();
let subject = SubjectKey::from_digest([0x5a; 32]);
let key = Check::new(&policy, subject).counter_key();
let bytes = key.to_bytes();
assert_eq!(&bytes[..32], policy.fingerprint().as_bytes());
assert_eq!(&bytes[32..], subject.as_bytes());
assert!(format!("{key:?}").contains("[REDACTED]"));
assert!(!format!("{key:?}").contains("5a5a"));
}
}