1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! Callback based registration to a the netreg infrastructure

use core::mem::MaybeUninit;

use super::FullDemuxContext;
use crate::error::NegativeErrorExt;
use crate::gnrc_pktbuf as pktbuf;

/// Storage for everything that is needed to serve a registered GNRC netreg [Callback].
///
/// It can be created through [Default::default()]. It is used as a `&'static mut`
/// in [`register_static`], which is most easily obtained through the `static_cell` crate.
///
/// ## Internal invariants
///
/// When created, all fields are uninitialized; any registration that uses them places data in
/// there, and if it ever handed out the data structure again (usually it doesn't and consumes a
/// `&'static mut Slot`), they would be uninit again.
pub struct Slot<C>(
    MaybeUninit<riot_sys::gnrc_netreg_entry_t>,
    MaybeUninit<riot_sys::gnrc_netreg_entry_cbd_t>,
    MaybeUninit<C>,
);

impl<C> Default for Slot<C> {
    fn default() -> Self {
        Self(
            MaybeUninit::uninit(),
            MaybeUninit::uninit(),
            MaybeUninit::uninit(),
        )
    }
}

/// Callback trait for registration with netreg.
///
/// This is expressed as a trait rather than a FnMut because the callback generally needs to be
/// statically allocated, and a closure (which can not be named) can not.
pub trait Callback: Send {
    /// A network command to which the callback has been registered happened.
    ///
    /// This just takes a `&self` because multiple threads could concurrently create network events
    /// from different devices.
    fn called(&self, cmd: Command, snip: pktbuf::Pktsnip<pktbuf::Shared>);
}

/// Set up a `callback` for whenever a package matching `context` arrives.
///
/// This requires a statically allocated [`Slot`], as can conveniently be created by the caller
/// through the `static_cell` crate.
///
/// The callback's [Callback::called] method will be called whenever there is a packet is sent or
/// received that matches the given `context`.
pub fn register_static<C: Callback>(
    slot: &'static mut Slot<C>,
    callback: C,
    context: FullDemuxContext,
) {
    unsafe extern "C" fn c_callback<C: Callback>(
        cmd: u16,
        pkt: *mut riot_sys::gnrc_pktsnip_t,
        ctx: *mut riot_sys::libc::c_void,
    ) {
        // unsafe: Constructed through the opposite cast, and API promises to deliver that value
        let callback = unsafe { &mut *(ctx as *mut C) };
        let cmd = match cmd as _ {
            riot_sys::GNRC_NETAPI_MSG_TYPE_RCV => Command::Receive,
            riot_sys::GNRC_NETAPI_MSG_TYPE_SND => Command::Send,
            _ => panic!("gnc_netreg_entry_cb_t precondition failed"),
        };
        // unsafe: Trusting the C API to produce a snip along with ownership
        let pkt = unsafe { pktbuf::Pktsnip::<pktbuf::Shared>::from_ptr(pkt) };
        callback.called(cmd, pkt)
    }

    slot.2.write(callback);

    slot.1.write(riot_sys::gnrc_netreg_entry_cbd_t {
        cb: Some(c_callback::<C>),
        ctx: slot.2.as_mut_ptr() as *mut riot_sys::libc::c_void,
    });

    unsafe {
        riot_sys::inline::gnrc_netreg_entry_init_cb(
            crate::inline_cast_mut(slot.0.as_mut_ptr()),
            context.demux_ctx,
            crate::inline_cast_mut(slot.1.as_mut_ptr()),
        );

        riot_sys::gnrc_netreg_register(context.nettype, slot.0.as_mut_ptr())
            .negative_to_error()
            .unwrap();
    }
}

/// Values of the command argument of a netreg callback
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq)]
pub enum Command {
    Receive,
    Send,
}