123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- use byteorder::{ByteOrder, NetworkEndian};
- use core::fmt;
- use super::{Error, Result};
- use crate::time::Duration;
- use crate::wire::ip::checksum;
- use crate::wire::{Ipv4Address, Ipv4AddressExt};
- enum_with_unknown! {
-
- pub enum Message(u8) {
-
- MembershipQuery = 0x11,
-
- MembershipReportV2 = 0x16,
-
- LeaveGroup = 0x17,
-
- MembershipReportV1 = 0x12
- }
- }
- #[derive(Debug)]
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
- pub struct Packet<T: AsRef<[u8]>> {
- buffer: T,
- }
- mod field {
- use crate::wire::field::*;
- pub const TYPE: usize = 0;
- pub const MAX_RESP_CODE: usize = 1;
- pub const CHECKSUM: Field = 2..4;
- pub const GROUP_ADDRESS: Field = 4..8;
- }
- impl fmt::Display for Message {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match *self {
- Message::MembershipQuery => write!(f, "membership query"),
- Message::MembershipReportV2 => write!(f, "version 2 membership report"),
- Message::LeaveGroup => write!(f, "leave group"),
- Message::MembershipReportV1 => write!(f, "version 1 membership report"),
- Message::Unknown(id) => write!(f, "{id}"),
- }
- }
- }
- impl<T: AsRef<[u8]>> Packet<T> {
-
- pub const fn new_unchecked(buffer: T) -> Packet<T> {
- Packet { buffer }
- }
-
-
-
-
- pub fn new_checked(buffer: T) -> Result<Packet<T>> {
- let packet = Self::new_unchecked(buffer);
- packet.check_len()?;
- Ok(packet)
- }
-
-
- pub fn check_len(&self) -> Result<()> {
- let len = self.buffer.as_ref().len();
- if len < field::GROUP_ADDRESS.end {
- Err(Error)
- } else {
- Ok(())
- }
- }
-
- pub fn into_inner(self) -> T {
- self.buffer
- }
-
- #[inline]
- pub fn msg_type(&self) -> Message {
- let data = self.buffer.as_ref();
- Message::from(data[field::TYPE])
- }
-
-
-
-
- #[inline]
- pub fn max_resp_code(&self) -> u8 {
- let data = self.buffer.as_ref();
- data[field::MAX_RESP_CODE]
- }
-
- #[inline]
- pub fn checksum(&self) -> u16 {
- let data = self.buffer.as_ref();
- NetworkEndian::read_u16(&data[field::CHECKSUM])
- }
-
- #[inline]
- pub fn group_addr(&self) -> Ipv4Address {
- let data = self.buffer.as_ref();
- Ipv4Address::from_bytes(&data[field::GROUP_ADDRESS])
- }
-
-
-
-
- pub fn verify_checksum(&self) -> bool {
- if cfg!(fuzzing) {
- return true;
- }
- let data = self.buffer.as_ref();
- checksum::data(data) == !0
- }
- }
- impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
-
- #[inline]
- pub fn set_msg_type(&mut self, value: Message) {
- let data = self.buffer.as_mut();
- data[field::TYPE] = value.into()
- }
-
-
- #[inline]
- pub fn set_max_resp_code(&mut self, value: u8) {
- let data = self.buffer.as_mut();
- data[field::MAX_RESP_CODE] = value;
- }
-
- #[inline]
- pub fn set_checksum(&mut self, value: u16) {
- let data = self.buffer.as_mut();
- NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
- }
-
- #[inline]
- pub fn set_group_address(&mut self, addr: Ipv4Address) {
- let data = self.buffer.as_mut();
- data[field::GROUP_ADDRESS].copy_from_slice(&addr.octets());
- }
-
- pub fn fill_checksum(&mut self) {
- self.set_checksum(0);
- let checksum = {
- let data = self.buffer.as_ref();
- !checksum::data(data)
- };
- self.set_checksum(checksum)
- }
- }
- #[derive(Debug, PartialEq, Eq, Clone)]
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
- pub enum Repr {
- MembershipQuery {
- max_resp_time: Duration,
- group_addr: Ipv4Address,
- version: IgmpVersion,
- },
- MembershipReport {
- group_addr: Ipv4Address,
- version: IgmpVersion,
- },
- LeaveGroup {
- group_addr: Ipv4Address,
- },
- }
- #[derive(Debug, PartialEq, Eq, Clone, Copy)]
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
- pub enum IgmpVersion {
-
- Version1,
-
- Version2,
- }
- impl Repr {
-
-
- pub fn parse<T>(packet: &Packet<&T>) -> Result<Repr>
- where
- T: AsRef<[u8]> + ?Sized,
- {
- packet.check_len()?;
-
- let addr = packet.group_addr();
- if !addr.is_unspecified() && !addr.is_multicast() {
- return Err(Error);
- }
-
- match packet.msg_type() {
- Message::MembershipQuery => {
- let max_resp_time = max_resp_code_to_duration(packet.max_resp_code());
-
- let version = if packet.max_resp_code() == 0 {
- IgmpVersion::Version1
- } else {
- IgmpVersion::Version2
- };
- Ok(Repr::MembershipQuery {
- max_resp_time,
- group_addr: addr,
- version,
- })
- }
- Message::MembershipReportV2 => Ok(Repr::MembershipReport {
- group_addr: packet.group_addr(),
- version: IgmpVersion::Version2,
- }),
- Message::LeaveGroup => Ok(Repr::LeaveGroup {
- group_addr: packet.group_addr(),
- }),
- Message::MembershipReportV1 => {
-
- Ok(Repr::MembershipReport {
- group_addr: packet.group_addr(),
- version: IgmpVersion::Version1,
- })
- }
- _ => Err(Error),
- }
- }
-
- pub const fn buffer_len(&self) -> usize {
-
- field::GROUP_ADDRESS.end
- }
-
- pub fn emit<T>(&self, packet: &mut Packet<&mut T>)
- where
- T: AsRef<[u8]> + AsMut<[u8]> + ?Sized,
- {
- match *self {
- Repr::MembershipQuery {
- max_resp_time,
- group_addr,
- version,
- } => {
- packet.set_msg_type(Message::MembershipQuery);
- match version {
- IgmpVersion::Version1 => packet.set_max_resp_code(0),
- IgmpVersion::Version2 => {
- packet.set_max_resp_code(duration_to_max_resp_code(max_resp_time))
- }
- }
- packet.set_group_address(group_addr);
- }
- Repr::MembershipReport {
- group_addr,
- version,
- } => {
- match version {
- IgmpVersion::Version1 => packet.set_msg_type(Message::MembershipReportV1),
- IgmpVersion::Version2 => packet.set_msg_type(Message::MembershipReportV2),
- };
- packet.set_max_resp_code(0);
- packet.set_group_address(group_addr);
- }
- Repr::LeaveGroup { group_addr } => {
- packet.set_msg_type(Message::LeaveGroup);
- packet.set_group_address(group_addr);
- }
- }
- packet.fill_checksum()
- }
- }
- fn max_resp_code_to_duration(value: u8) -> Duration {
- let value: u64 = value.into();
- let decisecs = if value < 128 {
- value
- } else {
- let mant = value & 0xF;
- let exp = (value >> 4) & 0x7;
- (mant | 0x10) << (exp + 3)
- };
- Duration::from_millis(decisecs * 100)
- }
- const fn duration_to_max_resp_code(duration: Duration) -> u8 {
- let decisecs = duration.total_millis() / 100;
- if decisecs < 128 {
- decisecs as u8
- } else if decisecs < 31744 {
- let mut mant = decisecs >> 3;
- let mut exp = 0u8;
- while mant > 0x1F && exp < 0x8 {
- mant >>= 1;
- exp += 1;
- }
- 0x80 | (exp << 4) | (mant as u8 & 0xF)
- } else {
- 0xFF
- }
- }
- impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match Repr::parse(self) {
- Ok(repr) => write!(f, "{repr}"),
- Err(err) => write!(f, "IGMP ({err})"),
- }
- }
- }
- impl fmt::Display for Repr {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match *self {
- Repr::MembershipQuery {
- max_resp_time,
- group_addr,
- version,
- } => write!(
- f,
- "IGMP membership query max_resp_time={max_resp_time} group_addr={group_addr} version={version:?}"
- ),
- Repr::MembershipReport {
- group_addr,
- version,
- } => write!(
- f,
- "IGMP membership report group_addr={group_addr} version={version:?}"
- ),
- Repr::LeaveGroup { group_addr } => {
- write!(f, "IGMP leave group group_addr={group_addr})")
- }
- }
- }
- }
- use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
- impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
- fn pretty_print(
- buffer: &dyn AsRef<[u8]>,
- f: &mut fmt::Formatter,
- indent: &mut PrettyIndent,
- ) -> fmt::Result {
- match Packet::new_checked(buffer) {
- Err(err) => writeln!(f, "{indent}({err})"),
- Ok(packet) => writeln!(f, "{indent}{packet}"),
- }
- }
- }
- #[cfg(test)]
- mod test {
- use super::*;
- static LEAVE_PACKET_BYTES: [u8; 8] = [0x17, 0x00, 0x02, 0x69, 0xe0, 0x00, 0x06, 0x96];
- static REPORT_PACKET_BYTES: [u8; 8] = [0x16, 0x00, 0x08, 0xda, 0xe1, 0x00, 0x00, 0x25];
- #[test]
- fn test_leave_group_deconstruct() {
- let packet = Packet::new_unchecked(&LEAVE_PACKET_BYTES[..]);
- assert_eq!(packet.msg_type(), Message::LeaveGroup);
- assert_eq!(packet.max_resp_code(), 0);
- assert_eq!(packet.checksum(), 0x269);
- assert_eq!(
- packet.group_addr(),
- Ipv4Address::from_bytes(&[224, 0, 6, 150])
- );
- assert!(packet.verify_checksum());
- }
- #[test]
- fn test_report_deconstruct() {
- let packet = Packet::new_unchecked(&REPORT_PACKET_BYTES[..]);
- assert_eq!(packet.msg_type(), Message::MembershipReportV2);
- assert_eq!(packet.max_resp_code(), 0);
- assert_eq!(packet.checksum(), 0x08da);
- assert_eq!(
- packet.group_addr(),
- Ipv4Address::from_bytes(&[225, 0, 0, 37])
- );
- assert!(packet.verify_checksum());
- }
- #[test]
- fn test_leave_construct() {
- let mut bytes = vec![0xa5; 8];
- let mut packet = Packet::new_unchecked(&mut bytes);
- packet.set_msg_type(Message::LeaveGroup);
- packet.set_max_resp_code(0);
- packet.set_group_address(Ipv4Address::from_bytes(&[224, 0, 6, 150]));
- packet.fill_checksum();
- assert_eq!(&*packet.into_inner(), &LEAVE_PACKET_BYTES[..]);
- }
- #[test]
- fn test_report_construct() {
- let mut bytes = vec![0xa5; 8];
- let mut packet = Packet::new_unchecked(&mut bytes);
- packet.set_msg_type(Message::MembershipReportV2);
- packet.set_max_resp_code(0);
- packet.set_group_address(Ipv4Address::from_bytes(&[225, 0, 0, 37]));
- packet.fill_checksum();
- assert_eq!(&*packet.into_inner(), &REPORT_PACKET_BYTES[..]);
- }
- #[test]
- fn max_resp_time_to_duration_and_back() {
- for i in 0..256usize {
- let time1 = i as u8;
- let duration = max_resp_code_to_duration(time1);
- let time2 = duration_to_max_resp_code(duration);
- assert!(time1 == time2);
- }
- }
- #[test]
- fn duration_to_max_resp_time_max() {
- for duration in 31744..65536 {
- let time = duration_to_max_resp_code(Duration::from_millis(duration * 100));
- assert_eq!(time, 0xFF);
- }
- }
- }
|