packet_formats/igmp/
types.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! IGMP parsing and serialization helper types.
6
7use core::time::Duration;
8
9use super::IgmpMaxRespCode;
10use crate::gmp::{ExactConversionError, LinExpConversion, OverflowError};
11
12/// Thin wrapper around `u8` that provides maximum response time parsing
13/// for IGMP v2.
14///
15/// Provides conversions to and from `Duration` for parsing and
16/// and serializing in the correct format, following that the underlying `u8`
17/// is the maximum response time in tenths of seconds.
18#[derive(Debug, PartialEq)]
19pub struct IgmpResponseTimeV2(u8);
20
21impl IgmpMaxRespCode for IgmpResponseTimeV2 {
22    fn as_code(&self) -> u8 {
23        self.0
24    }
25
26    fn from_code(code: u8) -> Self {
27        Self(code)
28    }
29}
30
31impl TryFrom<Duration> for IgmpResponseTimeV2 {
32    type Error = OverflowError;
33
34    fn try_from(value: Duration) -> Result<Self, Self::Error> {
35        let tenths = value.as_millis() / 100;
36        Ok(Self(tenths.try_into().map_err(|_| OverflowError)?))
37    }
38}
39
40impl From<IgmpResponseTimeV2> for Duration {
41    fn from(value: IgmpResponseTimeV2) -> Duration {
42        let v: u64 = value.0.into();
43        Self::from_millis(v * 100)
44    }
45}
46
47/// Thin wrapper around u8 that provides maximum response time parsing
48/// for IGMP v3.
49///
50/// Provides conversions to and from `Duration` for parsing and
51/// and serializing in the correct format.
52#[derive(Debug, PartialEq, Copy, Clone)]
53pub struct IgmpResponseTimeV3(u8);
54
55impl IgmpMaxRespCode for IgmpResponseTimeV3 {
56    fn as_code(&self) -> u8 {
57        self.0
58    }
59
60    fn from_code(code: u8) -> Self {
61        Self(code)
62    }
63}
64
65impl LinExpConversion<Duration> for IgmpResponseTimeV3 {
66    const NUM_MANT_BITS: u8 = 4;
67    const NUM_EXP_BITS: u8 = 3;
68
69    fn lossy_try_from(value: Duration) -> Result<Self, OverflowError> {
70        let tenths: u32 = (value.as_millis() / 100).try_into().map_err(|_| OverflowError)?;
71        let code = Self::lossy_try_from_expanded(tenths)?.try_into().map_err(|_| OverflowError)?;
72        Ok(Self(code))
73    }
74}
75
76impl IgmpResponseTimeV3 {
77    /// Creates a new `IgmpResponseTimeV3` allowing lossy conversion from
78    /// `value`.
79    pub fn new_lossy(value: Duration) -> Result<Self, OverflowError> {
80        Self::lossy_try_from(value)
81    }
82
83    /// Creates a new `IgmpResponseTimeV3` rejecting lossy conversion from
84    /// `value`.
85    pub fn new_exact(value: Duration) -> Result<Self, ExactConversionError> {
86        Self::exact_try_from(value)
87    }
88}
89
90impl From<IgmpResponseTimeV3> for Duration {
91    fn from(IgmpResponseTimeV3(value): IgmpResponseTimeV3) -> Duration {
92        // ResponseTime v3 is represented in tenths of seconds and coded
93        // with specific floating point schema.
94        let tenths: u64 = IgmpResponseTimeV3::to_expanded(value.into()).into();
95        Duration::from_millis(tenths * 100)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    pub fn parse_and_serialize_code_v2() {
105        for code in 0..=255 {
106            let response = IgmpResponseTimeV2::from_code(code);
107            assert_eq!(response.as_code(), code);
108            let dur = Duration::from(response);
109            let back = IgmpResponseTimeV2::try_from(dur).unwrap();
110            assert_eq!(dur.as_millis(), u128::from(code) * 100);
111            assert_eq!(code, back.as_code());
112        }
113
114        // test that anything larger than max u8 tenths of seconds will cause
115        // try_from to fail:
116        assert_eq!(
117            IgmpResponseTimeV2::try_from(Duration::from_millis(
118                (u64::from(core::u8::MAX) + 1) * 100,
119            )),
120            Err(OverflowError)
121        );
122    }
123
124    #[test]
125    pub fn parse_and_serialize_code_v3() {
126        let r = Duration::from(IgmpResponseTimeV3::from_code(0x80 | 0x01));
127        assert_eq!(r.as_millis(), 13600);
128        let t = IgmpResponseTimeV3::new_lossy(Duration::from_millis((128 + 8) * 100)).unwrap();
129        assert_eq!(t.as_code(), (0x80 | 0x01));
130        for code in 0..=255 {
131            let response = IgmpResponseTimeV3::from_code(code);
132            assert_eq!(response.as_code(), code);
133            let dur = Duration::from(response);
134            let back = IgmpResponseTimeV3::new_lossy(dur).unwrap();
135            assert_eq!(code, back.as_code());
136        }
137
138        // test that anything larger than max u8 tenths of seconds will cause
139        // try_from to fail:
140        assert_eq!(
141            IgmpResponseTimeV3::new_lossy(Duration::from_millis(
142                (IgmpResponseTimeV3::MAX_VALUE as u64 + 1) * 100,
143            )),
144            Err(OverflowError)
145        );
146    }
147}