1use heapless::Vec;
15
16use super::clcw::Clcw;
17
18const MAX_ACTIONS: usize = 16;
20
21#[derive(Debug, Copy, Clone, Eq, PartialEq)]
23pub enum FopState {
24 Active,
26 Retransmitting,
28 InitNoBC,
30 InitWithSetVr,
32 InitWithUnlock,
34 Initial,
36}
37
38#[derive(Debug, Clone)]
40pub enum FopEvent<'a> {
41 SendAd {
43 fdu: &'a [u8],
45 },
46
47 SendBd {
49 fdu: &'a [u8],
51 },
52
53 ClcwReceived {
55 clcw: Clcw,
57 },
58
59 TimerExpired,
61
62 InitAdNoClcw,
64
65 InitAdWithClcw,
67
68 InitAdSetVr {
70 vr: u8,
72 },
73
74 InitAdUnlock,
76
77 Terminate,
79
80 Resume,
82}
83
84#[derive(Debug, Copy, Clone, Eq, PartialEq)]
86pub enum FopAction {
87 TransmitAd {
90 seq: u8,
92 },
93
94 TransmitBd,
96
97 TransmitBcUnlock,
99
100 TransmitBcSetVr {
102 vr: u8,
104 },
105
106 StartTimer,
108
109 StopTimer,
111
112 Accept,
114
115 Reject,
117
118 Alert,
120
121 Terminated,
123
124 Acknowledged {
126 seq: u8,
128 },
129}
130
131#[derive(Debug)]
133pub struct FopActions {
134 inner: Vec<FopAction, MAX_ACTIONS>,
135}
136
137impl FopActions {
138 pub fn new() -> Self {
140 Self { inner: Vec::new() }
141 }
142
143 fn push(&mut self, action: FopAction) {
144 let _ = self.inner.push(action);
145 }
146
147 fn clear(&mut self) {
148 self.inner.clear();
149 }
150
151 pub fn iter(&self) -> impl Iterator<Item = &FopAction> {
153 self.inner.iter()
154 }
155
156 pub fn is_empty(&self) -> bool {
158 self.inner.is_empty()
159 }
160
161 pub fn len(&self) -> usize {
163 self.inner.len()
164 }
165}
166
167impl Default for FopActions {
168 fn default() -> Self {
169 Self::new()
170 }
171}
172
173impl<'a> IntoIterator for &'a FopActions {
174 type Item = &'a FopAction;
175 type IntoIter = core::slice::Iter<'a, FopAction>;
176
177 fn into_iter(self) -> Self::IntoIter {
178 self.inner.iter()
179 }
180}
181
182#[derive(Debug, Clone)]
184#[derive(bon::Builder)]
185pub struct FopConfig {
186 pub window_width: u8,
188 pub transmission_limit: u8,
190 pub timeout_type: u8,
193}
194
195#[derive(Clone)]
197struct FduSlot {
198 occupied: bool,
200 seq: u8,
202 offset: usize,
204 len: usize,
206 transmission_count: u8,
208}
209
210impl Default for FduSlot {
211 fn default() -> Self {
212 Self {
213 occupied: false,
214 seq: 0,
215 offset: 0,
216 len: 0,
217 transmission_count: 0,
218 }
219 }
220}
221
222pub struct FopMachine<const WIN: usize, const BUF: usize> {
232 state: FopState,
233 config: FopConfig,
234
235 vs: u8,
237 nnr: u8,
239
240 sent_queue: [FduSlot; WIN],
242 data: [u8; BUF],
244 write_pos: usize,
246
247 bc_transmission_count: u8,
249 set_vr_value: u8,
251
252 pending_bd: Option<(usize, usize)>,
254}
255
256impl<const WIN: usize, const BUF: usize> FopMachine<WIN, BUF> {
257 pub fn new(config: FopConfig) -> Self {
259 Self {
260 state: FopState::Initial,
261 config,
262 vs: 0,
263 nnr: 0,
264 sent_queue: core::array::from_fn(|_| FduSlot::default()),
265 data: [0u8; BUF],
266 write_pos: 0,
267 bc_transmission_count: 0,
268 set_vr_value: 0,
269 pending_bd: None,
270 }
271 }
272
273 pub fn state(&self) -> FopState {
275 self.state
276 }
277
278 pub fn vs(&self) -> u8 {
280 self.vs
281 }
282
283 pub fn get_fdu(&self, seq: u8) -> Option<&[u8]> {
285 for slot in &self.sent_queue {
286 if slot.occupied && slot.seq == seq {
287 return Some(&self.data[slot.offset..slot.offset + slot.len]);
288 }
289 }
290 None
291 }
292
293 pub fn get_bd_fdu(&self) -> Option<&[u8]> {
295 self.pending_bd
296 .map(|(off, len)| &self.data[off..off + len])
297 }
298
299 pub fn sent_count(&self) -> usize {
301 self.sent_queue.iter().filter(|s| s.occupied).count()
302 }
303
304 pub fn handle(
306 &mut self,
307 event: FopEvent<'_>,
308 actions: &mut FopActions,
309 ) {
310 actions.clear();
311
312 match event {
313 FopEvent::SendAd { fdu } => {
314 self.handle_send_ad(fdu, actions);
315 }
316 FopEvent::SendBd { fdu } => {
317 self.handle_send_bd(fdu, actions);
318 }
319 FopEvent::ClcwReceived { clcw } => {
320 self.handle_clcw(clcw, actions);
321 }
322 FopEvent::TimerExpired => {
323 self.handle_timer_expired(actions);
324 }
325 FopEvent::InitAdNoClcw => {
326 self.handle_init_no_clcw(actions);
327 }
328 FopEvent::InitAdWithClcw => {
329 self.handle_init_with_clcw(actions);
330 }
331 FopEvent::InitAdSetVr { vr } => {
332 self.handle_init_set_vr(vr, actions);
333 }
334 FopEvent::InitAdUnlock => {
335 self.handle_init_unlock(actions);
336 }
337 FopEvent::Terminate => {
338 self.handle_terminate(actions);
339 }
340 FopEvent::Resume => {
341 self.handle_resume(actions);
342 }
343 }
344 }
345
346 fn handle_send_ad(&mut self, fdu: &[u8], actions: &mut FopActions) {
348 match self.state {
349 FopState::Active | FopState::Retransmitting => {
350 let Some(slot_idx) = self.find_empty_slot() else {
351 actions.push(FopAction::Reject);
352 return;
353 };
354 let Some(offset) = self.buffer_fdu(fdu) else {
355 actions.push(FopAction::Reject);
356 return;
357 };
358
359 let seq = self.vs;
360 self.sent_queue[slot_idx] = FduSlot {
361 occupied: true,
362 seq,
363 offset,
364 len: fdu.len(),
365 transmission_count: 0,
366 };
367 self.vs = self.vs.wrapping_add(1);
368
369 if self.state == FopState::Active {
371 self.sent_queue[slot_idx].transmission_count = 1;
372 actions.push(FopAction::TransmitAd { seq });
373 actions.push(FopAction::StartTimer);
374 }
375 }
376 _ => {
377 actions.push(FopAction::Reject);
378 }
379 }
380 }
381
382 fn handle_send_bd(&mut self, fdu: &[u8], actions: &mut FopActions) {
384 let Some(offset) = self.buffer_fdu(fdu) else {
386 actions.push(FopAction::Reject);
387 return;
388 };
389 self.pending_bd = Some((offset, fdu.len()));
390 actions.push(FopAction::TransmitBd);
391 }
392
393 fn handle_clcw(&mut self, clcw: Clcw, actions: &mut FopActions) {
395 let nr = clcw.report_value();
396
397 match self.state {
398 FopState::Active => {
399 if clcw.lockout() {
400 self.alert(actions);
402 return;
403 }
404
405 self.remove_acknowledged(nr, actions);
407 self.nnr = nr;
408
409 if clcw.retransmit() {
410 self.initiate_retransmission(actions);
412 self.state = FopState::Retransmitting;
413 } else if self.sent_count() == 0 {
414 actions.push(FopAction::StopTimer);
415 }
416 }
417 FopState::Retransmitting => {
418 if clcw.lockout() {
419 self.alert(actions);
420 return;
421 }
422
423 self.remove_acknowledged(nr, actions);
424 self.nnr = nr;
425
426 if !clcw.retransmit() {
427 self.state = FopState::Active;
429 if self.sent_count() == 0 {
430 actions.push(FopAction::StopTimer);
431 }
432 }
433 }
434 FopState::InitWithSetVr => {
435 if nr == self.set_vr_value
436 && !clcw.lockout()
437 && !clcw.retransmit()
438 {
439 self.vs = self.set_vr_value;
441 self.nnr = nr;
442 self.purge_sent_queue();
443 self.state = FopState::Active;
444 actions.push(FopAction::StopTimer);
445 actions.push(FopAction::Accept);
446 }
447 }
448 FopState::InitWithUnlock => {
449 if !clcw.lockout() && !clcw.retransmit() {
450 self.nnr = nr;
452 self.vs = nr;
453 self.purge_sent_queue();
454 self.state = FopState::Active;
455 actions.push(FopAction::StopTimer);
456 actions.push(FopAction::Accept);
457 }
458 }
459 FopState::InitNoBC | FopState::Initial => {
460 }
462 }
463 }
464
465 fn handle_timer_expired(&mut self, actions: &mut FopActions) {
467 match self.state {
468 FopState::Active | FopState::Retransmitting => {
469 let limit_exceeded = self.sent_queue.iter().any(|s| {
471 s.occupied
472 && s.transmission_count >= self.config.transmission_limit
473 });
474
475 if limit_exceeded {
476 self.alert(actions);
477 } else {
478 self.initiate_retransmission(actions);
479 self.state = FopState::Retransmitting;
480 }
481 }
482 FopState::InitWithSetVr => {
483 self.bc_transmission_count += 1;
484 if self.bc_transmission_count >= self.config.transmission_limit
485 {
486 self.alert(actions);
487 } else {
488 actions.push(FopAction::TransmitBcSetVr {
489 vr: self.set_vr_value,
490 });
491 actions.push(FopAction::StartTimer);
492 }
493 }
494 FopState::InitWithUnlock => {
495 self.bc_transmission_count += 1;
496 if self.bc_transmission_count >= self.config.transmission_limit
497 {
498 self.alert(actions);
499 } else {
500 actions.push(FopAction::TransmitBcUnlock);
501 actions.push(FopAction::StartTimer);
502 }
503 }
504 FopState::InitNoBC | FopState::Initial => {}
505 }
506 }
507
508 fn handle_init_no_clcw(&mut self, actions: &mut FopActions) {
510 match self.state {
511 FopState::Initial => {
512 self.purge_sent_queue();
513 self.state = FopState::Active;
514 actions.push(FopAction::Accept);
515 }
516 _ => {
517 actions.push(FopAction::Reject);
518 }
519 }
520 }
521
522 fn handle_init_with_clcw(&mut self, actions: &mut FopActions) {
524 match self.state {
525 FopState::Initial => {
526 self.purge_sent_queue();
527 self.state = FopState::InitNoBC;
528 actions.push(FopAction::StartTimer);
529 }
530 _ => {
531 actions.push(FopAction::Reject);
532 }
533 }
534 }
535
536 fn handle_init_set_vr(&mut self, vr: u8, actions: &mut FopActions) {
538 match self.state {
539 FopState::Initial => {
540 self.set_vr_value = vr;
541 self.bc_transmission_count = 1;
542 self.purge_sent_queue();
543 self.state = FopState::InitWithSetVr;
544 actions.push(FopAction::TransmitBcSetVr { vr });
545 actions.push(FopAction::StartTimer);
546 }
547 _ => {
548 actions.push(FopAction::Reject);
549 }
550 }
551 }
552
553 fn handle_init_unlock(&mut self, actions: &mut FopActions) {
555 match self.state {
556 FopState::Initial => {
557 self.bc_transmission_count = 1;
558 self.purge_sent_queue();
559 self.state = FopState::InitWithUnlock;
560 actions.push(FopAction::TransmitBcUnlock);
561 actions.push(FopAction::StartTimer);
562 }
563 _ => {
564 actions.push(FopAction::Reject);
565 }
566 }
567 }
568
569 fn handle_terminate(&mut self, actions: &mut FopActions) {
571 actions.push(FopAction::StopTimer);
572 self.purge_sent_queue();
573 self.state = FopState::Initial;
574 actions.push(FopAction::Terminated);
575 }
576
577 fn handle_resume(&mut self, actions: &mut FopActions) {
579 match self.state {
580 FopState::Initial => {
581 self.state = FopState::Active;
582 actions.push(FopAction::Accept);
583 }
584 _ => {
585 actions.push(FopAction::Reject);
586 }
587 }
588 }
589
590 fn remove_acknowledged(&mut self, nr: u8, actions: &mut FopActions) {
592 for slot in &mut self.sent_queue {
593 if !slot.occupied {
594 continue;
595 }
596 let dist = nr.wrapping_sub(slot.seq);
599 if dist > 0 && dist < 128 {
600 let seq = slot.seq;
601 slot.occupied = false;
602 actions.push(FopAction::Acknowledged { seq });
603 }
604 }
605 }
606
607 fn initiate_retransmission(&mut self, actions: &mut FopActions) {
609 let mut seqs: Vec<u8, WIN> = Vec::new();
611 for slot in &self.sent_queue {
612 if slot.occupied {
613 let _ = seqs.push(slot.seq);
614 }
615 }
616
617 let nnr = self.nnr;
620 for i in 1..seqs.len() {
622 let key = seqs[i];
623 let key_dist = key.wrapping_sub(nnr);
624 let mut j = i;
625 while j > 0 {
626 let prev_dist = seqs[j - 1].wrapping_sub(nnr);
627 if prev_dist <= key_dist {
628 break;
629 }
630 seqs[j] = seqs[j - 1];
631 j -= 1;
632 }
633 seqs[j] = key;
634 }
635
636 for &seq in &seqs {
637 for slot in &mut self.sent_queue {
638 if slot.occupied && slot.seq == seq {
639 slot.transmission_count += 1;
640 break;
641 }
642 }
643 actions.push(FopAction::TransmitAd { seq });
644 }
645
646 if !seqs.is_empty() {
647 actions.push(FopAction::StartTimer);
648 }
649 }
650
651 fn alert(&mut self, actions: &mut FopActions) {
653 actions.push(FopAction::StopTimer);
654 self.purge_sent_queue();
655 self.state = FopState::Initial;
656 actions.push(FopAction::Alert);
657 }
658
659 fn purge_sent_queue(&mut self) {
661 for slot in &mut self.sent_queue {
662 slot.occupied = false;
663 }
664 self.write_pos = 0;
665 }
666
667 fn find_empty_slot(&self) -> Option<usize> {
669 self.sent_queue.iter().position(|s| !s.occupied)
670 }
671
672 fn buffer_fdu(&mut self, fdu: &[u8]) -> Option<usize> {
674 if self.write_pos + fdu.len() > BUF {
675 return None;
676 }
677 let offset = self.write_pos;
678 self.data[offset..offset + fdu.len()].copy_from_slice(fdu);
679 self.write_pos += fdu.len();
680 Some(offset)
681 }
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687
688 fn make_config() -> FopConfig {
689 FopConfig::builder()
690 .window_width(4)
691 .transmission_limit(3)
692 .timeout_type(0)
693 .build()
694 }
695
696 #[test]
697 fn starts_in_initial_state() {
698 let fop: FopMachine<8, 1024> = FopMachine::new(make_config());
699 assert_eq!(fop.state(), FopState::Initial);
700 }
701
702 #[test]
703 fn init_no_clcw_activates() {
704 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
705 let mut actions = FopActions::new();
706
707 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
708
709 assert_eq!(fop.state(), FopState::Active);
710 assert!(actions.iter().any(|a| matches!(a, FopAction::Accept)));
711 }
712
713 #[test]
714 fn send_ad_in_active_state() {
715 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
716 let mut actions = FopActions::new();
717
718 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
719 fop.handle(
720 FopEvent::SendAd { fdu: &[1, 2, 3] },
721 &mut actions,
722 );
723
724 assert!(actions
725 .iter()
726 .any(|a| matches!(a, FopAction::TransmitAd { seq: 0 })));
727 assert_eq!(fop.vs(), 1);
728 assert_eq!(fop.sent_count(), 1);
729 }
730
731 #[test]
732 fn send_ad_rejected_in_initial() {
733 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
734 let mut actions = FopActions::new();
735
736 fop.handle(
737 FopEvent::SendAd { fdu: &[1, 2, 3] },
738 &mut actions,
739 );
740
741 assert!(actions.iter().any(|a| matches!(a, FopAction::Reject)));
742 }
743
744 #[test]
745 fn clcw_acknowledges_frames() {
746 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
747 let mut actions = FopActions::new();
748
749 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
750
751 fop.handle(
753 FopEvent::SendAd { fdu: &[1] },
754 &mut actions,
755 );
756 fop.handle(
757 FopEvent::SendAd { fdu: &[2] },
758 &mut actions,
759 );
760 assert_eq!(fop.sent_count(), 2);
761
762 let mut clcw = Clcw::new();
764 clcw.set_report_value(2);
765 fop.handle(
766 FopEvent::ClcwReceived { clcw },
767 &mut actions,
768 );
769
770 assert_eq!(fop.sent_count(), 0);
771 assert!(actions
772 .iter()
773 .any(|a| matches!(a, FopAction::Acknowledged { seq: 0 })));
774 assert!(actions
775 .iter()
776 .any(|a| matches!(a, FopAction::Acknowledged { seq: 1 })));
777 }
778
779 #[test]
780 fn retransmit_flag_triggers_go_back_n() {
781 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
782 let mut actions = FopActions::new();
783
784 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
785 fop.handle(
786 FopEvent::SendAd { fdu: &[1] },
787 &mut actions,
788 );
789 fop.handle(
790 FopEvent::SendAd { fdu: &[2] },
791 &mut actions,
792 );
793
794 let mut clcw = Clcw::new();
796 clcw.set_retransmit(true);
797 clcw.set_report_value(0);
798 fop.handle(
799 FopEvent::ClcwReceived { clcw },
800 &mut actions,
801 );
802
803 assert_eq!(fop.state(), FopState::Retransmitting);
804 let transmit_count = actions
806 .iter()
807 .filter(|a| matches!(a, FopAction::TransmitAd { .. }))
808 .count();
809 assert_eq!(transmit_count, 2);
810 }
811
812 #[test]
813 fn lockout_clcw_causes_alert() {
814 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
815 let mut actions = FopActions::new();
816
817 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
818
819 let mut clcw = Clcw::new();
820 clcw.set_lockout(true);
821 fop.handle(
822 FopEvent::ClcwReceived { clcw },
823 &mut actions,
824 );
825
826 assert_eq!(fop.state(), FopState::Initial);
827 assert!(actions.iter().any(|a| matches!(a, FopAction::Alert)));
828 }
829
830 #[test]
831 fn timer_expired_retransmits() {
832 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
833 let mut actions = FopActions::new();
834
835 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
836 fop.handle(
837 FopEvent::SendAd { fdu: &[1] },
838 &mut actions,
839 );
840
841 fop.handle(FopEvent::TimerExpired, &mut actions);
842
843 assert_eq!(fop.state(), FopState::Retransmitting);
844 assert!(actions
845 .iter()
846 .any(|a| matches!(a, FopAction::TransmitAd { seq: 0 })));
847 }
848
849 #[test]
850 fn timer_expired_alerts_after_limit() {
851 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
852 let mut actions = FopActions::new();
853
854 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
855 fop.handle(
856 FopEvent::SendAd { fdu: &[1] },
857 &mut actions,
858 );
859
860 for _ in 0..3 {
862 fop.handle(FopEvent::TimerExpired, &mut actions);
863 }
864
865 assert_eq!(fop.state(), FopState::Initial);
866 assert!(actions.iter().any(|a| matches!(a, FopAction::Alert)));
867 }
868
869 #[test]
870 fn init_with_unlock() {
871 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
872 let mut actions = FopActions::new();
873
874 fop.handle(FopEvent::InitAdUnlock, &mut actions);
875
876 assert_eq!(fop.state(), FopState::InitWithUnlock);
877 assert!(actions
878 .iter()
879 .any(|a| matches!(a, FopAction::TransmitBcUnlock)));
880
881 let clcw = Clcw::new(); fop.handle(
884 FopEvent::ClcwReceived { clcw },
885 &mut actions,
886 );
887
888 assert_eq!(fop.state(), FopState::Active);
889 assert!(actions.iter().any(|a| matches!(a, FopAction::Accept)));
890 }
891
892 #[test]
893 fn init_with_set_vr() {
894 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
895 let mut actions = FopActions::new();
896
897 fop.handle(
898 FopEvent::InitAdSetVr { vr: 42 },
899 &mut actions,
900 );
901
902 assert_eq!(fop.state(), FopState::InitWithSetVr);
903 assert!(actions
904 .iter()
905 .any(|a| matches!(a, FopAction::TransmitBcSetVr { vr: 42 })));
906
907 let mut clcw = Clcw::new();
909 clcw.set_report_value(42);
910 fop.handle(
911 FopEvent::ClcwReceived { clcw },
912 &mut actions,
913 );
914
915 assert_eq!(fop.state(), FopState::Active);
916 assert_eq!(fop.vs(), 42);
917 }
918
919 #[test]
920 fn terminate_resets_to_initial() {
921 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
922 let mut actions = FopActions::new();
923
924 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
925 fop.handle(
926 FopEvent::SendAd { fdu: &[1] },
927 &mut actions,
928 );
929
930 fop.handle(FopEvent::Terminate, &mut actions);
931
932 assert_eq!(fop.state(), FopState::Initial);
933 assert_eq!(fop.sent_count(), 0);
934 assert!(actions
935 .iter()
936 .any(|a| matches!(a, FopAction::Terminated)));
937 }
938
939 #[test]
940 fn bd_frame_can_be_sent() {
941 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
942 let mut actions = FopActions::new();
943
944 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
945 fop.handle(
946 FopEvent::SendBd { fdu: &[0xAA, 0xBB] },
947 &mut actions,
948 );
949
950 assert!(actions
951 .iter()
952 .any(|a| matches!(a, FopAction::TransmitBd)));
953 let bd_data = fop.get_bd_fdu().unwrap();
954 assert_eq!(bd_data, &[0xAA, 0xBB]);
955 }
956
957 #[test]
958 fn get_fdu_returns_correct_data() {
959 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
960 let mut actions = FopActions::new();
961
962 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
963 fop.handle(
964 FopEvent::SendAd { fdu: &[10, 20, 30] },
965 &mut actions,
966 );
967
968 let fdu = fop.get_fdu(0).unwrap();
969 assert_eq!(fdu, &[10, 20, 30]);
970 }
971
972 #[test]
973 fn wrapping_sequence_numbers() {
974 let mut fop: FopMachine<8, 1024> = FopMachine::new(make_config());
975 let mut actions = FopActions::new();
976
977 fop.handle(FopEvent::InitAdNoClcw, &mut actions);
978
979 fop.handle(FopEvent::Terminate, &mut actions);
981 fop.handle(
982 FopEvent::InitAdSetVr { vr: 254 },
983 &mut actions,
984 );
985 let mut clcw = Clcw::new();
986 clcw.set_report_value(254);
987 fop.handle(
988 FopEvent::ClcwReceived { clcw },
989 &mut actions,
990 );
991 assert_eq!(fop.vs(), 254);
992
993 fop.handle(
995 FopEvent::SendAd { fdu: &[1] },
996 &mut actions,
997 );
998 assert!(actions
999 .iter()
1000 .any(|a| matches!(a, FopAction::TransmitAd { seq: 254 })));
1001
1002 fop.handle(
1003 FopEvent::SendAd { fdu: &[2] },
1004 &mut actions,
1005 );
1006 assert!(actions
1007 .iter()
1008 .any(|a| matches!(a, FopAction::TransmitAd { seq: 255 })));
1009
1010 fop.handle(
1011 FopEvent::SendAd { fdu: &[3] },
1012 &mut actions,
1013 );
1014 assert!(actions
1015 .iter()
1016 .any(|a| matches!(a, FopAction::TransmitAd { seq: 0 })));
1017
1018 assert_eq!(fop.vs(), 1);
1019 }
1020}