1use crate::types::RosMessage;
35use core::fmt;
36use nros_serdes::{CdrReader, CdrWriter, DeserError, Deserialize, SerError, Serialize};
37
38pub trait RosAction: Sized {
54 type Goal: RosMessage;
56
57 type Result: RosMessage + Default;
59
60 type Feedback: RosMessage;
62
63 type SendGoalRequest: RosMessage;
65
66 type SendGoalResponse: RosMessage;
68
69 type GetResultRequest: RosMessage;
71
72 type GetResultResponse: RosMessage;
74
75 type FeedbackMessage: RosMessage;
77
78 const ACTION_NAME: &'static str;
80
81 const ACTION_HASH: &'static str;
83
84 #[allow(clippy::result_unit_err)]
100 fn register_protocol_types() -> Result<(), ()> {
101 Ok(())
102 }
103}
104
105#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
110#[repr(i8)]
111pub enum GoalStatus {
112 #[default]
114 Unknown = 0,
115 Accepted = 1,
117 Executing = 2,
119 Canceling = 3,
121 Succeeded = 4,
123 Canceled = 5,
125 Aborted = 6,
127}
128
129impl GoalStatus {
130 pub fn is_terminal(&self) -> bool {
132 matches!(
133 self,
134 GoalStatus::Succeeded | GoalStatus::Canceled | GoalStatus::Aborted
135 )
136 }
137
138 pub fn is_active(&self) -> bool {
140 matches!(
141 self,
142 GoalStatus::Accepted | GoalStatus::Executing | GoalStatus::Canceling
143 )
144 }
145
146 pub fn from_i8(value: i8) -> Option<Self> {
148 match value {
149 0 => Some(GoalStatus::Unknown),
150 1 => Some(GoalStatus::Accepted),
151 2 => Some(GoalStatus::Executing),
152 3 => Some(GoalStatus::Canceling),
153 4 => Some(GoalStatus::Succeeded),
154 5 => Some(GoalStatus::Canceled),
155 6 => Some(GoalStatus::Aborted),
156 _ => None,
157 }
158 }
159}
160
161impl fmt::Display for GoalStatus {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 match self {
164 GoalStatus::Unknown => write!(f, "UNKNOWN"),
165 GoalStatus::Accepted => write!(f, "ACCEPTED"),
166 GoalStatus::Executing => write!(f, "EXECUTING"),
167 GoalStatus::Canceling => write!(f, "CANCELING"),
168 GoalStatus::Succeeded => write!(f, "SUCCEEDED"),
169 GoalStatus::Canceled => write!(f, "CANCELED"),
170 GoalStatus::Aborted => write!(f, "ABORTED"),
171 }
172 }
173}
174
175impl Serialize for GoalStatus {
176 fn serialize(&self, writer: &mut CdrWriter) -> Result<(), SerError> {
177 writer.write_i8(*self as i8)
178 }
179}
180
181impl Deserialize for GoalStatus {
182 fn deserialize(reader: &mut CdrReader) -> Result<Self, DeserError> {
183 let value = reader.read_i8()?;
184 GoalStatus::from_i8(value).ok_or(DeserError::InvalidData)
185 }
186}
187
188#[derive(Clone, Copy, PartialEq, Eq, Hash)]
192pub struct GoalId {
193 pub uuid: [u8; 16],
195}
196
197impl GoalId {
198 pub const UUID_LEN: usize = 16;
200
201 pub const SEQ_PREFIX_LEN: usize = 0;
212
213 pub const fn new(uuid: [u8; 16]) -> Self {
215 Self { uuid }
216 }
217
218 pub const fn zero() -> Self {
220 Self { uuid: [0; 16] }
221 }
222
223 pub fn is_zero(&self) -> bool {
225 self.uuid == [0; 16]
226 }
227
228 pub fn from_counter(counter: u64) -> Self {
233 let mut uuid = [0u8; 16];
234 uuid[8..16].copy_from_slice(&counter.to_be_bytes());
236 uuid[6] = (uuid[6] & 0x0f) | 0x40; uuid[8] = (uuid[8] & 0x3f) | 0x80; Self { uuid }
240 }
241}
242
243impl Default for GoalId {
244 fn default() -> Self {
245 Self::zero()
246 }
247}
248
249impl Serialize for GoalId {
250 fn serialize(&self, writer: &mut CdrWriter) -> Result<(), SerError> {
251 for byte in &self.uuid {
252 writer.write_u8(*byte)?;
253 }
254 Ok(())
255 }
256}
257
258impl Deserialize for GoalId {
259 fn deserialize(reader: &mut CdrReader) -> Result<Self, DeserError> {
260 let mut uuid = [0u8; 16];
261 for byte in &mut uuid {
262 *byte = reader.read_u8()?;
263 }
264 Ok(Self { uuid })
265 }
266}
267
268impl fmt::Debug for GoalId {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 write!(
272 f,
273 "GoalId({:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x})",
274 self.uuid[0],
275 self.uuid[1],
276 self.uuid[2],
277 self.uuid[3],
278 self.uuid[4],
279 self.uuid[5],
280 self.uuid[6],
281 self.uuid[7],
282 self.uuid[8],
283 self.uuid[9],
284 self.uuid[10],
285 self.uuid[11],
286 self.uuid[12],
287 self.uuid[13],
288 self.uuid[14],
289 self.uuid[15]
290 )
291 }
292}
293
294impl fmt::Display for GoalId {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 write!(
297 f,
298 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
299 self.uuid[0],
300 self.uuid[1],
301 self.uuid[2],
302 self.uuid[3],
303 self.uuid[4],
304 self.uuid[5],
305 self.uuid[6],
306 self.uuid[7],
307 self.uuid[8],
308 self.uuid[9],
309 self.uuid[10],
310 self.uuid[11],
311 self.uuid[12],
312 self.uuid[13],
313 self.uuid[14],
314 self.uuid[15]
315 )
316 }
317}
318
319#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
324pub struct GoalInfo {
325 pub goal_id: GoalId,
327 pub stamp_sec: i32,
329 pub stamp_nanosec: u32,
331}
332
333impl GoalInfo {
334 pub const fn new(goal_id: GoalId, stamp_sec: i32, stamp_nanosec: u32) -> Self {
336 Self {
337 goal_id,
338 stamp_sec,
339 stamp_nanosec,
340 }
341 }
342
343 pub const fn with_id(goal_id: GoalId) -> Self {
345 Self {
346 goal_id,
347 stamp_sec: 0,
348 stamp_nanosec: 0,
349 }
350 }
351}
352
353impl Serialize for GoalInfo {
354 fn serialize(&self, writer: &mut CdrWriter) -> Result<(), SerError> {
355 self.goal_id.serialize(writer)?;
356 writer.write_i32(self.stamp_sec)?;
357 writer.write_u32(self.stamp_nanosec)?;
358 Ok(())
359 }
360}
361
362impl Deserialize for GoalInfo {
363 fn deserialize(reader: &mut CdrReader) -> Result<Self, DeserError> {
364 Ok(Self {
365 goal_id: GoalId::deserialize(reader)?,
366 stamp_sec: reader.read_i32()?,
367 stamp_nanosec: reader.read_u32()?,
368 })
369 }
370}
371
372#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
376pub struct GoalStatusStamped {
377 pub goal_info: GoalInfo,
379 pub status: GoalStatus,
381}
382
383impl GoalStatusStamped {
384 pub const fn new(goal_info: GoalInfo, status: GoalStatus) -> Self {
386 Self { goal_info, status }
387 }
388}
389
390impl Serialize for GoalStatusStamped {
391 fn serialize(&self, writer: &mut CdrWriter) -> Result<(), SerError> {
392 self.goal_info.serialize(writer)?;
393 writer.write_i8(self.status as i8)?;
394 Ok(())
395 }
396}
397
398impl Deserialize for GoalStatusStamped {
399 fn deserialize(reader: &mut CdrReader) -> Result<Self, DeserError> {
400 let goal_info = GoalInfo::deserialize(reader)?;
401 let status_val = reader.read_i8()?;
402 let status = GoalStatus::from_i8(status_val).unwrap_or(GoalStatus::Unknown);
403 Ok(Self { goal_info, status })
404 }
405}
406
407#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
411#[repr(i8)]
412pub enum CancelResponse {
413 #[default]
415 Ok = 0,
416 Rejected = 1,
418 UnknownGoal = 2,
420 GoalTerminated = 3,
422}
423
424impl CancelResponse {
425 pub fn from_i8(value: i8) -> Option<Self> {
427 match value {
428 0 => Some(CancelResponse::Ok),
429 1 => Some(CancelResponse::Rejected),
430 2 => Some(CancelResponse::UnknownGoal),
431 3 => Some(CancelResponse::GoalTerminated),
432 _ => None,
433 }
434 }
435}
436
437impl Serialize for CancelResponse {
438 fn serialize(&self, writer: &mut CdrWriter) -> Result<(), SerError> {
439 writer.write_i8(*self as i8)
440 }
441}
442
443impl Deserialize for CancelResponse {
444 fn deserialize(reader: &mut CdrReader) -> Result<Self, DeserError> {
445 let value = reader.read_i8()?;
446 CancelResponse::from_i8(value).ok_or(DeserError::InvalidData)
447 }
448}
449
450#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
452#[repr(i8)]
453pub enum GoalResponse {
454 #[default]
456 Reject = 0,
457 AcceptAndExecute = 1,
459 AcceptAndDefer = 2,
461}
462
463impl GoalResponse {
464 pub fn from_i8(value: i8) -> Option<Self> {
466 match value {
467 0 => Some(GoalResponse::Reject),
468 1 => Some(GoalResponse::AcceptAndExecute),
469 2 => Some(GoalResponse::AcceptAndDefer),
470 _ => None,
471 }
472 }
473
474 pub fn is_accepted(&self) -> bool {
476 matches!(
477 self,
478 GoalResponse::AcceptAndExecute | GoalResponse::AcceptAndDefer
479 )
480 }
481}
482
483pub struct ActionServer<A: RosAction> {
488 pub name: &'static str,
490 _marker: core::marker::PhantomData<A>,
492}
493
494impl<A: RosAction> ActionServer<A> {
495 pub fn new(name: &'static str) -> Self {
497 Self {
498 name,
499 _marker: core::marker::PhantomData,
500 }
501 }
502
503 pub fn name(&self) -> &str {
505 self.name
506 }
507
508 pub fn action_type(&self) -> &'static str {
510 A::ACTION_NAME
511 }
512
513 pub fn action_hash(&self) -> &'static str {
515 A::ACTION_HASH
516 }
517}
518
519pub struct ActionClient<A: RosAction> {
524 pub name: &'static str,
526 _marker: core::marker::PhantomData<A>,
528}
529
530impl<A: RosAction> ActionClient<A> {
531 pub fn new(name: &'static str) -> Self {
533 Self {
534 name,
535 _marker: core::marker::PhantomData,
536 }
537 }
538
539 pub fn name(&self) -> &str {
541 self.name
542 }
543
544 pub fn action_type(&self) -> &'static str {
546 A::ACTION_NAME
547 }
548
549 pub fn action_hash(&self) -> &'static str {
551 A::ACTION_HASH
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 extern crate std;
558
559 use super::*;
560 use std::format;
561
562 #[test]
563 fn test_goal_status_is_terminal() {
564 assert!(!GoalStatus::Unknown.is_terminal());
565 assert!(!GoalStatus::Accepted.is_terminal());
566 assert!(!GoalStatus::Executing.is_terminal());
567 assert!(!GoalStatus::Canceling.is_terminal());
568 assert!(GoalStatus::Succeeded.is_terminal());
569 assert!(GoalStatus::Canceled.is_terminal());
570 assert!(GoalStatus::Aborted.is_terminal());
571 }
572
573 #[test]
574 fn test_goal_status_is_active() {
575 assert!(!GoalStatus::Unknown.is_active());
576 assert!(GoalStatus::Accepted.is_active());
577 assert!(GoalStatus::Executing.is_active());
578 assert!(GoalStatus::Canceling.is_active());
579 assert!(!GoalStatus::Succeeded.is_active());
580 assert!(!GoalStatus::Canceled.is_active());
581 assert!(!GoalStatus::Aborted.is_active());
582 }
583
584 #[test]
585 fn test_goal_status_from_i8() {
586 assert_eq!(GoalStatus::from_i8(0), Some(GoalStatus::Unknown));
587 assert_eq!(GoalStatus::from_i8(1), Some(GoalStatus::Accepted));
588 assert_eq!(GoalStatus::from_i8(2), Some(GoalStatus::Executing));
589 assert_eq!(GoalStatus::from_i8(3), Some(GoalStatus::Canceling));
590 assert_eq!(GoalStatus::from_i8(4), Some(GoalStatus::Succeeded));
591 assert_eq!(GoalStatus::from_i8(5), Some(GoalStatus::Canceled));
592 assert_eq!(GoalStatus::from_i8(6), Some(GoalStatus::Aborted));
593 assert_eq!(GoalStatus::from_i8(7), None);
594 assert_eq!(GoalStatus::from_i8(-1), None);
595 }
596
597 #[test]
598 fn test_goal_id_zero() {
599 let id = GoalId::zero();
600 assert!(id.is_zero());
601 assert_eq!(id.uuid, [0; 16]);
602 }
603
604 #[test]
605 fn test_goal_id_from_counter() {
606 let id1 = GoalId::from_counter(1);
607 let id2 = GoalId::from_counter(2);
608
609 assert!(!id1.is_zero());
610 assert!(!id2.is_zero());
611 assert_ne!(id1, id2);
612
613 let id1_again = GoalId::from_counter(1);
615 assert_eq!(id1, id1_again);
616 }
617
618 #[test]
619 fn test_goal_id_display() {
620 let id = GoalId::new([
621 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
622 0x77, 0x88,
623 ]);
624 let s = format!("{}", id);
625 assert_eq!(s, "12345678-9abc-def0-1122-334455667788");
626 }
627
628 #[test]
629 fn test_cancel_response_from_i8() {
630 assert_eq!(CancelResponse::from_i8(0), Some(CancelResponse::Ok));
631 assert_eq!(CancelResponse::from_i8(1), Some(CancelResponse::Rejected));
632 assert_eq!(
633 CancelResponse::from_i8(2),
634 Some(CancelResponse::UnknownGoal)
635 );
636 assert_eq!(
637 CancelResponse::from_i8(3),
638 Some(CancelResponse::GoalTerminated)
639 );
640 assert_eq!(CancelResponse::from_i8(4), None);
641 }
642
643 #[test]
644 fn test_goal_response_is_accepted() {
645 assert!(!GoalResponse::Reject.is_accepted());
646 assert!(GoalResponse::AcceptAndExecute.is_accepted());
647 assert!(GoalResponse::AcceptAndDefer.is_accepted());
648 }
649
650 #[test]
651 fn test_goal_info_new() {
652 let goal_id = GoalId::from_counter(42);
653 let info = GoalInfo::new(goal_id, 123, 456);
654 assert_eq!(info.goal_id, goal_id);
655 assert_eq!(info.stamp_sec, 123);
656 assert_eq!(info.stamp_nanosec, 456);
657 }
658
659 #[test]
660 fn test_goal_info_with_id() {
661 let goal_id = GoalId::from_counter(42);
662 let info = GoalInfo::with_id(goal_id);
663 assert_eq!(info.goal_id, goal_id);
664 assert_eq!(info.stamp_sec, 0);
665 assert_eq!(info.stamp_nanosec, 0);
666 }
667
668 #[test]
669 fn test_goal_status_stamped() {
670 let goal_id = GoalId::from_counter(1);
671 let info = GoalInfo::with_id(goal_id);
672 let stamped = GoalStatusStamped::new(info, GoalStatus::Executing);
673 assert_eq!(stamped.goal_info.goal_id, goal_id);
674 assert_eq!(stamped.status, GoalStatus::Executing);
675 }
676
677 #[test]
678 fn test_goal_id_serialization() {
679 use nros_serdes::{CdrReader, CdrWriter};
680
681 let original = GoalId::new([
682 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
683 0x0f, 0x10,
684 ]);
685
686 let mut buf = [0u8; 32];
688 let mut writer = CdrWriter::new(&mut buf);
689 original.serialize(&mut writer).unwrap();
690 let len = writer.position();
691 assert_eq!(len, 16); let mut reader = CdrReader::new(&buf[..len]);
695 let deserialized = GoalId::deserialize(&mut reader).unwrap();
696
697 assert_eq!(original, deserialized);
698 }
699
700 #[test]
701 fn test_goal_status_serialization() {
702 use nros_serdes::{CdrReader, CdrWriter};
703
704 for status in [
705 GoalStatus::Unknown,
706 GoalStatus::Accepted,
707 GoalStatus::Executing,
708 GoalStatus::Canceling,
709 GoalStatus::Succeeded,
710 GoalStatus::Canceled,
711 GoalStatus::Aborted,
712 ] {
713 let mut buf = [0u8; 8];
714 let len = {
715 let mut writer = CdrWriter::new(&mut buf);
716 status.serialize(&mut writer).unwrap();
717 writer.position()
718 };
719
720 let mut reader = CdrReader::new(&buf[..len]);
721 let deserialized = GoalStatus::deserialize(&mut reader).unwrap();
722
723 assert_eq!(status, deserialized);
724 }
725 }
726
727 #[test]
728 fn test_goal_info_serialization() {
729 use nros_serdes::{CdrReader, CdrWriter};
730
731 let goal_id = GoalId::from_counter(42);
732 let original = GoalInfo::new(goal_id, 1234567890, 123456789);
733
734 let mut buf = [0u8; 64];
736 let mut writer = CdrWriter::new(&mut buf);
737 original.serialize(&mut writer).unwrap();
738 let len = writer.position();
739 assert_eq!(len, 24);
741
742 let mut reader = CdrReader::new(&buf[..len]);
744 let deserialized = GoalInfo::deserialize(&mut reader).unwrap();
745
746 assert_eq!(original.goal_id, deserialized.goal_id);
747 assert_eq!(original.stamp_sec, deserialized.stamp_sec);
748 assert_eq!(original.stamp_nanosec, deserialized.stamp_nanosec);
749 }
750
751 #[test]
752 fn test_goal_status_stamped_serialization() {
753 use nros_serdes::{CdrReader, CdrWriter};
754
755 let goal_id = GoalId::from_counter(99);
756 let goal_info = GoalInfo::new(goal_id, 987654321, 111222333);
757 let original = GoalStatusStamped::new(goal_info, GoalStatus::Succeeded);
758
759 let mut buf = [0u8; 64];
761 let mut writer = CdrWriter::new(&mut buf);
762 original.serialize(&mut writer).unwrap();
763 let len = writer.position();
764 assert_eq!(len, 25);
766
767 let mut reader = CdrReader::new(&buf[..len]);
769 let deserialized = GoalStatusStamped::deserialize(&mut reader).unwrap();
770
771 assert_eq!(original.goal_info.goal_id, deserialized.goal_info.goal_id);
772 assert_eq!(
773 original.goal_info.stamp_sec,
774 deserialized.goal_info.stamp_sec
775 );
776 assert_eq!(
777 original.goal_info.stamp_nanosec,
778 deserialized.goal_info.stamp_nanosec
779 );
780 assert_eq!(original.status, deserialized.status);
781 }
782}
783
784#[cfg(kani)]
789mod verification {
790 use super::*;
791
792 #[kani::proof]
795 fn goal_status_from_i8_valid_range() {
796 let val: i8 = kani::any();
797 let status = GoalStatus::from_i8(val);
798 if (0..=6).contains(&val) {
799 assert!(status.is_some());
800 } else {
801 assert!(status.is_none());
802 }
803 }
804
805 #[kani::proof]
806 fn goal_status_terminal_active_exclusive() {
807 let val: i8 = kani::any();
808 kani::assume((0..=6).contains(&val));
809 let status = GoalStatus::from_i8(val).unwrap();
810 assert!(!(status.is_terminal() && status.is_active()));
812 if val == 0 {
814 assert!(!status.is_terminal());
815 assert!(!status.is_active());
816 }
817 if (1..=3).contains(&val) {
819 assert!(status.is_active());
820 assert!(!status.is_terminal());
821 }
822 if (4..=6).contains(&val) {
824 assert!(status.is_terminal());
825 assert!(!status.is_active());
826 }
827 }
828
829 #[kani::proof]
830 #[kani::unwind(5)]
831 fn goal_status_serialize_roundtrip() {
832 let val: i8 = kani::any();
833 kani::assume((0..=6).contains(&val));
834 let status = GoalStatus::from_i8(val).unwrap();
835
836 let mut buf = [0u8; 8];
837 let len = {
838 let mut writer = CdrWriter::new(&mut buf);
839 status.serialize(&mut writer).unwrap();
840 writer.position()
841 };
842
843 let mut reader = CdrReader::new(&buf[..len]);
844 let deserialized = GoalStatus::deserialize(&mut reader).unwrap();
845 assert_eq!(status, deserialized);
846 }
847
848 #[kani::proof]
851 fn goal_response_from_i8_valid_range() {
852 let val: i8 = kani::any();
853 let resp = GoalResponse::from_i8(val);
854 if (0..=2).contains(&val) {
855 assert!(resp.is_some());
856 } else {
857 assert!(resp.is_none());
858 }
859 }
860
861 #[kani::proof]
862 fn goal_response_is_accepted_consistent() {
863 let val: i8 = kani::any();
864 kani::assume((0..=2).contains(&val));
865 let resp = GoalResponse::from_i8(val).unwrap();
866 assert_eq!(resp.is_accepted(), val >= 1);
868 }
869
870 #[kani::proof]
873 fn cancel_response_from_i8_valid_range() {
874 let val: i8 = kani::any();
875 let resp = CancelResponse::from_i8(val);
876 if (0..=3).contains(&val) {
877 assert!(resp.is_some());
878 } else {
879 assert!(resp.is_none());
880 }
881 }
882
883 #[kani::proof]
886 fn goal_id_zero_is_zero() {
887 let id = GoalId::zero();
888 assert!(id.is_zero());
889 }
890
891 #[kani::proof]
892 fn goal_id_from_counter_deterministic() {
893 let counter: u64 = kani::any();
894 kani::assume(counter <= 1_000_000);
896 let id1 = GoalId::from_counter(counter);
897 let id2 = GoalId::from_counter(counter);
898 assert_eq!(id1, id2);
899 }
900
901 #[kani::proof]
902 fn goal_id_from_counter_not_zero() {
903 let counter: u64 = kani::any();
904 kani::assume(counter > 0 && counter <= 1_000_000);
905 let id = GoalId::from_counter(counter);
906 assert!(!id.is_zero());
907 }
908
909 #[kani::proof]
910 #[kani::unwind(20)]
911 fn goal_id_serialize_roundtrip() {
912 let mut uuid = [0u8; 16];
913 uuid[0] = kani::any();
915 uuid[7] = kani::any();
916 uuid[15] = kani::any();
917 let id = GoalId::new(uuid);
918
919 let mut buf = [0u8; 32];
920 let len = {
921 let mut writer = CdrWriter::new(&mut buf);
922 id.serialize(&mut writer).unwrap();
923 writer.position()
924 };
925
926 let mut reader = CdrReader::new(&buf[..len]);
927 let deserialized = GoalId::deserialize(&mut reader).unwrap();
928 assert_eq!(id, deserialized);
929 }
930}