1use pamoja_lorawan::mac::{MacCommand, MacCommands};
17use pamoja_lorawan::Direction;
18
19use crate::lorawan::PamojaLorawanDirection;
20use crate::{read_bytes, set_last_error, PamojaStatus};
21
22pub const PAMOJA_LORAWAN_FOPTS_MAX: usize = 15;
24
25pub const PAMOJA_LORAWAN_MAC_MAX: usize = 27;
27
28const _: () = assert!(PAMOJA_LORAWAN_FOPTS_MAX == pamoja_lorawan::mac::FOPTS_MAX);
31const _: () = assert!(PAMOJA_LORAWAN_MAC_MAX == pamoja_lorawan::mac::MAX_COMMAND);
32
33#[repr(C)]
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub struct PamojaLorawanMacCommand {
40 pub cid: u8,
42 pub direction: PamojaLorawanDirection,
44 pub margin: u8,
46 pub gateways: u8,
48 pub data_rate: u8,
50 pub tx_power: u8,
52 pub channel_mask: u16,
54 pub mask_control: u8,
56 pub transmissions: u8,
58 pub power_ack: u8,
60 pub data_rate_ack: u8,
62 pub channel_mask_ack: u8,
64 pub max_duty_cycle: u8,
66 pub rx1_offset: u8,
68 pub rx2_data_rate: u8,
70 pub frequency_hz: u32,
72 pub rx1_offset_ack: u8,
74 pub rx2_data_rate_ack: u8,
76 pub channel_ack: u8,
78 pub battery: u8,
80 pub snr_margin: i8,
82 pub index: u8,
84 pub max_data_rate: u8,
86 pub min_data_rate: u8,
88 pub data_rate_range_ok: u8,
90 pub frequency_ok: u8,
92 pub delay: u8,
94 pub max_eirp: u8,
96 pub uplink_dwell: u8,
98 pub downlink_dwell: u8,
100 pub uplink_frequency_exists: u8,
102 pub seconds: u32,
104 pub fraction: u8,
106 pub enabled: u8,
108 pub cad_periodicity: u8,
110 pub default_channel_index: u8,
112 pub second_channel_index: u8,
114 pub second_channel_data_rate: u8,
116 pub second_channel_ack_offset: u8,
118 pub cad_periodicity_ack: u8,
120 pub default_channel_index_ack: u8,
122 pub second_channel_index_ack: u8,
124 pub second_channel_data_rate_ack: u8,
126 pub second_channel_ack_offset_ack: u8,
128 pub second_channel_frequency_ack: u8,
130 pub relay_mode: u8,
132 pub smart_enable_level: u8,
134 pub back_off: u8,
136 pub action: u8,
138 pub eui_len: u8,
140 pub eui: [u8; 16],
142 pub combined_rules_ack: u8,
144 pub eui_len_ack: u8,
146 pub action_ack: u8,
148 pub reload_rate: u8,
150 pub bucket_size: u8,
152 pub dev_addr: u32,
154 pub wfcnt: u32,
156 pub root_wor_s_key: [u8; 16],
158 pub index_ack: u8,
160 pub reset_limit_counters: u8,
162 pub join_request_reload_rate: u8,
164 pub notify_reload_rate: u8,
166 pub global_uplink_reload_rate: u8,
168 pub overall_reload_rate: u8,
170 pub join_request_bucket_size: u8,
172 pub notify_bucket_size: u8,
174 pub global_uplink_bucket_size: u8,
176 pub overall_bucket_size: u8,
178 pub rssi_dbm: i16,
180 pub snr_db: i8,
182}
183
184impl PamojaLorawanMacCommand {
185 const fn blank(cid: u8, direction: PamojaLorawanDirection) -> PamojaLorawanMacCommand {
186 PamojaLorawanMacCommand {
187 cid,
188 direction,
189 margin: 0,
190 gateways: 0,
191 data_rate: 0,
192 tx_power: 0,
193 channel_mask: 0,
194 mask_control: 0,
195 transmissions: 0,
196 power_ack: 0,
197 data_rate_ack: 0,
198 channel_mask_ack: 0,
199 max_duty_cycle: 0,
200 rx1_offset: 0,
201 rx2_data_rate: 0,
202 frequency_hz: 0,
203 rx1_offset_ack: 0,
204 rx2_data_rate_ack: 0,
205 channel_ack: 0,
206 battery: 0,
207 snr_margin: 0,
208 index: 0,
209 max_data_rate: 0,
210 min_data_rate: 0,
211 data_rate_range_ok: 0,
212 frequency_ok: 0,
213 delay: 0,
214 max_eirp: 0,
215 uplink_dwell: 0,
216 downlink_dwell: 0,
217 uplink_frequency_exists: 0,
218 seconds: 0,
219 fraction: 0,
220 enabled: 0,
221 cad_periodicity: 0,
222 default_channel_index: 0,
223 second_channel_index: 0,
224 second_channel_data_rate: 0,
225 second_channel_ack_offset: 0,
226 cad_periodicity_ack: 0,
227 default_channel_index_ack: 0,
228 second_channel_index_ack: 0,
229 second_channel_data_rate_ack: 0,
230 second_channel_ack_offset_ack: 0,
231 second_channel_frequency_ack: 0,
232 relay_mode: 0,
233 smart_enable_level: 0,
234 back_off: 0,
235 action: 0,
236 eui_len: 0,
237 eui: [0; 16],
238 combined_rules_ack: 0,
239 eui_len_ack: 0,
240 action_ack: 0,
241 reload_rate: 0,
242 bucket_size: 0,
243 dev_addr: 0,
244 wfcnt: 0,
245 root_wor_s_key: [0; 16],
246 index_ack: 0,
247 reset_limit_counters: 0,
248 join_request_reload_rate: 0,
249 notify_reload_rate: 0,
250 global_uplink_reload_rate: 0,
251 overall_reload_rate: 0,
252 join_request_bucket_size: 0,
253 notify_bucket_size: 0,
254 global_uplink_bucket_size: 0,
255 overall_bucket_size: 0,
256 rssi_dbm: 0,
257 snr_db: 0,
258 }
259 }
260}
261
262fn crossing(direction: PamojaLorawanDirection) -> Direction {
263 match direction {
264 PamojaLorawanDirection::Uplink => Direction::Uplink,
265 PamojaLorawanDirection::Downlink => Direction::Downlink,
266 }
267}
268
269fn traveling(direction: Direction) -> PamojaLorawanDirection {
270 match direction {
271 Direction::Uplink => PamojaLorawanDirection::Uplink,
272 Direction::Downlink => PamojaLorawanDirection::Downlink,
273 }
274}
275
276pub(crate) fn write_command(command: MacCommand) -> PamojaLorawanMacCommand {
278 let mut flat = PamojaLorawanMacCommand::blank(command.cid(), traveling(command.direction()));
279 match command {
280 MacCommand::LinkCheckReq
281 | MacCommand::DutyCycleAns
282 | MacCommand::DevStatusReq
283 | MacCommand::RxTimingSetupAns
284 | MacCommand::TxParamSetupAns
285 | MacCommand::DeviceTimeReq => {}
286 MacCommand::LinkCheckAns { margin, gateways } => {
287 flat.margin = margin;
288 flat.gateways = gateways;
289 }
290 MacCommand::LinkAdrReq {
291 data_rate,
292 tx_power,
293 channel_mask,
294 mask_control,
295 transmissions,
296 } => {
297 flat.data_rate = data_rate;
298 flat.tx_power = tx_power;
299 flat.channel_mask = channel_mask;
300 flat.mask_control = mask_control;
301 flat.transmissions = transmissions;
302 }
303 MacCommand::LinkAdrAns {
304 power_ack,
305 data_rate_ack,
306 channel_mask_ack,
307 } => {
308 flat.power_ack = u8::from(power_ack);
309 flat.data_rate_ack = u8::from(data_rate_ack);
310 flat.channel_mask_ack = u8::from(channel_mask_ack);
311 }
312 MacCommand::DutyCycleReq { max_duty_cycle } => flat.max_duty_cycle = max_duty_cycle,
313 MacCommand::RxParamSetupReq {
314 rx1_offset,
315 rx2_data_rate,
316 frequency_hz,
317 } => {
318 flat.rx1_offset = rx1_offset;
319 flat.rx2_data_rate = rx2_data_rate;
320 flat.frequency_hz = frequency_hz;
321 }
322 MacCommand::RxParamSetupAns {
323 rx1_offset_ack,
324 rx2_data_rate_ack,
325 channel_ack,
326 } => {
327 flat.rx1_offset_ack = u8::from(rx1_offset_ack);
328 flat.rx2_data_rate_ack = u8::from(rx2_data_rate_ack);
329 flat.channel_ack = u8::from(channel_ack);
330 }
331 MacCommand::DevStatusAns { battery, margin } => {
332 flat.battery = battery;
333 flat.snr_margin = margin;
334 }
335 MacCommand::NewChannelReq {
336 index,
337 frequency_hz,
338 max_data_rate,
339 min_data_rate,
340 } => {
341 flat.index = index;
342 flat.frequency_hz = frequency_hz;
343 flat.max_data_rate = max_data_rate;
344 flat.min_data_rate = min_data_rate;
345 }
346 MacCommand::NewChannelAns {
347 data_rate_range_ok,
348 frequency_ok,
349 } => {
350 flat.data_rate_range_ok = u8::from(data_rate_range_ok);
351 flat.frequency_ok = u8::from(frequency_ok);
352 }
353 MacCommand::RxTimingSetupReq { delay } => flat.delay = delay,
354 MacCommand::TxParamSetupReq {
355 max_eirp,
356 uplink_dwell,
357 downlink_dwell,
358 } => {
359 flat.max_eirp = max_eirp;
360 flat.uplink_dwell = u8::from(uplink_dwell);
361 flat.downlink_dwell = u8::from(downlink_dwell);
362 }
363 MacCommand::DlChannelReq {
364 index,
365 frequency_hz,
366 } => {
367 flat.index = index;
368 flat.frequency_hz = frequency_hz;
369 }
370 MacCommand::DlChannelAns {
371 uplink_frequency_exists,
372 frequency_ok,
373 } => {
374 flat.uplink_frequency_exists = u8::from(uplink_frequency_exists);
375 flat.frequency_ok = u8::from(frequency_ok);
376 }
377 MacCommand::DeviceTimeAns { seconds, fraction } => {
378 flat.seconds = seconds;
379 flat.fraction = fraction;
380 }
381 MacCommand::RelayConfReq {
382 enabled,
383 cad_periodicity,
384 default_channel_index,
385 second_channel_index,
386 second_channel_data_rate,
387 second_channel_ack_offset,
388 second_channel_frequency_hz,
389 } => {
390 flat.enabled = u8::from(enabled);
391 flat.cad_periodicity = cad_periodicity;
392 flat.default_channel_index = default_channel_index;
393 flat.second_channel_index = second_channel_index;
394 flat.second_channel_data_rate = second_channel_data_rate;
395 flat.second_channel_ack_offset = second_channel_ack_offset;
396 flat.frequency_hz = second_channel_frequency_hz;
397 }
398 MacCommand::RelayConfAns {
399 cad_periodicity_ack,
400 default_channel_index_ack,
401 second_channel_index_ack,
402 second_channel_data_rate_ack,
403 second_channel_ack_offset_ack,
404 second_channel_frequency_ack,
405 } => {
406 flat.cad_periodicity_ack = u8::from(cad_periodicity_ack);
407 flat.default_channel_index_ack = u8::from(default_channel_index_ack);
408 flat.second_channel_index_ack = u8::from(second_channel_index_ack);
409 flat.second_channel_data_rate_ack = u8::from(second_channel_data_rate_ack);
410 flat.second_channel_ack_offset_ack = u8::from(second_channel_ack_offset_ack);
411 flat.second_channel_frequency_ack = u8::from(second_channel_frequency_ack);
412 }
413 MacCommand::EndDeviceConfReq {
414 relay_mode,
415 smart_enable_level,
416 back_off,
417 second_channel_index,
418 second_channel_data_rate,
419 second_channel_ack_offset,
420 second_channel_frequency_hz,
421 } => {
422 flat.relay_mode = relay_mode;
423 flat.smart_enable_level = smart_enable_level;
424 flat.back_off = back_off;
425 flat.second_channel_index = second_channel_index;
426 flat.second_channel_data_rate = second_channel_data_rate;
427 flat.second_channel_ack_offset = second_channel_ack_offset;
428 flat.frequency_hz = second_channel_frequency_hz;
429 }
430 MacCommand::EndDeviceConfAns {
431 second_channel_ack_offset_ack,
432 second_channel_index_ack,
433 second_channel_data_rate_ack,
434 second_channel_frequency_ack,
435 } => {
436 flat.second_channel_ack_offset_ack = u8::from(second_channel_ack_offset_ack);
437 flat.second_channel_index_ack = u8::from(second_channel_index_ack);
438 flat.second_channel_data_rate_ack = u8::from(second_channel_data_rate_ack);
439 flat.second_channel_frequency_ack = u8::from(second_channel_frequency_ack);
440 }
441 MacCommand::FilterListReq {
442 index,
443 action,
444 eui_len,
445 eui,
446 } => {
447 flat.index = index;
448 flat.action = action;
449 flat.eui_len = eui_len;
450 flat.eui = eui;
451 }
452 MacCommand::FilterListAns {
453 combined_rules_ack,
454 eui_len_ack,
455 action_ack,
456 } => {
457 flat.combined_rules_ack = u8::from(combined_rules_ack);
458 flat.eui_len_ack = u8::from(eui_len_ack);
459 flat.action_ack = u8::from(action_ack);
460 }
461 MacCommand::UpdateUplinkListReq {
462 index,
463 reload_rate,
464 bucket_size,
465 dev_addr,
466 wfcnt,
467 root_wor_s_key,
468 } => {
469 flat.index = index;
470 flat.reload_rate = reload_rate;
471 flat.bucket_size = bucket_size;
472 flat.dev_addr = dev_addr;
473 flat.wfcnt = wfcnt;
474 flat.root_wor_s_key = root_wor_s_key;
475 }
476 MacCommand::UpdateUplinkListAns => {}
477 MacCommand::CtrlUplinkListReq { index, action } => {
478 flat.index = index;
479 flat.action = action;
480 }
481 MacCommand::CtrlUplinkListAns { index_ack, wfcnt } => {
482 flat.index_ack = u8::from(index_ack);
483 flat.wfcnt = wfcnt;
484 }
485 MacCommand::ConfigureFwdLimitReq {
486 reset_limit_counters,
487 join_request_reload_rate,
488 notify_reload_rate,
489 global_uplink_reload_rate,
490 overall_reload_rate,
491 join_request_bucket_size,
492 notify_bucket_size,
493 global_uplink_bucket_size,
494 overall_bucket_size,
495 } => {
496 flat.reset_limit_counters = reset_limit_counters;
497 flat.join_request_reload_rate = join_request_reload_rate;
498 flat.notify_reload_rate = notify_reload_rate;
499 flat.global_uplink_reload_rate = global_uplink_reload_rate;
500 flat.overall_reload_rate = overall_reload_rate;
501 flat.join_request_bucket_size = join_request_bucket_size;
502 flat.notify_bucket_size = notify_bucket_size;
503 flat.global_uplink_bucket_size = global_uplink_bucket_size;
504 flat.overall_bucket_size = overall_bucket_size;
505 }
506 MacCommand::ConfigureFwdLimitAns => {}
507 MacCommand::NotifyNewEndDeviceReq {
508 dev_addr,
509 rssi_dbm,
510 snr_db,
511 } => {
512 flat.dev_addr = dev_addr;
513 flat.rssi_dbm = rssi_dbm;
514 flat.snr_db = snr_db;
515 }
516 }
517 flat
518}
519
520pub(crate) fn read_command(flat: &PamojaLorawanMacCommand) -> Option<MacCommand> {
523 use pamoja_lorawan::mac;
524
525 let down = matches!(flat.direction, PamojaLorawanDirection::Downlink);
526 let on = |value: u8| value != 0;
527
528 let command = match (flat.cid, down) {
529 (mac::CID_LINK_CHECK, false) => MacCommand::LinkCheckReq,
530 (mac::CID_LINK_CHECK, true) => MacCommand::LinkCheckAns {
531 margin: flat.margin,
532 gateways: flat.gateways,
533 },
534 (mac::CID_LINK_ADR, true) => MacCommand::LinkAdrReq {
535 data_rate: flat.data_rate,
536 tx_power: flat.tx_power,
537 channel_mask: flat.channel_mask,
538 mask_control: flat.mask_control,
539 transmissions: flat.transmissions,
540 },
541 (mac::CID_LINK_ADR, false) => MacCommand::LinkAdrAns {
542 power_ack: on(flat.power_ack),
543 data_rate_ack: on(flat.data_rate_ack),
544 channel_mask_ack: on(flat.channel_mask_ack),
545 },
546 (mac::CID_DUTY_CYCLE, true) => MacCommand::DutyCycleReq {
547 max_duty_cycle: flat.max_duty_cycle,
548 },
549 (mac::CID_DUTY_CYCLE, false) => MacCommand::DutyCycleAns,
550 (mac::CID_RX_PARAM_SETUP, true) => MacCommand::RxParamSetupReq {
551 rx1_offset: flat.rx1_offset,
552 rx2_data_rate: flat.rx2_data_rate,
553 frequency_hz: flat.frequency_hz,
554 },
555 (mac::CID_RX_PARAM_SETUP, false) => MacCommand::RxParamSetupAns {
556 rx1_offset_ack: on(flat.rx1_offset_ack),
557 rx2_data_rate_ack: on(flat.rx2_data_rate_ack),
558 channel_ack: on(flat.channel_ack),
559 },
560 (mac::CID_DEV_STATUS, true) => MacCommand::DevStatusReq,
561 (mac::CID_DEV_STATUS, false) => MacCommand::DevStatusAns {
562 battery: flat.battery,
563 margin: flat.snr_margin,
564 },
565 (mac::CID_NEW_CHANNEL, true) => MacCommand::NewChannelReq {
566 index: flat.index,
567 frequency_hz: flat.frequency_hz,
568 max_data_rate: flat.max_data_rate,
569 min_data_rate: flat.min_data_rate,
570 },
571 (mac::CID_NEW_CHANNEL, false) => MacCommand::NewChannelAns {
572 data_rate_range_ok: on(flat.data_rate_range_ok),
573 frequency_ok: on(flat.frequency_ok),
574 },
575 (mac::CID_RX_TIMING_SETUP, true) => MacCommand::RxTimingSetupReq { delay: flat.delay },
576 (mac::CID_RX_TIMING_SETUP, false) => MacCommand::RxTimingSetupAns,
577 (mac::CID_TX_PARAM_SETUP, true) => MacCommand::TxParamSetupReq {
578 max_eirp: flat.max_eirp,
579 uplink_dwell: on(flat.uplink_dwell),
580 downlink_dwell: on(flat.downlink_dwell),
581 },
582 (mac::CID_TX_PARAM_SETUP, false) => MacCommand::TxParamSetupAns,
583 (mac::CID_DL_CHANNEL, true) => MacCommand::DlChannelReq {
584 index: flat.index,
585 frequency_hz: flat.frequency_hz,
586 },
587 (mac::CID_DL_CHANNEL, false) => MacCommand::DlChannelAns {
588 uplink_frequency_exists: on(flat.uplink_frequency_exists),
589 frequency_ok: on(flat.frequency_ok),
590 },
591 (mac::CID_DEVICE_TIME, false) => MacCommand::DeviceTimeReq,
592 (mac::CID_DEVICE_TIME, true) => MacCommand::DeviceTimeAns {
593 seconds: flat.seconds,
594 fraction: flat.fraction,
595 },
596 (mac::CID_RELAY_CONF, true) => MacCommand::RelayConfReq {
597 enabled: on(flat.enabled),
598 cad_periodicity: flat.cad_periodicity,
599 default_channel_index: flat.default_channel_index,
600 second_channel_index: flat.second_channel_index,
601 second_channel_data_rate: flat.second_channel_data_rate,
602 second_channel_ack_offset: flat.second_channel_ack_offset,
603 second_channel_frequency_hz: flat.frequency_hz,
604 },
605 (mac::CID_RELAY_CONF, false) => MacCommand::RelayConfAns {
606 cad_periodicity_ack: on(flat.cad_periodicity_ack),
607 default_channel_index_ack: on(flat.default_channel_index_ack),
608 second_channel_index_ack: on(flat.second_channel_index_ack),
609 second_channel_data_rate_ack: on(flat.second_channel_data_rate_ack),
610 second_channel_ack_offset_ack: on(flat.second_channel_ack_offset_ack),
611 second_channel_frequency_ack: on(flat.second_channel_frequency_ack),
612 },
613 (mac::CID_END_DEVICE_CONF, true) => MacCommand::EndDeviceConfReq {
614 relay_mode: flat.relay_mode,
615 smart_enable_level: flat.smart_enable_level,
616 back_off: flat.back_off,
617 second_channel_index: flat.second_channel_index,
618 second_channel_data_rate: flat.second_channel_data_rate,
619 second_channel_ack_offset: flat.second_channel_ack_offset,
620 second_channel_frequency_hz: flat.frequency_hz,
621 },
622 (mac::CID_END_DEVICE_CONF, false) => MacCommand::EndDeviceConfAns {
623 second_channel_ack_offset_ack: on(flat.second_channel_ack_offset_ack),
624 second_channel_index_ack: on(flat.second_channel_index_ack),
625 second_channel_data_rate_ack: on(flat.second_channel_data_rate_ack),
626 second_channel_frequency_ack: on(flat.second_channel_frequency_ack),
627 },
628 (mac::CID_FILTER_LIST, true) => MacCommand::FilterListReq {
629 index: flat.index,
630 action: flat.action,
631 eui_len: flat.eui_len,
632 eui: flat.eui,
633 },
634 (mac::CID_FILTER_LIST, false) => MacCommand::FilterListAns {
635 combined_rules_ack: on(flat.combined_rules_ack),
636 eui_len_ack: on(flat.eui_len_ack),
637 action_ack: on(flat.action_ack),
638 },
639 (mac::CID_UPDATE_UPLINK_LIST, true) => MacCommand::UpdateUplinkListReq {
640 index: flat.index,
641 reload_rate: flat.reload_rate,
642 bucket_size: flat.bucket_size,
643 dev_addr: flat.dev_addr,
644 wfcnt: flat.wfcnt,
645 root_wor_s_key: flat.root_wor_s_key,
646 },
647 (mac::CID_UPDATE_UPLINK_LIST, false) => MacCommand::UpdateUplinkListAns,
648 (mac::CID_CTRL_UPLINK_LIST, true) => MacCommand::CtrlUplinkListReq {
649 index: flat.index,
650 action: flat.action,
651 },
652 (mac::CID_CTRL_UPLINK_LIST, false) => MacCommand::CtrlUplinkListAns {
653 index_ack: on(flat.index_ack),
654 wfcnt: flat.wfcnt,
655 },
656 (mac::CID_CONFIGURE_FWD_LIMIT, true) => MacCommand::ConfigureFwdLimitReq {
657 reset_limit_counters: flat.reset_limit_counters,
658 join_request_reload_rate: flat.join_request_reload_rate,
659 notify_reload_rate: flat.notify_reload_rate,
660 global_uplink_reload_rate: flat.global_uplink_reload_rate,
661 overall_reload_rate: flat.overall_reload_rate,
662 join_request_bucket_size: flat.join_request_bucket_size,
663 notify_bucket_size: flat.notify_bucket_size,
664 global_uplink_bucket_size: flat.global_uplink_bucket_size,
665 overall_bucket_size: flat.overall_bucket_size,
666 },
667 (mac::CID_CONFIGURE_FWD_LIMIT, false) => MacCommand::ConfigureFwdLimitAns,
668 (mac::CID_NOTIFY_NEW_END_DEVICE, false) => MacCommand::NotifyNewEndDeviceReq {
669 dev_addr: flat.dev_addr,
670 rssi_dbm: flat.rssi_dbm,
671 snr_db: flat.snr_db,
672 },
673 _ => return None,
674 };
675 Some(command)
676}
677
678#[no_mangle]
697pub unsafe extern "C" fn pamoja_lorawan_mac_count(
698 direction: PamojaLorawanDirection,
699 bytes: *const u8,
700 len: usize,
701 out_count: *mut usize,
702) -> PamojaStatus {
703 if out_count.is_null() {
704 set_last_error("the count must not be null".to_owned());
705 return PamojaStatus::InvalidArgument;
706 }
707 let field = match read_bytes(bytes, len) {
708 Ok(field) => field,
709 Err(status) => return status,
710 };
711 let read = MacCommands::new(crossing(direction), &field)
712 .take_while(Result::is_ok)
713 .count();
714 *out_count = read;
715 PamojaStatus::Ok
716}
717
718#[no_mangle]
739pub unsafe extern "C" fn pamoja_lorawan_mac_at(
740 direction: PamojaLorawanDirection,
741 bytes: *const u8,
742 len: usize,
743 index: usize,
744 out_command: *mut PamojaLorawanMacCommand,
745) -> PamojaStatus {
746 if out_command.is_null() {
747 set_last_error("the command must not be null".to_owned());
748 return PamojaStatus::InvalidArgument;
749 }
750 let field = match read_bytes(bytes, len) {
751 Ok(field) => field,
752 Err(status) => return status,
753 };
754 let found = MacCommands::new(crossing(direction), &field)
755 .take_while(Result::is_ok)
756 .nth(index)
757 .and_then(Result::ok);
758 match found {
759 Some(command) => {
760 *out_command = write_command(command);
761 PamojaStatus::Ok
762 }
763 None => {
764 set_last_error(format!("there is no readable command at position {index}"));
765 PamojaStatus::InvalidArgument
766 }
767 }
768}
769
770#[no_mangle]
791pub unsafe extern "C" fn pamoja_lorawan_mac_encode(
792 command: *const PamojaLorawanMacCommand,
793 out: *mut u8,
794 capacity: usize,
795 out_written: *mut usize,
796) -> PamojaStatus {
797 if command.is_null() || out.is_null() || out_written.is_null() {
798 set_last_error("the command, the buffer, and the count must not be null".to_owned());
799 return PamojaStatus::InvalidArgument;
800 }
801 let Some(command) = read_command(&*command) else {
802 set_last_error(format!(
803 "identifier {:#04x} names no command in that direction",
804 (*command).cid
805 ));
806 return PamojaStatus::InvalidArgument;
807 };
808
809 let mut buffer = [0u8; PAMOJA_LORAWAN_MAC_MAX];
810 let written = match command.encode(&mut buffer) {
811 Ok(written) => written,
812 Err(error) => {
813 set_last_error(error.to_string());
814 return PamojaStatus::InvalidArgument;
815 }
816 };
817 if written > capacity {
818 set_last_error(format!(
819 "the command takes {written} bytes and there is room for {capacity}"
820 ));
821 return PamojaStatus::InvalidArgument;
822 }
823
824 std::ptr::copy_nonoverlapping(buffer.as_ptr(), out, written);
825 *out_written = written;
826 PamojaStatus::Ok
827}
828
829#[cfg(test)]
830mod tests {
831 use super::*;
832
833 fn at(
834 direction: PamojaLorawanDirection,
835 bytes: &[u8],
836 index: usize,
837 ) -> PamojaLorawanMacCommand {
838 let mut command = PamojaLorawanMacCommand::blank(0, PamojaLorawanDirection::Uplink);
839 let status = unsafe {
840 pamoja_lorawan_mac_at(direction, bytes.as_ptr(), bytes.len(), index, &mut command)
841 };
842 assert_eq!(status, PamojaStatus::Ok);
843 command
844 }
845
846 #[test]
847 fn the_same_bytes_read_as_different_commands_in_each_direction() {
848 let bytes = [0x03, 0x52, 0xff, 0x00, 0x01];
849
850 let down = at(PamojaLorawanDirection::Downlink, &bytes, 0);
851 assert_eq!(down.data_rate, 5);
852 assert_eq!(down.tx_power, 2);
853 assert_eq!(down.channel_mask, 0x00ff);
854
855 let up = at(PamojaLorawanDirection::Uplink, &bytes, 0);
856 assert_eq!(up.cid, down.cid, "the same identifier");
857 assert_eq!(up.data_rate_ack, 1, "read as a status byte instead");
858 assert_eq!(up.data_rate, 0, "and the request fields carry nothing");
859 }
860
861 #[test]
862 fn a_field_of_commands_is_counted_and_read_one_by_one() {
863 let bytes = [0x02, 0x0d];
864 let mut count = 0usize;
865 let status = unsafe {
866 pamoja_lorawan_mac_count(
867 PamojaLorawanDirection::Uplink,
868 bytes.as_ptr(),
869 bytes.len(),
870 &mut count,
871 )
872 };
873
874 assert_eq!(status, PamojaStatus::Ok);
875 assert_eq!(count, 2);
876 assert_eq!(at(PamojaLorawanDirection::Uplink, &bytes, 0).cid, 0x02);
877 assert_eq!(at(PamojaLorawanDirection::Uplink, &bytes, 1).cid, 0x0d);
878 }
879
880 #[test]
881 fn counting_stops_where_reading_does() {
882 let bytes = [0x02, 0x7f, 0x11];
885 let mut count = 0usize;
886 unsafe {
887 pamoja_lorawan_mac_count(
888 PamojaLorawanDirection::Uplink,
889 bytes.as_ptr(),
890 bytes.len(),
891 &mut count,
892 );
893 }
894 assert_eq!(count, 1);
895
896 let mut command = PamojaLorawanMacCommand::blank(0, PamojaLorawanDirection::Uplink);
897 let status = unsafe {
898 pamoja_lorawan_mac_at(
899 PamojaLorawanDirection::Uplink,
900 bytes.as_ptr(),
901 bytes.len(),
902 1,
903 &mut command,
904 )
905 };
906 assert_eq!(status, PamojaStatus::InvalidArgument);
907 }
908
909 #[test]
910 fn a_command_written_out_reads_back_as_itself() {
911 let mut command = PamojaLorawanMacCommand::blank(0x07, PamojaLorawanDirection::Downlink);
912 command.index = 3;
913 command.frequency_hz = 867_100_000;
914 command.max_data_rate = 5;
915 command.min_data_rate = 0;
916
917 let mut out = [0u8; PAMOJA_LORAWAN_MAC_MAX];
918 let mut written = 0usize;
919 let status = unsafe {
920 pamoja_lorawan_mac_encode(&command, out.as_mut_ptr(), out.len(), &mut written)
921 };
922
923 assert_eq!(status, PamojaStatus::Ok);
924 assert_eq!(&out[..written], &[0x07, 0x03, 0x18, 0x4f, 0x84, 0x50]);
925 assert_eq!(
926 at(PamojaLorawanDirection::Downlink, &out[..written], 0),
927 command
928 );
929 }
930
931 #[test]
932 fn a_relay_command_carries_its_key_and_eui_prefix_across() {
933 let mut command = PamojaLorawanMacCommand::blank(0x43, PamojaLorawanDirection::Downlink);
934 command.index = 3;
935 command.reload_rate = 10;
936 command.bucket_size = 2;
937 command.dev_addr = 0x2601_1bda;
938 command.wfcnt = 7;
939 command.root_wor_s_key = core::array::from_fn(|at| at as u8);
940
941 let mut out = [0u8; PAMOJA_LORAWAN_MAC_MAX];
942 let mut written = 0usize;
943 let status = unsafe {
944 pamoja_lorawan_mac_encode(&command, out.as_mut_ptr(), out.len(), &mut written)
945 };
946 assert_eq!(status, PamojaStatus::Ok);
947 assert_eq!(written, PAMOJA_LORAWAN_MAC_MAX);
948 assert_eq!(
949 at(PamojaLorawanDirection::Downlink, &out[..written], 0),
950 command
951 );
952
953 let mut filter = PamojaLorawanMacCommand::blank(0x42, PamojaLorawanDirection::Downlink);
954 filter.index = 1;
955 filter.action = 1;
956 filter.eui_len = 3;
957 filter.eui[..3].copy_from_slice(&[0xab, 0xcd, 0xef]);
958 let status = unsafe {
959 pamoja_lorawan_mac_encode(&filter, out.as_mut_ptr(), out.len(), &mut written)
960 };
961 assert_eq!(status, PamojaStatus::Ok);
962 assert_eq!(&out[..written], &[0x42, 0xa3, 0x00, 0xef, 0xcd, 0xab]);
963 assert_eq!(
964 at(PamojaLorawanDirection::Downlink, &out[..written], 0),
965 filter
966 );
967
968 let mut notice = PamojaLorawanMacCommand::blank(0x46, PamojaLorawanDirection::Uplink);
969 notice.dev_addr = 0x2601_1bda;
970 notice.rssi_dbm = -100;
971 notice.snr_db = 5;
972 let status = unsafe {
973 pamoja_lorawan_mac_encode(¬ice, out.as_mut_ptr(), out.len(), &mut written)
974 };
975 assert_eq!(status, PamojaStatus::Ok);
976 assert_eq!(&out[..written], &[0x46, 0xda, 0x1b, 0x01, 0x26, 0xb9, 0x0a]);
977 }
978
979 #[test]
980 fn an_identifier_that_names_no_command_is_refused() {
981 let command = PamojaLorawanMacCommand::blank(0x7f, PamojaLorawanDirection::Downlink);
982 let mut out = [0u8; PAMOJA_LORAWAN_MAC_MAX];
983 let mut written = 0usize;
984 let status = unsafe {
985 pamoja_lorawan_mac_encode(&command, out.as_mut_ptr(), out.len(), &mut written)
986 };
987
988 assert_eq!(status, PamojaStatus::InvalidArgument);
989 }
990
991 #[test]
992 fn a_buffer_too_small_for_the_command_is_refused() {
993 let mut command = PamojaLorawanMacCommand::blank(0x02, PamojaLorawanDirection::Downlink);
994 command.margin = 20;
995 command.gateways = 3;
996
997 let mut out = [0u8; 2];
998 let mut written = 0usize;
999 let status = unsafe {
1000 pamoja_lorawan_mac_encode(&command, out.as_mut_ptr(), out.len(), &mut written)
1001 };
1002
1003 assert_eq!(status, PamojaStatus::InvalidArgument);
1004 assert_eq!(written, 0, "and nothing was written");
1005 }
1006}