RadioLibSmol/src/protocols/LoRaWAN/LoRaWAN.cpp
Jan Gromeš e44e9b4bce
[PHY] Get/Set modem (#1294)
* [PHY] Added set modem method

* Added new keyword

* [SX126x] Added setModem implementation

* [LoRaWAN] Use setModem

* [PHY] Added getModem

* [LoRaWAN] Use getModem instead of caching modulation

* [SX126x] Implement getModem

* Added new keywords

* [LR11x0] Added get/set modem

* [LLCC68] Added get/set modem

* [SX126x] Added missing default branch

* [SX127x] Added get/set modem

* [SX128x] Added get/set modem

* [CI] Drop Hellschreiber from AVR builds

* [CI] Drop Arduino Uno from CI
2024-10-26 17:49:35 +02:00

3420 lines
130 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "LoRaWAN.h"
#include <string.h>
#if defined(ESP_PLATFORM)
#include "esp_attr.h"
#endif
#if !RADIOLIB_EXCLUDE_LORAWAN
LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t subBand) {
this->phyLayer = phy;
this->band = band;
this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2;
this->txPowerMax = this->band->powerMax;
this->subBand = subBand;
this->dwellTimeEnabledUp = this->dwellTimeUp != 0;
this->dwellTimeEnabledDn = this->dwellTimeDn != 0;
memset(this->channelPlan, 0, sizeof(this->channelPlan));
}
#if defined(RADIOLIB_BUILD_ARDUINO)
int16_t LoRaWANNode::sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) {
int16_t state = RADIOLIB_ERR_UNKNOWN;
const char* dataUp = strUp.c_str();
// build a temporary buffer
// LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL
size_t lenDown = 0;
uint8_t dataDown[251];
state = this->sendReceive((uint8_t*)dataUp, strlen(dataUp), fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown);
if(state == RADIOLIB_ERR_NONE) {
// add null terminator
dataDown[lenDown] = '\0';
// initialize Arduino String class
strDown = String((char*)dataDown);
}
return(state);
}
#endif
int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) {
// build a temporary buffer
// LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL
size_t lenDown = 0;
uint8_t dataDown[251];
return(this->sendReceive((uint8_t*)strUp, strlen(strUp), fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown));
}
int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) {
return(this->sendReceive((uint8_t*)strUp, strlen(strUp), fPort, dataDown, lenDown, isConfirmed, eventUp, eventDown));
}
int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) {
// build a temporary buffer
// LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL
size_t lenDown = 0;
uint8_t dataDown[251];
return(this->sendReceive(dataUp, lenUp, fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown));
}
int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) {
if(!dataUp || !dataDown || !lenDown) {
return(RADIOLIB_ERR_NULL_POINTER);
}
int16_t state = RADIOLIB_ERR_UNKNOWN;
Module* mod = this->phyLayer->getMod();
// if after (at) ADR_ACK_LIMIT frames no RekeyConf was received, revert to Join state
if(this->fCntUp == (1UL << this->adrLimitExp)) {
state = this->getMacPayload(RADIOLIB_LORAWAN_MAC_REKEY, this->fOptsUp, this->fOptsUpLen, NULL, RADIOLIB_LORAWAN_UPLINK);
if(state == RADIOLIB_ERR_NONE) {
this->clearSession();
}
}
// if not joined, don't do anything
if(!this->isActivated()) {
return(RADIOLIB_ERR_NETWORK_NOT_JOINED);
}
// check if the requested payload + fPort are allowed, also given dutycycle
uint8_t totalLen = lenUp + this->fOptsUpLen;
state = this->isValidUplink(&totalLen, fPort);
RADIOLIB_ASSERT(state);
// in case of TS009, a payload that is too long may have gotten clipped,
// so recalculate the actual payload length
// (outside of TS009, a payload that is too long throws an error)
lenUp = totalLen - this->fOptsUpLen;
// the first 16 bytes are reserved for MIC calculation blocks
size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(lenUp, this->fOptsUpLen);
#if RADIOLIB_STATIC_ONLY
uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
#else
uint8_t* uplinkMsg = new uint8_t[uplinkMsgLen];
#endif
// build the encrypted uplink message
this->composeUplink(dataUp, lenUp, uplinkMsg, fPort, isConfirmed);
// reset Time-on-Air as we are starting new uplink sequence
this->lastToA = 0;
// repeat uplink+downlink up to 'nbTrans' times (ADR)
uint8_t trans = 0;
for(; trans < this->nbTrans; trans++) {
// keep track of number of hopped channels
uint8_t numHops = this->maxChanges;
// number of additional CAD tries
uint8_t numBackoff = 0;
if(this->backoffMax) {
numBackoff = this->phyLayer->random(1, this->backoffMax + 1);
}
do {
// select a pair of Tx/Rx channels for uplink+downlink
this->selectChannels();
// generate and set uplink MIC (depends on selected channel)
this->micUplink(uplinkMsg, uplinkMsgLen);
// if CSMA is enabled, repeat channel selection & encryption up to numHops times
} while(this->csmaEnabled && numHops-- > 0 && !this->csmaChannelClear(this->difsSlots, numBackoff));
// send it (without the MIC calculation blocks)
state = this->transmitUplink(&this->channels[RADIOLIB_LORAWAN_UPLINK],
&uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS],
(uint8_t)(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS),
trans > 0);
if(state != RADIOLIB_ERR_NONE) {
#if !RADIOLIB_STATIC_ONLY
delete[] uplinkMsg;
#endif
return(state);
}
// handle Rx1 and Rx2 windows - returns window > 0 if a downlink is received
state = receiveCommon(RADIOLIB_LORAWAN_DOWNLINK, this->channels, this->rxDelays, 2, this->rxDelayStart);
// RETRANSMIT_TIMEOUT is 2s +/- 1s (RP v1.0.4)
// must be present after any confirmed frame, so we force this here
if(isConfirmed) {
mod->hal->delay(this->phyLayer->random(1000, 3000));
}
// if an error occured or a downlink was received, stop retransmission
if(state != RADIOLIB_ERR_NONE) {
break;
}
// if no downlink was received, go on
} // end of transmission & reception
// note: if an error occured, it may still be the case that a transmission occured
// therefore, we act as if a transmission occured before throwing the actual error
// this feels to be the best way to comply to spec
// increase frame counter by one for the next uplink
this->fCntUp += 1;
// the downlink confirmation was acknowledged, so clear the counter value
this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE;
// pass the uplink info if requested
if(eventUp) {
eventUp->dir = RADIOLIB_LORAWAN_UPLINK;
eventUp->confirmed = isConfirmed;
eventUp->confirming = (this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE);
eventUp->datarate = this->channels[RADIOLIB_LORAWAN_UPLINK].dr;
eventUp->freq = this->channels[RADIOLIB_LORAWAN_UPLINK].freq / 10000.0;
eventUp->power = this->txPowerMax - this->txPowerSteps * 2;
eventUp->fCnt = this->fCntUp;
eventUp->fPort = fPort;
eventUp->nbTrans = trans;
}
#if !RADIOLIB_STATIC_ONLY
delete[] uplinkMsg;
#endif
// if a hardware error occurred, return
if(state < RADIOLIB_ERR_NONE) {
return(state);
}
uint8_t rxWindow = state;
// if no downlink was received, do an early exit
if(rxWindow == 0) {
// check if ADR backoff must occur
if(this->adrEnabled) {
this->adrBackoff();
}
// remove only non-persistent MAC commands, the other commands should be re-sent until downlink is received
LoRaWANNode::clearMacCommands(this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK);
return(rxWindow);
}
// a downlink was received, so we can clear the whole MAC uplink buffer
memset(this->fOptsUp, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN);
this->fOptsUpLen = 0;
state = this->parseDownlink(dataDown, lenDown, eventDown);
// return an error code, if any, otherwise return Rx window (which is > 0)
RADIOLIB_ASSERT(state);
return(rxWindow);
}
void LoRaWANNode::clearNonces() {
// clear & set all the device credentials
memset(this->bufferNonces, 0, RADIOLIB_LORAWAN_NONCES_BUF_SIZE);
this->keyCheckSum = 0;
this->devNonce = 0;
this->joinNonce = 0;
this->isActive = false;
this->rev = 0;
}
uint8_t* LoRaWANNode::getBufferNonces() {
// set the device credentials
LoRaWANNode::hton<uint16_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION], RADIOLIB_LORAWAN_NONCES_VERSION_VAL);
LoRaWANNode::hton<uint16_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE], this->lwMode);
LoRaWANNode::hton<uint8_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CLASS], this->lwClass);
LoRaWANNode::hton<uint8_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_PLAN], this->band->bandNum);
LoRaWANNode::hton<uint16_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CHECKSUM], this->keyCheckSum);
// generate the signature of the Nonces buffer, and store it in the last two bytes of the Nonces buffer
uint16_t signature = LoRaWANNode::checkSum16(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE - 2);
LoRaWANNode::hton<uint16_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE], signature);
return(this->bufferNonces);
}
int16_t LoRaWANNode::setBufferNonces(uint8_t* persistentBuffer) {
if(this->isActivated()) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Did not update buffer: session already active");
return(RADIOLIB_ERR_NONE);
}
int16_t state = LoRaWANNode::checkBufferCommon(persistentBuffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE);
RADIOLIB_ASSERT(state);
bool isSameKeys = LoRaWANNode::ntoh<uint16_t>(&persistentBuffer[RADIOLIB_LORAWAN_NONCES_CHECKSUM]) == this->keyCheckSum;
bool isSameMode = LoRaWANNode::ntoh<uint16_t>(&persistentBuffer[RADIOLIB_LORAWAN_NONCES_MODE]) == this->lwMode;
bool isSameClass = LoRaWANNode::ntoh<uint8_t>(&persistentBuffer[RADIOLIB_LORAWAN_NONCES_CLASS]) == this->lwClass;
bool isSamePlan = LoRaWANNode::ntoh<uint8_t>(&persistentBuffer[RADIOLIB_LORAWAN_NONCES_PLAN]) == this->band->bandNum;
// check if Nonces buffer matches the current configuration
if(!isSameKeys || !isSameMode || !isSameClass || !isSamePlan) {
// if configuration did not match, discard whatever is currently in the buffers and start fresh
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Configuration mismatch (keys: %d, mode: %d, class: %d, plan: %d)", isSameKeys, isSameMode, isSameClass, isSamePlan);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Discarding the Nonces buffer:");
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(persistentBuffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE);
return(RADIOLIB_ERR_NONCES_DISCARDED);
}
// copy the whole buffer over
memcpy(this->bufferNonces, persistentBuffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE);
this->devNonce = LoRaWANNode::ntoh<uint16_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE]);
this->joinNonce = LoRaWANNode::ntoh<uint32_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_JOIN_NONCE], 3);
// revert to inactive as long as no session is restored
this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false;
this->isActive = false;
return(state);
}
void LoRaWANNode::clearSession() {
memset(this->bufferSession, 0, RADIOLIB_LORAWAN_SESSION_BUF_SIZE);
memset(this->fOptsUp, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN);
memset(this->fOptsDown, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN);
this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false;
this->isActive = false;
// reset all frame counters
this->fCntUp = 0;
this->aFCntDown = 0;
this->nFCntDown = 0;
this->confFCntUp = RADIOLIB_LORAWAN_FCNT_NONE;
this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE;
this->adrFCnt = 0;
// reset number of retransmissions from ADR
this->nbTrans = 1;
// clear CSMA settings
this->csmaEnabled = false;
this->maxChanges = 0;
this->difsSlots = 0;
this->backoffMax = 0;
}
void LoRaWANNode::createSession(uint16_t lwMode, uint8_t initialDr) {
this->clearSession();
// setup JoinRequest uplink/downlink frequencies and datarates
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
this->selectChannelPlanDyn(true);
} else {
this->selectChannelPlanFix();
}
uint8_t drUp = RADIOLIB_LORAWAN_DATA_RATE_UNUSED;
// on fixed bands, the first OTAA uplink (JoinRequest) is sent on fixed datarate
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && lwMode == RADIOLIB_LORAWAN_MODE_OTAA) {
// randomly select one of 8 or 9 channels and find corresponding datarate
uint8_t numChannels = this->band->numTxSpans == 1 ? 8 : 9;
uint8_t rand = this->phyLayer->random(numChannels) + 1; // range 1-8 or 1-9
if(rand <= 8) {
drUp = this->band->txSpans[0].drJoinRequest; // if one of the first 8 channels, select datarate of span 0
} else {
drUp = this->band->txSpans[1].drJoinRequest; // if ninth channel, select datarate of span 1
}
} else {
// on dynamic bands, the first OTAA uplink (JoinRequest) can be any available datarate
// this is also true for ABP on both dynamic and fixed bands, as there is no JoinRequest
if(initialDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
uint8_t i = 0;
for(; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) {
if(initialDr >= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin
&& initialDr <= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax) {
drUp = initialDr;
break;
}
}
}
// if there is no channel that allowed the user-specified datarate, revert to default datarate
if(i == RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Datarate %d is not valid - using default", initialDr);
initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED;
}
}
// if there is no (channel that allowed the) user-specified datarate, use a default datarate
// we use the floor of the average datarate of the first enabled channel
if(initialDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) {
uint8_t drMin = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin;
uint8_t drMax = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax;
drUp = (drMin + drMax) / 2;
}
}
}
}
uint8_t cOcts[5]; // 5 = maximum downlink payload length
uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR;
uint8_t cLen = 1; // only apply Dr/Tx field
cOcts[0] = (drUp << 4); // set uplink datarate
cOcts[0] |= 0; // default to max Tx Power
(void)execMacCommand(cid, cOcts, cLen);
cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE;
this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
uint8_t maxDCyclePower = 0;
switch(this->band->dutyCycle) {
case(3600):
maxDCyclePower = 10;
break;
case(36000):
maxDCyclePower = 7;
break;
}
cOcts[0] = maxDCyclePower;
(void)execMacCommand(cid, cOcts, cLen);
cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP;
(void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = (RADIOLIB_LORAWAN_RX1_DR_OFFSET << 4);
cOcts[0] |= this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr; // may be set by user, otherwise band's default upon initialization
LoRaWANNode::hton<uint32_t>(&cOcts[1], this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq, 3);
(void)execMacCommand(cid, cOcts, cLen);
cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP;
(void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = (RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS / 1000);
(void)execMacCommand(cid, cOcts, cLen);
cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP;
(void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = (this->band->dwellTimeDn > 0 ? 1 : 0) << 5;
cOcts[0] |= (this->band->dwellTimeUp > 0 ? 1 : 0) << 4;
uint8_t maxEIRPRaw;
switch(this->band->powerMax) {
case(12):
maxEIRPRaw = 2;
break;
case(14):
maxEIRPRaw = 4;
break;
case(16):
maxEIRPRaw = 5;
break;
case(19): // this option does not exist for the TxParamSetupReq but will be caught during execution
maxEIRPRaw = 7;
break;
case(30):
maxEIRPRaw = 13;
break;
default:
maxEIRPRaw = 2;
break;
}
cOcts[0] |= maxEIRPRaw;
(void)execMacCommand(cid, cOcts, cLen);
cid = RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP;
(void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = (RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP << 4);
cOcts[0] |= RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP;
(void)execMacCommand(cid, cOcts, cLen);
cid = RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP;
(void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = (RADIOLIB_LORAWAN_REJOIN_MAX_TIME_N << 4);
cOcts[0] |= RADIOLIB_LORAWAN_REJOIN_MAX_COUNT_N;
(void)execMacCommand(cid, cOcts, cLen);
}
uint8_t* LoRaWANNode::getBufferSession() {
// store all frame counters
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN], this->aFCntDown);
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN], this->nFCntDown);
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP], this->confFCntUp);
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN], this->confFCntDown);
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_FCNT], this->adrFCnt);
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FCNT_UP], this->fCntUp);
// store the enabled channels
uint64_t chMaskGrp0123 = 0;
uint32_t chMaskGrp45 = 0;
this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45);
LoRaWANNode::hton<uint64_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR] + 1, chMaskGrp0123);
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR] + 9, chMaskGrp45);
// store the available/unused channels
uint16_t chMask = 0x0000;
(void)this->getAvailableChannels(&chMask);
LoRaWANNode::hton<uint16_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS], chMask);
// store the current uplink MAC command queue
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE], this->fOptsUp, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_LEN], &this->fOptsUpLen, 1);
// generate the signature of the Session buffer, and store it in the last two bytes of the Session buffer
uint16_t signature = LoRaWANNode::checkSum16(this->bufferSession, RADIOLIB_LORAWAN_SESSION_BUF_SIZE - 2);
LoRaWANNode::hton<uint16_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SIGNATURE], signature);
return(this->bufferSession);
}
int16_t LoRaWANNode::setBufferSession(uint8_t* persistentBuffer) {
if(this->isActivated()) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Did not update buffer: session already active");
return(RADIOLIB_ERR_NONE);
}
int16_t state = LoRaWANNode::checkBufferCommon(persistentBuffer, RADIOLIB_LORAWAN_SESSION_BUF_SIZE);
RADIOLIB_ASSERT(state);
// the Nonces buffer holds a checksum signature - compare this to the signature that is in the session buffer
uint16_t signatureNonces = LoRaWANNode::ntoh<uint16_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE]);
uint16_t signatureInSession = LoRaWANNode::ntoh<uint16_t>(&persistentBuffer[RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE]);
if(signatureNonces != signatureInSession) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The Session buffer (%04x) does not match the Nonces buffer (%04x)",
signatureInSession, signatureNonces);
return(RADIOLIB_ERR_SESSION_DISCARDED);
}
// copy the whole buffer over
memcpy(this->bufferSession, persistentBuffer, RADIOLIB_LORAWAN_SESSION_BUF_SIZE);
//// this code can be used in case breaking chances must be caught:
// uint8_t nvm_table_version = this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION];
// if (RADIOLIB_LORAWAN_NONCES_VERSION_VAL > nvm_table_version) {
// // set default values for variables that are new or something
// }
// pull all authentication keys from persistent storage
this->devAddr = LoRaWANNode::ntoh<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DEV_ADDR]);
memcpy(this->appSKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], RADIOLIB_AES128_BLOCK_SIZE);
memcpy(this->nwkSEncKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], RADIOLIB_AES128_BLOCK_SIZE);
memcpy(this->fNwkSIntKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], RADIOLIB_AES128_BLOCK_SIZE);
memcpy(this->sNwkSIntKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], RADIOLIB_AES128_BLOCK_SIZE);
// restore session parameters
this->rev = LoRaWANNode::ntoh<uint8_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_VERSION]);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LoRaWAN session: v1.%d", this->rev);
this->homeNetId = LoRaWANNode::ntoh<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_HOMENET_ID]);
this->aFCntDown = LoRaWANNode::ntoh<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN]);
this->nFCntDown = LoRaWANNode::ntoh<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN]);
this->confFCntUp = LoRaWANNode::ntoh<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP]);
this->confFCntDown = LoRaWANNode::ntoh<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN]);
this->adrFCnt = LoRaWANNode::ntoh<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_FCNT]);
this->fCntUp = LoRaWANNode::ntoh<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FCNT_UP]);
// restore the complete MAC state
uint8_t cOcts[14] = { 0 }; // TODO explain
uint8_t cid;
uint8_t cLen = 0;
// setup the default channels
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
this->selectChannelPlanDyn();
} else { // type == RADIOLIB_LORAWAN_BAND_FIXED)
this->selectChannelPlanFix();
}
// for dynamic bands, the additional channels must be restored per-channel
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
// all-zero buffer used for checking if MAC commands are set
uint8_t bufferZeroes[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 };
// restore the session channels
uint8_t *startChannelsUp = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS];
cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL;
(void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
memcpy(cOcts, startChannelsUp + (i * cLen), cLen);
if(memcmp(cOcts, bufferZeroes, cLen) != 0) { // only execute if it is not all zeroes
(void)execMacCommand(cid, cOcts, cLen);
}
}
uint8_t *startChannelsDown = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS];
cid = RADIOLIB_LORAWAN_MAC_DL_CHANNEL;
(void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
memcpy(cOcts, startChannelsDown + (i * cLen), cLen);
if(memcmp(cOcts, bufferZeroes, cLen) != 0) { // only execute if it is not all zeroes
(void)execMacCommand(cid, cOcts, cLen);
}
}
}
// restore the MAC state - ADR needs special care, the rest is straight default
cid = RADIOLIB_LORAWAN_MAC_LINK_ADR;
cLen = 14; // special internal ADR command
memcpy(cOcts, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], cLen);
(void)execMacCommand(cid, cOcts, cLen);
uint8_t cids[6] = {
RADIOLIB_LORAWAN_MAC_DUTY_CYCLE, RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP,
RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP, RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP,
RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP, RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP
};
uint16_t locs[6] = {
RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE, RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP,
RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP, RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP,
RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP, RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP
};
for(uint8_t i = 0; i < 6; i++) {
(void)this->getMacLen(cids[i], &cLen, RADIOLIB_LORAWAN_DOWNLINK);
memcpy(cOcts, &this->bufferSession[locs[i]], cLen);
(void)execMacCommand(cids[i], cOcts, cLen);
}
// set the available channels
uint16_t chMask = LoRaWANNode::ntoh<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS]);
this->setAvailableChannels(chMask);
// copy uplink MAC command queue back in place
memcpy(this->fOptsUp, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE], RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN);
memcpy(&this->fOptsUpLen, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_LEN], 1);
// as both the Nonces and session are restored, revert to active session
this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true;
return(state);
}
int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey) {
if(!appKey) {
return(RADIOLIB_ERR_NULL_POINTER);
}
// clear all the device credentials in case there were any
this->clearNonces();
this->joinEUI = joinEUI;
this->devEUI = devEUI;
memcpy(this->appKey, appKey, RADIOLIB_AES128_KEY_SIZE);
if(nwkKey) {
this->rev = 1;
memcpy(this->nwkKey, nwkKey, RADIOLIB_AES128_KEY_SIZE);
}
// generate activation key checksum
this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast<uint8_t*>(&joinEUI), sizeof(uint64_t));
this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast<uint8_t*>(&devEUI), sizeof(uint64_t));
this->keyCheckSum ^= LoRaWANNode::checkSum16(appKey, RADIOLIB_AES128_KEY_SIZE);
if(nwkKey) {
this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkKey, RADIOLIB_AES128_KEY_SIZE);
}
this->lwMode = RADIOLIB_LORAWAN_MODE_OTAA;
this->lwClass = RADIOLIB_LORAWAN_CLASS_A;
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey) {
if(!nwkSEncKey || !appSKey) {
return(RADIOLIB_ERR_NULL_POINTER);
}
// clear all the device credentials in case there were any
this->clearNonces();
this->devAddr = addr;
memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE);
memcpy(this->nwkSEncKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE);
if(fNwkSIntKey && sNwkSIntKey) {
this->rev = 1;
memcpy(this->fNwkSIntKey, fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE);
memcpy(this->sNwkSIntKey, sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE);
} else {
memcpy(this->fNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE);
memcpy(this->sNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE);
}
// generate activation key checksum
this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast<uint8_t*>(&addr), sizeof(uint32_t));
this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkSEncKey, RADIOLIB_AES128_KEY_SIZE);
this->keyCheckSum ^= LoRaWANNode::checkSum16(appSKey, RADIOLIB_AES128_KEY_SIZE);
if(fNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); }
if(sNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); }
this->lwMode = RADIOLIB_LORAWAN_MODE_ABP;
this->lwClass = RADIOLIB_LORAWAN_CLASS_A;
return(RADIOLIB_ERR_NONE);
}
void LoRaWANNode::composeJoinRequest(uint8_t* out) {
// copy devNonce currently in use
uint16_t devNonceUsed = this->devNonce;
// set the packet fields
out[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
LoRaWANNode::hton<uint64_t>(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], this->joinEUI);
LoRaWANNode::hton<uint64_t>(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], this->devEUI);
LoRaWANNode::hton<uint16_t>(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonceUsed);
// add the authentication code
uint32_t mic = 0;
if(this->rev == 1) {
mic =this->generateMIC(out, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->nwkKey);
} else {
mic =this->generateMIC(out, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->appKey);
}
LoRaWANNode::hton<uint32_t>(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t)], mic);
}
int16_t LoRaWANNode::processJoinAccept(LoRaWANJoinEvent_t *joinEvent) {
int16_t state = RADIOLIB_ERR_UNKNOWN;
// build the buffer for the reply data
uint8_t joinAcceptMsgEnc[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN];
// check received length
size_t lenRx = this->phyLayer->getPacketLength(true);
if((lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) && (lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN)) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinAccept reply length mismatch, expected %dB got %luB", RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN, (unsigned long)lenRx);
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// read the packet
state = this->phyLayer->readData(joinAcceptMsgEnc, lenRx);
// downlink frames are sent without CRC, which will raise error on SX127x
// we can ignore that error
if(state != RADIOLIB_ERR_LORA_HEADER_DAMAGED) {
RADIOLIB_ASSERT(state);
} else {
state = RADIOLIB_ERR_NONE;
}
// check reply message type
if((joinAcceptMsgEnc[0] & RADIOLIB_LORAWAN_MHDR_MTYPE_MASK) != RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinAccept reply message type invalid, expected 0x%02x got 0x%02x", RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT, joinAcceptMsgEnc[0]);
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// decrypt the join accept message
// this is done by encrypting again in ECB mode
// the first byte is the MAC header which is not encrypted
uint8_t joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN];
joinAcceptMsg[0] = joinAcceptMsgEnc[0];
if(this->rev == 1) {
RadioLibAES128Instance.init(this->nwkKey);
} else {
RadioLibAES128Instance.init(this->appKey);
}
RadioLibAES128Instance.encryptECB(&joinAcceptMsgEnc[1], RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - 1, &joinAcceptMsg[1]);
// get current joinNonce from downlink
uint32_t joinNonceNew = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinAccept (JoinNonce = %lu, previously %lu):", (unsigned long)joinNonceNew, (unsigned long)this->joinNonce);
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(joinAcceptMsg, lenRx);
if(this->rev == 1) {
// for v1.1, the JoinNonce received must be greater than the last joinNonce heard, else error
if((this->joinNonce > 0) && (joinNonceNew <= this->joinNonce)) {
return(RADIOLIB_ERR_JOIN_NONCE_INVALID);
}
} else {
// for v1.0.4, the JoinNonce is simply a non-repeating value (we only check the last value)
if(joinNonceNew == this->joinNonce) {
return(RADIOLIB_ERR_JOIN_NONCE_INVALID);
}
}
this->joinNonce = joinNonceNew;
this->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]);
// check LoRaWAN revision (the MIC verification depends on this)
uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS];
this->rev = (dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) >> 7;
RADIOLIB_DEBUG_PROTOCOL_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;
LoRaWANNode::hton<uint64_t>(&keyDerivationBuff[1], this->devEUI);
RadioLibAES128Instance.init(this->nwkKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->jSIntKey);
// prepare the buffer for MIC calculation
uint8_t micBuff[3*RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
micBuff[0] = RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE;
LoRaWANNode::hton<uint64_t>(&micBuff[1], this->joinEUI);
LoRaWANNode::hton<uint16_t>(&micBuff[9], this->devNonce - 1);
memcpy(&micBuff[11], joinAcceptMsg, lenRx);
if(!verifyMIC(micBuff, lenRx + 11, this->jSIntKey)) {
return(RADIOLIB_ERR_CRC_MISMATCH);
}
} else {
// 1.0 version
if(!verifyMIC(joinAcceptMsg, lenRx, this->appKey)) {
return(RADIOLIB_ERR_CRC_MISMATCH);
}
}
// in case of dynamic band, reset the channels to clear JoinRequest-specific channels
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
this->selectChannelPlanDyn(false);
}
uint8_t cOcts[5];
uint8_t cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP;
uint8_t cLen = 0;
(void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = dlSettings & 0x7F;
LoRaWANNode::hton<uint32_t>(&cOcts[1], this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq, 3);
(void)execMacCommand(cid, cOcts, cLen);
cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP;
(void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
cOcts[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS];
(void)execMacCommand(cid, cOcts, cLen);
// process CFlist if present (and if CFListType matches used band type)
if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN && joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_TYPE_POS] == this->band->bandType) {
this->processCFList(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS]);
}
// if no (valid) CFList was received, default or subband are already setup so don't need to do anything else
uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_NONCE_POS], this->joinNonce, 3);
// check protocol version (1.0 vs 1.1)
if(this->rev == 1) {
// 1.1 version, derive the keys
LoRaWANNode::hton<uint64_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_EUI_POS], this->joinEUI);
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_DEV_NONCE_POS], this->devNonce - 1);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
RadioLibAES128Instance.init(this->appKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY;
RadioLibAES128Instance.init(this->nwkKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->fNwkSIntKey);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY;
RadioLibAES128Instance.init(this->nwkKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->sNwkSIntKey);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY;
RadioLibAES128Instance.init(this->nwkKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey);
} else {
// 1.0 version, just derive the keys
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], this->homeNetId, 3);
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], this->devNonce - 1);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
RadioLibAES128Instance.init(this->appKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY;
RadioLibAES128Instance.init(this->appKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->fNwkSIntKey);
memcpy(this->sNwkSIntKey, this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE);
memcpy(this->nwkSEncKey, this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE);
}
// for LW v1.1, send the RekeyInd MAC command
if(this->rev == 1) {
// enqueue the RekeyInd MAC command to be sent in the next uplink
cid = RADIOLIB_LORAWAN_MAC_REKEY;
this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_UPLINK);
cOcts[0] = this->rev;
state = LoRaWANNode::pushMacCommand(cid, cOcts, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK);
RADIOLIB_ASSERT(state);
}
LoRaWANNode::hton<uint32_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_JOIN_NONCE], this->joinNonce, 3);
this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true;
// generate the signature of the Nonces buffer, and store it in the last two bytes of the Nonces buffer
uint16_t signature = LoRaWANNode::checkSum16(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE - 2);
LoRaWANNode::hton<uint16_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE], signature);
// store DevAddr and all keys
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DEV_ADDR], this->devAddr);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], this->appSKey, RADIOLIB_AES128_KEY_SIZE);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], this->nwkSEncKey, RADIOLIB_AES128_KEY_SIZE);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], this->sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE);
// set the signature of the Nonces buffer in the Session buffer
LoRaWANNode::hton<uint16_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE], signature);
// store network parameters
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_HOMENET_ID], this->homeNetId);
LoRaWANNode::hton<uint8_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_VERSION], this->rev);
this->isActive = true;
// received JoinAccept, so update JoinNonce value in event
if(joinEvent) {
joinEvent->joinNonce = this->joinNonce;
}
return(state);
}
int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) {
// check if there is an active session
if(this->isActivated()) {
// already activated, don't do anything
return(RADIOLIB_ERR_NONE);
}
if(this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE]) {
// session restored but not yet activated - do so now
this->isActive = true;
return(RADIOLIB_LORAWAN_SESSION_RESTORED);
}
int16_t state = RADIOLIB_ERR_UNKNOWN;
Module* mod = this->phyLayer->getMod();
// starting a new session, so make sure to update event fields already
if(joinEvent) {
joinEvent->newSession = true;
joinEvent->devNonce = this->devNonce;
joinEvent->joinNonce = this->joinNonce;
}
// setup all MAC properties to default values
this->createSession(RADIOLIB_LORAWAN_MODE_OTAA, joinDr);
// build the JoinRequest message
uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN];
this->composeJoinRequest(joinRequestMsg);
// select a random pair of Tx/Rx channels
state = this->selectChannels();
RADIOLIB_ASSERT(state);
// set the physical layer configuration for uplink
state = this->setPhyProperties(&this->channels[RADIOLIB_LORAWAN_UPLINK],
RADIOLIB_LORAWAN_UPLINK,
this->txPowerMax - 2*this->txPowerSteps);
RADIOLIB_ASSERT(state);
// calculate JoinRequest time-on-air in milliseconds
if(this->dwellTimeEnabledUp) {
RadioLibTime_t toa = this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_JOIN_REQUEST_LEN) / 1000;
if(toa > this->dwellTimeUp) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Dwell time exceeded: ToA = %lu, max = %d", (unsigned long)toa, this->dwellTimeUp);
return(RADIOLIB_ERR_DWELL_TIME_EXCEEDED);
}
}
// if requested, delay until transmitting JoinRequest
RadioLibTime_t tNow = mod->hal->millis();
if(this->tUplink > tNow) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow));
if(this->tUplink > mod->hal->millis()) {
mod->hal->delay(this->tUplink - mod->hal->millis());
}
}
// send it
state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN);
this->rxDelayStart = mod->hal->millis();
RADIOLIB_ASSERT(state);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinRequest sent (DevNonce = %d) <-- Rx Delay start", this->devNonce);
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN);
// JoinRequest successfully sent, so increase & save devNonce
this->devNonce += 1;
LoRaWANNode::hton<uint16_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE], this->devNonce);
// set the Time on Air of the JoinRequest
this->lastToA = this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_JOIN_REQUEST_LEN) / 1000;
// configure Rx1 and Rx2 delay for JoinAccept message - these are re-configured once a valid JoinAccept is received
this->rxDelays[1] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS;
this->rxDelays[2] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS;
// handle Rx1 and Rx2 windows - returns window > 0 if a downlink is received
state = receiveCommon(RADIOLIB_LORAWAN_DOWNLINK, this->channels, this->rxDelays, 2, this->rxDelayStart);
if(state < RADIOLIB_ERR_NONE) {
return(state);
} else if (state == RADIOLIB_ERR_NONE) {
return(RADIOLIB_ERR_NO_JOIN_ACCEPT);
}
// process JoinAccept message
state = this->processJoinAccept(joinEvent);
RADIOLIB_ASSERT(state);
return(RADIOLIB_LORAWAN_NEW_SESSION);
}
int16_t LoRaWANNode::activateABP(uint8_t initialDr) {
// check if there is an active session
if(this->isActivated()) {
// already activated, don't do anything
return(RADIOLIB_ERR_NONE);
}
if(this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE]) {
// session restored but not yet activated - do so now
this->isActive = true;
return(RADIOLIB_LORAWAN_SESSION_RESTORED);
}
// setup all MAC properties to default values
this->createSession(RADIOLIB_LORAWAN_MODE_ABP, initialDr);
// new session all good, so set active-bit to true
this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true;
// generate the signature of the Nonces buffer, and store it in the last two bytes of the Nonces buffer
uint16_t signature = LoRaWANNode::checkSum16(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE - 2);
LoRaWANNode::hton<uint16_t>(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE], signature);
// store DevAddr and all keys
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DEV_ADDR], this->devAddr);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
// set the signature of the Nonces buffer in the Session buffer
LoRaWANNode::hton<uint16_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE], signature);
// store network parameters
LoRaWANNode::hton<uint32_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_HOMENET_ID], this->homeNetId);
LoRaWANNode::hton<uint8_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_VERSION], this->rev);
this->isActive = true;
return(RADIOLIB_LORAWAN_NEW_SESSION);
}
void LoRaWANNode::processCFList(uint8_t* cfList) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Processing CFList");
uint8_t cOcts[14] = { 0 }; // TODO explain
uint8_t cid;
uint8_t cLen = 0;
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
// retrieve number of existing (default) channels
size_t num = 0;
for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
if(!this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) {
break;
}
num++;
}
cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL;
(void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK);
uint8_t freqZero[3] = { 0 };
// datarate range for all new channels is equal to the default channels
cOcts[4] = (this->band->txFreqs[0].drMax << 4) | this->band->txFreqs[0].drMin;
for(uint8_t i = 0; i < 5; i++, num++) {
// if the frequency fields are all zero, there are no more channels in the CFList
if(memcmp(&cfList[i*3], freqZero, 3) == 0) {
break;
}
cOcts[0] = num;
memcpy(&cOcts[1], &cfList[i*3], 3);
(void)execMacCommand(cid, cOcts, cLen);
}
} else { // RADIOLIB_LORAWAN_BAND_FIXED
// complete channel mask received, so clear all existing channels
for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE;
}
// copy channel mask straight over to LinkAdr MAC command
cid = RADIOLIB_LORAWAN_MAC_LINK_ADR;
cLen = 14; // special internal ADR length
cOcts[0] = 0xFF; // same datarate and cOcts
memcpy(&cOcts[1], cfList, 12); // copy mask
cOcts[13] = 0; // set NbTrans = 0 -> keep the same
(void)execMacCommand(cid, cOcts, cLen);
}
}
bool LoRaWANNode::isActivated() {
return(this->isActive);
}
int16_t LoRaWANNode::isValidUplink(uint8_t* len, uint8_t fPort) {
// check destination fPort
switch(fPort) {
case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: {
// MAC FPort only good if internally overruled
if (!this->isMACPayload) {
return(RADIOLIB_ERR_INVALID_PORT);
}
// if this is MAC only payload, continue and reset for next uplink
this->isMACPayload = false;
} break;
case RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN ... RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX: {
// all good
} break;
case RADIOLIB_LORAWAN_FPORT_TS009: {
// TS009 FPort only good if overruled during verification testing
if(!this->TS009) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is not enabled.", fPort);
return(RADIOLIB_ERR_INVALID_PORT);
}
} break;
case RADIOLIB_LORAWAN_FPORT_TS011: {
// TS011 FPort only good if overruled during relay exchange
if(!this->TS011) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is not enabled.", fPort);
return(RADIOLIB_ERR_INVALID_PORT);
}
} break;
default: {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is reserved.", fPort);
} break;
}
// check maximum payload len as defined in band
uint8_t maxPayLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr];
if(this->TS011) {
maxPayLen = RADIOLIB_MIN(maxPayLen, 230); // payload length is limited to 230 if under repeater
}
if(*len > maxPayLen) {
// normally, throw an error if the packet is too long
if(this->TS009 == false) {
return(RADIOLIB_ERR_PACKET_TOO_LONG);
}
// if testing with TS009 Specification Verification Protocol, don't throw error but clip the message
*len = maxPayLen;
}
return(RADIOLIB_ERR_NONE);
}
void LoRaWANNode::adrBackoff() {
// check if we need to do ADR stuff
uint32_t adrLimit = 0x01 << this->adrLimitExp;
uint32_t adrDelay = 0x01 << this->adrDelayExp;
// check if we already tried everything (adrFCnt == FCNT_NONE)
if(this->adrFCnt == RADIOLIB_LORAWAN_FCNT_NONE) {
return;
}
// no need to do any backoff for first Limit+Delay uplinks
if((this->fCntUp - this->adrFCnt) < (adrLimit + adrDelay)) {
return;
}
// only perform backoff every Delay uplinks
if((this->fCntUp - this->adrFCnt - adrLimit) % adrDelay != 0) {
return;
}
// if we hit the Limit + Delay, try one of three, in order:
// set TxPower to max, set DR to min, enable all default channels
// if the TxPower field has some offset, remove it and switch to maximum power
if(this->txPowerSteps > 0) {
// set the maximum power supported by both the module and the band
if(this->setTxPower(this->txPowerMax) == RADIOLIB_ERR_NONE) {
return;
}
}
// try to decrease the datarate
if(this->channels[RADIOLIB_LORAWAN_UPLINK].dr > 0) {
if(this->setDatarate(this->channels[RADIOLIB_LORAWAN_UPLINK].dr - 1) == RADIOLIB_ERR_NONE) {
return;
}
}
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
this->selectChannelPlanDyn(false); // revert to default frequencies
} else {
this->selectChannelPlanFix(); // go back to default selected subband
}
this->nbTrans = 1;
// as there is nothing more to do, set ADR counter to maximum value to indicate that we've tried everything
this->adrFCnt = RADIOLIB_LORAWAN_FCNT_NONE;
return;
}
void LoRaWANNode::composeUplink(uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t fPort, bool isConfirmed) {
// set the packet fields
if(isConfirmed) {
out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_UP;
this->confFCntUp = this->fCntUp;
} else {
out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP;
}
out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] |= RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
LoRaWANNode::hton<uint32_t>(&out[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr);
out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00;
if(this->adrEnabled) {
out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED;
// AdrAckReq is set if no downlink has been received for >=Limit uplinks
// but it is unset once backoff has been completed (which is internally denoted by adrFCnt == FCNT_NONE)
uint32_t adrLimit = 0x01 << this->adrLimitExp;
if(this->adrFCnt != RADIOLIB_LORAWAN_FCNT_NONE && (this->fCntUp - this->adrFCnt) >= adrLimit) {
out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ;
}
}
// check if we have some MAC commands to append
out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= this->fOptsUpLen;
// if the saved confirm-fCnt is set, set the ACK bit
if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) {
out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ACK;
}
LoRaWANNode::hton<uint16_t>(&out[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)this->fCntUp);
if(this->fOptsUpLen > 0) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC payload:");
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(this->fOptsUp, this->fOptsUpLen);
if(this->rev == 1) {
// in LoRaWAN v1.1, the FOpts are encrypted using the NwkSEncKey
processAES(this->fOptsUp, this->fOptsUpLen, this->nwkSEncKey, &out[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fCntUp, RADIOLIB_LORAWAN_UPLINK, 0x01, true);
} else {
// in LoRaWAN v1.0.x, the FOpts are unencrypted
memcpy(&out[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fOptsUp, this->fOptsUpLen);
}
}
// set the fPort
out[RADIOLIB_LORAWAN_FHDR_FPORT_POS(this->fOptsUpLen)] = fPort;
// select encryption key based on the target fPort
uint8_t* encKey = this->appSKey;
if((fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) || (fPort == RADIOLIB_LORAWAN_FPORT_TS011)) {
encKey = this->nwkSEncKey;
}
// encrypt the frame payload
processAES(in, lenIn, encKey, &out[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(this->fOptsUpLen)], this->fCntUp, RADIOLIB_LORAWAN_UPLINK, 0x00, true);
}
void LoRaWANNode::micUplink(uint8_t* inOut, uint8_t lenInOut) {
// create blocks for MIC calculation
uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC;
block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_UPLINK;
LoRaWANNode::hton<uint32_t>(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
LoRaWANNode::hton<uint32_t>(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], this->fCntUp);
block0[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = lenInOut - RADIOLIB_AES128_BLOCK_SIZE - sizeof(uint32_t);
uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
memcpy(block1, block0, RADIOLIB_AES128_BLOCK_SIZE);
if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) {
LoRaWANNode::hton<uint16_t>(&block1[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntDown);
}
block1[RADIOLIB_LORAWAN_MIC_DATA_RATE_POS] = this->channels[RADIOLIB_LORAWAN_UPLINK].dr;
block1[RADIOLIB_LORAWAN_MIC_CH_INDEX_POS] = this->channels[RADIOLIB_LORAWAN_UPLINK].idx;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink (FCntUp = %lu) decoded:", (unsigned long)this->fCntUp);
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(inOut, lenInOut);
// calculate authentication codes
memcpy(inOut, block1, RADIOLIB_AES128_BLOCK_SIZE);
uint32_t micS = this->generateMIC(inOut, lenInOut - sizeof(uint32_t), this->sNwkSIntKey);
memcpy(inOut, block0, RADIOLIB_AES128_BLOCK_SIZE);
uint32_t micF = this->generateMIC(inOut, lenInOut - sizeof(uint32_t), this->fNwkSIntKey);
// check LoRaWAN revision
if(this->rev == 1) {
uint32_t mic = ((uint32_t)(micF & 0x0000FF00) << 16) | ((uint32_t)(micF & 0x0000000FF) << 16) | ((uint32_t)(micS & 0x0000FF00) >> 0) | ((uint32_t)(micS & 0x0000000FF) >> 0);
LoRaWANNode::hton<uint32_t>(&inOut[lenInOut - sizeof(uint32_t)], mic);
} else {
LoRaWANNode::hton<uint32_t>(&inOut[lenInOut - sizeof(uint32_t)], micF);
}
}
int16_t LoRaWANNode::transmitUplink(LoRaWANChannel_t* chnl, uint8_t* in, uint8_t len, bool retrans) {
int16_t state = RADIOLIB_ERR_UNKNOWN;
Module* mod = this->phyLayer->getMod();
// check if the Rx windows were closed after sending the previous uplink
// this FORCES a user to call downlink() after an uplink()
if(this->rxDelayEnd < this->rxDelayStart) {
// not enough time elapsed since the last uplink, we may still be in an Rx window
return(RADIOLIB_ERR_UPLINK_UNAVAILABLE);
}
RadioLibTime_t tNow = mod->hal->millis();
// if scheduled uplink time is in the past, reschedule to now
if(this->tUplink < tNow) {
this->tUplink = tNow;
}
// if dutycycle is enabled and the time since last uplink + interval has not elapsed, return an error
// but: don't check this for retransmissions
if(!retrans && this->dutyCycleEnabled) {
if(this->rxDelayStart + (RadioLibTime_t)dutyCycleInterval(this->dutyCycle, this->lastToA) > this->tUplink) {
return(RADIOLIB_ERR_UPLINK_UNAVAILABLE);
}
}
// set the physical layer configuration for uplink
state = this->setPhyProperties(chnl,
RADIOLIB_LORAWAN_UPLINK,
this->txPowerMax - 2*this->txPowerSteps);
RADIOLIB_ASSERT(state);
// if requested, wait until transmitting uplink
tNow = mod->hal->millis();
if(this->tUplink > tNow) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow));
if(this->tUplink > mod->hal->millis()) {
mod->hal->delay(this->tUplink - mod->hal->millis());
}
}
state = this->phyLayer->transmit(in, len);
// set the timestamp so that we can measure when to start receiving
this->rxDelayStart = mod->hal->millis();
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink sent <-- Rx Delay start");
// increase Time on Air of the uplink sequence
this->lastToA += this->phyLayer->getTimeOnAir(len) / 1000;
return(state);
}
// flag to indicate whether there was some action during Rx mode (timeout or downlink)
static volatile bool downlinkAction = false;
// interrupt service routine to handle downlinks automatically
#if defined(ESP8266) || defined(ESP32)
IRAM_ATTR
#endif
static void LoRaWANNodeOnDownlinkAction(void) {
downlinkAction = true;
}
int16_t LoRaWANNode::receiveCommon(uint8_t dir, const LoRaWANChannel_t* dlChannels, const RadioLibTime_t* dlDelays, uint8_t numWindows, RadioLibTime_t tReference) {
Module* mod = this->phyLayer->getMod();
int16_t state = RADIOLIB_ERR_UNKNOWN;
// check if there are any upcoming Rx windows
// if the Rx1 window has already started, you're too late, because most downlinks happen in Rx1
RadioLibTime_t now = mod->hal->millis(); // fix the current timestamp to prevent negative delays
if(now > tReference + dlDelays[1] - this->scanGuard) {
// if function was called while Rx windows are in progress,
// wait until last window closes to prevent very bad stuff
if(now < tReference + dlDelays[numWindows]) {
mod->hal->delay(dlDelays[numWindows] + tReference - now);
}
// update the end timestamp in case user got stuck between uplink and downlink
this->rxDelayEnd = mod->hal->millis();
return(RADIOLIB_ERR_NO_RX_WINDOW);
}
// setup interrupt
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlinkAction);
RadioLibTime_t tOpen = 0;
int16_t timedOut = 0;
// listen during the specified windows
uint8_t window = 1;
for(; window <= numWindows; window++) {
downlinkAction = false;
// set the physical layer configuration for downlink
this->phyLayer->standby();
state = this->setPhyProperties(&dlChannels[window], dir, this->txPowerMax - 2*this->txPowerSteps);
RADIOLIB_ASSERT(state);
// calculate the Rx timeout
RadioLibTime_t timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*this->scanGuard*1000;
RadioLibTime_t timeoutMod = this->phyLayer->calculateRxTimeout(timeoutHost);
// wait for the start of the Rx window
RadioLibTime_t waitLen = tReference + dlDelays[window] - mod->hal->millis();
// make sure that no underflow occured; if so, clip the delay (although this will likely miss any downlink)
if(waitLen > dlDelays[window]) {
waitLen = dlDelays[window];
}
// the waiting duration is shortened a bit to cover any possible timing errors
if(waitLen > this->scanGuard) {
waitLen -= this->scanGuard;
}
mod->hal->delay(waitLen);
// open Rx window by starting receive with specified timeout
// TODO remove default arguments
state = this->phyLayer->startReceive(timeoutMod, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
tOpen = mod->hal->millis();
RADIOLIB_ASSERT(state);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Opening Rx%d window (%d ms timeout)... <-- Rx Delay end ", window, (int)(timeoutHost / 1000 + scanGuard / 2));
// wait for the timeout to complete (and a small additional delay)
mod->hal->delay(timeoutHost / 1000 + this->scanGuard / 2);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Closing Rx%d window", window);
// if the IRQ bit for Rx Timeout is not set, something is received, so stop the windows
timedOut = this->phyLayer->checkIrq(RADIOLIB_IRQ_TIMEOUT);
if(timedOut == RADIOLIB_ERR_UNSUPPORTED) {
return(timedOut);
}
if(!timedOut) {
break;
}
}
// Rx windows are now closed
this->rxDelayEnd = mod->hal->millis();
// if we got here due to a timeout, stop ongoing activities
if(timedOut) {
this->phyLayer->standby();
return(RADIOLIB_ERR_NONE);
}
// get the maximum allowed Time-on-Air of a packet given the current datarate
uint8_t maxPayLen = this->band->payloadLenMax[dlChannels[window].dr];
if(this->TS011) {
maxPayLen = RADIOLIB_MIN(maxPayLen, 222); // payload length is limited to 222 if under repeater
}
RadioLibTime_t tMax = this->phyLayer->getTimeOnAir(maxPayLen + 13) / 1000; // mandatory FHDR is 12/13 bytes
bool downlinkComplete = true;
// wait for the DIO to fire indicating a downlink is received
while(!downlinkAction) {
mod->hal->yield();
// stay in Rx mode for the maximum allowed Time-on-Air plus small grace period
if(mod->hal->millis() - tOpen > tMax + scanGuard) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink missing!");
downlinkComplete = false;
break;
}
}
// update time of downlink reception
if(downlinkComplete) {
this->tDownlink = mod->hal->millis();
}
// we have a message, clear actions, go to standby
this->phyLayer->clearPacketReceivedAction();
this->phyLayer->standby();
// if all windows passed without receiving anything, set return value to 0
if(!downlinkComplete) {
state = 0;
// if we received something during a window, set return value to the window number
} else {
state = window;
}
// Any frame received by an end-device containing a MACPayload greater than
// the specified maximum length M over the data rate used to receive the frame
// SHALL be silently discarded.
if(this->phyLayer->getPacketLength() > (size_t)(maxPayLen + 13)) { // mandatory FHDR is 12/13 bytes
return(0); // act as if no downlink was received
}
return(state);
}
int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) {
int16_t state = RADIOLIB_ERR_UNKNOWN;
// set user-data length to 0 to prevent undefined behaviour in case of bad use
// if there is user-data, this will be handled at the appropriate place
*len = 0;
// get the packet length
size_t downlinkMsgLen = this->phyLayer->getPacketLength();
// check the minimum required frame length
// an extra byte is subtracted because downlink frames may not have a fPort
if(downlinkMsgLen < RADIOLIB_LORAWAN_FRAME_LEN(0, 0) - 1 - RADIOLIB_AES128_BLOCK_SIZE) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink message too short (%lu bytes)", (unsigned long)downlinkMsgLen);
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// build the buffer for the downlink message
// the first 16 bytes are reserved for MIC calculation block
#if !RADIOLIB_STATIC_ONLY
uint8_t* downlinkMsg = new uint8_t[RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen];
#else
uint8_t downlinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
#endif
// read the data
state = this->phyLayer->readData(&downlinkMsg[RADIOLIB_AES128_BLOCK_SIZE], downlinkMsgLen);
// downlink frames are sent without CRC, which will raise error on SX127x
// we can ignore that error
if(state == RADIOLIB_ERR_LORA_HEADER_DAMAGED) {
state = RADIOLIB_ERR_NONE;
}
if(state != RADIOLIB_ERR_NONE) {
#if !RADIOLIB_STATIC_ONLY
delete[] downlinkMsg;
#endif
return(state);
}
// check the address
uint32_t addr = LoRaWANNode::ntoh<uint32_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS]);
if(addr != this->devAddr) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Device address mismatch, expected 0x%08lX, got 0x%08lX",
(unsigned long)this->devAddr, (unsigned long)addr);
#if !RADIOLIB_STATIC_ONLY
delete[] downlinkMsg;
#endif
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// calculate length of piggy-backed FOpts
uint8_t fOptsPbLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK;
// MHDR(1) - DevAddr(4) - FCtrl(1) - FCnt(2) - FOptsPb - Payload - MIC(4)
// potentially also an FPort, will find out next
uint8_t payLen = downlinkMsgLen - 1 - 4 - 1 - 2 - fOptsPbLen - 4;
// in LoRaWAN v1.1, a frame is a Network frame if there is no Application payload
// i.e.: either no payload at all (empty frame or FOpts only), or MAC only payload
uint8_t fPort = RADIOLIB_LORAWAN_FPORT_MAC_COMMAND;
bool isAppDownlink = false;
if(this->rev == 0) {
isAppDownlink = true;
}
if(payLen > 0) {
payLen -= 1; // subtract one as fPort is set
fPort = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(fOptsPbLen)];
// check if fPort value is actually allowed
switch(fPort) {
case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: {
// payload consists of all MAC commands (or is empty)
} break;
case RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN ... RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX: {
// payload is user-defined (or empty) - may carry piggybacked MAC commands
isAppDownlink = true;
} break;
case RADIOLIB_LORAWAN_FPORT_TS009: {
// TS009 FPort only good if overruled during verification testing
if(!this->TS009) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is not enabled.", fPort);
#if !RADIOLIB_STATIC_ONLY
delete[] downlinkMsg;
#endif
return(RADIOLIB_ERR_INVALID_PORT);
}
isAppDownlink = true;
} break;
case RADIOLIB_LORAWAN_FPORT_TS011: {
// TS011 FPort only good if overruled during relay exchange
if(!this->TS011) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is not enabled.", fPort);
#if !RADIOLIB_STATIC_ONLY
delete[] downlinkMsg;
#endif
return(RADIOLIB_ERR_INVALID_PORT);
}
isAppDownlink = true;
} break;
default: {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is reserved.", fPort);
#if !RADIOLIB_STATIC_ONLY
delete[] downlinkMsg;
#endif
return(RADIOLIB_ERR_INVALID_PORT);
} break;
}
}
// MAC commands SHALL NOT be present in the payload field and the frame options field simultaneously.
// Should this occur, the end-device SHALL silently discard the frame.
if(fOptsPbLen > 0 && payLen > 0 && fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
#if !RADIOLIB_STATIC_ONLY
delete[] downlinkMsg;
#endif
return(RADIOLIB_ERR_INVALID_PORT);
}
// get the frame counter
uint16_t fCnt16 = LoRaWANNode::ntoh<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]);
// check the fCntDown value (Network or Application)
uint32_t fCntDownPrev = 0;
if (isAppDownlink) {
fCntDownPrev = this->aFCntDown;
} else {
fCntDownPrev = this->nFCntDown;
}
// if this is not the first downlink...
// assume a 16-bit to 32-bit rollover if difference between counters 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
uint32_t fCnt32 = fCnt16;
if(fCntDownPrev > 0) {
if((fCnt16 <= fCntDownPrev) && ((0xFFFF - (uint16_t)fCntDownPrev + fCnt16) > RADIOLIB_LORAWAN_MAX_FCNT_GAP)) {
#if !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 |= ((uint32_t)msb << 16); // add back the MSB part
}
}
// check if the ACK bit is set, indicating this frame acknowledges the previous uplink
bool isConfirmingUp = false;
if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FCTRL_ACK)) {
isConfirmingUp = true;
}
// set the MIC calculation blocks
memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE);
downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC;
// if this downlink is confirming an uplink, the MIC was generated with the least-significant 16 bits of that fCntUp
// (LoRaWAN v1.1 only)
if(isConfirmingUp && (this->rev == 1)) {
LoRaWANNode::hton<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntUp);
}
downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_DOWNLINK;
LoRaWANNode::hton<uint32_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
LoRaWANNode::hton<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fCnt32);
downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t);
// check the MIC
if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) {
#if !RADIOLIB_STATIC_ONLY
delete[] downlinkMsg;
#endif
return(RADIOLIB_ERR_CRC_MISMATCH);
}
// save current fCnt to respective frame counter
if (isAppDownlink) {
this->aFCntDown = fCnt32;
} else {
this->nFCntDown = fCnt32;
}
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink (%sFCntDown = %lu) encoded:",
isAppDownlink ? "A" : "N",
(unsigned long)(isAppDownlink ? this->aFCntDown : this->nFCntDown));
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen);
// if this is a confirmed frame, save the downlink number (only app frames can be confirmed)
bool isConfirmedDown = false;
if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] & 0xFE) == RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN) {
this->confFCntDown = this->aFCntDown;
isConfirmedDown = true;
}
// a downlink was received, so reset the ADR counter to the last uplink's fCnt
this->adrFCnt = this->getFCntUp();
// if this downlink is on FPort 0, the FOptsLen is the length of the payload
// in any other case, the payload (length) is user accessible
uint8_t fOptsLen = fOptsPbLen;
if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND && payLen > 0) {
fOptsLen = payLen;
} else {
*len = payLen;
}
#if !RADIOLIB_STATIC_ONLY
uint8_t* fOpts = new uint8_t[fOptsLen];
#else
uint8_t fOpts[RADIOLIB_STATIC_ARRAY_SIZE];
#endif
// figure out if the payload should end up in user data or internal FOpts buffer
uint8_t* dest;
if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
dest = fOpts;
} else {
dest = data;
}
// figure out which key to use to decrypt the payload
uint8_t* encKey = this->appSKey;
if((fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) || (fPort == RADIOLIB_LORAWAN_FPORT_TS011)) {
encKey = this->nwkSEncKey;
}
// decrypt the frame payload
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(fOptsPbLen)], payLen, encKey, dest, fCnt32, RADIOLIB_LORAWAN_DOWNLINK, 0x00, true);
// decrypt any piggy-backed FOpts
if(fOptsPbLen > 0) {
// the decryption depends on the LoRaWAN version
if(this->rev == 1) {
// in LoRaWAN v1.1, the piggy-backed FOpts are encrypted using the NwkSEncKey
uint8_t ctrId = 0x01 + isAppDownlink; // see LoRaWAN v1.1 errata
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsPbLen, this->nwkSEncKey, fOpts, fCnt32, RADIOLIB_LORAWAN_DOWNLINK, ctrId, true);
} else {
// in LoRaWAN v1.0.x, the piggy-backed FOpts are unencrypted
memcpy(fOpts, &downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsPbLen);
}
}
// clear the previous MAC commands, if any
memset(this->fOptsDown, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN);
// process FOpts (if there are any)
uint8_t cid;
uint8_t fLen = 1;
uint8_t* mPtr = fOpts;
uint8_t procLen = 0;
#if !RADIOLIB_STATIC_ONLY
uint8_t* fOptsRe = new uint8_t[250];
#else
uint8_t fOptsRe[RADIOLIB_STATIC_ARRAY_SIZE];
#endif
uint8_t fOptsReLen = 0;
// indication whether LinkAdr MAC command has been processed
bool mAdr = false;
while(procLen < fOptsLen) {
cid = *mPtr; // MAC id is the first byte
state = this->getMacLen(cid, &fLen, RADIOLIB_LORAWAN_DOWNLINK, true);
RADIOLIB_ASSERT(state);
uint8_t fLenRe = 0;
state = this->getMacLen(cid, &fLenRe, RADIOLIB_LORAWAN_UPLINK, true);
RADIOLIB_ASSERT(state);
if(procLen + fLen > fOptsLen) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Incomplete MAC command %02x (%d bytes, expected %d)", cid, fOptsLen, fLen);
return(RADIOLIB_ERR_INVALID_CID);
}
bool reply = false;
// if this is a LinkAdr MAC command, pre-process contiguous commands into one atomic block
if(cid == RADIOLIB_LORAWAN_MAC_LINK_ADR) {
// if there was any LinkAdr command before, set NACK and continue without processing
if(mAdr) {
reply = true;
fOptsRe[fOptsReLen + 1] = 0x00;
// if this is the first LinkAdr command, do some special treatment:
} else {
mAdr = true;
uint8_t fAdrLen = 5;
uint8_t mAdrOpt[14] = { 0 };
// retrieve all contiguous LinkAdr commands
while(procLen + fLen + fAdrLen < fOptsLen + 1 && *(mPtr + fLen) == RADIOLIB_LORAWAN_MAC_LINK_ADR) {
fLen += 5; // ADR command is 5 bytes
fLenRe += 2; // ADR response is 2 bytes
}
// pre-process them into a single complete channel mask (stored in mAdrOpt)
LoRaWANNode::preprocessMacLinkAdr(mPtr, fLen, mAdrOpt);
// execute like a normal MAC command (but pointing to mAdrOpt instead)
reply = this->execMacCommand(cid, mAdrOpt, 14, &fOptsRe[fOptsReLen + 1]);
// in LoRaWAN v1.0.x, all ACK bytes should have equal status - fix in post-processing
if(this->rev == 0) {
LoRaWANNode::postprocessMacLinkAdr(&fOptsRe[fOptsReLen], fLen);
// in LoRaWAN v1.1, just provide one ACK, so no post-processing but cut off reply length
} else {
fLenRe = 2;
}
}
// MAC command other than LinkAdr, just process the payload
} else {
reply = this->execMacCommand(cid, mPtr + 1, fLen - 1, &fOptsRe[fOptsReLen + 1]);
}
if(reply) {
fOptsRe[fOptsReLen] = cid;
fOptsReLen += fLenRe;
}
procLen += fLen;
mPtr += fLen;
}
// remove all MAC commands except those whose payload can be requested by the user
// (which are LinkCheck and DeviceTime)
if(fOptsLen > 0) {
LoRaWANNode::clearMacCommands(fOpts, &fOptsLen, RADIOLIB_LORAWAN_DOWNLINK);
memcpy(this->fOptsDown, fOpts, fOptsLen);
}
this->fOptsDownLen = fOptsLen;
// if fOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink
if(fOptsReLen > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC-only payload (%d bytes):", fOptsReLen);
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fOptsRe, fOptsReLen);
this->isMACPayload = true;
// temporarily lift dutyCycle restrictions to allow immediate MAC response
bool prevDC = this->dutyCycleEnabled;
this->dutyCycleEnabled = false;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Sending MAC-only uplink .. ");
this->sendReceive(fOptsRe, fOptsReLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND);
this->dutyCycleEnabled = prevDC;
} else { // fOptsReLen <= 15
memcpy(this->fOptsUp, fOptsRe, fOptsReLen);
this->fOptsUpLen = fOptsReLen;
}
// pass the extra info if requested
if(event) {
event->dir = RADIOLIB_LORAWAN_DOWNLINK;
event->confirmed = isConfirmedDown;
event->confirming = isConfirmingUp;
event->frmPending = (downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FCTRL_FRAME_PENDING) != 0;
event->datarate = this->channels[RADIOLIB_LORAWAN_DOWNLINK].dr;
event->freq = channels[event->dir].freq / 10000.0;
event->power = this->txPowerMax - this->txPowerSteps * 2;
event->fCnt = isAppDownlink ? this->aFCntDown : this->nFCntDown;
event->fPort = fPort;
}
#if !RADIOLIB_STATIC_ONLY
delete[] fOpts;
delete[] fOptsRe;
delete[] downlinkMsg;
#endif
return(RADIOLIB_ERR_NONE);
}
bool LoRaWANNode::execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn) {
uint8_t buff[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN];
return(this->execMacCommand(cid, optIn, lenIn, buff));
}
bool LoRaWANNode::execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("[MAC] 0x%02x", cid);
RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(optIn, lenIn);
if(cid >= RADIOLIB_LORAWAN_MAC_PROPRIETARY) {
// TODO call user-provided callback for proprietary MAC commands?
return(false);
}
switch(cid) {
case(RADIOLIB_LORAWAN_MAC_RESET): {
// get the server version
uint8_t srvVersion = optIn[0];
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ResetConf: server version 1.%d", srvVersion);
if(srvVersion == this->rev) {
// valid server version, stop sending the ResetInd MAC command
LoRaWANNode::deleteMacCommand(RADIOLIB_LORAWAN_MAC_RESET, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK);
}
return(false);
} break;
case(RADIOLIB_LORAWAN_MAC_LINK_CHECK): {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkCheckAns: [user]");
return(false);
} break;
case(RADIOLIB_LORAWAN_MAC_LINK_ADR): {
// get the ADR configuration
uint8_t macDrUp = (optIn[0] & 0xF0) >> 4;
uint8_t macTxSteps = optIn[0] & 0x0F;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkAdrReq: dataRate = %d, txSteps = %d, nbTrans = %d", macDrUp, macTxSteps, lenIn > 1 ? optIn[13] : 0);
uint8_t chMaskAck = 0;
uint8_t drAck = 0;
uint8_t pwrAck = 0;
// first, get current configuration
uint64_t chMaskGrp0123 = 0;
uint32_t chMaskGrp45 = 0;
this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45);
uint16_t chMaskActive = 0;
(void)this->getAvailableChannels(&chMaskActive);
uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr;
// only apply channel mask if present (internal Dr/Tx commands do not set channel mask)
if(lenIn > 1) {
uint64_t macChMaskGrp0123 = LoRaWANNode::ntoh<uint64_t>(&optIn[1]);
uint32_t macChMaskGrp45 = LoRaWANNode::ntoh<uint32_t>(&optIn[9]);
// apply requested channel mask and enable all of them for testing datarate
chMaskAck = this->applyChannelMask(macChMaskGrp0123, macChMaskGrp45);
} else {
chMaskAck = true;
}
this->setAvailableChannels(0xFFFF);
int16_t state;
// try to apply the datarate configuration
// if value is set to 'keep current values', retrieve current value
if(macDrUp == 0x0F) {
macDrUp = currentDr;
}
if (this->band->dataRates[macDrUp] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
// check if the module supports this data rate
DataRate_t dr;
state = this->findDataRate(macDrUp, &dr);
// if datarate in hardware all good, set datarate for now
// and check if there are any available Tx channels for this datarate
if(state == RADIOLIB_ERR_NONE) {
this->channels[RADIOLIB_LORAWAN_UPLINK].dr = macDrUp;
// only if we have available Tx channels, we set an Ack
if(this->getAvailableChannels(NULL) > 0) {
drAck = 1;
} else {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR: no channels available for datarate %d", macDrUp);
}
} else {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR: hardware failure configurating datarate %d, code %d", macDrUp, state);
}
}
// try to apply the power configuration
// if value is set to 'keep current values', retrieve current value
if(macTxSteps == 0x0F) {
macTxSteps = this->txPowerSteps;
}
int8_t power = this->txPowerMax - 2*macTxSteps;
int8_t powerActual = 0;
state = this->phyLayer->checkOutputPower(power, &powerActual);
// only acknowledge if the radio is able to operate at or below the requested power level
if(state == RADIOLIB_ERR_NONE || (state == RADIOLIB_ERR_INVALID_OUTPUT_POWER && powerActual < power)) {
pwrAck = 1;
this->txPowerSteps = macTxSteps;
} else {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR failed to configure Tx power %d, code %d!", power, state);
}
// set ACK bits
optOut[0] = (pwrAck << 2) | (drAck << 1) | (chMaskAck << 0);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkAdrAns: %02x", optOut[0]);
// if ACK not completely successful, revert and stop
if(optOut[0] != 0x07) {
this->applyChannelMask(chMaskGrp0123, chMaskGrp45);
this->setAvailableChannels(chMaskActive);
this->channels[RADIOLIB_LORAWAN_UPLINK].dr = currentDr;
// Tx power was not modified
return(true);
}
// ACK successful, so apply and save
this->txPowerSteps = macTxSteps;
if(lenIn > 1) {
uint8_t macNbTrans = optIn[13] & 0x0F;
// if there is a value for NbTrans > 0, apply it
if(macNbTrans) {
this->nbTrans = macNbTrans;
} else {
// for LoRaWAN v1.0.4, if NbTrans == 0, the end-device SHALL use the default value (being 1)
if(this->rev == 0) {
this->nbTrans = 1;
}
// for LoRaWAN v1.1, if NbTrans == 0, the end-device SHALL keep the current NbTrans value unchanged
// so, don't do anything
}
}
// restore original active channels
this->setAvailableChannels(chMaskActive);
// save to the ADR MAC location
// but first re-set the Dr/Tx/NbTrans field to make sure they're not set to 0xF
optIn[0] = (this->channels[RADIOLIB_LORAWAN_UPLINK].dr) << 4;
optIn[0] |= this->txPowerSteps;
if(lenIn > 1) {
optIn[13] = this->nbTrans;
}
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], optIn, lenIn);
return(true);
} break;
case(RADIOLIB_LORAWAN_MAC_DUTY_CYCLE): {
uint8_t maxDutyCycle = optIn[0] & 0x0F;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DutyCycleReq: max duty cycle = 1/2^%d", maxDutyCycle);
if(maxDutyCycle == 0) {
this->dutyCycle = this->band->dutyCycle;
} else {
this->dutyCycle = (RadioLibTime_t)60 * (RadioLibTime_t)60 * (RadioLibTime_t)1000 / (RadioLibTime_t)(1UL << maxDutyCycle);
}
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE], optIn, lenIn);
return(true);
} break;
case(RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP): {
// get the configuration
uint8_t macRx1DrOffset = (optIn[0] & 0x70) >> 4;
uint8_t macRx2Dr = optIn[0] & 0x0F;
uint32_t macRx2Freq = LoRaWANNode::ntoh<uint32_t>(&optIn[1], 3);
uint8_t rx1DrOsAck = 0;
uint8_t rx2DrAck = 0;
uint8_t rx2FreqAck = 0;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXParamSetupReq: Rx1DrOffset = %d, rx2DataRate = %d, freq = %7.3f",
macRx1DrOffset, macRx2Dr, macRx2Freq / 10000.0);
// check the requested configuration
uint8_t uplinkDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr;
DataRate_t dr;
if(this->band->rx1DrTable[uplinkDr][macRx1DrOffset] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
if(this->findDataRate(this->band->rx1DrTable[uplinkDr][macRx1DrOffset], &dr) == RADIOLIB_ERR_NONE) {
rx1DrOsAck = 1;
}
}
if(macRx2Dr >= this->band->rx2.drMin && macRx2Dr <= this->band->rx2.drMax) {
if(this->band->dataRates[macRx2Dr] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
if(this->findDataRate(macRx2Dr, &dr) == RADIOLIB_ERR_NONE) {
rx2DrAck = 1;
}
}
}
if(macRx2Freq >= this->band->freqMin && macRx2Freq <= this->band->freqMax) {
if(this->phyLayer->setFrequency(macRx2Freq / 10000.0) == RADIOLIB_ERR_NONE) {
rx2FreqAck = 1;
}
}
optOut[0] = (rx1DrOsAck << 2) | (rx2DrAck << 1) | (rx2FreqAck << 0);
// if not fully acknowledged, return now without applying the requested configuration
if(optOut[0] != 0x07) {
return(true);
}
// passed ACK, so apply configuration
this->rx1DrOffset = macRx1DrOffset;
this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr = macRx2Dr;
this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq = macRx2Freq;
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP], optIn, lenIn);
return(true);
} break;
case(RADIOLIB_LORAWAN_MAC_DEV_STATUS): {
// set the uplink reply
optOut[0] = this->battLevel;
int8_t snr = this->phyLayer->getSNR();
optOut[1] = snr & 0x3F;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DevStatusAns: status = 0x%02x%02x", optOut[0], optOut[1]);
return(true);
} break;
case(RADIOLIB_LORAWAN_MAC_NEW_CHANNEL): {
// only implemented on dynamic bands
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) {
return(false);
}
// get the configuration
uint8_t macChIndex = optIn[0];
uint32_t macFreq = LoRaWANNode::ntoh<uint32_t>(&optIn[1], 3);
uint8_t macDrMax = (optIn[4] & 0xF0) >> 4;
uint8_t macDrMin = optIn[4] & 0x0F;
uint8_t drAck = 0;
uint8_t freqAck = 0;
// the default channels shall not be modified, so check if this is a default channel
// if the channel index is set, this channel is defined, so return a NACK
if(macChIndex < 3 && this->band->txFreqs[macChIndex].idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) {
optOut[0] = 0;
return(true);
}
// check if the outermost datarates are defined and if the device supports them
DataRate_t dr;
if(this->band->dataRates[macDrMin] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED && this->findDataRate(macDrMin, &dr) == RADIOLIB_ERR_NONE) {
if(this->band->dataRates[macDrMax] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED && this->findDataRate(macDrMax, &dr) == RADIOLIB_ERR_NONE) {
drAck = 1;
}
}
// check if the frequency is allowed and possible
if(macFreq >= this->band->freqMin && macFreq <= this->band->freqMax) {
if(this->phyLayer->setFrequency((float)macFreq / 10000.0) == RADIOLIB_ERR_NONE) {
freqAck = 1;
}
// otherwise, if frequency is 0, disable the channel which is also a valid option
} else if(macFreq == 0) {
freqAck = 1;
}
// set ACK bits
optOut[0] = (drAck << 1) | (freqAck << 0);
// if not fully acknowledged, return now without applying the requested configuration
if(optOut[0] != 0x03) {
return(true);
}
// ACK successful, so apply and save
if(macFreq > 0) {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].enabled = true;
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].idx = macChIndex;
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq = macFreq;
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMin = macDrMin;
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMax = macDrMax;
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].available = true;
// downlink channel is identical to uplink channel
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex] = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex];
} else {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex] = RADIOLIB_LORAWAN_CHANNEL_NONE;
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex] = RADIOLIB_LORAWAN_CHANNEL_NONE;
}
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)",
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].idx,
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].enabled,
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq / 10000.0,
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMin,
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMax,
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].idx,
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].enabled,
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].freq / 10000.0,
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].drMin,
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].drMax
);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS] + macChIndex * lenIn, optIn, lenIn);
return(true);
} break;
case(RADIOLIB_LORAWAN_MAC_DL_CHANNEL): {
// only implemented on dynamic bands
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) {
return(false);
}
// get the configuration
uint8_t macChIndex = optIn[0];
uint32_t macFreq = LoRaWANNode::ntoh<uint32_t>(&optIn[1], 3);
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DlChannelReq: index = %d, freq = %7.3f MHz", macChIndex, macFreq / 10000.0);
uint8_t freqDlAck = 0;
uint8_t freqUlAck = 0;
// check if the frequency is allowed possible
if(macFreq >= this->band->freqMin && macFreq <= this->band->freqMax) {
if(this->phyLayer->setFrequency(macFreq / 10000.0) == RADIOLIB_ERR_NONE) {
freqDlAck = 1;
}
}
// check if the corresponding uplink frequency is actually set
if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq > 0) {
freqUlAck = 1;
}
// set ACK bits
optOut[0] = (freqUlAck << 1) | (freqDlAck << 0);
// if not fully acknowledged, return now without applying the requested configuration
if(optOut[0] != 0x03) {
return(true);
}
// ACK successful, so apply and save
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].freq = macFreq;
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS] + macChIndex * lenIn, optIn, lenIn);
return(true);
} break;
case(RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP): {
// get the configuration
uint8_t delay = optIn[0] & 0x0F;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXTimingSetupReq: delay = %d sec", delay);
// apply the configuration
if(delay == 0) {
delay = 1;
}
this->rxDelays[1] = delay * 1000; // Rx1 delay
this->rxDelays[2] = this->rxDelays[1] + 1000; // Rx2 delay
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP], optIn, lenIn);
return(true);
} break;
case(RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP): {
// TxParamSetupReq is only supported on a subset of bands
// in other bands, silently ignore without response
if(!this->band->txParamSupported) {
return(false);
}
uint8_t dlDwell = (optIn[0] & 0x20) >> 5;
uint8_t ulDwell = (optIn[0] & 0x10) >> 4;
uint8_t maxEirpRaw = optIn[0] & 0x0F;
// who the f came up with this ...
const uint8_t eirpEncoding[] = { 8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36 };
this->txPowerMax = eirpEncoding[maxEirpRaw];
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("TxParamSetupReq: dlDwell = %d, ulDwell = %d, maxEirp = %d dBm", dlDwell, ulDwell, eirpEncoding[maxEirpRaw]);
this->dwellTimeEnabledUp = ulDwell ? true : false;
this->dwellTimeUp = ulDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0;
this->dwellTimeEnabledDn = dlDwell ? true : false;
this->dwellTimeDn = dlDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0;
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], optIn, lenIn);
return(true);
} break;
case(RADIOLIB_LORAWAN_MAC_REKEY): {
// get the server version
uint8_t srvVersion = optIn[0];
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RekeyConf: server version = 1.%d", srvVersion);
// If the servers version is invalid the device SHALL discard the RekeyConf command and retransmit the RekeyInd in the next uplink frame
if((srvVersion > 0) && (srvVersion <= this->rev)) {
// valid server version, accept
this->rev = srvVersion;
} else {
// if not a valid server version, retransmit RekeyInd
uint8_t cLen = 0;
this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_UPLINK);
uint8_t cOcts[1] = { this->rev };
(void)LoRaWANNode::pushMacCommand(cid, cOcts, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK);
// discard RekeyConf, therefore return false so it doesn't send a reply
return(false);
}
optOut[0] = this->rev;
LoRaWANNode::hton<uint8_t>(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_VERSION], this->rev);
return(false);
} break;
case(RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP): {
this->adrLimitExp = (optIn[0] & 0xF0) >> 4;
this->adrDelayExp = optIn[0] & 0x0F;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADRParamSetupReq: limitExp = %d, delayExp = %d", this->adrLimitExp, this->adrDelayExp);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP], optIn, lenIn);
return(true);
} break;
case(RADIOLIB_LORAWAN_MAC_DEVICE_TIME): {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DeviceTimeAns: [user]");
return(false);
} break;
case(RADIOLIB_LORAWAN_MAC_FORCE_REJOIN): {
// TODO implement this
uint16_t rejoinReq = LoRaWANNode::ntoh<uint16_t>(optIn);
uint8_t period = (rejoinReq & 0x3800) >> 11;
uint8_t maxRetries = (rejoinReq & 0x0700) >> 8;
uint8_t rejoinType = (rejoinReq & 0x0070) >> 4;
uint8_t dr = rejoinReq & 0x000F;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ForceRejoinReq: period = %d, maxRetries = %d, rejoinType = %d, dr = %d", period, maxRetries, rejoinType, dr);
(void)period;
(void)maxRetries;
(void)rejoinType;
(void)dr;
return(false);
} break;
case(RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP): {
// TODO implement this
uint8_t maxTime = (optIn[0] & 0xF0) >> 4;
uint8_t maxCount = optIn[0] & 0x0F;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RejoinParamSetupReq: maxTime = %d, maxCount = %d", maxTime, maxCount);
memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP], optIn, lenIn);
lenIn = 0;
optIn[0] = (1 << 1) | 1;
(void)maxTime;
(void)maxCount;
return(true);
} break;
default: {
// derived classes may implement additional MAC commands
return(derivedMacHandler(cid, optIn, lenIn, optOut));
}
}
return(false);
}
bool LoRaWANNode::derivedMacHandler(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut) {
(void)cid;
(void)optIn;
(void)lenIn;
(void)optOut;
return(false);
}
void LoRaWANNode::preprocessMacLinkAdr(uint8_t* mPtr, uint8_t cLen, uint8_t* mAdrOpt) {
uint8_t fLen = 5; // single ADR command is 5 bytes
uint8_t numOpts = cLen / fLen;
uint64_t chMaskGrp0123 = 0;
uint32_t chMaskGrp45 = 0;
// set Dr/Tx field from last MAC command
mAdrOpt[0] = mPtr[cLen - fLen + 1];
// set NbTrans partial field from last MAC command
mAdrOpt[13] = mPtr[cLen - fLen + 4] & 0x0F;
uint8_t opt = 0;
while(opt < numOpts) {
uint8_t chMaskCntl = (mPtr[opt * fLen + 4] & 0x70) >> 4;
uint16_t chMask = LoRaWANNode::ntoh<uint16_t>(&mPtr[opt * fLen + 2]);
switch(chMaskCntl) {
case 0 ... 3:
chMaskGrp0123 |= (uint64_t)chMask << (16 * chMaskCntl);
break;
case 4:
chMaskGrp45 |= (uint32_t)chMask;
break;
case 5:
// for CN500, this is just a normal channel mask
// for all other bands, the first 10 bits enable banks of 8 125kHz channels
if(this->band->bandNum == BandCN500) {
chMaskGrp45 |= (uint32_t)chMask << 16;
} else {
int bank = 0;
for(; bank < 8; bank++) {
if(chMask & ((uint16_t)1 << bank)) {
chMaskGrp0123 |= (0xFF << (8 * bank));
}
}
for(; bank < 10; bank++) {
if(chMask & ((uint16_t)1 << bank)) {
chMaskGrp45 |= (0xFF << (8 * (bank - 8)));
}
}
}
break;
case 6:
// for dynamic bands: all channels ON (that are currently defined)
// for fixed bands: all 125kHz channels ON, channel mask similar to ChMask = 4
// except for CN500: all 125kHz channels ON
// for dynamic bands: retrieve all currently defined channels
// for fixed bands: cannot store all defined channels, so select a random one from each bank
this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45);
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && this->band->bandNum != BandCN500) {
chMaskGrp45 |= (uint32_t)chMask;
}
break;
case 7:
// for fixed bands: all 125kHz channels ON, channel mask similar to ChMask = 4
// except for CN500: RFU
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && this->band->bandNum != BandCN500) {
chMaskGrp0123 = 0;
chMaskGrp45 |= (uint32_t)chMask;
}
break;
}
opt++;
}
LoRaWANNode::hton<uint64_t>(&mAdrOpt[1], chMaskGrp0123);
LoRaWANNode::hton<uint32_t>(&mAdrOpt[9], chMaskGrp45);
}
void LoRaWANNode::postprocessMacLinkAdr(uint8_t* ack, uint8_t cLen) {
uint8_t fLen = 5; // single ADR command is 5 bytes
uint8_t numOpts = cLen / fLen;
// duplicate the ACK bits of the atomic block response 'numOpts' times
// skip one, as the first response is already there
for(int opt = 1; opt < numOpts; opt++) {
ack[opt*2 + 0] = RADIOLIB_LORAWAN_MAC_LINK_ADR;
ack[opt*2 + 1] = ack[1];
}
}
int16_t LoRaWANNode::getMacCommand(uint8_t cid, LoRaWANMacCommand_t* cmd) {
for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_MAC_COMMANDS; i++) {
if(MacTable[i].cid == cid) {
memcpy((void*)cmd, (void*)&MacTable[i], sizeof(LoRaWANMacCommand_t));
return(RADIOLIB_ERR_NONE);
}
}
// didn't find this CID, check if derived class can help (if any)
int16_t state = this->derivedMacFinder(cid, cmd);
return(state);
}
int16_t LoRaWANNode::derivedMacFinder(uint8_t cid, LoRaWANMacCommand_t* cmd) {
(void)cid;
(void)cmd;
return(RADIOLIB_ERR_INVALID_CID);
}
int16_t LoRaWANNode::sendMacCommandReq(uint8_t cid) {
LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE;
int16_t state = this->getMacCommand(cid, &cmd);
RADIOLIB_ASSERT(state);
if(!cmd.user) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("You are not allowed to request this MAC command");
return(RADIOLIB_ERR_INVALID_CID);
}
// if there are already 15 MAC bytes in the uplink queue, we can't add a new one
if(fOptsUpLen >= RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The maximum size of FOpts payload was reached");
return(RADIOLIB_ERR_COMMAND_QUEUE_FULL);
}
// if this MAC command is already in the queue, silently stop
if(this->getMacPayload(cid, this->fOptsUp, this->fOptsUpLen, NULL, RADIOLIB_LORAWAN_UPLINK) == RADIOLIB_ERR_NONE) {
return(RADIOLIB_ERR_NONE);
}
state = LoRaWANNode::pushMacCommand(cid, NULL, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK);
return(state);
}
int16_t LoRaWANNode::getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt) {
uint8_t payload[2] = { 0 };
int16_t state = this->getMacPayload(RADIOLIB_LORAWAN_MAC_LINK_CHECK, this->fOptsDown, fOptsDownLen, payload, RADIOLIB_LORAWAN_DOWNLINK);
RADIOLIB_ASSERT(state);
if(margin) { *margin = payload[0]; }
if(gwCnt) { *gwCnt = payload[1]; }
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix) {
uint8_t payload[5] = { 0 };
int16_t state = this->getMacPayload(RADIOLIB_LORAWAN_MAC_DEVICE_TIME, this->fOptsDown, fOptsDownLen, payload, RADIOLIB_LORAWAN_DOWNLINK);
RADIOLIB_ASSERT(state);
if(gpsEpoch) {
*gpsEpoch = LoRaWANNode::ntoh<uint32_t>(&payload[0]);
if(returnUnix) {
uint32_t unixOffset = 315964800UL - 18UL; // 18 leap seconds since GPS epoch (Jan. 6th 1980)
*gpsEpoch += unixOffset;
}
}
if(fraction) { *fraction = payload[4]; }
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::getMacLen(uint8_t cid, uint8_t* len, uint8_t dir, bool inclusive) {
LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE;
int16_t state = this->getMacCommand(cid, &cmd);
RADIOLIB_ASSERT(state);
if(dir == RADIOLIB_LORAWAN_UPLINK) {
*len = cmd.lenUp;
} else {
*len = cmd.lenDn;
}
if(inclusive) {
*len += 1; // add one byte for CID
}
return(RADIOLIB_ERR_NONE);
}
bool LoRaWANNode::isPersistentMacCommand(uint8_t cid, uint8_t dir) {
// if this MAC command doesn't exist, it wouldn't even get into the queue, so don't care about outcome
LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE;
(void)this->getMacCommand(cid, &cmd);
// in the uplink direction, MAC payload should persist per spec
if(dir == RADIOLIB_LORAWAN_UPLINK) {
return(cmd.persist);
// in the downlink direction, MAC payload should persist if it is user-accessible
// which is the case for LinkCheck and DeviceTime
} else {
return(cmd.user);
}
return(false);
}
int16_t LoRaWANNode::pushMacCommand(uint8_t cid, uint8_t* cOcts, uint8_t* out, uint8_t* lenOut, uint8_t dir) {
uint8_t fLen = 0;
int16_t state = this->getMacLen(cid, &fLen, dir, true);
RADIOLIB_ASSERT(state);
// check if we can even append the MAC command into the buffer
if(*lenOut + fLen > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) {
return(RADIOLIB_ERR_COMMAND_QUEUE_FULL);
}
out[*lenOut] = cid; // add MAC id
memcpy(&out[*lenOut + 1], cOcts, fLen - 1); // copy payload into buffer
*lenOut += fLen; // payload + command ID
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::getMacPayload(uint8_t cid, uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t dir) {
size_t i = 0;
while(i < lenIn) {
uint8_t id = in[i];
uint8_t fLen = 0;
int16_t state = this->getMacLen(id, &fLen, dir, true);
RADIOLIB_ASSERT(state);
if(lenIn < i + fLen) {
return(RADIOLIB_ERR_INVALID_CID);
}
// if this is the requested MAC id, copy the payload over
if(id == cid) {
// only copy payload if destination is supplied
if(out) {
memcpy(out, &in[i + 1], fLen - 1);
}
return(RADIOLIB_ERR_NONE);
}
// move on to next MAC command
i += fLen;
}
return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND);
}
int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, uint8_t* inOut, uint8_t* lenInOut, uint8_t dir) {
size_t i = 0;
while(i < *lenInOut) {
uint8_t id = inOut[i];
uint8_t fLen = 0;
int16_t state = this->getMacLen(id, &fLen, dir);
RADIOLIB_ASSERT(state);
if(*lenInOut < i + fLen) {
return(RADIOLIB_ERR_INVALID_CID);
}
// if this is the requested MAC id,
if(id == cid) {
// remove it by moving the rest of the payload forward
memmove(&inOut[i], &inOut[i + fLen], *lenInOut - i - fLen);
// set the remainder of the queue to 0
memset(&inOut[i + fLen], 0, *lenInOut - i - fLen);
*lenInOut -= fLen;
return(RADIOLIB_ERR_NONE);
}
// move on to next MAC command
i += fLen;
}
return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND);
}
void LoRaWANNode::clearMacCommands(uint8_t* inOut, uint8_t* lenInOut, uint8_t dir) {
size_t i = 0;
uint8_t numDeleted = 0;
while(i < *lenInOut) {
uint8_t id = inOut[i];
uint8_t fLen = 1; // if there is an incorrect MAC command, we should at least move forward by one byte
(void)this->getMacLen(id, &fLen, dir, true);
// only clear MAC command if it should not persist until a downlink is received
if(!this->isPersistentMacCommand(id, dir)) {
// remove it by moving the rest of the payload forward
memmove(&inOut[i], &inOut[i + fLen], *lenInOut - i - fLen);
// set the remainder of the queue to 0
memset(&inOut[i + fLen], 0, *lenInOut - i - fLen);
numDeleted += fLen;
}
// move on to next MAC command
i += fLen;
}
*lenInOut -= numDeleted;
}
int16_t LoRaWANNode::setDatarate(uint8_t drUp) {
// scan through all enabled channels and check if the requested datarate is available
bool isValidDR = false;
for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
LoRaWANChannel_t *chnl = &(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i]);
if(chnl->enabled) {
if(drUp >= chnl->drMin && drUp <= chnl->drMax) {
isValidDR = true;
break;
}
}
}
if(!isValidDR) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("No defined channel allows datarate %d", drUp);
return(RADIOLIB_ERR_INVALID_DATA_RATE);
}
uint8_t cOcts[1];
uint8_t cAck[1];
uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR;
uint8_t cLen = 1; // only apply Dr/Tx field
cOcts[0] = (drUp << 4); // set requested datarate
cOcts[0] |= 0x0F; // keep Tx Power the same
(void)execMacCommand(cid, cOcts, cLen, cAck);
// check if ACK is set for Datarate
if(!(cAck[0] & 0x02)) {
return(RADIOLIB_ERR_INVALID_DATA_RATE);
}
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::setTxPower(int8_t txPower) {
// only allow values within the band's (or MAC state) maximum
if(txPower > this->txPowerMax) {
return(RADIOLIB_ERR_INVALID_OUTPUT_POWER);
}
// Tx Power is set in steps of two
// the selected value is rounded down to nearest multiple of two away from txPowerMax
// e.g. on EU868, max is 16; if 13 is selected then we set to 12
uint8_t numSteps = (this->txPowerMax - txPower + 1) / (-RADIOLIB_LORAWAN_POWER_STEP_SIZE_DBM);
uint8_t cOcts[1];
uint8_t cAck[1];
uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR;
uint8_t cLen = 1; // only apply Dr/Tx field
cOcts[0] = 0xF0; // keep datarate the same
cOcts[0] |= numSteps; // set requested Tx Power
(void)execMacCommand(cid, cOcts, cLen, cAck);
// check if ACK is set for Tx Power
if(!(cAck[0] & 0x04)) {
return(RADIOLIB_ERR_INVALID_OUTPUT_POWER);
}
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::setRx2Dr(uint8_t dr) {
// this can only be configured in ABP mode
if(this->lwMode != RADIOLIB_LORAWAN_MODE_ABP) {
return(RADIOLIB_ERR_INVALID_MODE);
}
// can only configure different datarate for dynamic bands
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) {
return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE);
}
// check if datarate is available in the selected band
if(this->band->dataRates[dr] == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
return(RADIOLIB_ERR_INVALID_DATA_RATE);
}
// find and check if the datarate is available for this radio module
DataRate_t dataRate;
int16_t state = findDataRate(dr, &dataRate);
RADIOLIB_ASSERT(state);
// passed all checks, so configure the datarate
this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr = dr;
return(state);
}
void LoRaWANNode::setADR(bool enable) {
this->adrEnabled = enable;
}
void LoRaWANNode::setDutyCycle(bool enable, RadioLibTime_t msPerHour) {
this->dutyCycleEnabled = enable;
if(!enable) {
this->dutyCycle = 0;
}
if(msPerHour == 0) {
this->dutyCycle = this->band->dutyCycle;
} else {
this->dutyCycle = msPerHour;
}
}
void LoRaWANNode::setDwellTime(bool enable, RadioLibTime_t msPerUplink) {
this->dwellTimeEnabledUp = enable;
if(msPerUplink == 0) {
this->dwellTimeUp = this->band->dwellTimeUp;
} else {
this->dwellTimeUp = msPerUplink;
}
}
// A user may enable CSMA to provide frames an additional layer of protection from interference.
// https://resources.lora-alliance.org/technical-recommendations/tr013-1-0-0-csma
void LoRaWANNode::setCSMA(bool csmaEnabled, uint8_t maxChanges, uint8_t backoffMax, uint8_t difsSlots) {
this->csmaEnabled = csmaEnabled;
if(csmaEnabled) {
this->maxChanges = maxChanges;
this->difsSlots = difsSlots;
this->backoffMax = backoffMax;
} else {
// disable all values
this->maxChanges = 0;
this->difsSlots = 0;
this->backoffMax = 0;
}
}
void LoRaWANNode::setDeviceStatus(uint8_t battLevel) {
this->battLevel = battLevel;
}
void LoRaWANNode::scheduleTransmission(RadioLibTime_t tUplink) {
this->tUplink = tUplink;
}
// return fCnt of last uplink; also return 0 if no uplink occured yet
uint32_t LoRaWANNode::getFCntUp() {
if(this->fCntUp == 0) {
return(0);
}
return(this->fCntUp - 1);
}
uint32_t LoRaWANNode::getNFCntDown() {
return(this->nFCntDown);
}
uint32_t LoRaWANNode::getAFCntDown() {
return(this->aFCntDown);
}
void LoRaWANNode::resetFCntDown() {
this->nFCntDown = 0;
this->aFCntDown = 0;
}
uint32_t LoRaWANNode::getDevAddr() {
return(this->devAddr);
}
RadioLibTime_t LoRaWANNode::getLastToA() {
return(this->lastToA);
}
int16_t LoRaWANNode::setPhyProperties(const LoRaWANChannel_t* chnl, uint8_t dir, int8_t pwr, size_t pre) {
// set the physical layer configuration
int16_t state = this->phyLayer->standby();
if(state != RADIOLIB_ERR_NONE) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Failed to set radio into standby - is it connected?");
return(state);
}
// get the currently configured modem from the radio
ModemType_t modem;
state = this->phyLayer->getModem(&modem);
RADIOLIB_ASSERT(state);
// set modem-dependent functions
switch(this->band->dataRates[chnl->dr] & RADIOLIB_LORAWAN_DATA_RATE_MODEM) {
case(RADIOLIB_LORAWAN_DATA_RATE_LORA):
if(modem != ModemType_t::LoRa) {
state = this->phyLayer->setModem(ModemType_t::LoRa);
RADIOLIB_ASSERT(state);
}
modem = ModemType_t::LoRa;
// downlink messages are sent with inverted IQ
if(dir == RADIOLIB_LORAWAN_DOWNLINK) {
state = this->phyLayer->invertIQ(true);
} else {
state = this->phyLayer->invertIQ(false);
}
RADIOLIB_ASSERT(state);
break;
case(RADIOLIB_LORAWAN_DATA_RATE_FSK):
if(modem != ModemType_t::FSK) {
state = this->phyLayer->setModem(ModemType_t::FSK);
RADIOLIB_ASSERT(state);
}
modem = ModemType_t::FSK;
state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0);
RADIOLIB_ASSERT(state);
state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING);
RADIOLIB_ASSERT(state);
break;
case(RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS):
if(modem != ModemType_t::LRFHSS) {
state = this->phyLayer->setModem(ModemType_t::LRFHSS);
RADIOLIB_ASSERT(state);
}
modem = ModemType_t::LRFHSS;
break;
default:
return(RADIOLIB_ERR_UNSUPPORTED);
}
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("");
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: Frequency = %7.3f MHz, TX = %d dBm", chnl->freq / 10000.0, pwr);
state = this->phyLayer->setFrequency(chnl->freq / 10000.0);
RADIOLIB_ASSERT(state);
// at this point, assume that Tx power value is already checked, so ignore the return value
// this call is only used to clip a value that is higher than the module supports
(void)this->phyLayer->checkOutputPower(pwr, &pwr);
state = this->phyLayer->setOutputPower(pwr);
RADIOLIB_ASSERT(state);
DataRate_t dr;
state = findDataRate(chnl->dr, &dr);
RADIOLIB_ASSERT(state);
state = this->phyLayer->setDataRate(dr);
RADIOLIB_ASSERT(state);
// this only needs to be done once-ish
uint8_t syncWord[4] = { 0 };
uint8_t syncWordLen = 0;
size_t preLen = 0;
switch(modem) {
case(ModemType_t::FSK): {
preLen = 8*RADIOLIB_LORAWAN_GFSK_PREAMBLE_LEN;
syncWord[0] = (uint8_t)(RADIOLIB_LORAWAN_GFSK_SYNC_WORD >> 16);
syncWord[1] = (uint8_t)(RADIOLIB_LORAWAN_GFSK_SYNC_WORD >> 8);
syncWord[2] = (uint8_t)RADIOLIB_LORAWAN_GFSK_SYNC_WORD;
syncWordLen = 3;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("FSK: BR = %4.1f, FD = %4.1f kHz",
dr.fsk.bitRate, dr.fsk.freqDev);
} break;
case(ModemType_t::LoRa): {
preLen = RADIOLIB_LORAWAN_LORA_PREAMBLE_LEN;
syncWord[0] = RADIOLIB_LORAWAN_LORA_SYNC_WORD;
syncWordLen = 1;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LoRa: SF = %d, BW = %5.1f kHz, CR = 4/%d, IQ: %c",
dr.lora.spreadingFactor, dr.lora.bandwidth, dr.lora.codingRate, dir ? 'D' : 'U');
} break;
case(ModemType_t::LRFHSS): {
syncWord[0] = (uint8_t)(RADIOLIB_LORAWAN_LR_FHSS_SYNC_WORD >> 24);
syncWord[1] = (uint8_t)(RADIOLIB_LORAWAN_LR_FHSS_SYNC_WORD >> 16);
syncWord[2] = (uint8_t)(RADIOLIB_LORAWAN_LR_FHSS_SYNC_WORD >> 8);
syncWord[3] = (uint8_t)RADIOLIB_LORAWAN_LR_FHSS_SYNC_WORD;
syncWordLen = 4;
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LR-FHSS: BW = 0x%02x, CR = 0x%02x kHz, grid = %c",
dr.lrfhss.bw, dr.lrfhss.cr, dr.lrFhss.narrowGrid ? 'N' : 'W');
} break;
default:
return(RADIOLIB_ERR_WRONG_MODEM);
}
state = this->phyLayer->setSyncWord(syncWord, syncWordLen);
RADIOLIB_ASSERT(state);
// if a preamble length is supplied, overrule the 'calculated' preamble length
if(pre) {
preLen = pre;
}
if(modem != ModemType_t::LRFHSS) {
state = this->phyLayer->setPreambleLength(preLen);
}
return(state);
}
// The following function implements LMAC, a CSMA scheme for LoRa as specified
// in the LoRa Alliance Technical Recommendation #13.
bool LoRaWANNode::csmaChannelClear(uint8_t difs, uint8_t numBackoff) {
// DIFS phase: perform #DIFS CAD operations
uint16_t numCads = 0;
for (; numCads < difs; numCads++) {
if (!this->cadChannelClear()) {
return(false);
}
}
// BO phase: perform #numBackoff additional CAD operations
for (; numCads < difs + numBackoff; numCads++) {
if (!this->cadChannelClear()) {
return(false);
}
}
// none of the CADs showed activity, so all clear
return(true);
}
bool LoRaWANNode::cadChannelClear() {
int16_t state = this->phyLayer->scanChannel();
// if activity was detected, channel is not clear
if ((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) {
return(false);
}
return(true);
}
void LoRaWANNode::getChannelPlanMask(uint64_t* chMaskGrp0123, uint32_t* chMaskGrp45) {
// clear masks in case anything was set
*chMaskGrp0123 = 0;
*chMaskGrp45 = 0;
uint8_t numCh = this->getAvailableChannels(NULL);
// if there are any channels selected, create the mask from those channels
if(numCh > 0) {
for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
uint8_t idx = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx;
if(idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) {
if(idx < 64) {
*chMaskGrp0123 |= ((uint64_t)1 << idx);
} else {
*chMaskGrp45 |= ((uint32_t)1 << (idx - 64));
}
}
}
return;
}
// it should not happen that no channels are set for dynamic band
// but in that case, simply return
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
return;
} else { // bandType == RADIOLIB_LORAWAN_BAND_FIXED
// if a subband is set, we can set the channel indices straight from subband
if(this->subBand > 0 && this->subBand <= 8) {
// for sub band 1-8, set bank of 8 125kHz + single 500kHz channel
*chMaskGrp0123 |= 0xFF << ((this->subBand - 1) * 8);
*chMaskGrp45 |= 0x01 << ((this->subBand - 1) * 8);
} else if(this->subBand > 8 && this->subBand <= 12) {
// CN500 only: for sub band 9-12, set bank of 8 125kHz channels
*chMaskGrp45 |= 0xFF << ((this->subBand - 9) * 8);
} else {
// if subband is set to 0, all 125kHz channels are enabled.
// however, we can 'only' store 16 channels, so we don't use all channels at once.
// instead, we select a random channel from each bank of 8 channels + 1 from second plan.
uint8_t num125kHz = this->band->txSpans[0].numChannels;
uint8_t numBanks = num125kHz / 8;
for(uint8_t bank = 0; bank < numBanks; bank++) {
uint8_t bankIdx = this->phyLayer->random(8);
uint8_t idx = bank * 8 + bankIdx;
if(idx < 64) {
*chMaskGrp0123 |= ((uint64_t)1 << idx);
} else {
*chMaskGrp45 |= ((uint32_t)1 << (idx - 64));
}
}
// the 500 kHz channels are in the usual channel plan however
// these are the channel indices 64-71 for bands other than CN500
if(this->band->bandNum != BandCN500) {
for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
uint8_t idx = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx;
if(idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE && idx >= 64) {
*chMaskGrp45 |= ((uint32_t)1 << (idx - 64));
}
}
}
}
}
}
void LoRaWANNode::selectChannelPlanDyn(bool joinRequest) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Setting up dynamic channels");
size_t num = 0;
// copy the default defined channels into the first slots (where Tx = Rx)
for(; num < 3 && this->band->txFreqs[num].enabled; num++) {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = this->band->txFreqs[num];
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][num] = this->band->txFreqs[num];
}
// if we're about to send a JoinRequest, copy the JoinRequest channels to the next slots
if(joinRequest) {
size_t numJR = 0;
for(; numJR < 3 && this->band->txJoinReq[num].enabled; numJR++, num++) {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = this->band->txFreqs[num];
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][num] = this->band->txFreqs[num];
}
}
// clear all remaining channels
for(; num < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; num++) {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = RADIOLIB_LORAWAN_CHANNEL_NONE;
}
// make sure the Rx2 settings are back to this band's default
this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2;
// make all enabled channels available for uplink selection
this->setAvailableChannels(0xFFFF);
#if RADIOLIB_DEBUG_PROTOCOL
this->printChannels();
#endif
}
// setup a subband and its corresponding JoinRequest datarate
// WARNING: subBand starts at 1 (corresponds to all populair schemes)
void LoRaWANNode::selectChannelPlanFix() {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Setting up fixed channels (subband %d)", this->subBand);
// clear all existing channels
for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE;
}
// get channel masks for this subband
uint64_t chMaskGrp0123 = 0;
uint32_t chMaskGrp45 = 0;
this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45);
// apply channel mask
this->applyChannelMask(chMaskGrp0123, chMaskGrp45);
// make sure the Rx2 settings are back to this band's default
this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2;
// make all enabled channels available for uplink selection
this->setAvailableChannels(0xFFFF);
}
uint8_t LoRaWANNode::getAvailableChannels(uint16_t* chMask) {
uint8_t num = 0;
uint16_t mask = 0;
uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr;
for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
// if channel is available and usable for current datarate, set corresponding bit
if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available) {
if(currentDr >= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin &&
currentDr <= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax) {
num++;
mask |= (0x0001 << i);
}
}
}
if(chMask) {
*chMask = mask;
}
return(num);
}
void LoRaWANNode::setAvailableChannels(uint16_t mask) {
for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
// if channel is enabled, set to available
if(mask & (0x0001 << i) && this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available = true;
} else {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available = false;
}
}
}
int16_t LoRaWANNode::selectChannels() {
uint16_t chMask = 0x0000;
uint8_t numChannels = this->getAvailableChannels(&chMask);
// if there are no available channels, try resetting them all to available
if(numChannels == 0) {
this->setAvailableChannels(0xFFFF);
numChannels = this->getAvailableChannels(&chMask);
// if there are still no channels available, give up
if(numChannels == 0) {
return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE);
}
}
// select a random value within the number of possible channels
int chRand = this->phyLayer->random(numChannels);
// retrieve the index of this channel by looping through the channel mask
int chIdx = -1;
while(chRand >= 0) {
chIdx++;
if(chMask & 0x0001) {
chRand--;
}
chMask >>= 1;
}
// as we are now going to use this channel, mark unavailable for next uplink
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][chIdx].available = false;
uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr;
this->channels[RADIOLIB_LORAWAN_UPLINK] = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][chIdx];
this->channels[RADIOLIB_LORAWAN_UPLINK].dr = currentDr;
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
// for dynamic bands, the downlink channel is the one matched to the uplink channel
this->channels[RADIOLIB_LORAWAN_DOWNLINK] = this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][chIdx];
} else { // RADIOLIB_LORAWAN_BAND_FIXED
// for fixed bands, the downlink channel is the uplink channel ID `modulo` number of downlink channels
LoRaWANChannel_t channelDn = RADIOLIB_LORAWAN_CHANNEL_NONE;
channelDn.enabled = true;
channelDn.idx = this->channels[RADIOLIB_LORAWAN_UPLINK].idx % this->band->rx1Span.numChannels;
channelDn.freq = this->band->rx1Span.freqStart + channelDn.idx*this->band->rx1Span.freqStep;
channelDn.drMin = this->band->rx1Span.drMin;
channelDn.drMax = this->band->rx1Span.drMax;
this->channels[RADIOLIB_LORAWAN_DOWNLINK] = channelDn;
}
uint8_t rx1Dr = this->band->rx1DrTable[currentDr][this->rx1DrOffset];
// if downlink dwelltime is enabled, datarate < 2 cannot be used, so clip to 2
// only in use on AS923_x bands
if(this->dwellTimeEnabledDn && rx1Dr < 2) {
rx1Dr = 2;
}
this->channels[RADIOLIB_LORAWAN_DOWNLINK].dr = rx1Dr;
return(RADIOLIB_ERR_NONE);
}
bool LoRaWANNode::applyChannelMask(uint64_t chMaskGrp0123, uint32_t chMaskGrp45) {
if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) {
for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
if(chMaskGrp0123 & ((uint64_t)1 << i)) {
// if it should be enabled but is not currently defined, stop immediately
if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx == RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) {
return(false);
}
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled = true;
} else {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled = false;
}
}
} else { // bandType == RADIOLIB_LORAWAN_BAND_FIXED
// full channel mask received, so clear all existing channels
LoRaWANChannel_t chnl = RADIOLIB_LORAWAN_CHANNEL_NONE;
for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i] = chnl;
}
int num = 0;
uint8_t spanNum = 0;
int chNum = 0;
int chOfs = 0;
for(; chNum < 64; chNum++) {
if(chMaskGrp0123 & ((uint64_t)1 << chNum)) {
chnl.enabled = true;
chnl.idx = chNum;
chnl.freq = this->band->txSpans[spanNum].freqStart + chNum*this->band->txSpans[spanNum].freqStep;
chnl.drMin = this->band->txSpans[spanNum].drMin;
chnl.drMax = this->band->txSpans[spanNum].drMax;
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num++] = chnl;
}
}
if(this->band->numTxSpans > 1) {
spanNum += 1;
chNum = 0;
chOfs = 64;
}
for(; chNum < this->band->txSpans[spanNum].numChannels; chNum++) {
if(chMaskGrp45 & ((uint32_t)1 << chNum)) {
chnl.enabled = true;
chnl.idx = chNum + chOfs;
chnl.freq = this->band->txSpans[spanNum].freqStart + chNum*this->band->txSpans[spanNum].freqStep;
chnl.drMin = this->band->txSpans[spanNum].drMin;
chnl.drMax = this->band->txSpans[spanNum].drMax;
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num++] = chnl;
}
}
}
#if RADIOLIB_DEBUG_PROTOCOL
this->printChannels();
#endif
return(true);
}
#if RADIOLIB_DEBUG_PROTOCOL
void LoRaWANNode::printChannels() {
for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) {
if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)",
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx,
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled,
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].freq / 10000.0,
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin,
this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax,
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].idx,
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].enabled,
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].freq / 10000.0,
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].drMin,
this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].drMax
);
}
}
}
#endif
uint32_t LoRaWANNode::generateMIC(uint8_t* msg, size_t len, uint8_t* key) {
if((msg == NULL) || (len == 0)) {
return(0);
}
RadioLibAES128Instance.init(key);
uint8_t cmac[RADIOLIB_AES128_BLOCK_SIZE];
RadioLibAES128Instance.generateCMAC(msg, len, cmac);
return(((uint32_t)cmac[0]) | ((uint32_t)cmac[1] << 8) | ((uint32_t)cmac[2] << 16) | ((uint32_t)cmac[3]) << 24);
}
bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) {
if((msg == NULL) || (len < sizeof(uint32_t))) {
return(0);
}
// extract MIC from the message
uint32_t micReceived = LoRaWANNode::ntoh<uint32_t>(&msg[len - sizeof(uint32_t)]);
// calculate the expected value and compare
uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key);
if(micCalculated != micReceived) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("MIC mismatch, expected %08lx, got %08lx",
(unsigned long)micCalculated, (unsigned long)micReceived);
return(false);
}
return(true);
}
// given an airtime in milliseconds, calculate the minimum uplink interval
// to adhere to a given dutyCycle
RadioLibTime_t LoRaWANNode::dutyCycleInterval(RadioLibTime_t msPerHour, RadioLibTime_t airtime) {
if(msPerHour == 0 || airtime == 0) {
return(0);
}
RadioLibTime_t oneHourInMs = (RadioLibTime_t)60 * (RadioLibTime_t)60 * (RadioLibTime_t)1000;
float numPackets = msPerHour / airtime;
RadioLibTime_t delayMs = oneHourInMs / numPackets + 1; // + 1 to prevent rounding problems
return(delayMs);
}
RadioLibTime_t LoRaWANNode::timeUntilUplink() {
Module* mod = this->phyLayer->getMod();
RadioLibTime_t nextUplink = this->rxDelayStart + dutyCycleInterval(this->dutyCycle, this->lastToA);
if(mod->hal->millis() > nextUplink){
return(0);
}
return(nextUplink - mod->hal->millis() + 1);
}
uint8_t LoRaWANNode::getMaxPayloadLen() {
// configure the uplink channel properties
this->setPhyProperties(&this->channels[RADIOLIB_LORAWAN_UPLINK],
RADIOLIB_LORAWAN_UPLINK,
this->txPowerMax - 2*this->txPowerSteps);
uint8_t minLen = 0;
uint8_t maxLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr];
if(this->TS011) {
maxLen = RADIOLIB_MIN(maxLen, 222); // payload length is limited to N=222 if under repeater
}
maxLen += 13; // mandatory FHDR is 12/13 bytes
// if not limited by dwell-time, just return maximum
if(!this->dwellTimeEnabledUp) {
// subtract FHDR (13 bytes) as well as any FOpts
return(maxLen - 13 - this->fOptsUpLen);
}
// fast exit in case upper limit is already good
if(this->phyLayer->getTimeOnAir(maxLen) / 1000 <= this->dwellTimeUp) {
// subtract FHDR (13 bytes) as well as any FOpts
return(maxLen - 13 - this->fOptsUpLen);
}
// do some binary search to find maximum allowed length
uint8_t curLen = (minLen + maxLen) / 2;
while(curLen != minLen && curLen != maxLen) {
if(this->phyLayer->getTimeOnAir(curLen) / 1000 > this->dwellTimeUp) {
maxLen = curLen;
} else {
minLen = curLen;
}
curLen = (minLen + maxLen) / 2;
}
// subtract FHDR (13 bytes) as well as any FOpts
return(curLen - 13 - this->fOptsUpLen);
}
int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) {
int16_t state = this->phyLayer->standby();
if(state != RADIOLIB_ERR_NONE) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Failed to set radio into standby - is it connected?");
return(state);
}
uint8_t dataRateBand = this->band->dataRates[dr];
switch(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_MODEM) {
case(RADIOLIB_LORAWAN_DATA_RATE_LORA):
dataRate->lora.spreadingFactor = ((dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_SF) >> 3) + 7;
switch(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_BW) {
case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ):
dataRate->lora.bandwidth = 125.0;
break;
case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ):
dataRate->lora.bandwidth = 250.0;
break;
case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ):
dataRate->lora.bandwidth = 500.0;
break;
default:
return(RADIOLIB_ERR_UNSUPPORTED);
}
dataRate->lora.codingRate = 5;
break;
case(RADIOLIB_LORAWAN_DATA_RATE_FSK):
dataRate->fsk.bitRate = 50;
dataRate->fsk.freqDev = 25;
break;
case(RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS):
// not yet supported by DataRate_t
return(RADIOLIB_ERR_UNSUPPORTED);
default:
return(RADIOLIB_ERR_UNSUPPORTED);
}
state = this->phyLayer->checkDataRate(*dataRate);
return(state);
}
void LoRaWANNode::processAES(const uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fCnt, uint8_t dir, uint8_t ctrId, bool counter) {
// figure out how many encryption blocks are there
size_t numBlocks = len/RADIOLIB_AES128_BLOCK_SIZE;
if(len % RADIOLIB_AES128_BLOCK_SIZE) {
numBlocks++;
}
// generate the encryption blocks
uint8_t encBuffer[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
uint8_t encBlock[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
encBlock[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC;
encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_ID_POS] = ctrId;
encBlock[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = dir;
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fCnt);
// now encrypt the input
// on downlink frames, this has a decryption effect because server actually "decrypts" the plaintext
size_t remLen = len;
for(size_t i = 0; i < numBlocks; i++) {
if(counter) {
encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS] = i + 1;
}
// encrypt the buffer
RadioLibAES128Instance.init(key);
RadioLibAES128Instance.encryptECB(encBlock, RADIOLIB_AES128_BLOCK_SIZE, encBuffer);
// now xor the buffer with the input
size_t xorLen = remLen;
if(xorLen > RADIOLIB_AES128_BLOCK_SIZE) {
xorLen = RADIOLIB_AES128_BLOCK_SIZE;
}
for(uint8_t j = 0; j < xorLen; j++) {
out[i*RADIOLIB_AES128_BLOCK_SIZE + j] = in[i*RADIOLIB_AES128_BLOCK_SIZE + j] ^ encBuffer[j];
}
remLen -= xorLen;
}
}
int16_t LoRaWANNode::checkBufferCommon(uint8_t *buffer, uint16_t size) {
// check if there are actually values in the buffer
size_t i = 0;
for(; i < size; i++) {
if(buffer[i]) {
break;
}
}
if(i == size) {
return(RADIOLIB_ERR_NETWORK_NOT_JOINED);
}
// check integrity of the whole buffer (compare checksum to included checksum)
uint16_t checkSum = LoRaWANNode::checkSum16(buffer, size - 2);
uint16_t signature = LoRaWANNode::ntoh<uint16_t>(&buffer[size - 2]);
if(signature != checkSum) {
RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Calculated checksum: %04x, expected: %04x", checkSum, signature);
return(RADIOLIB_ERR_CHECKSUM_MISMATCH);
}
return(RADIOLIB_ERR_NONE);
}
uint16_t LoRaWANNode::checkSum16(const uint8_t *key, uint16_t keyLen) {
uint16_t checkSum = 0;
for(uint16_t i = 0; i < keyLen; i += 2) {
uint16_t word = (key[i] << 8);
if(i + 1 < keyLen) {
word |= key[i + 1];
}
checkSum ^= word;
}
return(checkSum);
}
#endif