esb 0.1.0

Implementation of Nordic's Enhanced ShockBurst communication protocol
Documentation
use crate::Error;
use bbqueue::{
    framed::{FrameGrantR, FrameGrantW},
    ArrayLength,
};
use core::ops::{Deref, DerefMut};

// | SW USE                        |               ACTUAL DMA PART                                    |
// | rssi - 1 byte | pipe - 1 byte | length - 1 byte | pid_no_ack - 1 byte | payload - 1 to 252 bytes |

/// A builder for an `EsbHeader` structure
///
/// The builder is converted into an `EsbHeader` by calling the
/// `check()` method.
///
/// ## Example
///
/// ```rust
/// use esb::EsbHeaderBuilder;
///
/// let header_result = EsbHeaderBuilder::default()
///     .max_payload(252)
///     .pid(0)
///     .pipe(0)
///     .no_ack(true)
///     .check();
///
/// assert!(header_result.is_ok());
/// ```
///
/// ## Default Header Contents
///
/// By default, the following settings will be used:
///
/// | Field     | Default Value |
/// | :---      | :---          |
/// | pid       | 0             |
/// | no_ack    | true          |
/// | length    | 0             |
/// | pipe      | 0             |
///
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct EsbHeaderBuilder(EsbHeader);

impl Default for EsbHeaderBuilder {
    fn default() -> Self {
        EsbHeaderBuilder(EsbHeader {
            rssi: 0,
            pid_no_ack: 0,
            length: 0,
            pipe: 0,
        })
    }
}

impl EsbHeaderBuilder {
    /// Set the pipe. Must be in the range 0..=7.
    pub fn pipe(mut self, pipe: u8) -> Self {
        self.0.pipe = pipe;
        self
    }

    /// Set the max payload. Must be in the range 0..=252.
    pub fn max_payload(mut self, max_payload: u8) -> Self {
        self.0.length = max_payload;
        self
    }

    /// Enable/disable acknowledgment
    pub fn no_ack(mut self, no_ack: bool) -> Self {
        // TODO(AJM): We should probably just call this
        // method "ack", or "enable_ack", because "no_ack"
        // is really confusing.
        if no_ack {
            self.0.pid_no_ack &= 0b1111_1110;
        } else {
            self.0.pid_no_ack |= 0b0000_0001;
        }
        self
    }

    /// Set the pid. Must be in the range 0..=3.
    pub fn pid(mut self, pid: u8) -> Self {
        // TODO(AJM): Do we want the user to set the pid? isn't this an
        // internal "retry" counter?
        self.0.pid_no_ack &= 0b0000_0001;
        self.0.pid_no_ack |= pid << 1;
        self
    }

    /// Finalize the header.
    ///
    /// If the set parameters are out of range, an error will be returned.
    pub fn check(self) -> Result<EsbHeader, Error> {
        let bad_length = self.0.length > 252;
        let bad_pipe = self.0.pipe > 7;

        // This checks if "pid" > 3, where pid_no_ack is pid << 1.
        let bad_pid = self.0.pid_no_ack > 0b0000_0111;

        if bad_length || bad_pid || bad_pipe {
            return Err(Error::InvalidParameters);
        }

        Ok(self.0)
    }
}

/// The non-payload portion of an ESB packet
///
/// This is typically used to create a Packet Grant using methods
/// from the [`EsbApp`](struct.EsbApp.html) or [`EsbIrq`](struct.EsbIrq.html)
/// interfaces.
///
/// ## Example
///
/// ```rust
/// use esb::EsbHeader;
///
/// let builder_result = EsbHeader::build()
///     .max_payload(252)
///     .pid(0)
///     .pipe(1)
///     .no_ack(true)
///     .check();
///
/// let new_result = EsbHeader::new(
///     252,
///     0,
///     1,
///     true,
/// );
///
/// assert_eq!(builder_result, new_result);
/// ```
///
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct EsbHeader {
    rssi: u8,
    // TODO(AJM): We can probably combine the 3 bits of pipe
    // into the pid_no_ack field to save another byte of space.
    // We just need to mask it out in EsbIrq before handing it
    // to the radio to process.
    pipe: u8,
    pub(crate) length: u8,
    pid_no_ack: u8,
}

