Skip to main content

LoraPacket

Struct LoraPacket 

Source
pub struct LoraPacket {
    pub phy_payload: Vec<u8>,
    pub mhdr: Mhdr,
    pub mic: [u8; 4],
    pub payload: Payload,
}
Expand description

A LoRaWAN PHYPayload, parsed into structured fields.

LoraPacket is always exactly one of the five message types described by Payload. The variant carries every field that is meaningful for that message type; fields that do not apply are not representable.

Construct from wire bytes with from_wire, or compose from fields with builder.

§Examples

Parse and dispatch on the message variant:

use lora_packet::{LoraPacket, Payload};

let bytes = hex::decode("40f17dbe4900020001954378762b11ff0d")?;
let packet = LoraPacket::from_wire(&bytes)?;

match &packet.payload {
  Payload::Data(d) => {
    assert_eq!(d.dev_addr.as_bytes(), &[0x49, 0xbe, 0x7d, 0xf1]);
    assert_eq!(d.f_cnt(), 2);
  }
  Payload::JoinRequest(_) => unreachable!(),
  Payload::JoinAccept(_) => unreachable!(),
  Payload::RejoinRequest(_) => unreachable!(),
  Payload::Proprietary(_) => unreachable!(),
}

Fields§

§phy_payload: Vec<u8>

Full wire bytes (MHDR + MACPayload + MIC).

Kept around so that MIC re-computation does not need to re-serialise. to_wire rebuilds these bytes from the typed fields.

§mhdr: Mhdr

MAC header byte.

§mic: [u8; 4]

4-byte message integrity code (the last 4 bytes of phy_payload).

§payload: Payload

Type-specific payload fields. See Payload.

Implementations§

Source§

impl LoraPacket

Source

pub fn m_type(&self) -> MType

Message type from the MHDR.

§Panics

Never panics on a LoraPacket produced by from_wire or the builder; both reject invalid MType bytes up front. When a LoraPacket is struct-constructed directly with an invalid Mhdr byte, this method will panic. Prefer construction via from_wire or builder().

Source

pub const fn is_data(&self) -> bool

true for any data frame (confirmed or unconfirmed, uplink or downlink).

Source

pub fn is_confirmed(&self) -> bool

true for ConfirmedDataUp or ConfirmedDataDown.

Source

pub const fn is_join_request(&self) -> bool

true for Join Request.

Source

pub const fn is_join_accept(&self) -> bool

true for Join Accept.

Source

pub const fn is_rejoin_request(&self) -> bool

true for Rejoin Request.

Source

pub const fn as_data(&self) -> Option<&Data>

Borrow as Data if this is a data frame, else None.

Source

pub const fn as_data_mut(&mut self) -> Option<&mut Data>

Mutably borrow as Data if this is a data frame.

Source

pub const fn as_join_request(&self) -> Option<&JoinRequest>

Borrow as JoinRequest if applicable, else None.

Source

pub const fn as_join_accept(&self) -> Option<&JoinAccept>

Borrow as JoinAccept if applicable, else None.

Source

pub const fn as_rejoin_request(&self) -> Option<&RejoinRequest>

Borrow as RejoinRequest if applicable, else None.

Source

pub const fn direction(&self) -> Option<Direction>

Direction of this packet on the LoRaWAN network.

  • JoinRequest is always uplink (device to network server).
  • JoinAccept is always downlink (network server to device).
  • Data returns the direction encoded in its MType.
  • RejoinRequest is always uplink.
  • Payload::Proprietary has no protocol-defined direction; this method returns None in that case.
§Examples
use lora_packet::{LoraPacket, Direction};

let bytes = hex::decode("40f17dbe4900020001954378762b11ff0d").unwrap();
let packet = LoraPacket::from_wire(&bytes).unwrap();
assert_eq!(packet.direction(), Some(Direction::Uplink));
Source

