rpki-rtr 0.2.1

A library for RPKI RTR.
Documentation
//! Raw protocol data.
//!
//! This module contains types that represent the protocol data units of
//! RTR in their wire representation. That is, these types can be given to
//! read and write operations as buffers.  See section 5 of RFC 6810 and
//! RFC 8210. Annoyingly, the format of the `EndOfData` PDU changes between
//! the two versions.

use std::{io, mem, slice};
use std::convert::TryFrom;
use std::marker::Unpin;
use std::net::{Ipv4Addr, Ipv6Addr};
use tokio::io::{
    AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt
};
use crate::payload;
use super::state::{Serial, State};


//------------ Macro for Common Impls ----------------------------------------

macro_rules! common {
    ( $type:ident ) => {
        #[allow(dead_code)]
        impl $type {
            /// Writes a value to a writer.
            pub async fn write<A: AsyncWrite + Unpin>(
                &self,
                a: &mut A
            ) -> Result<(), io::Error> {
                a.write_all(self.as_ref()).await
            }
        }

        impl AsRef<[u8]> for $type {
            fn as_ref(&self) -> &[u8] {
                unsafe {
                    slice::from_raw_parts(
                        self as *const Self as *const u8,
                        mem::size_of::<Self>()
                    )
                }
            }
        }

        impl AsMut<[u8]> for $type {
            fn as_mut(&mut self) -> &mut [u8] {
                unsafe {
                    slice::from_raw_parts_mut(
                        self as *mut Self as *mut u8,
                        mem::size_of::<Self>()
                    )
                }
            }
        }
    }
}

macro_rules! concrete {
    ( $type:ident ) => {
        common!($type);

        #[allow(dead_code)]
        impl $type {
            /// Returns the value of the version field of the header.
            pub fn version(&self) -> u8 {
                self.header.version()
            }

            /// Returns the value of the session field of the header.
            ///
            /// Note that this field is used for other purposes in some PDU
            /// types.
            pub fn session(&self) -> u16 {
                self.header.session()
            }

            /// Returns the PDU size.
            ///
            /// The size is returned as a `u32` since that type is used in
            /// the header.
            pub fn size() -> u32 {
                mem::size_of::<Self>() as u32
            }

            /// Reads a value from a reader.
            ///
            /// If a value with a different PDU type is received, returns an
            /// error.
            pub async fn read<Sock: AsyncRead + Unpin>(
                sock: &mut Sock 
            ) -> Result<Self, io::Error> {
                let mut res = Self::default();
                sock.read_exact(res.header.as_mut()).await?;
                if res.header.pdu() != Self::PDU {
                    return Err(io::Error::new(
                        io::ErrorKind::InvalidData,
                        concat!(
                            "PDU type mismatch when expecting ",
                            stringify!($type)
                        )
                    ))
                }
                if res.header.length() as usize != res.as_ref().len() {
                    return Err(io::Error::new(
                        io::ErrorKind::InvalidData,
                        concat!(
                            "invalid length for ",
                            stringify!($type)
                        )
                    ))
                }
                sock.read_exact(&mut res.as_mut()[Header::LEN..]).await?;
                Ok(res)
            }

            /// Tries to read a value from a reader.
            ///
            /// If a different PDU type is received, returns the header as
            /// the error case of the ok case.
            pub async fn try_read<Sock: AsyncRead + Unpin>(
                sock: &mut Sock 
            ) -> Result<Result<Self, Header>, io::Error> {
                let mut res = Self::default();
                sock.read_exact(res.header.as_mut()).await?;
                if res.header.pdu() == Error::PDU {
                    // Since we should drop the session after an error, we
                    // can safely ignore all the rest of the error for now.
                    return Ok(Err(res.header))
                }
                if res.header.pdu() != Self::PDU {
                    return Err(io::Error::new(
                        io::ErrorKind::InvalidData,
                        concat!(
                            "PDU type mismatch when expecting ",
                            stringify!($type)
                        )
                    ))
                }
                if res.header.length() as usize != res.as_ref().len() {
                    return Err(io::Error::new(
                        io::ErrorKind::InvalidData,
                        concat!(
                            "invalid length for ",
                            stringify!($type)
                        )
                    ))
                }
                sock.read_exact(&mut res.as_mut()[Header::LEN..]).await?;
                Ok(Ok(res))
            }

            /// Reads only the payload part of a value from a reader.
            ///
            /// Assuming that the header was already read and is passed via
            /// `header`, the function reads the rest of the PUD from the
            /// reader and returns the complete value.
            pub async fn read_payload<Sock: AsyncRead + Unpin>(
                header: Header, sock: &mut Sock
            ) -> Result<Self, io::Error> {
                if header.length() as usize != mem::size_of::<Self>() {
                    return Err(io::Error::new(
                        io::ErrorKind::InvalidData,
                        concat!(
                            "invalid length for ",
                            stringify!($type),
                            " PDU"
                        )
                    ))
                }
                let mut res = Self::default();
                sock.read_exact(&mut res.as_mut()[Header::LEN..]).await?;
                res.header = header;
                Ok(res)
            }
        }
    }
}


