1use core::time::Duration;
11
12use assert_matches::assert_matches;
13use net_types::ip::Ip;
14use net_types::MulticastAddr;
15use netstack3_base::Instant;
16use packet_formats::utils::NonZeroDuration;
17use rand::Rng;
18
19use crate::internal::gmp::{
20 self, GmpBindingsContext, GmpContext, GmpContextInner, GmpEnabledGroup, GmpGroupState, GmpMode,
21 GmpStateRef, GroupJoinResult, GroupLeaveResult, IpExt, NotAMemberErr, QueryTarget,
22};
23
24#[derive(Debug, Eq, PartialEq, Hash, Clone)]
28pub(super) struct DelayedReportTimerId<I: Ip>(pub(super) GmpEnabledGroup<I::Addr>);
29
30#[cfg(test)]
31impl<I: IpExt> DelayedReportTimerId<I> {
32 pub(super) fn new_multicast(addr: MulticastAddr<I::Addr>) -> Self {
33 Self(GmpEnabledGroup::try_new(addr).expect("not GMP enabled"))
34 }
35}
36
37#[derive(Debug, Eq, PartialEq)]
39pub(super) enum GmpMessageType {
40 Report,
41 Leave,
42}
43
44#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
46pub(super) struct JoinGroupActions {
47 send_report_and_schedule_timer: Option<Duration>,
48}
49
50impl JoinGroupActions {
51 const NOOP: Self = Self { send_report_and_schedule_timer: None };
52}
53
54#[derive(Debug, PartialEq, Eq)]
56pub(super) struct LeaveGroupActions {
57 send_leave: bool,
58 stop_timer: bool,
59}
60
61impl LeaveGroupActions {
62 const NOOP: Self = Self { send_leave: false, stop_timer: false };
63}
64
65#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
67pub(super) struct ReportReceivedActions {
68 pub(super) stop_timer: bool,
69}
70
71impl ReportReceivedActions {
72 const NOOP: Self = Self { stop_timer: false };
73}
74
75#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
77pub(super) enum QueryReceivedActions {
78 ScheduleTimer(Duration),
79 StopTimerAndSendReport,
80 None,
81}
82
83#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
85pub(super) struct ReportTimerExpiredActions;
86
87pub trait ProtocolConfig {
90 fn unsolicited_report_interval(&self) -> Duration;
92
93 fn send_leave_anyway(&self) -> bool;
96
97 fn get_max_resp_time(&self, resp_time: Duration) -> Option<NonZeroDuration>;
102}
103
104struct Transition<S, Actions>(S, Actions);
109
110#[cfg_attr(test, derive(Debug))]
118pub(super) struct NonMember;
119
120#[cfg_attr(test, derive(Debug))]
122pub(super) struct DelayingMember<I: Instant> {
123 timer_expiration: I,
126
127 last_reporter: bool,
131}
132
133#[cfg_attr(test, derive(Debug))]
135pub(super) struct IdleMember {
136 last_reporter: bool,
139}
140
141#[cfg_attr(test, derive(Debug))]
149pub(super) enum MemberState<I: Instant> {
150 NonMember(NonMember),
151 Delaying(DelayingMember<I>),
152 Idle(IdleMember),
153}
154
155impl<I: Instant> From<NonMember> for MemberState<I> {
156 fn from(s: NonMember) -> Self {
157 MemberState::NonMember(s)
158 }
159}
160
161impl<I: Instant> From<DelayingMember<I>> for MemberState<I> {
162 fn from(s: DelayingMember<I>) -> Self {
163 MemberState::Delaying(s)
164 }
165}
166
167impl<I: Instant> From<IdleMember> for MemberState<I> {
168 fn from(s: IdleMember) -> Self {
169 MemberState::Idle(s)
170 }
171}
172
173impl<S, A> Transition<S, A> {
174 fn into_state_actions<I: Instant>(self) -> (MemberState<I>, A)
175 where
176 MemberState<I>: From<S>,
177 {
178 (self.0.into(), self.1)
179 }
180}
181
182fn member_query_received<R: Rng, I: Instant, C: ProtocolConfig>(
193 rng: &mut R,
194 last_reporter: bool,
195 timer_expiration: Option<I>,
196 max_resp_time: Duration,
197 now: I,
198 cfg: &C,
199) -> (MemberState<I>, QueryReceivedActions) {
200 let (transition, actions) = match cfg.get_max_resp_time(max_resp_time) {
201 None => (IdleMember { last_reporter }.into(), QueryReceivedActions::StopTimerAndSendReport),
202 Some(max_resp_time) => {
203 let max_resp_time = max_resp_time.get();
204 let new_deadline = now.saturating_add(max_resp_time);
205
206 let (timer_expiration, action) = match timer_expiration {
207 Some(old) if new_deadline >= old => (old, QueryReceivedActions::None),
208 None | Some(_) => {
209 let delay = gmp::random_report_timeout(rng, max_resp_time);
210 (now.saturating_add(delay), QueryReceivedActions::ScheduleTimer(delay))
211 }
212 };
213
214 (DelayingMember { last_reporter, timer_expiration }.into(), action)
215 }
216 };
217
218 (transition, actions)
219}
220
221impl NonMember {
222 fn join_group<I: Instant, R: Rng, C: ProtocolConfig>(
223 self,
224 rng: &mut R,
225 now: I,
226 cfg: &C,
227 ) -> Transition<DelayingMember<I>, JoinGroupActions> {
228 let duration = cfg.unsolicited_report_interval();
229 let delay = gmp::random_report_timeout(rng, duration);
230 let actions = JoinGroupActions { send_report_and_schedule_timer: Some(delay) };
231 Transition(
232 DelayingMember {
233 last_reporter: true,
234 timer_expiration: now.checked_add(delay).expect("timer expiration overflowed"),
235 },
236 actions,
237 )
238 }
239
240 fn leave_group(self) -> Transition<NonMember, LeaveGroupActions> {
241 Transition(NonMember, LeaveGroupActions::NOOP)
242 }
243}
244
245impl<I: Instant> DelayingMember<I> {
246 fn query_received<R: Rng, C: ProtocolConfig>(
247 self,
248 rng: &mut R,
249 max_resp_time: Duration,
250 now: I,
251 cfg: &C,
252 ) -> (MemberState<I>, QueryReceivedActions) {
253 let DelayingMember { last_reporter, timer_expiration } = self;
254 member_query_received(rng, last_reporter, Some(timer_expiration), max_resp_time, now, cfg)
255 }
256
257 fn leave_group<C: ProtocolConfig>(self, cfg: &C) -> Transition<NonMember, LeaveGroupActions> {
258 let actions = LeaveGroupActions {
259 send_leave: self.last_reporter || cfg.send_leave_anyway(),
260 stop_timer: true,
261 };
262 Transition(NonMember, actions)
263 }
264
265 fn report_received(self) -> Transition<IdleMember, ReportReceivedActions> {
266 Transition(IdleMember { last_reporter: false }, ReportReceivedActions { stop_timer: true })
267 }
268
269 fn report_timer_expired(self) -> Transition<IdleMember, ReportTimerExpiredActions> {
270 Transition(IdleMember { last_reporter: true }, ReportTimerExpiredActions)
271 }
272}
273
274impl IdleMember {
275 fn query_received<I: Instant, R: Rng, C: ProtocolConfig>(
276 self,
277 rng: &mut R,
278 max_resp_time: Duration,
279 now: I,
280 cfg: &C,
281 ) -> (MemberState<I>, QueryReceivedActions) {
282 let IdleMember { last_reporter } = self;
283 member_query_received(rng, last_reporter, None, max_resp_time, now, cfg)
284 }
285
286 fn leave_group<C: ProtocolConfig>(self, cfg: &C) -> Transition<NonMember, LeaveGroupActions> {
287 let actions = LeaveGroupActions {
288 send_leave: self.last_reporter || cfg.send_leave_anyway(),
289 stop_timer: false,
290 };
291 Transition(NonMember, actions)
292 }
293}
294
295impl<I: Instant> MemberState<I> {
296 fn join_group<R: Rng, C: ProtocolConfig>(
299 cfg: &C,
300 rng: &mut R,
301 now: I,
302 gmp_disabled: bool,
303 ) -> (MemberState<I>, JoinGroupActions) {
304 let non_member = NonMember;
305 if gmp_disabled {
306 (non_member.into(), JoinGroupActions::NOOP)
307 } else {
308 non_member.join_group(rng, now, cfg).into_state_actions()
309 }
310 }
311
312 fn leave_group<C: ProtocolConfig>(self, cfg: &C) -> (MemberState<I>, LeaveGroupActions) {
315 match self {
319 MemberState::NonMember(state) => state.leave_group(),
320 MemberState::Delaying(state) => state.leave_group(cfg),
321 MemberState::Idle(state) => state.leave_group(cfg),
322 }
323 .into_state_actions()
324 }
325
326 fn query_received<R: Rng, C: ProtocolConfig>(
327 self,
328 rng: &mut R,
329 max_resp_time: Duration,
330 now: I,
331 cfg: &C,
332 ) -> (MemberState<I>, QueryReceivedActions) {
333 match self {
334 state @ MemberState::NonMember(_) => (state, QueryReceivedActions::None),
335 MemberState::Delaying(state) => state.query_received(rng, max_resp_time, now, cfg),
336 MemberState::Idle(state) => state.query_received(rng, max_resp_time, now, cfg),
337 }
338 }
339
340 fn report_received(self) -> (MemberState<I>, ReportReceivedActions) {
341 match self {
342 state @ MemberState::Idle(_) | state @ MemberState::NonMember(_) => {
343 (state, ReportReceivedActions::NOOP)
344 }
345 MemberState::Delaying(state) => state.report_received().into_state_actions(),
346 }
347 }
348
349 fn report_timer_expired(self) -> (MemberState<I>, ReportTimerExpiredActions) {
350 match self {
351 MemberState::Idle(_) | MemberState::NonMember(_) => {
352 unreachable!("got report timer in non-delaying state")
353 }
354 MemberState::Delaying(state) => state.report_timer_expired().into_state_actions(),
355 }
356 }
357}
358
359#[cfg_attr(test, derive(Debug))]
360pub struct GmpStateMachine<I: Instant> {
361 pub(super) inner: Option<MemberState<I>>,
367}
368
369impl<I: Instant> GmpStateMachine<I> {
370 pub(super) fn join_group<R: Rng, C: ProtocolConfig>(
376 rng: &mut R,
377 now: I,
378 gmp_disabled: bool,
379 cfg: &C,
380 ) -> (GmpStateMachine<I>, JoinGroupActions) {
381 let (state, actions) = MemberState::join_group(cfg, rng, now, gmp_disabled);
382 (GmpStateMachine { inner: Some(state) }, actions)
383 }
384
385 fn join_if_non_member<R: Rng, C: ProtocolConfig>(
391 &mut self,
392 rng: &mut R,
393 now: I,
394 cfg: &C,
395 ) -> JoinGroupActions {
396 self.update(|s| match s {
397 MemberState::NonMember(s) => s.join_group(rng, now, cfg).into_state_actions(),
398 state @ MemberState::Delaying(_) | state @ MemberState::Idle(_) => {
399 (state, JoinGroupActions::NOOP)
400 }
401 })
402 }
403
404 fn leave_if_member<C: ProtocolConfig>(&mut self, cfg: &C) -> LeaveGroupActions {
408 self.update(|s| s.leave_group(cfg))
409 }
410
411 pub(super) fn leave_group<C: ProtocolConfig>(self, cfg: &C) -> LeaveGroupActions {
416 let (_state, actions) = self.inner.unwrap().leave_group(cfg);
419 actions
420 }
421
422 pub(super) fn query_received<R: Rng, C: ProtocolConfig>(
424 &mut self,
425 rng: &mut R,
426 max_resp_time: Duration,
427 now: I,
428 cfg: &C,
429 ) -> QueryReceivedActions {
430 self.update(|s| s.query_received(rng, max_resp_time, now, cfg))
431 }
432
433 pub(super) fn report_received(&mut self) -> ReportReceivedActions {
435 self.update(MemberState::report_received)
436 }
437
438 pub(super) fn report_timer_expired(&mut self) -> ReportTimerExpiredActions {
440 self.update(MemberState::report_timer_expired)
441 }
442
443 fn update<A, F: FnOnce(MemberState<I>) -> (MemberState<I>, A)>(&mut self, f: F) -> A {
445 let (s, a) = f(self.inner.take().unwrap());
446 self.inner = Some(s);
447 a
448 }
449
450 pub(super) fn new_for_mode_transition() -> Self {
462 Self { inner: Some(MemberState::Idle(IdleMember { last_reporter: false })) }
463 }
464
465 #[cfg(test)]
466 pub(super) fn get_inner(&self) -> &MemberState<I> {
467 self.inner.as_ref().unwrap()
468 }
469}
470
471pub(super) fn handle_timer<I, CC, BC>(
472 core_ctx: &mut CC,
473 bindings_ctx: &mut BC,
474 device: &CC::DeviceId,
475 state: GmpStateRef<'_, I, CC::TypeLayout, BC>,
476 timer: DelayedReportTimerId<I>,
477) where
478 BC: GmpBindingsContext,
479 CC: GmpContextInner<I, BC>,
480 I: IpExt,
481{
482 let GmpStateRef { enabled: _, groups, gmp, config: _ } = state;
483 debug_assert!(gmp.gmp_mode().is_v1());
484 let DelayedReportTimerId(group_addr) = timer;
485 let ReportTimerExpiredActions {} = groups
486 .get_mut(group_addr.as_ref())
487 .expect("get state for group with expired report timer")
488 .v1_mut()
489 .report_timer_expired();
490
491 core_ctx.send_message_v1(bindings_ctx, &device, &gmp.mode, group_addr, GmpMessageType::Report);
492}
493
494pub(super) fn handle_report_message<I, BC, CC>(
495 core_ctx: &mut CC,
496 bindings_ctx: &mut BC,
497 device: &CC::DeviceId,
498 group_addr: MulticastAddr<I::Addr>,
499) -> Result<(), NotAMemberErr<I>>
500where
501 BC: GmpBindingsContext,
502 CC: GmpContext<I, BC>,
503 I: IpExt,
504{
505 core_ctx.with_gmp_state_mut(device, |state| {
506 let GmpStateRef { enabled: _, groups, gmp, config: _ } = state;
507 if !gmp.gmp_mode().is_v1() {
524 return Ok(());
525 }
526 let group_addr =
527 GmpEnabledGroup::try_new(group_addr).map_err(|addr| NotAMemberErr(*addr))?;
528 let ReportReceivedActions { stop_timer } = groups
529 .get_mut(group_addr.as_ref())
530 .ok_or_else(|| NotAMemberErr(*group_addr.multicast_addr()))
531 .map(|a| a.v1_mut().report_received())?;
532 if stop_timer {
533 assert_matches!(
534 gmp.timers.cancel(bindings_ctx, &DelayedReportTimerId(group_addr).into()),
535 Some(_)
536 );
537 }
538 Ok(())
539 })
540}
541
542pub(super) trait QueryMessage<I: Ip> {
544 fn group_addr(&self) -> I::Addr;
546
547 fn max_response_time(&self) -> Duration;
549}
550
551pub(super) fn handle_query_message<I, CC, BC, Q>(
552 core_ctx: &mut CC,
553 bindings_ctx: &mut BC,
554 device: &CC::DeviceId,
555 query: &Q,
556) -> Result<(), NotAMemberErr<I>>
557where
558 BC: GmpBindingsContext,
559 CC: GmpContext<I, BC>,
560 I: IpExt,
561 Q: QueryMessage<I>,
562{
563 core_ctx.with_gmp_state_mut_and_ctx(device, |mut core_ctx, mut state| {
564 let new_mode =
579 core_ctx.mode_update_from_v1_query(bindings_ctx, query, &state.gmp, &state.config);
580 gmp::enter_mode(bindings_ctx, state.as_mut(), new_mode);
581 let compat = match state.gmp.gmp_mode() {
582 GmpMode::V1 { compat } => compat,
583 GmpMode::V2 => {
584 panic!("protocol refused to switch to v1");
590 }
591 };
592 if compat {
593 gmp::schedule_v1_compat(bindings_ctx, state.as_mut())
594 }
595
596 handle_query_message_inner(&mut core_ctx, bindings_ctx, device, state, query)
597 })
598}
599
600pub(super) fn handle_query_message_inner<I, CC, BC, Q>(
601 core_ctx: &mut CC,
602 bindings_ctx: &mut BC,
603 device: &CC::DeviceId,
604 state: GmpStateRef<'_, I, CC::TypeLayout, BC>,
605 query: &Q,
606) -> Result<(), NotAMemberErr<I>>
607where
608 BC: GmpBindingsContext,
609 CC: GmpContextInner<I, BC>,
610 I: IpExt,
611 Q: QueryMessage<I>,
612{
613 let GmpStateRef { enabled: _, groups, gmp, config } = state;
614
615 let now = bindings_ctx.now();
616
617 let target = query.group_addr();
618 let target = QueryTarget::new(target).ok_or(NotAMemberErr(target))?;
619 let iter = match target {
620 QueryTarget::Unspecified => either::Either::Left(
621 groups
622 .iter_mut()
623 .filter_map(|(addr, state)| GmpEnabledGroup::new(*addr).map(|addr| (addr, state))),
624 ),
625 QueryTarget::Specified(group_addr) => {
626 let group_addr =
627 GmpEnabledGroup::try_new(group_addr).map_err(|addr| NotAMemberErr(*addr))?;
628 let state = groups
629 .get_mut(group_addr.as_ref())
630 .ok_or_else(|| NotAMemberErr(*group_addr.into_multicast_addr()))?;
631 either::Either::Right(core::iter::once((group_addr, state)))
632 }
633 };
634
635 for (group_addr, state) in iter {
636 let actions = state.v1_mut().query_received(
637 &mut bindings_ctx.rng(),
638 query.max_response_time(),
639 now,
640 config,
641 );
642 let send_msg = match actions {
643 QueryReceivedActions::None => None,
644 QueryReceivedActions::ScheduleTimer(delay) => {
645 let _: Option<(BC::Instant, ())> = gmp.timers.schedule_after(
646 bindings_ctx,
647 DelayedReportTimerId(group_addr).into(),
648 (),
649 delay,
650 );
651 None
652 }
653 QueryReceivedActions::StopTimerAndSendReport => {
654 let _: Option<(BC::Instant, ())> =
655 gmp.timers.cancel(bindings_ctx, &DelayedReportTimerId(group_addr).into());
656 Some(GmpMessageType::Report)
657 }
658 };
659
660 if let Some(msg) = send_msg {
661 core_ctx.send_message_v1(bindings_ctx, device, &gmp.mode, group_addr, msg);
662 }
663 }
664
665 Ok(())
666}
667
668pub(super) fn handle_enabled<I, CC, BC>(
669 core_ctx: &mut CC,
670 bindings_ctx: &mut BC,
671 device: &CC::DeviceId,
672 state: GmpStateRef<'_, I, CC::TypeLayout, BC>,
673) where
674 BC: GmpBindingsContext,
675 CC: GmpContextInner<I, BC>,
676 I: IpExt,
677{
678 let GmpStateRef { enabled: _, groups, gmp, config } = state;
679 debug_assert!(gmp.gmp_mode().is_v1());
680
681 let now = bindings_ctx.now();
682
683 for (group_addr, state) in groups.iter_mut() {
684 let group_addr = match GmpEnabledGroup::new(*group_addr) {
685 Some(a) => a,
686 None => continue,
687 };
688
689 let JoinGroupActions { send_report_and_schedule_timer } =
690 state.v1_mut().join_if_non_member(&mut bindings_ctx.rng(), now, config);
691 let Some(delay) = send_report_and_schedule_timer else {
692 continue;
693 };
694 assert_matches!(
695 gmp.timers.schedule_after(
696 bindings_ctx,
697 DelayedReportTimerId(group_addr).into(),
698 (),
699 delay
700 ),
701 None
702 );
703 core_ctx.send_message_v1(
704 bindings_ctx,
705 device,
706 &gmp.mode,
707 group_addr,
708 GmpMessageType::Report,
709 );
710 }
711}
712
713pub(super) fn handle_disabled<I, CC, BC>(
714 core_ctx: &mut CC,
715 bindings_ctx: &mut BC,
716 device: &CC::DeviceId,
717 state: GmpStateRef<'_, I, CC::TypeLayout, BC>,
718) where
719 BC: GmpBindingsContext,
720 CC: GmpContextInner<I, BC>,
721 I: IpExt,
722{
723 let GmpStateRef { enabled: _, groups, gmp, config } = state;
724 debug_assert!(gmp.gmp_mode().is_v1());
725
726 for (group_addr, state) in groups.groups_mut() {
727 let group_addr = match GmpEnabledGroup::new(*group_addr) {
728 Some(a) => a,
729 None => continue,
730 };
731 let LeaveGroupActions { send_leave, stop_timer } = state.v1_mut().leave_if_member(config);
732 if stop_timer {
733 assert_matches!(
734 gmp.timers.cancel(bindings_ctx, &DelayedReportTimerId(group_addr).into()),
735 Some(_)
736 );
737 }
738 if send_leave {
739 core_ctx.send_message_v1(
740 bindings_ctx,
741 device,
742 &gmp.mode,
743 group_addr,
744 GmpMessageType::Leave,
745 );
746 }
747 }
748}
749
750pub(super) fn join_group<I, CC, BC>(
751 core_ctx: &mut CC,
752 bindings_ctx: &mut BC,
753 device: &CC::DeviceId,
754 group_addr: MulticastAddr<I::Addr>,
755 state: GmpStateRef<'_, I, CC::TypeLayout, BC>,
756) -> GroupJoinResult
757where
758 BC: GmpBindingsContext,
759 CC: GmpContextInner<I, BC>,
760 I: IpExt,
761{
762 let GmpStateRef { enabled, groups, gmp, config } = state;
763 debug_assert!(gmp.gmp_mode().is_v1());
764 let now = bindings_ctx.now();
765
766 let group_addr_witness = GmpEnabledGroup::try_new(group_addr);
767 let gmp_disabled = !enabled || group_addr_witness.is_err();
768 let result = groups.join_group_with(group_addr, || {
769 let (state, actions) =
770 GmpStateMachine::join_group(&mut bindings_ctx.rng(), now, gmp_disabled, config);
771 (GmpGroupState::new_v1(state), actions)
772 });
773 result.map(|JoinGroupActions { send_report_and_schedule_timer }| {
774 if let Some(delay) = send_report_and_schedule_timer {
775 let group_addr = group_addr_witness.expect("generated report for GMP-disabled group");
778 assert_matches!(
779 gmp.timers.schedule_after(
780 bindings_ctx,
781 DelayedReportTimerId(group_addr).into(),
782 (),
783 delay
784 ),
785 None
786 );
787
788 core_ctx.send_message_v1(
789 bindings_ctx,
790 device,
791 &gmp.mode,
792 group_addr,
793 GmpMessageType::Report,
794 );
795 }
796 })
797}
798
799pub(super) fn leave_group<I, CC, BC>(
800 core_ctx: &mut CC,
801 bindings_ctx: &mut BC,
802 device: &CC::DeviceId,
803 group_addr: MulticastAddr<I::Addr>,
804 state: GmpStateRef<'_, I, CC::TypeLayout, BC>,
805) -> GroupLeaveResult
806where
807 BC: GmpBindingsContext,
808 CC: GmpContextInner<I, BC>,
809 I: IpExt,
810{
811 let GmpStateRef { enabled: _, groups, gmp, config } = state;
812 debug_assert!(gmp.gmp_mode().is_v1());
813
814 groups.leave_group(group_addr).map(|state| {
815 let actions = state.into_v1().leave_group(config);
816 let group_addr = match GmpEnabledGroup::new(group_addr) {
817 Some(addr) => addr,
818 None => {
819 assert_eq!(actions, LeaveGroupActions::NOOP);
822 return;
823 }
824 };
825 let LeaveGroupActions { send_leave, stop_timer } = actions;
826 if stop_timer {
827 assert_matches!(
828 gmp.timers.cancel(bindings_ctx, &DelayedReportTimerId(group_addr).into()),
829 Some(_)
830 );
831 }
832 if send_leave {
833 core_ctx.send_message_v1(
834 bindings_ctx,
835 device,
836 &gmp.mode,
837 group_addr,
838 GmpMessageType::Leave,
839 );
840 }
841 })
842}
843
844#[cfg(test)]
845mod test {
846 use assert_matches::assert_matches;
847 use ip_test_macro::ip_test;
848 use netstack3_base::testutil::{new_rng, FakeDeviceId, FakeInstant};
849 use test_util::assert_lt;
850
851 use super::*;
852
853 const DEFAULT_UNSOLICITED_REPORT_INTERVAL: Duration = Duration::from_secs(10);
854
855 #[derive(Debug, Default)]
857 struct FakeConfig(bool);
858
859 impl ProtocolConfig for FakeConfig {
860 fn unsolicited_report_interval(&self) -> Duration {
861 DEFAULT_UNSOLICITED_REPORT_INTERVAL
862 }
863
864 fn send_leave_anyway(&self) -> bool {
865 let Self(cfg) = self;
866 *cfg
867 }
868
869 fn get_max_resp_time(&self, resp_time: Duration) -> Option<NonZeroDuration> {
870 NonZeroDuration::new(resp_time)
871 }
872 }
873
874 type FakeGmpStateMachine = GmpStateMachine<FakeInstant>;
875
876 #[test]
877 fn test_gmp_state_non_member_to_delay_should_set_flag() {
878 let (s, _actions) = FakeGmpStateMachine::join_group(
879 &mut new_rng(0),
880 FakeInstant::default(),
881 false,
882 &FakeConfig::default(),
883 );
884 match s.get_inner() {
885 MemberState::Delaying(s) => assert!(s.last_reporter),
886 _ => panic!("Wrong State!"),
887 }
888 }
889
890 #[test]
891 fn test_gmp_state_non_member_to_delay_actions() {
892 let (_state, actions) = FakeGmpStateMachine::join_group(
893 &mut new_rng(0),
894 FakeInstant::default(),
895 false,
896 &FakeConfig::default(),
897 );
898 assert_matches!(
899 actions,
900 JoinGroupActions { send_report_and_schedule_timer: Some(d) } if d <= DEFAULT_UNSOLICITED_REPORT_INTERVAL
901 );
902 }
903
904 #[test]
905 fn test_gmp_state_delay_no_reset_timer() {
906 let mut rng = new_rng(0);
907 let cfg = FakeConfig::default();
908 let (mut s, _actions) =
909 FakeGmpStateMachine::join_group(&mut rng, FakeInstant::default(), false, &cfg);
910 assert_eq!(
911 s.query_received(
912 &mut rng,
913 DEFAULT_UNSOLICITED_REPORT_INTERVAL + Duration::from_secs(1),
914 FakeInstant::default(),
915 &cfg
916 ),
917 QueryReceivedActions::None,
918 );
919 }
920
921 #[test]
922 fn test_gmp_state_delay_reset_timer() {
923 let mut rng = new_rng(10);
924 let cfg = FakeConfig::default();
925 let (mut s, JoinGroupActions { send_report_and_schedule_timer }) =
926 FakeGmpStateMachine::join_group(&mut rng, FakeInstant::default(), false, &cfg);
927 let first_duration = send_report_and_schedule_timer.expect("starts delaying member");
928 let actions = s.query_received(
929 &mut rng,
930 first_duration.checked_sub(Duration::from_micros(1)).unwrap(),
931 FakeInstant::default(),
932 &cfg,
933 );
934 let new_duration = assert_matches!(actions,
935 QueryReceivedActions::ScheduleTimer(d) => d
936 );
937 assert_lt!(new_duration, first_duration);
938 }
939
940 #[test]
941 fn test_gmp_state_delay_to_idle_with_report_no_flag() {
942 let (mut s, _actions) = FakeGmpStateMachine::join_group(
943 &mut new_rng(0),
944 FakeInstant::default(),
945 false,
946 &FakeConfig::default(),
947 );
948 assert_eq!(s.report_received(), ReportReceivedActions { stop_timer: true });
949 match s.get_inner() {
950 MemberState::Idle(s) => {
951 assert!(!s.last_reporter);
952 }
953 _ => panic!("Wrong State!"),
954 }
955 }
956
957 #[test]
958 fn test_gmp_state_delay_to_idle_without_report_set_flag() {
959 let (mut s, _actions) = FakeGmpStateMachine::join_group(
960 &mut new_rng(0),
961 FakeInstant::default(),
962 false,
963 &FakeConfig::default(),
964 );
965 assert_eq!(s.report_timer_expired(), ReportTimerExpiredActions,);
966 match s.get_inner() {
967 MemberState::Idle(s) => {
968 assert!(s.last_reporter);
969 }
970 _ => panic!("Wrong State!"),
971 }
972 }
973
974 #[test]
975 fn test_gmp_state_leave_should_send_leave() {
976 let mut rng = new_rng(0);
977 let cfg = FakeConfig::default();
978 let (s, _actions) =
979 FakeGmpStateMachine::join_group(&mut rng, FakeInstant::default(), false, &cfg);
980 assert_eq!(s.leave_group(&cfg), LeaveGroupActions { send_leave: true, stop_timer: true });
981 let (mut s, _actions) =
982 FakeGmpStateMachine::join_group(&mut rng, FakeInstant::default(), false, &cfg);
983 assert_eq!(s.report_timer_expired(), ReportTimerExpiredActions,);
984 assert_eq!(s.leave_group(&cfg), LeaveGroupActions { send_leave: true, stop_timer: false });
985 }
986
987 #[test]
988 fn test_gmp_state_delay_to_other_states_should_stop_timer() {
989 let mut rng = new_rng(0);
990 let cfg = FakeConfig::default();
991 let (s, _actions) =
992 FakeGmpStateMachine::join_group(&mut rng, FakeInstant::default(), false, &cfg);
993 assert_eq!(s.leave_group(&cfg), LeaveGroupActions { send_leave: true, stop_timer: true },);
994 let (mut s, _actions) =
995 FakeGmpStateMachine::join_group(&mut rng, FakeInstant::default(), false, &cfg);
996 assert_eq!(s.report_received(), ReportReceivedActions { stop_timer: true });
997 }
998
999 #[test]
1000 fn test_gmp_state_other_states_to_delay_should_schedule_timer() {
1001 let mut rng = new_rng(0);
1002 let cfg = FakeConfig::default();
1003 let (mut s, actions) =
1004 FakeGmpStateMachine::join_group(&mut rng, FakeInstant::default(), false, &cfg);
1005 assert_matches!(
1006 actions,
1007 JoinGroupActions { send_report_and_schedule_timer: Some(d) } if d <= DEFAULT_UNSOLICITED_REPORT_INTERVAL
1008 );
1009 assert_eq!(s.report_received(), ReportReceivedActions { stop_timer: true });
1010 assert_eq!(
1011 s.query_received(&mut rng, Duration::from_secs(1), FakeInstant::default(), &cfg),
1012 QueryReceivedActions::ScheduleTimer(Duration::from_micros(1))
1013 );
1014 }
1015
1016 #[test]
1017 fn test_gmp_state_leave_send_anyway_do_send() {
1018 let mut cfg = FakeConfig::default();
1019 let (mut s, _actions) =
1020 FakeGmpStateMachine::join_group(&mut new_rng(0), FakeInstant::default(), false, &cfg);
1021 cfg = FakeConfig(true);
1022 assert_eq!(s.report_received(), ReportReceivedActions { stop_timer: true });
1023 match s.get_inner() {
1024 MemberState::Idle(s) => assert!(!s.last_reporter),
1025 _ => panic!("Wrong State!"),
1026 }
1027 assert_eq!(s.leave_group(&cfg), LeaveGroupActions { send_leave: true, stop_timer: false });
1028 }
1029
1030 #[test]
1031 fn test_gmp_state_leave_not_the_last_do_nothing() {
1032 let cfg = FakeConfig::default();
1033 let (mut s, _actions) =
1034 FakeGmpStateMachine::join_group(&mut new_rng(0), FakeInstant::default(), false, &cfg);
1035 assert_eq!(s.report_received(), ReportReceivedActions { stop_timer: true });
1036 assert_eq!(s.leave_group(&cfg), LeaveGroupActions { send_leave: false, stop_timer: false })
1037 }
1038
1039 #[ip_test(I)]
1040 fn ignores_reports_if_v2<I: gmp::testutil::TestIpExt>() {
1041 let gmp::testutil::FakeCtx { mut core_ctx, mut bindings_ctx } =
1042 gmp::testutil::new_context_with_mode::<I>(GmpMode::V2);
1043 assert_eq!(
1044 handle_report_message(&mut core_ctx, &mut bindings_ctx, &FakeDeviceId, I::GROUP_ADDR1),
1045 Ok(())
1048 );
1049 core_ctx.gmp.timers.assert_timers([]);
1051 assert_eq!(core_ctx.gmp.mode, GmpMode::V2);
1053 }
1054}