1use crate::crypto::Cipher;
4use crate::error::LorawanError;
5use crate::frame::{PhyPayload, MTYPE_JOIN_ACCEPT, MTYPE_JOIN_REQUEST, MTYPE_MASK};
6use crate::session::Session;
7
8pub(crate) const JOIN_REQUEST_LEN: usize = 1 + 8 + 8 + 2 + 4;
10
11pub struct Device {
22 dev_eui: [u8; 8],
23 app_eui: [u8; 8],
24 app_key: [u8; 16],
25}
26
27impl Device {
28 pub fn new(dev_eui: [u8; 8], app_eui: [u8; 8], app_key: [u8; 16]) -> Self {
40 Device {
41 dev_eui,
42 app_eui,
43 app_key,
44 }
45 }
46
47 pub fn join_request(&self, dev_nonce: u16) -> PhyPayload {
58 let mut buf = [0u8; JOIN_REQUEST_LEN];
59 buf[0] = MTYPE_JOIN_REQUEST;
60 copy_reversed(&mut buf[1..9], &self.app_eui);
61 copy_reversed(&mut buf[9..17], &self.dev_eui);
62 buf[17..19].copy_from_slice(&dev_nonce.to_le_bytes());
63 let tag = Cipher::new(&self.app_key).cmac(&buf[..19]);
64 buf[19..23].copy_from_slice(&tag[..4]);
65 PhyPayload::new(&buf).expect("a join-request always fits a frame")
66 }
67
68 pub fn accept_join(&self, bytes: &[u8], dev_nonce: u16) -> Result<JoinAccept, LorawanError> {
89 if bytes.is_empty() {
90 return Err(LorawanError::FrameTooShort);
91 }
92 if bytes[0] & MTYPE_MASK != MTYPE_JOIN_ACCEPT {
93 return Err(LorawanError::UnsupportedMType(bytes[0] & MTYPE_MASK));
94 }
95 let encrypted = &bytes[1..];
96 if encrypted.len() != 16 && encrypted.len() != 32 {
98 return Err(LorawanError::MalformedFrame);
99 }
100
101 let cipher = Cipher::new(&self.app_key);
102 let mut clear = [0u8; 32];
104 for (i, chunk) in encrypted.chunks(16).enumerate() {
105 let block: [u8; 16] = chunk.try_into().map_err(|_| LorawanError::MalformedFrame)?;
106 clear[i * 16..i * 16 + 16].copy_from_slice(&cipher.encrypt_block(&block));
107 }
108 let clear = &clear[..encrypted.len()];
109
110 let mic_at = clear.len() - 4;
112 let mut signed = [0u8; 1 + 28];
113 signed[0] = bytes[0];
114 signed[1..1 + mic_at].copy_from_slice(&clear[..mic_at]);
115 let tag = cipher.cmac(&signed[..1 + mic_at]);
116 if clear[mic_at..] != tag[..4] {
117 return Err(LorawanError::MicMismatch);
118 }
119
120 let app_nonce = &clear[0..3];
121 let net_id_bytes = &clear[3..6];
122 let dev_addr = u32::from_le_bytes([clear[6], clear[7], clear[8], clear[9]]);
123 let dl_settings = clear[10];
124 let rx_delay = clear[11];
125 let net_id = u32::from_le_bytes([net_id_bytes[0], net_id_bytes[1], net_id_bytes[2], 0]);
126
127 let nwk_skey = derive_key(&cipher, 0x01, app_nonce, net_id_bytes, dev_nonce);
128 let app_skey = derive_key(&cipher, 0x02, app_nonce, net_id_bytes, dev_nonce);
129
130 Ok(JoinAccept {
131 session: Session::new(dev_addr, nwk_skey, app_skey),
132 net_id,
133 dev_addr,
134 dl_settings,
135 rx_delay,
136 })
137 }
138}
139
140#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub struct JoinAccept {
144 session: Session,
145 net_id: u32,
146 dev_addr: u32,
147 dl_settings: u8,
148 rx_delay: u8,
149}
150
151impl JoinAccept {
152 pub fn session(&self) -> Session {
158 self.session
159 }
160
161 pub fn dev_addr(&self) -> u32 {
167 self.dev_addr
168 }
169
170 pub fn net_id(&self) -> u32 {
176 self.net_id
177 }
178
179 pub fn dl_settings(&self) -> u8 {
185 self.dl_settings
186 }
187
188 pub fn rx_delay(&self) -> u8 {
194 self.rx_delay
195 }
196}
197
198pub(crate) fn copy_reversed(dst: &mut [u8], src: &[u8]) {
201 for (d, s) in dst.iter_mut().zip(src.iter().rev()) {
202 *d = *s;
203 }
204}
205
206pub(crate) fn derive_key(
208 cipher: &Cipher,
209 kind: u8,
210 app_nonce: &[u8],
211 net_id: &[u8],
212 dev_nonce: u16,
213) -> [u8; 16] {
214 let mut block = [0u8; 16];
215 block[0] = kind;
216 block[1..4].copy_from_slice(app_nonce);
217 block[4..7].copy_from_slice(net_id);
218 block[7..9].copy_from_slice(&dev_nonce.to_le_bytes());
219 cipher.encrypt_block(&block)
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::Uplink;
226
227 const APP_KEY: [u8; 16] = [0xAB; 16];
228 const DEV_EUI: [u8; 8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77];
229 const APP_EUI: [u8; 8] = [0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
230 const DEV_NONCE: u16 = 0x1234;
231
232 fn make_join_accept(
234 app_key: &[u8; 16],
235 app_nonce: [u8; 3],
236 net_id: [u8; 3],
237 dev_addr: u32,
238 dl_settings: u8,
239 rx_delay: u8,
240 ) -> [u8; 17] {
241 let cipher = Cipher::new(app_key);
242 let mut clear = [0u8; 16];
243 clear[0..3].copy_from_slice(&app_nonce);
244 clear[3..6].copy_from_slice(&net_id);
245 clear[6..10].copy_from_slice(&dev_addr.to_le_bytes());
246 clear[10] = dl_settings;
247 clear[11] = rx_delay;
248 let mut signed = [0u8; 13];
249 signed[0] = MTYPE_JOIN_ACCEPT;
250 signed[1..13].copy_from_slice(&clear[..12]);
251 let tag = cipher.cmac(&signed);
252 clear[12..16].copy_from_slice(&tag[..4]);
253
254 let mut frame = [0u8; 17];
255 frame[0] = MTYPE_JOIN_ACCEPT;
256 frame[1..17].copy_from_slice(&cipher.decrypt_block(&clear));
257 frame
258 }
259
260 #[test]
261 fn a_join_request_is_well_formed() {
262 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
263 let request = device.join_request(DEV_NONCE);
264 let bytes = request.as_bytes();
265 assert_eq!(bytes.len(), JOIN_REQUEST_LEN);
266 assert_eq!(bytes[0], MTYPE_JOIN_REQUEST);
267 assert_eq!(
269 &bytes[1..9],
270 &[0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88]
271 );
272 assert_eq!(&bytes[17..19], &DEV_NONCE.to_le_bytes());
273 }
274
275 #[test]
276 fn a_join_activates_a_session_that_secures_data() {
277 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
278 let frame = make_join_accept(
279 &APP_KEY,
280 [0x01, 0x02, 0x03],
281 [0x04, 0x05, 0x06],
282 0x2601_1BDA,
283 0x00,
284 0x01,
285 );
286
287 let accepted = device.accept_join(&frame, DEV_NONCE).unwrap();
288 assert_eq!(accepted.dev_addr(), 0x2601_1BDA);
289 assert_eq!(accepted.net_id(), 0x0006_0504);
290 assert_eq!(accepted.rx_delay(), 0x01);
291
292 let session = accepted.session();
294 let uplink = session
295 .encode_uplink(&Uplink::new(1, 1, b"joined"))
296 .unwrap();
297 let rx = session.decode(uplink.as_bytes(), 1).unwrap();
298 assert_eq!(rx.payload(), b"joined");
299 }
300
301 #[test]
302 fn a_tampered_join_accept_fails_the_mic() {
303 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
304 let mut frame = make_join_accept(
305 &APP_KEY,
306 [0x01, 0x02, 0x03],
307 [0x04, 0x05, 0x06],
308 0x2601_1BDA,
309 0x00,
310 0x01,
311 );
312 frame[5] ^= 0xff;
313 assert_eq!(
314 device.accept_join(&frame, DEV_NONCE),
315 Err(LorawanError::MicMismatch)
316 );
317 }
318
319 #[test]
320 fn the_wrong_root_key_rejects_the_join() {
321 let device = Device::new(DEV_EUI, APP_EUI, [0x00; 16]);
322 let frame = make_join_accept(
323 &APP_KEY,
324 [0x01, 0x02, 0x03],
325 [0x04, 0x05, 0x06],
326 0x2601_1BDA,
327 0x00,
328 0x01,
329 );
330 assert_eq!(
331 device.accept_join(&frame, DEV_NONCE),
332 Err(LorawanError::MicMismatch)
333 );
334 }
335
336 #[test]
337 fn a_join_accept_of_the_wrong_length_is_malformed() {
338 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
339 assert_eq!(
340 device.accept_join(&[MTYPE_JOIN_ACCEPT; 20], DEV_NONCE),
341 Err(LorawanError::MalformedFrame)
342 );
343 }
344
345 #[test]
346 fn a_non_join_frame_is_rejected() {
347 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
348 let mut frame = [0u8; 17];
349 frame[0] = MTYPE_JOIN_REQUEST; assert_eq!(
351 device.accept_join(&frame, DEV_NONCE),
352 Err(LorawanError::UnsupportedMType(0x00))
353 );
354 }
355
356 fn make_join_accept_with_cflist(app_key: &[u8; 16], cflist: [u8; 16]) -> [u8; 33] {
358 let cipher = Cipher::new(app_key);
359 let mut clear = [0u8; 32];
360 clear[0..3].copy_from_slice(&[0x01, 0x02, 0x03]); clear[3..6].copy_from_slice(&[0x04, 0x05, 0x06]); clear[6..10].copy_from_slice(&0x2601_1BDAu32.to_le_bytes()); clear[10] = 0x00; clear[11] = 0x01; clear[12..28].copy_from_slice(&cflist);
366 let mut signed = [0u8; 29];
367 signed[0] = MTYPE_JOIN_ACCEPT;
368 signed[1..29].copy_from_slice(&clear[..28]);
369 let tag = cipher.cmac(&signed);
370 clear[28..32].copy_from_slice(&tag[..4]);
371
372 let mut frame = [0u8; 33];
373 frame[0] = MTYPE_JOIN_ACCEPT;
374 let first: [u8; 16] = clear[0..16].try_into().unwrap();
375 let second: [u8; 16] = clear[16..32].try_into().unwrap();
376 frame[1..17].copy_from_slice(&cipher.decrypt_block(&first));
377 frame[17..33].copy_from_slice(&cipher.decrypt_block(&second));
378 frame
379 }
380
381 #[test]
382 fn a_join_accept_with_a_channel_list_activates() {
383 let device = Device::new(DEV_EUI, APP_EUI, APP_KEY);
384 let frame = make_join_accept_with_cflist(&APP_KEY, [0x11; 16]);
385 let accepted = device.accept_join(&frame, DEV_NONCE).unwrap();
386 assert_eq!(accepted.dev_addr(), 0x2601_1BDA);
387 let session = accepted.session();
389 let uplink = session.encode_uplink(&Uplink::new(1, 1, b"cf")).unwrap();
390 assert_eq!(
391 session.decode(uplink.as_bytes(), 1).unwrap().payload(),
392 b"cf"
393 );
394 }
395}