use core::time::Duration;
use iceoryx2_bb_container::semantic_string::SemanticString;
use iceoryx2_bb_elementary::{lazy_singleton::*, CallbackProgression};
use iceoryx2_bb_posix::{
file::{FileBuilder, FileOpenError},
shared_memory::AccessMode,
system_configuration::get_global_config_path,
};
use iceoryx2_bb_system_types::file_name::FileName;
use iceoryx2_bb_system_types::file_path::FilePath;
use iceoryx2_bb_system_types::path::Path;
use serde::{Deserialize, Serialize};
use iceoryx2_bb_log::{debug, fail, fatal_panic, info, trace, warn};
use crate::port::unable_to_deliver_strategy::UnableToDeliverStrategy;
use iceoryx2_pal_configuration::settings::ICEORYX2_ROOT_PATH;
const DEFAULT_CONFIG_FILE_NAME: &[u8] = b"iceoryx2.toml";
const RELATIVE_LOCAL_CONFIG_PATH: &[u8] = b"config";
const RELATIVE_CONFIG_FILE_PATH: &[u8] = b"iceoryx2";
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
enum ConfigIterationFailure {
UnableToAcquireCurrentUserDetails,
TooLongUserConfigDirectory,
ConfigDirectoryNotAvailable,
}
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub enum ConfigCreationError {
FailedToReadConfigFileContents,
UnableToDeserializeContents,
InsufficientPermissions,
ConfigFileDoesNotExist,
UnableToOpenConfigFile,
}
impl core::fmt::Display for ConfigCreationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "ConfigCreationError::{self:?}")
}
}
impl core::error::Error for ConfigCreationError {}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[serde(default)]
pub struct Service {
pub directory: Path,
pub data_segment_suffix: FileName,
pub static_config_storage_suffix: FileName,
pub dynamic_config_storage_suffix: FileName,
pub creation_timeout: Duration,
pub connection_suffix: FileName,
pub event_connection_suffix: FileName,
pub blackboard_mgmt_suffix: FileName,
pub blackboard_data_suffix: FileName,
}
impl Default for Service {
fn default() -> Self {
Self {
directory: Path::new(b"services").unwrap(),
data_segment_suffix: FileName::new(b".data").unwrap(),
static_config_storage_suffix: FileName::new(b".service").unwrap(),
dynamic_config_storage_suffix: FileName::new(b".dynamic").unwrap(),
creation_timeout: Duration::from_millis(500),
connection_suffix: FileName::new(b".connection").unwrap(),
event_connection_suffix: FileName::new(b".event").unwrap(),
blackboard_mgmt_suffix: FileName::new(b".blackboard_mgmt").unwrap(),
blackboard_data_suffix: FileName::new(b".blackboard_data").unwrap(),
}
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[serde(default)]
pub struct Node {
pub directory: Path,
pub monitor_suffix: FileName,
pub static_config_suffix: FileName,
pub service_tag_suffix: FileName,
pub cleanup_dead_nodes_on_creation: bool,
pub cleanup_dead_nodes_on_destruction: bool,
}
impl Default for Node {
fn default() -> Self {
Self {
directory: Path::new(b"nodes").unwrap(),
monitor_suffix: FileName::new(b".node_monitor").unwrap(),
static_config_suffix: FileName::new(b".details").unwrap(),
service_tag_suffix: FileName::new(b".service_tag").unwrap(),
cleanup_dead_nodes_on_creation: true,
cleanup_dead_nodes_on_destruction: true,
}
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[serde(default)]
pub struct Global {
root_path: Path,
pub prefix: FileName,
pub service: Service,
pub node: Node,
}
impl Default for Global {
fn default() -> Self {
Self {
root_path: Path::new(ICEORYX2_ROOT_PATH).unwrap(),
prefix: FileName::new(b"iox2_").unwrap(),
service: Service::default(),
node: Node::default(),
}
}
}
impl Global {
pub fn service_dir(&self) -> Path {
let mut path = self.root_path().clone();
path.add_path_entry(&self.service.directory).unwrap();
path
}
pub fn node_dir(&self) -> Path {
let mut path = self.root_path().clone();
path.add_path_entry(&self.node.directory).unwrap();
path
}
pub fn root_path(&self) -> &Path {
&self.root_path
}
pub fn set_root_path(&mut self, value: &Path) {
self.root_path = value.clone();
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[serde(default)]
pub struct Defaults {
pub publish_subscribe: PublishSubscribe,
pub event: Event,
pub request_response: RequestResonse,
pub blackboard: Blackboard,
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[serde(default)]
pub struct PublishSubscribe {
pub max_subscribers: usize,
pub max_publishers: usize,
pub max_nodes: usize,
pub subscriber_max_buffer_size: usize,
pub subscriber_max_borrowed_samples: usize,
pub publisher_max_loaned_samples: usize,
pub publisher_history_size: usize,
pub enable_safe_overflow: bool,
pub unable_to_deliver_strategy: UnableToDeliverStrategy,
pub subscriber_expired_connection_buffer: usize,
}
impl Default for PublishSubscribe {
fn default() -> Self {
Self {
max_subscribers: 8,
max_publishers: 2,
max_nodes: 20,
publisher_history_size: 0,
subscriber_max_buffer_size: 2,
subscriber_max_borrowed_samples: 2,
publisher_max_loaned_samples: 2,
enable_safe_overflow: true,
unable_to_deliver_strategy: UnableToDeliverStrategy::Block,
subscriber_expired_connection_buffer: 128,
}
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[serde(default)]
pub struct Event {
pub max_listeners: usize,
pub max_notifiers: usize,
pub max_nodes: usize,
pub event_id_max_value: usize,
pub deadline: Option<Duration>,
pub notifier_created_event: Option<usize>,
pub notifier_dropped_event: Option<usize>,
pub notifier_dead_event: Option<usize>,
}
impl Default for Event {
fn default() -> Self {
Self {
max_listeners: 16,
max_notifiers: 16,
max_nodes: 36,
event_id_max_value: 255,
deadline: None,
notifier_created_event: None,
notifier_dropped_event: None,
notifier_dead_event: None,
}
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[serde(default)]
pub struct RequestResonse {
pub enable_safe_overflow_for_requests: bool,
pub enable_safe_overflow_for_responses: bool,
pub max_active_requests_per_client: usize,
pub max_response_buffer_size: usize,
pub max_servers: usize,
pub max_clients: usize,
pub max_nodes: usize,
pub max_borrowed_responses_per_pending_response: usize,
pub max_loaned_requests: usize,
pub server_max_loaned_responses_per_request: usize,
pub client_unable_to_deliver_strategy: UnableToDeliverStrategy,
pub server_unable_to_deliver_strategy: UnableToDeliverStrategy,
pub client_expired_connection_buffer: usize,
pub enable_fire_and_forget_requests: bool,
pub server_expired_connection_buffer: usize,
}
impl Default for RequestResonse {
fn default() -> Self {
Self {
enable_safe_overflow_for_requests: true,
enable_safe_overflow_for_responses: true,
max_active_requests_per_client: 4,
max_response_buffer_size: 2,
max_servers: 2,
max_clients: 8,
max_nodes: 20,
max_borrowed_responses_per_pending_response: 2,
max_loaned_requests: 2,
server_max_loaned_responses_per_request: 2,
client_unable_to_deliver_strategy: UnableToDeliverStrategy::Block,
server_unable_to_deliver_strategy: UnableToDeliverStrategy::Block,
client_expired_connection_buffer: 128,
server_expired_connection_buffer: 128,
enable_fire_and_forget_requests: true,
}
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[serde(default)]
pub struct Blackboard {
pub max_readers: usize,
pub max_nodes: usize,
}
impl Default for Blackboard {
fn default() -> Self {
Self {
max_readers: 8,
max_nodes: 20,
}
}
}
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[serde(default)]
pub struct Config {
pub global: Global,
pub defaults: Defaults,
}
static ICEORYX2_CONFIG: LazySingleton<Config> = LazySingleton::<Config>::new();
impl Config {
fn relative_local_config_path() -> Path {
fatal_panic!(from "Config::relative_local_config_path",
when Path::new(RELATIVE_LOCAL_CONFIG_PATH),
"This should never happen! The relative local config path contains invalid symbols.")
}
pub fn default_config_file_name() -> FileName {
fatal_panic!(from "Config::default_config_file",
when FileName::new(DEFAULT_CONFIG_FILE_NAME),
"This should never happen! The default config file name contains invalid symbols.")
}
pub fn default_config_file_path() -> FilePath {
fatal_panic!(from "Config::default_config_file_path",
when FilePath::from_path_and_file(&Self::relative_local_config_path(), &Self::default_config_file_name()),
"This should never happen! The default config file path contains invalid symbols.")
}
pub fn relative_config_path() -> Path {
fatal_panic!(from "Config::relative_config_path",
when Path::new(RELATIVE_CONFIG_FILE_PATH),
"This should never happen! The relative config path contains invalid symbols.")
}
pub fn default_user_config_file_path() -> FilePath {
fatal_panic!(from "Config::default_config_file_path",
when FilePath::from_path_and_file(&Self::relative_config_path(), &Self::default_config_file_name()),
"This should never happen! The default config file path contains invalid symbols.")
}
fn load_user_config_path(origin: &str, msg: &str) -> Result<FilePath, ConfigIterationFailure> {
let user = fail!(from origin,
when iceoryx2_bb_posix::user::User::from_self(),
with ConfigIterationFailure::UnableToAcquireCurrentUserDetails,
"{} since the current user details could not be acquired.", msg);
match user.details() {
Some(details) => {
let mut user_config = details.config_dir().clone();
fail!(from origin,
when user_config.add_path_entry(&Self::relative_config_path()),
with ConfigIterationFailure::TooLongUserConfigDirectory,
"{} since the resulting user config directory would be too long.", msg);
let user_config = fail!(from origin,
when FilePath::from_path_and_file(&user_config, &Self::default_config_file_name()),
with ConfigIterationFailure::TooLongUserConfigDirectory,
"{} since the resulting user config directory would be too long.", msg);
Ok(user_config)
}
None => {
fail!(from origin,
with ConfigIterationFailure::ConfigDirectoryNotAvailable,
"{} since the user config directory is not available on the current platform.",
msg);
}
}
}
fn load_global_config_path(
origin: &str,
msg: &str,
) -> Result<FilePath, ConfigIterationFailure> {
let mut global_config = get_global_config_path();
fail!(from origin,
when global_config.add_path_entry(&Self::relative_config_path()),
with ConfigIterationFailure::TooLongUserConfigDirectory,
"{} since the resulting global config directory would be too long.", msg);
let global_config = fail!(from origin,
when FilePath::from_path_and_file(&global_config, &Self::default_config_file_name()),
with ConfigIterationFailure::TooLongUserConfigDirectory,
"{} since the resulting global config directory would be too long.", msg);
Ok(global_config)
}
fn iterate_over_config_files<F: FnMut(FilePath) -> CallbackProgression>(
mut callback: F,
) -> Result<(), ConfigIterationFailure> {
let msg = "Unable to consider all possible config file paths";
let origin = "Config::iterate_over_config_files";
let local_project_config = Self::default_config_file_path();
if callback(local_project_config) == CallbackProgression::Stop {
return Ok(());
}
if let Ok(user_config) = Self::load_user_config_path(origin, msg) {
if callback(user_config) == CallbackProgression::Stop {
return Ok(());
}
}
if let Ok(global_config) = Self::load_global_config_path(origin, msg) {
if callback(global_config) == CallbackProgression::Stop {
return Ok(());
}
}
Ok(())
}
pub fn from_file(config_file: &FilePath) -> Result<Config, ConfigCreationError> {
let msg = "Failed to create config";
let mut new_config = Self::default();
let file = match FileBuilder::new(config_file).open_existing(AccessMode::Read) {
Ok(file) => file,
Err(FileOpenError::InsufficientPermissions) => {
fail!(from new_config,
with ConfigCreationError::InsufficientPermissions,
"{} since the config file \"{}\" could not be opened due to insufficient permissions.",
msg, config_file);
}
Err(FileOpenError::FileDoesNotExist) => {
fail!(from new_config,
with ConfigCreationError::ConfigFileDoesNotExist,
"{} since the config file \"{}\" does not exist.",
msg, config_file);
}
Err(e) => {
fail!(from new_config,
with ConfigCreationError::UnableToOpenConfigFile,
"{} since the config file \"{}\" could not be open due to an internal error ({:?}).",
msg, config_file, e);
}
};
let mut contents = String::new();
fail!(from new_config, when file.read_to_string(&mut contents),
with ConfigCreationError::FailedToReadConfigFileContents,
"{} since the config file contents could not be read.", msg);
match toml::from_str(&contents) {
Ok(v) => new_config = v,
Err(e) => {
fail!(from new_config, with ConfigCreationError::UnableToDeserializeContents,
"{} since the contents could not be deserialized ({}).", msg, e);
}
}
trace!(from new_config, "Loaded.");
Ok(new_config)
}
pub fn setup_global_config_from_file(
config_file: &FilePath,
) -> Result<&'static Config, ConfigCreationError> {
if ICEORYX2_CONFIG.is_initialized() {
return Ok(ICEORYX2_CONFIG.get());
}
if !ICEORYX2_CONFIG.set_value(Config::from_file(config_file)?) {
warn!(
from ICEORYX2_CONFIG.get(),
"Configuration already loaded and set up, cannot load another one. This may happen when this function is called from multiple threads."
);
return Ok(ICEORYX2_CONFIG.get());
}
trace!(from ICEORYX2_CONFIG.get(), "Set as global config.");
Ok(ICEORYX2_CONFIG.get())
}
pub fn global_config() -> &'static Config {
let origin = "Config::global_config()";
if !ICEORYX2_CONFIG.is_initialized() {
let mut is_config_file_set = false;
if let Err(e) = Self::iterate_over_config_files(|config_file_path| {
match Config::setup_global_config_from_file(&config_file_path) {
Ok(_) => {
info!(from origin, "Using config file at \"{}\"", config_file_path);
is_config_file_set = true;
CallbackProgression::Stop
}
Err(ConfigCreationError::ConfigFileDoesNotExist) => {
debug!(from origin, "No config file found at \"{}\"", config_file_path);
CallbackProgression::Continue
}
Err(e) => {
warn!(from origin,
"Config file found \"{}\" but a failure occurred ({:?}) while reading the content.",
config_file_path, e);
CallbackProgression::Continue
}
}
}) {
warn!(from origin,
"A failure occurred ({:?}) while looking up the available config files.", e);
}
if !is_config_file_set {
warn!(from origin,
"No config file was loaded, a config with default values will be used.");
ICEORYX2_CONFIG.set_value(Config::default());
}
}
ICEORYX2_CONFIG.get()
}
}