use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
MessageTooShort,
NoTags,
InvalidOffsets,
MissingTag(u32),
InvalidFieldSize(u32),
InvalidPublicKeyEncoding,
SignatureInvalid,
DelegationNotValidAtMidpoint,
InvalidMerklePathLength,
MerkleRootMismatch,
MerkleIndexOutOfBounds,
BeforeFloor {
candidate_secs: u64,
floor_secs: u64,
},
NoValidResponses,
ClockUnavailable(crate::clock::ClockError),
RandomUnavailable,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MessageTooShort => f.write_str("Roughtime message too short"),
Self::NoTags => f.write_str("Roughtime message declares zero tags"),
Self::InvalidOffsets => f.write_str("Roughtime message has invalid tag offsets"),
Self::MissingTag(tag) => write!(f, "missing required tag {tag:#010x}"),
Self::InvalidFieldSize(tag) => write!(f, "invalid field size for tag {tag:#010x}"),
Self::InvalidPublicKeyEncoding => f.write_str("invalid base64 public key encoding"),
Self::SignatureInvalid => f.write_str("Ed25519 signature verification failed"),
Self::DelegationNotValidAtMidpoint => {
f.write_str("delegated key is not valid at the reported midpoint")
}
Self::InvalidMerklePathLength => {
f.write_str("Merkle path length is not a multiple of 32 bytes")
}
Self::MerkleRootMismatch => f.write_str("computed Merkle root does not match ROOT"),
Self::MerkleIndexOutOfBounds => {
f.write_str("Merkle index out of bounds for the path depth")
}
Self::BeforeFloor {
candidate_secs,
floor_secs,
} => write!(
f,
"candidate time {candidate_secs}s is before the anti-rollback floor {floor_secs}s"
),
Self::NoValidResponses => {
f.write_str("no valid Roughtime responses received from any server")
}
Self::ClockUnavailable(clock_err) => {
write!(f, "system clock unavailable: {clock_err:?}")
}
Self::RandomUnavailable => {
f.write_str("cryptographically secure random source unavailable")
}
}
}
}
impl core::error::Error for Error {}
#[cfg(test)]
mod tests {
use super::Error;
use crate::clock::ClockError;
use alloc::string::ToString as _;
#[test]
fn display_covers_every_variant() {
let cases: &[(Error, &str)] = &[
(Error::MessageTooShort, "Roughtime message too short"),
(Error::NoTags, "Roughtime message declares zero tags"),
(
Error::InvalidOffsets,
"Roughtime message has invalid tag offsets",
),
(
Error::MissingTag(0x1234_5678),
"missing required tag 0x12345678",
),
(
Error::InvalidFieldSize(0x1234_5678),
"invalid field size for tag 0x12345678",
),
(
Error::InvalidPublicKeyEncoding,
"invalid base64 public key encoding",
),
(
Error::SignatureInvalid,
"Ed25519 signature verification failed",
),
(
Error::DelegationNotValidAtMidpoint,
"delegated key is not valid at the reported midpoint",
),
(
Error::InvalidMerklePathLength,
"Merkle path length is not a multiple of 32 bytes",
),
(
Error::MerkleRootMismatch,
"computed Merkle root does not match ROOT",
),
(
Error::MerkleIndexOutOfBounds,
"Merkle index out of bounds for the path depth",
),
(
Error::BeforeFloor {
candidate_secs: 1,
floor_secs: 2,
},
"candidate time 1s is before the anti-rollback floor 2s",
),
(
Error::NoValidResponses,
"no valid Roughtime responses received from any server",
),
(
Error::RandomUnavailable,
"cryptographically secure random source unavailable",
),
];
for (err, expected) in cases {
assert_eq!(&err.to_string(), expected);
}
let clock_err = Error::ClockUnavailable(ClockError::Unsupported);
assert!(clock_err.to_string().contains("system clock unavailable"));
}
#[test]
fn errors_are_comparable_and_cloneable() {
let a = Error::MessageTooShort;
let b = a;
assert_eq!(a, b);
}
}