#![doc = include_str!("../README.md")]
use byteorder::{LittleEndian, ReadBytesExt};
use crc32fast::Hasher;
use std::convert::TryFrom;
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum WirestripperError {
#[error("Wirestripper doesn't support netANALYZER non-transparent mode records")]
OpaqueModeUnsupported,
#[error("Wirestripper doesn't support netANALYZER records with header version {0}")]
HeaderVersionUnsupported(u8),
#[error("PCAP record too short to have valid netANALYZER header")]
RecordTooShort,
#[error("netANALYZER GPIO mode is unsupported")]
GPIOModeUnsupported,
#[error("PCAP record too short to have valid FCS")]
RecordTooShortForFCS,
#[error("Invalid value for packet length: {header_value} (data is {packet_length} bytes)")]
InvalidFrameLength {
header_value: usize,
packet_length: usize,
},
#[error("expected first non-preamble byte to be SFD (0xD5), but got {0}")]
NoSFDAfterPreamble(u8),
#[error("SFD not found in 802.3 frame")]
NoSFDFound,
#[error("Header FCS error flag is {0} but that isn't correct (our result={1})")]
FCSErrorError(bool, bool),
#[error("Header SFD error flag ({0}) doesn't match (SFD is present at offset {1})")]
SFDErrorError(bool, usize),
#[error("Header frame-too-short error flag is {0}, but frame is {1} bytes long (normal is >= 64 bytes)")]
FrameTooShortErrorError(bool, usize),
#[error("Header preamble-too-short error flag is {0}, but preamble is {1} bytes long (normal is 7 bytes)")]
PreambleTooShortErrorError(bool, usize),
#[error("Header preamble-too-long error flag is {0}, but preamble is {1} bytes long (normal is 7 bytes)")]
PreambleTooLongErrorError(bool, usize),
#[error("Header reserved bits are not zero ({0}) but record is version 1, not 2")]
ReservedBitsNonZero(u8),
#[error("Invalid combination of error flags ({0:?})")]
InvalidErrorFlagCombination(ErrorFlags),
#[error("Generic header decoding error")]
DecodeError,
}
#[derive(PartialEq, Debug)]
pub enum PortType {
Ethernet,
GPIO,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ErrorFlags {
pub preamble_too_long: bool,
pub preamble_too_short: bool,
pub frame_too_short: bool,
pub sfd_not_found: bool,
pub frame_too_long: bool,
pub fcs_incorrect: bool,
pub alignment_problem: bool,
pub mii_receive_error: bool,
}
#[derive(Debug)]
pub struct Header {
pub(crate) reserved: u8,
pub frame_length: u16,
pub port_number: u8,
pub header_version: u8,
pub transparent_mode: bool,
pub port_type: PortType,
pub errors: ErrorFlags,
}
impl Header {
pub fn from_bytes(mut b: &[u8]) -> Header {
let w = b.read_u32::<LittleEndian>().unwrap();
Header {
reserved: (w >> 28) as u8,
frame_length: ((w >> 16) & 0x0fff) as u16,
port_number: ((w >> 14) & 0x3) as u8,
header_version: ((w >> 10) & 0xf) as u8,
transparent_mode: ((w >> 9) & 0x1) == 0x1,
port_type: match (w >> 8) & 0x1 {
0 => PortType::Ethernet,
1 => PortType::GPIO,
_ => panic!("impossible for 1-bit port_type to have > 2 values"),
},
errors: ErrorFlags {
preamble_too_long: w & 0x80 != 0,
preamble_too_short: w & 0x40 != 0,
frame_too_short: w & 0x20 != 0,
sfd_not_found: w & 0x10 != 0,
frame_too_long: w & 0x8 != 0,
fcs_incorrect: w & 0x4 != 0,
alignment_problem: w & 0x2 != 0,
mii_receive_error: w & 0x1 != 0,
},
}
}
}
#[derive(Debug)]
pub struct Record {
pub header: Header,
has_error: bool,
packet: Vec<u8>,
sfd_offset: usize,
}
impl Record {
pub fn report_errors(&self) -> Option<Vec<String>> {
if self.has_error {
let mut errors: Vec<String> = Vec::new();
if self.header.errors.preamble_too_long {
errors.push(format!("preamble too long ({})", self.sfd_offset))
}
if self.header.errors.preamble_too_short {
errors.push(format!("preamble too short ({})", self.sfd_offset))
}
if self.header.errors.frame_too_short {
errors.push(format!("frame too short ({})", self.packet.len()))
}
if self.header.errors.sfd_not_found {
errors.push("sfd not found)".to_string())
}
if self.header.errors.frame_too_long {
errors.push(format!("frame too long ({})", self.packet.len()))
}
if self.header.errors.fcs_incorrect {
errors.push("fcs incorrect".to_string())
}
if self.header.errors.alignment_problem {
errors.push("alignment problem".to_string())
}
if self.header.errors.alignment_problem {
errors.push("mii receive error".to_string())
}
return Some(errors);
}
None
}
pub fn validate_header(&self) -> Result<(), Vec<WirestripperError>> {
let mut errors = Vec::new();
let fcs_problem = !self.fcs_is_valid();
if self.header.errors.fcs_incorrect ^ fcs_problem {
errors.push(WirestripperError::FCSErrorError(
self.header.errors.fcs_incorrect,
fcs_problem,
));
}
let sfd_problem = self.sfd_offset == 0;
if self.header.errors.sfd_not_found ^ sfd_problem {
errors.push(WirestripperError::SFDErrorError(
self.header.errors.sfd_not_found,
self.sfd_offset,
));
}
let frame_too_short = self.frame().len() < 64;
if self.header.errors.frame_too_short ^ frame_too_short {
errors.push(WirestripperError::FrameTooShortErrorError(
self.header.errors.frame_too_short,
self.frame().len(),
));
}
let preamble_too_short = self.sfd_offset < 7;
if self.header.errors.preamble_too_short ^ preamble_too_short {
errors.push(WirestripperError::PreambleTooShortErrorError(
self.header.errors.preamble_too_short,
self.sfd_offset,
));
}
let preamble_too_long = self.sfd_offset > 7;
if self.header.errors.preamble_too_long ^ preamble_too_long {
errors.push(WirestripperError::PreambleTooLongErrorError(
self.header.errors.preamble_too_long,
self.sfd_offset,
));
}
if self.header.frame_length as usize != self.packet.len() {
errors.push(WirestripperError::InvalidFrameLength {
header_value: self.header.frame_length as usize,
packet_length: self.packet.len(),
});
}
if self.header.reserved != 0 {
errors.push(WirestripperError::ReservedBitsNonZero(self.header.reserved));
}
if (self.header.errors.preamble_too_long && self.header.errors.preamble_too_short)
|| (self.header.errors.frame_too_long && self.header.errors.frame_too_short)
|| (self.header.errors.sfd_not_found && self.header.errors.preamble_too_long)
{
errors.push(WirestripperError::InvalidErrorFlagCombination(
self.header.errors,
));
}
if !errors.is_empty() {
return Err(errors);
}
Ok(())
}
pub fn fcs(&self) -> &[u8] {
&self.packet[0..self.sfd_offset]
}
pub fn packet(&self) -> &[u8] {
&self.packet
}
pub fn frame(&self) -> &[u8] {
&self.packet[self.sfd_offset + 1..self.packet.len()]
}
pub fn frame_without_fcs(&self) -> &[u8] {
&self.packet[self.sfd_offset + 1..self.packet.len() - 4]
}
pub fn fcs_is_valid(&self) -> bool {
let frame_fcs = self
.packet
.windows(4)
.last()
.unwrap()
.read_u32::<LittleEndian>()
.unwrap();
let mut hasher = Hasher::new();
hasher.update(&self.packet[self.sfd_offset + 1..self.packet.len() - 4]);
let computed_fcs = hasher.finalize();
frame_fcs == computed_fcs
}
pub fn any_error_flags_set(&self) -> bool {
self.has_error
}
}
impl TryFrom<&[u8]> for Record {
type Error = WirestripperError;
fn try_from(data: &[u8]) -> Result<Self, WirestripperError> {
if data.len() < 4 {
return Err(WirestripperError::RecordTooShort);
}
if data.len() < 8 {
return Err(WirestripperError::RecordTooShortForFCS);
}
let header = Header::from_bytes(&data[0..4]);
if !header.transparent_mode {
return Err(WirestripperError::OpaqueModeUnsupported);
}
if header.header_version != 1 {
return Err(WirestripperError::HeaderVersionUnsupported(
header.header_version,
));
}
if header.port_type == PortType::GPIO {
return Err(WirestripperError::GPIOModeUnsupported);
}
let has_error = data[0] != 0;
let mut preamble_byte_count: u8 = 0;
let frame_iter = data.iter().skip(4);
let mut sfd_offset = 0usize;
for (i, b) in frame_iter.enumerate() {
if *b != 0x55u8 {
if *b != 0xd5u8 {
return Err(WirestripperError::NoSFDAfterPreamble(*b));
}
sfd_offset = i;
break;
}
preamble_byte_count += 1;
}
if preamble_byte_count == 0 {
return Err(WirestripperError::NoSFDFound);
}
Ok(Record {
header,
has_error,
sfd_offset,
packet: data[4..data.len()].to_vec(),
})
}
}
#[cfg(test)]
mod tests {
use crate::{ErrorFlags, Header, PortType, Record, WirestripperError};
use pretty_hex::pretty_hex;
use std::convert::TryFrom;
#[test]
fn test_header_sample() {
let test: [u8; 4] = [0x0, 0x6, 0x48, 0x0];
let h = Header::from_bytes(&test[..]);
assert!(!h.errors.mii_receive_error);
assert!(!h.errors.alignment_problem);
assert!(!h.errors.fcs_incorrect);
assert!(!h.errors.frame_too_long);
assert!(!h.errors.sfd_not_found);
assert!(!h.errors.frame_too_short);
assert!(!h.errors.preamble_too_short);
assert!(!h.errors.preamble_too_long);
assert_eq!(h.port_type, PortType::Ethernet);
assert!(h.transparent_mode);
assert_eq!(h.header_version, 1);
assert_eq!(h.port_number, 0);
assert_eq!(h.frame_length, 72);
assert_eq!(h.reserved, 0);
println!("{:?}", h)
}
#[test]
fn test_header_errors_msb() {
let test: [u8; 4] = [0x80, 0x6, 0x48, 0x00];
let h = Header::from_bytes(&test[..]);
println!("Test header bytes: {}", pretty_hex(&test));
assert!(h.errors.preamble_too_long);
assert!(!h.errors.mii_receive_error);
}
#[test]
fn test_header_errors_lsb() {
let test: [u8; 4] = [0x01, 0x6, 0x48, 0x00];
let h = Header::from_bytes(&test[..]);
assert!(!h.errors.preamble_too_long);
assert!(h.errors.mii_receive_error);
}
#[test]
fn test_header_port0() {
let test: [u8; 4] = [0x0, 0x6, 0x7a, 0x0];
let h = Header::from_bytes(&test[..]);
assert_eq!(h.port_number, 0);
}
#[test]
fn test_header_port1() {
let test: [u8; 4] = [0x0, 0x46, 0x44, 0x0];
let h = Header::from_bytes(&test[..]);
assert_eq!(h.port_number, 1);
}
const GOOD_SAMPLE_RECORD: [u8; 76] = [
0x00u8, 0x06u8, 0x48u8, 0x00u8, 0x55u8, 0x55u8, 0x55u8, 0x55u8, 0x55u8, 0x55u8, 0x55u8,
0xd5u8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0x0eu8, 0x2bu8, 0x7cu8, 0xffu8,
0xd4u8, 0xb2u8, 0x08u8, 0x06u8, 0x00u8, 0x01u8, 0x08u8, 0x00u8, 0x06u8, 0x04u8, 0x00u8,
0x01u8, 0x0eu8, 0x2bu8, 0x7cu8, 0xffu8, 0xd4u8, 0xb2u8, 0xc0u8, 0xa8u8, 0x07u8, 0x01u8,
0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0xc0u8, 0xa8u8, 0x07u8, 0x04u8, 0x00u8,
0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8,
0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x97u8, 0xa2u8, 0xc3u8, 0x13u8,
];
#[test]
fn test_record_sample() {
let r = Record::try_from(&GOOD_SAMPLE_RECORD[..]).unwrap();
r.validate_header().unwrap();
assert!(!r.any_error_flags_set());
assert!(r.fcs_is_valid());
}
const BAD_SAMPLE_RECORD: [u8; 76] = [
0xc0u8, 0x06u8, 0x48u8, 0x00u8, 0x55u8, 0x55u8, 0x55u8, 0x55u8, 0x55u8, 0x55u8, 0x55u8,
0xd5u8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0xffu8, 0x0eu8, 0x2bu8, 0x7cu8, 0xffu8,
0xd4u8, 0xb2u8, 0x08u8, 0x06u8, 0x00u8, 0x01u8, 0x08u8, 0x00u8, 0x06u8, 0x04u8, 0x00u8,
0x01u8, 0x0eu8, 0x2bu8, 0x7cu8, 0xffu8, 0xd4u8, 0xb2u8, 0xc0u8, 0xa8u8, 0x07u8, 0x01u8,
0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0xc0u8, 0xa8u8, 0x07u8, 0x04u8, 0x00u8,
0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8,
0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x00u8, 0x97u8, 0xa2u8, 0xc3u8, 0x13u8,
];
#[test]
fn test_record_badcombo() {
let r = Record::try_from(&BAD_SAMPLE_RECORD[..]).unwrap();
match r.validate_header() {
Err(e) => {
let bad_flags = ErrorFlags {
preamble_too_long: true,
preamble_too_short: true,
frame_too_short: false,
sfd_not_found: false,
frame_too_long: false,
fcs_incorrect: false,
alignment_problem: false,
mii_receive_error: false,
};
assert!(e.contains(&WirestripperError::InvalidErrorFlagCombination(bad_flags)))
}
Ok(_) => {
panic!("there must be errors!")
}
}
}
}