Skip to main content

pamoja_lorawan/relay/
forward.rs

1//! An uplink a relay forwards to its network, TS011-1.0.1 section 9.1.
2
3use crate::mac::{relay_rssi_code, relay_snr_code};
4use crate::LorawanError;
5
6/// The bytes `ForwardUplinkReq` adds in front of the end device's frame: three of metadata
7/// and three of frequency.
8pub const FORWARD_OVERHEAD: usize = 6;
9
10/// Which of a relay's channels a WOR frame arrived on, TS011-1.0.1 table 28.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12pub enum WorChannel {
13    /// The default channel.
14    Default,
15    /// The second channel a network configured.
16    Second,
17}
18
19/// What a relay heard of an uplink, TS011-1.0.1 table 27.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub struct UplinkMetadata {
22    /// The channel the WOR frame came in on.
23    pub wor_channel: WorChannel,
24    /// The uplink's signal strength in dBm, carried from -142 to -15.
25    pub rssi_dbm: i16,
26    /// Its signal-to-noise ratio in dB, carried from -20 to 11.
27    pub snr_db: i8,
28    /// The data rate the uplink arrived at.
29    pub data_rate: u8,
30}
31
32/// An end device's uplink as a relay forwards it: the payload of a relay uplink on
33/// [`LA_FPORT_RELAY`](super::LA_FPORT_RELAY).
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct ForwardedUplink<'a> {
36    /// What the relay heard of it.
37    pub metadata: UplinkMetadata,
38    /// The frequency it arrived on, in hertz.
39    pub frequency_hz: u32,
40    /// The end device's frame, as the relay received it.
41    pub phy_payload: &'a [u8],
42}
43
44impl<'a> ForwardedUplink<'a> {
45    /// Writes the forwarded uplink out.
46    ///
47    /// A strength or ratio past what its field carries goes out as the closest value, as
48    /// section 9.1 asks.
49    ///
50    /// # Arguments
51    ///
52    /// * `out` - where to write it.
53    ///
54    /// # Returns
55    ///
56    /// How many bytes were written: [`FORWARD_OVERHEAD`] and the frame.
57    ///
58    /// # Errors
59    ///
60    /// Returns [`LorawanError::PayloadTooLong`] when `out` is too short, and
61    /// [`LorawanError::MalformedFrame`] for a data rate past 15 or a frequency the three-byte
62    /// field cannot carry.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use pamoja_lorawan::relay::{ForwardedUplink, UplinkMetadata, WorChannel};
68    /// use pamoja_lorawan::{Session, Uplink};
69    ///
70    /// // A sensor's reading, which the relay heard and carries on without reading it.
71    /// let sensor = Session::new(0x2601_1BDA, [0x2B; 16], [0x99; 16]);
72    /// let frame = sensor.encode_uplink(&Uplink::new(7, 2, b"21.5"))?;
73    ///
74    /// // What the relay adds: where and how well it heard the frame.
75    /// let forwarded = ForwardedUplink {
76    ///     metadata: UplinkMetadata {
77    ///         wor_channel: WorChannel::Second,
78    ///         rssi_dbm: -100,
79    ///         snr_db: 5,
80    ///         data_rate: 5,
81    ///     },
82    ///     frequency_hz: 868_100_000,
83    ///     phy_payload: frame.as_bytes(),
84    /// };
85    /// let mut out = [0u8; 64];
86    /// let len = forwarded.encode(&mut out)?;
87    ///
88    /// // The network reads back exactly what the relay heard, and the sensor's frame intact.
89    /// assert_eq!(ForwardedUplink::parse(&out[..len])?, forwarded);
90    /// # Ok::<(), pamoja_lorawan::LorawanError>(())
91    /// ```
92    pub fn encode(&self, out: &mut [u8]) -> Result<usize, LorawanError> {
93        let len = FORWARD_OVERHEAD + self.phy_payload.len();
94        if out.len() < len {
95            return Err(LorawanError::PayloadTooLong);
96        }
97        if self.metadata.data_rate > 0x0f || !self.frequency_hz.is_multiple_of(100) {
98            return Err(LorawanError::MalformedFrame);
99        }
100        let steps = self.frequency_hz / 100;
101        if steps > 0x00ff_ffff {
102            return Err(LorawanError::MalformedFrame);
103        }
104        let channel = match self.metadata.wor_channel {
105            WorChannel::Default => 0u32,
106            WorChannel::Second => 1,
107        };
108        let metadata = (channel << 16)
109            | (u32::from(relay_rssi_code(self.metadata.rssi_dbm)) << 9)
110            | (u32::from(relay_snr_code(self.metadata.snr_db)) << 4)
111            | u32::from(self.metadata.data_rate);
112        out[..3].copy_from_slice(&metadata.to_le_bytes()[..3]);
113        out[3..6].copy_from_slice(&steps.to_le_bytes()[..3]);
114        out[FORWARD_OVERHEAD..len].copy_from_slice(self.phy_payload);
115        Ok(len)
116    }
117
118    /// Reads a forwarded uplink.
119    ///
120    /// # Arguments
121    ///
122    /// * `bytes` - the payload of a relay uplink on port 226.
123    ///
124    /// # Returns
125    ///
126    /// The forwarded uplink, borrowing the end device's frame from `bytes`.
127    ///
128    /// # Errors
129    ///
130    /// Returns [`LorawanError::FrameTooShort`] for fewer than [`FORWARD_OVERHEAD`] bytes, and
131    /// [`LorawanError::MalformedFrame`] for a reserved WOR channel.
132    pub fn parse(bytes: &'a [u8]) -> Result<ForwardedUplink<'a>, LorawanError> {
133        if bytes.len() < FORWARD_OVERHEAD {
134            return Err(LorawanError::FrameTooShort);
135        }
136        let metadata = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], 0]);
137        let wor_channel = match (metadata >> 16) & 0x03 {
138            0 => WorChannel::Default,
139            1 => WorChannel::Second,
140            _ => return Err(LorawanError::MalformedFrame),
141        };
142        Ok(ForwardedUplink {
143            metadata: UplinkMetadata {
144                wor_channel,
145                rssi_dbm: -i16::from(((metadata >> 9) & 0x7f) as u8) - 15,
146                snr_db: (((metadata >> 4) & 0x1f) as i8) - 20,
147                data_rate: (metadata & 0x0f) as u8,
148            },
149            frequency_hz: u32::from_le_bytes([bytes[3], bytes[4], bytes[5], 0]) * 100,
150            phy_payload: &bytes[FORWARD_OVERHEAD..],
151        })
152    }
153}