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 const SEND_GOAL_SERVICE_HASH: &'static str = Self::ACTION_HASH;
93
94 const GET_RESULT_SERVICE_HASH: &'static str = Self::ACTION_HASH;
97
98 const CANCEL_GOAL_SERVICE_HASH: &'static str = Self::ACTION_HASH;
104
105 const STATUS_MESSAGE_HASH: &'static str = Self::ACTION_HASH;
108
109 #[allow(clippy::result_unit_err)]
125 fn register_protocol_types() -> Result<(), ()> {
126 Ok(())
127 }
128}
129
130#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
138#[repr(i8)]
139pub enum GoalStatusCode {
140 #[default]
142 Unknown = 0,
143 Accepted = 1,
145 Executing = 2,
147 Canceling = 3,
149 Succeeded = 4,
151 Canceled = 5,
153 Aborted = 6,
155}
156
157impl GoalStatusCode {
158 pub fn is_terminal(&self) -> bool {
160 matches!(
161 self,
162 GoalStatusCode::Succeeded | GoalStatusCode::Canceled | GoalStatusCode::Aborted
163 )
164 }
165
166 pub fn is_active(&self) -> bool {
168 matches!(
169 self,
170 GoalStatusCode::Accepted | GoalStatusCode::Executing | GoalStatusCode::Canceling
171 )
172 }
173
174 pub fn from_i8(value: i8) -> Option<Self> {
176 match value {
177 0 => Some(GoalStatusCode::Unknown),
178 1 => Some(GoalStatusCode::Accepted),
179 2 => Some(GoalStatusCode::Executing),
180 3 => Some(GoalStatusCode::Canceling),
181 4 => Some(GoalStatusCode::Succeeded),
182 5 => Some(GoalStatusCode::Canceled),
183 6 => Some(GoalStatusCode::Aborted),
184 _ => None,
185 }
186 }
187}
188
189impl fmt::Display for GoalStatusCode {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 match self {
192 GoalStatusCode::Unknown => write!(f, "UNKNOWN"),
193 GoalStatusCode::Accepted => write!(f, "ACCEPTED"),
194 GoalStatusCode::Executing => write!(f, "EXECUTING"),
195 GoalStatusCode::Canceling => write!(f, "CANCELING"),
196 GoalStatusCode::Succeeded => write!(f, "SUCCEEDED"),
197 GoalStatusCode::Canceled => write!(f, "CANCELED"),
198 GoalStatusCode::Aborted => write!(f, "ABORTED"),
199 }
200 }
201}
202
203impl Serialize for GoalStatusCode {
204 fn serialize(&self, writer: &mut CdrWriter) -> Result<(), SerError> {
205 writer.write_i8(*self as i8)
206 }
207}
208
209impl Deserialize for GoalStatusCode {
210 fn deserialize(reader: &mut CdrReader) -> Result<Self, DeserError> {
211 let value = reader.read_i8()?;
212 GoalStatusCode::from_i8(value).ok_or(DeserError::InvalidData)
213 }
214}
215
216#[derive(Clone, Copy, PartialEq, Eq, Hash)]
221pub struct GoalUuid {
222 pub uuid: [u8; 16],
224}
225
226impl GoalUuid {
227 pub const UUID_LEN: usize = 16;
229
230 pub const SEQ_PREFIX_LEN: usize = 0;
241
242 pub const fn new(uuid: [u8; 16]) -> Self {
244 Self { uuid }
245 }
246
247 pub const fn zero() -> Self {
249 Self { uuid: [0; 16] }
250 }
251
252 pub fn is_zero(&self) -> bool {
254 self.uuid == [0; 16]
255 }
256
257 pub fn from_counter(counter: u64) -> Self {
262 let mut uuid = [0u8; 16];
263 uuid[8..16].copy_from_slice(&counter.to_be_bytes());
265 uuid[6] = (uuid[6] & 0x0f) | 0x40; uuid[8] = (uuid[8] & 0x3f) | 0x80; Self { uuid }
269 }
270}
271
272impl Default for GoalUuid {
273 fn default() -> Self {
274 Self::zero()
275 }
276}
277
278impl Serialize for GoalUuid {
279 fn serialize(&self, writer: &mut CdrWriter) -> Result<(), SerError> {
280 for byte in &self.uuid {
281 writer.write_u8(*byte)?;
282 }
283 Ok(())
284 }
285}
286
287impl Deserialize for GoalUuid {
288 fn deserialize(reader: &mut CdrReader) -> Result<Self, DeserError> {
289 let mut uuid = [0u8; 16];
290 for byte in &mut uuid {
291 *byte = reader.read_u8()?;
292 }
293 Ok(Self { uuid })
294 }
295}
296
297impl fmt::Debug for GoalUuid {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 write!(
301 f,
302 "GoalUuid({:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x})",
303 self.uuid[0],
304 self.uuid[1],
305 self.uuid[2],
306 self.uuid[3],
307 self.uuid[4],
308 self.uuid[5],
309 self.uuid[6],
310 self.uuid[7],
311 self.uuid[8],
312 self.uuid[9],
313 self.uuid[10],
314 self.uuid[11],
315 self.uuid[12],
316 self.uuid[13],
317 self.uuid[14],
318 self.uuid[15]
319 )
320 }
321}
322
323impl fmt::Display for GoalUuid {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 write!(
326 f,
327 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
328 self.uuid[0],
329 self.uuid[1],
330 self.uuid[2],
331 self.uuid[3],
332 self.uuid[4],
333 self.uuid[5],
334 self.uuid[6],
335 self.uuid[7],
336 self.uuid[8],
337 self.uuid[9],
338 self.uuid[10],
339 self.uuid[11],
340 self.uuid[12],
341 self.uuid[13],
342 self.uuid[14],
343 self.uuid[15]
344 )
345 }
346}
347
348#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
353pub struct GoalInfo {
354 pub goal_id: GoalUuid,
356 pub stamp_sec: i32,
358 pub stamp_nanosec: u32,
360}
361
362impl GoalInfo {
363 pub const fn new(goal_id: GoalUuid, stamp_sec: i32, stamp_nanosec: u32) -> Self {
365 Self {
366 goal_id,
367 stamp_sec,
368 stamp_nanosec,
369 }
370 }
371
372 pub const fn with_id(goal_id: GoalUuid) -> Self {
374 Self {
375 goal_id,
376 stamp_sec: 0,
377 stamp_nanosec: 0,
378 }
379 }
380}
381
382impl Serialize for GoalInfo {
383 fn serialize(&self, writer: &mut CdrWriter) -> Result<(), SerError> {
384 self.goal_id.serialize(writer)?;
385 writer.write_i32(self.stamp_sec)?;
386 writer.write_u32(self.stamp_nanosec)?;
387 Ok(())
388 }
389}
390
391impl Deserialize for GoalInfo {
392 fn deserialize(reader: &mut CdrReader) -> Result<Self, DeserError> {
393 Ok(Self {
394 goal_id: GoalUuid::deserialize(reader)?,
395 stamp_sec: reader.read_i32()?,
396 stamp_nanosec: reader.read_u32()?,
397 })
398 }
399}
400
401#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
405pub struct GoalStatusStamped {
406 pub goal_info: GoalInfo,
408 pub status: GoalStatusCode,
410}
411
412impl GoalStatusStamped {
413 pub const fn new(goal_info: GoalInfo, status: GoalStatusCode) -> Self {
415 Self { goal_info, status }
416 }
417}
418
419impl Serialize for GoalStatusStamped {
420 fn serialize(&self, writer: &mut CdrWriter) -> Result<(), SerError> {
421 self.goal_info.serialize(writer)?;
422 writer.write_i8(self.status as i8)?;
423 Ok(())
424 }
425}
426
427impl Deserialize for GoalStatusStamped {
428 fn deserialize(reader: &mut CdrReader) -> Result<Self, DeserError> {
429 let goal_info = GoalInfo::deserialize(reader)?;
430 let status_val = reader.read_i8()?;
431 let status = GoalStatusCode::from_i8(status_val).unwrap_or(GoalStatusCode::Unknown);
432 Ok(Self { goal_info, status })
433 }
434}
435
436#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
455#[repr(i8)]
456pub enum CancelResponseCode {
457 #[default]
459 Ok = 0,
460 Rejected = 1,
462 UnknownGoal = 2,
464 GoalTerminated = 3,
466}
467
468impl CancelResponseCode {
469 pub fn from_i8(value: i8) -> Option<Self> {
471 match value {
472 0 => Some(CancelResponseCode::Ok),
473 1 => Some(CancelResponseCode::Rejected),
474 2 => Some(CancelResponseCode::UnknownGoal),
475 3 => Some(CancelResponseCode::GoalTerminated),
476 _ => None,
477 }
478 }
479}
480
481impl Serialize for CancelResponseCode {
482 fn serialize(&self, writer: &mut CdrWriter) -> Result<(), SerError> {
483 writer.write_i8(*self as i8)
484 }
485}
486
487impl Deserialize for CancelResponseCode {
488 fn deserialize(reader: &mut CdrReader) -> Result<Self, DeserError> {
489 let value = reader.read_i8()?;
490 CancelResponseCode::from_i8(value).ok_or(DeserError::InvalidData)
491 }
492}
493
494#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
506#[repr(i8)]
507pub enum CancelResponse {
508 #[default]
510 Reject = 0,
511 Accept = 1,
513}
514
515impl CancelResponse {
516 pub fn from_i8(value: i8) -> Option<Self> {
518 match value {
519 0 => Some(CancelResponse::Reject),
520 1 => Some(CancelResponse::Accept),
521 _ => None,
522 }
523 }
524}
525
526#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
528#[repr(i8)]
529pub enum GoalResponse {
530 #[default]
532 Reject = 0,
533 AcceptAndExecute = 1,
535 AcceptAndDefer = 2,
537}
538
539impl GoalResponse {
540 pub fn from_i8(value: i8) -> Option<Self> {
542 match value {
543 0 => Some(GoalResponse::Reject),
544 1 => Some(GoalResponse::AcceptAndExecute),
545 2 => Some(GoalResponse::AcceptAndDefer),
546 _ => None,
547 }
548 }
549
550 pub fn is_accepted(&self) -> bool {
552 matches!(
553 self,
554 GoalResponse::AcceptAndExecute | GoalResponse::AcceptAndDefer
555 )
556 }
557}
558
559#[deprecated(
572 since = "0.6.0",
573 note = "renamed to `GoalUuid`, rclrs's name (phase-515 R8)"
574)]
575pub type GoalId = GoalUuid;
576
577#[deprecated(
579 since = "0.6.0",
580 note = "renamed to `GoalStatusCode`, rclrs's name (phase-515 R8)"
581)]
582pub type GoalStatus = GoalStatusCode;
583
584#[deprecated(
586 since = "0.6.0",
587 note = "renamed to `CancelResponseCode`, rclrs's name (phase-515 R8)"
588)]
589pub type CancelReturnCode = CancelResponseCode;
590
591#[cfg(test)]
592mod tests {
593 extern crate std;
594
595 use super::*;
596 use std::format;
597
598 #[test]
599 fn test_goal_status_is_terminal() {
600 assert!(!GoalStatusCode::Unknown.is_terminal());
601 assert!(!GoalStatusCode::Accepted.is_terminal());
602 assert!(!GoalStatusCode::Executing.is_terminal());
603 assert!(!GoalStatusCode::Canceling.is_terminal());
604 assert!(GoalStatusCode::Succeeded.is_terminal());
605 assert!(GoalStatusCode::Canceled.is_terminal());
606 assert!(GoalStatusCode::Aborted.is_terminal());
607 }
608
609 #[test]
610 fn test_goal_status_is_active() {
611 assert!(!GoalStatusCode::Unknown.is_active());
612 assert!(GoalStatusCode::Accepted.is_active());
613 assert!(GoalStatusCode::Executing.is_active());
614 assert!(GoalStatusCode::Canceling.is_active());
615 assert!(!GoalStatusCode::Succeeded.is_active());
616 assert!(!GoalStatusCode::Canceled.is_active());
617 assert!(!GoalStatusCode::Aborted.is_active());
618 }
619
620 #[test]
621 fn test_goal_status_from_i8() {
622 assert_eq!(GoalStatusCode::from_i8(0), Some(GoalStatusCode::Unknown));
623 assert_eq!(GoalStatusCode::from_i8(1), Some(GoalStatusCode::Accepted));
624 assert_eq!(GoalStatusCode::from_i8(2), Some(GoalStatusCode::Executing));
625 assert_eq!(GoalStatusCode::from_i8(3), Some(GoalStatusCode::Canceling));
626 assert_eq!(GoalStatusCode::from_i8(4), Some(GoalStatusCode::Succeeded));
627 assert_eq!(GoalStatusCode::from_i8(5), Some(GoalStatusCode::Canceled));
628 assert_eq!(GoalStatusCode::from_i8(6), Some(GoalStatusCode::Aborted));
629 assert_eq!(GoalStatusCode::from_i8(7), None);
630 assert_eq!(GoalStatusCode::from_i8(-1), None);
631 }
632
633 #[test]
634 fn test_goal_id_zero() {
635 let id = GoalUuid::zero();
636 assert!(id.is_zero());
637 assert_eq!(id.uuid, [0; 16]);
638 }
639
640 #[test]
641 fn test_goal_id_from_counter() {
642 let id1 = GoalUuid::from_counter(1);
643 let id2 = GoalUuid::from_counter(2);
644
645 assert!(!id1.is_zero());
646 assert!(!id2.is_zero());
647 assert_ne!(id1, id2);
648
649 let id1_again = GoalUuid::from_counter(1);
651 assert_eq!(id1, id1_again);
652 }
653
654 #[test]
655 fn test_goal_id_display() {
656 let id = GoalUuid::new([
657 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
658 0x77, 0x88,
659 ]);
660 let s = format!("{}", id);
661 assert_eq!(s, "12345678-9abc-def0-1122-334455667788");
662 }
663
664 #[test]
665 fn test_cancel_return_code_from_i8() {
666 assert_eq!(CancelResponseCode::from_i8(0), Some(CancelResponseCode::Ok));
667 assert_eq!(
668 CancelResponseCode::from_i8(1),
669 Some(CancelResponseCode::Rejected)
670 );
671 assert_eq!(
672 CancelResponseCode::from_i8(2),
673 Some(CancelResponseCode::UnknownGoal)
674 );
675 assert_eq!(
676 CancelResponseCode::from_i8(3),
677 Some(CancelResponseCode::GoalTerminated)
678 );
679 assert_eq!(CancelResponseCode::from_i8(4), None);
680 }
681
682 #[test]
688 fn test_cancel_response_is_not_a_return_code() {
689 assert_eq!(CancelResponse::from_i8(0), Some(CancelResponse::Reject));
690 assert_eq!(CancelResponse::from_i8(1), Some(CancelResponse::Accept));
691 assert_eq!(CancelResponse::from_i8(2), None);
692 assert_eq!(CancelResponse::default(), CancelResponse::Reject);
693
694 assert_eq!(CancelResponse::Reject as i8, CancelResponseCode::Ok as i8);
696 assert_eq!(
697 CancelResponse::Accept as i8,
698 CancelResponseCode::Rejected as i8
699 );
700 }
701
702 #[test]
703 fn test_goal_response_is_accepted() {
704 assert!(!GoalResponse::Reject.is_accepted());
705 assert!(GoalResponse::AcceptAndExecute.is_accepted());
706 assert!(GoalResponse::AcceptAndDefer.is_accepted());
707 }
708
709 #[test]
710 fn test_goal_info_new() {
711 let goal_id = GoalUuid::from_counter(42);
712 let info = GoalInfo::new(goal_id, 123, 456);
713 assert_eq!(info.goal_id, goal_id);
714 assert_eq!(info.stamp_sec, 123);
715 assert_eq!(info.stamp_nanosec, 456);
716 }
717
718 #[test]
719 fn test_goal_info_with_id() {
720 let goal_id = GoalUuid::from_counter(42);
721 let info = GoalInfo::with_id(goal_id);
722 assert_eq!(info.goal_id, goal_id);
723 assert_eq!(info.stamp_sec, 0);
724 assert_eq!(info.stamp_nanosec, 0);
725 }
726
727 #[test]
728 fn test_goal_status_stamped() {
729 let goal_id = GoalUuid::from_counter(1);
730 let info = GoalInfo::with_id(goal_id);
731 let stamped = GoalStatusStamped::new(info, GoalStatusCode::Executing);
732 assert_eq!(stamped.goal_info.goal_id, goal_id);
733 assert_eq!(stamped.status, GoalStatusCode::Executing);
734 }
735
736 #[test]
737 fn test_goal_id_serialization() {
738 use nros_serdes::{CdrReader, CdrWriter};
739
740 let original = GoalUuid::new([
741 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
742 0x0f, 0x10,
743 ]);
744
745 let mut buf = [0u8; 32];
747 let mut writer = CdrWriter::new(&mut buf);
748 original.serialize(&mut writer).unwrap();
749 let len = writer.position();
750 assert_eq!(len, 16); let mut reader = CdrReader::new(&buf[..len]);
754 let deserialized = GoalUuid::deserialize(&mut reader).unwrap();
755
756 assert_eq!(original, deserialized);
757 }
758
759 #[test]
760 fn test_goal_status_serialization() {
761 use nros_serdes::{CdrReader, CdrWriter};
762
763 for status in [
764 GoalStatusCode::Unknown,
765 GoalStatusCode::Accepted,
766 GoalStatusCode::Executing,
767 GoalStatusCode::Canceling,
768 GoalStatusCode::Succeeded,
769 GoalStatusCode::Canceled,
770 GoalStatusCode::Aborted,
771 ] {
772 let mut buf = [0u8; 8];
773 let len = {
774 let mut writer = CdrWriter::new(&mut buf);
775 status.serialize(&mut writer).unwrap();
776 writer.position()
777 };
778
779 let mut reader = CdrReader::new(&buf[..len]);
780 let deserialized = GoalStatusCode::deserialize(&mut reader).unwrap();
781
782 assert_eq!(status, deserialized);
783 }
784 }
785
786 #[test]
787 fn test_goal_info_serialization() {
788 use nros_serdes::{CdrReader, CdrWriter};
789
790 let goal_id = GoalUuid::from_counter(42);
791 let original = GoalInfo::new(goal_id, 1234567890, 123456789);
792
793 let mut buf = [0u8; 64];
795 let mut writer = CdrWriter::new(&mut buf);
796 original.serialize(&mut writer).unwrap();
797 let len = writer.position();
798 assert_eq!(len, 24);
800
801 let mut reader = CdrReader::new(&buf[..len]);
803 let deserialized = GoalInfo::deserialize(&mut reader).unwrap();
804
805 assert_eq!(original.goal_id, deserialized.goal_id);
806 assert_eq!(original.stamp_sec, deserialized.stamp_sec);
807 assert_eq!(original.stamp_nanosec, deserialized.stamp_nanosec);
808 }
809
810 #[test]
811 fn test_goal_status_stamped_serialization() {
812 use nros_serdes::{CdrReader, CdrWriter};
813
814 let goal_id = GoalUuid::from_counter(99);
815 let goal_info = GoalInfo::new(goal_id, 987654321, 111222333);
816 let original = GoalStatusStamped::new(goal_info, GoalStatusCode::Succeeded);
817
818 let mut buf = [0u8; 64];
820 let mut writer = CdrWriter::new(&mut buf);
821 original.serialize(&mut writer).unwrap();
822 let len = writer.position();
823 assert_eq!(len, 25);
825
826 let mut reader = CdrReader::new(&buf[..len]);
828 let deserialized = GoalStatusStamped::deserialize(&mut reader).unwrap();
829
830 assert_eq!(original.goal_info.goal_id, deserialized.goal_info.goal_id);
831 assert_eq!(
832 original.goal_info.stamp_sec,
833 deserialized.goal_info.stamp_sec
834 );
835 assert_eq!(
836 original.goal_info.stamp_nanosec,
837 deserialized.goal_info.stamp_nanosec
838 );
839 assert_eq!(original.status, deserialized.status);
840 }
841}
842
843#[cfg(kani)]
848mod verification {
849 use super::*;
850
851 #[kani::proof]
854 fn goal_status_from_i8_valid_range() {
855 let val: i8 = kani::any();
856 let status = GoalStatusCode::from_i8(val);
857 if (0..=6).contains(&val) {
858 assert!(status.is_some());
859 } else {
860 assert!(status.is_none());
861 }
862 }
863
864 #[kani::proof]
865 fn goal_status_terminal_active_exclusive() {
866 let val: i8 = kani::any();
867 kani::assume((0..=6).contains(&val));
868 let status = GoalStatusCode::from_i8(val).unwrap();
869 assert!(!(status.is_terminal() && status.is_active()));
871 if val == 0 {
873 assert!(!status.is_terminal());
874 assert!(!status.is_active());
875 }
876 if (1..=3).contains(&val) {
878 assert!(status.is_active());
879 assert!(!status.is_terminal());
880 }
881 if (4..=6).contains(&val) {
883 assert!(status.is_terminal());
884 assert!(!status.is_active());
885 }
886 }
887
888 #[kani::proof]
889 #[kani::unwind(5)]
890 fn goal_status_serialize_roundtrip() {
891 let val: i8 = kani::any();
892 kani::assume((0..=6).contains(&val));
893 let status = GoalStatusCode::from_i8(val).unwrap();
894
895 let mut buf = [0u8; 8];
896 let len = {
897 let mut writer = CdrWriter::new(&mut buf);
898 status.serialize(&mut writer).unwrap();
899 writer.position()
900 };
901
902 let mut reader = CdrReader::new(&buf[..len]);
903 let deserialized = GoalStatusCode::deserialize(&mut reader).unwrap();
904 assert_eq!(status, deserialized);
905 }
906
907 #[kani::proof]
910 fn goal_response_from_i8_valid_range() {
911 let val: i8 = kani::any();
912 let resp = GoalResponse::from_i8(val);
913 if (0..=2).contains(&val) {
914 assert!(resp.is_some());
915 } else {
916 assert!(resp.is_none());
917 }
918 }
919
920 #[kani::proof]
921 fn goal_response_is_accepted_consistent() {
922 let val: i8 = kani::any();
923 kani::assume((0..=2).contains(&val));
924 let resp = GoalResponse::from_i8(val).unwrap();
925 assert_eq!(resp.is_accepted(), val >= 1);
927 }
928
929 #[kani::proof]
932 fn cancel_return_code_from_i8_valid_range() {
933 let val: i8 = kani::any();
934 let resp = CancelResponseCode::from_i8(val);
935 if (0..=3).contains(&val) {
936 assert!(resp.is_some());
937 } else {
938 assert!(resp.is_none());
939 }
940 }
941
942 #[kani::proof]
945 fn cancel_response_from_i8_valid_range() {
946 let val: i8 = kani::any();
947 let resp = CancelResponse::from_i8(val);
948 if (0..=1).contains(&val) {
949 assert!(resp.is_some());
950 } else {
951 assert!(resp.is_none());
952 }
953 }
954
955 #[kani::proof]
958 fn goal_id_zero_is_zero() {
959 let id = GoalUuid::zero();
960 assert!(id.is_zero());
961 }
962
963 #[kani::proof]
964 fn goal_id_from_counter_deterministic() {
965 let counter: u64 = kani::any();
966 kani::assume(counter <= 1_000_000);
968 let id1 = GoalUuid::from_counter(counter);
969 let id2 = GoalUuid::from_counter(counter);
970 assert_eq!(id1, id2);
971 }
972
973 #[kani::proof]
974 fn goal_id_from_counter_not_zero() {
975 let counter: u64 = kani::any();
976 kani::assume(counter > 0 && counter <= 1_000_000);
977 let id = GoalUuid::from_counter(counter);
978 assert!(!id.is_zero());
979 }
980
981 #[kani::proof]
982 #[kani::unwind(20)]
983 fn goal_id_serialize_roundtrip() {
984 let mut uuid = [0u8; 16];
985 uuid[0] = kani::any();
987 uuid[7] = kani::any();
988 uuid[15] = kani::any();
989 let id = GoalUuid::new(uuid);
990
991 let mut buf = [0u8; 32];
992 let len = {
993 let mut writer = CdrWriter::new(&mut buf);
994 id.serialize(&mut writer).unwrap();
995 writer.position()
996 };
997
998 let mut reader = CdrReader::new(&buf[..len]);
999 let deserialized = GoalUuid::deserialize(&mut reader).unwrap();
1000 assert_eq!(id, deserialized);
1001 }
1002}