//------------ SerialNotify --------------------------------------------------

/// A serial notify informs a client that a cache has new data.
#[derive(Default)]
#[repr(packed)]
#[allow(dead_code)]
pub struct SerialNotify {
    header: Header,
    serial: u32,
}

impl SerialNotify {
    /// The PDU type of a serial notify.
    pub const PDU: u8 = 0;

    /// Creates a new serial notify PDU.
    pub fn new(version: u8, state: State) -> Self {
        SerialNotify {
            header: Header::new(
                version, Self::PDU, state.session(), Self::size(),
            ),
            serial: state.serial().to_be(),
        }
    }
}

concrete!(SerialNotify);


//------------ SerialQuery ---------------------------------------------------

/// A serial query requests all updates since a router’s last update.
#[derive(Default)]
#[repr(packed)]
#[allow(dead_code)]
pub struct SerialQuery {
    header: Header,
    payload: SerialQueryPayload,
}

impl SerialQuery {
    /// The payload type of a serial query.
    pub const PDU: u8 = 1;

    /// Creates a new serial query from the given state.
    pub fn new(version: u8, state: State) -> Self {
        SerialQuery {
            header: Header::new(
                version, Self::PDU, state.session(), Self::size()
            ),
            payload: SerialQueryPayload::new(state.serial()),
        }
    }
}

concrete!(SerialQuery);


//------------ SerialQueryPayload --------------------------------------------

/// The payload of a serial query.
///
/// This the serial query PDU without the header.
#[derive(Default)]
#[repr(packed)]
pub struct SerialQueryPayload {
    serial: u32
}

impl SerialQueryPayload {
    /// Creates a new serial query payload from a serial number.
    pub fn new(serial: Serial) -> Self {
        SerialQueryPayload {
            serial: serial.to_be()
        }
    }

    /// Reads the serial query payload from a reader.
    pub async fn read<Sock: AsyncRead + Unpin>(
        sock: &mut Sock 
    ) -> Result<Self, io::Error> {
        let mut res = Self::default();
        sock.read_exact(res.as_mut()).await?;
        Ok(res)
    }

    /// Returns the router’s serial number announced in the serial query.
    pub fn serial(&self) -> Serial {
        Serial::from_be(self.serial)
    }
}

common!(SerialQueryPayload);


//------------ ResetQuery ----------------------------------------------------

/// A reset query requests the complete current set of data.
#[derive(Default)]
#[repr(packed)]
pub struct ResetQuery {
    header: Header
}

impl ResetQuery {
    /// The payload type of a reset query.
    pub const PDU: u8 = 2;

