use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuiltinSecurityTopicProfile {
pub reliability: ReliabilityKind,
pub durability: DurabilityKind,
pub history: HistoryKind,
pub require_receiver_specific_macs: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReliabilityKind {
BestEffort,
Reliable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DurabilityKind {
Volatile,
TransientLocal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HistoryKind {
KeepLast(u32),
KeepAll,
}
impl fmt::Display for ReliabilityKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::BestEffort => "BEST_EFFORT",
Self::Reliable => "RELIABLE",
})
}
}
#[must_use]
pub fn stateless_message_profile() -> BuiltinSecurityTopicProfile {
BuiltinSecurityTopicProfile {
reliability: ReliabilityKind::BestEffort,
durability: DurabilityKind::Volatile,
history: HistoryKind::KeepLast(1),
require_receiver_specific_macs: false,
}
}
#[must_use]
pub fn volatile_message_secure_profile() -> BuiltinSecurityTopicProfile {
BuiltinSecurityTopicProfile {
reliability: ReliabilityKind::Reliable,
durability: DurabilityKind::Volatile,
history: HistoryKind::KeepAll,
require_receiver_specific_macs: true,
}
}
pub fn validate_security_topic_profile(
topic_name: &str,
actual: BuiltinSecurityTopicProfile,
) -> Result<(), &'static str> {
let expected = match topic_name {
crate::generic_message::TOPIC_STATELESS_MESSAGE => stateless_message_profile(),
crate::generic_message::TOPIC_VOLATILE_MESSAGE_SECURE => volatile_message_secure_profile(),
_ => return Err("not a security builtin topic"),
};
if actual.reliability != expected.reliability {
return Err("reliability mismatch");
}
if actual.durability != expected.durability {
return Err("durability mismatch");
}
if actual.history != expected.history {
return Err("history mismatch");
}
if actual.require_receiver_specific_macs != expected.require_receiver_specific_macs {
return Err("receiver-specific-MAC requirement mismatch");
}
Ok(())
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn stateless_profile_is_best_effort() {
let p = stateless_message_profile();
assert_eq!(p.reliability, ReliabilityKind::BestEffort);
assert_eq!(p.durability, DurabilityKind::Volatile);
assert_eq!(p.history, HistoryKind::KeepLast(1));
assert!(!p.require_receiver_specific_macs);
}
#[test]
fn volatile_secure_is_reliable_with_receiver_macs() {
let p = volatile_message_secure_profile();
assert_eq!(p.reliability, ReliabilityKind::Reliable);
assert_eq!(p.history, HistoryKind::KeepAll);
assert!(p.require_receiver_specific_macs);
}
#[test]
fn validate_known_topic_with_matching_profile() {
validate_security_topic_profile(
crate::generic_message::TOPIC_STATELESS_MESSAGE,
stateless_message_profile(),
)
.unwrap();
validate_security_topic_profile(
crate::generic_message::TOPIC_VOLATILE_MESSAGE_SECURE,
volatile_message_secure_profile(),
)
.unwrap();
}
#[test]
fn validate_rejects_wrong_reliability_on_volatile_topic() {
let mut wrong = volatile_message_secure_profile();
wrong.reliability = ReliabilityKind::BestEffort;
let err = validate_security_topic_profile(
crate::generic_message::TOPIC_VOLATILE_MESSAGE_SECURE,
wrong,
)
.unwrap_err();
assert_eq!(err, "reliability mismatch");
}
#[test]
fn validate_rejects_missing_receiver_macs() {
let mut wrong = volatile_message_secure_profile();
wrong.require_receiver_specific_macs = false;
let err = validate_security_topic_profile(
crate::generic_message::TOPIC_VOLATILE_MESSAGE_SECURE,
wrong,
)
.unwrap_err();
assert_eq!(err, "receiver-specific-MAC requirement mismatch");
}
#[test]
fn validate_rejects_unknown_topic() {
let err =
validate_security_topic_profile("Other", stateless_message_profile()).unwrap_err();
assert_eq!(err, "not a security builtin topic");
}
#[test]
fn reliability_display_matches_spec() {
assert_eq!(
alloc::format!("{}", ReliabilityKind::BestEffort),
"BEST_EFFORT"
);
assert_eq!(alloc::format!("{}", ReliabilityKind::Reliable), "RELIABLE");
}
}