Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion etherparse/src/transport/icmpv4_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ mod test {
fn read(
non_timestamp_type in any::<u8>().prop_filter(
"type must be a non timestamp type",
|v| (*v != icmpv4::TYPE_TIMESTAMP_REPLY && *v != icmpv4::TYPE_TIMESTAMP)
|v| *v != icmpv4::TYPE_TIMESTAMP_REPLY && *v != icmpv4::TYPE_TIMESTAMP
),
non_zero_code in 1u8..=u8::MAX,
bytes in any::<[u8;icmpv4::TimestampMessage::LEN]>()
Expand Down
122 changes: 122 additions & 0 deletions etherparse/src/transport/icmpv6/icmpv6_payload/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
mod neighbor_advertisement_payload;
pub use neighbor_advertisement_payload::*;

mod neighbor_solicitation_payload;
pub use neighbor_solicitation_payload::*;

mod redirect_payload;
pub use redirect_payload::*;

mod router_advertisement_payload;
pub use router_advertisement_payload::*;

mod router_solicitation_payload;
pub use router_solicitation_payload::*;

/// Owned, structured payload data that follows the first 8 bytes of an ICMPv6 packet.
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Icmpv6Payload {
/// Payload of a Router Solicitation message.
RouterSolicitation(RouterSolicitationPayload),
/// Payload of a Router Advertisement message.
RouterAdvertisement(RouterAdvertisementPayload),
/// Payload of a Neighbor Solicitation message.
NeighborSolicitation(NeighborSolicitationPayload),
/// Payload of a Neighbor Advertisement message.
NeighborAdvertisement(NeighborAdvertisementPayload),
/// Payload of a Redirect message.
Redirect(RedirectPayload),
}

impl Icmpv6Payload {
/// Returns the serialized payload length in bytes.
pub fn len(&self) -> usize {
use Icmpv6Payload::*;
match self {
RouterSolicitation(_) => RouterSolicitationPayload::LEN,
RouterAdvertisement(_) => RouterAdvertisementPayload::LEN,
NeighborSolicitation(_) => NeighborSolicitationPayload::LEN,
NeighborAdvertisement(_) => NeighborAdvertisementPayload::LEN,
Redirect(_) => RedirectPayload::LEN,
}
}

/// Write the fixed payload bytes to the writer.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn write<T: std::io::Write + Sized>(
&self,
writer: &mut T,
) -> Result<(), std::io::Error> {
match self {
Icmpv6Payload::RouterSolicitation(value) => writer.write_all(&value.to_bytes()),
Icmpv6Payload::RouterAdvertisement(value) => writer.write_all(&value.to_bytes()),
Icmpv6Payload::NeighborSolicitation(value) => writer.write_all(&value.to_bytes()),
Icmpv6Payload::NeighborAdvertisement(value) => writer.write_all(&value.to_bytes()),
Icmpv6Payload::Redirect(value) => writer.write_all(&value.to_bytes()),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use alloc::format;

Check warning on line 65 in etherparse/src/transport/icmpv6/icmpv6_payload/mod.rs

View workflow job for this annotation

GitHub Actions / codecoverage_test

unused import: `alloc::format`
use proptest::prelude::*;

#[test]
fn router_solicitation_payload_to_bytes() {
assert_eq!([] as [u8; 0], RouterSolicitationPayload.to_bytes());
}

proptest! {
#[test]
fn payloads_to_bytes(
reachable_time in any::<u32>(),
retrans_timer in any::<u32>(),
target_address in any::<[u8;16]>(),
destination_address in any::<[u8;16]>()
) {
let reachable_time_be = reachable_time.to_be_bytes();
let retrans_timer_be = retrans_timer.to_be_bytes();
let mut expected_router_advertisement = [0u8; RouterAdvertisementPayload::LEN];
expected_router_advertisement[..4].copy_from_slice(&reachable_time_be);
expected_router_advertisement[4..].copy_from_slice(&retrans_timer_be);

let mut expected_redirect = [0u8; RedirectPayload::LEN];
expected_redirect[..16].copy_from_slice(&target_address);
expected_redirect[16..].copy_from_slice(&destination_address);

assert_eq!(
RouterAdvertisementPayload {
reachable_time,
retrans_timer,
}.to_bytes(),
expected_router_advertisement
);
assert_eq!(
NeighborSolicitationPayload {
target_address: core::net::Ipv6Addr::from(target_address),
}
.to_bytes(),
target_address
);
assert_eq!(
NeighborAdvertisementPayload {
target_address: core::net::Ipv6Addr::from(target_address),
}
.to_bytes(),
target_address
);
assert_eq!(
RedirectPayload {
target_address: core::net::Ipv6Addr::from(target_address),
destination_address: core::net::Ipv6Addr::from(destination_address),
}
.to_bytes(),
expected_redirect
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use core::net::Ipv6Addr;

/// Owned payload of a Neighbor Advertisement message (RFC 4861, Section 4.4).
///
/// The full packet layout is:
/// ```text
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Type | Code | Checksum |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |R|S|O| Reserved |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | |
/// + +
/// | |
/// + Target Address +
/// | |
/// + +
/// | |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Options ...
/// +-+-+-+-+-+-+-+-+-+-+-+-
/// ```
///
/// In this crate, `R`, `S`, and `O` are represented by
/// [`crate::icmpv6::NeighborAdvertisementHeader`] in
/// [`crate::Icmpv6Type::NeighborAdvertisement`]. This payload struct represents
/// the fixed `Target Address` bytes.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct NeighborAdvertisementPayload {
/// Target IPv6 address.
pub target_address: Ipv6Addr,
}

impl NeighborAdvertisementPayload {
/// Fixed payload length in bytes after the ICMPv6 header.
pub const LEN: usize = 16;

/// Convert to on-the-wire bytes.
pub const fn to_bytes(&self) -> [u8; Self::LEN] {
self.target_address.octets()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use core::net::Ipv6Addr;

/// Owned payload of a Neighbor Solicitation message (RFC 4861, Section 4.3).
///
/// The full packet layout is:
/// ```text
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Type | Code | Checksum |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Reserved |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | |
/// + +
/// | |
/// + Target Address +
/// | |
/// + +
/// | |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Options ...
/// +-+-+-+-+-+-+-+-+-+-+-+-
/// ```
///
/// In this crate, the first 8 bytes (including `Reserved`) are represented by
/// [`crate::Icmpv6Type::NeighborSolicitation`]. This payload struct represents
/// the fixed `Target Address` bytes.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct NeighborSolicitationPayload {
/// Target IPv6 address.
pub target_address: Ipv6Addr,
}

impl NeighborSolicitationPayload {
/// Fixed payload length in bytes after the ICMPv6 header.
pub const LEN: usize = 16;

/// Convert to on-the-wire bytes.
pub const fn to_bytes(&self) -> [u8; Self::LEN] {
self.target_address.octets()
}
}
59 changes: 59 additions & 0 deletions etherparse/src/transport/icmpv6/icmpv6_payload/redirect_payload.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use core::net::Ipv6Addr;

/// Owned payload of a Redirect message (RFC 4861, Section 4.5).
///
/// The full packet layout is:
/// ```text
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Type | Code | Checksum |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Reserved |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | |
/// + +
/// | |
/// + Target Address +
/// | |
/// + +
/// | |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | |
/// + +
/// | |
/// + Destination Address +
/// | |
/// + +
/// | |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Options ...
/// +-+-+-+-+-+-+-+-+-+-+-+-
/// ```
///
/// In this crate, the first 8 bytes (including `Reserved`) are represented by
/// [`crate::Icmpv6Type::Redirect`]. This payload struct represents the fixed bytes:
/// - `Target Address`
/// - `Destination Address`
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct RedirectPayload {
/// Better first-hop target address.
pub target_address: Ipv6Addr,
/// Destination address being redirected.
pub destination_address: Ipv6Addr,
}

impl RedirectPayload {
/// Fixed payload length in bytes after the ICMPv6 header.
pub const LEN: usize = 32;

/// Convert to on-the-wire bytes.
pub const fn to_bytes(&self) -> [u8; Self::LEN] {
let mut out = [0u8; Self::LEN];
// Safety: unwraps are safe because Self::LEN == 32, which is larger than the number of
// octets in IPv6 address
*out.first_chunk_mut().unwrap() = self.target_address.octets();
*out.last_chunk_mut().unwrap() = self.destination_address.octets();
out
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// Owned payload of a Router Advertisement message (RFC 4861, Section 4.2).
///
/// The full packet layout is:
/// ```text
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Type | Code | Checksum |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Cur Hop Limit |M|O| Reserved | Router Lifetime |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Reachable Time |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Retrans Timer |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Options ...
/// +-+-+-+-+-+-+-+-+-+-+-+-
/// ```
///
/// In this crate, `Cur Hop Limit`, `M`, `O`, and `Router Lifetime` are represented by
/// [`crate::icmpv6::RouterAdvertisementHeader`] in [`crate::Icmpv6Type::RouterAdvertisement`].
/// This payload struct represents the fixed bytes after that:
/// - `Reachable Time`
/// - `Retrans Timer`
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct RouterAdvertisementPayload {
/// Reachable time in milliseconds.
pub reachable_time: u32,
/// Retransmit timer in milliseconds.
pub retrans_timer: u32,
}

impl RouterAdvertisementPayload {
/// Fixed payload length in bytes after the ICMPv6 header.
pub const LEN: usize = 8;

/// Convert to on-the-wire bytes.
pub fn to_bytes(&self) -> [u8; Self::LEN] {
let mut out = [0u8; Self::LEN];

// Safety: unwraps are safe because Self::LEN == 8, which is larger than the number of
// octets in u32
*out.first_chunk_mut().unwrap() = self.reachable_time.to_be_bytes();
*out.last_chunk_mut().unwrap() = self.retrans_timer.to_be_bytes();

out
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// Owned payload of a Router Solicitation message (RFC 4861, Section 4.1).
///
/// The full packet layout is:
/// ```text
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Type | Code | Checksum |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Reserved |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Options ...
/// +-+-+-+-+-+-+-+-+-+-+-+-
/// ```
///
/// In this crate, the first 8 bytes (including `Reserved`) are represented by
/// [`crate::Icmpv6Type::RouterSolicitation`], so this payload struct has no fixed bytes.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct RouterSolicitationPayload;

impl RouterSolicitationPayload {
/// Fixed payload length in bytes after the ICMPv6 header.
pub const LEN: usize = 0;

/// Convert to on-the-wire bytes.
pub const fn to_bytes(&self) -> [u8; Self::LEN] {
[]
}
}
Loading
Loading