lorawan

package
v0.331.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 21, 2026 License: AGPL-3.0 Imports: 4 Imported by: 0

Documentation

Overview

Package lorawan decodes LoRaWAN PHYPayload frames — the MAC-layer packet format used by LoRaWAN 1.0.x and 1.1 networks. Pure offline parser; no transport, no hardware.

Wrap-vs-native judgement: LoRaWAN is a fully open specification (LoRa Alliance LoRaWAN 1.0.x / 1.1 specifications). The walker is bit-level decoding over a ~12-300 byte frame with a documented MAC-header byte and a FHDR / FPort / FRMPayload split. Wrapping a FAP for this would require an SD-card install + a firmware-fork dependency for a pure parser. Native delivers offline analysis — operators paste a captured PHYPayload (from a Flipper LoRa sub-board, a CatSniffer, or any LoRa SDR) and inspect every MAC-layer field without an antenna attached.

Pairs with the bruce_lora_scan capability (which gets a device-side LoRa scan running) and with future LoRaWAN containerbridge integrations — this Spec is the offline- analyst entry point.

What this package covers:

  • PHYPayload split: MHDR + MACPayload + 4-byte MIC
  • MHDR decode: MType (Join Request / Accept, Confirmed / Unconfirmed Data Up / Down, Rejoin, Proprietary) + Major
  • Data-frame MACPayload walk: FHDR (DevAddr little-endian, FCtrl bitfield, FCnt, FOpts MAC commands), FPort, FRMPayload (surfaced as hex; encrypted under AppSKey)
  • FCtrl bitfield decode with uplink vs downlink interpretation (uplink: ADR / ADRACKReq / ACK / ClassB / FOptsLen; downlink: ADR / RFU / ACK / FPending / FOptsLen)
  • Join Request decode: JoinEUI + DevEUI + DevNonce (all little-endian on the wire)
  • Join Accept decode: AppNonce + NetID + DevAddr + DLSettings
  • RxDelay + optional CFList (encrypted under AppKey; we surface structure when called with the decrypted form)

What this package does NOT cover (deliberately out of scope):

  • AES-CMAC MIC validation (needs NwkSKey / NwkSEncKey)
  • FRMPayload decryption (needs AppSKey)
  • Join Accept decryption (needs AppKey — the Join Accept decoder walks the cleartext-structure form; operators decrypt with their own AppKey before passing)
  • PHY-layer decode (chirp / spreading-factor / CR — those are the SDR's job, not the MAC-layer parser's)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type DataPayload

type DataPayload struct {
	FHDR FHDR `json:"fhdr"`
	// FPort: 1 byte. nil when FRMPayload is empty.
	FPort *int `json:"f_port,omitempty"`
	// FRMPayloadHex is the encrypted application payload as hex.
	// Empty when no FRMPayload is present. We do not decrypt
	// (needs AppSKey / NwkSEncKey out-of-band).
	FRMPayloadHex string `json:"frm_payload_hex,omitempty"`
}

DataPayload is the structured view of a data-frame MACPayload (Unconfirmed/Confirmed Data Up/Down).

type FCtrl

type FCtrl struct {
	Raw int  `json:"raw"`
	ADR bool `json:"adr"`
	// ADRACKReq is set on uplinks only.
	ADRACKReq bool `json:"adr_ack_req,omitempty"`
	ACK       bool `json:"ack"`
	// ClassB is set on uplinks only; FPending on downlinks only.
	ClassB   bool `json:"class_b,omitempty"`
	FPending bool `json:"f_pending,omitempty"`
	FOptsLen int  `json:"f_opts_len"`
}

FCtrl is the decoded 1-byte FCtrl field of an FHDR. Bit interpretations differ between uplink and downlink frames.

type FHDR

type FHDR struct {
	// DevAddr is the 32-bit device address. Stored on the wire
	// little-endian; we render the big-endian hex form here so
	// it matches the form network servers / chirpstack use.
	DevAddrHex string `json:"dev_addr_hex"`
	DevAddr    uint32 `json:"dev_addr"`
	FCtrl      FCtrl  `json:"f_ctrl"`
	FCnt       int    `json:"f_cnt"`
	// FOptsHex is the optional MAC-command field (up to 15
	// bytes). Surfaced as hex; we don't dissect MAC commands
	// here (that's a follow-on Spec when a caller materialises).
	FOptsHex string `json:"f_opts_hex,omitempty"`
}

FHDR is the Frame Header — DevAddr + FCtrl + FCnt + FOpts.