/// The "packed" representation of an [`EsbHeader`]
pub(crate) struct HeaderBytes(pub(crate) [u8; 4]);

impl EsbHeader {
    /// Create a new packet header using a builder pattern
    ///
    /// See the docs for [`EsbBuilder`](struct.EsbHeaderBuilder.html) for more
    /// information.
    pub fn build() -> EsbHeaderBuilder {
        EsbHeaderBuilder::default()
    }

    /// Create a new packet header
    ///
    /// Notes on valid values:
    ///
    /// * `max_payload_length` must be between 0 and 252 bytes, inclusive.
    /// * `pid` must be between 0 and 3, inclusive.
    /// * `pipe` must be between 0 and 7, inclusive.
    pub fn new(max_payload_length: u8, pid: u8, pipe: u8, no_ack: bool) -> Result<Self, Error> {
        EsbHeaderBuilder::default()
            .max_payload(max_payload_length)
            .pid(pid)
            .pipe(pipe)
            .no_ack(no_ack)
            .check()
    }

    /// convert into a packed representation meant for internal
    /// data queuing purposes
    fn into_bytes(self) -> HeaderBytes {
        HeaderBytes([
            self.rssi,
            self.pipe,
            // DO NOT REORDER!
            self.length,
            self.pid_no_ack,
        ])
    }

    /// convert from a packed representation
    pub(crate) fn from_bytes(bytes: HeaderBytes) -> Self {
        Self {
            rssi: bytes.0[Self::rssi_idx()],
            pipe: bytes.0[Self::pipe_idx()],
            length: bytes.0[Self::length_idx()],
            pid_no_ack: bytes.0[Self::pid_no_ack_idx()],
        }
    }

    /// Accessor for the Pipe ID of the packet
    pub fn pid(self) -> u8 {
        self.pid_no_ack >> 1
    }

    /// Accessor for the no-ack field of the packet
    pub fn no_ack(self) -> bool {
        self.pid_no_ack & 1 != 1
    }

    /// Accessor for the length (in bytes) of the payload
    pub fn payload_len(self) -> usize {
        usize::from(self.length)
    }

    /// Accessor for the rssi of the payload
    pub fn rssi(self) -> u8 {
        self.rssi
    }

    /// Byte index of the RSSI field
    const fn rssi_idx() -> usize {
        0
    }

    /// Byte index of the pipe field
    const fn pipe_idx() -> usize {
        1
    }

    /// Byte index of the payload length field
    const fn length_idx() -> usize {
        // DO NOT CHANGE! HW DEPENDANT
        2
    }

    /// Byte index of the pid_no_ack field
    const fn pid_no_ack_idx() -> usize {
        // DO NOT CHANGE! HW DEPENDANT
        3
    }

    /// Size of the header (packed) in bytes
    pub(crate) const fn header_size() -> usize {
        core::mem::size_of::<HeaderBytes>()
    }

    /// Offset of the bytes needed for DMA processing
    const fn dma_payload_offset() -> usize {
        2
    }
}

/// A handle representing a grant of a readable packet
///
/// This exposes the bytes of a payload that have either
/// been sent FROM the app, and are being read by the RADIO,
/// or a payload that has send FROM the Radio, and is being
/// read by the app
pub struct PayloadR<N: ArrayLength<u8>> {
    grant: FrameGrantR<'static, N>,
}

