extern crate alloc;
use crate::config;
use crate::constants::{MAX_BLACKBOARD_KEY_ALIGNMENT, MAX_BLACKBOARD_KEY_SIZE};
use crate::service::builder::{self, ServiceCreateError};
use crate::service::config_scheme::{blackboard_data_config, blackboard_mgmt_config};
use crate::service::naming_scheme::blackboard_name;
use crate::service::resource::RemoveStaleResourcesError;
use crate::service::static_config::StaticConfig;
use crate::service::{
self, resource::ServiceResource, static_config::message_type_details::TypeDetail,
};
use alloc::format;
use alloc::sync::Arc;
use core::alloc::Layout;
use core::mem::MaybeUninit;
use core::{fmt::Debug, ptr::NonNull};
use iceoryx2_bb_concurrency::atomic::AtomicU64;
use iceoryx2_bb_container::queue::RelocatableContainer;
use iceoryx2_bb_container::string::String;
use iceoryx2_bb_container::vector::Vector;
use iceoryx2_bb_container::{flatmap::RelocatableFlatMap, vector::RelocatableVec};
use iceoryx2_bb_derive_macros::ZeroCopySend;
use iceoryx2_bb_elementary::static_assert_align_of;
use iceoryx2_bb_elementary_traits::{
testing::abandonable::Abandonable, zero_copy_send::ZeroCopySend,
};
use iceoryx2_bb_memory::bump_allocator::{Allocate, BumpAllocator};
use iceoryx2_bb_posix::file::AccessMode;
use iceoryx2_cal::dynamic_storage::{DynamicStorage, DynamicStorageBuilder};
use iceoryx2_cal::event::NamedConceptMgmt;
use iceoryx2_cal::named_concept::{NamedConceptBuilder, NamedConceptRemoveError};
use iceoryx2_cal::shared_memory::SharedMemory;
use iceoryx2_cal::shared_memory::SharedMemoryBuilder;
use iceoryx2_log::fail;
use iceoryx2_log::{error, trace};
#[doc(hidden)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum KeyMemoryError {
ValueTooLarge,
ValueAlignmentTooLarge,
}
#[doc(hidden)]
#[repr(C)]
#[repr(align(8))]
#[derive(Debug, Clone, Copy, Eq, PartialEq, ZeroCopySend)]
pub struct KeyMemory<const CAPACITY: usize> {
pub data: [u8; CAPACITY],
}
impl<const CAPACITY: usize> KeyMemory<CAPACITY> {
pub fn try_from<T: Copy>(value: &T) -> Result<Self, KeyMemoryError> {
static_assert_align_of!(KeyMemory<1>, MAX_BLACKBOARD_KEY_ALIGNMENT);
let origin = "KeyMemory::try_from()";
let msg = "Unable to create KeyMemory";
if size_of::<T>() > CAPACITY {
fail!(from origin, with KeyMemoryError::ValueTooLarge,
"{} since the passed value is too large. Its size must be <= {}.", msg, CAPACITY);
}
if align_of::<T>() > MAX_BLACKBOARD_KEY_ALIGNMENT {
fail!(from origin, with KeyMemoryError::ValueAlignmentTooLarge,
"{} since the alignment of the passed value is too large. The alignment must be <= {}.",
msg, MAX_BLACKBOARD_KEY_ALIGNMENT);
}
let mut new_self = Self {
data: [0; CAPACITY],
};
unsafe { core::ptr::copy_nonoverlapping(value, new_self.data.as_mut_ptr() as *mut T, 1) };
Ok(new_self)
}
pub unsafe fn try_from_ptr(ptr: *const u8, key_layout: Layout) -> Result<Self, KeyMemoryError> {
static_assert_align_of!(KeyMemory<1>, MAX_BLACKBOARD_KEY_ALIGNMENT);
let origin = "KeyMemory::try_from_ptr()";
let msg = "Unable to create KeyMemory";
if key_layout.size() > CAPACITY {
fail!(from origin, with KeyMemoryError::ValueTooLarge,
"{} since the passed key size is too large. The size must be <= {}.", msg, CAPACITY);
}
if key_layout.align() > MAX_BLACKBOARD_KEY_ALIGNMENT {
fail!(from origin, with KeyMemoryError::ValueAlignmentTooLarge,
"{} since the alignment of the passed key is too large. The alignment must be <= {}.",
msg, MAX_BLACKBOARD_KEY_ALIGNMENT);
}
let mut new_self = Self {
data: [0; CAPACITY],
};
unsafe {
core::ptr::copy_nonoverlapping(ptr, new_self.data.as_mut_ptr(), key_layout.size())
};
Ok(new_self)
}
pub fn default_key_eq_comparison<T: Eq>(lhs: *const u8, rhs: *const u8) -> bool {
let lhs = unsafe { *(lhs as *const KeyMemory<CAPACITY>) };
let rhs = unsafe { *(rhs as *const KeyMemory<CAPACITY>) };
unsafe { *(lhs.data.as_ptr() as *const T) == *(rhs.data.as_ptr() as *const T) }
}
pub fn key_eq_comparison<F: Fn(*const u8, *const u8) -> bool>(
lhs: *const u8,
rhs: *const u8,
eq_func: &F,
) -> bool {
let lhs = unsafe { *(lhs as *const KeyMemory<CAPACITY>) };
let rhs = unsafe { *(rhs as *const KeyMemory<CAPACITY>) };
eq_func(lhs.data.as_ptr(), rhs.data.as_ptr())
}
}
#[repr(C)]
#[derive(Debug, ZeroCopySend)]
pub(crate) struct Entry {
pub(crate) type_details: TypeDetail,
pub(crate) offset: AtomicU64,
}
#[repr(C)]
#[derive(Debug, ZeroCopySend)]
pub(crate) struct Mgmt {
pub(crate) map: RelocatableFlatMap<KeyMemory<MAX_BLACKBOARD_KEY_SIZE>, usize>,
pub(crate) entries: RelocatableVec<Entry>,
}
pub(crate) struct BlackboardResources<ServiceType: service::Service> {
pub(crate) mgmt: ServiceType::BlackboardMgmt<Mgmt>,
pub(crate) data: ServiceType::BlackboardPayload,
pub(crate) key_eq_func: Arc<dyn Fn(*const u8, *const u8) -> bool + Send + Sync>,
}
impl<ServiceType: service::Service> Debug for BlackboardResources<ServiceType> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"BlackboardResource<{}> {{ mgmt: {:?}, data: {:?} }}",
core::any::type_name::<ServiceType>(),
self.mgmt,
self.data,
)
}
}
impl<ServiceType: service::Service> Abandonable for BlackboardResources<ServiceType> {
unsafe fn abandon_in_place(mut this: NonNull<Self>) {
let this = unsafe { this.as_mut() };
unsafe {
ServiceType::BlackboardMgmt::<Mgmt>::abandon_in_place(NonNull::from_mut(&mut this.mgmt))
};
unsafe {
ServiceType::BlackboardPayload::abandon_in_place(NonNull::from_mut(&mut this.data))
};
}
}
impl<ServiceType: service::Service> ServiceResource for BlackboardResources<ServiceType> {
type Config = builder::blackboard::BuilderConfig<ServiceType>;
fn acquire_ownership(&self) {
self.data.acquire_ownership();
self.mgmt.acquire_ownership();
}
unsafe fn remove_stale_resources(
config: &config::Config,
static_config: &StaticConfig,
) -> Result<(), RemoveStaleResourcesError> {
let origin = format!(
"BlackboardResource<{}>::remove_stale_resources()",
core::any::type_name::<ServiceType>()
);
let msg = "Unable to remove stale blackboard resources";
let service_id = static_config.unique_service_id();
let blackboard_name = blackboard_name(service_id);
let blackboard_payload_config = blackboard_data_config::<ServiceType>(config);
let blackboard_mgmt_name = static_config.blackboard().type_details.type_name;
match unsafe {
<ServiceType::BlackboardPayload as NamedConceptMgmt>::remove_cfg(
&blackboard_name,
&blackboard_payload_config,
)
} {
Ok(true) => {
trace!(from origin, "Remove blackboard payload segment.");
}
Ok(false) => {}
Err(NamedConceptRemoveError::InsufficientPermissions) => {
fail!(from origin, with RemoveStaleResourcesError::InsufficientPermissions,
"{msg} since the blackboard payload segment could not be removed due to insufficient permissions.");
}
Err(NamedConceptRemoveError::Interrupt) => {
fail!(from origin, with RemoveStaleResourcesError::InterruptedBySignal,
"{msg} since the removal of the blackboard payload segment was interrupted by a signal.");
}
Err(NamedConceptRemoveError::InternalError) => {
fail!(from origin, with RemoveStaleResourcesError::InternalFailure,
"{msg} since the blackboard payload segment cannot be removed due to an internal failure.");
}
}
let mut blackboard_mgmt_config =
crate::service::config_scheme::blackboard_mgmt_config::<ServiceType, u64>(config);
unsafe {
<ServiceType::BlackboardMgmt<u64> as DynamicStorage<u64>>::__internal_set_type_name_in_config(
&mut blackboard_mgmt_config,
blackboard_mgmt_name.as_str(),
)
};
match unsafe {
<ServiceType::BlackboardMgmt<u64> as NamedConceptMgmt>::remove_cfg(
&blackboard_name,
&blackboard_mgmt_config,
)
} {
Ok(true) => {
trace!(from origin, "Remove blackboard mgmt segment.");
Ok(())
}
Ok(false) => Ok(()),
Err(NamedConceptRemoveError::InsufficientPermissions) => {
fail!(from origin, with RemoveStaleResourcesError::InsufficientPermissions,
"{msg} since the blackboard mgmt segment cannot be removed due to insufficient permissions.");
}
Err(NamedConceptRemoveError::Interrupt) => {
fail!(from origin, with RemoveStaleResourcesError::InterruptedBySignal,
"{msg} since the removal of the blackboard mgmt segment was interrupted by a signal.");
}
Err(NamedConceptRemoveError::InternalError) => {
fail!(from origin, with RemoveStaleResourcesError::InternalFailure,
"{msg} since the blackboard mgmt segment cannot be removed due to an internal failure.");
}
}
}
fn open(
static_config: &StaticConfig,
resource_config: &Self::Config,
) -> Result<Self, builder::ServiceOpenError> {
let origin = format!(
"BlackboardResource<{}>::open()",
core::any::type_name::<ServiceType>()
);
let msg = "Failed to open blackboard service resources";
let shared_node = &resource_config.base.shared_node;
let blackboard_config = resource_config.config_details();
let key_eq_func = resource_config.key_eq_func.clone();
let name = blackboard_name(static_config.unique_service_id());
let mut mgmt_config = blackboard_mgmt_config::<ServiceType, Mgmt>(shared_node.config());
let mgmt_name = blackboard_config.type_details.type_name.as_str();
unsafe {
<ServiceType::BlackboardMgmt<Mgmt> as DynamicStorage<
Mgmt,
>>::__internal_set_type_name_in_config(
&mut mgmt_config, mgmt_name
)
};
let mgmt_storage = fail!(from origin, when
<ServiceType::BlackboardMgmt<Mgmt> as DynamicStorage<Mgmt>
>::Builder::new(&name)
.config(&mgmt_config)
.has_ownership(false)
.open(AccessMode::ReadWrite),
with builder::ServiceOpenError::ServiceInCorruptedState,
"{} since the blackboard management information could not be opened. This could indicate a corrupted system.", msg);
let shm_config = blackboard_data_config::<ServiceType>(shared_node.config());
let payload_shm = match <<ServiceType::BlackboardPayload as SharedMemory<
iceoryx2_cal::shm_allocator::bump_allocator::BumpAllocator,
>>::Builder as NamedConceptBuilder<ServiceType::BlackboardPayload>>::new(
&name
)
.config(&shm_config)
.open(AccessMode::ReadWrite)
{
Ok(v) => v,
Err(_) => {
fail!(from origin, with builder::ServiceOpenError::ServiceInCorruptedState,
"{} since the blackboard payload data segment could not be opened. This could indicate a corrupted system.",
msg);
}
};
Ok(BlackboardResources {
mgmt: mgmt_storage,
data: payload_shm,
key_eq_func,
})
}
fn create(
service_config: &StaticConfig,
resource_config: &Self::Config,
) -> Result<BlackboardResources<ServiceType>, ServiceCreateError> {
let origin = format!(
"BlackboardResource<{}>::create()",
core::any::type_name::<ServiceType>()
);
let msg = "Failed to create blackboard service resources";
let blackboard_config = *resource_config.config_details();
let key_eq_func = resource_config.key_eq_func.clone();
let builder_internals = resource_config.internals.as_slice();
let shared_node = &resource_config.base.shared_node;
let name = blackboard_name(service_config.unique_service_id());
let shm_config = blackboard_data_config::<ServiceType>(shared_node.config());
let mut payload_size = 0;
for i in builder_internals.iter() {
payload_size += i.internal_value_size + i.internal_value_alignment - 1;
}
let payload_shm = match <<ServiceType::BlackboardPayload as SharedMemory<
iceoryx2_cal::shm_allocator::bump_allocator::BumpAllocator,
>>::Builder as NamedConceptBuilder<ServiceType::BlackboardPayload>>::new(
&name
)
.config(&shm_config)
.has_ownership(true)
.size(payload_size)
.create(&iceoryx2_cal::shared_memory::bump_allocator::Config::default())
{
Ok(v) => v,
Err(_) => {
fail!(from origin, with ServiceCreateError::ServiceInCorruptedState,
"{} since the blackboard payload data segment could not be created. This could indicate a corrupted system.",
msg);
}
};
let capacity = builder_internals.len();
let mut mgmt_config = blackboard_mgmt_config::<ServiceType, Mgmt>(shared_node.config());
let mgmt_name = blackboard_config.type_details.type_name.as_str();
unsafe {
<ServiceType::BlackboardMgmt<Mgmt> as DynamicStorage::<
Mgmt,
>>::__internal_set_type_name_in_config(&mut mgmt_config, mgmt_name)
};
let mgmt_storage = fail!(from origin, when
<ServiceType::BlackboardMgmt<Mgmt> as DynamicStorage<Mgmt,
>>::Builder::new(&name)
.config(&mgmt_config)
.has_ownership(true)
.supplementary_size(RelocatableFlatMap::<KeyMemory<MAX_BLACKBOARD_KEY_SIZE>, usize>::const_memory_size(capacity)+RelocatableVec::<Entry>::const_memory_size(capacity))
.initializer(|mgmt: &mut MaybeUninit<Mgmt>, allocator: &mut BumpAllocator| {
mgmt.write(Mgmt {
map: unsafe { RelocatableFlatMap::<KeyMemory<MAX_BLACKBOARD_KEY_SIZE>, usize>::new_uninit(capacity) },
entries: unsafe { RelocatableVec::<Entry>::new_uninit(capacity) },
});
let mgmt = unsafe { mgmt.assume_init_mut() };
if unsafe {mgmt.map.init(allocator)}.is_err() || unsafe {mgmt.entries.init(allocator).is_err()} {
return false
}
for entry in builder_internals.iter() {
let mem = match payload_shm.allocate(unsafe { Layout::from_size_align_unchecked(entry.internal_value_size, entry.internal_value_alignment) })
{
Ok(m) => m,
Err(_) => {
error!(from origin, "Writing the value to the blackboard data segment failed.");
return false
}
};
(*entry.value_writer)(mem.data_ptr);
let res = mgmt.entries.push(Entry{type_details: entry.value_type_details, offset: AtomicU64::new(mem.offset.offset() as u64)});
if res.is_err() {
error!(from origin, "Writing the value offset to the blackboard management segment failed.");
return false
}
let res = unsafe {mgmt.map.__internal_insert(entry.key, mgmt.entries.len() - 1, &*key_eq_func)};
if res.is_err() {
error!(from origin, "Inserting the key-value pair into the blackboard management segment failed.");
return false
}
}
true})
.create(),
with ServiceCreateError::ServiceInCorruptedState, "{} since the blackboard management segment could not be created. This could indicate a corrupted system.",
msg);
Ok(BlackboardResources {
mgmt: mgmt_storage,
data: payload_shm,
key_eq_func,
})
}
}