use crate::clock::CompactTimestamp;
use crate::error::{CRDTError, CRDTResult};
use crate::memory::{MemoryConfig, NodeId};
use crate::traits::{BoundedCRDT, CRDT, RealTimeCRDT};
use core::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum ASILLevel {
QM = 0,
AsilA = 1,
AsilB = 2,
AsilC = 3,
AsilD = 4,
}
impl ASILLevel {
pub fn is_safety_critical(&self) -> bool {
*self != ASILLevel::QM
}
pub fn verification_level(&self) -> u8 {
match self {
ASILLevel::QM => 0,
ASILLevel::AsilA => 1,
ASILLevel::AsilB => 2,
ASILLevel::AsilC => 3,
ASILLevel::AsilD => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SafetyLevel {
Automotive(ASILLevel),
Industrial(u8),
Aerospace(u8),
Custom(u8),
}
impl SafetyLevel {
pub fn automotive(level: ASILLevel) -> Self {
SafetyLevel::Automotive(level)
}
pub fn priority(&self) -> u8 {
match self {
SafetyLevel::Automotive(asil) => *asil as u8,
SafetyLevel::Industrial(sil) => *sil,
SafetyLevel::Aerospace(dal) => *dal,
SafetyLevel::Custom(level) => *level,
}
}
pub fn is_safety_critical(&self) -> bool {
match self {
SafetyLevel::Automotive(asil) => asil.is_safety_critical(),
SafetyLevel::Industrial(sil) => *sil > 0,
SafetyLevel::Aerospace(dal) => *dal > 0,
SafetyLevel::Custom(level) => *level > 0,
}
}
}
#[derive(Debug, Clone)]
pub struct SafetyCRDT<T, C: MemoryConfig> {
value: Option<T>,
safety_level: SafetyLevel,
timestamp: CompactTimestamp,
node_id: NodeId,
local_node_id: NodeId,
local_safety_level: SafetyLevel,
_phantom: core::marker::PhantomData<C>,
}
impl<T, C: MemoryConfig> SafetyCRDT<T, C>
where
T: Clone + PartialEq,
{
pub fn new(node_id: NodeId, safety_level: SafetyLevel) -> Self {
Self {
value: None,
safety_level,
timestamp: CompactTimestamp::new(0),
node_id,
local_node_id: node_id,
local_safety_level: safety_level,
_phantom: core::marker::PhantomData,
}
}
pub fn set(&mut self, value: T, timestamp: u64) -> CRDTResult<()> {
let new_timestamp = CompactTimestamp::new(timestamp);
if !self.local_safety_level.is_safety_critical()
&& self.safety_level.is_safety_critical()
&& self.safety_level > self.local_safety_level
{
return Err(CRDTError::SafetyViolation);
}
let should_update = match self.local_safety_level.cmp(&self.safety_level) {
Ordering::Greater => true, Ordering::Equal => new_timestamp > self.timestamp, Ordering::Less => false, };
if should_update {
self.value = Some(value);
self.safety_level = self.local_safety_level;
self.timestamp = new_timestamp;
self.node_id = self.local_node_id;
}
Ok(())
}
pub fn get(&self) -> Option<&T> {
self.value.as_ref()
}
pub fn current_safety_level(&self) -> SafetyLevel {
self.safety_level
}
pub fn timestamp(&self) -> CompactTimestamp {
self.timestamp
}
pub fn value_node_id(&self) -> NodeId {
self.node_id
}
pub fn is_safety_critical(&self) -> bool {
self.safety_level.is_safety_critical()
}
pub fn verify_safety(&self) -> CRDTResult<()> {
if self.safety_level.priority() > 4 {
return Err(CRDTError::InvalidSafetyLevel);
}
if self.node_id as usize >= C::MAX_NODES {
return Err(CRDTError::InvalidNodeId);
}
Ok(())
}
}
impl<T, C: MemoryConfig> CRDT<C> for SafetyCRDT<T, C>
where
T: Clone + PartialEq + core::fmt::Debug,
{
type Error = CRDTError;
fn merge(&mut self, other: &Self) -> CRDTResult<()> {
if let Some(ref other_value) = other.value {
let should_merge = match self.safety_level.cmp(&other.safety_level) {
Ordering::Less => {
true
}
Ordering::Greater => {
false
}
Ordering::Equal => {
other.timestamp > self.timestamp
}
};
if should_merge {
self.value = Some(other_value.clone());
self.safety_level = other.safety_level;
self.timestamp = other.timestamp;
self.node_id = other.node_id;
}
}
Ok(())
}
fn eq(&self, other: &Self) -> bool {
self.value == other.value
&& self.safety_level == other.safety_level
&& self.timestamp == other.timestamp
&& self.node_id == other.node_id
}
fn size_bytes(&self) -> usize {
core::mem::size_of::<Self>()
}
fn validate(&self) -> CRDTResult<()> {
self.verify_safety()
}
fn state_hash(&self) -> u32 {
let mut hash = 0u32;
if let Some(ref value) = self.value {
let value_ptr = value as *const T as usize;
hash ^= value_ptr as u32;
}
hash ^= (self.safety_level.priority() as u32) << 24;
hash ^= (self.timestamp.as_u64() as u32) << 8;
hash ^= self.node_id as u32;
hash
}
fn can_merge(&self, _other: &Self) -> bool {
true
}
}
impl<T, C: MemoryConfig> BoundedCRDT<C> for SafetyCRDT<T, C>
where
T: Clone + PartialEq + core::fmt::Debug,
{
const MAX_SIZE_BYTES: usize = core::mem::size_of::<Self>();
const MAX_ELEMENTS: usize = 1;
fn memory_usage(&self) -> usize {
core::mem::size_of::<Self>()
}
fn element_count(&self) -> usize {
if self.value.is_some() { 1 } else { 0 }
}
fn compact(&mut self) -> CRDTResult<usize> {
Ok(0)
}
fn can_add_element(&self) -> bool {
true
}
}
impl<T, C: MemoryConfig> RealTimeCRDT<C> for SafetyCRDT<T, C>
where
T: Clone + PartialEq + core::fmt::Debug,
{
const MAX_MERGE_CYCLES: u32 = 50; const MAX_VALIDATE_CYCLES: u32 = 25;
const MAX_SERIALIZE_CYCLES: u32 = 30;
fn merge_bounded(&mut self, other: &Self) -> CRDTResult<()> {
self.merge(other)
}
fn validate_bounded(&self) -> CRDTResult<()> {
self.validate()
}
fn remaining_budget(&self) -> Option<u32> {
None
}
fn set_budget(&mut self, _cycles: u32) {
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory::DefaultConfig;
#[test]
fn test_asil_level_ordering() {
assert!(ASILLevel::AsilD > ASILLevel::AsilC);
assert!(ASILLevel::AsilC > ASILLevel::AsilB);
assert!(ASILLevel::AsilB > ASILLevel::AsilA);
assert!(ASILLevel::AsilA > ASILLevel::QM);
assert!(ASILLevel::AsilD.is_safety_critical());
assert!(!ASILLevel::QM.is_safety_critical());
}
#[test]
fn test_safety_level_priority() {
let qm = SafetyLevel::automotive(ASILLevel::QM);
let asil_d = SafetyLevel::automotive(ASILLevel::AsilD);
assert!(asil_d > qm);
assert!(asil_d.is_safety_critical());
assert!(!qm.is_safety_critical());
}
#[test]
fn test_safety_crdt_creation() {
let crdt =
SafetyCRDT::<u32, DefaultConfig>::new(1, SafetyLevel::automotive(ASILLevel::AsilD));
assert_eq!(crdt.get(), None);
assert_eq!(
crdt.current_safety_level(),
SafetyLevel::automotive(ASILLevel::AsilD)
);
assert!(crdt.is_safety_critical());
}
#[test]
fn test_safety_prioritized_merge() {
let mut asil_d_crdt =
SafetyCRDT::<u32, DefaultConfig>::new(1, SafetyLevel::automotive(ASILLevel::AsilD));
asil_d_crdt.set(100, 1000).unwrap();
let mut qm_crdt =
SafetyCRDT::<u32, DefaultConfig>::new(2, SafetyLevel::automotive(ASILLevel::QM));
qm_crdt.set(50, 2000).unwrap();
asil_d_crdt.merge(&qm_crdt).unwrap();
assert_eq!(asil_d_crdt.get(), Some(&100));
qm_crdt.merge(&asil_d_crdt).unwrap();
assert_eq!(qm_crdt.get(), Some(&100)); }
#[test]
fn test_same_safety_level_timestamp_ordering() {
let mut crdt1 =
SafetyCRDT::<u32, DefaultConfig>::new(1, SafetyLevel::automotive(ASILLevel::AsilC));
crdt1.set(100, 1000).unwrap();
let mut crdt2 =
SafetyCRDT::<u32, DefaultConfig>::new(2, SafetyLevel::automotive(ASILLevel::AsilC));
crdt2.set(200, 2000).unwrap();
crdt1.merge(&crdt2).unwrap();
assert_eq!(crdt1.get(), Some(&200)); }
#[test]
fn test_safety_verification() {
let crdt =
SafetyCRDT::<u32, DefaultConfig>::new(1, SafetyLevel::automotive(ASILLevel::AsilD));
assert!(crdt.verify_safety().is_ok());
assert!(crdt.validate().is_ok());
}
#[test]
fn test_bounded_crdt_implementation() {
let mut crdt =
SafetyCRDT::<u32, DefaultConfig>::new(1, SafetyLevel::automotive(ASILLevel::AsilB));
assert_eq!(crdt.element_count(), 0);
assert!(crdt.can_add_element());
crdt.set(42, 1000).unwrap();
assert_eq!(crdt.element_count(), 1);
assert!(crdt.memory_usage() > 0);
}
#[test]
fn test_real_time_crdt_implementation() {
let mut crdt1 =
SafetyCRDT::<u32, DefaultConfig>::new(1, SafetyLevel::automotive(ASILLevel::AsilD));
let crdt2 =
SafetyCRDT::<u32, DefaultConfig>::new(2, SafetyLevel::automotive(ASILLevel::AsilC));
assert!(crdt1.merge_bounded(&crdt2).is_ok());
assert!(crdt1.validate_bounded().is_ok());
}
}