[LoRaWAN] Implement full session persistence & more v1.1 specification (#835)

* Implement session persistence & more 1.1 protocol

* [LoRaW] Improve session persistence, check frame counters & Nonces, multiple MAC commands

* [LoRaWAN] fix popping MAC command from queue

I just realized that the method popMacCommand did not correctly remove items from the queue - this should solve the problem

* [LoRaWAN] implement improvements from #835

* [LoRaWAN] String --> uint8_t[]

* [LoRaWAN] Fix typo
This commit is contained in:
StevenCellist 2023-10-23 17:50:16 +02:00 committed by GitHub
parent 29c891e017
commit 556f37f608
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 404 additions and 82 deletions

View file

@ -291,8 +291,9 @@ setModem KEYWORD2
# LoRaWAN
wipe KEYWORD2
restoreOTAA KEYWORD2
beginOTAA KEYWORD2
beginAPB KEYWORD2
beginABP KEYWORD2
uplink KEYWORD2
downlink KEYWORD2
configureChannel KEYWORD2
@ -401,5 +402,10 @@ RADIOLIB_ERR_INVALID_PORT LITERAL1
RADIOLIB_ERR_NO_RX_WINDOW LITERAL1
RADIOLIB_ERR_INVALID_CHANNEL LITERAL1
RADIOLIB_ERR_INVALID_CID LITERAL1
RADIOLIB_ERR_UPLINK_UNAVAILABLE LITERAL1
RADIOLIB_ERR_COMMAND_QUEUE_FULL LITERAL1
RADIOLIB_ERR_COMMAND_QUEUE_EMPTY LITERAL1
RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND LITERAL1
RADIOLIB_ERR_JOIN_NONCE_INVALID LITERAL1
RADIOLIB_ERR_N_FCNT_DOWN_INVALID LITERAL1
RADIOLIB_ERR_A_FCNT_DOWN_INVALID LITERAL1

View file

@ -442,7 +442,7 @@
// the amount of space allocated to the persistent storage
#if !defined(RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE)
#define RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE (0x60)
#define RADIOLIB_HAL_PERSISTENT_STORAGE_SIZE (0xD0)
#endif
// This only compiles on STM32 boards with SUBGHZ module, but also

View file

@ -7,25 +7,47 @@
#include "BuildOpt.h"
// list of persistent parameters
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID (0)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID (1)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID (2)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID (3)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID (4)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID (5)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID (6)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID (7)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID (0) // this is NOT the LoRaWAN version, but version of this table
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID (1)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID (2)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID (3)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID (4)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID (5)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID (6)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID (7)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID (8)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID (9)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_DL_SETTINGS_ID (10)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_CF_LIST_ID (11)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX_DELAY_ID (12)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID (13)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID (14)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID (15)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_ID (16)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_ID (17)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID (18)
static const uint32_t RadioLibPersistentParamTable[] = {
0x00, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID
0x04, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID
0x08, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID
0x0C, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID
0x00, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION
0x08, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID
0x0C, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID
0x10, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID
0x20, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID
0x30, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID
0x40, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID
0x50, // end
0x50, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID
0x54, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID
0x58, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID
0x5C, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_DL_SETTINGS_ID
0x60, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_CF_LIST
0x70, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX_DELAY_ID
0x74, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID
0x78, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_AFCNT_DOWN_ID
0x7C, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_NFCNT_DOWN_ID
0x80, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_CONF_FCNT_ID
0x84, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_ADR_ID
0x88, // RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID
0xD0, // end
};
/*!

View file

@ -533,6 +533,26 @@
*/
#define RADIOLIB_ERR_COMMAND_QUEUE_EMPTY (-1110)
/*!
\brief Unable to delete MAC command because it was not found in the queue.
*/
#define RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND (-1111)
/*!
\brief Unable to join network because JoinNonce is not higher than saved value.
*/
#define RADIOLIB_ERR_JOIN_NONCE_INVALID (-1112)
/*!
\brief Received downlink Network frame counter is invalid (lower than last heard value).
*/
#define RADIOLIB_ERR_N_FCNT_DOWN_INVALID (-1113)
/*!
\brief Received downlink Application frame counter is invalid (lower than last heard value).
*/
#define RADIOLIB_ERR_A_FCNT_DOWN_INVALID (-1114)
/*!
\}
*/

View file

@ -44,7 +44,7 @@ void LoRaWANNode::wipe() {
mod->hal->wipePersistentStorage();
}
int16_t LoRaWANNode::begin() {
int16_t LoRaWANNode::restoreOTAA() {
int16_t state = this->setPhyProperties();
RADIOLIB_ASSERT(state);
@ -55,12 +55,99 @@ int16_t LoRaWANNode::begin() {
return(RADIOLIB_ERR_NETWORK_NOT_JOINED);
}
// in case of future revisions of NVM, use a version parameter to allow transitioning from one version to another while keeping session alive
uint16_t nvm_table_version = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID);
// if (RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION > nvm_table_version) {
// // set default values for variables that are new or something
// }
(void)nvm_table_version;
// pull all needed information from persistent storage
this->devAddr = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID);
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE);
RADIOLIB_DEBUG_PRINTLN("appSKey:");
RADIOLIB_DEBUG_HEXDUMP(this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
uint32_t dlSettings = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DL_SETTINGS_ID);
this->rev = (dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) >> 7;
uint8_t rx1DrOffset = (dlSettings & 0x70) >> 4;
uint8_t rx2DataRate = dlSettings & 0x0F;
RADIOLIB_DEBUG_PRINTLN("LoRaWAN revision: %d", this->rev);
// TODO process the RX2 data rate
(void)rx2DataRate;
// TODO process the data rate offset
(void)rx1DrOffset;
// parse Rx1 delay (and subsequently Rx2)
this->rxDelays[0] = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX_DELAY_ID);
if(this->rxDelays[0] == 0) {
this->rxDelays[0] = RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS;
}
this->rxDelays[1] = this->rxDelays[0] + 1000;
// process CFlist if any bit is non-zero
uint8_t cfList[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN] = { 0 };
uint8_t allZero[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN] = { 0 };
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CF_LIST_ID), cfList, RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN);
RADIOLIB_DEBUG_PRINTLN("cfList:");
RADIOLIB_DEBUG_HEXDUMP(cfList, RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN);
if(memcmp(cfList, allZero, RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN)) {
if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) {
// list of frequencies
for(uint8_t i = 0; i < 5; i++) {
uint32_t freq = LoRaWANNode::ntoh<uint32_t>(&cfList[3*i], 3);
availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i] = (float)freq/10000.0;
availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK][i] = availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i];
RADIOLIB_DEBUG_PRINTLN("Channel UL/DL %d frequency = %f MHz", i, availableChannelsFreq[RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK][i]);
}
} else {
// frequency mask, we need to find out which frequencies are actually being used
uint8_t channelId = 0;
uint8_t chSpan = 0;
uint8_t chNum = 0;
for(uint8_t i = 0; i < 5; i++) {
uint16_t mask = LoRaWANNode::ntoh<uint16_t>(&cfList[2*i]);
RADIOLIB_DEBUG_PRINTLN("mask[%d] = 0x%04x", i, mask);
for(uint8_t j = 0; j < 16; j++) {
if(chNum >= this->band->defaultChannels[chSpan].numChannels) {
chNum -= this->band->defaultChannels[chSpan].numChannels;
chSpan++;
if(chSpan >= this->band->numChannelSpans) {
RADIOLIB_DEBUG_PRINTLN("channel bitmask overrun!");
return(RADIOLIB_ERR_UNKNOWN);
}
}
if(mask & (1UL << j)) {
RADIOLIB_DEBUG_PRINTLN("chNum = %d, chSpan = %d", chNum, chSpan);
uint8_t dir = this->band->defaultChannels[chSpan].direction;
float freq = this->band->defaultChannels[chSpan].freqStart + chNum*this->band->defaultChannels[chSpan].freqStep;
availableChannelsFreq[dir][channelId] = freq;
RADIOLIB_DEBUG_PRINTLN("Channel %cL %d frequency = %f MHz", dir ? 'U': 'D', channelId, availableChannelsFreq[dir][channelId]);
channelId++;
}
chNum++;
}
}
}
}
uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)] = { 0 };
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t));
memcpy(&queueBuff, &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t));
state = this->setupChannels();
RADIOLIB_ASSERT(state);
return(RADIOLIB_ERR_NONE);
}
@ -69,7 +156,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
Module* mod = this->phyLayer->getMod();
if(!force && (mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) {
// the device has joined already, we can just pull the data from persistent storage
return(this->begin());
return(this->restoreOTAA());
}
// set the physical layer configuration
@ -175,9 +262,23 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
RADIOLIB_DEBUG_PRINTLN("joinAcceptMsg:");
RADIOLIB_DEBUG_HEXDUMP(joinAcceptMsg, lenRx);
// get current JoinNonce from downlink and previous JoinNonce from NVM
uint32_t joinNonce = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3);
uint32_t joinNoncePrev = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID);
RADIOLIB_DEBUG_PRINTLN("JoinNoncePrev: %d, JoinNonce: %d", joinNoncePrev, joinNonce);
// JoinNonce received must be greater than the last JoinNonce heard, else error
if(joinNonce <= joinNoncePrev) {
return(RADIOLIB_ERR_JOIN_NONCE_INVALID);
}
// check LoRaWAN revision (the MIC verification depends on this)
uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS];
if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) {
this->rev = (dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) >> 7;
RADIOLIB_DEBUG_PRINTLN("LoRaWAN revision: 1.%d", this->rev);
// verify MIC
if(this->rev == 1) {
// 1.1 version, first we need to derive the join accept integrity key
uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_INT_KEY;
@ -203,11 +304,20 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
}
}
uint8_t rx1DrOffset = (dlSettings & 0x70) >> 4;
uint8_t rx2DataRate = dlSettings & 0x0F;
// TODO process the RX2 data rate
(void)rx2DataRate;
// parse the contents
uint32_t joinNonce = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3);
// TODO process the data rate offset
(void)rx1DrOffset;
// parse other contents
uint32_t homeNetId = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3);
this->devAddr = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS]);
// parse Rx1 delay (and subsequently Rx2)
this->rxDelays[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]*1000;
if(this->rxDelays[0] == 0) {
this->rxDelays[0] = RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS;
@ -215,7 +325,9 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
this->rxDelays[1] = this->rxDelays[0] + 1000;
// process CFlist if present
uint8_t cfList[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN] = { 0 };
if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) {
memcpy(&cfList[0], &joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS], RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN);
if(this->band->cfListType == RADIOLIB_LORAWAN_CFLIST_TYPE_FREQUENCIES) {
// list of frequencies
for(uint8_t i = 0; i < 5; i++) {
@ -262,15 +374,14 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
}
}
}
}
// prepare buffer for key derivation
uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], joinNonce, 3);
// check protocol version (1.0 vs 1.1)
if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) {
if(this->rev == 1) {
// 1.1 version, derive the keys
LoRaWANNode::hton<uint64_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS], joinEUI);
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], devNonce);
@ -292,7 +403,6 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey);
// enqueue the RekeyInd MAC command to be sent in the next uplink
this->rev = 1;
LoRaWANMacCommand_t cmd = {
.cid = RADIOLIB_LORAWAN_MAC_CMD_REKEY,
.len = sizeof(uint8_t),
@ -304,7 +414,6 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
} else {
// 1.0 version, just derive the keys
this->rev = 0;
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3);
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonce);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
@ -329,13 +438,27 @@ int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKe
mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_NWK_SENC_KEY_ID), this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE);
// save uplink parameters
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_JOIN_NONCE_ID, joinNonce);
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_HOME_NET_ID, homeNetId);
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_RX_DELAY_ID, this->rxDelays[0]);
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DL_SETTINGS_ID, (uint32_t)dlSettings);
// save cfList (all 0 if none is present)
mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_CF_LIST_ID), cfList, RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN);
// all complete, reset device counters and set the magic number
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, 0);
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID, 0);
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID, 0);
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID, RADIOLIB_LORAWAN_MAGIC);
// everything written to NVM, write current version to NVM
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID, RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION);
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::beginAPB(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey) {
int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey) {
this->devAddr = addr;
memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE);
memcpy(this->nwkSEncKey, nwkSKey, RADIOLIB_AES128_KEY_SIZE);
@ -355,7 +478,13 @@ int16_t LoRaWANNode::beginAPB(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey,
// setup uplink/downlink frequencies and datarates
state = this->setupChannels();
return(state);
RADIOLIB_ASSERT(state);
// everything written to NVM, write current version to NVM
Module* mod = this->phyLayer->getMod();
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION_ID, RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION);
return(RADIOLIB_ERR_NONE);
}
#if defined(RADIOLIB_BUILD_ARDUINO)
@ -373,12 +502,24 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
if(port > 0xDF) {
return(RADIOLIB_ERR_INVALID_PORT);
}
// port 0 is only allowed for MAC-only payloads
if(port == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
if (!isMACPayload) {
return(RADIOLIB_ERR_INVALID_PORT);
}
// if this is MAC only payload, continue and reset for next uplink
isMACPayload = false;
}
Module* mod = this->phyLayer->getMod();
// check if there are some MAC commands to piggyback
size_t foptsLen = 0;
if(this->commandsUp.numCommands > 0) {
// check if there are some MAC commands to piggyback (only when piggybacking onto a application-frame)
uint8_t foptsLen = 0;
size_t foptsBufSize = 0;
if(this->commandsUp.numCommands > 0 && port != RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
// there are, assume the maximum possible FOpts len for buffer allocation
foptsLen = 15;
foptsLen = this->commandsUp.len;
foptsBufSize = 15;
}
// check maximum payload len as defined in phy
@ -392,7 +533,6 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
RADIOLIB_ASSERT(state);
// check if sufficient time has elapsed since the last uplink
Module* mod = this->phyLayer->getMod();
if(mod->hal->millis() - this->rxDelayStart < rxDelays[1]) {
// not enough time elapsed since the last uplink, we may still be in an RX window
return(RADIOLIB_ERR_UPLINK_UNAVAILABLE);
@ -400,7 +540,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
// build the uplink message
// the first 16 bytes are reserved for MIC calculation blocks
size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen);
size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsBufSize);
#if defined(RADIOLIB_STATIC_ONLY)
uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
#else
@ -420,22 +560,37 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt);
LoRaWANNode::hton<uint16_t>(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)fcnt);
// check if we have some MAC command to append
// TODO implement appending multiple MAC commands
LoRaWANMacCommand_t cmd = { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, };
if(popMacCommand(&cmd, &this->commandsUp) == RADIOLIB_ERR_NONE) {
// we do, add it to fopts
uint8_t foptsBuff[RADIOLIB_AES128_BLOCK_SIZE];
foptsBuff[0] = cmd.cid;
for(size_t i = 1; i < cmd.len; i++) {
foptsBuff[i] = cmd.payload[i];
// check if we have some MAC commands to append
if(foptsLen > 0) {
uint8_t foptsNum = this->commandsUp.numCommands;
uint8_t foptsBuff[foptsBufSize];
size_t idx = 0;
for (size_t i = 0; i < foptsNum; i++) {
LoRaWANMacCommand_t cmd = { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, };
popMacCommand(&cmd, &this->commandsUp, i);
if (cmd.cid == 0) {
break;
}
foptsBuff[idx] = cmd.cid;
for(size_t i = 1; i < cmd.len; i++) {
foptsBuff[idx + i] = cmd.payload[i];
}
idx += cmd.len + 1;
}
foptsLen = 1 + cmd.len;
RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", foptsNum);
RADIOLIB_DEBUG_HEXDUMP(foptsBuff, foptsBufSize);
uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen);
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= foptsLen;
// encrypt it
processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x01, true);
// write the current MAC command queue to nvm for next uplink
uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)];
memcpy(&queueBuff, &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t));
mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t));
}
// set the port
@ -448,6 +603,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
}
// encrypt the frame payload
// TODO check ctrId --> erratum says it should be 0x01?
processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true);
// create blocks for MIC calculation
@ -685,15 +841,6 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
if(state == RADIOLIB_ERR_LORA_HEADER_DAMAGED) {
state = RADIOLIB_ERR_NONE;
}
// get the frame counter and set it to the MIC calculation block
// TODO this will not handle overflow into 32-bits!
// TODO cache the ADR bit?
uint16_t fcnt = LoRaWANNode::ntoh<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]);
LoRaWANNode::hton<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt);
RADIOLIB_DEBUG_PRINTLN("downlinkMsg:");
RADIOLIB_DEBUG_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen);
if(state != RADIOLIB_ERR_NONE) {
#if !defined(RADIOLIB_STATIC_ONLY)
@ -702,6 +849,55 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
return(state);
}
// get the frame counter and set it to the MIC calculation block
// TODO cache the ADR bit?
uint16_t fcnt16 = LoRaWANNode::ntoh<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]);
LoRaWANNode::hton<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt16);
uint32_t fcnt32 = fcnt16; // calculate possible rollover once decided if this is network downlink or application downlink
RADIOLIB_DEBUG_PRINTLN("downlinkMsg:");
RADIOLIB_DEBUG_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen);
// calculate length of FOpts and payload
uint8_t foptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK;
int payLen = downlinkMsgLen - 8 - foptsLen - sizeof(uint32_t);
bool isAppDownlink = true;
if (payLen <= 0 && this->rev == 1) { // no payload => MAC commands only => Network frame (LoRaWAN v1.1 only)
isAppDownlink = false;
}
// check the FcntDown value (Network or Application)
uint32_t fcntDownPrev = 0;
if (isAppDownlink) {
fcntDownPrev = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID);
} else {
fcntDownPrev = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID);
}
// assume a 16-bit to 32-bit rollover when difference in LSB is smaller than MAX_FCNT_GAP
// if that isn't the case and the received fcnt is smaller or equal to the last heard fcnt, then error
if ((fcnt16 <= fcntDownPrev) && ((0xFFFF - (uint16_t)fcntDownPrev + fcnt16) > RADIOLIB_LORAWAN_MAX_FCNT_GAP)) {
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] downlinkMsg;
#endif
if (isAppDownlink) {
return(RADIOLIB_ERR_A_FCNT_DOWN_INVALID);
} else {
return(RADIOLIB_ERR_N_FCNT_DOWN_INVALID);
}
} else if (fcnt16 <= fcntDownPrev) {
uint16_t msb = (fcntDownPrev >> 16) + 1; // assume a rollover
fcnt32 |= (msb << 16); // add back the MSB part
}
// save current fcnt to NVM
if (isAppDownlink) {
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_A_FCNT_DOWN_ID, fcnt32);
} else {
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_N_FCNT_DOWN_ID, fcnt32);
}
// check the MIC
if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) {
#if !defined(RADIOLIB_STATIC_ONLY)
@ -720,15 +916,14 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// check fopts len
uint8_t foptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK;
// process FOpts (if there are any)
if(foptsLen > 0) {
// there are some Fopts, decrypt them
uint8_t fopts[RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK];
// according to the specification, the last two arguments should be 0x00 and false,
// but that will fail even for LoRaWAN 1.1.0 server
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)foptsLen, this->nwkSEncKey, fopts, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x01, true);
// TODO it COULD be the case that the assumed rollover is incorrect, if possible figure out a way to catch this and retry with just fcnt16
uint8_t ctrId = 0x01 + isAppDownlink; // see LoRaWAN v1.1 errata
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)foptsLen, this->nwkSEncKey, fopts, fcnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, ctrId, true);
RADIOLIB_DEBUG_PRINTLN("fopts:");
RADIOLIB_DEBUG_HEXDUMP(fopts, foptsLen);
@ -753,23 +948,59 @@ int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
remLen -= processedLen;
foptsPtr += processedLen;
}
// if FOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink
if(this->commandsUp.len > 15) {
uint8_t foptsNum = this->commandsUp.numCommands;
size_t foptsBufSize = this->commandsUp.len;
uint8_t foptsBuff[foptsBufSize];
size_t idx = 0;
for(size_t i = 0; i < foptsNum; i++) {
LoRaWANMacCommand_t cmd = { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, };
popMacCommand(&cmd, &this->commandsUp, i);
if(cmd.cid == 0) {
break;
}
foptsBuff[idx] = cmd.cid;
for(size_t i = 1; i < cmd.len; i++) {
foptsBuff[idx + i] = cmd.payload[i];
}
idx += cmd.len + 1;
}
RADIOLIB_DEBUG_PRINTLN("Uplink MAC payload (%d commands):", foptsNum);
RADIOLIB_DEBUG_HEXDUMP(foptsBuff, foptsBufSize);
isMACPayload = true;
this->uplink(foptsBuff, foptsBufSize, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND);
uint8_t strDown[this->band->payloadLenMax[this->dataRate[RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK]]];
size_t lenDown = 0;
state = this->downlink(strDown, &lenDown);
RADIOLIB_ASSERT(state);
}
// write the MAC command queue to nvm for next uplink
uint8_t queueBuff[sizeof(LoRaWANMacCommandQueue_t)];
memcpy(&queueBuff, &this->commandsUp, sizeof(LoRaWANMacCommandQueue_t));
mod->hal->writePersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FOPTS_ID), queueBuff, sizeof(LoRaWANMacCommandQueue_t));
}
// fopts are processed or not present, check if there is payload
int payLen = downlinkMsgLen - 8 - foptsLen - sizeof(uint32_t);
// process payload (if there is any)
if(payLen <= 0) {
// no payload
*len = 0;
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] downlinkMsg;
#endif
return(RADIOLIB_ERR_NONE);
}
// there is payload, and so there should be a port too
// TODO pass the port?
*len = payLen - 1;
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], downlinkMsgLen, this->appSKey, data, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true);
// TODO it COULD be the case that the assumed rollover is incorrect, then figure out a way to catch this and retry with just fcnt16
// TODO does the erratum hold here as well?
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], downlinkMsgLen, this->appSKey, data, fcnt32, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true);
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] downlinkMsg;
@ -1124,38 +1355,69 @@ int16_t LoRaWANNode::pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQ
queue->commands[queue->numCommands - 1].payload[4],
queue->commands[queue->numCommands - 1].repeat);*/
queue->numCommands++;
queue->len += 1 + cmd->len; // 1 byte for command ID, len bytes for payload
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, bool force) {
int16_t LoRaWANNode::popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, size_t index) {
if(queue->numCommands == 0) {
return(RADIOLIB_ERR_COMMAND_QUEUE_EMPTY);
}
if(cmd) {
/*RADIOLIB_DEBUG_PRINTLN("pop MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ",
queue->commands[queue->numCommands - 1].cid,
queue->commands[queue->numCommands - 1].len,
queue->commands[queue->numCommands - 1].payload[0],
queue->commands[queue->numCommands - 1].payload[1],
queue->commands[queue->numCommands - 1].payload[2],
queue->commands[queue->numCommands - 1].payload[3],
queue->commands[queue->numCommands - 1].payload[4],
queue->commands[queue->numCommands - 1].repeat);*/
memcpy(cmd, &queue->commands[queue->numCommands - 1], sizeof(LoRaWANMacCommand_t));
// RADIOLIB_DEBUG_PRINTLN("pop MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ",
// queue->commands[index].cid,
// queue->commands[index].len,
// queue->commands[index].payload[0],
// queue->commands[index].payload[1],
// queue->commands[index].payload[2],
// queue->commands[index].payload[3],
// queue->commands[index].payload[4],
// queue->commands[index].repeat);
memcpy(cmd, &queue->commands[index], sizeof(LoRaWANMacCommand_t));
}
if((!force) && (queue->commands[queue->numCommands - 1].repeat > 0)) {
queue->commands[queue->numCommands - 1].repeat--;
if(queue->commands[index].repeat > 0) {
queue->commands[index].repeat--;
} else {
queue->commands[queue->numCommands - 1].repeat = 0;
queue->numCommands--;
deleteMacCommand(queue->commands[index].cid, queue);
}
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue) {
if(queue->numCommands == 0) {
return(RADIOLIB_ERR_COMMAND_QUEUE_EMPTY);
}
for(size_t index = 0; index < queue->numCommands; index++) {
if(queue->commands[index].cid == cid) {
// RADIOLIB_DEBUG_PRINTLN("delete MAC CID = %02x, len = %d, payload = %02x %02x %02x %02x %02x, repeat = %d ",
// queue->commands[index].cid,
// queue->commands[index].len,
// queue->commands[index].payload[0],
// queue->commands[index].payload[1],
// queue->commands[index].payload[2],
// queue->commands[index].payload[3],
// queue->commands[index].payload[4],
// queue->commands[index].repeat);
queue->len -= (1 + queue->commands[index].len); // 1 byte for command ID, len for payload
// move all subsequent commands one forward in the queue
if(index < RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1) {
memmove(&queue->commands[index], &queue->commands[index + 1], (RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - index) * sizeof(LoRaWANMacCommand_t));
}
// set the latest element to all 0
memset(&queue->commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE - 1], 0x00, sizeof(LoRaWANMacCommand_t));
queue->numCommands--;
return(RADIOLIB_ERR_NONE);
}
}
return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND);
}
size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
RADIOLIB_DEBUG_PRINTLN("exe MAC CID = %02x, len = %d", cmd->cid, cmd->len);
@ -1171,7 +1433,7 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
RADIOLIB_DEBUG_PRINTLN("Server version: 1.%d", srvVersion);
if(srvVersion == this->rev) {
// valid server version, stop sending the ResetInd MAC command
popMacCommand(NULL, &this->commandsUp, true);
deleteMacCommand(RADIOLIB_LORAWAN_MAC_CMD_RESET, &this->commandsUp);
}
return(1);
} break;
@ -1378,7 +1640,7 @@ size_t LoRaWANNode::execMacCommand(LoRaWANMacCommand_t* cmd) {
RADIOLIB_DEBUG_PRINTLN("Server version: 1.%d", srvVersion);
if((srvVersion > 0) && (srvVersion <= this->rev)) {
// valid server version, stop sending the ReKey MAC command
popMacCommand(NULL, &this->commandsUp, true);
deleteMacCommand(RADIOLIB_LORAWAN_MAC_CMD_REKEY, &this->commandsUp);
}
return(1);
} break;

