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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! Experimental area for GNRC utility functions
//!
//! These are implemented direclty in Rust and do not wrap any RIOT libraries, but seem useful at
//! least for the purpose of the author's experiments. It may turn out that they'd make nice
//! additions to the RIOT API, or are completely misguided.

#[cfg(riot_module_ipv6)]
use crate::gnrc::ipv6;
use crate::gnrc::pktbuf::{NotEnoughSpace, Pktsnip, Shared};
use crate::thread::KernelPID;

#[cfg(riot_module_gnrc_udp)]
use riot_sys::gnrc_nettype_t_GNRC_NETTYPE_UDP as GNRC_NETTYPE_UDP;
use riot_sys::{
    gnrc_netif_hdr_t, gnrc_nettype_t_GNRC_NETTYPE_NETIF as GNRC_NETTYPE_NETIF, udp_hdr_t,
};

/// Trait of data structures that store all the information needed to respond to a Pktsnip in some
/// way; the data (typically address and port information) is copied into the trait implementation
/// and persisted there while the original snip is dropped and a new one is written.
///
/// This trait, in future, might also participate in the re-use of snips that are not dropped and
/// re-allocated but turned into responses in-place, but whether that makes sense here remains to
/// be seen.
pub trait RoundtripData: Sized {
    /// Read round trip data from an incoming packet.
    ///
    /// This returns something if the packet can fundamentally be responded to, which is usually
    /// the case.
    fn from_incoming(incoming: &Pktsnip<Shared>) -> Option<Self>;
    fn wrap(self, payload: Pktsnip<Shared>) -> Result<Pktsnip<Shared>, NotEnoughSpace>;
}

#[derive(Debug)]
pub struct NetifRoundtripData {
    // This would be more compact if we promised to never make UNKNOWN or ISR into KernelPIDs.
    pid: Option<KernelPID>,
}

impl RoundtripData for NetifRoundtripData {
    fn from_incoming(incoming: &Pktsnip<Shared>) -> Option<Self> {
        Some(Self {
            pid: incoming.search_type(GNRC_NETTYPE_NETIF).map(|s| {
                let netif_hdr: &gnrc_netif_hdr_t = unsafe { &*(s.data.as_ptr() as *const _) };
                KernelPID::new(netif_hdr.if_pid).unwrap()
            }),
        })
    }

    fn wrap(self, payload: Pktsnip<Shared>) -> Result<Pktsnip<Shared>, NotEnoughSpace> {
        match self.pid {
            None => Ok(payload),
            Some(pid) => unsafe {
                let mut netif = payload.netif_hdr_build(None, None)?;

                let data: &mut gnrc_netif_hdr_t = ::core::mem::transmute(netif.data_mut().as_ptr());
                data.if_pid = pid.into();

                Ok(netif.into())
            },
        }
    }
}

#[cfg(riot_module_ipv6)]
#[derive(Debug)]
pub struct IPv6RoundtripDataFull<N: RoundtripData> {
    remote: ipv6::Address,
    local: ipv6::Address,
    // We "only" need the NetifRoundtripData if our destination address has a %interface part in it
    // -- which fortunately is a typical case during development, for otherwise that step might
    // easily be forgotten and IPv6RoundtripDataFull might be missing its next.
    next: N,
}

#[cfg(riot_module_ipv6)]
impl<N: RoundtripData> RoundtripData for IPv6RoundtripDataFull<N> {
    fn from_incoming(incoming: &Pktsnip<Shared>) -> Option<Self> {
        let ip = incoming.ipv6_get_header().unwrap();

        Some(Self {
            remote: *ip.src(),
            local: *ip.dst(),
            next: N::from_incoming(incoming)?,
        })
    }

    fn wrap(self, payload: Pktsnip<Shared>) -> Result<Pktsnip<Shared>, NotEnoughSpace> {
        self.next.wrap(
            payload
                .ipv6_hdr_build(Some(&self.local), Some(&self.remote))?
                .into(),
        )
    }
}

// It'd be nice to have a UDPRoundtripData (not full) that wouldn't need to store the local port
// (b/c it's usually known in the context), but how would that get past a .wrap()-ish API?
#[cfg(riot_module_gnrc_udp)]
#[derive(Debug)]
pub struct UDPRoundtripDataFull<N: RoundtripData> {
    remote: core::num::NonZeroU16,
    local: core::num::NonZeroU16,
    next: N,
}

#[cfg(riot_module_gnrc_udp)]
impl<N: RoundtripData> RoundtripData for UDPRoundtripDataFull<N> {
    fn from_incoming(incoming: &Pktsnip<Shared>) -> Option<Self> {
        let (src, dst) = incoming
            .search_type(GNRC_NETTYPE_UDP)
            .map(|s| {
                let hdr: &udp_hdr_t = unsafe { &*(s.data.as_ptr() as *const _) };
                (
                    u16::from_be_bytes(unsafe { (*hdr).src_port.u8_ }),
                    u16::from_be_bytes(unsafe { (*hdr).dst_port.u8_ }),
                )
            })
            .unwrap();

        Some(Self {
            // Taking RFC 768 literally, the remote port can easily be 0 if no responses are
            // expected. (Treating the local port the same for practical reasons; it won't be zero
            // at least if the data is from listening on a concrete UDP port).
            remote: src.try_into().ok()?,
            local: dst.try_into().ok()?,
            next: N::from_incoming(incoming)?,
        })
    }

    fn wrap(self, payload: Pktsnip<Shared>) -> Result<Pktsnip<Shared>, NotEnoughSpace> {
        self.next
            .wrap(payload.udp_hdr_build(self.local, self.remote)?.into())
    }
}