    /// Creates a new reset query.
    pub fn new(version: u8) -> Self {
        ResetQuery {
            header: Header::new(version, 2, 0, 8)
        }
    }
}

concrete!(ResetQuery);


//------------ CacheResponse -------------------------------------------------

/// The cache response starts a sequence of payload PDUs with data.
#[derive(Default)]
#[repr(packed)]
pub struct CacheResponse {
    header: Header
}

impl CacheResponse {
    /// The payload type of a cache response.
    pub const PDU: u8 = 3;

    /// Creates a new cache response for the given state.
    pub fn new(version: u8, state: State) -> Self {
        CacheResponse {
            header: Header::new(version, 3, state.session(), 8)
        }
    }
}

concrete!(CacheResponse);


//------------ Ipv4Prefix ----------------------------------------------------

/// An IPv4 prefix is the payload PDU for route origin authorisation in IPv4.
#[derive(Default)]
#[repr(packed)]
#[allow(dead_code)]
pub struct Ipv4Prefix {
    header: Header,
    flags: u8,
    prefix_len: u8,
    max_len: u8,
    zero: u8,
    prefix: u32,
    asn: u32
}

impl Ipv4Prefix {
    /// The payload type of an IPv4 prefix.
    pub const PDU: u8 = 4;

    /// Creates a new IPv4 prefix from all the various fields.
    pub fn new(
        version: u8,
        flags: u8,
        prefix_len: u8,
        max_len: u8,
        prefix: Ipv4Addr,
        asn: u32
    ) -> Self {
        Ipv4Prefix {
            header: Header::new(version, Self::PDU, 0, 20),
            flags,
            prefix_len,
            max_len,
            zero: 0,
            prefix: u32::from(prefix).to_be(),
            asn: asn.to_be()
        }
    }

    /// Returns the flags field of the prefix.
    ///
    /// The only flag currently used is the least significant but that is
    /// 1 for an announcement and 0 for a withdrawal.
    pub fn flags(&self) -> u8 {
        self.flags
    }

    /// Returns the prefix length.
    pub fn prefix_len(&self) -> u8 {
        self.prefix_len
    }

    /// Returns the max length.
    pub fn max_len(&self) -> u8 {
        self.max_len
    }

    /// Returns the prefix as IPv4 address.
    pub fn prefix(&self) -> Ipv4Addr {
        u32::from_be(self.prefix).into()
    }

    /// Returns the autonomous system number.
    pub fn asn(&self) -> u32 {
        u32::from_be(self.asn)
    }
}

concrete!(Ipv4Prefix);


//------------ Ipv6Prefix ----------------------------------------------------

/// An IPv6 prefix is the payload PDU for route origin authorisation in IPv46.
#[derive(Default)]
#[repr(packed)]
#[allow(dead_code)]
pub struct Ipv6Prefix {
    header: Header,
    flags: u8,
    prefix_len: u8,
    max_len: u8,
    zero: u8,
    prefix: u128,
    asn: u32,
}

impl Ipv6Prefix {
    /// The payload type of an IPv6 prefix.
    pub const PDU: u8 = 6;

    /// Creates a new IPv6 prefix from all the various fields.
    pub fn new(
        version: u8,
        flags: u8,
        prefix_len: u8,
        max_len: u8,
        prefix: Ipv6Addr,
        asn: u32
    ) -> Self {
        Ipv6Prefix {
            header: Header::new(version, Self::PDU, 0, 32),
            flags,
            prefix_len,
            max_len,
            zero: 0,
            prefix: u128::from(prefix).to_be(),
            asn: asn.to_be()
        }
    }

    /// Returns the flags field of the prefix.
    ///
    /// The only flag currently used is the least significant but that is
    /// 1 for an announcement and 0 for a withdrawal.
    pub fn flags(&self) -> u8 {
        self.flags
    }

    /// Returns the prefix length.
    pub fn prefix_len(&self) -> u8 {
        self.prefix_len
    }

