pamoja_ffi/lorawan.rs
1//! The C ABI for LoRaWAN 1.0.x MAC framing.
2//!
3//! These functions wrap [`pamoja_lorawan`] for callers that reach the SDK through
4//! the flat C boundary: the secured frame a long-range node puts on the air, and
5//! the over-the-air activation that hands it its session keys.
6//!
7//! A session and a device hold key material, so they cross as opaque handles and
8//! the keys never leave the library once set. An encoded frame comes back as a
9//! [`PamojaBuffer`], and a decoded one as a handle carrying its recovered payload.
10//! The header flags a sender chooses are only scalars, so they cross by value as
11//! [`PamojaLorawanFlags`].
12
13use std::ptr;
14
15use pamoja_lorawan::{
16 Device, Direction, Downlink, FrameHeader, JoinAccept, JoinGrant, JoinRequest, LorawanError,
17 MessageType, PhyPayload, RxData, Session, Uplink,
18};
19
20use crate::{read_bytes, set_last_error, PamojaBuffer, PamojaStatus};
21
22/// The largest LoRaWAN frame, in bytes, this build accepts.
23pub const PAMOJA_LORAWAN_FRAME_MAX: usize = 256;
24
25/// The largest application payload, in bytes, a single frame can carry.
26pub const PAMOJA_LORAWAN_PAYLOAD_MAX: usize = 243;
27
28/// The length of a LoRaWAN key, in bytes.
29pub const PAMOJA_LORAWAN_KEY_LEN: usize = 16;
30
31/// The length of a LoRaWAN EUI, in bytes.
32pub const PAMOJA_LORAWAN_EUI_LEN: usize = 8;
33
34/// The direction a frame travelled, which its MIC and encryption both fold in.
35#[repr(C)]
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum PamojaLorawanDirection {
38 /// From an end device up to the network.
39 Uplink = 0,
40 /// From the network down to an end device.
41 Downlink = 1,
42}
43
44/// The header flags a sender sets on a data frame.
45///
46/// Each is `1` for on and `0` for off. `fpending` applies to a downlink only and
47/// is ignored when encoding an uplink.
48#[repr(C)]
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub struct PamojaLorawanFlags {
51 /// Ask the far end to acknowledge this frame.
52 pub confirmed: u8,
53 /// Mark the frame as taking part in adaptive data rate.
54 pub adr: u8,
55 /// Acknowledge the last confirmed frame from the far end.
56 pub ack: u8,
57 /// Tell the device more downlink data is waiting.
58 pub fpending: u8,
59}
60
61/// An opaque handle to an activated LoRaWAN session.
62///
63/// Holds a device address and the two session keys, and never hands them back.
64/// Release it with [`pamoja_lorawan_session_free`].
65pub struct PamojaLorawanSession {
66 session: Session,
67}
68
69/// An opaque handle to the root credentials of a device.
70///
71/// Holds the EUIs and the application key that over-the-air activation is built
72/// on. Release it with [`pamoja_lorawan_device_free`].
73pub struct PamojaLorawanDevice {
74 device: Device,
75}
76
77/// An opaque handle to an accepted join.
78///
79/// Read the network settings off it, then take the session it grants with
80/// [`pamoja_lorawan_join_accept_session`]. Release it with
81/// [`pamoja_lorawan_join_accept_free`].
82pub struct PamojaLorawanJoinAccept {
83 accept: JoinAccept,
84}
85
86/// An opaque handle to a decoded data frame.
87///
88/// What a successful [`pamoja_lorawan_session_decode`] produces: the header fields
89/// and the decrypted payload. Release it with [`pamoja_lorawan_rx_free`].
90pub struct PamojaLorawanRx {
91 rx: RxData,
92}
93
94/// Creates a session from a device address and its two session keys.
95///
96/// # Arguments
97///
98/// * `dev_addr` - the device address the network assigned.
99/// * `nwk_skey` - the 16-byte network session key, which authenticates frames.
100/// * `nwk_skey_len` - its length, which must be [`PAMOJA_LORAWAN_KEY_LEN`].
101/// * `app_skey` - the 16-byte application session key, which encrypts payloads.
102/// * `app_skey_len` - its length, which must be [`PAMOJA_LORAWAN_KEY_LEN`].
103/// * `out_session` - receives the new session.
104///
105/// # Returns
106///
107/// [`PamojaStatus::Ok`] on success, with `*out_session` set to a handle the caller
108/// must release with [`pamoja_lorawan_session_free`], or
109/// [`PamojaStatus::InvalidArgument`] if either key is the wrong length.
110///
111/// # Safety
112///
113/// Each key pointer must point to at least its stated length in readable bytes,
114/// and `out_session` must point to a writable `*mut PamojaLorawanSession`.
115#[no_mangle]
116pub unsafe extern "C" fn pamoja_lorawan_session_new(
117 dev_addr: u32,
118 nwk_skey: *const u8,
119 nwk_skey_len: usize,
120 app_skey: *const u8,
121 app_skey_len: usize,
122 out_session: *mut *mut PamojaLorawanSession,
123) -> PamojaStatus {
124 if out_session.is_null() {
125 set_last_error("out_session must not be null".to_owned());
126 return PamojaStatus::InvalidArgument;
127 }
128 let slot = &mut *out_session;
129 *slot = ptr::null_mut();
130
131 let nwk_skey = match key(nwk_skey, nwk_skey_len, "the network session key") {
132 Ok(key) => key,
133 Err(status) => return status,
134 };
135 let app_skey = match key(app_skey, app_skey_len, "the application session key") {
136 Ok(key) => key,
137 Err(status) => return status,
138 };
139
140 *slot = Box::into_raw(Box::new(PamojaLorawanSession {
141 session: Session::new(dev_addr, nwk_skey, app_skey),
142 }));
143 PamojaStatus::Ok
144}
145
146/// Returns the device address a session is bound to.
147///
148/// # Returns
149///
150/// The device address, or 0 if `session` is null.
151///
152/// # Safety
153///
154/// `session` must be a live handle from a call that produced one, or null.
155#[no_mangle]
156pub unsafe extern "C" fn pamoja_lorawan_session_dev_addr(
157 session: *const PamojaLorawanSession,
158) -> u32 {
159 if session.is_null() {
160 return 0;
161 }
162 (*session).session.dev_addr()
163}
164
165/// Encodes an uplink data frame, encrypting the payload and appending the MIC.
166///
167/// # Arguments
168///
169/// * `session` - the activated session to send from.
170/// * `fcnt` - the frame counter for this uplink.
171/// * `fport` - the port; `0` for MAC commands, otherwise an application port.
172/// * `payload` - the application payload to carry.
173/// * `payload_len` - its length.
174/// * `fopts` - the frame options to carry in the header, at most 15 bytes.
175/// * `fopts_len` - their length.
176/// * `flags` - the header flags to set; `fpending` is ignored on an uplink.
177/// * `out_frame` - receives the encoded frame.
178///
179/// # Returns
180///
181/// [`PamojaStatus::Ok`] on success, with `*out_frame` set to a buffer the caller
182/// must release with [`pamoja_buffer_free`](crate::pamoja_buffer_free), or
183/// [`PamojaStatus::InvalidArgument`] if the payload and options do not fit one
184/// frame.
185///
186/// # Safety
187///
188/// `payload` and `fopts` must each point to at least their stated lengths in
189/// readable bytes when those lengths are non-zero, and `out_frame` must point to a
190/// writable `*mut PamojaBuffer`.
191#[no_mangle]
192#[allow(clippy::too_many_arguments)]
193pub unsafe extern "C" fn pamoja_lorawan_session_encode_uplink(
194 session: *const PamojaLorawanSession,
195 fcnt: u32,
196 fport: u8,
197 payload: *const u8,
198 payload_len: usize,
199 fopts: *const u8,
200 fopts_len: usize,
201 flags: PamojaLorawanFlags,
202 out_frame: *mut *mut PamojaBuffer,
203) -> PamojaStatus {
204 encode(
205 session,
206 payload,
207 payload_len,
208 fopts,
209 fopts_len,
210 out_frame,
211 |session, payload, fopts| {
212 let mut uplink = Uplink::new(fcnt, fport, payload).with_fopts(fopts);
213 if flags.confirmed != 0 {
214 uplink = uplink.confirmed();
215 }
216 if flags.adr != 0 {
217 uplink = uplink.with_adr();
218 }
219 if flags.ack != 0 {
220 uplink = uplink.with_ack();
221 }
222 session.encode_uplink(&uplink)
223 },
224 )
225}
226
227/// Encodes a downlink data frame, encrypting the payload and appending the MIC.
228///
229/// # Arguments
230///
231/// * `session` - the session the frame is addressed to.
232/// * `fcnt` - the frame counter for this downlink.
233/// * `fport` - the port; `0` for MAC commands, otherwise an application port.
234/// * `payload` - the application payload to carry.
235/// * `payload_len` - its length.
236/// * `fopts` - the frame options to carry in the header, at most 15 bytes.
237/// * `fopts_len` - their length.
238/// * `flags` - the header flags to set.
239/// * `out_frame` - receives the encoded frame.
240///
241/// # Returns
242///
243/// [`PamojaStatus::Ok`] on success, with `*out_frame` set to a buffer the caller
244/// must release with [`pamoja_buffer_free`](crate::pamoja_buffer_free), or
245/// [`PamojaStatus::InvalidArgument`] if the payload and options do not fit one
246/// frame.
247///
248/// # Safety
249///
250/// `payload` and `fopts` must each point to at least their stated lengths in
251/// readable bytes when those lengths are non-zero, and `out_frame` must point to a
252/// writable `*mut PamojaBuffer`.
253#[no_mangle]
254#[allow(clippy::too_many_arguments)]
255pub unsafe extern "C" fn pamoja_lorawan_session_encode_downlink(
256 session: *const PamojaLorawanSession,
257 fcnt: u32,
258 fport: u8,
259 payload: *const u8,
260 payload_len: usize,
261 fopts: *const u8,
262 fopts_len: usize,
263 flags: PamojaLorawanFlags,
264 out_frame: *mut *mut PamojaBuffer,
265) -> PamojaStatus {
266 encode(
267 session,
268 payload,
269 payload_len,
270 fopts,
271 fopts_len,
272 out_frame,
273 |session, payload, fopts| {
274 let mut downlink = Downlink::new(fcnt, fport, payload).with_fopts(fopts);
275 if flags.confirmed != 0 {
276 downlink = downlink.confirmed();
277 }
278 if flags.adr != 0 {
279 downlink = downlink.with_adr();
280 }
281 if flags.ack != 0 {
282 downlink = downlink.with_ack();
283 }
284 if flags.fpending != 0 {
285 downlink = downlink.with_fpending();
286 }
287 session.encode_downlink(&downlink)
288 },
289 )
290}
291
292/// Decodes a received data frame: verifies the MIC, then decrypts the payload.
293///
294/// # Arguments
295///
296/// * `session` - the session the frame belongs to.
297/// * `bytes` - the frame exactly as it came off the radio.
298/// * `bytes_len` - its length.
299/// * `fcnt` - the full 32-bit frame counter expected for this frame; its low 16
300/// bits must match the counter the frame carries.
301/// * `out_rx` - receives the decoded frame.
302///
303/// # Returns
304///
305/// [`PamojaStatus::Ok`] on success, with `*out_rx` set to a handle the caller must
306/// release with [`pamoja_lorawan_rx_free`], [`PamojaStatus::Auth`] if the MIC does
307/// not verify or the counter does not match, or [`PamojaStatus::Codec`] if the
308/// frame is truncated or is not a data frame.
309///
310/// # Safety
311///
312/// `bytes` must point to at least `bytes_len` readable bytes when that length is
313/// non-zero, and `out_rx` must point to a writable `*mut PamojaLorawanRx`.
314#[no_mangle]
315pub unsafe extern "C" fn pamoja_lorawan_session_decode(
316 session: *const PamojaLorawanSession,
317 bytes: *const u8,
318 bytes_len: usize,
319 fcnt: u32,
320 out_rx: *mut *mut PamojaLorawanRx,
321) -> PamojaStatus {
322 if out_rx.is_null() {
323 set_last_error("out_rx must not be null".to_owned());
324 return PamojaStatus::InvalidArgument;
325 }
326 let slot = &mut *out_rx;
327 *slot = ptr::null_mut();
328
329 if session.is_null() {
330 set_last_error("session must not be null".to_owned());
331 return PamojaStatus::InvalidArgument;
332 }
333 let bytes = match read_bytes(bytes, bytes_len) {
334 Ok(bytes) => bytes,
335 Err(status) => return status,
336 };
337 match (*session).session.decode(&bytes, fcnt) {
338 Ok(rx) => {
339 *slot = Box::into_raw(Box::new(PamojaLorawanRx { rx }));
340 PamojaStatus::Ok
341 }
342 Err(error) => failed(error),
343 }
344}
345
346/// Releases a session handle.
347///
348/// Passing null is a no-op.
349///
350/// # Safety
351///
352/// `session` must be a handle from a call that produced one and that has not
353/// already been freed, or null. After this call it must not be used again.
354#[no_mangle]
355pub unsafe extern "C" fn pamoja_lorawan_session_free(session: *mut PamojaLorawanSession) {
356 if !session.is_null() {
357 drop(Box::from_raw(session));
358 }
359}
360
361/// Returns the direction a decoded frame travelled.
362///
363/// # Returns
364///
365/// The direction, or [`PamojaLorawanDirection::Uplink`] if `rx` is null.
366///
367/// # Safety
368///
369/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
370#[no_mangle]
371pub unsafe extern "C" fn pamoja_lorawan_rx_direction(
372 rx: *const PamojaLorawanRx,
373) -> PamojaLorawanDirection {
374 if rx.is_null() {
375 return PamojaLorawanDirection::Uplink;
376 }
377 match (*rx).rx.direction() {
378 Direction::Uplink => PamojaLorawanDirection::Uplink,
379 Direction::Downlink => PamojaLorawanDirection::Downlink,
380 }
381}
382
383/// Returns the device address a decoded frame carries.
384///
385/// # Returns
386///
387/// The device address, or 0 if `rx` is null.
388///
389/// # Safety
390///
391/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
392#[no_mangle]
393pub unsafe extern "C" fn pamoja_lorawan_rx_dev_addr(rx: *const PamojaLorawanRx) -> u32 {
394 if rx.is_null() {
395 return 0;
396 }
397 (*rx).rx.dev_addr()
398}
399
400/// Returns the low 16 bits of the frame counter a decoded frame carries.
401///
402/// # Returns
403///
404/// The counter, or 0 if `rx` is null.
405///
406/// # Safety
407///
408/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
409#[no_mangle]
410pub unsafe extern "C" fn pamoja_lorawan_rx_fcnt(rx: *const PamojaLorawanRx) -> u16 {
411 if rx.is_null() {
412 return 0;
413 }
414 (*rx).rx.fcnt()
415}
416
417/// Reports whether a decoded frame asks to be acknowledged.
418///
419/// # Returns
420///
421/// `true` when the frame is confirmed, or `false` if `rx` is null.
422///
423/// # Safety
424///
425/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
426#[no_mangle]
427pub unsafe extern "C" fn pamoja_lorawan_rx_confirmed(rx: *const PamojaLorawanRx) -> bool {
428 !rx.is_null() && (*rx).rx.confirmed()
429}
430
431/// Reports whether a decoded frame takes part in adaptive data rate.
432///
433/// # Returns
434///
435/// `true` when the ADR bit is set, or `false` if `rx` is null.
436///
437/// # Safety
438///
439/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
440#[no_mangle]
441pub unsafe extern "C" fn pamoja_lorawan_rx_adr(rx: *const PamojaLorawanRx) -> bool {
442 !rx.is_null() && (*rx).rx.adr()
443}
444
445/// Reports whether a decoded frame acknowledges the last confirmed one.
446///
447/// # Returns
448///
449/// `true` when the ACK bit is set, or `false` if `rx` is null.
450///
451/// # Safety
452///
453/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
454#[no_mangle]
455pub unsafe extern "C" fn pamoja_lorawan_rx_ack(rx: *const PamojaLorawanRx) -> bool {
456 !rx.is_null() && (*rx).rx.ack()
457}
458
459/// Reports whether the network has more downlink data waiting.
460///
461/// # Returns
462///
463/// `true` when the frame-pending bit is set, or `false` if `rx` is null.
464///
465/// # Safety
466///
467/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
468#[no_mangle]
469pub unsafe extern "C" fn pamoja_lorawan_rx_fpending(rx: *const PamojaLorawanRx) -> bool {
470 !rx.is_null() && (*rx).rx.fpending()
471}
472
473/// Returns the port a decoded frame was sent on.
474///
475/// # Arguments
476///
477/// * `rx` - the decoded frame.
478/// * `out_fport` - receives the port.
479///
480/// # Returns
481///
482/// `true` when the frame carries a port, with `*out_fport` written, or `false`
483/// for a frame that carries only frame options and so has none.
484///
485/// # Safety
486///
487/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null,
488/// and `out_fport` must point to a writable `uint8_t`.
489#[no_mangle]
490pub unsafe extern "C" fn pamoja_lorawan_rx_fport(
491 rx: *const PamojaLorawanRx,
492 out_fport: *mut u8,
493) -> bool {
494 if rx.is_null() || out_fport.is_null() {
495 return false;
496 }
497 match (*rx).rx.fport() {
498 Some(fport) => {
499 *out_fport = fport;
500 true
501 }
502 None => false,
503 }
504}
505
506/// Returns a pointer to the frame options a decoded frame carries.
507///
508/// Use [`pamoja_lorawan_rx_fopts_len`] for the length. The pointer is valid until
509/// the frame is freed.
510///
511/// # Returns
512///
513/// A pointer to the options, or null if `rx` is null or there are none.
514///
515/// # Safety
516///
517/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
518#[no_mangle]
519pub unsafe extern "C" fn pamoja_lorawan_rx_fopts(rx: *const PamojaLorawanRx) -> *const u8 {
520 if rx.is_null() {
521 return ptr::null();
522 }
523 let fopts = (*rx).rx.fopts();
524 if fopts.is_empty() {
525 ptr::null()
526 } else {
527 fopts.as_ptr()
528 }
529}
530
531/// Returns the length in bytes of the frame options a decoded frame carries.
532///
533/// # Returns
534///
535/// The length, or 0 if `rx` is null.
536///
537/// # Safety
538///
539/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
540#[no_mangle]
541pub unsafe extern "C" fn pamoja_lorawan_rx_fopts_len(rx: *const PamojaLorawanRx) -> usize {
542 if rx.is_null() {
543 return 0;
544 }
545 (*rx).rx.fopts().len()
546}
547
548/// Returns a pointer to the decrypted payload of a decoded frame.
549///
550/// Use [`pamoja_lorawan_rx_payload_len`] for the length. The pointer is valid
551/// until the frame is freed.
552///
553/// # Returns
554///
555/// A pointer to the payload, or null if `rx` is null or the payload is empty.
556///
557/// # Safety
558///
559/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
560#[no_mangle]
561pub unsafe extern "C" fn pamoja_lorawan_rx_payload(rx: *const PamojaLorawanRx) -> *const u8 {
562 if rx.is_null() {
563 return ptr::null();
564 }
565 let payload = (*rx).rx.payload();
566 if payload.is_empty() {
567 ptr::null()
568 } else {
569 payload.as_ptr()
570 }
571}
572
573/// Returns the length in bytes of the decrypted payload of a decoded frame.
574///
575/// # Returns
576///
577/// The payload length, or 0 if `rx` is null.
578///
579/// # Safety
580///
581/// `rx` must be a live handle from [`pamoja_lorawan_session_decode`], or null.
582#[no_mangle]
583pub unsafe extern "C" fn pamoja_lorawan_rx_payload_len(rx: *const PamojaLorawanRx) -> usize {
584 if rx.is_null() {
585 return 0;
586 }
587 (*rx).rx.payload().len()
588}
589
590/// Releases a decoded frame handle.
591///
592/// Passing null is a no-op.
593///
594/// # Safety
595///
596/// `rx` must be a handle from [`pamoja_lorawan_session_decode`] that has not
597/// already been freed, or null. After this call it must not be used again.
598#[no_mangle]
599pub unsafe extern "C" fn pamoja_lorawan_rx_free(rx: *mut PamojaLorawanRx) {
600 if !rx.is_null() {
601 drop(Box::from_raw(rx));
602 }
603}
604
605/// Creates a device from the root credentials over-the-air activation uses.
606///
607/// # Arguments
608///
609/// * `dev_eui` - the 8-byte device EUI.
610/// * `dev_eui_len` - its length, which must be [`PAMOJA_LORAWAN_EUI_LEN`].
611/// * `app_eui` - the 8-byte application (join) EUI.
612/// * `app_eui_len` - its length, which must be [`PAMOJA_LORAWAN_EUI_LEN`].
613/// * `app_key` - the 16-byte application key the join exchange is secured with.
614/// * `app_key_len` - its length, which must be [`PAMOJA_LORAWAN_KEY_LEN`].
615/// * `out_device` - receives the new device.
616///
617/// # Returns
618///
619/// [`PamojaStatus::Ok`] on success, with `*out_device` set to a handle the caller
620/// must release with [`pamoja_lorawan_device_free`], or
621/// [`PamojaStatus::InvalidArgument`] if any credential is the wrong length.
622///
623/// # Safety
624///
625/// Each pointer must point to at least its stated length in readable bytes, and
626/// `out_device` must point to a writable `*mut PamojaLorawanDevice`.
627#[no_mangle]
628pub unsafe extern "C" fn pamoja_lorawan_device_new(
629 dev_eui: *const u8,
630 dev_eui_len: usize,
631 app_eui: *const u8,
632 app_eui_len: usize,
633 app_key: *const u8,
634 app_key_len: usize,
635 out_device: *mut *mut PamojaLorawanDevice,
636) -> PamojaStatus {
637 if out_device.is_null() {
638 set_last_error("out_device must not be null".to_owned());
639 return PamojaStatus::InvalidArgument;
640 }
641 let slot = &mut *out_device;
642 *slot = ptr::null_mut();
643
644 let dev_eui = match eui(dev_eui, dev_eui_len, "the device EUI") {
645 Ok(eui) => eui,
646 Err(status) => return status,
647 };
648 let app_eui = match eui(app_eui, app_eui_len, "the application EUI") {
649 Ok(eui) => eui,
650 Err(status) => return status,
651 };
652 let app_key = match key(app_key, app_key_len, "the application key") {
653 Ok(key) => key,
654 Err(status) => return status,
655 };
656
657 *slot = Box::into_raw(Box::new(PamojaLorawanDevice {
658 device: Device::new(dev_eui, app_eui, app_key),
659 }));
660 PamojaStatus::Ok
661}
662
663/// Builds the join request a device broadcasts to activate.
664///
665/// # Arguments
666///
667/// * `device` - the device to activate.
668/// * `dev_nonce` - a nonce that must never repeat for this device, since the
669/// network rejects a replayed one.
670/// * `out_frame` - receives the encoded join request.
671///
672/// # Returns
673///
674/// [`PamojaStatus::Ok`] on success, with `*out_frame` set to a buffer the caller
675/// must release with [`pamoja_buffer_free`](crate::pamoja_buffer_free), or
676/// [`PamojaStatus::InvalidArgument`] if `device` is null.
677///
678/// # Safety
679///
680/// `device` must be a live handle from [`pamoja_lorawan_device_new`], or null, and
681/// `out_frame` must point to a writable `*mut PamojaBuffer`.
682#[no_mangle]
683pub unsafe extern "C" fn pamoja_lorawan_device_join_request(
684 device: *const PamojaLorawanDevice,
685 dev_nonce: u16,
686 out_frame: *mut *mut PamojaBuffer,
687) -> PamojaStatus {
688 if out_frame.is_null() {
689 set_last_error("out_frame must not be null".to_owned());
690 return PamojaStatus::InvalidArgument;
691 }
692 let slot = &mut *out_frame;
693 *slot = ptr::null_mut();
694
695 if device.is_null() {
696 set_last_error("device must not be null".to_owned());
697 return PamojaStatus::InvalidArgument;
698 }
699 let request = (*device).device.join_request(dev_nonce);
700 *slot = PamojaBuffer::into_raw(request.as_bytes().to_vec());
701 PamojaStatus::Ok
702}
703
704/// Turns the join accept a network sent into the settings it grants.
705///
706/// # Arguments
707///
708/// * `device` - the device that sent the join request.
709/// * `bytes` - the join accept exactly as it arrived.
710/// * `bytes_len` - its length.
711/// * `dev_nonce` - the nonce the matching join request carried.
712/// * `out_accept` - receives the accepted join.
713///
714/// # Returns
715///
716/// [`PamojaStatus::Ok`] on success, with `*out_accept` set to a handle the caller
717/// must release with [`pamoja_lorawan_join_accept_free`],
718/// [`PamojaStatus::Auth`] if the MIC does not verify, or
719/// [`PamojaStatus::Codec`] if the frame is truncated or is not a join accept.
720///
721/// # Safety
722///
723/// `device` must be a live handle from [`pamoja_lorawan_device_new`], or null,
724/// `bytes` must point to at least `bytes_len` readable bytes when that length is
725/// non-zero, and `out_accept` must point to a writable
726/// `*mut PamojaLorawanJoinAccept`.
727#[no_mangle]
728pub unsafe extern "C" fn pamoja_lorawan_device_accept_join(
729 device: *const PamojaLorawanDevice,
730 bytes: *const u8,
731 bytes_len: usize,
732 dev_nonce: u16,
733 out_accept: *mut *mut PamojaLorawanJoinAccept,
734) -> PamojaStatus {
735 if out_accept.is_null() {
736 set_last_error("out_accept must not be null".to_owned());
737 return PamojaStatus::InvalidArgument;
738 }
739 let slot = &mut *out_accept;
740 *slot = ptr::null_mut();
741
742 if device.is_null() {
743 set_last_error("device must not be null".to_owned());
744 return PamojaStatus::InvalidArgument;
745 }
746 let bytes = match read_bytes(bytes, bytes_len) {
747 Ok(bytes) => bytes,
748 Err(status) => return status,
749 };
750 match (*device).device.accept_join(&bytes, dev_nonce) {
751 Ok(accept) => {
752 *slot = Box::into_raw(Box::new(PamojaLorawanJoinAccept { accept }));
753 PamojaStatus::Ok
754 }
755 Err(error) => failed(error),
756 }
757}
758
759/// Releases a device handle.
760///
761/// Passing null is a no-op.
762///
763/// # Safety
764///
765/// `device` must be a handle from [`pamoja_lorawan_device_new`] that has not
766/// already been freed, or null. After this call it must not be used again.
767#[no_mangle]
768pub unsafe extern "C" fn pamoja_lorawan_device_free(device: *mut PamojaLorawanDevice) {
769 if !device.is_null() {
770 drop(Box::from_raw(device));
771 }
772}
773
774/// Returns the device address a join grants.
775///
776/// # Returns
777///
778/// The device address, or 0 if `accept` is null.
779///
780/// # Safety
781///
782/// `accept` must be a live handle from [`pamoja_lorawan_device_accept_join`], or
783/// null.
784#[no_mangle]
785pub unsafe extern "C" fn pamoja_lorawan_join_accept_dev_addr(
786 accept: *const PamojaLorawanJoinAccept,
787) -> u32 {
788 if accept.is_null() {
789 return 0;
790 }
791 (*accept).accept.dev_addr()
792}
793
794/// Returns the identifier of the network that accepted a join.
795///
796/// # Returns
797///
798/// The network identifier, or 0 if `accept` is null.
799///
800/// # Safety
801///
802/// `accept` must be a live handle from [`pamoja_lorawan_device_accept_join`], or
803/// null.
804#[no_mangle]
805pub unsafe extern "C" fn pamoja_lorawan_join_accept_net_id(
806 accept: *const PamojaLorawanJoinAccept,
807) -> u32 {
808 if accept.is_null() {
809 return 0;
810 }
811 (*accept).accept.net_id()
812}
813
814/// Returns the downlink settings byte a join grants.
815///
816/// # Returns
817///
818/// The settings byte, which carries the second receive window data rate and the
819/// first window offset, or 0 if `accept` is null.
820///
821/// # Safety
822///
823/// `accept` must be a live handle from [`pamoja_lorawan_device_accept_join`], or
824/// null.
825#[no_mangle]
826pub unsafe extern "C" fn pamoja_lorawan_join_accept_dl_settings(
827 accept: *const PamojaLorawanJoinAccept,
828) -> u8 {
829 if accept.is_null() {
830 return 0;
831 }
832 (*accept).accept.dl_settings()
833}
834
835/// Returns the delay before the first receive window, in seconds.
836///
837/// # Returns
838///
839/// The delay, or 0 if `accept` is null.
840///
841/// # Safety
842///
843/// `accept` must be a live handle from [`pamoja_lorawan_device_accept_join`], or
844/// null.
845#[no_mangle]
846pub unsafe extern "C" fn pamoja_lorawan_join_accept_rx_delay(
847 accept: *const PamojaLorawanJoinAccept,
848) -> u8 {
849 if accept.is_null() {
850 return 0;
851 }
852 (*accept).accept.rx_delay()
853}
854
855/// Takes the activated session a join grants.
856///
857/// # Arguments
858///
859/// * `accept` - the accepted join.
860/// * `out_session` - receives the session.
861///
862/// # Returns
863///
864/// [`PamojaStatus::Ok`] on success, with `*out_session` set to a handle the caller
865/// must release with [`pamoja_lorawan_session_free`], or
866/// [`PamojaStatus::InvalidArgument`] if `accept` is null.
867///
868/// # Safety
869///
870/// `accept` must be a live handle from [`pamoja_lorawan_device_accept_join`], or
871/// null, and `out_session` must point to a writable `*mut PamojaLorawanSession`.
872#[no_mangle]
873pub unsafe extern "C" fn pamoja_lorawan_join_accept_session(
874 accept: *const PamojaLorawanJoinAccept,
875 out_session: *mut *mut PamojaLorawanSession,
876) -> PamojaStatus {
877 if out_session.is_null() {
878 set_last_error("out_session must not be null".to_owned());
879 return PamojaStatus::InvalidArgument;
880 }
881 let slot = &mut *out_session;
882 *slot = ptr::null_mut();
883
884 if accept.is_null() {
885 set_last_error("accept must not be null".to_owned());
886 return PamojaStatus::InvalidArgument;
887 }
888 *slot = Box::into_raw(Box::new(PamojaLorawanSession {
889 session: (*accept).accept.session(),
890 }));
891 PamojaStatus::Ok
892}
893
894/// Releases an accepted join handle.
895///
896/// Passing null is a no-op.
897///
898/// # Safety
899///
900/// `accept` must be a handle from [`pamoja_lorawan_device_accept_join`] that has
901/// not already been freed, or null. After this call it must not be used again.
902#[no_mangle]
903pub unsafe extern "C" fn pamoja_lorawan_join_accept_free(accept: *mut PamojaLorawanJoinAccept) {
904 if !accept.is_null() {
905 drop(Box::from_raw(accept));
906 }
907}
908
909/// Encodes a data frame with whichever builder the caller asked for.
910///
911/// # Safety
912///
913/// `payload` and `fopts` must each point to at least their stated lengths in
914/// readable bytes when those lengths are non-zero, and `out_frame` must point to a
915/// writable `*mut PamojaBuffer`.
916unsafe fn encode(
917 session: *const PamojaLorawanSession,
918 payload: *const u8,
919 payload_len: usize,
920 fopts: *const u8,
921 fopts_len: usize,
922 out_frame: *mut *mut PamojaBuffer,
923 build: impl FnOnce(&Session, &[u8], &[u8]) -> Result<PhyPayload, LorawanError>,
924) -> PamojaStatus {
925 if out_frame.is_null() {
926 set_last_error("out_frame must not be null".to_owned());
927 return PamojaStatus::InvalidArgument;
928 }
929 let slot = &mut *out_frame;
930 *slot = ptr::null_mut();
931
932 if session.is_null() {
933 set_last_error("session must not be null".to_owned());
934 return PamojaStatus::InvalidArgument;
935 }
936 let payload = match read_bytes(payload, payload_len) {
937 Ok(bytes) => bytes,
938 Err(status) => return status,
939 };
940 let fopts = match read_bytes(fopts, fopts_len) {
941 Ok(bytes) => bytes,
942 Err(status) => return status,
943 };
944 match build(&(*session).session, &payload, &fopts) {
945 Ok(frame) => {
946 *slot = PamojaBuffer::into_raw(frame.as_bytes().to_vec());
947 PamojaStatus::Ok
948 }
949 Err(error) => failed(error),
950 }
951}
952
953/// Copies a borrowed 16-byte key.
954///
955/// # Safety
956///
957/// `bytes` must point to at least `len` readable bytes when that length is
958/// non-zero.
959unsafe fn key(bytes: *const u8, len: usize, what: &str) -> Result<[u8; 16], PamojaStatus> {
960 let bytes = read_bytes(bytes, len)?;
961 <[u8; PAMOJA_LORAWAN_KEY_LEN]>::try_from(&bytes[..]).map_err(|_| {
962 set_last_error(format!(
963 "{what} must be exactly {PAMOJA_LORAWAN_KEY_LEN} bytes"
964 ));
965 PamojaStatus::InvalidArgument
966 })
967}
968
969/// Copies a borrowed 8-byte EUI.
970///
971/// # Safety
972///
973/// `bytes` must point to at least `len` readable bytes when that length is
974/// non-zero.
975unsafe fn eui(bytes: *const u8, len: usize, what: &str) -> Result<[u8; 8], PamojaStatus> {
976 let bytes = read_bytes(bytes, len)?;
977 <[u8; PAMOJA_LORAWAN_EUI_LEN]>::try_from(&bytes[..]).map_err(|_| {
978 set_last_error(format!(
979 "{what} must be exactly {PAMOJA_LORAWAN_EUI_LEN} bytes"
980 ));
981 PamojaStatus::InvalidArgument
982 })
983}
984
985/// Records a LoRaWAN error and classifies it.
986///
987/// # Arguments
988///
989/// * `error` - the failure the LoRaWAN crate reported.
990///
991/// # Returns
992///
993/// [`PamojaStatus::Auth`] when a frame failed its integrity or counter check,
994/// [`PamojaStatus::InvalidArgument`] when the caller asked for a frame that cannot
995/// be built, and [`PamojaStatus::Codec`] when a received frame could not be read.
996fn failed(error: LorawanError) -> PamojaStatus {
997 set_last_error(error.to_string());
998 match error {
999 LorawanError::MicMismatch | LorawanError::FcntMismatch => PamojaStatus::Auth,
1000 LorawanError::PayloadTooLong => PamojaStatus::InvalidArgument,
1001 _ => PamojaStatus::Codec,
1002 }
1003}
1004
1005/// What kind of message a frame is, read from its header.
1006#[repr(C)]
1007#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1008pub enum PamojaLorawanMessageType {
1009 /// A device asking to join a network.
1010 JoinRequest = 0,
1011 /// A network admitting a device.
1012 JoinAccept = 1,
1013 /// Data from a device that does not need acknowledging.
1014 UnconfirmedUp = 2,
1015 /// Data from a device that asks to be acknowledged.
1016 ConfirmedUp = 3,
1017 /// Data to a device that does not need acknowledging.
1018 UnconfirmedDown = 4,
1019 /// Data to a device that asks to be acknowledged.
1020 ConfirmedDown = 5,
1021}
1022
1023/// What a frame says about itself before any key is involved.
1024///
1025/// Every field is a scalar, so this crosses the boundary by value. `is_data` is
1026/// `1` when `dev_addr` and `fcnt` are meaningful, which is every message type
1027/// except the two join frames, and `has_fport` is `1` when `fport` is.
1028///
1029/// Nothing here is authenticated, since checking the MIC needs the session key.
1030/// Treat it as a routing hint until [`pamoja_lorawan_session_decode`] has verified
1031/// the frame.
1032#[repr(C)]
1033#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1034pub struct PamojaLorawanHeader {
1035 /// The length of the still-encrypted payload, in bytes.
1036 pub payload_len: usize,
1037 /// The device address, meaningful only when `is_data` is `1`.
1038 pub dev_addr: u32,
1039 /// The low 16 bits of the frame counter, meaningful only when `is_data` is `1`.
1040 pub fcnt: u16,
1041 /// What kind of message the frame is.
1042 pub message_type: PamojaLorawanMessageType,
1043 /// The port the frame was sent on, meaningful only when `has_fport` is `1`.
1044 pub fport: u8,
1045 /// `1` for a data frame, `0` for one of the two join frames.
1046 pub is_data: u8,
1047 /// `1` when the frame carries a port rather than only frame options.
1048 pub has_fport: u8,
1049 /// `1` when the frame asks to be acknowledged.
1050 pub confirmed: u8,
1051 /// `1` when the frame takes part in adaptive data rate.
1052 pub adr: u8,
1053 /// `1` when the frame acknowledges the last confirmed one.
1054 pub ack: u8,
1055 /// `1` when the network has more downlink data waiting.
1056 pub fpending: u8,
1057 /// How many bytes of frame options the header carries, from 0 to 15.
1058 pub fopts_len: u8,
1059}
1060
1061/// What a network grants a device that joined.
1062///
1063/// Every field is a scalar, so this crosses the boundary by value. The optional
1064/// channel list is passed alongside it, since it is bytes rather than a scalar.
1065#[repr(C)]
1066#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1067pub struct PamojaLorawanGrant {
1068 /// A nonce this network must not reuse for the device; low 24 bits only.
1069 pub app_nonce: u32,
1070 /// The network identifier; low 24 bits only.
1071 pub net_id: u32,
1072 /// The address to assign the device.
1073 pub dev_addr: u32,
1074 /// The downlink settings byte.
1075 pub dl_settings: u8,
1076 /// The delay before the first receive window, in seconds.
1077 pub rx_delay: u8,
1078}
1079
1080/// An opaque handle to a verified join-request.
1081///
1082/// Read it with the `pamoja_lorawan_join_request_*` calls, then release it with
1083/// [`pamoja_lorawan_join_request_free`].
1084pub struct PamojaLorawanJoinRequest {
1085 request: JoinRequest,
1086}
1087
1088/// Reads a frame far enough to route it, without any key.
1089///
1090/// A receiver holding many sessions uses this to find which one a frame belongs
1091/// to: the device address travels in the clear, so it can be read before the
1092/// session that would verify the frame is even known.
1093///
1094/// # Arguments
1095///
1096/// * `bytes` - the raw frame as it came off the radio.
1097/// * `bytes_len` - its length.
1098/// * `out_header` - receives the header fields.
1099///
1100/// # Returns
1101///
1102/// [`PamojaStatus::Ok`] on success, with `*out_header` filled in, or
1103/// [`PamojaStatus::Codec`] if the frame is truncated, carries a message type this
1104/// build does not read, or declares more frame options than it holds.
1105///
1106/// # Safety
1107///
1108/// `bytes` must point to at least `bytes_len` readable bytes when that length is
1109/// non-zero, and `out_header` must point to a writable [`PamojaLorawanHeader`].
1110#[no_mangle]
1111pub unsafe extern "C" fn pamoja_lorawan_header_parse(
1112 bytes: *const u8,
1113 bytes_len: usize,
1114 out_header: *mut PamojaLorawanHeader,
1115) -> PamojaStatus {
1116 if out_header.is_null() {
1117 set_last_error("out_header must not be null".to_owned());
1118 return PamojaStatus::InvalidArgument;
1119 }
1120 let bytes = match read_bytes(bytes, bytes_len) {
1121 Ok(bytes) => bytes,
1122 Err(status) => return status,
1123 };
1124 let header = match FrameHeader::parse(&bytes) {
1125 Ok(header) => header,
1126 Err(error) => return failed(error),
1127 };
1128
1129 *out_header = PamojaLorawanHeader {
1130 payload_len: header.payload_len(),
1131 dev_addr: header.dev_addr().unwrap_or(0),
1132 fcnt: header.fcnt().unwrap_or(0),
1133 message_type: match header.message_type() {
1134 MessageType::JoinRequest => PamojaLorawanMessageType::JoinRequest,
1135 MessageType::JoinAccept => PamojaLorawanMessageType::JoinAccept,
1136 MessageType::UnconfirmedUp => PamojaLorawanMessageType::UnconfirmedUp,
1137 MessageType::ConfirmedUp => PamojaLorawanMessageType::ConfirmedUp,
1138 MessageType::UnconfirmedDown => PamojaLorawanMessageType::UnconfirmedDown,
1139 MessageType::ConfirmedDown => PamojaLorawanMessageType::ConfirmedDown,
1140 },
1141 fport: header.fport().unwrap_or(0),
1142 is_data: u8::from(header.message_type().is_data()),
1143 has_fport: u8::from(header.fport().is_some()),
1144 confirmed: u8::from(header.confirmed()),
1145 adr: u8::from(header.adr()),
1146 ack: u8::from(header.ack()),
1147 fpending: u8::from(header.fpending()),
1148 fopts_len: header.fopts_len() as u8,
1149 };
1150 PamojaStatus::Ok
1151}
1152
1153/// Verifies a join-request and reads the identifiers out of it.
1154///
1155/// # Arguments
1156///
1157/// * `bytes` - the raw join-request as it came off the radio.
1158/// * `bytes_len` - its length.
1159/// * `app_key` - the 16-byte application root key the device shares.
1160/// * `app_key_len` - its length, which must be [`PAMOJA_LORAWAN_KEY_LEN`].
1161/// * `out_request` - receives the verified request.
1162///
1163/// # Returns
1164///
1165/// [`PamojaStatus::Ok`] on success, with `*out_request` set to a handle the caller
1166/// must release with [`pamoja_lorawan_join_request_free`], [`PamojaStatus::Auth`]
1167/// if the MIC does not verify, or [`PamojaStatus::Codec`] if the frame is not a
1168/// well-formed join-request.
1169///
1170/// # Safety
1171///
1172/// `bytes` and `app_key` must each point to at least their stated lengths in
1173/// readable bytes, and `out_request` must point to a writable
1174/// `*mut PamojaLorawanJoinRequest`.
1175#[no_mangle]
1176pub unsafe extern "C" fn pamoja_lorawan_join_request_parse(
1177 bytes: *const u8,
1178 bytes_len: usize,
1179 app_key: *const u8,
1180 app_key_len: usize,
1181 out_request: *mut *mut PamojaLorawanJoinRequest,
1182) -> PamojaStatus {
1183 if out_request.is_null() {
1184 set_last_error("out_request must not be null".to_owned());
1185 return PamojaStatus::InvalidArgument;
1186 }
1187 let slot = &mut *out_request;
1188 *slot = ptr::null_mut();
1189
1190 let bytes = match read_bytes(bytes, bytes_len) {
1191 Ok(bytes) => bytes,
1192 Err(status) => return status,
1193 };
1194 let app_key = match key(app_key, app_key_len, "the application key") {
1195 Ok(key) => key,
1196 Err(status) => return status,
1197 };
1198 match JoinRequest::parse(&bytes, &app_key) {
1199 Ok(request) => {
1200 *slot = Box::into_raw(Box::new(PamojaLorawanJoinRequest { request }));
1201 PamojaStatus::Ok
1202 }
1203 Err(error) => failed(error),
1204 }
1205}
1206
1207/// Copies the device identifier out of a verified join-request.
1208///
1209/// # Arguments
1210///
1211/// * `request` - the verified request.
1212/// * `out_dev_eui` - receives [`PAMOJA_LORAWAN_EUI_LEN`] bytes, most-significant
1213/// byte first.
1214///
1215/// # Returns
1216///
1217/// `true` when the identifier was written, or `false` if either pointer is null.
1218///
1219/// # Safety
1220///
1221/// `request` must be a live handle from [`pamoja_lorawan_join_request_parse`], or
1222/// null, and `out_dev_eui` must point to at least
1223/// [`PAMOJA_LORAWAN_EUI_LEN`] writable bytes.
1224#[no_mangle]
1225pub unsafe extern "C" fn pamoja_lorawan_join_request_dev_eui(
1226 request: *const PamojaLorawanJoinRequest,
1227 out_dev_eui: *mut u8,
1228) -> bool {
1229 if request.is_null() || out_dev_eui.is_null() {
1230 return false;
1231 }
1232 let eui = (*request).request.dev_eui();
1233 ptr::copy_nonoverlapping(eui.as_ptr(), out_dev_eui, PAMOJA_LORAWAN_EUI_LEN);
1234 true
1235}
1236
1237/// Copies the application identifier out of a verified join-request.
1238///
1239/// # Arguments
1240///
1241/// * `request` - the verified request.
1242/// * `out_app_eui` - receives [`PAMOJA_LORAWAN_EUI_LEN`] bytes, most-significant
1243/// byte first.
1244///
1245/// # Returns
1246///
1247/// `true` when the identifier was written, or `false` if either pointer is null.
1248///
1249/// # Safety
1250///
1251/// `request` must be a live handle from [`pamoja_lorawan_join_request_parse`], or
1252/// null, and `out_app_eui` must point to at least
1253/// [`PAMOJA_LORAWAN_EUI_LEN`] writable bytes.
1254#[no_mangle]
1255pub unsafe extern "C" fn pamoja_lorawan_join_request_app_eui(
1256 request: *const PamojaLorawanJoinRequest,
1257 out_app_eui: *mut u8,
1258) -> bool {
1259 if request.is_null() || out_app_eui.is_null() {
1260 return false;
1261 }
1262 let eui = (*request).request.app_eui();
1263 ptr::copy_nonoverlapping(eui.as_ptr(), out_app_eui, PAMOJA_LORAWAN_EUI_LEN);
1264 true
1265}
1266
1267/// Returns the nonce a verified join-request carried.
1268///
1269/// A network must remember the nonces a device has used and refuse a repeat, since
1270/// replaying one would re-derive the same session keys.
1271///
1272/// # Returns
1273///
1274/// The DevNonce, or 0 if `request` is null.
1275///
1276/// # Safety
1277///
1278/// `request` must be a live handle from [`pamoja_lorawan_join_request_parse`], or
1279/// null.
1280#[no_mangle]
1281pub unsafe extern "C" fn pamoja_lorawan_join_request_dev_nonce(
1282 request: *const PamojaLorawanJoinRequest,
1283) -> u16 {
1284 if request.is_null() {
1285 return 0;
1286 }
1287 (*request).request.dev_nonce()
1288}
1289
1290/// Releases a verified join-request handle.
1291///
1292/// Passing null is a no-op.
1293///
1294/// # Safety
1295///
1296/// `request` must be a handle from [`pamoja_lorawan_join_request_parse`] that has
1297/// not already been freed, or null. After this call it must not be used again.
1298#[no_mangle]
1299pub unsafe extern "C" fn pamoja_lorawan_join_request_free(request: *mut PamojaLorawanJoinRequest) {
1300 if !request.is_null() {
1301 drop(Box::from_raw(request));
1302 }
1303}
1304
1305/// Builds the signed join-accept a network sends to admit a device.
1306///
1307/// # Arguments
1308///
1309/// * `grant` - the address and settings to grant.
1310/// * `cflist` - the optional 16-byte channel list, or null for none.
1311/// * `cflist_len` - its length, either 0 or 16.
1312/// * `app_key` - the 16-byte application root key the device shares.
1313/// * `app_key_len` - its length, which must be [`PAMOJA_LORAWAN_KEY_LEN`].
1314/// * `dev_nonce` - the nonce the matching join-request carried.
1315/// * `out_frame` - receives the encoded join-accept.
1316///
1317/// # Returns
1318///
1319/// [`PamojaStatus::Ok`] on success, with `*out_frame` set to a buffer the caller
1320/// must release with [`pamoja_buffer_free`](crate::pamoja_buffer_free), or
1321/// [`PamojaStatus::InvalidArgument`] if the key or the channel list is the wrong
1322/// length.
1323///
1324/// # Safety
1325///
1326/// `cflist` and `app_key` must each point to at least their stated lengths in
1327/// readable bytes, and `out_frame` must point to a writable `*mut PamojaBuffer`.
1328#[no_mangle]
1329pub unsafe extern "C" fn pamoja_lorawan_grant_accept(
1330 grant: PamojaLorawanGrant,
1331 cflist: *const u8,
1332 cflist_len: usize,
1333 app_key: *const u8,
1334 app_key_len: usize,
1335 dev_nonce: u16,
1336 out_frame: *mut *mut PamojaBuffer,
1337) -> PamojaStatus {
1338 if out_frame.is_null() {
1339 set_last_error("out_frame must not be null".to_owned());
1340 return PamojaStatus::InvalidArgument;
1341 }
1342 let slot = &mut *out_frame;
1343 *slot = ptr::null_mut();
1344
1345 let (grant, app_key) = match granted(grant, cflist, cflist_len, app_key, app_key_len) {
1346 Ok(pair) => pair,
1347 Err(status) => return status,
1348 };
1349 *slot = PamojaBuffer::into_raw(grant.accept(&app_key, dev_nonce).as_bytes().to_vec());
1350 PamojaStatus::Ok
1351}
1352
1353/// Derives the session a grant activates, the same one the device computes.
1354///
1355/// # Arguments
1356///
1357/// * `grant` - the address and settings granted.
1358/// * `cflist` - the optional 16-byte channel list, or null for none.
1359/// * `cflist_len` - its length, either 0 or 16.
1360/// * `app_key` - the 16-byte application root key the device shares.
1361/// * `app_key_len` - its length, which must be [`PAMOJA_LORAWAN_KEY_LEN`].
1362/// * `dev_nonce` - the nonce the matching join-request carried.
1363/// * `out_session` - receives the session.
1364///
1365/// # Returns
1366///
1367/// [`PamojaStatus::Ok`] on success, with `*out_session` set to a handle the caller
1368/// must release with [`pamoja_lorawan_session_free`], or
1369/// [`PamojaStatus::InvalidArgument`] if the key or the channel list is the wrong
1370/// length.
1371///
1372/// # Safety
1373///
1374/// `cflist` and `app_key` must each point to at least their stated lengths in
1375/// readable bytes, and `out_session` must point to a writable
1376/// `*mut PamojaLorawanSession`.
1377#[no_mangle]
1378pub unsafe extern "C" fn pamoja_lorawan_grant_session(
1379 grant: PamojaLorawanGrant,
1380 cflist: *const u8,
1381 cflist_len: usize,
1382 app_key: *const u8,
1383 app_key_len: usize,
1384 dev_nonce: u16,
1385 out_session: *mut *mut PamojaLorawanSession,
1386) -> PamojaStatus {
1387 if out_session.is_null() {
1388 set_last_error("out_session must not be null".to_owned());
1389 return PamojaStatus::InvalidArgument;
1390 }
1391 let slot = &mut *out_session;
1392 *slot = ptr::null_mut();
1393
1394 let (grant, app_key) = match granted(grant, cflist, cflist_len, app_key, app_key_len) {
1395 Ok(pair) => pair,
1396 Err(status) => return status,
1397 };
1398 *slot = Box::into_raw(Box::new(PamojaLorawanSession {
1399 session: grant.session(&app_key, dev_nonce),
1400 }));
1401 PamojaStatus::Ok
1402}
1403
1404/// Rebuilds a Rust grant and the key it is used with from what crossed the ABI.
1405///
1406/// # Safety
1407///
1408/// `cflist` and `app_key` must each point to at least their stated lengths in
1409/// readable bytes when those lengths are non-zero.
1410unsafe fn granted(
1411 grant: PamojaLorawanGrant,
1412 cflist: *const u8,
1413 cflist_len: usize,
1414 app_key: *const u8,
1415 app_key_len: usize,
1416) -> Result<(JoinGrant, [u8; 16]), PamojaStatus> {
1417 let app_key = key(app_key, app_key_len, "the application key")?;
1418 let mut built = JoinGrant::new(grant.app_nonce, grant.net_id, grant.dev_addr)
1419 .with_dl_settings(grant.dl_settings)
1420 .with_rx_delay(grant.rx_delay);
1421
1422 let cflist = read_bytes(cflist, cflist_len)?;
1423 if !cflist.is_empty() {
1424 let Ok(cflist) = <[u8; 16]>::try_from(&cflist[..]) else {
1425 set_last_error("the channel list must be exactly 16 bytes".to_owned());
1426 return Err(PamojaStatus::InvalidArgument);
1427 };
1428 built = built.with_cflist(cflist);
1429 }
1430 Ok((built, app_key))
1431}
1432
1433#[cfg(test)]
1434mod tests {
1435 use super::*;
1436
1437 use crate::{pamoja_buffer_data, pamoja_buffer_free, pamoja_buffer_len};
1438
1439 const NWK_SKEY: [u8; 16] = [0x2B; 16];
1440 const APP_SKEY: [u8; 16] = [0x99; 16];
1441
1442 /// Every flag off, which is a plain unconfirmed frame.
1443 fn quiet() -> PamojaLorawanFlags {
1444 PamojaLorawanFlags {
1445 confirmed: 0,
1446 adr: 0,
1447 ack: 0,
1448 fpending: 0,
1449 }
1450 }
1451
1452 /// Creates a session over the test keys.
1453 ///
1454 /// # Safety
1455 ///
1456 /// The returned handle must be released with [`pamoja_lorawan_session_free`].
1457 unsafe fn session() -> *mut PamojaLorawanSession {
1458 let mut session = ptr::null_mut();
1459 assert_eq!(
1460 pamoja_lorawan_session_new(
1461 0x2601_1BDA,
1462 NWK_SKEY.as_ptr(),
1463 NWK_SKEY.len(),
1464 APP_SKEY.as_ptr(),
1465 APP_SKEY.len(),
1466 &mut session
1467 ),
1468 PamojaStatus::Ok
1469 );
1470 session
1471 }
1472
1473 #[test]
1474 fn the_constants_match_the_lorawan_crate() {
1475 assert_eq!(PAMOJA_LORAWAN_FRAME_MAX, pamoja_lorawan::MAX_FRAME);
1476 assert_eq!(PAMOJA_LORAWAN_PAYLOAD_MAX, pamoja_lorawan::MAX_PAYLOAD);
1477 }
1478
1479 #[test]
1480 fn a_confirmed_uplink_round_trips_through_the_boundary() {
1481 // Safety: every pointer below is valid and every handle is released.
1482 unsafe {
1483 let session = session();
1484 assert_eq!(pamoja_lorawan_session_dev_addr(session), 0x2601_1BDA);
1485
1486 let payload = b"temp=4.8";
1487 let mut frame = ptr::null_mut();
1488 let flags = PamojaLorawanFlags {
1489 confirmed: 1,
1490 adr: 1,
1491 ..quiet()
1492 };
1493 assert_eq!(
1494 pamoja_lorawan_session_encode_uplink(
1495 session,
1496 42,
1497 1,
1498 payload.as_ptr(),
1499 payload.len(),
1500 ptr::null(),
1501 0,
1502 flags,
1503 &mut frame
1504 ),
1505 PamojaStatus::Ok
1506 );
1507 let on_air =
1508 std::slice::from_raw_parts(pamoja_buffer_data(frame), pamoja_buffer_len(frame))
1509 .to_vec();
1510 pamoja_buffer_free(frame);
1511
1512 let mut rx = ptr::null_mut();
1513 assert_eq!(
1514 pamoja_lorawan_session_decode(session, on_air.as_ptr(), on_air.len(), 42, &mut rx),
1515 PamojaStatus::Ok
1516 );
1517 assert_eq!(
1518 pamoja_lorawan_rx_direction(rx),
1519 PamojaLorawanDirection::Uplink
1520 );
1521 assert!(pamoja_lorawan_rx_confirmed(rx));
1522 assert!(pamoja_lorawan_rx_adr(rx));
1523 assert_eq!(pamoja_lorawan_rx_fcnt(rx), 42);
1524 assert_eq!(pamoja_lorawan_rx_dev_addr(rx), 0x2601_1BDA);
1525
1526 let mut fport = 0u8;
1527 assert!(pamoja_lorawan_rx_fport(rx, &mut fport));
1528 assert_eq!(fport, 1);
1529
1530 let recovered = std::slice::from_raw_parts(
1531 pamoja_lorawan_rx_payload(rx),
1532 pamoja_lorawan_rx_payload_len(rx),
1533 );
1534 assert_eq!(recovered, payload);
1535
1536 pamoja_lorawan_rx_free(rx);
1537 pamoja_lorawan_session_free(session);
1538 }
1539 }
1540
1541 #[test]
1542 fn a_downlink_carries_its_frame_options() {
1543 // Safety: every pointer below is valid and every handle is released.
1544 unsafe {
1545 let session = session();
1546 let fopts = [0x03u8, 0x50, 0x00];
1547 let mut frame = ptr::null_mut();
1548 let flags = PamojaLorawanFlags {
1549 fpending: 1,
1550 ..quiet()
1551 };
1552 assert_eq!(
1553 pamoja_lorawan_session_encode_downlink(
1554 session,
1555 7,
1556 2,
1557 ptr::null(),
1558 0,
1559 fopts.as_ptr(),
1560 fopts.len(),
1561 flags,
1562 &mut frame
1563 ),
1564 PamojaStatus::Ok
1565 );
1566 let on_air =
1567 std::slice::from_raw_parts(pamoja_buffer_data(frame), pamoja_buffer_len(frame))
1568 .to_vec();
1569 pamoja_buffer_free(frame);
1570
1571 let mut rx = ptr::null_mut();
1572 assert_eq!(
1573 pamoja_lorawan_session_decode(session, on_air.as_ptr(), on_air.len(), 7, &mut rx),
1574 PamojaStatus::Ok
1575 );
1576 assert_eq!(
1577 pamoja_lorawan_rx_direction(rx),
1578 PamojaLorawanDirection::Downlink
1579 );
1580 assert!(pamoja_lorawan_rx_fpending(rx));
1581 let recovered = std::slice::from_raw_parts(
1582 pamoja_lorawan_rx_fopts(rx),
1583 pamoja_lorawan_rx_fopts_len(rx),
1584 );
1585 assert_eq!(recovered, fopts);
1586 assert_eq!(pamoja_lorawan_rx_payload_len(rx), 0);
1587 assert!(pamoja_lorawan_rx_payload(rx).is_null());
1588
1589 pamoja_lorawan_rx_free(rx);
1590 pamoja_lorawan_session_free(session);
1591 }
1592 }
1593
1594 #[test]
1595 fn a_forged_frame_fails_its_integrity_check() {
1596 // Safety: every pointer below is valid and every handle is released.
1597 unsafe {
1598 let session = session();
1599 let payload = b"reading";
1600 let mut frame = ptr::null_mut();
1601 assert_eq!(
1602 pamoja_lorawan_session_encode_uplink(
1603 session,
1604 1,
1605 1,
1606 payload.as_ptr(),
1607 payload.len(),
1608 ptr::null(),
1609 0,
1610 quiet(),
1611 &mut frame
1612 ),
1613 PamojaStatus::Ok
1614 );
1615 let mut on_air =
1616 std::slice::from_raw_parts(pamoja_buffer_data(frame), pamoja_buffer_len(frame))
1617 .to_vec();
1618 pamoja_buffer_free(frame);
1619 let last = on_air.len() - 1;
1620 on_air[last] ^= 0xFF;
1621
1622 let mut rx = ptr::null_mut();
1623 assert_eq!(
1624 pamoja_lorawan_session_decode(session, on_air.as_ptr(), on_air.len(), 1, &mut rx),
1625 PamojaStatus::Auth
1626 );
1627 assert!(rx.is_null());
1628 pamoja_lorawan_session_free(session);
1629 }
1630 }
1631
1632 #[test]
1633 fn a_counter_that_does_not_match_is_refused() {
1634 // Safety: every pointer below is valid and every handle is released.
1635 unsafe {
1636 let session = session();
1637 let payload = b"reading";
1638 let mut frame = ptr::null_mut();
1639 assert_eq!(
1640 pamoja_lorawan_session_encode_uplink(
1641 session,
1642 1,
1643 1,
1644 payload.as_ptr(),
1645 payload.len(),
1646 ptr::null(),
1647 0,
1648 quiet(),
1649 &mut frame
1650 ),
1651 PamojaStatus::Ok
1652 );
1653 let on_air =
1654 std::slice::from_raw_parts(pamoja_buffer_data(frame), pamoja_buffer_len(frame))
1655 .to_vec();
1656 pamoja_buffer_free(frame);
1657
1658 let mut rx = ptr::null_mut();
1659 assert_eq!(
1660 pamoja_lorawan_session_decode(session, on_air.as_ptr(), on_air.len(), 2, &mut rx),
1661 PamojaStatus::Auth
1662 );
1663 assert!(rx.is_null());
1664 pamoja_lorawan_session_free(session);
1665 }
1666 }
1667
1668 #[test]
1669 fn a_key_of_the_wrong_length_is_refused() {
1670 let short = [0u8; 8];
1671 let mut session = ptr::null_mut();
1672 // Safety: the buffers and out-pointer are valid.
1673 unsafe {
1674 assert_eq!(
1675 pamoja_lorawan_session_new(
1676 1,
1677 short.as_ptr(),
1678 short.len(),
1679 APP_SKEY.as_ptr(),
1680 APP_SKEY.len(),
1681 &mut session
1682 ),
1683 PamojaStatus::InvalidArgument
1684 );
1685 assert!(session.is_null());
1686 }
1687 }
1688
1689 #[test]
1690 fn a_join_request_crosses_the_boundary_byte_for_byte() {
1691 let dev_eui = [0x01u8, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
1692 let app_eui = [0x11u8, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18];
1693 let app_key = [0x2Bu8; 16];
1694 // Safety: every pointer below is valid and every handle is released.
1695 unsafe {
1696 let mut device = ptr::null_mut();
1697 assert_eq!(
1698 pamoja_lorawan_device_new(
1699 dev_eui.as_ptr(),
1700 dev_eui.len(),
1701 app_eui.as_ptr(),
1702 app_eui.len(),
1703 app_key.as_ptr(),
1704 app_key.len(),
1705 &mut device
1706 ),
1707 PamojaStatus::Ok
1708 );
1709
1710 let mut request = ptr::null_mut();
1711 assert_eq!(
1712 pamoja_lorawan_device_join_request(device, 0x0102, &mut request),
1713 PamojaStatus::Ok
1714 );
1715 let bytes =
1716 std::slice::from_raw_parts(pamoja_buffer_data(request), pamoja_buffer_len(request));
1717 let reference = Device::new(dev_eui, app_eui, app_key).join_request(0x0102);
1718 assert_eq!(bytes, reference.as_bytes());
1719 pamoja_buffer_free(request);
1720
1721 // A join accept the network never signed must not activate a session.
1722 let forged = [0x20u8; 17];
1723 let mut accept = ptr::null_mut();
1724 assert_eq!(
1725 pamoja_lorawan_device_accept_join(
1726 device,
1727 forged.as_ptr(),
1728 forged.len(),
1729 0x0102,
1730 &mut accept
1731 ),
1732 PamojaStatus::Auth
1733 );
1734 assert!(accept.is_null());
1735
1736 pamoja_lorawan_device_free(device);
1737 }
1738 }
1739
1740 #[test]
1741 fn null_handles_are_tolerated() {
1742 // Safety: every call below is documented to accept null.
1743 unsafe {
1744 assert_eq!(pamoja_lorawan_session_dev_addr(ptr::null()), 0);
1745 assert_eq!(pamoja_lorawan_rx_fcnt(ptr::null()), 0);
1746 assert!(!pamoja_lorawan_rx_confirmed(ptr::null()));
1747 assert!(pamoja_lorawan_rx_payload(ptr::null()).is_null());
1748 assert_eq!(pamoja_lorawan_join_accept_dev_addr(ptr::null()), 0);
1749 pamoja_lorawan_session_free(ptr::null_mut());
1750 pamoja_lorawan_rx_free(ptr::null_mut());
1751 pamoja_lorawan_device_free(ptr::null_mut());
1752 pamoja_lorawan_join_accept_free(ptr::null_mut());
1753 }
1754 }
1755
1756 #[test]
1757 fn a_header_routes_a_frame_before_any_key_is_known() {
1758 // Safety: every pointer below is valid and every handle is released.
1759 unsafe {
1760 let session = session();
1761 let payload = b"temp=4.8";
1762 let mut frame = ptr::null_mut();
1763 let flags = PamojaLorawanFlags {
1764 confirmed: 1,
1765 adr: 1,
1766 ..quiet()
1767 };
1768 assert_eq!(
1769 pamoja_lorawan_session_encode_uplink(
1770 session,
1771 42,
1772 1,
1773 payload.as_ptr(),
1774 payload.len(),
1775 ptr::null(),
1776 0,
1777 flags,
1778 &mut frame
1779 ),
1780 PamojaStatus::Ok
1781 );
1782 let on_air =
1783 std::slice::from_raw_parts(pamoja_buffer_data(frame), pamoja_buffer_len(frame))
1784 .to_vec();
1785 pamoja_buffer_free(frame);
1786
1787 let mut header = std::mem::zeroed::<PamojaLorawanHeader>();
1788 assert_eq!(
1789 pamoja_lorawan_header_parse(on_air.as_ptr(), on_air.len(), &mut header),
1790 PamojaStatus::Ok
1791 );
1792 assert_eq!(header.message_type, PamojaLorawanMessageType::ConfirmedUp);
1793 assert_eq!(header.is_data, 1);
1794 assert_eq!(header.dev_addr, 0x2601_1BDA);
1795 assert_eq!(header.fcnt, 42);
1796 assert_eq!(header.has_fport, 1);
1797 assert_eq!(header.fport, 1);
1798 assert_eq!(header.confirmed, 1);
1799 assert_eq!(header.adr, 1);
1800 assert_eq!(header.payload_len, payload.len());
1801 pamoja_lorawan_session_free(session);
1802 }
1803 }
1804
1805 #[test]
1806 fn a_network_completes_an_activation_across_the_boundary() {
1807 let dev_eui = [0x00u8, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77];
1808 let app_eui = [0x88u8, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
1809 let app_key = [0xABu8; 16];
1810 let grant = PamojaLorawanGrant {
1811 app_nonce: 0x0003_0201,
1812 net_id: 0x0006_0504,
1813 dev_addr: 0x2601_1BDA,
1814 dl_settings: 0x00,
1815 rx_delay: 0x01,
1816 };
1817 // Safety: every pointer below is valid and every handle is released.
1818 unsafe {
1819 let mut device = ptr::null_mut();
1820 assert_eq!(
1821 pamoja_lorawan_device_new(
1822 dev_eui.as_ptr(),
1823 dev_eui.len(),
1824 app_eui.as_ptr(),
1825 app_eui.len(),
1826 app_key.as_ptr(),
1827 app_key.len(),
1828 &mut device
1829 ),
1830 PamojaStatus::Ok
1831 );
1832
1833 // The device asks, and this network reads the request back.
1834 let mut request_frame = ptr::null_mut();
1835 assert_eq!(
1836 pamoja_lorawan_device_join_request(device, 0x1234, &mut request_frame),
1837 PamojaStatus::Ok
1838 );
1839 let on_air = std::slice::from_raw_parts(
1840 pamoja_buffer_data(request_frame),
1841 pamoja_buffer_len(request_frame),
1842 )
1843 .to_vec();
1844 pamoja_buffer_free(request_frame);
1845
1846 let mut request = ptr::null_mut();
1847 assert_eq!(
1848 pamoja_lorawan_join_request_parse(
1849 on_air.as_ptr(),
1850 on_air.len(),
1851 app_key.as_ptr(),
1852 app_key.len(),
1853 &mut request
1854 ),
1855 PamojaStatus::Ok
1856 );
1857 let mut read_eui = [0u8; 8];
1858 assert!(pamoja_lorawan_join_request_dev_eui(
1859 request,
1860 read_eui.as_mut_ptr()
1861 ));
1862 assert_eq!(read_eui, dev_eui);
1863 assert!(pamoja_lorawan_join_request_app_eui(
1864 request,
1865 read_eui.as_mut_ptr()
1866 ));
1867 assert_eq!(read_eui, app_eui);
1868 let dev_nonce = pamoja_lorawan_join_request_dev_nonce(request);
1869 assert_eq!(dev_nonce, 0x1234);
1870 pamoja_lorawan_join_request_free(request);
1871
1872 // This network answers, and the device activates on the reply.
1873 let mut reply = ptr::null_mut();
1874 assert_eq!(
1875 pamoja_lorawan_grant_accept(
1876 grant,
1877 ptr::null(),
1878 0,
1879 app_key.as_ptr(),
1880 app_key.len(),
1881 dev_nonce,
1882 &mut reply
1883 ),
1884 PamojaStatus::Ok
1885 );
1886 let accept_bytes =
1887 std::slice::from_raw_parts(pamoja_buffer_data(reply), pamoja_buffer_len(reply))
1888 .to_vec();
1889 pamoja_buffer_free(reply);
1890
1891 let mut accept = ptr::null_mut();
1892 assert_eq!(
1893 pamoja_lorawan_device_accept_join(
1894 device,
1895 accept_bytes.as_ptr(),
1896 accept_bytes.len(),
1897 dev_nonce,
1898 &mut accept
1899 ),
1900 PamojaStatus::Ok
1901 );
1902 assert_eq!(pamoja_lorawan_join_accept_dev_addr(accept), grant.dev_addr);
1903 assert_eq!(pamoja_lorawan_join_accept_net_id(accept), grant.net_id);
1904 assert_eq!(pamoja_lorawan_join_accept_rx_delay(accept), grant.rx_delay);
1905
1906 // Both sides now hold a session, and each can read what the other secures.
1907 let mut device_session = ptr::null_mut();
1908 assert_eq!(
1909 pamoja_lorawan_join_accept_session(accept, &mut device_session),
1910 PamojaStatus::Ok
1911 );
1912 let mut network_session = ptr::null_mut();
1913 assert_eq!(
1914 pamoja_lorawan_grant_session(
1915 grant,
1916 ptr::null(),
1917 0,
1918 app_key.as_ptr(),
1919 app_key.len(),
1920 dev_nonce,
1921 &mut network_session
1922 ),
1923 PamojaStatus::Ok
1924 );
1925
1926 let payload = b"joined";
1927 let mut uplink = ptr::null_mut();
1928 assert_eq!(
1929 pamoja_lorawan_session_encode_uplink(
1930 device_session,
1931 1,
1932 1,
1933 payload.as_ptr(),
1934 payload.len(),
1935 ptr::null(),
1936 0,
1937 quiet(),
1938 &mut uplink
1939 ),
1940 PamojaStatus::Ok
1941 );
1942 let uplink_bytes =
1943 std::slice::from_raw_parts(pamoja_buffer_data(uplink), pamoja_buffer_len(uplink))
1944 .to_vec();
1945 pamoja_buffer_free(uplink);
1946
1947 let mut rx = ptr::null_mut();
1948 assert_eq!(
1949 pamoja_lorawan_session_decode(
1950 network_session,
1951 uplink_bytes.as_ptr(),
1952 uplink_bytes.len(),
1953 1,
1954 &mut rx
1955 ),
1956 PamojaStatus::Ok,
1957 "the network reads what the device it just admitted sent"
1958 );
1959 let recovered = std::slice::from_raw_parts(
1960 pamoja_lorawan_rx_payload(rx),
1961 pamoja_lorawan_rx_payload_len(rx),
1962 );
1963 assert_eq!(recovered, payload);
1964
1965 pamoja_lorawan_rx_free(rx);
1966 pamoja_lorawan_session_free(network_session);
1967 pamoja_lorawan_session_free(device_session);
1968 pamoja_lorawan_join_accept_free(accept);
1969 pamoja_lorawan_device_free(device);
1970 }
1971 }
1972
1973 #[test]
1974 fn a_request_signed_with_another_key_is_refused_at_the_boundary() {
1975 let app_key = [0xABu8; 16];
1976 let other = [0x00u8; 16];
1977 // Safety: every pointer below is valid and every handle is released.
1978 unsafe {
1979 let mut device = ptr::null_mut();
1980 assert_eq!(
1981 pamoja_lorawan_device_new(
1982 [0x11u8; 8].as_ptr(),
1983 8,
1984 [0x22u8; 8].as_ptr(),
1985 8,
1986 other.as_ptr(),
1987 other.len(),
1988 &mut device
1989 ),
1990 PamojaStatus::Ok
1991 );
1992 let mut frame = ptr::null_mut();
1993 pamoja_lorawan_device_join_request(device, 1, &mut frame);
1994 let on_air =
1995 std::slice::from_raw_parts(pamoja_buffer_data(frame), pamoja_buffer_len(frame))
1996 .to_vec();
1997 pamoja_buffer_free(frame);
1998 pamoja_lorawan_device_free(device);
1999
2000 let mut request = ptr::null_mut();
2001 assert_eq!(
2002 pamoja_lorawan_join_request_parse(
2003 on_air.as_ptr(),
2004 on_air.len(),
2005 app_key.as_ptr(),
2006 app_key.len(),
2007 &mut request
2008 ),
2009 PamojaStatus::Auth
2010 );
2011 assert!(request.is_null());
2012 }
2013 }
2014}