/// # Support of the CCSDS Unified Space Data Link Protocol (USLP)
use crate::{crc::CRC_CCITT_FALSE, ByteConversionError};

/// Only this version is supported by the library
pub const USLP_VERSION_NUMBER: u8 = 0b1100;

/// Identifies the association of the data contained in the transfer frame.
#[derive(Debug, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)]
pub enum SourceOrDestField {
    /// SCID refers to the source of the transfer frame.
    Source = 0,
    /// SCID refers to the destination of the transfer frame.
    Dest = 1,
}

#[derive(Debug, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u1, exhaustive = true)]
#[repr(u8)]
pub enum BypassSequenceControlFlag {
    /// Acceptance of this frame on the receiving end is subject to normal frame acceptance
    /// checks of FARM.
    SequenceControlledQoS = 0,
    /// Frame Acceptance Checks of FARM by the receiving end shall be bypassed.
    ExpeditedQoS = 1,
}

#[derive(
    Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive,
)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum ProtocolControlCommandFlag {
    TfdfContainsUserData = 0,
    TfdfContainsProtocolInfo = 1,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
pub enum UslpError {
    #[error("byte conversion error: {0}")]
    ByteConversion(#[from] ByteConversionError),
    #[error("header is truncated, which is not supported")]
    HeaderIsTruncated,
    #[error("invalid protocol id: {0}")]
    InvalidProtocolId(u8),
    #[error("invalid construction rule: {0}")]
    InvalidConstructionRule(u8),
    #[error("invalid version number: {0}")]
    InvalidVersionNumber(u8),
    #[error("invalid virtual channel ID: {0}")]
    InvalidVcId(u8),
    #[error("invalid MAP ID: {0}")]
    InvalidMapId(u8),
    #[error("checksum failure")]
    ChecksumFailure(u16),
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct InvalidValueForLen {
    value: u64,
    len: u8,
}

#[derive(Debug, Copy, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PrimaryHeader {
    pub spacecraft_id: u16,
    pub source_or_dest_field: SourceOrDestField,
    pub vc_id: u8,
    pub map_id: u8,
    frame_len_field: u16,
    pub sequence_control_flag: BypassSequenceControlFlag,
    pub protocol_control_command_flag: ProtocolControlCommandFlag,
    pub ocf_flag: bool,
    vc_frame_count_len: u8,
    vc_frame_count: u64,
}

impl PrimaryHeader {
    pub fn new(
        spacecraft_id: u16,
        source_or_dest_field: SourceOrDestField,
        vc_id: u8,
        map_id: u8,
        frame_len: u16,
    ) -> Result<Self, UslpError> {
        if vc_id > 0b111111 {
            return Err(UslpError::InvalidVcId(vc_id));
        }
        if map_id > 0b1111 {
            return Err(UslpError::InvalidMapId(map_id));
        }
        Ok(Self {
            spacecraft_id,
            source_or_dest_field,
            vc_id,
            map_id,
            frame_len_field: frame_len.saturating_sub(1),
            sequence_control_flag: BypassSequenceControlFlag::SequenceControlledQoS,
            protocol_control_command_flag: ProtocolControlCommandFlag::TfdfContainsUserData,
            ocf_flag: false,
            vc_frame_count_len: 0,
            vc_frame_count: 0,
        })
    }

    pub fn set_vc_frame_count(
        &mut self,
        count_len: u8,
        count: u64,
    ) -> Result<(), InvalidValueForLen> {
        if count > 2_u64.pow(count_len as u32 * 8) - 1 {
            return Err(InvalidValueForLen {
                value: count,
                len: count_len,
            });
        }
        self.vc_frame_count_len = count_len;
        self.vc_frame_count = count;
        Ok(())
    }

    #[inline]
    pub fn vc_frame_count(&self) -> u64 {
        self.vc_frame_count
    }

    #[inline]
    pub fn vc_frame_count_len(&self) -> u8 {
        self.vc_frame_count_len
    }

    pub fn from_bytes(buf: &[u8]) -> Result<Self, UslpError> {
        if buf.len() < 4 {
            return Err(ByteConversionError::FromSliceTooSmall {
                found: buf.len(),
                expected: 4,
            }
            .into());
        }
        // Can only deal with regular frames for now.
        if (buf[3] & 0b1) == 1 {
            return Err(UslpError::HeaderIsTruncated);
        }
        // We could check this above, but this is a better error for the case where the user
        // tries to read a truncated frame.
        if buf.len() < 7 {
            return Err(ByteConversionError::FromSliceTooSmall {
                found: buf.len(),
                expected: 7,
            }
            .into());
        }
        let version_number = (buf[0] >> 4) & 0b1111;
        if version_number != USLP_VERSION_NUMBER {
            return Err(UslpError::InvalidVersionNumber(version_number));
        }
        let source_or_dest_field = match (buf[2] >> 3) & 1 {
            0 => SourceOrDestField::Source,
            1 => SourceOrDestField::Dest,
            _ => unreachable!(),
        };
        let vc_frame_count_len = buf[6] & 0b111;
        if buf.len() < 7 + vc_frame_count_len as usize {
            return Err(ByteConversionError::FromSliceTooSmall {
                found: buf.len(),
                expected: 7 + vc_frame_count_len as usize,
            }
            .into());
        }
        let vc_frame_count = match vc_frame_count_len {
            1 => buf[7] as u64,
            2 => u16::from_be_bytes(buf[7..9].try_into().unwrap()) as u64,
            4 => u32::from_be_bytes(buf[7..11].try_into().unwrap()) as u64,
            len => {
                let mut vcf_count = 0u64;
                let mut end = len;
                for byte in buf[7..7 + len as usize].iter() {
                    vcf_count |= (*byte as u64) << ((end - 1) * 8);
                    end -= 1;
                }
                vcf_count
            }
        };
        Ok(Self {
            spacecraft_id: (((buf[0] as u16) & 0b1111) << 12)
                | ((buf[1] as u16) << 4)
                | ((buf[2] as u16) >> 4) & 0b1111,
            source_or_dest_field,
            vc_id: ((buf[2] & 0b111) << 3) | (buf[3] >> 5) & 0b111,
            map_id: (buf[3] >> 1) & 0b1111,
            frame_len_field: ((buf[4] as u16) << 8) | buf[5] as u16,
            sequence_control_flag: ((buf[6] >> 7) & 0b1).try_into().unwrap(),
            protocol_control_command_flag: ((buf[6] >> 6) & 0b1).try_into().unwrap(),
            ocf_flag: ((buf[6] >> 3) & 0b1) != 0,
            vc_frame_count_len,
            vc_frame_count,
        })
    }

    pub fn write_to_be_bytes(&self, buf: &mut [u8]) -> Result<usize, ByteConversionError> {
        if buf.len() < self.len_header() {
            return Err(ByteConversionError::ToSliceTooSmall {
                found: buf.len(),
                expected: self.len_header(),
            });
        }
        buf[0] = (USLP_VERSION_NUMBER << 4) | ((self.spacecraft_id >> 12) as u8) & 0b1111;
        buf[1] = (self.spacecraft_id >> 4) as u8;
        buf[2] = (((self.spacecraft_id & 0b1111) as u8) << 4)
            | ((self.source_or_dest_field as u8) << 3)
            | (self.vc_id >> 3) & 0b111;
        buf[3] = ((self.vc_id & 0b111) << 5) | (self.map_id << 1);
        buf[4..6].copy_from_slice(&self.frame_len_field.to_be_bytes());
        buf[6] = ((self.sequence_control_flag as u8) << 7)
            | ((self.protocol_control_command_flag as u8) << 6)
            | ((self.ocf_flag as u8) << 3)
            | self.vc_frame_count_len;
        let mut packet_idx = 7;
        for idx in (0..self.vc_frame_count_len).rev() {
            buf[packet_idx] = ((self.vc_frame_count >> (idx * 8)) & 0xff) as u8;
            packet_idx += 1;
        }
        Ok(self.len_header())
    }

    #[inline(always)]
    pub fn set_frame_len(&mut self, frame_len: usize) {
        // 4.1.2.7.2
        // The field contains a length count C that equals one fewer than the total octets
        // in the transfer frame.
        self.frame_len_field = frame_len.saturating_sub(1) as u16;
    }

    #[inline(always)]
    pub fn len_header(&self) -> usize {
        7 + self.vc_frame_count_len as usize
    }

    #[inline(always)]
    pub fn len_frame(&self) -> usize {
        // 4.1.2.7.2
        // The field contains a length count C that equals one fewer than the total octets
        // in the transfer frame.
        self.frame_len_field as usize + 1
    }
}

/// Custom implementation which skips the check whether the VC frame count length field is equal.
/// Only the actual VC count value is compared.
impl PartialEq for PrimaryHeader {
    fn eq(&self, other: &Self) -> bool {
        self.spacecraft_id == other.spacecraft_id
            && self.source_or_dest_field == other.source_or_dest_field
            && self.vc_id == other.vc_id
            && self.map_id == other.map_id
            && self.frame_len_field == other.frame_len_field
            && self.sequence_control_flag == other.sequence_control_flag
            && self.protocol_control_command_flag == other.protocol_control_command_flag
            && self.ocf_flag == other.ocf_flag
            && self.vc_frame_count == other.vc_frame_count
    }
}

#[derive(Debug, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u5, exhaustive = false)]
#[repr(u8)]
#[non_exhaustive]
pub enum UslpProtocolId {
    SpacePacketsOrEncapsulation = 0b00000,
    /// COP-1 control commands within the TFDZ.
    Cop1ControlCommands = 0b00001,
    /// COP-P control commands within the TFDZ.
    CopPControlCommands = 0b00010,
    /// SDLS control commands within the TFDZ.
    Sdls = 0b00011,
    UserDefinedOctetStream = 0b00100,
    /// Proximity-1 Supervisory Protocol Data Units (SPDUs) within the TFDZ.
    Spdu = 0b00111,
    /// Entire fixed-length TFDZ contains idle data.
    Idle = 0b11111,
}

#[derive(Debug, PartialEq, Eq, num_enum::TryFromPrimitive, num_enum::IntoPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[bitbybit::bitenum(u3, exhaustive = true)]
#[repr(u8)]
pub enum ConstructionRule {
    /// Indicated fixed-length TFDZ whose contents are CCSDS packets concatenated together, which
    /// span transfer frame boundaries. The First Header Pointer (FHP) is required for packet
    /// extraction.
    PacketSpanningMultipleFrames = 0b000,
    StartOfMapaSduOrVcaSdu = 0b001,
    ContinuingPortionOfMapaSdu = 0b010,
    OctetStream = 0b011,
    StartingSegment = 0b100,
    ContinuingSegment = 0b101,
    LastSegment = 0b110,
    NoSegmentation = 0b111,
}

impl ConstructionRule {
    pub const fn applicable_to_fixed_len_tfdz(&self) -> bool {
        match self {
            ConstructionRule::PacketSpanningMultipleFrames => true,
            ConstructionRule::StartOfMapaSduOrVcaSdu => true,
            ConstructionRule::ContinuingPortionOfMapaSdu => true,
            ConstructionRule::OctetStream => false,
            ConstructionRule::StartingSegment => false,
            ConstructionRule::ContinuingSegment => false,
            ConstructionRule::LastSegment => false,
            ConstructionRule::NoSegmentation => false,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TransferFrameDataFieldHeader {
    /// Construction rule for the TFDZ.
    construction_rule: ConstructionRule,
    uslp_protocol_id: UslpProtocolId,
    /// First header or last valid octet pointer. Only present if the constuction rule indicated
    /// a fixed-length TFDZ.
    fhp_or_lvo: Option<u16>,
}

impl TransferFrameDataFieldHeader {
    #[inline]
    pub const fn len_header(&self) -> usize {
        if self.construction_rule.applicable_to_fixed_len_tfdz() {
            3
        } else {
            1
        }
    }

    #[inline]
    pub const fn construction_rule(&self) -> ConstructionRule {
        self.construction_rule
    }

    #[inline]
    pub const fn uslp_protocol_id(&self) -> UslpProtocolId {
        self.uslp_protocol_id
    }

    #[inline]
    pub const fn fhp_or_lvo(&self) -> Option<u16> {
        self.fhp_or_lvo
    }

    pub fn from_bytes(buf: &[u8]) -> Result<Self, UslpError> {
        if buf.is_empty() {
            return Err(ByteConversionError::FromSliceTooSmall {
                found: 0,
                expected: 1,
            }
            .into());
        }
        let construction_rule = ConstructionRule::try_from((buf[0] >> 5) & 0b111)
            .map_err(|e| UslpError::InvalidConstructionRule(e.number))?;
        let mut fhp_or_lvo = None;
        if construction_rule.applicable_to_fixed_len_tfdz() {
            if buf.len() < 3 {
                return Err(ByteConversionError::FromSliceTooSmall {
                    found: buf.len(),
                    expected: 3,
                }
                .into());
            }
            fhp_or_lvo = Some(u16::from_be_bytes(buf[1..3].try_into().unwrap()));
        }
        Ok(Self {
            construction_rule,
            uslp_protocol_id: UslpProtocolId::try_from(buf[0] & 0b11111)
                .map_err(|e| UslpError::InvalidProtocolId(e.number))?,
            fhp_or_lvo,
        })
    }
}

/// Simple USLP transfer frame reader.
///
/// Currently, only insert zone lengths of 0 are supported.
#[derive(Debug)]
pub struct TransferFrameReader<'buf> {
    primary_header: PrimaryHeader,
    data_field_header: TransferFrameDataFieldHeader,
    data: &'buf [u8],
    operational_control_field: Option<u32>,
}

impl<'buf> TransferFrameReader<'buf> {
    /// This function assumes an insert zone length of 0.
    pub fn from_bytes(
        buf: &'buf [u8],
        has_fecf: bool,
    ) -> Result<TransferFrameReader<'buf>, UslpError> {
        let primary_header = PrimaryHeader::from_bytes(buf)?;
        if primary_header.len_frame() > buf.len() {
            return Err(ByteConversionError::FromSliceTooSmall {
                expected: primary_header.len_frame(),
                found: buf.len(),
            }
            .into());
        }
        let data_field_header =
            TransferFrameDataFieldHeader::from_bytes(&buf[primary_header.len_header()..])?;
        let data_idx = primary_header.len_header() + data_field_header.len_header();
        let frame_len = primary_header.len_frame();
        let mut operational_control_field = None;
        let mut data_len = frame_len - data_idx;
        if has_fecf {
            data_len -= 2;
        }
        if primary_header.ocf_flag {
            data_len -= 4;
            operational_control_field = Some(u32::from_be_bytes(
                buf[data_idx + data_len..data_idx + data_len + 4]
                    .try_into()
                    .unwrap(),
            ));
        }
        let data_end = data_idx + data_len;
        if has_fecf {
            let mut digest = CRC_CCITT_FALSE.digest();
            digest.update(&buf[0..frame_len]);
            if digest.finalize() != 0 {
                return Err(UslpError::ChecksumFailure(u16::from_be_bytes(
                    buf[frame_len - 2..frame_len].try_into().unwrap(),
                )));
            }
        }
        Ok(Self {
            primary_header,
            data_field_header,
            data: buf[data_idx..data_end].try_into().unwrap(),
            operational_control_field,
        })
    }

    #[inline]
    pub fn len_frame(&self) -> usize {
        self.primary_header.len_frame()
    }

    #[inline]
    pub fn primary_header(&self) -> &PrimaryHeader {
        &self.primary_header
    }

    #[inline]
    pub fn data_field_header(&self) -> &TransferFrameDataFieldHeader {
        &self.data_field_header
    }

    #[inline]
    pub fn data(&self) -> &'buf [u8] {
        self.data
    }

    #[inline]
    pub fn operational_control_field(&self) -> &Option<u32> {
        &self.operational_control_field
    }
}

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

    fn common_basic_check(buf: &[u8]) {
        assert_eq!(buf[0] >> 4, USLP_VERSION_NUMBER);
        // First four bits SCID.
        assert_eq!(buf[0] & 0b1111, 0b1010);
        // Next eight bits SCID.
        assert_eq!(buf[1], 0b01011100);
        // Last four bits SCID.
        assert_eq!(buf[2] >> 4, 0b0011);
        assert_eq!((buf[2] >> 3) & 0b1, SourceOrDestField::Dest as u8);
        // First three bits VCID.
        assert_eq!(buf[2] & 0b111, 0b110);
        // Last three bits VCID.
        assert_eq!(buf[3] >> 5, 0b101);
        // MAP ID
        assert_eq!((buf[3] >> 1) & 0b1111, 0b1010);
        // End of primary header flag
        assert_eq!(buf[3] & 0b1, 0);
        assert_eq!(u16::from_be_bytes(buf[4..6].try_into().unwrap()), 0x2344);
    }

    #[test]
    fn test_basic_0() {
        let mut buf: [u8; 8] = [0; 8];
        // Should be all zeros after writing.
        buf[6] = 0xff;
        let primary_header = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b110101,
            0b1010,
            0x2345,
        )
        .unwrap();
        // Virtual channel count 0.
        assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 7);
        common_basic_check(&buf);
        assert_eq!(primary_header.vc_frame_count_len(), 0);
        assert_eq!(primary_header.vc_frame_count(), 0);
        // Bypass / Sequence Control Flag.
        assert_eq!(
            (buf[6] >> 7) & 0b1,
            BypassSequenceControlFlag::SequenceControlledQoS as u8
        );
        // Protocol Control Command Flag.
        assert_eq!(
            (buf[6] >> 6) & 0b1,
            ProtocolControlCommandFlag::TfdfContainsUserData as u8
        );
        // OCF flag.
        assert_eq!((buf[6] >> 3) & 0b1, false as u8);
        // VCF count length.
        assert_eq!(buf[6] & 0b111, 0);
    }

    #[test]
    fn test_basic_1() {
        let mut buf: [u8; 16] = [0; 16];
        // Should be all zeros after writing.
        buf[6] = 0xff;
        let mut primary_header = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b110101,
            0b1010,
            0x2345,
        )
        .unwrap();
        primary_header.sequence_control_flag = BypassSequenceControlFlag::ExpeditedQoS;
        primary_header.protocol_control_command_flag =
            ProtocolControlCommandFlag::TfdfContainsProtocolInfo;
        primary_header.ocf_flag = true;
        primary_header.set_vc_frame_count(4, 0x12345678).unwrap();
        // Virtual channel count 4.
        assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 11);
        assert_eq!(primary_header.vc_frame_count_len(), 4);
        assert_eq!(primary_header.vc_frame_count(), 0x12345678);
        common_basic_check(&buf);
        // Bypass / Sequence Control Flag.
        assert_eq!(
            (buf[6] >> 7) & 0b1,
            BypassSequenceControlFlag::ExpeditedQoS as u8
        );
        // Protocol Control Command Flag.
        assert_eq!(
            (buf[6] >> 6) & 0b1,
            ProtocolControlCommandFlag::TfdfContainsProtocolInfo as u8
        );
        // OCF flag.
        assert_eq!((buf[6] >> 3) & 0b1, true as u8);
        // VCF count length.
        assert_eq!(buf[6] & 0b111, 4);
        assert_eq!(
            u32::from_be_bytes(buf[7..11].try_into().unwrap()),
            0x12345678
        );
    }

    #[test]
    fn test_vcf_count_len_two() {
        let mut buf: [u8; 16] = [0; 16];
        // Should be all zeros after writing.
        buf[6] = 0xff;
        let mut primary_header = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b110101,
            0b1010,
            0x2345,
        )
        .unwrap();
        primary_header.set_vc_frame_count(2, 5).unwrap();
        assert_eq!(primary_header.vc_frame_count_len(), 2);
        assert_eq!(primary_header.vc_frame_count(), 5);
        assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 9);
        assert_eq!(buf[6] & 0b111, 2);
        assert_eq!(u16::from_be_bytes(buf[7..9].try_into().unwrap()), 5);

        let primary_header = PrimaryHeader::from_bytes(&buf).unwrap();
        assert_eq!(primary_header.vc_frame_count_len(), 2);
        assert_eq!(primary_header.vc_frame_count(), 5);
    }

    #[test]
    fn test_vcf_count_len_one() {
        let mut buf: [u8; 16] = [0; 16];
        // Should be all zeros after writing.
        buf[6] = 0xff;
        let mut primary_header = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b110101,
            0b1010,
            0x2345,
        )
        .unwrap();
        primary_header.set_vc_frame_count(1, 255).unwrap();
        assert_eq!(primary_header.vc_frame_count_len(), 1);
        assert_eq!(primary_header.vc_frame_count(), 255);
        assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 8);
        assert_eq!(buf[6] & 0b111, 1);
        assert_eq!(buf[7], 255);

        let primary_header = PrimaryHeader::from_bytes(&buf).unwrap();
        assert_eq!(primary_header.vc_frame_count_len(), 1);
        assert_eq!(primary_header.vc_frame_count(), 255);
    }

    #[test]
    fn test_reading_0() {
        let mut buf: [u8; 8] = [0; 8];
        let primary_header = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b110101,
            0b1010,
            0x2345,
        )
        .unwrap();
        assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 7);
        let parsed_header = PrimaryHeader::from_bytes(&buf).unwrap();
        assert_eq!(parsed_header, primary_header);
    }

    #[test]
    fn test_reading_1() {
        let mut buf: [u8; 16] = [0; 16];
        let mut primary_header = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b110101,
            0b1010,
            0x2345,
        )
        .unwrap();
        primary_header.sequence_control_flag = BypassSequenceControlFlag::ExpeditedQoS;
        primary_header.protocol_control_command_flag =
            ProtocolControlCommandFlag::TfdfContainsProtocolInfo;
        primary_header.ocf_flag = true;
        primary_header.set_vc_frame_count(4, 0x12345678).unwrap();
        assert_eq!(primary_header.write_to_be_bytes(&mut buf).unwrap(), 11);
        let parsed_header = PrimaryHeader::from_bytes(&buf).unwrap();
        assert_eq!(parsed_header, primary_header);
    }

    #[test]
    fn test_invalid_vcid() {
        let error = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b1101011,
            0b1010,
            0x2345,
        );
        assert!(error.is_err());
        let error = error.unwrap_err();
        matches!(error, UslpError::InvalidVcId(0b1101011));
    }

    #[test]
    fn test_invalid_mapid() {
        let error = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b110101,
            0b10101,
            0x2345,
        );
        assert!(error.is_err());
        let error = error.unwrap_err();
        matches!(error, UslpError::InvalidMapId(0b10101));
    }

    #[test]
    fn test_invalid_vc_count() {
        let mut primary_header = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b110101,
            0b1010,
            0x2345,
        )
        .unwrap();
        matches!(
            primary_header.set_vc_frame_count(0, 1).unwrap_err(),
            InvalidValueForLen { value: 1, len: 0 }
        );
        matches!(
            primary_header.set_vc_frame_count(1, 256).unwrap_err(),
            InvalidValueForLen { value: 256, len: 1 }
        );
    }

    #[test]
    fn test_frame_parser() {
        let mut buf: [u8; 32] = [0; 32];
        // Build a variable frame manually.
        let mut primary_header =
            PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap();
        let header_len = primary_header.len_header();
        buf[header_len] = ((ConstructionRule::NoSegmentation as u8) << 5)
            | (UslpProtocolId::UserDefinedOctetStream as u8) & 0b11111;
        buf[header_len + 1] = 0x42;
        // 1 byte TFDH, 1 byte data, 2 bytes CRC.
        primary_header.set_frame_len(header_len + 4);
        primary_header.write_to_be_bytes(&mut buf).unwrap();
        // Calculate and write CRC16.
        let mut digest = CRC_CCITT_FALSE.digest();
        digest.update(&buf[0..header_len + 2]);
        buf[header_len + 2..header_len + 4].copy_from_slice(&digest.finalize().to_be_bytes());
        // Now parse the frame.
        let frame = TransferFrameReader::from_bytes(&buf, true).unwrap();
        assert_eq!(*frame.primary_header(), primary_header);
        assert_eq!(frame.data().len(), 1);
        assert_eq!(frame.data()[0], 0x42);
        assert_eq!(
            frame.data_field_header().uslp_protocol_id(),
            UslpProtocolId::UserDefinedOctetStream
        );
        assert_eq!(
            frame.data_field_header().construction_rule(),
            ConstructionRule::NoSegmentation
        );
        assert!(frame.data_field_header().fhp_or_lvo().is_none());
        assert_eq!(frame.len_frame(), 11);
        assert!(frame.operational_control_field().is_none());
    }

    #[test]
    fn test_frame_parser_invalid_checksum() {
        let mut buf: [u8; 32] = [0; 32];
        // Build a variable frame manually.
        let mut primary_header =
            PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap();
        let header_len = primary_header.len_header();
        buf[header_len] = ((ConstructionRule::NoSegmentation as u8) << 5)
            | (UslpProtocolId::UserDefinedOctetStream as u8) & 0b11111;
        buf[header_len + 1] = 0x42;
        // 1 byte TFDH, 1 byte data, 2 bytes CRC.
        primary_header.set_frame_len(header_len + 4);
        primary_header.write_to_be_bytes(&mut buf).unwrap();
        // Now parse the frame without having calculated the checksum.
        match TransferFrameReader::from_bytes(&buf, true) {
            Ok(_) => panic!("transfer frame read call did not fail"),
            Err(e) => {
                assert_eq!(e, UslpError::ChecksumFailure(0));
            }
        }
    }

    #[test]
    fn test_frame_parser_buf_too_small() {
        let mut buf: [u8; 32] = [0; 32];
        // Build a variable frame manually.
        let mut primary_header =
            PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap();
        let header_len = primary_header.len_header();
        buf[header_len] = ((ConstructionRule::NoSegmentation as u8) << 5)
            | (UslpProtocolId::UserDefinedOctetStream as u8) & 0b11111;
        buf[header_len + 1] = 0x42;
        // 1 byte TFDH, 1 byte data, 2 bytes CRC.
        primary_header.set_frame_len(header_len + 4);
        primary_header.write_to_be_bytes(&mut buf).unwrap();
        // Now parse the frame.
        let error = TransferFrameReader::from_bytes(&buf[0..7], true).unwrap_err();
        assert_eq!(
            error,
            ByteConversionError::FromSliceTooSmall {
                expected: primary_header.len_frame(),
                found: 7
            }
            .into()
        );
    }

    #[test]
    fn test_from_bytes_too_small_0() {
        let buf: [u8; 3] = [0; 3];
        assert_eq!(
            PrimaryHeader::from_bytes(&buf).unwrap_err(),
            ByteConversionError::FromSliceTooSmall {
                found: 3,
                expected: 4
            }
            .into()
        );
    }

    #[test]
    fn test_from_bytes_too_small_1() {
        let buf: [u8; 6] = [0; 6];
        assert_eq!(
            PrimaryHeader::from_bytes(&buf).unwrap_err(),
            ByteConversionError::FromSliceTooSmall {
                found: 6,
                expected: 7
            }
            .into()
        );
    }

    #[test]
    fn test_from_bytes_truncated_not_supported() {
        let mut buf: [u8; 7] = [0; 7];
        let primary_header =
            PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap();
        primary_header.write_to_be_bytes(&mut buf).unwrap();
        // Set truncated header flag manually.
        buf[3] |= 0b1;
        assert_eq!(
            PrimaryHeader::from_bytes(&buf).unwrap_err(),
            UslpError::HeaderIsTruncated
        );
    }

    #[test]
    fn test_from_bytes_too_small_2() {
        let mut buf: [u8; 16] = [0; 16];
        // Should be all zeros after writing.
        buf[6] = 0xff;
        let mut primary_header = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b110101,
            0b1010,
            0x2345,
        )
        .unwrap();
        primary_header.set_vc_frame_count(4, 0x12345678).unwrap();
        primary_header.write_to_be_bytes(&mut buf).unwrap();

        assert_eq!(
            PrimaryHeader::from_bytes(&buf[0..8]).unwrap_err(),
            UslpError::ByteConversion(ByteConversionError::FromSliceTooSmall {
                found: 8,
                expected: 11
            })
        );
    }

    #[test]
    fn test_invalid_version_number() {
        let mut buf: [u8; 7] = [0; 7];
        let primary_header =
            PrimaryHeader::new(0x01, SourceOrDestField::Dest, 0b110101, 0b1010, 0).unwrap();
        primary_header.write_to_be_bytes(&mut buf).unwrap();
        buf[0] &= 0b00001111;
        assert_eq!(
            PrimaryHeader::from_bytes(&buf).unwrap_err(),
            UslpError::InvalidVersionNumber(0)
        );
    }

    #[test]
    fn test_primary_header_buf_too_small() {
        let primary_header = PrimaryHeader::new(
            0b10100101_11000011,
            SourceOrDestField::Dest,
            0b110101,
            0b1010,
            0x2345,
        )
        .unwrap();
        if let Err(ByteConversionError::ToSliceTooSmall { found, expected }) =
            primary_header.write_to_be_bytes(&mut [0; 4])
        {
            assert_eq!(found, 4);
            assert_eq!(expected, 7);
        } else {
            panic!("writing primary header did not fail or failed with wrong error");
        }
    }

    #[test]
    fn test_applicability_contr_rules() {
        assert!(ConstructionRule::PacketSpanningMultipleFrames.applicable_to_fixed_len_tfdz());
        assert!(ConstructionRule::StartOfMapaSduOrVcaSdu.applicable_to_fixed_len_tfdz());
        assert!(ConstructionRule::ContinuingPortionOfMapaSdu.applicable_to_fixed_len_tfdz());
        assert!(!ConstructionRule::OctetStream.applicable_to_fixed_len_tfdz());
        assert!(!ConstructionRule::StartingSegment.applicable_to_fixed_len_tfdz());
        assert!(!ConstructionRule::ContinuingSegment.applicable_to_fixed_len_tfdz());
        assert!(!ConstructionRule::LastSegment.applicable_to_fixed_len_tfdz());
        assert!(!ConstructionRule::NoSegmentation.applicable_to_fixed_len_tfdz());
    }

    #[test]
    fn test_header_len_correctness() {
        let mut tfdh = TransferFrameDataFieldHeader {
            construction_rule: ConstructionRule::PacketSpanningMultipleFrames,
            uslp_protocol_id: UslpProtocolId::UserDefinedOctetStream,
            fhp_or_lvo: Some(0),
        };
        assert_eq!(tfdh.len_header(), 3);
        tfdh = TransferFrameDataFieldHeader {
            construction_rule: ConstructionRule::StartOfMapaSduOrVcaSdu,
            uslp_protocol_id: UslpProtocolId::UserDefinedOctetStream,
            fhp_or_lvo: Some(0),
        };
        assert_eq!(tfdh.len_header(), 3);
        tfdh = TransferFrameDataFieldHeader {
            construction_rule: ConstructionRule::ContinuingPortionOfMapaSdu,
            uslp_protocol_id: UslpProtocolId::UserDefinedOctetStream,
            fhp_or_lvo: Some(0),
        };
        assert_eq!(tfdh.len_header(), 3);
        tfdh = TransferFrameDataFieldHeader {
            construction_rule: ConstructionRule::OctetStream,
            uslp_protocol_id: UslpProtocolId::UserDefinedOctetStream,
            fhp_or_lvo: None,
        };
        assert_eq!(tfdh.len_header(), 1);
    }

    #[test]
    fn test_frame_data_field_header_from_bytes_too_small() {
        let buf: [u8; 0] = [];
        assert_eq!(
            TransferFrameDataFieldHeader::from_bytes(&buf).unwrap_err(),
            ByteConversionError::FromSliceTooSmall {
                found: 0,
                expected: 1
            }
            .into()
        );
    }
}