    /// Returns the max length.
    pub fn max_len(&self) -> u8 {
        self.max_len
    }

    /// Returns the prefix as an IPv6 address.
    pub fn prefix(&self) -> Ipv6Addr {
        u128::from_be(self.prefix).into()
    }

    /// Returns the autonomous system number.
    pub fn asn(&self) -> u32 {
        u32::from_be(self.asn)
    }
}

concrete!(Ipv6Prefix);


//------------ Payload -------------------------------------------------------

/// All possible payload types.
pub enum Payload {
    /// An IPv4 prefix.
    V4(Ipv4Prefix),

    /// An IPv6 prefix.
    V6(Ipv6Prefix),
}

impl Payload {
    /// Creates an payload value for the given payload.
    pub fn new(version: u8, flags: u8, payload: payload::Payload) -> Self {
        match payload {
            payload::Payload::V4(prefix) => {
                Payload::V4(
                    Ipv4Prefix::new(
                        version,
                        flags,
                        prefix.prefix_len,
                        prefix.max_len,
                        prefix.prefix,
                        prefix.asn
                    )
                )
            }
            payload::Payload::V6(prefix) => {
                Payload::V6(
                    Ipv6Prefix::new(
                        version,
                        flags,
                        prefix.prefix_len,
                        prefix.max_len,
                        prefix.prefix,
                        prefix.asn
                    )
                )
            }
        }
    }

    /// Reads a payload PDU from a reader.
    ///
    /// The return type is a little convoluted, but hey. The method returns
    /// `Ok(Ok(Some(payload)))` if reading went well and there was a payload
    /// PDU that we support. It returns `Ok(Ok(None))`, if reading went well
    /// and there was a payload PDU but we don’t actually support it. If
    /// reading went well but we received an end-of-data PDU, it will be
    /// returned as `Ok(Err(eod))`. If reading fails or any other PDU is
    /// received, an error is returned.
    ///
    /// The reason we are just not returning unsupported payload types is that
    /// router keys are variable length and we would need to allocate data.
    /// Which is a bit wasteful if we then just proceed to throw it away.
    pub async fn read<Sock: AsyncRead + Unpin>(
        sock: &mut Sock
    ) -> Result<Result<Option<Self>, EndOfData>, io::Error> {
        let header = Header::read(sock).await?;
        match header.pdu {
            Ipv4Prefix::PDU => {
                Ipv4Prefix::read_payload(header, sock).await.map(|res| {
                    Ok(Some(Payload::V4(res)))
                })
            }
            Ipv6Prefix::PDU => {
                Ipv6Prefix::read_payload(header, sock).await.map(|res| {
                    Ok(Some(Payload::V6(res)))
                })
            }
            9 => { // Router Key
                // Let’s skip over this in blocks of one 1k.
                let mut buf = [0u8; 1024];
                let mut len = header.length() as usize;
                while len > 1024 {
                    sock.read_exact(&mut buf).await?;
                    len -= 1024;
                }
                sock.read_exact(&mut buf[..len]).await?;
                Ok(Ok(None))
            }
            EndOfData::PDU => {
                EndOfData::read_payload(header, sock).await.map(Err)
            }
            _ => {
                Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    "unexpected PDU in payload sequence"
                ))
            }
        }
    }

    /// Returns the RTR version of the payload PDU.
    pub fn version(&self) -> u8 {
        match *self {
            Payload::V4(ref data) => data.version(),
            Payload::V6(ref data) => data.version(),
        }
    }

    /// Returns the flags of the payload PDU.
    pub fn flags(&self) -> u8 {
        match *self {
            Payload::V4(ref data) => data.flags(),
            Payload::V6(ref data) => data.flags(),
        }
    }

    /// Writes the payload PDU to a writer.
    pub async fn write<A: AsyncWrite + Unpin>(
        &self,
        a: &mut A
    ) -> Result<(), io::Error> {
        a.write_all(self.as_ref()).await
    }

    /// Converts the payload PDU into action and payload.
    pub fn to_payload(&self) -> (payload::Action, payload::Payload) {
        (
            payload::Action::from_flags(self.flags()),
            match self {
                Payload::V4(data) => {
                    payload::Payload::V4(payload::Ipv4Prefix {
                        prefix: data.prefix(),
                        prefix_len: data.prefix_len(),
                        max_len: data.max_len(),
                        asn: data.asn(),
                    })
                }
                Payload::V6(data) => {
                    payload::Payload::V6(payload::Ipv6Prefix {
                        prefix: data.prefix(),
                        prefix_len: data.prefix_len(),
                        max_len: data.max_len(),
                        asn: data.asn(),
                    })
                }
            }
        )
    }
}

