diff --git a/features/lorawan/lorastack/mac/LoRaMac.cpp b/features/lorawan/lorastack/mac/LoRaMac.cpp new file mode 100644 index 0000000000..4d7418e24c --- /dev/null +++ b/features/lorawan/lorastack/mac/LoRaMac.cpp @@ -0,0 +1,3606 @@ +/** + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech + ___ _____ _ ___ _ _____ ___ ___ ___ ___ +/ __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| +\__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| +|___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| +embedded.connectivity.solutions=============== + +Description: LoRa MAC layer implementation + +License: Revised BSD License, see LICENSE.TXT file include in the project + +Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel Jaeckle ( STACKFORCE ) + +Copyright (c) 2017, Arm Limited and affiliates. + +SPDX-License-Identifier: BSD-3-Clause +*/ +#include +#include "events/EventQueue.h" +#include "rtos/Thread.h" +#include "LoRaMac.h" +#include "LoRaMacCrypto.h" +#include "LoRaMacTest.h" +#include "netsocket/LoRaRadio.h" +#include "lorastack/phy/LoRaPHY.h" +#if defined(FEATURE_COMMON_PAL) +#include "mbed_trace.h" +#define TRACE_GROUP "LMAC" +#else +#define tr_debug(...) (void(0)) //dummies if feature common pal is not added +#define tr_info(...) (void(0)) //dummies if feature common pal is not added +#define tr_error(...) (void(0)) //dummies if feature common pal is not added +#endif //defined(FEATURE_COMMON_PAL) + + +/** + * LoRa PHY layer object storage + */ +static LoRaPHY *lora_phy; + +/** + * Radio event callback handlers for MAC + */ +radio_events_t RadioEvents; + +/*! + * Maximum PHY layer payload size + */ +#define LORAMAC_PHY_MAXPAYLOAD 255 + +/*! + * Maximum MAC commands buffer size + */ +#define LORA_MAC_COMMAND_MAX_LENGTH 128 + +/*! + * Maximum length of the fOpts field + */ +#define LORA_MAC_COMMAND_MAX_FOPTS_LENGTH 15 + +/*! + * LoRaMac duty cycle for the back-off procedure during the first hour. + */ +#define BACKOFF_DC_1_HOUR 100 + +/*! + * LoRaMac duty cycle for the back-off procedure during the next 10 hours. + */ +#define BACKOFF_DC_10_HOURS 1000 + +/*! + * LoRaMac duty cycle for the back-off procedure during the next 24 hours. + */ +#define BACKOFF_DC_24_HOURS 10000 + +/*! + * Device IEEE EUI + */ +static uint8_t *LoRaMacDevEui; + +/*! + * Application IEEE EUI + */ +static uint8_t *LoRaMacAppEui; + +/*! + * AES encryption/decryption cipher application key + */ +static uint8_t *LoRaMacAppKey; + +/*! + * AES encryption/decryption cipher network session key + */ +static uint8_t LoRaMacNwkSKey[] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/*! + * AES encryption/decryption cipher application session key + */ +static uint8_t LoRaMacAppSKey[] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/*! + * Device nonce is a random value extracted by issuing a sequence of RSSI + * measurements + */ +static uint16_t LoRaMacDevNonce; + +/*! + * Network ID ( 3 bytes ) + */ +static uint32_t LoRaMacNetID; + +/*! + * Mote Address + */ +static uint32_t LoRaMacDevAddr; + +/*! + * Multicast channels linked list + */ +static MulticastParams_t *MulticastChannels = NULL; + +/*! + * Actual device class + */ +static DeviceClass_t LoRaMacDeviceClass; + +/*! + * Indicates if the node is connected to a private or public network + */ +static bool PublicNetwork; + +/*! + * Indicates if the node supports repeaters + */ +static bool RepeaterSupport; + +/*! + * Buffer containing the data to be sent or received. + */ +static uint8_t LoRaMacBuffer[LORAMAC_PHY_MAXPAYLOAD]; + +/*! + * Length of packet in LoRaMacBuffer + */ +static uint16_t LoRaMacBufferPktLen = 0; + +/*! + * Length of the payload in LoRaMacBuffer + */ +static uint8_t LoRaMacTxPayloadLen = 0; + +/*! + * Buffer containing the upper layer data. + */ +static uint8_t LoRaMacRxPayload[LORAMAC_PHY_MAXPAYLOAD]; + +/*! + * LoRaMAC frame counter. Each time a packet is sent the counter is incremented. + * Only the 16 LSB bits are sent + */ +static uint32_t UpLinkCounter = 0; + +/*! + * LoRaMAC frame counter. Each time a packet is received the counter is incremented. + * Only the 16 LSB bits are received + */ +static uint32_t DownLinkCounter = 0; + +/*! + * IsPacketCounterFixed enables the MIC field tests by fixing the + * UpLinkCounter value + */ +static bool IsUpLinkCounterFixed = false; + +/*! + * Used for test purposes. Disables the opening of the reception windows. + */ +static bool IsRxWindowsEnabled = true; + +/*! + * Indicates if the MAC layer has already joined a network. + */ +static bool IsLoRaMacNetworkJoined = false; + +/*! + * LoRaMac ADR control status + */ +static bool AdrCtrlOn = false; + +/*! + * Counts the number of missed ADR acknowledgements + */ +static uint32_t AdrAckCounter = 0; + +/*! + * If the node has sent a FRAME_TYPE_DATA_CONFIRMED_UP this variable indicates + * if the nodes needs to manage the server acknowledgement. + */ +static bool NodeAckRequested = false; + +/*! + * If the server has sent a FRAME_TYPE_DATA_CONFIRMED_DOWN this variable indicates + * if the ACK bit must be set for the next transmission + */ +static bool SrvAckRequested = false; + +/*! + * Indicates if the MAC layer wants to send MAC commands + */ +static bool MacCommandsInNextTx = false; + +/*! + * Contains the current MacCommandsBuffer index + */ +static uint8_t MacCommandsBufferIndex = 0; + +/*! + * Contains the current MacCommandsBuffer index for MAC commands to repeat + */ +static uint8_t MacCommandsBufferToRepeatIndex = 0; + +/*! + * Buffer containing the MAC layer commands + */ +static uint8_t MacCommandsBuffer[LORA_MAC_COMMAND_MAX_LENGTH]; + +/*! + * Buffer containing the MAC layer commands which must be repeated + */ +static uint8_t MacCommandsBufferToRepeat[LORA_MAC_COMMAND_MAX_LENGTH]; + +/*! + * LoRaMac parameters + */ +LoRaMacParams_t LoRaMacParams; + +/*! + * LoRaMac default parameters + */ +LoRaMacParams_t LoRaMacParamsDefaults; + +/*! + * Uplink messages repetitions counter + */ +static uint8_t ChannelsNbRepCounter = 0; + +/*! + * Maximum duty cycle + * \remark Possibility to shutdown the device. + */ +static uint8_t MaxDCycle = 0; + +/*! + * Aggregated duty cycle management + */ +static uint16_t AggregatedDCycle; +static TimerTime_t AggregatedLastTxDoneTime; +static TimerTime_t AggregatedTimeOff; + +/*! + * Enables/Disables duty cycle management (Test only) + */ +static bool DutyCycleOn; + +/*! + * Current channel index + */ +static uint8_t Channel; + +/*! + * Current channel index + */ +static uint8_t LastTxChannel; + +/*! + * Set to true, if the last uplink was a join request + */ +static bool LastTxIsJoinRequest; + +/*! + * Stores the time at LoRaMac initialization. + * + * \remark Used for the BACKOFF_DC computation. + */ +static TimerTime_t LoRaMacInitializationTime = 0; + +/*! + * LoRaMac internal states + */ +enum eLoRaMacState +{ + LORAMAC_IDLE = 0x00000000, + LORAMAC_TX_RUNNING = 0x00000001, + LORAMAC_RX = 0x00000002, + LORAMAC_ACK_REQ = 0x00000004, + LORAMAC_ACK_RETRY = 0x00000008, + LORAMAC_TX_DELAYED = 0x00000010, + LORAMAC_TX_CONFIG = 0x00000020, + LORAMAC_RX_ABORT = 0x00000040, +}; + +/*! + * LoRaMac internal state + */ +uint32_t LoRaMacState = LORAMAC_IDLE; + +/*! + * LoRaMac timer used to check the LoRaMacState (runs every second) + */ +static TimerEvent_t MacStateCheckTimer; + +/** + * Timer to handle the application data transmission duty cycle + */ +static TimerEvent_t TxNextPacketTimer; + +/*! + * LoRaMac upper layer event functions + */ +static LoRaMacPrimitives_t *LoRaMacPrimitives; + +/*! + * LoRaMac upper layer callback functions + */ +static LoRaMacCallback_t *LoRaMacCallbacks; + +/*! + * LoRaMac duty cycle delayed Tx timer + */ +static TimerEvent_t TxDelayedTimer; + +/*! + * LoRaMac reception windows timers + */ +static TimerEvent_t RxWindowTimer1; +static TimerEvent_t RxWindowTimer2; + +/*! + * LoRaMac reception windows delay + * \remark normal frame: RxWindowXDelay = ReceiveDelayX - RADIO_WAKEUP_TIME + * join frame : RxWindowXDelay = JoinAcceptDelayX - RADIO_WAKEUP_TIME + */ +static uint32_t RxWindow1Delay; +static uint32_t RxWindow2Delay; + +/*! + * LoRaMac Rx windows configuration + */ +static RxConfigParams_t RxWindow1Config; +static RxConfigParams_t RxWindow2Config; + +/*! + * Acknowledge timeout timer. Used for packet retransmissions. + */ +static TimerEvent_t AckTimeoutTimer; + +/*! + * Number of trials to get a frame acknowledged + */ +static uint8_t AckTimeoutRetries = 1; + +/*! + * Number of trials to get a frame acknowledged + */ +static uint8_t AckTimeoutRetriesCounter = 1; + +/*! + * Indicates if the AckTimeout timer has expired or not + */ +static bool AckTimeoutRetry = false; + +/*! + * Last transmission time on air + */ +TimerTime_t TxTimeOnAir = 0; + +/*! + * Number of trials for the Join Request + */ +static uint8_t JoinRequestTrials; + +/*! + * Maximum number of trials for the Join Request + */ +static uint8_t MaxJoinRequestTrials; + +/*! + * Structure to hold an MCPS indication data. + */ +static McpsIndication_t McpsIndication; + +/*! + * Structure to hold MCPS confirm data. + */ +static McpsConfirm_t McpsConfirm; + +/*! + * Structure to hold MLME confirm data. + */ +static MlmeConfirm_t MlmeConfirm; + +/*! + * Holds the current rx window slot + */ +static uint8_t RxSlot = 0; + +/*! + * LoRaMac tx/rx operation state + */ +LoRaMacFlags_t LoRaMacFlags; + +/*! + * \brief Function to be executed on Radio Tx Done event + */ +static void OnRadioTxDone( void ); + +/*! + * \brief This function prepares the MAC to abort the execution of function + * OnRadioRxDone in case of a reception error. + */ +static void PrepareRxDoneAbort( void ); + +/*! + * \brief Function to be executed on Radio Rx Done event + */ +static void OnRadioRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr ); + +/*! + * \brief Function executed on Radio Tx Timeout event + */ +static void OnRadioTxTimeout( void ); + +/*! + * \brief Function executed on Radio Rx error event + */ +static void OnRadioRxError( void ); + +/*! + * \brief Function executed on Radio Rx Timeout event + */ +static void OnRadioRxTimeout( void ); + +/*! + * \brief Function executed on Resend Frame timer event. + */ +static void OnMacStateCheckTimerEvent( void ); + +/*! + * \brief Function executed on duty cycle delayed Tx timer event + */ +static void OnTxDelayedTimerEvent( void ); + +/** + * \brief Function to be executed when next Tx is possible + */ +static void OnNextTx( void ); + +/*! + * \brief Function executed on first Rx window timer event + */ +static void OnRxWindow1TimerEvent( void ); + +/*! + * \brief Function executed on second Rx window timer event + */ +static void OnRxWindow2TimerEvent( void ); + +/*! + * \brief Function executed on AckTimeout timer event + */ +static void OnAckTimeoutTimerEvent( void ); + +/*! + * \brief Initializes and opens the reception window + * + * \param [IN] rxContinuous Set to true, if the RX is in continuous mode + * \param [IN] maxRxWindow Maximum RX window timeout + */ +static void RxWindowSetup( bool rxContinuous, uint32_t maxRxWindow ); + +/*! + * \brief Adds a new MAC command to be sent. + * + * \Remark MAC layer internal function + * + * \param [in] cmd MAC command to be added + * [MOTE_MAC_LINK_CHECK_REQ, + * MOTE_MAC_LINK_ADR_ANS, + * MOTE_MAC_DUTY_CYCLE_ANS, + * MOTE_MAC_RX2_PARAM_SET_ANS, + * MOTE_MAC_DEV_STATUS_ANS + * MOTE_MAC_NEW_CHANNEL_ANS] + * \param [in] p1 1st parameter ( optional depends on the command ) + * \param [in] p2 2nd parameter ( optional depends on the command ) + * + * \retval status Function status [0: OK, 1: Unknown command, 2: Buffer full] + */ +static LoRaMacStatus_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 ); + +/*! + * \brief Parses the MAC commands which must be repeated. + * + * \Remark MAC layer internal function + * + * \param [IN] cmdBufIn Buffer which stores the MAC commands to send + * \param [IN] length Length of the input buffer to parse + * \param [OUT] cmdBufOut Buffer which stores the MAC commands which must be + * repeated. + * + * \retval Size of the MAC commands to repeat. + */ +static uint8_t ParseMacCommandsToRepeat( uint8_t* cmdBufIn, uint8_t length, uint8_t* cmdBufOut ); + +/*! + * \brief Validates if the payload fits into the frame, taking the datarate + * into account. + * + * \details Refer to chapter 4.3.2 of the LoRaWAN specification, v1.0 + * + * \param lenN Length of the application payload. The length depends on the + * datarate and is region specific + * + * \param datarate Current datarate + * + * \param fOptsLen Length of the fOpts field + * + * \retval [false: payload does not fit into the frame, true: payload fits into + * the frame] + */ +static bool ValidatePayloadLength( uint8_t lenN, int8_t datarate, uint8_t fOptsLen ); + +/*! + * \brief Decodes MAC commands in the fOpts field and in the payload + */ +static void ProcessMacCommands( uint8_t *payload, uint8_t macIndex, uint8_t commandsSize, uint8_t snr ); + +/*! + * \brief LoRaMAC layer generic send frame + * + * \param [IN] macHdr MAC header field + * \param [IN] fPort MAC payload port + * \param [IN] fBuffer MAC data buffer to be sent + * \param [IN] fBufferSize MAC data buffer size + * \retval status Status of the operation. + */ +LoRaMacStatus_t Send( LoRaMacHeader_t *macHdr, uint8_t fPort, void *fBuffer, uint16_t fBufferSize ); + +/*! + * \brief LoRaMAC layer frame buffer initialization + * + * \param [IN] macHdr MAC header field + * \param [IN] fCtrl MAC frame control field + * \param [IN] fOpts MAC commands buffer + * \param [IN] fPort MAC payload port + * \param [IN] fBuffer MAC data buffer to be sent + * \param [IN] fBufferSize MAC data buffer size + * \retval status Status of the operation. + */ +LoRaMacStatus_t PrepareFrame( LoRaMacHeader_t *macHdr, LoRaMacFrameCtrl_t *fCtrl, uint8_t fPort, void *fBuffer, uint16_t fBufferSize ); + +/* + * \brief Schedules the frame according to the duty cycle + * + * \retval Status of the operation + */ +static LoRaMacStatus_t ScheduleTx( void ); + +/* + * \brief Calculates the back-off time for the band of a channel. + * + * \param [IN] channel The last Tx channel index + */ +static void CalculateBackOff( uint8_t channel ); + +/*! + * \brief LoRaMAC layer prepared frame buffer transmission with channel specification + * + * \remark PrepareFrame must be called at least once before calling this + * function. + * + * \param [IN] channel Channel to transmit on + * \retval status Status of the operation. + */ +LoRaMacStatus_t SendFrameOnChannel( uint8_t channel ); + +/*! + * \brief Sets the radio in continuous transmission mode + * + * \remark Uses the radio parameters set on the previous transmission. + * + * \param [IN] timeout Time in seconds while the radio is kept in continuous wave mode + * \retval status Status of the operation. + */ +LoRaMacStatus_t SetTxContinuousWave( uint16_t timeout ); + +/*! + * \brief Sets the radio in continuous transmission mode + * + * \remark Uses the radio parameters set on the previous transmission. + * + * \param [IN] timeout Time in seconds while the radio is kept in continuous wave mode + * \param [IN] frequency RF frequency to be set. + * \param [IN] power RF output power to be set. + * \retval status Status of the operation. + */ +LoRaMacStatus_t SetTxContinuousWave1( uint16_t timeout, uint32_t frequency, uint8_t power ); + +/*! + * \brief Resets MAC specific parameters to default + */ +static void ResetMacParameters( void ); + + +/** + * Prototypes for ISR handlers + */ +static void handle_cad_done(bool cad); +static void handle_tx_done(void); +static void handle_rx_done(uint8_t *payload, uint16_t size, int16_t rssi, + int8_t snr); +static void handle_rx_error(void); +static void handle_rx_timeout(void); +static void handle_tx_timeout(void); +static void handle_fhss_change_channel(uint8_t cur_channel); +static void handle_rx1_timer_event(void); +static void handle_rx2_timer_event(void); +static void handle_ack_timeout(void); +static void handle_delayed_tx_timer_event(void); +static void handle_mac_state_check_timer_event(void); +static void handle_next_tx_timer_event(void); + +/*************************************************************************** + * LoRaMACTask class - Handles deferred callbacks * + * and timers from Interrupt context * + **************************************************************************/ +/** + * An internal class to facilitate MAC related tasks. + * Idea is to mould Semtech code as less as possible so + * as to make future integration smoother. + * + * This class is present only if RTOS is being used. + */ +#ifdef MBED_CONF_RTOS_PRESENT + +class LoRaMACTask { + +public: + LoRaMACTask(); + events::EventQueue queue; + +private: + rtos::Thread t; +}; + +LoRaMACTask::LoRaMACTask() + : queue(16 * EVENTS_EVENT_SIZE), + t(osPriorityNormal, 2048) +{ + t.start(callback(&queue, &events::EventQueue::dispatch_forever)); +} + +static LoRaMACTask mac_task; + +#endif //MBED_CONF_RTOS_PRESENT + +/*************************************************************************** + * ISRs - Handlers * + **************************************************************************/ +static void handle_tx_done(void) +{ +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnRadioTxDone); +#else + OnRadioTxDone(); +#endif +} + +static void handle_rx_done(uint8_t *payload, uint16_t size, int16_t rssi, + int8_t snr) +{ +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnRadioRxDone, payload, size, rssi, snr); +#else + OnRadioRxDone(payload, size, rssi, snr); +#endif +} + +static void handle_rx_error(void) +{ +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnRadioRxError); +#else + OnRadioRxError(); +#endif +} + +static void handle_rx_timeout(void) +{ +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnRadioRxTimeout); +#else + OnRadioRxTimeout(); +#endif +} + +static void handle_tx_timeout(void) +{ +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnRadioTxTimeout); +#else + OnRadioTxTimeout(); +#endif +} + +static void handle_cad_done(bool cad) +{ +#ifdef MBED_CONF_RTOS_PRESENT + //mac_task.queue.call(OnRadioCadDone, cad); +#else + //TODO Doesn't exist yet + //OnRadioCadDOne(cad); +#endif +} + +static void handle_fhss_change_channel(uint8_t cur_channel) +{ +#ifdef MBED_CONF_RTOS_PRESENT + //mac_task.queue.call(OnRadioFHSSChangeChannel, cur_channel); +#else + // TODO Not implemented yet + //OnRadioFHSSChangeChannel(cur_channel); +#endif +} +static void handle_mac_state_check_timer_event(void) +{ +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnMacStateCheckTimerEvent); +#else + OnMacStateCheckTimerEvent(); +#endif +} + +static void handle_next_tx_timer_event(void) +{ + // Validate if the MAC is in a correct state + if ((LoRaMacState & LORAMAC_TX_RUNNING) == LORAMAC_TX_RUNNING) { + return; + } +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnNextTx); +#else + OnNextTx(); +#endif +} + +static void handle_delayed_tx_timer_event(void) +{ +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnTxDelayedTimerEvent); +#else + OnTxDelayedTimerEvent(); +#endif +} + +static void handle_ack_timeout() +{ +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnAckTimeoutTimerEvent); +#else + OnAckTimeoutTimerEvent(); +#endif +} + +static void handle_rx1_timer_event(void) +{ +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnRxWindow1TimerEvent); +#else + OnRxWindow1TimerEvent(); +#endif +} + +static void handle_rx2_timer_event(void) +{ +#ifdef MBED_CONF_RTOS_PRESENT + mac_task.queue.call(OnRxWindow2TimerEvent); +#else + OnRxWindow2TimerEvent(); +#endif +} + +/*************************************************************************** + * Radio event callbacks - delegated to Radio driver * + **************************************************************************/ +static void OnRadioTxDone( void ) +{ + GetPhyParams_t getPhy; + PhyParam_t phyParam; + SetBandTxDoneParams_t txDone; + TimerTime_t curTime = TimerGetCurrentTime( ); + + if( LoRaMacDeviceClass != CLASS_C ) + { + lora_phy->put_radio_to_sleep(); + } + else + { + handle_rx2_timer_event(); + } + + // Setup timers + if( IsRxWindowsEnabled == true ) + { + TimerSetValue( &RxWindowTimer1, RxWindow1Delay ); + TimerStart( &RxWindowTimer1 ); + if( LoRaMacDeviceClass != CLASS_C ) + { + TimerSetValue( &RxWindowTimer2, RxWindow2Delay ); + TimerStart( &RxWindowTimer2 ); + } + if( ( LoRaMacDeviceClass == CLASS_C ) || ( NodeAckRequested == true ) ) + { + getPhy.Attribute = PHY_ACK_TIMEOUT; + phyParam = lora_phy->get_phy_params(&getPhy); + TimerSetValue( &AckTimeoutTimer, RxWindow2Delay + phyParam.Value ); + TimerStart( &AckTimeoutTimer ); + } + } + else + { + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK; + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT; + + if( LoRaMacFlags.Value == 0 ) + { + LoRaMacFlags.Bits.McpsReq = 1; + } + LoRaMacFlags.Bits.MacDone = 1; + } + + // Verify if the last uplink was a join request + if( ( LoRaMacFlags.Bits.MlmeReq == 1 ) && ( MlmeConfirm.MlmeRequest == MLME_JOIN ) ) + { + LastTxIsJoinRequest = true; + } + else + { + LastTxIsJoinRequest = false; + } + + // Store last Tx channel + LastTxChannel = Channel; + // Update last tx done time for the current channel + txDone.Channel = Channel; + txDone.Joined = IsLoRaMacNetworkJoined; + txDone.LastTxDoneTime = curTime; + lora_phy->set_band_tx_done(&txDone); + // Update Aggregated last tx done time + AggregatedLastTxDoneTime = curTime; + + if( NodeAckRequested == false ) + { + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK; + ChannelsNbRepCounter++; + } +} + +static void PrepareRxDoneAbort( void ) +{ + LoRaMacState |= LORAMAC_RX_ABORT; + + if( NodeAckRequested ) + { + handle_ack_timeout(); + } + + LoRaMacFlags.Bits.McpsInd = 1; + LoRaMacFlags.Bits.MacDone = 1; + + // Trig OnMacCheckTimerEvent call as soon as possible + TimerSetValue( &MacStateCheckTimer, 1 ); + TimerStart( &MacStateCheckTimer ); +} + +static void OnRadioRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr ) +{ + LoRaMacHeader_t macHdr; + LoRaMacFrameCtrl_t fCtrl; + ApplyCFListParams_t applyCFList; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + bool skipIndication = false; + + uint8_t pktHeaderLen = 0; + uint32_t address = 0; + uint8_t appPayloadStartIndex = 0; + uint8_t port = 0xFF; + uint8_t frameLen = 0; + uint32_t mic = 0; + uint32_t micRx = 0; + + uint16_t sequenceCounter = 0; + uint16_t sequenceCounterPrev = 0; + uint16_t sequenceCounterDiff = 0; + uint32_t downLinkCounter = 0; + + MulticastParams_t *curMulticastParams = NULL; + uint8_t *nwkSKey = LoRaMacNwkSKey; + uint8_t *appSKey = LoRaMacAppSKey; + + uint8_t multicast = 0; + + bool isMicOk = false; + + McpsConfirm.AckReceived = false; + McpsIndication.Rssi = rssi; + McpsIndication.Snr = snr; + McpsIndication.RxSlot = RxSlot; + McpsIndication.Port = 0; + McpsIndication.Multicast = 0; + McpsIndication.FramePending = 0; + McpsIndication.Buffer = NULL; + McpsIndication.BufferSize = 0; + McpsIndication.RxData = false; + McpsIndication.AckReceived = false; + McpsIndication.DownLinkCounter = 0; + McpsIndication.McpsIndication = MCPS_UNCONFIRMED; + + lora_phy->put_radio_to_sleep(); + + TimerStop( &RxWindowTimer2 ); + + macHdr.Value = payload[pktHeaderLen++]; + + switch( macHdr.Bits.MType ) + { + case FRAME_TYPE_JOIN_ACCEPT: + if( IsLoRaMacNetworkJoined == true ) + { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR; + PrepareRxDoneAbort( ); + return; + } + + if (0 != LoRaMacJoinDecrypt( payload + 1, size - 1, LoRaMacAppKey, LoRaMacRxPayload + 1 )) { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL; + return; + } + + LoRaMacRxPayload[0] = macHdr.Value; + + if (0 != LoRaMacJoinComputeMic( LoRaMacRxPayload, size - LORAMAC_MFR_LEN, LoRaMacAppKey, &mic )) { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL; + return; + } + + micRx |= ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN]; + micRx |= ( ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 1] << 8 ); + micRx |= ( ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 2] << 16 ); + micRx |= ( ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 3] << 24 ); + + if( micRx == mic ) + { + if (0 != LoRaMacJoinComputeSKeys( LoRaMacAppKey, LoRaMacRxPayload + 1, LoRaMacDevNonce, LoRaMacNwkSKey, LoRaMacAppSKey )) { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL; + return; + } + + LoRaMacNetID = ( uint32_t )LoRaMacRxPayload[4]; + LoRaMacNetID |= ( ( uint32_t )LoRaMacRxPayload[5] << 8 ); + LoRaMacNetID |= ( ( uint32_t )LoRaMacRxPayload[6] << 16 ); + + LoRaMacDevAddr = ( uint32_t )LoRaMacRxPayload[7]; + LoRaMacDevAddr |= ( ( uint32_t )LoRaMacRxPayload[8] << 8 ); + LoRaMacDevAddr |= ( ( uint32_t )LoRaMacRxPayload[9] << 16 ); + LoRaMacDevAddr |= ( ( uint32_t )LoRaMacRxPayload[10] << 24 ); + + // DLSettings + LoRaMacParams.Rx1DrOffset = ( LoRaMacRxPayload[11] >> 4 ) & 0x07; + LoRaMacParams.Rx2Channel.Datarate = LoRaMacRxPayload[11] & 0x0F; + + // RxDelay + LoRaMacParams.ReceiveDelay1 = ( LoRaMacRxPayload[12] & 0x0F ); + if( LoRaMacParams.ReceiveDelay1 == 0 ) + { + LoRaMacParams.ReceiveDelay1 = 1; + } + LoRaMacParams.ReceiveDelay1 *= 1000; + LoRaMacParams.ReceiveDelay2 = LoRaMacParams.ReceiveDelay1 + 1000; + + // Apply CF list + applyCFList.Payload = &LoRaMacRxPayload[13]; + // Size of the regular payload is 12. Plus 1 byte MHDR and 4 bytes MIC + applyCFList.Size = size - 17; + + lora_phy->apply_cf_list(&applyCFList); + + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK; + IsLoRaMacNetworkJoined = true; + LoRaMacParams.ChannelsDatarate = LoRaMacParamsDefaults.ChannelsDatarate; + } + else + { + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL; + } + break; + case FRAME_TYPE_DATA_CONFIRMED_DOWN: + case FRAME_TYPE_DATA_UNCONFIRMED_DOWN: + { + // Check if the received payload size is valid + getPhy.UplinkDwellTime = LoRaMacParams.DownlinkDwellTime; + getPhy.Datarate = McpsIndication.RxDatarate; + getPhy.Attribute = PHY_MAX_PAYLOAD; + + // Get the maximum payload length + if( RepeaterSupport == true ) + { + getPhy.Attribute = PHY_MAX_PAYLOAD_REPEATER; + } + phyParam = lora_phy->get_phy_params(&getPhy); + if( MAX( 0, ( int16_t )( ( int16_t )size - ( int16_t )LORA_MAC_FRMPAYLOAD_OVERHEAD ) ) > phyParam.Value ) + { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR; + PrepareRxDoneAbort( ); + return; + } + + address = payload[pktHeaderLen++]; + address |= ( (uint32_t)payload[pktHeaderLen++] << 8 ); + address |= ( (uint32_t)payload[pktHeaderLen++] << 16 ); + address |= ( (uint32_t)payload[pktHeaderLen++] << 24 ); + + if( address != LoRaMacDevAddr ) + { + curMulticastParams = MulticastChannels; + while( curMulticastParams != NULL ) + { + if( address == curMulticastParams->Address ) + { + multicast = 1; + nwkSKey = curMulticastParams->NwkSKey; + appSKey = curMulticastParams->AppSKey; + downLinkCounter = curMulticastParams->DownLinkCounter; + break; + } + curMulticastParams = curMulticastParams->Next; + } + if( multicast == 0 ) + { + // We are not the destination of this frame. + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL; + PrepareRxDoneAbort( ); + return; + } + } + else + { + multicast = 0; + nwkSKey = LoRaMacNwkSKey; + appSKey = LoRaMacAppSKey; + downLinkCounter = DownLinkCounter; + } + + fCtrl.Value = payload[pktHeaderLen++]; + + sequenceCounter = ( uint16_t )payload[pktHeaderLen++]; + sequenceCounter |= ( uint16_t )payload[pktHeaderLen++] << 8; + + appPayloadStartIndex = 8 + fCtrl.Bits.FOptsLen; + + micRx |= ( uint32_t )payload[size - LORAMAC_MFR_LEN]; + micRx |= ( ( uint32_t )payload[size - LORAMAC_MFR_LEN + 1] << 8 ); + micRx |= ( ( uint32_t )payload[size - LORAMAC_MFR_LEN + 2] << 16 ); + micRx |= ( ( uint32_t )payload[size - LORAMAC_MFR_LEN + 3] << 24 ); + + sequenceCounterPrev = ( uint16_t )downLinkCounter; + sequenceCounterDiff = ( sequenceCounter - sequenceCounterPrev ); + + if( sequenceCounterDiff < ( 1 << 15 ) ) + { + downLinkCounter += sequenceCounterDiff; + LoRaMacComputeMic( payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounter, &mic ); + if( micRx == mic ) + { + isMicOk = true; + } + } + else + { + // check for sequence roll-over + uint32_t downLinkCounterTmp = downLinkCounter + 0x10000 + ( int16_t )sequenceCounterDiff; + LoRaMacComputeMic( payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounterTmp, &mic ); + if( micRx == mic ) + { + isMicOk = true; + downLinkCounter = downLinkCounterTmp; + } + } + + // Check for a the maximum allowed counter difference + getPhy.Attribute = PHY_MAX_FCNT_GAP; + phyParam = lora_phy->get_phy_params( &getPhy ); + if( sequenceCounterDiff >= phyParam.Value ) + { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_TOO_MANY_FRAMES_LOSS; + McpsIndication.DownLinkCounter = downLinkCounter; + PrepareRxDoneAbort( ); + return; + } + + if( isMicOk == true ) + { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_OK; + McpsIndication.Multicast = multicast; + McpsIndication.FramePending = fCtrl.Bits.FPending; + McpsIndication.Buffer = NULL; + McpsIndication.BufferSize = 0; + McpsIndication.DownLinkCounter = downLinkCounter; + + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK; + + AdrAckCounter = 0; + MacCommandsBufferToRepeatIndex = 0; + + // Update 32 bits downlink counter + if( multicast == 1 ) + { + McpsIndication.McpsIndication = MCPS_MULTICAST; + + if( ( curMulticastParams->DownLinkCounter == downLinkCounter ) && + ( curMulticastParams->DownLinkCounter != 0 ) ) + { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED; + McpsIndication.DownLinkCounter = downLinkCounter; + PrepareRxDoneAbort( ); + return; + } + curMulticastParams->DownLinkCounter = downLinkCounter; + } + else + { + if( macHdr.Bits.MType == FRAME_TYPE_DATA_CONFIRMED_DOWN ) + { + SrvAckRequested = true; + McpsIndication.McpsIndication = MCPS_CONFIRMED; + + if( ( DownLinkCounter == downLinkCounter ) && + ( DownLinkCounter != 0 ) ) + { + // Duplicated confirmed downlink. Skip indication. + // In this case, the MAC layer shall accept the MAC commands + // which are included in the downlink retransmission. + // It should not provide the same frame to the application + // layer again. + skipIndication = true; + } + } + else + { + SrvAckRequested = false; + McpsIndication.McpsIndication = MCPS_UNCONFIRMED; + + if( ( DownLinkCounter == downLinkCounter ) && + ( DownLinkCounter != 0 ) ) + { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED; + McpsIndication.DownLinkCounter = downLinkCounter; + PrepareRxDoneAbort( ); + return; + } + } + DownLinkCounter = downLinkCounter; + } + + // This must be done before parsing the payload and the MAC commands. + // We need to reset the MacCommandsBufferIndex here, since we need + // to take retransmissions and repetitions into account. Error cases + // will be handled in function OnMacStateCheckTimerEvent. + if( McpsConfirm.McpsRequest == MCPS_CONFIRMED ) + { + if( fCtrl.Bits.Ack == 1 ) + {// Reset MacCommandsBufferIndex when we have received an ACK. + MacCommandsBufferIndex = 0; + } + } + else + {// Reset the variable if we have received any valid frame. + MacCommandsBufferIndex = 0; + } + + // Process payload and MAC commands + if( ( ( size - 4 ) - appPayloadStartIndex ) > 0 ) + { + port = payload[appPayloadStartIndex++]; + frameLen = ( size - 4 ) - appPayloadStartIndex; + + McpsIndication.Port = port; + + if( port == 0 ) + { + // Only allow frames which do not have fOpts + if( fCtrl.Bits.FOptsLen == 0 ) + { + if (0 != LoRaMacPayloadDecrypt( payload + appPayloadStartIndex, + frameLen, + nwkSKey, + address, + DOWN_LINK, + downLinkCounter, + LoRaMacRxPayload )) { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL; + } + + // Decode frame payload MAC commands + ProcessMacCommands( LoRaMacRxPayload, 0, frameLen, snr ); + } + else + { + skipIndication = true; + } + } + else + { + if( fCtrl.Bits.FOptsLen > 0 ) + { + // Decode Options field MAC commands. Omit the fPort. + ProcessMacCommands( payload, 8, appPayloadStartIndex - 1, snr ); + } + + if (0 != LoRaMacPayloadDecrypt( payload + appPayloadStartIndex, + frameLen, + appSKey, + address, + DOWN_LINK, + downLinkCounter, + LoRaMacRxPayload )) { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL; + } + + if( skipIndication == false ) + { + McpsIndication.Buffer = LoRaMacRxPayload; + McpsIndication.BufferSize = frameLen; + McpsIndication.RxData = true; + } + } + } + else + { + if( fCtrl.Bits.FOptsLen > 0 ) + { + // Decode Options field MAC commands + ProcessMacCommands( payload, 8, appPayloadStartIndex, snr ); + } + } + + if( skipIndication == false ) + { + // Check if the frame is an acknowledgement + if( fCtrl.Bits.Ack == 1 ) + { + McpsConfirm.AckReceived = true; + McpsIndication.AckReceived = true; + + // Stop the AckTimeout timer as no more retransmissions + // are needed. + TimerStop( &AckTimeoutTimer ); + } + else + { + McpsConfirm.AckReceived = false; + + if( AckTimeoutRetriesCounter > AckTimeoutRetries ) + { + // Stop the AckTimeout timer as no more retransmissions + // are needed. + TimerStop( &AckTimeoutTimer ); + } + } + } + // Provide always an indication, skip the callback to the user application, + // in case of a confirmed downlink retransmission. + LoRaMacFlags.Bits.McpsInd = 1; + LoRaMacFlags.Bits.McpsIndSkip = skipIndication; + } + else + { + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL; + + PrepareRxDoneAbort( ); + return; + } + } + break; + case FRAME_TYPE_PROPRIETARY: + { + memcpy( LoRaMacRxPayload, &payload[pktHeaderLen], size ); + + McpsIndication.McpsIndication = MCPS_PROPRIETARY; + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_OK; + McpsIndication.Buffer = LoRaMacRxPayload; + McpsIndication.BufferSize = size - pktHeaderLen; + + LoRaMacFlags.Bits.McpsInd = 1; + break; + } + default: + McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR; + PrepareRxDoneAbort( ); + break; + } + LoRaMacFlags.Bits.MacDone = 1; + + // Trig OnMacCheckTimerEvent call as soon as possible + TimerSetValue( &MacStateCheckTimer, 1 ); + TimerStart( &MacStateCheckTimer ); +} + +static void OnRadioTxTimeout( void ) +{ + if( LoRaMacDeviceClass != CLASS_C ) + { + lora_phy->put_radio_to_sleep(); + } + else + { + handle_rx2_timer_event(); + } + + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT; + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT; + LoRaMacFlags.Bits.MacDone = 1; +} + +static void OnRadioRxError( void ) +{ + if( LoRaMacDeviceClass != CLASS_C ) + { + lora_phy->put_radio_to_sleep(); + } + else + { + handle_rx2_timer_event(); + } + + if( RxSlot == 0 ) + { + if( NodeAckRequested == true ) + { + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX1_ERROR; + } + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX1_ERROR; + + if( TimerGetElapsedTime( AggregatedLastTxDoneTime ) >= RxWindow2Delay ) + { + LoRaMacFlags.Bits.MacDone = 1; + } + } + else + { + if( NodeAckRequested == true ) + { + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_ERROR; + } + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_ERROR; + LoRaMacFlags.Bits.MacDone = 1; + } +} + +static void OnRadioRxTimeout( void ) +{ + if( LoRaMacDeviceClass != CLASS_C ) + { + lora_phy->put_radio_to_sleep(); + } + else + { + handle_rx2_timer_event(); + } + + if( RxSlot == 0 ) + { + if( NodeAckRequested == true ) + { + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX1_TIMEOUT; + } + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX1_TIMEOUT; + + if( TimerGetElapsedTime( AggregatedLastTxDoneTime ) >= RxWindow2Delay ) + { + LoRaMacFlags.Bits.MacDone = 1; + } + } + else + { + if( NodeAckRequested == true ) + { + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT; + } + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT; + + if( LoRaMacDeviceClass != CLASS_C ) + { + LoRaMacFlags.Bits.MacDone = 1; + } + } +} + +/*************************************************************************** + * Timer event callbacks - deliberated locally * + **************************************************************************/ +static void OnMacStateCheckTimerEvent( void ) +{ + GetPhyParams_t getPhy; + PhyParam_t phyParam; + bool txTimeout = false; + + TimerStop( &MacStateCheckTimer ); + + if( LoRaMacFlags.Bits.MacDone == 1 ) + { + if( ( LoRaMacState & LORAMAC_RX_ABORT ) == LORAMAC_RX_ABORT ) + { + LoRaMacState &= ~LORAMAC_RX_ABORT; + LoRaMacState &= ~LORAMAC_TX_RUNNING; + } + + if( ( LoRaMacFlags.Bits.MlmeReq == 1 ) || ( ( LoRaMacFlags.Bits.McpsReq == 1 ) ) ) + { + if( ( McpsConfirm.Status == LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT ) || + ( MlmeConfirm.Status == LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT ) ) + { + // Stop transmit cycle due to tx timeout. + LoRaMacState &= ~LORAMAC_TX_RUNNING; + MacCommandsBufferIndex = 0; + McpsConfirm.NbRetries = AckTimeoutRetriesCounter; + McpsConfirm.AckReceived = false; + McpsConfirm.TxTimeOnAir = 0; + txTimeout = true; + } + } + + if( ( NodeAckRequested == false ) && ( txTimeout == false ) ) + { + if( ( LoRaMacFlags.Bits.MlmeReq == 1 ) || ( ( LoRaMacFlags.Bits.McpsReq == 1 ) ) ) + { + if( ( LoRaMacFlags.Bits.MlmeReq == 1 ) && ( MlmeConfirm.MlmeRequest == MLME_JOIN ) ) + {// Procedure for the join request + MlmeConfirm.NbRetries = JoinRequestTrials; + + if( MlmeConfirm.Status == LORAMAC_EVENT_INFO_STATUS_OK ) + {// Node joined successfully + UpLinkCounter = 0; + ChannelsNbRepCounter = 0; + LoRaMacState &= ~LORAMAC_TX_RUNNING; + } + else + { + if( JoinRequestTrials >= MaxJoinRequestTrials ) + { + LoRaMacState &= ~LORAMAC_TX_RUNNING; + } + else + { + LoRaMacFlags.Bits.MacDone = 0; + // Sends the same frame again + handle_delayed_tx_timer_event(); + } + } + } + else + {// Procedure for all other frames + if( ( ChannelsNbRepCounter >= LoRaMacParams.ChannelsNbRep ) || ( LoRaMacFlags.Bits.McpsInd == 1 ) ) + { + if( LoRaMacFlags.Bits.McpsInd == 0 ) + { // Maximum repetitions without downlink. Reset MacCommandsBufferIndex. Increase ADR Ack counter. + // Only process the case when the MAC did not receive a downlink. + MacCommandsBufferIndex = 0; + AdrAckCounter++; + } + + ChannelsNbRepCounter = 0; + + if( IsUpLinkCounterFixed == false ) + { + UpLinkCounter++; + } + + LoRaMacState &= ~LORAMAC_TX_RUNNING; + } + else + { + LoRaMacFlags.Bits.MacDone = 0; + // Sends the same frame again + handle_delayed_tx_timer_event(); + } + } + } + } + + if( LoRaMacFlags.Bits.McpsInd == 1 ) + {// Procedure if we received a frame + if( ( McpsConfirm.AckReceived == true ) || ( AckTimeoutRetriesCounter > AckTimeoutRetries ) ) + { + AckTimeoutRetry = false; + NodeAckRequested = false; + if( IsUpLinkCounterFixed == false ) + { + UpLinkCounter++; + } + McpsConfirm.NbRetries = AckTimeoutRetriesCounter; + + LoRaMacState &= ~LORAMAC_TX_RUNNING; + } + } + + if( ( AckTimeoutRetry == true ) && ( ( LoRaMacState & LORAMAC_TX_DELAYED ) == 0 ) ) + {// Retransmissions procedure for confirmed uplinks + AckTimeoutRetry = false; + if( ( AckTimeoutRetriesCounter < AckTimeoutRetries ) && ( AckTimeoutRetriesCounter <= MAX_ACK_RETRIES ) ) + { + AckTimeoutRetriesCounter++; + + if( ( AckTimeoutRetriesCounter % 2 ) == 1 ) + { + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime; + getPhy.Datarate = LoRaMacParams.ChannelsDatarate; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParams.ChannelsDatarate = phyParam.Value; + } + // Try to send the frame again + if( ScheduleTx( ) == LORAMAC_STATUS_OK ) + { + LoRaMacFlags.Bits.MacDone = 0; + } + else + { + // The DR is not applicable for the payload size + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_TX_DR_PAYLOAD_SIZE_ERROR; + + MacCommandsBufferIndex = 0; + LoRaMacState &= ~LORAMAC_TX_RUNNING; + NodeAckRequested = false; + McpsConfirm.AckReceived = false; + McpsConfirm.NbRetries = AckTimeoutRetriesCounter; + McpsConfirm.Datarate = LoRaMacParams.ChannelsDatarate; + if( IsUpLinkCounterFixed == false ) + { + UpLinkCounter++; + } + } + } + else + { + lora_phy->load_defaults(INIT_TYPE_RESTORE); + + LoRaMacState &= ~LORAMAC_TX_RUNNING; + + MacCommandsBufferIndex = 0; + NodeAckRequested = false; + McpsConfirm.AckReceived = false; + McpsConfirm.NbRetries = AckTimeoutRetriesCounter; + if( IsUpLinkCounterFixed == false ) + { + UpLinkCounter++; + } + } + } + } + // Handle reception for Class B and Class C + if( ( LoRaMacState & LORAMAC_RX ) == LORAMAC_RX ) + { + LoRaMacState &= ~LORAMAC_RX; + } + if( LoRaMacState == LORAMAC_IDLE ) + { + if( LoRaMacFlags.Bits.McpsReq == 1 ) + { + LoRaMacPrimitives->MacMcpsConfirm( &McpsConfirm ); + LoRaMacFlags.Bits.McpsReq = 0; + } + + if( LoRaMacFlags.Bits.MlmeReq == 1 ) + { + LoRaMacPrimitives->MacMlmeConfirm( &MlmeConfirm ); + LoRaMacFlags.Bits.MlmeReq = 0; + } + + // Procedure done. Reset variables. + LoRaMacFlags.Bits.MacDone = 0; + } + else + { + // Operation not finished restart timer + TimerSetValue( &MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT ); + TimerStart( &MacStateCheckTimer ); + } + + if( LoRaMacFlags.Bits.McpsInd == 1 ) + { + if( LoRaMacDeviceClass == CLASS_C ) + {// Activate RX2 window for Class C + handle_rx2_timer_event(); + } + if( LoRaMacFlags.Bits.McpsIndSkip == 0 ) + { + LoRaMacPrimitives->MacMcpsIndication( &McpsIndication ); + } + LoRaMacFlags.Bits.McpsIndSkip = 0; + LoRaMacFlags.Bits.McpsInd = 0; + } +} + +static void OnNextTx( void ) +{ + TimerStop( &TxNextPacketTimer ); + LoRaMacCallbacks->TxNextPacketTimerEvent( ); +} + +LoRaMacStatus_t LoRaMacSetTxTimer( uint32_t TxDutyCycleTime ) +{ + TimerSetValue(&TxNextPacketTimer, TxDutyCycleTime); + TimerStart(&TxNextPacketTimer); + + return LORAMAC_STATUS_OK; +} + +LoRaMacStatus_t LoRaMacStopTxTimer( ) +{ + TimerStop(&TxNextPacketTimer); + + return LORAMAC_STATUS_OK; +} + +static void OnTxDelayedTimerEvent( void ) +{ + LoRaMacHeader_t macHdr; + LoRaMacFrameCtrl_t fCtrl; + AlternateDrParams_t altDr; + + TimerStop( &TxDelayedTimer ); + LoRaMacState &= ~LORAMAC_TX_DELAYED; + + if( ( LoRaMacFlags.Bits.MlmeReq == 1 ) && ( MlmeConfirm.MlmeRequest == MLME_JOIN ) ) + { + ResetMacParameters( ); + + altDr.NbTrials = JoinRequestTrials + 1; + LoRaMacParams.ChannelsDatarate = lora_phy->get_alternate_DR(&altDr); + + macHdr.Value = 0; + macHdr.Bits.MType = FRAME_TYPE_JOIN_REQ; + + fCtrl.Value = 0; + fCtrl.Bits.Adr = AdrCtrlOn; + + /* In case of join request retransmissions, the stack must prepare + * the frame again, because the network server keeps track of the random + * LoRaMacDevNonce values to prevent reply attacks. */ + PrepareFrame( &macHdr, &fCtrl, 0, NULL, 0 ); + } + + ScheduleTx( ); +} + +static void OnRxWindow1TimerEvent( void ) +{ + TimerStop( &RxWindowTimer1 ); + RxSlot = 0; + + RxWindow1Config.Channel = Channel; + RxWindow1Config.DrOffset = LoRaMacParams.Rx1DrOffset; + RxWindow1Config.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime; + RxWindow1Config.RepeaterSupport = RepeaterSupport; + RxWindow1Config.RxContinuous = false; + RxWindow1Config.Window = RxSlot; + + if( LoRaMacDeviceClass == CLASS_C ) + { + lora_phy->put_radio_to_standby(); + } + + lora_phy->rx_config(&RxWindow1Config, ( int8_t* )&McpsIndication.RxDatarate); + RxWindowSetup( RxWindow1Config.RxContinuous, LoRaMacParams.MaxRxWindow ); +} + +static void OnRxWindow2TimerEvent( void ) +{ + TimerStop( &RxWindowTimer2 ); + + RxWindow2Config.Channel = Channel; + RxWindow2Config.Frequency = LoRaMacParams.Rx2Channel.Frequency; + RxWindow2Config.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime; + RxWindow2Config.RepeaterSupport = RepeaterSupport; + RxWindow2Config.Window = 1; + + if( LoRaMacDeviceClass != CLASS_C ) + { + RxWindow2Config.RxContinuous = false; + } + else + { + RxWindow2Config.RxContinuous = true; + } + + if(lora_phy->rx_config(&RxWindow2Config, ( int8_t* )&McpsIndication.RxDatarate) == true ) + { + RxWindowSetup( RxWindow2Config.RxContinuous, LoRaMacParams.MaxRxWindow ); + RxSlot = RxWindow2Config.Window; + } +} + +static void OnAckTimeoutTimerEvent( void ) +{ + TimerStop( &AckTimeoutTimer ); + + if( NodeAckRequested == true ) + { + AckTimeoutRetry = true; + LoRaMacState &= ~LORAMAC_ACK_REQ; + } + if( LoRaMacDeviceClass == CLASS_C ) + { + LoRaMacFlags.Bits.MacDone = 1; + } +} + +static void RxWindowSetup( bool rxContinuous, uint32_t maxRxWindow ) +{ + lora_phy->setup_rx_window(rxContinuous, maxRxWindow); +} + +static bool ValidatePayloadLength( uint8_t lenN, int8_t datarate, uint8_t fOptsLen ) +{ + GetPhyParams_t getPhy; + PhyParam_t phyParam; + uint16_t maxN = 0; + uint16_t payloadSize = 0; + + // Setup PHY request + getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime; + getPhy.Datarate = datarate; + getPhy.Attribute = PHY_MAX_PAYLOAD; + + // Get the maximum payload length + if( RepeaterSupport == true ) + { + getPhy.Attribute = PHY_MAX_PAYLOAD_REPEATER; + } + phyParam = lora_phy->get_phy_params(&getPhy); + maxN = phyParam.Value; + + // Calculate the resulting payload size + payloadSize = ( lenN + fOptsLen ); + + // Validation of the application payload size + if( ( payloadSize <= maxN ) && ( payloadSize <= LORAMAC_PHY_MAXPAYLOAD ) ) + { + return true; + } + return false; +} + +static LoRaMacStatus_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 ) +{ + LoRaMacStatus_t status = LORAMAC_STATUS_BUSY; + // The maximum buffer length must take MAC commands to re-send into account. + uint8_t bufLen = LORA_MAC_COMMAND_MAX_LENGTH - MacCommandsBufferToRepeatIndex; + + switch( cmd ) + { + case MOTE_MAC_LINK_CHECK_REQ: + if( MacCommandsBufferIndex < bufLen ) + { + MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; + // No payload for this command + status = LORAMAC_STATUS_OK; + } + break; + case MOTE_MAC_LINK_ADR_ANS: + if( MacCommandsBufferIndex < ( bufLen - 1 ) ) + { + MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; + // Margin + MacCommandsBuffer[MacCommandsBufferIndex++] = p1; + status = LORAMAC_STATUS_OK; + } + break; + case MOTE_MAC_DUTY_CYCLE_ANS: + if( MacCommandsBufferIndex < bufLen ) + { + MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; + // No payload for this answer + status = LORAMAC_STATUS_OK; + } + break; + case MOTE_MAC_RX_PARAM_SETUP_ANS: + if( MacCommandsBufferIndex < ( bufLen - 1 ) ) + { + MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; + // Status: Datarate ACK, Channel ACK + MacCommandsBuffer[MacCommandsBufferIndex++] = p1; + status = LORAMAC_STATUS_OK; + } + break; + case MOTE_MAC_DEV_STATUS_ANS: + if( MacCommandsBufferIndex < ( bufLen - 2 ) ) + { + MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; + // 1st byte Battery + // 2nd byte Margin + MacCommandsBuffer[MacCommandsBufferIndex++] = p1; + MacCommandsBuffer[MacCommandsBufferIndex++] = p2; + status = LORAMAC_STATUS_OK; + } + break; + case MOTE_MAC_NEW_CHANNEL_ANS: + if( MacCommandsBufferIndex < ( bufLen - 1 ) ) + { + MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; + // Status: Datarate range OK, Channel frequency OK + MacCommandsBuffer[MacCommandsBufferIndex++] = p1; + status = LORAMAC_STATUS_OK; + } + break; + case MOTE_MAC_RX_TIMING_SETUP_ANS: + if( MacCommandsBufferIndex < bufLen ) + { + MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; + // No payload for this answer + status = LORAMAC_STATUS_OK; + } + break; + case MOTE_MAC_TX_PARAM_SETUP_ANS: + if( MacCommandsBufferIndex < bufLen ) + { + MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; + // No payload for this answer + status = LORAMAC_STATUS_OK; + } + break; + case MOTE_MAC_DL_CHANNEL_ANS: + if( MacCommandsBufferIndex < bufLen ) + { + MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; + // Status: Uplink frequency exists, Channel frequency OK + MacCommandsBuffer[MacCommandsBufferIndex++] = p1; + status = LORAMAC_STATUS_OK; + } + break; + default: + return LORAMAC_STATUS_SERVICE_UNKNOWN; + } + if( status == LORAMAC_STATUS_OK ) + { + MacCommandsInNextTx = true; + } + return status; +} + +static uint8_t ParseMacCommandsToRepeat( uint8_t* cmdBufIn, uint8_t length, uint8_t* cmdBufOut ) +{ + uint8_t i = 0; + uint8_t cmdCount = 0; + + if( ( cmdBufIn == NULL ) || ( cmdBufOut == NULL ) ) + { + return 0; + } + + for( i = 0; i < length; i++ ) + { + switch( cmdBufIn[i] ) + { + // STICKY + case MOTE_MAC_DL_CHANNEL_ANS: + case MOTE_MAC_RX_PARAM_SETUP_ANS: + { // 1 byte payload + cmdBufOut[cmdCount++] = cmdBufIn[i++]; + cmdBufOut[cmdCount++] = cmdBufIn[i]; + break; + } + case MOTE_MAC_RX_TIMING_SETUP_ANS: + { // 0 byte payload + cmdBufOut[cmdCount++] = cmdBufIn[i]; + break; + } + // NON-STICKY + case MOTE_MAC_DEV_STATUS_ANS: + { // 2 bytes payload + i += 2; + break; + } + case MOTE_MAC_LINK_ADR_ANS: + case MOTE_MAC_NEW_CHANNEL_ANS: + { // 1 byte payload + i++; + break; + } + case MOTE_MAC_TX_PARAM_SETUP_ANS: + case MOTE_MAC_DUTY_CYCLE_ANS: + case MOTE_MAC_LINK_CHECK_REQ: + { // 0 byte payload + break; + } + default: + break; + } + } + + return cmdCount; +} + +static void ProcessMacCommands( uint8_t *payload, uint8_t macIndex, uint8_t commandsSize, uint8_t snr ) +{ + uint8_t status = 0; + + while( macIndex < commandsSize ) + { + // Decode Frame MAC commands + switch( payload[macIndex++] ) + { + case SRV_MAC_LINK_CHECK_ANS: + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK; + MlmeConfirm.DemodMargin = payload[macIndex++]; + MlmeConfirm.NbGateways = payload[macIndex++]; + break; + case SRV_MAC_LINK_ADR_REQ: + { + LinkAdrReqParams_t linkAdrReq; + int8_t linkAdrDatarate = DR_0; + int8_t linkAdrTxPower = TX_POWER_0; + uint8_t linkAdrNbRep = 0; + uint8_t linkAdrNbBytesParsed = 0; + + // Fill parameter structure + linkAdrReq.Payload = &payload[macIndex - 1]; + linkAdrReq.PayloadSize = commandsSize - ( macIndex - 1 ); + linkAdrReq.AdrEnabled = AdrCtrlOn; + linkAdrReq.UplinkDwellTime = LoRaMacParams.UplinkDwellTime; + linkAdrReq.CurrentDatarate = LoRaMacParams.ChannelsDatarate; + linkAdrReq.CurrentTxPower = LoRaMacParams.ChannelsTxPower; + linkAdrReq.CurrentNbRep = LoRaMacParams.ChannelsNbRep; + + // Process the ADR requests + status = lora_phy->link_ADR_request(&linkAdrReq, &linkAdrDatarate, + &linkAdrTxPower, &linkAdrNbRep, + &linkAdrNbBytesParsed); + + if( ( status & 0x07 ) == 0x07 ) + { + LoRaMacParams.ChannelsDatarate = linkAdrDatarate; + LoRaMacParams.ChannelsTxPower = linkAdrTxPower; + LoRaMacParams.ChannelsNbRep = linkAdrNbRep; + } + + // Add the answers to the buffer + for( uint8_t i = 0; i < ( linkAdrNbBytesParsed / 5 ); i++ ) + { + AddMacCommand( MOTE_MAC_LINK_ADR_ANS, status, 0 ); + } + // Update MAC index + macIndex += linkAdrNbBytesParsed - 1; + } + break; + case SRV_MAC_DUTY_CYCLE_REQ: + MaxDCycle = payload[macIndex++]; + AggregatedDCycle = 1 << MaxDCycle; + AddMacCommand( MOTE_MAC_DUTY_CYCLE_ANS, 0, 0 ); + break; + case SRV_MAC_RX_PARAM_SETUP_REQ: + { + RxParamSetupReqParams_t rxParamSetupReq; + status = 0x07; + + rxParamSetupReq.DrOffset = ( payload[macIndex] >> 4 ) & 0x07; + rxParamSetupReq.Datarate = payload[macIndex] & 0x0F; + macIndex++; + + rxParamSetupReq.Frequency = ( uint32_t )payload[macIndex++]; + rxParamSetupReq.Frequency |= ( uint32_t )payload[macIndex++] << 8; + rxParamSetupReq.Frequency |= ( uint32_t )payload[macIndex++] << 16; + rxParamSetupReq.Frequency *= 100; + + // Perform request on region + status = lora_phy->setup_rx_params(&rxParamSetupReq); + + if( ( status & 0x07 ) == 0x07 ) + { + LoRaMacParams.Rx2Channel.Datarate = rxParamSetupReq.Datarate; + LoRaMacParams.Rx2Channel.Frequency = rxParamSetupReq.Frequency; + LoRaMacParams.Rx1DrOffset = rxParamSetupReq.DrOffset; + } + AddMacCommand( MOTE_MAC_RX_PARAM_SETUP_ANS, status, 0 ); + } + break; + case SRV_MAC_DEV_STATUS_REQ: + { + uint8_t batteryLevel = BAT_LEVEL_NO_MEASURE; + if( ( LoRaMacCallbacks != NULL ) && ( LoRaMacCallbacks->GetBatteryLevel != NULL ) ) + { + batteryLevel = LoRaMacCallbacks->GetBatteryLevel( ); + } + AddMacCommand( MOTE_MAC_DEV_STATUS_ANS, batteryLevel, snr ); + break; + } + case SRV_MAC_NEW_CHANNEL_REQ: + { + NewChannelReqParams_t newChannelReq; + ChannelParams_t chParam; + status = 0x03; + + newChannelReq.ChannelId = payload[macIndex++]; + newChannelReq.NewChannel = &chParam; + + chParam.Frequency = ( uint32_t )payload[macIndex++]; + chParam.Frequency |= ( uint32_t )payload[macIndex++] << 8; + chParam.Frequency |= ( uint32_t )payload[macIndex++] << 16; + chParam.Frequency *= 100; + chParam.Rx1Frequency = 0; + chParam.DrRange.Value = payload[macIndex++]; + + status = lora_phy->request_new_channel(&newChannelReq); + + AddMacCommand( MOTE_MAC_NEW_CHANNEL_ANS, status, 0 ); + } + break; + case SRV_MAC_RX_TIMING_SETUP_REQ: + { + uint8_t delay = payload[macIndex++] & 0x0F; + + if( delay == 0 ) + { + delay++; + } + LoRaMacParams.ReceiveDelay1 = delay * 1000; + LoRaMacParams.ReceiveDelay2 = LoRaMacParams.ReceiveDelay1 + 1000; + AddMacCommand( MOTE_MAC_RX_TIMING_SETUP_ANS, 0, 0 ); + } + break; + case SRV_MAC_TX_PARAM_SETUP_REQ: + { + TxParamSetupReqParams_t txParamSetupReq; + uint8_t eirpDwellTime = payload[macIndex++]; + + txParamSetupReq.UplinkDwellTime = 0; + txParamSetupReq.DownlinkDwellTime = 0; + + if( ( eirpDwellTime & 0x20 ) == 0x20 ) + { + txParamSetupReq.DownlinkDwellTime = 1; + } + if( ( eirpDwellTime & 0x10 ) == 0x10 ) + { + txParamSetupReq.UplinkDwellTime = 1; + } + txParamSetupReq.MaxEirp = eirpDwellTime & 0x0F; + + // Check the status for correctness + if( lora_phy->setup_tx_params(&txParamSetupReq ) != -1 ) + { + // Accept command + LoRaMacParams.UplinkDwellTime = txParamSetupReq.UplinkDwellTime; + LoRaMacParams.DownlinkDwellTime = txParamSetupReq.DownlinkDwellTime; + LoRaMacParams.MaxEirp = LoRaMacMaxEirpTable[txParamSetupReq.MaxEirp]; + // Add command response + AddMacCommand( MOTE_MAC_TX_PARAM_SETUP_ANS, 0, 0 ); + } + } + break; + case SRV_MAC_DL_CHANNEL_REQ: + { + DlChannelReqParams_t dlChannelReq; + status = 0x03; + + dlChannelReq.ChannelId = payload[macIndex++]; + dlChannelReq.Rx1Frequency = ( uint32_t )payload[macIndex++]; + dlChannelReq.Rx1Frequency |= ( uint32_t )payload[macIndex++] << 8; + dlChannelReq.Rx1Frequency |= ( uint32_t )payload[macIndex++] << 16; + dlChannelReq.Rx1Frequency *= 100; + + status = lora_phy->dl_channel_request(&dlChannelReq); + + AddMacCommand( MOTE_MAC_DL_CHANNEL_ANS, status, 0 ); + } + break; + default: + // Unknown command. ABORT MAC commands processing + return; + } + } +} + +// This is not actual transmission. It just schedules a message in response +// to MCPS request +LoRaMacStatus_t Send( LoRaMacHeader_t *macHdr, uint8_t fPort, void *fBuffer, uint16_t fBufferSize ) +{ + LoRaMacFrameCtrl_t fCtrl; + LoRaMacStatus_t status = LORAMAC_STATUS_PARAMETER_INVALID; + + fCtrl.Value = 0; + fCtrl.Bits.FOptsLen = 0; + fCtrl.Bits.FPending = 0; + fCtrl.Bits.Ack = false; + fCtrl.Bits.AdrAckReq = false; + fCtrl.Bits.Adr = AdrCtrlOn; + + // Prepare the frame + status = PrepareFrame( macHdr, &fCtrl, fPort, fBuffer, fBufferSize ); + + // Validate status + if( status != LORAMAC_STATUS_OK ) + { + return status; + } + + // Reset confirm parameters + McpsConfirm.NbRetries = 0; + McpsConfirm.AckReceived = false; + McpsConfirm.UpLinkCounter = UpLinkCounter; + + status = ScheduleTx( ); + + return status; +} + +static LoRaMacStatus_t ScheduleTx( void ) +{ + TimerTime_t dutyCycleTimeOff = 0; + NextChanParams_t nextChan; + + // Check if the device is off + if( MaxDCycle == 255 ) + { + return LORAMAC_STATUS_DEVICE_OFF; + } + if( MaxDCycle == 0 ) + { + AggregatedTimeOff = 0; + } + + // Update Backoff + CalculateBackOff( LastTxChannel ); + + nextChan.AggrTimeOff = AggregatedTimeOff; + nextChan.Datarate = LoRaMacParams.ChannelsDatarate; + DutyCycleOn = LORAWAN_DUTYCYCLE_ON; + nextChan.DutyCycleEnabled = DutyCycleOn; + nextChan.Joined = IsLoRaMacNetworkJoined; + nextChan.LastAggrTx = AggregatedLastTxDoneTime; + + // Select channel + while( lora_phy->set_next_channel(&nextChan, &Channel, &dutyCycleTimeOff, &AggregatedTimeOff ) == false ) + { + // Set the default datarate + LoRaMacParams.ChannelsDatarate = LoRaMacParamsDefaults.ChannelsDatarate; + // Update datarate in the function parameters + nextChan.Datarate = LoRaMacParams.ChannelsDatarate; + } + + tr_debug("Next Channel Idx=%d, DR=%d", Channel, nextChan.Datarate); + + // Compute Rx1 windows parameters + uint8_t dr_offset = lora_phy->apply_DR_offset(LoRaMacParams.DownlinkDwellTime, + LoRaMacParams.ChannelsDatarate, + LoRaMacParams.Rx1DrOffset); + + lora_phy->compute_rx_win_params(dr_offset, LoRaMacParams.MinRxSymbols, + LoRaMacParams.SystemMaxRxError, + &RxWindow1Config ); + // Compute Rx2 windows parameters + lora_phy->compute_rx_win_params(LoRaMacParams.Rx2Channel.Datarate, + LoRaMacParams.MinRxSymbols, + LoRaMacParams.SystemMaxRxError, + &RxWindow2Config ); + + if( IsLoRaMacNetworkJoined == false ) + { + RxWindow1Delay = LoRaMacParams.JoinAcceptDelay1 + RxWindow1Config.WindowOffset; + RxWindow2Delay = LoRaMacParams.JoinAcceptDelay2 + RxWindow2Config.WindowOffset; + } + else + { + if( ValidatePayloadLength( LoRaMacTxPayloadLen, LoRaMacParams.ChannelsDatarate, MacCommandsBufferIndex ) == false ) + { + return LORAMAC_STATUS_LENGTH_ERROR; + } + RxWindow1Delay = LoRaMacParams.ReceiveDelay1 + RxWindow1Config.WindowOffset; + RxWindow2Delay = LoRaMacParams.ReceiveDelay2 + RxWindow2Config.WindowOffset; + } + + // Schedule transmission of frame + if( dutyCycleTimeOff == 0 ) + { + // Try to send now + return SendFrameOnChannel( Channel ); + } + else + { + // Send later - prepare timer + LoRaMacState |= LORAMAC_TX_DELAYED; + tr_debug("Next Transmission in %lu ms", dutyCycleTimeOff); + TimerSetValue( &TxDelayedTimer, dutyCycleTimeOff ); + TimerStart( &TxDelayedTimer ); + + return LORAMAC_STATUS_OK; + } +} + +static void CalculateBackOff( uint8_t channel ) +{ + CalcBackOffParams_t calcBackOff; + + calcBackOff.Joined = IsLoRaMacNetworkJoined; + DutyCycleOn = LORAWAN_DUTYCYCLE_ON; + calcBackOff.DutyCycleEnabled = DutyCycleOn; + calcBackOff.Channel = channel; + calcBackOff.ElapsedTime = TimerGetElapsedTime( LoRaMacInitializationTime ); + calcBackOff.TxTimeOnAir = TxTimeOnAir; + calcBackOff.LastTxIsJoinRequest = LastTxIsJoinRequest; + + // Update regional back-off + lora_phy->calculate_backoff(&calcBackOff); + + // Update aggregated time-off + AggregatedTimeOff = AggregatedTimeOff + ( TxTimeOnAir * AggregatedDCycle - TxTimeOnAir ); +} + +static void ResetMacParameters( void ) +{ + IsLoRaMacNetworkJoined = false; + + // Counters + UpLinkCounter = 0; + DownLinkCounter = 0; + AdrAckCounter = 0; + + ChannelsNbRepCounter = 0; + + AckTimeoutRetries = 1; + AckTimeoutRetriesCounter = 1; + AckTimeoutRetry = false; + + MaxDCycle = 0; + AggregatedDCycle = 1; + + MacCommandsBufferIndex = 0; + MacCommandsBufferToRepeatIndex = 0; + + IsRxWindowsEnabled = true; + + LoRaMacParams.ChannelsTxPower = LoRaMacParamsDefaults.ChannelsTxPower; + LoRaMacParams.ChannelsDatarate = LoRaMacParamsDefaults.ChannelsDatarate; + LoRaMacParams.Rx1DrOffset = LoRaMacParamsDefaults.Rx1DrOffset; + LoRaMacParams.Rx2Channel = LoRaMacParamsDefaults.Rx2Channel; + LoRaMacParams.UplinkDwellTime = LoRaMacParamsDefaults.UplinkDwellTime; + LoRaMacParams.DownlinkDwellTime = LoRaMacParamsDefaults.DownlinkDwellTime; + LoRaMacParams.MaxEirp = LoRaMacParamsDefaults.MaxEirp; + LoRaMacParams.AntennaGain = LoRaMacParamsDefaults.AntennaGain; + + NodeAckRequested = false; + SrvAckRequested = false; + MacCommandsInNextTx = false; + + // Reset Multicast downlink counters + MulticastParams_t *cur = MulticastChannels; + while( cur != NULL ) + { + cur->DownLinkCounter = 0; + cur = cur->Next; + } + + // Initialize channel index. + Channel = 0; + LastTxChannel = Channel; +} + +void memcpy_convert_endianess( uint8_t *dst, const uint8_t *src, uint16_t size ) +{ + dst = dst + ( size - 1 ); + while( size-- ) + { + *dst-- = *src++; + } +} + +LoRaMacStatus_t PrepareFrame( LoRaMacHeader_t *macHdr, LoRaMacFrameCtrl_t *fCtrl, uint8_t fPort, void *fBuffer, uint16_t fBufferSize ) +{ + AdrNextParams_t adrNext; + uint16_t i; + uint8_t pktHeaderLen = 0; + uint32_t mic = 0; + const void* payload = fBuffer; + uint8_t framePort = fPort; + LoRaMacStatus_t status = LORAMAC_STATUS_OK; + + LoRaMacBufferPktLen = 0; + + NodeAckRequested = false; + + if( fBuffer == NULL ) + { + fBufferSize = 0; + } + + LoRaMacTxPayloadLen = fBufferSize; + + LoRaMacBuffer[pktHeaderLen++] = macHdr->Value; + + switch( macHdr->Bits.MType ) + { + case FRAME_TYPE_JOIN_REQ: + LoRaMacBufferPktLen = pktHeaderLen; + + memcpy_convert_endianess( LoRaMacBuffer + LoRaMacBufferPktLen, LoRaMacAppEui, 8 ); + LoRaMacBufferPktLen += 8; + memcpy_convert_endianess( LoRaMacBuffer + LoRaMacBufferPktLen, LoRaMacDevEui, 8 ); + LoRaMacBufferPktLen += 8; + + LoRaMacDevNonce = lora_phy->get_radio_rng(); + + LoRaMacBuffer[LoRaMacBufferPktLen++] = LoRaMacDevNonce & 0xFF; + LoRaMacBuffer[LoRaMacBufferPktLen++] = ( LoRaMacDevNonce >> 8 ) & 0xFF; + + if (0 != LoRaMacJoinComputeMic( LoRaMacBuffer, LoRaMacBufferPktLen & 0xFF, LoRaMacAppKey, &mic )) { + return LORAMAC_STATUS_CRYPTO_FAIL; + } + + LoRaMacBuffer[LoRaMacBufferPktLen++] = mic & 0xFF; + LoRaMacBuffer[LoRaMacBufferPktLen++] = ( mic >> 8 ) & 0xFF; + LoRaMacBuffer[LoRaMacBufferPktLen++] = ( mic >> 16 ) & 0xFF; + LoRaMacBuffer[LoRaMacBufferPktLen++] = ( mic >> 24 ) & 0xFF; + + break; + case FRAME_TYPE_DATA_CONFIRMED_UP: + NodeAckRequested = true; + //Intentional fallthrough + case FRAME_TYPE_DATA_UNCONFIRMED_UP: + if( IsLoRaMacNetworkJoined == false ) + { + return LORAMAC_STATUS_NO_NETWORK_JOINED; // No network has been joined yet + } + + // Adr next request + adrNext.UpdateChanMask = true; + adrNext.AdrEnabled = fCtrl->Bits.Adr; + adrNext.AdrAckCounter = AdrAckCounter; + adrNext.Datarate = LoRaMacParams.ChannelsDatarate; + adrNext.TxPower = LoRaMacParams.ChannelsTxPower; + adrNext.UplinkDwellTime = LoRaMacParams.UplinkDwellTime; + + fCtrl->Bits.AdrAckReq = lora_phy->get_next_ADR(&adrNext, + &LoRaMacParams.ChannelsDatarate, + &LoRaMacParams.ChannelsTxPower, + &AdrAckCounter); + + if( SrvAckRequested == true ) + { + SrvAckRequested = false; + fCtrl->Bits.Ack = 1; + } + + LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr ) & 0xFF; + LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 8 ) & 0xFF; + LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 16 ) & 0xFF; + LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 24 ) & 0xFF; + + LoRaMacBuffer[pktHeaderLen++] = fCtrl->Value; + + LoRaMacBuffer[pktHeaderLen++] = UpLinkCounter & 0xFF; + LoRaMacBuffer[pktHeaderLen++] = ( UpLinkCounter >> 8 ) & 0xFF; + + // Copy the MAC commands which must be re-send into the MAC command buffer + memcpy( &MacCommandsBuffer[MacCommandsBufferIndex], MacCommandsBufferToRepeat, MacCommandsBufferToRepeatIndex ); + MacCommandsBufferIndex += MacCommandsBufferToRepeatIndex; + + if( ( payload != NULL ) && ( LoRaMacTxPayloadLen > 0 ) ) + { + if( MacCommandsInNextTx == true ) + { + if( MacCommandsBufferIndex <= LORA_MAC_COMMAND_MAX_FOPTS_LENGTH ) + { + fCtrl->Bits.FOptsLen += MacCommandsBufferIndex; + + // Update FCtrl field with new value of OptionsLength + LoRaMacBuffer[0x05] = fCtrl->Value; + for( i = 0; i < MacCommandsBufferIndex; i++ ) + { + LoRaMacBuffer[pktHeaderLen++] = MacCommandsBuffer[i]; + } + } + else + { + LoRaMacTxPayloadLen = MacCommandsBufferIndex; + payload = MacCommandsBuffer; + framePort = 0; + } + } + } + else + { + if( ( MacCommandsBufferIndex > 0 ) && ( MacCommandsInNextTx == true ) ) + { + LoRaMacTxPayloadLen = MacCommandsBufferIndex; + payload = MacCommandsBuffer; + framePort = 0; + } + } + MacCommandsInNextTx = false; + // Store MAC commands which must be re-send in case the device does not receive a downlink anymore + MacCommandsBufferToRepeatIndex = ParseMacCommandsToRepeat( MacCommandsBuffer, MacCommandsBufferIndex, MacCommandsBufferToRepeat ); + if( MacCommandsBufferToRepeatIndex > 0 ) + { + MacCommandsInNextTx = true; + } + + if( ( payload != NULL ) && ( LoRaMacTxPayloadLen > 0 ) ) + { + LoRaMacBuffer[pktHeaderLen++] = framePort; + + if( framePort == 0 ) + { + // Reset buffer index as the mac commands are being sent on port 0 + MacCommandsBufferIndex = 0; + if (0 != LoRaMacPayloadEncrypt( (uint8_t* ) payload, LoRaMacTxPayloadLen, LoRaMacNwkSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, &LoRaMacBuffer[pktHeaderLen] )) { + status = LORAMAC_STATUS_CRYPTO_FAIL; + } + + } + else + { + if (0 != LoRaMacPayloadEncrypt( (uint8_t* ) payload, LoRaMacTxPayloadLen, LoRaMacAppSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, &LoRaMacBuffer[pktHeaderLen] )) { + status = LORAMAC_STATUS_CRYPTO_FAIL; + } + } + } + LoRaMacBufferPktLen = pktHeaderLen + LoRaMacTxPayloadLen; + + if (0 != LoRaMacComputeMic( LoRaMacBuffer, LoRaMacBufferPktLen, LoRaMacNwkSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, &mic )) { + status = LORAMAC_STATUS_CRYPTO_FAIL; + } + + LoRaMacBuffer[LoRaMacBufferPktLen + 0] = mic & 0xFF; + LoRaMacBuffer[LoRaMacBufferPktLen + 1] = ( mic >> 8 ) & 0xFF; + LoRaMacBuffer[LoRaMacBufferPktLen + 2] = ( mic >> 16 ) & 0xFF; + LoRaMacBuffer[LoRaMacBufferPktLen + 3] = ( mic >> 24 ) & 0xFF; + + LoRaMacBufferPktLen += LORAMAC_MFR_LEN; + + break; + case FRAME_TYPE_PROPRIETARY: + if( ( fBuffer != NULL ) && ( LoRaMacTxPayloadLen > 0 ) ) + { + memcpy( LoRaMacBuffer + pktHeaderLen, ( uint8_t* ) fBuffer, LoRaMacTxPayloadLen ); + LoRaMacBufferPktLen = pktHeaderLen + LoRaMacTxPayloadLen; + } + break; + default: + status = LORAMAC_STATUS_SERVICE_UNKNOWN; + } + + return status; +} + +LoRaMacStatus_t SendFrameOnChannel( uint8_t channel ) +{ + TxConfigParams_t txConfig; + int8_t txPower = 0; + + txConfig.Channel = channel; + txConfig.Datarate = LoRaMacParams.ChannelsDatarate; + txConfig.TxPower = LoRaMacParams.ChannelsTxPower; + txConfig.MaxEirp = LoRaMacParams.MaxEirp; + txConfig.AntennaGain = LoRaMacParams.AntennaGain; + txConfig.PktLen = LoRaMacBufferPktLen; + + lora_phy->tx_config(&txConfig, &txPower, &TxTimeOnAir); + + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR; + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR; + McpsConfirm.Datarate = LoRaMacParams.ChannelsDatarate; + McpsConfirm.TxPower = txPower; + + // Store the time on air + McpsConfirm.TxTimeOnAir = TxTimeOnAir; + MlmeConfirm.TxTimeOnAir = TxTimeOnAir; + + // Starts the MAC layer status check timer + TimerSetValue( &MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT ); + TimerStart( &MacStateCheckTimer ); + + if( IsLoRaMacNetworkJoined == false ) + { + JoinRequestTrials++; + } + + // Send now + lora_phy->handle_send(LoRaMacBuffer, LoRaMacBufferPktLen); + + LoRaMacState |= LORAMAC_TX_RUNNING; + + return LORAMAC_STATUS_OK; +} + +LoRaMacStatus_t SetTxContinuousWave( uint16_t timeout ) +{ + ContinuousWaveParams_t continuousWave; + + continuousWave.Channel = Channel; + continuousWave.Datarate = LoRaMacParams.ChannelsDatarate; + continuousWave.TxPower = LoRaMacParams.ChannelsTxPower; + continuousWave.MaxEirp = LoRaMacParams.MaxEirp; + continuousWave.AntennaGain = LoRaMacParams.AntennaGain; + continuousWave.Timeout = timeout; + + lora_phy->set_tx_cont_mode(&continuousWave); + + // Starts the MAC layer status check timer + TimerSetValue( &MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT ); + TimerStart( &MacStateCheckTimer ); + + LoRaMacState |= LORAMAC_TX_RUNNING; + + return LORAMAC_STATUS_OK; +} + +LoRaMacStatus_t SetTxContinuousWave1( uint16_t timeout, uint32_t frequency, uint8_t power ) +{ + lora_phy->setup_tx_cont_wave_mode(frequency, power, timeout); + + // Starts the MAC layer status check timer + TimerSetValue( &MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT ); + TimerStart( &MacStateCheckTimer ); + + LoRaMacState |= LORAMAC_TX_RUNNING; + + return LORAMAC_STATUS_OK; +} + +LoRaMacStatus_t LoRaMacInitialization( LoRaMacPrimitives_t *primitives, LoRaMacCallback_t *callbacks, LoRaPHY& phy) +{ + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + if(!primitives || !callbacks) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + lora_phy = &phy; + + LoRaMacPrimitives = primitives; + LoRaMacCallbacks = callbacks; + + LoRaMacFlags.Value = 0; + + LoRaMacDeviceClass = CLASS_A; + LoRaMacState = LORAMAC_IDLE; + + JoinRequestTrials = 0; + MaxJoinRequestTrials = 1; + RepeaterSupport = false; + + // Reset duty cycle times + AggregatedLastTxDoneTime = 0; + AggregatedTimeOff = 0; + + // Reset to defaults + getPhy.Attribute = PHY_DUTY_CYCLE; + phyParam = lora_phy->get_phy_params(&getPhy); + // load default at this moment. Later can be changed using json + DutyCycleOn = ( bool ) phyParam.Value; + + getPhy.Attribute = PHY_DEF_TX_POWER; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.ChannelsTxPower = phyParam.Value; + + getPhy.Attribute = PHY_DEF_TX_DR; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.ChannelsDatarate = phyParam.Value; + + getPhy.Attribute = PHY_MAX_RX_WINDOW; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.MaxRxWindow = phyParam.Value; + + getPhy.Attribute = PHY_RECEIVE_DELAY1; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.ReceiveDelay1 = phyParam.Value; + + getPhy.Attribute = PHY_RECEIVE_DELAY2; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.ReceiveDelay2 = phyParam.Value; + + getPhy.Attribute = PHY_JOIN_ACCEPT_DELAY1; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.JoinAcceptDelay1 = phyParam.Value; + + getPhy.Attribute = PHY_JOIN_ACCEPT_DELAY2; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.JoinAcceptDelay2 = phyParam.Value; + + getPhy.Attribute = PHY_DEF_DR1_OFFSET; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.Rx1DrOffset = phyParam.Value; + + getPhy.Attribute = PHY_DEF_RX2_FREQUENCY; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.Rx2Channel.Frequency = phyParam.Value; + + getPhy.Attribute = PHY_DEF_RX2_DR; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.Rx2Channel.Datarate = phyParam.Value; + + getPhy.Attribute = PHY_DEF_UPLINK_DWELL_TIME; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.UplinkDwellTime = phyParam.Value; + + getPhy.Attribute = PHY_DEF_DOWNLINK_DWELL_TIME; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.DownlinkDwellTime = phyParam.Value; + + getPhy.Attribute = PHY_DEF_MAX_EIRP; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.MaxEirp = phyParam.fValue; + + getPhy.Attribute = PHY_DEF_ANTENNA_GAIN; + phyParam = lora_phy->get_phy_params( &getPhy ); + LoRaMacParamsDefaults.AntennaGain = phyParam.fValue; + + lora_phy->load_defaults(INIT_TYPE_INIT); + + // Init parameters which are not set in function ResetMacParameters + LoRaMacParamsDefaults.ChannelsNbRep = 1; + LoRaMacParamsDefaults.SystemMaxRxError = 10; + LoRaMacParamsDefaults.MinRxSymbols = 6; + + LoRaMacParams.SystemMaxRxError = LoRaMacParamsDefaults.SystemMaxRxError; + LoRaMacParams.MinRxSymbols = LoRaMacParamsDefaults.MinRxSymbols; + LoRaMacParams.MaxRxWindow = LoRaMacParamsDefaults.MaxRxWindow; + LoRaMacParams.ReceiveDelay1 = LoRaMacParamsDefaults.ReceiveDelay1; + LoRaMacParams.ReceiveDelay2 = LoRaMacParamsDefaults.ReceiveDelay2; + LoRaMacParams.JoinAcceptDelay1 = LoRaMacParamsDefaults.JoinAcceptDelay1; + LoRaMacParams.JoinAcceptDelay2 = LoRaMacParamsDefaults.JoinAcceptDelay2; + LoRaMacParams.ChannelsNbRep = LoRaMacParamsDefaults.ChannelsNbRep; + + ResetMacParameters( ); + + // Random seed initialization + srand(lora_phy->get_radio_rng()); + + PublicNetwork = LORAWAN_PUBLIC_NETWORK; + lora_phy->setup_public_network_mode(PublicNetwork); + lora_phy->put_radio_to_sleep(); + + // Initialize timers + TimerInit(&MacStateCheckTimer, handle_mac_state_check_timer_event); + TimerSetValue(&MacStateCheckTimer, MAC_STATE_CHECK_TIMEOUT); + + TimerInit(&TxDelayedTimer, handle_delayed_tx_timer_event); + TimerInit(&RxWindowTimer1, handle_rx1_timer_event); + TimerInit(&RxWindowTimer2, handle_rx2_timer_event); + TimerInit(&AckTimeoutTimer, handle_ack_timeout); + TimerInit(&TxNextPacketTimer, handle_next_tx_timer_event); + + // Store the current initialization time + LoRaMacInitializationTime = TimerGetCurrentTime(); + + return LORAMAC_STATUS_OK; +} + +LoRaMacStatus_t LoRaMacQueryTxPossible( uint8_t size, LoRaMacTxInfo_t* txInfo ) +{ + AdrNextParams_t adrNext; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + int8_t datarate = LoRaMacParamsDefaults.ChannelsDatarate; + int8_t txPower = LoRaMacParamsDefaults.ChannelsTxPower; + uint8_t fOptLen = MacCommandsBufferIndex + MacCommandsBufferToRepeatIndex; + + if( txInfo == NULL ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + // Setup ADR request + adrNext.UpdateChanMask = false; + adrNext.AdrEnabled = AdrCtrlOn; + adrNext.AdrAckCounter = AdrAckCounter; + adrNext.Datarate = LoRaMacParams.ChannelsDatarate; + adrNext.TxPower = LoRaMacParams.ChannelsTxPower; + adrNext.UplinkDwellTime = LoRaMacParams.UplinkDwellTime; + + // We call the function for information purposes only. We don't want to + // apply the datarate, the tx power and the ADR ack counter. + lora_phy->get_next_ADR(&adrNext, &datarate, &txPower, &AdrAckCounter); + + // Setup PHY request + getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime; + getPhy.Datarate = datarate; + getPhy.Attribute = PHY_MAX_PAYLOAD; + + // Change request in case repeater is supported + if( RepeaterSupport == true ) + { + getPhy.Attribute = PHY_MAX_PAYLOAD_REPEATER; + } + phyParam = lora_phy->get_phy_params( &getPhy ); + txInfo->CurrentPayloadSize = phyParam.Value; + + // Verify if the fOpts fit into the maximum payload + if( txInfo->CurrentPayloadSize >= fOptLen ) + { + txInfo->MaxPossiblePayload = txInfo->CurrentPayloadSize - fOptLen; + } + else + { + txInfo->MaxPossiblePayload = txInfo->CurrentPayloadSize; + // The fOpts don't fit into the maximum payload. Omit the MAC commands to + // ensure that another uplink is possible. + fOptLen = 0; + MacCommandsBufferIndex = 0; + MacCommandsBufferToRepeatIndex = 0; + } + + // Verify if the fOpts and the payload fit into the maximum payload + if( ValidatePayloadLength( size, datarate, fOptLen ) == false ) + { + return LORAMAC_STATUS_LENGTH_ERROR; + } + return LORAMAC_STATUS_OK; +} + +LoRaMacStatus_t LoRaMacMibGetRequestConfirm( MibRequestConfirm_t *mibGet ) +{ + LoRaMacStatus_t status = LORAMAC_STATUS_OK; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + if( mibGet == NULL ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + switch( mibGet->Type ) + { + case MIB_DEVICE_CLASS: + { + mibGet->Param.Class = LoRaMacDeviceClass; + break; + } + case MIB_NETWORK_JOINED: + { + mibGet->Param.IsNetworkJoined = IsLoRaMacNetworkJoined; + break; + } + case MIB_ADR: + { + mibGet->Param.AdrEnable = AdrCtrlOn; + break; + } + case MIB_NET_ID: + { + mibGet->Param.NetID = LoRaMacNetID; + break; + } + case MIB_DEV_ADDR: + { + mibGet->Param.DevAddr = LoRaMacDevAddr; + break; + } + case MIB_NWK_SKEY: + { + mibGet->Param.NwkSKey = LoRaMacNwkSKey; + break; + } + case MIB_APP_SKEY: + { + mibGet->Param.AppSKey = LoRaMacAppSKey; + break; + } + case MIB_PUBLIC_NETWORK: + { + mibGet->Param.EnablePublicNetwork = PublicNetwork; + break; + } + case MIB_REPEATER_SUPPORT: + { + mibGet->Param.EnableRepeaterSupport = RepeaterSupport; + break; + } + case MIB_CHANNELS: + { + getPhy.Attribute = PHY_CHANNELS; + phyParam = lora_phy->get_phy_params( &getPhy ); + + mibGet->Param.ChannelList = phyParam.Channels; + break; + } + case MIB_RX2_CHANNEL: + { + mibGet->Param.Rx2Channel = LoRaMacParams.Rx2Channel; + break; + } + case MIB_RX2_DEFAULT_CHANNEL: + { + mibGet->Param.Rx2Channel = LoRaMacParamsDefaults.Rx2Channel; + break; + } + case MIB_CHANNELS_DEFAULT_MASK: + { + getPhy.Attribute = PHY_CHANNELS_DEFAULT_MASK; + phyParam = lora_phy->get_phy_params( &getPhy ); + + mibGet->Param.ChannelsDefaultMask = phyParam.ChannelsMask; + break; + } + case MIB_CHANNELS_MASK: + { + getPhy.Attribute = PHY_CHANNELS_MASK; + phyParam = lora_phy->get_phy_params( &getPhy ); + + mibGet->Param.ChannelsMask = phyParam.ChannelsMask; + break; + } + case MIB_CHANNELS_NB_REP: + { + mibGet->Param.ChannelNbRep = LoRaMacParams.ChannelsNbRep; + break; + } + case MIB_MAX_RX_WINDOW_DURATION: + { + mibGet->Param.MaxRxWindow = LoRaMacParams.MaxRxWindow; + break; + } + case MIB_RECEIVE_DELAY_1: + { + mibGet->Param.ReceiveDelay1 = LoRaMacParams.ReceiveDelay1; + break; + } + case MIB_RECEIVE_DELAY_2: + { + mibGet->Param.ReceiveDelay2 = LoRaMacParams.ReceiveDelay2; + break; + } + case MIB_JOIN_ACCEPT_DELAY_1: + { + mibGet->Param.JoinAcceptDelay1 = LoRaMacParams.JoinAcceptDelay1; + break; + } + case MIB_JOIN_ACCEPT_DELAY_2: + { + mibGet->Param.JoinAcceptDelay2 = LoRaMacParams.JoinAcceptDelay2; + break; + } + case MIB_CHANNELS_DEFAULT_DATARATE: + { + mibGet->Param.ChannelsDefaultDatarate = LoRaMacParamsDefaults.ChannelsDatarate; + break; + } + case MIB_CHANNELS_DATARATE: + { + mibGet->Param.ChannelsDatarate = LoRaMacParams.ChannelsDatarate; + break; + } + case MIB_CHANNELS_DEFAULT_TX_POWER: + { + mibGet->Param.ChannelsDefaultTxPower = LoRaMacParamsDefaults.ChannelsTxPower; + break; + } + case MIB_CHANNELS_TX_POWER: + { + mibGet->Param.ChannelsTxPower = LoRaMacParams.ChannelsTxPower; + break; + } + case MIB_UPLINK_COUNTER: + { + mibGet->Param.UpLinkCounter = UpLinkCounter; + break; + } + case MIB_DOWNLINK_COUNTER: + { + mibGet->Param.DownLinkCounter = DownLinkCounter; + break; + } + case MIB_MULTICAST_CHANNEL: + { + mibGet->Param.MulticastList = MulticastChannels; + break; + } + case MIB_SYSTEM_MAX_RX_ERROR: + { + mibGet->Param.SystemMaxRxError = LoRaMacParams.SystemMaxRxError; + break; + } + case MIB_MIN_RX_SYMBOLS: + { + mibGet->Param.MinRxSymbols = LoRaMacParams.MinRxSymbols; + break; + } + case MIB_ANTENNA_GAIN: + { + mibGet->Param.AntennaGain = LoRaMacParams.AntennaGain; + break; + } + default: + status = LORAMAC_STATUS_SERVICE_UNKNOWN; + break; + } + + return status; +} + +LoRaMacStatus_t LoRaMacMibSetRequestConfirm( MibRequestConfirm_t *mibSet ) +{ + LoRaMacStatus_t status = LORAMAC_STATUS_OK; + ChanMaskSetParams_t chanMaskSet; + VerifyParams_t verify; + + if( mibSet == NULL ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + switch( mibSet->Type ) + { + case MIB_DEVICE_CLASS: + { + LoRaMacDeviceClass = mibSet->Param.Class; + switch( LoRaMacDeviceClass ) + { + case CLASS_A: + { + // Set the radio into sleep to setup a defined state + lora_phy->put_radio_to_sleep(); + break; + } + case CLASS_B: + { + break; + } + case CLASS_C: + { + // Set the NodeAckRequested indicator to default + NodeAckRequested = false; + handle_rx2_timer_event(); + break; + } + } + break; + } + case MIB_NETWORK_JOINED: + { + IsLoRaMacNetworkJoined = mibSet->Param.IsNetworkJoined; + break; + } + case MIB_ADR: + { + AdrCtrlOn = mibSet->Param.AdrEnable; + break; + } + case MIB_NET_ID: + { + LoRaMacNetID = mibSet->Param.NetID; + break; + } + case MIB_DEV_ADDR: + { + LoRaMacDevAddr = mibSet->Param.DevAddr; + break; + } + case MIB_NWK_SKEY: + { + if( mibSet->Param.NwkSKey != NULL ) + { + memcpy( LoRaMacNwkSKey, mibSet->Param.NwkSKey, + sizeof( LoRaMacNwkSKey ) ); + } + else + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_APP_SKEY: + { + if( mibSet->Param.AppSKey != NULL ) + { + memcpy( LoRaMacAppSKey, mibSet->Param.AppSKey, + sizeof( LoRaMacAppSKey ) ); + } + else + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_PUBLIC_NETWORK: + { + PublicNetwork = mibSet->Param.EnablePublicNetwork; + lora_phy->setup_public_network_mode(PublicNetwork); + break; + } + case MIB_REPEATER_SUPPORT: + { + RepeaterSupport = mibSet->Param.EnableRepeaterSupport; + break; + } + case MIB_RX2_CHANNEL: + { + verify.DatarateParams.Datarate = mibSet->Param.Rx2Channel.Datarate; + verify.DatarateParams.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime; + + if( lora_phy->verify(&verify, PHY_RX_DR) == true ) + { + LoRaMacParams.Rx2Channel = mibSet->Param.Rx2Channel; + + if( ( LoRaMacDeviceClass == CLASS_C ) && ( IsLoRaMacNetworkJoined == true ) ) + { + // Compute Rx2 windows parameters + lora_phy->compute_rx_win_params(LoRaMacParams.Rx2Channel.Datarate, + LoRaMacParams.MinRxSymbols, + LoRaMacParams.SystemMaxRxError, + &RxWindow2Config); + + RxWindow2Config.Channel = Channel; + RxWindow2Config.Frequency = LoRaMacParams.Rx2Channel.Frequency; + RxWindow2Config.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime; + RxWindow2Config.RepeaterSupport = RepeaterSupport; + RxWindow2Config.Window = 1; + RxWindow2Config.RxContinuous = true; + + if(lora_phy->rx_config(&RxWindow2Config, ( int8_t* )&McpsIndication.RxDatarate) == true ) + { + RxWindowSetup( RxWindow2Config.RxContinuous, LoRaMacParams.MaxRxWindow ); + RxSlot = RxWindow2Config.Window; + } + else + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + } + } + else + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_RX2_DEFAULT_CHANNEL: + { + verify.DatarateParams.Datarate = mibSet->Param.Rx2Channel.Datarate; + verify.DatarateParams.DownlinkDwellTime = LoRaMacParams.DownlinkDwellTime; + + if( lora_phy->verify(&verify, PHY_RX_DR) == true ) + { + LoRaMacParamsDefaults.Rx2Channel = mibSet->Param.Rx2DefaultChannel; + } + else + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_CHANNELS_DEFAULT_MASK: + { + chanMaskSet.ChannelsMaskIn = mibSet->Param.ChannelsMask; + chanMaskSet.ChannelsMaskType = CHANNELS_DEFAULT_MASK; + + if(lora_phy->set_channel_mask(&chanMaskSet) == false ) + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_CHANNELS_MASK: + { + chanMaskSet.ChannelsMaskIn = mibSet->Param.ChannelsMask; + chanMaskSet.ChannelsMaskType = CHANNELS_MASK; + + if(lora_phy->set_channel_mask(&chanMaskSet) == false ) + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_CHANNELS_NB_REP: + { + if( ( mibSet->Param.ChannelNbRep >= 1 ) && + ( mibSet->Param.ChannelNbRep <= 15 ) ) + { + LoRaMacParams.ChannelsNbRep = mibSet->Param.ChannelNbRep; + } + else + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_MAX_RX_WINDOW_DURATION: + { + LoRaMacParams.MaxRxWindow = mibSet->Param.MaxRxWindow; + break; + } + case MIB_RECEIVE_DELAY_1: + { + LoRaMacParams.ReceiveDelay1 = mibSet->Param.ReceiveDelay1; + break; + } + case MIB_RECEIVE_DELAY_2: + { + LoRaMacParams.ReceiveDelay2 = mibSet->Param.ReceiveDelay2; + break; + } + case MIB_JOIN_ACCEPT_DELAY_1: + { + LoRaMacParams.JoinAcceptDelay1 = mibSet->Param.JoinAcceptDelay1; + break; + } + case MIB_JOIN_ACCEPT_DELAY_2: + { + LoRaMacParams.JoinAcceptDelay2 = mibSet->Param.JoinAcceptDelay2; + break; + } + case MIB_CHANNELS_DEFAULT_DATARATE: + { + verify.DatarateParams.Datarate = mibSet->Param.ChannelsDefaultDatarate; + + if(lora_phy->verify(&verify, PHY_DEF_TX_DR) == true) + { + LoRaMacParamsDefaults.ChannelsDatarate = verify.DatarateParams.Datarate; + } + else + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_CHANNELS_DATARATE: + { + verify.DatarateParams.Datarate = mibSet->Param.ChannelsDatarate; + + if(lora_phy->verify(&verify, PHY_TX_DR) == true) + { + LoRaMacParams.ChannelsDatarate = verify.DatarateParams.Datarate; + } + else + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_CHANNELS_DEFAULT_TX_POWER: + { + verify.TxPower = mibSet->Param.ChannelsDefaultTxPower; + + if(lora_phy->verify(&verify, PHY_DEF_TX_POWER) == true) + { + LoRaMacParamsDefaults.ChannelsTxPower = verify.TxPower; + } + else + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_CHANNELS_TX_POWER: + { + verify.TxPower = mibSet->Param.ChannelsTxPower; + + if(lora_phy->verify(&verify, PHY_TX_POWER) == true) + { + LoRaMacParams.ChannelsTxPower = verify.TxPower; + } + else + { + status = LORAMAC_STATUS_PARAMETER_INVALID; + } + break; + } + case MIB_UPLINK_COUNTER: + { + UpLinkCounter = mibSet->Param.UpLinkCounter; + break; + } + case MIB_DOWNLINK_COUNTER: + { + DownLinkCounter = mibSet->Param.DownLinkCounter; + break; + } + case MIB_SYSTEM_MAX_RX_ERROR: + { + LoRaMacParams.SystemMaxRxError = LoRaMacParamsDefaults.SystemMaxRxError = mibSet->Param.SystemMaxRxError; + break; + } + case MIB_MIN_RX_SYMBOLS: + { + LoRaMacParams.MinRxSymbols = LoRaMacParamsDefaults.MinRxSymbols = mibSet->Param.MinRxSymbols; + break; + } + case MIB_ANTENNA_GAIN: + { + LoRaMacParams.AntennaGain = mibSet->Param.AntennaGain; + break; + } + default: + status = LORAMAC_STATUS_SERVICE_UNKNOWN; + break; + } + + return status; +} + +LoRaMacStatus_t LoRaMacChannelAdd( uint8_t id, ChannelParams_t params ) +{ + ChannelAddParams_t channelAdd; + + // Validate if the MAC is in a correct state + if( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) + { + if( ( LoRaMacState & LORAMAC_TX_CONFIG ) != LORAMAC_TX_CONFIG ) + { + return LORAMAC_STATUS_BUSY; + } + } + + channelAdd.NewChannel = ¶ms; + channelAdd.ChannelId = id; + + return lora_phy->add_channel(&channelAdd); +} + +LoRaMacStatus_t LoRaMacChannelRemove( uint8_t id ) +{ + ChannelRemoveParams_t channelRemove; + + if( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) + { + if( ( LoRaMacState & LORAMAC_TX_CONFIG ) != LORAMAC_TX_CONFIG ) + { + return LORAMAC_STATUS_BUSY; + } + } + + channelRemove.ChannelId = id; + + if(lora_phy->remove_channel(&channelRemove) == false) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + lora_phy->put_radio_to_sleep(); + + return LORAMAC_STATUS_OK; +} + +LoRaMacStatus_t LoRaMacMulticastChannelLink( MulticastParams_t *channelParam ) +{ + if( channelParam == NULL ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + if( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) + { + return LORAMAC_STATUS_BUSY; + } + + // Reset downlink counter + channelParam->DownLinkCounter = 0; + + if( MulticastChannels == NULL ) + { + // New node is the fist element + MulticastChannels = channelParam; + } + else + { + MulticastParams_t *cur = MulticastChannels; + + // Search the last node in the list + while( cur->Next != NULL ) + { + cur = cur->Next; + } + // This function always finds the last node + cur->Next = channelParam; + } + + return LORAMAC_STATUS_OK; +} + +LoRaMacStatus_t LoRaMacMulticastChannelUnlink( MulticastParams_t *channelParam ) +{ + if( channelParam == NULL ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + if( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) + { + return LORAMAC_STATUS_BUSY; + } + + if( MulticastChannels != NULL ) + { + if( MulticastChannels == channelParam ) + { + // First element + MulticastChannels = channelParam->Next; + } + else + { + MulticastParams_t *cur = MulticastChannels; + + // Search the node in the list + while( cur->Next && cur->Next != channelParam ) + { + cur = cur->Next; + } + // If we found the node, remove it + if( cur->Next ) + { + cur->Next = channelParam->Next; + } + } + channelParam->Next = NULL; + } + + return LORAMAC_STATUS_OK; +} + +LoRaMacStatus_t LoRaMacMlmeRequest( MlmeReq_t *mlmeRequest ) +{ + LoRaMacStatus_t status = LORAMAC_STATUS_SERVICE_UNKNOWN; + LoRaMacHeader_t macHdr; + AlternateDrParams_t altDr; + VerifyParams_t verify; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + if( mlmeRequest == NULL ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + if( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) + { + return LORAMAC_STATUS_BUSY; + } + + memset( ( uint8_t* ) &MlmeConfirm, 0, sizeof( MlmeConfirm ) ); + + MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR; + + switch( mlmeRequest->Type ) + { + case MLME_JOIN: + { + if( ( LoRaMacState & LORAMAC_TX_DELAYED ) == LORAMAC_TX_DELAYED ) + { + return LORAMAC_STATUS_BUSY; + } + + if( ( mlmeRequest->Req.Join.DevEui == NULL ) || + ( mlmeRequest->Req.Join.AppEui == NULL ) || + ( mlmeRequest->Req.Join.AppKey == NULL ) || + ( mlmeRequest->Req.Join.NbTrials == 0 ) ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + // Verify the parameter NbTrials for the join procedure + verify.NbJoinTrials = mlmeRequest->Req.Join.NbTrials; + + if(lora_phy->verify(&verify, PHY_NB_JOIN_TRIALS) == false) + { + // Value not supported, get default + getPhy.Attribute = PHY_DEF_NB_JOIN_TRIALS; + phyParam = lora_phy->get_phy_params( &getPhy ); + mlmeRequest->Req.Join.NbTrials = ( uint8_t ) phyParam.Value; + } + + LoRaMacFlags.Bits.MlmeReq = 1; + MlmeConfirm.MlmeRequest = mlmeRequest->Type; + + LoRaMacDevEui = mlmeRequest->Req.Join.DevEui; + LoRaMacAppEui = mlmeRequest->Req.Join.AppEui; + LoRaMacAppKey = mlmeRequest->Req.Join.AppKey; + MaxJoinRequestTrials = mlmeRequest->Req.Join.NbTrials; + + // Reset variable JoinRequestTrials + JoinRequestTrials = 0; + + // Setup header information + macHdr.Value = 0; + macHdr.Bits.MType = FRAME_TYPE_JOIN_REQ; + + ResetMacParameters( ); + + altDr.NbTrials = JoinRequestTrials + 1; + + LoRaMacParams.ChannelsDatarate = lora_phy->get_alternate_DR(&altDr); + + status = Send( &macHdr, 0, NULL, 0 ); + break; + } + case MLME_LINK_CHECK: + { + LoRaMacFlags.Bits.MlmeReq = 1; + // LoRaMac will send this command piggy-pack + MlmeConfirm.MlmeRequest = mlmeRequest->Type; + + status = AddMacCommand( MOTE_MAC_LINK_CHECK_REQ, 0, 0 ); + break; + } + case MLME_TXCW: + { + MlmeConfirm.MlmeRequest = mlmeRequest->Type; + LoRaMacFlags.Bits.MlmeReq = 1; + status = SetTxContinuousWave( mlmeRequest->Req.TxCw.Timeout ); + break; + } + case MLME_TXCW_1: + { + MlmeConfirm.MlmeRequest = mlmeRequest->Type; + LoRaMacFlags.Bits.MlmeReq = 1; + status = SetTxContinuousWave1( mlmeRequest->Req.TxCw.Timeout, mlmeRequest->Req.TxCw.Frequency, mlmeRequest->Req.TxCw.Power ); + break; + } + default: + break; + } + + if( status != LORAMAC_STATUS_OK ) + { + NodeAckRequested = false; + LoRaMacFlags.Bits.MlmeReq = 0; + } + + return status; +} + +LoRaMacStatus_t LoRaMacMcpsRequest( McpsReq_t *mcpsRequest ) +{ + GetPhyParams_t getPhy; + PhyParam_t phyParam; + LoRaMacStatus_t status = LORAMAC_STATUS_SERVICE_UNKNOWN; + LoRaMacHeader_t macHdr; + VerifyParams_t verify; + uint8_t fPort = 0; + void *fBuffer; + uint16_t fBufferSize; + int8_t datarate; + bool readyToSend = false; + + if( mcpsRequest == NULL ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + if( ( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) || + ( ( LoRaMacState & LORAMAC_TX_DELAYED ) == LORAMAC_TX_DELAYED ) ) + { + return LORAMAC_STATUS_BUSY; + } + + macHdr.Value = 0; + memset ( ( uint8_t* ) &McpsConfirm, 0, sizeof( McpsConfirm ) ); + McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR; + + // AckTimeoutRetriesCounter must be reset every time a new request (unconfirmed or confirmed) is performed. + AckTimeoutRetriesCounter = 1; + + switch( mcpsRequest->Type ) + { + case MCPS_UNCONFIRMED: + { + readyToSend = true; + AckTimeoutRetries = 1; + + macHdr.Bits.MType = FRAME_TYPE_DATA_UNCONFIRMED_UP; + fPort = mcpsRequest->Req.Unconfirmed.fPort; + fBuffer = mcpsRequest->Req.Unconfirmed.fBuffer; + fBufferSize = mcpsRequest->Req.Unconfirmed.fBufferSize; + datarate = mcpsRequest->Req.Unconfirmed.Datarate; + break; + } + case MCPS_CONFIRMED: + { + readyToSend = true; + AckTimeoutRetries = mcpsRequest->Req.Confirmed.NbTrials; + + macHdr.Bits.MType = FRAME_TYPE_DATA_CONFIRMED_UP; + fPort = mcpsRequest->Req.Confirmed.fPort; + fBuffer = mcpsRequest->Req.Confirmed.fBuffer; + fBufferSize = mcpsRequest->Req.Confirmed.fBufferSize; + datarate = mcpsRequest->Req.Confirmed.Datarate; + break; + } + case MCPS_PROPRIETARY: + { + readyToSend = true; + AckTimeoutRetries = 1; + + macHdr.Bits.MType = FRAME_TYPE_PROPRIETARY; + fBuffer = mcpsRequest->Req.Proprietary.fBuffer; + fBufferSize = mcpsRequest->Req.Proprietary.fBufferSize; + datarate = mcpsRequest->Req.Proprietary.Datarate; + break; + } + default: + break; + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = LoRaMacParams.UplinkDwellTime; + phyParam = lora_phy->get_phy_params( &getPhy ); + // Apply the minimum possible datarate. + // Some regions have limitations for the minimum datarate. + datarate = MAX( datarate, phyParam.Value ); + + if( readyToSend == true ) + { + if( AdrCtrlOn == false ) + { + verify.DatarateParams.Datarate = datarate; + verify.DatarateParams.UplinkDwellTime = LoRaMacParams.UplinkDwellTime; + + if(lora_phy->verify(&verify, PHY_TX_DR) == true) + { + LoRaMacParams.ChannelsDatarate = verify.DatarateParams.Datarate; + } + else + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + } + + status = Send( &macHdr, fPort, fBuffer, fBufferSize ); + if( status == LORAMAC_STATUS_OK ) + { + McpsConfirm.McpsRequest = mcpsRequest->Type; + LoRaMacFlags.Bits.McpsReq = 1; + } + else + { + NodeAckRequested = false; + } + } + + return status; +} + +radio_events_t *GetPhyEventHandlers() +{ + RadioEvents.tx_done = handle_tx_done; + RadioEvents.rx_done = handle_rx_done; + RadioEvents.rx_error = handle_rx_error; + RadioEvents.tx_timeout = handle_tx_timeout; + RadioEvents.rx_timeout = handle_rx_timeout; + + return &RadioEvents; +} + +/*************************************************************************** +* TODO: Something related to Testing ? Need to figure out what is it * + **************************************************************************/ +void LoRaMacTestRxWindowsOn( bool enable ) +{ + IsRxWindowsEnabled = enable; +} + +void LoRaMacTestSetMic( uint16_t txPacketCounter ) +{ + UpLinkCounter = txPacketCounter; + IsUpLinkCounterFixed = true; +} + +void LoRaMacTestSetDutyCycleOn( bool enable ) +{ + VerifyParams_t verify; + + verify.DutyCycle = enable; + + if(lora_phy->verify(&verify, PHY_DUTY_CYCLE) == true) + { + DutyCycleOn = enable; + } +} + +void LoRaMacTestSetChannel( uint8_t channel ) +{ + Channel = channel; +} diff --git a/features/lorawan/lorastack/mac/LoRaMac.h b/features/lorawan/lorastack/mac/LoRaMac.h new file mode 100644 index 0000000000..6981b68ae8 --- /dev/null +++ b/features/lorawan/lorastack/mac/LoRaMac.h @@ -0,0 +1,353 @@ +/** + * \file LoRaMac.h + * + * \brief LoRa MAC layer implementation + * + * \copyright Revised BSD License, see LICENSE.TXT file include in the project + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * \author Miguel Luis ( Semtech ) + * + * \author Gregory Cristian ( Semtech ) + * + * \author Daniel Jaeckle ( STACKFORCE ) + * + * \defgroup LORAMAC LoRa MAC layer implementation + * This module specifies the API implementation of the LoRaMAC layer. + * This is a placeholder for a detailed description of the LoRaMac + * layer and the supported features. + * + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: BSD-3-Clause + * + */ +#ifndef __LORAMAC_H__ +#define __LORAMAC_H__ + +#include "lorawan/system/LoRaWANTimer.h" +#include "netsocket/LoRaRadio.h" +#include "lorastack/phy/LoRaPHY.h" +#include "lorawan/system/lorawan_data_structures.h" + +/*! + * Check the MAC layer state every MAC_STATE_CHECK_TIMEOUT in ms. + */ +#define MAC_STATE_CHECK_TIMEOUT 1000 + +/*! + * The maximum number of times the MAC layer tries to get an acknowledge. + */ +#define MAX_ACK_RETRIES 8 + +/*! + * The frame direction definition for uplink communications. + */ +#define UP_LINK 0 + +/*! + * The frame direction definition for downlink communications. + */ +#define DOWN_LINK 1 + + +/*! + * LoRaMAC max EIRP (dBm) table. + */ +static const uint8_t LoRaMacMaxEirpTable[] = { 8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36 }; + + +/*! + * \brief LoRaMAC layer initialization + * + * \details In addition to the initialization of the LoRaMAC layer, this + * function initializes the callback primitives of the MCPS and + * MLME services. Every data field of \ref LoRaMacPrimitives_t must be + * set to a valid callback function. + * + * \param primitives [in] - A pointer to the structure defining the LoRaMAC + * event functions. Refer to \ref LoRaMacPrimitives_t. + * + * \param callbacks [in] - A pointer to the structure defining the LoRaMAC + * callback functions. Refer to \ref LoRaMacCallback_t. + * + * \param phy [in]- A reference to the selected PHY layer. + * + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_PARAMETER_INVALID + * \ref LORAMAC_STATUS_REGION_NOT_SUPPORTED. + */ +LoRaMacStatus_t LoRaMacInitialization( LoRaMacPrimitives_t *primitives, LoRaMacCallback_t *callbacks, LoRaPHY& phy); + +/*! + * \brief Queries the LoRaMAC whether it is possible to send the next frame with + * a given payload size. The LoRaMAC takes the scheduled MAC commands into + * account and reports when the frame can be sent. + * + * \param size [in]- The size of the applicable payload to be sent next. + * \param txInfo [out] - The structure \ref LoRaMacTxInfo_t contains + * information on the actual maximum payload possible + * (according to the configured datarate or the next + * datarate according to ADR), and the maximum frame + * size, taking the scheduled MAC commands into account. + * + * \retval `LoRaMacStatus_t` The status of the operation. When the parameters are + * not valid, the function returns \ref LORAMAC_STATUS_PARAMETER_INVALID. + * In case of a length error caused by the applicable payload in combination + * with the MAC commands, the function returns \ref LORAMAC_STATUS_LENGTH_ERROR. + * Please note that if the size of the MAC commands in the queue do + * not fit into the payload size on the related datarate, the LoRaMAC will + * omit the MAC commands. + * If the query is valid, and the LoRaMAC is able to send the frame, + * the function returns \ref LORAMAC_STATUS_OK. + */ +LoRaMacStatus_t LoRaMacQueryTxPossible( uint8_t size, LoRaMacTxInfo_t* txInfo ); + +/*! + * \brief LoRaMAC channel add service. + * + * \details Adds a new channel to the channel list and activates the ID in + * the channel mask. Please note that this functionality is not available + * in all regions. Information on the allowed ranges is available at the LoRaWAN Regional Parameters V1.0.2rB. + * + * \param id [in] - The ID of the channel. + * + * \param params [in] - The channel parameters to set. + * + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_BUSY + * \ref LORAMAC_STATUS_PARAMETER_INVALID + */ +LoRaMacStatus_t LoRaMacChannelAdd( uint8_t id, ChannelParams_t params ); + +/*! + * \brief LoRaMAC channel remove service. + * + * \details Deactivates the ID in the channel mask. + * + * \param id - Id of the channel. + * + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_BUSY + * \ref LORAMAC_STATUS_PARAMETER_INVALID + */ +LoRaMacStatus_t LoRaMacChannelRemove( uint8_t id ); + +/*! + * \brief LoRaMAC multicast channel link service. + * + * \details Links a multicast channel into the linked list. + * + * \param [in] channelParam - The multicast channel parameters to link. + * + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_BUSY + * \ref LORAMAC_STATUS_PARAMETER_INVALID + */ +LoRaMacStatus_t LoRaMacMulticastChannelLink( MulticastParams_t *channelParam ); + +/*! + * \brief LoRaMAC multicast channel unlink service. + * + * \details Unlinks a multicast channel from the linked list. + * + * \param [in] channelParam - The multicast channel parameters to unlink. + * + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_BUSY + * \ref LORAMAC_STATUS_PARAMETER_INVALID + */ +LoRaMacStatus_t LoRaMacMulticastChannelUnlink( MulticastParams_t *channelParam ); + +/*! + * \brief LoRaMAC MIB-GET. + * + * \details The MAC information base service to get the attributes of the LoRaMac layer. + * + * The following code-snippet shows how to use the API to get the + * parameter `AdrEnable`, defined by the enumeration type + * \ref MIB_ADR. + * \code + * MibRequestConfirm_t mibReq; + * mibReq.Type = MIB_ADR; + * + * if( LoRaMacMibGetRequestConfirm( &mibReq ) == LORAMAC_STATUS_OK ) + * { + * // LoRaMAC updated the parameter mibParam.AdrEnable + * } + * \endcode + * + * \param [in] mibGet - The MIB-GET request to perform. Refer to \ref MibRequestConfirm_t. + * + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_SERVICE_UNKNOWN + * \ref LORAMAC_STATUS_PARAMETER_INVALID + */ +LoRaMacStatus_t LoRaMacMibGetRequestConfirm( MibRequestConfirm_t *mibGet ); + +/*! + * \brief LoRaMAC MIB-SET. + * + * \details The MAC information base service to set the attributes of the LoRaMac layer. + * + * The following code-snippet shows how to use the API to set the + * parameter `AdrEnable`, defined by the enumeration type + * \ref MIB_ADR. + * + * \code + * MibRequestConfirm_t mibReq; + * mibReq.Type = MIB_ADR; + * mibReq.Param.AdrEnable = true; + * + * if( LoRaMacMibGetRequestConfirm( &mibReq ) == LORAMAC_STATUS_OK ) + * { + * // LoRaMAC updated the parameter + * } + * \endcode + * + * \param [in] mibSet - The MIB-SET request to perform. Refer to \ref MibRequestConfirm_t. + * + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_BUSY + * \ref LORAMAC_STATUS_SERVICE_UNKNOWN + * \ref LORAMAC_STATUS_PARAMETER_INVALID + */ +LoRaMacStatus_t LoRaMacMibSetRequestConfirm( MibRequestConfirm_t *mibSet ); + +/*! + * \brief LoRaMAC MLME request + * + * \details The MAC layer management entity handles the management services. The + * following code-snippet shows how to use the API to perform a + * network join request. + * + * \code + * static uint8_t DevEui[] = + * { + * 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + * }; + * static uint8_t AppEui[] = + * { + * 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + * }; + * static uint8_t AppKey[] = + * { + * 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, + * 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C + * }; + * + * MlmeReq_t mlmeReq; + * mlmeReq.Type = MLME_JOIN; + * mlmeReq.Req.Join.DevEui = DevEui; + * mlmeReq.Req.Join.AppEui = AppEui; + * mlmeReq.Req.Join.AppKey = AppKey; + * + * if( LoRaMacMlmeRequest( &mlmeReq ) == LORAMAC_STATUS_OK ) + * { + * // Service started successfully. Waiting for the Mlme-Confirm event + * } + * \endcode + * + * \param [in] mlmeRequest - The MLME request to perform. Refer to \ref MlmeReq_t. + * + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_BUSY + * \ref LORAMAC_STATUS_SERVICE_UNKNOWN + * \ref LORAMAC_STATUS_PARAMETER_INVALID + * \ref LORAMAC_STATUS_NO_NETWORK_JOINED + * \ref LORAMAC_STATUS_LENGTH_ERROR + * \ref LORAMAC_STATUS_DEVICE_OFF + */ +LoRaMacStatus_t LoRaMacMlmeRequest( MlmeReq_t *mlmeRequest ); + +/*! + * \brief LoRaMAC MCPS request + * + * \details The MAC Common Part Sublayer handles the data services. The following + * code-snippet shows how to use the API to send an unconfirmed + * LoRaMAC frame. + * + * \code + * uint8_t myBuffer[] = { 1, 2, 3 }; + * + * McpsReq_t mcpsReq; + * mcpsReq.Type = MCPS_UNCONFIRMED; + * mcpsReq.Req.Unconfirmed.fPort = 1; + * mcpsReq.Req.Unconfirmed.fBuffer = myBuffer; + * mcpsReq.Req.Unconfirmed.fBufferSize = sizeof( myBuffer ); + * + * if( LoRaMacMcpsRequest( &mcpsReq ) == LORAMAC_STATUS_OK ) + * { + * // Service started successfully. Waiting for the MCPS-Confirm event + * } + * \endcode + * + * \param [in] mcpsRequest - The MCPS request to perform. Refer to \ref McpsReq_t. + * + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_BUSY + * \ref LORAMAC_STATUS_SERVICE_UNKNOWN + * \ref LORAMAC_STATUS_PARAMETER_INVALID + * \ref LORAMAC_STATUS_NO_NETWORK_JOINED + * \ref LORAMAC_STATUS_LENGTH_ERROR + * \ref LORAMAC_STATUS_DEVICE_OFF + */ +LoRaMacStatus_t LoRaMacMcpsRequest( McpsReq_t *mcpsRequest ); + +/** + * \brief LoRaMAC layer provides its callback functions for + * PHY layer + * + * \return Pointer to callback functions for radio events + */ +radio_events_t *GetPhyEventHandlers(); + +/** + * \brief LoRaMAC set tx timer. + * + * \details Sets up a timer for next transmission (application specific timers). + * + * \param [in] NextTxTime - Periodic time for next uplink. + + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_PARAMETER_INVALID + */ +LoRaMacStatus_t LoRaMacSetTxTimer( uint32_t NextTxTime ); + +/** + * \brief LoRaMAC stop tx timer. + * + * \details Stops the next tx timer. + * + * \retval `LoRaMacStatus_t` The status of the operation. The possible values are: + * \ref LORAMAC_STATUS_OK + * \ref LORAMAC_STATUS_PARAMETER_INVALID + */ +LoRaMacStatus_t LoRaMacStopTxTimer( ); + +#endif // __LORAMAC_H__ diff --git a/features/lorawan/lorastack/mac/LoRaMacCrypto.cpp b/features/lorawan/lorastack/mac/LoRaMacCrypto.cpp new file mode 100644 index 0000000000..56998c36a9 --- /dev/null +++ b/features/lorawan/lorastack/mac/LoRaMacCrypto.cpp @@ -0,0 +1,347 @@ +/** + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * Description: LoRa MAC crypto implementation + * + * License: Revised BSD License, see LICENSE.TXT file include in the project + * + * Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel Jäckle ( STACKFORCE ) + * + * + * Copyright (c) 2017, Arm Limited and affiliates. + * + * SPDX-License-Identifier: BSD-3-Clause +*/ + +#include +#include +#include "lorastack/mac/LoRaMacCrypto.h" +#include "lorawan/system/lorawan_data_structures.h" + +#include "mbedtls/aes.h" +#include "mbedtls/cmac.h" + +#if defined(MBEDTLS_CMAC_C) && defined(MBEDTLS_AES_C) && defined(MBEDTLS_CIPHER_C) + +/** + * CMAC/AES Message Integrity Code (MIC) Block B0 size + */ +#define LORAMAC_MIC_BLOCK_B0_SIZE 16 + +/** + * MIC field computation initial data + */ +static uint8_t MicBlockB0[] = { 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + +/** + * Contains the computed MIC field. + * + * \remark Only the 4 first bytes are used + */ +static uint8_t Mic[16]; + +/** + * Encryption aBlock and sBlock + */ +static uint8_t aBlock[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; +static uint8_t sBlock[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + +/** + * AES computation context variable + */ +static mbedtls_aes_context AesContext; + +/** + * CMAC computation context variable + */ +static mbedtls_cipher_context_t AesCmacCtx[1]; + +#define AES_CMAC_KEY_LENGTH 16 + +/** + * \brief Computes the LoRaMAC frame MIC field + * + * \param [in] buffer Data buffer + * \param [in] size Data buffer size + * \param [in] key AES key to be used + * \param [in] address Frame address + * \param [in] dir Frame direction [0: uplink, 1: downlink] + * \param [in] sequenceCounter Frame sequence counter + * \param [out] mic Computed MIC field + */ +int LoRaMacComputeMic( const uint8_t *buffer, uint16_t size, const uint8_t *key, uint32_t address, uint8_t dir, uint32_t sequenceCounter, uint32_t *mic ) +{ + + int ret = 0; + + MicBlockB0[5] = dir; + + MicBlockB0[6] = ( address ) & 0xFF; + MicBlockB0[7] = ( address >> 8 ) & 0xFF; + MicBlockB0[8] = ( address >> 16 ) & 0xFF; + MicBlockB0[9] = ( address >> 24 ) & 0xFF; + + MicBlockB0[10] = ( sequenceCounter ) & 0xFF; + MicBlockB0[11] = ( sequenceCounter >> 8 ) & 0xFF; + MicBlockB0[12] = ( sequenceCounter >> 16 ) & 0xFF; + MicBlockB0[13] = ( sequenceCounter >> 24 ) & 0xFF; + + MicBlockB0[15] = size & 0xFF; + + mbedtls_cipher_init(AesCmacCtx); + const mbedtls_cipher_info_t* cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB); + if (NULL != cipher_info) { + ret = mbedtls_cipher_setup(AesCmacCtx, cipher_info); + if (0 != ret) + goto exit; + + ret = mbedtls_cipher_cmac_starts(AesCmacCtx, key, AES_CMAC_KEY_LENGTH*8); + if (0 != ret) + goto exit; + + ret = mbedtls_cipher_cmac_update(AesCmacCtx, MicBlockB0, LORAMAC_MIC_BLOCK_B0_SIZE); + if (0 != ret) + goto exit; + + ret = mbedtls_cipher_cmac_update(AesCmacCtx, buffer, size & 0xFF); + if (0 != ret) + goto exit; + + ret = mbedtls_cipher_cmac_finish(AesCmacCtx, Mic); + if (0 != ret) + goto exit; + + *mic = ( uint32_t )( ( uint32_t )Mic[3] << 24 | ( uint32_t )Mic[2] << 16 | ( uint32_t )Mic[1] << 8 | ( uint32_t )Mic[0] ); + } else { + ret = MBEDTLS_ERR_CIPHER_ALLOC_FAILED; + } + +exit: + mbedtls_cipher_free( AesCmacCtx ); + return ret; +} + +int LoRaMacPayloadEncrypt( const uint8_t *buffer, uint16_t size, const uint8_t *key, uint32_t address, uint8_t dir, uint32_t sequenceCounter, uint8_t *encBuffer ) +{ + uint16_t i; + uint8_t bufferIndex = 0; + uint16_t ctr = 1; + int ret = 0; + + mbedtls_aes_init(&AesContext); + ret = mbedtls_aes_setkey_enc(&AesContext, key, 16*8); + if (0 != ret) + goto exit; + + aBlock[5] = dir; + + aBlock[6] = ( address ) & 0xFF; + aBlock[7] = ( address >> 8 ) & 0xFF; + aBlock[8] = ( address >> 16 ) & 0xFF; + aBlock[9] = ( address >> 24 ) & 0xFF; + + aBlock[10] = ( sequenceCounter ) & 0xFF; + aBlock[11] = ( sequenceCounter >> 8 ) & 0xFF; + aBlock[12] = ( sequenceCounter >> 16 ) & 0xFF; + aBlock[13] = ( sequenceCounter >> 24 ) & 0xFF; + + while( size >= 16 ) + { + aBlock[15] = ( ( ctr ) & 0xFF ); + ctr++; + ret = mbedtls_aes_crypt_ecb(&AesContext, MBEDTLS_AES_ENCRYPT, aBlock, sBlock); + if (0 != ret) + goto exit; + + for( i = 0; i < 16; i++ ) + { + encBuffer[bufferIndex + i] = buffer[bufferIndex + i] ^ sBlock[i]; + } + size -= 16; + bufferIndex += 16; + } + + if( size > 0 ) + { + aBlock[15] = ( ( ctr ) & 0xFF ); + ret = mbedtls_aes_crypt_ecb(&AesContext, MBEDTLS_AES_ENCRYPT, aBlock, sBlock); + if (0 != ret) + goto exit; + + for( i = 0; i < size; i++ ) + { + encBuffer[bufferIndex + i] = buffer[bufferIndex + i] ^ sBlock[i]; + } + } + +exit: + mbedtls_aes_free(&AesContext); + return ret; +} + +int LoRaMacPayloadDecrypt( const uint8_t *buffer, uint16_t size, const uint8_t *key, uint32_t address, uint8_t dir, uint32_t sequenceCounter, uint8_t *decBuffer ) +{ + return LoRaMacPayloadEncrypt( buffer, size, key, address, dir, sequenceCounter, decBuffer ); +} + +int LoRaMacJoinComputeMic( const uint8_t *buffer, uint16_t size, const uint8_t *key, uint32_t *mic ) +{ + int ret = 0; + + mbedtls_cipher_init(AesCmacCtx); + const mbedtls_cipher_info_t* cipher_info = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB); + if (NULL != cipher_info) { + ret = mbedtls_cipher_setup(AesCmacCtx, cipher_info); + if (0 != ret) + goto exit; + + ret = mbedtls_cipher_cmac_starts(AesCmacCtx, key, AES_CMAC_KEY_LENGTH*8); + if (0 != ret) + goto exit; + + ret = mbedtls_cipher_cmac_update(AesCmacCtx, buffer, size & 0xFF); + if (0 != ret) + goto exit; + + ret = mbedtls_cipher_cmac_finish(AesCmacCtx, Mic); + if (0 != ret) + goto exit; + + *mic = ( uint32_t )( ( uint32_t )Mic[3] << 24 | ( uint32_t )Mic[2] << 16 | ( uint32_t )Mic[1] << 8 | ( uint32_t )Mic[0] ); + } else { + ret = MBEDTLS_ERR_CIPHER_ALLOC_FAILED; + } + +exit: + mbedtls_cipher_free(AesCmacCtx); + return ret; +} + +int LoRaMacJoinDecrypt( const uint8_t *buffer, uint16_t size, const uint8_t *key, uint8_t *decBuffer ) +{ + int ret = 0; + + mbedtls_aes_init(&AesContext); + + ret = mbedtls_aes_setkey_enc(&AesContext, key, 16*8); + if (0 != ret) + goto exit; + + ret = mbedtls_aes_crypt_ecb(&AesContext, MBEDTLS_AES_ENCRYPT, buffer, decBuffer); + if (0 != ret) + goto exit; + + // Check if optional CFList is included + if( size >= 16 ) + { + ret = mbedtls_aes_crypt_ecb(&AesContext, MBEDTLS_AES_ENCRYPT, buffer + 16, decBuffer + 16); + } + +exit: + mbedtls_aes_free(&AesContext); + return ret; +} + +int LoRaMacJoinComputeSKeys( const uint8_t *key, const uint8_t *appNonce, uint16_t devNonce, uint8_t *nwkSKey, uint8_t *appSKey ) +{ + uint8_t nonce[16]; + uint8_t *pDevNonce = ( uint8_t * )&devNonce; + int ret = 0; + + mbedtls_aes_init(&AesContext); + + ret = mbedtls_aes_setkey_enc(&AesContext, key, 16*8); + if (0 != ret) + goto exit; + + memset( nonce, 0, sizeof( nonce ) ); + nonce[0] = 0x01; + memcpy( nonce + 1, appNonce, 6 ); + memcpy( nonce + 7, pDevNonce, 2 ); + ret = mbedtls_aes_crypt_ecb(&AesContext, MBEDTLS_AES_ENCRYPT, nonce, nwkSKey); + if (0 != ret) + goto exit; + + memset( nonce, 0, sizeof( nonce ) ); + nonce[0] = 0x02; + memcpy( nonce + 1, appNonce, 6 ); + memcpy( nonce + 7, pDevNonce, 2 ); + ret = mbedtls_aes_crypt_ecb(&AesContext, MBEDTLS_AES_ENCRYPT, nonce, appSKey); + +exit: + mbedtls_aes_free(&AesContext); + return ret; +} +#else + +// If mbedTLS is not configured properly, these dummies will ensure that +// user knows what is wrong and in addition to that these ensure that +// Mbed-OS compiles properly under normal conditions where LoRaWAN in conjunction +// with mbedTLS is not being used. +int LoRaMacComputeMic( const uint8_t *, uint16_t , const uint8_t *, uint32_t, + uint8_t dir, uint32_t, uint32_t * ) +{ + MBED_ASSERT("[LoRaCrypto] Must enable AES, CMAC & CIPHER from mbedTLS"); + + // Never actually reaches here + return LORA_MAC_STATUS_CRYPTO_FAIL; +} + +int LoRaMacPayloadEncrypt( const uint8_t *, uint16_t , const uint8_t *, uint32_t, + uint8_t , uint32_t , uint8_t * ) +{ + MBED_ASSERT("[LoRaCrypto] Must enable AES, CMAC & CIPHER from mbedTLS"); + + // Never actually reaches here + return LORA_MAC_STATUS_CRYPTO_FAIL; +} + +int LoRaMacPayloadDecrypt( const uint8_t *, uint16_t , const uint8_t *, uint32_t, + uint8_t , uint32_t , uint8_t * ) +{ + MBED_ASSERT("[LoRaCrypto] Must enable AES, CMAC & CIPHER from mbedTLS"); + + // Never actually reaches here + return LORA_MAC_STATUS_CRYPTO_FAIL; +} +int LoRaMacJoinComputeMic( const uint8_t *, uint16_t , const uint8_t *, uint32_t * ) +{ + MBED_ASSERT("[LoRaCrypto] Must enable AES, CMAC & CIPHER from mbedTLS"); + + // Never actually reaches here + return LORA_MAC_STATUS_CRYPTO_FAIL; +} + +int LoRaMacJoinDecrypt( const uint8_t *, uint16_t , const uint8_t *, uint8_t * ) +{ + MBED_ASSERT("[LoRaCrypto] Must enable AES, CMAC & CIPHER from mbedTLS"); + + // Never actually reaches here + return LORA_MAC_STATUS_CRYPTO_FAIL; +} + +int LoRaMacJoinComputeSKeys( const uint8_t *, const uint8_t *, uint16_t , uint8_t *, uint8_t * ) +{ + MBED_ASSERT("[LoRaCrypto] Must enable AES, CMAC & CIPHER from mbedTLS"); + + // Never actually reaches here + return LORA_MAC_STATUS_CRYPTO_FAIL; +} + +#endif diff --git a/features/lorawan/lorastack/mac/LoRaMacCrypto.h b/features/lorawan/lorastack/mac/LoRaMacCrypto.h new file mode 100644 index 0000000000..cf09becb3e --- /dev/null +++ b/features/lorawan/lorastack/mac/LoRaMacCrypto.h @@ -0,0 +1,112 @@ +/** + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech + ___ _____ _ ___ _ _____ ___ ___ ___ ___ +/ __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| +\__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| +|___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| +embedded.connectivity.solutions=============== + +Description: LoRa MAC Crypto implementation + +License: Revised BSD License, see LICENSE.TXT file include in the project + +Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel Jaeckle ( STACKFORCE ) + + +Copyright (c) 2017, Arm Limited and affiliates. + +SPDX-License-Identifier: BSD-3-Clause +*/ + +#ifndef MBED_LORAWAN_MAC_LORAMAC_CRYPTO_H__ +#define MBED_LORAWAN_MAC_LORAMAC_CRYPTO_H__ + +/** + * Computes the LoRaMAC frame MIC field + * + * \param [in] buffer - Data buffer + * \param [in] size - Data buffer size + * \param [in] key - AES key to be used + * \param [in] address - Frame address + * \param [in] dir - Frame direction [0: uplink, 1: downlink] + * \param [in] sequenceCounter - Frame sequence counter + * \param [out] mic - Computed MIC field + * + * \return 0 if successful, or a cipher specific error code + */ +int LoRaMacComputeMic( const uint8_t *buffer, uint16_t size, const uint8_t *key, uint32_t address, uint8_t dir, uint32_t sequenceCounter, uint32_t *mic ); + +/** + * Computes the LoRaMAC payload encryption + * + * \param [in] buffer - Data buffer + * \param [in] size - Data buffer size + * \param [in] key - AES key to be used + * \param [in] address - Frame address + * \param [in] dir - Frame direction [0: uplink, 1: downlink] + * \param [in] sequenceCounter - Frame sequence counter + * \param [out] encBuffer - Encrypted buffer + * + * \return 0 if successful, or a cipher specific error code + */ +int LoRaMacPayloadEncrypt( const uint8_t *buffer, uint16_t size, const uint8_t *key, uint32_t address, uint8_t dir, uint32_t sequenceCounter, uint8_t *encBuffer ); + +/** + * Computes the LoRaMAC payload decryption + * + * \param [in] buffer - Data buffer + * \param [in] size - Data buffer size + * \param [in] key - AES key to be used + * \param [in] address - Frame address + * \param [in] dir - Frame direction [0: uplink, 1: downlink] + * \param [in] sequenceCounter - Frame sequence counter + * \param [out] decBuffer - Decrypted buffer + * + * \return 0 if successful, or a cipher specific error code + */ +int LoRaMacPayloadDecrypt( const uint8_t *buffer, uint16_t size, const uint8_t *key, uint32_t address, uint8_t dir, uint32_t sequenceCounter, uint8_t *decBuffer ); + +/** + * Computes the LoRaMAC Join Request frame MIC field + * + * \param [in] buffer - Data buffer + * \param [in] size - Data buffer size + * \param [in] key - AES key to be used + * \param [out] mic - Computed MIC field + * + * \return 0 if successful, or a cipher specific error code + * + */ +int LoRaMacJoinComputeMic( const uint8_t *buffer, uint16_t size, const uint8_t *key, uint32_t *mic ); + +/** + * Computes the LoRaMAC join frame decryption + * + * \param [in] buffer - Data buffer + * \param [in] size - Data buffer size + * \param [in] key - AES key to be used + * \param [out] decBuffer - Decrypted buffer + * + * \return 0 if successful, or a cipher specific error code + */ +int LoRaMacJoinDecrypt( const uint8_t *buffer, uint16_t size, const uint8_t *key, uint8_t *decBuffer ); + +/** + * Computes the LoRaMAC join frame decryption + * + * \param [in] key - AES key to be used + * \param [in] appNonce - Application nonce + * \param [in] devNonce - Device nonce + * \param [out] nwkSKey - Network session key + * \param [out] appSKey - Application session key + * + * \return 0 if successful, or a cipher specific error code + */ +int LoRaMacJoinComputeSKeys( const uint8_t *key, const uint8_t *appNonce, uint16_t devNonce, uint8_t *nwkSKey, uint8_t *appSKey ); + +#endif // MBED_LORAWAN_MAC_LORAMAC_CRYPTO_H__ diff --git a/features/lorawan/lorastack/mac/LoRaMacTest.h b/features/lorawan/lorastack/mac/LoRaMacTest.h new file mode 100644 index 0000000000..cf51a1c29b --- /dev/null +++ b/features/lorawan/lorastack/mac/LoRaMacTest.h @@ -0,0 +1,68 @@ +/** + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech + ___ _____ _ ___ _ _____ ___ ___ ___ ___ +/ __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| +\__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| +|___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| +embedded.connectivity.solutions=============== + +Description: LoRa MAC layer test function implementation + +License: Revised BSD License, see LICENSE.TXT file include in the project + +Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel Jaeckle ( STACKFORCE ) + + +Copyright (c) 2017, Arm Limited and affiliates. + +SPDX-License-Identifier: BSD-3-Clause +*/ +#ifndef __LORAMACTEST_H__ +#define __LORAMACTEST_H__ + +/** + * \brief Enabled or disables the reception windows + * + * \details This is a test function. It shall be used for testing purposes only. + * Changing this attribute may lead to a non-conformance LoRaMac operation. + * + * \param [in] enable - Enabled or disables the reception windows + */ +void LoRaMacTestRxWindowsOn( bool enable ); + +/** + * \brief Enables the MIC field test + * + * \details This is a test function. It shall be used for testing purposes only. + * Changing this attribute may lead to a non-conformance LoRaMac operation. + * + * \param [in] txPacketCounter - Fixed Tx packet counter value + */ +void LoRaMacTestSetMic( uint16_t txPacketCounter ); + +/** + * \brief Enabled or disables the duty cycle + * + * \details This is a test function. It shall be used for testing purposes only. + * Changing this attribute may lead to a non-conformance LoRaMac operation. + * + * \param [in] enable - Enabled or disables the duty cycle + */ +void LoRaMacTestSetDutyCycleOn( bool enable ); + +/** + * \brief Sets the channel index + * + * \details This is a test function. It shall be used for testing purposes only. + * Changing this attribute may lead to a non-conformance LoRaMac operation. + * + * \param [in] channel - Channel index + */ +void LoRaMacTestSetChannel( uint8_t channel ); + +#endif // __LORAMACTEST_H__