|
@@ -0,0 +1,176 @@
|
|
|
+use super::{Error, Ipv6Option, Ipv6OptionRepr, Ipv6OptionsIterator, Result};
|
|
|
+
|
|
|
+use heapless::Vec;
|
|
|
+
|
|
|
+/// A read/write wrapper around an IPv6 Hop-by-Hop Header buffer.
|
|
|
+pub struct Header<T: AsRef<[u8]>> {
|
|
|
+ buffer: T,
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: AsRef<[u8]>> Header<T> {
|
|
|
+ /// Create a raw octet buffer with an IPv6 Hop-by-Hop Header structure.
|
|
|
+ pub const fn new_unchecked(buffer: T) -> Self {
|
|
|
+ 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<Self> {
|
|
|
+ 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<()> {
|
|
|
+ if self.buffer.as_ref().is_empty() {
|
|
|
+ return Err(Error);
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Consume the header, returning the underlying buffer.
|
|
|
+ pub fn into_inner(self) -> T {
|
|
|
+ self.buffer
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a, T: AsRef<[u8]> + ?Sized> Header<&'a T> {
|
|
|
+ /// Return the options of the IPv6 Hop-by-Hop header.
|
|
|
+ pub fn options(&self) -> &'a [u8] {
|
|
|
+ self.buffer.as_ref()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Header<&'a mut T> {
|
|
|
+ /// Return a mutable pointer to the options of the IPv6 Hop-by-Hop header.
|
|
|
+ pub fn options_mut(&mut self) -> &mut [u8] {
|
|
|
+ self.buffer.as_mut()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// A high-level representation of an IPv6 Hop-by-Hop Header.
|
|
|
+#[derive(Debug, PartialEq, Eq, Clone)]
|
|
|
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
|
+pub struct Repr<'a> {
|
|
|
+ pub options: heapless::Vec<Ipv6OptionRepr<'a>, { crate::config::IPV6_HBH_MAX_OPTIONS }>,
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a> Repr<'a> {
|
|
|
+ /// Parse an IPv6 Hop-by-Hop Header and return a high-level representation.
|
|
|
+ pub fn parse<T>(header: &'a Header<&'a T>) -> Result<Repr<'a>>
|
|
|
+ where
|
|
|
+ T: AsRef<[u8]> + ?Sized,
|
|
|
+ {
|
|
|
+ let mut options = Vec::new();
|
|
|
+
|
|
|
+ let iter = Ipv6OptionsIterator::new(header.options());
|
|
|
+
|
|
|
+ for option in iter {
|
|
|
+ let option = option?;
|
|
|
+
|
|
|
+ if let Err(e) = options.push(option) {
|
|
|
+ net_trace!("eror when parsing hop-by-hop options: {}", e);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(Self { options })
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Return the length, in bytes, of a header that will be emitted from this high-level
|
|
|
+ /// representation.
|
|
|
+ pub fn buffer_len(&self) -> usize {
|
|
|
+ self.options.iter().map(|o| o.buffer_len()).sum()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Emit a high-level representation into an IPv6 Hop-by-Hop Header.
|
|
|
+ pub fn emit<T: AsRef<[u8]> + AsMut<[u8]> + ?Sized>(&self, header: &mut Header<&mut T>) {
|
|
|
+ let mut buffer = header.options_mut();
|
|
|
+
|
|
|
+ for opt in &self.options {
|
|
|
+ opt.emit(&mut Ipv6Option::new_unchecked(
|
|
|
+ &mut buffer[..opt.buffer_len()],
|
|
|
+ ));
|
|
|
+ buffer = &mut buffer[opt.buffer_len()..];
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use super::*;
|
|
|
+ use crate::wire::Error;
|
|
|
+
|
|
|
+ // A Hop-by-Hop Option header with a PadN option of option data length 4.
|
|
|
+ static REPR_PACKET_PAD4: [u8; 6] = [0x1, 0x4, 0x0, 0x0, 0x0, 0x0];
|
|
|
+
|
|
|
+ // A Hop-by-Hop Option header with a PadN option of option data length 12.
|
|
|
+ static REPR_PACKET_PAD12: [u8; 14] = [
|
|
|
+ 0x1, 0x0C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
|
+ ];
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_check_len() {
|
|
|
+ // zero byte buffer
|
|
|
+ assert_eq!(
|
|
|
+ Err(Error),
|
|
|
+ Header::new_unchecked(&REPR_PACKET_PAD4[..0]).check_len()
|
|
|
+ );
|
|
|
+ // valid
|
|
|
+ assert_eq!(Ok(()), Header::new_unchecked(&REPR_PACKET_PAD4).check_len());
|
|
|
+ // valid
|
|
|
+ assert_eq!(
|
|
|
+ Ok(()),
|
|
|
+ Header::new_unchecked(&REPR_PACKET_PAD12).check_len()
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_repr_parse_valid() {
|
|
|
+ let header = Header::new_unchecked(&REPR_PACKET_PAD4);
|
|
|
+ let repr = Repr::parse(&header).unwrap();
|
|
|
+
|
|
|
+ let mut options = Vec::new();
|
|
|
+ options.push(Ipv6OptionRepr::PadN(4)).unwrap();
|
|
|
+ assert_eq!(repr, Repr { options });
|
|
|
+
|
|
|
+ let header = Header::new_unchecked(&REPR_PACKET_PAD12);
|
|
|
+ let repr = Repr::parse(&header).unwrap();
|
|
|
+
|
|
|
+ let mut options = Vec::new();
|
|
|
+ options.push(Ipv6OptionRepr::PadN(12)).unwrap();
|
|
|
+ assert_eq!(repr, Repr { options });
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_repr_emit() {
|
|
|
+ let mut options = Vec::new();
|
|
|
+ options.push(Ipv6OptionRepr::PadN(4)).unwrap();
|
|
|
+ let repr = Repr { options };
|
|
|
+
|
|
|
+ let mut bytes = [0u8; 6];
|
|
|
+ let mut header = Header::new_unchecked(&mut bytes);
|
|
|
+ repr.emit(&mut header);
|
|
|
+
|
|
|
+ assert_eq!(header.into_inner(), &REPR_PACKET_PAD4[..]);
|
|
|
+
|
|
|
+ let mut options = Vec::new();
|
|
|
+ options.push(Ipv6OptionRepr::PadN(12)).unwrap();
|
|
|
+ let repr = Repr { options };
|
|
|
+
|
|
|
+ let mut bytes = [0u8; 14];
|
|
|
+ let mut header = Header::new_unchecked(&mut bytes);
|
|
|
+ repr.emit(&mut header);
|
|
|
+
|
|
|
+ assert_eq!(header.into_inner(), &REPR_PACKET_PAD12[..]);
|
|
|
+ }
|
|
|
+}
|