/*!
* @file TinyLoRa.cpp
*
* @mainpage TinyLoRa RFM95/96W breakout driver
*
* @section intro_sec Introduction
*
* This is the documentation for Adafruit's Feather LoRa for the
* Arduino platform. It is designed specifically to work with the
* Adafruit Feather 32u4 LoRa.
*
* This library uses SPI to communicate, 4 pins (SCL, SDA, IRQ, SS)
* are required to interface with the HopeRF RFM95/96 breakout.
*
* Adafruit invests time and resources providing this open source code,
* please support Adafruit and open-source hardware by purchasing
* products from Adafruit!
*
* @section dependencies Dependencies
*
* This library has no dependencies.
*
* @section author Author
*
* Copyright 2015, 2016 Ideetron B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
*
* Modified by Brent Rubell for Adafruit Industries.
*
* @section license License
*
* LGPL license, all text here must be included in any redistribution.
*
*/
#include "TinyLoRa.h"
#include
extern uint8_t NwkSkey[16]; ///< Network Session Key
extern uint8_t AppSkey[16]; ///< Application Session Key
extern uint8_t DevAddr[4]; ///< Device Address
static SPISettings RFM_spisettings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
/*
*****************************************************************************************
* Description: TTN regional frequency plans
*****************************************************************************************
*/
#ifdef AU915
const unsigned char PROGMEM TinyLoRa::LoRa_Frequency[8][3] = {
{ 0xE5, 0x33, 0x5A }, //Channel 0 916.800 MHz / 61.035 Hz = 15020890 = 0xE5335A
{ 0xE5, 0x40, 0x26 }, //Channel 2 917.000 MHz / 61.035 Hz = 15024166 = 0xE54026
{ 0xE5, 0x4C, 0xF3 }, //Channel 3 917.200 MHz / 61.035 Hz = 15027443 = 0xE54CF3
{ 0xE5, 0x59, 0xC0 }, //Channel 4 917.400 MHz / 61.035 Hz = 15030720 = 0xE559C0
{ 0xE5, 0x66, 0x8D }, //Channel 5 917.600 MHz / 61.035 Hz = 15033997 = 0xE5668D
{ 0xE5, 0x73, 0x5A }, //Channel 6 917.800 MHz / 61.035 Hz = 15037274 = 0xE5735A
{ 0xE5, 0x80, 0x27 }, //Channel 7 918.000 MHz / 61.035 Hz = 15040551 = 0xE58027
{ 0xE5, 0x8C, 0xF3 } //Channel 8 918.200 MHz / 61.035 Hz = 15043827 = 0xE58CF3
};
#endif
#ifdef EU863
const unsigned char PROGMEM TinyLoRa::LoRa_Frequency[8][3] = {
{ 0xD9, 0x06, 0x8B }, //Channel 0 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B
{ 0xD9, 0x13, 0x58 }, //Channel 1 868.300 MHz / 61.035 Hz = 14226264 = 0xD91358
{ 0xD9, 0x20, 0x24 }, //Channel 2 868.500 MHz / 61.035 Hz = 14229540 = 0xD92024
{ 0xD8, 0xC6, 0x8B }, //Channel 3 867.100 MHz / 61.035 Hz = 14206603 = 0xD8C68B
{ 0xD8, 0xD3, 0x58 }, //Channel 4 867.300 MHz / 61.035 Hz = 14209880 = 0xD8D358
{ 0xD8, 0xE0, 0x24 }, //Channel 5 867.500 MHz / 61.035 Hz = 14213156 = 0xD8E024
{ 0xD8, 0xEC, 0xF1 }, //Channel 6 867.700 MHz / 61.035 Hz = 14216433 = 0xD8ECF1
{ 0xD8, 0xF9, 0xBE } //Channel 7 867.900 MHz / 61.035 Hz = 14219710 = 0xD8F9BE
};
#endif
#ifdef US902
const unsigned char PROGMEM TinyLoRa::LoRa_Frequency[8][3] = {
{ 0xE1, 0xF9, 0xC0 }, //Channel 0 903.900 MHz / 61.035 Hz = 14809536 = 0xE1F9C0
{ 0xE2, 0x06, 0x8C }, //Channel 1 904.100 MHz / 61.035 Hz = 14812812 = 0xE2068C
{ 0xE2, 0x13, 0x59}, //Channel 2 904.300 MHz / 61.035 Hz = 14816089 = 0xE21359
{ 0xE2, 0x20, 0x26 }, //Channel 3 904.500 MHz / 61.035 Hz = 14819366 = 0xE22026
{ 0xE2, 0x2C, 0xF3 }, //Channel 4 904.700 MHz / 61.035 Hz = 14822643 = 0xE22CF3
{ 0xE2, 0x39, 0xC0 }, //Channel 5 904.900 MHz / 61.035 Hz = 14825920 = 0xE239C0
{ 0xE2, 0x46, 0x8C }, //Channel 6 905.100 MHz / 61.035 Hz = 14829196 = 0xE2468C
{ 0xE2, 0x53, 0x59 } //Channel 7 905.300 MHz / 61.035 Hz = 14832473 = 0xE25359
};
#endif
#ifdef AS920
const unsigned char PROGMEM TinyLoRa::LoRa_Frequency[8][3] = {
{ 0xE6, 0xCC, 0xF4 }, //Channel 0 868.100 MHz / 61.035 Hz = 15125748 = 0xE6CCF4
{ 0xE6, 0xD9, 0xC0 }, //Channel 1 868.300 MHz / 61.035 Hz = 15129024 = 0xE6D9C0
{ 0xE6, 0x8C, 0xF3 }, //Channel 2 868.500 MHz / 61.035 Hz = 15109363 = 0xE68CF3
{ 0xE6, 0x99, 0xC0 }, //Channel 3 867.100 MHz / 61.035 Hz = 15112640 = 0xE699C0
{ 0xE6, 0xA6, 0x8D }, //Channel 4 867.300 MHz / 61.035 Hz = 15115917 = 0xE6A68D
{ 0xE6, 0xB3, 0x5A }, //Channel 5 867.500 MHz / 61.035 Hz = 15119194 = 0xE6B35A
{ 0xE6, 0xC0, 0x27 }, //Channel 6 867.700 MHz / 61.035 Hz = 15122471 = 0xE6C027
{ 0xE6, 0x80, 0x27 } //Channel 7 867.900 MHz / 61.035 Hz = 15106087 = 0xE68027
};
#endif
/*
*****************************************************************************************
* Description: S_Table used for AES encription
*****************************************************************************************
*/
const unsigned char PROGMEM TinyLoRa::S_Table[16][16] = {
{0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76},
{0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0},
{0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15},
{0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75},
{0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84},
{0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF},
{0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8},
{0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2},
{0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73},
{0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB},
{0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79},
{0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08},
{0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A},
{0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E},
{0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF},
{0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16}
};
/***************************************************************************
CONSTRUCTORS
***************************************************************************/
/**************************************************************************/
/*!
@brief Sets the RFM datarate
@param datarate Bandwidth and Frequency plan.
*/
/**************************************************************************/
void TinyLoRa::setDatarate(rfm_datarates_t datarate) {
_sf, _bw, _modemcfg = 0;
switch(datarate) {
case SF7BW125:
_sf = 0x74;
_bw = 0x72;
_modemcfg = 0x04;
break;
case SF7BW250:
_sf = 0x74;
_bw = 0x82;
_modemcfg = 0x04;
break;
case SF8BW125:
_sf = 0x84;
_bw = 0x72;
_modemcfg = 0x04;
break;
case SF9BW125:
_sf = 0x94;
_bw = 0x72;
_modemcfg = 0x04;
break;
case SF10BW125:
_sf = 0xA4;
_bw = 0x72;
_modemcfg = 0x04;
break;
case SF11BW125:
_sf = 0xB4;
_bw = 0x72;
_modemcfg = 0x0C;
break;
case SF12BW125:
_sf = 0xC4;
_bw = 0x72;
_modemcfg = 0x0C;
break;
default:
_sf = 0x74;
_bw = 0x72;
_modemcfg = 0x04;
break;
}
}
/**************************************************************************/
/*!
@brief Sets the RFM channel.
@param channel Which channel to send data
*/
/**************************************************************************/
void TinyLoRa::setChannel(rfm_channels_t channel) {
_rfmMSB, _rfmLSB, _rfmMID = 0;
switch (channel)
{
case CH0:
_rfmLSB = pgm_read_byte(&(LoRa_Frequency[0][2]));
_rfmMID = pgm_read_byte(&(LoRa_Frequency[0][1]));
_rfmMSB = pgm_read_byte(&(LoRa_Frequency[0][0]));
_isMultiChan = 0;
break;
case CH1:
_rfmLSB = pgm_read_byte(&(LoRa_Frequency[1][2]));
_rfmMID = pgm_read_byte(&(LoRa_Frequency[1][1]));
_rfmMSB = pgm_read_byte(&(LoRa_Frequency[1][0]));
_isMultiChan = 0;
break;
case CH2:
_rfmLSB = pgm_read_byte(&(LoRa_Frequency[2][2]));
_rfmMID = pgm_read_byte(&(LoRa_Frequency[2][1]));
_rfmMSB = pgm_read_byte(&(LoRa_Frequency[2][0]));
_isMultiChan = 0;
break;
case CH3:
_rfmLSB = pgm_read_byte(&(LoRa_Frequency[3][2]));
_rfmMID = pgm_read_byte(&(LoRa_Frequency[3][1]));
_rfmMSB = pgm_read_byte(&(LoRa_Frequency[3][0]));
_isMultiChan = 0;
break;
case CH4:
_rfmLSB = pgm_read_byte(&(LoRa_Frequency[4][2]));
_rfmMID = pgm_read_byte(&(LoRa_Frequency[4][1]));
_rfmMSB = pgm_read_byte(&(LoRa_Frequency[4][0]));
_isMultiChan = 0;
break;
case CH5:
_rfmLSB = pgm_read_byte(&(LoRa_Frequency[5][2]));
_rfmMID = pgm_read_byte(&(LoRa_Frequency[5][1]));
_rfmMSB = pgm_read_byte(&(LoRa_Frequency[5][0]));
_isMultiChan = 0;
break;
case CH6:
_rfmLSB = pgm_read_byte(&(LoRa_Frequency[6][2]));
_rfmMID = pgm_read_byte(&(LoRa_Frequency[6][1]));
_rfmMSB = pgm_read_byte(&(LoRa_Frequency[6][0]));
_isMultiChan = 0;
break;
case CH7:
_rfmLSB = pgm_read_byte(&(LoRa_Frequency[7][2]));
_rfmMID = pgm_read_byte(&(LoRa_Frequency[7][1]));
_rfmMSB = pgm_read_byte(&(LoRa_Frequency[7][0]));
_isMultiChan = 0;
break;
case MULTI:
_isMultiChan = 1;
break;
default:
_isMultiChan = 1;
break;
}
}
/**************************************************************************/
/*!
@brief Instanciates a new TinyLoRa class, including assigning
irq and cs pins to the RFM breakout.
@param rfm_irq
The RFM module's interrupt pin (rfm_nss).
@param rfm_nss
The RFM module's slave select pin (rfm_nss).
*/
/**************************************************************************/
TinyLoRa::TinyLoRa(int8_t rfm_irq, int8_t rfm_nss) {
_irq = rfm_irq;
_cs = rfm_nss;
}
/***************************************************************************
PUBLIC FUNCTIONS
***************************************************************************/
/**************************************************************************/
/*!
@brief Initializes the RFM, including configuring SPI, configuring
the frameCounter and txrandomNum.
@return True if the RFM has been initialized
*/
/**************************************************************************/
bool TinyLoRa::begin()
{
// start and configure SPI
SPI.begin();
// RFM95 ss as output
pinMode(_cs, OUTPUT);
// RFM95 _irq as input
pinMode(_irq, INPUT);
uint8_t ver = RFM_Read(0x42);
if(ver!=18){
return 0;
}
//Switch RFM to sleep
RFM_Write(0x01,MODE_SLEEP);
//Set RFM in LoRa mode
RFM_Write(0x01,MODE_LORA);
//PA pin (maximal power)
RFM_Write(0x09,0xFF);
//Rx Timeout set to 37 symbols
RFM_Write(0x1F,0x25);
//Preamble length set to 8 symbols
//0x0008 + 4 = 12
RFM_Write(REG_PREAMBLE_MSB,0x00);
RFM_Write(REG_PREAMBLE_LSB,0x08);
//Low datarate optimization off AGC auto on
RFM_Write(0x26,0x0C);
//Set LoRa sync word
RFM_Write(0x39,0x34);
//Set IQ to normal values
RFM_Write(0x33,0x27);
RFM_Write(0x3B,0x1D);
//Set FIFO pointers
//TX base adress
RFM_Write(0x0E,0x80);
//Rx base adress
RFM_Write(0x0F,0x00);
// init frame counter
uint16_t frameCounter = 0x0000;
// init tx random number for first use
uint8_t txrandomNum = 0x00;
return 1;
}
/**************************************************************************/
/*!
@brief Sends a package with the RFM module.
@param *RFM_Tx_Package
Pointer to array containing data to be sent.
@param Package_Length
Length of the package to be sent.
*/
/**************************************************************************/
void TinyLoRa::RFM_Send_Package(unsigned char *RFM_Tx_Package, unsigned char Package_Length)
{
unsigned char i;
//Set RFM in Standby mode wait on mode ready
RFM_Write(MODE_STDBY,0x81);
// wait for standby mode
delay(10);
//Switch _irq to TxDone
RFM_Write(0x40,0x40);
// select rfm channel
if (_isMultiChan == 1) {
RFM_Write(REG_FRF_MSB, pgm_read_byte(&(LoRa_Frequency[randomNum][0])));
RFM_Write(REG_FRF_MID, pgm_read_byte(&(LoRa_Frequency[randomNum][1])));
RFM_Write(REG_FRF_LSB, pgm_read_byte(&(LoRa_Frequency[randomNum][2])));
} else {
RFM_Write(REG_FRF_MSB, _rfmMSB);
RFM_Write(REG_FRF_MID, _rfmMID);
RFM_Write(REG_FRF_LSB, _rfmLSB);
}
/* Set RFM Datarate */
RFM_Write(REG_FEI_LSB, _sf);
RFM_Write(REG_FEI_MSB, _bw);
RFM_Write(REG_MODEM_CONFIG, _modemcfg);
//Set payload length to the right length
RFM_Write(0x22,Package_Length);
//Set SPI pointer to start of Tx part in FiFo
RFM_Write(0x0D,0x80);
//Write Payload to FiFo
for (i = 0;i < Package_Length; i++)
{
RFM_Write(0x00,*RFM_Tx_Package);
RFM_Tx_Package++;
}
//Switch RFM to Tx
RFM_Write(0x01,MODE_TX);
//Wait _irq to pull high
while(digitalRead(_irq) == LOW)
{
}
//Switch RFM to sleep
RFM_Write(0x01,MODE_SLEEP);
}
/**************************************************************************/
/*!
@brief Function which writes to a register from the RFM.
@param RFM_Address
An address of the register to be written.
@param RFM_Data
Data to be written to the register.
*/
/**************************************************************************/
void TinyLoRa::RFM_Write(unsigned char RFM_Address, unsigned char RFM_Data)
{
// br: SPI Transfer Debug
#ifdef DEBUG
Serial.print("SPI Write ADDR: ");
Serial.print(RFM_Address, HEX);
Serial.print(" DATA: ");
Serial.println(RFM_Data, HEX);
#endif
SPI.beginTransaction(RFM_spisettings);
//Set NSS pin Low to start communication
digitalWrite(_cs, LOW);
//Send Address with MSB 1 to make it a writ command
SPI.transfer(RFM_Address | 0x80);
//Send Data
SPI.transfer(RFM_Data);
//Set NSS pin High to end communication
digitalWrite(_cs, HIGH);
}
/**************************************************************************/
/*!
@brief Funtion that reads a register from the RFM
@param RFM_Address
An address of the register to be read.
@return Value exchaged in SPI transaction
*/
/**************************************************************************/
uint8_t TinyLoRa::RFM_Read(uint8_t RFM_Address) {
SPI.beginTransaction(RFM_spisettings);
digitalWrite(_cs, LOW);
SPI.transfer(RFM_Address & 0x7F);
uint8_t RFM_Data = SPI.transfer(0x00);
digitalWrite(_cs, HIGH);
// br: SPI Transfer Debug
#ifdef DEBUG
Serial.print("SPI Read ADDR: ");
Serial.print(RFM_Address, HEX);
Serial.print(" DATA: ");
Serial.println(RFM_Data, HEX);
#endif
return RFM_Data;
}
/**************************************************************************/
/*!
@brief Function to assemble and send a LoRaWAN package.
@param *Data
Pointer to the array of data to be transmitted.
@param Frame_Counter_Tx
Frame counter for transfer frames.
@param Data_Length
Length of data to be sent.
*/
/**************************************************************************/
void TinyLoRa::sendData(unsigned char *Data, unsigned char Data_Length, unsigned char Port, unsigned int Frame_Counter_Tx)
{
//Define variables
unsigned char i;
//Direction of frame is up
unsigned char Direction = 0x00;
unsigned char RFM_Data[64];
unsigned char RFM_Package_Length;
unsigned char MIC[4];
//Unconfirmed data up
unsigned char Mac_Header = 0x40;
unsigned char Frame_Control = 0x00;
//unsigned char Frame_Port = 0x02;
unsigned char Frame_Port = Port;
//make a copy of Data
unsigned char tmpData[Data_Length];
for (int i = 0; i < Data_Length; i++)
{
tmpData[i] = Data[i];
}
//Encrypt Data (data argument is overwritten in this function)
Encrypt_Payload(tmpData, Data_Length, Frame_Counter_Tx, Direction);
//Build the Radio Package
RFM_Data[0] = Mac_Header;
RFM_Data[1] = DevAddr[3];
RFM_Data[2] = DevAddr[2];
RFM_Data[3] = DevAddr[1];
RFM_Data[4] = DevAddr[0];
RFM_Data[5] = Frame_Control;
RFM_Data[6] = (Frame_Counter_Tx & 0x00FF);
RFM_Data[7] = ((Frame_Counter_Tx >> 8) & 0x00FF);
RFM_Data[8] = Frame_Port;
//Set Current package length
RFM_Package_Length = 9;
//Load Data
for(i = 0; i < Data_Length; i++)
{
RFM_Data[RFM_Package_Length + i] = tmpData[i];
}
//Add data Lenth to package length
RFM_Package_Length = RFM_Package_Length + Data_Length;
#ifdef DEBUG
Serial.print("Package length: ");
Serial.println(RFM_Package_Length);
#endif
//Calculate MIC
Calculate_MIC(RFM_Data, MIC, RFM_Package_Length, Frame_Counter_Tx, Direction);
//Load MIC in package
for(i = 0; i < 4; i++)
{
RFM_Data[i + RFM_Package_Length] = MIC[i];
}
//Add MIC length to RFM package length
RFM_Package_Length = RFM_Package_Length + 4;
//Send Package
RFM_Send_Package(RFM_Data, RFM_Package_Length);
#ifdef DEBUG
Serial.println("sent package!");
#endif
}
/**************************************************************************/
/*!
@brief Function used to encrypt and decrypt the data in a LoRaWAN
data packet.
@param *Data
Pointer to the data to decrypt or encrypt.
@param Data_Length
Number of bytes to be transmitted.
@param Frame_Counter
Counts upstream frames.
@param Direction
Direction of message (is up).
*/
/**************************************************************************/
void TinyLoRa::Encrypt_Payload(unsigned char *Data, unsigned char Data_Length, unsigned int Frame_Counter, unsigned char Direction)
{
unsigned char i = 0x00;
unsigned char j;
unsigned char Number_of_Blocks = 0x00;
unsigned char Incomplete_Block_Size = 0x00;
unsigned char Block_A[16];
//Calculate number of blocks
Number_of_Blocks = Data_Length / 16;
Incomplete_Block_Size = Data_Length % 16;
if(Incomplete_Block_Size != 0)
{
Number_of_Blocks++;
}
for(i = 1; i <= Number_of_Blocks; i++)
{
Block_A[0] = 0x01;
Block_A[1] = 0x00;
Block_A[2] = 0x00;
Block_A[3] = 0x00;
Block_A[4] = 0x00;
Block_A[5] = Direction;
Block_A[6] = DevAddr[3];
Block_A[7] = DevAddr[2];
Block_A[8] = DevAddr[1];
Block_A[9] = DevAddr[0];
Block_A[10] = (Frame_Counter & 0x00FF);
Block_A[11] = ((Frame_Counter >> 8) & 0x00FF);
Block_A[12] = 0x00; //Frame counter upper Bytes
Block_A[13] = 0x00;
Block_A[14] = 0x00;
Block_A[15] = i;
//Calculate S
AES_Encrypt(Block_A,AppSkey); //original
//Check for last block
if(i != Number_of_Blocks)
{
for(j = 0; j < 16; j++)
{
*Data = *Data ^ Block_A[j];
Data++;
}
}
else
{
if(Incomplete_Block_Size == 0)
{
Incomplete_Block_Size = 16;
}
for(j = 0; j < Incomplete_Block_Size; j++)
{
*Data = *Data ^ Block_A[j];
Data++;
}
}
}
}
/**************************************************************************/
/*!
@brief Function used to calculate the validity of data messages.
@param *Data
Pointer to the data to decrypt or encrypt.
@param Data_Length
Number of bytes to be transmitted.
@param *Final_Mic
Pointer to MIC array (4 bytes).
@param Frame_Counter
Frame counter of upstream frames.
@param Direction
Direction of message (is up?).
*/
/**************************************************************************/
void TinyLoRa::Calculate_MIC(unsigned char *Data, unsigned char *Final_MIC, unsigned char Data_Length, unsigned int Frame_Counter, unsigned char Direction)
{
unsigned char i;
unsigned char Block_B[16];
unsigned char Key_K1[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
unsigned char Key_K2[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
//unsigned char Data_Copy[16];
unsigned char Old_Data[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
unsigned char New_Data[16] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
unsigned char Number_of_Blocks = 0x00;
unsigned char Incomplete_Block_Size = 0x00;
unsigned char Block_Counter = 0x01;
//Create Block_B
Block_B[0] = 0x49;
Block_B[1] = 0x00;
Block_B[2] = 0x00;
Block_B[3] = 0x00;
Block_B[4] = 0x00;
Block_B[5] = Direction;
Block_B[6] = DevAddr[3];
Block_B[7] = DevAddr[2];
Block_B[8] = DevAddr[1];
Block_B[9] = DevAddr[0];
Block_B[10] = (Frame_Counter & 0x00FF);
Block_B[11] = ((Frame_Counter >> 8) & 0x00FF);
Block_B[12] = 0x00; //Frame counter upper bytes
Block_B[13] = 0x00;
Block_B[14] = 0x00;
Block_B[15] = Data_Length;
//Calculate number of Blocks and blocksize of last block
Number_of_Blocks = Data_Length / 16;
Incomplete_Block_Size = Data_Length % 16;
if(Incomplete_Block_Size != 0)
{
Number_of_Blocks++;
}
Generate_Keys(Key_K1, Key_K2);
//Preform Calculation on Block B0
//Preform AES encryption
AES_Encrypt(Block_B,NwkSkey);
//Copy Block_B to Old_Data
for(i = 0; i < 16; i++)
{
Old_Data[i] = Block_B[i];
}
//Preform full calculating until n-1 messsage blocks
while(Block_Counter < Number_of_Blocks)
{
//Copy data into array
for(i = 0; i < 16; i++)
{
New_Data[i] = *Data;
Data++;
}
//Preform XOR with old data
XOR(New_Data,Old_Data);
//Preform AES encryption
AES_Encrypt(New_Data,NwkSkey);
//Copy New_Data to Old_Data
for(i = 0; i < 16; i++)
{
Old_Data[i] = New_Data[i];
}
//Raise Block counter
Block_Counter++;
}
//Perform calculation on last block
//Check if Datalength is a multiple of 16
if(Incomplete_Block_Size == 0)
{
//Copy last data into array
for(i = 0; i < 16; i++)
{
New_Data[i] = *Data;
Data++;
}
//Preform XOR with Key 1
XOR(New_Data,Key_K1);
//Preform XOR with old data
XOR(New_Data,Old_Data);
//Preform last AES routine
// read NwkSkey from PROGMEM
AES_Encrypt(New_Data,NwkSkey);
}
else
{
//Copy the remaining data and fill the rest
for(i = 0; i < 16; i++)
{
if(i < Incomplete_Block_Size)
{
New_Data[i] = *Data;
Data++;
}
if(i == Incomplete_Block_Size)
{
New_Data[i] = 0x80;
}
if(i > Incomplete_Block_Size)
{
New_Data[i] = 0x00;
}
}
//Preform XOR with Key 2
XOR(New_Data,Key_K2);
//Preform XOR with Old data
XOR(New_Data,Old_Data);
//Preform last AES routine
AES_Encrypt(New_Data,NwkSkey);
}
Final_MIC[0] = New_Data[0];
Final_MIC[1] = New_Data[1];
Final_MIC[2] = New_Data[2];
Final_MIC[3] = New_Data[3];
// Generate a random number between 0 and 7 to select next transmit channel
randomNum = Final_MIC[3] & 0x07;
// Generate a random number between 0 and 7 to randomise next transmit message schedule
txrandomNum = Final_MIC[2] & 0x07;
}
/**************************************************************************/
/*!
@brief Function used to generate keys for the MIC calculation.
@param *K1
Pointer to Key1.
@param *K2
Pointer to Key2.
*/
/**************************************************************************/
void TinyLoRa::Generate_Keys(unsigned char *K1, unsigned char *K2)
{
unsigned char i;
unsigned char MSB_Key;
//Encrypt the zeros in K1 with the NwkSkey
AES_Encrypt(K1,NwkSkey);
//Create K1
//Check if MSB is 1
if((K1[0] & 0x80) == 0x80)
{
MSB_Key = 1;
}
else
{
MSB_Key = 0;
}
//Shift K1 one bit left
Shift_Left(K1);
//if MSB was 1
if(MSB_Key == 1)
{
K1[15] = K1[15] ^ 0x87;
}
//Copy K1 to K2
for( i = 0; i < 16; i++)
{
K2[i] = K1[i];
}
//Check if MSB is 1
if((K2[0] & 0x80) == 0x80)
{
MSB_Key = 1;
}
else
{
MSB_Key = 0;
}
//Shift K2 one bit left
Shift_Left(K2);
//Check if MSB was 1
if(MSB_Key == 1)
{
K2[15] = K2[15] ^ 0x87;
}
}
void TinyLoRa::Shift_Left(unsigned char *Data)
{
unsigned char i;
unsigned char Overflow = 0;
//unsigned char High_Byte, Low_Byte;
for(i = 0; i < 16; i++)
{
//Check for overflow on next byte except for the last byte
if(i < 15)
{
//Check if upper bit is one
if((Data[i+1] & 0x80) == 0x80)
{
Overflow = 1;
}
else
{
Overflow = 0;
}
}
else
{
Overflow = 0;
}
//Shift one left
Data[i] = (Data[i] << 1) + Overflow;
}
}
/**************************************************************************/
/*!
@brief Function to XOR two character arrays.
@param *New_Data
A pointer to the calculated data.
@param *Old_Data
A pointer to the data to be xor'd.
*/
/**************************************************************************/
void TinyLoRa::XOR(unsigned char *New_Data,unsigned char *Old_Data)
{
unsigned char i;
for(i = 0; i < 16; i++)
{
New_Data[i] = New_Data[i] ^ Old_Data[i];
}
}
/**************************************************************************/
/*!
@brief Function used to perform AES encryption.
@param *Data
Pointer to the data to decrypt or encrypt.
@param *Key
Pointer to AES encryption key.
*/
/**************************************************************************/
void TinyLoRa::AES_Encrypt(unsigned char *Data, unsigned char *Key)
{
unsigned char Row, Column, Round = 0;
unsigned char Round_Key[16];
unsigned char State[4][4];
// Copy input to State arry
for( Column = 0; Column < 4; Column++ )
{
for( Row = 0; Row < 4; Row++ )
{
State[Row][Column] = Data[Row + (Column << 2)];
}
}
// Copy key to round key
memcpy( &Round_Key[0], &Key[0], 16 );
// Add round key
AES_Add_Round_Key( Round_Key, State );
// Preform 9 full rounds with mixed collums
for( Round = 1 ; Round < 10 ; Round++ )
{
// Perform Byte substitution with S table
for( Column = 0 ; Column < 4 ; Column++ )
{
for( Row = 0 ; Row < 4 ; Row++ )
{
State[Row][Column] = AES_Sub_Byte( State[Row][Column] );
}
}
// Perform Row Shift
AES_Shift_Rows(State);
// Mix Collums
AES_Mix_Collums(State);
// Calculate new round key
AES_Calculate_Round_Key(Round, Round_Key);
// Add the round key to the Round_key
AES_Add_Round_Key(Round_Key, State);
}
// Perform Byte substitution with S table whitout mix collums
for( Column = 0 ; Column < 4 ; Column++ )
{
for( Row = 0; Row < 4; Row++ )
{
State[Row][Column] = AES_Sub_Byte(State[Row][Column]);
}
}
// Shift rows
AES_Shift_Rows(State);
// Calculate new round key
AES_Calculate_Round_Key( Round, Round_Key );
// Add round key
AES_Add_Round_Key( Round_Key, State );
// Copy the State into the data array
for( Column = 0; Column < 4; Column++ )
{
for( Row = 0; Row < 4; Row++ )
{
Data[Row + (Column << 2)] = State[Row][Column];
}
}
}
/**************************************************************************/
/*!
@brief Function performs AES AddRoundKey step.
@param *Round_Key
Pointer to the round subkey.
@param *State
Pointer to bytes of the states-to-be-xor'd.
*/
/**************************************************************************/
void TinyLoRa::AES_Add_Round_Key(unsigned char *Round_Key, unsigned char (*State)[4])
{
unsigned char Row, Collum;
for(Collum = 0; Collum < 4; Collum++)
{
for(Row = 0; Row < 4; Row++)
{
State[Row][Collum] ^= Round_Key[Row + (Collum << 2)];
}
}
}
/**************************************************************************/
/*!
@brief Function performs AES SubBytes step.
@param Byte
Individual byte, from state array.
*/
/**************************************************************************/
unsigned char TinyLoRa::AES_Sub_Byte(unsigned char Byte)
{
// unsigned char S_Row,S_Collum;
// unsigned char S_Byte;
//
// S_Row = ((Byte >> 4) & 0x0F);
// S_Collum = ((Byte >> 0) & 0x0F);
// S_Byte = S_Table [S_Row][S_Collum];
//return S_Table [ ((Byte >> 4) & 0x0F) ] [ ((Byte >> 0) & 0x0F) ]; // original
return pgm_read_byte(&(S_Table [((Byte >> 4) & 0x0F)] [((Byte >> 0) & 0x0F)]));
}
/**************************************************************************/
/*!
@brief Function performs AES ShiftRows step.
@param *State
Pointer to state array.
*/
/**************************************************************************/
void TinyLoRa::AES_Shift_Rows(unsigned char (*State)[4])
{
unsigned char Buffer;
//Store firt byte in buffer
Buffer = State[1][0];
//Shift all bytes
State[1][0] = State[1][1];
State[1][1] = State[1][2];
State[1][2] = State[1][3];
State[1][3] = Buffer;
Buffer = State[2][0];
State[2][0] = State[2][2];
State[2][2] = Buffer;
Buffer = State[2][1];
State[2][1] = State[2][3];
State[2][3] = Buffer;
Buffer = State[3][3];
State[3][3] = State[3][2];
State[3][2] = State[3][1];
State[3][1] = State[3][0];
State[3][0] = Buffer;
}
/**************************************************************************/
/*!
@brief Function performs AES MixColumns step.
@param *State
Pointer to state array.
*/
/**************************************************************************/
void TinyLoRa::AES_Mix_Collums(unsigned char (*State)[4])
{
unsigned char Row,Collum;
unsigned char a[4], b[4];
for(Collum = 0; Collum < 4; Collum++)
{
for(Row = 0; Row < 4; Row++)
{
a[Row] = State[Row][Collum];
b[Row] = (State[Row][Collum] << 1);
if((State[Row][Collum] & 0x80) == 0x80)
{
b[Row] ^= 0x1B;
}
}
State[0][Collum] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3];
State[1][Collum] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3];
State[2][Collum] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3];
State[3][Collum] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3];
}
} // AES_Mix_Collums
/**************************************************************************/
/*!
@brief Function performs AES Round Key Calculation.
@param Round
Number of rounds to perform (depends on key size).
@param Round_Key
Pointer to round key.
*/
/**************************************************************************/
void TinyLoRa::AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key)
{
unsigned char i, j, b, Rcon;
unsigned char Temp[4];
//Calculate Rcon
Rcon = 0x01;
while(Round != 1)
{
b = Rcon & 0x80;
Rcon = Rcon << 1;
if(b == 0x80)
{
Rcon ^= 0x1b;
}
Round--;
}
// Calculate first Temp
// Copy laste byte from previous key and subsitute the byte, but shift the array contents around by 1.
Temp[0] = AES_Sub_Byte( Round_Key[12 + 1] );
Temp[1] = AES_Sub_Byte( Round_Key[12 + 2] );
Temp[2] = AES_Sub_Byte( Round_Key[12 + 3] );
Temp[3] = AES_Sub_Byte( Round_Key[12 + 0] );
// XOR with Rcon
Temp[0] ^= Rcon;
// Calculate new key
for(i = 0; i < 4; i++)
{
for(j = 0; j < 4; j++)
{
Round_Key[j + (i << 2)] ^= Temp[j];
Temp[j] = Round_Key[j + (i << 2)];
}
}
}