Skip to main content

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}