1use nros_core::{GoalStatusCode, GoalUuid};
31
32pub const CDR_HEADER_LEN: usize = nros_serdes::CDR_HEADER_LEN;
34
35pub const REPLY_STATUS_OFFSET: usize = CDR_HEADER_LEN;
38
39pub const RESULT_PAYLOAD_OFFSET: usize = (REPLY_STATUS_OFFSET + 1 + 3) & !3;
42
43pub const FEEDBACK_PAYLOAD_OFFSET: usize =
46 CDR_HEADER_LEN + GoalUuid::SEQ_PREFIX_LEN + GoalUuid::UUID_LEN;
47
48pub const GOAL_REQUEST_PAYLOAD_OFFSET: usize = FEEDBACK_PAYLOAD_OFFSET;
51
52pub const CANCEL_GOALS_COUNT_OFFSET: usize = (REPLY_STATUS_OFFSET + 1 + 3) & !3;
55
56pub const CANCEL_GOALS_OFFSET: usize = CANCEL_GOALS_COUNT_OFFSET + 4;
58
59pub const GOAL_INFO_LEN: usize = GoalUuid::UUID_LEN + 4 + 4;
63
64const _: () = assert!(RESULT_PAYLOAD_OFFSET == 8);
65const _: () = assert!(CANCEL_GOALS_OFFSET == 12);
66const _: () = assert!(FEEDBACK_PAYLOAD_OFFSET == 20);
67
68pub fn goal_accepted(reply: &[u8]) -> bool {
70 reply.len() > REPLY_STATUS_OFFSET && reply[REPLY_STATUS_OFFSET] != 0
71}
72
73pub fn feedback_goal_id(msg: &[u8]) -> Option<GoalUuid> {
75 let bytes = msg.get(CDR_HEADER_LEN..FEEDBACK_PAYLOAD_OFFSET)?;
76 let mut uuid = [0u8; GoalUuid::UUID_LEN];
77 uuid.copy_from_slice(bytes);
78 Some(GoalUuid { uuid })
79}
80
81pub fn result_status(reply: &[u8]) -> Option<GoalStatusCode> {
85 if reply.len() < RESULT_PAYLOAD_OFFSET {
86 return None;
87 }
88 Some(match reply[REPLY_STATUS_OFFSET] {
89 4 => GoalStatusCode::Succeeded,
90 5 => GoalStatusCode::Canceled,
91 6 => GoalStatusCode::Aborted,
92 _ => GoalStatusCode::Unknown,
93 })
94}
95
96pub fn result_fields(reply: &[u8]) -> Option<&[u8]> {
99 reply.get(RESULT_PAYLOAD_OFFSET..)
100}
101
102pub fn feedback_fields(msg: &[u8]) -> Option<&[u8]> {
106 if msg.len() <= FEEDBACK_PAYLOAD_OFFSET {
107 return None;
108 }
109 msg.get(FEEDBACK_PAYLOAD_OFFSET..)
110}
111
112pub fn goal_request_fields(req: &[u8]) -> Option<&[u8]> {
115 if req.len() <= GOAL_REQUEST_PAYLOAD_OFFSET {
116 return None;
117 }
118 req.get(GOAL_REQUEST_PAYLOAD_OFFSET..)
119}
120
121pub fn cancel_return_code(reply: &[u8]) -> Option<i8> {
123 reply.get(REPLY_STATUS_OFFSET).map(|b| *b as i8)
124}
125
126pub fn cancel_goals_count(reply: &[u8]) -> Option<usize> {
130 let raw = reply.get(CANCEL_GOALS_COUNT_OFFSET..CANCEL_GOALS_OFFSET)?;
131 let n = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
132 let need = n
133 .checked_mul(GOAL_INFO_LEN)?
134 .checked_add(CANCEL_GOALS_OFFSET)?;
135 (reply.len() >= need).then_some(n)
136}
137
138pub fn cancel_goal_info(reply: &[u8], i: usize) -> Option<(GoalUuid, i32, u32)> {
142 if i >= cancel_goals_count(reply)? {
143 return None;
144 }
145 let at = CANCEL_GOALS_OFFSET + i * GOAL_INFO_LEN;
146 let b = reply.get(at..at + GOAL_INFO_LEN)?;
147 let mut uuid = [0u8; GoalUuid::UUID_LEN];
148 uuid.copy_from_slice(&b[..GoalUuid::UUID_LEN]);
149 let s = GoalUuid::UUID_LEN;
150 let sec = i32::from_le_bytes([b[s], b[s + 1], b[s + 2], b[s + 3]]);
151 let nanosec = u32::from_le_bytes([b[s + 4], b[s + 5], b[s + 6], b[s + 7]]);
152 Some((GoalUuid { uuid }, sec, nanosec))
153}
154
155pub fn splice(frame: &[u8], fields: &[u8], out: &mut [u8]) -> Option<usize> {
159 if frame.len() < CDR_HEADER_LEN || out.len() < CDR_HEADER_LEN {
160 return None;
161 }
162 let n = fields.len().min(out.len() - CDR_HEADER_LEN);
163 out[..CDR_HEADER_LEN].copy_from_slice(&frame[..CDR_HEADER_LEN]);
164 out[CDR_HEADER_LEN..CDR_HEADER_LEN + n].copy_from_slice(&fields[..n]);
165 Some(CDR_HEADER_LEN + n)
166}
167
168pub fn splice_result(reply: &[u8], out: &mut [u8]) -> Option<usize> {
170 splice(reply, result_fields(reply)?, out)
171}
172
173pub fn splice_feedback(msg: &[u8], out: &mut [u8]) -> Option<usize> {
175 splice(msg, feedback_fields(msg)?, out)
176}
177
178pub fn splice_goal_request(req: &[u8], out: &mut [u8]) -> Option<usize> {
180 splice(req, goal_request_fields(req)?, out)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn result_payload_starts_after_the_alignment_pad() {
189 let reply = [0, 1, 0, 0, 4, 0xEE, 0xEE, 0xEE, 0xAA, 0xBB];
191 assert_eq!(result_status(&reply), Some(GoalStatusCode::Succeeded));
192 assert_eq!(result_fields(&reply), Some(&[0xAA, 0xBB][..]));
193 let mut out = [0u8; 16];
194 let n = splice_result(&reply, &mut out).unwrap();
195 assert_eq!(&out[..n], &[0, 1, 0, 0, 0xAA, 0xBB]);
196 }
197
198 #[test]
199 fn cancel_reply_lists_its_canceling_goals() {
200 let mut reply = [0u8; CANCEL_GOALS_OFFSET + 2 * GOAL_INFO_LEN];
202 reply[..4].copy_from_slice(&[0, 1, 0, 0]);
203 reply[5..8].copy_from_slice(&[0xEE; 3]);
204 reply[8..12].copy_from_slice(&2u32.to_le_bytes());
205 reply[12] = 9;
206 reply[28..32].copy_from_slice(&5i32.to_le_bytes());
207 reply[32..36].copy_from_slice(&7u32.to_le_bytes());
208 reply[36] = 10;
209 assert_eq!(cancel_return_code(&reply), Some(0));
210 assert_eq!(cancel_goals_count(&reply), Some(2));
211 let (g, sec, ns) = cancel_goal_info(&reply, 0).unwrap();
212 assert_eq!((g.uuid[0], sec, ns), (9, 5, 7));
213 assert_eq!(cancel_goal_info(&reply, 1).unwrap().0.uuid[0], 10);
214 assert!(cancel_goal_info(&reply, 2).is_none());
215 assert_eq!(cancel_goals_count(&reply[..CANCEL_GOALS_OFFSET + 4]), None);
217 assert_eq!(cancel_goals_count(&[0, 1, 0, 0, 0]), None);
218 }
219
220 #[test]
221 fn short_frames_carry_nothing() {
222 assert_eq!(result_status(&[0, 1, 0, 0, 4]), None);
223 assert_eq!(feedback_fields(&[0u8; FEEDBACK_PAYLOAD_OFFSET]), None);
224 assert!(!goal_accepted(&[0, 1, 0, 0]));
225 }
226
227 #[test]
228 fn feedback_splices_the_frames_own_header() {
229 let mut msg = [0u8; FEEDBACK_PAYLOAD_OFFSET + 2];
230 msg[..4].copy_from_slice(&[0, 0, 0, 0]); msg[4] = 7; msg[FEEDBACK_PAYLOAD_OFFSET..].copy_from_slice(&[1, 2]);
233 assert_eq!(feedback_goal_id(&msg).unwrap().uuid[0], 7);
234 let mut out = [0u8; 8];
235 let n = splice_feedback(&msg, &mut out).unwrap();
236 assert_eq!(&out[..n], &[0, 0, 0, 0, 1, 2]);
237 }
238}