use zerodds_rtps::endpoint_security_info::{EndpointSecurityInfo, attrs, plugin_attrs};
use crate::policy::ProtectionLevel;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EndpointProtection {
pub level: ProtectionLevel,
}
impl EndpointProtection {
pub const PLAIN: Self = Self {
level: ProtectionLevel::None,
};
#[must_use]
pub const fn new(level: ProtectionLevel) -> Self {
Self { level }
}
#[must_use]
pub fn from_info(info: Option<&EndpointSecurityInfo>) -> Self {
let Some(info) = info else {
return Self::PLAIN;
};
if !info.is_valid() {
return Self::PLAIN;
}
if info.is_payload_encrypted() || info.is_submessage_encrypted() {
return Self::new(ProtectionLevel::Encrypt);
}
if info.is_submessage_protected() || info.is_payload_protected() {
return Self::new(ProtectionLevel::Sign);
}
Self::PLAIN
}
#[must_use]
pub fn to_info(self) -> EndpointSecurityInfo {
let mut endpoint = attrs::IS_VALID;
let mut plugin = plugin_attrs::IS_VALID;
match self.level {
ProtectionLevel::None => {}
ProtectionLevel::Sign => {
endpoint |= attrs::IS_SUBMESSAGE_PROTECTED;
}
ProtectionLevel::Encrypt => {
endpoint |= attrs::IS_SUBMESSAGE_PROTECTED | attrs::IS_PAYLOAD_PROTECTED;
plugin |=
plugin_attrs::IS_SUBMESSAGE_ENCRYPTED | plugin_attrs::IS_PAYLOAD_ENCRYPTED;
}
}
EndpointSecurityInfo {
endpoint_security_attributes: endpoint,
plugin_endpoint_security_attributes: plugin,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EndpointMatch {
Accept(ProtectionLevel),
Reject(MatchRejectReason),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MatchRejectReason {
LegacyPeerVsProtection,
}
#[must_use]
pub fn match_endpoints(
writer: &EndpointProtection,
reader: &EndpointProtection,
writer_has_info: bool,
reader_has_info: bool,
) -> EndpointMatch {
let writer_wants_protection = !matches!(writer.level, ProtectionLevel::None);
let reader_wants_protection = !matches!(reader.level, ProtectionLevel::None);
if writer_wants_protection && !reader_has_info {
return EndpointMatch::Reject(MatchRejectReason::LegacyPeerVsProtection);
}
if reader_wants_protection && !writer_has_info {
return EndpointMatch::Reject(MatchRejectReason::LegacyPeerVsProtection);
}
EndpointMatch::Accept(writer.level.stronger(reader.level))
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn from_info_none_is_plain() {
assert_eq!(
EndpointProtection::from_info(None),
EndpointProtection::PLAIN
);
}
#[test]
fn from_info_invalid_valid_bit_is_plain() {
let info = EndpointSecurityInfo {
endpoint_security_attributes: attrs::IS_SUBMESSAGE_PROTECTED,
plugin_endpoint_security_attributes: plugin_attrs::IS_SUBMESSAGE_ENCRYPTED,
};
assert_eq!(
EndpointProtection::from_info(Some(&info)),
EndpointProtection::PLAIN
);
}
#[test]
fn from_info_plain_legacy_is_plain() {
let info = EndpointSecurityInfo::plain();
assert_eq!(
EndpointProtection::from_info(Some(&info)),
EndpointProtection::PLAIN
);
}
#[test]
fn from_info_submessage_protected_without_encrypt_is_sign() {
let info = EndpointSecurityInfo {
endpoint_security_attributes: attrs::IS_VALID | attrs::IS_SUBMESSAGE_PROTECTED,
plugin_endpoint_security_attributes: plugin_attrs::IS_VALID,
};
assert_eq!(
EndpointProtection::from_info(Some(&info)).level,
ProtectionLevel::Sign
);
}
#[test]
fn from_info_submessage_encrypted_is_encrypt() {
let info = EndpointSecurityInfo {
endpoint_security_attributes: attrs::IS_VALID | attrs::IS_SUBMESSAGE_PROTECTED,
plugin_endpoint_security_attributes: plugin_attrs::IS_VALID
| plugin_attrs::IS_SUBMESSAGE_ENCRYPTED,
};
assert_eq!(
EndpointProtection::from_info(Some(&info)).level,
ProtectionLevel::Encrypt
);
}
#[test]
fn from_info_payload_encrypted_is_encrypt() {
let info = EndpointSecurityInfo {
endpoint_security_attributes: attrs::IS_VALID | attrs::IS_PAYLOAD_PROTECTED,
plugin_endpoint_security_attributes: plugin_attrs::IS_VALID
| plugin_attrs::IS_PAYLOAD_ENCRYPTED,
};
assert_eq!(
EndpointProtection::from_info(Some(&info)).level,
ProtectionLevel::Encrypt
);
}
#[test]
fn to_info_none_sets_only_valid_bits() {
let info = EndpointProtection::PLAIN.to_info();
assert!(info.is_valid());
assert!(!info.is_submessage_protected());
assert!(!info.is_payload_protected());
assert!(!info.is_submessage_encrypted());
}
#[test]
fn to_info_sign_sets_submessage_protected_without_encryption() {
let info = EndpointProtection::new(ProtectionLevel::Sign).to_info();
assert!(info.is_valid());
assert!(info.is_submessage_protected());
assert!(!info.is_submessage_encrypted());
assert!(!info.is_payload_encrypted());
}
#[test]
fn to_info_encrypt_sets_both_protection_and_encryption() {
let info = EndpointProtection::new(ProtectionLevel::Encrypt).to_info();
assert!(info.is_valid());
assert!(info.is_submessage_protected());
assert!(info.is_payload_protected());
assert!(info.is_submessage_encrypted());
assert!(info.is_payload_encrypted());
}
#[test]
fn to_info_from_info_roundtrip_for_all_levels() {
for lvl in [
ProtectionLevel::None,
ProtectionLevel::Sign,
ProtectionLevel::Encrypt,
] {
let ep = EndpointProtection::new(lvl);
let info = ep.to_info();
let back = EndpointProtection::from_info(Some(&info));
assert_eq!(back, ep, "roundtrip scheitert fuer {lvl:?}");
}
}
#[test]
fn dod_writer_encrypt_reader_no_plugin_is_reject() {
let w = EndpointProtection::new(ProtectionLevel::Encrypt);
let r = EndpointProtection::PLAIN;
let result = match_endpoints(
&w, &r, true, false,
);
assert_eq!(
result,
EndpointMatch::Reject(MatchRejectReason::LegacyPeerVsProtection)
);
}
#[test]
fn dod_writer_sign_reader_encrypt_accepts_with_encrypt() {
let w = EndpointProtection::new(ProtectionLevel::Sign);
let r = EndpointProtection::new(ProtectionLevel::Encrypt);
let result = match_endpoints(&w, &r, true, true);
assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::Encrypt));
}
#[test]
fn writer_none_reader_none_both_info_accepts_none() {
let w = EndpointProtection::PLAIN;
let r = EndpointProtection::PLAIN;
let result = match_endpoints(&w, &r, true, true);
assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::None));
}
#[test]
fn writer_none_reader_none_both_legacy_accepts_none() {
let w = EndpointProtection::PLAIN;
let r = EndpointProtection::PLAIN;
let result = match_endpoints(&w, &r, false, false);
assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::None));
}
#[test]
fn writer_encrypt_reader_encrypt_accepts_encrypt() {
let w = EndpointProtection::new(ProtectionLevel::Encrypt);
let r = EndpointProtection::new(ProtectionLevel::Encrypt);
let result = match_endpoints(&w, &r, true, true);
assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::Encrypt));
}
#[test]
fn writer_plain_reader_encrypt_rejects_if_writer_legacy() {
let w = EndpointProtection::PLAIN;
let r = EndpointProtection::new(ProtectionLevel::Encrypt);
let result = match_endpoints(&w, &r, false, true);
assert_eq!(
result,
EndpointMatch::Reject(MatchRejectReason::LegacyPeerVsProtection)
);
}
#[test]
fn writer_encrypt_reader_sign_accepts_with_encrypt() {
let w = EndpointProtection::new(ProtectionLevel::Encrypt);
let r = EndpointProtection::new(ProtectionLevel::Sign);
let result = match_endpoints(&w, &r, true, true);
assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::Encrypt));
}
#[test]
fn writer_sign_reader_sign_accepts_with_sign() {
let w = EndpointProtection::new(ProtectionLevel::Sign);
let r = EndpointProtection::new(ProtectionLevel::Sign);
let result = match_endpoints(&w, &r, true, true);
assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::Sign));
}
#[test]
fn writer_none_with_info_reader_sign_rejects_only_if_writer_cant() {
let w = EndpointProtection::PLAIN;
let r = EndpointProtection::new(ProtectionLevel::Sign);
let result = match_endpoints(&w, &r, true, true);
assert_eq!(result, EndpointMatch::Accept(ProtectionLevel::Sign));
}
}