1 /*!
2  * \file      LmhpClockSync.c
3  *
4  * \brief     Implements the LoRa-Alliance clock synchronization package
5  *            Specification: https://lora-alliance.org/sites/default/files/2018-09/application_layer_clock_synchronization_v1.0.0.pdf
6  *
7  * \copyright Revised BSD License, see section \ref LICENSE.
8  *
9  * \code
10  *                ______                              _
11  *               / _____)             _              | |
12  *              ( (____  _____ ____ _| |_ _____  ____| |__
13  *               \____ \| ___ |    (_   _) ___ |/ ___)  _ \
14  *               _____) ) ____| | | || |_| ____( (___| | | |
15  *              (______/|_____)_|_|_| \__)_____)\____)_| |_|
16  *              (C)2013-2018 Semtech
17  *
18  * \endcode
19  *
20  * \author    Miguel Luis ( Semtech )
21  */
22 #include "LmHandler.h"
23 #include "LmhpClockSync.h"
24 
25 /*!
26  * LoRaWAN Application Layer Clock Synchronization Specification
27  */
28 #define CLOCK_SYNC_PORT                             202
29 
30 #define CLOCK_SYNC_ID                               1
31 #define CLOCK_SYNC_VERSION                          1
32 
33 /*!
34  * Package current context
35  */
36 typedef struct LmhpClockSyncState_s
37 {
38     bool Initialized;
39     bool IsTxPending;
40     uint8_t DataBufferMaxSize;
41     uint8_t *DataBuffer;
42     union
43     {
44         uint8_t Value;
45         struct
46         {
47             uint8_t TokenReq:    4;
48             uint8_t AnsRequired: 1;
49             uint8_t RFU:         3;
50         }Fields;
51     }TimeReqParam;
52     bool AppTimeReqPending;
53     bool AdrEnabledPrev;
54     uint8_t NbTransPrev;
55     uint8_t DataratePrev;
56     uint8_t NbTransmissions;
57 }LmhpClockSyncState_t;
58 
59 typedef enum LmhpClockSyncMoteCmd_e
60 {
61     CLOCK_SYNC_PKG_VERSION_ANS       = 0x00,
62     CLOCK_SYNC_APP_TIME_REQ          = 0x01,
63     CLOCK_SYNC_APP_TIME_PERIOD_ANS   = 0x02,
64     CLOCK_SYNC_FORCE_RESYNC_ANS      = 0x03,
65 }LmhpClockSyncMoteCmd_t;
66 
67 typedef enum LmhpClockSyncSrvCmd_e
68 {
69     CLOCK_SYNC_PKG_VERSION_REQ       = 0x00,
70     CLOCK_SYNC_APP_TIME_ANS          = 0x01,
71     CLOCK_SYNC_APP_TIME_PERIOD_REQ   = 0x02,
72     CLOCK_SYNC_FORCE_RESYNC_REQ      = 0x03,
73 }LmhpClockSyncSrvCmd_t;
74 
75 /*!
76  * Initializes the package with provided parameters
77  *
78  * \param [IN] params            Pointer to the package parameters
79  * \param [IN] dataBuffer        Pointer to main application buffer
80  * \param [IN] dataBufferMaxSize Main application buffer maximum size
81  */
82 static void LmhpClockSyncInit( void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize );
83 
84 /*!
85  * Returns the current package initialization status.
86  *
87  * \retval status Package initialization status
88  *                [true: Initialized, false: Not initialized]
89  */
90 static bool LmhpClockSyncIsInitialized( void );
91 
92 /*!
93  * Returns if a package transmission is pending or not.
94  *
95  * \retval status Package transmission status
96  *                [true: pending, false: Not pending]
97  */
98 static bool LmhpClockSyncIsTxPending( void );
99 
100 /*!
101  * Processes the internal package events.
102  */
103 static void LmhpClockSyncProcess( void );
104 
105 /*!
106  * Processes the MCSP Confirm
107  *
108  * \param [IN] mcpsConfirm MCPS confirmation primitive data
109  */
110 static void LmhpClockSyncOnMcpsConfirm( McpsConfirm_t *mcpsConfirm );
111 
112 /*!
113  * Processes the MCPS Indication
114  *
115  * \param [IN] mcpsIndication     MCPS indication primitive data
116  */
117 static void LmhpClockSyncOnMcpsIndication( McpsIndication_t *mcpsIndication );
118 
119 static LmhpClockSyncState_t LmhpClockSyncState =
120 {
121     .Initialized = false,
122     .IsTxPending = false,
123     .TimeReqParam.Value = 0,
124     .AppTimeReqPending = false,
125     .AdrEnabledPrev = false,
126     .NbTransPrev = 0,
127     .NbTransmissions = 0,
128 };
129 
130 static LmhPackage_t LmhpClockSyncPackage =
131 {
132     .Port = CLOCK_SYNC_PORT,
133     .Init = LmhpClockSyncInit,
134     .IsInitialized = LmhpClockSyncIsInitialized,
135     .IsTxPending = LmhpClockSyncIsTxPending,
136     .Process = LmhpClockSyncProcess,
137     .OnMcpsConfirmProcess = LmhpClockSyncOnMcpsConfirm,
138     .OnMcpsIndicationProcess = LmhpClockSyncOnMcpsIndication,
139     .OnMlmeConfirmProcess = NULL,                              // Not used in this package
140     .OnMlmeIndicationProcess = NULL,                           // Not used in this package
141     .OnMacMcpsRequest = NULL,                                  // To be initialized by LmHandler
142     .OnMacMlmeRequest = NULL,                                  // To be initialized by LmHandler
143     .OnJoinRequest = NULL,                                     // To be initialized by LmHandler
144     .OnDeviceTimeRequest = NULL,                               // To be initialized by LmHandler
145     .OnSysTimeUpdate = NULL,                                   // To be initialized by LmHandler
146 };
147 
LmphClockSyncPackageFactory(void)148 LmhPackage_t *LmphClockSyncPackageFactory( void )
149 {
150     return &LmhpClockSyncPackage;
151 }
152 
LmhpClockSyncInit(void * params,uint8_t * dataBuffer,uint8_t dataBufferMaxSize)153 static void LmhpClockSyncInit( void * params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize )
154 {
155     if( dataBuffer != NULL )
156     {
157         LmhpClockSyncState.DataBuffer = dataBuffer;
158         LmhpClockSyncState.DataBufferMaxSize = dataBufferMaxSize;
159         LmhpClockSyncState.Initialized = true;
160     }
161     else
162     {
163         LmhpClockSyncState.Initialized = false;
164     }
165     LmhpClockSyncState.IsTxPending = false;
166 }
167 
LmhpClockSyncIsInitialized(void)168 static bool LmhpClockSyncIsInitialized( void )
169 {
170     return LmhpClockSyncState.Initialized;
171 }
172 
LmhpClockSyncIsTxPending(void)173 static bool LmhpClockSyncIsTxPending( void )
174 {
175     return LmhpClockSyncState.IsTxPending;
176 }
177 
LmhpClockSyncProcess(void)178 static void LmhpClockSyncProcess( void )
179 {
180     if( LmhpClockSyncState.NbTransmissions > 0 )
181     {
182         if( LmhpClockSyncAppTimeReq( ) == LORAMAC_HANDLER_SUCCESS )
183         {
184             LmhpClockSyncState.NbTransmissions--;
185         }
186     }
187 }
188 
LmhpClockSyncOnMcpsConfirm(McpsConfirm_t * mcpsConfirm)189 static void LmhpClockSyncOnMcpsConfirm( McpsConfirm_t *mcpsConfirm )
190 {
191     MibRequestConfirm_t mibReq;
192 
193     if( LmhpClockSyncState.AppTimeReqPending == true )
194     {
195         // Revert ADR setting
196         mibReq.Type = MIB_ADR;
197         mibReq.Param.AdrEnable = LmhpClockSyncState.AdrEnabledPrev;
198         LoRaMacMibSetRequestConfirm( &mibReq );
199 
200         // Revert NbTrans setting
201         mibReq.Type = MIB_CHANNELS_NB_TRANS;
202         mibReq.Param.ChannelsNbTrans = LmhpClockSyncState.NbTransPrev;
203         LoRaMacMibSetRequestConfirm( &mibReq );
204 
205         // Revert data rate setting
206         mibReq.Type = MIB_CHANNELS_DATARATE;
207         mibReq.Param.ChannelsDatarate = LmhpClockSyncState.DataratePrev;
208         LoRaMacMibSetRequestConfirm( &mibReq );
209 
210         LmhpClockSyncState.AppTimeReqPending = false;
211     }
212 }
213 
LmhpClockSyncOnMcpsIndication(McpsIndication_t * mcpsIndication)214 static void LmhpClockSyncOnMcpsIndication( McpsIndication_t *mcpsIndication )
215 {
216     uint8_t cmdIndex = 0;
217     uint8_t dataBufferIndex = 0;
218 
219     if( mcpsIndication->Port != CLOCK_SYNC_PORT )
220     {
221         return;
222     }
223 
224     while( cmdIndex < mcpsIndication->BufferSize )
225     {
226         switch( mcpsIndication->Buffer[cmdIndex++] )
227         {
228             case CLOCK_SYNC_PKG_VERSION_REQ:
229             {
230                 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_PKG_VERSION_ANS;
231                 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_ID;
232                 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_VERSION;
233                 break;
234             }
235             case CLOCK_SYNC_APP_TIME_ANS:
236             {
237                 LmhpClockSyncState.NbTransmissions = 0;
238 
239                 // Check if a more precise time correction has been received.
240                 // If yes then don't process and ignore this answer.
241                 if( mcpsIndication->DeviceTimeAnsReceived == true )
242                 {
243                     cmdIndex += 5;
244                     break;
245                 }
246                 int32_t timeCorrection = 0;
247                 timeCorrection  = ( mcpsIndication->Buffer[cmdIndex++] << 0  ) & 0x000000FF;
248                 timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 8  ) & 0x0000FF00;
249                 timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000;
250                 timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000;
251                 if( ( mcpsIndication->Buffer[cmdIndex++] & 0x0F ) == LmhpClockSyncState.TimeReqParam.Fields.TokenReq )
252                 {
253                     SysTime_t curTime = { .Seconds = 0, .SubSeconds = 0 };
254                     curTime = SysTimeGet( );
255                     curTime.Seconds += timeCorrection;
256                     SysTimeSet( curTime );
257                     LmhpClockSyncState.TimeReqParam.Fields.TokenReq = ( LmhpClockSyncState.TimeReqParam.Fields.TokenReq + 1 ) & 0x0F;
258                     if( LmhpClockSyncPackage.OnSysTimeUpdate != NULL )
259                     {
260 #if( LMH_SYS_TIME_UPDATE_NEW_API == 1 )
261                         LmhpClockSyncPackage.OnSysTimeUpdate(
262                                         ( timeCorrection >= -1 ) && ( timeCorrection <= 1 ),
263                                         timeCorrection );
264 #else
265                         if( ( timeCorrection >= -1 ) && ( timeCorrection <= 1 ) )
266                         {
267                             LmhpClockSyncPackage.OnSysTimeUpdate( );
268                         }
269 #endif
270                     }
271                 }
272                 break;
273             }
274             case CLOCK_SYNC_APP_TIME_PERIOD_REQ:
275             {
276                 // Increment index
277                 cmdIndex++;
278                 // TODO implement command prosessing and handling
279                 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_APP_TIME_PERIOD_ANS;
280                 // Answer status not supported.
281                 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = 0x01;
282 
283                 SysTime_t curTime = SysTimeGet( );
284                 // Substract Unix to Gps epcoh offset. The system time is based on Unix time.
285                 curTime.Seconds -= UNIX_GPS_EPOCH_OFFSET;
286                 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 0  ) & 0xFF;
287                 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 8  ) & 0xFF;
288                 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 16 ) & 0xFF;
289                 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 24 ) & 0xFF;
290                 break;
291             }
292             case CLOCK_SYNC_FORCE_RESYNC_REQ:
293             {
294                 LmhpClockSyncState.NbTransmissions = mcpsIndication->Buffer[cmdIndex++] & 0X07;
295                 break;
296             }
297         }
298     }
299 
300     if( dataBufferIndex != 0 )
301     {
302         // Answer commands
303         LmHandlerAppData_t appData =
304         {
305             .Buffer = LmhpClockSyncState.DataBuffer,
306             .BufferSize = dataBufferIndex,
307             .Port = CLOCK_SYNC_PORT
308         };
309         LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG );
310     }
311 }
312 
LmhpClockSyncAppTimeReq(void)313 LmHandlerErrorStatus_t LmhpClockSyncAppTimeReq( void )
314 {
315     if( LmHandlerIsBusy( ) == true )
316     {
317         return LORAMAC_HANDLER_ERROR;
318     }
319 
320     if( LmhpClockSyncState.AppTimeReqPending == false )
321     {
322         MibRequestConfirm_t mibReq;
323 
324         // Disable ADR
325         mibReq.Type = MIB_ADR;
326         LoRaMacMibGetRequestConfirm( &mibReq );
327         LmhpClockSyncState.AdrEnabledPrev = mibReq.Param.AdrEnable;
328         mibReq.Param.AdrEnable = false;
329         LoRaMacMibSetRequestConfirm( &mibReq );
330 
331         // Set NbTrans = 1
332         mibReq.Type = MIB_CHANNELS_NB_TRANS;
333         LoRaMacMibGetRequestConfirm( &mibReq );
334         LmhpClockSyncState.NbTransPrev = mibReq.Param.ChannelsNbTrans;
335         mibReq.Param.ChannelsNbTrans = 1;
336         LoRaMacMibSetRequestConfirm( &mibReq );
337 
338         // Store data rate
339         mibReq.Type = MIB_CHANNELS_DATARATE;
340         LoRaMacMibGetRequestConfirm( &mibReq );
341         LmhpClockSyncState.DataratePrev = mibReq.Param.ChannelsDatarate;
342 
343         // Add DeviceTimeReq MAC command.
344         // In case the network server supports this more precise command
345         // this package will use DeviceTimeAns answer as clock synchronization
346         // mechanism.
347         LmhpClockSyncPackage.OnDeviceTimeRequest( );
348     }
349 
350     SysTime_t curTime = SysTimeGet( );
351     uint8_t dataBufferIndex = 0;
352 
353     // Substract Unix to Gps epcoh offset. The system time is based on Unix time.
354     curTime.Seconds -= UNIX_GPS_EPOCH_OFFSET;
355 
356     LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_APP_TIME_REQ;
357     LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 0  ) & 0xFF;
358     LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 8  ) & 0xFF;
359     LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 16 ) & 0xFF;
360     LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 24 ) & 0xFF;
361     LmhpClockSyncState.TimeReqParam.Fields.AnsRequired = 0;
362     LmhpClockSyncState.DataBuffer[dataBufferIndex++] = LmhpClockSyncState.TimeReqParam.Value;
363 
364     LmHandlerAppData_t appData =
365     {
366         .Buffer = LmhpClockSyncState.DataBuffer,
367         .BufferSize = dataBufferIndex,
368         .Port = CLOCK_SYNC_PORT
369     };
370     LmhpClockSyncState.AppTimeReqPending = true;
371     return LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG );
372 }
373