impl AsRef<[u8]> for Payload {
    fn as_ref(&self) -> &[u8] {
        match *self {
            Payload::V4(ref prefix) => prefix.as_ref(),
            Payload::V6(ref prefix) => prefix.as_ref(),
        }
    }
}

impl AsMut<[u8]> for Payload {
    fn as_mut(&mut self) -> &mut [u8] {
        match *self {
            Payload::V4(ref mut prefix) => prefix.as_mut(),
            Payload::V6(ref mut prefix) => prefix.as_mut(),
        }
    }
}


//------------ EndOfData -----------------------------------------------------

/// End-of-data marks the end of sequence of payload PDUs.
///
/// This PDU differs between version 0 and 1 of RTR. Consequently, this
/// generic version is an enum that can be both, depending on the version
/// requested.
pub enum EndOfData {
    V0(EndOfDataV0),
    V1(EndOfDataV1),
}

impl EndOfData {
    /// The PDU type of the end-of-data PDU.
    pub const PDU: u8 = 7;

    /// Creates a new end-of-data PDU from the data given.
    ///
    /// If version is 0, the `V0` variant is created and the three timer
    /// values are ignored. Otherwise, a `V1` variant is created with the
    /// given version.
    pub fn new(
        version: u8,
        state: State,
        timing: payload::Timing,
    ) -> Self {
        if version == 0 {
            EndOfData::V0(EndOfDataV0::new(state))
        }
        else {
            EndOfData::V1(EndOfDataV1::new(
                version, state, timing
            ))
        }
    }

    /// Reads the end-of-data payload from a reader.
    ///
    /// Which version of the end-of-data PDU is expected depends on the
    /// version field of the `header`. On success, the return value contains
    /// a full PDU, filling in missing data from `header`.
    pub async fn read_payload<Sock: AsyncRead + Unpin>(
        header: Header, sock: &mut Sock
    ) -> Result<Self, io::Error> {
        match header.version() {
            0 => {
                EndOfDataV0::read_payload(header, sock)
                    .await.map(EndOfData::V0)
            }
            1 => {
                EndOfDataV1::read_payload(header, sock)
                    .await.map(EndOfData::V1)
            }
            _ => {
                Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    "invalid version in end of data PDU"
                ))
            }
        }
    }

    /// Returns the version field of the PDU.
    pub fn version(&self) -> u8 {
        match *self {
            EndOfData::V0(_) => 0,
            EndOfData::V1(_) => 1,
        }
    }

    /// Returns the session ID.
    pub fn session(&self) -> u16 {
        match *self {
            EndOfData::V0(ref data) => data.session(),
            EndOfData::V1(ref data) => data.session(),
        }
    }

    /// Returns the serial number.
    pub fn serial(&self) -> Serial {
        match *self {
            EndOfData::V0(ref data) => data.serial(),
            EndOfData::V1(ref data) => data.serial(),
        }
    }

    /// Returns the state by combing session ID and serial number.
    pub fn state(&self) -> State {
        State::from_parts(self.session(), self.serial())
    }

    /// Returns the three timing values if they are available.
    ///
    /// The values are only available in the `V1` variant.
    pub fn timing(&self) -> Option<payload::Timing> {
        match *self {
            EndOfData::V0(_) => None,
            EndOfData::V1(ref data) => Some(data.timing()),
        }
    }

    /// Writes the PDU to a writer.
    pub async fn write<A: AsyncWrite + Unpin>(
        &self, a: &mut A
    ) -> Result<(), io::Error> {
        a.write_all(self.as_ref()).await
    }
}