impl<N> PayloadR<N>
where
    N: ArrayLength<u8>,
{
    /// Create a wrapped Payload Grant from a raw BBQueue Framed Grant
    pub(crate) fn new(raw_grant: FrameGrantR<'static, N>) -> Self {
        Self { grant: raw_grant }
    }

    /// Obtain a copy of the header encoded in the current grant
    pub fn get_header(&self) -> EsbHeader {
        const LEN: usize = EsbHeader::header_size();
        let mut bytes = [0u8; LEN];
        bytes.copy_from_slice(&self.grant[..LEN]);
        EsbHeader::from_bytes(HeaderBytes(bytes))
    }

    /// Obtain a pointer to the data to provide to the RADIO DMA.
    ///
    /// This includes part of the header, as well as the full payload
    pub(crate) fn dma_pointer(&self) -> *const u8 {
        (&self.grant[EsbHeader::dma_payload_offset()..]).as_ptr()
    }

    /// Utility method to use with the CCM peripheral present in Nordic's devices. This gives a
    /// slice starting from the pipe field of the header.
    pub fn ccm_slice(&self) -> &[u8] {
        &self.grant[EsbHeader::pipe_idx()..]
    }

    /// An accessor function for the pipe of the current grant
    pub fn pipe(&self) -> u8 {
        self.grant[EsbHeader::pipe_idx()]
    }

    /// An accessor function to get the pipe id of the current grant
    pub fn pid(&self) -> u8 {
        self.grant[EsbHeader::pid_no_ack_idx()] >> 1
    }

    /// An accessor function for the no-ack field of the current grant
    pub fn no_ack(&self) -> bool {
        self.grant[EsbHeader::pid_no_ack_idx()] & 1 != 1
    }

    /// An accessor function to get the size of the payload of the current grant
    pub fn payload_len(&self) -> usize {
        self.grant[EsbHeader::length_idx()] as usize
    }

    /// This function marks the packet as read, and restores the space
    /// in the buffer for re-use.
    ///
    /// If this function is NOT explicitly called (e.g. the grant is just)
    /// implicitly dropped. The packet will not be released, and the next
    /// PayloadR grant will contain the *same* packet.
    pub fn release(self) {
        self.grant.release()
    }
}

impl<N> Deref for PayloadR<N>
where
    N: ArrayLength<u8>,
{
    type Target = [u8];

    /// Provide read only access to the payload of a grant
    fn deref(&self) -> &Self::Target {
        &self.grant[EsbHeader::header_size()..]
    }
}

impl<N> DerefMut for PayloadR<N>
where
    N: ArrayLength<u8>,
{
    /// provide read/write access to the payload portion of the grant
    fn deref_mut(&mut self) -> &mut [u8] {
        &mut self.grant[EsbHeader::header_size()..]
    }
}

pub struct PayloadW<N: ArrayLength<u8>> {
    grant: FrameGrantW<'static, N>,
}

