mqtt_sn_client_misc.c
Go to the documentation of this file.
1 /**
2  * @file mqtt_sn_client_misc.c
3  * @brief Helper functions for MQTT-SN client
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2024 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @author Oryx Embedded SARL (www.oryx-embedded.com)
28  * @version 2.4.0
29  **/
30 
31 //Switch to the appropriate trace level
32 #define TRACE_LEVEL MQTT_SN_TRACE_LEVEL
33 
34 //Dependencies
35 #include "core/net.h"
36 #include "mqtt_sn/mqtt_sn_client.h"
40 #include "debug.h"
41 
42 //Check TCP/IP stack configuration
43 #if (MQTT_SN_CLIENT_SUPPORT == ENABLED)
44 
45 
46 /**
47  * @brief Process MQTT-SN client events
48  * @param[in] context Pointer to the MQTT-SN client context
49  * @param[in] timeout Maximum time to wait before returning
50  * @return Error code
51  **/
52 
54  systime_t timeout)
55 {
56  error_t error;
57  systime_t d;
58  systime_t startTime;
59  systime_t currentTime;
60  IpAddr ipAddr;
61  uint16_t port;
62 
63  //Flush buffer
64  context->message.length = 0;
65 
66  //Save current time
67  currentTime = osGetSystemTime();
68  startTime = currentTime;
69 
70  //Initialize status code
71  error = NO_ERROR;
72 
73  //Process events
74  do
75  {
76  //Maximum time to wait for an incoming datagram
77  if(timeCompare(startTime + timeout, currentTime) > 0)
78  {
79  d = startTime + timeout - currentTime;
80  }
81  else
82  {
83  d = 0;
84  }
85 
86  //Limit the delay
88 
89  //Wait for an incoming datagram
90  error = mqttSnClientReceiveDatagram(context, &ipAddr, &port,
91  context->message.buffer, MQTT_SN_MAX_MSG_SIZE,
92  &context->message.length, d);
93 
94  //Get current time
95  currentTime = osGetSystemTime();
96 
97  //Any datagram received?
98  if(error == NO_ERROR)
99  {
100  //Terminate the payload with a NULL character
101  context->message.buffer[context->message.length] = '\0';
102 
103  //Process the received MQTT-SN message
104  mqttSnClientProcessMessage(context, &context->message, &ipAddr, port);
105  }
106  else if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
107  {
108  //No datagram has been received
109  error = NO_ERROR;
110  }
111  else
112  {
113  //A communication error has occurred
114  }
115 
116  //Check status code
117  if(!error)
118  {
119  //A keep-alive value of zero has the effect of turning off the keep
120  //alive mechanism
121  if(context->keepAlive != 0)
122  {
123  //Make sure the MQTT-SN client is connected
124  if(context->state == MQTT_SN_CLIENT_STATE_ACTIVE ||
125  context->state == MQTT_SN_CLIENT_STATE_SENDING_REQ ||
126  context->state == MQTT_SN_CLIENT_STATE_RESP_RECEIVED)
127  {
128  //Check whether the keep-alive timer has expired
129  if(timeCompare(currentTime, context->keepAliveTimestamp +
130  context->keepAlive) >= 0)
131  {
132  //Check retransmission counter
133  if(context->keepAliveCounter < MQTT_SN_CLIENT_MAX_KEEP_ALIVE_PROBES)
134  {
135  //Send a PINGREQ message to the gateway
136  error = mqttSnClientSendPingReq(context);
137 
138  //Increment retransmission counter
139  context->keepAliveCounter++;
140  }
141  else
142  {
143  //If a client does not receive a PINGRESP from the gateway
144  //even after multiple retransmissions of the PINGREQ message,
145  //then the gateway is considered offline
146  context->state = MQTT_SN_CLIENT_STATE_DISCONNECTING;
147 
148  //The connection is lost
149  error = ERROR_NOT_CONNECTED;
150  }
151  }
152  }
153  }
154  }
155 
156  //Check whether the timeout has elapsed
157  } while(error == NO_ERROR && context->message.length == 0 && d > 0);
158 
159  //Return status code
160  return error;
161 }
162 
163 
164 /**
165  * @brief Deliver a PUBLISH message to the application
166  * @param[in] context Pointer to the MQTT-SN client context
167  * @param[in] flags Flags
168  * @param[in] topicId Topic identifier
169  * @param[in] data Message payload
170  * @param[in] dataLen Length of the message payload
171  * @return Error code
172  **/
173 
175  MqttSnFlags flags, uint16_t topicId, const uint8_t *data, size_t dataLen)
176 {
177  const char_t *topicName;
178  char_t shortTopicName[3];
180 
181  //Check the type of topic identifier
182  if(flags.topicIdType == MQTT_SN_NORMAL_TOPIC_ID)
183  {
184  //Retrieve the topic name associated with the normal topic ID
186  }
187  else if(flags.topicIdType == MQTT_SN_PREDEFINED_TOPIC_ID)
188  {
189  //Retrieve the topic name associated with the predefined topic ID
191  }
192  else if(flags.topicIdType == MQTT_SN_SHORT_TOPIC_NAME)
193  {
194  //Short topic names are topic names that have a fixed length of two
195  //octets. They are short enough for being carried together with the
196  //data within PUBLISH messages
197  shortTopicName[0] = MSB(topicId);
198  shortTopicName[1] = LSB(topicId);
199  shortTopicName[2] = '\0';
200 
201  //Point to the resulting topic name
202  topicName = shortTopicName;
203  }
204  else
205  {
206  //The value of the TopicIdType flag is not valid
207  topicName = NULL;
208  }
209 
210  //Check whether the topic name has been successfully resolved
211  if(topicName != NULL)
212  {
213  //Any registered callback?
214  if(context->publishCallback != NULL)
215  {
216  //Deliver the message to the application
217  context->publishCallback(context, topicName, data, dataLen,
218  (MqttSnQosLevel) flags.qos, flags.retain);
219  }
220 
221  //Successfull processing
223  }
224  else
225  {
226  //The client has received a PUBLISH message with an unknown topic ID
228  }
229 
230  //The return code indicates whether the PUBLISH message has been accepted
231  //or rejected
232  return returnCode;
233 }
234 
235 
236 /**
237  * @brief Add a new entry to the topic table
238  * @param[in] context Pointer to the MQTT-SN client context
239  * @param[in] topicName Topic name
240  * @param[in] topicId Topic identifier
241  * @return Error code
242  **/
243 
245  const char_t *topicName, uint16_t topicId)
246 {
247  uint_t i;
248 
249  //Make sure the name of the topic name is acceptable
251  return ERROR_INVALID_LENGTH;
252 
253  //Loop through the topic table
254  for(i = 0; i < MQTT_SN_CLIENT_TOPIC_TABLE_SIZE; i++)
255  {
256  //Check whether the topic name has already been registered
257  if(!osStrcmp(context->topicTable[i].topicName, topicName))
258  {
259  //Update topic identifier
260  context->topicTable[i].topicId = topicId;
261 
262  //We are done
263  return NO_ERROR;
264  }
265  }
266 
267  //Loop through the topic table
268  for(i = 0; i < MQTT_SN_CLIENT_TOPIC_TABLE_SIZE; i++)
269  {
270  //Check whether the current entry is free
271  if(context->topicTable[i].topicName[0] == '\0')
272  {
273  //Save mapping between topic name and topic ID
274  osStrcpy(context->topicTable[i].topicName, topicName);
275  context->topicTable[i].topicId = topicId;
276 
277  //A new entry has been successfully created
278  return NO_ERROR;
279  }
280  }
281 
282  //The table runs out of entries
283  return ERROR_OUT_OF_RESOURCES;
284 }
285 
286 
287 /**
288  * @brief Remove an entry in the topic table
289  * @param[in] context Pointer to the MQTT-SN client context
290  * @param[in] topicName Topic name
291  * @return Error code
292  **/
293 
295  const char_t *topicName)
296 {
297  uint_t i;
298 
299  //Loop through the list of topics that have been registered
300  for(i = 0; i < MQTT_SN_CLIENT_TOPIC_TABLE_SIZE; i++)
301  {
302  //Matching topic name?
303  if(!osStrcmp(context->topicTable[i].topicName, topicName))
304  {
305  //Release current entry
306  context->topicTable[i].topicName[0] = '\0';
307 
308  //We are done
309  return NO_ERROR;
310  }
311  }
312 
313  //The specified topic name does not exist
314  return ERROR_NOT_FOUND;
315 }
316 
317 
318 /**
319  * @brief Retrieve the topic name associated with a given topic ID
320  * @param[in] context Pointer to the MQTT-SN client context
321  * @param[in] topicId Topic identifier
322  * @return Topic name
323  **/
324 
326  uint16_t topicId)
327 {
328  uint_t i;
329  const char_t *topicName;
330 
331  //Initialize topic name
332  topicName = NULL;
333 
334  //Valid topic identifier?
336  {
337  //Loop through the list of topics that have been registered
338  for(i = 0; i < MQTT_SN_CLIENT_TOPIC_TABLE_SIZE; i++)
339  {
340  //Matching topic identifier?
341  if(context->topicTable[i].topicId == topicId)
342  {
343  //Retrieve the corresponding topic name
344  topicName = context->topicTable[i].topicName;
345  break;
346  }
347  }
348  }
349 
350  //Return the corresponding topic name
351  return topicName;
352 }
353 
354 
355 /**
356  * @brief Retrieve the topic ID associated with a given topic name
357  * @param[in] context Pointer to the MQTT-SN client context
358  * @param[in] topicName Topic name
359  * @return Topic identifier
360  **/
361 
363  const char_t *topicName)
364 {
365  uint_t i;
366  uint16_t topicId;
367 
368  //Initialize topic identifier
370 
371  //Valid topic name?
372  if(topicName != NULL)
373  {
374  //Loop through the list of registered topics that have been registered
375  for(i = 0; i < MQTT_SN_CLIENT_TOPIC_TABLE_SIZE; i++)
376  {
377  //Matching topic name?
378  if(!osStrcmp(context->topicTable[i].topicName, topicName))
379  {
380  //Retrieve the corresponding topic identifier
381  topicId = context->topicTable[i].topicId;
382  break;
383  }
384  }
385  }
386 
387  //Return the corresponding topic identifier
388  return topicId;
389 }
390 
391 
392 /**
393  * @brief Retrieve the topic name associated with a predefined topic ID
394  * @param[in] context Pointer to the MQTT-SN client context
395  * @param[in] topicId Predefined topic identifier
396  * @return Topic name
397  **/
398 
400  uint16_t topicId)
401 {
402  uint_t i;
403  const char_t *topicName;
404 
405  //Initialize topic name
406  topicName = NULL;
407 
408  //Valid topic identifier?
410  {
411  //Loop through the list of predefined topics
412  for(i = 0; i < context->predefinedTopicTableSize; i++)
413  {
414  //Matching topic identifier?
415  if(context->predefinedTopicTable[i].topicId == topicId)
416  {
417  //Retrieve the corresponding topic name
418  topicName = context->predefinedTopicTable[i].topicName;
419  break;
420  }
421  }
422  }
423 
424  //Return the corresponding topic name
425  return topicName;
426 }
427 
428 
429 /**
430  * @brief Retrieve the topic ID associated with a predefined topic name
431  * @param[in] context Pointer to the MQTT-SN client context
432  * @param[in] topicName Predefined topic name
433  * @return Topic identifier
434  **/
435 
437  const char_t *topicName)
438 {
439  uint_t i;
440  uint16_t topicId;
441 
442  //Initialize topic identifier
444 
445  //Valid topic name?
446  if(topicName != NULL)
447  {
448  //Loop through the list of predefined topics
449  for(i = 0; i < context->predefinedTopicTableSize; i++)
450  {
451  //Matching topic name?
452  if(!osStrcmp(context->predefinedTopicTable[i].topicName, topicName))
453  {
454  //Retrieve the corresponding topic identifier
455  topicId = context->predefinedTopicTable[i].topicId;
456  break;
457  }
458  }
459  }
460 
461  //Return the corresponding topic identifier
462  return topicId;
463 }
464 
465 
466 /**
467  * @brief Generate a new message identifier
468  * @param[in] context Pointer to the MQTT-SN client context
469  * @return 16-bit message identifier
470  **/
471 
473 {
474  //Increment message identifier and wrap around if necessary
475  if(context->msgId < UINT16_MAX)
476  {
477  context->msgId++;
478  }
479  else
480  {
481  context->msgId = 1;
482  }
483 
484  //Return current value
485  return context->msgId;
486 }
487 
488 
489 /**
490  * @brief Store message ID (QoS 2 message processing)
491  * @param[in] context Pointer to the MQTT-SN client context
492  * @param[in] msgId Message identifier
493  * @return Error code
494  **/
495 
497  uint16_t msgId)
498 {
499  uint_t i;
500 
501  //Loop through the list of message identifiers
502  for(i = 0; i < MQTT_SN_CLIENT_MSG_ID_TABLE_SIZE; i++)
503  {
504  //Check whether the message ID has already been accepted by the client
505  if(context->msgIdTable[i].ownership &&
506  context->msgIdTable[i].msgId == msgId)
507  {
508  //We are done
509  return NO_ERROR;
510  }
511  }
512 
513  //Loop through the list of message identifiers
514  for(i = 0; i < MQTT_SN_CLIENT_MSG_ID_TABLE_SIZE; i++)
515  {
516  //Check whether the current entry is free
517  if(!context->msgIdTable[i].ownership)
518  {
519  //Create a new entry
520  context->msgIdTable[i].msgId = msgId;
521  context->msgIdTable[i].ownership = TRUE;
522 
523  //We are done
524  return NO_ERROR;
525  }
526  }
527 
528  //The table runs out of entries
529  return ERROR_OUT_OF_RESOURCES;
530 }
531 
532 
533 /**
534  * @brief Discard message ID (QoS 2 message processing)
535  * @param[in] context Pointer to the MQTT-SN client context
536  * @param[in] msgId Message identifier
537  * @return Error code
538  **/
539 
541  uint16_t msgId)
542 {
543  uint_t i;
544 
545  //Loop through the list of message identifiers
546  for(i = 0; i < MQTT_SN_CLIENT_MSG_ID_TABLE_SIZE; i++)
547  {
548  //Matching message identifier?
549  if(context->msgIdTable[i].ownership &&
550  context->msgIdTable[i].msgId == msgId)
551  {
552  //Release current entry
553  context->msgIdTable[i].msgId = 0;
554  context->msgIdTable[i].ownership = FALSE;
555 
556  //We are done
557  return NO_ERROR;
558  }
559  }
560 
561  //The specified message identifier does not exist
562  return ERROR_NOT_FOUND;
563 }
564 
565 
566 /**
567  * @brief Check whether the message ID is a duplicate (QoS 2 message processing)
568  * @param[in] context Pointer to the MQTT-SN client context
569  * @param[in] msgId Message identifier
570  * @return TRUE if the message ID is a duplicate, else FALSE
571  **/
572 
574  uint16_t msgId)
575 {
576  uint_t i;
577  bool_t duplicate;
578 
579  //Initialize flag
580  duplicate = FALSE;
581 
582  //Loop through the list of message identifiers
583  for(i = 0; i < MQTT_SN_CLIENT_MSG_ID_TABLE_SIZE; i++)
584  {
585  //Check whether the message ID has already been accepted by the client
586  if(context->msgIdTable[i].ownership &&
587  context->msgIdTable[i].msgId == msgId)
588  {
589  //The message ID is a duplicate
590  duplicate = TRUE;
591  break;
592  }
593  }
594 
595  //Return TRUE if the message ID is a duplicate
596  return duplicate;
597 }
598 
599 
600 /**
601  * @brief Check whether a topic name is a short topic name
602  * @param[in] topicName Topic name
603  * @return TRUE if the specified topic name is a short topic name, else FALSE
604  **/
605 
607 {
608  bool_t res;
609 
610  //Initialize variable
611  res = FALSE;
612 
613  //A short topic name is a topic name that has a fixed length of two octets
614  if(osStrlen(topicName) == 2)
615  {
616  //Ensure the topic name does not contains wildcard characters
617  if(osStrchr(topicName, '#') == NULL && osStrchr(topicName, '+') == NULL)
618  {
619  //The short topic name is a valid
620  res = TRUE;
621  }
622  }
623 
624  //Return TRUE if the specified topic name is a short topic name
625  return res;
626 }
627 
628 #endif
unsigned int uint_t
Definition: compiler_port.h:50
char char_t
Definition: compiler_port.h:48
int bool_t
Definition: compiler_port.h:53
Debugging facilities.
uint16_t port
Definition: dns_common.h:267
error_t
Error codes.
Definition: error.h:43
@ ERROR_WOULD_BLOCK
Definition: error.h:96
@ ERROR_NOT_FOUND
Definition: error.h:147
@ ERROR_TIMEOUT
Definition: error.h:95
@ ERROR_OUT_OF_RESOURCES
Definition: error.h:64
@ ERROR_NOT_CONNECTED
Definition: error.h:80
@ NO_ERROR
Success.
Definition: error.h:44
@ ERROR_INVALID_LENGTH
Definition: error.h:111
uint8_t data[]
Definition: ethernet.h:222
Ipv4Addr ipAddr
Definition: ipcp.h:105
MQTT-SN client.
#define MQTT_SN_CLIENT_MAX_KEEP_ALIVE_PROBES
#define MQTT_SN_CLIENT_TOPIC_TABLE_SIZE
#define MQTT_SN_CLIENT_MSG_ID_TABLE_SIZE
#define MQTT_SN_CLIENT_MAX_TOPIC_NAME_LEN
#define MQTT_SN_CLIENT_TICK_INTERVAL
@ MQTT_SN_CLIENT_STATE_ACTIVE
@ MQTT_SN_CLIENT_STATE_RESP_RECEIVED
@ MQTT_SN_CLIENT_STATE_DISCONNECTING
@ MQTT_SN_CLIENT_STATE_SENDING_REQ
#define MqttSnClientContext
error_t mqttSnClientSendPingReq(MqttSnClientContext *context)
Send PINGREQ message.
error_t mqttSnClientProcessMessage(MqttSnClientContext *context, MqttSnMessage *message, const IpAddr *ipAddr, uint16_t port)
Process incoming MQTT-SN message.
MQTT-SN message formatting and parsing.
bool_t mqttSnClientIsDuplicateMessageId(MqttSnClientContext *context, uint16_t msgId)
Check whether the message ID is a duplicate (QoS 2 message processing)
error_t mqttSnClientProcessEvents(MqttSnClientContext *context, systime_t timeout)
Process MQTT-SN client events.
uint16_t mqttSnClientFindTopicName(MqttSnClientContext *context, const char_t *topicName)
Retrieve the topic ID associated with a given topic name.
bool_t mqttSnClientIsShortTopicName(const char_t *topicName)
Check whether a topic name is a short topic name.
uint16_t mqttSnClientFindPredefTopicName(MqttSnClientContext *context, const char_t *topicName)
Retrieve the topic ID associated with a predefined topic name.
error_t mqttSnClientStoreMessageId(MqttSnClientContext *context, uint16_t msgId)
Store message ID (QoS 2 message processing)
error_t mqttSnClientAddTopic(MqttSnClientContext *context, const char_t *topicName, uint16_t topicId)
Add a new entry to the topic table.
MqttSnReturnCode mqttSnDeliverPublishMessage(MqttSnClientContext *context, MqttSnFlags flags, uint16_t topicId, const uint8_t *data, size_t dataLen)
Deliver a PUBLISH message to the application.
uint16_t mqttSnClientGenerateMessageId(MqttSnClientContext *context)
Generate a new message identifier.
const char_t * mqttSnClientFindPredefTopicId(MqttSnClientContext *context, uint16_t topicId)
Retrieve the topic name associated with a predefined topic ID.
const char_t * mqttSnClientFindTopicId(MqttSnClientContext *context, uint16_t topicId)
Retrieve the topic name associated with a given topic ID.
error_t mqttSnClientDeleteTopic(MqttSnClientContext *context, const char_t *topicName)
Remove an entry in the topic table.
error_t mqttSnClientDiscardMessageId(MqttSnClientContext *context, uint16_t msgId)
Discard message ID (QoS 2 message processing)
Helper functions for MQTT-SN client.
error_t mqttSnClientReceiveDatagram(MqttSnClientContext *context, IpAddr *srcIpAddr, uint16_t *srcPort, void *data, size_t size, size_t *received, systime_t timeout)
Receive a datagram.
Transport protocol abstraction layer.
#define MQTT_SN_INVALID_TOPIC_ID
MqttSnFlags
uint16_t msgId
MqttSnQosLevel
Quality of service level.
@ MQTT_SN_NORMAL_TOPIC_ID
Normal topic ID.
@ MQTT_SN_SHORT_TOPIC_NAME
Short topic name.
@ MQTT_SN_PREDEFINED_TOPIC_ID
Predefined topic ID.
char_t topicName[]
uint16_t topicId
uint8_t returnCode
MqttSnReturnCode
MQTT-SN return codes.
@ MQTT_SN_RETURN_CODE_ACCEPTED
@ MQTT_SN_RETURN_CODE_REJECTED_INVALID_TOPIC_ID
#define MQTT_SN_MAX_MSG_SIZE
TCP/IP stack core.
#define osStrcmp(s1, s2)
Definition: os_port.h:171
#define timeCompare(t1, t2)
Definition: os_port.h:40
#define LSB(x)
Definition: os_port.h:55
#define MIN(a, b)
Definition: os_port.h:63
#define osStrchr(s, c)
Definition: os_port.h:195
#define osStrlen(s)
Definition: os_port.h:165
#define TRUE
Definition: os_port.h:50
#define FALSE
Definition: os_port.h:46
#define osStrcpy(s1, s2)
Definition: os_port.h:207
#define MSB(x)
Definition: os_port.h:59
systime_t osGetSystemTime(void)
Retrieve system time.
uint32_t systime_t
System time.
const uint8_t res[]
uint32_t dataLen
Definition: sftp_common.h:229
IP network address.
Definition: ip.h:79
uint8_t flags
Definition: tcp.h:351