impl AsRef<[u8]> for EndOfData {
    fn as_ref(&self) -> &[u8] {
        match *self {
            EndOfData::V0(ref inner) => inner.as_ref(),
            EndOfData::V1(ref inner) => inner.as_ref(),
        }
    }
}

impl AsMut<[u8]> for EndOfData {
    fn as_mut(&mut self) -> &mut [u8] {
        match *self {
            EndOfData::V0(ref mut inner) => inner.as_mut(),
            EndOfData::V1(ref mut inner) => inner.as_mut(),
        }
    }
}


//------------ EndOfDataV0 ---------------------------------------------------

/// End-of-data marks the end of sequence of payload PDUs.
///
/// This type is the version used in protocol version 0.
#[derive(Default)]
#[repr(packed)]
pub struct EndOfDataV0 {
    header: Header,
    serial: u32
}

impl EndOfDataV0 {
    /// The PDU type of end-of-date.
    pub const PDU: u8 = 7;

    /// Creates a new end-of-data PDU from the given state.
    pub fn new(state: State) -> Self {
        EndOfDataV0 {
            header: Header::new(0, Self::PDU, state.session(), 12),
            serial: state.serial().to_be()
        }
    }

    /// Returns the serial number.
    pub fn serial(&self) -> Serial {
        Serial::from_be(self.serial)
    }
}

concrete!(EndOfDataV0);
    

//------------ EndOfDataV1 ---------------------------------------------------

/// End-of-data marks the end of sequence of payload PDUs.
///
/// This type is the version used beginning with protocol version 1.
#[derive(Default)]
#[repr(packed)]
pub struct EndOfDataV1 {
    header: Header,
    serial: u32,
    refresh: u32,
    retry: u32,
    expire: u32,
}

impl EndOfDataV1 {
    /// The PDU type of end-of-data.
    pub const PDU: u8 = 7;

    /// Creates a new end-of-data PDU from state and timer values.
    pub fn new(
        version: u8,
        state: State,
        timing: payload::Timing,
    ) -> Self {
        EndOfDataV1 {
            header: Header::new(version, Self::PDU, state.session(), 24),
            serial: state.serial().to_be(),
            refresh: timing.refresh.to_be(),
            retry: timing.retry.to_be(),
            expire: timing.expire.to_be(),
        }
    }

    /// Returns the serial number.
    pub fn serial(&self) -> Serial {
        Serial::from_be(self.serial)
    }

    /// Returns the timing paramters.
    pub fn timing(&self) -> payload::Timing {
        payload::Timing {
            refresh: u32::from_be(self.refresh),
            retry: u32::from_be(self.retry),
            expire: u32::from_be(self.expire),
        }
    }
}

concrete!(EndOfDataV1);


//------------ CacheReset ----------------------------------------------------

/// Cache reset is a response to a serial query indicating unavailability.
///
/// If a cache doesn’t have information available that reaches back to the
/// serial number indicated in the serial query, it responds with a cache
/// reset.
#[derive(Default)]
#[repr(packed)]
pub struct CacheReset {
    header: Header
}

impl CacheReset {
    /// The PDU type for a cache reset.
    pub const PDU: u8 = 7;

    /// Creates a cache reset.
    pub fn new(version: u8) -> Self {
        CacheReset {
            header: Header::new(version, Self::PDU, 0, 8)
        }
    }
}

concrete!(CacheReset);


//------------ Error ---------------------------------------------------------

