[LoRaWAN] Added support for 1.1 and downlink (#58)

This commit is contained in:
jgromes 2023-08-12 19:49:37 +02:00
parent d34902ee46
commit cd9ac9916c
5 changed files with 719 additions and 169 deletions

View file

@ -60,10 +60,10 @@ void setup() {
// the end device in TTN and perform the join procedure again!
//node.wipe();
// application identifier - in LoRaWAN 1.1, it is also called joinEUI
// application identifier - pre-LoRaWAN 1.1.0, this was called appEUI
// when adding new end device in TTN, you will have to enter this number
// you can pick any number you want, but it has to be unique
uint64_t appEUI = 0x12AD1011B0C0FFEE;
uint64_t joinEUI = 0x12AD1011B0C0FFEE;
// device identifier - this number can be anything
// when adding new end device in TTN, you can generate this number,
@ -76,10 +76,14 @@ void setup() {
const char nwkKey[] = "topSecretKey1234";
const char appKey[] = "aDifferentKeyABC";
// prior to LoRaWAN 1.1.0, only a single "nwkKey" is used
// when connecting to LoRaWAN 1.0 network, "appKey" will be disregarded
// and can be set to NULL
// now we can start the activation
// this can take up to 20 seconds, and requires a LoRaWAN gateway in range
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
state = node.beginOTAA(appEUI, devEUI, (uint8_t*)nwkKey, (uint8_t*)appKey);
state = node.beginOTAA(joinEUI, devEUI, (uint8_t*)nwkKey, (uint8_t*)appKey);
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
} else {
@ -110,8 +114,8 @@ int count = 0;
void loop() {
// send uplink to port 10
Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
String str = "Hello World! #" + String(count++);
int state = node.uplink(str, 10);
String strUp = "Hello World! #" + String(count++);
int state = node.uplink(strUp, 10);
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
} else {
@ -119,6 +123,43 @@ void loop() {
Serial.println(state);
}
// wait before sending another one
// after uplink, you can call downlink(),
// to receive any possible reply from the server
// this function must be called within a few seconds
// after uplink to receive the downlink!
Serial.print(F("[LoRaWAN] Waiting for downlink ... "));
String strDown;
state = node.downlink(strDown);
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
// print data of the packet
Serial.print(F("[LoRaWAN] Data:\t\t"));
Serial.println(strDown);
// print RSSI (Received Signal Strength Indicator)
Serial.print(F("[LoRaWAN] RSSI:\t\t"));
Serial.print(radio.getRSSI());
Serial.println(F(" dBm"));
// print SNR (Signal-to-Noise Ratio)
Serial.print(F("[LoRaWAN] SNR:\t\t"));
Serial.print(radio.getSNR());
Serial.println(F(" dB"));
// print frequency error
Serial.print(F("[LoRaWAN] Frequency error:\t"));
Serial.print(radio.getFrequencyError());
Serial.println(F(" Hz"));
} else if(state == RADIOLIB_ERR_RX_TIMEOUT) {
Serial.println(F("timeout!"));
} else {
Serial.print(F("failed, code "));
Serial.println(state);
}
// wait before sending another packet
delay(10000);
}

View file

@ -72,6 +72,10 @@ void setup() {
const char nwkSKey[] = "topSecretKey1234";
const char appSKey[] = "aDifferentKeyABC";
// prior to LoRaWAN 1.1.0, only a single "nwkKey" is used
// when connecting to LoRaWAN 1.0 network, "appKey" will be disregarded
// and can be set to NULL
// start the device by directly providing the encryption keys and device address
Serial.print(F("[LoRaWAN] Attempting over-the-air activation ... "));
state = node.beginAPB(devAddr, (uint8_t*)nwkSKey, (uint8_t*)appSKey);
@ -105,8 +109,8 @@ int count = 0;
void loop() {
// send uplink to port 10
Serial.print(F("[LoRaWAN] Sending uplink packet ... "));
String str = "Hello World! #" + String(count++);
int state = node.uplink(str, 10);
String strUp = "Hello World! #" + String(count++);
int state = node.uplink(strUp, 10);
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
} else {
@ -114,6 +118,43 @@ void loop() {
Serial.println(state);
}
// wait before sending another one
// after uplink, you can call downlink(),
// to receive any possible reply from the server
// this function must be called within a few seconds
// after uplink to receive the downlink!
Serial.print(F("[LoRaWAN] Waiting for downlink ... "));
String strDown;
state = node.downlink(strDown);
if(state == RADIOLIB_ERR_NONE) {
Serial.println(F("success!"));
// print data of the packet
Serial.print(F("[LoRaWAN] Data:\t\t"));
Serial.println(strDown);
// print RSSI (Received Signal Strength Indicator)
Serial.print(F("[LoRaWAN] RSSI:\t\t"));
Serial.print(radio.getRSSI());
Serial.println(F(" dBm"));
// print SNR (Signal-to-Noise Ratio)
Serial.print(F("[LoRaWAN] SNR:\t\t"));
Serial.print(radio.getSNR());
Serial.println(F(" dB"));
// print frequency error
Serial.print(F("[LoRaWAN] Frequency error:\t"));
Serial.print(radio.getFrequencyError());
Serial.println(F(" Hz"));
} else if(state == RADIOLIB_ERR_RX_TIMEOUT) {
Serial.println(F("timeout!"));
} else {
Serial.print(F("failed, code "));
Serial.println(state);
}
// wait before sending another packet
delay(10000);
}

View file

@ -292,6 +292,7 @@ wipe KEYWORD2
beginOTAA KEYWORD2
beginAPB KEYWORD2
uplink KEYWORD2
downlink KEYWORD2
configureChannel KEYWORD2
#######################################

View file

@ -15,6 +15,17 @@ static void LoRaWANNodeOnDownlink(void) {
downlinkReceived = true;
}
// flag to indicate whether channel scan operation is complete
static volatile bool scanFlag = false;
// interrupt service routine to handle downlinks automatically
#if defined(ESP8266) || defined(ESP32)
IRAM_ATTR
#endif
static void LoRaWANNodeOnChannelScan(void) {
scanFlag = true;
}
LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band) {
this->phyLayer = phy;
this->band = band;
@ -34,12 +45,11 @@ int16_t LoRaWANNode::begin() {
Module* mod = this->phyLayer->getMod();
if(mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) != RADIOLIB_LORAWAN_MAGIC) {
// the magic value is not set, user will have to do perform the join procedure
return(RADIOLIB_ERR_CHIP_NOT_FOUND);
return(RADIOLIB_ERR_NETWORK_NOT_JOINED);
}
// pull all needed information from persistent storage
this->devAddr = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_ADDR_ID);
RADIOLIB_DEBUG_PRINTLN("devAddr = 0x%08x", this->devAddr);
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_APP_S_KEY_ID), this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FNWK_SINT_KEY_ID), this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
mod->hal->readPersistentStorage(mod->hal->getPersistentAddr(RADIOLIB_PERSISTENT_PARAM_LORAWAN_SNWK_SINT_KEY_ID), this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
@ -47,7 +57,7 @@ int16_t LoRaWANNode::begin() {
return(RADIOLIB_ERR_NONE);
}
int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force) {
int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force) {
// check if we actually need to send the join request
Module* mod = this->phyLayer->getMod();
if(!force && (mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_MAGIC_ID) == RADIOLIB_LORAWAN_MAGIC)) {
@ -62,14 +72,13 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
// get dev nonce from persistent storage and increment it
uint16_t devNonce = mod->hal->getPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID);
mod->hal->setPersistentParameter<uint16_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_DEV_NONCE_ID, devNonce + 1);
RADIOLIB_DEBUG_PRINTLN("devNonce = %d", devNonce);
// build the join-request message
uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN];
// set the packet fields
joinRequestMsg[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
LoRaWANNode::hton<uint64_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], appEUI);
LoRaWANNode::hton<uint64_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], joinEUI);
LoRaWANNode::hton<uint64_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], devEUI);
LoRaWANNode::hton<uint16_t>(&joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonce);
@ -85,6 +94,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink);
// downlink messages are sent with inverted IQ
// TODO use downlink() for this
if(!this->FSK) {
state = this->phyLayer->invertIQ(true);
RADIOLIB_ASSERT(state);
@ -122,7 +132,7 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
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_PRINTLN("joinAccept reply length mismatch, expected %luB got %luB", RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN, lenRx);
return(RADIOLIB_ERR_RX_TIMEOUT);
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// read the packet
@ -136,30 +146,59 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
// check reply message type
if((joinAcceptMsgEnc[0] & RADIOLIB_LORAWAN_MHDR_MTYPE_MASK) != RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT) {
RADIOLIB_DEBUG_PRINTLN("joinAccept reply message type invalid, expected 0x%02x got 0x%02x", RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT, joinAcceptMsgEnc[0]);
return(RADIOLIB_ERR_RX_TIMEOUT);
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 encrpyted
// the first byte is the MAC header which is not encrypted
uint8_t joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN];
joinAcceptMsg[0] = joinAcceptMsgEnc[0];
RadioLibAES128Instance.init(nwkKey);
RadioLibAES128Instance.encryptECB(&joinAcceptMsgEnc[1], RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - 1, &joinAcceptMsg[1]);
//Module::hexdump(joinAcceptMsg, RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN);
//Module::hexdump(joinAcceptMsg, lenRx);
// check LoRaWAN revision (the MIC verification depends on this)
uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS];
if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) {
// 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], devEUI);
RadioLibAES128Instance.init(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], joinEUI);
LoRaWANNode::hton<uint16_t>(&micBuff[9], devNonce);
memcpy(&micBuff[11], joinAcceptMsg, lenRx);
//Module::hexdump(micBuff, lenRx + 11);
if(!verifyMIC(micBuff, lenRx + 11, this->jSIntKey)) {
return(RADIOLIB_ERR_CRC_MISMATCH);
}
} else {
// 1.0 version
if(!verifyMIC(joinAcceptMsg, lenRx, nwkKey)) {
return(RADIOLIB_ERR_CRC_MISMATCH);
}
// verify MIC
if(!verifyMIC(joinAcceptMsg, lenRx, nwkKey)) {
return(RADIOLIB_ERR_CRC_MISMATCH);
}
// parse the contents
uint32_t joinNonce = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3);
uint32_t homeNetId = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3);
this->devAddr = LoRaWANNode::ntoh<uint32_t>(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS]);
uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS];
this->rxDelay = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS];
this->rxDelays[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]*1000;
if(this->rxDelays[0] == 0) {
this->rxDelays[0] = RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS;
}
this->rxDelays[1] = this->rxDelays[0] + 1000;
// process CFlist if present
if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) {
@ -174,35 +213,59 @@ int16_t LoRaWANNode::beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey
} else {
// TODO list of masks
RADIOLIB_DEBUG_PRINTLN("CFlist masks not supported (yet)");
return(RADIOLIB_ERR_UNSUPPORTED_ENCODING);
return(RADIOLIB_ERR_UNSUPPORTED);
}
}
RADIOLIB_DEBUG_PRINTLN("joinNonce = %lu", joinNonce);
RADIOLIB_DEBUG_PRINTLN("homeNetId = %lu", homeNetId);
RADIOLIB_DEBUG_PRINTLN("devAddr = 0x%08x", devAddr);
RADIOLIB_DEBUG_PRINTLN("dlSettings = 0x%02x", dlSettings);
RADIOLIB_DEBUG_PRINTLN("rxDelay = %d", this->rxDelay);
// prepare buffer for key derivation
uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], joinNonce, 3);
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3);
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonce);
// check protocol version (1.0 vs 1.1)
if(dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) {
// TODO implement 1.1
// 1.1 version, derive the keys
LoRaWANNode::hton<uint64_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS], joinEUI);
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS], devNonce);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
//Module::hexdump(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE);
RadioLibAES128Instance.init(appKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey);
//Module::hexdump(this->appSKey, RADIOLIB_AES128_BLOCK_SIZE);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY;
RadioLibAES128Instance.init(nwkKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->fNwkSIntKey);
//Module::hexdump(this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY;
RadioLibAES128Instance.init(nwkKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->sNwkSIntKey);
//Module::hexdump(this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY;
RadioLibAES128Instance.init(nwkKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey);
//Module::hexdump(this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE);
// send the RekeyInd MAC command
this->rev = 1;
RADIOLIB_DEBUG_PRINTLN("LoRaWAN 1.1 not supported (yet)");
(void)appKey;
return(RADIOLIB_ERR_UNSUPPORTED_ENCODING);
uint8_t serverRev = 0xFF;
state = sendMacCommand(RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND, &this->rev, sizeof(uint8_t), &serverRev, sizeof(uint8_t));
RADIOLIB_ASSERT(state);
// check the supported server version
if(serverRev != this->rev) {
return(RADIOLIB_ERR_INVALID_REVISION);
}
} else {
// 1.0 version, just derive the keys
this->rev = 0;
LoRaWANNode::hton<uint32_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], homeNetId, 3);
LoRaWANNode::hton<uint16_t>(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], devNonce);
keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY;
RadioLibAES128Instance.init(nwkKey);
RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey);
@ -262,18 +325,28 @@ int16_t LoRaWANNode::uplink(const char* str, uint8_t port) {
int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
// check destination port
RADIOLIB_CHECK_RANGE(port, 0x01, 0xDF, RADIOLIB_ERR_INVALID_PAYLOAD);
RADIOLIB_CHECK_RANGE(port, 0x00, 0xDF, RADIOLIB_ERR_INVALID_PORT);
// check maximum payload len as defiend in phy
// TODO implement Fopts
// check if there is a MAC command to piggyback
uint8_t foptsLen = 0;
if(this->command) {
foptsLen = 1 + this->command->len;
}
// check maximum payload len as defined in phy
if(len > this->band->payloadLenMax[this->dataRate]) {
return(RADIOLIB_ERR_PACKET_TOO_LONG);
}
// check if sufficient time has elapsed since the last uplink
Module* mod = this->phyLayer->getMod();
/*if(mod->hal->millis() - this->rxDelayStart < ) {
}*/
// build the uplink message
// the first 16 bytes are reserved for MIC calculation blocks
size_t uplinkMsgLen = RADIOLIB_LORAWAN_UPLINK_LEN(len, foptsLen);
size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(len, foptsLen);
#if defined(RADIOLIB_STATIC_ONLY)
uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
#else
@ -281,62 +354,41 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
#endif
// set the packet fields
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
LoRaWANNode::hton<uint32_t>(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_DEV_ADDR_POS], this->devAddr);
uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP | RADIOLIB_LORAWAN_MHDR_MAJOR_R1;
LoRaWANNode::hton<uint32_t>(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr);
// TODO implement adaptive data rate
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FCTRL_POS] = 0x00;
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00 | foptsLen;
// get frame counter from persistent storage
Module* mod = this->phyLayer->getMod();
uint32_t fcnt = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID);
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt + 1);
LoRaWANNode::hton<uint16_t>(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FCNT_POS], (uint16_t)fcnt);
uint32_t fcnt = mod->hal->getPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID) + 1;
mod->hal->setPersistentParameter<uint32_t>(RADIOLIB_PERSISTENT_PARAM_LORAWAN_FCNT_UP_ID, fcnt);
LoRaWANNode::hton<uint16_t>(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)fcnt);
// TODO implement FOpts
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_FPORT_POS(foptsLen)] = port;
// check if there is something in FOpts
if(this->command) {
// append MAC command
uint8_t foptsBuff[RADIOLIB_AES128_BLOCK_SIZE];
foptsBuff[0] = this->command->cid;
for(size_t i = 1; i < this->command->len; i++) {
foptsBuff[i] = this->command->payload[i];
}
// encrypt it
processAES(foptsBuff, foptsLen, this->nwkSEncKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(0)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, false);
}
// set the port
uplinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(foptsLen)] = port;
// select encryption key based on the target port
uint8_t* encKey = this->appSKey;
if(port == 0) {
if(port == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) {
encKey = this->nwkSEncKey;
}
// 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_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK;
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
LoRaWANNode::hton<uint32_t>(&encBlock[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fcnt);
//Module::hexdump(uplinkMsg, uplinkMsgLen);
// now encrypt the payload
size_t remLen = len;
for(size_t i = 0; i < numBlocks; i++) {
encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS] = i + 1;
// encrypt the buffer
RadioLibAES128Instance.init(encKey);
RadioLibAES128Instance.encryptECB(encBlock, RADIOLIB_AES128_BLOCK_SIZE, encBuffer);
// now xor the buffer with the payload
size_t xorLen = remLen;
if(xorLen > RADIOLIB_AES128_BLOCK_SIZE) {
xorLen = RADIOLIB_AES128_BLOCK_SIZE;
}
for(uint8_t j = 0; j < xorLen; j++) {
uplinkMsg[RADIOLIB_LORAWAN_UPLINK_PAYLOAD_POS(foptsLen) + i*RADIOLIB_AES128_BLOCK_SIZE + j] = data[i*RADIOLIB_AES128_BLOCK_SIZE + j] ^ encBuffer[j];
}
remLen -= xorLen;
}
// encrypt the frame payload
processAES(data, len, encKey, &uplinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(foptsLen)], fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_UPLINK, 0x00, true);
// create blocks for MIC calculation
uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 };
@ -362,7 +414,7 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
// check LoRaWAN revision
if(this->rev == 1) {
uint32_t mic = ((uint32_t)(micS & 0x00FF) << 24) | ((uint32_t)(micS & 0xFF00) << 8) | ((uint32_t)(micF & 0xFF00) >> 8) | ((uint32_t)(micF & 0x00FF) << 8);
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>(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], mic);
} else {
LoRaWANNode::hton<uint32_t>(&uplinkMsg[uplinkMsgLen - sizeof(uint32_t)], micF);
@ -371,17 +423,297 @@ int16_t LoRaWANNode::uplink(uint8_t* data, size_t len, uint8_t port) {
//Module::hexdump(uplinkMsg, uplinkMsgLen);
// send it (without the MIC calculation blocks)
int16_t state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS);
uint32_t txStart = mod->hal->millis();
uint32_t timeOnAir = this->phyLayer->getTimeOnAir(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS) / 1000;
int16_t state = this->phyLayer->transmit(&uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS);
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] uplinkMsg;
#endif
RADIOLIB_ASSERT(state);
// TODO implement listening for downlinks in RX1/RX2 slots
// set the timestamp so that we can measure when to start receiving
this->command = NULL;
this->rxDelayStart = txStart + timeOnAir;
return(RADIOLIB_ERR_NONE);
}
#if defined(RADIOLIB_BUILD_ARDUINO)
int16_t LoRaWANNode::downlink(String& str) {
int16_t state = RADIOLIB_ERR_NONE;
// build a temporary buffer
// LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL
size_t length = 0;
uint8_t data[251];
// wait for downlink
state = this->downlink(data, &length);
if(state == RADIOLIB_ERR_NONE) {
// add null terminator
data[length] = '\0';
// initialize Arduino String class
str = String((char*)data);
}
return(state);
}
#endif
int16_t LoRaWANNode::downlink(uint8_t* data, size_t* len) {
// check if there are any upcoming Rx windows
Module* mod = this->phyLayer->getMod();
const uint32_t scanGuard = 500;
if(mod->hal->millis() - this->rxDelayStart > (this->rxDelays[1] + scanGuard)) {
// time since last Tx is greater than RX2 delay + some guard period
// we have nothing to downlink
return(RADIOLIB_ERR_NO_RX_WINDOW);
}
// downlink messages are sent with inverted IQ
int16_t state = RADIOLIB_ERR_UNKNOWN;
if(!this->FSK) {
state = this->phyLayer->invertIQ(true);
RADIOLIB_ASSERT(state);
}
// calculate the channel scanning timeout
// according to the spec, this must be at least enough time to effectively detect a preamble
uint32_t scanTimeout = this->phyLayer->getTimeOnAir(0)/1000;
// set up everything for channel scan
downlinkReceived = false;
scanFlag = false;
bool packetDetected = false;
this->phyLayer->setChannelScanAction(LoRaWANNodeOnChannelScan);
// perform listening in the two Rx windows
for(uint8_t i = 0; i < 2; i++) {
// wait for the start of the Rx window
// the waiting duration is shortened a bit to cover any possible timing errors
uint32_t waitLen = this->rxDelays[i] - (mod->hal->millis() - this->rxDelayStart);
if(waitLen > scanGuard) {
waitLen -= scanGuard;
}
mod->hal->delay(waitLen);
// wait until we get a preamble
uint32_t scanStart = mod->hal->millis();
while((mod->hal->millis() - scanStart) < (scanTimeout + scanGuard)) {
// check channel detection timeout
state = this->phyLayer->startChannelScan();
RADIOLIB_ASSERT(state);
// wait with some timeout, though it should not be hit
uint32_t cadStart = mod->hal->millis();
while(!scanFlag) {
mod->hal->yield();
if(mod->hal->millis() - cadStart >= 3000) {
// timed out, stop waiting
break;
}
}
// check the scan result
scanFlag = false;
state = this->phyLayer->getChannelScanResult();
if((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) {
packetDetected = true;
break;
}
}
// check if we have a packet
if(packetDetected) {
break;
} else if(i == 0) {
// nothing in the first window, configure for the second
state = this->phyLayer->setFrequency(this->band->backupChannel.freqStart);
RADIOLIB_ASSERT(state);
DataRate_t datr;
findDataRate(RADIOLIB_LORAWAN_DATA_RATE_UNUSED, &datr, &this->band->backupChannel);
state = this->phyLayer->setDataRate(datr);
RADIOLIB_ASSERT(state);
}
}
// check if we received a packet at all
if(!packetDetected) {
this->phyLayer->standby();
if(!this->FSK) {
this->phyLayer->invertIQ(false);
}
// restore the original uplink channel
this->configureChannel(0, this->dataRate);
return(RADIOLIB_ERR_RX_TIMEOUT);
}
// channel scan is finished, swap the actions
this->phyLayer->clearChannelScanAction();
downlinkReceived = false;
this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlink);
// start receiving
state = this->phyLayer->startReceive();
RADIOLIB_ASSERT(state);
// wait for reception with some timeout
uint32_t rxStart = mod->hal->millis();
while(!downlinkReceived) {
mod->hal->yield();
// let's hope 30 seconds is long enough timeout
if(mod->hal->millis() - rxStart >= 30000) {
// timed out
this->phyLayer->standby();
if(!this->FSK) {
this->phyLayer->invertIQ(false);
}
return(RADIOLIB_ERR_RX_TIMEOUT);
}
}
// we have a message, clear actions, go to standby and reset the IQ inversion
downlinkReceived = false;
this->phyLayer->standby();
this->phyLayer->clearPacketReceivedAction();
if(!this->FSK) {
state = this->phyLayer->invertIQ(false);
RADIOLIB_ASSERT(state);
}
// 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 port
if(downlinkMsgLen < RADIOLIB_LORAWAN_FRAME_LEN(0, 0) - 1 - RADIOLIB_AES128_BLOCK_SIZE) {
RADIOLIB_DEBUG_PRINTLN("Downlink message too short (%lu bytes)", downlinkMsgLen);
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// build the buffer for the downlink message
// the first 16 bytes are reserved for MIC calculation block
#if !defined(RADIOLIB_STATIC_ONLY)
uint8_t* downlinkMsg = new uint8_t[RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen];
#else
uint8_t downlinkMsg[RADIOLIB_STATIC_ARRAY_SIZE];
#endif
// set the MIC calculation block
// TODO implement confirmed frames
memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE);
downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC;
LoRaWANNode::hton<uint32_t>(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr);
downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK;
downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t);
// 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;
}
//Module::hexdump(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen);
if(state != RADIOLIB_ERR_NONE) {
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] downlinkMsg;
#endif
return(state);
}
// check the MIC
if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) {
return(RADIOLIB_ERR_CRC_MISMATCH);
}
// check the address
uint32_t addr = LoRaWANNode::ntoh<uint32_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS]);
if(addr != this->devAddr) {
RADIOLIB_DEBUG_PRINTLN("Device address mismatch, expected 0x%08X, got 0x%08X", this->devAddr, addr);
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// TODO cache the ADR bit?
// TODO cache and check fcnt?
uint16_t fcnt = LoRaWANNode::ntoh<uint16_t>(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]);
// check fopts len
uint8_t foptsLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK;
if(foptsLen > 0) {
// there are some Fopts, decrypt them
*len = foptsLen;
// according to the specification, the last two arguments should be 0x00 and false,
// but that will fail even for LoRaWAN 1.1.0 server
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], foptsLen, this->nwkSEncKey, data, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x01, true);
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] downlinkMsg;
#endif
return(RADIOLIB_ERR_NONE);
}
// no fopts, just payload
// TODO implement decoding piggybacked Fopts?
*len = downlinkMsgLen;
processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], downlinkMsgLen, this->appSKey, data, fcnt, RADIOLIB_LORAWAN_CHANNEL_DIR_DOWNLINK, 0x00, true);
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] downlinkMsg;
#endif
return(state);
}
void LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* datr, const LoRaWANChannelSpan_t* span) {
uint8_t dataRateBand = span->dataRates[dr];
this->dataRate = dr;
if(dr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
for(uint8_t i = 0; i < RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES; i++) {
if(span->dataRates[i] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
dataRateBand = span->dataRates[i];
this->dataRate = i;
break;
}
}
}
if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) {
datr->fsk.bitRate = 50;
datr->fsk.freqDev = 25;
} else {
uint8_t bw = dataRateBand & 0x03;
switch(bw) {
case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ):
datr->lora.bandwidth = 125.0;
break;
case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ):
datr->lora.bandwidth = 250.0;
break;
case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ):
datr->lora.bandwidth = 500.0;
break;
default:
datr->lora.bandwidth = 125.0;
}
datr->lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6;
}
}
int16_t LoRaWANNode::configureChannel(uint8_t chan, uint8_t dr) {
// find the span based on the channel ID
uint8_t span = 0;
@ -401,58 +733,19 @@ int16_t LoRaWANNode::configureChannel(uint8_t chan, uint8_t dr) {
}
if(!found) {
return(RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS);
return(RADIOLIB_ERR_INVALID_CHANNEL);
}
this->chIndex = chan;
RADIOLIB_DEBUG_PRINTLN("Channel span %d, channel %d", span, spanChannelId);
// set the frequency
float freq = this->band->defaultChannels[span].freqStart + this->band->defaultChannels[span].freqStep * (float)spanChannelId;
RADIOLIB_DEBUG_PRINTLN("Frequency %f MHz", freq);
int state = this->phyLayer->setFrequency(freq);
RADIOLIB_ASSERT(state);
// set the data rate
uint8_t dataRateBand = this->band->defaultChannels[span].dataRates[dr];
this->dataRate = dr;
if(dr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
// find the first usable data rate
for(uint8_t i = 0; i < RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES; i++) {
if(this->band->defaultChannels[span].dataRates[i] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) {
dataRateBand = this->band->defaultChannels[span].dataRates[i];
this->dataRate = i;
break;
}
}
}
RADIOLIB_DEBUG_PRINTLN("Data rate %02x", dataRateBand);
DataRate_t datr;
if(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_FSK_50_K) {
datr.fsk.bitRate = 50;
datr.fsk.freqDev = 25;
} else {
uint8_t bw = dataRateBand & 0x03;
switch(bw) {
case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ):
datr.lora.bandwidth = 125.0;
break;
case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ):
datr.lora.bandwidth = 250.0;
break;
case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ):
datr.lora.bandwidth = 500.0;
break;
default:
return(RADIOLIB_ERR_INVALID_BANDWIDTH);
}
datr.lora.spreadingFactor = ((dataRateBand & 0x70) >> 4) + 6;
}
findDataRate(dr, &datr, &this->band->defaultChannels[span]);
state = this->phyLayer->setDataRate(datr);
return(state);
@ -480,7 +773,6 @@ bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) {
// calculate the expected value and compare
uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key);
if(micCalculated != micReceived) {
RADIOLIB_DEBUG_PRINTLN("MIC mismatch, expected 0x%08x, got 0x%08x", micCalculated, micReceived);
return(false);
}
@ -536,6 +828,113 @@ int16_t LoRaWANNode::setPhyProperties() {
return(state);
}
int16_t LoRaWANNode::sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloadLen, uint8_t* reply, size_t replyLen) {
// build the command
size_t macReqLen = 1 + payloadLen;
#if !defined(RADIOLIB_STATIC_ONLY)
uint8_t* macReqBuff = new uint8_t[macReqLen];
#else
uint8_t macReqBuff[RADIOLIB_STATIC_ARRAY_SIZE];
#endif
macReqBuff[0] = cid;
memcpy(&macReqBuff[1], payload, payloadLen);
// uplink it
int16_t state = this->uplink(macReqBuff, macReqLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND);
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] macReqBuff;
#endif
RADIOLIB_ASSERT(state);
// build the reply buffer
size_t macRplLen = 1 + replyLen;
#if !defined(RADIOLIB_STATIC_ONLY)
uint8_t* macRplBuff = new uint8_t[this->band->payloadLenMax[this->dataRate]];
#else
uint8_t macRplBuff[RADIOLIB_STATIC_ARRAY_SIZE];
#endif
// wait for reply from the server
size_t rxRplLen = 0;
state = this->downlink(macRplBuff, &rxRplLen);
if(state != RADIOLIB_ERR_NONE) {
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] macRplBuff;
#endif
return(state);
}
//Module::hexdump(macRplBuff, rxRplLen);
// check the length - it may be longer than expected
// if the server decided to append more MAC commands, but never shorter
// TODO how to handle the additional command(s)?
if(rxRplLen < macRplLen) {
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] macRplBuff;
#endif
return(RADIOLIB_ERR_DOWNLINK_MALFORMED);
}
// check the CID
if(macRplBuff[0] != cid) {
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] macRplBuff;
#endif
return(RADIOLIB_ERR_INVALID_CID);
}
// copy the data
memcpy(reply, &macRplBuff[1], replyLen);
#if !defined(RADIOLIB_STATIC_ONLY)
delete[] macRplBuff;
#endif
return(state);
}
void LoRaWANNode::processAES(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);
//Module::hexdump(uplinkMsg, uplinkMsgLen);
// 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;
}
}
template<typename T>
T LoRaWANNode::ntoh(uint8_t* buff, size_t size) {
uint8_t* buffPtr = buff;

View file

@ -68,6 +68,7 @@
// recommended default settings
#define RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS (1000)
#define RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS ((RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS) + 1000)
#define RADIOLIB_LORAWAN_RX_WINDOW_LEN_MS (500)
#define RADIOLIB_LORAWAN_RX1_DR_OFFSET (0)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS (5000)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS (6000)
@ -83,14 +84,20 @@
#define RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS (1)
#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS (9)
#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS (17)
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE (0xFF)
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_0 (0x00)
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_1 (0x01)
#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_2 (0x02)
// join accept message layout
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN (33)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS (1)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS (4)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS (7)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_EUI_POS (4)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS (11)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS (12)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_NONCE_POS (12)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS (13)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN (16)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_TYPE_POS (RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS + RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN - 1)
@ -102,17 +109,20 @@
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY (0x02)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY (0x03)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY (0x04)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_ENC_KEY (0x05)
#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_INT_KEY (0x06)
// uplink message layout
#define RADIOLIB_LORAWAN_UPLINK_LEN(PAYLOAD, FOPTS) (16 + 13 + (PAYLOAD) + (FOPTS))
#define RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS (16)
#define RADIOLIB_LORAWAN_UPLINK_DEV_ADDR_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 1)
#define RADIOLIB_LORAWAN_UPLINK_FCTRL_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 5)
#define RADIOLIB_LORAWAN_UPLINK_FCNT_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 6)
#define RADIOLIB_LORAWAN_UPLINK_FOPTS_POS (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 8)
#define RADIOLIB_LORAWAN_UPLINK_FOPTS_MAX_LEN (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 16)
#define RADIOLIB_LORAWAN_UPLINK_FPORT_POS(FOPTS) (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 8 + (FOPTS))
#define RADIOLIB_LORAWAN_UPLINK_PAYLOAD_POS(FOPTS) (RADIOLIB_LORAWAN_UPLINK_LEN_START_OFFS + 9 + (FOPTS))
// frame header layout
#define RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS (16)
#define RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 1)
#define RADIOLIB_LORAWAN_FHDR_FCTRL_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 5)
#define RADIOLIB_LORAWAN_FHDR_FCNT_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 6)
#define RADIOLIB_LORAWAN_FHDR_FOPTS_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8)
#define RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK (0x0F)
#define RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 16)
#define RADIOLIB_LORAWAN_FHDR_FPORT_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8 + (FOPTS))
#define RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 9 + (FOPTS))
#define RADIOLIB_LORAWAN_FRAME_LEN(PAYLOAD, FOPTS) (16 + 13 + (PAYLOAD) + (FOPTS))
// payload encryption/MIC blocks common layout
#define RADIOLIB_LORAWAN_BLOCK_MAGIC_POS (0)
@ -122,6 +132,7 @@
// payload encryption block layout
#define RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC (0x01)
#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_ID_POS (4)
#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS (15)
// payload MIC blocks layout
@ -133,6 +144,22 @@
// magic word saved in persistent memory upon activation
#define RADIOLIB_LORAWAN_MAGIC (0x12AD101B)
// MAC commands
#define RADIOLIB_LORAWAN_MAC_CMD_RESET_IND (0x01)
#define RADIOLIB_LORAWAN_MAC_CMD_LINK_CHECK_REQ (0x02)
#define RADIOLIB_LORAWAN_MAC_CMD_LINK_ADR_ANS (0x03)
#define RADIOLIB_LORAWAN_MAC_CMD_DUTY_CYCLE_ANS (0x04)
#define RADIOLIB_LORAWAN_MAC_CMD_RX_PARAM_SETUP_ANS (0x05)
#define RADIOLIB_LORAWAN_MAC_CMD_DEV_STATUS_ANS (0x06)
#define RADIOLIB_LORAWAN_MAC_CMD_NEW_CHANNEL_ANS (0x07)
#define RADIOLIB_LORAWAN_MAC_CMD_RX_TIMING_SETUP_ANS (0x08)
#define RADIOLIB_LORAWAN_MAC_CMD_TX_PARAM_SETUP_ANS (0x09)
#define RADIOLIB_LORAWAN_MAC_CMD_DI_CHANNEL_ANS (0x0A)
#define RADIOLIB_LORAWAN_MAC_CMD_REKEY_IND (0x0B)
#define RADIOLIB_LORAWAN_MAC_CMD_ADR_PARAM_SETUP_ANS (0x0C)
#define RADIOLIB_LORAWAN_MAC_CMD_DEVICE_TIME_REQ (0x0D)
#define RADIOLIB_LORAWAN_MAC_CMD_REJOIN_PARAM_SETUP_ANS (0x0F)
/*!
\struct LoRaWANChannelSpan_t
\brief Structure to save information about LoRaWAN channels.
@ -208,6 +235,12 @@ extern const LoRaWANBand_t AS923;
extern const LoRaWANBand_t KR920;
extern const LoRaWANBand_t IN865;
struct LoRaWANMacCommand_t {
uint8_t cid;
size_t len;
uint8_t* payload;
};
/*!
\class LoRaWANNode
\brief LoRaWAN-compatible node (class A device).
@ -239,14 +272,14 @@ class LoRaWANNode {
/*!
\brief Join network by performing over-the-air activation. By this procedure,
the device will perform an exchange with the network server and set all necessary configuration.
\param appEUI 8-byte application identifier.
\param joinEUI 8-byte application identifier.
\param devEUI 8-byte device identifier.
\param nwkKey Pointer to the network AES-128 key.
\param appKey Pointer to the application AES-128 key.
\param force Set to true to force joining even if previously joined.
\returns \ref status_codes
*/
int16_t beginOTAA(uint64_t appEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force = false);
int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey, bool force = false);
/*!
\brief Join network by performing activation by personalization.
@ -287,6 +320,60 @@ class LoRaWANNode {
*/
int16_t uplink(uint8_t* data, size_t len, uint8_t port);
#if defined(RADIOLIB_BUILD_ARDUINO)
/*!
\brief Wait for downlink from the server in either RX1 or RX2 window.
\param str Address of Arduino String to save the received data.
\returns \ref status_codes
*/
int16_t downlink(String& str);
#endif
/*!
\brief Wait for downlink from the server in either RX1 or RX2 window.
\param data Buffer to save received data into.
\param len Pointer to variable that will be used to save the number of received bytes.
\returns \ref status_codes
*/
int16_t downlink(uint8_t* data, size_t* len);
#if !defined(RADIOLIB_GODMODE)
private:
#endif
PhysicalLayer* phyLayer = NULL;
const LoRaWANBand_t* band = NULL;
LoRaWANMacCommand_t* command = NULL;
// the following is either provided by the network server (OTAA)
// or directly entered by the user (ABP)
uint32_t devAddr = 0;
uint8_t appSKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
uint8_t fNwkSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
uint8_t sNwkSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
uint8_t nwkSEncKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
uint8_t jSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 };
float availableChannelsFreq[5] = { 0 };
uint16_t availableChannelsMask[6] = { 0 };
// LoRaWAN revision (1.0 vs 1.1)
uint8_t rev = 0;
// currently configured data rate DR0 - DR15 (band-dependent!)
uint8_t dataRate = 0;
// currently configured channel (band-dependent!)
uint8_t chIndex = 0;
// timestamp to measure the RX1/2 delay (from uplink end)
uint32_t rxDelayStart = 0;
// delays between the uplink and RX1/2 windows
uint32_t rxDelays[2] = { RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS, RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS };
// find the first usable data rate in a given channel span
void findDataRate(uint8_t dr, DataRate_t* datr, const LoRaWANChannelSpan_t* span);
/*!
\brief Configure the radio to a given channel frequency and data rate.
\param chan Channel ID to set.
@ -295,32 +382,6 @@ class LoRaWANNode {
*/
int16_t configureChannel(uint8_t chan, uint8_t dr);
#if !defined(RADIOLIB_GODMODE)
private:
#endif
PhysicalLayer* phyLayer;
const LoRaWANBand_t* band;
// the following is either provided by the network server (OTAA)
// or directly entered by the user (ABP)
uint32_t devAddr;
uint8_t appSKey[RADIOLIB_AES128_KEY_SIZE];
uint8_t fNwkSIntKey[RADIOLIB_AES128_KEY_SIZE];
uint8_t sNwkSIntKey[RADIOLIB_AES128_KEY_SIZE];
uint8_t nwkSEncKey[RADIOLIB_AES128_KEY_SIZE];
uint8_t rxDelay;
float availableChannelsFreq[5];
uint16_t availableChannelsMask[6];
// LoRaWAN revision (1.0 vs 1.1)
uint8_t rev;
// currently configured data rate DR0 - DR15 (band-dependent!)
uint8_t dataRate;
// currently configured channel (band-dependent!)
uint8_t chIndex;
// method to generate message integrity code
uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key);
@ -328,8 +389,15 @@ class LoRaWANNode {
// it assumes that the MIC is the last 4 bytes of the message
bool verifyMIC(uint8_t* msg, size_t len, uint8_t* key);
// configure the physical layer properties (frequency, sync word etc.)
int16_t setPhyProperties();
// send a MAC command to the network server
int16_t sendMacCommand(uint8_t cid, uint8_t* payload, size_t payloadLen, uint8_t* reply, size_t replyLen);
// function to encrypt and decrypt payloads
void processAES(uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fcnt, uint8_t dir, uint8_t ctrId, bool counter);
// network-to-host conversion method - takes data from network packet and converts it to the host endians
template<typename T>
static T ntoh(uint8_t* buff, size_t size = 0);