use core::fmt;
use cloud_sdk_sanitization::sanitize_bytes;
use subtle::{Choice, ConstantTimeEq};
use super::fingerprint::FingerprintRef;
pub const MIN_IDEMPOTENCY_INTENT_BYTES: usize = 16;
pub const MAX_IDEMPOTENCY_INTENT_BYTES: usize = 64;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IdempotencyIntentError {
TooShort,
TooLong,
AllZero,
}
impl_static_error!(IdempotencyIntentError,
Self::TooShort => "idempotency intent is too short",
Self::TooLong => "idempotency intent is too long",
Self::AllZero => "idempotency intent cannot be all zero",
);
pub struct IdempotencyIntent<'secret> {
bytes: &'secret mut [u8],
}
impl<'secret> IdempotencyIntent<'secret> {
pub fn new(source: &'secret mut [u8]) -> Result<Self, IdempotencyIntentError> {
if let Err(error) = validate_source(source) {
sanitize_bytes(source);
return Err(error);
}
Ok(Self { bytes: source })
}
#[must_use]
pub const fn len(&self) -> usize {
self.bytes.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
false
}
}
impl fmt::Debug for IdempotencyIntent<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("IdempotencyIntent([redacted])")
}
}
impl Drop for IdempotencyIntent<'_> {
fn drop(&mut self) {
sanitize_bytes(self.bytes);
}
}
fn validate_source(source: &[u8]) -> Result<(), IdempotencyIntentError> {
if source.len() < MIN_IDEMPOTENCY_INTENT_BYTES {
return Err(IdempotencyIntentError::TooShort);
}
if source.len() > MAX_IDEMPOTENCY_INTENT_BYTES {
return Err(IdempotencyIntentError::TooLong);
}
let mut any_nonzero = Choice::from(0);
for byte in source {
any_nonzero |= !byte.ct_eq(&0);
}
if !bool::from(any_nonzero) {
return Err(IdempotencyIntentError::AllZero);
}
Ok(())
}
pub struct IdempotencyBinding<'a> {
intent: IdempotencyIntent<'a>,
fingerprint: FingerprintRef<'a>,
}
impl<'a> IdempotencyBinding<'a> {
#[must_use]
pub const fn bind(intent: IdempotencyIntent<'a>, fingerprint: FingerprintRef<'a>) -> Self {
Self {
intent,
fingerprint,
}
}
#[must_use]
pub const fn intent_len(&self) -> usize {
self.intent.len()
}
pub(crate) fn matches(&self, fingerprint: FingerprintRef<'_>) -> bool {
self.fingerprint.matches(fingerprint)
}
}
impl fmt::Debug for IdempotencyBinding<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("IdempotencyBinding")
.field("intent_len", &self.intent.len())
.field("fingerprint", &"[redacted]")
.finish()
}
}