type JoinAccept

type JoinAccept struct {
	// AppNonce is the 3-byte network-supplied nonce.
	AppNonceHex string `json:"app_nonce_hex"`
	// NetID is the 3-byte network identifier.
	NetIDHex string `json:"net_id_hex"`
	// DevAddr is the 4-byte device address assigned to the
	// device for this session.
	DevAddrHex string `json:"dev_addr_hex"`
	DevAddr    uint32 `json:"dev_addr"`
	// DLSettings is the downlink-settings byte (RX1 DR offset +
	// RX2 data rate).
	DLSettings int `json:"dl_settings"`
	// RxDelay is the receive delay byte (0-15 seconds).
	RxDelay int `json:"rx_delay"`
	// CFListHex is the optional 16-byte channel-frequency list.
	// Empty when absent.
	CFListHex string `json:"cf_list_hex,omitempty"`
}

JoinAccept is the structured view of a (decrypted) Join Accept MACPayload. Network servers encrypt Join Accept with AppKey using AES-128-ECB before transmit; operators bring the decrypted bytes before calling this decoder.

type JoinRequest

type JoinRequest struct {
	// JoinEUI is the 8-byte EUI-64 of the Join Server (LoRaWAN
	// 1.0.x called this AppEUI). Wire form is little-endian; we
	// render the big-endian hex here so it matches the form
	// printed on device labels.
	JoinEUIHex string `json:"join_eui_hex"`
	// DevEUI is the 8-byte EUI-64 of the device. Same little-
	// endian-on-wire convention.
	DevEUIHex string `json:"dev_eui_hex"`
	// DevNonce is the 2-byte little-endian device-supplied nonce.
	DevNonce int `json:"dev_nonce"`
}

JoinRequest is the structured view of a Join Request MACPayload.

type MHDR

type MHDR struct {
	Raw    int    `json:"raw"`
	MType  int    `json:"mtype"`
	Name   string `json:"mtype_name"`
	Major  int    `json:"major"`
	Uplink bool   `json:"uplink"`
}

MHDR is the 1-byte MAC header.

type MType

type MType int

MType is the 3-bit Message Type field at the top of the MHDR.

const (
	MTypeJoinRequest         MType = 0
	MTypeJoinAccept          MType = 1
	MTypeUnconfirmedDataUp   MType = 2
	MTypeUnconfirmedDataDown MType = 3
	MTypeConfirmedDataUp     MType = 4
	MTypeConfirmedDataDown   MType = 5
	MTypeRejoinRequest       MType = 6
	MTypeProprietary         MType = 7
)
func (m MType) IsUplink() bool

IsUplink reports whether the MType is one of the uplink kinds (Join Request, Confirmed/Unconfirmed Data Up, Rejoin Request). Drives the FCtrl bitfield interpretation.

func (MType) String

func (m MType) String() string

type PHYPayload

type PHYPayload struct {
	// MHDR is the parsed MAC header.
	MHDR MHDR `json:"mhdr"`
	// MIC is the 4-byte Message Integrity Code at the frame end.
	MICHex string `json:"mic_hex"`
	// Data is set for the data-frame MTypes (2-5).
	Data *DataPayload `json:"data,omitempty"`
	// JoinRequest is set when MType == 0.
	JoinRequest *JoinRequest `json:"join_request,omitempty"`
	// JoinAccept is set when MType == 1 (assumes operator has
	// decrypted the body).
	JoinAccept *JoinAccept `json:"join_accept,omitempty"`
	// RejoinRequestHex is set when MType == 6 (Rejoin Request).
	// We surface the raw bytes — Rejoin Type byte then variable
	// fields per LoRaWAN 1.1 §6.2.4.
	RejoinRequestHex string `json:"rejoin_request_hex,omitempty"`
	// ProprietaryHex is set when MType == 7.
	ProprietaryHex string `json:"proprietary_hex,omitempty"`
	// PayloadHex is the raw PHYPayload for callers that want to
	// re-render or audit.
	PayloadHex string `json:"payload_hex"`
}

PHYPayload is the top-level decoded frame.

func Decode

func Decode(hexBlob string) (PHYPayload, error)

Decode parses a hex-encoded LoRaWAN PHYPayload frame. Tolerates ':' / '-' / '_' / whitespace separators.

func DecodeBytes

func DecodeBytes(b []byte) (PHYPayload, error)

DecodeBytes is the byte-slice variant of Decode.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL