use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DtlsMode {
NoSec,
PreSharedKey {
identity: String,
key: Vec<u8>,
},
RawPublicKey {
public_key: Vec<u8>,
spki_der: Vec<u8>,
},
Certificate {
cert_chain: Vec<Vec<u8>>,
trust_anchors: Vec<Vec<u8>>,
},
}
impl DtlsMode {
#[must_use]
pub fn is_secure(&self) -> bool {
!matches!(self, Self::NoSec)
}
#[must_use]
pub fn default_port(&self) -> u16 {
if self.is_secure() { 5684 } else { 5683 }
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::NoSec => "NoSec",
Self::PreSharedKey { .. } => "PreSharedKey",
Self::RawPublicKey { .. } => "RawPublicKey",
Self::Certificate { .. } => "Certificate",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DtlsConfigError {
PskTooShort,
EmptyIdentity,
EmptyCertChain,
NoTrustAnchors,
InvalidPublicKey,
}
impl core::fmt::Display for DtlsConfigError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::PskTooShort => write!(f, "PskTooShort"),
Self::EmptyIdentity => write!(f, "EmptyIdentity"),
Self::EmptyCertChain => write!(f, "EmptyCertChain"),
Self::NoTrustAnchors => write!(f, "NoTrustAnchors"),
Self::InvalidPublicKey => write!(f, "InvalidPublicKey"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for DtlsConfigError {}
pub fn validate_dtls_mode(mode: &DtlsMode) -> Result<(), DtlsConfigError> {
match mode {
DtlsMode::NoSec => Ok(()),
DtlsMode::PreSharedKey { identity, key } => {
if identity.is_empty() {
return Err(DtlsConfigError::EmptyIdentity);
}
if key.len() < 16 {
return Err(DtlsConfigError::PskTooShort);
}
Ok(())
}
DtlsMode::RawPublicKey {
public_key,
spki_der,
} => {
if public_key.is_empty() || spki_der.is_empty() {
return Err(DtlsConfigError::InvalidPublicKey);
}
Ok(())
}
DtlsMode::Certificate {
cert_chain,
trust_anchors,
} => {
if cert_chain.is_empty() {
return Err(DtlsConfigError::EmptyCertChain);
}
if trust_anchors.is_empty() {
return Err(DtlsConfigError::NoTrustAnchors);
}
Ok(())
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn nosec_is_not_secure() {
assert!(!DtlsMode::NoSec.is_secure());
assert_eq!(DtlsMode::NoSec.default_port(), 5683);
assert_eq!(DtlsMode::NoSec.name(), "NoSec");
}
#[test]
fn psk_is_secure() {
let m = DtlsMode::PreSharedKey {
identity: "client-1".into(),
key: alloc::vec![0; 32],
};
assert!(m.is_secure());
assert_eq!(m.default_port(), 5684);
assert_eq!(m.name(), "PreSharedKey");
}
#[test]
fn raw_public_key_is_secure() {
let m = DtlsMode::RawPublicKey {
public_key: alloc::vec![0; 64],
spki_der: alloc::vec![0; 91],
};
assert!(m.is_secure());
assert_eq!(m.name(), "RawPublicKey");
}
#[test]
fn certificate_is_secure() {
let m = DtlsMode::Certificate {
cert_chain: alloc::vec![alloc::vec![0; 100]],
trust_anchors: alloc::vec![alloc::vec![0; 100]],
};
assert!(m.is_secure());
assert_eq!(m.default_port(), 5684);
assert_eq!(m.name(), "Certificate");
}
#[test]
fn validate_psk_short_key_rejected() {
let m = DtlsMode::PreSharedKey {
identity: "a".into(),
key: alloc::vec![0; 8],
};
assert_eq!(validate_dtls_mode(&m), Err(DtlsConfigError::PskTooShort));
}
#[test]
fn validate_psk_empty_identity_rejected() {
let m = DtlsMode::PreSharedKey {
identity: "".into(),
key: alloc::vec![0; 16],
};
assert_eq!(validate_dtls_mode(&m), Err(DtlsConfigError::EmptyIdentity));
}
#[test]
fn validate_psk_valid() {
let m = DtlsMode::PreSharedKey {
identity: "client-1".into(),
key: alloc::vec![0; 16],
};
assert!(validate_dtls_mode(&m).is_ok());
}
#[test]
fn validate_certificate_empty_chain_rejected() {
let m = DtlsMode::Certificate {
cert_chain: alloc::vec![],
trust_anchors: alloc::vec![alloc::vec![0; 100]],
};
assert_eq!(validate_dtls_mode(&m), Err(DtlsConfigError::EmptyCertChain));
}
#[test]
fn validate_certificate_no_trust_anchors_rejected() {
let m = DtlsMode::Certificate {
cert_chain: alloc::vec![alloc::vec![0; 100]],
trust_anchors: alloc::vec![],
};
assert_eq!(validate_dtls_mode(&m), Err(DtlsConfigError::NoTrustAnchors));
}
#[test]
fn validate_raw_public_key_empty_rejected() {
let m = DtlsMode::RawPublicKey {
public_key: alloc::vec![],
spki_der: alloc::vec![0; 91],
};
assert_eq!(
validate_dtls_mode(&m),
Err(DtlsConfigError::InvalidPublicKey)
);
}
#[test]
fn validate_nosec_passes() {
assert!(validate_dtls_mode(&DtlsMode::NoSec).is_ok());
}
}