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}