pub fn verify_mic_v1_0(&self, keys: &V1_0MicKeys<'_>) -> Result<bool>

Verify the MIC using the LoRaWAN 1.0 key set.

Compares against self.mic in constant time (via subtle::ConstantTimeEq). Returns Ok(true) on match, Ok(false) on mismatch, and an error only if a required key is missing from keys.

§Errors

crate::Error::MissingKey if a required key for the message type is not in keys (e.g. nwk_s_key for a Data frame).

§Examples
use lora_packet::{LoraPacket, NwkSKey, V1_0MicKeys};

let bytes = hex::decode("40f17dbe4900020001954378762b11ff0d")?;
let packet = LoraPacket::from_wire(&bytes)?;
let nwk_s_key = NwkSKey::from_slice(&hex::decode("44024241ed4ce9a68c6a8bc055233fd3")?)?;
let keys = V1_0MicKeys { nwk_s_key: Some(&nwk_s_key), ..Default::default() };
assert!(packet.verify_mic_v1_0(&keys)?);
Source

pub fn verify_mic_v1_1(&self, keys: &V1_1MicKeys<'_>) -> Result<bool>

Verify the MIC using the LoRaWAN 1.1 key set.

1.1 uplinks use a dual-MIC construction with both FNwkSIntKey and SNwkSIntKey; downlinks use SNwkSIntKey only. Join Accept frames also need join_eui, dev_nonce, and join_req_type in the keyset.

§Errors

crate::Error::MissingKey if a required key for the message type is not in keys.

Source

pub fn calculate_mic_v1_0(&self, keys: &V1_0MicKeys<'_>) -> Result<[u8; 4]>

Calculate the MIC under LoRaWAN 1.0 without overwriting self.mic.

Useful when you want to compare against an expected value without running Self::verify_mic_v1_0, or when you need to record both the computed MIC and the received MIC for debugging.

§Errors
Source

pub fn calculate_mic_v1_1(&self, keys: &V1_1MicKeys<'_>) -> Result<[u8; 4]>

Calculate the MIC under LoRaWAN 1.1 without overwriting self.mic.

Dispatches by MType and direction:

  • Data uplink: dual-MIC with FNwkSIntKey + SNwkSIntKey.
  • Data downlink: single MIC with SNwkSIntKey.
  • Join Request: NwkKey.
  • Join Accept: JSIntKey + JoinEUI + DevNonce + JoinReqType.
  • Rejoin Type 1: JSIntKey.
  • Rejoin Type 0/2: SNwkSIntKey.
§Errors

crate::Error::MissingKey if a required key (or context field) for the message type is not in keys.

Source

pub fn recalculate_mic_v1_0(&mut self, keys: &V1_0MicKeys<'_>) -> Result<()>

Recompute and overwrite the MIC under LoRaWAN 1.0.

Also rewrites phy_payload so its trailing 4 bytes match the new MIC. Use after mutating fields on a parsed packet, or after building an unsigned packet you want to sign in place.

§Errors

crate::Error::MissingKey if a required key for the message type is not in keys.

Source

pub fn recalculate_mic_v1_1(&mut self, keys: &V1_1MicKeys<'_>) -> Result<()>

Recompute and overwrite the MIC under LoRaWAN 1.1.

Also rewrites phy_payload so its trailing 4 bytes match the new MIC.

§Errors

crate::Error::MissingKey if a required key for the message type is not in keys.

Source§

impl LoraPacket

Source

pub fn from_wire(bytes: &[u8]) -> Result<Self>

Parse a complete PHYPayload from wire bytes.

This is the primary entry point for inbound traffic. The returned LoraPacket keeps the full wire bytes in LoraPacket::phy_payload so that MIC re-computation is cheap.

Join Accept frames are encrypted on the wire and cannot be parsed directly. Use JoinAccept::decrypt_from_wire first, then either pass the result to JoinAccept::from_plaintext or work with the returned plaintext bytes directly.

§Errors
§Examples
use lora_packet::{LoraPacket, MType};

let bytes = hex::decode("40f17dbe4900020001954378762b11ff0d")?;
let packet = LoraPacket::from_wire(&bytes)?;

assert!(packet.is_data());
assert_eq!(packet.m_type(), MType::UnconfirmedDataUp);
assert_eq!(packet.mic, [0x2b, 0x11, 0xff, 0x0d]);
Source

pub fn to_wire(&self) -> Vec<u8>

Serialise back to wire bytes.

Re-encodes the typed fields into the little-endian wire layout and appends self.mic unchanged. Call recalculate_mic_v1_0 / recalculate_mic_v1_1 first if you have mutated fields and need a fresh MIC.

For round-trip parsing, the output of from_wire(bytes).to_wire() equals bytes (subject to MIC bytes being preserved).

Source§

impl LoraPacket

Source

pub fn from_hex(s: &str) -> Result<Self>

Parse from a hex-encoded wire frame.

§Errors

crate::Error::Hex for invalid hex; otherwise any error from LoraPacket::from_wire.

Source

pub fn from_base64(s: &str) -> Result<Self>

Parse from a standard base64-encoded wire frame.

§Errors

crate::Error::Base64 for invalid base64; otherwise any error from LoraPacket::from_wire.

Source§

impl LoraPacket

Source

pub fn builder() -> LoraPacketBuilder

Begin building a packet field by field.

See LoraPacketBuilder for the full surface and finalisation methods.

§Examples

Build a Data uplink, encrypt, and sign in one expression:

use lora_packet::{LoraPacket, Direction, DevAddr, AppSKey, NwkSKey};

let app_s_key = AppSKey::new([0u8; 16]);
let nwk_s_key = NwkSKey::new([0u8; 16]);
let packet = LoraPacket::builder()
  .data(Direction::Uplink, false)
  .dev_addr(DevAddr::new([0x49, 0xbe, 0x7d, 0xf1]))
  .f_cnt(2)
  .f_port(1)
  .payload(b"hi")
  .sign_and_encrypt(&app_s_key, &nwk_s_key)?;
assert!(packet.is_data());

Build and sign a Join Request:

use lora_packet::{LoraPacket, AppKey, AppEui, DevEui, DevNonce};

let app_key = AppKey::new([0u8; 16]);
let packet = LoraPacket::builder()
  .join_request()
  .join_eui(AppEui::new([0u8; 8]))
  .dev_eui(DevEui::new([0u8; 8]))
  .dev_nonce(DevNonce::new([0u8; 2]))
  .sign_join_request(&app_key)?;
assert!(packet.is_join_request());

Trait Implementations§

Source§

impl Clone for LoraPacket

Source§

fn clone(&self) -> LoraPacket

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for LoraPacket

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for LoraPacket

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for LoraPacket

Source§

fn eq(&self, other: &LoraPacket) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Serialize for LoraPacket

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl Eq for LoraPacket

Source§

impl StructuralPartialEq for LoraPacket

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,