Skip to main content

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}