/*! * \file LmhpRemoteMcastSetup.c * * \brief Implements the LoRa-Alliance remote multicast setup package * Specification: https://lora-alliance.org/sites/default/files/2018-09/remote_multicast_setup_v1.0.0.pdf * * \copyright Revised BSD License, see section \ref LICENSE. * * \code * ______ _ * / _____) _ | | * ( (____ _____ ____ _| |_ _____ ____| |__ * \____ \| ___ | (_ _) ___ |/ ___) _ \ * _____) ) ____| | | || |_| ____( (___| | | | * (______/|_____)_|_|_| \__)_____)\____)_| |_| * (C)2013-2018 Semtech * * \endcode * * \author Miguel Luis ( Semtech ) */ #include "LmHandler.h" #include "LmhpRemoteMcastSetup.h" #define DBG_TRACE 1 #if DBG_TRACE == 1 #include /*! * Works in the same way as the printf function does. */ #define DBG( ... ) \ do \ { \ printf( __VA_ARGS__ ); \ }while( 0 ) #else #define DBG( ... ) #endif /*! * LoRaWAN Application Layer Remote multicast setup Specification */ #define REMOTE_MCAST_SETUP_PORT 200 #define REMOTE_MCAST_SETUP_ID 2 #define REMOTE_MCAST_SETUP_VERSION 1 typedef enum LmhpRemoteMcastSetupSessionStates_e { REMOTE_MCAST_SETUP_SESSION_STATE_IDLE, REMOTE_MCAST_SETUP_SESSION_STATE_START, REMOTE_MCAST_SETUP_SESSION_STATE_STOP, }LmhpRemoteMcastSetupSessionStates_t; /*! * Package current context */ typedef struct LmhpRemoteMcastSetupState_s { bool Initialized; bool IsTxPending; LmhpRemoteMcastSetupSessionStates_t SessionState; uint8_t DataBufferMaxSize; uint8_t *DataBuffer; }LmhpRemoteMcastSetupState_t; typedef enum LmhpRemoteMcastSetupMoteCmd_e { REMOTE_MCAST_SETUP_PKG_VERSION_ANS = 0x00, REMOTE_MCAST_SETUP_MC_GROUP_STATUS_ANS = 0x01, REMOTE_MCAST_SETUP_MC_GROUP_SETUP_ANS = 0x02, REMOTE_MCAST_SETUP_MC_GROUP_DELETE_ANS = 0x03, REMOTE_MCAST_SETUP_MC_GROUP_CLASS_C_SESSION_ANS = 0x04, REMOTE_MCAST_SETUP_MC_GROUP_CLASS_B_SESSION_ANS = 0x05, }LmhpRemoteMcastSetupMoteCmd_t; typedef enum LmhpRemoteMcastSetupSrvCmd_e { REMOTE_MCAST_SETUP_PKG_VERSION_REQ = 0x00, REMOTE_MCAST_SETUP_MC_GROUP_STATUS_REQ = 0x01, REMOTE_MCAST_SETUP_MC_GROUP_SETUP_REQ = 0x02, REMOTE_MCAST_SETUP_MC_GROUP_DELETE_REQ = 0x03, REMOTE_MCAST_SETUP_MC_GROUP_CLASS_C_SESSION_REQ = 0x04, REMOTE_MCAST_SETUP_MC_GROUP_CLASS_B_SESSION_REQ = 0x05, }LmhpRemoteMcastSetupSrvCmd_t; /*! * Initializes the package with provided parameters * * \param [IN] params Pointer to the package parameters * \param [IN] dataBuffer Pointer to main application buffer * \param [IN] dataBufferMaxSize Main application buffer maximum size */ static void LmhpRemoteMcastSetupInit( void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize ); /*! * Returns the current package initialization status. * * \retval status Package initialization status * [true: Initialized, false: Not initialized] */ static bool LmhpRemoteMcastSetupIsInitialized( void ); /*! * Returns if a package transmission is pending or not. * * \retval status Package transmission status * [true: pending, false: Not pending] */ static bool LmhpRemoteMcastSetupIsTxPending( void ); /*! * Processes the internal package events. */ static void LmhpRemoteMcastSetupProcess( void ); /*! * Processes the MCPS Indication * * \param [IN] mcpsIndication MCPS indication primitive data */ static void LmhpRemoteMcastSetupOnMcpsIndication( McpsIndication_t *mcpsIndication ); static void OnSessionStartTimer( void *context ); static void OnSessionStopTimer( void *context ); static LmhpRemoteMcastSetupState_t LmhpRemoteMcastSetupState = { .Initialized = false, .IsTxPending = false, .SessionState = REMOTE_MCAST_SETUP_SESSION_STATE_IDLE, }; typedef struct McGroupData_s { union { uint8_t Value; struct { uint8_t McGroupId: 2; uint8_t RFU: 6; }Fields; }IdHeader; uint32_t McAddr; uint8_t McKeyEncrypted[16]; uint32_t McFCountMin; uint32_t McFCountMax; }McGroupData_t; typedef enum eSessionState { SESSION_STOPED, SESSION_STARTED }SessionState_t; typedef struct McSessionData_s { McGroupData_t McGroupData; SessionState_t SessionState; uint32_t SessionTime; uint8_t SessionTimeout; McRxParams_t RxParams; }McSessionData_t; McSessionData_t McSessionData[LORAMAC_MAX_MC_CTX]; /*! * Session start timer */ static TimerEvent_t SessionStartTimer; /*! * Session start timer */ static TimerEvent_t SessionStopTimer; static LmhPackage_t LmhpRemoteMcastSetupPackage = { .Port = REMOTE_MCAST_SETUP_PORT, .Init = LmhpRemoteMcastSetupInit, .IsInitialized = LmhpRemoteMcastSetupIsInitialized, .IsTxPending = LmhpRemoteMcastSetupIsTxPending, .Process = LmhpRemoteMcastSetupProcess, .OnMcpsConfirmProcess = NULL, // Not used in this package .OnMcpsIndicationProcess = LmhpRemoteMcastSetupOnMcpsIndication, .OnMlmeConfirmProcess = NULL, // Not used in this package .OnMlmeIndicationProcess = NULL, // Not used in this package .OnMacMcpsRequest = NULL, // To be initialized by LmHandler .OnMacMlmeRequest = NULL, // To be initialized by LmHandler .OnJoinRequest = NULL, // To be initialized by LmHandler .OnDeviceTimeRequest = NULL, // To be initialized by LmHandler .OnSysTimeUpdate = NULL, // To be initialized by LmHandler }; LmhPackage_t *LmhpRemoteMcastSetupPackageFactory( void ) { return &LmhpRemoteMcastSetupPackage; } static void LmhpRemoteMcastSetupInit( void * params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize ) { if( dataBuffer != NULL ) { LmhpRemoteMcastSetupState.DataBuffer = dataBuffer; LmhpRemoteMcastSetupState.DataBufferMaxSize = dataBufferMaxSize; LmhpRemoteMcastSetupState.Initialized = true; TimerInit( &SessionStartTimer, OnSessionStartTimer ); TimerInit( &SessionStopTimer, OnSessionStopTimer ); } else { LmhpRemoteMcastSetupState.Initialized = false; } LmhpRemoteMcastSetupState.IsTxPending = false; } static bool LmhpRemoteMcastSetupIsInitialized( void ) { return LmhpRemoteMcastSetupState.Initialized; } static bool LmhpRemoteMcastSetupIsTxPending( void ) { return LmhpRemoteMcastSetupState.IsTxPending; } static void LmhpRemoteMcastSetupProcess( void ) { LmhpRemoteMcastSetupSessionStates_t state; CRITICAL_SECTION_BEGIN( ); state = LmhpRemoteMcastSetupState.SessionState; LmhpRemoteMcastSetupState.SessionState = REMOTE_MCAST_SETUP_SESSION_STATE_IDLE; CRITICAL_SECTION_END( ); switch( state ) { case REMOTE_MCAST_SETUP_SESSION_STATE_START: // Switch to Class C LmHandlerRequestClass( CLASS_C ); TimerSetValue( &SessionStopTimer, ( 1 << McSessionData[0].SessionTimeout ) * 1000 ); TimerStart( &SessionStopTimer ); break; case REMOTE_MCAST_SETUP_SESSION_STATE_STOP: // Switch back to Class A LmHandlerRequestClass( CLASS_A ); break; case REMOTE_MCAST_SETUP_SESSION_STATE_IDLE: // Intentional fall through default: // Nothing to do. break; } } static void LmhpRemoteMcastSetupOnMcpsIndication( McpsIndication_t *mcpsIndication ) { uint8_t cmdIndex = 0; uint8_t dataBufferIndex = 0; if( mcpsIndication->Port != REMOTE_MCAST_SETUP_PORT ) { return; } while( cmdIndex < mcpsIndication->BufferSize ) { switch( mcpsIndication->Buffer[cmdIndex++] ) { case REMOTE_MCAST_SETUP_PKG_VERSION_REQ: { LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = REMOTE_MCAST_SETUP_PKG_VERSION_ANS; LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = REMOTE_MCAST_SETUP_ID; LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = REMOTE_MCAST_SETUP_VERSION; break; } case REMOTE_MCAST_SETUP_MC_GROUP_STATUS_REQ: { // TODO implement command prosessing and handling break; } case REMOTE_MCAST_SETUP_MC_GROUP_SETUP_REQ: { uint8_t id = mcpsIndication->Buffer[cmdIndex++]; McSessionData[id].McGroupData.IdHeader.Value = id; McSessionData[id].McGroupData.McAddr = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x000000FF; McSessionData[id].McGroupData.McAddr += ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0x0000FF00; McSessionData[id].McGroupData.McAddr += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000; McSessionData[id].McGroupData.McAddr += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000; for( int8_t i = 0; i < 16; i++ ) { McSessionData[id].McGroupData.McKeyEncrypted[i] = mcpsIndication->Buffer[cmdIndex++]; } McSessionData[id].McGroupData.McFCountMin = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x000000FF; McSessionData[id].McGroupData.McFCountMin += ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0x0000FF00; McSessionData[id].McGroupData.McFCountMin += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000; McSessionData[id].McGroupData.McFCountMin += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000; McSessionData[id].McGroupData.McFCountMax = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x000000FF; McSessionData[id].McGroupData.McFCountMax += ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0x0000FF00; McSessionData[id].McGroupData.McFCountMax += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000; McSessionData[id].McGroupData.McFCountMax += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000; McChannelParams_t channel = { .IsRemotelySetup = true, .Class = CLASS_C, // Field not used for multicast channel setup. Must be initialized to something .IsEnabled = true, .GroupID = ( AddressIdentifier_t )McSessionData[id].McGroupData.IdHeader.Fields.McGroupId, .Address = McSessionData[id].McGroupData.McAddr, .McKeys.McKeyE = McSessionData[id].McGroupData.McKeyEncrypted, .FCountMin = McSessionData[id].McGroupData.McFCountMin, .FCountMax = McSessionData[id].McGroupData.McFCountMax, .RxParams.ClassC = // Field not used for multicast channel setup. Must be initialized to something { .Frequency = 0, .Datarate = 0 } }; uint8_t idError = 0x01; // One bit value if( LoRaMacMcChannelSetup( &channel ) == LORAMAC_STATUS_OK ) { idError = 0x00; } LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = REMOTE_MCAST_SETUP_MC_GROUP_SETUP_ANS; LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = ( idError << 2 ) | McSessionData[id].McGroupData.IdHeader.Fields.McGroupId; break; } case REMOTE_MCAST_SETUP_MC_GROUP_DELETE_REQ: { uint8_t status = 0x00; uint8_t id = mcpsIndication->Buffer[cmdIndex++] & 0x03; status = id; LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = REMOTE_MCAST_SETUP_MC_GROUP_DELETE_ANS; if( LoRaMacMcChannelDelete( ( AddressIdentifier_t )id ) != LORAMAC_STATUS_OK ) { status |= 0x04; // McGroupUndefined bit set } LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = status; break; } case REMOTE_MCAST_SETUP_MC_GROUP_CLASS_C_SESSION_REQ: { uint8_t status = 0x00; uint8_t id = mcpsIndication->Buffer[cmdIndex++] & 0x03; McSessionData[id].SessionTime = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x000000FF; McSessionData[id].SessionTime += ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0x0000FF00; McSessionData[id].SessionTime += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000; McSessionData[id].SessionTime += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000; // Add Unix to Gps epcoh offset. The system time is based on Unix time. McSessionData[id].SessionTime += UNIX_GPS_EPOCH_OFFSET; McSessionData[id].SessionTimeout = mcpsIndication->Buffer[cmdIndex++] & 0x0F; McSessionData[id].RxParams.ClassC.Frequency = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x000000FF; McSessionData[id].RxParams.ClassC.Frequency |= ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0x0000FF00; McSessionData[id].RxParams.ClassC.Frequency |= ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000; McSessionData[id].RxParams.ClassC.Frequency *= 100; McSessionData[id].RxParams.ClassC.Datarate = mcpsIndication->Buffer[cmdIndex++]; LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = REMOTE_MCAST_SETUP_MC_GROUP_CLASS_C_SESSION_ANS; if( LoRaMacMcChannelSetupRxParams( ( AddressIdentifier_t )id, &McSessionData[id].RxParams, &status ) == LORAMAC_STATUS_OK ) { SysTime_t curTime = { .Seconds = 0, .SubSeconds = 0 }; curTime = SysTimeGet( ); int32_t timeToSessionStart = McSessionData[id].SessionTime - curTime.Seconds; if( timeToSessionStart > 0 ) { // Start session start timer TimerSetValue( &SessionStartTimer, timeToSessionStart * 1000 ); TimerStart( &SessionStartTimer ); DBG( "Time2SessionStart: %ld ms\n", timeToSessionStart * 1000 ); LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = status; LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = ( timeToSessionStart >> 0 ) & 0xFF; LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = ( timeToSessionStart >> 8 ) & 0xFF; LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = ( timeToSessionStart >> 16 ) & 0xFF; break; } else { // Session start time before current device time status |= 0x10; } } LmhpRemoteMcastSetupState.DataBuffer[dataBufferIndex++] = status; break; } case REMOTE_MCAST_SETUP_MC_GROUP_CLASS_B_SESSION_REQ: { // TODO implement command prosessing and handling break; } default: { break; } } } if( dataBufferIndex != 0 ) { // Answer commands LmHandlerAppData_t appData = { .Buffer = LmhpRemoteMcastSetupState.DataBuffer, .BufferSize = dataBufferIndex, .Port = REMOTE_MCAST_SETUP_PORT }; LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG ); DBG( "ID : %d\n", McSessionData[0].McGroupData.IdHeader.Fields.McGroupId ); DBG( "McAddr : %08lX\n", McSessionData[0].McGroupData.McAddr ); DBG( "McKey : %02X", McSessionData[0].McGroupData.McKeyEncrypted[0] ); for( int i = 1; i < 16; i++ ) { DBG( "-%02X", McSessionData[0].McGroupData.McKeyEncrypted[i] ); } DBG( "\n" ); DBG( "McFCountMin : %lu\n", McSessionData[0].McGroupData.McFCountMin ); DBG( "McFCountMax : %lu\n", McSessionData[0].McGroupData.McFCountMax ); DBG( "SessionTime : %lu\n", McSessionData[0].SessionTime ); DBG( "SessionTimeT: %d\n", McSessionData[0].SessionTimeout ); DBG( "Rx Freq : %lu\n", McSessionData[0].RxParams.ClassC.Frequency ); DBG( "Rx DR : DR_%d\n", McSessionData[0].RxParams.ClassC.Datarate ); } } static void OnSessionStartTimer( void *context ) { TimerStop( &SessionStartTimer ); LmhpRemoteMcastSetupState.SessionState = REMOTE_MCAST_SETUP_SESSION_STATE_START; } static void OnSessionStopTimer( void *context ) { TimerStop( &SessionStopTimer ); LmhpRemoteMcastSetupState.SessionState = REMOTE_MCAST_SETUP_SESSION_STATE_STOP; }