pamoja_lorawan/relay/ack.rs
1//! The acknowledgment a relay answers a wake-on-radio frame with, TS011-1.0.1 section 6.
2
3use super::keys::WorKeys;
4use super::wor::Carrier;
5use super::WOR_ACK_LEN;
6use crate::crypto::Cipher;
7use crate::LorawanError;
8
9/// How often a relay scans a channel for a wake-on-radio preamble, TS011-1.0.1 table 18.
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub enum CadPeriodicity {
12 /// Once a second, the default.
13 Ms1000,
14 /// Every 500 milliseconds.
15 Ms500,
16 /// Every 250 milliseconds.
17 Ms250,
18 /// Every 100 milliseconds.
19 Ms100,
20 /// Every 50 milliseconds.
21 Ms50,
22 /// Every 20 milliseconds.
23 Ms20,
24}
25
26impl CadPeriodicity {
27 /// Reads a coded periodicity.
28 ///
29 /// # Arguments
30 ///
31 /// * `code` - the value a frame or command carries, 0 through 5.
32 ///
33 /// # Returns
34 ///
35 /// The periodicity, or `None` for 6 and 7, which are reserved.
36 #[must_use]
37 pub const fn from_code(code: u8) -> Option<CadPeriodicity> {
38 Some(match code {
39 0 => CadPeriodicity::Ms1000,
40 1 => CadPeriodicity::Ms500,
41 2 => CadPeriodicity::Ms250,
42 3 => CadPeriodicity::Ms100,
43 4 => CadPeriodicity::Ms50,
44 5 => CadPeriodicity::Ms20,
45 _ => return None,
46 })
47 }
48
49 /// Returns the value a frame or command carries.
50 ///
51 /// # Returns
52 ///
53 /// The code, 0 through 5.
54 #[must_use]
55 pub const fn code(self) -> u8 {
56 self as u8
57 }
58
59 /// Returns the time between two scans of one channel.
60 ///
61 /// # Returns
62 ///
63 /// The period in microseconds.
64 #[must_use]
65 pub const fn period_us(self) -> u64 {
66 match self {
67 CadPeriodicity::Ms1000 => 1_000_000,
68 CadPeriodicity::Ms500 => 500_000,
69 CadPeriodicity::Ms250 => 250_000,
70 CadPeriodicity::Ms100 => 100_000,
71 CadPeriodicity::Ms50 => 50_000,
72 CadPeriodicity::Ms20 => 20_000,
73 }
74 }
75}
76
77/// How many symbols a relay takes from detecting activity to receiving, TS011-1.0.1
78/// table 15.
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
80pub enum CadToRx {
81 /// Two symbols.
82 Symbols2,
83 /// Four symbols.
84 Symbols4,
85 /// Six symbols.
86 Symbols6,
87 /// Eight symbols, which an end device assumes before it has heard from a relay.
88 Symbols8,
89}
90
91impl CadToRx {
92 /// Reads a coded value, 0 through 3.
93 ///
94 /// # Arguments
95 ///
96 /// * `code` - the two bits a WOR ACK carries.
97 ///
98 /// # Returns
99 ///
100 /// The value; only the low two bits are read.
101 #[must_use]
102 pub const fn from_code(code: u8) -> CadToRx {
103 match code & 0x03 {
104 0 => CadToRx::Symbols2,
105 1 => CadToRx::Symbols4,
106 2 => CadToRx::Symbols6,
107 _ => CadToRx::Symbols8,
108 }
109 }
110
111 /// Returns the value a WOR ACK carries.
112 ///
113 /// # Returns
114 ///
115 /// The code, 0 through 3.
116 #[must_use]
117 pub const fn code(self) -> u8 {
118 self as u8
119 }
120
121 /// Returns the number of symbols.
122 ///
123 /// # Returns
124 ///
125 /// 2, 4, 6 or 8.
126 #[must_use]
127 pub const fn symbols(self) -> u16 {
128 (self as u16 + 1) * 2
129 }
130}
131
132/// How accurate a relay's crystal is, TS011-1.0.1 table 17.
133#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
134pub enum XtalAccuracy {
135 /// Better than 10 parts per million.
136 Ppm10,
137 /// Better than 20.
138 Ppm20,
139 /// Better than 30.
140 Ppm30,
141 /// Better than 40, which an end device assumes before it has heard from a relay.
142 Ppm40,
143}
144
145impl XtalAccuracy {
146 /// Reads a coded value, 0 through 3.
147 ///
148 /// # Arguments
149 ///
150 /// * `code` - the two bits a WOR ACK carries.
151 ///
152 /// # Returns
153 ///
154 /// The value; only the low two bits are read.
155 #[must_use]
156 pub const fn from_code(code: u8) -> XtalAccuracy {
157 match code & 0x03 {
158 0 => XtalAccuracy::Ppm10,
159 1 => XtalAccuracy::Ppm20,
160 2 => XtalAccuracy::Ppm30,
161 _ => XtalAccuracy::Ppm40,
162 }
163 }
164
165 /// Returns the value a WOR ACK carries.
166 ///
167 /// # Returns
168 ///
169 /// The code, 0 through 3.
170 #[must_use]
171 pub const fn code(self) -> u8 {
172 self as u8
173 }
174
175 /// Returns the accuracy.
176 ///
177 /// # Returns
178 ///
179 /// Parts per million: 10, 20, 30 or 40.
180 #[must_use]
181 pub const fn ppm(self) -> u32 {
182 (self as u32 + 1) * 10
183 }
184}
185
186/// Whether a relay will forward the uplink after a WOR frame, TS011-1.0.1 table 16.
187#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
188pub enum Forward {
189 /// The relay has room to forward it.
190 Available,
191 /// A forwarding limit is reached; try again in 30 minutes.
192 RetryIn30Minutes,
193 /// A forwarding limit is reached; try again in 60 minutes.
194 RetryIn60Minutes,
195 /// Forwarding is off.
196 Disabled,
197}
198
199impl Forward {
200 /// Reads a coded value, 0 through 3.
201 ///
202 /// # Arguments
203 ///
204 /// * `code` - the two bits a WOR ACK carries.
205 ///
206 /// # Returns
207 ///
208 /// The value; only the low two bits are read.
209 #[must_use]
210 pub const fn from_code(code: u8) -> Forward {
211 match code & 0x03 {
212 0 => Forward::Available,
213 1 => Forward::RetryIn30Minutes,
214 2 => Forward::RetryIn60Minutes,
215 _ => Forward::Disabled,
216 }
217 }
218
219 /// Returns the value a WOR ACK carries.
220 ///
221 /// # Returns
222 ///
223 /// The code, 0 through 3.
224 #[must_use]
225 pub const fn code(self) -> u8 {
226 self as u8
227 }
228}
229
230/// What a relay tells an end device about itself in a WOR ACK, TS011-1.0.1 table 14.
231#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
232pub struct StateSync {
233 /// How long the relay takes from detecting activity to receiving.
234 pub cad_to_rx: CadToRx,
235 /// Whether it will forward the uplink.
236 pub forward: Forward,
237 /// The data rate the relay forwards at, which bounds the device's payload.
238 pub relay_data_rate: u8,
239 /// How accurate its crystal is.
240 pub xtal_accuracy: XtalAccuracy,
241 /// How often it scans.
242 pub cad_periodicity: CadPeriodicity,
243 /// The milliseconds from the start of the scan that heard the frame to the end of the
244 /// frame's preamble, eleven bits wide.
245 pub t_offset_ms: u16,
246}
247
248impl StateSync {
249 /// The largest offset the eleven-bit field holds, in milliseconds.
250 pub const MAX_T_OFFSET_MS: u16 = 0x07ff;
251
252 /// Packs the state into the 24 bits a WOR ACK carries.
253 ///
254 /// # Returns
255 ///
256 /// The bits: CadToRx 23:22, Forward 21:20, RelayDataRate 19:16, XTALAccuracy 15:14,
257 /// CADPeriodicity 13:11 and TOffset 10:0.
258 ///
259 /// # Errors
260 ///
261 /// Returns [`LorawanError::MalformedFrame`] for a data rate past 15 or an offset past
262 /// [`MAX_T_OFFSET_MS`](Self::MAX_T_OFFSET_MS).
263 pub fn to_bits(&self) -> Result<u32, LorawanError> {
264 if self.relay_data_rate > 0x0f || self.t_offset_ms > Self::MAX_T_OFFSET_MS {
265 return Err(LorawanError::MalformedFrame);
266 }
267 Ok((u32::from(self.cad_to_rx.code()) << 22)
268 | (u32::from(self.forward.code()) << 20)
269 | (u32::from(self.relay_data_rate) << 16)
270 | (u32::from(self.xtal_accuracy.code()) << 14)
271 | (u32::from(self.cad_periodicity.code()) << 11)
272 | u32::from(self.t_offset_ms))
273 }
274
275 /// Unpacks the 24 bits a WOR ACK carries.
276 ///
277 /// # Arguments
278 ///
279 /// * `bits` - the state; bits past the 24th are ignored.
280 ///
281 /// # Returns
282 ///
283 /// The state.
284 ///
285 /// # Errors
286 ///
287 /// Returns [`LorawanError::MalformedFrame`] for a reserved scan periodicity.
288 pub fn from_bits(bits: u32) -> Result<StateSync, LorawanError> {
289 Ok(StateSync {
290 cad_to_rx: CadToRx::from_code((bits >> 22) as u8),
291 forward: Forward::from_code((bits >> 20) as u8),
292 relay_data_rate: ((bits >> 16) & 0x0f) as u8,
293 xtal_accuracy: XtalAccuracy::from_code((bits >> 14) as u8),
294 cad_periodicity: CadPeriodicity::from_code(((bits >> 11) & 0x07) as u8)
295 .ok_or(LorawanError::MalformedFrame)?,
296 t_offset_ms: (bits & 0x07ff) as u16,
297 })
298 }
299}
300
301/// Builds a relay's WOR ACK, TS011-1.0.1 section 6.2.
302///
303/// # Arguments
304///
305/// * `keys` - the end device's keys.
306/// * `dev_addr` - its address.
307/// * `wfcnt` - the 32-bit counter of the WOR frame being acknowledged.
308/// * `ack` - the carrier the acknowledgment goes out on, which its encryption folds in.
309/// * `uplink` - the carrier the WOR frame said the uplink follows on, which its integrity
310/// code folds in.
311/// * `state` - what the relay tells the device.
312///
313/// # Returns
314///
315/// The acknowledgment.
316///
317/// # Errors
318///
319/// Returns [`LorawanError::MalformedFrame`] for a state the bits cannot carry or a carrier
320/// the fields cannot.
321pub fn wor_ack(
322 keys: &WorKeys,
323 dev_addr: u32,
324 wfcnt: u32,
325 ack: Carrier,
326 uplink: Carrier,
327 state: StateSync,
328) -> Result<[u8; WOR_ACK_LEN], LorawanError> {
329 let plain = state.to_bits()?.to_le_bytes();
330 let stream = ack_stream(keys, dev_addr, wfcnt, ack)?;
331 let mut frame = [0u8; WOR_ACK_LEN];
332 for at in 0..3 {
333 frame[at] = plain[at] ^ stream[at];
334 }
335 let mic = ack_mic(
336 keys,
337 dev_addr,
338 wfcnt,
339 uplink,
340 [frame[0], frame[1], frame[2]],
341 )?;
342 frame[3..7].copy_from_slice(&mic);
343 Ok(frame)
344}
345
346/// Checks and reads a WOR ACK, TS011-1.0.1 section 6.2.
347///
348/// # Arguments
349///
350/// * `bytes` - the acknowledgment the end device received.
351/// * `keys` - the device's keys.
352/// * `dev_addr` - its address.
353/// * `wfcnt` - the 32-bit counter of the WOR frame it sent.
354/// * `ack` - the carrier the acknowledgment arrived on.
355/// * `uplink` - the carrier the WOR frame named for the uplink.
356///
357/// # Returns
358///
359/// What the relay said about itself.
360///
361/// # Errors
362///
363/// Returns [`LorawanError::MalformedFrame`] for a frame that is not seven bytes or a reserved
364/// periodicity, and [`LorawanError::MicMismatch`] when its integrity code does not verify.
365pub fn open_wor_ack(
366 bytes: &[u8],
367 keys: &WorKeys,
368 dev_addr: u32,
369 wfcnt: u32,
370 ack: Carrier,
371 uplink: Carrier,
372) -> Result<StateSync, LorawanError> {
373 if bytes.len() != WOR_ACK_LEN {
374 return Err(LorawanError::MalformedFrame);
375 }
376 let encrypted = [bytes[0], bytes[1], bytes[2]];
377 if ack_mic(keys, dev_addr, wfcnt, uplink, encrypted)? != bytes[3..7] {
378 return Err(LorawanError::MicMismatch);
379 }
380 let stream = ack_stream(keys, dev_addr, wfcnt, ack)?;
381 let bits = u32::from_le_bytes([
382 encrypted[0] ^ stream[0],
383 encrypted[1] ^ stream[1],
384 encrypted[2] ^ stream[2],
385 0,
386 ]);
387 StateSync::from_bits(bits)
388}
389
390/// The block `SACK` an acknowledgment is XORed with: `aes128_encrypt(WorSEncKey, AACK)`,
391/// table 19.
392fn ack_stream(
393 keys: &WorKeys,
394 dev_addr: u32,
395 wfcnt: u32,
396 ack: Carrier,
397) -> Result<[u8; 16], LorawanError> {
398 let carrier = ack.encode()?;
399 let mut block = [0u8; 16];
400 block[0] = 0x01;
401 block[3] = 0x01;
402 block[4..8].copy_from_slice(&dev_addr.to_le_bytes());
403 block[8..12].copy_from_slice(&wfcnt.to_le_bytes());
404 block[12..15].copy_from_slice(&carrier[1..4]);
405 block[15] = carrier[0];
406 Ok(Cipher::new(keys.encryption()).encrypt_block(&block))
407}
408
409/// The integrity code of an acknowledgment: CMAC over
410/// `B0 | AckUplinkEnc | WOR | pad16`, tables 20 and 21.
411fn ack_mic(
412 keys: &WorKeys,
413 dev_addr: u32,
414 wfcnt: u32,
415 uplink: Carrier,
416 encrypted: [u8; 3],
417) -> Result<[u8; 4], LorawanError> {
418 let carrier = uplink.encode()?;
419 let mut message = [0u8; 32];
420 message[0] = 0x49;
421 message[5] = 0x01;
422 message[6..10].copy_from_slice(&dev_addr.to_le_bytes());
423 message[10..14].copy_from_slice(&wfcnt.to_le_bytes());
424 message[15] = 0x07;
425 message[16..19].copy_from_slice(&encrypted);
426 message[19..23].copy_from_slice(&carrier);
427 message[23..25].copy_from_slice(&(wfcnt as u16).to_le_bytes());
428 message[25..29].copy_from_slice(&dev_addr.to_le_bytes());
429 let cmac = Cipher::new(keys.integrity()).cmac(&message);
430 Ok([cmac[0], cmac[1], cmac[2], cmac[3]])
431}