renet/src/wire/sixlowpan.rs

1766 lines
64 KiB
Rust

/// Implementation of [RFC 6282] which specifies a compression format for IPv6 datagrams over
/// IEEE802.154-based networks.
///
/// [RFC 6282]: https://datatracker.ietf.org/doc/html/rfc6282
use crate::wire::ieee802154::Address as LlAddress;
use crate::wire::ipv6;
use crate::wire::IpProtocol;
use crate::Error;
use crate::Result;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum NextHeader {
Compressed,
Uncompressed(IpProtocol),
}
/// A wrapper around the address provided in the 6LoWPAN_IPHC header.
/// This requires some context to convert it the an IPv6 address in some cases.
/// For 802.15.4 the context are the short/extended addresses.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Address<'a> {
Complete(ipv6::Address),
WithContext(&'a [u8]),
Elided,
Reserved,
}
impl<'a> Address<'a> {
/// Resolve the address provided by the IPHC encoding.
pub(crate) fn resolve(self, ll_addr: Option<LlAddress>) -> Result<ipv6::Address> {
match self {
Address::Complete(addr) => Ok(addr),
Address::Elided => {
let mut bytes = [0; 16];
bytes[0] = 0xfe;
bytes[1] = 0x80;
match ll_addr {
Some(LlAddress::Short(ll)) => {
bytes[11] = 0xff;
bytes[12] = 0xfe;
bytes[14..].copy_from_slice(&ll);
}
Some(LlAddress::Extended(ll)) => {
bytes[8..].copy_from_slice(&LlAddress::Extended(ll).as_eui_64().unwrap());
}
_ => return Err(Error::Malformed),
}
Ok(ipv6::Address::from_bytes(&bytes))
}
Address::WithContext(_) => Err(Error::NotSupported),
Address::Reserved => Err(Error::Malformed),
}
}
}
pub mod iphc {
use crate::wire::ieee802154::Address as LlAddress;
use crate::wire::ipv6;
use crate::wire::IpProtocol;
use crate::Error;
use crate::Result;
use byteorder::{ByteOrder, NetworkEndian};
use super::Address;
use super::NextHeader;
mod field {
#![allow(non_snake_case)]
use crate::wire::field::*;
pub const IPHC_FIELD: Field = 0..2;
}
const DISPATCH: u8 = 0b011;
macro_rules! get_field {
($name:ident, $mask:expr, $shift:expr) => {
fn $name(&self) -> u8 {
let data = self.buffer.as_ref();
let raw = NetworkEndian::read_u16(&data[field::IPHC_FIELD]);
((raw >> $shift) & $mask) as u8
}
};
}
macro_rules! set_field {
($name:ident, $mask:expr, $shift:expr) => {
fn $name(&mut self, val: u8) {
let data = &mut self.buffer.as_mut()[field::IPHC_FIELD];
let mut raw = NetworkEndian::read_u16(data);
raw = (raw & !($mask << $shift)) | ((val as u16) << $shift);
NetworkEndian::write_u16(data, raw);
}
};
}
/// A read/write wrapper around a LOWPAN_IPHC frame buffer.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Packet<T: AsRef<[u8]>> {
buffer: T,
}
impl<T: AsRef<[u8]>> Packet<T> {
/// Input a raw octet buffer with a 6LoWPAN_IPHC frame structure.
pub fn new_unchecked(buffer: T) -> Packet<T> {
Packet { 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<Packet<T>> {
let packet = Self::new_unchecked(buffer);
packet.check_len()?;
Ok(packet)
}
/// Ensure that no accessor method will panic if called.
/// Returns `Err(Error::Truncated)` if the buffer is too short.
pub fn check_len(&self) -> Result<()> {
let buffer = self.buffer.as_ref();
if buffer.len() < 2 {
return Err(Error::Truncated);
}
let mut offset = self.ip_fields_start()
+ self.traffic_class_size()
+ self.next_header_size()
+ self.hop_limit_size();
offset += self.src_address_size();
offset += self.dst_address_size();
if offset as usize > buffer.len() {
return Err(Error::Truncated);
}
Ok(())
}
/// Consumes the frame, returning the underlying buffer.
pub fn into_inner(self) -> T {
self.buffer
}
/// Return the Next Header field of this IPHC packet.
pub fn next_header(&self) -> NextHeader {
let nh = self.nh_field();
if nh == 1 {
// The next header field is compressed.
// It is also encoded using LOWPAN_NHC.
NextHeader::Compressed
} else {
// The full 8 bits for Next Header are carried in-line.
let start = (self.ip_fields_start() + self.traffic_class_size()) as usize;
let data = self.buffer.as_ref();
let nh = data[start..start + 1][0];
NextHeader::Uncompressed(IpProtocol::from(nh))
}
}
/// Return the Hop Limit of this IPHC packet.
pub fn hop_limit(&self) -> u8 {
match self.hlim_field() {
0b00 => {
let start = (self.ip_fields_start()
+ self.traffic_class_size()
+ self.next_header_size()) as usize;
let data = self.buffer.as_ref();
data[start..start + 1][0]
}
0b01 => 1,
0b10 => 64,
0b11 => 255,
_ => unreachable!(),
}
}
/// Return the Source Context Identifier of this IPHC packet.
pub fn src_context_id(&self) -> Option<u8> {
if self.cid_field() == 1 {
let data = self.buffer.as_ref();
Some(data[1] >> 4)
} else {
None
}
}
/// Return the Destination Context Identifier of this IPHC packet.
pub fn dst_context_id(&self) -> Option<u8> {
if self.cid_field() == 1 {
let data = self.buffer.as_ref();
Some(data[1] & 0x0f)
} else {
None
}
}
/// Return the Source Address of this IPHC packet.
pub fn src_addr(&self) -> Result<Address> {
let start = (self.ip_fields_start()
+ self.traffic_class_size()
+ self.next_header_size()
+ self.hop_limit_size()) as usize;
match (self.sac_field(), self.sam_field()) {
(0, 0b00) => {
// The full address is carried in-line.
let data = self.buffer.as_ref();
Ok(Address::Complete(ipv6::Address::from_bytes(
&data[start..start + 16],
)))
}
(0, 0b01) => {
// The first 64-bits of the address is elided.
// The value of those bits is the link-local prefix padded with zeros.
// The remaining 64-bits are carried in-line.
let data = self.buffer.as_ref();
let mut bytes = [0u8; 16];
// Link-local prefix
bytes[0] = 0xfe;
bytes[1] = 0x80;
bytes[8..].copy_from_slice(&data[start..start + 8]);
Ok(Address::Complete(ipv6::Address::from_bytes(&bytes)))
}
(0, 0b10) => {
// The first 112 bits of the address are elided.
// The value of the 64 bits is the link-local prefix padded with zeros.
// The following 64 bits are 0000:00ff:fe00:XXXX,
// where XXXX are the bits carried in-line.
let data = self.buffer.as_ref();
let mut bytes = [0u8; 16];
// Link-local prefix
bytes[0] = 0xfe;
bytes[1] = 0x80;
bytes[11] = 0xff;
bytes[12] = 0xfe;
bytes[14..].copy_from_slice(&data[start..start + 2]);
Ok(Address::Complete(ipv6::Address::from_bytes(&bytes)))
}
(0, 0b11) => {
// The address is fully elided.
// The first 64 bits of the address are the link-local prefix padded with zeros.
// The remaining 64 bits are computed from the encapsulating header.
Ok(Address::Elided)
}
(1, 0b00) => Ok(Address::Complete(ipv6::Address::UNSPECIFIED)),
(1, 0b01) => {
// The address is derived using context information and the 64 bits carried in-line.
// Bits covered by context information are always used.
// Any IID bits not covered by context information are directly from the corresponding bits carried in-line.
// Any remaining bits are zero.
let data = self.buffer.as_ref();
let bytes = &data[start..start + 8];
Ok(Address::WithContext(bytes))
}
(1, 0b10) => {
// The address is derived using context information and the 16 bits carried in-line.
// Bits covered by context information are always used.
// Any IID bits not covered by context information are directly from the corresponding bits carried in-line.
// Any remaining bits are zero.
let data = self.buffer.as_ref();
let bytes = &data[start..start + 2];
Ok(Address::WithContext(bytes))
}
(1, 0b11) => {
// The address is fully elided and is derived using context information and the encapsulating header.
// Bits covered by context information are always used.
// Any IID bits not covered by context information are always used.
// Any IID bits not covered by context information are directly from the corresponding bits carried in-line.
// Any remaining bits are zero.
Ok(Address::WithContext(&[]))
}
_ => Err(Error::Malformed),
}
}
/// Return the Destination Address of this IPHC packet.
pub fn dst_addr(&self) -> Result<Address> {
let start = (self.ip_fields_start()
+ self.traffic_class_size()
+ self.next_header_size()
+ self.hop_limit_size()
+ self.src_address_size()) as usize;
match (self.m_field(), self.dac_field(), self.dam_field()) {
(0, 0, 0b00) => {
// The full address is carried in-line.
let data = self.buffer.as_ref();
Ok(Address::Complete(ipv6::Address::from_bytes(
&data[start..start + 16],
)))
}
(0, 0, 0b01) => {
// The first 64-bits of the address is elided.
// The value of those bits is the link-local prefix padded with zeros.
// The remaining 64-bits are carried in-line.
let data = self.buffer.as_ref();
let mut bytes = [0u8; 16];
// Link-local prefix
bytes[0] = 0xfe;
bytes[1] = 0x80;
bytes[8..].copy_from_slice(&data[start..start + 8]);
Ok(Address::Complete(ipv6::Address::from_bytes(&bytes)))
}
(0, 0, 0b10) => {
// The first 112 bits of the address are elided.
// The value of the 64 bits is the link-local prefix padded with zeros.
// The following 64 bits are 0000:00ff:fe00:XXXX,
// where XXXX are the bits carried in-line.
let data = self.buffer.as_ref();
let mut bytes = [0u8; 16];
// Link-local prefix
bytes[0] = 0xfe;
bytes[1] = 0x80;
bytes[11] = 0xff;
bytes[12] = 0xfe;
bytes[14..].copy_from_slice(&data[start..start + 2]);
Ok(Address::Complete(ipv6::Address::from_bytes(&bytes)))
}
(0, 0, 0b11) => {
// The address is fully elided.
// The first 64 bits of the address are the link-local prefix padded with zeros.
// The remaining 64 bits are computed from the encapsulating header.
Ok(Address::Elided)
}
(0, 1, 0b00) => Ok(Address::Reserved),
(0, 1, 0b01) => {
// The address is derived using context information and the 64 bits carried in-line.
// Bits covered by context information are always used.
// Any IID bits not covered by context information are directly from the corresponding bits carried in-line.
// Any remaining bits are zero.
let data = self.buffer.as_ref();
let bytes = &data[start..start + 8];
Ok(Address::WithContext(bytes))
}
(0, 1, 0b10) => {
// The address is derived using context information and the 16 bits carried in-line.
// Bits covered by context information are always used.
// Any IID bits not covered by context information are directly from the corresponding bits carried in-line.
// Any remaining bits are zero.
let data = self.buffer.as_ref();
let bytes = &data[start..start + 2];
Ok(Address::WithContext(bytes))
}
(0, 1, 0b11) => {
// The address is fully elided and is derived using context information and the encapsulating header.
// Bits covered by context information are always used.
// Any IID bits not covered by context information are always used.
// Any IID bits not covered by context information are directly from the corresponding bits carried in-line.
// Any remaining bits are zero.
Ok(Address::WithContext(&[]))
}
(1, 0, 0b00) => {
// The full address is carried in-line.
let data = self.buffer.as_ref();
Ok(Address::Complete(ipv6::Address::from_bytes(
&data[start..start + 16],
)))
}
(1, 0, 0b01) => {
// The address takes the form ffXX::00XX:XXXX:XXXX
let data = self.buffer.as_ref();
let mut bytes = [0u8; 16];
bytes[0] = 0xff;
bytes[1] = data[start];
bytes[11..].copy_from_slice(&data[start + 1..start + 6]);
Ok(Address::Complete(ipv6::Address::from_bytes(&bytes)))
}
(1, 0, 0b10) => {
// The address takes the form ffXX::00XX:XXXX
let data = self.buffer.as_ref();
let mut bytes = [0u8; 16];
bytes[0] = 0xff;
bytes[1] = data[start];
bytes[13..].copy_from_slice(&data[start + 1..start + 4]);
Ok(Address::Complete(ipv6::Address::from_bytes(&bytes)))
}
(1, 0, 0b11) => {
// The address takes the form ff02::00XX
let data = self.buffer.as_ref();
let mut bytes = [0u8; 16];
bytes[0] = 0xff;
bytes[1] = 0x02;
bytes[15] = data[start];
Ok(Address::Complete(ipv6::Address::from_bytes(&bytes)))
}
(1, 1, 0b00) => {
// This format is designed to match Unicast-Prefix-based IPv6 Multicast Addresses.
// The multicast takes the form ffXX:XXLL:PPPP:PPPP:PPPP:PPPP:XXXX:XXXX.
// X are octets that are carried in-line, in the order in which they appear.
// P are octets used to encode the prefix itself.
// L are octets used to encode the prefix length.
// The prefix information P and L is taken from the specified context.
Err(Error::NotSupported)
}
(1, 1, 0b01 | 0b10 | 0b11) => Ok(Address::Reserved),
_ => Err(Error::Malformed),
}
}
get_field!(dispatch_field, 0b111, 13);
get_field!(tf_field, 0b11, 11);
get_field!(nh_field, 0b1, 10);
get_field!(hlim_field, 0b11, 8);
get_field!(cid_field, 0b1, 7);
get_field!(sac_field, 0b1, 6);
get_field!(sam_field, 0b11, 4);
get_field!(m_field, 0b1, 3);
get_field!(dac_field, 0b1, 2);
get_field!(dam_field, 0b11, 0);
/// Return the start for the IP fields.
fn ip_fields_start(&self) -> u8 {
2 + self.cid_size()
}
/// Get the size in octets of the traffic class field.
fn traffic_class_size(&self) -> u8 {
match self.tf_field() {
0b00 => 4,
0b01 => 3,
0b10 => 1,
0b11 => 0,
_ => unreachable!(),
}
}
/// Get the size in octets of the next header field.
fn next_header_size(&self) -> u8 {
(self.nh_field() != 1) as u8
}
/// Get the size in octets of the hop limit field.
fn hop_limit_size(&self) -> u8 {
(self.hlim_field() == 0b00) as u8
}
/// Get the size in octets of the CID field.
fn cid_size(&self) -> u8 {
(self.cid_field() == 1) as u8
}
/// Get the size in octets of the source address.
fn src_address_size(&self) -> u8 {
match (self.sac_field(), self.sam_field()) {
(0, 0b00) => 16, // The full address is carried in-line.
(0, 0b01) => 8, // The first 64 bits are elided.
(0, 0b10) => 2, // The first 112 bits are elided.
(0, 0b11) => 0, // The address is fully elided.
(1, 0b00) => 0, // The UNSPECIFIED address.
(1, 0b01) => 8, // Address derived using context information.
(1, 0b10) => 2, // Address derived using context information.
(1, 0b11) => 0, // Address derived using context information.
_ => unreachable!(),
}
}
/// Get the size in octets of the address address.
fn dst_address_size(&self) -> u8 {
match (self.m_field(), self.dac_field(), self.dam_field()) {
(0, 0, 0b00) => 16, // The full address is carried in-line.
(0, 0, 0b01) => 8, // The first 64 bits are elided.
(0, 0, 0b10) => 2, // The first 112 bits are elided.
(0, 0, 0b11) => 0, // The address is fully elided.
(0, 1, 0b00) => 0, // Reserved.
(0, 1, 0b01) => 8, // Address derived using context information.
(0, 1, 0b10) => 2, // Address derived using context information.
(0, 1, 0b11) => 0, // Address derived using context information.
(1, 0, 0b00) => 16, // The full address is carried in-line.
(1, 0, 0b01) => 6, // The address takes the form ffXX::00XX:XXXX:XXXX.
(1, 0, 0b10) => 4, // The address takes the form ffXX::00XX:XXXX.
(1, 0, 0b11) => 1, // The address takes the form ff02::00XX.
(1, 1, 0b00) => 6, // Match Unicast-Prefix-based IPv6.
(1, 1, 0b01) => 0, // Reserved.
(1, 1, 0b10) => 0, // Reserved.
(1, 1, 0b11) => 0, // Reserved.
_ => unreachable!(),
}
}
}
impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
/// Return a pointer to the payload.
pub fn payload(&self) -> &'a [u8] {
let mut len = self.ip_fields_start();
len += self.traffic_class_size();
len += self.next_header_size();
len += self.hop_limit_size();
len += self.src_address_size();
len += self.dst_address_size();
let len = len as usize;
let data = self.buffer.as_ref();
&data[len..]
}
}
impl<'a, T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
/// Set the dispatch field to `0b011`.
fn set_dispatch_field(&mut self) {
let data = &mut self.buffer.as_mut()[field::IPHC_FIELD];
let mut raw = NetworkEndian::read_u16(data);
raw = (raw & !(0b111 << 13)) | (0b11 << 13);
NetworkEndian::write_u16(data, raw);
}
set_field!(set_tf_field, 0b11, 11);
set_field!(set_nh_field, 0b1, 10);
set_field!(set_hlim_field, 0b11, 8);
set_field!(set_cid_field, 0b1, 7);
set_field!(set_sac_field, 0b1, 6);
set_field!(set_sam_field, 0b11, 4);
set_field!(set_m_field, 0b1, 3);
set_field!(set_dac_field, 0b1, 2);
set_field!(set_dam_field, 0b11, 0);
fn set_field(&mut self, idx: usize, value: &[u8]) {
let raw = self.buffer.as_mut();
raw[idx..idx + value.len()].copy_from_slice(value);
}
/// Set the Next Header of this IPHC packet.
///
/// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
fn set_next_header(&mut self, nh: NextHeader, mut idx: usize) -> usize {
match nh {
NextHeader::Uncompressed(nh) => {
self.set_nh_field(0);
self.set_field(idx, &[nh.into()]);
idx += 1;
}
NextHeader::Compressed => self.set_nh_field(1),
}
idx
}
/// Set the Hop Limit of this IPHC packet.
///
/// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
fn set_hop_limit(&mut self, hl: u8, mut idx: usize) -> usize {
match hl {
255 => self.set_hlim_field(0b11),
64 => self.set_hlim_field(0b10),
1 => self.set_hlim_field(0b01),
_ => {
self.set_hlim_field(0b00);
self.set_field(idx, &[hl]);
idx += 1;
}
}
idx
}
/// Set the Source Address based on the IPv6 address and the Link-Local address.
///
/// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
fn set_src_address(
&mut self,
src_addr: ipv6::Address,
ll_src_addr: Option<LlAddress>,
mut idx: usize,
) -> usize {
self.set_cid_field(0);
self.set_sac_field(0);
self.set_sam_field(0b11);
let src = src_addr.as_bytes();
if src_addr == ipv6::Address::UNSPECIFIED {
self.set_sac_field(1);
self.set_sam_field(0b00);
} else if src_addr.is_link_local() {
// We have a link local address.
// The remainder of the address can be elided when the context contains
// a 802.15.4 short address or a 802.15.4 extended address which can be
// converted to a eui64 address.
let is_eui_64 = ll_src_addr
.map(|addr| {
addr.as_eui_64()
.map(|addr| addr[..] == src[8..])
.unwrap_or(false)
})
.unwrap_or(false);
if src[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
let ll = [src[14], src[15]];
if ll_src_addr == Some(LlAddress::Short(ll)) {
// We have the context from the 802.15.4 frame.
// The context contains the short address.
// We can elide the source address.
self.set_sam_field(0b11);
} else {
// We don't have the context from the 802.15.4 frame.
// We cannot elide the source address, however we can elide 112 bits.
self.set_sam_field(0b10);
self.set_field(idx, &src[14..]);
idx += 2;
}
} else if is_eui_64 {
// We have the context from the 802.15.4 frame.
// The context contains the extended address.
// We can elide the source address.
self.set_sam_field(0b11);
} else {
// We cannot elide the source address, however we can elide 64 bits.
self.set_sam_field(0b01);
self.set_field(idx, &src[8..]);
idx += 8;
}
} else {
// We cannot elide anything.
self.set_field(idx, src);
idx += 16;
}
idx
}
/// Set the Destination Address based on the IPv6 address and the Link-Local address.
///
/// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
fn set_dst_address(
&mut self,
dst_addr: ipv6::Address,
ll_dst_addr: Option<LlAddress>,
mut idx: usize,
) -> usize {
self.set_dac_field(0);
self.set_dam_field(0);
self.set_m_field(0);
let dst = dst_addr.as_bytes();
if dst_addr.is_multicast() {
self.set_m_field(1);
if dst[1] == 0x02 && dst[2..15] == [0; 13] {
self.set_dam_field(0b11);
self.set_field(idx, &[dst[15]]);
idx += 1;
} else if dst[2..13] == [0; 11] {
self.set_dam_field(0b10);
self.set_field(idx, &[dst[1]]);
idx += 1;
self.set_field(idx, &dst[13..]);
idx += 3;
} else if dst[2..11] == [0; 9] {
self.set_dam_field(0b01);
self.set_field(idx, &[dst[1]]);
idx += 1;
self.set_field(idx, &dst[11..]);
idx += 5;
} else {
self.set_dam_field(0b11);
self.set_field(idx, dst);
idx += 16;
}
} else if dst_addr.is_link_local() {
let is_eui_64 = ll_dst_addr
.map(|addr| {
addr.as_eui_64()
.map(|addr| addr[..] == dst[8..])
.unwrap_or(false)
})
.unwrap_or(false);
if dst[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
let ll = [dst[14], dst[15]];
if ll_dst_addr == Some(LlAddress::Short(ll)) {
self.set_dam_field(0b11);
} else {
self.set_dam_field(0b10);
self.set_field(idx, &dst[14..]);
idx += 2;
}
} else if is_eui_64 {
self.set_dam_field(0b11);
} else {
self.set_dam_field(0b01);
self.set_field(idx, &dst[8..]);
idx += 8;
}
} else {
self.set_dam_field(0b00);
self.set_field(idx, dst);
idx += 16;
}
idx
}
/// Return a mutable pointer to the payload.
pub fn payload_mut(&mut self) -> &mut [u8] {
let mut len = self.ip_fields_start();
len += self.traffic_class_size();
len += self.next_header_size();
len += self.hop_limit_size();
len += self.src_address_size();
len += self.dst_address_size();
let len = len as usize;
let data = self.buffer.as_mut();
&mut data[len..]
}
}
/// A high-level representation of a LOWPAN_IPHC header.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Repr {
pub src_addr: ipv6::Address,
pub ll_src_addr: Option<LlAddress>,
pub dst_addr: ipv6::Address,
pub ll_dst_addr: Option<LlAddress>,
pub next_header: NextHeader,
pub hop_limit: u8,
}
impl Repr {
/// Parse a LOWPAN_IPHC packet and return a high-level representation.
///
/// The `ll_src_addr` and `ll_dst_addr` are the link-local addresses used for resolving the
/// IPv6 packets.
pub fn parse<T: AsRef<[u8]> + ?Sized>(
packet: &Packet<&T>,
ll_src_addr: Option<LlAddress>,
ll_dst_addr: Option<LlAddress>,
) -> Result<Repr> {
// Ensure basic accessors will work.
packet.check_len()?;
if packet.dispatch_field() != DISPATCH {
// This is not an LOWPAN_IPHC packet.
return Err(Error::Malformed);
}
let src_addr = packet.src_addr()?.resolve(ll_src_addr)?;
let dst_addr = packet.dst_addr()?.resolve(ll_dst_addr)?;
Ok(Repr {
src_addr,
ll_src_addr,
dst_addr,
ll_dst_addr,
next_header: packet.next_header(),
hop_limit: packet.hop_limit(),
})
}
/// Return the length of a header that will be emitted from this high-level representation.
pub fn buffer_len(&self) -> usize {
let mut len = 0;
len += 2; // The minimal header length
len += if self.next_header == NextHeader::Compressed {
0 // The next header is compressed (we don't need to inline what the next header is)
} else {
1 // The next header field is inlined
};
// Hop Limit size
len += match self.hop_limit {
255 | 64 | 1 => 0, // We can inline the hop limit
_ => 1,
};
// Add the lenght of the source address
len += if self.src_addr == ipv6::Address::UNSPECIFIED {
0
} else if self.src_addr.is_link_local() {
let src = self.src_addr.as_bytes();
let ll = [src[14], src[15]];
let is_eui_64 = self
.ll_src_addr
.map(|addr| {
addr.as_eui_64()
.map(|addr| addr[..] == src[8..])
.unwrap_or(false)
})
.unwrap_or(false);
if src[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
if self.ll_src_addr == Some(LlAddress::Short(ll)) {
0
} else {
2
}
} else if is_eui_64 {
0
} else {
8
}
} else {
16
};
// Add the size of the destination header
let dst = self.dst_addr.as_bytes();
len += if self.dst_addr.is_multicast() {
if dst[1] == 0x02 && dst[2..15] == [0; 13] {
1
} else if dst[2..13] == [0; 11] {
4
} else if dst[2..11] == [0; 9] {
6
} else {
16
}
} else if self.dst_addr.is_link_local() {
let is_eui_64 = self
.ll_dst_addr
.map(|addr| {
addr.as_eui_64()
.map(|addr| addr[..] == dst[8..])
.unwrap_or(false)
})
.unwrap_or(false);
if dst[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
let ll = [dst[14], dst[15]];
if self.ll_dst_addr == Some(LlAddress::Short(ll)) {
0
} else {
2
}
} else if is_eui_64 {
0
} else {
8
}
} else {
16
};
// Add the size of the traffic flow.
// TODO(thvdveld): implement traffic flow for sixlowpan
len += 0;
len
}
/// Emit a high-level representation into a LOWPAN_IPHC packet.
pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
let idx = 2;
packet.set_dispatch_field();
// SETTING THE TRAFIC FLOW
// TODO(thvdveld): needs more work.
packet.set_tf_field(0b11);
let idx = packet.set_next_header(self.next_header, idx);
let idx = packet.set_hop_limit(self.hop_limit, idx);
let idx = packet.set_src_address(self.src_addr, self.ll_src_addr, idx);
packet.set_dst_address(self.dst_addr, self.ll_dst_addr, idx);
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn iphc_fields() {
let bytes = [
0x7a, 0x33, // IPHC
0x3a, // Next header
];
let packet = Packet::new_unchecked(bytes);
assert_eq!(packet.dispatch_field(), 0b011);
assert_eq!(packet.tf_field(), 0b11);
assert_eq!(packet.nh_field(), 0b0);
assert_eq!(packet.hlim_field(), 0b10);
assert_eq!(packet.cid_field(), 0b0);
assert_eq!(packet.sac_field(), 0b0);
assert_eq!(packet.sam_field(), 0b11);
assert_eq!(packet.m_field(), 0b0);
assert_eq!(packet.dac_field(), 0b0);
assert_eq!(packet.dam_field(), 0b11);
assert_eq!(
packet.next_header(),
NextHeader::Uncompressed(IpProtocol::Icmpv6)
);
assert_eq!(packet.src_address_size(), 0);
assert_eq!(packet.dst_address_size(), 0);
assert_eq!(packet.hop_limit(), 64);
assert_eq!(packet.src_addr(), Ok(Address::Elided));
assert_eq!(packet.dst_addr(), Ok(Address::Elided));
let bytes = [
0x7e, 0xf7, // IPHC,
0x00, // CID
];
let packet = Packet::new_unchecked(bytes);
assert_eq!(packet.dispatch_field(), 0b011);
assert_eq!(packet.tf_field(), 0b11);
assert_eq!(packet.nh_field(), 0b1);
assert_eq!(packet.hlim_field(), 0b10);
assert_eq!(packet.cid_field(), 0b1);
assert_eq!(packet.sac_field(), 0b1);
assert_eq!(packet.sam_field(), 0b11);
assert_eq!(packet.m_field(), 0b0);
assert_eq!(packet.dac_field(), 0b1);
assert_eq!(packet.dam_field(), 0b11);
assert_eq!(packet.next_header(), NextHeader::Compressed);
assert_eq!(packet.src_address_size(), 0);
assert_eq!(packet.dst_address_size(), 0);
assert_eq!(packet.hop_limit(), 64);
assert_eq!(packet.src_addr(), Ok(Address::WithContext(&[])));
assert_eq!(packet.dst_addr(), Ok(Address::WithContext(&[])));
}
}
}
pub mod nhc {
use crate::wire::ip::checksum;
use crate::wire::ip::Address as IpAddress;
use crate::wire::ipv6;
use crate::wire::udp::Repr as UdpRepr;
use crate::wire::IpProtocol;
use crate::Error;
use crate::Result;
use byteorder::{ByteOrder, NetworkEndian};
use ipv6::Address;
use super::NextHeader;
macro_rules! get_field {
($name:ident, $mask:expr, $shift:expr) => {
fn $name(&self) -> u8 {
let data = self.buffer.as_ref();
let raw = &data[0];
((raw >> $shift) & $mask) as u8
}
};
}
macro_rules! set_field {
($name:ident, $mask:expr, $shift:expr) => {
fn $name(&mut self, val: u8) {
let data = self.buffer.as_mut();
let mut raw = data[0];
raw = (raw & !($mask << $shift)) | (val << $shift);
data[0] = raw;
}
};
}
/// A read/write wrapper around a LOWPAN_NHC frame buffer.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Packet<T: AsRef<[u8]>> {
ExtensionHeader(ExtensionHeaderPacket<T>),
UdpHeader(UdpPacket<T>),
}
impl<T: AsRef<[u8]>> Packet<T> {
pub fn dispatch(buffer: T) -> Result<Packet<T>> {
let raw = buffer.as_ref();
#[cfg(feature = "std")]
println!("{:02x?}", raw[0]);
if raw[0] >> 4 == 0b1110 {
// We have a compressed IPv6 Extension Header.
Ok(Packet::ExtensionHeader(ExtensionHeaderPacket::new_checked(
buffer,
)?))
} else if raw[0] >> 3 == 0b11110 {
// We have a compressed UDP header.
Ok(Packet::UdpHeader(UdpPacket::new_checked(buffer)?))
} else {
Err(Error::Unrecognized)
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ExtensionHeaderId {
HopByHopHeader,
RoutingHeader,
FragmentHeader,
DestinationOptionsHeader,
MobilityHeader,
Header,
Reserved,
}
impl From<ExtensionHeaderId> for IpProtocol {
fn from(val: ExtensionHeaderId) -> Self {
match val {
ExtensionHeaderId::HopByHopHeader => IpProtocol::HopByHop,
ExtensionHeaderId::RoutingHeader => IpProtocol::Ipv6Route,
ExtensionHeaderId::FragmentHeader => IpProtocol::Ipv6Frag,
ExtensionHeaderId::DestinationOptionsHeader => IpProtocol::Ipv6Opts,
ExtensionHeaderId::MobilityHeader => IpProtocol::Unknown(0),
ExtensionHeaderId::Header => IpProtocol::Unknown(0),
ExtensionHeaderId::Reserved => IpProtocol::Unknown(0),
}
}
}
pub(crate) const EXT_HEADER_DISPATCH: u8 = 0b1110;
/// A read/write wrapper around a LOWPAN_NHC Next Header frame buffer.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ExtensionHeaderPacket<T: AsRef<[u8]>> {
buffer: T,
}
impl<T: AsRef<[u8]>> ExtensionHeaderPacket<T> {
/// Input a raw octet buffer with a LOWPAN_NHC Extension Header frame structure.
pub fn new_unchecked(buffer: T) -> ExtensionHeaderPacket<T> {
ExtensionHeaderPacket { 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<ExtensionHeaderPacket<T>> {
let packet = Self::new_unchecked(buffer);
packet.check_len()?;
Ok(packet)
}
/// Ensure that no accessor method will panic if called.
/// Returns `Err(Error::Truncated)` if the buffer is too short.
pub fn check_len(&self) -> Result<()> {
let buffer = self.buffer.as_ref();
if buffer.is_empty() {
Err(Error::Truncated)
} else {
Ok(())
}
}
/// Consumes the frame, returning the underlying buffer.
pub fn into_inner(self) -> T {
self.buffer
}
get_field!(dispatch_field, 0b1111, 4);
get_field!(eid_field, 0b111, 1);
get_field!(nh_field, 0b1, 0);
/// Return the Extension Header ID.
pub fn extension_header_id(&self) -> ExtensionHeaderId {
match self.eid_field() {
0 => ExtensionHeaderId::HopByHopHeader,
1 => ExtensionHeaderId::RoutingHeader,
2 => ExtensionHeaderId::FragmentHeader,
3 => ExtensionHeaderId::DestinationOptionsHeader,
4 => ExtensionHeaderId::MobilityHeader,
5 | 6 => ExtensionHeaderId::Reserved,
7 => ExtensionHeaderId::Header,
_ => unreachable!(),
}
}
/// Return the length field.
pub fn length_field(&self) -> u8 {
let start = 1 + self.next_header_size();
let data = self.buffer.as_ref();
data[start]
}
/// Parse the next header field.
pub fn next_header(&self) -> NextHeader {
if self.nh_field() == 1 {
NextHeader::Compressed
} else {
// The full 8 bits for Next Header are carried in-line.
let start = 1;
let data = self.buffer.as_ref();
let nh = data[start];
NextHeader::Uncompressed(IpProtocol::from(nh))
}
}
/// Return the size of the Next Header field.
fn next_header_size(&self) -> usize {
// If nh is set, then the Next Header is compressed using LOWPAN_NHC
if self.nh_field() == 1 {
0
} else {
1
}
}
}
impl<'a, T: AsRef<[u8]> + ?Sized> ExtensionHeaderPacket<&'a T> {
/// Return a pointer to the payload.
pub fn payload(&self) -> &'a [u8] {
let start = 2 + self.next_header_size();
&self.buffer.as_ref()[start..]
}
}
impl<'a, T: AsRef<[u8]> + AsMut<[u8]>> ExtensionHeaderPacket<T> {
/// Return a mutable pointer to the payload.
pub fn payload_mut(&mut self) -> &mut [u8] {
let start = 2 + self.next_header_size();
&mut self.buffer.as_mut()[start..]
}
/// Set the dispatch field to `0b1110`.
fn set_dispatch_field(&mut self) {
let data = self.buffer.as_mut();
data[0] = (data[0] & !(0b1111 << 4)) | (EXT_HEADER_DISPATCH << 4);
}
set_field!(set_eid_field, 0b111, 1);
set_field!(set_nh_field, 0b1, 0);
/// Set the Extension Header ID field.
fn set_extension_header_id(&mut self, ext_header_id: ExtensionHeaderId) {
let id = match ext_header_id {
ExtensionHeaderId::HopByHopHeader => 0,
ExtensionHeaderId::RoutingHeader => 1,
ExtensionHeaderId::FragmentHeader => 2,
ExtensionHeaderId::DestinationOptionsHeader => 3,
ExtensionHeaderId::MobilityHeader => 4,
ExtensionHeaderId::Header => 7,
_ => unreachable!(),
};
self.set_eid_field(id);
}
/// Set the Next Header.
fn set_next_header(&mut self, next_header: NextHeader) {
match next_header {
NextHeader::Compressed => self.set_nh_field(0b1),
NextHeader::Uncompressed(nh) => {
self.set_nh_field(0b0);
let start = 1;
let data = self.buffer.as_mut();
data[start] = nh.into();
}
}
}
/// Set the length.
fn set_length(&mut self, length: u8) {
let start = 1 + self.next_header_size();
let data = self.buffer.as_mut();
data[start] = length;
}
}
/// A high-level representation of an LOWPAN_NHC Extension Header header.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ExtensionHeaderRepr {
ext_header_id: ExtensionHeaderId,
next_header: NextHeader,
length: u8,
}
impl ExtensionHeaderRepr {
/// Parse a LOWPAN_NHC Extension Header packet and return a high-level representation.
pub fn parse<T: AsRef<[u8]> + ?Sized>(
packet: &ExtensionHeaderPacket<&T>,
) -> Result<ExtensionHeaderRepr> {
// Ensure basic accessors will work.
packet.check_len()?;
if packet.dispatch_field() != EXT_HEADER_DISPATCH {
return Err(Error::Malformed);
}
Ok(ExtensionHeaderRepr {
ext_header_id: packet.extension_header_id(),
next_header: packet.next_header(),
length: packet.payload().len() as u8,
})
}
/// Return the length of a header that will be emitted from this high-level representation.
pub fn buffer_len(&self) -> usize {
let mut len = 1; // The minimal header size
if self.next_header != NextHeader::Compressed {
len += 1;
}
len += 1; // The length
len
}
/// Emit a high-level representaiton into a LOWPAN_NHC Extension Header packet.
pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut ExtensionHeaderPacket<T>) {
packet.set_dispatch_field();
packet.set_extension_header_id(self.ext_header_id);
packet.set_next_header(self.next_header);
packet.set_length(self.length);
}
}
pub(crate) const UDP_DISPATCH: u8 = 0b11110;
/// A read/write wrapper around a 6LoWPAN_NHC_UDP frame buffer.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct UdpPacket<T: AsRef<[u8]>> {
buffer: T,
}
impl<T: AsRef<[u8]>> UdpPacket<T> {
/// Input a raw octet buffer with a LOWPAN_NHC frame structure for UDP.
pub fn new_unchecked(buffer: T) -> UdpPacket<T> {
UdpPacket { 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<UdpPacket<T>> {
let packet = Self::new_unchecked(buffer);
packet.check_len()?;
Ok(packet)
}
/// Ensure that no accessor method will panic if called.
/// Returns `Err(Error::Truncated)` if the buffer is too short.
pub fn check_len(&self) -> Result<()> {
let buffer = self.buffer.as_ref();
if buffer.is_empty() {
return Err(Error::Truncated);
}
let index = 1 + self.ports_size() + self.checksum_size();
if index > buffer.len() {
return Err(Error::Truncated);
}
Ok(())
}
/// Consumes the frame, returning the underlying buffer.
pub fn into_inner(self) -> T {
self.buffer
}
get_field!(dispatch_field, 0b11111, 3);
get_field!(checksum_field, 0b1, 2);
get_field!(ports_field, 0b11, 0);
/// Returns the index of the start of the next header compressed fields.
fn nhc_fields_start(&self) -> usize {
1
}
/// Return the source port number.
pub fn src_port(&self) -> u16 {
match self.ports_field() {
0b00 | 0b01 => {
// The full 16 bits are carried in-line.
let data = self.buffer.as_ref();
let start = self.nhc_fields_start();
NetworkEndian::read_u16(&data[start..start + 2])
}
0b10 => {
// The first 8 bits are elided.
let data = self.buffer.as_ref();
let start = self.nhc_fields_start();
0xf000 + data[start] as u16
}
0b11 => {
// The first 12 bits are elided.
let data = self.buffer.as_ref();
let start = self.nhc_fields_start();
0xf0b0 + (data[start] >> 4) as u16
}
_ => unreachable!(),
}
}
/// Return the destination port number.
pub fn dst_port(&self) -> u16 {
match self.ports_field() {
0b00 => {
// The full 16 bits are carried in-line.
let data = self.buffer.as_ref();
let idx = self.nhc_fields_start();
NetworkEndian::read_u16(&data[idx + 2..idx + 4])
}
0b01 => {
// The first 8 bits are elided.
let data = self.buffer.as_ref();
let idx = self.nhc_fields_start();
0xf000 + data[idx] as u16
}
0b10 => {
// The full 16 bits are carried in-line.
let data = self.buffer.as_ref();
let idx = self.nhc_fields_start();
NetworkEndian::read_u16(&data[idx + 1..idx + 1 + 2])
}
0b11 => {
// The first 12 bits are elided.
let data = self.buffer.as_ref();
let start = self.nhc_fields_start();
0xf0b0 + (data[start] & 0xff) as u16
}
_ => unreachable!(),
}
}
/// Return the checksum.
pub fn checksum(&self) -> Option<u16> {
if self.checksum_field() == 0b0 {
// The first 12 bits are elided.
let data = self.buffer.as_ref();
let start = self.nhc_fields_start() + self.ports_size();
Some(NetworkEndian::read_u16(&data[start..start + 2]))
} else {
// The checksum is ellided and needs to be recomputed on the 6LoWPAN termination point.
None
}
}
// Return the size of the checksum field.
fn checksum_size(&self) -> usize {
match self.checksum_field() {
0b0 => 2,
0b1 => 0,
_ => unreachable!(),
}
}
/// Returns the total size of both port numbers.
fn ports_size(&self) -> usize {
match self.ports_field() {
0b00 => 4, // 16 bits + 16 bits
0b01 => 3, // 16 bits + 8 bits
0b10 => 3, // 8 bits + 16 bits
0b11 => 1, // 4 bits + 4 bits
_ => unreachable!(),
}
}
}
impl<'a, T: AsRef<[u8]> + ?Sized> UdpPacket<&'a T> {
/// Return a pointer to the payload.
pub fn payload(&self) -> &'a [u8] {
let start = 1 + self.ports_size() + self.checksum_size();
&self.buffer.as_ref()[start..]
}
}
impl<'a, T: AsRef<[u8]> + AsMut<[u8]>> UdpPacket<T> {
/// Return a mutable pointer to the payload.
pub fn payload_mut(&mut self) -> &mut [u8] {
let start = 1 + self.ports_size() + 2; // XXX(thvdveld): we assume we put the checksum inlined.
&mut self.buffer.as_mut()[start..]
}
/// Set the dispatch field to `0b11110`.
fn set_dispatch_field(&mut self) {
let data = self.buffer.as_mut();
data[0] = (data[0] & !(0b11111 << 3)) | (UDP_DISPATCH << 3);
}
set_field!(set_checksum_field, 0b1, 2);
set_field!(set_ports_field, 0b11, 0);
fn set_ports(&mut self, src_port: u16, dst_port: u16) {
let mut idx = 1;
match (src_port, dst_port) {
(0xf0b0..=0xf0bf, 0xf0b0..=0xf0bf) => {
// We can compress both the source and destination ports.
self.set_ports_field(0b11);
let data = self.buffer.as_mut();
data[idx] = (((src_port - 0xf0b0) as u8) << 4) & ((dst_port - 0xf0b0) as u8);
}
(0xf000..=0xf0ff, _) => {
// We can compress the source port, but not the destination port.
self.set_ports_field(0b10);
let data = self.buffer.as_mut();
data[idx] = (src_port - 0xf000) as u8;
idx += 1;
NetworkEndian::write_u16(&mut data[idx..idx + 2], dst_port);
}
(_, 0xf000..=0xf0ff) => {
// We can compress the destination port, but not the source port.
self.set_ports_field(0b01);
let data = self.buffer.as_mut();
NetworkEndian::write_u16(&mut data[idx..idx + 2], src_port);
idx += 2;
data[idx] = (dst_port - 0xf000) as u8;
}
(_, _) => {
// We cannot compress any port.
self.set_ports_field(0b00);
let data = self.buffer.as_mut();
NetworkEndian::write_u16(&mut data[idx..idx + 2], src_port);
idx += 2;
NetworkEndian::write_u16(&mut data[idx..idx + 2], dst_port);
}
};
}
fn set_checksum(&mut self, checksum: u16) {
self.set_checksum_field(0b0);
let idx = 1 + self.ports_size();
let data = self.buffer.as_mut();
NetworkEndian::write_u16(&mut data[idx..idx + 2], checksum);
}
}
/// A high-level representation of a LOWPAN_NHC UDP header.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct UdpNhcRepr(pub UdpRepr);
impl<'a> UdpNhcRepr {
/// Parse a LOWWPAN_NHC UDP packet and return a high-level representation.
pub fn parse<T: AsRef<[u8]> + ?Sized>(
packet: &UdpPacket<&'a T>,
src_addr: &ipv6::Address,
dst_addr: &ipv6::Address,
_checksum: Option<u16>,
) -> Result<UdpNhcRepr> {
// Ensure basic accessors will work.
packet.check_len()?;
if packet.dispatch_field() != UDP_DISPATCH {
return Err(Error::Malformed);
}
let payload_len = packet.payload().len();
let chk_sum = !checksum::combine(&[
checksum::pseudo_header(
&IpAddress::Ipv6(*src_addr),
&IpAddress::Ipv6(*dst_addr),
crate::wire::ip::Protocol::Udp,
payload_len as u32 + 8,
),
packet.src_port(),
packet.dst_port(),
payload_len as u16 + 8,
checksum::data(packet.payload()),
]);
if let Some(checksum) = packet.checksum() {
if chk_sum != checksum {
return Err(Error::Checksum);
}
} else {
net_trace!("Currently we do not support ellided checksums.");
return Err(Error::Unrecognized);
};
Ok(UdpNhcRepr(UdpRepr {
src_port: packet.src_port(),
dst_port: packet.dst_port(),
}))
}
/// Return the length of a packet that will be emitted from this high-level representation.
pub fn header_len(&self) -> usize {
let mut len = 1; // The minimal header size
len += 2; // XXX We assume we will add the checksum at the end
// Check if we can compress the source and destination ports
match (self.src_port, self.dst_port) {
(0xf0b0..=0xf0bf, 0xf0b0..=0xf0bf) => len + 1,
(0xf000..=0xf0ff, _) | (_, 0xf000..=0xf0ff) => len + 3,
(_, _) => len + 4,
}
}
/// Emit a high-level representation into a LOWPAN_NHC UDP header.
pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(
&self,
packet: &mut UdpPacket<T>,
src_addr: &Address,
dst_addr: &Address,
payload_len: usize,
emit_payload: impl FnOnce(&mut [u8]),
) {
packet.set_dispatch_field();
packet.set_ports(self.src_port, self.dst_port);
emit_payload(packet.payload_mut());
let chk_sum = !checksum::combine(&[
checksum::pseudo_header(
&IpAddress::Ipv6(*src_addr),
&IpAddress::Ipv6(*dst_addr),
crate::wire::ip::Protocol::Udp,
payload_len as u32 + 8,
),
self.src_port,
self.dst_port,
payload_len as u16 + 8,
checksum::data(packet.payload_mut()),
]);
packet.set_checksum(chk_sum);
}
}
impl core::ops::Deref for UdpNhcRepr {
type Target = UdpRepr;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl core::ops::DerefMut for UdpNhcRepr {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ext_header_nhc_fields() {
let bytes = [0xe3, 0x06, 0x03, 0x00, 0xff, 0x00, 0x00, 0x00];
let packet = ExtensionHeaderPacket::new_checked(&bytes[..]).unwrap();
assert_eq!(packet.dispatch_field(), EXT_HEADER_DISPATCH);
assert_eq!(packet.length_field(), 6);
assert_eq!(
packet.extension_header_id(),
ExtensionHeaderId::RoutingHeader
);
assert_eq!(packet.payload(), [0x03, 0x00, 0xff, 0x00, 0x00, 0x00]);
}
#[test]
fn ext_header_emit() {
let ext_header = ExtensionHeaderRepr {
ext_header_id: ExtensionHeaderId::RoutingHeader,
next_header: NextHeader::Compressed,
length: 6,
};
let len = ext_header.buffer_len();
let mut buffer = [0u8; 127];
let mut packet = ExtensionHeaderPacket::new_unchecked(&mut buffer[..len]);
ext_header.emit(&mut packet);
assert_eq!(packet.dispatch_field(), EXT_HEADER_DISPATCH);
assert_eq!(packet.next_header(), NextHeader::Compressed);
assert_eq!(packet.length_field(), 6);
assert_eq!(
packet.extension_header_id(),
ExtensionHeaderId::RoutingHeader
);
}
#[test]
fn udp_nhc_fields() {
let bytes = [0xf0, 0x16, 0x2e, 0x22, 0x3d, 0x28, 0xc4];
let packet = UdpPacket::new_checked(&bytes[..]).unwrap();
assert_eq!(packet.dispatch_field(), UDP_DISPATCH);
assert_eq!(packet.checksum(), Some(0x28c4));
assert_eq!(packet.src_port(), 5678);
assert_eq!(packet.dst_port(), 8765);
}
#[test]
fn udp_emit() {
let udp = UdpNhcRepr(UdpRepr {
src_port: 0xf0b1,
dst_port: 0xf001,
});
let payload = b"Hello World!";
let src_addr = ipv6::Address::default();
let dst_addr = ipv6::Address::default();
let len = udp.header_len() + payload.len();
let mut buffer = [0u8; 127];
let mut packet = UdpPacket::new_unchecked(&mut buffer[..len]);
udp.emit(&mut packet, &src_addr, &dst_addr, payload.len(), |buf| {
buf.copy_from_slice(&payload[..])
});
assert_eq!(packet.dispatch_field(), UDP_DISPATCH);
assert_eq!(packet.src_port(), 0xf0b1);
assert_eq!(packet.dst_port(), 0xf001);
assert_eq!(packet.payload_mut(), b"Hello World!");
}
}
}
#[cfg(test)]
mod test {
//use super::*;
//#[test]
//fn ieee802154_udp() {
//use crate::wire::ieee802154::Frame as Ieee802154Frame;
//use crate::wire::ieee802154::Repr as Ieee802154Repr;
//use crate::wire::ipv6routing;
//// This data is captured using Wireshark from the communication between a RPL 6LoWPAN server
//// and a RPL 6LoWPAN client.
//// The frame is thus an IEEE802.15.4 frame, containing a 6LoWPAN packet,
//// containing a RPL extension header and an UDP header.
//let bytes: &[u8] = &[
//0x61, 0xdc, 0xdd, 0xcd, 0xab, 0xc7, 0xd9, 0xb5, 0x14, 0x00, 0x4b, 0x12, 0x00, 0xbf,
//0x9b, 0x15, 0x06, 0x00, 0x4b, 0x12, 0x00, 0x7e, 0xf7, 0x00, 0xe3, 0x06, 0x03, 0x00,
//0xff, 0x00, 0x00, 0x00, 0xf0, 0x16, 0x2e, 0x22, 0x3d, 0x28, 0xc4, 0x68, 0x65, 0x6c,
//0x6c, 0x6f, 0x20, 0x36, 0x35, 0x18, 0xb9,
//];
//let ieee802154_frame = Ieee802154Frame::new_checked(bytes).unwrap();
//let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap();
//let iphc_frame = iphc::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap();
//let iphc_repr = iphc::Repr::parse(
//&iphc_frame,
//ieee802154_repr.src_addr,
//ieee802154_repr.dst_addr,
//)
//.unwrap();
//// The next header is compressed.
//assert_eq!(iphc_repr.next_header, NextHeader::Compressed);
//// We dispatch the NHC packet.
//let nhc_packet = nhc::Packet::dispatch(iphc_frame.payload()).unwrap();
//let udp_payload = match nhc_packet {
//nhc::Packet::ExtensionHeader(ext_packet) => {
//// The next header is compressed (it is the UDP NHC compressed header).
//assert_eq!(ext_packet.next_header(), NextHeader::Compressed);
//assert_eq!(ext_packet.length_field(), 6);
//let payload = ext_packet.payload();
//let length = ext_packet.length_field() as usize;
//let ext_packet_payload = &payload[..length];
//match ext_packet.extension_header_id() {
//nhc::ExtensionHeaderId::RoutingHeader => {
//// We are not intersted in the Next Header protocol.
//let proto = ipv6::Protocol::Unknown(0);
//let mut new_payload = [0; 8];
//new_payload[0] = proto.into();
//new_payload[1] = (2 + length - 8) as u8;
//new_payload[2..].copy_from_slice(ext_packet_payload);
//let routing = ipv6routing::Header::new_checked(new_payload).unwrap();
//assert_eq!(routing.routing_type(), ipv6routing::Type::Rpl);
//assert_eq!(routing.segments_left(), 0);
//assert_eq!(routing.cmpr_e(), 0xf);
//assert_eq!(routing.cmpr_i(), 0xf);
//}
//_ => unreachable!(),
//}
//&payload[length..]
//}
//_ => unreachable!(),
//};
//let udp_nhc_frame = nhc::UdpPacket::new_checked(udp_payload).unwrap();
//let udp_repr = nhc::UdpNhcRepr::parse(
//&udp_nhc_frame,
//&iphc_repr.src_addr,
//&iphc_repr.dst_addr,
//None,
//)
//.unwrap();
//assert_eq!(udp_repr.src_port, 5678);
//assert_eq!(udp_repr.dst_port, 8765);
//assert_eq!(udp_nhc_frame.checksum(), Some(0x28c4));
//}
}