1use crate::ClientTrait;
34
35pub const SEQUENCE_ID_UNKNOWN: i64 = i64::MIN;
40
41pub const MAX_STALE_DRAIN_PER_POLL: usize = 8;
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum SendRefusal {
50 Full,
52 Unkeyed,
56}
57
58#[derive(Clone, Copy, Debug)]
64pub struct RequestCorrelator<K: Copy, const N: usize> {
65 entries: [(i64, K); N],
66 len: u8,
67 orphans: u8,
72 dropped: u32,
75}
76
77impl<K: Copy + Default, const N: usize> Default for RequestCorrelator<K, N> {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl<K: Copy + Default, const N: usize> RequestCorrelator<K, N> {
84 pub fn new() -> Self {
86 const { assert!(N >= 1 && N <= u8::MAX as usize, "N must be 1..=255") };
87 Self {
88 entries: [(SEQUENCE_ID_UNKNOWN, K::default()); N],
89 len: 0,
90 orphans: 0,
91 dropped: 0,
92 }
93 }
94}
95
96impl<K: Copy, const N: usize> RequestCorrelator<K, N> {
97 pub const CAPACITY: usize = N;
99
100 pub fn len(&self) -> usize {
102 self.len as usize
103 }
104
105 pub fn is_empty(&self) -> bool {
107 self.len == 0
108 }
109
110 pub fn dropped(&self) -> u32 {
112 self.dropped
113 }
114
115 fn live(&self) -> &[(i64, K)] {
116 &self.entries[..self.len as usize]
117 }
118
119 pub fn can_send(&self) -> Result<(), SendRefusal> {
122 if self.live().iter().any(|(s, _)| *s == SEQUENCE_ID_UNKNOWN) {
123 return Err(SendRefusal::Unkeyed);
124 }
125 if self.len as usize >= N {
126 return Err(SendRefusal::Full);
127 }
128 Ok(())
129 }
130
131 pub fn contains(&self, seq: i64) -> bool {
133 self.live().iter().any(|(s, _)| *s == seq)
134 }
135
136 pub fn insert(&mut self, seq: i64, key: K) -> Result<(), SendRefusal> {
140 if self.len as usize >= N {
141 return Err(SendRefusal::Full);
142 }
143 self.entries[self.len as usize] = (seq, key);
144 self.len += 1;
145 Ok(())
146 }
147
148 pub fn insert_evicting(&mut self, seq: i64, key: K) {
153 if self.len as usize >= N {
154 let evicted = self.entries[0].0;
155 self.remove_at(0);
156 self.note_abandoned(evicted);
157 }
158 let _ = self.insert(seq, key);
159 }
160
161 fn remove_at(&mut self, i: usize) {
162 let len = self.len as usize;
163 self.entries.copy_within(i + 1..len, i);
164 self.len -= 1;
165 }
166
167 fn note_abandoned(&mut self, seq: i64) {
168 if seq == SEQUENCE_ID_UNKNOWN {
169 self.orphans = self.orphans.saturating_add(1);
170 }
171 }
172
173 pub fn remove(&mut self, seq: i64) -> bool {
176 match self.live().iter().position(|(s, _)| *s == seq) {
177 Some(i) => {
178 self.remove_at(i);
179 self.note_abandoned(seq);
180 true
181 }
182 None => false,
183 }
184 }
185
186 pub fn remove_key(&mut self, key: &K) -> bool
194 where
195 K: PartialEq,
196 {
197 match self.live().iter().position(|(_, k)| k == key) {
198 Some(i) => {
199 let seq = self.entries[i].0;
200 self.remove_at(i);
201 self.note_abandoned(seq);
202 true
203 }
204 None => false,
205 }
206 }
207
208 pub fn clear(&mut self) {
211 for i in 0..self.len as usize {
212 let seq = self.entries[i].0;
213 self.note_abandoned(seq);
214 }
215 self.len = 0;
216 }
217
218 pub fn resolve(&mut self, reply_seq: i64) -> Option<K> {
222 let matched = if reply_seq == SEQUENCE_ID_UNKNOWN {
223 if self.orphans > 0 {
224 self.orphans -= 1;
227 None
228 } else if self.len == 1 {
229 Some(0)
230 } else {
231 None
232 }
233 } else {
234 match self.live().iter().position(|(s, _)| *s == reply_seq) {
235 Some(i) => Some(i),
236 None if self.len == 1 && self.entries[0].0 == SEQUENCE_ID_UNKNOWN => Some(0),
239 None => None,
240 }
241 };
242 match matched {
243 Some(i) => {
244 let key = self.entries[i].1;
245 self.remove_at(i);
246 Some(key)
247 }
248 None => {
249 self.dropped = self.dropped.wrapping_add(1);
250 None
251 }
252 }
253 }
254}
255
256pub trait ReplyMatcher {
259 fn match_reply(&mut self, reply_seq: i64) -> bool;
262}
263
264impl<K: Copy, const N: usize> ReplyMatcher for RequestCorrelator<K, N> {
265 fn match_reply(&mut self, reply_seq: i64) -> bool {
266 self.resolve(reply_seq).is_some()
267 }
268}
269
270pub fn take_correlated<C, K, const N: usize>(
277 client: &mut C,
278 buf: &mut [u8],
279 pending: &mut RequestCorrelator<K, N>,
280) -> Result<Option<(usize, i64, K)>, C::Error>
281where
282 C: ClientTrait + ?Sized,
283 K: Copy,
284{
285 for _ in 0..MAX_STALE_DRAIN_PER_POLL {
286 match client.take_response_raw(buf)? {
287 None => return Ok(None),
288 Some((len, seq)) => {
289 if let Some(key) = pending.resolve(seq) {
290 return Ok(Some((len, seq, key)));
291 }
292 }
293 }
294 }
295 Ok(None)
296}
297
298pub fn take_matched<C>(
300 client: &mut C,
301 buf: &mut [u8],
302 pending: &mut dyn ReplyMatcher,
303) -> Result<Option<(usize, i64)>, C::Error>
304where
305 C: ClientTrait + ?Sized,
306{
307 for _ in 0..MAX_STALE_DRAIN_PER_POLL {
308 match client.take_response_raw(buf)? {
309 None => return Ok(None),
310 Some((len, seq)) => {
311 if pending.match_reply(seq) {
312 return Ok(Some((len, seq)));
313 }
314 }
315 }
316 }
317 Ok(None)
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::TransportError;
324 use std::vec::Vec;
325
326 type Table = RequestCorrelator<u32, 4>;
327
328 #[test]
329 fn a_reply_reaches_only_the_request_it_names() {
330 let mut t = Table::new();
331 t.insert(10, 1).unwrap();
332 t.insert(11, 2).unwrap();
333 assert_eq!(t.resolve(11), Some(2));
335 assert_eq!(t.resolve(10), Some(1));
336 assert!(t.is_empty());
337 }
338
339 #[test]
340 fn remove_key_frees_one_slot_and_drops_its_late_reply() {
341 let mut t = Table::new();
342 for (seq, key) in [(10, 7), (11, 8), (12, 7), (13, 9)] {
343 t.insert(seq, key).unwrap();
344 }
345 assert_eq!(t.can_send(), Err(SendRefusal::Full));
346 assert!(t.remove_key(&7), "the oldest request about 7 is forgotten");
347 assert_eq!(t.len(), 3);
348 assert!(t.can_send().is_ok(), "the freed slot takes a new request");
349 assert_eq!(
350 t.resolve(10),
351 None,
352 "the forgotten request's late reply is dropped"
353 );
354 assert_eq!(
355 t.resolve(12),
356 Some(7),
357 "the other request about 7 is still pending"
358 );
359 assert!(!t.remove_key(&42), "nothing pending about 42");
360 assert_eq!(t.len(), 2);
361 }
362
363 #[test]
364 fn a_reply_for_a_removed_request_is_dropped() {
365 let mut t = Table::new();
366 t.insert(10, 1).unwrap();
367 assert!(t.remove(10));
368 t.insert(11, 2).unwrap();
369 assert_eq!(t.resolve(10), None, "the late reply goes to nobody");
370 assert_eq!(t.dropped(), 1);
371 assert_eq!(
372 t.resolve(11),
373 Some(2),
374 "the next request still gets its own"
375 );
376 }
377
378 #[test]
379 fn a_full_table_refuses_the_send() {
380 let mut t = RequestCorrelator::<(), 2>::new();
381 t.insert(1, ()).unwrap();
382 t.insert(2, ()).unwrap();
383 assert_eq!(t.can_send(), Err(SendRefusal::Full));
384 assert_eq!(t.insert(3, ()), Err(SendRefusal::Full));
385 assert_eq!(t.len(), 2, "a refused insert records nothing");
386 assert_eq!(t.resolve(1), Some(()));
387 assert_eq!(t.can_send(), Ok(()));
388 }
389
390 #[test]
391 fn evicting_insert_retires_the_oldest_generation() {
392 let mut t = RequestCorrelator::<(), 2>::new();
393 t.insert_evicting(1, ());
394 t.insert_evicting(2, ());
395 t.insert_evicting(3, ());
396 assert!(!t.contains(1));
397 assert!(t.contains(2) && t.contains(3));
398 }
399
400 #[test]
401 fn unkeyed_backend_keeps_one_request_in_flight() {
402 let mut t = Table::new();
403 t.insert(SEQUENCE_ID_UNKNOWN, 7).unwrap();
404 assert_eq!(t.can_send(), Err(SendRefusal::Unkeyed));
405 assert_eq!(t.resolve(SEQUENCE_ID_UNKNOWN), Some(7));
406 assert_eq!(t.can_send(), Ok(()));
407 }
408
409 #[test]
410 fn an_unkeyed_reply_is_ambiguous_with_two_pending() {
411 let mut t = Table::new();
412 t.insert(1, 1).unwrap();
413 t.insert(2, 2).unwrap();
414 assert_eq!(t.resolve(SEQUENCE_ID_UNKNOWN), None);
415 assert_eq!(
416 t.len(),
417 2,
418 "neither pending request was given someone else's reply"
419 );
420 }
421
422 #[test]
423 fn an_abandoned_unkeyed_request_costs_the_next_unkeyed_reply() {
424 let mut t = Table::new();
425 t.insert(SEQUENCE_ID_UNKNOWN, 1).unwrap();
426 t.clear();
427 t.insert(SEQUENCE_ID_UNKNOWN, 2).unwrap();
428 assert_eq!(
429 t.resolve(SEQUENCE_ID_UNKNOWN),
430 None,
431 "the reply may be the abandoned request's: dropped, not mismatched"
432 );
433 assert_eq!(t.resolve(SEQUENCE_ID_UNKNOWN), Some(2));
434 }
435
436 struct Scripted(Vec<(u8, i64)>);
438 impl ClientTrait for Scripted {
439 type Error = TransportError;
440 fn send_request_raw(&mut self, _r: &[u8]) -> Result<i64, TransportError> {
441 Ok(0)
442 }
443 fn take_response_raw(
444 &mut self,
445 buf: &mut [u8],
446 ) -> Result<Option<(usize, i64)>, TransportError> {
447 if self.0.is_empty() {
448 return Ok(None);
449 }
450 let (byte, seq) = self.0.remove(0);
451 buf[0] = byte;
452 Ok(Some((1, seq)))
453 }
454 }
455
456 #[test]
457 fn take_correlated_skips_stale_replies_to_reach_a_pending_one() {
458 let mut c = Scripted(std::vec![(0xAA, 99), (0xBB, 5)]);
459 let mut t = Table::new();
460 t.insert(5, 42).unwrap();
461 let mut buf = [0u8; 4];
462 let got = take_correlated(&mut c, &mut buf, &mut t).unwrap();
463 assert_eq!(got, Some((1, 5, 42)));
464 assert_eq!(buf[0], 0xBB);
465 assert_eq!(t.dropped(), 1);
466 }
467
468 #[test]
469 fn take_matched_reports_nothing_when_only_stale_replies_arrive() {
470 let mut c = Scripted(std::vec![(0xAA, 99)]);
471 let mut t = RequestCorrelator::<(), 1>::new();
472 t.insert(5, ()).unwrap();
473 let mut buf = [0u8; 4];
474 assert_eq!(take_matched(&mut c, &mut buf, &mut t).unwrap(), None);
475 assert!(t.contains(5), "the pending request is still waiting");
476 }
477
478 #[derive(Debug, Clone, PartialEq)]
483 struct Sum(i32);
484 impl nros_core::Serialize for Sum {
485 fn serialize(&self, w: &mut nros_core::CdrWriter) -> Result<(), nros_core::SerError> {
486 w.write_i32(self.0)
487 }
488 }
489 impl nros_core::Deserialize for Sum {
490 fn deserialize(r: &mut nros_core::CdrReader) -> Result<Self, nros_core::DeserError> {
491 Ok(Sum(r.read_i32()?))
492 }
493 }
494 impl nros_core::RosMessage for Sum {
495 const TYPE_NAME: &'static str = "test::srv::dds_::Sum_";
496 const TYPE_HASH: &'static str =
497 "0000000000000000000000000000000000000000000000000000000000000000";
498 }
499 struct SumSrv;
500 impl nros_core::RosService for SumSrv {
501 type Request = Sum;
502 type Reply = Sum;
503 const SERVICE_NAME: &'static str = "test::srv::dds_::Sum_";
504 const SERVICE_HASH: &'static str =
505 "0000000000000000000000000000000000000000000000000000000000000000";
506 }
507
508 struct TypedScripted {
511 next_seq: i64,
512 replies: Vec<(i32, i64)>,
513 }
514 impl ClientTrait for TypedScripted {
515 type Error = TransportError;
516 fn send_request_raw(&mut self, _r: &[u8]) -> Result<i64, TransportError> {
517 self.next_seq += 1;
518 Ok(self.next_seq)
519 }
520 fn take_response_raw(
521 &mut self,
522 buf: &mut [u8],
523 ) -> Result<Option<(usize, i64)>, TransportError> {
524 if self.replies.is_empty() {
525 return Ok(None);
526 }
527 let (v, seq) = self.replies.remove(0);
528 let mut w = nros_core::CdrWriter::new_with_header(buf).unwrap();
529 w.write_i32(v).unwrap();
530 Ok(Some((w.position(), seq)))
531 }
532 }
533
534 #[test]
535 fn typed_send_returns_the_id_the_backend_assigned() {
536 let mut c = TypedScripted {
537 next_seq: 40,
538 replies: Vec::new(),
539 };
540 let mut req = [0u8; 16];
541 assert_eq!(c.send_request::<SumSrv>(&Sum(1), &mut req).unwrap(), 41);
542 assert_eq!(c.send_request::<SumSrv>(&Sum(2), &mut req).unwrap(), 42);
543 }
544
545 #[test]
546 fn typed_take_reports_the_id_of_the_request_a_reply_answers() {
547 let mut c = TypedScripted {
548 next_seq: 0,
549 replies: std::vec![(7, 3)],
550 };
551 let mut buf = [0u8; 16];
552 assert_eq!(
553 c.take_response::<SumSrv>(&mut buf).unwrap(),
554 Some((Sum(7), 3))
555 );
556 }
557
558 #[test]
562 fn typed_take_for_drops_the_late_reply_of_an_abandoned_request() {
563 let mut c = TypedScripted {
564 next_seq: 0,
565 replies: Vec::new(),
566 };
567 let mut req = [0u8; 16];
568 let stale = c.send_request::<SumSrv>(&Sum(1), &mut req).unwrap();
569 let fresh = c.send_request::<SumSrv>(&Sum(2), &mut req).unwrap();
571 assert_ne!(stale, fresh);
572 c.replies.push((100, stale)); c.replies.push((200, fresh));
574 let mut buf = [0u8; 16];
575 assert_eq!(
576 c.take_response_for::<SumSrv>(fresh, &mut buf).unwrap(),
577 Some(Sum(200)),
578 "the resend got the abandoned request's reply"
579 );
580 assert!(c.replies.is_empty());
581 }
582
583 #[test]
584 fn typed_take_for_reports_nothing_when_only_a_stale_reply_arrived() {
585 let mut c = TypedScripted {
586 next_seq: 0,
587 replies: std::vec![(100, 1)],
588 };
589 let mut buf = [0u8; 16];
590 assert_eq!(c.take_response_for::<SumSrv>(2, &mut buf).unwrap(), None);
591 }
592
593 #[test]
594 fn typed_take_for_accepts_an_idless_reply_for_the_request_in_mind() {
595 let mut c = TypedScripted {
596 next_seq: 0,
597 replies: std::vec![(5, SEQUENCE_ID_UNKNOWN)],
598 };
599 let mut buf = [0u8; 16];
600 assert_eq!(
601 c.take_response_for::<SumSrv>(9, &mut buf).unwrap(),
602 Some(Sum(5))
603 );
604 }
605}