/// An error report signals that something went wrong.
///
/// Error reports contain an error code and can contain both the erroneous
/// PDU and some diagnostic error text. Because of this, values of this type
/// are not fixed size byte arrays but rather are allocated according to the
/// contents of these two fields.
#[derive(Default)]
pub struct Error {
    octets: Vec<u8>,
}
/*
    /// The header of the error PDU.
    header: Header,

    /// The size of the embedded PDU in network byte order.
    pdu_len: u32,

    /// The embedded PDU.
    pdu: P,

    /// The size of the embedded reason text in network byte order.
    text_len: u32,

    /// The embedded text.
    text: T
*/

impl Error {
    /// The PDU type of an error PDU.
    pub const PDU: u8 = 10;

    /// Creates a new error PDU from components.
    pub fn new(
        version: u8,
        error_code: u16,
        pdu: impl AsRef<[u8]>,
        text: impl AsRef<[u8]>,
    ) -> Self {
        let pdu = pdu.as_ref();
        let text = text.as_ref();

        let size = 
            mem::size_of::<Header>()
            + 2 * mem::size_of::<u32>()
            + pdu.len() + text.len()
        ;
        let header = Header::new(
            version, 10, error_code, u32::try_from(size).unwrap()
        );

        let mut octets = Vec::with_capacity(size);
        octets.extend_from_slice(header.as_ref());
        octets.extend_from_slice(
            u32::try_from(pdu.len()).unwrap().to_be_bytes().as_ref()
        );
        octets.extend_from_slice(pdu);
        octets.extend_from_slice(
            u32::try_from(text.len()).unwrap().to_be_bytes().as_ref()
        );
        octets.extend_from_slice(text);

        Error { octets }
    }

    /// Writes the PUD to a writer.
    pub async fn write<A: AsyncWrite + Unpin>(
        &self, a: &mut A
    ) -> Result<(), io::Error> {
        a.write_all(self.as_ref()).await
    }
}


//--- AsRef and AsMut

impl AsRef<[u8]> for Error {
    fn as_ref(&self) -> &[u8] {
        self.octets.as_ref()
    }
}

impl AsMut<[u8]> for Error {
    fn as_mut(&mut self) -> &mut [u8] {
        self.octets.as_mut()
    }
}


//------------ Header --------------------------------------------------------

/// The header portion of an RTR PDU.
#[derive(Clone, Copy, Default)]
#[repr(packed)]
pub struct Header {
    /// The version of the PDU.
    version: u8,

    /// The PDU type.
    pdu: u8,

    /// The session ID for this RTR session.
    ///
    /// This field is re-used by some PDUs for other purposes.
    session: u16,

    /// The length of the PDU in network byte order.
    ///
    /// This is the size of the whole PDU including the header.
    length: u32,
}

impl Header {
    /// The size of the header.
    const LEN: usize = mem::size_of::<Self>();

    /// Creates a new header.
    pub fn new(version: u8, pdu: u8, session: u16, length: u32) -> Self {
        Header {
            version,
            pdu,
            session: session.to_be(),
            length: length.to_be(),
        }
    }

    /// Reads the header from a reader.
    pub async fn read<Sock: AsyncRead + Unpin>(
        sock: &mut Sock 
    ) -> Result<Self, io::Error> {
        let mut res = Self::default();
        sock.read_exact(res.as_mut()).await?;
        Ok(res)
    }

    /// Returns the version of this PDU.
    pub fn version(self) -> u8 {
        self.version
    }

    /// Returns the PDU type.
    pub fn pdu(self) -> u8 {
        self.pdu
    }

    /// Returns the session ID of this session.
    pub fn session(self) -> u16 {
        u16::from_be(self.session)
    }

    /// Returns the length of the PDU.
    ///
    /// This is the length of the full PDU including the header.
    pub fn length(self) -> u32 {
        u32::from_be(self.length)
    }
}

common!(Header);