impl<N> PayloadW<N>
where
    N: ArrayLength<u8>,
{
    /// Update the header contained within this grant.
    ///
    /// This can be used to modify the pipe, length, etc. of the
    /// packet.
    ///
    /// ## NOTE:
    ///
    /// The `length` of the packet can not be increased, only shrunk. If a larger
    /// payload is needed, you must drop the current payload grant, and obtain a new
    /// one. If the new header has a larger `length` than the current `length`, then
    /// it will be truncated.
    pub fn update_header(&mut self, mut header: EsbHeader) {
        // TODO(AJM): Technically, we could drop the current grant, and request a larger one
        // here, and it would totally work. However for now, let's just truncate, because growing
        // the buffer would first have to be implemented in BBQueue.

        // `length` must always be 0..=252 (checked by constructor), so `u8` cast is
        // appropriate here
        header.length = header.length.min(self.payload_len() as u8);
        self.grant[..EsbHeader::header_size()].copy_from_slice(&header.into_bytes().0);
    }

    /// Utility method to use with the CCM peripheral present in Nordic's devices. This gives a
    /// slice starting from the pipe field of the header.
    ///
    /// # Safety
    ///
    /// This gives raw mutable access to the header part of the packet, modification on these parts
    /// are not checked, the user must ensure that:
    ///
    /// * The pipe field remains in a valid range.
    /// * The pid_no_ack remains valid and accepted by the esb protocol.
    /// * The length field remains smaller or equal than the length used when requesting this
    ///   particular PayloadW.
    ///
    /// This method is not a recommended way to update the header, it is here to provide a way to
    /// use this abstraction with the CCM hardware peripheral.
    /// The length field present in this slice contains the payload length requested during the
    /// creation of this type, that is, the maximum payload size that this particular grant can
    /// contain. When using this slice to store the output of the CCM operation, the CCM peripheral
    /// will modify this field, the user must ensure that this field remains in a valid range.
    pub unsafe fn ccm_slice(&mut self) -> &mut [u8] {
        &mut self.grant[EsbHeader::pipe_idx()..]
    }

    /// Obtain a writable grant from the application side.
    ///
    /// This method should only be used from within `EsbApp`.
    pub(crate) fn new_from_app(mut raw_grant: FrameGrantW<'static, N>, header: EsbHeader) -> Self {
        raw_grant[..EsbHeader::header_size()].copy_from_slice(&header.into_bytes().0);
        Self { grant: raw_grant }
    }

    /// Obtain a writable grant from the RADIO/interrupt side.
    ///
    /// This method should only be used from within `EsbIrq`.
    pub(crate) fn new_from_radio(raw_grant: FrameGrantW<'static, N>) -> Self {
        Self { grant: raw_grant }
    }

    pub(crate) fn dma_pointer(&mut self) -> *mut u8 {
        (&mut self.grant[EsbHeader::dma_payload_offset()..]).as_mut_ptr()
    }

    /// Update the pipe field.
    ///
    /// Pipe must be between 0 and 7, inclusive.
    #[inline]
    pub(crate) fn set_pipe(&mut self, pipe: u8) {
        self.grant[EsbHeader::pipe_idx()] = pipe;
    }

    /// Update the rssi field.
    #[inline]
    pub(crate) fn set_rssi(&mut self, rssi: u8) {
        self.grant[EsbHeader::rssi_idx()] = rssi;
    }

    /// An accessor function to get the pipe id of the current grant
    pub fn pipe(&self) -> u8 {
        self.grant[EsbHeader::pipe_idx()]
    }

    /// An accessor function to get the pipe id of the current grant
    pub fn pid(&self) -> u8 {
        self.grant[EsbHeader::pid_no_ack_idx()] >> 1
    }

    /// An accessor function for the no-ack field of the current grant
    pub fn no_ack(&self) -> bool {
        self.grant[EsbHeader::pid_no_ack_idx()] & 1 != 1
    }

    /// An accessor function to get the maximum size of the payload of the current grant
    pub fn payload_len(&self) -> usize {
        self.grant[EsbHeader::length_idx()] as usize
    }

    /// Commit the entire granted packet and payload
    ///
    /// If this function or `commit` are not explicitly called, e.g.
    /// the `PayloadW` is implicitly dropped, the packet will not be
    /// sent.
    pub fn commit_all(self) {
        let payload_len = self.payload_len();
        self.grant.commit(payload_len + EsbHeader::header_size())
    }

    /// Commit the packed, including the first `used` bytes of the payload
    ///
    /// If this function or `commit_all` are not explicitly called, e.g.
    /// the `PayloadW` is implicitly dropped, the packet will not be
    /// sent.
    ///
    /// If `used` is larger than the maximum size of the grant (or of the
    /// ESB protocol), the packet will be truncated.
    pub fn commit(mut self, used: usize) {
        let payload_len = self.payload_len().min(used);
        self.grant[EsbHeader::length_idx()] = payload_len as u8;

        self.grant.commit(payload_len + EsbHeader::header_size())
    }
}

impl<N> Deref for PayloadW<N>
where
    N: ArrayLength<u8>,
{
    type Target = [u8];

    /// provide read only access to the payload portion of the grant
    fn deref(&self) -> &Self::Target {
        &self.grant[EsbHeader::header_size()..]
    }
}

impl<N> DerefMut for PayloadW<N>
where
    N: ArrayLength<u8>,
{
    /// provide read/write access to the payload portion of the grant
    fn deref_mut(&mut self) -> &mut [u8] {
        &mut self.grant[EsbHeader::header_size()..]
    }
}