1use std::ptr;
11
12use pamoja_lorawan::mac::relay_second_channel;
13use pamoja_lorawan::relay::{
14 self, open_wor_ack, t_offset_ms, unsynchronized_preamble_symbols, wor_ack, wor_join_request,
15 wor_uplink, CadPeriodicity, CadToRx, Carrier, Forward, ForwardedUplink, StateSync,
16 Synchronization, UplinkMetadata, Wor, WorChannel, WorKeys, XtalAccuracy,
17};
18
19use crate::lora_region::PamojaLoraRelayChannel;
20use crate::lorawan::{failed, PamojaLorawanSession, PAMOJA_LORAWAN_KEY_LEN};
21use crate::{read_bytes, set_last_error, PamojaBuffer, PamojaStatus};
22
23pub const PAMOJA_LORAWAN_LA_FPORT_RELAY: u8 = 226;
25pub const PAMOJA_LORAWAN_TRUSTED_ED_NUMBER: usize = 16;
27pub const PAMOJA_LORAWAN_WOR_ATTEMPTS_WO_ACK: u8 = 8;
29pub const PAMOJA_LORAWAN_WOR_DATA_DELAY_US: u32 = 50_000;
31pub const PAMOJA_LORAWAN_WOR_ACK_DELAY_US: u32 = 50_000;
33pub const PAMOJA_LORAWAN_RELAY_FWD_DELAY_US: u32 = 50_000;
35pub const PAMOJA_LORAWAN_RXR_DELAY_US: u32 = 18_000_000;
37pub const PAMOJA_LORAWAN_WOR_JOIN_REQUEST_LEN: usize = 5;
39pub const PAMOJA_LORAWAN_WOR_UPLINK_LEN: usize = 15;
41pub const PAMOJA_LORAWAN_WOR_ACK_LEN: usize = 7;
43pub const PAMOJA_LORAWAN_FORWARD_OVERHEAD: usize = 6;
45pub const PAMOJA_LORAWAN_MIN_WOR_PREAMBLE_SYMBOLS: u16 = 8;
47
48const _: () = assert!(PAMOJA_LORAWAN_LA_FPORT_RELAY == relay::LA_FPORT_RELAY);
51const _: () = assert!(PAMOJA_LORAWAN_TRUSTED_ED_NUMBER == relay::TRUSTED_ED_NUMBER);
52const _: () = assert!(PAMOJA_LORAWAN_WOR_ATTEMPTS_WO_ACK == relay::WOR_ATTEMPTS_WO_ACK);
53const _: () = assert!(PAMOJA_LORAWAN_WOR_DATA_DELAY_US == relay::WOR_DATA_DELAY_US);
54const _: () = assert!(PAMOJA_LORAWAN_WOR_ACK_DELAY_US == relay::WOR_ACK_DELAY_US);
55const _: () = assert!(PAMOJA_LORAWAN_RELAY_FWD_DELAY_US == relay::RELAY_FWD_DELAY_US);
56const _: () = assert!(PAMOJA_LORAWAN_RXR_DELAY_US == relay::RXR_DELAY_US);
57const _: () = assert!(PAMOJA_LORAWAN_WOR_JOIN_REQUEST_LEN == relay::WOR_JOIN_REQUEST_LEN);
58const _: () = assert!(PAMOJA_LORAWAN_WOR_UPLINK_LEN == relay::WOR_UPLINK_LEN);
59const _: () = assert!(PAMOJA_LORAWAN_WOR_ACK_LEN == relay::WOR_ACK_LEN);
60const _: () = assert!(PAMOJA_LORAWAN_FORWARD_OVERHEAD == relay::FORWARD_OVERHEAD);
61const _: () = assert!(PAMOJA_LORAWAN_MIN_WOR_PREAMBLE_SYMBOLS == relay::MIN_WOR_PREAMBLE_SYMBOLS);
62
63pub const PAMOJA_LORAWAN_WOR_JOIN_REQUEST: u8 = 0;
65pub const PAMOJA_LORAWAN_WOR_UPLINK: u8 = 1;
67
68#[repr(C)]
70#[derive(Clone, Copy, PartialEq, Eq)]
71pub struct PamojaLorawanWorKeys {
72 pub integrity: [u8; 16],
74 pub encryption: [u8; 16],
76}
77
78#[repr(C)]
80#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81pub struct PamojaLorawanCarrier {
82 pub frequency_hz: u32,
84 pub data_rate: u8,
86}
87
88#[repr(C)]
90#[derive(Clone, Copy, Debug, PartialEq, Eq)]
91pub struct PamojaLorawanWor {
92 pub kind: u8,
94 pub uplink: PamojaLorawanCarrier,
97 pub dev_addr: u32,
99 pub wfcnt: u16,
101}
102
103#[repr(C)]
106#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107pub struct PamojaLorawanStateSync {
108 pub cad_to_rx: u8,
110 pub forward: u8,
112 pub relay_data_rate: u8,
114 pub xtal_accuracy: u8,
116 pub cad_periodicity: u8,
118 pub t_offset_ms: u16,
120}
121
122#[repr(C)]
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125pub struct PamojaLorawanUplinkMetadata {
126 pub wor_channel: u8,
128 pub rssi_dbm: i16,
130 pub snr_db: i8,
132 pub data_rate: u8,
134}
135
136#[repr(C)]
138#[derive(Clone, Copy, Debug, PartialEq, Eq)]
139pub struct PamojaLorawanSynchronization {
140 pub reference_us: u64,
142 pub cad_periodicity: u8,
144 pub relay_xtal: u8,
146 pub cad_to_rx: u8,
148}
149
150#[repr(C)]
152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
153pub struct PamojaLorawanWorSlot {
154 pub start_us: u64,
156 pub preamble_symbols: u16,
158}
159
160fn keys_in(keys: &PamojaLorawanWorKeys) -> WorKeys {
161 WorKeys::new(keys.integrity, keys.encryption)
162}
163
164fn keys_out(keys: &WorKeys) -> PamojaLorawanWorKeys {
165 PamojaLorawanWorKeys {
166 integrity: *keys.integrity(),
167 encryption: *keys.encryption(),
168 }
169}
170
171fn carrier_in(carrier: &PamojaLorawanCarrier) -> Carrier {
172 Carrier::new(carrier.frequency_hz, carrier.data_rate)
173}
174
175fn carrier_out(carrier: Carrier) -> PamojaLorawanCarrier {
176 PamojaLorawanCarrier {
177 frequency_hz: carrier.frequency_hz,
178 data_rate: carrier.data_rate,
179 }
180}
181
182fn two_bits(value: u8, what: &str) -> Result<u8, PamojaStatus> {
183 if value > 3 {
184 set_last_error(format!("{what} is coded in two bits, and {value} is not"));
185 return Err(PamojaStatus::InvalidArgument);
186 }
187 Ok(value)
188}
189
190fn periodicity(code: u8) -> Result<CadPeriodicity, PamojaStatus> {
191 CadPeriodicity::from_code(code).ok_or_else(|| {
192 set_last_error(format!(
193 "{code} is not a scan periodicity, which runs 0 to 5"
194 ));
195 PamojaStatus::InvalidArgument
196 })
197}
198
199fn state_in(state: &PamojaLorawanStateSync) -> Result<StateSync, PamojaStatus> {
200 Ok(StateSync {
201 cad_to_rx: CadToRx::from_code(two_bits(state.cad_to_rx, "CadToRx")?),
202 forward: Forward::from_code(two_bits(state.forward, "Forward")?),
203 relay_data_rate: state.relay_data_rate,
204 xtal_accuracy: XtalAccuracy::from_code(two_bits(state.xtal_accuracy, "XTALAccuracy")?),
205 cad_periodicity: periodicity(state.cad_periodicity)?,
206 t_offset_ms: state.t_offset_ms,
207 })
208}
209
210fn state_out(state: StateSync) -> PamojaLorawanStateSync {
211 PamojaLorawanStateSync {
212 cad_to_rx: state.cad_to_rx.code(),
213 forward: state.forward.code(),
214 relay_data_rate: state.relay_data_rate,
215 xtal_accuracy: state.xtal_accuracy.code(),
216 cad_periodicity: state.cad_periodicity.code(),
217 t_offset_ms: state.t_offset_ms,
218 }
219}
220
221fn null(what: &str) -> PamojaStatus {
222 set_last_error(format!("{what} must not be null"));
223 PamojaStatus::InvalidArgument
224}
225
226#[no_mangle]
248pub unsafe extern "C" fn pamoja_lorawan_relay_root_wor_s_key(
249 network_key: *const u8,
250 network_key_len: usize,
251 out_key: *mut u8,
252) -> PamojaStatus {
253 if out_key.is_null() {
254 return null("out_key");
255 }
256 let key = match sixteen(network_key, network_key_len, "the network key") {
257 Ok(key) => key,
258 Err(status) => return status,
259 };
260 let root = relay::root_wor_s_key(&key);
261 ptr::copy_nonoverlapping(root.as_ptr(), out_key, root.len());
262 PamojaStatus::Ok
263}
264
265#[no_mangle]
287pub unsafe extern "C" fn pamoja_lorawan_relay_wor_keys(
288 root_key: *const u8,
289 root_key_len: usize,
290 dev_addr: u32,
291 out_keys: *mut PamojaLorawanWorKeys,
292) -> PamojaStatus {
293 if out_keys.is_null() {
294 return null("out_keys");
295 }
296 let root = match sixteen(root_key, root_key_len, "the root key") {
297 Ok(root) => root,
298 Err(status) => return status,
299 };
300 *out_keys = keys_out(&WorKeys::derive(&root, dev_addr));
301 PamojaStatus::Ok
302}
303
304#[no_mangle]
323pub unsafe extern "C" fn pamoja_lorawan_session_root_wor_s_key(
324 session: *const PamojaLorawanSession,
325 out_key: *mut u8,
326) -> PamojaStatus {
327 let (Some(session), false) = (session.as_ref(), out_key.is_null()) else {
328 return null("session and out_key");
329 };
330 let root = session.session.root_wor_s_key();
331 ptr::copy_nonoverlapping(root.as_ptr(), out_key, root.len());
332 PamojaStatus::Ok
333}
334
335#[no_mangle]
354pub unsafe extern "C" fn pamoja_lorawan_session_wor_keys(
355 session: *const PamojaLorawanSession,
356 out_keys: *mut PamojaLorawanWorKeys,
357) -> PamojaStatus {
358 let (Some(session), false) = (session.as_ref(), out_keys.is_null()) else {
359 return null("session and out_keys");
360 };
361 *out_keys = keys_out(&session.session.wor_keys());
362 PamojaStatus::Ok
363}
364
365#[no_mangle]
386pub unsafe extern "C" fn pamoja_lorawan_relay_wor_join_request(
387 uplink: *const PamojaLorawanCarrier,
388 out_frame: *mut u8,
389) -> PamojaStatus {
390 let (Some(uplink), false) = (uplink.as_ref(), out_frame.is_null()) else {
391 return null("uplink and out_frame");
392 };
393 match wor_join_request(carrier_in(uplink)) {
394 Ok(frame) => {
395 ptr::copy_nonoverlapping(frame.as_ptr(), out_frame, frame.len());
396 PamojaStatus::Ok
397 }
398 Err(error) => failed(error),
399 }
400}
401
402#[no_mangle]
427pub unsafe extern "C" fn pamoja_lorawan_relay_wor_uplink(
428 keys: *const PamojaLorawanWorKeys,
429 dev_addr: u32,
430 wfcnt: u32,
431 uplink: *const PamojaLorawanCarrier,
432 wor: *const PamojaLorawanCarrier,
433 out_frame: *mut u8,
434) -> PamojaStatus {
435 let (Some(keys), Some(uplink), Some(wor), false) = (
436 keys.as_ref(),
437 uplink.as_ref(),
438 wor.as_ref(),
439 out_frame.is_null(),
440 ) else {
441 return null("keys, uplink, wor and out_frame");
442 };
443 match wor_uplink(
444 &keys_in(keys),
445 dev_addr,
446 wfcnt,
447 carrier_in(uplink),
448 carrier_in(wor),
449 ) {
450 Ok(frame) => {
451 ptr::copy_nonoverlapping(frame.as_ptr(), out_frame, frame.len());
452 PamojaStatus::Ok
453 }
454 Err(error) => failed(error),
455 }
456}
457
458#[no_mangle]
479pub unsafe extern "C" fn pamoja_lorawan_relay_wor_parse(
480 frame: *const u8,
481 frame_len: usize,
482 out_wor: *mut PamojaLorawanWor,
483) -> PamojaStatus {
484 if out_wor.is_null() {
485 return null("out_wor");
486 }
487 let bytes = match read_bytes(frame, frame_len) {
488 Ok(bytes) => bytes,
489 Err(status) => return status,
490 };
491 match Wor::parse(&bytes) {
492 Ok(Wor::JoinRequest { uplink }) => {
493 *out_wor = PamojaLorawanWor {
494 kind: PAMOJA_LORAWAN_WOR_JOIN_REQUEST,
495 uplink: carrier_out(uplink),
496 dev_addr: 0,
497 wfcnt: 0,
498 };
499 PamojaStatus::Ok
500 }
501 Ok(Wor::Uplink(sealed)) => {
502 *out_wor = PamojaLorawanWor {
503 kind: PAMOJA_LORAWAN_WOR_UPLINK,
504 uplink: PamojaLorawanCarrier {
505 frequency_hz: 0,
506 data_rate: 0,
507 },
508 dev_addr: sealed.dev_addr(),
509 wfcnt: sealed.wfcnt(),
510 };
511 PamojaStatus::Ok
512 }
513 Err(error) => failed(error),
514 }
515}
516
517#[no_mangle]
543pub unsafe extern "C" fn pamoja_lorawan_relay_wor_open(
544 frame: *const u8,
545 frame_len: usize,
546 keys: *const PamojaLorawanWorKeys,
547 wfcnt: u32,
548 wor: *const PamojaLorawanCarrier,
549 out_uplink: *mut PamojaLorawanCarrier,
550) -> PamojaStatus {
551 let (Some(keys), Some(wor), false) = (keys.as_ref(), wor.as_ref(), out_uplink.is_null()) else {
552 return null("keys, wor and out_uplink");
553 };
554 let bytes = match read_bytes(frame, frame_len) {
555 Ok(bytes) => bytes,
556 Err(status) => return status,
557 };
558 let sealed = match Wor::parse(&bytes) {
559 Ok(Wor::Uplink(sealed)) => sealed,
560 Ok(Wor::JoinRequest { .. }) => {
561 set_last_error("a join request WOR carries nothing sealed".to_owned());
562 return PamojaStatus::Codec;
563 }
564 Err(error) => return failed(error),
565 };
566 match sealed.open(&keys_in(keys), wfcnt, carrier_in(wor)) {
567 Ok(uplink) => {
568 *out_uplink = carrier_out(uplink);
569 PamojaStatus::Ok
570 }
571 Err(error) => failed(error),
572 }
573}
574
575#[no_mangle]
601pub unsafe extern "C" fn pamoja_lorawan_relay_wor_ack(
602 keys: *const PamojaLorawanWorKeys,
603 dev_addr: u32,
604 wfcnt: u32,
605 ack: *const PamojaLorawanCarrier,
606 uplink: *const PamojaLorawanCarrier,
607 state: *const PamojaLorawanStateSync,
608 out_frame: *mut u8,
609) -> PamojaStatus {
610 let (Some(keys), Some(ack), Some(uplink), Some(state), false) = (
611 keys.as_ref(),
612 ack.as_ref(),
613 uplink.as_ref(),
614 state.as_ref(),
615 out_frame.is_null(),
616 ) else {
617 return null("keys, ack, uplink, state and out_frame");
618 };
619 let state = match state_in(state) {
620 Ok(state) => state,
621 Err(status) => return status,
622 };
623 match wor_ack(
624 &keys_in(keys),
625 dev_addr,
626 wfcnt,
627 carrier_in(ack),
628 carrier_in(uplink),
629 state,
630 ) {
631 Ok(frame) => {
632 ptr::copy_nonoverlapping(frame.as_ptr(), out_frame, frame.len());
633 PamojaStatus::Ok
634 }
635 Err(error) => failed(error),
636 }
637}
638
639#[no_mangle]
667pub unsafe extern "C" fn pamoja_lorawan_relay_wor_ack_open(
668 frame: *const u8,
669 frame_len: usize,
670 keys: *const PamojaLorawanWorKeys,
671 dev_addr: u32,
672 wfcnt: u32,
673 ack: *const PamojaLorawanCarrier,
674 uplink: *const PamojaLorawanCarrier,
675 out_state: *mut PamojaLorawanStateSync,
676) -> PamojaStatus {
677 let (Some(keys), Some(ack), Some(uplink), false) = (
678 keys.as_ref(),
679 ack.as_ref(),
680 uplink.as_ref(),
681 out_state.is_null(),
682 ) else {
683 return null("keys, ack, uplink and out_state");
684 };
685 let bytes = match read_bytes(frame, frame_len) {
686 Ok(bytes) => bytes,
687 Err(status) => return status,
688 };
689 match open_wor_ack(
690 &bytes,
691 &keys_in(keys),
692 dev_addr,
693 wfcnt,
694 carrier_in(ack),
695 carrier_in(uplink),
696 ) {
697 Ok(state) => {
698 *out_state = state_out(state);
699 PamojaStatus::Ok
700 }
701 Err(error) => failed(error),
702 }
703}
704
705#[no_mangle]
730pub unsafe extern "C" fn pamoja_lorawan_relay_forward_encode(
731 metadata: *const PamojaLorawanUplinkMetadata,
732 frequency_hz: u32,
733 phy_payload: *const u8,
734 phy_payload_len: usize,
735 out_payload: *mut *mut PamojaBuffer,
736) -> PamojaStatus {
737 let (Some(metadata), false) = (metadata.as_ref(), out_payload.is_null()) else {
738 return null("metadata and out_payload");
739 };
740 let wor_channel = match metadata.wor_channel {
741 0 => WorChannel::Default,
742 1 => WorChannel::Second,
743 other => {
744 set_last_error(format!("{other} is not a WOR channel, which is 0 or 1"));
745 return PamojaStatus::InvalidArgument;
746 }
747 };
748 let frame = match read_bytes(phy_payload, phy_payload_len) {
749 Ok(frame) => frame,
750 Err(status) => return status,
751 };
752 let forwarded = ForwardedUplink {
753 metadata: UplinkMetadata {
754 wor_channel,
755 rssi_dbm: metadata.rssi_dbm,
756 snr_db: metadata.snr_db,
757 data_rate: metadata.data_rate,
758 },
759 frequency_hz,
760 phy_payload: &frame,
761 };
762 let mut out = vec![0u8; relay::FORWARD_OVERHEAD + frame.len()];
763 match forwarded.encode(&mut out) {
764 Ok(_) => {
765 *out_payload = PamojaBuffer::into_raw(out);
766 PamojaStatus::Ok
767 }
768 Err(error) => failed(error),
769 }
770}
771
772#[no_mangle]
797pub unsafe extern "C" fn pamoja_lorawan_relay_forward_parse(
798 payload: *const u8,
799 payload_len: usize,
800 out_metadata: *mut PamojaLorawanUplinkMetadata,
801 out_frequency_hz: *mut u32,
802 out_phy_payload: *mut *mut PamojaBuffer,
803) -> PamojaStatus {
804 if out_metadata.is_null() || out_frequency_hz.is_null() || out_phy_payload.is_null() {
805 return null("out_metadata, out_frequency_hz and out_phy_payload");
806 }
807 let bytes = match read_bytes(payload, payload_len) {
808 Ok(bytes) => bytes,
809 Err(status) => return status,
810 };
811 match ForwardedUplink::parse(&bytes) {
812 Ok(forwarded) => {
813 *out_metadata = PamojaLorawanUplinkMetadata {
814 wor_channel: match forwarded.metadata.wor_channel {
815 WorChannel::Default => 0,
816 WorChannel::Second => 1,
817 },
818 rssi_dbm: forwarded.metadata.rssi_dbm,
819 snr_db: forwarded.metadata.snr_db,
820 data_rate: forwarded.metadata.data_rate,
821 };
822 *out_frequency_hz = forwarded.frequency_hz;
823 *out_phy_payload = PamojaBuffer::into_raw(forwarded.phy_payload.to_vec());
824 PamojaStatus::Ok
825 }
826 Err(error) => failed(error),
827 }
828}
829
830#[no_mangle]
852pub unsafe extern "C" fn pamoja_lorawan_relay_unsynchronized_preamble(
853 cad_periodicity: u8,
854 symbol_us: u64,
855 cad_to_rx: u8,
856 out_symbols: *mut u16,
857) -> PamojaStatus {
858 if out_symbols.is_null() {
859 return null("out_symbols");
860 }
861 let (period, receive) = match (periodicity(cad_periodicity), two_bits(cad_to_rx, "CadToRx")) {
862 (Ok(period), Ok(receive)) => (period, CadToRx::from_code(receive)),
863 (Err(status), _) | (_, Err(status)) => return status,
864 };
865 *out_symbols = unsynchronized_preamble_symbols(period, symbol_us, receive);
866 PamojaStatus::Ok
867}
868
869#[no_mangle]
891pub unsafe extern "C" fn pamoja_lorawan_relay_t_offset_ms(
892 scan_start_us: u64,
893 preamble_end_us: u64,
894 out_offset_ms: *mut u16,
895) -> PamojaStatus {
896 if out_offset_ms.is_null() {
897 return null("out_offset_ms");
898 }
899 match t_offset_ms(scan_start_us, preamble_end_us) {
900 Some(offset) => {
901 *out_offset_ms = offset;
902 PamojaStatus::Ok
903 }
904 None => {
905 set_last_error(
906 "the preamble ended before the scan, or past the eleven bits a WOR ACK carries"
907 .to_owned(),
908 );
909 PamojaStatus::InvalidArgument
910 }
911 }
912}
913
914#[no_mangle]
937pub unsafe extern "C" fn pamoja_lorawan_relay_synchronization(
938 wor_start_us: u64,
939 preamble_symbols: u16,
940 symbol_us: u64,
941 state: *const PamojaLorawanStateSync,
942 out_synchronization: *mut PamojaLorawanSynchronization,
943) -> PamojaStatus {
944 let (Some(state), false) = (state.as_ref(), out_synchronization.is_null()) else {
945 return null("state and out_synchronization");
946 };
947 let state = match state_in(state) {
948 Ok(state) => state,
949 Err(status) => return status,
950 };
951 let sync = Synchronization::from_ack(wor_start_us, preamble_symbols, symbol_us, &state);
952 *out_synchronization = PamojaLorawanSynchronization {
953 reference_us: sync.reference_us,
954 cad_periodicity: sync.cad_periodicity.code(),
955 relay_xtal: sync.relay_xtal.code(),
956 cad_to_rx: sync.cad_to_rx.code(),
957 };
958 PamojaStatus::Ok
959}
960
961#[no_mangle]
988pub unsafe extern "C" fn pamoja_lorawan_relay_next_wor(
989 synchronization: *const PamojaLorawanSynchronization,
990 now_us: u64,
991 device_xtal_ppm: u32,
992 symbol_us: u64,
993 other_channel: u8,
994 out_slot: *mut PamojaLorawanWorSlot,
995 out_synchronized: *mut u8,
996) -> PamojaStatus {
997 let (Some(sync), false, false) = (
998 synchronization.as_ref(),
999 out_slot.is_null(),
1000 out_synchronized.is_null(),
1001 ) else {
1002 return null("synchronization, out_slot and out_synchronized");
1003 };
1004 let sync = match (
1005 periodicity(sync.cad_periodicity),
1006 two_bits(sync.relay_xtal, "the relay crystal accuracy"),
1007 two_bits(sync.cad_to_rx, "CadToRx"),
1008 ) {
1009 (Ok(period), Ok(xtal), Ok(receive)) => Synchronization {
1010 reference_us: sync.reference_us,
1011 cad_periodicity: period,
1012 relay_xtal: XtalAccuracy::from_code(xtal),
1013 cad_to_rx: CadToRx::from_code(receive),
1014 },
1015 (Err(status), _, _) | (_, Err(status), _) | (_, _, Err(status)) => return status,
1016 };
1017 match sync.next_wor(now_us, device_xtal_ppm, symbol_us, other_channel != 0) {
1018 Some(slot) => {
1019 *out_slot = PamojaLorawanWorSlot {
1020 start_us: slot.start_us,
1021 preamble_symbols: slot.preamble_symbols,
1022 };
1023 *out_synchronized = 1;
1024 }
1025 None => {
1026 *out_slot = PamojaLorawanWorSlot {
1027 start_us: 0,
1028 preamble_symbols: 0,
1029 };
1030 *out_synchronized = 0;
1031 }
1032 }
1033 PamojaStatus::Ok
1034}
1035
1036#[no_mangle]
1059pub unsafe extern "C" fn pamoja_lorawan_relay_second_channel(
1060 second_channel_index: u8,
1061 data_rate: u8,
1062 ack_offset: u8,
1063 frequency_hz: u32,
1064 out_channel: *mut PamojaLoraRelayChannel,
1065) -> PamojaStatus {
1066 if out_channel.is_null() {
1067 return null("out_channel");
1068 }
1069 match relay_second_channel(second_channel_index, data_rate, ack_offset, frequency_hz) {
1070 Some(channel) => {
1071 *out_channel = PamojaLoraRelayChannel {
1072 wor_frequency_hz: channel.wor_frequency_hz,
1073 ack_frequency_hz: channel.ack_frequency_hz,
1074 data_rate: channel.data_rate,
1075 };
1076 PamojaStatus::Ok
1077 }
1078 None => {
1079 set_last_error(format!(
1080 "index {second_channel_index} with offset {ack_offset} names no second channel"
1081 ));
1082 PamojaStatus::InvalidArgument
1083 }
1084 }
1085}
1086
1087unsafe fn sixteen(bytes: *const u8, len: usize, what: &str) -> Result<[u8; 16], PamojaStatus> {
1088 let bytes = read_bytes(bytes, len)?;
1089 <[u8; PAMOJA_LORAWAN_KEY_LEN]>::try_from(&bytes[..]).map_err(|_| {
1090 set_last_error(format!(
1091 "{what} must be exactly {PAMOJA_LORAWAN_KEY_LEN} bytes"
1092 ));
1093 PamojaStatus::InvalidArgument
1094 })
1095}
1096
1097#[cfg(test)]
1098mod tests {
1099 use super::*;
1100 use crate::lorawan::{pamoja_lorawan_session_free, pamoja_lorawan_session_new};
1101 use crate::{pamoja_buffer_data, pamoja_buffer_free, pamoja_buffer_len};
1102
1103 #[test]
1104 fn a_wor_goes_out_opens_and_is_acknowledged_across_the_boundary() {
1105 unsafe {
1106 let mut session = ptr::null_mut();
1107 assert_eq!(
1108 pamoja_lorawan_session_new(
1109 0x2601_1BDA,
1110 [0x2B; 16].as_ptr(),
1111 16,
1112 [0x99; 16].as_ptr(),
1113 16,
1114 &mut session
1115 ),
1116 PamojaStatus::Ok
1117 );
1118 let mut keys = PamojaLorawanWorKeys {
1119 integrity: [0; 16],
1120 encryption: [0; 16],
1121 };
1122 assert_eq!(
1123 pamoja_lorawan_session_wor_keys(session, &mut keys),
1124 PamojaStatus::Ok
1125 );
1126 let mut root = [0u8; 16];
1127 pamoja_lorawan_session_root_wor_s_key(session, root.as_mut_ptr());
1128 let mut derived = keys;
1129 derived.integrity = [0; 16];
1130 assert_eq!(
1131 pamoja_lorawan_relay_wor_keys(root.as_ptr(), 16, 0x2601_1BDA, &mut derived),
1132 PamojaStatus::Ok
1133 );
1134 assert!(derived == keys, "the session and the root key agree");
1135
1136 let wor = PamojaLorawanCarrier {
1137 frequency_hz: 865_100_000,
1138 data_rate: 3,
1139 };
1140 let uplink = PamojaLorawanCarrier {
1141 frequency_hz: 868_100_000,
1142 data_rate: 5,
1143 };
1144 let mut frame = [0u8; PAMOJA_LORAWAN_WOR_UPLINK_LEN];
1145 assert_eq!(
1146 pamoja_lorawan_relay_wor_uplink(
1147 &keys,
1148 0x2601_1BDA,
1149 1,
1150 &uplink,
1151 &wor,
1152 frame.as_mut_ptr()
1153 ),
1154 PamojaStatus::Ok
1155 );
1156 assert_eq!(frame[..5], [0x01, 0xda, 0x1b, 0x01, 0x26]);
1157
1158 let mut read = std::mem::zeroed::<PamojaLorawanWor>();
1159 assert_eq!(
1160 pamoja_lorawan_relay_wor_parse(frame.as_ptr(), frame.len(), &mut read),
1161 PamojaStatus::Ok
1162 );
1163 assert_eq!(
1164 (read.kind, read.dev_addr, read.wfcnt),
1165 (PAMOJA_LORAWAN_WOR_UPLINK, 0x2601_1BDA, 1)
1166 );
1167 let mut opened = std::mem::zeroed::<PamojaLorawanCarrier>();
1168 assert_eq!(
1169 pamoja_lorawan_relay_wor_open(
1170 frame.as_ptr(),
1171 frame.len(),
1172 &keys,
1173 1,
1174 &wor,
1175 &mut opened
1176 ),
1177 PamojaStatus::Ok
1178 );
1179 assert_eq!(opened, uplink);
1180 assert_eq!(
1181 pamoja_lorawan_relay_wor_open(
1182 frame.as_ptr(),
1183 frame.len(),
1184 &keys,
1185 0x0001_0001,
1186 &wor,
1187 &mut opened
1188 ),
1189 PamojaStatus::Auth
1190 );
1191
1192 let ack = PamojaLorawanCarrier {
1193 frequency_hz: 865_300_000,
1194 data_rate: 3,
1195 };
1196 let state = PamojaLorawanStateSync {
1197 cad_to_rx: 1,
1198 forward: 0,
1199 relay_data_rate: 5,
1200 xtal_accuracy: 2,
1201 cad_periodicity: 1,
1202 t_offset_ms: 892,
1203 };
1204 let mut answer = [0u8; PAMOJA_LORAWAN_WOR_ACK_LEN];
1205 assert_eq!(
1206 pamoja_lorawan_relay_wor_ack(
1207 &keys,
1208 0x2601_1BDA,
1209 1,
1210 &ack,
1211 &uplink,
1212 &state,
1213 answer.as_mut_ptr()
1214 ),
1215 PamojaStatus::Ok
1216 );
1217 assert_eq!(answer, [0xc0, 0x91, 0xac, 0x43, 0x19, 0x78, 0xef]);
1218 let mut heard = std::mem::zeroed::<PamojaLorawanStateSync>();
1219 assert_eq!(
1220 pamoja_lorawan_relay_wor_ack_open(
1221 answer.as_ptr(),
1222 answer.len(),
1223 &keys,
1224 0x2601_1BDA,
1225 1,
1226 &ack,
1227 &uplink,
1228 &mut heard
1229 ),
1230 PamojaStatus::Ok
1231 );
1232 assert_eq!(heard, state);
1233
1234 let mut bad = state;
1235 bad.cad_periodicity = 6;
1236 assert_eq!(
1237 pamoja_lorawan_relay_wor_ack(
1238 &keys,
1239 0x2601_1BDA,
1240 1,
1241 &ack,
1242 &uplink,
1243 &bad,
1244 answer.as_mut_ptr()
1245 ),
1246 PamojaStatus::InvalidArgument
1247 );
1248 pamoja_lorawan_session_free(session);
1249 }
1250 }
1251
1252 #[test]
1253 fn a_forwarded_uplink_and_the_timing_cross_the_boundary() {
1254 unsafe {
1255 let metadata = PamojaLorawanUplinkMetadata {
1256 wor_channel: 1,
1257 rssi_dbm: -100,
1258 snr_db: 5,
1259 data_rate: 5,
1260 };
1261 let frame = [0x40u8, 0x01, 0x02];
1262 let mut payload = ptr::null_mut();
1263 assert_eq!(
1264 pamoja_lorawan_relay_forward_encode(
1265 &metadata,
1266 868_100_000,
1267 frame.as_ptr(),
1268 frame.len(),
1269 &mut payload
1270 ),
1271 PamojaStatus::Ok
1272 );
1273 let bytes =
1274 std::slice::from_raw_parts(pamoja_buffer_data(payload), pamoja_buffer_len(payload))
1275 .to_vec();
1276 pamoja_buffer_free(payload);
1277 assert_eq!(
1278 bytes,
1279 [0x95, 0xab, 0x01, 0x28, 0x76, 0x84, 0x40, 0x01, 0x02]
1280 );
1281
1282 let mut read = std::mem::zeroed::<PamojaLorawanUplinkMetadata>();
1283 let mut frequency = 0u32;
1284 let mut phy = ptr::null_mut();
1285 assert_eq!(
1286 pamoja_lorawan_relay_forward_parse(
1287 bytes.as_ptr(),
1288 bytes.len(),
1289 &mut read,
1290 &mut frequency,
1291 &mut phy
1292 ),
1293 PamojaStatus::Ok
1294 );
1295 assert_eq!((read, frequency), (metadata, 868_100_000));
1296 assert_eq!(pamoja_buffer_len(phy), 3);
1297 pamoja_buffer_free(phy);
1298
1299 let mut symbols = 0u16;
1300 assert_eq!(
1301 pamoja_lorawan_relay_unsynchronized_preamble(1, 8_192, 1, &mut symbols),
1302 PamojaStatus::Ok
1303 );
1304 assert_eq!(symbols, 72);
1305 let mut offset = 0u16;
1306 assert_eq!(
1307 pamoja_lorawan_relay_t_offset_ms(87_654_000, 88_545_584, &mut offset),
1308 PamojaStatus::Ok
1309 );
1310 assert_eq!(offset, 892);
1311
1312 let state = PamojaLorawanStateSync {
1313 cad_to_rx: 1,
1314 forward: 0,
1315 relay_data_rate: 3,
1316 xtal_accuracy: 2,
1317 cad_periodicity: 1,
1318 t_offset_ms: 892,
1319 };
1320 let mut sync = std::mem::zeroed::<PamojaLorawanSynchronization>();
1321 assert_eq!(
1322 pamoja_lorawan_relay_synchronization(1_234_000, 133, 8_192, &state, &mut sync),
1323 PamojaStatus::Ok
1324 );
1325 assert_eq!(sync.reference_us, 1_431_536);
1326 let mut slot = std::mem::zeroed::<PamojaLorawanWorSlot>();
1327 let mut synchronized = 0u8;
1328 assert_eq!(
1329 pamoja_lorawan_relay_next_wor(
1330 &sync,
1331 61_000_000,
1332 20,
1333 8_192,
1334 0,
1335 &mut slot,
1336 &mut synchronized
1337 ),
1338 PamojaStatus::Ok
1339 );
1340 assert_eq!(
1341 (synchronized, slot.start_us, slot.preamble_symbols),
1342 (1, 61_430_036, 11)
1343 );
1344
1345 let mut channel = std::mem::zeroed::<PamojaLoraRelayChannel>();
1346 assert_eq!(
1347 pamoja_lorawan_relay_second_channel(1, 3, 1, 868_100_000, &mut channel),
1348 PamojaStatus::Ok
1349 );
1350 assert_eq!(channel.ack_frequency_hz, 868_300_000);
1351 }
1352 }
1353}