ros2_types/traits.rs
1//! Core traits for ROS2 message types
2//!
3//! This module provides traits for ROS2 messages, services, and actions
4//! that are used by the derive macros.
5
6use crate::Result;
7use std::ffi::c_void;
8
9/// Trait for types that have type support information.
10///
11/// This allows the runtime to understand the structure of messages
12/// for serialization and deserialization.
13///
14/// # Serialization
15///
16/// The `to_bytes` and `from_bytes` methods provide CDR serialization:
17/// - For RCL (DDS-based): Uses `rmw_serialize`/`rmw_deserialize` internally
18/// - For native Zenoh: Uses serde with `cdr-encoding` crate
19pub trait TypeSupport: 'static + Send + Sync {
20 /// Returns an opaque pointer to the type support structure.
21 ///
22 /// The actual type of this pointer depends on the implementation
23 /// (e.g., `rosidl_message_type_support_t` in RCL).
24 fn type_support() -> *const c_void {
25 std::ptr::null()
26 }
27
28 /// Serialize this message to CDR-encoded bytes.
29 ///
30 /// # Implementation
31 /// - `rcl` feature: Uses RMW serialization functions
32 /// - `zenoh` feature: Uses serde + cdr-encoding crate
33 ///
34 /// # Errors
35 /// Returns `Error::CdrError` if serialization fails.
36 fn to_bytes(&self) -> Result<Vec<u8>>;
37
38 /// Deserialize a message from CDR-encoded bytes.
39 ///
40 /// # Implementation
41 /// - `rcl` feature: Uses RMW deserialization functions
42 /// - `zenoh` feature: Uses serde + cdr-encoding crate
43 ///
44 /// # Errors
45 /// Returns `Error::CdrError` if deserialization fails.
46 fn from_bytes(bytes: &[u8]) -> Result<Self>
47 where
48 Self: Sized;
49
50 /// Returns the type name in DDS format.
51 ///
52 /// Example: `"std_msgs::msg::dds_::String_"`
53 ///
54 /// This is used for Zenoh key expressions and type matching.
55 fn type_name() -> &'static str;
56
57 /// Returns the RIHS01 type hash for this message type.
58 ///
59 /// # Implementation
60 /// - For RCL: Returns empty string (hash is handled by rosidl typesupport)
61 /// - For Zenoh: Computes hash from TypeDescription
62 ///
63 /// The hash format is: `RIHS01_<64_character_hex_sha256>`
64 fn type_hash() -> Result<::std::string::String> {
65 Ok("RIHS01_00".to_string())
66 }
67}
68
69/// Trait for type that can fail cloning
70///
71/// Used for FFI types where cloning may fail due to memory allocation.
72pub trait TryClone: Sized {
73 /// Returns Some(Self) if clone succeeds else None
74 fn try_clone(&self) -> Option<Self>;
75}
76
77/// Trait for ROS2 service message types.
78///
79/// Services consist of a request and response message pair.
80pub trait ServiceMsg: 'static + Send + Sync {
81 /// The request message type.
82 type Request: TypeSupport;
83
84 /// The response message type.
85 type Response: TypeSupport;
86
87 /// Returns an opaque pointer to the service type support structure.
88 fn type_support() -> *const c_void {
89 std::ptr::null()
90 }
91
92 /// Returns the type name in DDS format.
93 ///
94 /// Example: `"example_interfaces::srv::dds_::AddTwoInts_"`
95 ///
96 /// This is used for Zenoh key expressions and type matching.
97 fn type_name() -> &'static str;
98
99 /// Returns the RIHS01 type hash for this message type.
100 ///
101 /// # Implementation
102 /// - For RCL: Returns empty string (hash is handled by rosidl typesupport)
103 /// - For Zenoh: Computes hash from TypeDescription
104 ///
105 /// The hash format is: `RIHS01_<64_character_hex_sha256>`
106 fn type_hash() -> Result<::std::string::String> {
107 Ok("RIHS01_00".to_string())
108 }
109}
110
111/// Trait for ROS2 action message types.
112///
113/// Actions are more complex than services and include goals, results,
114/// and feedback messages.
115pub trait ActionMsg: 'static + Send + Sync {
116 /// The goal service type.
117 type Goal: ActionGoal;
118
119 /// The result service type.
120 type Result: ActionResult;
121
122 /// The feedback message type.
123 type Feedback: TypeSupport + GetUUID;
124
125 /// Returns an opaque pointer to the action type support structure.
126 fn type_support() -> *const c_void {
127 std::ptr::null()
128 }
129
130 /// Returns the type name in DDS format.
131 ///
132 /// Example: `"example_interfaces::srv::dds_::AddTwoInts_"`
133 ///
134 /// This is used for Zenoh key expressions and type matching.
135 fn type_name() -> &'static str;
136
137 /// The goal content type (the actual goal data).
138 type GoalContent: TypeSupport;
139
140 /// Create a new goal request with the given goal and UUID.
141 fn new_goal_request(
142 goal: Self::GoalContent,
143 uuid: [u8; 16],
144 ) -> <Self::Goal as ActionGoal>::Request;
145
146 /// The result content type (the actual result data).
147 type ResultContent: TypeSupport + TryClone;
148
149 /// Create a new result response with the given status and result.
150 fn new_result_response(
151 status: u8,
152 result: Self::ResultContent,
153 ) -> <Self::Result as ActionResult>::Response;
154
155 /// The feedback content type (the actual feedback data).
156 type FeedbackContent: TypeSupport;
157
158 /// Create a new feedback message with the given feedback and UUID.
159 fn new_feedback_message(feedback: Self::FeedbackContent, uuid: [u8; 16]) -> Self::Feedback;
160
161 /// Returns the RIHS01 type hash for this message type.
162 ///
163 /// # Implementation
164 /// - For RCL: Returns empty string (hash is handled by rosidl typesupport)
165 /// - For Zenoh: Computes hash from TypeDescription
166 ///
167 /// The hash format is: `RIHS01_<64_character_hex_sha256>`
168 fn type_hash() -> Result<::std::string::String> {
169 Ok("RIHS01_00".to_string())
170 }
171}
172
173/// Trait for action goal types.
174pub trait ActionGoal: 'static + Send + Sync {
175 /// The request message type for sending a goal.
176 type Request: TypeSupport + GetUUID;
177
178 /// The response message type for goal acceptance/rejection.
179 type Response: TypeSupport + GoalResponse;
180
181 /// Returns an opaque pointer to the goal service type support structure.
182 fn type_support() -> *const c_void {
183 std::ptr::null()
184 }
185}
186
187/// Trait for types that contain a UUID.
188///
189/// Used for tracking goals and feedback in actions.
190pub trait GetUUID: 'static + Send + Sync {
191 /// Returns a reference to the UUID.
192 fn get_uuid(&self) -> &[u8; 16];
193}
194
195/// Trait for action goal response types.
196pub trait GoalResponse: 'static + Send + Sync {
197 /// Returns whether the goal was accepted.
198 fn is_accepted(&self) -> bool;
199
200 /// Returns the timestamp of the response.
201 fn get_time_stamp(&self) -> UnsafeTime;
202
203 /// Creates a new goal response with the given acceptance status and timestamp.
204 fn new(accepted: bool, stamp: UnsafeTime) -> Self;
205}
206
207/// Trait for action result types.
208pub trait ActionResult: 'static + Send + Sync {
209 /// The request message type for getting a result.
210 type Request: TypeSupport + GetUUID;
211
212 /// The response message type containing the result.
213 type Response: TypeSupport + ResultResponse;
214
215 /// Returns an opaque pointer to the result service type support structure.
216 fn type_support() -> *const c_void {
217 std::ptr::null()
218 }
219}
220
221/// Trait for action result response types.
222pub trait ResultResponse: 'static + Send + Sync {
223 /// Returns the status code of the result.
224 fn get_status(&self) -> u8;
225}
226
227/// Represents a timestamp that may not be safe across all platforms.
228///
229/// The "Unsafe" prefix indicates this is subject to the year-2038 problem
230/// on 32-bit systems since `sec` is an `i32`.
231///
232/// This is compatible with ROS2's builtin_interfaces/Time.
233#[repr(C)]
234#[derive(
235 Debug,
236 Clone,
237 Copy,
238 Default,
239 PartialEq,
240 Eq,
241 PartialOrd,
242 Ord,
243 serde::Serialize,
244 serde::Deserialize,
245)]
246pub struct UnsafeTime {
247 /// Seconds since UNIX epoch.
248 pub sec: i32,
249 /// Nanoseconds component (0-999999999).
250 pub nanosec: u32,
251}
252
253impl UnsafeTime {
254 /// Creates a new UnsafeTime instance.
255 pub const fn new(sec: i32, nanosec: u32) -> Self {
256 Self { sec, nanosec }
257 }
258
259 /// Creates an UnsafeTime representing the UNIX epoch (0 seconds).
260 pub const fn zero() -> Self {
261 Self { sec: 0, nanosec: 0 }
262 }
263}
264
265// Conversions to/from std types for UnsafeTime
266use std::time::{Duration, SystemTime};
267
268impl From<&SystemTime> for UnsafeTime {
269 fn from(t: &SystemTime) -> Self {
270 let dur = t.duration_since(SystemTime::UNIX_EPOCH).unwrap();
271
272 let sec = dur.as_secs();
273 if sec > i32::MAX as u64 {
274 panic!("SystemTime too far in future (year-2038 problem)");
275 }
276
277 UnsafeTime {
278 sec: sec as i32,
279 nanosec: dur.subsec_nanos(),
280 }
281 }
282}
283
284impl From<SystemTime> for UnsafeTime {
285 fn from(t: SystemTime) -> Self {
286 (&t).into()
287 }
288}
289
290impl From<&UnsafeTime> for SystemTime {
291 fn from(t: &UnsafeTime) -> Self {
292 let nanos = Duration::from_nanos(t.nanosec as u64);
293 let secs = Duration::from_secs(t.sec as u64);
294 let dur = nanos + secs;
295 SystemTime::UNIX_EPOCH + dur
296 }
297}
298
299impl From<UnsafeTime> for SystemTime {
300 fn from(t: UnsafeTime) -> Self {
301 (&t).into()
302 }
303}
304
305/// Represents a duration that may not be safe across all platforms.
306///
307/// The "Unsafe" prefix indicates this is subject to the year-2038 problem
308/// on 32-bit systems since `sec` is an `i32`.
309#[repr(C)]
310#[derive(
311 Debug,
312 Clone,
313 Copy,
314 Default,
315 PartialEq,
316 Eq,
317 PartialOrd,
318 Ord,
319 serde::Serialize,
320 serde::Deserialize,
321)]
322pub struct UnsafeDuration {
323 /// Seconds component.
324 pub sec: i32,
325 /// Nanoseconds component.
326 pub nanosec: u32,
327}
328
329impl UnsafeDuration {
330 /// Creates a new UnsafeDuration instance.
331 pub const fn new(sec: i32, nanosec: u32) -> Self {
332 Self { sec, nanosec }
333 }
334
335 /// Creates a zero duration.
336 pub const fn zero() -> Self {
337 Self { sec: 0, nanosec: 0 }
338 }
339}
340
341impl From<&Duration> for UnsafeDuration {
342 fn from(t: &Duration) -> Self {
343 let sec = t.as_secs();
344
345 if sec > i32::MAX as u64 {
346 panic!("Duration too long (year-2038 problem)");
347 }
348
349 let nanosec = t.subsec_nanos();
350
351 UnsafeDuration {
352 sec: sec as i32,
353 nanosec,
354 }
355 }
356}
357
358impl From<Duration> for UnsafeDuration {
359 fn from(t: Duration) -> Self {
360 (&t).into()
361 }
362}
363
364impl From<&UnsafeDuration> for Duration {
365 fn from(t: &UnsafeDuration) -> Self {
366 Duration::from_secs(t.sec as u64) + Duration::from_nanos(t.nanosec as u64)
367 }
368}
369
370impl From<UnsafeDuration> for Duration {
371 fn from(t: UnsafeDuration) -> Self {
372 (&t).into()
373 }
374}
375
376/// Raw sequence type for FFI compatibility
377///
378/// This represents a dynamically-sized sequence of elements as used in ROS2 C API.
379///
380/// # Memory Management
381///
382/// - **With `rcl` feature**: Memory is managed by ROS2 C libraries through FFI.
383/// Use `TryClone` for proper copying via FFI functions.
384/// - **Without `rcl` feature**: Memory can be managed by Rust using `from_vec()`.
385/// The sequence takes ownership and will free memory on drop.
386#[repr(C)]
387#[derive(Debug)]
388pub struct SequenceRaw<T> {
389 /// Pointer to the data array
390 pub data: *mut T,
391 /// Current number of elements
392 pub size: usize,
393 /// Allocated capacity
394 pub capacity: usize,
395}
396
397impl<T> SequenceRaw<T> {
398 /// Create a null/empty sequence
399 pub const fn null() -> Self {
400 Self {
401 data: std::ptr::null_mut(),
402 size: 0,
403 capacity: 0,
404 }
405 }
406
407 /// Check if the sequence is empty
408 pub fn is_empty(&self) -> bool {
409 self.size == 0
410 }
411
412 /// Get the length of the sequence
413 pub fn len(&self) -> usize {
414 self.size
415 }
416
417 /// Get a slice of the sequence data
418 ///
419 pub fn as_slice(&self) -> &[T] {
420 if self.data.is_null() || self.size == 0 {
421 &[]
422 } else {
423 unsafe { std::slice::from_raw_parts(self.data, self.size) }
424 }
425 }
426 pub fn iter(&self) -> std::slice::Iter<'_, T> {
427 self.as_slice().iter()
428 }
429
430 /// Get a mutable slice of the sequence data
431 ///
432 #[deprecated(note = "use as_mut_slice instead")]
433 pub fn as_slice_mut(&mut self) -> &mut [T] {
434 if self.data.is_null() || self.size == 0 {
435 &mut []
436 } else {
437 unsafe { std::slice::from_raw_parts_mut(self.data, self.size) }
438 }
439 }
440
441 /// Get a mutable slice of the sequence data
442 ///
443 pub fn as_mut_slice(&mut self) -> &mut [T] {
444 if self.data.is_null() || self.size == 0 {
445 &mut []
446 } else {
447 unsafe { std::slice::from_raw_parts_mut(self.data, self.size) }
448 }
449 }
450 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> {
451 self.as_mut_slice().iter_mut()
452 }
453}
454
455// Non-rcl implementations: Rust-managed memory using Vec
456#[cfg(not(feature = "rcl"))]
457impl<T> SequenceRaw<T> {
458 /// Create a sequence from a Vec (takes ownership)
459 ///
460 /// The Vec's memory is transferred to the sequence. The sequence will
461 /// free the memory when dropped.
462 pub fn from_vec(mut vec: Vec<T>) -> Self {
463 let data = vec.as_mut_ptr();
464 let size = vec.len();
465 let capacity = vec.capacity();
466 std::mem::forget(vec); // Don't drop the Vec, we own the memory now
467 Self {
468 data,
469 size,
470 capacity,
471 }
472 }
473
474 /// Convert the sequence back to a Vec (takes ownership)
475 ///
476 /// # Safety
477 /// Only call this on sequences created with `from_vec()` or that you know
478 /// were allocated by Rust.
479 pub unsafe fn into_vec(self) -> Vec<T> {
480 if self.data.is_null() {
481 Vec::new()
482 } else {
483 let vec = unsafe { Vec::from_raw_parts(self.data, self.size, self.capacity) };
484 std::mem::forget(self); // Don't run our Drop
485 vec
486 }
487 }
488}
489
490// Clone implementation for non-rcl: actually clone the data
491#[cfg(not(feature = "rcl"))]
492impl<T: Clone> Clone for SequenceRaw<T> {
493 fn clone(&self) -> Self {
494 if self.data.is_null() || self.size == 0 {
495 Self::null()
496 } else {
497 // Clone the data into a new Vec
498 let slice = unsafe { std::slice::from_raw_parts(self.data, self.size) };
499 let vec: Vec<T> = slice.to_vec();
500 Self::from_vec(vec)
501 }
502 }
503}
504
505// Clone implementation for rcl: shallow copy (use TryClone for proper cloning)
506#[cfg(feature = "rcl")]
507impl<T> Clone for SequenceRaw<T> {
508 fn clone(&self) -> Self {
509 // For rcl, we do a shallow copy. The actual data cloning should be done
510 // through TryClone which uses FFI copy functions.
511 Self {
512 data: self.data,
513 size: self.size,
514 capacity: self.capacity,
515 }
516 }
517}
518
519// PartialEq implementation: compare actual elements
520impl<T: PartialEq> PartialEq for SequenceRaw<T> {
521 fn eq(&self, other: &Self) -> bool {
522 if self.size != other.size {
523 return false;
524 }
525 if self.data.is_null() && other.data.is_null() {
526 return true;
527 }
528 if self.data.is_null() || other.data.is_null() {
529 return false;
530 }
531 // Compare actual elements
532 let self_slice = unsafe { std::slice::from_raw_parts(self.data, self.size) };
533 let other_slice = unsafe { std::slice::from_raw_parts(other.data, other.size) };
534 self_slice == other_slice
535 }
536}
537
538// Default implementation for SequenceRaw
539impl<T> Default for SequenceRaw<T> {
540 fn default() -> Self {
541 Self::null()
542 }
543}
544
545// Drop implementation for non-rcl: free Rust-managed memory
546#[cfg(not(feature = "rcl"))]
547impl<T> Drop for SequenceRaw<T> {
548 fn drop(&mut self) {
549 if !self.data.is_null() && self.capacity > 0 {
550 // Reconstruct the Vec and let it drop, freeing the memory
551 unsafe {
552 let _ = Vec::from_raw_parts(self.data, self.size, self.capacity);
553 }
554 }
555 }
556}
557
558// Serde implementations for non-rcl: serialize as a sequence
559#[cfg(not(feature = "rcl"))]
560impl<T: serde::Serialize> serde::Serialize for SequenceRaw<T> {
561 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
562 where
563 S: serde::Serializer,
564 {
565 use serde::ser::SerializeSeq;
566 let slice = self.as_slice();
567 let mut seq = serializer.serialize_seq(Some(slice.len()))?;
568 for element in slice {
569 seq.serialize_element(element)?;
570 }
571 seq.end()
572 }
573}
574
575#[cfg(not(feature = "rcl"))]
576impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for SequenceRaw<T> {
577 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
578 where
579 D: serde::Deserializer<'de>,
580 {
581 let vec = Vec::<T>::deserialize(deserializer)?;
582 Ok(Self::from_vec(vec))
583 }
584}
585
586unsafe impl<T: Send> Send for SequenceRaw<T> {}
587unsafe impl<T: Sync> Sync for SequenceRaw<T> {}