pamoja_lorawan/lib.rs
1#![cfg_attr(not(test), no_std)]
2
3//! LoRaWAN 1.0.x MAC framing for the pamoja SDK.
4//!
5//! LoRaWAN is how a low-power node reaches a network kilometres away over a license-free
6//! radio, which is why it is the SDK's first-class answer for rural and remote reach. The
7//! [`pamoja-lora`](https://docs.rs/pamoja-lora) crate gives the link budget, the exact
8//! time a transmission spends on air; this crate gives the bytes that go in it: the
9//! secured LoRaWAN frame.
10//!
11//! A LoRaWAN frame is not just a payload with an address. The standard wraps every frame
12//! in two cryptographic guarantees, because a long-range public-band link is wide open: a
13//! message integrity code keyed to the network proves the frame is authentic and intact,
14//! and the payload is encrypted to the application so only its owner can read it. This
15//! crate builds and verifies exactly that, with no radio and no allocation:
16//!
17//! - [`Session`] - an activated device's address and session keys. It [encodes](Session::encode_uplink)
18//! an uplink or downlink data frame, encrypting the payload and appending the MIC, and
19//! [decodes](Session::decode) one received, verifying the MIC before decrypting.
20//! - [`Uplink`] and [`Downlink`] - the data frame to send, built up from the fields a
21//! sender sets (confirmed, adaptive data rate, acknowledgement, frame options).
22//! - [`RxData`] - a decoded frame: its header fields and its recovered payload.
23//! - [`Device`] - the root credentials for over-the-air activation: it builds the
24//! join-request a device broadcasts and turns the network's join-accept into a ready
25//! [`Session`], deriving the session keys the spec prescribes.
26//! - [`JoinRequest`] and [`JoinGrant`] - the other half of that exchange, so a deployment
27//! can run its own network instead of joining someone else's: verify the request a
28//! device sent, then grant it an address and sign the reply. Both sides derive the same
29//! session keys from the same nonces, with no key ever on the air.
30//! - [`FrameHeader`] - what a frame says about itself before any key is involved: its
31//! message type, the device address, and the counter. A receiver holding many sessions
32//! reads this first to find the one a frame belongs to, then decodes.
33//!
34//! The cryptography is the LoRaWAN construction over AES-128: an AES-CMAC MIC and an
35//! AES keystream for the payload, with the device address and frame counter folded into
36//! both so a frame cannot be lifted out of its place in the stream. Driving the radio
37//! arrives with the hardware-I/O layer; this is the secured-packet half ahead of it.
38//!
39//! # Examples
40//!
41//! ```
42//! use pamoja_lorawan::{Session, Uplink};
43//!
44//! // A node activated with a device address and its two session keys.
45//! let session = Session::new(0x2601_1BDA, [0x2B; 16], [0x99; 16]);
46//!
47//! // Encode a confirmed uplink reading; the payload is encrypted and the MIC appended.
48//! let frame = session
49//! .encode_uplink(&Uplink::new(42, 1, b"temp=4.8").confirmed())
50//! .unwrap();
51//!
52//! // The network, holding the same session, verifies and decrypts it.
53//! let rx = session.decode(frame.as_bytes(), 42).unwrap();
54//! assert!(rx.confirmed());
55//! assert_eq!(rx.payload(), b"temp=4.8");
56//! ```
57
58mod crypto;
59mod error;
60mod frame;
61mod header;
62mod join;
63mod network;
64mod session;
65
66pub use error::LorawanError;
67pub use frame::{Direction, PhyPayload, MAX_FRAME, MAX_PAYLOAD};
68pub use header::{FrameHeader, MessageType};
69pub use join::{Device, JoinAccept};
70pub use network::{JoinGrant, JoinRequest};
71pub use session::{Downlink, RxData, Session, Uplink};