1use std::ptr;
11
12use pamoja_lorawan::packages::clock::{ClockSync, PORT as CLOCK_PORT};
13use pamoja_lorawan::packages::firmware::{
14 FirmwareManager, Image, UpImageStatus, PORT as FIRMWARE_PORT,
15};
16use pamoja_lorawan::packages::fragment::{
17 data_block_int_key, parity_line, prbs23, BlockMic, BlockMicKey, Defragmenter, FragError,
18 Fragmenter, Progress, MAX_FRAGMENTS, PORT as FRAGMENT_PORT,
19};
20use pamoja_lorawan::packages::multicast::{
21 mc_app_s_key, mc_ke_key, mc_key, mc_nwk_s_key, mc_root_key_for, wrap_mc_key,
22 PORT as MULTICAST_PORT,
23};
24
25use pamoja_lorawan::packages::clock::ClockCommand;
26use pamoja_lorawan::packages::firmware::{DeleteStatus, FirmwareCommand};
27use pamoja_lorawan::packages::fragment::{FragCommand, SetupStatus};
28use pamoja_lorawan::packages::multicast::{McCommand, SessionStatus};
29use pamoja_lorawan::packages::PackageVersion;
30use pamoja_lorawan::Direction;
31
32use crate::{read_bytes, set_last_error, PamojaStatus};
33
34pub const PAMOJA_LORAWAN_CLOCK_PORT: u8 = 202;
36pub const PAMOJA_LORAWAN_FRAGMENT_PORT: u8 = 201;
38pub const PAMOJA_LORAWAN_MULTICAST_PORT: u8 = 200;
40pub const PAMOJA_LORAWAN_FIRMWARE_PORT: u8 = 203;
42pub const PAMOJA_LORAWAN_MAX_FRAGMENTS: u16 = 16383;
44
45const _: () = assert!(PAMOJA_LORAWAN_CLOCK_PORT == CLOCK_PORT);
48const _: () = assert!(PAMOJA_LORAWAN_FRAGMENT_PORT == FRAGMENT_PORT);
49const _: () = assert!(PAMOJA_LORAWAN_MULTICAST_PORT == MULTICAST_PORT);
50const _: () = assert!(PAMOJA_LORAWAN_FIRMWARE_PORT == FIRMWARE_PORT);
51const _: () = assert!(PAMOJA_LORAWAN_MAX_FRAGMENTS == MAX_FRAGMENTS);
52
53pub const PAMOJA_LORAWAN_IMAGE_NONE: u8 = 0;
55pub const PAMOJA_LORAWAN_IMAGE_CORRUPT: u8 = 1;
57pub const PAMOJA_LORAWAN_IMAGE_WRONG_HARDWARE: u8 = 2;
59pub const PAMOJA_LORAWAN_IMAGE_VALID: u8 = 3;
61
62#[no_mangle]
83pub unsafe extern "C" fn pamoja_lorawan_mc_root_key(
84 root_key: *const u8,
85 lorawan_11: u8,
86 out_key: *mut u8,
87) -> PamojaStatus {
88 derive(root_key, out_key, |key| {
89 mc_root_key_for(key, lorawan_11 != 0)
90 })
91}
92
93#[no_mangle]
112pub unsafe extern "C" fn pamoja_lorawan_mc_ke_key(
113 mc_root_key: *const u8,
114 out_key: *mut u8,
115) -> PamojaStatus {
116 derive(mc_root_key, out_key, mc_ke_key)
117}
118
119#[no_mangle]
139pub unsafe extern "C" fn pamoja_lorawan_mc_key(
140 mc_ke_key: *const u8,
141 wrapped: *const u8,
142 out_key: *mut u8,
143) -> PamojaStatus {
144 let (Some(ke), Some(wrapped), false) =
145 (sixteen(mc_ke_key), sixteen(wrapped), out_key.is_null())
146 else {
147 return missing();
148 };
149 ptr::copy_nonoverlapping(mc_key(&ke, &wrapped).as_ptr(), out_key, 16);
150 PamojaStatus::Ok
151}
152
153#[no_mangle]
173pub unsafe extern "C" fn pamoja_lorawan_wrap_mc_key(
174 mc_ke_key: *const u8,
175 mc_key: *const u8,
176 out_wrapped: *mut u8,
177) -> PamojaStatus {
178 let (Some(ke), Some(key), false) = (sixteen(mc_ke_key), sixteen(mc_key), out_wrapped.is_null())
179 else {
180 return missing();
181 };
182 ptr::copy_nonoverlapping(wrap_mc_key(&ke, &key).as_ptr(), out_wrapped, 16);
183 PamojaStatus::Ok
184}
185
186#[no_mangle]
206pub unsafe extern "C" fn pamoja_lorawan_mc_app_s_key(
207 mc_key: *const u8,
208 mc_addr: u32,
209 out_key: *mut u8,
210) -> PamojaStatus {
211 derive(mc_key, out_key, |key| mc_app_s_key(key, mc_addr))
212}
213
214#[no_mangle]
234pub unsafe extern "C" fn pamoja_lorawan_mc_nwk_s_key(
235 mc_key: *const u8,
236 mc_addr: u32,
237 out_key: *mut u8,
238) -> PamojaStatus {
239 derive(mc_key, out_key, |key| mc_nwk_s_key(key, mc_addr))
240}
241
242#[no_mangle]
261pub unsafe extern "C" fn pamoja_lorawan_data_block_int_key(
262 root_key: *const u8,
263 out_key: *mut u8,
264) -> PamojaStatus {
265 derive(root_key, out_key, data_block_int_key)
266}
267
268#[no_mangle]
278pub extern "C" fn pamoja_lorawan_frag_prbs23(x: u32) -> u32 {
279 prbs23(x)
280}
281
282#[no_mangle]
305pub unsafe extern "C" fn pamoja_lorawan_frag_parity_line(
306 coded: u16,
307 nb_frag: u16,
308 out_line: *mut u8,
309 line_len: usize,
310 out_ones: *mut usize,
311) -> PamojaStatus {
312 if out_line.is_null() || out_ones.is_null() || nb_frag == 0 {
313 return missing();
314 }
315 let needed = usize::from(nb_frag).div_ceil(8);
316 if line_len < needed {
317 set_last_error(format!("the line needs {needed} bytes, not {line_len}"));
318 return PamojaStatus::InvalidArgument;
319 }
320 let line = core::slice::from_raw_parts_mut(out_line, line_len);
321 *out_ones = parity_line(coded, nb_frag, line);
322 PamojaStatus::Ok
323}
324
325#[no_mangle]
347pub unsafe extern "C" fn pamoja_lorawan_frag_session(
348 block_len: usize,
349 frag_size: u8,
350 out_nb_frag: *mut u16,
351 out_padding: *mut u8,
352) -> PamojaStatus {
353 if out_nb_frag.is_null() || out_padding.is_null() || frag_size == 0 || block_len == 0 {
354 return missing();
355 }
356 let size = usize::from(frag_size);
357 let nb_frag = block_len.div_ceil(size);
358 if nb_frag > MAX_FRAGMENTS as usize {
359 set_last_error(format!(
360 "{nb_frag} fragments is more than a session carries"
361 ));
362 return PamojaStatus::InvalidArgument;
363 }
364 *out_nb_frag = nb_frag as u16;
365 *out_padding = (nb_frag * size - block_len) as u8;
366 PamojaStatus::Ok
367}
368
369#[no_mangle]
394pub unsafe extern "C" fn pamoja_lorawan_frag_fragment(
395 block: *const u8,
396 block_len: usize,
397 frag_size: u8,
398 n: u16,
399 out_fragment: *mut u8,
400 out_len: usize,
401) -> PamojaStatus {
402 let bytes = match read_bytes(block, block_len) {
403 Ok(bytes) => bytes,
404 Err(status) => return status,
405 };
406 if out_fragment.is_null() {
407 return missing();
408 }
409 let Ok(sender) = Fragmenter::new(&bytes, frag_size) else {
410 set_last_error("the block and fragment size do not make a session".to_owned());
411 return PamojaStatus::InvalidArgument;
412 };
413 let out = core::slice::from_raw_parts_mut(out_fragment, out_len);
414 match sender.fragment(n, out) {
415 Ok(_) => PamojaStatus::Ok,
416 Err(error) => {
417 set_last_error(error.to_string());
418 PamojaStatus::InvalidArgument
419 }
420 }
421}
422
423pub struct PamojaLorawanDefrag {
426 block: Vec<u8>,
427 matrix: Vec<u8>,
428 nb_frag: u16,
429 frag_size: u8,
430 progress: Progress,
431 missing: u16,
432}
433
434#[no_mangle]
445pub extern "C" fn pamoja_lorawan_defrag_matrix_len(nb_frag: u16, max_lost: u16) -> usize {
446 Defragmenter::matrix_len(nb_frag, max_lost)
447}
448
449#[no_mangle]
471pub unsafe extern "C" fn pamoja_lorawan_defrag_new(
472 nb_frag: u16,
473 frag_size: u8,
474 max_lost: u16,
475 out_session: *mut *mut PamojaLorawanDefrag,
476) -> PamojaStatus {
477 if out_session.is_null() {
478 return missing();
479 }
480 *out_session = ptr::null_mut();
481 if nb_frag == 0 || nb_frag > MAX_FRAGMENTS || frag_size == 0 {
482 set_last_error("the session is not one this build runs".to_owned());
483 return PamojaStatus::InvalidArgument;
484 }
485 let block = vec![0u8; usize::from(nb_frag) * usize::from(frag_size)];
486 let matrix = vec![0u8; Defragmenter::matrix_len(nb_frag, max_lost.min(nb_frag))];
487 let session = PamojaLorawanDefrag {
488 block,
489 matrix,
490 nb_frag,
491 frag_size,
492 progress: Progress::default(),
493 missing: nb_frag,
494 };
495 *out_session = Box::into_raw(Box::new(session));
496 PamojaStatus::Ok
497}
498
499#[no_mangle]
509pub unsafe extern "C" fn pamoja_lorawan_defrag_free(session: *mut PamojaLorawanDefrag) {
510 if !session.is_null() {
511 drop(Box::from_raw(session));
512 }
513}
514
515#[no_mangle]
539pub unsafe extern "C" fn pamoja_lorawan_defrag_fragment(
540 session: *mut PamojaLorawanDefrag,
541 n: u16,
542 fragment: *const u8,
543 fragment_len: usize,
544 out_done: *mut u8,
545) -> PamojaStatus {
546 let (Some(session), false) = (session.as_mut(), out_done.is_null()) else {
547 return missing();
548 };
549 let bytes = match read_bytes(fragment, fragment_len) {
550 Ok(bytes) => bytes,
551 Err(status) => return status,
552 };
553 let mut receiver = match Defragmenter::resumed(
556 session.nb_frag,
557 session.frag_size,
558 &mut session.block,
559 &mut session.matrix,
560 session.progress,
561 ) {
562 Ok(receiver) => receiver,
563 Err(error) => {
564 set_last_error(error.to_string());
565 return PamojaStatus::InvalidArgument;
566 }
567 };
568 let outcome = receiver.fragment(n, &bytes);
569 let progress = receiver.progress();
570 let missing = receiver.missing();
571 session.progress = progress;
572 session.missing = missing;
573 match outcome {
574 Ok(done) => {
575 *out_done = u8::from(done);
576 PamojaStatus::Ok
577 }
578 Err(FragError::Memory) => {
579 set_last_error(FragError::Memory.to_string());
580 PamojaStatus::Other
581 }
582 Err(error) => {
583 set_last_error(error.to_string());
584 PamojaStatus::InvalidArgument
585 }
586 }
587}
588
589#[no_mangle]
610pub unsafe extern "C" fn pamoja_lorawan_defrag_status(
611 session: *const PamojaLorawanDefrag,
612 out_received: *mut u16,
613 out_missing: *mut u16,
614 out_done: *mut u8,
615) -> PamojaStatus {
616 let Some(session) = session.as_ref() else {
617 return missing();
618 };
619 if !out_received.is_null() {
620 *out_received = session.progress.received;
621 }
622 if !out_missing.is_null() {
623 *out_missing = session.missing;
624 }
625 if !out_done.is_null() {
626 *out_done = u8::from(session.progress.done);
627 }
628 PamojaStatus::Ok
629}
630
631#[no_mangle]
653pub unsafe extern "C" fn pamoja_lorawan_defrag_block(
654 session: *const PamojaLorawanDefrag,
655 out_block: *mut u8,
656 capacity: usize,
657 out_len: *mut usize,
658) -> PamojaStatus {
659 let (Some(session), false) = (session.as_ref(), out_len.is_null()) else {
660 return missing();
661 };
662 let len = usize::from(session.nb_frag) * usize::from(session.frag_size);
663 *out_len = len;
664 if out_block.is_null() {
665 return PamojaStatus::Ok;
666 }
667 if capacity < len {
668 set_last_error(format!("the block needs {len} bytes, not {capacity}"));
669 return PamojaStatus::InvalidArgument;
670 }
671 ptr::copy_nonoverlapping(session.block.as_ptr(), out_block, len);
672 PamojaStatus::Ok
673}
674
675pub struct PamojaLorawanBlockMic {
677 key: BlockMicKey,
678 mic: Option<BlockMic<'static>>,
679}
680
681#[no_mangle]
707pub unsafe extern "C" fn pamoja_lorawan_block_mic_start(
708 data_block_int_key: *const u8,
709 session_cnt: u16,
710 frag_index: u8,
711 descriptor: *const u8,
712 block_len: u32,
713 out_mic: *mut *mut PamojaLorawanBlockMic,
714) -> PamojaStatus {
715 let (Some(key), false, false) = (
716 sixteen(data_block_int_key),
717 descriptor.is_null(),
718 out_mic.is_null(),
719 ) else {
720 return missing();
721 };
722 *out_mic = ptr::null_mut();
723 let mut four = [0u8; 4];
724 ptr::copy_nonoverlapping(descriptor, four.as_mut_ptr(), 4);
725
726 let mut held = Box::new(PamojaLorawanBlockMic {
727 key: BlockMicKey::new(&key),
728 mic: None,
729 });
730 let borrowed: &'static BlockMicKey = &*ptr::from_ref(&held.key);
733 held.mic = Some(borrowed.start(session_cnt, frag_index, four, block_len));
734 *out_mic = Box::into_raw(held);
735 PamojaStatus::Ok
736}
737
738#[no_mangle]
758pub unsafe extern "C" fn pamoja_lorawan_block_mic_update(
759 mic: *mut PamojaLorawanBlockMic,
760 data: *const u8,
761 data_len: usize,
762) -> PamojaStatus {
763 let Some(held) = mic.as_mut() else {
764 return missing();
765 };
766 let bytes = match read_bytes(data, data_len) {
767 Ok(bytes) => bytes,
768 Err(status) => return status,
769 };
770 if let Some(mic) = held.mic.as_mut() {
771 mic.update(&bytes);
772 }
773 PamojaStatus::Ok
774}
775
776#[no_mangle]
795pub unsafe extern "C" fn pamoja_lorawan_block_mic_finish(
796 mic: *mut PamojaLorawanBlockMic,
797 out_mic: *mut u8,
798) -> PamojaStatus {
799 if mic.is_null() || out_mic.is_null() {
800 return missing();
801 }
802 let mut held = Box::from_raw(mic);
803 let Some(taken) = held.mic.take() else {
804 return missing();
805 };
806 let code = taken.finish();
807 ptr::copy_nonoverlapping(code.as_ptr(), out_mic, 4);
808 PamojaStatus::Ok
809}
810
811#[no_mangle]
821pub unsafe extern "C" fn pamoja_lorawan_block_mic_free(mic: *mut PamojaLorawanBlockMic) {
822 if !mic.is_null() {
823 let mut held = Box::from_raw(mic);
824 held.mic = None;
825 }
826}
827
828pub struct PamojaLorawanClockSync {
831 sync: ClockSync,
832}
833
834#[no_mangle]
855pub unsafe extern "C" fn pamoja_lorawan_clock_sync_new(
856 self_managed: u8,
857 out_sync: *mut *mut PamojaLorawanClockSync,
858) -> PamojaStatus {
859 if out_sync.is_null() {
860 return missing();
861 }
862 let sync = if self_managed != 0 {
863 ClockSync::self_managed()
864 } else {
865 ClockSync::new()
866 };
867 *out_sync = Box::into_raw(Box::new(PamojaLorawanClockSync { sync }));
868 PamojaStatus::Ok
869}
870
871#[no_mangle]
881pub unsafe extern "C" fn pamoja_lorawan_clock_sync_free(sync: *mut PamojaLorawanClockSync) {
882 if !sync.is_null() {
883 drop(Box::from_raw(sync));
884 }
885}
886
887#[no_mangle]
911pub unsafe extern "C" fn pamoja_lorawan_clock_sync_request(
912 sync: *mut PamojaLorawanClockSync,
913 device_time: u32,
914 ans_required: u8,
915 out_command: *mut u8,
916 capacity: usize,
917 out_len: *mut usize,
918) -> PamojaStatus {
919 let (Some(sync), false, false) = (sync.as_mut(), out_command.is_null(), out_len.is_null())
920 else {
921 return missing();
922 };
923 let out = core::slice::from_raw_parts_mut(out_command, capacity);
924 match sync.sync.app_time_req(device_time, ans_required != 0, out) {
925 Ok(written) => {
926 *out_len = written;
927 PamojaStatus::Ok
928 }
929 Err(error) => {
930 set_last_error(error.to_string());
931 PamojaStatus::InvalidArgument
932 }
933 }
934}
935
936#[no_mangle]
962#[allow(clippy::too_many_arguments)]
963pub unsafe extern "C" fn pamoja_lorawan_clock_sync_heard(
964 sync: *mut PamojaLorawanClockSync,
965 payload: *const u8,
966 payload_len: usize,
967 out_correction: *mut i32,
968 out_has_correction: *mut u8,
969 out_more: *mut u8,
970 out_resync: *mut u8,
971 out_answer_due: *mut u8,
972) -> PamojaStatus {
973 let Some(sync) = sync.as_mut() else {
974 return missing();
975 };
976 let bytes = match read_bytes(payload, payload_len) {
977 Ok(bytes) => bytes,
978 Err(status) => return status,
979 };
980 let heard = match sync.sync.heard(&bytes) {
981 Ok(heard) => heard,
982 Err(error) => {
983 set_last_error(error.to_string());
984 return PamojaStatus::Codec;
985 }
986 };
987 if !out_correction.is_null() {
988 *out_correction = heard.correction.unwrap_or(0);
989 }
990 if !out_has_correction.is_null() {
991 *out_has_correction = u8::from(heard.correction.is_some());
992 }
993 if !out_more.is_null() {
994 *out_more = u8::from(heard.more_correction);
995 }
996 if !out_resync.is_null() {
997 *out_resync = heard.resync.unwrap_or(0);
998 }
999 if !out_answer_due.is_null() {
1000 *out_answer_due = u8::from(heard.answer_due);
1001 }
1002 PamojaStatus::Ok
1003}
1004
1005#[no_mangle]
1028pub unsafe extern "C" fn pamoja_lorawan_clock_sync_answer(
1029 sync: *mut PamojaLorawanClockSync,
1030 device_time: u32,
1031 out_command: *mut u8,
1032 capacity: usize,
1033 out_len: *mut usize,
1034) -> PamojaStatus {
1035 let (Some(sync), false, false) = (sync.as_mut(), out_command.is_null(), out_len.is_null())
1036 else {
1037 return missing();
1038 };
1039 let out = core::slice::from_raw_parts_mut(out_command, capacity);
1040 match sync.sync.answer(device_time, out) {
1041 Ok(written) => {
1042 *out_len = written;
1043 PamojaStatus::Ok
1044 }
1045 Err(error) => {
1046 set_last_error(error.to_string());
1047 PamojaStatus::InvalidArgument
1048 }
1049 }
1050}
1051
1052#[no_mangle]
1073pub unsafe extern "C" fn pamoja_lorawan_clock_sync_status(
1074 sync: *const PamojaLorawanClockSync,
1075 out_token: *mut u8,
1076 out_period_s: *mut u32,
1077 out_answer_due: *mut u8,
1078) -> PamojaStatus {
1079 let Some(sync) = sync.as_ref() else {
1080 return missing();
1081 };
1082 if !out_token.is_null() {
1083 *out_token = sync.sync.token();
1084 }
1085 if !out_period_s.is_null() {
1086 *out_period_s = sync.sync.period_s();
1087 }
1088 if !out_answer_due.is_null() {
1089 *out_answer_due = u8::from(sync.sync.answer_due());
1090 }
1091 PamojaStatus::Ok
1092}
1093
1094pub struct PamojaLorawanFirmware {
1097 manager: FirmwareManager,
1098}
1099
1100#[no_mangle]
1121pub unsafe extern "C" fn pamoja_lorawan_firmware_new(
1122 firmware: u32,
1123 hardware: u32,
1124 out_manager: *mut *mut PamojaLorawanFirmware,
1125) -> PamojaStatus {
1126 if out_manager.is_null() {
1127 return missing();
1128 }
1129 *out_manager = Box::into_raw(Box::new(PamojaLorawanFirmware {
1130 manager: FirmwareManager::new(firmware, hardware),
1131 }));
1132 PamojaStatus::Ok
1133}
1134
1135#[no_mangle]
1145pub unsafe extern "C" fn pamoja_lorawan_firmware_free(manager: *mut PamojaLorawanFirmware) {
1146 if !manager.is_null() {
1147 drop(Box::from_raw(manager));
1148 }
1149}
1150
1151#[no_mangle]
1172pub unsafe extern "C" fn pamoja_lorawan_firmware_set_image(
1173 manager: *mut PamojaLorawanFirmware,
1174 status: u8,
1175 version: u32,
1176) -> PamojaStatus {
1177 let Some(manager) = manager.as_mut() else {
1178 return missing();
1179 };
1180 manager.manager.set_image(match status {
1181 PAMOJA_LORAWAN_IMAGE_NONE => None,
1182 PAMOJA_LORAWAN_IMAGE_VALID => Some(Image::valid(version)),
1183 other => Some(Image::refused(UpImageStatus::from_bits(other))),
1184 });
1185 PamojaStatus::Ok
1186}
1187
1188#[no_mangle]
1216#[allow(clippy::too_many_arguments)]
1217pub unsafe extern "C" fn pamoja_lorawan_firmware_heard(
1218 manager: *mut PamojaLorawanFirmware,
1219 payload: *const u8,
1220 payload_len: usize,
1221 now_s: u32,
1222 has_now: u8,
1223 out_answers: *mut u8,
1224 capacity: usize,
1225 out_len: *mut usize,
1226) -> PamojaStatus {
1227 let (Some(manager), false, false) =
1228 (manager.as_mut(), out_answers.is_null(), out_len.is_null())
1229 else {
1230 return missing();
1231 };
1232 let bytes = match read_bytes(payload, payload_len) {
1233 Ok(bytes) => bytes,
1234 Err(status) => return status,
1235 };
1236 let out = core::slice::from_raw_parts_mut(out_answers, capacity);
1237 let now = (has_now != 0).then_some(now_s);
1238 match manager.manager.heard_at(&bytes, now, out) {
1239 Ok(written) => {
1240 *out_len = written;
1241 PamojaStatus::Ok
1242 }
1243 Err(error) => {
1244 set_last_error(error.to_string());
1245 PamojaStatus::Codec
1246 }
1247 }
1248}
1249
1250#[no_mangle]
1273pub unsafe extern "C" fn pamoja_lorawan_firmware_reboot(
1274 manager: *const PamojaLorawanFirmware,
1275 out_at_s: *mut u32,
1276 out_has_at: *mut u8,
1277 out_in_s: *mut u32,
1278 out_has_in: *mut u8,
1279 out_now: *mut u8,
1280) -> PamojaStatus {
1281 let Some(manager) = manager.as_ref() else {
1282 return missing();
1283 };
1284 let at = manager.manager.reboot_at_s();
1285 let left = manager.manager.reboot_in_s();
1286 if !out_at_s.is_null() {
1287 *out_at_s = at.unwrap_or(0);
1288 }
1289 if !out_has_at.is_null() {
1290 *out_has_at = u8::from(at.is_some());
1291 }
1292 if !out_in_s.is_null() {
1293 *out_in_s = left.unwrap_or(0);
1294 }
1295 if !out_has_in.is_null() {
1296 *out_has_in = u8::from(left.is_some());
1297 }
1298 if !out_now.is_null() {
1299 *out_now = u8::from(manager.manager.reboot_now());
1300 }
1301 PamojaStatus::Ok
1302}
1303
1304#[no_mangle]
1327pub unsafe extern "C" fn pamoja_lorawan_firmware_image(
1328 manager: *const PamojaLorawanFirmware,
1329 out_status: *mut u8,
1330 out_next_version: *mut u32,
1331 out_has_next_version: *mut u8,
1332) -> PamojaStatus {
1333 let Some(manager) = manager.as_ref() else {
1334 return missing();
1335 };
1336 let image = manager.manager.image();
1337 if !out_status.is_null() {
1338 *out_status = image.map_or(UpImageStatus::None, |image| image.status) as u8;
1339 }
1340 let next = image
1341 .filter(|image| matches!(image.status, UpImageStatus::Valid))
1342 .map(|image| image.version);
1343 if !out_next_version.is_null() {
1344 *out_next_version = next.unwrap_or(0);
1345 }
1346 if !out_has_next_version.is_null() {
1347 *out_has_next_version = u8::from(next.is_some());
1348 }
1349 PamojaStatus::Ok
1350}
1351
1352#[no_mangle]
1370pub unsafe extern "C" fn pamoja_lorawan_firmware_rebooted(
1371 manager: *mut PamojaLorawanFirmware,
1372) -> PamojaStatus {
1373 let Some(manager) = manager.as_mut() else {
1374 return missing();
1375 };
1376 manager.manager.rebooted();
1377 PamojaStatus::Ok
1378}
1379
1380unsafe fn sixteen(from: *const u8) -> Option<[u8; 16]> {
1382 if from.is_null() {
1383 return None;
1384 }
1385 let mut key = [0u8; 16];
1386 ptr::copy_nonoverlapping(from, key.as_mut_ptr(), 16);
1387 Some(key)
1388}
1389
1390fn missing() -> PamojaStatus {
1392 set_last_error("a required argument was null".to_owned());
1393 PamojaStatus::InvalidArgument
1394}
1395
1396unsafe fn derive(
1398 from: *const u8,
1399 out: *mut u8,
1400 step: impl FnOnce(&[u8; 16]) -> [u8; 16],
1401) -> PamojaStatus {
1402 let (Some(key), false) = (sixteen(from), out.is_null()) else {
1403 return missing();
1404 };
1405 ptr::copy_nonoverlapping(step(&key).as_ptr(), out, 16);
1406 PamojaStatus::Ok
1407}
1408
1409#[repr(C)]
1417#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1418pub struct PamojaLorawanPackageCommand {
1419 pub port: u8,
1421 pub cid: u8,
1423 pub uplink: u8,
1425 pub package: u8,
1427 pub version: u8,
1429 pub device_time: u32,
1431 pub time_correction: i32,
1433 pub token: u8,
1435 pub ans_required: u8,
1437 pub period: u8,
1439 pub not_supported: u8,
1441 pub transmissions: u8,
1443 pub firmware: u32,
1445 pub hardware: u32,
1447 pub reboot: u32,
1449 pub image_status: u8,
1451 pub next_version: u32,
1453 pub has_next_version: u8,
1455 pub delete_version: u32,
1457 pub no_valid_image: u8,
1459 pub invalid_version: u8,
1461 pub frag_index: u8,
1463 pub mc_group_bit_mask: u8,
1465 pub nb_frag: u16,
1467 pub frag_size: u8,
1469 pub ack_reception: u8,
1471 pub frag_algo: u8,
1473 pub block_ack_delay: u8,
1475 pub padding: u8,
1477 pub descriptor: [u8; 4],
1479 pub session_cnt: u16,
1481 pub mic: [u8; 4],
1483 pub received: u16,
1485 pub missing: u8,
1487 pub mic_error: u8,
1489 pub memory_error: u8,
1491 pub no_session: u8,
1493 pub unsupported_algorithm: u8,
1495 pub unsupported_index: u8,
1497 pub wrong_descriptor: u8,
1499 pub session_replay: u8,
1501 pub all_participants: u8,
1503 pub status_item: u8,
1506 pub fragment_n: u16,
1508 pub mc_group_id: u8,
1510 pub mc_addr: u32,
1512 pub mc_key_encrypted: [u8; 16],
1514 pub min_mc_fcnt: u32,
1516 pub max_mc_fcnt: u32,
1518 pub group_mask: u8,
1520 pub nb_total_groups: u8,
1522 pub id_error: u8,
1524 pub session_time: u32,
1526 pub time_out: u8,
1528 pub periodicity: u8,
1530 pub dl_frequency_hz: u32,
1532 pub data_rate: u8,
1534 pub time_to_start: u32,
1536 pub has_time_to_start: u8,
1538 pub dr_error: u8,
1540 pub freq_error: u8,
1542 pub start_missed: u8,
1544}
1545
1546impl Default for PamojaLorawanPackageCommand {
1547 fn default() -> PamojaLorawanPackageCommand {
1548 unsafe { core::mem::zeroed() }
1551 }
1552}
1553
1554#[no_mangle]
1583pub unsafe extern "C" fn pamoja_lorawan_package_parse(
1584 port: u8,
1585 uplink: u8,
1586 payload: *const u8,
1587 payload_len: usize,
1588 out_command: *mut PamojaLorawanPackageCommand,
1589 out_taken: *mut usize,
1590) -> PamojaStatus {
1591 if out_command.is_null() || out_taken.is_null() {
1592 return missing();
1593 }
1594 let bytes = match read_bytes(payload, payload_len) {
1595 Ok(bytes) => bytes,
1596 Err(status) => return status,
1597 };
1598 let direction = if uplink != 0 {
1599 Direction::Uplink
1600 } else {
1601 Direction::Downlink
1602 };
1603 let read = match port {
1604 CLOCK_PORT => ClockCommand::parse(direction, &bytes)
1605 .map(|(command, taken)| (clock_out(command), taken)),
1606 FIRMWARE_PORT => FirmwareCommand::parse(direction, &bytes)
1607 .map(|(command, taken)| (firmware_out(command), taken)),
1608 FRAGMENT_PORT => {
1609 FragCommand::parse(direction, &bytes).map(|(command, taken)| (frag_out(command), taken))
1610 }
1611 MULTICAST_PORT => {
1612 McCommand::parse(direction, &bytes).map(|(command, taken)| (mc_out(command), taken))
1613 }
1614 other => {
1615 set_last_error(format!("port {other} is not an application layer package"));
1616 return PamojaStatus::InvalidArgument;
1617 }
1618 };
1619 match read {
1620 Ok((command, taken)) => {
1621 *out_command = command;
1622 *out_taken = taken;
1623 PamojaStatus::Ok
1624 }
1625 Err(error) => {
1626 set_last_error(error.to_string());
1627 PamojaStatus::Codec
1628 }
1629 }
1630}
1631
1632#[no_mangle]
1660pub unsafe extern "C" fn pamoja_lorawan_package_encode(
1661 command: *const PamojaLorawanPackageCommand,
1662 data: *const u8,
1663 data_len: usize,
1664 out_payload: *mut u8,
1665 capacity: usize,
1666 out_len: *mut usize,
1667) -> PamojaStatus {
1668 let (Some(command), false, false) =
1669 (command.as_ref(), out_payload.is_null(), out_len.is_null())
1670 else {
1671 return missing();
1672 };
1673 let carried = match read_bytes(data, data_len) {
1674 Ok(bytes) => bytes,
1675 Err(status) => return status,
1676 };
1677 let out = core::slice::from_raw_parts_mut(out_payload, capacity);
1678 let written = match command.port {
1679 CLOCK_PORT => clock_in(command).map(|c| c.encode(out)),
1680 FIRMWARE_PORT => firmware_in(command).map(|c| c.encode(out)),
1681 FRAGMENT_PORT => frag_in(command, &carried).map(|c| c.encode(out)),
1682 MULTICAST_PORT => mc_in(command).map(|c| c.encode(out)),
1683 other => {
1684 set_last_error(format!("port {other} is not an application layer package"));
1685 return PamojaStatus::InvalidArgument;
1686 }
1687 };
1688 match written {
1689 Some(Ok(len)) => {
1690 *out_len = len;
1691 PamojaStatus::Ok
1692 }
1693 Some(Err(error)) => {
1694 set_last_error(error.to_string());
1695 PamojaStatus::InvalidArgument
1696 }
1697 None => {
1698 set_last_error(format!(
1699 "command {:#04x} on port {} is not one this build writes",
1700 command.cid, command.port
1701 ));
1702 PamojaStatus::Codec
1703 }
1704 }
1705}
1706
1707fn clock_out(command: ClockCommand) -> PamojaLorawanPackageCommand {
1709 let mut flat = PamojaLorawanPackageCommand {
1710 port: CLOCK_PORT,
1711 cid: command.cid(),
1712 uplink: u8::from(matches!(command.direction(), Direction::Uplink)),
1713 ..PamojaLorawanPackageCommand::default()
1714 };
1715 match command {
1716 ClockCommand::PackageVersionAns(version) => {
1717 flat.package = version.package;
1718 flat.version = version.version;
1719 }
1720 ClockCommand::AppTimeReq {
1721 device_time,
1722 ans_required,
1723 token,
1724 } => {
1725 flat.device_time = device_time;
1726 flat.ans_required = u8::from(ans_required);
1727 flat.token = token;
1728 }
1729 ClockCommand::AppTimeAns {
1730 time_correction,
1731 token,
1732 } => {
1733 flat.time_correction = time_correction;
1734 flat.token = token;
1735 }
1736 ClockCommand::DeviceAppTimePeriodicityReq { period } => flat.period = period,
1737 ClockCommand::DeviceAppTimePeriodicityAns {
1738 not_supported,
1739 device_time,
1740 } => {
1741 flat.not_supported = u8::from(not_supported);
1742 flat.device_time = device_time;
1743 }
1744 ClockCommand::ForceDeviceResyncCmd { transmissions } => flat.transmissions = transmissions,
1745 ClockCommand::PackageVersionReq => {}
1746 }
1747 flat
1748}
1749
1750fn clock_in(flat: &PamojaLorawanPackageCommand) -> Option<ClockCommand> {
1752 let up = flat.uplink != 0;
1753 Some(match (flat.cid, up) {
1754 (0x00, false) => ClockCommand::PackageVersionReq,
1755 (0x00, true) => ClockCommand::PackageVersionAns(PackageVersion {
1756 package: flat.package,
1757 version: flat.version,
1758 }),
1759 (0x01, true) => ClockCommand::AppTimeReq {
1760 device_time: flat.device_time,
1761 ans_required: flat.ans_required != 0,
1762 token: flat.token,
1763 },
1764 (0x01, false) => ClockCommand::AppTimeAns {
1765 time_correction: flat.time_correction,
1766 token: flat.token,
1767 },
1768 (0x02, false) => ClockCommand::DeviceAppTimePeriodicityReq {
1769 period: flat.period,
1770 },
1771 (0x02, true) => ClockCommand::DeviceAppTimePeriodicityAns {
1772 not_supported: flat.not_supported != 0,
1773 device_time: flat.device_time,
1774 },
1775 (0x03, false) => ClockCommand::ForceDeviceResyncCmd {
1776 transmissions: flat.transmissions,
1777 },
1778 _ => return None,
1779 })
1780}
1781
1782fn firmware_out(command: FirmwareCommand) -> PamojaLorawanPackageCommand {
1784 let mut flat = PamojaLorawanPackageCommand {
1785 port: FIRMWARE_PORT,
1786 cid: command.cid(),
1787 uplink: u8::from(matches!(command.direction(), Direction::Uplink)),
1788 ..PamojaLorawanPackageCommand::default()
1789 };
1790 match command {
1791 FirmwareCommand::PackageVersionAns(version) => {
1792 flat.package = version.package;
1793 flat.version = version.version;
1794 }
1795 FirmwareCommand::DevVersionAns { firmware, hardware } => {
1796 flat.firmware = firmware;
1797 flat.hardware = hardware;
1798 }
1799 FirmwareCommand::DevRebootTimeReq { reboot_time }
1800 | FirmwareCommand::DevRebootTimeAns { reboot_time } => flat.reboot = reboot_time,
1801 FirmwareCommand::DevRebootCountdownReq { countdown }
1802 | FirmwareCommand::DevRebootCountdownAns { countdown } => flat.reboot = countdown,
1803 FirmwareCommand::DevUpgradeImageAns {
1804 status,
1805 next_version,
1806 } => {
1807 flat.image_status = status as u8;
1808 flat.next_version = next_version.unwrap_or(0);
1809 flat.has_next_version = u8::from(next_version.is_some());
1810 }
1811 FirmwareCommand::DevDeleteImageReq { version } => flat.delete_version = version,
1812 FirmwareCommand::DevDeleteImageAns(status) => {
1813 flat.no_valid_image = u8::from(status.no_valid_image);
1814 flat.invalid_version = u8::from(status.invalid_version);
1815 }
1816 FirmwareCommand::PackageVersionReq
1817 | FirmwareCommand::DevVersionReq
1818 | FirmwareCommand::DevUpgradeImageReq => {}
1819 }
1820 flat
1821}
1822
1823fn firmware_in(flat: &PamojaLorawanPackageCommand) -> Option<FirmwareCommand> {
1825 let up = flat.uplink != 0;
1826 Some(match (flat.cid, up) {
1827 (0x00, false) => FirmwareCommand::PackageVersionReq,
1828 (0x00, true) => FirmwareCommand::PackageVersionAns(PackageVersion {
1829 package: flat.package,
1830 version: flat.version,
1831 }),
1832 (0x01, false) => FirmwareCommand::DevVersionReq,
1833 (0x01, true) => FirmwareCommand::DevVersionAns {
1834 firmware: flat.firmware,
1835 hardware: flat.hardware,
1836 },
1837 (0x02, false) => FirmwareCommand::DevRebootTimeReq {
1838 reboot_time: flat.reboot,
1839 },
1840 (0x02, true) => FirmwareCommand::DevRebootTimeAns {
1841 reboot_time: flat.reboot,
1842 },
1843 (0x03, false) => FirmwareCommand::DevRebootCountdownReq {
1844 countdown: flat.reboot,
1845 },
1846 (0x03, true) => FirmwareCommand::DevRebootCountdownAns {
1847 countdown: flat.reboot,
1848 },
1849 (0x04, false) => FirmwareCommand::DevUpgradeImageReq,
1850 (0x04, true) => FirmwareCommand::DevUpgradeImageAns {
1851 status: UpImageStatus::from_bits(flat.image_status),
1852 next_version: (flat.has_next_version != 0).then_some(flat.next_version),
1853 },
1854 (0x05, false) => FirmwareCommand::DevDeleteImageReq {
1855 version: flat.delete_version,
1856 },
1857 (0x05, true) => FirmwareCommand::DevDeleteImageAns(DeleteStatus {
1858 no_valid_image: flat.no_valid_image != 0,
1859 invalid_version: flat.invalid_version != 0,
1860 }),
1861 _ => return None,
1862 })
1863}
1864
1865fn frag_out(command: FragCommand<'_>) -> PamojaLorawanPackageCommand {
1867 let mut flat = PamojaLorawanPackageCommand {
1868 port: FRAGMENT_PORT,
1869 cid: command.cid(),
1870 uplink: u8::from(matches!(command.direction(), Direction::Uplink)),
1871 ..PamojaLorawanPackageCommand::default()
1872 };
1873 match command {
1874 FragCommand::PackageVersionAns(version) => {
1875 flat.package = version.package;
1876 flat.version = version.version;
1877 }
1878 FragCommand::FragSessionStatusReq {
1879 frag_index,
1880 all_participants,
1881 } => {
1882 flat.frag_index = frag_index;
1883 flat.all_participants = u8::from(all_participants);
1884 }
1885 FragCommand::FragSessionStatusAns {
1886 frag_index,
1887 received,
1888 missing,
1889 mic_error,
1890 memory_error,
1891 no_session,
1892 } => {
1893 flat.frag_index = frag_index;
1894 flat.received = received;
1895 flat.missing = missing;
1896 flat.mic_error = u8::from(mic_error);
1897 flat.memory_error = u8::from(memory_error);
1898 flat.no_session = u8::from(no_session);
1899 }
1900 FragCommand::FragSessionSetupReq {
1901 frag_index,
1902 mc_group_bit_mask,
1903 nb_frag,
1904 frag_size,
1905 ack_reception,
1906 frag_algo,
1907 block_ack_delay,
1908 padding,
1909 descriptor,
1910 session_cnt,
1911 mic,
1912 } => {
1913 flat.frag_index = frag_index;
1914 flat.mc_group_bit_mask = mc_group_bit_mask;
1915 flat.nb_frag = nb_frag;
1916 flat.frag_size = frag_size;
1917 flat.ack_reception = u8::from(ack_reception);
1918 flat.frag_algo = frag_algo;
1919 flat.block_ack_delay = block_ack_delay;
1920 flat.padding = padding;
1921 flat.descriptor = descriptor;
1922 flat.session_cnt = session_cnt;
1923 flat.mic = mic;
1924 }
1925 FragCommand::FragSessionSetupAns(status) => {
1926 flat.frag_index = status.frag_index;
1927 flat.unsupported_algorithm = u8::from(status.unsupported_algorithm);
1928 flat.memory_error = u8::from(status.not_enough_memory);
1929 flat.unsupported_index = u8::from(status.unsupported_index);
1930 flat.wrong_descriptor = u8::from(status.wrong_descriptor);
1931 flat.session_replay = u8::from(status.session_replay);
1932 }
1933 FragCommand::FragSessionDeleteReq { frag_index } => flat.frag_index = frag_index,
1934 FragCommand::FragSessionDeleteAns {
1935 frag_index,
1936 no_session,
1937 } => {
1938 flat.frag_index = frag_index;
1939 flat.no_session = u8::from(no_session);
1940 }
1941 FragCommand::FragDataBlockReceivedReq {
1942 frag_index,
1943 mic_error,
1944 } => {
1945 flat.frag_index = frag_index;
1946 flat.mic_error = u8::from(mic_error);
1947 }
1948 FragCommand::FragDataBlockReceivedAns { frag_index } => flat.frag_index = frag_index,
1949 FragCommand::DataFragment {
1950 frag_index,
1951 n,
1952 data: _,
1953 } => {
1954 flat.frag_index = frag_index;
1955 flat.fragment_n = n;
1956 }
1957 FragCommand::PackageVersionReq => {}
1958 }
1959 flat
1960}
1961
1962fn frag_in<'a>(flat: &PamojaLorawanPackageCommand, data: &'a [u8]) -> Option<FragCommand<'a>> {
1964 let up = flat.uplink != 0;
1965 Some(match (flat.cid, up) {
1966 (0x00, false) => FragCommand::PackageVersionReq,
1967 (0x00, true) => FragCommand::PackageVersionAns(PackageVersion {
1968 package: flat.package,
1969 version: flat.version,
1970 }),
1971 (0x01, false) => FragCommand::FragSessionStatusReq {
1972 frag_index: flat.frag_index,
1973 all_participants: flat.all_participants != 0,
1974 },
1975 (0x01, true) => FragCommand::FragSessionStatusAns {
1976 frag_index: flat.frag_index,
1977 received: flat.received,
1978 missing: flat.missing,
1979 mic_error: flat.mic_error != 0,
1980 memory_error: flat.memory_error != 0,
1981 no_session: flat.no_session != 0,
1982 },
1983 (0x02, false) => FragCommand::FragSessionSetupReq {
1984 frag_index: flat.frag_index,
1985 mc_group_bit_mask: flat.mc_group_bit_mask,
1986 nb_frag: flat.nb_frag,
1987 frag_size: flat.frag_size,
1988 ack_reception: flat.ack_reception != 0,
1989 frag_algo: flat.frag_algo,
1990 block_ack_delay: flat.block_ack_delay,
1991 padding: flat.padding,
1992 descriptor: flat.descriptor,
1993 session_cnt: flat.session_cnt,
1994 mic: flat.mic,
1995 },
1996 (0x02, true) => FragCommand::FragSessionSetupAns(SetupStatus {
1997 unsupported_algorithm: flat.unsupported_algorithm != 0,
1998 not_enough_memory: flat.memory_error != 0,
1999 unsupported_index: flat.unsupported_index != 0,
2000 wrong_descriptor: flat.wrong_descriptor != 0,
2001 session_replay: flat.session_replay != 0,
2002 frag_index: flat.frag_index,
2003 }),
2004 (0x03, false) => FragCommand::FragSessionDeleteReq {
2005 frag_index: flat.frag_index,
2006 },
2007 (0x03, true) => FragCommand::FragSessionDeleteAns {
2008 frag_index: flat.frag_index,
2009 no_session: flat.no_session != 0,
2010 },
2011 (0x04, true) => FragCommand::FragDataBlockReceivedReq {
2012 frag_index: flat.frag_index,
2013 mic_error: flat.mic_error != 0,
2014 },
2015 (0x04, false) => FragCommand::FragDataBlockReceivedAns {
2016 frag_index: flat.frag_index,
2017 },
2018 (0x08, false) => FragCommand::DataFragment {
2019 frag_index: flat.frag_index,
2020 n: flat.fragment_n,
2021 data,
2022 },
2023 _ => return None,
2024 })
2025}
2026
2027fn mc_out(command: McCommand) -> PamojaLorawanPackageCommand {
2029 let mut flat = PamojaLorawanPackageCommand {
2030 port: MULTICAST_PORT,
2031 cid: command.cid(),
2032 uplink: u8::from(matches!(command.direction(), Direction::Uplink)),
2033 ..PamojaLorawanPackageCommand::default()
2034 };
2035 match command {
2036 McCommand::PackageVersionAns(version) => {
2037 flat.package = version.package;
2038 flat.version = version.version;
2039 }
2040 McCommand::McGroupStatusReq { req_group_mask } => flat.group_mask = req_group_mask,
2041 McCommand::McGroupStatusAns {
2042 ans_group_mask,
2043 nb_total_groups,
2044 } => {
2045 flat.group_mask = ans_group_mask;
2046 flat.nb_total_groups = nb_total_groups;
2047 }
2048 McCommand::McGroupStatusItem {
2049 mc_group_id,
2050 mc_addr,
2051 } => {
2052 flat.status_item = 1;
2053 flat.mc_group_id = mc_group_id;
2054 flat.mc_addr = mc_addr;
2055 }
2056 McCommand::McGroupSetupReq {
2057 mc_group_id,
2058 mc_addr,
2059 mc_key_encrypted,
2060 min_mc_fcnt,
2061 max_mc_fcnt,
2062 } => {
2063 flat.mc_group_id = mc_group_id;
2064 flat.mc_addr = mc_addr;
2065 flat.mc_key_encrypted = mc_key_encrypted;
2066 flat.min_mc_fcnt = min_mc_fcnt;
2067 flat.max_mc_fcnt = max_mc_fcnt;
2068 }
2069 McCommand::McGroupSetupAns {
2070 mc_group_id,
2071 id_error,
2072 } => {
2073 flat.mc_group_id = mc_group_id;
2074 flat.id_error = u8::from(id_error);
2075 }
2076 McCommand::McGroupDeleteReq { mc_group_id } => flat.mc_group_id = mc_group_id,
2077 McCommand::McGroupDeleteAns {
2078 mc_group_id,
2079 group_undefined,
2080 } => {
2081 flat.mc_group_id = mc_group_id;
2082 flat.no_session = u8::from(group_undefined);
2083 }
2084 McCommand::McClassCSessionReq {
2085 mc_group_id,
2086 session_time,
2087 time_out,
2088 dl_frequency_hz,
2089 data_rate,
2090 } => {
2091 flat.mc_group_id = mc_group_id;
2092 flat.session_time = session_time;
2093 flat.time_out = time_out;
2094 flat.dl_frequency_hz = dl_frequency_hz;
2095 flat.data_rate = data_rate;
2096 }
2097 McCommand::McClassBSessionReq {
2098 mc_group_id,
2099 session_time,
2100 time_out,
2101 periodicity,
2102 dl_frequency_hz,
2103 data_rate,
2104 } => {
2105 flat.mc_group_id = mc_group_id;
2106 flat.session_time = session_time;
2107 flat.time_out = time_out;
2108 flat.periodicity = periodicity;
2109 flat.dl_frequency_hz = dl_frequency_hz;
2110 flat.data_rate = data_rate;
2111 }
2112 McCommand::McClassCSessionAns {
2113 status,
2114 time_to_start,
2115 }
2116 | McCommand::McClassBSessionAns {
2117 status,
2118 time_to_start,
2119 } => {
2120 flat.mc_group_id = status.mc_group_id;
2121 flat.dr_error = u8::from(status.dr_error);
2122 flat.freq_error = u8::from(status.freq_error);
2123 flat.no_session = u8::from(status.group_undefined);
2124 flat.start_missed = u8::from(status.start_missed);
2125 flat.time_to_start = time_to_start.unwrap_or(0);
2126 flat.has_time_to_start = u8::from(time_to_start.is_some());
2127 }
2128 McCommand::PackageVersionReq => {}
2129 }
2130 flat
2131}
2132
2133fn mc_in(flat: &PamojaLorawanPackageCommand) -> Option<McCommand> {
2135 let up = flat.uplink != 0;
2136 let status = SessionStatus {
2137 mc_group_id: flat.mc_group_id,
2138 dr_error: flat.dr_error != 0,
2139 freq_error: flat.freq_error != 0,
2140 group_undefined: flat.no_session != 0,
2141 start_missed: flat.start_missed != 0,
2142 };
2143 let time_to_start = (flat.has_time_to_start != 0).then_some(flat.time_to_start);
2144 if flat.status_item != 0 {
2145 return Some(McCommand::McGroupStatusItem {
2146 mc_group_id: flat.mc_group_id,
2147 mc_addr: flat.mc_addr,
2148 });
2149 }
2150 Some(match (flat.cid, up) {
2151 (0x00, false) => McCommand::PackageVersionReq,
2152 (0x00, true) => McCommand::PackageVersionAns(PackageVersion {
2153 package: flat.package,
2154 version: flat.version,
2155 }),
2156 (0x01, false) => McCommand::McGroupStatusReq {
2157 req_group_mask: flat.group_mask,
2158 },
2159 (0x01, true) => McCommand::McGroupStatusAns {
2160 ans_group_mask: flat.group_mask,
2161 nb_total_groups: flat.nb_total_groups,
2162 },
2163 (0x02, false) => McCommand::McGroupSetupReq {
2164 mc_group_id: flat.mc_group_id,
2165 mc_addr: flat.mc_addr,
2166 mc_key_encrypted: flat.mc_key_encrypted,
2167 min_mc_fcnt: flat.min_mc_fcnt,
2168 max_mc_fcnt: flat.max_mc_fcnt,
2169 },
2170 (0x02, true) => McCommand::McGroupSetupAns {
2171 mc_group_id: flat.mc_group_id,
2172 id_error: flat.id_error != 0,
2173 },
2174 (0x03, false) => McCommand::McGroupDeleteReq {
2175 mc_group_id: flat.mc_group_id,
2176 },
2177 (0x03, true) => McCommand::McGroupDeleteAns {
2178 mc_group_id: flat.mc_group_id,
2179 group_undefined: flat.no_session != 0,
2180 },
2181 (0x04, false) => McCommand::McClassCSessionReq {
2182 mc_group_id: flat.mc_group_id,
2183 session_time: flat.session_time,
2184 time_out: flat.time_out,
2185 dl_frequency_hz: flat.dl_frequency_hz,
2186 data_rate: flat.data_rate,
2187 },
2188 (0x04, true) => McCommand::McClassCSessionAns {
2189 status,
2190 time_to_start,
2191 },
2192 (0x05, false) => McCommand::McClassBSessionReq {
2193 mc_group_id: flat.mc_group_id,
2194 session_time: flat.session_time,
2195 time_out: flat.time_out,
2196 periodicity: flat.periodicity,
2197 dl_frequency_hz: flat.dl_frequency_hz,
2198 data_rate: flat.data_rate,
2199 },
2200 (0x05, true) => McCommand::McClassBSessionAns {
2201 status,
2202 time_to_start,
2203 },
2204 _ => return None,
2205 })
2206}
2207
2208#[no_mangle]
2228pub unsafe extern "C" fn pamoja_lorawan_package_status_item(
2229 payload: *const u8,
2230 payload_len: usize,
2231 out_command: *mut PamojaLorawanPackageCommand,
2232) -> PamojaStatus {
2233 if out_command.is_null() {
2234 return missing();
2235 }
2236 let bytes = match read_bytes(payload, payload_len) {
2237 Ok(bytes) => bytes,
2238 Err(status) => return status,
2239 };
2240 match McCommand::status_item(&bytes) {
2241 Ok((command, _)) => {
2242 *out_command = mc_out(command);
2243 PamojaStatus::Ok
2244 }
2245 Err(error) => {
2246 set_last_error(error.to_string());
2247 PamojaStatus::Codec
2248 }
2249 }
2250}
2251
2252#[cfg(test)]
2253mod tests {
2254 use super::*;
2255
2256 #[test]
2258 fn a_command_of_each_package_crosses_the_boundary_both_ways() {
2259 unsafe {
2260 let cases: [(u8, u8, &[u8]); 6] = [
2262 (
2263 PAMOJA_LORAWAN_CLOCK_PORT,
2264 1,
2265 &[0x01, 0x78, 0x56, 0x34, 0x12, 0x1A],
2266 ),
2267 (PAMOJA_LORAWAN_CLOCK_PORT, 0, &[0x03, 0x02]),
2268 (PAMOJA_LORAWAN_FIRMWARE_PORT, 0, &[0x03, 0x10, 0x0E, 0x00]),
2269 (
2270 PAMOJA_LORAWAN_FRAGMENT_PORT,
2271 0,
2272 &[
2273 0x02, 0x31, 0x00, 0x04, 0x80, 0x0D, 0x40, 0x01, 0x02, 0x03, 0x04, 0x80,
2274 0x00, 0x01, 0x02, 0x03, 0x04,
2275 ],
2276 ),
2277 (
2278 PAMOJA_LORAWAN_FRAGMENT_PORT,
2279 1,
2280 &[0x01, 0x05, 0x00, 0xC4, 0x80],
2281 ),
2282 (
2283 PAMOJA_LORAWAN_MULTICAST_PORT,
2284 0,
2285 &[
2286 0x04, 0x02, 0x00, 0x04, 0x00, 0x00, 0x0F, 0x28, 0x76, 0x84, 0x05,
2287 ],
2288 ),
2289 ];
2290
2291 for (port, uplink, bytes) in cases {
2292 let mut command = PamojaLorawanPackageCommand::default();
2293 let mut taken = 0usize;
2294 assert_eq!(
2295 pamoja_lorawan_package_parse(
2296 port,
2297 uplink,
2298 bytes.as_ptr(),
2299 bytes.len(),
2300 &mut command,
2301 &mut taken,
2302 ),
2303 PamojaStatus::Ok,
2304 "reading {bytes:02x?} on port {port}"
2305 );
2306 assert_eq!(taken, bytes.len());
2307
2308 let mut out = [0u8; 64];
2309 let mut written = 0usize;
2310 assert_eq!(
2311 pamoja_lorawan_package_encode(
2312 &command,
2313 ptr::null(),
2314 0,
2315 out.as_mut_ptr(),
2316 out.len(),
2317 &mut written,
2318 ),
2319 PamojaStatus::Ok,
2320 "writing it back"
2321 );
2322 assert_eq!(&out[..written], bytes, "on port {port}");
2323 }
2324 }
2325 }
2326
2327 #[test]
2329 fn a_data_fragment_carries_its_bytes_beside_the_command() {
2330 unsafe {
2331 let message = [0x08u8, 0x00, 0x84, 0x01, 0x02, 0x03, 0x04];
2332 let mut command = PamojaLorawanPackageCommand::default();
2333 let mut taken = 0usize;
2334 assert_eq!(
2335 pamoja_lorawan_package_parse(
2336 PAMOJA_LORAWAN_FRAGMENT_PORT,
2337 0,
2338 message.as_ptr(),
2339 message.len(),
2340 &mut command,
2341 &mut taken,
2342 ),
2343 PamojaStatus::Ok
2344 );
2345 assert_eq!((command.frag_index, command.fragment_n), (2, 1024));
2346 assert_eq!(taken, message.len());
2347
2348 let data = [0x01u8, 0x02, 0x03, 0x04];
2349 let mut out = [0u8; 16];
2350 let mut written = 0usize;
2351 assert_eq!(
2352 pamoja_lorawan_package_encode(
2353 &command,
2354 data.as_ptr(),
2355 data.len(),
2356 out.as_mut_ptr(),
2357 out.len(),
2358 &mut written,
2359 ),
2360 PamojaStatus::Ok
2361 );
2362 assert_eq!(&out[..written], &message);
2363 }
2364 }
2365
2366 #[test]
2368 fn a_port_that_names_no_package_is_refused() {
2369 unsafe {
2370 let bytes = [0x00u8];
2371 let mut command = PamojaLorawanPackageCommand::default();
2372 let mut taken = 0usize;
2373 assert_eq!(
2374 pamoja_lorawan_package_parse(
2375 1,
2376 0,
2377 bytes.as_ptr(),
2378 bytes.len(),
2379 &mut command,
2380 &mut taken,
2381 ),
2382 PamojaStatus::InvalidArgument
2383 );
2384
2385 let unknown = PamojaLorawanPackageCommand {
2386 port: PAMOJA_LORAWAN_CLOCK_PORT,
2387 cid: 0x7F,
2388 ..PamojaLorawanPackageCommand::default()
2389 };
2390 let mut out = [0u8; 16];
2391 let mut written = 0usize;
2392 assert_eq!(
2393 pamoja_lorawan_package_encode(
2394 &unknown,
2395 ptr::null(),
2396 0,
2397 out.as_mut_ptr(),
2398 out.len(),
2399 &mut written,
2400 ),
2401 PamojaStatus::Codec
2402 );
2403 }
2404 }
2405
2406 #[test]
2408 fn a_block_crosses_the_boundary_in_fragments_and_comes_back_whole() {
2409 unsafe {
2410 let block: [u8; 384] = core::array::from_fn(|i| (i * 13 + 5) as u8);
2413 let (mut nb_frag, mut padding) = (0u16, 0u8);
2414 assert_eq!(
2415 pamoja_lorawan_frag_session(block.len(), 16, &mut nb_frag, &mut padding),
2416 PamojaStatus::Ok
2417 );
2418 assert_eq!((nb_frag, padding), (24, 0));
2419
2420 let mut session: *mut PamojaLorawanDefrag = ptr::null_mut();
2421 assert_eq!(
2422 pamoja_lorawan_defrag_new(nb_frag, 16, 12, &mut session),
2423 PamojaStatus::Ok
2424 );
2425
2426 let mut fragment = [0u8; 16];
2427 let mut done = 0u8;
2428 for n in 1..=nb_frag * 3 {
2429 if n % 3 == 0 {
2430 continue;
2431 }
2432 assert_eq!(
2433 pamoja_lorawan_frag_fragment(
2434 block.as_ptr(),
2435 block.len(),
2436 16,
2437 n,
2438 fragment.as_mut_ptr(),
2439 fragment.len(),
2440 ),
2441 PamojaStatus::Ok
2442 );
2443 assert_eq!(
2444 pamoja_lorawan_defrag_fragment(
2445 session,
2446 n,
2447 fragment.as_ptr(),
2448 fragment.len(),
2449 &mut done,
2450 ),
2451 PamojaStatus::Ok
2452 );
2453 if done == 1 {
2454 break;
2455 }
2456 }
2457 assert_eq!(done, 1, "the coded fragments made up for the losses");
2458
2459 let (mut received, mut missing, mut finished) = (0u16, 0u16, 0u8);
2460 assert_eq!(
2461 pamoja_lorawan_defrag_status(session, &mut received, &mut missing, &mut finished),
2462 PamojaStatus::Ok
2463 );
2464 assert_eq!((missing, finished), (0, 1));
2465 assert!(received >= nb_frag);
2466
2467 let mut out = [0u8; 384];
2468 let mut len = 0usize;
2469 assert_eq!(
2470 pamoja_lorawan_defrag_block(session, out.as_mut_ptr(), out.len(), &mut len),
2471 PamojaStatus::Ok
2472 );
2473 assert_eq!(len, block.len());
2474 assert_eq!(out, block);
2475
2476 pamoja_lorawan_defrag_free(session);
2477 }
2478 }
2479
2480 #[test]
2482 fn a_group_key_and_a_block_code_cross_the_boundary() {
2483 unsafe {
2484 let app_key = [0x2Bu8; 16];
2485 let group_key = [0x77u8; 16];
2486 let mut root = [0u8; 16];
2487 let mut ke = [0u8; 16];
2488 assert_eq!(
2489 pamoja_lorawan_mc_root_key(app_key.as_ptr(), 0, root.as_mut_ptr()),
2490 PamojaStatus::Ok
2491 );
2492 assert_eq!(
2493 pamoja_lorawan_mc_ke_key(root.as_ptr(), ke.as_mut_ptr()),
2494 PamojaStatus::Ok
2495 );
2496
2497 let mut wrapped = [0u8; 16];
2498 let mut unwrapped = [0u8; 16];
2499 assert_eq!(
2500 pamoja_lorawan_wrap_mc_key(ke.as_ptr(), group_key.as_ptr(), wrapped.as_mut_ptr()),
2501 PamojaStatus::Ok
2502 );
2503 assert_eq!(
2504 pamoja_lorawan_mc_key(ke.as_ptr(), wrapped.as_ptr(), unwrapped.as_mut_ptr()),
2505 PamojaStatus::Ok
2506 );
2507 assert_eq!(unwrapped, group_key, "the device gets the group key back");
2508
2509 let mut app = [0u8; 16];
2510 let mut nwk = [0u8; 16];
2511 assert_eq!(
2512 pamoja_lorawan_mc_app_s_key(group_key.as_ptr(), 0x2601_1BDA, app.as_mut_ptr()),
2513 PamojaStatus::Ok
2514 );
2515 assert_eq!(
2516 pamoja_lorawan_mc_nwk_s_key(group_key.as_ptr(), 0x2601_1BDA, nwk.as_mut_ptr()),
2517 PamojaStatus::Ok
2518 );
2519 assert_ne!(app, nwk);
2520
2521 let mut block_key = [0u8; 16];
2523 assert_eq!(
2524 pamoja_lorawan_data_block_int_key(app_key.as_ptr(), block_key.as_mut_ptr()),
2525 PamojaStatus::Ok
2526 );
2527 let block = [0xA5u8; 40];
2528 let descriptor = *b"PJU1";
2529 let mut whole: *mut PamojaLorawanBlockMic = ptr::null_mut();
2530 assert_eq!(
2531 pamoja_lorawan_block_mic_start(
2532 block_key.as_ptr(),
2533 7,
2534 2,
2535 descriptor.as_ptr(),
2536 block.len() as u32,
2537 &mut whole,
2538 ),
2539 PamojaStatus::Ok
2540 );
2541 assert_eq!(
2542 pamoja_lorawan_block_mic_update(whole, block.as_ptr(), block.len()),
2543 PamojaStatus::Ok
2544 );
2545 let mut one = [0u8; 4];
2546 assert_eq!(
2547 pamoja_lorawan_block_mic_finish(whole, one.as_mut_ptr()),
2548 PamojaStatus::Ok
2549 );
2550
2551 let mut pieces: *mut PamojaLorawanBlockMic = ptr::null_mut();
2552 assert_eq!(
2553 pamoja_lorawan_block_mic_start(
2554 block_key.as_ptr(),
2555 7,
2556 2,
2557 descriptor.as_ptr(),
2558 block.len() as u32,
2559 &mut pieces,
2560 ),
2561 PamojaStatus::Ok
2562 );
2563 for chunk in block.chunks(7) {
2564 assert_eq!(
2565 pamoja_lorawan_block_mic_update(pieces, chunk.as_ptr(), chunk.len()),
2566 PamojaStatus::Ok
2567 );
2568 }
2569 let mut two = [0u8; 4];
2570 assert_eq!(
2571 pamoja_lorawan_block_mic_finish(pieces, two.as_mut_ptr()),
2572 PamojaStatus::Ok
2573 );
2574 assert_eq!(one, two);
2575 }
2576 }
2577
2578 #[test]
2580 fn a_clock_correction_and_a_reboot_cross_the_boundary() {
2581 unsafe {
2582 let mut sync: *mut PamojaLorawanClockSync = ptr::null_mut();
2583 assert_eq!(
2584 pamoja_lorawan_clock_sync_new(0, &mut sync),
2585 PamojaStatus::Ok
2586 );
2587
2588 let mut request = [0u8; 8];
2589 let mut len = 0usize;
2590 assert_eq!(
2591 pamoja_lorawan_clock_sync_request(
2592 sync,
2593 1_000,
2594 1,
2595 request.as_mut_ptr(),
2596 request.len(),
2597 &mut len,
2598 ),
2599 PamojaStatus::Ok
2600 );
2601 assert_eq!(len, 6);
2602
2603 let answer = [0x01u8, 12, 0, 0, 0, 0];
2605 let (mut correction, mut has, mut more, mut resync, mut due) =
2606 (0i32, 0u8, 0u8, 0u8, 0u8);
2607 assert_eq!(
2608 pamoja_lorawan_clock_sync_heard(
2609 sync,
2610 answer.as_ptr(),
2611 answer.len(),
2612 &mut correction,
2613 &mut has,
2614 &mut more,
2615 &mut resync,
2616 &mut due,
2617 ),
2618 PamojaStatus::Ok
2619 );
2620 assert_eq!((correction, has, more), (12, 1, 0));
2621 let mut token = 0u8;
2622 assert_eq!(
2623 pamoja_lorawan_clock_sync_status(
2624 sync,
2625 &mut token,
2626 ptr::null_mut(),
2627 ptr::null_mut()
2628 ),
2629 PamojaStatus::Ok
2630 );
2631 assert_eq!(token, 1, "the token moved on with the correction");
2632 pamoja_lorawan_clock_sync_free(sync);
2633
2634 let mut manager: *mut PamojaLorawanFirmware = ptr::null_mut();
2635 assert_eq!(
2636 pamoja_lorawan_firmware_new(0x0001_0203, 0xAABB_CCDD, &mut manager),
2637 PamojaStatus::Ok
2638 );
2639 assert_eq!(
2640 pamoja_lorawan_firmware_set_image(manager, PAMOJA_LORAWAN_IMAGE_VALID, 0x0001_0204),
2641 PamojaStatus::Ok
2642 );
2643
2644 let asked = [0x04u8, 0x03, 0x10, 0x0E, 0x00];
2646 let mut answers = [0u8; 32];
2647 let mut written = 0usize;
2648 assert_eq!(
2649 pamoja_lorawan_firmware_heard(
2650 manager,
2651 asked.as_ptr(),
2652 asked.len(),
2653 0,
2654 0,
2655 answers.as_mut_ptr(),
2656 answers.len(),
2657 &mut written,
2658 ),
2659 PamojaStatus::Ok
2660 );
2661 assert_eq!(&answers[..2], &[0x04, PAMOJA_LORAWAN_IMAGE_VALID]);
2662
2663 let (mut left, mut has_in) = (0u32, 0u8);
2664 assert_eq!(
2665 pamoja_lorawan_firmware_reboot(
2666 manager,
2667 ptr::null_mut(),
2668 ptr::null_mut(),
2669 &mut left,
2670 &mut has_in,
2671 ptr::null_mut(),
2672 ),
2673 PamojaStatus::Ok
2674 );
2675 assert_eq!((left, has_in), (3_600, 1));
2676 pamoja_lorawan_firmware_free(manager);
2677 }
2678 }
2679}