610 lines
20 KiB
Rust
610 lines
20 KiB
Rust
use core::{cmp, fmt};
|
|
use byteorder::{ByteOrder, NetworkEndian};
|
|
|
|
use {Error, Result};
|
|
use phy::ChecksumCapabilities;
|
|
use super::ip::checksum;
|
|
use super::{Ipv4Packet, Ipv4Repr};
|
|
|
|
enum_with_unknown! {
|
|
/// Internet protocol control message type.
|
|
pub doc enum Message(u8) {
|
|
/// Echo reply
|
|
EchoReply = 0,
|
|
/// Destination unreachable
|
|
DstUnreachable = 3,
|
|
/// Message redirect
|
|
Redirect = 5,
|
|
/// Echo request
|
|
EchoRequest = 8,
|
|
/// Router advertisement
|
|
RouterAdvert = 9,
|
|
/// Router solicitation
|
|
RouterSolicit = 10,
|
|
/// Time exceeded
|
|
TimeExceeded = 11,
|
|
/// Parameter problem
|
|
ParamProblem = 12,
|
|
/// Timestamp
|
|
Timestamp = 13,
|
|
/// Timestamp reply
|
|
TimestampReply = 14
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Message {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
&Message::EchoReply => write!(f, "echo reply"),
|
|
&Message::DstUnreachable => write!(f, "destination unreachable"),
|
|
&Message::Redirect => write!(f, "message redirect"),
|
|
&Message::EchoRequest => write!(f, "echo request"),
|
|
&Message::RouterAdvert => write!(f, "router advertisement"),
|
|
&Message::RouterSolicit => write!(f, "router solicitation"),
|
|
&Message::TimeExceeded => write!(f, "time exceeded"),
|
|
&Message::ParamProblem => write!(f, "parameter problem"),
|
|
&Message::Timestamp => write!(f, "timestamp"),
|
|
&Message::TimestampReply => write!(f, "timestamp reply"),
|
|
&Message::Unknown(id) => write!(f, "{}", id)
|
|
}
|
|
}
|
|
}
|
|
|
|
enum_with_unknown! {
|
|
/// Internet protocol control message subtype for type "Destination Unreachable".
|
|
pub doc enum DstUnreachable(u8) {
|
|
/// Destination network unreachable
|
|
NetUnreachable = 0,
|
|
/// Destination host unreachable
|
|
HostUnreachable = 1,
|
|
/// Destination protocol unreachable
|
|
ProtoUnreachable = 2,
|
|
/// Destination port unreachable
|
|
PortUnreachable = 3,
|
|
/// Fragmentation required, and DF flag set
|
|
FragRequired = 4,
|
|
/// Source route failed
|
|
SrcRouteFailed = 5,
|
|
/// Destination network unknown
|
|
DstNetUnknown = 6,
|
|
/// Destination host unknown
|
|
DstHostUnknown = 7,
|
|
/// Source host isolated
|
|
SrcHostIsolated = 8,
|
|
/// Network administratively prohibited
|
|
NetProhibited = 9,
|
|
/// Host administratively prohibited
|
|
HostProhibited = 10,
|
|
/// Network unreachable for ToS
|
|
NetUnreachToS = 11,
|
|
/// Host unreachable for ToS
|
|
HostUnreachToS = 12,
|
|
/// Communication administratively prohibited
|
|
CommProhibited = 13,
|
|
/// Host precedence violation
|
|
HostPrecedViol = 14,
|
|
/// Precedence cutoff in effect
|
|
PrecedCutoff = 15
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for DstUnreachable {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
&DstUnreachable::NetUnreachable =>
|
|
write!(f, "destination network unreachable"),
|
|
&DstUnreachable::HostUnreachable =>
|
|
write!(f, "destination host unreachable"),
|
|
&DstUnreachable::ProtoUnreachable =>
|
|
write!(f, "destination protocol unreachable"),
|
|
&DstUnreachable::PortUnreachable =>
|
|
write!(f, "destination port unreachable"),
|
|
&DstUnreachable::FragRequired =>
|
|
write!(f, "fragmentation required, and DF flag set"),
|
|
&DstUnreachable::SrcRouteFailed =>
|
|
write!(f, "source route failed"),
|
|
&DstUnreachable::DstNetUnknown =>
|
|
write!(f, "destination network unknown"),
|
|
&DstUnreachable::DstHostUnknown =>
|
|
write!(f, "destination host unknown"),
|
|
&DstUnreachable::SrcHostIsolated =>
|
|
write!(f, "source host isolated"),
|
|
&DstUnreachable::NetProhibited =>
|
|
write!(f, "network administratively prohibited"),
|
|
&DstUnreachable::HostProhibited =>
|
|
write!(f, "host administratively prohibited"),
|
|
&DstUnreachable::NetUnreachToS =>
|
|
write!(f, "network unreachable for ToS"),
|
|
&DstUnreachable::HostUnreachToS =>
|
|
write!(f, "host unreachable for ToS"),
|
|
&DstUnreachable::CommProhibited =>
|
|
write!(f, "communication administratively prohibited"),
|
|
&DstUnreachable::HostPrecedViol =>
|
|
write!(f, "host precedence violation"),
|
|
&DstUnreachable::PrecedCutoff =>
|
|
write!(f, "precedence cutoff in effect"),
|
|
&DstUnreachable::Unknown(id) =>
|
|
write!(f, "{}", id)
|
|
}
|
|
}
|
|
}
|
|
|
|
enum_with_unknown! {
|
|
/// Internet protocol control message subtype for type "Redirect Message".
|
|
pub doc enum Redirect(u8) {
|
|
/// Redirect Datagram for the Network
|
|
Net = 0,
|
|
/// Redirect Datagram for the Host
|
|
Host = 1,
|
|
/// Redirect Datagram for the ToS & network
|
|
NetToS = 2,
|
|
/// Redirect Datagram for the ToS & host
|
|
HostToS = 3
|
|
}
|
|
}
|
|
|
|
enum_with_unknown! {
|
|
/// Internet protocol control message subtype for type "Time Exceeded".
|
|
pub doc enum TimeExceeded(u8) {
|
|
/// TTL expired in transit
|
|
TtlExpired = 0,
|
|
/// Fragment reassembly time exceeded
|
|
FragExpired = 1
|
|
}
|
|
}
|
|
|
|
enum_with_unknown! {
|
|
/// Internet protocol control message subtype for type "Parameter Problem".
|
|
pub doc enum ParamProblem(u8) {
|
|
/// Pointer indicates the error
|
|
AtPointer = 0,
|
|
/// Missing a required option
|
|
MissingOption = 1,
|
|
/// Bad length
|
|
BadLength = 2
|
|
}
|
|
}
|
|
|
|
/// A read/write wrapper around an Internet Control Message Protocol version 4 packet buffer.
|
|
#[derive(Debug)]
|
|
pub struct Packet<T: AsRef<[u8]>> {
|
|
buffer: T
|
|
}
|
|
|
|
mod field {
|
|
use wire::field::*;
|
|
|
|
pub const TYPE: usize = 0;
|
|
pub const CODE: usize = 1;
|
|
pub const CHECKSUM: Field = 2..4;
|
|
|
|
pub const UNUSED: Field = 4..8;
|
|
|
|
pub const ECHO_IDENT: Field = 4..6;
|
|
pub const ECHO_SEQNO: Field = 6..8;
|
|
}
|
|
|
|
impl<T: AsRef<[u8]>> Packet<T> {
|
|
/// Imbue a raw octet buffer with ICMPv4 packet structure.
|
|
pub fn new(buffer: T) -> Packet<T> {
|
|
Packet { buffer }
|
|
}
|
|
|
|
/// Shorthand for a combination of [new] and [check_len].
|
|
///
|
|
/// [new]: #method.new
|
|
/// [check_len]: #method.check_len
|
|
pub fn new_checked(buffer: T) -> Result<Packet<T>> {
|
|
let packet = Self::new(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.
|
|
///
|
|
/// 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::CHECKSUM.end {
|
|
Err(Error::Truncated)
|
|
} else {
|
|
if len < self.header_len() as usize {
|
|
Err(Error::Truncated)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Consume the packet, returning the underlying buffer.
|
|
pub fn into_inner(self) -> T {
|
|
self.buffer
|
|
}
|
|
|
|
/// Return the message type field.
|
|
#[inline]
|
|
pub fn msg_type(&self) -> Message {
|
|
let data = self.buffer.as_ref();
|
|
Message::from(data[field::TYPE])
|
|
}
|
|
|
|
/// Return the message code field.
|
|
#[inline]
|
|
pub fn msg_code(&self) -> u8 {
|
|
let data = self.buffer.as_ref();
|
|
data[field::CODE]
|
|
}
|
|
|
|
/// Return the checksum field.
|
|
#[inline]
|
|
pub fn checksum(&self) -> u16 {
|
|
let data = self.buffer.as_ref();
|
|
NetworkEndian::read_u16(&data[field::CHECKSUM])
|
|
}
|
|
|
|
/// Return the identifier field (for echo request and reply packets).
|
|
///
|
|
/// # Panics
|
|
/// This function may panic if this packet is not an echo request or reply packet.
|
|
#[inline]
|
|
pub fn echo_ident(&self) -> u16 {
|
|
let data = self.buffer.as_ref();
|
|
NetworkEndian::read_u16(&data[field::ECHO_IDENT])
|
|
}
|
|
|
|
/// Return the sequence number field (for echo request and reply packets).
|
|
///
|
|
/// # Panics
|
|
/// This function may panic if this packet is not an echo request or reply packet.
|
|
#[inline]
|
|
pub fn echo_seq_no(&self) -> u16 {
|
|
let data = self.buffer.as_ref();
|
|
NetworkEndian::read_u16(&data[field::ECHO_SEQNO])
|
|
}
|
|
|
|
/// Return the header length.
|
|
/// The result depends on the value of the message type field.
|
|
pub fn header_len(&self) -> usize {
|
|
match self.msg_type() {
|
|
Message::EchoRequest => field::ECHO_SEQNO.end,
|
|
Message::EchoReply => field::ECHO_SEQNO.end,
|
|
Message::DstUnreachable => field::UNUSED.end,
|
|
_ => field::CHECKSUM.end // make a conservative assumption
|
|
}
|
|
}
|
|
|
|
/// Validate the header checksum.
|
|
///
|
|
/// # Fuzzing
|
|
/// This function always returns `true` when fuzzing.
|
|
pub fn verify_checksum(&self) -> bool {
|
|
if cfg!(fuzzing) { return true }
|
|
|
|
let data = self.buffer.as_ref();
|
|
checksum::data(data) == !0
|
|
}
|
|
}
|
|
|
|
impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
|
|
/// Return a pointer to the type-specific data.
|
|
#[inline]
|
|
pub fn data(&self) -> &'a [u8] {
|
|
let data = self.buffer.as_ref();
|
|
&data[self.header_len()..]
|
|
}
|
|
}
|
|
|
|
impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|
/// Set the message type field.
|
|
#[inline]
|
|
pub fn set_msg_type(&mut self, value: Message) {
|
|
let data = self.buffer.as_mut();
|
|
data[field::TYPE] = value.into()
|
|
}
|
|
|
|
/// Set the message code field.
|
|
#[inline]
|
|
pub fn set_msg_code(&mut self, value: u8) {
|
|
let data = self.buffer.as_mut();
|
|
data[field::CODE] = value
|
|
}
|
|
|
|
/// Set the checksum field.
|
|
#[inline]
|
|
pub fn set_checksum(&mut self, value: u16) {
|
|
let data = self.buffer.as_mut();
|
|
NetworkEndian::write_u16(&mut data[field::CHECKSUM], value)
|
|
}
|
|
|
|
/// Set the identifier field (for echo request and reply packets).
|
|
///
|
|
/// # Panics
|
|
/// This function may panic if this packet is not an echo request or reply packet.
|
|
#[inline]
|
|
pub fn set_echo_ident(&mut self, value: u16) {
|
|
let data = self.buffer.as_mut();
|
|
NetworkEndian::write_u16(&mut data[field::ECHO_IDENT], value)
|
|
}
|
|
|
|
/// Set the sequence number field (for echo request and reply packets).
|
|
///
|
|
/// # Panics
|
|
/// This function may panic if this packet is not an echo request or reply packet.
|
|
#[inline]
|
|
pub fn set_echo_seq_no(&mut self, value: u16) {
|
|
let data = self.buffer.as_mut();
|
|
NetworkEndian::write_u16(&mut data[field::ECHO_SEQNO], value)
|
|
}
|
|
|
|
/// Compute and fill in the header checksum.
|
|
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)
|
|
}
|
|
}
|
|
|
|
impl<'a, T: AsRef<[u8]> + AsMut<[u8]> + ?Sized> Packet<&'a mut T> {
|
|
/// Return a mutable pointer to the type-specific data.
|
|
#[inline]
|
|
pub fn data_mut(&mut self) -> &mut [u8] {
|
|
let range = self.header_len()..;
|
|
let data = self.buffer.as_mut();
|
|
&mut data[range]
|
|
}
|
|
}
|
|
|
|
/// A high-level representation of an Internet Control Message Protocol version 4 packet header.
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub enum Repr<'a> {
|
|
EchoRequest {
|
|
ident: u16,
|
|
seq_no: u16,
|
|
data: &'a [u8]
|
|
},
|
|
EchoReply {
|
|
ident: u16,
|
|
seq_no: u16,
|
|
data: &'a [u8]
|
|
},
|
|
DstUnreachable {
|
|
reason: DstUnreachable,
|
|
header: Ipv4Repr,
|
|
data: &'a [u8]
|
|
},
|
|
#[doc(hidden)]
|
|
__Nonexhaustive
|
|
}
|
|
|
|
impl<'a> Repr<'a> {
|
|
/// Parse an Internet Control Message Protocol version 4 packet and return
|
|
/// a high-level representation.
|
|
pub fn parse<T>(packet: &Packet<&'a T>, checksum_caps: &ChecksumCapabilities)
|
|
-> Result<Repr<'a>>
|
|
where T: AsRef<[u8]> + ?Sized {
|
|
// Valid checksum is expected.
|
|
if checksum_caps.icmpv4.rx() && !packet.verify_checksum() { return Err(Error::Checksum) }
|
|
|
|
match (packet.msg_type(), packet.msg_code()) {
|
|
(Message::EchoRequest, 0) => {
|
|
Ok(Repr::EchoRequest {
|
|
ident: packet.echo_ident(),
|
|
seq_no: packet.echo_seq_no(),
|
|
data: packet.data()
|
|
})
|
|
},
|
|
|
|
(Message::EchoReply, 0) => {
|
|
Ok(Repr::EchoReply {
|
|
ident: packet.echo_ident(),
|
|
seq_no: packet.echo_seq_no(),
|
|
data: packet.data()
|
|
})
|
|
},
|
|
|
|
(Message::DstUnreachable, code) => {
|
|
let ip_packet = Ipv4Packet::new_checked(packet.data())?;
|
|
|
|
let payload = &packet.data()[ip_packet.header_len() as usize..];
|
|
// RFC 792 requires exactly eight bytes to be returned.
|
|
// We allow more, since there isn't a reason not to, but require at least eight.
|
|
if payload.len() < 8 { return Err(Error::Truncated) }
|
|
|
|
Ok(Repr::DstUnreachable {
|
|
reason: DstUnreachable::from(code),
|
|
header: Ipv4Repr {
|
|
src_addr: ip_packet.src_addr(),
|
|
dst_addr: ip_packet.dst_addr(),
|
|
protocol: ip_packet.protocol(),
|
|
payload_len: payload.len(),
|
|
ttl: ip_packet.ttl()
|
|
},
|
|
data: payload
|
|
})
|
|
}
|
|
_ => Err(Error::Unrecognized)
|
|
}
|
|
}
|
|
|
|
/// Return the length of a packet that will be emitted from this high-level representation.
|
|
pub fn buffer_len(&self) -> usize {
|
|
match self {
|
|
&Repr::EchoRequest { data, .. } |
|
|
&Repr::EchoReply { data, .. } => {
|
|
field::ECHO_SEQNO.end + data.len()
|
|
},
|
|
&Repr::DstUnreachable { header, data, .. } => {
|
|
field::UNUSED.end + header.buffer_len() + data.len()
|
|
}
|
|
&Repr::__Nonexhaustive => unreachable!()
|
|
}
|
|
}
|
|
|
|
/// Emit a high-level representation into an Internet Control Message Protocol version 4
|
|
/// packet.
|
|
pub fn emit<T>(&self, packet: &mut Packet<&mut T>, checksum_caps: &ChecksumCapabilities)
|
|
where T: AsRef<[u8]> + AsMut<[u8]> + ?Sized {
|
|
packet.set_msg_code(0);
|
|
match self {
|
|
&Repr::EchoRequest { ident, seq_no, data } => {
|
|
packet.set_msg_type(Message::EchoRequest);
|
|
packet.set_msg_code(0);
|
|
packet.set_echo_ident(ident);
|
|
packet.set_echo_seq_no(seq_no);
|
|
let data_len = cmp::min(packet.data_mut().len(), data.len());
|
|
packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
|
|
},
|
|
|
|
&Repr::EchoReply { ident, seq_no, data } => {
|
|
packet.set_msg_type(Message::EchoReply);
|
|
packet.set_msg_code(0);
|
|
packet.set_echo_ident(ident);
|
|
packet.set_echo_seq_no(seq_no);
|
|
let data_len = cmp::min(packet.data_mut().len(), data.len());
|
|
packet.data_mut()[..data_len].copy_from_slice(&data[..data_len])
|
|
},
|
|
|
|
&Repr::DstUnreachable { reason, header, data } => {
|
|
packet.set_msg_type(Message::DstUnreachable);
|
|
packet.set_msg_code(reason.into());
|
|
|
|
let mut ip_packet = Ipv4Packet::new(packet.data_mut());
|
|
header.emit(&mut ip_packet, checksum_caps);
|
|
let payload = &mut ip_packet.into_inner()[header.buffer_len()..];
|
|
payload.copy_from_slice(&data[..])
|
|
}
|
|
|
|
&Repr::__Nonexhaustive => unreachable!()
|
|
}
|
|
|
|
if checksum_caps.icmpv4.tx() {
|
|
packet.fill_checksum()
|
|
} else {
|
|
// make sure we get a consistently zeroed checksum, since implementations might rely on it
|
|
packet.set_checksum(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
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, &ChecksumCapabilities::default()) {
|
|
Ok(repr) => write!(f, "{}", repr),
|
|
Err(err) => {
|
|
write!(f, "ICMPv4 ({})", err)?;
|
|
write!(f, " type={:?}", self.msg_type())?;
|
|
match self.msg_type() {
|
|
Message::DstUnreachable =>
|
|
write!(f, " code={:?}", DstUnreachable::from(self.msg_code())),
|
|
_ => write!(f, " code={}", self.msg_code())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> fmt::Display for Repr<'a> {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
&Repr::EchoRequest { ident, seq_no, data } =>
|
|
write!(f, "ICMPv4 echo request id={} seq={} len={}",
|
|
ident, seq_no, data.len()),
|
|
&Repr::EchoReply { ident, seq_no, data } =>
|
|
write!(f, "ICMPv4 echo reply id={} seq={} len={}",
|
|
ident, seq_no, data.len()),
|
|
&Repr::DstUnreachable { reason, .. } =>
|
|
write!(f, "ICMPv4 destination unreachable ({})",
|
|
reason),
|
|
&Repr::__Nonexhaustive => unreachable!()
|
|
}
|
|
}
|
|
}
|
|
|
|
use super::pretty_print::{PrettyPrint, PrettyIndent};
|
|
|
|
impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
|
|
fn pretty_print(buffer: &AsRef<[u8]>, f: &mut fmt::Formatter,
|
|
indent: &mut PrettyIndent) -> fmt::Result {
|
|
let packet = match Packet::new_checked(buffer) {
|
|
Err(err) => return write!(f, "{}({})\n", indent, err),
|
|
Ok(packet) => packet
|
|
};
|
|
write!(f, "{}{}\n", indent, packet)?;
|
|
|
|
indent.increase();
|
|
match packet.msg_type() {
|
|
Message::DstUnreachable =>
|
|
super::Ipv4Packet::<&[u8]>::pretty_print(&packet.data(), f, indent),
|
|
_ => Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
static ECHO_PACKET_BYTES: [u8; 12] =
|
|
[0x08, 0x00, 0x8e, 0xfe,
|
|
0x12, 0x34, 0xab, 0xcd,
|
|
0xaa, 0x00, 0x00, 0xff];
|
|
|
|
static ECHO_DATA_BYTES: [u8; 4] =
|
|
[0xaa, 0x00, 0x00, 0xff];
|
|
|
|
#[test]
|
|
fn test_echo_deconstruct() {
|
|
let packet = Packet::new(&ECHO_PACKET_BYTES[..]);
|
|
assert_eq!(packet.msg_type(), Message::EchoRequest);
|
|
assert_eq!(packet.msg_code(), 0);
|
|
assert_eq!(packet.checksum(), 0x8efe);
|
|
assert_eq!(packet.echo_ident(), 0x1234);
|
|
assert_eq!(packet.echo_seq_no(), 0xabcd);
|
|
assert_eq!(packet.data(), &ECHO_DATA_BYTES[..]);
|
|
assert_eq!(packet.verify_checksum(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn test_echo_construct() {
|
|
let mut bytes = vec![0xa5; 12];
|
|
let mut packet = Packet::new(&mut bytes);
|
|
packet.set_msg_type(Message::EchoRequest);
|
|
packet.set_msg_code(0);
|
|
packet.set_echo_ident(0x1234);
|
|
packet.set_echo_seq_no(0xabcd);
|
|
packet.data_mut().copy_from_slice(&ECHO_DATA_BYTES[..]);
|
|
packet.fill_checksum();
|
|
assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]);
|
|
}
|
|
|
|
fn echo_packet_repr() -> Repr<'static> {
|
|
Repr::EchoRequest {
|
|
ident: 0x1234,
|
|
seq_no: 0xabcd,
|
|
data: &ECHO_DATA_BYTES
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_echo_parse() {
|
|
let packet = Packet::new(&ECHO_PACKET_BYTES[..]);
|
|
let repr = Repr::parse(&packet, &ChecksumCapabilities::default()).unwrap();
|
|
assert_eq!(repr, echo_packet_repr());
|
|
}
|
|
|
|
#[test]
|
|
fn test_echo_emit() {
|
|
let repr = echo_packet_repr();
|
|
let mut bytes = vec![0xa5; repr.buffer_len()];
|
|
let mut packet = Packet::new(&mut bytes);
|
|
repr.emit(&mut packet, &ChecksumCapabilities::default());
|
|
assert_eq!(&packet.into_inner()[..], &ECHO_PACKET_BYTES[..]);
|
|
}
|
|
}
|