| use super::{Error, Result}; |
| use core::fmt; |
| |
| use crate::wire::Ipv6Address as Address; |
| |
| enum_with_unknown! { |
| /// IPv6 Extension Routing Header Routing Type |
| pub enum Type(u8) { |
| /// Source Route (DEPRECATED) |
| /// |
| /// See https://tools.ietf.org/html/rfc5095 for details. |
| Type0 = 0, |
| /// Nimrod (DEPRECATED 2009-05-06) |
| Nimrod = 1, |
| /// Type 2 Routing Header for Mobile IPv6 |
| /// |
| /// See https://tools.ietf.org/html/rfc6275#section-6.4 for details. |
| Type2 = 2, |
| /// RPL Source Routing Header |
| /// |
| /// See https://tools.ietf.org/html/rfc6554 for details. |
| Rpl = 3, |
| /// RFC3692-style Experiment 1 |
| /// |
| /// See https://tools.ietf.org/html/rfc4727 for details. |
| Experiment1 = 253, |
| /// RFC3692-style Experiment 2 |
| /// |
| /// See https://tools.ietf.org/html/rfc4727 for details. |
| Experiment2 = 254, |
| /// Reserved for future use |
| Reserved = 252 |
| } |
| } |
| |
| impl fmt::Display for Type { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match *self { |
| Type::Type0 => write!(f, "Type0"), |
| Type::Nimrod => write!(f, "Nimrod"), |
| Type::Type2 => write!(f, "Type2"), |
| Type::Rpl => write!(f, "Rpl"), |
| Type::Experiment1 => write!(f, "Experiment1"), |
| Type::Experiment2 => write!(f, "Experiment2"), |
| Type::Reserved => write!(f, "Reserved"), |
| Type::Unknown(id) => write!(f, "{id}"), |
| } |
| } |
| } |
| |
| /// A read/write wrapper around an IPv6 Routing Header buffer. |
| #[derive(Debug, PartialEq, Eq)] |
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| pub struct Header<T: AsRef<[u8]>> { |
| buffer: T, |
| } |
| |
| // Format of the Routing Header |
| // |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | Next Header | Hdr Ext Len | Routing Type | Segments Left | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | | |
| // . . |
| // . type-specific data . |
| // . . |
| // | | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // |
| // |
| // See https://tools.ietf.org/html/rfc8200#section-4.4 for details. |
| // |
| // **NOTE**: The fields start counting after the header length field. |
| mod field { |
| #![allow(non_snake_case)] |
| |
| use crate::wire::field::*; |
| |
| // Minimum size of the header. |
| pub const MIN_HEADER_SIZE: usize = 2; |
| |
| // 8-bit identifier of a particular Routing header variant. |
| pub const TYPE: usize = 0; |
| // 8-bit unsigned integer. The number of route segments remaining. |
| pub const SEG_LEFT: usize = 1; |
| |
| // The Type 2 Routing Header has the following format: |
| // |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | Next Header | Hdr Ext Len=2 | Routing Type=2|Segments Left=1| |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | Reserved | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | | |
| // + + |
| // | | |
| // + Home Address + |
| // | | |
| // + + |
| // | | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| // 16-byte field containing the home address of the destination mobile node. |
| pub const HOME_ADDRESS: Field = 6..22; |
| |
| // The RPL Source Routing Header has the following format: |
| // |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | Next Header | Hdr Ext Len | Routing Type | Segments Left | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | CmprI | CmprE | Pad | Reserved | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | | |
| // . . |
| // . Addresses[1..n] . |
| // . . |
| // | | |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| // 8-bit field containing the CmprI and CmprE values. |
| pub const CMPR: usize = 2; |
| // 8-bit field containing the Pad value. |
| pub const PAD: usize = 3; |
| // Variable length field containing addresses |
| pub const ADDRESSES: usize = 6; |
| } |
| |
| /// Core getter methods relevant to any routing type. |
| impl<T: AsRef<[u8]>> Header<T> { |
| /// Create a raw octet buffer with an IPv6 Routing Header structure. |
| pub const fn new_unchecked(buffer: T) -> Header<T> { |
| Header { buffer } |
| } |
| |
| /// Shorthand for a combination of [new_unchecked] and [check_len]. |
| /// |
| /// [new_unchecked]: #method.new_unchecked |
| /// [check_len]: #method.check_len |
| pub fn new_checked(buffer: T) -> Result<Header<T>> { |
| let header = Self::new_unchecked(buffer); |
| header.check_len()?; |
| Ok(header) |
| } |
| |
| /// Ensure that no accessor method will panic if called. |
| /// Returns `Err(Error)` if the buffer is too short. |
| /// |
| /// The result of this check is invalidated by calling [set_header_len]. |
| /// |
| /// [set_header_len]: #method.set_header_len |
| pub fn check_len(&self) -> Result<()> { |
| let len = self.buffer.as_ref().len(); |
| if len < field::MIN_HEADER_SIZE { |
| return Err(Error); |
| } |
| |
| match self.routing_type() { |
| Type::Type2 if len < field::HOME_ADDRESS.end => return Err(Error), |
| Type::Rpl if len < field::ADDRESSES => return Err(Error), |
| _ => (), |
| } |
| |
| Ok(()) |
| } |
| |
| /// Consume the header, returning the underlying buffer. |
| pub fn into_inner(self) -> T { |
| self.buffer |
| } |
| |
| /// Return the routing type field. |
| #[inline] |
| pub fn routing_type(&self) -> Type { |
| let data = self.buffer.as_ref(); |
| Type::from(data[field::TYPE]) |
| } |
| |
| /// Return the segments left field. |
| #[inline] |
| pub fn segments_left(&self) -> u8 { |
| let data = self.buffer.as_ref(); |
| data[field::SEG_LEFT] |
| } |
| } |
| |
| /// Getter methods for the Type 2 Routing Header routing type. |
| impl<T: AsRef<[u8]>> Header<T> { |
| /// Return the IPv6 Home Address |
| /// |
| /// # Panics |
| /// This function may panic if this header is not the Type2 Routing Header routing type. |
| pub fn home_address(&self) -> Address { |
| let data = self.buffer.as_ref(); |
| Address::from_bytes(&data[field::HOME_ADDRESS]) |
| } |
| } |
| |
| /// Getter methods for the RPL Source Routing Header routing type. |
| impl<T: AsRef<[u8]>> Header<T> { |
| /// Return the number of prefix octets elided from addresses[1..n-1]. |
| /// |
| /// # Panics |
| /// This function may panic if this header is not the RPL Source Routing Header routing type. |
| pub fn cmpr_i(&self) -> u8 { |
| let data = self.buffer.as_ref(); |
| data[field::CMPR] >> 4 |
| } |
| |
| /// Return the number of prefix octets elided from the last address (`addresses[n]`). |
| /// |
| /// # Panics |
| /// This function may panic if this header is not the RPL Source Routing Header routing type. |
| pub fn cmpr_e(&self) -> u8 { |
| let data = self.buffer.as_ref(); |
| data[field::CMPR] & 0xf |
| } |
| |
| /// Return the number of octets used for padding after `addresses[n]`. |
| /// |
| /// # Panics |
| /// This function may panic if this header is not the RPL Source Routing Header routing type. |
| pub fn pad(&self) -> u8 { |
| let data = self.buffer.as_ref(); |
| data[field::PAD] >> 4 |
| } |
| |
| /// Return the address vector in bytes |
| /// |
| /// # Panics |
| /// This function may panic if this header is not the RPL Source Routing Header routing type. |
| pub fn addresses(&self) -> &[u8] { |
| let data = self.buffer.as_ref(); |
| &data[field::ADDRESSES..] |
| } |
| } |
| |
| /// Core setter methods relevant to any routing type. |
| impl<T: AsRef<[u8]> + AsMut<[u8]>> Header<T> { |
| /// Set the routing type. |
| #[inline] |
| pub fn set_routing_type(&mut self, value: Type) { |
| let data = self.buffer.as_mut(); |
| data[field::TYPE] = value.into(); |
| } |
| |
| /// Set the segments left field. |
| #[inline] |
| pub fn set_segments_left(&mut self, value: u8) { |
| let data = self.buffer.as_mut(); |
| data[field::SEG_LEFT] = value; |
| } |
| |
| /// Initialize reserved fields to 0. |
| /// |
| /// # Panics |
| /// This function may panic if the routing type is not set. |
| #[inline] |
| pub fn clear_reserved(&mut self) { |
| let routing_type = self.routing_type(); |
| let data = self.buffer.as_mut(); |
| |
| match routing_type { |
| Type::Type2 => { |
| data[4] = 0; |
| data[5] = 0; |
| data[6] = 0; |
| data[7] = 0; |
| } |
| Type::Rpl => { |
| // Retain the higher order 4 bits of the padding field |
| data[field::PAD] &= 0xF0; |
| data[4] = 0; |
| data[5] = 0; |
| } |
| |
| _ => panic!("Unrecognized routing type when clearing reserved fields."), |
| } |
| } |
| } |
| |
| /// Setter methods for the RPL Source Routing Header routing type. |
| impl<T: AsRef<[u8]> + AsMut<[u8]>> Header<T> { |
| /// Set the Ipv6 Home Address |
| /// |
| /// # Panics |
| /// This function may panic if this header is not the Type 2 Routing Header routing type. |
| pub fn set_home_address(&mut self, value: Address) { |
| let data = self.buffer.as_mut(); |
| data[field::HOME_ADDRESS].copy_from_slice(value.as_bytes()); |
| } |
| } |
| |
| /// Setter methods for the RPL Source Routing Header routing type. |
| impl<T: AsRef<[u8]> + AsMut<[u8]>> Header<T> { |
| /// Set the number of prefix octets elided from addresses[1..n-1]. |
| /// |
| /// # Panics |
| /// This function may panic if this header is not the RPL Source Routing Header routing type. |
| pub fn set_cmpr_i(&mut self, value: u8) { |
| let data = self.buffer.as_mut(); |
| let raw = (value << 4) | (data[field::CMPR] & 0xF); |
| data[field::CMPR] = raw; |
| } |
| |
| /// Set the number of prefix octets elided from the last address (`addresses[n]`). |
| /// |
| /// # Panics |
| /// This function may panic if this header is not the RPL Source Routing Header routing type. |
| pub fn set_cmpr_e(&mut self, value: u8) { |
| let data = self.buffer.as_mut(); |
| let raw = (value & 0xF) | (data[field::CMPR] & 0xF0); |
| data[field::CMPR] = raw; |
| } |
| |
| /// Set the number of octets used for padding after `addresses[n]`. |
| /// |
| /// # Panics |
| /// This function may panic if this header is not the RPL Source Routing Header routing type. |
| pub fn set_pad(&mut self, value: u8) { |
| let data = self.buffer.as_mut(); |
| data[field::PAD] = value << 4; |
| } |
| |
| /// Set address data |
| /// |
| /// # Panics |
| /// This function may panic if this header is not the RPL Source Routing Header routing type. |
| pub fn set_addresses(&mut self, value: &[u8]) { |
| let data = self.buffer.as_mut(); |
| let addresses = &mut data[field::ADDRESSES..]; |
| addresses.copy_from_slice(value); |
| } |
| } |
| |
| impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Header<&'a T> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match Repr::parse(self) { |
| Ok(repr) => write!(f, "{repr}"), |
| Err(err) => { |
| write!(f, "IPv6 Routing ({err})")?; |
| Ok(()) |
| } |
| } |
| } |
| } |
| |
| /// A high-level representation of an IPv6 Routing Header. |
| #[derive(Debug, PartialEq, Eq, Clone, Copy)] |
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| #[non_exhaustive] |
| pub enum Repr<'a> { |
| Type2 { |
| /// Number of route segments remaining. |
| segments_left: u8, |
| /// The home address of the destination mobile node. |
| home_address: Address, |
| }, |
| Rpl { |
| /// Number of route segments remaining. |
| segments_left: u8, |
| /// Number of prefix octets from each segment, except the last segment, that are elided. |
| cmpr_i: u8, |
| /// Number of prefix octets from the last segment that are elided. |
| cmpr_e: u8, |
| /// Number of octets that are used for padding after `address[n]` at the end of the |
| /// RPL Source Route Header. |
| pad: u8, |
| /// Vector of addresses, numbered 1 to `n`. |
| addresses: &'a [u8], |
| }, |
| } |
| |
| impl<'a> Repr<'a> { |
| /// Parse an IPv6 Routing Header and return a high-level representation. |
| pub fn parse<T>(header: &'a Header<&'a T>) -> Result<Repr<'a>> |
| where |
| T: AsRef<[u8]> + ?Sized, |
| { |
| match header.routing_type() { |
| Type::Type2 => Ok(Repr::Type2 { |
| segments_left: header.segments_left(), |
| home_address: header.home_address(), |
| }), |
| Type::Rpl => Ok(Repr::Rpl { |
| segments_left: header.segments_left(), |
| cmpr_i: header.cmpr_i(), |
| cmpr_e: header.cmpr_e(), |
| pad: header.pad(), |
| addresses: header.addresses(), |
| }), |
| |
| _ => Err(Error), |
| } |
| } |
| |
| /// Return the length, in bytes, of a header that will be emitted from this high-level |
| /// representation. |
| pub const fn buffer_len(&self) -> usize { |
| match self { |
| // Routing Type + Segments Left + Reserved + Home Address |
| Repr::Type2 { home_address, .. } => 2 + 4 + home_address.as_bytes().len(), |
| Repr::Rpl { addresses, .. } => 2 + 4 + addresses.len(), |
| } |
| } |
| |
| /// Emit a high-level representation into an IPv6 Routing Header. |
| pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) { |
| match *self { |
| Repr::Type2 { |
| segments_left, |
| home_address, |
| } => { |
| header.set_routing_type(Type::Type2); |
| header.set_segments_left(segments_left); |
| header.clear_reserved(); |
| header.set_home_address(home_address); |
| } |
| Repr::Rpl { |
| segments_left, |
| cmpr_i, |
| cmpr_e, |
| pad, |
| addresses, |
| } => { |
| header.set_routing_type(Type::Rpl); |
| header.set_segments_left(segments_left); |
| header.set_cmpr_i(cmpr_i); |
| header.set_cmpr_e(cmpr_e); |
| header.set_pad(pad); |
| header.clear_reserved(); |
| header.set_addresses(addresses); |
| } |
| } |
| } |
| } |
| |
| impl<'a> fmt::Display for Repr<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| match *self { |
| Repr::Type2 { |
| segments_left, |
| home_address, |
| } => { |
| write!( |
| f, |
| "IPv6 Routing type={} seg_left={} home_address={}", |
| Type::Type2, |
| segments_left, |
| home_address |
| ) |
| } |
| Repr::Rpl { |
| segments_left, |
| cmpr_i, |
| cmpr_e, |
| pad, |
| .. |
| } => { |
| write!( |
| f, |
| "IPv6 Routing type={} seg_left={} cmpr_i={} cmpr_e={} pad={}", |
| Type::Rpl, |
| segments_left, |
| cmpr_i, |
| cmpr_e, |
| pad |
| ) |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| // A Type 2 Routing Header |
| static BYTES_TYPE2: [u8; 22] = [ |
| 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, |
| 0x0, 0x0, 0x0, 0x1, |
| ]; |
| |
| // A representation of a Type 2 Routing header |
| static REPR_TYPE2: Repr = Repr::Type2 { |
| segments_left: 1, |
| home_address: Address::LOOPBACK, |
| }; |
| |
| // A Source Routing Header with full IPv6 addresses in bytes |
| static BYTES_SRH_FULL: [u8; 38] = [ |
| 0x3, 0x2, 0x0, 0x0, 0x0, 0x0, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, |
| 0x0, 0x0, 0x0, 0x2, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, |
| 0x3, 0x1, |
| ]; |
| |
| // A representation of a Source Routing Header with full IPv6 addresses |
| static REPR_SRH_FULL: Repr = Repr::Rpl { |
| segments_left: 2, |
| cmpr_i: 0, |
| cmpr_e: 0, |
| pad: 0, |
| addresses: &[ |
| 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xfd, |
| 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x1, |
| ], |
| }; |
| |
| // A Source Routing Header with elided IPv6 addresses in bytes |
| static BYTES_SRH_ELIDED: [u8; 14] = [ |
| 0x3, 0x2, 0xfe, 0x50, 0x0, 0x0, 0x2, 0x3, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, |
| ]; |
| |
| // A representation of a Source Routing Header with elided IPv6 addresses |
| static REPR_SRH_ELIDED: Repr = Repr::Rpl { |
| segments_left: 2, |
| cmpr_i: 15, |
| cmpr_e: 14, |
| pad: 5, |
| addresses: &[0x2, 0x3, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0], |
| }; |
| |
| #[test] |
| fn test_check_len() { |
| // less than min header size |
| assert_eq!( |
| Err(Error), |
| Header::new_unchecked(&BYTES_TYPE2[..3]).check_len() |
| ); |
| assert_eq!( |
| Err(Error), |
| Header::new_unchecked(&BYTES_SRH_FULL[..3]).check_len() |
| ); |
| assert_eq!( |
| Err(Error), |
| Header::new_unchecked(&BYTES_SRH_ELIDED[..3]).check_len() |
| ); |
| // valid |
| assert_eq!(Ok(()), Header::new_unchecked(&BYTES_TYPE2[..]).check_len()); |
| assert_eq!( |
| Ok(()), |
| Header::new_unchecked(&BYTES_SRH_FULL[..]).check_len() |
| ); |
| assert_eq!( |
| Ok(()), |
| Header::new_unchecked(&BYTES_SRH_ELIDED[..]).check_len() |
| ); |
| } |
| |
| #[test] |
| fn test_header_deconstruct() { |
| let header = Header::new_unchecked(&BYTES_TYPE2[..]); |
| assert_eq!(header.routing_type(), Type::Type2); |
| assert_eq!(header.segments_left(), 1); |
| assert_eq!(header.home_address(), Address::LOOPBACK); |
| |
| let header = Header::new_unchecked(&BYTES_SRH_FULL[..]); |
| assert_eq!(header.routing_type(), Type::Rpl); |
| assert_eq!(header.segments_left(), 2); |
| assert_eq!(header.addresses(), &BYTES_SRH_FULL[6..]); |
| |
| let header = Header::new_unchecked(&BYTES_SRH_ELIDED[..]); |
| assert_eq!(header.routing_type(), Type::Rpl); |
| assert_eq!(header.segments_left(), 2); |
| assert_eq!(header.addresses(), &BYTES_SRH_ELIDED[6..]); |
| } |
| |
| #[test] |
| fn test_repr_parse_valid() { |
| let header = Header::new_checked(&BYTES_TYPE2[..]).unwrap(); |
| let repr = Repr::parse(&header).unwrap(); |
| assert_eq!(repr, REPR_TYPE2); |
| |
| let header = Header::new_checked(&BYTES_SRH_FULL[..]).unwrap(); |
| let repr = Repr::parse(&header).unwrap(); |
| assert_eq!(repr, REPR_SRH_FULL); |
| |
| let header = Header::new_checked(&BYTES_SRH_ELIDED[..]).unwrap(); |
| let repr = Repr::parse(&header).unwrap(); |
| assert_eq!(repr, REPR_SRH_ELIDED); |
| } |
| |
| #[test] |
| fn test_repr_emit() { |
| let mut bytes = [0u8; 22]; |
| let mut header = Header::new_unchecked(&mut bytes[..]); |
| REPR_TYPE2.emit(&mut header); |
| assert_eq!(header.into_inner(), &BYTES_TYPE2[..]); |
| |
| let mut bytes = [0u8; 38]; |
| let mut header = Header::new_unchecked(&mut bytes[..]); |
| REPR_SRH_FULL.emit(&mut header); |
| assert_eq!(header.into_inner(), &BYTES_SRH_FULL[..]); |
| |
| let mut bytes = [0u8; 14]; |
| let mut header = Header::new_unchecked(&mut bytes[..]); |
| REPR_SRH_ELIDED.emit(&mut header); |
| assert_eq!(header.into_inner(), &BYTES_SRH_ELIDED[..]); |
| } |
| |
| #[test] |
| fn test_buffer_len() { |
| assert_eq!(REPR_TYPE2.buffer_len(), 22); |
| assert_eq!(REPR_SRH_FULL.buffer_len(), 38); |
| assert_eq!(REPR_SRH_ELIDED.buffer_len(), 14); |
| } |
| } |