pamoja_lorawan/relay/wor.rs
1//! The wake-on-radio frames an end device sends ahead of an uplink, TS011-1.0.1 section 5.3.
2
3use super::keys::WorKeys;
4use super::{WOR_JOIN_REQUEST_LEN, WOR_UPLINK_LEN};
5use crate::crypto::Cipher;
6use crate::LorawanError;
7
8const TYPE_JOIN_REQUEST: u8 = 0;
9const TYPE_UPLINK: u8 = 1;
10
11/// Where a frame goes and how fast: a frequency and a data rate.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub struct Carrier {
14 /// The frequency in hertz, which a frame carries in hundreds of hertz.
15 pub frequency_hz: u32,
16 /// The data rate, which a frame carries in four bits.
17 pub data_rate: u8,
18}
19
20impl Carrier {
21 /// Names a carrier.
22 ///
23 /// # Arguments
24 ///
25 /// * `frequency_hz` - the frequency in hertz.
26 /// * `data_rate` - the data rate.
27 ///
28 /// # Returns
29 ///
30 /// The carrier.
31 #[must_use]
32 pub const fn new(frequency_hz: u32, data_rate: u8) -> Carrier {
33 Carrier {
34 frequency_hz,
35 data_rate,
36 }
37 }
38
39 /// The data rate byte and the three frequency bytes, as every relay frame carries them.
40 pub(crate) fn encode(self) -> Result<[u8; 4], LorawanError> {
41 if self.data_rate > 0x0f || !self.frequency_hz.is_multiple_of(100) {
42 return Err(LorawanError::MalformedFrame);
43 }
44 let steps = self.frequency_hz / 100;
45 if steps > 0x00ff_ffff {
46 return Err(LorawanError::MalformedFrame);
47 }
48 let low = steps.to_le_bytes();
49 Ok([self.data_rate, low[0], low[1], low[2]])
50 }
51
52 /// Reads the data rate byte, whose top four bits are reserved, and a frequency.
53 pub(crate) fn decode(bytes: [u8; 4]) -> Carrier {
54 Carrier {
55 data_rate: bytes[0] & 0x0f,
56 frequency_hz: u32::from_le_bytes([bytes[1], bytes[2], bytes[3], 0]) * 100,
57 }
58 }
59}
60
61/// A wake-on-radio frame, as a relay reads it.
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum Wor {
64 /// Ahead of a join request. Nothing protects it, since the device has no session yet.
65 JoinRequest {
66 /// Where and how fast the join request follows.
67 uplink: Carrier,
68 },
69 /// Ahead of a Class A uplink, sealed with the device's [`WorKeys`].
70 Uplink(SealedWor),
71}
72
73impl Wor {
74 /// Reads a wake-on-radio frame.
75 ///
76 /// # Arguments
77 ///
78 /// * `bytes` - the frame the relay received.
79 ///
80 /// # Returns
81 ///
82 /// The frame. An uplink's is still sealed; [`SealedWor::open`] checks and decrypts it.
83 ///
84 /// # Errors
85 ///
86 /// Returns [`LorawanError::MalformedFrame`] for a reserved or proprietary WOR type, which
87 /// TS011-1.0.1 section 5.3 has a relay discard, and for a frame whose length is not its
88 /// type's.
89 pub fn parse(bytes: &[u8]) -> Result<Wor, LorawanError> {
90 let header = *bytes.first().ok_or(LorawanError::FrameTooShort)?;
91 match header & 0x0f {
92 TYPE_JOIN_REQUEST if bytes.len() == WOR_JOIN_REQUEST_LEN => Ok(Wor::JoinRequest {
93 uplink: Carrier::decode([bytes[1], bytes[2], bytes[3], bytes[4]]),
94 }),
95 TYPE_UPLINK if bytes.len() == WOR_UPLINK_LEN => Ok(Wor::Uplink(SealedWor {
96 dev_addr: u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]),
97 encrypted: [bytes[5], bytes[6], bytes[7], bytes[8]],
98 wfcnt: u16::from_le_bytes([bytes[9], bytes[10]]),
99 mic: [bytes[11], bytes[12], bytes[13], bytes[14]],
100 })),
101 _ => Err(LorawanError::MalformedFrame),
102 }
103 }
104}
105
106/// A wake-on-radio frame ahead of a Class A uplink, before its integrity code is checked.
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
108pub struct SealedWor {
109 dev_addr: u32,
110 encrypted: [u8; 4],
111 wfcnt: u16,
112 mic: [u8; 4],
113}
114
115impl SealedWor {
116 /// Returns the address the frame names, which picks the keys to open it with.
117 ///
118 /// # Returns
119 ///
120 /// The address.
121 #[must_use]
122 pub const fn dev_addr(&self) -> u32 {
123 self.dev_addr
124 }
125
126 /// Returns the low sixteen bits of the frame counter the frame carries.
127 ///
128 /// # Returns
129 ///
130 /// The counter's low bits, which a relay extends to the 32-bit counter it expects.
131 #[must_use]
132 pub const fn wfcnt(&self) -> u16 {
133 self.wfcnt
134 }
135
136 /// Checks the frame's integrity code and decrypts where the uplink will follow.
137 ///
138 /// # Arguments
139 ///
140 /// * `keys` - the device's keys.
141 /// * `wfcnt` - the full 32-bit frame counter the relay takes the frame to carry.
142 /// * `wor` - the carrier the frame arrived on, which the encryption folds in.
143 ///
144 /// # Returns
145 ///
146 /// Where and how fast the uplink follows.
147 ///
148 /// # Errors
149 ///
150 /// Returns [`LorawanError::FcntMismatch`] for a counter whose low bits are not the
151 /// frame's, and [`LorawanError::MicMismatch`] when the integrity code does not verify.
152 pub fn open(&self, keys: &WorKeys, wfcnt: u32, wor: Carrier) -> Result<Carrier, LorawanError> {
153 if wfcnt as u16 != self.wfcnt {
154 return Err(LorawanError::FcntMismatch);
155 }
156 if uplink_mic(keys, self.dev_addr, wfcnt, &self.encrypted) != self.mic {
157 return Err(LorawanError::MicMismatch);
158 }
159 let stream = uplink_stream(keys, self.dev_addr, wfcnt, wor)?;
160 let mut plain = [0u8; 4];
161 for (at, byte) in plain.iter_mut().enumerate() {
162 *byte = self.encrypted[at] ^ stream[at];
163 }
164 Ok(Carrier::decode(plain))
165 }
166}
167
168/// Builds the wake-on-radio frame ahead of a join request, TS011-1.0.1 section 5.3.1.
169///
170/// # Arguments
171///
172/// * `uplink` - where and how fast the join request follows.
173///
174/// # Returns
175///
176/// The frame.
177///
178/// # Errors
179///
180/// Returns [`LorawanError::MalformedFrame`] for a data rate past 15 or a frequency the
181/// three-byte field cannot carry.
182///
183/// # Examples
184///
185/// ```
186/// use pamoja_lorawan::relay::{wor_join_request, Carrier, Wor};
187///
188/// // A device wakes its relay to say a join request follows on 868.1 MHz at DR5.
189/// let uplink = Carrier::new(868_100_000, 5);
190/// let frame = wor_join_request(uplink)?;
191/// assert_eq!(frame.len(), 5, "a kind, a data rate, and three bytes of frequency");
192///
193/// // The relay reads where to listen for it.
194/// assert_eq!(Wor::parse(&frame)?, Wor::JoinRequest { uplink });
195/// # Ok::<(), pamoja_lorawan::LorawanError>(())
196/// ```
197pub fn wor_join_request(uplink: Carrier) -> Result<[u8; WOR_JOIN_REQUEST_LEN], LorawanError> {
198 let carrier = uplink.encode()?;
199 Ok([
200 TYPE_JOIN_REQUEST,
201 carrier[0],
202 carrier[1],
203 carrier[2],
204 carrier[3],
205 ])
206}
207
208/// Builds the wake-on-radio frame ahead of a Class A uplink, TS011-1.0.1 section 5.3.2.
209///
210/// The uplink's carrier is encrypted under `WorSEncKey` with a block that folds in the
211/// frame's own carrier and counter, and the frame is signed with `WorSIntKey`.
212///
213/// # Arguments
214///
215/// * `keys` - the device's keys.
216/// * `dev_addr` - the device's address.
217/// * `wfcnt` - the WOR frame counter, which the device raises for every WOR frame it sends.
218/// * `uplink` - where and how fast the uplink follows.
219/// * `wor` - the carrier this WOR frame goes out on.
220///
221/// # Returns
222///
223/// The frame.
224///
225/// # Errors
226///
227/// Returns [`LorawanError::MalformedFrame`] for a data rate past 15 or a frequency the
228/// three-byte field cannot carry, in either carrier.
229pub fn wor_uplink(
230 keys: &WorKeys,
231 dev_addr: u32,
232 wfcnt: u32,
233 uplink: Carrier,
234 wor: Carrier,
235) -> Result<[u8; WOR_UPLINK_LEN], LorawanError> {
236 let plain = uplink.encode()?;
237 let stream = uplink_stream(keys, dev_addr, wfcnt, wor)?;
238 let mut encrypted = [0u8; 4];
239 for (at, byte) in encrypted.iter_mut().enumerate() {
240 *byte = plain[at] ^ stream[at];
241 }
242 let mic = uplink_mic(keys, dev_addr, wfcnt, &encrypted);
243
244 let mut frame = [0u8; WOR_UPLINK_LEN];
245 frame[0] = TYPE_UPLINK;
246 frame[1..5].copy_from_slice(&dev_addr.to_le_bytes());
247 frame[5..9].copy_from_slice(&encrypted);
248 frame[9..11].copy_from_slice(&(wfcnt as u16).to_le_bytes());
249 frame[11..15].copy_from_slice(&mic);
250 Ok(frame)
251}
252
253/// The block `SWOR` a WOR uplink's payload is XORed with: `aes128_encrypt(WorSEncKey, AWOR)`,
254/// table 10.
255fn uplink_stream(
256 keys: &WorKeys,
257 dev_addr: u32,
258 wfcnt: u32,
259 wor: Carrier,
260) -> Result<[u8; 16], LorawanError> {
261 let carrier = wor.encode()?;
262 let mut block = [0u8; 16];
263 block[0] = 0x01;
264 block[4..8].copy_from_slice(&dev_addr.to_le_bytes());
265 block[8..12].copy_from_slice(&wfcnt.to_le_bytes());
266 block[12..15].copy_from_slice(&carrier[1..4]);
267 block[15] = carrier[0];
268 Ok(Cipher::new(keys.encryption()).encrypt_block(&block))
269}
270
271/// The integrity code of a WOR uplink: CMAC over `B0 | DevAddr | WorUplinkEnc | WFCnt`,
272/// table 11.
273fn uplink_mic(keys: &WorKeys, dev_addr: u32, wfcnt: u32, encrypted: &[u8; 4]) -> [u8; 4] {
274 let mut message = [0u8; 26];
275 message[0] = 0x49;
276 message[6..10].copy_from_slice(&dev_addr.to_le_bytes());
277 message[10..14].copy_from_slice(&wfcnt.to_le_bytes());
278 message[15] = 0x0e;
279 message[16..20].copy_from_slice(&dev_addr.to_le_bytes());
280 message[20..24].copy_from_slice(encrypted);
281 message[24..26].copy_from_slice(&(wfcnt as u16).to_le_bytes());
282 let cmac = Cipher::new(keys.integrity()).cmac(&message);
283 [cmac[0], cmac[1], cmac[2], cmac[3]]
284}