esb_ng/
payload.rs

1use crate::Error;
2use core::ops::{Deref, DerefMut};
3use bbq2::{
4    queue::BBQueue,
5    traits::{coordination::cas::AtomicCoord, notifier::maitake::MaiNotSpsc, storage::Inline},
6};
7
8// | SW USE                        |               ACTUAL DMA PART                                    |
9// | rssi - 1 byte | pipe - 1 byte | length - 1 byte | pid_no_ack - 1 byte | payload - 1 to 252 bytes |
10
11type FramedGrantR<const N: usize> = bbq2::prod_cons::framed::FramedGrantR<
12    &'static BBQueue<Inline<N>, AtomicCoord, MaiNotSpsc>,
13    Inline<N>,
14    AtomicCoord,
15    MaiNotSpsc,
16    u16,
17>;
18type FramedGrantW<const N: usize> = bbq2::prod_cons::framed::FramedGrantW<
19    &'static BBQueue<Inline<N>, AtomicCoord, MaiNotSpsc>,
20    Inline<N>,
21    AtomicCoord,
22    MaiNotSpsc,
23    u16,
24>;
25
26/// A builder for an `EsbHeader` structure
27///
28/// The builder is converted into an `EsbHeader` by calling the
29/// `check()` method.
30///
31/// ## Example
32///
33/// ```rust
34/// use esb::EsbHeaderBuilder;
35///
36/// let header_result = EsbHeaderBuilder::default()
37///     .max_payload(252)
38///     .pid(0)
39///     .pipe(0)
40///     .no_ack(true)
41///     .check();
42///
43/// assert!(header_result.is_ok());
44/// ```
45///
46/// ## Default Header Contents
47///
48/// By default, the following settings will be used:
49///
50/// | Field     | Default Value |
51/// | :---      | :---          |
52/// | pid       | 0             |
53/// | no_ack    | true          |
54/// | length    | 0             |
55/// | pipe      | 0             |
56///
57#[derive(Debug, Clone, Eq, PartialEq)]
58pub struct EsbHeaderBuilder(EsbHeader);
59
60impl Default for EsbHeaderBuilder {
61    fn default() -> Self {
62        EsbHeaderBuilder(EsbHeader {
63            rssi: 0,
64            pid_no_ack: 0,
65            length: 0,
66            pipe: 0,
67        })
68    }
69}
70
71impl EsbHeaderBuilder {
72    /// Set the pipe. Must be in the range 0..=7.
73    pub fn pipe(mut self, pipe: u8) -> Self {
74        self.0.pipe = pipe;
75        self
76    }
77
78    /// Set the max payload. Must be in the range 0..=252.
79    pub fn max_payload(mut self, max_payload: u8) -> Self {
80        self.0.length = max_payload;
81        self
82    }
83
84    /// Enable/disable acknowledgment
85    pub fn no_ack(mut self, no_ack: bool) -> Self {
86        // TODO(AJM): We should probably just call this
87        // method "ack", or "enable_ack", because "no_ack"
88        // is really confusing.
89        if no_ack {
90            self.0.pid_no_ack &= 0b1111_1110;
91        } else {
92            self.0.pid_no_ack |= 0b0000_0001;
93        }
94        self
95    }
96
97    /// Set the pid. Must be in the range 0..=3.
98    pub fn pid(mut self, pid: u8) -> Self {
99        // TODO(AJM): Do we want the user to set the pid? isn't this an
100        // internal "retry" counter?
101        self.0.pid_no_ack &= 0b0000_0001;
102        self.0.pid_no_ack |= pid << 1;
103        self
104    }
105
106    /// Finalize the header.
107    ///
108    /// If the set parameters are out of range, an error will be returned.
109    pub fn check(self) -> Result<EsbHeader, Error> {
110        let bad_length = self.0.length > 252;
111        let bad_pipe = self.0.pipe > 7;
112
113        // This checks if "pid" > 3, where pid_no_ack is pid << 1.
114        let bad_pid = self.0.pid_no_ack > 0b0000_0111;
115
116        if bad_length || bad_pid || bad_pipe {
117            return Err(Error::InvalidParameters);
118        }
119
120        Ok(self.0)
121    }
122}
123
124/// The non-payload portion of an ESB packet
125///
126/// This is typically used to create a Packet Grant using methods
127/// from the [`EsbApp`](struct.EsbApp.html) or [`EsbIrq`](struct.EsbIrq.html)
128/// interfaces.
129///
130/// ## Example
131///
132/// ```rust
133/// use esb::EsbHeader;
134///
135/// let builder_result = EsbHeader::build()
136///     .max_payload(252)
137///     .pid(0)
138///     .pipe(1)
139///     .no_ack(true)
140///     .check();
141///
142/// let new_result = EsbHeader::new(
143///     252,
144///     0,
145///     1,
146///     true,
147/// );
148///
149/// assert_eq!(builder_result, new_result);
150/// ```
151///
152#[derive(Debug, Clone, Copy, Eq, PartialEq)]
153pub struct EsbHeader {
154    rssi: u8,
155    // TODO(AJM): We can probably combine the 3 bits of pipe
156    // into the pid_no_ack field to save another byte of space.
157    // We just need to mask it out in EsbIrq before handing it
158    // to the radio to process.
159    pipe: u8,
160    pub(crate) length: u8,
161    pid_no_ack: u8,
162}
163
164/// The "packed" representation of an [`EsbHeader`]
165#[repr(transparent)]
166pub(crate) struct HeaderBytes(pub(crate) [u8; 4]);
167
168impl EsbHeader {
169    /// Create a new packet header using a builder pattern
170    ///
171    /// See the docs for [`EsbBuilder`](struct.EsbHeaderBuilder.html) for more
172    /// information.
173    pub fn build() -> EsbHeaderBuilder {
174        EsbHeaderBuilder::default()
175    }
176
177    /// Create a new packet header
178    ///
179    /// Notes on valid values:
180    ///
181    /// * `max_payload_length` must be between 0 and 252 bytes, inclusive.
182    /// * `pid` must be between 0 and 3, inclusive.
183    /// * `pipe` must be between 0 and 7, inclusive.
184    pub fn new(max_payload_length: u8, pid: u8, pipe: u8, no_ack: bool) -> Result<Self, Error> {
185        EsbHeaderBuilder::default()
186            .max_payload(max_payload_length)
187            .pid(pid)
188            .pipe(pipe)
189            .no_ack(no_ack)
190            .check()
191    }
192
193    /// convert into a packed representation meant for internal
194    /// data queuing purposes
195    fn into_bytes(self) -> HeaderBytes {
196        HeaderBytes([
197            self.rssi,
198            self.pipe,
199            // DO NOT REORDER!
200            self.length,
201            self.pid_no_ack,
202        ])
203    }
204
205    /// convert from a packed representation
206    pub(crate) fn from_bytes(bytes: HeaderBytes) -> Self {
207        Self {
208            rssi: bytes.0[Self::rssi_idx()],
209            pipe: bytes.0[Self::pipe_idx()],
210            length: bytes.0[Self::length_idx()],
211            pid_no_ack: bytes.0[Self::pid_no_ack_idx()],
212        }
213    }
214
215    /// Accessor for the Pipe ID of the packet
216    pub fn pid(self) -> u8 {
217        self.pid_no_ack >> 1
218    }
219
220    /// Accessor for the no-ack field of the packet
221    pub fn no_ack(self) -> bool {
222        self.pid_no_ack & 1 != 1
223    }
224
225    /// Accessor for the length (in bytes) of the payload
226    pub fn payload_len(self) -> u16 {
227        self.length.into()
228    }
229
230    /// Accessor for the rssi of the payload
231    pub fn rssi(self) -> u8 {
232        self.rssi
233    }
234
235    /// Byte index of the RSSI field
236    const fn rssi_idx() -> usize {
237        0
238    }
239
240    /// Byte index of the pipe field
241    const fn pipe_idx() -> usize {
242        1
243    }
244
245    /// Byte index of the payload length field
246    const fn length_idx() -> usize {
247        // DO NOT CHANGE! HW DEPENDANT
248        2
249    }
250
251    /// Byte index of the pid_no_ack field
252    const fn pid_no_ack_idx() -> usize {
253        // DO NOT CHANGE! HW DEPENDANT
254        3
255    }
256
257    /// Size of the header (packed) in bytes
258    pub(crate) const fn header_size() -> u16 {
259        core::mem::size_of::<HeaderBytes>() as u16
260    }
261
262    /// Offset of the bytes needed for DMA processing
263    const fn dma_payload_offset() -> usize {
264        2
265    }
266}
267
268/// A handle representing a grant of a readable packet
269///
270/// This exposes the bytes of a payload that have either
271/// been sent FROM the app, and are being read by the RADIO,
272/// or a payload that has send FROM the Radio, and is being
273/// read by the app
274pub struct PayloadR<const N: usize> {
275    grant: FramedGrantR<N>,
276}
277
278impl<const N: usize> PayloadR<N>
279{
280    /// Create a wrapped Payload Grant from a raw BBQueue Framed Grant
281    pub(crate) fn new(raw_grant: FramedGrantR<N>) -> Self {
282        Self { grant: raw_grant }
283    }
284
285    /// Obtain a copy of the header encoded in the current grant
286    pub fn get_header(&self) -> EsbHeader {
287        const LEN: usize = EsbHeader::header_size() as usize;
288        let mut bytes = [0u8; LEN];
289        bytes.copy_from_slice(&self.grant[..LEN]);
290        EsbHeader::from_bytes(HeaderBytes(bytes))
291    }
292
293    /// Obtain a pointer to the data to provide to the RADIO DMA.
294    ///
295    /// This includes part of the header, as well as the full payload
296    pub(crate) fn dma_pointer(&self) -> *const u8 {
297        self.grant[EsbHeader::dma_payload_offset()..].as_ptr()
298    }
299
300    /// Utility method to use with the CCM peripheral present in Nordic's devices. This gives a
301    /// slice starting from the pipe field of the header.
302    pub fn ccm_slice(&self) -> &[u8] {
303        &self.grant[EsbHeader::pipe_idx()..]
304    }
305
306    /// An accessor function for the pipe of the current grant
307    pub fn pipe(&self) -> u8 {
308        self.grant[EsbHeader::pipe_idx()]
309    }
310
311    /// An accessor function to get the pipe id of the current grant
312    pub fn pid(&self) -> u8 {
313        self.grant[EsbHeader::pid_no_ack_idx()] >> 1
314    }
315
316    /// An accessor function for the no-ack field of the current grant
317    pub fn no_ack(&self) -> bool {
318        self.grant[EsbHeader::pid_no_ack_idx()] & 1 != 1
319    }
320
321    /// An accessor function to get the size of the payload of the current grant
322    pub fn payload_len(&self) -> usize {
323        self.grant[EsbHeader::length_idx()] as usize
324    }
325
326    /// This function marks the packet as read, and restores the space
327    /// in the buffer for re-use.
328    ///
329    /// If this function is NOT explicitly called (e.g. the grant is just)
330    /// implicitly dropped. The packet will not be released, and the next
331    /// PayloadR grant will contain the *same* packet.
332    pub fn release(self) {
333        self.grant.release()
334    }
335
336    // /// Set whether the payload should automatically release on drop
337    // #[inline(always)]
338    // pub fn auto_release(&mut self, is_auto: bool) {
339    //     self.grant.auto_release(is_auto);
340    // }
341}
342
343impl<const N: usize> Deref for PayloadR<N>
344{
345    type Target = [u8];
346
347    /// Provide read only access to the payload of a grant
348    fn deref(&self) -> &Self::Target {
349        &self.grant[EsbHeader::header_size().into()..]
350    }
351}
352
353impl<const N: usize> DerefMut for PayloadR<N>
354{
355    /// provide read/write access to the payload portion of the grant
356    fn deref_mut(&mut self) -> &mut [u8] {
357        &mut self.grant[EsbHeader::header_size().into()..]
358    }
359}
360
361pub struct PayloadW<const N: usize> {
362    grant: FramedGrantW<N>,
363}
364
365impl<const N: usize> PayloadW<N>
366{
367    /// Update the header contained within this grant.
368    ///
369    /// This can be used to modify the pipe, length, etc. of the
370    /// packet.
371    ///
372    /// ## NOTE:
373    ///
374    /// The `length` of the packet can not be increased, only shrunk. If a larger
375    /// payload is needed, you must drop the current payload grant, and obtain a new
376    /// one. If the new header has a larger `length` than the current `length`, then
377    /// it will be truncated.
378    pub fn update_header(&mut self, mut header: EsbHeader) {
379        // TODO(AJM): Technically, we could drop the current grant, and request a larger one
380        // here, and it would totally work. However for now, let's just truncate, because growing
381        // the buffer would first have to be implemented in BBQueue.
382
383        // `length` must always be 0..=252 (checked by constructor), so `u8` cast is
384        // appropriate here
385        let payload_max = self.grant.len().saturating_sub(EsbHeader::header_size().into());
386        header.length = header.length.min(payload_max as u8);
387        self.grant[..EsbHeader::header_size().into()].copy_from_slice(&header.into_bytes().0);
388    }
389
390    /// Utility method to use with the CCM peripheral present in Nordic's devices. This gives a
391    /// slice starting from the pipe field of the header.
392    ///
393    /// # Safety
394    ///
395    /// This gives raw mutable access to the header part of the packet, modification on these parts
396    /// are not checked, the user must ensure that:
397    ///
398    /// * The pipe field remains in a valid range.
399    /// * The pid_no_ack remains valid and accepted by the esb protocol.
400    /// * The length field remains smaller or equal than the length used when requesting this
401    ///   particular PayloadW.
402    ///
403    /// This method is not a recommended way to update the header, it is here to provide a way to
404    /// use this abstraction with the CCM hardware peripheral.
405    /// The length field present in this slice contains the payload length requested during the
406    /// creation of this type, that is, the maximum payload size that this particular grant can
407    /// contain. When using this slice to store the output of the CCM operation, the CCM peripheral
408    /// will modify this field, the user must ensure that this field remains in a valid range.
409    pub unsafe fn ccm_slice(&mut self) -> &mut [u8] {
410        &mut self.grant[EsbHeader::pipe_idx()..]
411    }
412
413    /// Obtain a writable grant from the application side.
414    ///
415    /// This method should only be used from within `EsbApp`.
416    pub(crate) fn new_from_app(mut raw_grant: FramedGrantW<N>, header: EsbHeader) -> Self {
417        raw_grant[..EsbHeader::header_size().into()].copy_from_slice(&header.into_bytes().0);
418        Self { grant: raw_grant }
419    }
420
421    /// Obtain a writable grant from the RADIO/interrupt side.
422    ///
423    /// This method should only be used from within `EsbIrq`.
424    pub(crate) fn new_from_radio(raw_grant: FramedGrantW<N>) -> Self {
425        Self { grant: raw_grant }
426    }
427
428    pub(crate) fn dma_pointer(&mut self) -> *mut u8 {
429        self.grant[EsbHeader::dma_payload_offset()..].as_mut_ptr()
430    }
431
432    /// Update the pipe field.
433    ///
434    /// Pipe must be between 0 and 7, inclusive.
435    #[inline]
436    pub(crate) fn set_pipe(&mut self, pipe: u8) {
437        self.grant[EsbHeader::pipe_idx()] = pipe;
438    }
439
440    /// Update the rssi field.
441    #[inline]
442    pub(crate) fn set_rssi(&mut self, rssi: u8) {
443        self.grant[EsbHeader::rssi_idx()] = rssi;
444    }
445
446    /// An accessor function to get the pipe id of the current grant
447    pub fn pipe(&self) -> u8 {
448        self.grant[EsbHeader::pipe_idx()]
449    }
450
451    /// An accessor function to get the pipe id of the current grant
452    pub fn pid(&self) -> u8 {
453        self.grant[EsbHeader::pid_no_ack_idx()] >> 1
454    }
455
456    /// An accessor function for the no-ack field of the current grant
457    pub fn no_ack(&self) -> bool {
458        self.grant[EsbHeader::pid_no_ack_idx()] & 1 != 1
459    }
460
461    /// An accessor function to get the maximum size of the payload of the current grant
462    pub fn payload_len(&self) -> u16 {
463        self.grant[EsbHeader::length_idx()] as u16
464    }
465
466    /// Commit the entire granted packet and payload
467    ///
468    /// If this function or `commit` are not explicitly called, e.g.
469    /// the `PayloadW` is implicitly dropped, the packet will not be
470    /// sent.
471    pub fn commit_all(self) {
472        let payload_len = self.payload_len();
473        self.grant.commit(payload_len + EsbHeader::header_size())
474    }
475
476    // /// Set the amount to automatically commit on drop
477    // ///
478    // /// If `None` is given, then the packet will not be commited. If `Some(0)`
479    // /// is given, then an empty packet will be committed automatically
480    // pub fn to_commit(&mut self, amt: Option<usize>) {
481    //     if let Some(amt) = amt {
482    //         let payload_max = self.grant.len().saturating_sub(EsbHeader::header_size().into());
483    //         let payload_len = payload_max.min(amt);
484    //         self.grant[EsbHeader::length_idx()] = payload_len as u8;
485    //         self.grant.to_commit(payload_len + EsbHeader::header_size());
486    //     } else {
487    //         self.grant.to_commit(0);
488    //     }
489    // }
490
491    /// Commit the packed, including the first `used` bytes of the payload
492    ///
493    /// If this function or `commit_all` are not explicitly called, e.g.
494    /// the `PayloadW` is implicitly dropped, the packet will not be
495    /// sent.
496    ///
497    /// If `used` is larger than the maximum size of the grant (or of the
498    /// ESB protocol), the packet will be truncated.
499    pub fn commit(mut self, used: usize) {
500        let payload_len: u16 = self.payload_len().min(used as u16);
501        self.grant[EsbHeader::length_idx()] = payload_len as u8;
502
503        self.grant.commit(payload_len + EsbHeader::header_size())
504    }
505}
506
507impl<const N: usize> Deref for PayloadW<N>
508{
509    type Target = [u8];
510
511    /// provide read only access to the payload portion of the grant
512    fn deref(&self) -> &Self::Target {
513        &self.grant[EsbHeader::header_size().into()..]
514    }
515}
516
517impl<const N: usize> DerefMut for PayloadW<N>
518{
519    /// provide read/write access to the payload portion of the grant
520    fn deref_mut(&mut self) -> &mut [u8] {
521        &mut self.grant[EsbHeader::header_size().into()..]
522    }
523}