View file

@ -5,6 +5,9 @@
#include "../PhysicalLayer/PhysicalLayer.h"
#include "../../utils/Cryptography.h"
// version of NVM table layout (NOT the LoRaWAN version)
#define RADIOLIB_PERSISTENT_PARAM_LORAWAN_VERSION (0x01)
// preamble format
#define RADIOLIB_LORAWAN_LORA_SYNC_WORD (0x34)
#define RADIOLIB_LORAWAN_LORA_PREAMBLE_LEN (8)
@ -257,7 +260,7 @@ struct LoRaWANMacCommand_t {
uint8_t cid;
/*! \brief Length of the payload */
size_t len;
uint8_t len;
/*! \brief Payload buffer (5 bytes is the longest possible) */
uint8_t payload[5];
@ -269,6 +272,7 @@ struct LoRaWANMacCommand_t {
struct LoRaWANMacCommandQueue_t {
LoRaWANMacCommand_t commands[RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE];
size_t numCommands;
size_t len;
};
/*!
@ -306,10 +310,10 @@ class LoRaWANNode {
void wipe();
/*!
\brief Join network by loading information from persistent storage.
\brief Restore OTAA session by loading information from persistent storage.
\returns \ref status_codes
*/
int16_t begin();
int16_t restoreOTAA();
/*!
\brief Join network by performing over-the-air activation. By this procedure,
@ -333,7 +337,7 @@ class LoRaWANNode {
\param sNwkSIntKey Pointer to the network session S key (LoRaWAN 1.1), unused for LoRaWAN 1.0.
\returns \ref status_codes
*/
int16_t beginAPB(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey = NULL, uint8_t* sNwkSIntKey = NULL);
int16_t beginABP(uint32_t addr, uint8_t* nwkSKey, uint8_t* appSKey, uint8_t* fNwkSIntKey = NULL, uint8_t* sNwkSIntKey = NULL);
#if defined(RADIOLIB_BUILD_ARDUINO)
/*!
@ -395,10 +399,12 @@ class LoRaWANNode {
LoRaWANMacCommandQueue_t commandsUp = {
.commands = { { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, } },
.numCommands = 0,
.len = 0,
};
LoRaWANMacCommandQueue_t commandsDown = {
.commands = { { .cid = 0, .len = 0, .payload = { 0 }, .repeat = 0, } },
.numCommands = 0,
.len = 0,
};
// the following is either provided by the network server (OTAA)
@ -438,6 +444,9 @@ class LoRaWANNode {
// device status - battery level
uint8_t battLevel = 0xFF;
// indicates whether an uplink has MAC commands as payload
bool isMACPayload = false;
// method to generate message integrity code
uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key);
@ -475,7 +484,10 @@ class LoRaWANNode {
int16_t pushMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue);
// pop MAC command from queue, done by copy unless CMD is NULL
int16_t popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, bool force = false);
int16_t popMacCommand(LoRaWANMacCommand_t* cmd, LoRaWANMacCommandQueue_t* queue, size_t index);
// delete a specific MAC command from queue, indicated by the command ID
int16_t deleteMacCommand(uint8_t cid, LoRaWANMacCommandQueue_t* queue);
// execute mac command, return the number of processed bytes for sequential processing
size_t execMacCommand(LoRaWANMacCommand_t* cmd);