blob: ee87f5529a9fb7bc1eb30fce03bb8aa84d40f499 [file] [log] [blame]
use crate::*;
use arrayvec::ArrayVec;
/// MACsec SecTag header (present at the start of a
/// packet capsuled with MACsec).
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct MacsecHeader {
/// Payload type (contains encryption, modification flag as
/// well as the next ether type if available)
pub ptype: MacsecPType,
/// End station identifier (TCI.ES flag).
pub endstation_id: bool,
/// Ethernet passive optical network broadcast flag.
pub scb: bool,
/// Association number (identifies SAs).
pub an: MacsecAn,
/// Short length with reserved bits.
pub short_len: MacsecShortLen,
/// Packet number.
pub packet_nr: u32,
/// Secure channel identifier.
pub sci: Option<u64>,
}
impl MacsecHeader {
/// Minimum length of an MacSec header in bytes/octets.
pub const MIN_LEN: usize = 6;
/// Maximum length of an MacSec header (including ether type of payload) in bytes/octets.
pub const MAX_LEN: usize = 16;
/// Encryption flag, which indicates whether the user data is
/// encrypted (true = encrypted, TCI.E flag).
#[inline]
pub fn encrypted(&self) -> bool {
use MacsecPType::*;
matches!(self.ptype, Encrypted | EncryptedUnmodified)
}
/// Flag for change text, set if the user data is modified.
pub fn userdata_changed(&self) -> bool {
use MacsecPType::*;
matches!(self.ptype, Encrypted | Modified)
}
/// Ether type of the data following the mac sec tag.
pub fn next_ether_type(&self) -> Option<EtherType> {
if let MacsecPType::Unmodified(re) = self.ptype {
Some(re)
} else {
None
}
}
/// Try creating a [`MacsecHeaderSlice`] from a slice containing the
/// MACsec header & next ether type.
pub fn from_slice(slice: &[u8]) -> Result<MacsecHeader, err::macsec::HeaderSliceError> {
MacsecHeaderSlice::from_slice(slice).map(|v| v.to_header())
}
/// Serialize the mac sec header.
pub fn to_bytes(&self) -> ArrayVec<u8, { MacsecHeader::MAX_LEN }> {
// tci-an is composed of:
// ---------------------------------------
// | v | es | sc | scp | e | c | an | an |
// ---------------------------------------
// bits 8 7 6 5 4 3 2 1
//
// - version (0)
// - es (end station identifier bit)
// - sc (SCI present bit)
// - scp (Ethernet passive optical network broadcast bit)
// - e (encryption bit)
// - c (user data change bit)
// - an (Association number) [2 bits]
let tci_an = (self.an.value() & 0b11)
| if self.userdata_changed() { 0b100 } else { 0 }
| if self.encrypted() { 0b1000 } else { 0 }
| if self.scb { 0b1_0000 } else { 0 }
| if self.sci.is_some() { 0b10_0000 } else { 0 }
| if self.endstation_id { 0b100_0000 } else { 0 };
let pn_be = self.packet_nr.to_be_bytes();
let sci_be = self.sci.unwrap_or(0).to_be_bytes();
let et_be = if let MacsecPType::Unmodified(e) = self.ptype {
e.0
} else {
0
}
.to_be_bytes();
let mut result: ArrayVec<u8, { MacsecHeader::MAX_LEN }> = if self.sci.is_some() {
[
tci_an,
self.short_len.value() & 0b0011_1111,
pn_be[0],
pn_be[1],
pn_be[2],
pn_be[3],
sci_be[0],
sci_be[1],
sci_be[2],
sci_be[3],
sci_be[4],
sci_be[5],
sci_be[6],
sci_be[7],
et_be[0],
et_be[1],
]
} else {
[
tci_an,
self.short_len.value() & 0b0011_1111,
pn_be[0],
pn_be[1],
pn_be[2],
pn_be[3],
et_be[0],
et_be[1],
0,
0,
0,
0,
0,
0,
0,
0,
]
}
.into();
// SAFETY: Safe as the maximum size of 16 can not be exceeded.
unsafe {
result.set_len(
6 + if self.sci.is_some() { 8 } else { 0 }
+ if matches!(self.ptype, MacsecPType::Unmodified(_)) {
2
} else {
0
},
);
}
result
}
/// Try reading a MACsec header from the position of the reader.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn read<T: std::io::Read + Sized>(
reader: &mut T,
) -> Result<MacsecHeader, err::macsec::HeaderReadError> {
use err::macsec::HeaderError::*;
use err::macsec::HeaderReadError::*;
let mut bytes = [0; MacsecHeader::MAX_LEN];
reader.read_exact(&mut bytes[..6]).map_err(Io)?;
// check version bit
let tci_an = bytes[0];
if 0 != tci_an & 0b1000_0000 {
return Err(Content(UnexpectedVersion));
}
// validate short_len is not 1 in the unmodified case
let unmodified = 0 == tci_an & 0b1100;
if unmodified {
// SAFETY: Safe as the length was verified to be at least 6.
let short_len = bytes[1] & 0b0011_1111;
// short len must be zero (unknown) or at least 2 in case of an
// unmodified payload
if short_len == 1 {
return Err(Content(InvalidUnmodifiedShortLen));
}
}
// get the encrypted, changed flag (check if ether_type can be parsed)
let required_len =
6 + if unmodified { 2 } else { 0 } + if 0 != tci_an & 0b10_0000 { 8 } else { 0 };
if required_len > 6 {
reader.read_exact(&mut bytes[6..required_len]).map_err(Io)?;
}
Ok(MacsecHeaderSlice {
slice: &bytes[..required_len],
}
.to_header())
}
/// Writes a given MACsec header to the current position (SecTag & next
/// ether type if available).
#[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> {
writer.write_all(&self.to_bytes())
}
/// Length of the MACsec header (SecTag + next ether type if available).
#[inline]
pub fn header_len(&self) -> usize {
6 + if self.sci.is_some() { 8 } else { 0 }
+ if matches!(self.ptype, MacsecPType::Unmodified(_)) {
2
} else {
0
}
}
/// Returns the required length of the payload (data after header +
/// next_ether_type if present) if possible.
///
/// If the length cannot be determined (`short_len` is zero or less then
/// `2` when `ptype` `Unmodified`) `None` is returned.
#[inline]
pub fn expected_payload_len(&self) -> Option<usize> {
let sl = self.short_len.value() as usize;
if sl > 0 {
if matches!(self.ptype, MacsecPType::Unmodified(_)) {
if sl < 2 {
None
} else {
Some(sl - 2)
}
} else {
// no ether type (encrypted and/or modified payload)
Some(sl)
}
} else {
None
}
}
/// Set the `short_len` field based on the given payload byte len
/// (payload len excluding the ether_type if `ptype` `Unmodified`)
/// based on the current `ptype`.
#[inline]
pub fn set_payload_len(&mut self, payload_len: usize) {
if matches!(self.ptype, MacsecPType::Unmodified(_)) {
if payload_len > MacsecShortLen::MAX_USIZE - 2 {
self.short_len = MacsecShortLen::ZERO;
} else {
// SAFETY: Safe as payload_len + 2 <= MacsecShortLen::MAX_USIZE
// is guaranteed after the if above.
self.short_len =
unsafe { MacsecShortLen::from_u8_unchecked(payload_len as u8 + 2) };
}
} else if payload_len > MacsecShortLen::MAX_USIZE {
self.short_len = MacsecShortLen::ZERO;
} else {
// SAFETY: Safe as payload_len + 2 <= MacsecShortLen::MAX_USIZE
// is guaranteed after the if above.
self.short_len = unsafe { MacsecShortLen::from_u8_unchecked(payload_len as u8) };
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::test_gens::*;
use proptest::prelude::*;
use std::io::Cursor;
proptest! {
#[test]
fn from_slice_to_bytes(
header in macsec_any()
) {
let mut header = header.clone();
if matches!(header.ptype, MacsecPType::Unmodified(_)) && header.short_len.value() == 1 {
header.short_len = MacsecShortLen::ZERO;
}
let bytes = header.to_bytes();
let actual = MacsecHeader::from_slice(&bytes);
assert_eq!(actual, Ok(header.clone()));
}
}
proptest! {
#[test]
fn getter(
macsec in macsec_any(),
ethertype in ether_type_any(),
) {
let tests = [
// ptype, encrypted, userdata_changed, next_ether_type
(MacsecPType::Unmodified(ethertype), false, false, Some(ethertype)),
(MacsecPType::Modified, false, true, None),
(MacsecPType::Encrypted, true, true, None),
(MacsecPType::EncryptedUnmodified, true, false, None),
];
for test in tests {
let mut macsec = macsec.clone();
macsec.ptype = test.0;
assert_eq!(test.1, macsec.encrypted());
assert_eq!(test.2, macsec.userdata_changed());
assert_eq!(test.3, macsec.next_ether_type());
}
}
}
proptest! {
#[test]
fn header_len(
macsec in macsec_any(),
ethertype in ether_type_any(),
sci in any::<u64>(),
) {
// no ethertype
for ptype in [MacsecPType::Modified, MacsecPType::Encrypted, MacsecPType::EncryptedUnmodified] {
// no sci
{
let mut macsec = macsec.clone();
macsec.ptype = ptype;
macsec.sci = None;
assert_eq!(6, macsec.header_len());
}
// with sci
{
let mut macsec = macsec.clone();
macsec.ptype = ptype;
macsec.sci = Some(sci);
assert_eq!(14, macsec.header_len());
}
}
// with ethertype
// no sci
{
let mut macsec = macsec.clone();
macsec.ptype = MacsecPType::Unmodified(ethertype);
macsec.sci = None;
assert_eq!(8, macsec.header_len());
}
// with sci
{
let mut macsec = macsec.clone();
macsec.ptype = MacsecPType::Unmodified(ethertype);
macsec.sci = Some(sci);
assert_eq!(16, macsec.header_len());
}
}
}
proptest! {
#[test]
fn read(
macsec in macsec_any(),
ether_type in ether_type_any(),
sci in any::<u64>()
) {
use MacsecPType::*;
use err::macsec::*;
// variants
for ptype in [Unmodified(ether_type), Modified, Encrypted, EncryptedUnmodified] {
for has_sci in [false, true] {
let mut macsec = macsec.clone();
macsec.ptype = ptype;
macsec.sci = if has_sci {
Some(sci)
} else {
None
};
if matches!(ptype, MacsecPType::Unmodified(_)) && macsec.short_len.value() == 1 {
macsec.short_len = MacsecShortLen::ZERO;
}
// ok case
{
let mut bytes = ArrayVec::<u8, { MacsecHeader::MAX_LEN + 1 }>::new();
bytes.try_extend_from_slice(&macsec.to_bytes()).unwrap();
let mut cursor = Cursor::new(&bytes);
let m = MacsecHeader::read(&mut cursor).unwrap();
assert_eq!(m, macsec);
assert_eq!(macsec.header_len() as u64, cursor.position());
}
// version error
{
let mut bytes = ArrayVec::<u8, { MacsecHeader::MAX_LEN + 1 }>::new();
bytes.try_extend_from_slice(&macsec.to_bytes()).unwrap();
bytes.try_extend_from_slice(&[1]).unwrap();
// version bit
bytes[0] = bytes[0] | 0b1000_0000;
let mut cursor = Cursor::new(&bytes);
let m = MacsecHeader::read(&mut cursor).unwrap_err();
assert_eq!(m.content_error(), Some(HeaderError::UnexpectedVersion));
}
// short len error
if matches!(ptype, MacsecPType::Unmodified(_)) {
let mut macsec = macsec.clone();
macsec.short_len = MacsecShortLen::try_from_u8(1).unwrap();
let mut bytes = ArrayVec::<u8, { MacsecHeader::MAX_LEN + 1 }>::new();
bytes.try_extend_from_slice(&macsec.to_bytes()).unwrap();
let mut cursor = Cursor::new(&bytes);
let m = MacsecHeader::read(&mut cursor).unwrap_err();
assert_eq!(m.content_error(), Some(HeaderError::InvalidUnmodifiedShortLen));
}
// len error
for len in 0..macsec.header_len() {
let mut bytes = ArrayVec::<u8, { MacsecHeader::MAX_LEN + 1 }>::new();
bytes.try_extend_from_slice(&macsec.to_bytes()).unwrap();
let mut cursor = Cursor::new(&bytes[..len]);
let m = MacsecHeader::read(&mut cursor);
assert!(m.unwrap_err().io_error().is_some());
}
}
}
}
}
proptest! {
#[test]
fn write(
header in macsec_any()
) {
// ok case
{
let mut buffer = ArrayVec::<u8, {MacsecHeader::MAX_LEN}>::new();
header.write(&mut buffer).unwrap();
assert_eq!(&buffer, &header.to_bytes());
}
// not enough memory
{
let mut buffer = [0u8;MacsecHeader::MAX_LEN];
let mut cursor = Cursor::new(&mut buffer[..header.header_len() - 1]);
header.write(&mut cursor).unwrap_err();
}
}
}
proptest! {
#[test]
fn expected_payload_len(
header in macsec_any(),
ether_type in ether_type_any(),
valid_unmodified_len in 2u8..=MacsecShortLen::MAX_U8,
valid_modified_len in 1u8..=MacsecShortLen::MAX_U8
) {
// unmodified, payload len (non zero or one)
{
let mut header = header.clone();
header.ptype = MacsecPType::Unmodified(ether_type);
header.short_len = MacsecShortLen::try_from_u8(valid_unmodified_len).unwrap();
assert_eq!(Some(valid_unmodified_len as usize - 2), header.expected_payload_len());
}
// unmodified, unknown len
for short_len in 0..2u8 {
let mut header = header.clone();
header.ptype = MacsecPType::Unmodified(ether_type);
header.short_len = MacsecShortLen::try_from_u8(short_len).unwrap();
assert_eq!(None, header.expected_payload_len());
}
// modified, valid payload len (non zero)
for ptype in [MacsecPType::Modified, MacsecPType::Encrypted, MacsecPType::EncryptedUnmodified] {
let mut header = header.clone();
header.ptype = ptype;
header.short_len = MacsecShortLen::try_from_u8(valid_modified_len).unwrap();
assert_eq!(Some(valid_modified_len as usize), header.expected_payload_len());
}
// modified, unknown len
for ptype in [MacsecPType::Modified, MacsecPType::Encrypted, MacsecPType::EncryptedUnmodified] {
let mut header = header.clone();
header.ptype = ptype;
header.short_len = MacsecShortLen::ZERO;
assert_eq!(None, header.expected_payload_len());
}
}
}
proptest! {
#[test]
fn set_payload_len(
header in macsec_any(),
ether_type in ether_type_any(),
valid_unmodified_len in 0..=(MacsecShortLen::MAX_USIZE - 2),
invalid_unmodified_len in (MacsecShortLen::MAX_USIZE - 1)..=usize::MAX,
valid_modified_len in 1..=MacsecShortLen::MAX_USIZE,
invalid_modified_len in (MacsecShortLen::MAX_USIZE + 1)..=usize::MAX
) {
// unmodified, payload len (non zero or one)
{
let mut header = header.clone();
header.ptype = MacsecPType::Unmodified(ether_type);
header.set_payload_len(valid_unmodified_len);
assert_eq!(header.short_len.value() as usize, valid_unmodified_len + 2);
}
// unmodified, invalid len
{
let mut header = header.clone();
header.ptype = MacsecPType::Unmodified(ether_type);
header.set_payload_len(invalid_unmodified_len);
assert_eq!(0, header.short_len.value());
}
// modified, valid payload len (non zero)
for ptype in [MacsecPType::Modified, MacsecPType::Encrypted, MacsecPType::EncryptedUnmodified] {
let mut header = header.clone();
header.ptype = ptype;
header.set_payload_len(valid_modified_len);
assert_eq!(valid_modified_len, header.short_len.value() as usize);
}
// modified, unknown len
for ptype in [MacsecPType::Modified, MacsecPType::Encrypted, MacsecPType::EncryptedUnmodified] {
let mut header = header.clone();
header.ptype = ptype;
header.set_payload_len(invalid_modified_len);
assert_eq!(0, header.short_len.value());
}
}
}
}