diff --git a/rclrs/src/dynamic_message.rs b/rclrs/src/dynamic_message.rs index 9bbcf7e75..f3e5e3f5c 100644 --- a/rclrs/src/dynamic_message.rs +++ b/rclrs/src/dynamic_message.rs @@ -6,14 +6,18 @@ //! The central type of this module is [`DynamicMessage`]. use std::fmt::{self, Display}; +use std::ops::Deref; use std::path::PathBuf; use std::sync::Arc; +use rosidl_runtime_rs::RmwMessage; use crate::rcl_bindings::rosidl_typesupport_introspection_c__MessageMembers_s as rosidl_message_members_t; use crate::rcl_bindings::*; mod error; +mod message_structure; pub use error::*; +pub use message_structure::*; /// Factory for constructing messages in a certain package dynamically. /// @@ -49,17 +53,33 @@ struct MessageTypeName { /// can be used as a factory to create message instances. #[derive(Clone)] pub struct DynamicMessageMetadata { - #[allow(dead_code)] message_type: MessageTypeName, // The library needs to be kept loaded in order to keep the type_support_ptr valid. + // This is the introspection type support library, not the regular one. #[allow(dead_code)] introspection_type_support_library: Arc, - #[allow(dead_code)] type_support_ptr: *const rosidl_message_type_support_t, - #[allow(dead_code)] + structure: MessageStructure, fini_function: unsafe extern "C" fn(*mut std::os::raw::c_void), } +/// A message whose type is not known at compile-time. +/// +/// This type allows inspecting the structure of the message as well as the +/// values contained in it. +/// It also allows _modifying_ the values, but not the structure, because +/// even a dynamic message must always correspond to a specific message type. +// There is no clone function yet, we need to add that in rosidl. +pub struct DynamicMessage { + metadata: DynamicMessageMetadata, + // This is aligned to the maximum possible alignment of a message (8) + // by the use of a special allocation function. + storage: Box<[u8]>, + // This type allows moving the message contents out into another message, + // in which case the drop impl is not responsible for calling fini anymore + needs_fini: bool, +} + // ========================= impl for DynamicMessagePackage ========================= /// This is an analogue of rclcpp::get_typesupport_library. @@ -169,6 +189,8 @@ impl DynamicMessagePackage { let message_members: &rosidl_message_members_t = // SAFETY: The data pointer is supposed to be always valid. unsafe { &*(type_support.data as *const rosidl_message_members_t) }; + // SAFETY: The message members coming from a type support library will always be valid. + let structure = unsafe { MessageStructure::from_rosidl_message_members(message_members) }; // The fini function will always exist. let fini_function = message_members.fini_function.unwrap(); let metadata = DynamicMessageMetadata { @@ -177,6 +199,7 @@ impl DynamicMessagePackage { &self.introspection_type_support_library, ), type_support_ptr, + structure, fini_function, }; Ok(metadata) @@ -247,6 +270,137 @@ impl DynamicMessageMetadata { let pkg = DynamicMessagePackage::new(package_name)?; pkg.message_metadata(type_name) } + + /// Instantiates a new message. + pub fn create(&self) -> Result { + // Get an aligned boxed slice. This is inspired by the maligned crate. + use std::alloc::Layout; + // As mentioned in the struct definition, the maximum alignment required is 8. + let layout = Layout::from_size_align(self.structure.size, 8).unwrap(); + let mut storage = unsafe { + assert_ne!(self.structure.size, 0); + // SAFETY: The layout has non-zero size. + let ptr = std::alloc::alloc_zeroed(layout); + // SAFETY: This is valid, memory in ptr has appropriate size and is initialized + let slice = std::slice::from_raw_parts_mut(ptr, self.structure.size); + // The mutable reference decays into a (fat) *mut [u8] + Box::from_raw(slice) + }; + // SAFETY: The pointer returned by get_type_support_handle() is always valid. + let type_support = unsafe { &*self.type_support_ptr }; + let message_members: &rosidl_message_members_t = + // SAFETY: The data pointer is supposed to be always valid. + unsafe { &*(type_support.data as *const rosidl_message_members_t) }; + // SAFETY: The init function is passed zeroed memory of the correct alignment. + unsafe { + (message_members.init_function.unwrap())( + storage.as_mut_ptr() as _, + rosidl_runtime_c__message_initialization::ROSIDL_RUNTIME_C_MSG_INIT_ALL, + ); + }; + let dyn_msg = DynamicMessage { + metadata: self.clone(), + storage, + needs_fini: true, + }; + Ok(dyn_msg) + } + + /// Returns a description of the message structure. + pub fn structure(&self) -> &MessageStructure { + &self.structure + } +} + +// ========================= impl for DynamicMessage ========================= + +impl Drop for DynamicMessage { + fn drop(&mut self) { + if self.needs_fini { + // SAFETY: The fini_function expects to be passed a pointer to the message + unsafe { (self.metadata.fini_function)(self.storage.as_mut_ptr() as _) } + } + } +} + +impl PartialEq for DynamicMessage { + fn eq(&self, other: &Self) -> bool { + self.metadata.type_support_ptr == other.metadata.type_support_ptr + && self.storage == other.storage + } +} + +impl Eq for DynamicMessage {} + +impl DynamicMessage { + /// Dynamically loads a type support library for the specified type and creates a message instance. + /// + /// The full message type is of the form `/msg/`, e.g. + /// `std_msgs/msg/String`. + /// + /// The message instance will contain the default values of the message type. + pub fn new(full_message_type: &str) -> Result { + DynamicMessageMetadata::new(full_message_type)?.create() + } + + /// Returns a description of the message structure. + pub fn structure(&self) -> &MessageStructure { + &self.metadata.structure + } + + /// Converts a statically typed RMW-native message into a `DynamicMessage`. + pub fn convert_from_rmw_message(mut msg: T) -> Result + where + T: RmwMessage, + { + let mut dyn_msg = Self::new(::TYPE_NAME)?; + let align = std::mem::align_of::(); + assert_eq!(dyn_msg.storage.as_ptr().align_offset(align), 0); + { + // SAFETY: This transmutes the slice of bytes into a &mut T. This is fine, since + // under the hood it *is* a T. + // However, the resulting value is not seen as borrowing from dyn_msg by the borrow checker, + // so we are careful to not create a second mutable reference before dropping this one, + // since that would be UB. + let dyn_msg_transmuted = unsafe { &mut *(dyn_msg.storage.as_mut_ptr() as *mut T) }; + // We cannot simply overwrite one message with the other, or we will get a memory leak/double-free. + // Swapping is the solution. + std::mem::swap(&mut msg, dyn_msg_transmuted); + } + Ok(dyn_msg) + } + + /// Converts a `DynamicMessage` into a statically typed RMW-native message. + /// + /// If the RMW-native message type does not match the underlying message type of this `DynamicMessage`, + /// it is not converted but instead returned unchanged. + pub fn convert_into_rmw_message(mut self) -> Result + where + T: RmwMessage, + { + if ::TYPE_NAME == self.metadata.message_type.to_string() { + // SAFETY: Even though a zero-initialized message might not match RMW expectations for + // what a message should look like, it is safe to temporarily have a zero-initialized + // value, i.e. it is not undefined behavior to do this since it's a C struct, and an + // all-zeroes bit pattern is always a valid instance of any C struct. + let mut dest = unsafe { std::mem::zeroed::() }; + let dest_ptr = &mut dest as *mut T as *mut u8; + // This reinterprets the struct as a slice of bytes. + // The bytes copied into the dest slice are a valid value of T, as ensured by comparison + // of the type support pointers. + let dest_slice = + unsafe { std::slice::from_raw_parts_mut(dest_ptr, std::mem::size_of::()) }; + // This creates a shallow copy, with ownership of the "deep" (or inner) parts moving + // into the destination. + dest_slice.copy_from_slice(&*self.storage); + // Don't run the fini function on the src data anymore, because the inner parts would be + // double-freed by dst and src. + self.needs_fini = false; + Ok(dest) + } else { + Err(self) + } + } } #[cfg(test)] @@ -260,6 +414,8 @@ mod tests { fn all_types_are_sync_and_send() { assert_send::(); assert_sync::(); + assert_send::(); + assert_sync::(); } #[test] diff --git a/rclrs/src/dynamic_message/message_structure.rs b/rclrs/src/dynamic_message/message_structure.rs new file mode 100644 index 000000000..ab4e2224b --- /dev/null +++ b/rclrs/src/dynamic_message/message_structure.rs @@ -0,0 +1,254 @@ +use std::ffi::CStr; +use std::num::NonZeroUsize; + +#[cfg(any(ros_distro = "foxy", ros_distro = "galactic"))] +use crate::rcl_bindings::rosidl_typesupport_introspection_c__MessageMember as rosidl_message_member_t; +#[cfg(all(not(ros_distro = "foxy"), not(ros_distro = "galactic")))] +use crate::rcl_bindings::rosidl_typesupport_introspection_c__MessageMember_s as rosidl_message_member_t; +#[cfg(any(ros_distro = "foxy", ros_distro = "galactic"))] +use crate::rcl_bindings::rosidl_typesupport_introspection_c__MessageMembers as rosidl_message_members_t; +#[cfg(all(not(ros_distro = "foxy"), not(ros_distro = "galactic")))] +use crate::rcl_bindings::rosidl_typesupport_introspection_c__MessageMembers_s as rosidl_message_members_t; +use crate::rcl_bindings::*; + +/// Possible base types for fields in a message. +// The field variants are self-explaining, no need to add redundant documentation. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BaseType { + /// AKA `float32` in ROS .msg files. + Float, + /// AKA `float64` in ROS .msg files. + Double, + LongDouble, + Char, + WChar, + Boolean, + /// AKA `byte` in ROS .msg files. + Octet, + /// AKA `char` in ROS .msg files + Uint8, + Int8, + Uint16, + Int16, + Uint32, + Int32, + Uint64, + Int64, + String, + BoundedString { + upper_bound: NonZeroUsize, + }, + WString, + BoundedWString { + upper_bound: NonZeroUsize, + }, + Message(Box), +} + +/// A description of a single field in a [`DynamicMessage`][1]. +/// +/// The concrete type of a field is the combination of its [`BaseType`] with its [`ValueKind`]. +/// That is, the base types exist as single values, arrays, bounded sequences and unbounded sequences. +/// +/// [1]: crate::dynamic_message::DynamicMessage +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MessageFieldInfo { + /// The field name. + pub name: String, + /// The base type – number, string, etc. + pub base_type: BaseType, + /// Whether the field is a simple value, an array, or a (bounded) sequence. + pub value_kind: ValueKind, + pub(crate) string_upper_bound: usize, + pub(crate) resize_function: + Option bool>, + pub(crate) offset: usize, +} + +/// A description of the structure of a message. +/// +/// Namely, the list of fields and their types. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MessageStructure { + /// The set of fields in the message, ordered by their offset in the message. + /// + /// A `Vec` is easier to handle and faster than a `HashMap` for typical numbers of message fields. + /// If you need a `HashMap`, simply create your own from this `Vec`. + pub fields: Vec, + /// The size of this structure in bytes. + pub size: usize, + /// The namespace of this type. This is something like `geometry_msgs__msg`. + pub namespace: String, + /// The name of this type. This does not contain the package name. + pub type_name: String, +} + +/// Information on whether a field is a single value or a list of some kind. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ValueKind { + /// This field is a single value, which includes string values. + Simple, + /// This field is an array of values. + Array { + /// The array length. + length: usize, + }, + /// This field is a [`Sequence`][1] of values. + /// + /// [1]: rosidl_runtime_rs::Sequence + Sequence, + /// This field is a [`BoundedSequence`][1] of values. + /// + /// [1]: rosidl_runtime_rs::BoundedSequence + BoundedSequence { + /// The maximum sequence length. + upper_bound: usize, + }, +} + +// ========================= impl for BaseType ========================= + +impl BaseType { + // The inner message type support will be nullptr except for the case of a nested message. + // That function must be unsafe, since it is possible to safely create a garbage non-null + // pointer. + unsafe fn new( + type_id: u8, + string_upper_bound: Option, + inner: *const rosidl_message_type_support_t, + ) -> Self { + #[cfg(all(not(ros_distro = "foxy"), not(ros_distro = "galactic")))] + use rosidl_typesupport_introspection_c_field_types::*; + match u32::from(type_id) { + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_FLOAT as u32 => Self::Float, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_DOUBLE as u32 => Self::Double, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_LONG_DOUBLE as u32 => { + Self::LongDouble + } + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_CHAR as u32 => Self::Char, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_WCHAR as u32 => Self::WChar, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_BOOLEAN as u32 => Self::Boolean, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_OCTET as u32 => Self::Octet, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_UINT8 as u32 => Self::Uint8, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_INT8 as u32 => Self::Int8, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_UINT16 as u32 => Self::Uint16, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_INT16 as u32 => Self::Int16, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_UINT32 as u32 => Self::Uint32, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_INT32 as u32 => Self::Int32, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_UINT64 as u32 => Self::Uint64, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_INT64 as u32 => Self::Int64, + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_STRING as u32 => { + match string_upper_bound { + None => Self::String, + Some(upper_bound) => Self::BoundedString { upper_bound }, + } + } + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_WSTRING as u32 => { + match string_upper_bound { + None => Self::WString, + Some(upper_bound) => Self::BoundedWString { upper_bound }, + } + } + x if x == rosidl_typesupport_introspection_c__ROS_TYPE_MESSAGE as u32 => { + assert!(!inner.is_null()); + let type_support: &rosidl_message_type_support_t = &*inner; + let message_members: &rosidl_message_members_t = + // SAFETY: The data pointer is supposed to be always valid. + &*(type_support.data as *const rosidl_message_members_t); + let structure = MessageStructure::from_rosidl_message_members(message_members); + Self::Message(Box::new(structure)) + } + _ => panic!("Invalid field type"), + } + } +} + +// ========================= impl for MessageFieldInfo ========================= + +impl MessageFieldInfo { + // That function must be unsafe, since it is possible to safely create a garbage non-null + // pointer and store it in a rosidl_message_member_t. + unsafe fn from(rosidl_message_member: &rosidl_message_member_t) -> Self { + debug_assert!(!rosidl_message_member.name_.is_null()); + let name = /*unsafe*/ { CStr::from_ptr(rosidl_message_member.name_) } + .to_string_lossy() + .into_owned(); + let value_kind = match ( + rosidl_message_member.is_array_, + rosidl_message_member.resize_function.is_some(), + rosidl_message_member.is_upper_bound_, + ) { + (false, _, _) => ValueKind::Simple, + (true, false, _) => ValueKind::Array { + length: rosidl_message_member.array_size_, + }, + (true, true, false) => { + assert_eq!(rosidl_message_member.array_size_, 0); + ValueKind::Sequence + } + (true, true, true) => ValueKind::BoundedSequence { + upper_bound: rosidl_message_member.array_size_, + }, + }; + Self { + name, + base_type: BaseType::new( + rosidl_message_member.type_id_, + NonZeroUsize::new(rosidl_message_member.string_upper_bound_), + rosidl_message_member.members_, + ), + value_kind, + string_upper_bound: rosidl_message_member.string_upper_bound_, + resize_function: rosidl_message_member.resize_function, + offset: usize::try_from(rosidl_message_member.offset_).unwrap(), + } + } +} + +// ========================= impl for MessageStructure ========================= + +impl MessageStructure { + /// Parses the C struct containing a list of fields. + // That function must be unsafe, since it is possible to safely create a garbage non-null + // pointer and store it in a rosidl_message_members_t. + pub(crate) unsafe fn from_rosidl_message_members( + message_members: &rosidl_message_members_t, + ) -> Self { + debug_assert!(!message_members.members_.is_null()); + let num_fields: usize = usize::try_from(message_members.member_count_).unwrap(); + let mut fields: Vec<_> = (0..num_fields) + .map(|i| { + // SAFETY: This is an array as per the documentation + let rosidl_message_member: &rosidl_message_member_t = + /*unsafe*/ { &*message_members.members_.add(i) }; + // SAFETY: This is a valid string pointer + MessageFieldInfo::from(rosidl_message_member) + }) + .collect(); + fields.sort_by_key(|field_info| field_info.offset); + // SAFETY: Immediate conversion into owned string. + let namespace = /*unsafe*/ { + CStr::from_ptr(message_members.message_namespace_) + .to_string_lossy() + .into_owned() + }; + // SAFETY: Immediate conversion into owned string. + let type_name = /*unsafe*/ { + CStr::from_ptr(message_members.message_name_) + .to_string_lossy() + .into_owned() + }; + Self { + fields, + size: message_members.size_of_, + namespace, + type_name, + } + } + + /// Gets the field info corresponding to the specified field name, if any. + pub fn get_field_info(&self, field_name: &str) -> Option<&MessageFieldInfo> { + self.fields.iter().find(|field| field.name == field_name) + } +} diff --git a/rclrs_tests/Cargo.toml b/rclrs_tests/Cargo.toml index 2f484a352..e4c81a442 100644 --- a/rclrs_tests/Cargo.toml +++ b/rclrs_tests/Cargo.toml @@ -8,11 +8,11 @@ edition = "2021" path = "src/lib.rs" [dependencies] -anyhow = {version = "1", features = ["backtrace"]} -test_msgs = {version = "*"} +test_msgs = "*" [dependencies.rclrs] version = "*" +features = ["dyn_msg"] [dependencies.rosidl_runtime_rs] version = "*" diff --git a/rclrs_tests/build.rs b/rclrs_tests/build.rs new file mode 100644 index 000000000..d3f93e214 --- /dev/null +++ b/rclrs_tests/build.rs @@ -0,0 +1,19 @@ +use std::env; + +const ROS_DISTRO: &str = "ROS_DISTRO"; + +fn get_env_var_or_abort(env_var: &'static str) -> String { + if let Ok(value) = env::var(env_var) { + value + } else { + panic!( + "{} environment variable not set - please source ROS 2 installation first.", + env_var + ); + } +} + +fn main() { + let ros_distro = get_env_var_or_abort(ROS_DISTRO); + println!("cargo:rustc-cfg=ros_distro=\"{ros_distro}\""); +} diff --git a/rclrs_tests/src/dynamic_message_tests.rs b/rclrs_tests/src/dynamic_message_tests.rs new file mode 100644 index 000000000..77baded1e --- /dev/null +++ b/rclrs_tests/src/dynamic_message_tests.rs @@ -0,0 +1,767 @@ +use std::num::NonZeroUsize; + +use rclrs::dynamic_message::*; + +#[test] +fn max_alignment_is_8() { + // The DynamicMessage type makes sure that its storage is aligned to 8 + let alignments = [ + std::mem::align_of::(), + std::mem::align_of::(), + std::mem::align_of::(), + std::mem::align_of::(), + std::mem::align_of::(), + std::mem::align_of::(), + std::mem::align_of::(), + std::mem::align_of::(), + std::mem::align_of::(), + std::mem::align_of::(), + std::mem::align_of::(), + std::mem::align_of::(), + ]; + assert_eq!(alignments.into_iter().max().unwrap(), 8); +} + +#[test] +fn message_structure_is_accurate() { + let arrays_metadata = DynamicMessageMetadata::new("test_msgs/msg/Arrays").unwrap(); + let arrays_structure = Box::new(arrays_metadata.structure().clone()); + let builtins_metadata = DynamicMessageMetadata::new("test_msgs/msg/Builtins").unwrap(); + let builtins_structure = Box::new(builtins_metadata.structure().clone()); + let duration_metadata = DynamicMessageMetadata::new("builtin_interfaces/msg/Duration").unwrap(); + let duration_structure = Box::new(duration_metadata.structure().clone()); + let empty_metadata = DynamicMessageMetadata::new("test_msgs/msg/Empty").unwrap(); + let empty_structure = Box::new(empty_metadata.structure().clone()); + let time_metadata = DynamicMessageMetadata::new("builtin_interfaces/msg/Time").unwrap(); + let time_structure = Box::new(time_metadata.structure().clone()); + let basic_types_metadata = DynamicMessageMetadata::new("test_msgs/msg/BasicTypes").unwrap(); + let basic_types_structure = Box::new(basic_types_metadata.structure().clone()); + let bounded_sequences_metadata = + DynamicMessageMetadata::new("test_msgs/msg/BoundedSequences").unwrap(); + let bounded_sequences_structure = Box::new(bounded_sequences_metadata.structure().clone()); + let constants_metadata = DynamicMessageMetadata::new("test_msgs/msg/Constants").unwrap(); + let constants_structure = Box::new(constants_metadata.structure().clone()); + let multi_nested_metadata = DynamicMessageMetadata::new("test_msgs/msg/MultiNested").unwrap(); + let multi_nested_structure = Box::new(multi_nested_metadata.structure().clone()); + let nested_metadata = DynamicMessageMetadata::new("test_msgs/msg/Nested").unwrap(); + let nested_structure = Box::new(nested_metadata.structure().clone()); + let defaults_metadata = DynamicMessageMetadata::new("test_msgs/msg/Defaults").unwrap(); + let defaults_structure = Box::new(defaults_metadata.structure().clone()); + let strings_metadata = DynamicMessageMetadata::new("test_msgs/msg/Strings").unwrap(); + let strings_structure = Box::new(strings_metadata.structure().clone()); + let wstrings_metadata = DynamicMessageMetadata::new("test_msgs/msg/WStrings").unwrap(); + let wstrings_structure = Box::new(wstrings_metadata.structure().clone()); + let unbounded_sequences_metadata = + DynamicMessageMetadata::new("test_msgs/msg/UnboundedSequences").unwrap(); + let unbounded_sequences_structure = Box::new(unbounded_sequences_metadata.structure().clone()); + + let mut message_structures_and_fields = vec![]; + + // --------------------- Arrays --------------------- + + let arrays_fields = vec![ + ( + "bool_values", + BaseType::Boolean, + ValueKind::Array { length: 3 }, + ), + ( + "byte_values", + BaseType::Octet, + ValueKind::Array { length: 3 }, + ), + ( + "char_values", + BaseType::Uint8, // the msg to idl conversion converts char to uint8 + ValueKind::Array { length: 3 }, + ), + ( + "float32_values", + BaseType::Float, + ValueKind::Array { length: 3 }, + ), + ( + "float64_values", + BaseType::Double, + ValueKind::Array { length: 3 }, + ), + ( + "int8_values", + BaseType::Int8, + ValueKind::Array { length: 3 }, + ), + ( + "uint8_values", + BaseType::Uint8, + ValueKind::Array { length: 3 }, + ), + ( + "int16_values", + BaseType::Int16, + ValueKind::Array { length: 3 }, + ), + ( + "uint16_values", + BaseType::Uint16, + ValueKind::Array { length: 3 }, + ), + ( + "int32_values", + BaseType::Int32, + ValueKind::Array { length: 3 }, + ), + ( + "uint32_values", + BaseType::Uint32, + ValueKind::Array { length: 3 }, + ), + ( + "int64_values", + BaseType::Int64, + ValueKind::Array { length: 3 }, + ), + ( + "uint64_values", + BaseType::Uint64, + ValueKind::Array { length: 3 }, + ), + ( + "string_values", + BaseType::String, + ValueKind::Array { length: 3 }, + ), + ( + "basic_types_values", + BaseType::Message(basic_types_structure.clone()), + ValueKind::Array { length: 3 }, + ), + ( + "constants_values", + BaseType::Message(constants_structure.clone()), + ValueKind::Array { length: 3 }, + ), + ( + "defaults_values", + BaseType::Message(defaults_structure.clone()), + ValueKind::Array { length: 3 }, + ), + ( + "bool_values_default", + BaseType::Boolean, + ValueKind::Array { length: 3 }, + ), + ( + "byte_values_default", + BaseType::Octet, + ValueKind::Array { length: 3 }, + ), + ( + "char_values_default", + BaseType::Uint8, // the msg to idl conversion converts char to uint8 + ValueKind::Array { length: 3 }, + ), + ( + "float32_values_default", + BaseType::Float, + ValueKind::Array { length: 3 }, + ), + ( + "float64_values_default", + BaseType::Double, + ValueKind::Array { length: 3 }, + ), + ( + "int8_values_default", + BaseType::Int8, + ValueKind::Array { length: 3 }, + ), + ( + "uint8_values_default", + BaseType::Uint8, + ValueKind::Array { length: 3 }, + ), + ( + "int16_values_default", + BaseType::Int16, + ValueKind::Array { length: 3 }, + ), + ( + "uint16_values_default", + BaseType::Uint16, + ValueKind::Array { length: 3 }, + ), + ( + "int32_values_default", + BaseType::Int32, + ValueKind::Array { length: 3 }, + ), + ( + "uint32_values_default", + BaseType::Uint32, + ValueKind::Array { length: 3 }, + ), + ( + "int64_values_default", + BaseType::Int64, + ValueKind::Array { length: 3 }, + ), + ( + "uint64_values_default", + BaseType::Uint64, + ValueKind::Array { length: 3 }, + ), + ( + "string_values_default", + BaseType::String, + ValueKind::Array { length: 3 }, + ), + ("alignment_check", BaseType::Int32, ValueKind::Simple), + ]; + + message_structures_and_fields.push(("Arrays", arrays_structure.clone(), arrays_fields)); + + // --------------------- BasicTypes --------------------- + + let basic_types_fields = vec![ + ("bool_value", BaseType::Boolean, ValueKind::Simple), + ("byte_value", BaseType::Octet, ValueKind::Simple), + ("char_value", BaseType::Uint8, ValueKind::Simple), + ("float32_value", BaseType::Float, ValueKind::Simple), + ("float64_value", BaseType::Double, ValueKind::Simple), + ("int8_value", BaseType::Int8, ValueKind::Simple), + ("uint8_value", BaseType::Uint8, ValueKind::Simple), + ("int16_value", BaseType::Int16, ValueKind::Simple), + ("uint16_value", BaseType::Uint16, ValueKind::Simple), + ("int32_value", BaseType::Int32, ValueKind::Simple), + ("uint32_value", BaseType::Uint32, ValueKind::Simple), + ("int64_value", BaseType::Int64, ValueKind::Simple), + ("uint64_value", BaseType::Uint64, ValueKind::Simple), + ]; + + message_structures_and_fields.push(( + "BasicTypes", + basic_types_structure.clone(), + basic_types_fields, + )); + + // --------------------- BoundedSequences --------------------- + + let bounded_sequences_fields = vec![ + ( + "bool_values", + BaseType::Boolean, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "byte_values", + BaseType::Octet, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "char_values", + BaseType::Uint8, // the msg to idl conversion converts char to uint8 + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "float32_values", + BaseType::Float, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "float64_values", + BaseType::Double, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "int8_values", + BaseType::Int8, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "uint8_values", + BaseType::Uint8, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "int16_values", + BaseType::Int16, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "uint16_values", + BaseType::Uint16, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "int32_values", + BaseType::Int32, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "uint32_values", + BaseType::Uint32, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "int64_values", + BaseType::Int64, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "uint64_values", + BaseType::Uint64, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "string_values", + BaseType::String, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "basic_types_values", + BaseType::Message(basic_types_structure.clone()), + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "constants_values", + BaseType::Message(constants_structure.clone()), + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "defaults_values", + BaseType::Message(defaults_structure.clone()), + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "bool_values_default", + BaseType::Boolean, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "byte_values_default", + BaseType::Octet, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "char_values_default", + BaseType::Uint8, // the msg to idl conversion converts char to uint8 + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "float32_values_default", + BaseType::Float, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "float64_values_default", + BaseType::Double, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "int8_values_default", + BaseType::Int8, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "uint8_values_default", + BaseType::Uint8, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "int16_values_default", + BaseType::Int16, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "uint16_values_default", + BaseType::Uint16, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "int32_values_default", + BaseType::Int32, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "uint32_values_default", + BaseType::Uint32, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "int64_values_default", + BaseType::Int64, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "uint64_values_default", + BaseType::Uint64, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "string_values_default", + BaseType::String, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ("alignment_check", BaseType::Int32, ValueKind::Simple), + ]; + + message_structures_and_fields.push(( + "BoundedSequences", + bounded_sequences_structure.clone(), + bounded_sequences_fields, + )); + + // --------------------- Builtins --------------------- + + let builtins_fields = vec![ + ( + "duration_value", + BaseType::Message(duration_structure.clone()), + ValueKind::Simple, + ), + ( + "time_value", + BaseType::Message(time_structure.clone()), + ValueKind::Simple, + ), + ]; + + message_structures_and_fields.push(("Builtins", builtins_structure.clone(), builtins_fields)); + + // --------------------- Constants --------------------- + + let constants_fields: Vec<(&'static str, BaseType, ValueKind)> = vec![( + "structure_needs_at_least_one_member", + BaseType::Uint8, + ValueKind::Simple, + )]; + + message_structures_and_fields.push(( + "Constants", + constants_structure.clone(), + constants_fields, + )); + + // --------------------- Defaults --------------------- + + let defaults_fields = vec![ + ("bool_value", BaseType::Boolean, ValueKind::Simple), + ("byte_value", BaseType::Octet, ValueKind::Simple), + // the msg to idl conversion converts char to uint8 + ("char_value", BaseType::Uint8, ValueKind::Simple), + ("float32_value", BaseType::Float, ValueKind::Simple), + ("float64_value", BaseType::Double, ValueKind::Simple), + ("int8_value", BaseType::Int8, ValueKind::Simple), + ("uint8_value", BaseType::Uint8, ValueKind::Simple), + ("int16_value", BaseType::Int16, ValueKind::Simple), + ("uint16_value", BaseType::Uint16, ValueKind::Simple), + ("int32_value", BaseType::Int32, ValueKind::Simple), + ("uint32_value", BaseType::Uint32, ValueKind::Simple), + ("int64_value", BaseType::Int64, ValueKind::Simple), + ("uint64_value", BaseType::Uint64, ValueKind::Simple), + ]; + + message_structures_and_fields.push(("Defaults", defaults_structure.clone(), defaults_fields)); + + // --------------------- Empty --------------------- + + let empty_fields: Vec<(&'static str, BaseType, ValueKind)> = vec![( + "structure_needs_at_least_one_member", + BaseType::Uint8, + ValueKind::Simple, + )]; + + message_structures_and_fields.push(("Empty", empty_structure.clone(), empty_fields)); + + // --------------------- MultiNested --------------------- + + let multi_nested_fields = vec![ + ( + "array_of_arrays", + BaseType::Message(arrays_structure.clone()), + ValueKind::Array { length: 3 }, + ), + ( + "array_of_bounded_sequences", + BaseType::Message(bounded_sequences_structure.clone()), + ValueKind::Array { length: 3 }, + ), + ( + "array_of_unbounded_sequences", + BaseType::Message(unbounded_sequences_structure.clone()), + ValueKind::Array { length: 3 }, + ), + ( + "bounded_sequence_of_arrays", + BaseType::Message(arrays_structure.clone()), + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "bounded_sequence_of_bounded_sequences", + BaseType::Message(bounded_sequences_structure.clone()), + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "bounded_sequence_of_unbounded_sequences", + BaseType::Message(unbounded_sequences_structure.clone()), + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "unbounded_sequence_of_arrays", + BaseType::Message(arrays_structure.clone()), + ValueKind::Sequence, + ), + ( + "unbounded_sequence_of_bounded_sequences", + BaseType::Message(bounded_sequences_structure.clone()), + ValueKind::Sequence, + ), + ( + "unbounded_sequence_of_unbounded_sequences", + BaseType::Message(unbounded_sequences_structure.clone()), + ValueKind::Sequence, + ), + ]; + + message_structures_and_fields.push(( + "MultiNested", + multi_nested_structure.clone(), + multi_nested_fields, + )); + + // --------------------- Nested --------------------- + + let nested_fields = vec![( + "basic_types_value", + BaseType::Message(basic_types_structure.clone()), + ValueKind::Simple, + )]; + + message_structures_and_fields.push(("Nested", nested_structure.clone(), nested_fields)); + + // --------------------- Strings --------------------- + + let strings_fields = vec![ + ("string_value", BaseType::String, ValueKind::Simple), + ("string_value_default1", BaseType::String, ValueKind::Simple), + ("string_value_default2", BaseType::String, ValueKind::Simple), + ("string_value_default3", BaseType::String, ValueKind::Simple), + ("string_value_default4", BaseType::String, ValueKind::Simple), + ("string_value_default5", BaseType::String, ValueKind::Simple), + ( + "bounded_string_value", + BaseType::BoundedString { + upper_bound: NonZeroUsize::new(22).unwrap(), + }, + ValueKind::Simple, + ), + ( + "bounded_string_value_default1", + BaseType::BoundedString { + upper_bound: NonZeroUsize::new(22).unwrap(), + }, + ValueKind::Simple, + ), + ( + "bounded_string_value_default2", + BaseType::BoundedString { + upper_bound: NonZeroUsize::new(22).unwrap(), + }, + ValueKind::Simple, + ), + ( + "bounded_string_value_default3", + BaseType::BoundedString { + upper_bound: NonZeroUsize::new(22).unwrap(), + }, + ValueKind::Simple, + ), + ( + "bounded_string_value_default4", + BaseType::BoundedString { + upper_bound: NonZeroUsize::new(22).unwrap(), + }, + ValueKind::Simple, + ), + ( + "bounded_string_value_default5", + BaseType::BoundedString { + upper_bound: NonZeroUsize::new(22).unwrap(), + }, + ValueKind::Simple, + ), + ]; + + message_structures_and_fields.push(("Strings", strings_structure.clone(), strings_fields)); + + // --------------------- UnboundedSequences --------------------- + + let unbounded_sequences_fields = vec![ + ("bool_values", BaseType::Boolean, ValueKind::Sequence), + ("byte_values", BaseType::Octet, ValueKind::Sequence), + // the msg to idl conversion converts char to uint8 + ("char_values", BaseType::Uint8, ValueKind::Sequence), + ("float32_values", BaseType::Float, ValueKind::Sequence), + ("float64_values", BaseType::Double, ValueKind::Sequence), + ("int8_values", BaseType::Int8, ValueKind::Sequence), + ("uint8_values", BaseType::Uint8, ValueKind::Sequence), + ("int16_values", BaseType::Int16, ValueKind::Sequence), + ("uint16_values", BaseType::Uint16, ValueKind::Sequence), + ("int32_values", BaseType::Int32, ValueKind::Sequence), + ("uint32_values", BaseType::Uint32, ValueKind::Sequence), + ("int64_values", BaseType::Int64, ValueKind::Sequence), + ("uint64_values", BaseType::Uint64, ValueKind::Sequence), + ("string_values", BaseType::String, ValueKind::Sequence), + ( + "basic_types_values", + BaseType::Message(basic_types_structure.clone()), + ValueKind::Sequence, + ), + ( + "constants_values", + BaseType::Message(constants_structure.clone()), + ValueKind::Sequence, + ), + ( + "defaults_values", + BaseType::Message(defaults_structure.clone()), + ValueKind::Sequence, + ), + ( + "bool_values_default", + BaseType::Boolean, + ValueKind::Sequence, + ), + ("byte_values_default", BaseType::Octet, ValueKind::Sequence), + // the msg to idl conversion converts char to uint8 + ("char_values_default", BaseType::Uint8, ValueKind::Sequence), + ( + "float32_values_default", + BaseType::Float, + ValueKind::Sequence, + ), + ( + "float64_values_default", + BaseType::Double, + ValueKind::Sequence, + ), + ("int8_values_default", BaseType::Int8, ValueKind::Sequence), + ("uint8_values_default", BaseType::Uint8, ValueKind::Sequence), + ("int16_values_default", BaseType::Int16, ValueKind::Sequence), + ( + "uint16_values_default", + BaseType::Uint16, + ValueKind::Sequence, + ), + ("int32_values_default", BaseType::Int32, ValueKind::Sequence), + ( + "uint32_values_default", + BaseType::Uint32, + ValueKind::Sequence, + ), + ("int64_values_default", BaseType::Int64, ValueKind::Sequence), + ( + "uint64_values_default", + BaseType::Uint64, + ValueKind::Sequence, + ), + ( + "string_values_default", + BaseType::String, + ValueKind::Sequence, + ), + ("alignment_check", BaseType::Int32, ValueKind::Simple), + ]; + + message_structures_and_fields.push(( + "UnboundedSequences", + unbounded_sequences_structure.clone(), + unbounded_sequences_fields, + )); + + // --------------------- WStrings --------------------- + + let wstrings_fields = vec![ + ("wstring_value", BaseType::WString, ValueKind::Simple), + ( + "wstring_value_default1", + BaseType::WString, + ValueKind::Simple, + ), + ( + "wstring_value_default2", + BaseType::WString, + ValueKind::Simple, + ), + ( + "wstring_value_default3", + BaseType::WString, + ValueKind::Simple, + ), + ( + "array_of_wstrings", + BaseType::WString, + ValueKind::Array { length: 3 }, + ), + ( + "bounded_sequence_of_wstrings", + BaseType::WString, + ValueKind::BoundedSequence { upper_bound: 3 }, + ), + ( + "unbounded_sequence_of_wstrings", + BaseType::WString, + ValueKind::Sequence, + ), + ]; + + message_structures_and_fields.push(("WStrings", wstrings_structure.clone(), wstrings_fields)); + + // --------------------- Running the tests --------------------- + + for (message_name, structure, fields) in message_structures_and_fields { + assert_eq!( + structure + .fields + .iter() + .map(|field_info| field_info.name.to_owned()) + .collect::>(), + fields + .iter() + .map(|pair| pair.0.to_owned()) + .collect::>(), + "in message '{}'", + message_name + ); + + for (field_name, expected_base_type, expected_value_kind) in fields { + let field_type = structure.get_field_info(field_name).unwrap(); + assert_eq!( + field_type.base_type, expected_base_type, + "for field '{}' in message '{}'", + field_name, message_name + ); + assert_eq!( + field_type.value_kind, expected_value_kind, + "for field '{}' in message '{}'", + field_name, message_name, + ); + } + } + + // Explicitly drop to avoid clippy warnings + drop(arrays_structure); + drop(builtins_structure); + drop(duration_structure); + drop(empty_structure); + drop(time_structure); + drop(basic_types_structure); + drop(bounded_sequences_structure); + drop(constants_structure); + drop(multi_nested_structure); + drop(nested_structure); + drop(defaults_structure); + drop(strings_structure); + drop(wstrings_structure); + drop(unbounded_sequences_structure); +} diff --git a/rclrs_tests/src/lib.rs b/rclrs_tests/src/lib.rs index 7cabab86c..c45f95777 100644 --- a/rclrs_tests/src/lib.rs +++ b/rclrs_tests/src/lib.rs @@ -1,5 +1,8 @@ #![cfg(test)] mod client_service_tests; +// Disabled in Foxy due to https://github.com/ros2/rosidl/issues/598 +#[cfg(all(not(ros_distro = "foxy"), not(ros_distro = "galactic")))] +mod dynamic_message_tests; mod graph_tests; mod pub_sub_tests;