mod config;
mod crc;
mod e2e_checker;
mod e2e_protector;
mod error;
mod registry;
mod state;
pub use config::{Profile4Config, Profile5Config};
pub use e2e_checker::{check_profile4, check_profile5, check_profile5_with_header};
pub use e2e_protector::{
PROFILE4_HEADER_SIZE, PROFILE5_HEADER_SIZE, protect_profile4, protect_profile5,
protect_profile5_with_header,
};
pub use error::Error;
pub use registry::{E2E_REGISTRY_CAP, E2E_RX_STATE_CAP, E2ERegistry, E2ERegistryFull};
pub use state::{Profile4State, Profile5State};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum E2ECheckStatus {
Unchecked,
Ok,
CrcError,
Repeated,
OkSomeLost,
WrongSequence,
BadArgument,
}
impl E2ECheckStatus {
#[must_use]
pub fn to_return_code(self) -> u8 {
match self {
E2ECheckStatus::Unchecked => 0,
E2ECheckStatus::Ok => 1,
E2ECheckStatus::CrcError => 2,
E2ECheckStatus::Repeated => 3,
E2ECheckStatus::OkSomeLost => 4,
E2ECheckStatus::WrongSequence => 5,
E2ECheckStatus::BadArgument => 6,
}
}
}
#[derive(Debug, Clone)]
pub struct E2ECheckResult<'a> {
pub status: E2ECheckStatus,
pub counter: Option<u32>,
pub payload: Option<&'a [u8]>,
}
impl<'a> E2ECheckResult<'a> {
pub(crate) fn error(status: E2ECheckStatus) -> Self {
Self {
status,
counter: None,
payload: None,
}
}
pub(crate) fn success(status: E2ECheckStatus, counter: u32, payload: &'a [u8]) -> Self {
Self {
status,
counter: Some(counter),
payload: Some(payload),
}
}
#[cfg(feature = "std")]
#[must_use]
pub fn to_owned_payload(&self) -> Option<std::vec::Vec<u8>> {
self.payload.map(<[u8]>::to_vec)
}
}
#[derive(Debug, Clone)]
pub enum E2EProfile {
Profile4(Profile4Config),
Profile5(Profile5Config),
Profile5WithHeader(Profile5Config),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct E2EKey {
pub service_id: u16,
pub method_or_event_id: u16,
}
impl E2EKey {
#[must_use]
pub const fn new(service_id: u16, method_or_event_id: u16) -> Self {
Self {
service_id,
method_or_event_id,
}
}
#[must_use]
pub fn from_message_id(message_id: crate::protocol::MessageId) -> Self {
Self {
service_id: message_id.service_id(),
method_or_event_id: message_id.method_id(),
}
}
}
#[derive(Debug, Clone)]
pub(crate) enum E2EState {
Profile4(Profile4State),
Profile5(Profile5State),
}
impl E2EState {
pub(crate) fn from_profile(profile: &E2EProfile) -> Self {
match profile {
E2EProfile::Profile4(_) => Self::Profile4(Profile4State::new()),
E2EProfile::Profile5(_) | E2EProfile::Profile5WithHeader(_) => {
Self::Profile5(Profile5State::new())
}
}
}
}
pub(crate) fn e2e_check<'a>(
profile: &E2EProfile,
state: &mut E2EState,
payload: &'a [u8],
upper_header: [u8; 8],
) -> (E2ECheckStatus, &'a [u8]) {
let result = match (profile, state) {
(E2EProfile::Profile4(config), E2EState::Profile4(st)) => {
check_profile4(config, st, payload)
}
(E2EProfile::Profile5(config), E2EState::Profile5(st)) => {
check_profile5(config, st, payload)
}
(E2EProfile::Profile5WithHeader(config), E2EState::Profile5(st)) => {
check_profile5_with_header(config, st, payload, upper_header)
}
_ => return (E2ECheckStatus::BadArgument, payload),
};
let stripped = result.payload.unwrap_or(payload);
(result.status, stripped)
}
pub(crate) fn e2e_protect(
profile: &E2EProfile,
state: &mut E2EState,
payload: &[u8],
upper_header: [u8; 8],
output: &mut [u8],
) -> Result<usize, Error> {
match (profile, state) {
(E2EProfile::Profile4(config), E2EState::Profile4(st)) => {
protect_profile4(config, st, payload, output)
}
(E2EProfile::Profile5(config), E2EState::Profile5(st)) => {
protect_profile5(config, st, payload, output)
}
(E2EProfile::Profile5WithHeader(config), E2EState::Profile5(st)) => {
protect_profile5_with_header(config, st, payload, upper_header, output)
}
_ => unreachable!("E2EState is always created from E2EProfile"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_status_return_codes() {
assert_eq!(E2ECheckStatus::Unchecked.to_return_code(), 0);
assert_eq!(E2ECheckStatus::Ok.to_return_code(), 1);
assert_eq!(E2ECheckStatus::CrcError.to_return_code(), 2);
assert_eq!(E2ECheckStatus::Repeated.to_return_code(), 3);
assert_eq!(E2ECheckStatus::OkSomeLost.to_return_code(), 4);
assert_eq!(E2ECheckStatus::WrongSequence.to_return_code(), 5);
assert_eq!(E2ECheckStatus::BadArgument.to_return_code(), 6);
}
#[test]
fn test_profile4_roundtrip() {
let config = Profile4Config::new(0x1234_5678, 15);
let mut protect_state = Profile4State::new();
let mut check_state = Profile4State::new();
let payload = b"Test payload data";
let mut buf = [0u8; 256];
let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
let protected = &buf[..len];
assert_eq!(len, payload.len() + 12);
let result = check_profile4(&config, &mut check_state, protected);
assert_eq!(result.status, E2ECheckStatus::Ok);
assert_eq!(result.counter, Some(0));
assert_eq!(result.payload, Some(payload.as_slice()));
}
#[test]
fn test_profile5_roundtrip() {
let config = Profile5Config::new(0x1234, 20, 15);
let mut protect_state = Profile5State::new();
let mut check_state = Profile5State::new();
let mut payload = [0u8; 20];
payload[..17].copy_from_slice(b"Test payload data");
let mut buf = [0u8; 256];
let len = protect_profile5(&config, &mut protect_state, &payload, &mut buf).unwrap();
let protected = &buf[..len];
assert_eq!(len, payload.len() + 3);
let result = check_profile5(&config, &mut check_state, protected);
assert_eq!(result.status, E2ECheckStatus::Ok);
assert_eq!(result.counter, Some(0));
assert_eq!(result.payload, Some(payload.as_slice()));
}
#[test]
fn test_profile4_sequence_detection() {
let config = Profile4Config::new(0x1234_5678, 5);
let mut protect_state = Profile4State::new();
let mut check_state = Profile4State::new();
let payload = b"Test";
let mut buf1 = [0u8; 256];
let mut buf2 = [0u8; 256];
let len1 = protect_profile4(&config, &mut protect_state, payload, &mut buf1).unwrap();
let result1 = check_profile4(&config, &mut check_state, &buf1[..len1]);
assert_eq!(result1.status, E2ECheckStatus::Ok);
let len2 = protect_profile4(&config, &mut protect_state, payload, &mut buf2).unwrap();
let result2 = check_profile4(&config, &mut check_state, &buf2[..len2]);
assert_eq!(result2.status, E2ECheckStatus::Ok);
let result3 = check_profile4(&config, &mut check_state, &buf1[..len1]);
assert!(matches!(
result3.status,
E2ECheckStatus::Repeated | E2ECheckStatus::WrongSequence
));
}
#[test]
fn test_profile4_some_lost_detection() {
let config = Profile4Config::new(0x1234_5678, 5);
let mut protect_state = Profile4State::new();
let mut check_state = Profile4State::new();
let payload = b"Test";
let mut buf = [0u8; 256];
let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
let result1 = check_profile4(&config, &mut check_state, &buf[..len]);
assert_eq!(result1.status, E2ECheckStatus::Ok);
protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
let result4 = check_profile4(&config, &mut check_state, &buf[..len]);
assert_eq!(result4.status, E2ECheckStatus::OkSomeLost);
}
#[test]
fn test_profile4_wrong_sequence_detection() {
let config = Profile4Config::new(0x1234_5678, 2);
let mut protect_state = Profile4State::new();
let mut check_state = Profile4State::new();
let payload = b"Test";
let mut buf = [0u8; 256];
let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
let result1 = check_profile4(&config, &mut check_state, &buf[..len]);
assert_eq!(result1.status, E2ECheckStatus::Ok);
for _ in 0..5 {
protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
}
let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
let result = check_profile4(&config, &mut check_state, &buf[..len]);
assert_eq!(result.status, E2ECheckStatus::WrongSequence);
}
#[test]
fn test_profile4_crc_error() {
let config = Profile4Config::new(0x1234_5678, 15);
let mut protect_state = Profile4State::new();
let mut check_state = Profile4State::new();
let payload = b"Test";
let mut buf = [0u8; 256];
let len = protect_profile4(&config, &mut protect_state, payload, &mut buf).unwrap();
buf[8] ^= 0xFF;
let result = check_profile4(&config, &mut check_state, &buf[..len]);
assert_eq!(result.status, E2ECheckStatus::CrcError);
}
#[test]
fn test_profile5_crc_error() {
let config = Profile5Config::new(0x1234, 20, 15);
let mut protect_state = Profile5State::new();
let mut check_state = Profile5State::new();
let mut payload = [0u8; 20];
payload[..4].copy_from_slice(b"Test");
let mut buf = [0u8; 256];
let len = protect_profile5(&config, &mut protect_state, &payload, &mut buf).unwrap();
buf[1] ^= 0xFF;
let result = check_profile5(&config, &mut check_state, &buf[..len]);
assert_eq!(result.status, E2ECheckStatus::CrcError);
}
#[test]
fn test_profile4_bad_argument_short_message() {
let config = Profile4Config::new(0x1234_5678, 15);
let mut check_state = Profile4State::new();
let short_message = [0u8; 8];
let result = check_profile4(&config, &mut check_state, &short_message);
assert_eq!(result.status, E2ECheckStatus::BadArgument);
}
#[test]
fn test_profile5_bad_argument_short_message() {
let config = Profile5Config::new(0x1234, 20, 15);
let mut check_state = Profile5State::new();
let short_message = [0u8; 2];
let result = check_profile5(&config, &mut check_state, &short_message);
assert_eq!(result.status, E2ECheckStatus::BadArgument);
}
#[cfg(feature = "std")]
#[test]
fn test_check_result_to_owned_payload() {
let data = b"hello";
let result = E2ECheckResult::success(E2ECheckStatus::Ok, 0, data);
let owned = result.to_owned_payload();
assert_eq!(owned, Some(b"hello".to_vec()));
let err_result = E2ECheckResult::error(E2ECheckStatus::CrcError);
assert_eq!(err_result.to_owned_payload(), None);
}
#[test]
fn test_e2e_key_from_message_id() {
let mid = crate::protocol::MessageId::new_from_service_and_method(0x1234, 0x0001);
let key = E2EKey::from_message_id(mid);
assert_eq!(key.service_id, 0x1234);
assert_eq!(key.method_or_event_id, 0x0001);
}
}