From d6b4a750258b34c79e3c643595a0ae1cb0e18bed Mon Sep 17 00:00:00 2001
From: guowenxue <guowenxue@gmail.com>
Date: Sat, 26 Aug 2023 15:59:55 +0800
Subject: [PATCH] Add modules and MQTT project
---
project/mosquitto/etc/mqttd.conf | 69
modules/makefile | 34
project/coreMQTT/coreMQTT/core_mqtt_state.h | 310
project/coreMQTT/coreMQTT/core_mqtt_config_defaults.h | 204
project/openlibs/openssl/build.sh | 182
project/modules/pwm.h | 44
project/coreMQTT/etc/mqttd.conf | 69
project/coreMQTT/coreMQTT/core_mqtt_state.c | 1214 ++
project/gpsd/gpsd.c | 245
project/modules/leds.h | 58
project/booster/atcmd.h | 93
project/modules/modules.h | 24
project/openlibs/libgpiod/build.sh | 185
project/booster/atcmd.c | 300
project/coreMQTT/coreMQTT/transport_interface.h | 316
project/booster/iniparser.h | 359
project/booster/list.h | 723 +
project/coreMQTT/conf.c | 200
project/coreMQTT/coreMQTT/core_mqtt.c | 3314 +++++++
project/modules/sht20.c | 244
project/booster/util_proc.c | 432
project/modules/makefile | 38
project/coreMQTT/coreJSON/core_json.h | 339
modules/infrared.c | 274
project/coreMQTT/coreMQTT/core_mqtt.h | 1015 ++
project/booster/iniparser.c | 837 +
project/booster/util_proc.h | 89
project/mosquitto/.gitignore | 9
project/modules/sht20.h | 34
project/coreMQTT/coreSNTP/core_sntp_client.c | 959 ++
project/modules/relay.c | 156
project/booster/makefile | 35
project/coreMQTT/coreJSON/core_json.c | 1818 +++
modules/sht20.c | 331
project/coreMQTT/coreSNTP/core_sntp_client.h | 655 +
project/modules/relay.h | 56
project/modules/ds18b20.c | 119
project/modules/tsl2561.c | 209
project/booster/at-esp32.h | 140
project/coreMQTT/conf.h | 71
project/booster/at-esp32.c | 388
project/modules/tsl2561.h | 35
project/modules/ds18b20.h | 31
modules/relay.c | 286
project/mosquitto/conf.c | 200
.gitignore | 11
project/booster/comport.h | 74
project/openlibs/libevent/build.sh | 182
project/booster/comport.c | 520 +
project/coreMQTT/coreSNTP/README.md | 28
project/booster/README.md | 4
project/booster/dictionary.h | 174
project/coreMQTT/makefile | 69
project/mosquitto/conf.h | 71
project/booster/dictionary.c | 381
project/coreMQTT/coreSNTP/core_sntp_config_defaults.h | 144
project/coreMQTT/coreMQTT/core_mqtt_serializer.h | 1306 ++
project/booster/logger.h | 68
project/coreMQTT/main.c | 458
project/mosquitto/main.c | 458
project/coreMQTT/coreMQTT/core_mqtt_serializer.c | 2684 +++++
project/booster/logger.c | 279
project/booster/ringbuf.c | 107
project/coreMQTT/coreMQTT/makefile | 35
project/coreMQTT/coreSNTP/core_sntp_serializer.h | 535 +
project/booster/esp32.h | 35
project/mosquitto/makefile | 68
project/booster/ringbuf.h | 57
project/gpsd/makefile | 62
modules/pwm.c | 311
project/coreMQTT/coreMQTT/README.md | 35
project/coreMQTT/coreSNTP/core_sntp_serializer.c | 853 +
project/coreMQTT/coreSNTP/makefile | 35
project/openlibs/cjson/build.sh | 182
project/booster/esp32.c | 166
project/openlibs/mosquitto/build.sh | 188
modules/leds.c | 260
modules/tsl2561.c | 337
project/modules/leds.c | 162
modules/ds18b20.c | 134
project/openlibs/makefile | 10
project/coreMQTT/coreJSON/README.md | 65
project/coreMQTT/coreMQTT/core_mqtt_default_logging.h | 132
project/modules/pwm.c | 213
project/coreMQTT/coreJSON/makefile | 35
85 files changed, 27,671 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..18623c2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# git ignore files/folders in the list
+
+# ignore folders
+install/
+
+# ignore files
+*.so*
+*.o
+*.a
+cscope.*
+tags
diff --git a/modules/ds18b20.c b/modules/ds18b20.c
new file mode 100644
index 0000000..cb895a4
--- /dev/null
+++ b/modules/ds18b20.c
@@ -0,0 +1,134 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: ds18b20.c
+ * Description: This file is temperature sensor DS18B20 code
+ *
+ * Version: 1.0.0(2023/8/10)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "2023/8/10 12:13:26"
+ *
+ * Pin connection:
+ *
+ * DS18B20 Module Raspberry Pi Board
+ * VCC <-----> #Pin1(3.3V)
+ * DQ <-----> #Pin7(BCM GPIO4)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ *
+ * dtoverlay=w1-gpio-pullup,gpiopin=4
+ *
+ ********************************************************************************/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+int ds18b20_get_temperature(float *temp);
+
+int main(int argc, char *argv[])
+{
+ float temp;
+
+ if( ds18b20_get_temperature(&temp) < 0 )
+ {
+ printf("ERROR: ds18b20 get temprature failure\n");
+ return 1;
+ }
+
+ printf("DS18B20 get temperature: %f ℃\n", temp);
+ return 0;
+}
+
+
+/* File Content:
+ pi@raspberrypi:~/guowenxue $ cat /sys/bus/w1/devices/28-041731f7c0ff/w1_slave
+ 3a 01 4b 46 7f ff 0c 10 a5 : crc=a5 YES
+ 3a 01 4b 46 7f ff 0c 10 a5 t=19625
+ */
+
+int ds18b20_get_temperature(float *temp)
+{
+ char w1_path[50] = "/sys/bus/w1/devices/";
+ char chip[20];
+ char buf[128];
+ DIR *dirp;
+ struct dirent *direntp;
+ int fd =-1;
+ char *ptr;
+ float value;
+ int found = 0;
+
+ if( !temp )
+ {
+ return -1;
+ }
+
+ /*+-------------------------------------------------------------------+
+ *| open dierectory /sys/bus/w1/devices to get chipset Serial Number |
+ *+-------------------------------------------------------------------+*/
+ if((dirp = opendir(w1_path)) == NULL)
+ {
+ printf("opendir error: %s\n", strerror(errno));
+ return -2;
+ }
+
+ while((direntp = readdir(dirp)) != NULL)
+ {
+ if(strstr(direntp->d_name,"28-"))
+ {
+ /* find and get the chipset SN filename */
+ strcpy(chip,direntp->d_name);
+ found = 1;
+ break;
+ }
+ }
+ closedir(dirp);
+
+ if( !found )
+ {
+ printf("Can not find ds18b20 in %s\n", w1_path);
+ return -3;
+ }
+
+ /* get DS18B20 sample file full path: /sys/bus/w1/devices/28-xxxx/w1_slave */
+ strncat(w1_path, chip, sizeof(w1_path)-strlen(w1_path));
+ strncat(w1_path, "/w1_slave", sizeof(w1_path)-strlen(w1_path));
+
+ /* open file /sys/bus/w1/devices/28-xxxx/w1_slave to get temperature */
+ if( (fd=open(w1_path, O_RDONLY)) < 0 )
+ {
+ printf("open %s error: %s\n", w1_path, strerror(errno));
+ return -4;
+ }
+
+ if(read(fd, buf, sizeof(buf)) < 0)
+ {
+ printf("read %s error: %s\n", w1_path, strerror(errno));
+ return -5;
+ }
+
+ ptr = strstr(buf, "t=");
+ if( !ptr )
+ {
+ printf("ERROR: Can not get temperature\n");
+ return -6;
+ }
+
+ ptr+=2;
+
+ /* convert string value to float value */
+ *temp = atof(ptr)/1000;
+
+ close(fd);
+
+ return 0;
+}
diff --git a/modules/infrared.c b/modules/infrared.c
new file mode 100644
index 0000000..dd39d97
--- /dev/null
+++ b/modules/infrared.c
@@ -0,0 +1,274 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: led.c
+ * Description: This file is HC-SR501 infrared sensor code
+ *
+ *
+ * Pin connection:
+ * HC-SR501 Module Raspberry Pi Board
+ * VCC <-----> 5V
+ * I/O <-----> #Pin18(BCM GPIO24)
+ * GND <-----> GND
+ *
+ * System install:
+ * sudo apt install -y libgpiod-dev gpiod
+ *
+ *
+ ********************************************************************************/
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <getopt.h>
+#include <libgen.h>
+
+#include <gpiod.h>
+
+/* infrared code */
+enum
+{
+ IR1 = 0,
+ IR_CNT,
+};
+
+/* infrared hardware information */
+typedef struct ir_info_s
+{
+ const char *name; /* infrared name */
+ int gpio; /* infrared BCM pin number */
+ int active;/* infrared active GPIO level: 0->low 1->high */
+ struct gpiod_line *line; /* libgpiod line */
+} ir_info_t;
+
+static ir_info_t ir_info[IR_CNT] =
+{
+ {"IR1", 23, 1, NULL },
+};
+
+/* infrared API context */
+typedef struct ir_ctx_s
+{
+ struct gpiod_chip *chip;
+ ir_info_t *ir;
+ int count;
+} ir_ctx_t;
+
+int init_infrared(ir_ctx_t *ctx);
+int term_infrared(ir_ctx_t *ctx);
+int detect_infrared(ir_ctx_t *ctx, int which);
+static inline void msleep(unsigned long ms);
+
+static inline void banner(const char *progname)
+{
+ printf("%s program Version v1.0.0\n", progname);
+ printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
+}
+
+static void program_usage(const char *progname)
+{
+
+ printf("Usage: %s [OPTION]...\n", progname);
+ printf(" This is infrared detect program. \n");
+
+ printf(" -d[device ] Specify infrared device, such as 0\n");
+ printf(" -h[help ] Display this help information\n");
+ printf(" -v[version ] Display the program version\n");
+
+ printf("\n");
+ banner(progname);
+ return;
+}
+
+int g_stop = 0;
+void sig_handler(int signum)
+{
+ switch( signum )
+ {
+ case SIGINT:
+ case SIGTERM:
+ g_stop = 1;
+
+ default:
+ break;
+ }
+
+ return ;
+}
+
+int main(int argc, char **argv)
+{
+ int i, rv;
+ char *progname=NULL;
+
+ ir_ctx_t ir_ctx =
+ {
+ .chip = NULL,
+ .ir = ir_info,
+ .count = IR_CNT,
+ };
+
+ struct option long_options[] = {
+ {"version", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ progname = basename(argv[0]);
+
+ /* Parser the command line parameters */
+ while ((rv = getopt_long(argc, argv, "vh", long_options, NULL)) != -1)
+ {
+ switch (rv)
+ {
+ case 'v': /* Get software version */
+ banner(progname);
+ return EXIT_SUCCESS;
+
+ case 'h': /* Get help information */
+ program_usage(progname);
+ return 0;
+
+ default:
+ break;
+ }
+ }
+
+ if( (rv=init_infrared(&ir_ctx)) < 0 )
+ {
+ printf("initial infrared gpio failure, rv=%d\n", rv);
+ return 1;
+ }
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+
+ while( !g_stop )
+ {
+ for( i=0; i<ir_ctx.count; i++ )
+ {
+ rv = detect_infrared(&ir_ctx, i);
+ printf("Infrared[%d] monitor: %s\n", i, rv ? "Someone is closing!":"No one nearby!");
+ }
+ sleep(1);
+ }
+
+ term_infrared(&ir_ctx);
+ return 0;
+}
+
+int term_infrared(ir_ctx_t *ctx)
+{
+ int i;
+ ir_info_t *ir;
+
+ if( !ctx )
+ {
+ printf("Invalid input arguments\n");
+ return -1;
+ }
+
+ if( !ctx->chip )
+ return 0;
+
+ for(i=0; i<ctx->count; i++)
+ {
+ ir = &ctx->ir[i];
+
+ if( ir->line )
+ gpiod_line_release(ir->line);
+ }
+
+ gpiod_chip_close(ctx->chip);
+ return 0;
+}
+
+int init_infrared(ir_ctx_t *ctx)
+{
+ int i, rv;
+ ir_info_t *ir;
+
+ if( !ctx )
+ {
+ printf("Invalid input arguments\n");
+ return -1;
+ }
+
+ ctx->chip = gpiod_chip_open_by_name("gpiochip0");
+ if( !ctx->chip )
+ {
+ printf("open gpiochip failure, maybe you need running as root\n");
+ return -2;
+ }
+
+
+ for(i=0; i<ctx->count; i++)
+ {
+ ir = &ctx->ir[i];
+
+ ir ->line = gpiod_chip_get_line(ctx->chip, ir->gpio);
+ if( !ir->line )
+ {
+ printf("open gpioline for %s[%d] failed\n", ir->name, ir->gpio);
+ rv = -3;
+ goto failed;
+ }
+
+ rv = gpiod_line_request_input(ir->line, ir->name);
+ if( rv )
+ {
+ printf("request gpio input for %s[%d] failed\n", ir->name, ir->gpio);
+ rv = -4;
+ goto failed;
+ }
+ }
+
+ return 0;
+
+failed:
+ term_infrared(ctx);
+ return rv;
+}
+
+int detect_infrared(ir_ctx_t *ctx, int which)
+{
+ int rv = 0;
+ ir_info_t *ir;
+
+ if( !ctx || which<0 || which>=ctx->count )
+ {
+ printf("Invalid input arguments\n");
+ return 0;
+ }
+
+ ir = &ctx->ir[which];
+
+ return gpiod_line_get_value(ir->line)==ir->active ? 1 : 0;
+}
+
+
+static inline void msleep(unsigned long ms)
+{
+ struct timespec cSleep;
+ unsigned long ulTmp;
+
+ cSleep.tv_sec = ms / 1000;
+ if (cSleep.tv_sec == 0)
+ {
+ ulTmp = ms * 10000;
+ cSleep.tv_nsec = ulTmp * 100;
+ }
+ else
+ {
+ cSleep.tv_nsec = 0;
+ }
+
+ nanosleep(&cSleep, 0);
+
+ return ;
+}
diff --git a/modules/leds.c b/modules/leds.c
new file mode 100644
index 0000000..7ea158b
--- /dev/null
+++ b/modules/leds.c
@@ -0,0 +1,260 @@
+/*********************************************************************************
+ * Copyright: (C) 2021 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: led.c
+ * Description: This file is used to control RGB 3-colors LED
+ *
+ *
+ * Pin connection:
+ * RGB Led Module Raspberry Pi Board
+ * R <-----> #Pin33(BCM GPIO13)
+ * G <-----> #Pin35(BCM GPIO19)
+ * B <-----> #Pin37(BCM GPIO26)
+ * GND <-----> GND
+ *
+ * System install:
+ * sudo apt install -y libgpiod-dev gpiod
+ *
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <gpiod.h>
+
+#define DELAY 500
+
+#define ON 1
+#define OFF 0
+
+/* Three LEDs code */
+enum
+{
+ LED_R = 0,
+ LED_G,
+ LED_B,
+ LEDCNT,
+};
+
+/* Three LEDs hardware information */
+typedef struct led_info_s
+{
+ const char *name; /* RGB 3-color LED name */
+ int gpio; /* RGB 3-color LED BCM pin number */
+ int active;/* RGB 3-color LED active GPIO level: 0->low 1->high */
+ struct gpiod_line *line; /* libgpiod line */
+} led_info_t;
+
+static led_info_t leds_info[LEDCNT] =
+{
+ {"red", 13, 1, NULL },
+ {"green", 19, 1, NULL },
+ {"blue", 26, 1, NULL },
+};
+
+/* Three LEDs API context */
+typedef struct led_ctx_s
+{
+ struct gpiod_chip *chip;
+ led_info_t *leds;
+ int count;
+} led_ctx_t;
+
+int init_led(led_ctx_t *ctx);
+int term_led(led_ctx_t *ctx);
+int turn_led(led_ctx_t *ctx, int which, int cmd);
+static inline void msleep(unsigned long ms);
+
+
+int g_stop = 0;
+
+void sig_handler(int signum)
+{
+ switch( signum )
+ {
+ case SIGINT:
+ case SIGTERM:
+ g_stop = 1;
+
+ default:
+ break;
+ }
+
+ return ;
+}
+
+int main(int argc, char *argv[])
+{
+ int rv;
+ led_ctx_t led_ctx =
+ {
+ .chip = NULL,
+ .leds = leds_info,
+ .count = LEDCNT,
+ };
+
+ if( (rv=init_led(&led_ctx)) < 0 )
+ {
+ printf("initial leds gpio failure, rv=%d\n", rv);
+ return 1;
+ }
+ printf("initial RGB Led gpios okay\n", rv);
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+
+ while( !g_stop )
+ {
+ turn_led(&led_ctx, LED_R, ON);
+ msleep(DELAY);
+ turn_led(&led_ctx, LED_R, OFF);
+ msleep(DELAY);
+
+ turn_led(&led_ctx, LED_G, ON);
+ msleep(DELAY);
+ turn_led(&led_ctx, LED_G, OFF);
+ msleep(DELAY);
+
+ turn_led(&led_ctx, LED_B, ON);
+ msleep(DELAY);
+ turn_led(&led_ctx, LED_B, OFF);
+ msleep(DELAY);
+ }
+
+ term_led(&led_ctx);
+ return 0;
+}
+
+int term_led(led_ctx_t *ctx)
+{
+ int i;
+ led_info_t *led;
+
+ printf("terminate RGB Led gpios\n");
+
+ if( !ctx )
+ {
+ printf("Invalid input arguments\n");
+ return -1;
+ }
+
+ if( !ctx->chip )
+ return 0;
+
+ for(i=0; i<ctx->count; i++)
+ {
+ led = &ctx->leds[i];
+
+ if( led->line )
+ gpiod_line_release(led->line);
+ }
+
+ gpiod_chip_close(ctx->chip);
+ return 0;
+}
+
+
+int init_led(led_ctx_t *ctx)
+{
+ int i, rv;
+ led_info_t *led;
+
+ if( !ctx )
+ {
+ printf("Invalid input arguments\n");
+ return -1;
+ }
+
+ ctx->chip = gpiod_chip_open_by_name("gpiochip0");
+ if( !ctx->chip )
+ {
+ printf("open gpiochip failure, maybe you need running as root\n");
+ return -2;
+ }
+
+
+ for(i=0; i<ctx->count; i++)
+ {
+ led = &ctx->leds[i];
+
+ led->line = gpiod_chip_get_line(ctx->chip, led->gpio);
+ if( !led->line )
+ {
+ printf("open gpioline for %s[%d] failed\n", led->name, led->gpio);
+ rv = -3;
+ goto failed;
+ }
+
+ rv = gpiod_line_request_output(led->line, led->name, !led->active);
+ if( rv )
+ {
+ printf("request gpio output for %5s[%d] failed\n", led->name, led->gpio);
+ rv = -4;
+ goto failed;
+ }
+
+ //printf("request %5s led[%d] for gpio output okay\n", led->name, led->gpio);
+ }
+
+ return 0;
+
+failed:
+ term_led(ctx);
+ return rv;
+}
+
+int turn_led(led_ctx_t *ctx, int which, int cmd)
+{
+ int rv = 0;
+ led_info_t *led;
+
+ if( !ctx || which<0 || which>=ctx->count )
+ {
+ printf("Invalid input arguments\n");
+ return -1;
+ }
+
+ led = &ctx->leds[which];
+
+ if( OFF == cmd )
+ {
+ gpiod_line_set_value(led->line, !led->active);
+ }
+ else
+ {
+ gpiod_line_set_value(led->line, led->active);
+ }
+
+ return 0;
+}
+
+static inline void msleep(unsigned long ms)
+{
+ struct timespec cSleep;
+ unsigned long ulTmp;
+
+ cSleep.tv_sec = ms / 1000;
+ if (cSleep.tv_sec == 0)
+ {
+ ulTmp = ms * 10000;
+ cSleep.tv_nsec = ulTmp * 100;
+ }
+ else
+ {
+ cSleep.tv_nsec = 0;
+ }
+
+ nanosleep(&cSleep, 0);
+
+ return ;
+}
+
diff --git a/modules/makefile b/modules/makefile
new file mode 100644
index 0000000..dad362d
--- /dev/null
+++ b/modules/makefile
@@ -0,0 +1,34 @@
+#********************************************************************************
+# Copyright: (C) 2023 LingYun IoT System Studio
+# All rights reserved.
+#
+# Filename: Makefile
+# Description: This file used to compile all the C file to respective binary,
+# and it will auto detect cross compile or local compile.
+#
+# Version: 1.0.0(11/08/23)
+# Author: Guo Wenxue <guowenxue@gmail.com>
+# ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+#
+#*******************************************************************************
+
+
+BUILD_ARCH=$(shell uname -m)
+ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
+ CROSS_COMPILE?=arm-linux-gnueabihf-
+endif
+
+CC=${CROSS_COMPILE}gcc
+
+LDFLAGS+=-lm -lgpiod
+
+SRCFILES = $(wildcard *.c)
+BINARIES=$(SRCFILES:%.c=%)
+
+all: ${BINARIES}
+
+%: %.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+clean:
+ rm -f ${BINARIES}
diff --git a/modules/pwm.c b/modules/pwm.c
new file mode 100644
index 0000000..ac1412f
--- /dev/null
+++ b/modules/pwm.c
@@ -0,0 +1,311 @@
+/*********************************************************************************
+ * Copyright: (C) 2021 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: pwm.c
+ * Description: This file is used to control PWM buzzer/Led
+ *
+ * Pin connection:
+ * PWM Module Raspberry Pi Board
+ * VCC <-----> 5V
+ * buzzer <-----> #Pin32(BCM GPIO12)
+ * Led <-----> #Pin33(BCM GPIO13)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ *
+ * dtoverlay=pwm,pin=12,func=4 (Buzzer)
+ * dtoverlay=pwm,pin=13,func=4 (Led)
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#include <getopt.h>
+#include <libgen.h>
+
+#include <gpiod.h>
+
+#define PWMCHIP_PATH "/sys/class/pwm/pwmchip0"
+
+#define ENABLE 1
+#define DISABLE 0
+
+int init_pwm(int channel, int freq, int duty);
+int turn_pwm(int channel, int status);
+int term_pwm(int channel);
+static inline void msleep(unsigned long ms);
+
+static inline void banner(const char *progname)
+{
+ printf("%s program Version v1.0.0\n", progname);
+ printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
+}
+
+static void program_usage(const char *progname)
+{
+
+ printf("Usage: %s [OPTION]...\n", progname);
+ printf(" This is pwm control program. \n");
+
+ printf(" -c[channel ] Specify PWM channel, such as 0\n");
+ printf(" -f[freq ] Specify PWM frequency, default 2500(Hz)\n");
+ printf(" -d[duty ] Specify PWM duty, default 50(50%)\n");
+ printf(" -s[status ] Specify PWM status: 1->on(default), 0->off\n");
+ printf(" -h[help ] Display this help information\n");
+ printf(" -v[version ] Display the program version\n");
+ printf("\n");
+
+ printf("Example buzzer : %s -c 0 -f 2750 -d 50 -s 1\n", progname);
+ printf("Example Led : %s -c 1 -f 100 -d 50 -s 1\n", progname);
+ printf("Example disable: %s -c 0 -s 0\n", progname);
+
+ printf("\n");
+ banner(progname);
+ return;
+}
+
+int main(int argc, char **argv)
+{
+ int rv;
+ char *progname=NULL;
+ int channel = -1;
+ int freq = 2500;
+ int duty = 50;
+ int status = ENABLE;
+
+ struct option long_options[] = {
+ {"channel", required_argument, NULL, 'c'},
+ {"freq", required_argument, NULL, 'f'},
+ {"duty", required_argument, NULL, 'd'},
+ {"status", required_argument, NULL, 's'},
+ {"version", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ progname = basename(argv[0]);
+
+ /* Parser the command line parameters */
+ while ((rv = getopt_long(argc, argv, "c:f:d:s:vh", long_options, NULL)) != -1)
+ {
+ switch (rv)
+ {
+ case 'c': /* Set pwm channel, such as 0,1 */
+ channel = atoi(optarg);
+ break;
+
+ case 'f': /* Set pwm frequency */
+ freq = atoi(optarg);
+ break;
+
+ case 'd': /* Set pwm duty cycle */
+ duty = atoi(optarg);
+ break;
+
+ case 's': /* Set pwm status, 0 for disable and 1 for enable */
+ status = atoi(optarg);
+ break;
+
+ case 'v': /* Get software version */
+ banner(progname);
+ return EXIT_SUCCESS;
+
+ case 'h': /* Get help information */
+ program_usage(progname);
+ return 0;
+
+ default:
+ break;
+ }
+ }
+
+ if(channel < 0 )
+ {
+ program_usage(progname);
+ return 0;
+ }
+
+ if( status )
+ {
+ if( (rv=init_pwm(channel, freq, duty)) < 0 )
+ {
+ printf("initial PWM failure, rv=%d\n", rv);
+ return 1;
+ }
+
+ turn_pwm(channel, ENABLE);
+ }
+ else
+ {
+ term_pwm(channel);
+ }
+
+ return 0;
+}
+
+/* check PWM $channel export or not */
+int check_pwm(int channel)
+{
+ char path[256];
+
+ /* check /sys/class/pwm/pwmchip0/pwmN exist or not */
+ snprintf(path, sizeof(path), "%s/pwm%d", PWMCHIP_PATH, channel);
+ return access(path, F_OK) ? 0 : 1;
+}
+
+/* export($export=1)/unexport($export=0) PWM $channel */
+int export_pwm(int channel, int export)
+{
+ int fd;
+ char buf[32];
+ char path[256];
+
+ if( export && check_pwm(channel) )
+ return 0; /* export already when export */
+ else if( !export && !check_pwm(channel) )
+ return 0; /* unexport already when unexport */
+
+ /* export PWM channel by echo N > /sys/class/pwm/pwmchip0/export */
+ snprintf(path, sizeof(path), "%s/%s", PWMCHIP_PATH, export?"export":"unexport");
+ if( (fd=open(path, O_WRONLY)) < 0 )
+ {
+ printf("open '%s' failed: %s\n", path, strerror(errno));
+ return -3;
+ }
+
+ snprintf(buf, sizeof(buf), "%d", channel);
+ write(fd, buf, strlen(buf));
+
+ msleep(100);
+
+ if( export && check_pwm(channel) )
+ return 0; /* export already when export */
+ else if( !export && !check_pwm(channel) )
+ return 0; /* unexport already when unexport */
+
+ return -4;
+}
+
+/* configure PWM $channel */
+int config_pwm(int channel, int freq, int duty)
+{
+ int fd;
+ char buf[32];
+ char path[256];
+ int period;
+ int duty_cycle;
+
+ if( !check_pwm(channel) )
+ return -2;
+
+ /* open PWM period file and write period in ns */
+ snprintf(path, sizeof(path), "%s/pwm%d/period", PWMCHIP_PATH, channel);
+ if( (fd=open(path, O_WRONLY)) < 0 )
+ {
+ printf("open '%s' failed: %s\n", path, strerror(errno));
+ return -3;
+ }
+ period = 1000000000/freq;
+ snprintf(buf, sizeof(buf), "%d", period);
+ write(fd, buf, strlen(buf));
+
+ /* open PWM duty_cycle file and write duty */
+ snprintf(path, sizeof(path), "%s/pwm%d/duty_cycle", PWMCHIP_PATH, channel);
+ if( (fd=open(path, O_WRONLY)) < 0 )
+ {
+ printf("open '%s' failed: %s\n", path, strerror(errno));
+ return -3;
+ }
+ duty_cycle = (period*duty) / 100;
+ snprintf(buf, sizeof(buf), "%d", duty_cycle);
+ write(fd, buf, strlen(buf));
+
+ return 0;
+}
+
+int init_pwm(int channel, int freq, int duty)
+{
+ int rv;
+ char buf[32];
+ char path[256];
+
+ if( (rv=export_pwm(channel, 1)) )
+ {
+ printf("export PWM channel[%d] failed, rv=%d\n", channel, rv);
+ return rv;
+ }
+
+ if( (rv=config_pwm(channel, freq, duty)) )
+ {
+ printf("config PWM channel[%d] failed, rv=%d\n", channel, rv);
+ return rv;
+ }
+
+ printf("config pwm%d with freq[%d] duty[%d] okay\n", channel, freq, duty);
+
+ return 0;
+}
+
+int turn_pwm(int channel, int status)
+{
+ int fd;
+ char buf[32];
+ char path[256];
+
+ if( !check_pwm(channel) )
+ return -1;
+
+ /* open PWM enable file and enable(1)/disable(0) it */
+ snprintf(path, sizeof(path), "%s/pwm%d/enable", PWMCHIP_PATH, channel);
+ if( (fd=open(path, O_WRONLY)) < 0 )
+ {
+ printf("open '%s' failed: %s\n", path, strerror(errno));
+ return -3;
+ }
+ snprintf(buf, sizeof(buf), "%d", status?1:0);
+ write(fd, buf, strlen(buf));
+
+ printf("pwm[%d] %s\n", channel, status?"enable":"disable");
+
+ return 0;
+}
+
+int term_pwm(int channel)
+{
+ if( !check_pwm(channel) )
+ return 0;
+
+ turn_pwm(channel, DISABLE);
+ export_pwm(channel, 0);
+
+ return 0;
+}
+
+static inline void msleep(unsigned long ms)
+{
+ struct timespec cSleep;
+ unsigned long ulTmp;
+
+ cSleep.tv_sec = ms / 1000;
+ if (cSleep.tv_sec == 0)
+ {
+ ulTmp = ms * 10000;
+ cSleep.tv_nsec = ulTmp * 100;
+ }
+ else
+ {
+ cSleep.tv_nsec = 0;
+ }
+
+ nanosleep(&cSleep, 0);
+
+ return ;
+}
diff --git a/modules/relay.c b/modules/relay.c
new file mode 100644
index 0000000..4f14801
--- /dev/null
+++ b/modules/relay.c
@@ -0,0 +1,286 @@
+/*********************************************************************************
+ * Copyright: (C) 2021 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: relay.c
+ * Description: This file is used to control Relay
+ *
+ *
+ * Pin connection:
+ * Relay Module Raspberry Pi Board
+ * VCC <-----> 5V
+ * I <-----> #Pin16(BCM GPIO23)
+ * GND <-----> GND
+ *
+ * System install:
+ * sudo apt install -y libgpiod-dev gpiod
+ *
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#include <getopt.h>
+#include <libgen.h>
+
+#include <gpiod.h>
+
+#define DELAY 500
+
+#define ON 1
+#define OFF 0
+
+/* relay code */
+enum
+{
+ RELAY1 = 0,
+ RELAY_CNT,
+};
+
+/* Relay hardware information */
+typedef struct relay_info_s
+{
+ const char *name; /* Relay name */
+ int gpio; /* Relay BCM pin number */
+ int active;/* Relay active GPIO level: 0->low 1->high */
+ struct gpiod_line *line; /* libgpiod line */
+} relay_info_t;
+
+static relay_info_t relay_info[RELAY_CNT] =
+{
+ {"relay1", 23, 1, NULL },
+};
+
+/* Relay API context */
+typedef struct relay_ctx_s
+{
+ struct gpiod_chip *chip;
+ relay_info_t *relay;
+ int count;
+} relay_ctx_t;
+
+int init_relay(relay_ctx_t *ctx);
+int term_relay(relay_ctx_t *ctx);
+int turn_relay(relay_ctx_t *ctx, int which, int cmd);
+static inline void msleep(unsigned long ms);
+
+static inline void banner(const char *progname)
+{
+ printf("%s program Version v1.0.0\n", progname);
+ printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
+}
+
+static void program_usage(const char *progname)
+{
+
+ printf("Usage: %s [OPTION]...\n", progname);
+ printf(" This is relay control program. \n");
+
+ printf(" -d[device ] Specify relay device, such as 0\n");
+ printf(" -s[status ] Specify relay status, 0 for open, 1 for close\n");
+ printf(" -h[help ] Display this help information\n");
+ printf(" -v[version ] Display the program version\n");
+
+ printf("\n");
+ banner(progname);
+ return;
+}
+
+int main(int argc, char **argv)
+{
+ int rv;
+ char *progname=NULL;
+ int which = -1;
+ int status = ON;
+
+ relay_ctx_t relay_ctx =
+ {
+ .chip = NULL,
+ .relay = relay_info,
+ .count = RELAY_CNT,
+ };
+
+ struct option long_options[] = {
+ {"device", required_argument, NULL, 'd'},
+ {"status", required_argument, NULL, 's'},
+ {"version", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+
+ progname = basename(argv[0]);
+
+ /* Parser the command line parameters */
+ while ((rv = getopt_long(argc, argv, "d:s:vh", long_options, NULL)) != -1)
+ {
+ switch (rv)
+ {
+ case 'd': /* Set relay number, such as 0...max */
+ which = atoi(optarg);
+ break;
+
+ case 's': /* Set relay status, 0 for open and 1 for close */
+ status = atoi(optarg);
+ break;
+
+ case 'v': /* Get software version */
+ banner(progname);
+ return EXIT_SUCCESS;
+
+ case 'h': /* Get help information */
+ program_usage(progname);
+ return 0;
+
+ default:
+ break;
+ }
+ }
+
+ if( which<0 || which>=relay_ctx.count )
+ {
+ printf("ERROR: Invalid relay index [%d], max=%d\n", which, relay_ctx.count-1);
+ return 0;
+ }
+
+
+ if( (rv=init_relay(&relay_ctx)) < 0 )
+ {
+ printf("initial relay gpio failure, rv=%d\n", rv);
+ return 1;
+ }
+
+ turn_relay(&relay_ctx, which, status);
+
+ term_relay(&relay_ctx);
+ return 0;
+}
+
+int term_relay(relay_ctx_t *ctx)
+{
+ int i;
+ relay_info_t *relay;
+
+ if( !ctx )
+ {
+ printf("Invalid input arguments\n");
+ return -1;
+ }
+
+ if( !ctx->chip )
+ return 0;
+
+ for(i=0; i<ctx->count; i++)
+ {
+ relay = &ctx->relay[i];
+
+ if( relay->line )
+ gpiod_line_release(relay->line);
+ }
+
+ gpiod_chip_close(ctx->chip);
+ return 0;
+}
+
+
+int init_relay(relay_ctx_t *ctx)
+{
+ int i, rv;
+ relay_info_t *relay;
+
+ if( !ctx )
+ {
+ printf("Invalid input arguments\n");
+ return -1;
+ }
+
+ ctx->chip = gpiod_chip_open_by_name("gpiochip0");
+ if( !ctx->chip )
+ {
+ printf("open gpiochip failure, maybe you need running as root\n");
+ return -2;
+ }
+
+
+ for(i=0; i<ctx->count; i++)
+ {
+ relay = &ctx->relay[i];
+
+ relay->line = gpiod_chip_get_line(ctx->chip, relay->gpio);
+ if( !relay->line )
+ {
+ printf("open gpioline for %s[%d] failed\n", relay->name, relay->gpio);
+ rv = -3;
+ goto failed;
+ }
+
+ rv = gpiod_line_request_output(relay->line, relay->name, !relay->active);
+ if( rv )
+ {
+ printf("request gpio output for %5s[%d] failed\n", relay->name, relay->gpio);
+ rv = -4;
+ goto failed;
+ }
+
+ //printf("request %s[%d] for gpio output okay\n", relay->name, relay->gpio);
+ }
+
+ return 0;
+
+failed:
+ term_relay(ctx);
+ return rv;
+}
+
+int turn_relay(relay_ctx_t *ctx, int which, int cmd)
+{
+ int rv = 0;
+ relay_info_t *relay;
+
+ if( !ctx || which<0 || which>=ctx->count )
+ {
+ printf("Invalid input arguments\n");
+ return -1;
+ }
+
+ relay = &ctx->relay[which];
+
+ if( OFF == cmd )
+ {
+ gpiod_line_set_value(relay->line, !relay->active);
+ }
+ else
+ {
+ gpiod_line_set_value(relay->line, relay->active);
+ }
+
+ return 0;
+}
+
+static inline void msleep(unsigned long ms)
+{
+ struct timespec cSleep;
+ unsigned long ulTmp;
+
+ cSleep.tv_sec = ms / 1000;
+ if (cSleep.tv_sec == 0)
+ {
+ ulTmp = ms * 10000;
+ cSleep.tv_nsec = ulTmp * 100;
+ }
+ else
+ {
+ cSleep.tv_nsec = 0;
+ }
+
+ nanosleep(&cSleep, 0);
+
+ return ;
+}
diff --git a/modules/sht20.c b/modules/sht20.c
new file mode 100644
index 0000000..d84877f
--- /dev/null
+++ b/modules/sht20.c
@@ -0,0 +1,331 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: sht20.c
+ * Description: This file is temperature and relative humidity sensor SHT20 code
+ *
+ * Version: 1.0.0(10/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ * STH20 Module Raspberry Pi Board
+ * VCC <-----> #Pin1(3.3V)
+ * SDA <-----> #Pin3(SDA, BCM GPIO2)
+ * SCL <-----> #Pin5(SCL, BCM GPIO3)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ * dtoverlay=i2c1,pins_2_3
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+
+int i2c_write(int fd, uint8_t slave_addr, uint8_t *data, int len);
+int i2c_read(int fd, uint8_t slave_addr, uint8_t *buf, int size);
+int sht20_checksum(uint8_t *data, int len, int8_t checksum);
+static inline void msleep(unsigned long ms);
+static inline void dump_buf(const char *prompt, uint8_t *buf, int size);
+
+static inline void banner(const char *progname)
+{
+ printf("%s program Version v1.0.0\n", progname);
+ printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
+}
+
+static void program_usage(const char *progname)
+{
+
+ printf("Usage: %s [OPTION]...\n", progname);
+ printf(" %s is SHT20 temperature and humidity sensor program. \n", progname);
+
+ printf(" -d[device ] Specify I2C device, such as /dev/i2c-1\n");
+ printf(" -h[help ] Display this help information\n");
+ printf(" -v[version ] Display the program version\n");
+
+ printf("\n");
+ banner(progname);
+ return;
+}
+
+int main(int argc, char **argv)
+{
+ int fd, rv;
+ float temp, rh;
+ char *dev = "/dev/i2c-1";
+ char *progname=NULL;
+ uint8_t buf[4];
+
+ struct option long_options[] = {
+ {"device", required_argument, NULL, 'd'},
+ {"version", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ progname = basename(argv[0]);
+
+ /* Parser the command line parameters */
+ while ((rv = getopt_long(argc, argv, "d:vh", long_options, NULL)) != -1)
+ {
+ switch (rv)
+ {
+ case 'd': /* Set I2C device path: /dev/i2c-1 */
+ dev = optarg;
+ break;
+
+ case 'v': /* Get software version */
+ banner(progname);
+ return EXIT_SUCCESS;
+
+ case 'h': /* Get help information */
+ program_usage(progname);
+ return 0;
+
+ default:
+ break;
+ }
+ }
+
+
+ /*+--------------------------------+
+ *| open /dev/i2c-x device |
+ *+--------------------------------+*/
+ if( (fd=open(dev, O_RDWR)) < 0)
+ {
+ printf("i2c device '%s' open failed: %s\n", dev, strerror(errno));
+ return -1;
+ }
+
+ /*+--------------------------------+
+ *| software reset SHT20 sensor |
+ *+--------------------------------+*/
+
+ buf[0] = 0xFE;
+ i2c_write(fd, 0x40, buf, 1);
+
+ msleep(50);
+
+
+ /*+--------------------------------+
+ *| trigger temperature measure |
+ *+--------------------------------+*/
+
+ buf[0] = 0xF3;
+ i2c_write(fd, 0x40, buf, 1);
+
+ msleep(85); /* datasheet: typ=66, max=85 */
+
+ memset(buf, 0, sizeof(buf));
+ i2c_read(fd, 0x40, buf, 3);
+ dump_buf("Temperature sample data: ", buf, 3);
+
+ if( !sht20_checksum(buf, 2, buf[2]) )
+ {
+ printf("Temperature sample data CRC checksum failure.\n");
+ goto cleanup;
+ }
+
+ temp = 175.72 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;
+
+
+ /*+--------------------------------+
+ *| trigger humidity measure |
+ *+--------------------------------+*/
+
+ buf[0] = 0xF5;
+ i2c_write(fd, 0x40, buf, 1);
+
+ msleep(29); /* datasheet: typ=22, max=29 */
+
+ memset(buf, 0, sizeof(buf));
+ i2c_read(fd, 0x40, buf, 3);
+ dump_buf("Relative humidity sample data: ", buf, 3);
+
+ if( !sht20_checksum(buf, 2, buf[2]) )
+ {
+ printf("Relative humidity sample data CRC checksum failure.\n");
+ goto cleanup;
+ }
+
+ rh = 125 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 6;
+
+ /*+--------------------------------+
+ *| print the measure result |
+ *+--------------------------------+*/
+
+ printf("Temperature=%lf 'C relative humidity=%lf%%\n", temp, rh);
+
+cleanup:
+ close(fd);
+ return 0;
+}
+
+int sht20_checksum(uint8_t *data, int len, int8_t checksum)
+{
+ int8_t crc = 0;
+ int8_t bit;
+ int8_t byteCtr;
+
+ //calculates 8-Bit checksum with given polynomial: x^8 + x^5 + x^4 + 1
+ for (byteCtr = 0; byteCtr < len; ++byteCtr)
+ {
+ crc ^= (data[byteCtr]);
+ for ( bit = 8; bit > 0; --bit)
+ {
+ /* x^8 + x^5 + x^4 + 1 = 0001 0011 0001 = 0x131 */
+ if (crc & 0x80) crc = (crc << 1) ^ 0x131;
+ else crc = (crc << 1);
+ }
+ }
+
+ if (crc != checksum)
+ return 0;
+ else
+ return 1;
+}
+
+int i2c_write(int fd, uint8_t slave_addr, uint8_t *data, int len)
+{
+ struct i2c_rdwr_ioctl_data i2cdata;
+ int rv = 0;
+
+ if( !data || len<= 0)
+ {
+ printf("%s() invalid input arguments!\n", __func__);
+ return -1;
+ }
+
+ i2cdata.nmsgs = 1;
+ i2cdata.msgs = malloc( sizeof(struct i2c_msg)*i2cdata.nmsgs );
+ if ( !i2cdata.msgs )
+ {
+ printf("%s() msgs malloc failed!\n", __func__);
+ return -2;
+ }
+
+ i2cdata.msgs[0].addr = slave_addr;
+ i2cdata.msgs[0].flags = 0; //write
+ i2cdata.msgs[0].len = len;
+ i2cdata.msgs[0].buf = malloc(len);
+ if( !i2cdata.msgs[0].buf )
+ {
+ printf("%s() msgs malloc failed!\n", __func__);
+ rv = -3;
+ goto cleanup;
+ }
+ memcpy(i2cdata.msgs[0].buf, data, len);
+
+
+ if( ioctl(fd, I2C_RDWR, &i2cdata)<0 )
+ {
+ printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ rv = -4;
+ goto cleanup;
+ }
+
+cleanup:
+ if( i2cdata.msgs[0].buf )
+ free(i2cdata.msgs[0].buf);
+
+ if( i2cdata.msgs )
+ free(i2cdata.msgs);
+
+ return rv;
+}
+
+int i2c_read(int fd, uint8_t slave_addr, uint8_t *buf, int size)
+{
+ struct i2c_rdwr_ioctl_data i2cdata;
+ int rv = 0;
+
+ if( !buf || size<= 0)
+ {
+ printf("%s() invalid input arguments!\n", __func__);
+ return -1;
+ }
+
+ i2cdata.nmsgs = 1;
+ i2cdata.msgs = malloc( sizeof(struct i2c_msg)*i2cdata.nmsgs );
+ if ( !i2cdata.msgs )
+ {
+ printf("%s() msgs malloc failed!\n", __func__);
+ return -2;
+ }
+
+ i2cdata.msgs[0].addr = slave_addr;
+ i2cdata.msgs[0].flags = I2C_M_RD; //read
+ i2cdata.msgs[0].len = size;
+ i2cdata.msgs[0].buf = buf;
+ memset(buf, 0, size);
+
+ if( ioctl(fd, I2C_RDWR, &i2cdata)<0 )
+ {
+ printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ rv = -4;
+ }
+
+ free( i2cdata.msgs );
+ return rv;
+}
+
+static inline void msleep(unsigned long ms)
+{
+ struct timespec cSleep;
+ unsigned long ulTmp;
+
+ cSleep.tv_sec = ms / 1000;
+ if (cSleep.tv_sec == 0)
+ {
+ ulTmp = ms * 10000;
+ cSleep.tv_nsec = ulTmp * 100;
+ }
+ else
+ {
+ cSleep.tv_nsec = 0;
+ }
+
+ nanosleep(&cSleep, 0);
+ return ;
+}
+
+static inline void dump_buf(const char *prompt, uint8_t *buf, int size)
+{
+ int i;
+
+ if( !buf )
+ {
+ return ;
+ }
+
+ if( prompt )
+ {
+ printf("%-32s ", prompt);
+ }
+
+ for(i=0; i<size; i++)
+ {
+ printf("%02x ", buf[i]);
+ }
+ printf("\n");
+
+ return ;
+}
diff --git a/modules/tsl2561.c b/modules/tsl2561.c
new file mode 100644
index 0000000..20bd0c2
--- /dev/null
+++ b/modules/tsl2561.c
@@ -0,0 +1,337 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: sht20.c
+ * Description: This file is the Lux sensor TSL2561 code
+ *
+ * Version: 1.0.0(10/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ * STH20 Module Raspberry Pi Board
+ * VCC <-----> #Pin1(3.3V)
+ * SDA <-----> #Pin27(SDA, BCM GPIO0)
+ * SCL <-----> #Pin28(SCL, BCM GPIO1)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ * dtoverlay=i2c0,pins_0_1
+ *
+ ********************************************************************************/
+
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+#include <time.h>
+#include <errno.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define TSL2561_I2C_ADDR 0x39
+
+#define CONTROL_REG 0x80
+#define REG_COUNT 4
+
+#define POWER_UP 0x03
+#define POWER_DOWN 0x00
+
+#define OFF 0
+#define ON 1
+
+/* Register Address */
+enum
+{
+ /* Channel_0 = DATA0HIGH<<8 + DATA0LOW */
+ DATA0LOW = 0x8C,
+ DATA0HIGH,
+
+ /* Channel_1 = DATA1HIGH<<8 + DATA1LOW */
+ DATA1LOW,
+ DATA1HIGH,
+};
+
+static const int regs_addr[REG_COUNT]={DATA0LOW, DATA0HIGH, DATA1LOW, DATA1HIGH};
+
+float tsl2561_get_lux(int fd);
+static inline void print_datime(void);
+
+static inline void banner(const char *progname)
+{
+ printf("%s program Version v1.0.0\n", progname);
+ printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
+}
+
+static void program_usage(const char *progname)
+{
+
+ printf("Usage: %s [OPTION]...\n", progname);
+ printf(" %s is TSL2561 Lux sensor program.\n", progname);
+
+ printf(" -d[device ] Specify I2C device, such as /dev/i2c-0\n");
+ printf(" -h[help ] Display this help information\n");
+ printf(" -v[version ] Display the program version\n");
+
+ printf("\n");
+ banner(progname);
+ return;
+}
+
+int main(int argc, char **argv)
+{
+ int fd, rv;
+ float lux;
+ char *dev = "/dev/i2c-0";
+ char *progname=NULL;
+
+ struct option long_options[] = {
+ {"device", required_argument, NULL, 'd'},
+ {"version", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ progname = basename(argv[0]);
+
+ /* Parser the command line parameters */
+ while ((rv = getopt_long(argc, argv, "d:vh", long_options, NULL)) != -1)
+ {
+ switch (rv)
+ {
+ case 'd': /* Set I2C device path: /dev/i2c-1 */
+ dev = optarg;
+ break;
+
+ case 'v': /* Get software version */
+ banner(progname);
+ return EXIT_SUCCESS;
+
+ case 'h': /* Get help information */
+ program_usage(progname);
+ return 0;
+
+ default:
+ break;
+ }
+ }
+
+ /*+--------------------------------+
+ *| open /dev/i2c-x device |
+ *+--------------------------------+*/
+ if( (fd=open(dev, O_RDWR)) < 0)
+ {
+ printf("i2c device '%s' open failed: %s\n", dev, strerror(errno));
+ return -1;
+ }
+
+ while(1)
+ {
+ lux = tsl2561_get_lux(fd);
+
+ print_datime();
+ printf("TSLl2561 get lux: %.3f\n", lux);
+
+ sleep(1);
+ }
+
+ close(fd);
+ return 0;
+}
+
+static inline void print_datime(void)
+{
+ time_t tmp;
+ struct tm *p;
+
+ time(&tmp);
+
+ p=localtime(&tmp);
+
+
+ printf("%d-%02d-%02d %02d:%02d:%02d\t", (p->tm_year+1900),(p->tm_mon+1),
+ p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);
+
+}
+
+static inline void msleep(unsigned long ms)
+{
+ struct timespec cSleep;
+ unsigned long ulTmp;
+
+ cSleep.tv_sec = ms / 1000;
+ if (cSleep.tv_sec == 0)
+ {
+ ulTmp = ms * 10000;
+ cSleep.tv_nsec = ulTmp * 100;
+ }
+ else
+ {
+ cSleep.tv_nsec = 0;
+ }
+
+ nanosleep(&cSleep, 0);
+ return ;
+}
+
+void tsl2561_power(int fd, int cmd)
+{
+ struct i2c_msg msg;
+ struct i2c_rdwr_ioctl_data data;
+ unsigned char buf[2];
+
+ msg.addr= TSL2561_I2C_ADDR;
+ msg.flags=0; /* write */
+ msg.len= 1;
+ msg.buf= buf;
+
+ data.nmsgs= 1;
+ data.msgs= &msg;
+
+ msg.buf[0]=CONTROL_REG;
+ if( ioctl(fd, I2C_RDWR, &data) < 0 )
+ {
+ printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ return ;
+ }
+
+
+ if( cmd )
+ msg.buf[0]=POWER_UP;
+ else
+ msg.buf[0]=POWER_DOWN;
+
+ if( ioctl(fd, I2C_RDWR, &data) < 0 )
+ {
+ printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ return ;
+ }
+
+ return ;
+}
+
+int tsl2561_readreg(int fd, unsigned char regaddr, unsigned char *regval)
+{
+ struct i2c_msg msg;
+ struct i2c_rdwr_ioctl_data data;
+ unsigned char buf[2];
+
+ msg.addr= TSL2561_I2C_ADDR;
+ msg.flags=0; /* write */
+ msg.len= 1;
+ msg.buf= buf;
+ msg.buf[0] = regaddr;
+
+ data.nmsgs= 1;
+ data.msgs= &msg;
+
+ if( ioctl(fd, I2C_RDWR, &data) < 0 )
+ {
+ printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ msg.addr= TSL2561_I2C_ADDR;
+ msg.flags=I2C_M_RD; /* read */
+ msg.len= 1;
+ msg.buf= buf;
+
+ data.nmsgs= 1;
+ data.msgs= &msg;
+
+ if( ioctl(fd, I2C_RDWR, &data) < 0 )
+ {
+ printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ *regval = msg.buf[0];
+ return 0;
+}
+
+float tsl2561_get_lux(int fd)
+{
+ int i;
+ unsigned char reg_data[REG_COUNT];
+ unsigned char buf;
+
+ int chn0_data = 0;
+ int chn1_data = 0;
+
+ float div = 0.0;
+ float lux = 0.0;
+
+ tsl2561_power(fd, ON);
+
+ msleep(410); /* t(CONV) MAX 400ms */
+
+ /* Read register Channel0 and channel1 data from register */
+ for(i=0; i<REG_COUNT; i++)
+ {
+ tsl2561_readreg(fd, regs_addr[i], ®_data[i]);
+ }
+
+ chn0_data = reg_data[1]*256 + reg_data[0]; /* Channel0 = DATA0HIGH<<8 + DATA0LOW */
+ chn1_data = reg_data[3]*256 + reg_data[2]; /* channel1 = DATA1HIGH<<8 + DATA1LOW */
+
+ if( chn0_data<=0 || chn1_data<0 )
+ {
+ lux = 0.0;
+ goto OUT;
+ }
+
+ div = (float)chn1_data / (float)chn0_data;
+
+ if( div>0 && div<=0.5 )
+ lux = 0.304*chn0_data-0.062*chn0_data*pow(div,1.4);
+
+ else if( div>0.5 && div<=0.61 )
+ lux = 0.0224*chn0_data-0.031*chn1_data;
+
+ else if( div>0.61 && div<=0.8 )
+ lux = 0.0128*chn0_data-0.0153*chn1_data;
+
+ else if( div>0.8 && div<=1.3 )
+ lux = 0.00146*chn0_data-0.00112*chn1_data;
+
+ else if( div>1.3 )
+ lux = 0.0;
+
+OUT:
+ tsl2561_power(fd, OFF);
+ return lux;
+}
+
+static inline void dump_buf(const char *prompt, char *buf, int size)
+{
+ int i;
+
+ if( !buf )
+ {
+ return ;
+ }
+
+ if( prompt )
+ {
+ printf("%-32s ", prompt);
+ }
+
+ for(i=0; i<size; i++)
+ {
+ printf("%02x ", buf[i]);
+ }
+ printf("\n");
+
+ return ;
+}
diff --git a/project/booster/README.md b/project/booster/README.md
new file mode 100644
index 0000000..2b4e7a9
--- /dev/null
+++ b/project/booster/README.md
@@ -0,0 +1,4 @@
+## booster
+
+LingYun embedded C program basic library
+
diff --git a/project/booster/at-esp32.c b/project/booster/at-esp32.c
new file mode 100644
index 0000000..b89bfa1
--- /dev/null
+++ b/project/booster/at-esp32.c
@@ -0,0 +1,388 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: at-esp32.c
+ * Description: This file is ESP32 AT command low level API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#include "logger.h"
+#include "at-esp32.h"
+
+/*+------------------------+
+ *| Baisc AT command |
+ *+------------------------+*/
+
+static inline int check_at_ready(comport_t *comport)
+{
+ int times = 6;
+ int ready = 0;
+
+ while( times-- )
+ {
+ if( 0 == send_atcmd_check_ok(comport, "AT", 500) )
+ {
+ ready = 1;
+ break;
+ }
+ }
+
+ return ready;
+}
+
+/* AT command: AT+RST */
+int esp32_reset(comport_t *comport)
+{
+ int rv;
+
+ rv = send_atcmd(comport, "AT+RST", 5000, "ready", AT_ERRSTR, NULL, 0);
+ if( rv < 0)
+ {
+ log_error("send AT command to reset ESP32 failed, rv=%d\n", rv);
+ return -1;
+ }
+
+ if( check_at_ready(comport) )
+ {
+ log_info("send AT to reset ESP32 and AT command ready\n");
+ return 0;
+ }
+ else
+ {
+ log_info("send AT to reset ESP32 but AT command not ready\n");
+ return -3;
+ }
+}
+
+/* AT command: AT+RESTORE */
+int esp32_restore(comport_t *comport)
+{
+ int rv;
+
+ //rv = send_atcmd_check_ok(comport, "AT+RESTORE", 5000);
+ rv = send_atcmd(comport, "AT+RESTORE", 5000, "ready", AT_ERRSTR, NULL, 0);
+ if( rv < 0)
+ {
+ log_error("send AT command to restore ESP32 failed, rv=%d\n", rv);
+ return -1;
+ }
+
+ if( check_at_ready(comport) )
+ {
+ log_info("send AT command to resstore ESP32 ready\n");
+ return 0;
+ }
+ else
+ {
+ log_error("send AT command to restore ESP32 but AT not ready\n");
+ return -3;
+ }
+}
+
+/* AT command: ATE1 or ATE0 */
+int esp32_set_echo(comport_t *comport, int enable)
+{
+ char *at = enable? "ATE1" : "ATE0";
+
+ return send_atcmd_check_ok(comport, at, 500);
+}
+
+/* AT command: AT+GMR */
+int esp32_get_firmware(comport_t *comport, char *version, int size)
+{
+ if( !version || size<=0 )
+ return -1;
+
+ return send_atcmd_check_value(comport, "AT+GMR", 2000, version, size);
+}
+
+/* AT command: AT+SYSSTORE */
+int esp32_set_sysstore(comport_t *comport, int enable)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ snprintf(at, sizeof(at), "AT+SYSSTORE=%d", enable?1:0);
+
+ return send_atcmd_check_ok(comport, at, 1000);
+}
+
+/*+------------------------+
+ *| WiFi AT command |
+ *+------------------------+*/
+
+/* AT command: AT+CWMODE */
+int esp32_set_wmode(comport_t *comport, workmode_t mode, int autoconn)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ snprintf(at, sizeof(at), "AT+CWMODE=%d,%d", mode, autoconn?1:0);
+ return send_atcmd_check_ok(comport, at, 1500);
+}
+
+/* AT command: AT+CIPSTAMAC/CIPAPMAC */
+int esp32_get_macaddr(comport_t *comport, workmode_t mode, char *mac)
+{
+ if( !mac )
+ return -1;
+
+ if( MODE_SOFTAP == mode )
+ return send_atcmd_check_request(comport, "AT+CIPAPMAC?", 3000, mac, MAC_LEN);
+ else
+ return send_atcmd_check_request(comport, "AT+CIPSTAMAC?", 3000, mac, MAC_LEN);
+}
+
+/* AT command: AT+CIPSTA/AT+CIPAP */
+int esp32_set_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ if( !ip || !gateway )
+ return -1;
+
+ if( MODE_SOFTAP == mode )
+ snprintf(at, sizeof(at), "AT+CIPAP=\"%s\",\"%s\"", ip, gateway);
+ else
+ snprintf(at, sizeof(at), "AT+CIPSTA=\"%s\",\"%s\"", ip, gateway);
+
+ return send_atcmd_check_ok(comport, at, 2000);
+}
+
+/* AT command: AT+CIPSTA/AT+CIPAP */
+int esp32_get_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway)
+{
+ char *at = MODE_SOFTAP==mode? "AT+CIPAP?" : "AT+CIPSTA?";
+ char buf[ATCMD_REPLY_LEN];
+ int rv;
+
+ if( !ip || !gateway )
+ return -1;
+
+ rv = send_atcmd_check_value(comport, at, 3000, buf, sizeof(buf));
+ if( rv < 0)
+ {
+ log_error("AT command query IP address failed, rv=%d\n", rv);
+ return rv;
+ }
+
+ rv = parser_request_value(buf, "ip", ip, IP_LEN);
+ if( rv < 0 )
+ {
+ log_error("Parser query IP address failed, rv=%d\n", rv);
+ return rv;
+ }
+
+ rv = parser_request_value(buf, "gateway", gateway, IP_LEN);
+ if( rv < 0 )
+ {
+ log_error("Parser query gateway address failed, rv=%d\n", rv);
+ return rv;
+ }
+
+ return 0;
+}
+
+/* AT command: AT+CWDHCP */
+int esp32_set_dhcp(comport_t *comport, workmode_t mode, int enable)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ snprintf(at, sizeof(at), "AT+CWDHCP=%d,%d", enable?1:0, mode);
+ return send_atcmd_check_ok(comport, at, 1500);
+}
+
+/* AT command: AT+CWAUTOCONN */
+int esp32_set_autoconn(comport_t *comport, int enable)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ snprintf(at, sizeof(at), "AT+CWAUTOCONN=%d", enable?1:0);
+ return send_atcmd_check_ok(comport, at, 1500);
+}
+
+/* AT command: AT+CWLAP */
+int esp32_list_ap(comport_t *comport, char *buf, int size)
+{
+ if( !buf || size<=0 )
+ return -1;
+
+ return send_atcmd_check_value(comport, "AT+CWLAP", 8000, buf, size);
+}
+
+/* AT command: AT+CWJAP */
+int esp32_connect_ap(comport_t *comport, char *ssid, char *pwd)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ if( !ssid || !pwd )
+ return -1;
+
+ snprintf(at, sizeof(at), "AT+CWJAP=\"%s\",\"%s\"", ssid, pwd);
+ return send_atcmd_check_ok(comport, at, 15000);
+}
+
+/* AT command: AT+CWQAP */
+int esp32_disconn_ap(comport_t *comport)
+{
+ return send_atcmd_check_ok(comport, "AT+CWQAP", 5000);
+}
+
+
+/* AT command: AT+CWSTATE? status value:
+ * - 0: ESP32 station has not started any Wi-Fi connection.
+ * - 1: ESP32 station has connected to an AP, but does not get an IPv4 address yet.
+ * - 2: ESP32 station has connected to an AP, and got an IPv4 address.
+ * - 3: ESP32 station is in Wi-Fi connecting or reconnecting state.
+ * - 4: ESP32 station is in Wi-Fi disconnected state
+ */
+int esp32_join_status(comport_t *comport, int *status, char *ssid)
+{
+ char buf[ATCMD_REPLY_LEN];
+ int rv;
+
+ if( !status || !ssid )
+ return -1;
+
+ rv = send_atcmd_check_request(comport, "AT+CWSTATE?", 3000, buf, sizeof(buf));
+ if( rv < 0)
+ {
+ log_error("AT command query join status failed, rv=%d\n", rv);
+ return rv;
+ }
+
+ sscanf(buf, "%d,%s", status, ssid);
+
+ return 0;
+}
+
+/* AT command: AT+PING */
+int esp32_ping(comport_t *comport, char *host, int timeout)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ if( !host )
+ return -1;
+
+ snprintf(at, sizeof(at), "AT+PING=\"%s\"", host);
+ return send_atcmd_check_ok(comport, at, timeout);
+}
+
+/* AT command: AT+CWSAP */
+int esp32_set_softap(comport_t *comport, char *ssid, char *pwd, int channel, encmode_t encmode)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ if( !ssid || !pwd )
+ return -1;
+
+ snprintf(at, sizeof(at), "AT+CWSAP=\"%s\",\"%s\",%d,%d", ssid, pwd, channel, encmode);
+ return send_atcmd_check_ok(comport, at, 5000);
+}
+
+/* AT command: AT+CWLIF */
+int esp32_list_client(comport_t *comport, char *buf, int size)
+{
+ if( !buf || size<=0 )
+ return -1;
+
+ return send_atcmd_check_value(comport, "AT+CWLIF", 8000, buf, size);
+}
+
+/*+------------------------+
+ *| Socket AT command |
+ *+------------------------+*/
+
+/* AT command: AT+CIPMUX */
+int esp32_set_socket_mux(comport_t *comport, int enable)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ snprintf(at, sizeof(at), "AT+CIPMUX=%d", enable?1:0);
+
+ return send_atcmd_check_ok(comport, at, 1500);
+}
+
+/* AT command: AT+CIPSERVERMAXCONN */
+int esp32_set_socket_clients(comport_t *comport, int max)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ if( max <= 0 )
+ return -1;
+
+ snprintf(at, sizeof(at), "AT+CIPSERVERMAXCONN=%d", max);
+
+ return send_atcmd_check_ok(comport, at, 1500);
+}
+
+/* AT command: AT+CIPSTO, timeout unit second */
+int esp32_set_socket_timeout(comport_t *comport, int timeout)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ if( timeout <= 0 )
+ return -1;
+
+ snprintf(at, sizeof(at), "AT+CIPSTO=%d", timeout);
+
+ return send_atcmd_check_ok(comport, at, 1500);
+}
+
+/* AT command: AT+CIPSERVER */
+int esp32_set_tcp_server(comport_t *comport, int port)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ if( port <= 0 )
+ return -1;
+
+ snprintf(at, sizeof(at), "AT+CIPSERVER=1,%d,\"TCP\"", port);
+
+ return send_atcmd_check_ok(comport, at, 1500);
+}
+
+/* AT command: AT+CIPSERVER */
+int esp32_del_tcp_server(comport_t *comport, int port)
+{
+ char at[ATCMD_LEN]={'\0'};
+
+ if( port <= 0 )
+ return -1;
+
+ snprintf(at, sizeof(at), "AT+CIPSERVER=0,%d,\"TCP\"", port);
+
+ return send_atcmd_check_ok(comport, at, 1500);
+}
+
+/* AT command: AT+CIPSTART */
+int esp32_set_tcp_client(comport_t *comport, int mux, char *host, int port)
+{
+ char at[ATCMD_LEN]={'\0'};
+ char buf[ATCMD_REPLY_LEN]={'\0'};
+ int keepalive = 60; /* unit second */
+ int rv,linkid = 0;
+
+ if( !host || port <= 0 )
+ return -1;
+
+ rv = esp32_set_socket_mux(comport, mux);
+ if(rv < 0)
+ return rv;
+
+ if( mux )
+ snprintf(at, sizeof(at), "AT+CIPSTART=1,\"TCP\",\"%s\",%d,%d", host, port, keepalive);
+ else
+ snprintf(at, sizeof(at), "AT+CIPSTART=\"TCP\",\"%s\",%d,%d", host, port, keepalive);
+
+ rv = send_atcmd_check_value(comport, at, 1500, buf, sizeof(buf));
+ if(rv < 0)
+ return rv;
+
+ sscanf(buf, "%d,", &linkid);
+
+ return linkid;
+}
diff --git a/project/booster/at-esp32.h b/project/booster/at-esp32.h
new file mode 100644
index 0000000..3a8b57a
--- /dev/null
+++ b/project/booster/at-esp32.h
@@ -0,0 +1,140 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: at-esp32.h
+ * Description: This file is ESP32 AT command low level API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#ifndef _AT_ESP32_H_
+#define _AT_ESP32_H_
+
+#include "atcmd.h"
+
+enum
+{
+ DISABLE = 0, /* common disable */
+ ENABLE, /* common enable */
+};
+
+#define MAC_LEN 18 /* 11:22:33:44:55:66 */
+#define IP_LEN 16 /* 255.255.255.255 */
+
+/*+------------------------+
+ *| Baisc AT command |
+ *+------------------------+*/
+
+/* AT command: AT+RST */
+extern int esp32_reset(comport_t *comport);
+
+/* AT command: AT+RESTORE */
+extern int esp32_restore(comport_t *comport);
+
+/* AT command: ATE1 or ATE0 */
+extern int esp32_set_echo(comport_t *comport, int enable);
+
+/* AT command: AT+GMR */
+extern int esp32_get_firmware(comport_t *comport, char *version, int size);
+
+/* AT command: AT+SYSSTORE */
+extern int esp32_set_sysstore(comport_t *comport, int enable);
+
+
+/*+------------------------+
+ *| WiFi AT command |
+ *+------------------------+*/
+typedef enum
+{
+ MODE_DISABLE=0, /* Wi-Fi RF will be disabled */
+ MODE_STATION, /* Station mode */
+ MODE_SOFTAP, /* SoftAP mode */
+ MODE_WDS, /* Wireless Distribution System: Both Station & SoftAP mode */
+} workmode_t;
+
+/* AT command: AT+CWMODE */
+extern int esp32_set_wmode(comport_t *comport, workmode_t mode, int autoconn);
+
+/* AT command: AT+CWDHCP */
+extern int esp32_set_dhcp(comport_t *comport, workmode_t mode, int enable);
+
+/* AT command: AT+CWAUTOCONN */
+extern int esp32_set_autoconn(comport_t *comport, int enable);
+
+/* AT command: AT+CIPSTAMAC/CIPAPMAC */
+extern int esp32_get_macaddr(comport_t *comport, workmode_t mode, char *mac);
+
+/* AT command: AT+CIPSTA/CIPAP */
+extern int esp32_get_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway);
+
+/* AT command: AT+CIPSTA/AT+CIPAP */
+extern int esp32_set_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway);
+
+/* AT command: AT+CWLAP */
+extern int esp32_list_ap(comport_t *comport, char *buf, int size);
+
+/* AT command: AT+CWJAP */
+extern int esp32_connect_ap(comport_t *comport, char *ssid, char *pwd);
+
+/* AT command: AT+CWQAP */
+extern int esp32_disconn_ap(comport_t *comport);
+
+/* AT command: AT+CWSTATE? , status value:
+ * - 0: ESP32 station has not started any Wi-Fi connection.
+ * - 1: ESP32 station has connected to an AP, but does not get an IPv4 address yet.
+ * - 2: ESP32 station has connected to an AP, and got an IPv4 address.
+ * - 3: ESP32 station is in Wi-Fi connecting or reconnecting state.
+ * - 4: ESP32 station is in Wi-Fi disconnected state
+ */
+extern int esp32_join_status(comport_t *comport, int *status, char *ssid);
+
+/* AT command: AT+PING */
+extern int esp32_ping(comport_t *comport, char *host, int timeout);
+
+/* AT command: AT+CWSAP */
+typedef enum
+{
+ MODE_OPEN,
+ /* WEP not support */
+ MODE_WPAPSK = 2,
+ MODE_WPA2PSK,
+ MODE_WPA_WPA2PSK,
+} encmode_t;
+extern int esp32_set_softap(comport_t *comport, char *ssid, char *pwd, int channel, encmode_t encmode);
+
+/* AT command: AT+CWLIF */
+extern int esp32_list_client(comport_t *comport, char *buf, int size);
+
+
+/*+------------------------+
+ *| Socket AT command |
+ *+------------------------+*/
+
+/* AT command: CIPMUX */
+extern int esp32_set_socket_mux(comport_t *comport, int enable);
+
+/* AT command: AT+CIPSERVERMAXCONN */
+extern int esp32_set_socket_clients(comport_t *comport, int max);
+
+/* AT command: AT+CIPSTO, timeout unit second */
+extern int esp32_set_socket_timeout(comport_t *comport, int timeout);
+
+/* AT command: AT+CIPSERVER */
+extern int esp32_set_tcp_server(comport_t *comport, int port);
+
+/* AT command: AT+CIPSERVER */
+extern int esp32_del_tcp_server(comport_t *comport, int port);
+
+/* AT command: AT+CIPSTART */
+extern int esp32_set_tcp_client(comport_t *comport, int mux, char *host, int port);
+
+/*+------------------------+
+ *| BLE AT command |
+ *+------------------------+*/
+// RFU
+
+#endif /* ----- #ifndef _AT_ESP32_H_ ----- */
diff --git a/project/booster/atcmd.c b/project/booster/atcmd.c
new file mode 100644
index 0000000..425682f
--- /dev/null
+++ b/project/booster/atcmd.c
@@ -0,0 +1,300 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: atcmd.c
+ * Description: This file is lowlevel AT command send and parser API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#include <ctype.h>
+#include "logger.h"
+#include "atcmd.h"
+
+/* Description: this function used to send an AT command from serial port and wait for reply message from
+ * the communication module, and it will return once get expet/error string or timeout.
+ *
+ * Arugments:
+ * $comport: the serial port which connected to GPRS/3G/4G/NB-IoT/WiFi/BLE/Zigbee/LoRa...
+ * $at: the AT command need to be sent, without "\r\n"
+ * $timeout: wait for module reply for AT command timeout value, unit micro seconds(ms)
+ * $exepct: the expect string reply from communication module
+ * $error: the error string reply from communication module
+ * $reply: the module reply message output buffer
+ * $size: the output buffer ($reply) size
+ *
+ * Return value: <0: Function error 0: Got error string 1: Got expect string 2: Receive util timeout
+ *
+ */
+
+int send_atcmd(comport_t *comport, char *at, unsigned long timeout, char *expect, char *error, char *reply, int size)
+{
+ int i, rv = 0;
+ int res = ATRES_TIMEOUT;
+ int bytes = 0;
+ char buf[ATCMD_REPLY_LEN] = {'\0'};
+ char atcmd[ATCMD_LEN] = {'\0'};
+
+ if( !comport || !at )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ if( comport->fd <= 0 )
+ {
+ log_error("comport[%s] not opened\n");
+ return -2;
+ }
+
+ /* flushes both data received but not read, and data written but not transmitted in serial port */
+ tcflush(comport->fd, TCIOFLUSH);
+
+ snprintf(atcmd, sizeof(atcmd), "%s%s", at, AT_SUFFIX);
+ rv=comport_send( comport, atcmd, strlen(atcmd) );
+ if(rv < 0)
+ {
+ log_error("send AT command \"%s\" to \"%s\" failed, rv=%d\n", at, comport->devname, rv);
+ return -3;
+ }
+
+ res = ATRES_TIMEOUT;
+ memset( buf, 0, sizeof(buf) );
+
+ for(i=0; i<timeout/10; i++)
+ {
+ if( bytes >= sizeof(buf) )
+ break;
+
+ rv=comport_recv( comport, buf+bytes, sizeof(buf)-bytes, 10);
+ if(rv < 0)
+ {
+ log_error("send AT command \'%s\' to \'%s\' failed, rv=%d\n", at, comport->devname, rv);
+ return -3;
+ }
+
+ bytes += rv;
+
+ if( expect && strstr(buf, expect) )
+ {
+ log_debug("send AT command \"%s\" and got reply \"OK\"\n", at);
+ res = ATRES_EXPECT;
+ break;
+ }
+
+ if( error && strstr(buf, error) )
+ {
+ log_debug("send AT command \"%s\" and got reply \"ERROR\"\n", at);
+ res = ATRES_ERROR;
+ break;
+ }
+ }
+
+ if( bytes > 0 )
+ log_trace("AT command reply:%s", buf);
+
+ if( reply && size>0 )
+ {
+ bytes = strlen(buf)>size ? size : strlen(buf);
+ memset(reply, 0, size);
+ strncpy(reply, buf, bytes);
+
+ log_debug("copy out AT command \"%s\" reply message: \n%s", at, reply);
+ }
+
+ return res;
+}
+
+
+/*
+ * Description: Send AT command which will only reply by "OK" or "ERROR", such as AT:
+ * Reply: \r\nOK\r\n
+ * Return Value: 0: OK -X: Failure
+ */
+int send_atcmd_check_ok(comport_t *comport, char *at, unsigned long timeout)
+{
+ int rv;
+
+ if( !comport || !at )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ rv=send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, NULL, 0);
+ if( ATRES_EXPECT == rv )
+ {
+ return 0;
+ }
+ else
+ {
+ return -2;
+ }
+}
+
+
+/*
+ * Description: Send AT command which will reply by a value directly in a single line, such as AT+CGMM:
+ * Reply: \r\nEC20F\r\nOK\r\n
+ *
+ * Return Value: 0: OK -X: Failure
+ */
+int send_atcmd_check_value(comport_t *comport, char *at, unsigned long timeout, char *reply, int size)
+{
+ int rv, len;
+ char buf[ATCMD_REPLY_LEN];
+ char *ptr_start = buf;
+ char *ptr_end;
+
+ if( !comport || !at || !reply || size<=0 )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ rv = send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, buf, ATCMD_REPLY_LEN);
+ if( rv <= 0 )
+ {
+ return -2;
+ }
+
+ /* Skip the echo back command line */
+ if( !strncmp(buf, at, strlen(at)) )
+ {
+ ptr_start=strchr(buf, '\n');
+ if( !ptr_start )
+ {
+ log_error("reply message got wrong\n");
+ return -3;
+ }
+
+ ptr_start++; /* skip '\n' */
+ }
+
+ /* find end reply string "\r\nOK\r\n" */
+ ptr_end = strstr(ptr_start, AT_OKSTR);
+ if( ptr_end )
+ {
+ len = ptr_end - ptr_start;
+ }
+ else
+ {
+ len = strlen(buf) - (ptr_start-buf);
+ }
+
+ memset(reply, 0, size);
+
+ len = len>size ? size : len;
+ memcpy(reply, ptr_start, len);
+
+ return 0;
+}
+
+/*
+ * Description: Parser the $value from $key like "xxx: " line, such as AT+CSQ:
+ * Reply: \r\n+CSQ: 26,99\r\nOK\r\n
+ *
+ * Return Value: 0: OK -X: Failure
+ */
+int parser_request_value(char *buf, char *key, char *value, int size)
+{
+ char *ptr;
+ int i = 0;
+
+ if( !buf || !key || !value || size<=0 )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ ptr = strstr(buf, key);
+ if( !ptr )
+ {
+ log_debug("Not found key \"%s\" in %s\n", key, buf);
+ return -2;
+ }
+
+ ptr=strchr(ptr, ':'); /* found ':' before the value */
+ if( !ptr )
+ {
+ log_debug("Not found ':' before value\n");
+ return -3;
+ }
+ ptr++; /* skip ':' */
+
+ if( *ptr == '\"' ) /* skip " */
+ ptr++;
+
+ memset(value, 0, size);
+ while(*ptr!='\r' && i<size-1)
+ {
+ if( !isspace(*ptr) && *ptr!='\"') /* skip space,\r,\n ... */
+ value[i++] = *ptr;
+ ptr ++;
+ }
+
+ ptr++; /* skip */
+
+ return 0;
+}
+
+/*
+ * Description: Send AT command which will reply by the value with a prefix "+CMD: " line, such as AT+CSQ:
+ * Reply: \r\n+CSQ: 26,99\r\nOK\r\n
+ *
+ * Return Value: 0: OK -X: Failure
+ */
+int send_atcmd_check_request(comport_t *comport, char *at, unsigned long timeout, char *reply, int size)
+{
+ int i = 0;
+ int rv;
+ char buf[ATCMD_REPLY_LEN];
+ char *ptr;
+
+ if( !comport || !at || !reply || size<=0 )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ rv = send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, buf, ATCMD_REPLY_LEN);
+ if( rv <= 0 )
+ {
+ return -2;
+ }
+
+ ptr=strchr(buf, '+'); /* found '+' before the value */
+ if( !ptr )
+ {
+ log_error("reply message got wrong\n");
+ return -3;
+ }
+ ptr++; /* skip '+' */
+
+
+ ptr=strchr(buf, ':'); /* found ':' before the value */
+ if( !ptr )
+ {
+ log_error("reply message got wrong\n");
+ return -3;
+ }
+ ptr++; /* skip ':' */
+
+ if( *ptr == '\"' ) /* skip " */
+ ptr++;
+
+ memset(reply, 0, size);
+ while(*ptr!='\r' && i<size-1)
+ {
+ if( !isspace(*ptr) && *ptr!='\"') /* skip space,\r,\n ... */
+ reply[i++] = *ptr;
+ ptr ++;
+ }
+
+ return 0;
+}
+
diff --git a/project/booster/atcmd.h b/project/booster/atcmd.h
new file mode 100644
index 0000000..e8de8f1
--- /dev/null
+++ b/project/booster/atcmd.h
@@ -0,0 +1,93 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: atcmd.h
+ * Description: This file is lowlevel AT command send and parser API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+
+#ifndef _ATCMD_H_
+#define _ATCMD_H_
+
+#include "comport.h"
+
+/* AT command common reply message max length */
+#define ATCMD_REPLY_LEN 1024 /* Max AT command reply message length */
+#define ATCMD_LEN 256 /* Max AT command length */
+
+/* AT command reply message got expect or error string */
+#define AT_OKSTR "\r\nOK\r\n" /* expect string always be OK */
+#define AT_ERRSTR "\r\nERROR\r\n" /* error string always be ERROR */
+
+/* AT command should be end by $AT_SUFFIX */
+#define AT_SUFFIX "\r\n"
+
+/* send_atcmd)( return value status */
+enum
+{
+ ATRES_ERROR, /* AT command reply got error string, such as "ERROR\r\n" */
+ ATRES_EXPECT, /* AT command reply got expect string, such as "OK\r\n" */
+ ATRES_TIMEOUT, /* AT command not get error/expect string, receive util timeout */
+};
+
+/* Description: this function used to send an AT command from serial port and wait for reply message from
+ * the communication module, and it will return once get expet/error string or timeout.
+ *
+ * Arugments:
+ * $comport: the serial port which connected to GPRS/3G/4G/NB-IoT/WiFi/BLE/Zigbee/LoRa...
+ * $at: the AT command need to be sent, without "\r\n"
+ * $timeout: wait for module reply for AT command timeout value, unit micro seconds(ms)
+ * $exepct: the expect string reply from communication module
+ * $error: the error string reply from communication module
+ * $reply: the module reply message output buffer
+ * $size: the output buffer ($reply) size
+ *
+ * Return value: <0: Function error 0: Got error string 1: Got expect string 2: Receive util timeout
+ *
+ */
+int send_atcmd(comport_t *comport, char *at, unsigned long timeout, char *expect, char *error, char *reply, int size);
+
+
+/*
+ * Description: Send AT command which will only reply by "OK" or "ERROR", such as AT:
+ * Reply: \r\nOK\r\n
+ *
+ * Return Value: 0: OK -X: ERROR
+ */
+int send_atcmd_check_ok(comport_t *comport, char *at, unsigned long timeout);
+
+
+/*
+ * Description: Send AT command which will reply by a value directly in a single line, such as AT+CGMM:
+ * Reply: \r\nEC20F\r\nOK\r\n
+ *
+ * Return Value: 0: OK -X: ERROR
+ */
+int send_atcmd_check_value(comport_t *comport, char *at, unsigned long timeout, char *reply, int size);
+
+/*
+ * Description: Parser the $value from $key like "xxx: " line, such as AT+CSQ:
+ * Reply: \r\n+CSQ: 26,99\r\nOK\r\n
+ *
+ * Return Value: 0: OK -X: Failure
+ */
+int parser_request_value(char *buf, char *key, char *value, int size);
+
+
+/*
+ * Description: Send AT command which will reply by the value with a prefix "+CMD: " line, such as AT+CSQ:
+ * Reply: \r\n+CSQ: 26,99\r\nOK\r\n
+ *
+ * Return Value: 0: OK -X: ERROR
+ */
+int send_atcmd_check_request(comport_t *comport, char *at, unsigned long timeout, char *repy, int size);
+
+
+#endif /* ----- #ifndef _ATCMD_H_ ----- */
+
diff --git a/project/booster/comport.c b/project/booster/comport.c
new file mode 100644
index 0000000..86b3dba
--- /dev/null
+++ b/project/booster/comport.c
@@ -0,0 +1,520 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: comport.c
+ * Description: This file is linux comport common API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#include "comport.h"
+
+#define CONFIG_PRINT_LOGGER
+//#define CONFIG_PRINT_STDOUT
+
+#if ( defined CONFIG_PRINT_LOGGER )
+#include "logger.h"
+#define dbg_print(format,args...) log_error(format, ##args)
+
+#elif ( defined CONFIG_PRINT_STDOUT )
+#define dbg_print(format,args...) printf(format, ##args)
+
+#else
+#define dbg_print(format,args...) do{} while(0);
+#endif
+
+
+static inline void set_settings(comport_t * comport, const char *settings);
+
+/*
+ * description: Open the serial port
+ *
+ * input args: $comport: corresponding comport point
+ * $dev_name: The comport device name path, such as '/dev/ttyS3'
+ * $baudrate: The baudrate, such as 115200
+ * $settings: The databit,parity,stopbit,flowctrl settings, such as '8N1N'
+ *
+ * return value: The comport opened file description, <0 means failure
+ */
+int comport_open(comport_t *comport, const char *devname, long baudrate, const char *settings)
+{
+ int rv = -1;
+ struct termios old_cfg, new_cfg;
+ int old_flags;
+ long tmp;
+
+ if( !comport || !devname )
+ {
+ dbg_print("invalid input arugments\n");
+ return -1;
+ }
+
+ /*+-----------------------+
+ *| open the serial port |
+ *+-----------------------+*/
+
+ memset(comport, 0, sizeof(*comport));
+ strncpy(comport->devname, devname, sizeof(comport->devname));
+ comport->baudrate = baudrate;
+ comport->fd = -1;
+ comport->fragsize = CONFIG_DEF_FRAGSIZE;
+ set_settings(comport, settings);
+
+ if( !strstr(comport->devname, "tty") )
+ {
+ dbg_print("comport device \"%s\" is not tty device\n", comport->devname);
+ return -2;
+ }
+
+ comport->fd = open(comport->devname, O_RDWR | O_NOCTTY | O_NONBLOCK);
+ if( comport->fd<0 )
+ {
+ dbg_print("comport open \"%s\" failed:%s\n", comport->devname, strerror(errno));
+ return -3;
+ }
+
+ if( (-1 != (old_flags = fcntl(comport->fd, F_GETFL, 0)))
+ && (-1 != fcntl(comport->fd, F_SETFL, old_flags & ~O_NONBLOCK)) )
+ {
+ /* Flush input and output */
+ tcflush(comport->fd, TCIOFLUSH);
+ }
+ else
+ {
+ rv = -4;
+ goto CleanUp;
+ }
+
+ if (0 != tcgetattr(comport->fd, &old_cfg))
+ {
+ rv = -5;
+ goto CleanUp;
+ }
+
+
+ /*+-----------------------+
+ *| configure serial port |
+ *+-----------------------+*/
+
+ memset(&new_cfg, 0, sizeof(new_cfg));
+ new_cfg.c_cflag &= ~CSIZE;
+ new_cfg.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
+ new_cfg.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ new_cfg.c_oflag &= ~(OPOST);
+
+ /* Set the data bit */
+ switch (comport->databit)
+ {
+ case 0x07:
+ new_cfg.c_cflag |= CS7;
+ break;
+ case 0x06:
+ new_cfg.c_cflag |= CS6;
+ break;
+ case 0x05:
+ new_cfg.c_cflag |= CS5;
+ break;
+ default:
+ new_cfg.c_cflag |= CS8;
+ break;
+ }
+
+ /* Set the parity */
+ switch (comport->parity)
+ {
+ case 0x01: /* Odd */
+ new_cfg.c_cflag |= (PARENB | PARODD);
+ new_cfg.c_cflag |= (INPCK | ISTRIP);
+ break;
+ case 0x02: /* Even */
+ new_cfg.c_cflag |= PARENB;
+ new_cfg.c_cflag &= ~PARODD;;
+ new_cfg.c_cflag |= (INPCK | ISTRIP);
+ break;
+ case 0x03:
+ new_cfg.c_cflag &= ~PARENB;
+ new_cfg.c_cflag &= ~CSTOPB;
+ break;
+ default:
+ new_cfg.c_cflag &= ~PARENB;
+ }
+
+ /* Set Stop bit */
+ if (0x01 != comport->stopbit)
+ {
+ new_cfg.c_cflag |= CSTOPB;
+ }
+ else
+ {
+ new_cfg.c_cflag &= ~CSTOPB;
+ }
+
+ /* Set flow control */
+ switch (comport->flowctrl)
+ {
+ case 1: /* Software control */
+ case 3:
+ new_cfg.c_cflag &= ~(CRTSCTS);
+ new_cfg.c_iflag |= (IXON | IXOFF);
+ break;
+ case 2: /* Hardware control */
+ new_cfg.c_cflag |= CRTSCTS;
+ new_cfg.c_iflag &= ~(IXON | IXOFF);
+ break;
+ default: /* NONE */
+ new_cfg.c_cflag &= ~(CRTSCTS);
+ new_cfg.c_iflag &= ~(IXON | IXOFF);
+ break;
+ }
+
+ /* Set baudrate */
+ switch (comport->baudrate)
+ {
+ /* Upper is not POSIX(bits/termios-baud.h) */
+ case 4000000:
+ tmp = B4000000;
+ break;
+ case 3500000:
+ tmp = B3500000;
+ break;
+ case 3000000:
+ tmp = B3000000;
+ break;
+ case 2500000:
+ tmp = B2500000;
+ break;
+ case 2000000:
+ tmp = B2000000;
+ break;
+ case 1500000:
+ tmp = B1500000;
+ break;
+ case 1152000:
+ tmp = B1152000;
+ break;
+ case 1000000:
+ tmp = B1000000;
+ break;
+ case 921600:
+ tmp = B921600;
+ break;
+ case 576000:
+ tmp = B576000;
+ break;
+ case 500000:
+ tmp = B500000;
+ break;
+ case 460800:
+ tmp = B460800;
+ break;
+ case 230400:
+ tmp = B230400;
+ break;
+ case 115200:
+ tmp = B115200;
+ break;
+ case 57600:
+ tmp = B57600;
+ break;
+
+ /* Below is POSIX(bits/termios.h) */
+ case 38400:
+ tmp = B38400;
+ break;
+ case 19200:
+ tmp = B19200;
+ break;
+ case 9600:
+ tmp = B9600;
+ break;
+ case 4800:
+ tmp = B4800;
+ break;
+ case 2400:
+ tmp = B2400;
+ break;
+ case 1800:
+ tmp = B1800;
+ break;
+ case 1200:
+ tmp = B1200;
+ break;
+ case 600:
+ tmp = B600;
+ break;
+ case 300:
+ tmp = B300;
+ break;
+ case 200:
+ tmp = B200;
+ break;
+ case 150:
+ tmp = B150;
+ break;
+ case 134:
+ tmp = B134;
+ break;
+ case 110:
+ tmp = B110;
+ break;
+ case 75:
+ tmp = B75;
+ break;
+ case 50:
+ tmp = B50;
+ break;
+ default:
+ tmp = B115200;
+ }
+ cfsetispeed(&new_cfg, tmp);
+ cfsetispeed(&new_cfg, tmp);
+
+ /* Set the Com port timeout settings */
+ new_cfg.c_cc[VMIN] = 0;
+ new_cfg.c_cc[VTIME] = 0;
+
+ tcflush(comport->fd, TCIFLUSH);
+ if (0 != tcsetattr(comport->fd, TCSANOW, &new_cfg))
+ {
+ rv = -6; // Failed to set device com port settings
+ goto CleanUp;
+ }
+
+ rv = comport->fd;
+
+CleanUp:
+ return rv;
+}
+
+
+/*
+ * description: close comport
+ * input args: $comport: corresponding comport point
+ */
+
+void comport_close(comport_t *comport)
+{
+ if( !comport )
+ {
+ dbg_print("invalid input arugments\n");
+ return ;
+ }
+
+ if ( comport->fd >= 0 )
+ {
+ close(comport->fd);
+ }
+
+ comport->fd = -1;
+ return ;
+}
+
+/*
+ * description: write $data_bytes $data to $comport
+ * return value: 0: write ok <0: write failure
+ */
+
+int comport_send(comport_t *comport, char *data, int data_bytes)
+{
+ char *ptr;
+ int left, bytes = 0;
+ int rv = 0;
+
+ if( !comport || !data || data_bytes<=0 )
+ {
+ dbg_print("invalid input arugments\n");
+ return -1;
+ }
+
+ if( comport->fd < 0 )
+ {
+ dbg_print("Serail port not opened\n");
+ return -2;
+ }
+
+ ptr = data;
+ left = data_bytes;
+
+ while( left > 0 )
+ {
+ /* Large data, then slice them to frag and send */
+ bytes = left>comport->fragsize ? comport->fragsize : left;
+
+ rv = write(comport->fd, ptr, bytes);
+ if( rv<0 )
+ {
+ rv = -3;
+ break;
+ }
+
+ left -= rv;
+ ptr += rv;
+ }
+
+ return rv;
+}
+
+
+/*
+ * description: read data from $comport in $timeout <ms> to $buf no more than $buf_size bytes
+ * return value: the actual read data bytes, <0: read failure
+ */
+
+int comport_recv(comport_t *comport, char *buf, int buf_size, unsigned long timeout)
+{
+ fd_set rdfds, exfds;
+ struct timeval to, *to_ptr = NULL;
+ int ret, rv = 0;
+ int bytes = 0;
+
+ if ( !comport || !buf || buf_size<=0 )
+ {
+ dbg_print("invalid input arugments\n");
+ return -1;
+ }
+
+ if ( comport->fd < 0 )
+ {
+ dbg_print("Serail port not opened\n");
+ return -2;
+ }
+
+ memset(buf, 0, buf_size);
+
+ FD_ZERO(&rdfds);
+ FD_ZERO(&exfds);
+ FD_SET(comport->fd, &rdfds);
+ FD_SET(comport->fd, &exfds);
+
+ if( TIMEOUT_NONE != timeout )
+ {
+ to.tv_sec = (time_t) (timeout / 1000);
+ to.tv_usec = (long)(1000 * (timeout % 1000));
+ to_ptr = &to;
+ }
+
+ while( 1 )
+ {
+ /* check got data arrive or not */
+ ret = select(comport->fd+1, &rdfds, 0, &exfds, to_ptr);
+ if( ret<0 )
+ {
+ /* EINTR means catch interrupt signal */
+ dbg_print("comport select() failed: %s\n", strerror(errno));
+ rv = EINTR==errno ? 0 : -3;
+ break;
+ }
+ else if( 0 == ret ) /* timeout */
+ {
+ break;
+ }
+
+ /* read data from comport */
+ ret = read(comport->fd, buf+bytes, buf_size-bytes);
+ if(ret <= 0)
+ {
+ dbg_print("comport read() failed: %s\n", strerror(errno));
+ break;
+ }
+
+ bytes += ret;
+ if( bytes >= buf_size )
+ break;
+
+ /* try to read data in 1ms again, if no data arrive it will break */
+ to.tv_sec = 0;
+ to.tv_usec = 10000;
+ to_ptr = &to;
+ }
+
+ if( !rv )
+ rv = bytes;
+
+ return rv;
+}
+
+
+/**************************************************************************************
+ * Description: Set the comport databit,parity,stopbit,flowctrl into the comport structure
+ * Input Args: comport: the comport_t pointer
+ * settings: The databit/parity/stopbit/flowctrl settings as like "8N1N"
+ * Output Args: NONE
+ * Return Value: NONE
+ *************************************************************************************/
+static inline void set_settings(comport_t * comport, const char *settings)
+{
+ if( !settings || !comport )
+ {
+ dbg_print("invalid input arugments\n");
+ return ;
+ }
+
+ switch (settings[0]) /* data bit */
+ {
+ case '7':
+ comport->databit = 7;
+ break;
+ case '8':
+ default:
+ comport->databit = 8;
+ break;
+ }
+
+ switch (settings[1]) /* parity */
+ {
+ case 'O':
+ case 'o':
+ comport->parity = 1;
+ break;
+ case 'E':
+ case 'e':
+ comport->parity = 2;
+ break;
+ case 'S':
+ case 's':
+ comport->parity = 3;
+ break;
+ case 'N':
+ case 'n':
+ default:
+ comport->parity = 0;
+ break;
+ }
+
+ switch (settings[2]) /* stop bit */
+ {
+ case '0':
+ comport->stopbit = 0;
+ break;
+ case '1':
+ default:
+ comport->stopbit = 1;
+ break;
+ }
+
+ switch (settings[3]) /* flow control */
+ {
+ case 'S':
+ case 's':
+ comport->flowctrl = 1;
+ break;
+ case 'H':
+ case 'h':
+ comport->flowctrl = 2;
+ break;
+ case 'B':
+ case 'b':
+ comport->flowctrl = 3;
+ break;
+ case 'N':
+ case 'n':
+ default:
+ comport->flowctrl = 0;
+ break;
+ }
+}
+
diff --git a/project/booster/comport.h b/project/booster/comport.h
new file mode 100644
index 0000000..8c18f8f
--- /dev/null
+++ b/project/booster/comport.h
@@ -0,0 +1,74 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: comport.c
+ * Description: This file is linux comport common API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#ifndef _COMPORT_H_
+#define _COMPORT_H_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+
+#define CONFIG_DEF_FRAGSIZE 128
+typedef struct comport_s
+{
+ char devname[32];
+ unsigned char databit, parity, stopbit, flowctrl;
+ long baudrate;
+
+ int fd;
+ int fragsize; /* frag size when do large data send */
+} comport_t;
+
+
+/*
+ * description: Open the comport and returned by $comport
+ *
+ * input args: $comport: corresponding comport handler
+ * $devname: The comport device name path, such as '/dev/ttyS3'
+ * $baudrate: The baudrate, such as 115200
+ * $settings: The databit,parity,stopbit,flowctrl settings, such as '8N1N'
+ *
+ * return value: The comport opened file description, <0 means failure
+ */
+extern int comport_open(comport_t *comport, const char *devname, long baudrate, const char *settings);
+
+/*
+ * description: close comport
+ * input args: $comport: corresponding comport handler
+ */
+extern void comport_close(comport_t *comport);
+
+/*
+ * description: write $bytes $data to $comport
+ * return value: 0: write ok <0: write failure
+ */
+extern int comport_send(comport_t *comport, char *data, int data_bytes);
+
+/*
+ * description: read data from $comport in $timeout <ms> to $buf no more than $buf_size bytes
+ * return value: the actual read data bytes, <0: read failure
+ */
+#define TIMEOUT_NONE 0
+extern int comport_recv(comport_t *comport, char *buf, int buf_size, unsigned long timeout);
+
+#endif
diff --git a/project/booster/dictionary.c b/project/booster/dictionary.c
new file mode 100644
index 0000000..0fd5000
--- /dev/null
+++ b/project/booster/dictionary.c
@@ -0,0 +1,381 @@
+/*-------------------------------------------------------------------------*/
+/**
+ @file dictionary.c
+ @author N. Devillard
+ @brief Implements a dictionary for string variables.
+ @url https://github.com/ndevilla/iniparser
+
+ This module implements a simple dictionary object, i.e. a list
+ of string/string associations. This object is useful to store e.g.
+ informations retrieved from a configuration file (ini files).
+*/
+/*--------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------
+ Includes
+ ---------------------------------------------------------------------------*/
+#include "dictionary.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/** Maximum value size for integers and doubles. */
+#define MAXVALSZ 1024
+
+/** Minimal allocated number of entries in a dictionary */
+#define DICTMINSZ 128
+
+/** Invalid key token */
+#define DICT_INVALID_KEY ((char*)-1)
+
+/*---------------------------------------------------------------------------
+ Private functions
+ ---------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Duplicate a string
+ @param s String to duplicate
+ @return Pointer to a newly allocated string, to be freed with free()
+
+ This is a replacement for strdup(). This implementation is provided
+ for systems that do not have it.
+ */
+/*--------------------------------------------------------------------------*/
+static char * xstrdup(const char * s)
+{
+ char * t ;
+ size_t len ;
+ if (!s)
+ return NULL ;
+
+ len = strlen(s) + 1 ;
+ t = (char*) malloc(len) ;
+ if (t) {
+ memcpy(t, s, len) ;
+ }
+ return t ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Double the size of the dictionary
+ @param d Dictionary to grow
+ @return This function returns non-zero in case of failure
+ */
+/*--------------------------------------------------------------------------*/
+static int dictionary_grow(dictionary * d)
+{
+ char ** new_val ;
+ char ** new_key ;
+ unsigned * new_hash ;
+
+ new_val = (char**) calloc(d->size * 2, sizeof *d->val);
+ new_key = (char**) calloc(d->size * 2, sizeof *d->key);
+ new_hash = (unsigned*) calloc(d->size * 2, sizeof *d->hash);
+ if (!new_val || !new_key || !new_hash) {
+ /* An allocation failed, leave the dictionary unchanged */
+ if (new_val)
+ free(new_val);
+ if (new_key)
+ free(new_key);
+ if (new_hash)
+ free(new_hash);
+ return -1 ;
+ }
+ /* Initialize the newly allocated space */
+ memcpy(new_val, d->val, d->size * sizeof(char *));
+ memcpy(new_key, d->key, d->size * sizeof(char *));
+ memcpy(new_hash, d->hash, d->size * sizeof(unsigned));
+ /* Delete previous data */
+ free(d->val);
+ free(d->key);
+ free(d->hash);
+ /* Actually update the dictionary */
+ d->size *= 2 ;
+ d->val = new_val;
+ d->key = new_key;
+ d->hash = new_hash;
+ return 0 ;
+}
+
+/*---------------------------------------------------------------------------
+ Function codes
+ ---------------------------------------------------------------------------*/
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Compute the hash key for a string.
+ @param key Character string to use for key.
+ @return 1 unsigned int on at least 32 bits.
+
+ This hash function has been taken from an Article in Dr Dobbs Journal.
+ This is normally a collision-free function, distributing keys evenly.
+ The key is stored anyway in the struct so that collision can be avoided
+ by comparing the key itself in last resort.
+ */
+/*--------------------------------------------------------------------------*/
+unsigned dictionary_hash(const char * key)
+{
+ size_t len ;
+ unsigned hash ;
+ size_t i ;
+
+ if (!key)
+ return 0 ;
+
+ len = strlen(key);
+ for (hash=0, i=0 ; i<len ; i++) {
+ hash += (unsigned)key[i] ;
+ hash += (hash<<10);
+ hash ^= (hash>>6) ;
+ }
+ hash += (hash <<3);
+ hash ^= (hash >>11);
+ hash += (hash <<15);
+ return hash ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Create a new dictionary object.
+ @param size Optional initial size of the dictionary.
+ @return 1 newly allocated dictionary object.
+
+ This function allocates a new dictionary object of given size and returns
+ it. If you do not know in advance (roughly) the number of entries in the
+ dictionary, give size=0.
+ */
+/*-------------------------------------------------------------------------*/
+dictionary * dictionary_new(size_t size)
+{
+ dictionary * d ;
+
+ /* If no size was specified, allocate space for DICTMINSZ */
+ if (size<DICTMINSZ) size=DICTMINSZ ;
+
+ d = (dictionary*) calloc(1, sizeof *d) ;
+
+ if (d) {
+ d->size = size ;
+ d->val = (char**) calloc(size, sizeof *d->val);
+ d->key = (char**) calloc(size, sizeof *d->key);
+ d->hash = (unsigned*) calloc(size, sizeof *d->hash);
+ }
+ return d ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a dictionary object
+ @param d dictionary object to deallocate.
+ @return void
+
+ Deallocate a dictionary object and all memory associated to it.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_del(dictionary * d)
+{
+ ssize_t i ;
+
+ if (d==NULL) return ;
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]!=NULL)
+ free(d->key[i]);
+ if (d->val[i]!=NULL)
+ free(d->val[i]);
+ }
+ free(d->val);
+ free(d->key);
+ free(d->hash);
+ free(d);
+ return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get a value from a dictionary.
+ @param d dictionary object to search.
+ @param key Key to look for in the dictionary.
+ @param def Default value to return if key not found.
+ @return 1 pointer to internally allocated character string.
+
+ This function locates a key in a dictionary and returns a pointer to its
+ value, or the passed 'def' pointer if no such key can be found in
+ dictionary. The returned character pointer points to data internal to the
+ dictionary object, you should not try to free it or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+const char * dictionary_get(const dictionary * d, const char * key, const char * def)
+{
+ unsigned hash ;
+ ssize_t i ;
+
+ hash = dictionary_hash(key);
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ /* Compare hash */
+ if (hash==d->hash[i]) {
+ /* Compare string, to avoid hash collisions */
+ if (!strcmp(key, d->key[i])) {
+ return d->val[i] ;
+ }
+ }
+ }
+ return def ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set a value in a dictionary.
+ @param d dictionary object to modify.
+ @param key Key to modify or add.
+ @param val Value to add.
+ @return int 0 if Ok, anything else otherwise
+
+ If the given key is found in the dictionary, the associated value is
+ replaced by the provided one. If the key cannot be found in the
+ dictionary, it is added to it.
+
+ It is Ok to provide a NULL value for val, but NULL values for the dictionary
+ or the key are considered as errors: the function will return immediately
+ in such a case.
+
+ Notice that if you dictionary_set a variable to NULL, a call to
+ dictionary_get will return a NULL value: the variable will be found, and
+ its value (NULL) is returned. In other words, setting the variable
+ content to NULL is equivalent to deleting the variable from the
+ dictionary. It is not possible (in this implementation) to have a key in
+ the dictionary without value.
+
+ This function returns non-zero in case of failure.
+ */
+/*--------------------------------------------------------------------------*/
+int dictionary_set(dictionary * d, const char * key, const char * val)
+{
+ ssize_t i ;
+ unsigned hash ;
+
+ if (d==NULL || key==NULL) return -1 ;
+
+ /* Compute hash for this key */
+ hash = dictionary_hash(key) ;
+ /* Find if value is already in dictionary */
+ if (d->n>0) {
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ if (hash==d->hash[i]) { /* Same hash value */
+ if (!strcmp(key, d->key[i])) { /* Same key */
+ /* Found a value: modify and return */
+ if (d->val[i]!=NULL)
+ free(d->val[i]);
+ d->val[i] = (val ? xstrdup(val) : NULL);
+ /* Value has been modified: return */
+ return 0 ;
+ }
+ }
+ }
+ }
+ /* Add a new value */
+ /* See if dictionary needs to grow */
+ if (d->n==d->size) {
+ /* Reached maximum size: reallocate dictionary */
+ if (dictionary_grow(d) != 0)
+ return -1;
+ }
+
+ /* Insert key in the first empty slot. Start at d->n and wrap at
+ d->size. Because d->n < d->size this will necessarily
+ terminate. */
+ for (i=d->n ; d->key[i] ; ) {
+ if(++i == d->size) i = 0;
+ }
+ /* Copy key */
+ d->key[i] = xstrdup(key);
+ d->val[i] = (val ? xstrdup(val) : NULL) ;
+ d->hash[i] = hash;
+ d->n ++ ;
+ return 0 ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a key in a dictionary
+ @param d dictionary object to modify.
+ @param key Key to remove.
+ @return void
+
+ This function deletes a key in a dictionary. Nothing is done if the
+ key cannot be found.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_unset(dictionary * d, const char * key)
+{
+ unsigned hash ;
+ ssize_t i ;
+
+ if (key == NULL || d == NULL) {
+ return;
+ }
+
+ hash = dictionary_hash(key);
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ /* Compare hash */
+ if (hash==d->hash[i]) {
+ /* Compare string, to avoid hash collisions */
+ if (!strcmp(key, d->key[i])) {
+ /* Found key */
+ break ;
+ }
+ }
+ }
+ if (i>=d->size)
+ /* Key not found */
+ return ;
+
+ free(d->key[i]);
+ d->key[i] = NULL ;
+ if (d->val[i]!=NULL) {
+ free(d->val[i]);
+ d->val[i] = NULL ;
+ }
+ d->hash[i] = 0 ;
+ d->n -- ;
+ return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump
+ @param f Opened file pointer.
+ @return void
+
+ Dumps a dictionary onto an opened file pointer. Key pairs are printed out
+ as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
+ output file pointers.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_dump(const dictionary * d, FILE * out)
+{
+ ssize_t i ;
+
+ if (d==NULL || out==NULL) return ;
+ if (d->n<1) {
+ fprintf(out, "empty dictionary\n");
+ return ;
+ }
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]) {
+ fprintf(out, "%20s\t[%s]\n",
+ d->key[i],
+ d->val[i] ? d->val[i] : "UNDEF");
+ }
+ }
+ return ;
+}
diff --git a/project/booster/dictionary.h b/project/booster/dictionary.h
new file mode 100644
index 0000000..87d31d9
--- /dev/null
+++ b/project/booster/dictionary.h
@@ -0,0 +1,174 @@
+
+/*-------------------------------------------------------------------------*/
+/**
+ @file dictionary.h
+ @author N. Devillard
+ @brief Implements a dictionary for string variables.
+ @url https://github.com/ndevilla/iniparser
+
+ This module implements a simple dictionary object, i.e. a list
+ of string/string associations. This object is useful to store e.g.
+ informations retrieved from a configuration file (ini files).
+*/
+/*--------------------------------------------------------------------------*/
+
+#ifndef _DICTIONARY_H_
+#define _DICTIONARY_H_
+
+/*---------------------------------------------------------------------------
+ Includes
+ ---------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*---------------------------------------------------------------------------
+ New types
+ ---------------------------------------------------------------------------*/
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dictionary object
+
+ This object contains a list of string/string associations. Each
+ association is identified by a unique string key. Looking up values
+ in the dictionary is speeded up by the use of a (hopefully collision-free)
+ hash function.
+ */
+/*-------------------------------------------------------------------------*/
+typedef struct _dictionary_ {
+ int n ; /** Number of entries in dictionary */
+ ssize_t size ; /** Storage size */
+ char ** val ; /** List of string values */
+ char ** key ; /** List of string keys */
+ unsigned * hash ; /** List of hash values for keys */
+} dictionary ;
+
+
+/*---------------------------------------------------------------------------
+ Function prototypes
+ ---------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Compute the hash key for a string.
+ @param key Character string to use for key.
+ @return 1 unsigned int on at least 32 bits.
+
+ This hash function has been taken from an Article in Dr Dobbs Journal.
+ This is normally a collision-free function, distributing keys evenly.
+ The key is stored anyway in the struct so that collision can be avoided
+ by comparing the key itself in last resort.
+ */
+/*--------------------------------------------------------------------------*/
+unsigned dictionary_hash(const char * key);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Create a new dictionary object.
+ @param size Optional initial size of the dictionary.
+ @return 1 newly allocated dictionary object.
+
+ This function allocates a new dictionary object of given size and returns
+ it. If you do not know in advance (roughly) the number of entries in the
+ dictionary, give size=0.
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * dictionary_new(size_t size);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a dictionary object
+ @param d dictionary object to deallocate.
+ @return void
+
+ Deallocate a dictionary object and all memory associated to it.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_del(dictionary * vd);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get a value from a dictionary.
+ @param d dictionary object to search.
+ @param key Key to look for in the dictionary.
+ @param def Default value to return if key not found.
+ @return 1 pointer to internally allocated character string.
+
+ This function locates a key in a dictionary and returns a pointer to its
+ value, or the passed 'def' pointer if no such key can be found in
+ dictionary. The returned character pointer points to data internal to the
+ dictionary object, you should not try to free it or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+const char * dictionary_get(const dictionary * d, const char * key, const char * def);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set a value in a dictionary.
+ @param d dictionary object to modify.
+ @param key Key to modify or add.
+ @param val Value to add.
+ @return int 0 if Ok, anything else otherwise
+
+ If the given key is found in the dictionary, the associated value is
+ replaced by the provided one. If the key cannot be found in the
+ dictionary, it is added to it.
+
+ It is Ok to provide a NULL value for val, but NULL values for the dictionary
+ or the key are considered as errors: the function will return immediately
+ in such a case.
+
+ Notice that if you dictionary_set a variable to NULL, a call to
+ dictionary_get will return a NULL value: the variable will be found, and
+ its value (NULL) is returned. In other words, setting the variable
+ content to NULL is equivalent to deleting the variable from the
+ dictionary. It is not possible (in this implementation) to have a key in
+ the dictionary without value.
+
+ This function returns non-zero in case of failure.
+ */
+/*--------------------------------------------------------------------------*/
+int dictionary_set(dictionary * vd, const char * key, const char * val);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a key in a dictionary
+ @param d dictionary object to modify.
+ @param key Key to remove.
+ @return void
+
+ This function deletes a key in a dictionary. Nothing is done if the
+ key cannot be found.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_unset(dictionary * d, const char * key);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump
+ @param f Opened file pointer.
+ @return void
+
+ Dumps a dictionary onto an opened file pointer. Key pairs are printed out
+ as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
+ output file pointers.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_dump(const dictionary * d, FILE * out);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/project/booster/esp32.c b/project/booster/esp32.c
new file mode 100644
index 0000000..c26836c
--- /dev/null
+++ b/project/booster/esp32.c
@@ -0,0 +1,166 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: esp32.c
+ * Description: This file is ESP32 high level logic API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#include "logger.h"
+#include "esp32.h"
+
+int esp32_init_module(comport_t *comport)
+{
+ int rv;
+ char version[256] = {0};
+
+ if( !comport )
+ return -1;
+
+ rv = esp32_reset(comport);
+ if( rv < 0)
+ {
+ log_error("Reset ESP32 WiFi module failed: %d\n", rv);
+ return rv;
+ }
+
+ esp32_set_echo(comport, DISABLE);
+
+ esp32_set_sysstore(comport, ENABLE);
+
+ rv = esp32_get_firmware(comport, version, sizeof(version));
+ if( rv < 0)
+ {
+ log_error("Query ESP32 firmware version failed: %d\n", rv);
+ return rv;
+ }
+
+ log_info("ESP32 firmware version:\n%s\n", version);
+
+ return 0;
+}
+
+int esp32_setup_softap(comport_t *comport, char *ssid, char *pwd)
+{
+ esp32_set_wmode(comport, MODE_SOFTAP, ENABLE);
+
+ esp32_set_ipaddr(comport, MODE_SOFTAP, DEF_SOFTAP_IPADDR, DEF_SOFTAP_IPADDR);
+
+ esp32_set_dhcp(comport, MODE_SOFTAP, ENABLE);
+
+ esp32_set_softap(comport, ssid, pwd, 11, MODE_WPA2PSK);
+
+ return 0;
+}
+
+int esp32_join_network(comport_t *comport, char *ssid, char *pwd)
+{
+ int rv, status = 0;
+ int times=10;
+ char buf[128] = {0};
+
+ if( !comport )
+ return -1;
+
+ esp32_join_status(comport, &status, buf);
+ if( 2==status && !strcmp(buf, ssid) )
+ {
+ log_info("ESP32 connected to \"%s\" already\n", ssid);
+ return 0;
+ }
+
+ esp32_set_wmode(comport, MODE_STATION, ENABLE);
+
+ esp32_set_dhcp(comport, MODE_STATION, ENABLE);
+
+ rv = esp32_connect_ap(comport, ssid, pwd);
+ if( rv < 0 )
+ {
+ log_error("connect to AP \"%s\" failed, rv=%d", ssid, rv);
+ return rv;
+ }
+
+ while(times--)
+ {
+ rv = esp32_join_status(comport, &status, buf);
+ if( 2 == status )
+ {
+ log_info("ESP32 connected to \"%s\" already\n", ssid);
+ return 0;
+ }
+ rv = -3;
+ }
+
+ return rv;
+}
+
+
+int esp32_check_network(comport_t *comport)
+{
+ int rv, times=5;
+ char ip[IP_LEN] = {0};
+ char gateway[IP_LEN] = {0};
+
+ memset(ip, 0, sizeof(ip));
+ memset(gateway, 0, sizeof(gateway));
+ rv = esp32_get_ipaddr(comport, MODE_STATION, ip, gateway);
+ if( rv<0 )
+ return rv;
+
+ if( !strlen(ip) || !strlen(gateway) )
+ return -3;
+
+ log_info("IP address: %s, netmask: %s\n", ip, gateway);
+
+ while( times-- )
+ {
+ if( !(rv=esp32_ping(comport, gateway, 5000)) )
+ {
+ rv = 0;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int esp32_setup_tcp_server(comport_t *comport, int port)
+{
+ int rv;
+
+ rv = esp32_set_socket_mux(comport, ENABLE);
+ if(rv<0)
+ return rv;
+
+ rv = esp32_set_socket_clients(comport, 2);
+ if(rv<0)
+ return rv;
+
+ rv = esp32_set_tcp_server(comport, port);
+ if(rv<0)
+ return rv;
+
+ rv = esp32_set_socket_timeout(comport, 600);
+ if(rv<0)
+ return rv;
+
+ return 0;
+}
+
+int esp32_setup_tcp_client(comport_t *comport, char *host, int port)
+{
+ int rv;
+
+ rv = esp32_set_tcp_client(comport, DISABLE, host, port);
+ if(rv<0)
+ return rv;
+
+
+ return 0;
+}
+
diff --git a/project/booster/esp32.h b/project/booster/esp32.h
new file mode 100644
index 0000000..deb8450
--- /dev/null
+++ b/project/booster/esp32.h
@@ -0,0 +1,35 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: esp32.h
+ * Description: This file is ESP32 high level logic API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#ifndef _ESP32_H_
+#define _ESP32_H_
+
+#include "at-esp32.h"
+
+#define DEF_SOFTAP_IPADDR "192.168.8.1"
+#define DEF_SOFTAP_SSID "Router_ESP32"
+#define DEF_SOFTAP_PWD "12345678"
+
+extern int esp32_init_module(comport_t *comport);
+
+extern int esp32_setup_softap(comport_t *comport, char *ssid, char *pwd);
+
+extern int esp32_join_network(comport_t *comport, char *ssid, char *pwd);
+
+extern int esp32_check_network(comport_t *comport);
+
+extern int esp32_setup_tcp_server(comport_t *comport, int port);
+
+extern int esp32_setup_tcp_client(comport_t *comport, char *host, int port);
+
+#endif /* ----- #ifndef _ESP32_H_ ----- */
diff --git a/project/booster/iniparser.c b/project/booster/iniparser.c
new file mode 100644
index 0000000..3a446e5
--- /dev/null
+++ b/project/booster/iniparser.c
@@ -0,0 +1,837 @@
+
+/*-------------------------------------------------------------------------*/
+/**
+ @file iniparser.c
+ @author N. Devillard
+ @brief Parser for ini files.
+ @url https://github.com/ndevilla/iniparser
+*/
+/*--------------------------------------------------------------------------*/
+/*---------------------------- Includes ------------------------------------*/
+#include <ctype.h>
+#include <stdarg.h>
+#include "iniparser.h"
+
+/*---------------------------- Defines -------------------------------------*/
+#define ASCIILINESZ (1024)
+#define INI_INVALID_KEY ((char*)-1)
+
+/*---------------------------------------------------------------------------
+ Private to this module
+ ---------------------------------------------------------------------------*/
+/**
+ * This enum stores the status for each parsed line (internal use only).
+ */
+typedef enum _line_status_ {
+ LINE_UNPROCESSED,
+ LINE_ERROR,
+ LINE_EMPTY,
+ LINE_COMMENT,
+ LINE_SECTION,
+ LINE_VALUE
+} line_status ;
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Convert a string to lowercase.
+ @param in String to convert.
+ @param out Output buffer.
+ @param len Size of the out buffer.
+ @return ptr to the out buffer or NULL if an error occured.
+
+ This function convert a string into lowercase.
+ At most len - 1 elements of the input string will be converted.
+ */
+/*--------------------------------------------------------------------------*/
+static const char * strlwc(const char * in, char *out, unsigned len)
+{
+ unsigned i ;
+
+ if (in==NULL || out == NULL || len==0) return NULL ;
+ i=0 ;
+ while (in[i] != '\0' && i < len-1) {
+ out[i] = (char)tolower((int)in[i]);
+ i++ ;
+ }
+ out[i] = '\0';
+ return out ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Duplicate a string
+ @param s String to duplicate
+ @return Pointer to a newly allocated string, to be freed with free()
+
+ This is a replacement for strdup(). This implementation is provided
+ for systems that do not have it.
+ */
+/*--------------------------------------------------------------------------*/
+static char * xstrdup(const char * s)
+{
+ char * t ;
+ size_t len ;
+ if (!s)
+ return NULL ;
+
+ len = strlen(s) + 1 ;
+ t = (char*) malloc(len) ;
+ if (t) {
+ memcpy(t, s, len) ;
+ }
+ return t ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Remove blanks at the beginning and the end of a string.
+ @param str String to parse and alter.
+ @return unsigned New size of the string.
+ */
+/*--------------------------------------------------------------------------*/
+static unsigned strstrip(char * s)
+{
+ char *last = NULL ;
+ char *dest = s;
+
+ if (s==NULL) return 0;
+
+ last = s + strlen(s);
+ while (isspace((int)*s) && *s) s++;
+ while (last > s) {
+ if (!isspace((int)*(last-1)))
+ break ;
+ last -- ;
+ }
+ *last = (char)0;
+
+ memmove(dest,s,last - s + 1);
+ return last - s;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Default error callback for iniparser: wraps `fprintf(stderr, ...)`.
+ */
+/*--------------------------------------------------------------------------*/
+static int default_error_callback(const char *format, ...)
+{
+ int ret;
+ va_list argptr;
+ va_start(argptr, format);
+ ret = vfprintf(stderr, format, argptr);
+ va_end(argptr);
+ return ret;
+}
+
+static int (*iniparser_error_callback)(const char*, ...) = default_error_callback;
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Configure a function to receive the error messages.
+ @param errback Function to call.
+
+ By default, the error will be printed on stderr. If a null pointer is passed
+ as errback the error callback will be switched back to default.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_set_error_callback(int (*errback)(const char *, ...))
+{
+ if (errback) {
+ iniparser_error_callback = errback;
+ } else {
+ iniparser_error_callback = default_error_callback;
+ }
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get number of sections in a dictionary
+ @param d Dictionary to examine
+ @return int Number of sections found in dictionary
+
+ This function returns the number of sections found in a dictionary.
+ The test to recognize sections is done on the string stored in the
+ dictionary: a section name is given as "section" whereas a key is
+ stored as "section:key", thus the test looks for entries that do not
+ contain a colon.
+
+ This clearly fails in the case a section name contains a colon, but
+ this should simply be avoided.
+
+ This function returns -1 in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getnsec(const dictionary * d)
+{
+ int i ;
+ int nsec ;
+
+ if (d==NULL) return -1 ;
+ nsec=0 ;
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ if (strchr(d->key[i], ':')==NULL) {
+ nsec ++ ;
+ }
+ }
+ return nsec ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get name for section n in a dictionary.
+ @param d Dictionary to examine
+ @param n Section number (from 0 to nsec-1).
+ @return Pointer to char string
+
+ This function locates the n-th section in a dictionary and returns
+ its name as a pointer to a string statically allocated inside the
+ dictionary. Do not free or modify the returned string!
+
+ This function returns NULL in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+const char * iniparser_getsecname(const dictionary * d, int n)
+{
+ int i ;
+ int foundsec ;
+
+ if (d==NULL || n<0) return NULL ;
+ foundsec=0 ;
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ if (strchr(d->key[i], ':')==NULL) {
+ foundsec++ ;
+ if (foundsec>n)
+ break ;
+ }
+ }
+ if (foundsec<=n) {
+ return NULL ;
+ }
+ return d->key[i] ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump.
+ @param f Opened file pointer to dump to.
+ @return void
+
+ This function prints out the contents of a dictionary, one element by
+ line, onto the provided file pointer. It is OK to specify @c stderr
+ or @c stdout as output files. This function is meant for debugging
+ purposes mostly.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump(const dictionary * d, FILE * f)
+{
+ int i ;
+
+ if (d==NULL || f==NULL) return ;
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ if (d->val[i]!=NULL) {
+ fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
+ } else {
+ fprintf(f, "[%s]=UNDEF\n", d->key[i]);
+ }
+ }
+ return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Save a dictionary to a loadable ini file
+ @param d Dictionary to dump
+ @param f Opened file pointer to dump to
+ @return void
+
+ This function dumps a given dictionary into a loadable ini file.
+ It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump_ini(const dictionary * d, FILE * f)
+{
+ int i ;
+ int nsec ;
+ const char * secname ;
+
+ if (d==NULL || f==NULL) return ;
+
+ nsec = iniparser_getnsec(d);
+ if (nsec<1) {
+ /* No section in file: dump all keys as they are */
+ for (i=0 ; i<d->size ; i++) {
+ if (d->key[i]==NULL)
+ continue ;
+ fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
+ }
+ return ;
+ }
+ for (i=0 ; i<nsec ; i++) {
+ secname = iniparser_getsecname(d, i) ;
+ iniparser_dumpsection_ini(d, secname, f);
+ }
+ fprintf(f, "\n");
+ return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Save a dictionary section to a loadable ini file
+ @param d Dictionary to dump
+ @param s Section name of dictionary to dump
+ @param f Opened file pointer to dump to
+ @return void
+
+ This function dumps a given section of a given dictionary into a loadable ini
+ file. It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f)
+{
+ int j ;
+ char keym[ASCIILINESZ+1];
+ int seclen ;
+
+ if (d==NULL || f==NULL) return ;
+ if (! iniparser_find_entry(d, s)) return ;
+
+ seclen = (int)strlen(s);
+ fprintf(f, "\n[%s]\n", s);
+ sprintf(keym, "%s:", s);
+ for (j=0 ; j<d->size ; j++) {
+ if (d->key[j]==NULL)
+ continue ;
+ if (!strncmp(d->key[j], keym, seclen+1)) {
+ fprintf(f,
+ "%-30s = %s\n",
+ d->key[j]+seclen+1,
+ d->val[j] ? d->val[j] : "");
+ }
+ }
+ fprintf(f, "\n");
+ return ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the number of keys in a section of a dictionary.
+ @param d Dictionary to examine
+ @param s Section name of dictionary to examine
+ @return Number of keys in section
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getsecnkeys(const dictionary * d, const char * s)
+{
+ int seclen, nkeys ;
+ char keym[ASCIILINESZ+1];
+ int j ;
+
+ nkeys = 0;
+
+ if (d==NULL) return nkeys;
+ if (! iniparser_find_entry(d, s)) return nkeys;
+
+ seclen = (int)strlen(s);
+ strlwc(s, keym, sizeof(keym));
+ keym[seclen] = ':';
+
+ for (j=0 ; j<d->size ; j++) {
+ if (d->key[j]==NULL)
+ continue ;
+ if (!strncmp(d->key[j], keym, seclen+1))
+ nkeys++;
+ }
+
+ return nkeys;
+
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the number of keys in a section of a dictionary.
+ @param d Dictionary to examine
+ @param s Section name of dictionary to examine
+ @param keys Already allocated array to store the keys in
+ @return The pointer passed as `keys` argument or NULL in case of error
+
+ This function queries a dictionary and finds all keys in a given section.
+ The keys argument should be an array of pointers which size has been
+ determined by calling `iniparser_getsecnkeys` function prior to this one.
+
+ Each pointer in the returned char pointer-to-pointer is pointing to
+ a string allocated in the dictionary; do not free or modify them.
+ */
+/*--------------------------------------------------------------------------*/
+const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys)
+{
+ int i, j, seclen ;
+ char keym[ASCIILINESZ+1];
+
+ if (d==NULL || keys==NULL) return NULL;
+ if (! iniparser_find_entry(d, s)) return NULL;
+
+ seclen = (int)strlen(s);
+ strlwc(s, keym, sizeof(keym));
+ keym[seclen] = ':';
+
+ i = 0;
+
+ for (j=0 ; j<d->size ; j++) {
+ if (d->key[j]==NULL)
+ continue ;
+ if (!strncmp(d->key[j], keym, seclen+1)) {
+ keys[i] = d->key[j];
+ i++;
+ }
+ }
+
+ return keys;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param def Default value to return if key not found.
+ @return pointer to statically allocated character string
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the pointer passed as 'def' is returned.
+ The returned char pointer is pointing to a string allocated in
+ the dictionary, do not free or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+const char * iniparser_getstring(const dictionary * d, const char * key, const char * def)
+{
+ const char * lc_key ;
+ const char * sval ;
+ char tmp_str[ASCIILINESZ+1];
+
+ if (d==NULL || key==NULL)
+ return def ;
+
+ lc_key = strlwc(key, tmp_str, sizeof(tmp_str));
+ sval = dictionary_get(d, lc_key, def);
+ return sval ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to an long int
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return long integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ Supported values for integers include the usual C notation
+ so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
+ are supported. Examples:
+
+ "42" -> 42
+ "042" -> 34 (octal -> decimal)
+ "0x42" -> 66 (hexa -> decimal)
+
+ Warning: the conversion may overflow in various ways. Conversion is
+ totally outsourced to strtol(), see the associated man page for overflow
+ handling.
+
+ Credits: Thanks to A. Becker for suggesting strtol()
+ */
+/*--------------------------------------------------------------------------*/
+long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound)
+{
+ const char * str ;
+
+ str = iniparser_getstring(d, key, INI_INVALID_KEY);
+ if (str==INI_INVALID_KEY) return notfound ;
+ return strtol(str, NULL, 0);
+}
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to an int
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ Supported values for integers include the usual C notation
+ so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
+ are supported. Examples:
+
+ "42" -> 42
+ "042" -> 34 (octal -> decimal)
+ "0x42" -> 66 (hexa -> decimal)
+
+ Warning: the conversion may overflow in various ways. Conversion is
+ totally outsourced to strtol(), see the associated man page for overflow
+ handling.
+
+ Credits: Thanks to A. Becker for suggesting strtol()
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getint(const dictionary * d, const char * key, int notfound)
+{
+ return (int)iniparser_getlongint(d, key, notfound);
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a double
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return double
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+ */
+/*--------------------------------------------------------------------------*/
+double iniparser_getdouble(const dictionary * d, const char * key, double notfound)
+{
+ const char * str ;
+
+ str = iniparser_getstring(d, key, INI_INVALID_KEY);
+ if (str==INI_INVALID_KEY) return notfound ;
+ return atof(str);
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a boolean
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ A true boolean is found if one of the following is matched:
+
+ - A string starting with 'y'
+ - A string starting with 'Y'
+ - A string starting with 't'
+ - A string starting with 'T'
+ - A string starting with '1'
+
+ A false boolean is found if one of the following is matched:
+
+ - A string starting with 'n'
+ - A string starting with 'N'
+ - A string starting with 'f'
+ - A string starting with 'F'
+ - A string starting with '0'
+
+ The notfound value returned if no boolean is identified, does not
+ necessarily have to be 0 or 1.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getboolean(const dictionary * d, const char * key, int notfound)
+{
+ int ret ;
+ const char * c ;
+
+ c = iniparser_getstring(d, key, INI_INVALID_KEY);
+ if (c==INI_INVALID_KEY) return notfound ;
+ if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') {
+ ret = 1 ;
+ } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') {
+ ret = 0 ;
+ } else {
+ ret = notfound ;
+ }
+ return ret;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Finds out if a given entry exists in a dictionary
+ @param ini Dictionary to search
+ @param entry Name of the entry to look for
+ @return integer 1 if entry exists, 0 otherwise
+
+ Finds out if a given entry exists in the dictionary. Since sections
+ are stored as keys with NULL associated values, this is the only way
+ of querying for the presence of sections in a dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_find_entry(const dictionary * ini, const char * entry)
+{
+ int found=0 ;
+ if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) {
+ found = 1 ;
+ }
+ return found ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set an entry in a dictionary.
+ @param ini Dictionary to modify.
+ @param entry Entry to modify (entry name)
+ @param val New value to associate to the entry.
+ @return int 0 if Ok, -1 otherwise.
+
+ If the given entry can be found in the dictionary, it is modified to
+ contain the provided value. If it cannot be found, the entry is created.
+ It is Ok to set val to NULL.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_set(dictionary * ini, const char * entry, const char * val)
+{
+ char tmp_str[ASCIILINESZ+1];
+ return dictionary_set(ini, strlwc(entry, tmp_str, sizeof(tmp_str)), val) ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete an entry in a dictionary
+ @param ini Dictionary to modify
+ @param entry Entry to delete (entry name)
+ @return void
+
+ If the given entry can be found, it is deleted from the dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_unset(dictionary * ini, const char * entry)
+{
+ char tmp_str[ASCIILINESZ+1];
+ dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str)));
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Load a single line from an INI file
+ @param input_line Input line, may be concatenated multi-line input
+ @param section Output space to store section
+ @param key Output space to store key
+ @param value Output space to store value
+ @return line_status value
+ */
+/*--------------------------------------------------------------------------*/
+static line_status iniparser_line(
+ const char * input_line,
+ char * section,
+ char * key,
+ char * value)
+{
+ line_status sta ;
+ char * line = NULL;
+ size_t len ;
+
+ line = xstrdup(input_line);
+ len = strstrip(line);
+
+ sta = LINE_UNPROCESSED ;
+ if (len<1) {
+ /* Empty line */
+ sta = LINE_EMPTY ;
+ } else if (line[0]=='#' || line[0]==';') {
+ /* Comment line */
+ sta = LINE_COMMENT ;
+ } else if (line[0]=='[' && line[len-1]==']') {
+ /* Section name */
+ sscanf(line, "[%[^]]", section);
+ strstrip(section);
+ strlwc(section, section, len);
+ sta = LINE_SECTION ;
+ } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
+ || sscanf (line, "%[^=] = '%[^\']'", key, value) == 2) {
+ /* Usual key=value with quotes, with or without comments */
+ strstrip(key);
+ strlwc(key, key, len);
+ /* Don't strip spaces from values surrounded with quotes */
+ sta = LINE_VALUE ;
+ } else if (sscanf (line, "%[^=] = %[^;#]", key, value) == 2) {
+ /* Usual key=value without quotes, with or without comments */
+ strstrip(key);
+ strlwc(key, key, len);
+ strstrip(value);
+ /*
+ * sscanf cannot handle '' or "" as empty values
+ * this is done here
+ */
+ if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
+ value[0]=0 ;
+ }
+ sta = LINE_VALUE ;
+ } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
+ || sscanf(line, "%[^=] %[=]", key, value) == 2) {
+ /*
+ * Special cases:
+ * key=
+ * key=;
+ * key=#
+ */
+ strstrip(key);
+ strlwc(key, key, len);
+ value[0]=0 ;
+ sta = LINE_VALUE ;
+ } else {
+ /* Generate syntax error */
+ sta = LINE_ERROR ;
+ }
+
+ free(line);
+ return sta ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Parse an ini file and return an allocated dictionary object
+ @param ininame Name of the ini file to read.
+ @return Pointer to newly allocated dictionary
+
+ This is the parser for ini files. This function is called, providing
+ the name of the file to be read. It returns a dictionary object that
+ should not be accessed directly, but through accessor functions
+ instead.
+
+ The returned dictionary must be freed using iniparser_freedict().
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * iniparser_load(const char * ininame)
+{
+ FILE * in ;
+
+ char line [ASCIILINESZ+1] ;
+ char section [ASCIILINESZ+1] ;
+ char key [ASCIILINESZ+1] ;
+ char tmp [(ASCIILINESZ * 2) + 2] ;
+ char val [ASCIILINESZ+1] ;
+
+ int last=0 ;
+ int len ;
+ int lineno=0 ;
+ int errs=0;
+ int mem_err=0;
+
+ dictionary * dict ;
+
+ if ((in=fopen(ininame, "r"))==NULL) {
+ iniparser_error_callback("iniparser: cannot open %s\n", ininame);
+ return NULL ;
+ }
+
+ dict = dictionary_new(0) ;
+ if (!dict) {
+ fclose(in);
+ return NULL ;
+ }
+
+ memset(line, 0, ASCIILINESZ);
+ memset(section, 0, ASCIILINESZ);
+ memset(key, 0, ASCIILINESZ);
+ memset(val, 0, ASCIILINESZ);
+ last=0 ;
+
+ while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
+ lineno++ ;
+ len = (int)strlen(line)-1;
+ if (len<=0)
+ continue;
+ /* Safety check against buffer overflows */
+ if (line[len]!='\n' && !feof(in)) {
+ iniparser_error_callback(
+ "iniparser: input line too long in %s (%d)\n",
+ ininame,
+ lineno);
+ dictionary_del(dict);
+ fclose(in);
+ return NULL ;
+ }
+ /* Get rid of \n and spaces at end of line */
+ while ((len>=0) &&
+ ((line[len]=='\n') || (isspace(line[len])))) {
+ line[len]=0 ;
+ len-- ;
+ }
+ if (len < 0) { /* Line was entirely \n and/or spaces */
+ len = 0;
+ }
+ /* Detect multi-line */
+ if (line[len]=='\\') {
+ /* Multi-line value */
+ last=len ;
+ continue ;
+ } else {
+ last=0 ;
+ }
+ switch (iniparser_line(line, section, key, val)) {
+ case LINE_EMPTY:
+ case LINE_COMMENT:
+ break ;
+
+ case LINE_SECTION:
+ mem_err = dictionary_set(dict, section, NULL);
+ break ;
+
+ case LINE_VALUE:
+ sprintf(tmp, "%s:%s", section, key);
+ mem_err = dictionary_set(dict, tmp, val);
+ break ;
+
+ case LINE_ERROR:
+ iniparser_error_callback(
+ "iniparser: syntax error in %s (%d):\n-> %s\n",
+ ininame,
+ lineno,
+ line);
+ errs++ ;
+ break;
+
+ default:
+ break ;
+ }
+ memset(line, 0, ASCIILINESZ);
+ last=0;
+ if (mem_err<0) {
+ iniparser_error_callback("iniparser: memory allocation failure\n");
+ break ;
+ }
+ }
+ if (errs) {
+ dictionary_del(dict);
+ dict = NULL ;
+ }
+ fclose(in);
+ return dict ;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Free all memory associated to an ini dictionary
+ @param d Dictionary to free
+ @return void
+
+ Free all memory associated to an ini dictionary.
+ It is mandatory to call this function before the dictionary object
+ gets out of the current context.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_freedict(dictionary * d)
+{
+ dictionary_del(d);
+}
diff --git a/project/booster/iniparser.h b/project/booster/iniparser.h
new file mode 100644
index 0000000..984f4b2
--- /dev/null
+++ b/project/booster/iniparser.h
@@ -0,0 +1,359 @@
+
+/*-------------------------------------------------------------------------*/
+/**
+ @file iniparser.h
+ @author N. Devillard
+ @brief Parser for ini files.
+ @url https://github.com/ndevilla/iniparser
+*/
+/*--------------------------------------------------------------------------*/
+
+#ifndef _INIPARSER_H_
+#define _INIPARSER_H_
+
+/*---------------------------------------------------------------------------
+ Includes
+ ---------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * The following #include is necessary on many Unixes but not Linux.
+ * It is not needed for Windows platforms.
+ * Uncomment it if needed.
+ */
+/* #include <unistd.h> */
+
+#include "dictionary.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Configure a function to receive the error messages.
+ @param errback Function to call.
+
+ By default, the error will be printed on stderr. If a null pointer is passed
+ as errback the error callback will be switched back to default.
+ */
+/*--------------------------------------------------------------------------*/
+
+void iniparser_set_error_callback(int (*errback)(const char *, ...));
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get number of sections in a dictionary
+ @param d Dictionary to examine
+ @return int Number of sections found in dictionary
+
+ This function returns the number of sections found in a dictionary.
+ The test to recognize sections is done on the string stored in the
+ dictionary: a section name is given as "section" whereas a key is
+ stored as "section:key", thus the test looks for entries that do not
+ contain a colon.
+
+ This clearly fails in the case a section name contains a colon, but
+ this should simply be avoided.
+
+ This function returns -1 in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+
+int iniparser_getnsec(const dictionary * d);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get name for section n in a dictionary.
+ @param d Dictionary to examine
+ @param n Section number (from 0 to nsec-1).
+ @return Pointer to char string
+
+ This function locates the n-th section in a dictionary and returns
+ its name as a pointer to a string statically allocated inside the
+ dictionary. Do not free or modify the returned string!
+
+ This function returns NULL in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+
+const char * iniparser_getsecname(const dictionary * d, int n);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Save a dictionary to a loadable ini file
+ @param d Dictionary to dump
+ @param f Opened file pointer to dump to
+ @return void
+
+ This function dumps a given dictionary into a loadable ini file.
+ It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+
+void iniparser_dump_ini(const dictionary * d, FILE * f);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Save a dictionary section to a loadable ini file
+ @param d Dictionary to dump
+ @param s Section name of dictionary to dump
+ @param f Opened file pointer to dump to
+ @return void
+
+ This function dumps a given section of a given dictionary into a loadable ini
+ file. It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+
+void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump.
+ @param f Opened file pointer to dump to.
+ @return void
+
+ This function prints out the contents of a dictionary, one element by
+ line, onto the provided file pointer. It is OK to specify @c stderr
+ or @c stdout as output files. This function is meant for debugging
+ purposes mostly.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump(const dictionary * d, FILE * f);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the number of keys in a section of a dictionary.
+ @param d Dictionary to examine
+ @param s Section name of dictionary to examine
+ @return Number of keys in section
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getsecnkeys(const dictionary * d, const char * s);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the number of keys in a section of a dictionary.
+ @param d Dictionary to examine
+ @param s Section name of dictionary to examine
+ @param keys Already allocated array to store the keys in
+ @return The pointer passed as `keys` argument or NULL in case of error
+
+ This function queries a dictionary and finds all keys in a given section.
+ The keys argument should be an array of pointers which size has been
+ determined by calling `iniparser_getsecnkeys` function prior to this one.
+
+ Each pointer in the returned char pointer-to-pointer is pointing to
+ a string allocated in the dictionary; do not free or modify them.
+ */
+/*--------------------------------------------------------------------------*/
+const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param def Default value to return if key not found.
+ @return pointer to statically allocated character string
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the pointer passed as 'def' is returned.
+ The returned char pointer is pointing to a string allocated in
+ the dictionary, do not free or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+const char * iniparser_getstring(const dictionary * d, const char * key, const char * def);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to an int
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ Supported values for integers include the usual C notation
+ so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
+ are supported. Examples:
+
+ - "42" -> 42
+ - "042" -> 34 (octal -> decimal)
+ - "0x42" -> 66 (hexa -> decimal)
+
+ Warning: the conversion may overflow in various ways. Conversion is
+ totally outsourced to strtol(), see the associated man page for overflow
+ handling.
+
+ Credits: Thanks to A. Becker for suggesting strtol()
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getint(const dictionary * d, const char * key, int notfound);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to an long int
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ Supported values for integers include the usual C notation
+ so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
+ are supported. Examples:
+
+ - "42" -> 42
+ - "042" -> 34 (octal -> decimal)
+ - "0x42" -> 66 (hexa -> decimal)
+
+ Warning: the conversion may overflow in various ways. Conversion is
+ totally outsourced to strtol(), see the associated man page for overflow
+ handling.
+ */
+/*--------------------------------------------------------------------------*/
+long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a double
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return double
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+ */
+/*--------------------------------------------------------------------------*/
+double iniparser_getdouble(const dictionary * d, const char * key, double notfound);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a boolean
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ A true boolean is found if one of the following is matched:
+
+ - A string starting with 'y'
+ - A string starting with 'Y'
+ - A string starting with 't'
+ - A string starting with 'T'
+ - A string starting with '1'
+
+ A false boolean is found if one of the following is matched:
+
+ - A string starting with 'n'
+ - A string starting with 'N'
+ - A string starting with 'f'
+ - A string starting with 'F'
+ - A string starting with '0'
+
+ The notfound value returned if no boolean is identified, does not
+ necessarily have to be 0 or 1.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getboolean(const dictionary * d, const char * key, int notfound);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set an entry in a dictionary.
+ @param ini Dictionary to modify.
+ @param entry Entry to modify (entry name)
+ @param val New value to associate to the entry.
+ @return int 0 if Ok, -1 otherwise.
+
+ If the given entry can be found in the dictionary, it is modified to
+ contain the provided value. If it cannot be found, the entry is created.
+ It is Ok to set val to NULL.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_set(dictionary * ini, const char * entry, const char * val);
+
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete an entry in a dictionary
+ @param ini Dictionary to modify
+ @param entry Entry to delete (entry name)
+ @return void
+
+ If the given entry can be found, it is deleted from the dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_unset(dictionary * ini, const char * entry);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Finds out if a given entry exists in a dictionary
+ @param ini Dictionary to search
+ @param entry Name of the entry to look for
+ @return integer 1 if entry exists, 0 otherwise
+
+ Finds out if a given entry exists in the dictionary. Since sections
+ are stored as keys with NULL associated values, this is the only way
+ of querying for the presence of sections in a dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_find_entry(const dictionary * ini, const char * entry) ;
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Parse an ini file and return an allocated dictionary object
+ @param ininame Name of the ini file to read.
+ @return Pointer to newly allocated dictionary
+
+ This is the parser for ini files. This function is called, providing
+ the name of the file to be read. It returns a dictionary object that
+ should not be accessed directly, but through accessor functions
+ instead.
+
+ The returned dictionary must be freed using iniparser_freedict().
+ */
+/*--------------------------------------------------------------------------*/
+dictionary * iniparser_load(const char * ininame);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Free all memory associated to an ini dictionary
+ @param d Dictionary to free
+ @return void
+
+ Free all memory associated to an ini dictionary.
+ It is mandatory to call this function before the dictionary object
+ gets out of the current context.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_freedict(dictionary * d);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/project/booster/list.h b/project/booster/list.h
new file mode 100644
index 0000000..ffd505e
--- /dev/null
+++ b/project/booster/list.h
@@ -0,0 +1,723 @@
+/*********************************************************************************
+ * Copyright: (C) 2020 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: list.h
+ * Description: This file is copied from Linux kernel, which provide link list API.
+ *
+ * Version: 1.0.0(08/09/2020)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "08/09/2020 02:24:34 AM"
+ *
+ ********************************************************************************/
+
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#include <linux/stddef.h>
+
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#undef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+
+/*
+ * Architectures might want to move the poison pointer offset
+ * into some well-recognized area such as 0xdead000000000000,
+ * that is also not mappable by user-space exploits:
+ */
+#ifdef CONFIG_ILLEGAL_POINTER_VALUE
+# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
+#else
+# define POISON_POINTER_DELTA 0
+#endif
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)
+#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)
+
+#ifndef ARCH_HAS_PREFETCH
+#define ARCH_HAS_PREFETCH
+static inline void prefetch(const void *x) {;}
+#endif
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+static inline void INIT_LIST_HEAD(struct list_head *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head *prev, struct list_head *next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty() on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->next = LIST_POISON1;
+ entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_replace - replace old entry by new one
+ * @old : the element to be replaced
+ * @new : the new element to insert
+ *
+ * If @old was empty, it will be overwritten.
+ */
+static inline void list_replace(struct list_head *old,
+ struct list_head *new)
+{
+ new->next = old->next;
+ new->next->prev = new;
+ new->prev = old->prev;
+ new->prev->next = new;
+}
+
+static inline void list_replace_init(struct list_head *old,
+ struct list_head *new)
+{
+ list_replace(old, new);
+ INIT_LIST_HEAD(old);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+ struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add_tail(list, head);
+}
+
+/**
+ * list_is_last - tests whether @list is the last entry in list @head
+ * @list: the entry to test
+ * @head: the head of the list
+ */
+static inline int list_is_last(const struct list_head *list,
+ const struct list_head *head)
+{
+ return list->next == head;
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+ return head->next == head;
+}
+
+/**
+ * list_empty_careful - tests whether a list is empty and not being modified
+ * @head: the list to test
+ *
+ * Description:
+ * tests whether a list is empty _and_ checks that no other CPU might be
+ * in the process of modifying either member (next or prev)
+ *
+ * NOTE: using list_empty_careful() without synchronization
+ * can only be safe if the only activity that can happen
+ * to the list entry is list_del_init(). Eg. it cannot be used
+ * if another CPU could re-list_add() it.
+ */
+static inline int list_empty_careful(const struct list_head *head)
+{
+ struct list_head *next = head->next;
+ return (next == head) && (next == head->prev);
+}
+
+/**
+ * list_is_singular - tests whether a list has just one entry.
+ * @head: the list to test.
+ */
+static inline int list_is_singular(const struct list_head *head)
+{
+ return !list_empty(head) && (head->next == head->prev);
+}
+
+static inline void __list_cut_position(struct list_head *list,
+ struct list_head *head, struct list_head *entry)
+{
+ struct list_head *new_first = entry->next;
+ list->next = head->next;
+ list->next->prev = list;
+ list->prev = entry;
+ entry->next = list;
+ head->next = new_first;
+ new_first->prev = head;
+}
+
+/**
+ * list_cut_position - cut a list into two
+ * @list: a new list to add all removed entries
+ * @head: a list with entries
+ * @entry: an entry within head, could be the head itself
+ * and if so we won't cut the list
+ *
+ * This helper moves the initial part of @head, up to and
+ * including @entry, from @head to @list. You should
+ * pass on @entry an element you know is on @head. @list
+ * should be an empty list or a list you do not care about
+ * losing its data.
+ *
+ */
+static inline void list_cut_position(struct list_head *list,
+ struct list_head *head, struct list_head *entry)
+{
+ if (list_empty(head))
+ return;
+ if (list_is_singular(head) &&
+ (head->next != entry && head != entry))
+ return;
+ if (entry == head)
+ INIT_LIST_HEAD(list);
+ else
+ __list_cut_position(list, head, entry);
+}
+
+static inline void __list_splice(const struct list_head *list,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ struct list_head *first = list->next;
+ struct list_head *last = list->prev;
+
+ first->prev = prev;
+ prev->next = first;
+
+ last->next = next;
+ next->prev = last;
+}
+
+/**
+ * list_splice - join two lists, this is designed for stacks
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(const struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head, head->next);
+}
+
+/**
+ * list_splice_tail - join two lists, each list being a queue
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice_tail(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head->prev, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head, head->next);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_splice_tail_init - join two lists and reinitialise the emptied list
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * Each of the lists is a queue.
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_tail_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head->prev, head);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * list_first_entry - get the first element from a list
+ * @ptr: the list head to take the element from.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_first_entry(ptr, type, member) \
+ list_entry((ptr)->next, type, member)
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; prefetch(pos->next), pos != (head); \
+ pos = pos->next)
+
+/**
+ * __list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code, no prefetching is done.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list backwards
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \
+ pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop cursor.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_prev_safe(pos, n, head) \
+ for (pos = (head)->prev, n = pos->prev; \
+ prefetch(pos->prev), pos != (head); \
+ pos = n, n = pos->prev)
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member); \
+ prefetch(pos->member.next), &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member) \
+ for (pos = list_entry((head)->prev, typeof(*pos), member); \
+ prefetch(pos->member.prev), &pos->member != (head); \
+ pos = list_entry(pos->member.prev, typeof(*pos), member))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
+ * @pos: the type * to use as a start point
+ * @head: the head of the list
+ * @member: the name of the list_struct within the struct.
+ *
+ * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
+ */
+#define list_prepare_entry(pos, head, member) \
+ ((pos) ? : list_entry(head, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue - continue iteration over list of given type
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Continue to iterate over list of given type, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue(pos, head, member) \
+ for (pos = list_entry(pos->member.next, typeof(*pos), member); \
+ prefetch(pos->member.next), &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_continue_reverse - iterate backwards from the given point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Start to iterate over list of given type backwards, continuing after
+ * the current position.
+ */
+#define list_for_each_entry_continue_reverse(pos, head, member) \
+ for (pos = list_entry(pos->member.prev, typeof(*pos), member); \
+ prefetch(pos->member.prev), &pos->member != (head); \
+ pos = list_entry(pos->member.prev, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_from - iterate over list of given type from the current point
+ * @pos: the type * to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Iterate over list of given type, continuing from current position.
+ */
+#define list_for_each_entry_from(pos, head, member) \
+ for (; prefetch(pos->member.next), &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_entry_safe_continue
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Iterate over list of given type, continuing after current point,
+ * safe against removal of list entry.
+ */
+#define list_for_each_entry_safe_continue(pos, n, head, member) \
+ for (pos = list_entry(pos->member.next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_entry_safe_from
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Iterate over list of given type from current point, safe against
+ * removal of list entry.
+ */
+#define list_for_each_entry_safe_from(pos, n, head, member) \
+ for (n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+/**
+ * list_for_each_entry_safe_reverse
+ * @pos: the type * to use as a loop cursor.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ *
+ * Iterate backwards over list of given type, safe against removal
+ * of list entry.
+ */
+#define list_for_each_entry_safe_reverse(pos, n, head, member) \
+ for (pos = list_entry((head)->prev, typeof(*pos), member), \
+ n = list_entry(pos->member.prev, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.prev, typeof(*n), member))
+
+/*
+ * Double linked lists with a single pointer list head.
+ * Mostly useful for hash tables where the two pointer list head is
+ * too wasteful.
+ * You lose the ability to access the tail in O(1).
+ */
+
+struct hlist_head {
+ struct hlist_node *first;
+};
+
+struct hlist_node {
+ struct hlist_node *next, **pprev;
+};
+
+#define HLIST_HEAD_INIT { .first = NULL }
+#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
+#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
+static inline void INIT_HLIST_NODE(struct hlist_node *h)
+{
+ h->next = NULL;
+ h->pprev = NULL;
+}
+
+static inline int hlist_unhashed(const struct hlist_node *h)
+{
+ return !h->pprev;
+}
+
+static inline int hlist_empty(const struct hlist_head *h)
+{
+ return !h->first;
+}
+
+static inline void __hlist_del(struct hlist_node *n)
+{
+ struct hlist_node *next = n->next;
+ struct hlist_node **pprev = n->pprev;
+ *pprev = next;
+ if (next)
+ next->pprev = pprev;
+}
+
+static inline void hlist_del(struct hlist_node *n)
+{
+ __hlist_del(n);
+ n->next = LIST_POISON1;
+ n->pprev = LIST_POISON2;
+}
+
+static inline void hlist_del_init(struct hlist_node *n)
+{
+ if (!hlist_unhashed(n)) {
+ __hlist_del(n);
+ INIT_HLIST_NODE(n);
+ }
+}
+
+static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
+{
+ struct hlist_node *first = h->first;
+ n->next = first;
+ if (first)
+ first->pprev = &n->next;
+ h->first = n;
+ n->pprev = &h->first;
+}
+
+/* next must be != NULL */
+static inline void hlist_add_before(struct hlist_node *n,
+ struct hlist_node *next)
+{
+ n->pprev = next->pprev;
+ n->next = next;
+ next->pprev = &n->next;
+ *(n->pprev) = n;
+}
+
+static inline void hlist_add_after(struct hlist_node *n,
+ struct hlist_node *next)
+{
+ next->next = n->next;
+ n->next = next;
+ next->pprev = &n->next;
+
+ if(next->next)
+ next->next->pprev = &next->next;
+}
+
+#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
+
+#define hlist_for_each(pos, head) \
+ for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
+ pos = pos->next)
+
+#define hlist_for_each_safe(pos, n, head) \
+ for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
+ pos = n)
+
+/**
+ * hlist_for_each_entry - iterate over list of given type
+ * @tpos: the type * to use as a loop cursor.
+ * @pos: the &struct hlist_node to use as a loop cursor.
+ * @head: the head for your list.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry(tpos, pos, head, member) \
+ for (pos = (head)->first; \
+ pos && ({ prefetch(pos->next); 1;}) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next)
+
+/**
+ * hlist_for_each_entry_continue - iterate over a hlist continuing after current point
+ * @tpos: the type * to use as a loop cursor.
+ * @pos: the &struct hlist_node to use as a loop cursor.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_continue(tpos, pos, member) \
+ for (pos = (pos)->next; \
+ pos && ({ prefetch(pos->next); 1;}) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next)
+
+/**
+ * hlist_for_each_entry_from - iterate over a hlist continuing from current point
+ * @tpos: the type * to use as a loop cursor.
+ * @pos: the &struct hlist_node to use as a loop cursor.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_from(tpos, pos, member) \
+ for (; pos && ({ prefetch(pos->next); 1;}) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = pos->next)
+
+/**
+ * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @tpos: the type * to use as a loop cursor.
+ * @pos: the &struct hlist_node to use as a loop cursor.
+ * @n: another &struct hlist_node to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_safe(tpos, pos, n, head, member) \
+ for (pos = (head)->first; \
+ pos && ({ n = pos->next; 1; }) && \
+ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
+ pos = n)
+
+
+#endif
+
+
diff --git a/project/booster/logger.c b/project/booster/logger.c
new file mode 100644
index 0000000..217eb02
--- /dev/null
+++ b/project/booster/logger.c
@@ -0,0 +1,279 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: logger.c
+ * Description: This file is common logger API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <pthread.h>
+
+#include "logger.h"
+
+typedef void (*log_LockFn)(void *udata, int lock);
+
+static struct {
+ char file[32]; /* logger file name */
+ FILE *fp; /* logger file pointer */
+ long size; /* logger file max size */
+ int level; /* logger level */
+ log_LockFn lockfn; /* lock function */
+ void *udata; /* lock data */
+} L;
+
+static const char *level_names[] = {
+ "ERROR",
+ "WARN",
+ "INFO",
+ "DEBUG",
+ "TRACE"
+};
+
+static const char *level_colors[] = {
+ "\x1b[31m",
+ "\x1b[33m",
+ "\x1b[32m",
+ "\x1b[36m",
+ "\x1b[94m"
+};
+
+static inline void time_to_str(char *buf)
+{
+ struct timeval tv;
+ struct tm *tm;
+ int len;
+
+ gettimeofday(&tv, NULL);
+ tm = localtime(&tv.tv_sec);
+
+ len = sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d.%06d ",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec, (int)tv.tv_usec);
+
+ buf[len] = '\0';
+}
+
+static void mutex_lock(void *udata, int lock)
+{
+ int err;
+ pthread_mutex_t *l = (pthread_mutex_t *) udata;
+
+ if (lock)
+ {
+ if ( (err = pthread_mutex_lock(l)) != 0 )
+ log_error("Unable to lock log lock: %s", strerror(err));
+ }
+ else
+ {
+ if ( (err = pthread_mutex_unlock(l) != 0) )
+ log_error("Unable to unlock log lock: %s", strerror(err));
+ }
+}
+
+int log_open(char *fname, int level, int size, int lock)
+{
+ FILE *fp;
+
+ L.level = level;
+ L.size = size*1024;
+
+ if( !fname || !strcmp(fname, "console") || !strcmp(fname, "stderr") )
+ {
+ strcpy(L.file, "console");
+ L.fp = stderr;
+ L.size = 0; /* console don't need rollback */
+ }
+ else
+ {
+ if ( !(fp = fopen(fname, "a+")) )
+ {
+ fprintf(stderr, "%s() failed: %s\n", __func__, strerror(errno));
+ return -2;
+ }
+ L.fp = fp;
+ strncpy(L.file, fname, sizeof(L.file));
+ }
+
+
+ if( lock )
+ {
+ static pthread_mutex_t log_lock;
+
+ pthread_mutex_init(&log_lock, NULL);
+ L.udata = (void *)&log_lock;
+ L.lockfn = mutex_lock;
+ }
+
+ fprintf(L.fp, "\n");
+ log_info("logger system(%s) start: file:\"%s\", level:%s, maxsize:%luKiB\n\n",
+ LOG_VERSION, L.file, level_names[level], size);
+
+ return 0;
+}
+
+void log_close(void)
+{
+ if( L.fp && L.fp!=stderr )
+ fclose(L.fp);
+
+ if (L.udata )
+ pthread_mutex_destroy( L.udata);
+}
+
+static void log_rollback(void)
+{
+ char cmd[128]={0};
+ long fsize;
+
+ /* don't need rollback */
+ if(L.size <= 0 )
+ return ;
+
+ fsize = ftell(L.fp);
+ if( fsize < L.size )
+ return ;
+
+ /* backup current log file */
+ snprintf(cmd, sizeof(cmd), "cp %s %s.bak", L.file, L.file);
+ system(cmd);
+
+ /* rollback file */
+ fseek(L.fp, 0, SEEK_SET);
+ truncate(L.file, 0);
+
+ fprintf(L.fp, "\n");
+ log_info("logger system(%s) rollback: file:\"%s\", level:%s, maxsize:%luKiB\n\n",
+ LOG_VERSION, L.file, level_names[L.level], L.size/1024);
+
+ return ;
+}
+
+void _log_write(int level, const char *file, int line, const char *fmt, ...)
+{
+ va_list args;
+ char time_string[100];
+
+ if ( !L.fp || level>L.level )
+ return;
+
+ /* Acquire lock */
+ if ( L.lockfn )
+ L.lockfn(L.udata, 1);
+
+ log_rollback();
+
+ /* check and rollback file */
+ time_to_str(time_string);
+
+ /* Log to stderr */
+ if ( L.fp == stderr )
+ {
+ fprintf(L.fp, "%s %s %-5s\x1b[0m \x1b[90m%s:%03d:\x1b[0m ",
+ time_string, level_colors[level], level_names[level], file, line);
+ }
+ else /* Log to file */
+ {
+ fprintf(L.fp, "%s %-5s %s:%03d: ", time_string, level_names[level], file, line);
+ }
+
+ va_start(args, fmt);
+ vfprintf(L.fp, fmt, args);
+ va_end(args);
+
+ fflush(L.fp);
+
+ /* Release lock */
+ if ( L.lockfn )
+ L.lockfn(L.udata, 0);
+}
+
+#define LINELEN 81
+#define CHARS_PER_LINE 16
+static char *print_char =
+" "
+" "
+" !\"#$%&'()*+,-./"
+"0123456789:;<=>?"
+"@ABCDEFGHIJKLMNO"
+"PQRSTUVWXYZ[\\]^_"
+"`abcdefghijklmno"
+"pqrstuvwxyz{|}~ "
+" "
+" "
+" ???????????????"
+"????????????????"
+"????????????????"
+"????????????????"
+"????????????????"
+"????????????????";
+
+void log_dump(int level, const char *prompt, char *buf, size_t len)
+{
+ int rc;
+ int idx;
+ char prn[LINELEN];
+ char lit[CHARS_PER_LINE + 2];
+ char hc[4];
+ short line_done = 1;
+
+ if (!L.fp || level>L.level)
+ return;
+
+ if( prompt )
+ _log_write(level, __FILE__, __LINE__, "%s", prompt);
+
+ rc = len;
+ idx = 0;
+ lit[CHARS_PER_LINE] = '\0';
+
+ while (rc > 0)
+ {
+ if (line_done)
+ snprintf(prn, LINELEN, "%08X: ", idx);
+
+ do
+ {
+ unsigned char c = buf[idx];
+ snprintf(hc, 4, "%02X ", c);
+ strncat(prn, hc, LINELEN);
+
+ lit[idx % CHARS_PER_LINE] = print_char[c];
+ }
+ while (--rc > 0 && (++idx % CHARS_PER_LINE != 0));
+
+ line_done = (idx % CHARS_PER_LINE) == 0;
+ if (line_done)
+ {
+ if (L.fp)
+ fprintf(L.fp, "%s %s\n", prn, lit);
+ }
+ }
+
+ if (!line_done)
+ {
+ int ldx = idx % CHARS_PER_LINE;
+ lit[ldx++] = print_char[(int)buf[idx]];
+ lit[ldx] = '\0';
+
+ while ((++idx % CHARS_PER_LINE) != 0)
+ strncat(prn, " ", sizeof(prn)-strlen(prn));
+
+ if (L.fp)
+ fprintf(L.fp, "%s %s\n", prn, lit);
+
+ }
+}
diff --git a/project/booster/logger.h b/project/booster/logger.h
new file mode 100644
index 0000000..6347beb
--- /dev/null
+++ b/project/booster/logger.h
@@ -0,0 +1,68 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: logger.h
+ * Description: This file is common logger API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#ifndef _LOGGER_H_
+#define _LOGGER_H_
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#define LOG_VERSION "v0.1"
+
+/* log level */
+enum {
+ LOG_LEVEL_ERROR,
+ LOG_LEVEL_WARN,
+ LOG_LEVEL_INFO,
+ LOG_LEVEL_DEBUG,
+ LOG_LEVEL_TRACE,
+ LOG_LEVEL_MAX
+};
+
+enum {
+ LOG_LOCK_DISABLE, /* disable lock */
+ LOG_LOCK_ENABLE, /* enable lock */
+};
+
+#define ROLLBACK_NONE 0
+
+/* description: Initial the logger system
+ * arguments :
+ * $fname: logger file name, NULL/"console"/"stderr" will log to console
+ * $level: logger level above;
+ * $size : logger file max size in KiB
+ * $lock : thread lock enable or not
+ * return : <0: Failed ==0: Sucessfully
+ */
+int log_open(char *fname, int level, int size, int lock);
+
+
+/* description: Terminate the logger system */
+void log_close(void);
+
+
+/* description: log message into log file. Don't call this function directly. */
+void _log_write(int level, const char *file, int line, const char *fmt, ...);
+
+
+/* description: dump a buffer in hex to logger file */
+void log_dump(int level, const char *prompt, char *buf, size_t len);
+
+/* function: log message into logger file with different log level */
+#define log_trace(...) _log_write(LOG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__)
+#define log_debug(...) _log_write(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
+#define log_info(...) _log_write(LOG_LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__)
+#define log_warn(...) _log_write(LOG_LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__)
+#define log_error(...) _log_write(LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
+
+#endif
diff --git a/project/booster/makefile b/project/booster/makefile
new file mode 100644
index 0000000..b6ece0e
--- /dev/null
+++ b/project/booster/makefile
@@ -0,0 +1,35 @@
+#********************************************************************************
+# Copyright: (C) 2023 LingYun IoT System Studio
+# All rights reserved.
+#
+# Filename: Makefile
+# Description: This file used compile all the source code to static library
+#
+# Version: 1.0.0(11/08/23)
+# Author: Guo Wenxue <guowenxue@gmail.com>
+# ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+#
+#*******************************************************************************
+
+PWD=$(shell pwd )
+
+BUILD_ARCH=$(shell uname -m)
+ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
+ CROSS_COMPILE?=arm-linux-gnueabihf-
+endif
+
+LIBNAME=$(shell basename ${PWD} )
+TOPDIR=$(shell dirname ${PWD} )
+CFLAGS+=-D_GNU_SOURCE
+
+all: clean
+ @rm -f *.o
+ @${CROSS_COMPILE}gcc ${CFLAGS} -I${TOPDIR} -c *.c
+ ${CROSS_COMPILE}ar -rcs lib${LIBNAME}.a *.o
+
+clean:
+ @rm -f *.o
+ @rm -f *.a
+
+distclean:
+ @make clean
diff --git a/project/booster/ringbuf.c b/project/booster/ringbuf.c
new file mode 100644
index 0000000..44412c1
--- /dev/null
+++ b/project/booster/ringbuf.c
@@ -0,0 +1,107 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: ringbuf.c
+ * Description: This file is common ring buffer API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#include <string.h>
+#include <assert.h>
+#include "ringbuf.h"
+
+void rb_init (struct ring_buffer *ring, unsigned char* buff, int size)
+{
+ memset (ring, 0, sizeof (struct ring_buffer));
+ ring->rd_pointer = 0;
+ ring->wr_pointer = 0;
+ ring->buffer= buff;
+ ring->size = size;
+}
+
+
+int rb_write (struct ring_buffer *rb, unsigned char * buf, int len)
+{
+ int total;
+ int i;
+
+ /* total = len = min(space, len) */
+ total = rb_free_size(rb);
+ if(len > total)
+ len = total;
+ else
+ total = len;
+
+ i = rb->wr_pointer;
+ if(i + len > rb->size)
+ {
+ memcpy(rb->buffer + i, buf, rb->size - i);
+ buf += rb->size - i;
+ len -= rb->size - i;
+ i = 0;
+ }
+
+ memcpy(rb->buffer + i, buf, len);
+ rb->wr_pointer = i + len;
+ return total;
+}
+
+
+int rb_free_size (struct ring_buffer *rb)
+{
+ return (rb->size - 1 - rb_data_size(rb));
+}
+
+
+int rb_read (struct ring_buffer *rb, unsigned char * buf, int max)
+{
+ int total;
+ int i;
+
+ /* total = len = min(used, len) */
+ total = rb_data_size(rb);
+
+ if(max > total)
+ max = total;
+ else
+ total = max;
+
+
+ i = rb->rd_pointer;
+ if(i + max > rb->size)
+ {
+ memcpy(buf, rb->buffer + i, rb->size - i);
+ buf += rb->size - i;
+ max -= rb->size - i;
+ i = 0;
+ }
+
+ memcpy(buf, rb->buffer + i, max);
+ rb->rd_pointer = i + max;
+
+ return total;
+}
+
+int rb_data_size (struct ring_buffer *rb)
+{
+ return ((rb->wr_pointer - rb->rd_pointer) & (rb->size-1));
+}
+
+void rb_clear (struct ring_buffer *rb)
+{
+ memset(rb->buffer,0,rb->size);
+ rb->rd_pointer=0;
+ rb->wr_pointer=0;
+}
+
+unsigned char rb_peek(struct ring_buffer* rb, int index)
+{
+ assert(index < rb_data_size(rb));
+
+ return rb->buffer[((rb->rd_pointer + index) % rb->size)];
+}
diff --git a/project/booster/ringbuf.h b/project/booster/ringbuf.h
new file mode 100644
index 0000000..88b978e
--- /dev/null
+++ b/project/booster/ringbuf.h
@@ -0,0 +1,57 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio.
+ * All rights reserved.
+ *
+ * Filename: ringbuf.h
+ * Description: This file is common ring buffer API functions
+ *
+ * Version: 1.0.0(11/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+ *
+ ********************************************************************************/
+
+#ifndef _RINGBUF_H_
+#define _RINGBUF_H_
+
+struct ring_buffer
+{
+ unsigned char *buffer;
+ int wr_pointer;
+ int rd_pointer;
+ int size;
+};
+
+
+/* Initial the ring buffer */
+void rb_init (struct ring_buffer *ring, unsigned char *buff, int size) ;
+
+
+/* Description: Write $len bytes data in $buf into ring buffer $rb
+ * Return Value: The actual written into ring buffer data size, if ring buffer
+ * left space size small than $len, then only part of the data be written into.
+ */
+int rb_write (struct ring_buffer *rb, unsigned char *buf, int len) ;
+
+
+/* Get ring buffer left free size */
+int rb_free_size (struct ring_buffer *rb);
+
+
+/* Read $max bytes data from ring buffer $rb to $buf */
+int rb_read (struct ring_buffer *rb, unsigned char *buf, int max);
+
+
+/* Read a specify $index byte data in ring buffer $rb */
+unsigned char rb_peek(struct ring_buffer *rb, int index);
+
+
+/* Get data size in the ring buffer */
+int rb_data_size (struct ring_buffer *rb);
+
+
+/* Clear the ring buffer data */
+void rb_clear (struct ring_buffer *rb) ;
+
+#endif /* ----- #ifndef _RINGBUF_H_ ----- */
+
diff --git a/project/booster/util_proc.c b/project/booster/util_proc.c
new file mode 100644
index 0000000..dfdabae
--- /dev/null
+++ b/project/booster/util_proc.c
@@ -0,0 +1,432 @@
+/*********************************************************************************
+ * Copyright: (C) 2020 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: util_proc.c
+ * Description: This file is the process API
+ *
+ * Version: 1.0.0(7/06/2020)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "7/06/2020 09:19:02 PM"
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "util_proc.h"
+#include "logger.h"
+
+proc_signal_t g_signal={0};
+
+void proc_default_sighandler(int sig)
+{
+ switch(sig)
+ {
+ case SIGINT:
+ log_warn("SIGINT - stopping\n");
+ g_signal.stop = 1;
+ break;
+
+ case SIGTERM:
+ log_warn("SIGTERM - stopping\n");
+ g_signal.stop = 1;
+ break;
+
+ case SIGSEGV:
+ log_warn("SIGSEGV - stopping\n");
+#if 0
+ if(g_signal.stop)
+ exit(0);
+
+ g_signal.stop = 1;
+#endif
+ break;
+
+ case SIGPIPE:
+ log_warn("SIGPIPE - warnning\n");
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+/* install default signal process functions */
+void install_default_signal(void)
+{
+ struct sigaction sigact, sigign;
+
+ log_info("Install default signal handler.\n");
+
+ /* Initialize the catch signal structure. */
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigact.sa_handler = proc_default_sighandler;
+
+ /* Setup the ignore signal. */
+ sigemptyset(&sigign.sa_mask);
+ sigign.sa_flags = 0;
+ sigign.sa_handler = SIG_IGN;
+
+ sigaction(SIGTERM, &sigact, 0); /* catch terminate signal "kill" command */
+ sigaction(SIGINT, &sigact, 0); /* catch interrupt signal CTRL+C */
+ //sigaction(SIGSEGV, &sigact, 0); /* catch segmentation faults */
+ sigaction(SIGPIPE, &sigact, 0); /* catch broken pipe */
+#if 0
+ sigaction(SIGCHLD, &sigact, 0); /* catch child process return */
+ sigaction(SIGUSR2, &sigact, 0); /* catch USER signal */
+#endif
+}
+
+
+/* ****************************************************************************
+ * FunctionName: daemonize
+ * Description : Set the programe runs as daemon in background
+ * Inputs : nodir: DON'T change the work directory to / : 1:NoChange 0:Change
+ * noclose: close the opened file descrtipion or not 1:Noclose 0:Close
+ * Output : NONE
+ * Return : NONE
+ * *****************************************************************************/
+void daemonize(int nochdir, int noclose)
+{
+ int rv, fd;
+ int i;
+
+ /* already a daemon */
+ if (1 == getppid())
+ return;
+
+ /* fork error */
+ rv = fork();
+ if (rv < 0) exit(1);
+
+ /* parent process exit */
+ if (rv > 0)
+ exit(0);
+
+ /* obtain a new process session group */
+ setsid();
+
+ if (!noclose)
+ {
+ /* close all descriptors */
+ for (i = getdtablesize(); i >= 0; --i)
+ {
+ //if (i != g_logPtr->fd)
+ close(i);
+ }
+
+ /* Redirect Standard input [0] to /dev/null */
+ fd = open("/dev/null", O_RDWR);
+
+ /* Redirect Standard output [1] to /dev/null */
+ dup(fd);
+
+ /* Redirect Standard error [2] to /dev/null */
+ dup(fd);
+ }
+
+ umask(0);
+
+ if (!nochdir)
+ chdir("/");
+
+ return;
+}
+
+/* ****************************************************************************
+ * FunctionName: check_set_program_running
+ * Description : check program already running or not, if not then run it and
+ * record pid into $pidfile
+ * Inputs : daemon: set program running in daemon or not
+ * pid_file:The record PID file path
+ * Output : NONE
+ * Return : 0: Record successfully Else: Failure
+ * *****************************************************************************/
+
+int check_set_program_running(int daemon, char *pidfile)
+{
+ if( !pidfile )
+ return 0;
+
+ if( check_daemon_running(pidfile) )
+ {
+ log_error("Program already running, process exit now");
+ return -1;
+ }
+
+ if( daemon )
+ {
+ if( set_daemon_running(pidfile) < 0 )
+ {
+ log_error("set program running as daemon failure\n");
+ return -2;
+ }
+ }
+ else
+ {
+ if( record_daemon_pid(pidfile) < 0 )
+ {
+ log_error("record program running PID failure\n");
+ return -3;
+ }
+ }
+
+ return 0;
+}
+
+
+
+/* ****************************************************************************
+ * FunctionName: record_daemon_pid
+ * Description : Record the running daemon program PID to the file "pid_file"
+ * Inputs : pid_file:The record PID file path
+ * Output : NONE
+ * Return : 0: Record successfully Else: Failure
+ * *****************************************************************************/
+int record_daemon_pid(const char *pid_file)
+{
+ struct stat fStatBuf;
+ int fd = -1;
+ int mode = S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP | S_IRWXU;
+ char ipc_dir[64] = { 0 };
+
+ strncpy(ipc_dir, pid_file, 64);
+
+ /* dirname() will modify ipc_dir and save the result */
+ dirname(ipc_dir);
+
+ /* If folder pid_file PATH doesnot exist, then we will create it" */
+ if (stat(ipc_dir, &fStatBuf) < 0)
+ {
+ if (mkdir(ipc_dir, mode) < 0)
+ {
+ log_error("cannot create %s: %s\n", ipc_dir, strerror(errno));
+ return -1;
+ }
+
+ (void)chmod(ipc_dir, mode);
+ }
+
+ /* Create the process running PID file */
+ mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+ if ((fd = open(pid_file, O_RDWR | O_CREAT | O_TRUNC, mode)) >= 0)
+ {
+ char pid[PID_ASCII_SIZE];
+ snprintf(pid, sizeof(pid), "%u\n", (unsigned)getpid());
+ write(fd, pid, strlen(pid));
+ close(fd);
+
+ log_debug("Record PID<%u> to file %s.\n", getpid(), pid_file);
+ }
+ else
+ {
+ log_error("cannot create %s: %s\n", pid_file, strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+/* ****************************************************************************
+ * FunctionName: get_daemon_pid
+ * Description : Get the daemon process PID from the PID record file "pid_file"
+ * Inputs : pid_file: the PID record file
+ * Output : NONE
+ * Return : pid_t: The daemon process PID number
+ * *****************************************************************************/
+pid_t get_daemon_pid(const char *pid_file)
+{
+ FILE *f;
+ pid_t pid;
+
+ if ((f = fopen(pid_file, "rb")) != NULL)
+ {
+ char pid_ascii[PID_ASCII_SIZE];
+ (void)fgets(pid_ascii, PID_ASCII_SIZE, f);
+ (void)fclose(f);
+ pid = atoi(pid_ascii);
+ }
+ else
+ {
+ log_error("Can't open PID record file %s: %s\n", pid_file, strerror(errno));
+ return -1;
+ }
+ return pid;
+}
+
+/* ****************************************************************************
+ * FunctionName: check_daemon_running
+ * Description : Check the daemon program already running or not
+ * Inputs : pid_file: The record running daemon program PID
+ * Output : NONE
+ * Return : 1: The daemon program alread running 0: Not running
+ * *****************************************************************************/
+int check_daemon_running(const char *pid_file)
+{
+ int rv = -1;
+ struct stat fStatBuf;
+
+ rv = stat(pid_file, &fStatBuf);
+ if (0 == rv)
+ {
+ pid_t pid = -1;
+ printf("PID record file \"%s\" exist.\n", pid_file);
+
+ pid = get_daemon_pid(pid_file);
+ if (pid > 0) /* Process pid exist */
+ {
+ if ((rv = kill(pid, 0)) == 0)
+ {
+ printf("Program with PID[%d] seems running.\n", pid);
+ return 1;
+ }
+ else /* Send signal to the old process get no reply. */
+ {
+ printf("Program with PID[%d] seems exit.\n", pid);
+ remove(pid_file);
+ return 0;
+ }
+ }
+ else if (0 == pid)
+ {
+ printf("Can not read program PID form record file.\n");
+ remove(pid_file);
+ return 0;
+ }
+ else /* Read pid from file "pid_file" failure */
+ {
+ printf("Read record file \"%s\" failure, maybe program still running.\n", pid_file);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* ****************************************************************************
+ * FunctionName: stop_daemon_running
+ * Description : Stop the daemon program running
+ * Inputs : pid_file: The record running daemon program PID
+ * Output : NONE
+ * Return : 1: The daemon program alread running 0: Not running
+ * *****************************************************************************/
+int stop_daemon_running(const char *pid_file)
+{
+ pid_t pid = -1;
+ struct stat fStatBuf;
+
+ if ( stat(pid_file, &fStatBuf) < 0)
+ return 0;
+
+ printf("PID record file \"%s\" exist.\n", pid_file);
+ pid = get_daemon_pid(pid_file);
+ if (pid > 0) /* Process pid exist */
+ {
+ while ( (kill(pid, 0) ) == 0)
+ {
+ kill(pid, SIGTERM);
+ sleep(1);
+ }
+
+ remove(pid_file);
+ }
+
+ return 0;
+}
+
+
+
+/* ****************************************************************************
+ * FunctionName: set_daemon_running
+ * Description : Set the programe running as daemon if it's not running and record
+ * its PID to the pid_file.
+ * Inputs : pid_file: The record running daemon program PID
+ * Output : NONE
+ * Return : 0: Successfully. 1: Failure
+ * *****************************************************************************/
+int set_daemon_running(const char *pid_file)
+{
+ daemonize(0, 1);
+ log_info("Program running as daemon [PID:%d].\n", getpid());
+
+ if (record_daemon_pid(pid_file) < 0)
+ {
+ log_error("Record PID to file \"%s\" failure.\n", pid_file);
+ return -2;
+ }
+
+ return 0;
+}
+
+/* start a new thread to run $thread_workbody point function */
+int thread_start(pthread_t *thread_id, thread_body_t thread_workbody, void *thread_arg)
+{
+ int rv = 0;
+ pthread_t tid;
+
+ pthread_attr_t thread_attr;
+
+ /* Initialize the thread attribute */
+ rv = pthread_attr_init(&thread_attr);
+ if(rv)
+ return -1;
+
+ /* Set the stack size of the thread */
+ rv = pthread_attr_setstacksize(&thread_attr, 120 * 1024);
+ if(rv)
+ goto CleanUp;
+
+ /* Set thread to detached state:Don`t need pthread_join */
+ rv = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
+ if(rv)
+ goto CleanUp;
+
+ /* Create the thread */
+ rv = pthread_create(&tid, &thread_attr, thread_workbody, thread_arg);
+ if(rv)
+ goto CleanUp;
+
+CleanUp:
+
+
+ if( thread_id )
+ {
+ if( rv )
+ *thread_id = 0;
+ else
+ *thread_id = tid;
+ }
+
+ /* Destroy the attributes of thread */
+ pthread_attr_destroy(&thread_attr);
+ return rv;
+}
+
+
+/* excute a linux command by system() */
+void exec_system_cmd(const char *format, ...)
+{
+ char cmd[256];
+ va_list args;
+
+ memset(cmd, 0, sizeof(cmd));
+
+ va_start(args, format);
+ vsnprintf(cmd, sizeof(cmd), format, args);
+ va_end(args);
+
+ system(cmd);
+}
+
+
diff --git a/project/booster/util_proc.h b/project/booster/util_proc.h
new file mode 100644
index 0000000..89856c6
--- /dev/null
+++ b/project/booster/util_proc.h
@@ -0,0 +1,89 @@
+/********************************************************************************
+ * Copyright: (C) 2020 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: util_proc.h
+ * Description: This head file is for Linux process/thread API
+ *
+ * Version: 1.0.0(7/06/2012~)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "7/06/2012 09:21:33 PM"
+ *
+ ********************************************************************************/
+
+#ifndef __UTIL_PROC_H_
+#define __UTIL_PROC_H_
+
+#include <signal.h>
+#include <time.h>
+
+#define PID_ASCII_SIZE 11
+
+typedef struct proc_signal_s
+{
+ int signal;
+ unsigned stop; /* 0: Not term 1: Stop */
+} proc_signal_t;
+
+typedef void *(* thread_body_t) (void *thread_arg);
+
+extern proc_signal_t g_signal;
+
+/* install default signal process functions */
+extern void install_default_signal(void);
+
+/* excute a linux command by system() */
+extern void exec_system_cmd(const char *format, ...);
+
+/* check program already running or not, if not then run it and record pid into $pidfile */
+extern int check_set_program_running(int daemon, char *pidfile);
+
+/* check program already running or not from $pid_file */
+extern int check_daemon_running(const char *pid_file);
+
+/* set program daemon running and record pid in $pid_file */
+extern int set_daemon_running(const char *pid_file);
+
+/* record proces ID into $pid_file */
+extern int record_daemon_pid(const char *pid_file);
+
+/* stop program running from $pid_file */
+extern int stop_daemon_running(const char *pid_file);
+
+/* my implementation for set program running in daemon */
+extern void daemonize(int nochdir, int noclose);
+
+/* start a new thread to run $thread_workbody point function */
+extern int thread_start(pthread_t *thread_id, thread_body_t thread_workbody, void *thread_arg);
+
+/* +---------------------+
+ * | Low level API |
+ * +---------------------+*/
+
+/* get daemon process ID from $pid_file */
+extern pid_t get_daemon_pid(const char *pid_file);
+
+/* +------------------------+
+ * | inline functions API |
+ * +------------------------+*/
+static inline void msleep(unsigned long ms)
+{
+ struct timespec cSleep;
+ unsigned long ulTmp;
+
+ cSleep.tv_sec = ms / 1000;
+ if (cSleep.tv_sec == 0)
+ {
+ ulTmp = ms * 10000;
+ cSleep.tv_nsec = ulTmp * 100;
+ }
+ else
+ {
+ cSleep.tv_nsec = 0;
+ }
+
+ nanosleep(&cSleep, 0);
+ return ;
+}
+
+#endif
diff --git a/project/coreMQTT/conf.c b/project/coreMQTT/conf.c
new file mode 100644
index 0000000..4c0ee20
--- /dev/null
+++ b/project/coreMQTT/conf.c
@@ -0,0 +1,200 @@
+/*********************************************************************************
+ * Copyright: (C) 2019 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: conf.c
+ * Description: This file is mqttd configure file parser function
+ *
+ * Version: 1.0.0(2019年06月25日)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "2019年06月25日 22时23分55秒"
+ *
+ ********************************************************************************/
+#include "conf.h"
+#include "logger.h"
+#include "iniparser.h"
+
+
+int mqttd_parser_conf(const char *conf_file, mqtt_ctx_t *ctx, int debug)
+{
+ dictionary *ini;
+ const char *str;
+ int val;
+ int rv = 0;
+
+ if( !conf_file || !ctx )
+ {
+ fprintf(stderr, "%s() Invalid input arguments\n", __func__);
+ return -1;
+ }
+
+ memset(ctx, 0, sizeof(*ctx));
+
+ ini = iniparser_load(conf_file);
+ if( !ini )
+ {
+ fprintf(stderr, "ERROR: Configure file '%s' load failed\n", conf_file);
+ return -2;
+ }
+
+ /*+------------------------------------------------------+
+ *| parser logger settings and start logger system |
+ *+------------------------------------------------------+*/
+ if( !debug )
+ {
+ str = iniparser_getstring(ini, "logger:file", "/tmp/mqttd.log");
+ strncpy(ctx->logfile, str, sizeof(ctx->logfile));
+ ctx->logsize = iniparser_getint(ini, "logger:size", 1024);
+ ctx->loglevel = iniparser_getint(ini, "logger:level", LOG_LEVEL_INFO);
+ }
+ else
+ {
+ strncpy(ctx->logfile, "console", sizeof(ctx->logfile));
+ ctx->loglevel = LOG_LEVEL_DEBUG;
+ ctx->logsize = 0;
+ }
+
+ if( log_open(ctx->logfile, ctx->loglevel, ctx->logsize, LOG_LOCK_DISABLE) < 0 )
+ {
+ fprintf(stderr, "Logger system initialise failure\n");
+ return -2;
+ }
+
+ log_info("Logger system initialise ok\n");
+
+
+ /*+------------------------------------------------------+
+ *| parser production ID |
+ *+------------------------------------------------------+*/
+
+ if( !(str=iniparser_getstring(ini, "common:devid", NULL)) )
+ {
+ log_error("ERROR: Parser device ID failure\n");
+ rv = -3;
+ goto cleanup;
+ }
+ /* cJSON parser ID will get "" */
+ snprintf(ctx->devid, sizeof(ctx->devid), "\"%s\"", str);
+ log_info("Parser device ID [%s]\n", ctx->devid);
+
+
+ /*+------------------------------------------------------+
+ *| parser hardware module configuration |
+ *+------------------------------------------------------+*/
+
+ /* relay */
+ ctx->hwconf.relay=iniparser_getint(ini, "hardware:relay", 0);
+ if( !ctx->hwconf.relay )
+ log_warn("Parser relay module disabled\n");
+ else
+ log_info("Parser relay module enabled\n");
+
+ /* RGB 3-colors LED */
+ ctx->hwconf.led=iniparser_getint(ini, "hardware:rgbled", 0);
+ if( !ctx->hwconf.led )
+ log_warn("Parser RGB 3-colors Led module disabled\n");
+ else
+ log_info("Parser RGB 3-colors Led module enabled\n");
+
+ /* beeper */
+ ctx->hwconf.beeper=iniparser_getint(ini, "hardware:beep", 0);
+ if( !ctx->hwconf.beeper )
+ log_warn("Parser beeper module disabled\n");
+ else
+ log_info("Parser beeper module enabled\n");
+
+ /* DS18B20 temperature module */
+ ctx->hwconf.ds18b20=iniparser_getint(ini, "hardware:ds18b20", 0);
+ if( !ctx->hwconf.ds18b20 )
+ log_warn("Parser DS18B20 temperature module disabled\n");
+ else
+ log_info("Parser DS18B20 temperature module enabled\n");
+
+ /* SHT20 temperature and hummidity module */
+ ctx->hwconf.sht2x=iniparser_getint(ini, "hardware:sht2x", 0);
+ if( !ctx->hwconf.sht2x )
+ log_warn("Parser SHT2X temperature and hummidity module disabled\n");
+ else
+ log_info("Parser SHT2X temperature and hummidity module enabled\n");
+
+ /* TSL2561 light intensity sensor module */
+ ctx->hwconf.tsl2561=iniparser_getint(ini, "hardware:tsl2561", 0);
+ if( !ctx->hwconf.tsl2561 )
+ log_warn("Parser TSL2561 light intensity sensor module disabled\n");
+ else
+ log_info("Parser TSL2561 light intensity sensor module enabled\n");
+
+ /*+------------------------------------------------------+
+ *| parser broker settings |
+ *+------------------------------------------------------+*/
+
+ if( !(str=iniparser_getstring(ini, "broker:hostname", NULL)) )
+ {
+ log_error("ERROR: Parser MQTT broker server hostname failure\n");
+ rv = -4;
+ goto cleanup;
+ }
+ strncpy(ctx->host, str, sizeof(ctx->host) );
+
+ if( (val=iniparser_getint(ini, "broker:port", -1)) < 0 )
+ {
+ log_error("ERROR: Parser MQTT broker server port failure\n");
+ rv = -5;
+ goto cleanup;
+ }
+ ctx->port = val;
+ log_info("Parser MQTT broker server [%s:%d]\n", ctx->host, ctx->port);
+
+ str=iniparser_getstring(ini, "broker:username", NULL);
+ strncpy(ctx->uid, str, sizeof(ctx->uid) );
+
+ str=iniparser_getstring(ini, "broker:password", NULL);
+ strncpy(ctx->pwd, str, sizeof(ctx->pwd) );
+
+ if( ctx->uid && ctx->pwd )
+ log_info("Parser broker author by [%s:%s]\n", ctx->uid, ctx->pwd);
+
+ ctx->keepalive = iniparser_getint(ini, "broker:keepalive", DEF_KEEPALIVE);
+ log_info("Parser broker keepalive timeout [%d] seconds\n", ctx->keepalive);
+
+ /*+------------------------------------------------------+
+ *| parser publisher settings |
+ *+------------------------------------------------------+*/
+
+ if( !(str=iniparser_getstring(ini, "publisher:pubTopic", NULL)) )
+ {
+ log_error("ERROR: Parser MQTT broker publisher topic failure\n");
+ rv = -6;
+ goto cleanup;
+ }
+ strncpy(ctx->pubTopic, str, sizeof(ctx->pubTopic) );
+
+ ctx->pubQos = iniparser_getint(ini, "publisher:pubQos", DEF_QOS);
+ ctx->interval = iniparser_getint(ini, "publisher:interval", DEF_PUBINTERVAL);
+ log_info("Parser publisher topic \"%s\" with Qos[%d] interval[%d]\n", ctx->pubTopic, ctx->pubQos, ctx->interval);
+
+ /*+------------------------------------------------------+
+ *| parser subscriber settings |
+ *+------------------------------------------------------+*/
+
+ if( !(str=iniparser_getstring(ini, "subsciber:subTopic", NULL)) )
+ {
+ log_error("ERROR: Parser MQTT broker publisher topic failure\n");
+ rv = -7;
+ goto cleanup;
+ }
+ strncpy(ctx->subTopic, str, sizeof(ctx->subTopic) );
+
+ ctx->subQos = iniparser_getint(ini, "subsciber:subQos", DEF_QOS);
+ log_info("Parser subscriber topic \"%s\" with Qos[%d]\n", ctx->subTopic, ctx->subQos);
+
+cleanup:
+ if( ini )
+ iniparser_freedict(ini);
+
+ if( rv )
+ log_close();
+
+ return rv;
+}
+
diff --git a/project/coreMQTT/conf.h b/project/coreMQTT/conf.h
new file mode 100644
index 0000000..89d3a66
--- /dev/null
+++ b/project/coreMQTT/conf.h
@@ -0,0 +1,71 @@
+/*********************************************************************************
+ * Copyright: (C) 2019 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: conf.h
+ * Description: This file is mqttd configure file parser function
+ *
+ * Version: 1.0.0(2019年06月25日)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "2019年06月25日 22时23分55秒"
+ *
+ ********************************************************************************/
+#ifndef __CONF_H_
+#define __CONF_H_
+
+enum
+{
+ Qos0, /* 发送者只发送一次消息,不进行重试,Broker不会返回确认消息。在Qos0情况下,Broker可能没有接受到消息 */
+ Qos1, /* 发送者最少发送一次消息,确保消息到达Broker,Broker需要返回确认消息PUBACK。在Qos1情况下,Broker可能接受到重复消息 */
+ Qos2, /* Qos2使用两阶段确认来保证消息的不丢失和不重复。在Qos2情况下,Broker肯定会收到消息,且只收到一次 */
+};
+
+#define DEF_KEEPALIVE 30
+#define DEF_PUBINTERVAL 120
+#define DEF_QOS Qos2
+
+typedef struct hwconf_s
+{
+ int relay; /* relay aviable or not. 0:Disable 1: Enable */
+ int led; /* RGB led aviable or not. 0:Disable 1: Enable */
+ int beeper; /* beeper aviable or not. 0:Disable 1: Enable */
+ int ds18b20; /* DS1B820 aviable or not. 0:Disable 1: Enable */
+ int sht2x; /* SHT20 aviable or not. 0:Disable 1: Enable */
+ int tsl2561; /* TSL2561 aviable or not. 0:Disable 1: Enable */
+} hwconf_t;
+
+
+typedef struct mqtt_ctx_s
+{
+ char devid[32]; /* device ID */
+
+ /* hardware configuration */
+ hwconf_t hwconf;
+
+ /* logger settings */
+ char logfile[128]; /* logger record file */
+ int loglevel; /* logger level */
+ int logsize; /* logger file maxsize, oversize will rollback */
+
+ /* Broker settings */
+ char host[128]; /* MQTT broker server name */
+ int port; /* MQTT broker listen port */
+ char uid[64]; /* username */
+ char pwd[64]; /* password */
+ int keepalive; /* MQTT broker send PING message to subsciber/publisher keepalive timeout<seconds> */
+
+ /* Publisher settings */
+ char pubTopic[256]; /* Publisher topic */
+ int pubQos; /* Publisher Qos */
+ int interval ; /* Publish sensor data interval time, unit seconds */
+
+ /* Subscriber settings */
+ char subTopic[256]; /* Subscriber topic */
+ int subQos; /* Subscriber Qos */
+} mqtt_ctx_t;
+
+
+extern int mqttd_parser_conf(const char *conf_file, mqtt_ctx_t *ctx, int debug);
+
+#endif /* ----- #ifndef _CONF_H_ ----- */
+
diff --git a/project/coreMQTT/coreJSON/README.md b/project/coreMQTT/coreJSON/README.md
new file mode 100644
index 0000000..d08f61f
--- /dev/null
+++ b/project/coreMQTT/coreJSON/README.md
@@ -0,0 +1,65 @@
+## coreJSON Library
+
+This repository contains the coreJSON library, a parser that strictly enforces the ECMA-404 JSON standard and is suitable for low memory footprint embedded devices. The coreJSON library is distributed under the [MIT Open Source License](LICENSE).
+
+This library has gone through code quality checks including verification that no function has a [GNU Complexity](https://www.gnu.org/software/complexity/manual/complexity.html) score over 8, and checks against deviations from mandatory rules in the [MISRA coding standard](https://www.misra.org.uk). Deviations from the MISRA C:2012 guidelines are documented under [MISRA Deviations](MISRA.md). This library has also undergone both static code analysis from [Coverity static analysis](https://scan.coverity.com/), and validation of memory safety through the [CBMC automated reasoning tool](https://www.cprover.org/cbmc/).
+
+See memory requirements for this library [here](./docs/doxygen/include/size_table.md).
+
+**coreJSON v3.2.0 [source code](https://github.com/FreeRTOS/coreJSON/tree/v3.2.0/source) is part of the [FreeRTOS 202210.00 LTS](https://github.com/FreeRTOS/FreeRTOS-LTS/tree/202210.00-LTS) release.**
+
+**coreJSON v3.0.0 [source code](https://github.com/FreeRTOS/coreJSON/tree/v3.0.0/source) is part of the [FreeRTOS 202012.00 LTS](https://github.com/FreeRTOS/FreeRTOS-LTS/tree/202012.00-LTS) release.**
+
+## Reference example
+
+```c
+#include <stdio.h>
+#include "core_json.h"
+
+int main()
+{
+ // Variables used in this example.
+ JSONStatus_t result;
+ char buffer[] = "{\"foo\":\"abc\",\"bar\":{\"foo\":\"xyz\"}}";
+ size_t bufferLength = sizeof( buffer ) - 1;
+ char queryKey[] = "bar.foo";
+ size_t queryKeyLength = sizeof( queryKey ) - 1;
+ char * value;
+ size_t valueLength;
+
+ // Calling JSON_Validate() is not necessary if the document is guaranteed to be valid.
+ result = JSON_Validate( buffer, bufferLength );
+
+ if( result == JSONSuccess )
+ {
+ result = JSON_Search( buffer, bufferLength, queryKey, queryKeyLength,
+ &value, &valueLength );
+ }
+
+ if( result == JSONSuccess )
+ {
+ // The pointer "value" will point to a location in the "buffer".
+ char save = value[ valueLength ];
+ // After saving the character, set it to a null byte for printing.
+ value[ valueLength ] = '\0';
+ // "Found: bar.foo -> xyz" will be printed.
+ printf( "Found: %s -> %s\n", queryKey, value );
+ // Restore the original character.
+ value[ valueLength ] = save;
+ }
+
+ return 0;
+}
+```
+A search may descend through nested objects when the `queryKey` contains matching key strings joined by a separator, `.`. In the example above, `bar` has the value `{"foo":"xyz"}`. Therefore, a search for query key `bar.foo` would output `xyz`.
+
+## Documentation
+
+For pre-generated documentation, please see the documentation linked in the locations below:
+
+| Location |
+| :-: |
+| [FreeRTOS.org](https://freertos.org/Documentation/api-ref/coreJSON/docs/doxygen/output/html/index.html) |
+
+Note that the latest included version of the coreJSON library may differ across repositories.
+
diff --git a/project/coreMQTT/coreJSON/core_json.c b/project/coreMQTT/coreJSON/core_json.c
new file mode 100644
index 0000000..06a0cb4
--- /dev/null
+++ b/project/coreMQTT/coreJSON/core_json.c
@@ -0,0 +1,1818 @@
+/*
+ * coreJSON v3.2.0
+ * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_json.c
+ * @brief The source file that implements the user-facing functions in core_json.h.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "core_json.h"
+
+/** @cond DO_NOT_DOCUMENT */
+
+/* A compromise to satisfy both MISRA and CBMC */
+typedef union
+{
+ char c;
+ uint8_t u;
+} char_;
+
+#if ( CHAR_MIN == 0 )
+ #define isascii_( x ) ( ( x ) <= '\x7F' )
+#else
+ #define isascii_( x ) ( ( x ) >= '\0' )
+#endif
+#define iscntrl_( x ) ( isascii_( x ) && ( ( x ) < ' ' ) )
+#define isdigit_( x ) ( ( ( x ) >= '0' ) && ( ( x ) <= '9' ) )
+/* NB. This is whitespace as defined by the JSON standard (ECMA-404). */
+#define isspace_( x ) \
+ ( ( ( x ) == ' ' ) || ( ( x ) == '\t' ) || \
+ ( ( x ) == '\n' ) || ( ( x ) == '\r' ) )
+
+#define isOpenBracket_( x ) ( ( ( x ) == '{' ) || ( ( x ) == '[' ) )
+#define isCloseBracket_( x ) ( ( ( x ) == '}' ) || ( ( x ) == ']' ) )
+#define isCurlyPair_( x, y ) ( ( ( x ) == '{' ) && ( ( y ) == '}' ) )
+#define isSquarePair_( x, y ) ( ( ( x ) == '[' ) && ( ( y ) == ']' ) )
+#define isMatchingBracket_( x, y ) ( isCurlyPair_( x, y ) || isSquarePair_( x, y ) )
+#define isSquareOpen_( x ) ( ( x ) == '[' )
+#define isSquareClose_( x ) ( ( x ) == ']' )
+
+/**
+ * @brief Advance buffer index beyond whitespace.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ */
+static void skipSpace( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ size_t i;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ for( i = *start; i < max; i++ )
+ {
+ if( !isspace_( buf[ i ] ) )
+ {
+ break;
+ }
+ }
+
+ *start = i;
+}
+
+/**
+ * @brief Count the leading 1s in a byte.
+ *
+ * The high-order 1 bits of the first byte in a UTF-8 encoding
+ * indicate the number of additional bytes to follow.
+ *
+ * @return the count
+ */
+static size_t countHighBits( uint8_t c )
+{
+ uint8_t n = c;
+ size_t i = 0;
+
+ while( ( n & 0x80U ) != 0U )
+ {
+ i++;
+ n = ( n & 0x7FU ) << 1U;
+ }
+
+ return i;
+}
+
+/**
+ * @brief Is the value a legal Unicode code point and encoded with
+ * the fewest bytes?
+ *
+ * The last Unicode code point is 0x10FFFF.
+ *
+ * Unicode 3.1 disallows UTF-8 interpretation of non-shortest form sequences.
+ * 1 byte encodes 0 through 7 bits
+ * 2 bytes encode 8 through 5+6 = 11 bits
+ * 3 bytes encode 12 through 4+6+6 = 16 bits
+ * 4 bytes encode 17 through 3+6+6+6 = 21 bits
+ *
+ * Unicode 3.2 disallows UTF-8 code point values in the surrogate range,
+ * [U+D800 to U+DFFF].
+ *
+ * @note Disallow ASCII, as this is called only for multibyte sequences.
+ */
+static bool shortestUTF8( size_t length,
+ uint32_t value )
+{
+ bool ret = false;
+ uint32_t min, max;
+
+ assert( ( length >= 2U ) && ( length <= 4U ) );
+
+ switch( length )
+ {
+ case 2:
+ min = ( uint32_t ) 1 << 7U;
+ max = ( ( uint32_t ) 1 << 11U ) - 1U;
+ break;
+
+ case 3:
+ min = ( uint32_t ) 1 << 11U;
+ max = ( ( uint32_t ) 1 << 16U ) - 1U;
+ break;
+
+ default:
+ min = ( uint32_t ) 1 << 16U;
+ max = 0x10FFFFU;
+ break;
+ }
+
+ if( ( value >= min ) && ( value <= max ) &&
+ ( ( value < 0xD800U ) || ( value > 0xDFFFU ) ) )
+ {
+ ret = true;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond a UTF-8 code point.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @return true if a valid code point was present;
+ * false otherwise.
+ *
+ * 00-7F Single-byte character
+ * 80-BF Trailing byte
+ * C0-DF Leading byte of two-byte character
+ * E0-EF Leading byte of three-byte character
+ * F0-F7 Leading byte of four-byte character
+ * F8-FB Illegal (formerly leading byte of five-byte character)
+ * FC-FD Illegal (formerly leading byte of six-byte character)
+ * FE-FF Illegal
+ *
+ * The octet values C0, C1, and F5 to FF are illegal, since C0 and C1
+ * would introduce a non-shortest sequence, and F5 or above would
+ * introduce a value greater than the last code point, 0x10FFFF.
+ */
+static bool skipUTF8MultiByte( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ bool ret = false;
+ size_t i, bitCount, j;
+ uint32_t value = 0;
+ char_ c;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ i = *start;
+ assert( i < max );
+ assert( !isascii_( buf[ i ] ) );
+
+ c.c = buf[ i ];
+
+ if( ( c.u > 0xC1U ) && ( c.u < 0xF5U ) )
+ {
+ bitCount = countHighBits( c.u );
+ value = ( ( uint32_t ) c.u ) & ( ( ( uint32_t ) 1 << ( 7U - bitCount ) ) - 1U );
+
+ /* The bit count is 1 greater than the number of bytes,
+ * e.g., when j is 2, we skip one more byte. */
+ for( j = bitCount - 1U; j > 0U; j-- )
+ {
+ i++;
+
+ if( i >= max )
+ {
+ break;
+ }
+
+ c.c = buf[ i ];
+
+ /* Additional bytes must match 10xxxxxx. */
+ if( ( c.u & 0xC0U ) != 0x80U )
+ {
+ break;
+ }
+
+ value = ( value << 6U ) | ( c.u & 0x3FU );
+ }
+
+ if( ( j == 0U ) && ( shortestUTF8( bitCount, value ) == true ) )
+ {
+ *start = i + 1U;
+ ret = true;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond an ASCII or UTF-8 code point.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @return true if a valid code point was present;
+ * false otherwise.
+ */
+static bool skipUTF8( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ bool ret = false;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ if( *start < max )
+ {
+ if( isascii_( buf[ *start ] ) )
+ {
+ *start += 1U;
+ ret = true;
+ }
+ else
+ {
+ ret = skipUTF8MultiByte( buf, start, max );
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Convert a hexadecimal character to an integer.
+ *
+ * @param[in] c The character to convert.
+ *
+ * @return the integer value upon success or NOT_A_HEX_CHAR on failure.
+ */
+#define NOT_A_HEX_CHAR ( 0x10U )
+static uint8_t hexToInt( char c )
+{
+ char_ n;
+
+ n.c = c;
+
+ if( ( c >= 'a' ) && ( c <= 'f' ) )
+ {
+ n.c -= 'a';
+ n.u += 10U;
+ }
+ else if( ( c >= 'A' ) && ( c <= 'F' ) )
+ {
+ n.c -= 'A';
+ n.u += 10U;
+ }
+ else if( isdigit_( c ) )
+ {
+ n.c -= '0';
+ }
+ else
+ {
+ n.u = NOT_A_HEX_CHAR;
+ }
+
+ return n.u;
+}
+
+/**
+ * @brief Advance buffer index beyond a single \u Unicode
+ * escape sequence and output the value.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ * @param[out] outValue The value of the hex digits.
+ *
+ * @return true if a valid escape sequence was present;
+ * false otherwise.
+ *
+ * @note For the sake of security, \u0000 is disallowed.
+ */
+static bool skipOneHexEscape( const char * buf,
+ size_t * start,
+ size_t max,
+ uint16_t * outValue )
+{
+ bool ret = false;
+ size_t i, end;
+ uint16_t value = 0;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+ assert( outValue != NULL );
+
+ i = *start;
+#define HEX_ESCAPE_LENGTH ( 6U ) /* e.g., \u1234 */
+ /* MISRA Ref 14.3.1 [Configuration dependent invariant] */
+ /* More details at: https://github.com/FreeRTOS/coreJSON/blob/main/MISRA.md#rule-143 */
+ /* coverity[misra_c_2012_rule_14_3_violation] */
+ end = ( i <= ( SIZE_MAX - HEX_ESCAPE_LENGTH ) ) ? ( i + HEX_ESCAPE_LENGTH ) : SIZE_MAX;
+
+ if( ( end < max ) && ( buf[ i ] == '\\' ) && ( buf[ i + 1U ] == 'u' ) )
+ {
+ for( i += 2U; i < end; i++ )
+ {
+ uint8_t n = hexToInt( buf[ i ] );
+
+ if( n == NOT_A_HEX_CHAR )
+ {
+ break;
+ }
+
+ value = ( value << 4U ) | n;
+ }
+ }
+
+ if( ( i == end ) && ( value > 0U ) )
+ {
+ ret = true;
+ *outValue = value;
+ *start = i;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond one or a pair of \u Unicode escape sequences.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * Surrogate pairs are two escape sequences that together denote
+ * a code point outside the Basic Multilingual Plane. They must
+ * occur as a pair with the first "high" value in [U+D800, U+DBFF],
+ * and the second "low" value in [U+DC00, U+DFFF].
+ *
+ * @return true if a valid escape sequence was present;
+ * false otherwise.
+ *
+ * @note For the sake of security, \u0000 is disallowed.
+ */
+#define isHighSurrogate( x ) ( ( ( x ) >= 0xD800U ) && ( ( x ) <= 0xDBFFU ) )
+#define isLowSurrogate( x ) ( ( ( x ) >= 0xDC00U ) && ( ( x ) <= 0xDFFFU ) )
+
+static bool skipHexEscape( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ bool ret = false;
+ size_t i;
+ uint16_t value;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ i = *start;
+
+ if( skipOneHexEscape( buf, &i, max, &value ) == true )
+ {
+ if( isHighSurrogate( value ) )
+ {
+ if( ( skipOneHexEscape( buf, &i, max, &value ) == true ) &&
+ ( isLowSurrogate( value ) ) )
+ {
+ ret = true;
+ }
+ }
+ else if( isLowSurrogate( value ) )
+ {
+ /* premature low surrogate */
+ }
+ else
+ {
+ ret = true;
+ }
+ }
+
+ if( ret == true )
+ {
+ *start = i;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond an escape sequence.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @return true if a valid escape sequence was present;
+ * false otherwise.
+ *
+ * @note For the sake of security, \NUL is disallowed.
+ */
+static bool skipEscape( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ bool ret = false;
+ size_t i;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ i = *start;
+
+ if( ( i < ( max - 1U ) ) && ( buf[ i ] == '\\' ) )
+ {
+ char c = buf[ i + 1U ];
+
+ switch( c )
+ {
+ case '\0':
+ break;
+
+ case 'u':
+ ret = skipHexEscape( buf, &i, max );
+ break;
+
+ case '"':
+ case '\\':
+ case '/':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ i += 2U;
+ ret = true;
+ break;
+
+ default:
+
+ /* a control character: (NUL,SPACE) */
+ if( iscntrl_( c ) )
+ {
+ i += 2U;
+ ret = true;
+ }
+
+ break;
+ }
+ }
+
+ if( ret == true )
+ {
+ *start = i;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond a double-quoted string.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @return true if a valid string was present;
+ * false otherwise.
+ */
+static bool skipString( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ bool ret = false;
+ size_t i;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ i = *start;
+
+ if( ( i < max ) && ( buf[ i ] == '"' ) )
+ {
+ i++;
+
+ while( i < max )
+ {
+ if( buf[ i ] == '"' )
+ {
+ ret = true;
+ i++;
+ break;
+ }
+
+ if( buf[ i ] == '\\' )
+ {
+ if( skipEscape( buf, &i, max ) != true )
+ {
+ break;
+ }
+ }
+ /* An unescaped control character is not allowed. */
+ else if( iscntrl_( buf[ i ] ) )
+ {
+ break;
+ }
+ else if( skipUTF8( buf, &i, max ) != true )
+ {
+ break;
+ }
+ else
+ {
+ /* MISRA 15.7 */
+ }
+ }
+ }
+
+ if( ret == true )
+ {
+ *start = i;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Compare the leading n bytes of two character sequences.
+ *
+ * @param[in] a first character sequence
+ * @param[in] b second character sequence
+ * @param[in] n number of bytes
+ *
+ * @return true if the sequences are the same;
+ * false otherwise
+ */
+static bool strnEq( const char * a,
+ const char * b,
+ size_t n )
+{
+ size_t i;
+
+ assert( ( a != NULL ) && ( b != NULL ) );
+
+ for( i = 0; i < n; i++ )
+ {
+ if( a[ i ] != b[ i ] )
+ {
+ break;
+ }
+ }
+
+ return ( i == n ) ? true : false;
+}
+
+/**
+ * @brief Advance buffer index beyond a literal.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ * @param[in] literal The type of literal.
+ * @param[in] length The length of the literal.
+ *
+ * @return true if the literal was present;
+ * false otherwise.
+ */
+static bool skipLiteral( const char * buf,
+ size_t * start,
+ size_t max,
+ const char * literal,
+ size_t length )
+{
+ bool ret = false;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+ assert( literal != NULL );
+
+ if( ( *start < max ) && ( length <= ( max - *start ) ) )
+ {
+ ret = strnEq( &buf[ *start ], literal, length );
+ }
+
+ if( ret == true )
+ {
+ *start += length;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond a JSON literal.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @return true if a valid literal was present;
+ * false otherwise.
+ */
+static bool skipAnyLiteral( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ bool ret = false;
+
+#define skipLit_( x ) \
+ ( skipLiteral( buf, start, max, ( x ), ( sizeof( x ) - 1UL ) ) == true )
+
+ if( skipLit_( "true" ) || skipLit_( "false" ) || skipLit_( "null" ) )
+ {
+ ret = true;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond one or more digits.
+ * Optionally, output the integer value of the digits.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ * @param[out] outValue The integer value of the digits.
+ *
+ * @note outValue may be NULL. If not NULL, and the output
+ * exceeds ~2 billion, then -1 is output.
+ *
+ * @return true if a digit was present;
+ * false otherwise.
+ */
+#define MAX_FACTOR ( MAX_INDEX_VALUE / 10 )
+static bool skipDigits( const char * buf,
+ size_t * start,
+ size_t max,
+ int32_t * outValue )
+{
+ bool ret = false;
+ size_t i, saveStart;
+ int32_t value = 0;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ saveStart = *start;
+
+ for( i = *start; i < max; i++ )
+ {
+ if( !isdigit_( buf[ i ] ) )
+ {
+ break;
+ }
+
+ if( ( outValue != NULL ) && ( value > -1 ) )
+ {
+ int8_t n = ( int8_t ) hexToInt( buf[ i ] );
+
+ if( value <= MAX_FACTOR )
+ {
+ value = ( value * 10 ) + n;
+ }
+ else
+ {
+ value = -1;
+ }
+ }
+ }
+
+ if( i > saveStart )
+ {
+ ret = true;
+ *start = i;
+
+ if( outValue != NULL )
+ {
+ *outValue = value;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond the decimal portion of a number.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ */
+static void skipDecimals( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ size_t i;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ i = *start;
+
+ if( ( i < max ) && ( buf[ i ] == '.' ) )
+ {
+ i++;
+
+ if( skipDigits( buf, &i, max, NULL ) == true )
+ {
+ *start = i;
+ }
+ }
+}
+
+/**
+ * @brief Advance buffer index beyond the exponent portion of a number.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ */
+static void skipExponent( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ size_t i;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ i = *start;
+
+ if( ( i < max ) && ( ( buf[ i ] == 'e' ) || ( buf[ i ] == 'E' ) ) )
+ {
+ i++;
+
+ if( ( i < max ) && ( ( buf[ i ] == '-' ) || ( buf[ i ] == '+' ) ) )
+ {
+ i++;
+ }
+
+ if( skipDigits( buf, &i, max, NULL ) == true )
+ {
+ *start = i;
+ }
+ }
+}
+
+/**
+ * @brief Advance buffer index beyond a number.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @return true if a valid number was present;
+ * false otherwise.
+ */
+static bool skipNumber( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ bool ret = false;
+ size_t i;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ i = *start;
+
+ if( ( i < max ) && ( buf[ i ] == '-' ) )
+ {
+ i++;
+ }
+
+ if( i < max )
+ {
+ /* JSON disallows superfluous leading zeroes, so an
+ * initial zero must either be alone, or followed by
+ * a decimal or exponent.
+ *
+ * Should there be a digit after the zero, that digit
+ * will not be skipped by this function, and later parsing
+ * will judge this an illegal document. */
+ if( buf[ i ] == '0' )
+ {
+ ret = true;
+ i++;
+ }
+ else
+ {
+ ret = skipDigits( buf, &i, max, NULL );
+ }
+ }
+
+ if( ret == true )
+ {
+ skipDecimals( buf, &i, max );
+ skipExponent( buf, &i, max );
+ *start = i;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond a scalar value.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @return true if a scalar value was present;
+ * false otherwise.
+ */
+static bool skipAnyScalar( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ bool ret = false;
+
+ if( ( skipString( buf, start, max ) == true ) ||
+ ( skipAnyLiteral( buf, start, max ) == true ) ||
+ ( skipNumber( buf, start, max ) == true ) )
+ {
+ ret = true;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond a comma separator
+ * and surrounding whitespace.
+ *
+ * JSON uses a comma to separate values in an array and key-value
+ * pairs in an object. JSON does not permit a trailing comma.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @return true if a non-terminal comma was present;
+ * false otherwise.
+ */
+static bool skipSpaceAndComma( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ bool ret = false;
+ size_t i;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ skipSpace( buf, start, max );
+ i = *start;
+
+ if( ( i < max ) && ( buf[ i ] == ',' ) )
+ {
+ i++;
+ skipSpace( buf, &i, max );
+
+ if( ( i < max ) && !isCloseBracket_( buf[ i ] ) )
+ {
+ ret = true;
+ *start = i;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond the scalar values of an array.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @note Stops advance if a value is an object or array.
+ */
+static void skipArrayScalars( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ size_t i;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ i = *start;
+
+ while( i < max )
+ {
+ if( skipAnyScalar( buf, &i, max ) != true )
+ {
+ break;
+ }
+
+ if( skipSpaceAndComma( buf, &i, max ) != true )
+ {
+ break;
+ }
+ }
+
+ *start = i;
+}
+
+/**
+ * @brief Advance buffer index beyond the scalar key-value pairs
+ * of an object.
+ *
+ * In JSON, objects consist of comma-separated key-value pairs.
+ * A key is always a string (a scalar) while a value may be a
+ * scalar, an object, or an array. A colon must appear between
+ * each key and value.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @note Stops advance if a value is an object or array.
+ */
+static void skipObjectScalars( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ size_t i;
+ bool comma;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ i = *start;
+
+ while( i < max )
+ {
+ if( skipString( buf, &i, max ) != true )
+ {
+ break;
+ }
+
+ skipSpace( buf, &i, max );
+
+ if( ( i < max ) && ( buf[ i ] != ':' ) )
+ {
+ break;
+ }
+
+ i++;
+ skipSpace( buf, &i, max );
+
+ if( ( i < max ) && isOpenBracket_( buf[ i ] ) )
+ {
+ *start = i;
+ break;
+ }
+
+ if( skipAnyScalar( buf, &i, max ) != true )
+ {
+ break;
+ }
+
+ comma = skipSpaceAndComma( buf, &i, max );
+ *start = i;
+
+ if( comma != true )
+ {
+ break;
+ }
+ }
+}
+
+/**
+ * @brief Advance buffer index beyond one or more scalars.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ * @param[in] mode The first character of an array '[' or object '{'.
+ */
+static void skipScalars( const char * buf,
+ size_t * start,
+ size_t max,
+ char mode )
+{
+ assert( isOpenBracket_( mode ) );
+
+ skipSpace( buf, start, max );
+
+ if( mode == '[' )
+ {
+ skipArrayScalars( buf, start, max );
+ }
+ else
+ {
+ skipObjectScalars( buf, start, max );
+ }
+}
+
+/**
+ * @brief Advance buffer index beyond a collection and handle nesting.
+ *
+ * A stack is used to continue parsing the prior collection type
+ * when a nested collection is finished.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ *
+ * @return #JSONSuccess if the buffer contents are a valid JSON collection;
+ * #JSONIllegalDocument if the buffer contents are NOT valid JSON;
+ * #JSONMaxDepthExceeded if object and array nesting exceeds a threshold;
+ * #JSONPartial if the buffer contents are potentially valid but incomplete.
+ */
+#ifndef JSON_MAX_DEPTH
+ #define JSON_MAX_DEPTH 32
+#endif
+static JSONStatus_t skipCollection( const char * buf,
+ size_t * start,
+ size_t max )
+{
+ JSONStatus_t ret = JSONPartial;
+ char c, stack[ JSON_MAX_DEPTH ];
+ int16_t depth = -1;
+ size_t i;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+
+ i = *start;
+
+ while( i < max )
+ {
+ c = buf[ i ];
+ i++;
+
+ switch( c )
+ {
+ case '{':
+ case '[':
+ depth++;
+
+ if( depth == JSON_MAX_DEPTH )
+ {
+ ret = JSONMaxDepthExceeded;
+ break;
+ }
+
+ stack[ depth ] = c;
+ skipScalars( buf, &i, max, stack[ depth ] );
+ break;
+
+ case '}':
+ case ']':
+
+ if( ( depth > 0 ) && isMatchingBracket_( stack[ depth ], c ) )
+ {
+ depth--;
+
+ if( skipSpaceAndComma( buf, &i, max ) == true )
+ {
+ skipScalars( buf, &i, max, stack[ depth ] );
+ }
+
+ break;
+ }
+
+ ret = ( ( depth == 0 ) && isMatchingBracket_( stack[ depth ], c ) ) ?
+ JSONSuccess : JSONIllegalDocument;
+ break;
+
+ default:
+ ret = JSONIllegalDocument;
+ break;
+ }
+
+ if( ret != JSONPartial )
+ {
+ break;
+ }
+ }
+
+ if( ret == JSONSuccess )
+ {
+ *start = i;
+ }
+
+ return ret;
+}
+
+/** @endcond */
+
+/**
+ * See core_json.h for docs.
+ *
+ * Verify that the entire buffer contains exactly one scalar
+ * or collection within optional whitespace.
+ */
+JSONStatus_t JSON_Validate( const char * buf,
+ size_t max )
+{
+ JSONStatus_t ret;
+ size_t i = 0;
+
+ if( buf == NULL )
+ {
+ ret = JSONNullParameter;
+ }
+ else if( max == 0U )
+ {
+ ret = JSONBadParameter;
+ }
+ else
+ {
+ skipSpace( buf, &i, max );
+
+ /** @cond DO_NOT_DOCUMENT */
+ #ifndef JSON_VALIDATE_COLLECTIONS_ONLY
+ if( skipAnyScalar( buf, &i, max ) == true )
+ {
+ ret = JSONSuccess;
+ }
+ else
+ #endif
+ /** @endcond */
+ {
+ ret = skipCollection( buf, &i, max );
+ }
+ }
+
+ if( ( ret == JSONSuccess ) && ( i < max ) )
+ {
+ skipSpace( buf, &i, max );
+
+ if( i != max )
+ {
+ ret = JSONIllegalDocument;
+ }
+ }
+
+ return ret;
+}
+
+/** @cond DO_NOT_DOCUMENT */
+
+/**
+ * @brief Output index and length for the next value.
+ *
+ * Also advances the buffer index beyond the value.
+ * The value may be a scalar or a collection.
+ * The start index should point to the beginning of the value.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ * @param[out] value A pointer to receive the index of the value.
+ * @param[out] valueLength A pointer to receive the length of the value.
+ *
+ * @return true if a value was present;
+ * false otherwise.
+ */
+static bool nextValue( const char * buf,
+ size_t * start,
+ size_t max,
+ size_t * value,
+ size_t * valueLength )
+{
+ bool ret = true;
+ size_t i, valueStart;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+ assert( ( value != NULL ) && ( valueLength != NULL ) );
+
+ i = *start;
+ valueStart = i;
+
+ if( ( skipAnyScalar( buf, &i, max ) == true ) ||
+ ( skipCollection( buf, &i, max ) == JSONSuccess ) )
+ {
+ *value = valueStart;
+ *valueLength = i - valueStart;
+ }
+ else
+ {
+ ret = false;
+ }
+
+ if( ret == true )
+ {
+ *start = i;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Output indexes for the next key-value pair of an object.
+ *
+ * Also advances the buffer index beyond the key-value pair.
+ * The value may be a scalar or a collection.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ * @param[out] key A pointer to receive the index of the key.
+ * @param[out] keyLength A pointer to receive the length of the key.
+ * @param[out] value A pointer to receive the index of the value.
+ * @param[out] valueLength A pointer to receive the length of the value.
+ *
+ * @return true if a key-value pair was present;
+ * false otherwise.
+ */
+static bool nextKeyValuePair( const char * buf,
+ size_t * start,
+ size_t max,
+ size_t * key,
+ size_t * keyLength,
+ size_t * value,
+ size_t * valueLength )
+{
+ bool ret = true;
+ size_t i, keyStart;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
+ assert( ( key != NULL ) && ( keyLength != NULL ) );
+ assert( ( value != NULL ) && ( valueLength != NULL ) );
+
+ i = *start;
+ keyStart = i;
+
+ if( skipString( buf, &i, max ) == true )
+ {
+ *key = keyStart + 1U;
+ *keyLength = i - keyStart - 2U;
+ }
+ else
+ {
+ ret = false;
+ }
+
+ if( ret == true )
+ {
+ skipSpace( buf, &i, max );
+
+ if( ( i < max ) && ( buf[ i ] == ':' ) )
+ {
+ i++;
+ skipSpace( buf, &i, max );
+ }
+ else
+ {
+ ret = false;
+ }
+ }
+
+ if( ret == true )
+ {
+ ret = nextValue( buf, &i, max, value, valueLength );
+ }
+
+ if( ret == true )
+ {
+ *start = i;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Find a key in a JSON object and output a pointer to its value.
+ *
+ * @param[in] buf The buffer to search.
+ * @param[in] max size of the buffer.
+ * @param[in] query The object keys and array indexes to search for.
+ * @param[in] queryLength Length of the key.
+ * @param[out] outValue A pointer to receive the index of the value found.
+ * @param[out] outValueLength A pointer to receive the length of the value found.
+ *
+ * Iterate over the key-value pairs of an object, looking for a matching key.
+ *
+ * @return true if the query is matched and the value output;
+ * false otherwise.
+ *
+ * @note Parsing stops upon finding a match.
+ */
+static bool objectSearch( const char * buf,
+ size_t max,
+ const char * query,
+ size_t queryLength,
+ size_t * outValue,
+ size_t * outValueLength )
+{
+ bool ret = false;
+
+ size_t i = 0, key, keyLength, value = 0, valueLength = 0;
+
+ assert( ( buf != NULL ) && ( query != NULL ) );
+ assert( ( outValue != NULL ) && ( outValueLength != NULL ) );
+
+ skipSpace( buf, &i, max );
+
+ if( ( i < max ) && ( buf[ i ] == '{' ) )
+ {
+ i++;
+ skipSpace( buf, &i, max );
+
+ while( i < max )
+ {
+ if( nextKeyValuePair( buf, &i, max, &key, &keyLength,
+ &value, &valueLength ) != true )
+ {
+ break;
+ }
+
+ if( ( queryLength == keyLength ) &&
+ ( strnEq( query, &buf[ key ], keyLength ) == true ) )
+ {
+ ret = true;
+ break;
+ }
+
+ if( skipSpaceAndComma( buf, &i, max ) != true )
+ {
+ break;
+ }
+ }
+ }
+
+ if( ret == true )
+ {
+ *outValue = value;
+ *outValueLength = valueLength;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Find an index in a JSON array and output a pointer to its value.
+ *
+ * @param[in] buf The buffer to search.
+ * @param[in] max size of the buffer.
+ * @param[in] queryIndex The index to search for.
+ * @param[out] outValue A pointer to receive the index of the value found.
+ * @param[out] outValueLength A pointer to receive the length of the value found.
+ *
+ * Iterate over the values of an array, looking for a matching index.
+ *
+ * @return true if the queryIndex is found and the value output;
+ * false otherwise.
+ *
+ * @note Parsing stops upon finding a match.
+ */
+static bool arraySearch( const char * buf,
+ size_t max,
+ uint32_t queryIndex,
+ size_t * outValue,
+ size_t * outValueLength )
+{
+ bool ret = false;
+ size_t i = 0, value = 0, valueLength = 0;
+ uint32_t currentIndex = 0;
+
+ assert( buf != NULL );
+ assert( ( outValue != NULL ) && ( outValueLength != NULL ) );
+
+ skipSpace( buf, &i, max );
+
+ if( ( i < max ) && ( buf[ i ] == '[' ) )
+ {
+ i++;
+ skipSpace( buf, &i, max );
+
+ while( i < max )
+ {
+ if( nextValue( buf, &i, max, &value, &valueLength ) != true )
+ {
+ break;
+ }
+
+ if( currentIndex == queryIndex )
+ {
+ ret = true;
+ break;
+ }
+
+ if( skipSpaceAndComma( buf, &i, max ) != true )
+ {
+ break;
+ }
+
+ currentIndex++;
+ }
+ }
+
+ if( ret == true )
+ {
+ *outValue = value;
+ *outValueLength = valueLength;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Advance buffer index beyond a query part.
+ *
+ * The part is the portion of the query which is not
+ * a separator or array index.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in,out] start The index at which to begin.
+ * @param[in] max The size of the buffer.
+ * @param[out] outLength The length of the query part.
+ *
+ * @return true if a valid string was present;
+ * false otherwise.
+ */
+#ifndef JSON_QUERY_KEY_SEPARATOR
+ #define JSON_QUERY_KEY_SEPARATOR '.'
+#endif
+#define isSeparator_( x ) ( ( x ) == JSON_QUERY_KEY_SEPARATOR )
+static bool skipQueryPart( const char * buf,
+ size_t * start,
+ size_t max,
+ size_t * outLength )
+{
+ bool ret = false;
+ size_t i;
+
+ assert( ( buf != NULL ) && ( start != NULL ) && ( outLength != NULL ) );
+ assert( max > 0U );
+
+ i = *start;
+
+ while( ( i < max ) &&
+ !isSeparator_( buf[ i ] ) &&
+ !isSquareOpen_( buf[ i ] ) )
+ {
+ i++;
+ }
+
+ if( i > *start )
+ {
+ ret = true;
+ *outLength = i - *start;
+ *start = i;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Handle a nested search by iterating over the parts of the query.
+ *
+ * @param[in] buf The buffer to search.
+ * @param[in] max size of the buffer.
+ * @param[in] query The object keys and array indexes to search for.
+ * @param[in] queryLength Length of the key.
+ * @param[out] outValue A pointer to receive the index of the value found.
+ * @param[out] outValueLength A pointer to receive the length of the value found.
+ *
+ * @return #JSONSuccess if the query is matched and the value output;
+ * #JSONBadParameter if the query is empty, or any part is empty,
+ * or an index is too large to convert;
+ * #JSONNotFound if the query is NOT found.
+ *
+ * @note Parsing stops upon finding a match.
+ */
+static JSONStatus_t multiSearch( const char * buf,
+ size_t max,
+ const char * query,
+ size_t queryLength,
+ size_t * outValue,
+ size_t * outValueLength )
+{
+ JSONStatus_t ret = JSONSuccess;
+ size_t i = 0, start = 0, queryStart = 0, value = 0, length = max;
+
+ assert( ( buf != NULL ) && ( query != NULL ) );
+ assert( ( outValue != NULL ) && ( outValueLength != NULL ) );
+ assert( ( max > 0U ) && ( queryLength > 0U ) );
+
+ while( i < queryLength )
+ {
+ bool found = false;
+
+ if( isSquareOpen_( query[ i ] ) )
+ {
+ int32_t queryIndex = -1;
+ i++;
+
+ ( void ) skipDigits( query, &i, queryLength, &queryIndex );
+
+ if( ( queryIndex < 0 ) ||
+ ( i >= queryLength ) || !isSquareClose_( query[ i ] ) )
+ {
+ ret = JSONBadParameter;
+ break;
+ }
+
+ i++;
+
+ found = arraySearch( &buf[ start ], length, ( uint32_t ) queryIndex, &value, &length );
+ }
+ else
+ {
+ size_t keyLength = 0;
+
+ queryStart = i;
+
+ if( ( skipQueryPart( query, &i, queryLength, &keyLength ) != true ) ||
+ /* catch an empty key part or a trailing separator */
+ ( i == ( queryLength - 1U ) ) )
+ {
+ ret = JSONBadParameter;
+ break;
+ }
+
+ found = objectSearch( &buf[ start ], length, &query[ queryStart ], keyLength, &value, &length );
+ }
+
+ if( found == false )
+ {
+ ret = JSONNotFound;
+ break;
+ }
+
+ start += value;
+
+ if( ( i < queryLength ) && isSeparator_( query[ i ] ) )
+ {
+ i++;
+ }
+ }
+
+ if( ret == JSONSuccess )
+ {
+ *outValue = start;
+ *outValueLength = length;
+ }
+
+ return ret;
+}
+
+/**
+ * @brief Return a JSON type based on a separator character or
+ * the first character of a value.
+ *
+ * @param[in] c The character to classify.
+ *
+ * @return an enum of JSONTypes_t
+ */
+static JSONTypes_t getType( char c )
+{
+ JSONTypes_t t;
+
+ switch( c )
+ {
+ case '"':
+ t = JSONString;
+ break;
+
+ case '{':
+ t = JSONObject;
+ break;
+
+ case '[':
+ t = JSONArray;
+ break;
+
+ case 't':
+ t = JSONTrue;
+ break;
+
+ case 'f':
+ t = JSONFalse;
+ break;
+
+ case 'n':
+ t = JSONNull;
+ break;
+
+ default:
+ t = JSONNumber;
+ break;
+ }
+
+ return t;
+}
+
+/** @endcond */
+
+/**
+ * See core_json.h for docs.
+ */
+JSONStatus_t JSON_SearchConst( const char * buf,
+ size_t max,
+ const char * query,
+ size_t queryLength,
+ const char ** outValue,
+ size_t * outValueLength,
+ JSONTypes_t * outType )
+{
+ JSONStatus_t ret;
+ size_t value = 0U;
+
+ if( ( buf == NULL ) || ( query == NULL ) ||
+ ( outValue == NULL ) || ( outValueLength == NULL ) )
+ {
+ ret = JSONNullParameter;
+ }
+ else if( ( max == 0U ) || ( queryLength == 0U ) )
+ {
+ ret = JSONBadParameter;
+ }
+ else
+ {
+ ret = multiSearch( buf, max, query, queryLength, &value, outValueLength );
+ }
+
+ if( ret == JSONSuccess )
+ {
+ JSONTypes_t t = getType( buf[ value ] );
+
+ if( t == JSONString )
+ {
+ /* strip the surrounding quotes */
+ value++;
+ *outValueLength -= 2U;
+ }
+
+ *outValue = &buf[ value ];
+
+ if( outType != NULL )
+ {
+ *outType = t;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * See core_json.h for docs.
+ */
+JSONStatus_t JSON_SearchT( char * buf,
+ size_t max,
+ const char * query,
+ size_t queryLength,
+ char ** outValue,
+ size_t * outValueLength,
+ JSONTypes_t * outType )
+{
+ /* MISRA Ref 11.3.1 [Misaligned access] */
+ /* More details at: https://github.com/FreeRTOS/coreJSON/blob/main/MISRA.md#rule-113 */
+ /* coverity[misra_c_2012_rule_11_3_violation] */
+ return JSON_SearchConst( ( const char * ) buf, max, query, queryLength,
+ ( const char ** ) outValue, outValueLength, outType );
+}
+
+/** @cond DO_NOT_DOCUMENT */
+
+/**
+ * @brief Output the next key-value pair or value from a collection.
+ *
+ * @param[in] buf The buffer to search.
+ * @param[in] max size of the buffer.
+ * @param[in] start The index at which the collection begins.
+ * @param[in,out] next The index at which to seek the next value.
+ * @param[out] outKey A pointer to receive the index of the value found.
+ * @param[out] outKeyLength A pointer to receive the length of the value found.
+ * @param[out] outValue A pointer to receive the index of the value found.
+ * @param[out] outValueLength A pointer to receive the length of the value found.
+ *
+ * @return #JSONSuccess if a value is output;
+ * #JSONIllegalDocument if the buffer does not begin with '[' or '{';
+ * #JSONNotFound if there are no further values in the collection.
+ */
+static JSONStatus_t iterate( const char * buf,
+ size_t max,
+ size_t * start,
+ size_t * next,
+ size_t * outKey,
+ size_t * outKeyLength,
+ size_t * outValue,
+ size_t * outValueLength )
+{
+ JSONStatus_t ret = JSONNotFound;
+ bool found = false;
+
+ assert( ( buf != NULL ) && ( max > 0U ) );
+ assert( ( start != NULL ) && ( next != NULL ) );
+ assert( ( outKey != NULL ) && ( outKeyLength != NULL ) );
+ assert( ( outValue != NULL ) && ( outValueLength != NULL ) );
+
+ if( *start < max )
+ {
+ switch( buf[ *start ] )
+ {
+ case '[':
+ found = nextValue( buf, next, max, outValue, outValueLength );
+
+ if( found == true )
+ {
+ *outKey = 0;
+ *outKeyLength = 0;
+ }
+
+ break;
+
+ case '{':
+ found = nextKeyValuePair( buf, next, max, outKey, outKeyLength,
+ outValue, outValueLength );
+ break;
+
+ default:
+ ret = JSONIllegalDocument;
+ break;
+ }
+ }
+
+ if( found == true )
+ {
+ ret = JSONSuccess;
+ ( void ) skipSpaceAndComma( buf, next, max );
+ }
+
+ return ret;
+}
+
+/** @endcond */
+
+/**
+ * See core_json.h for docs.
+ */
+JSONStatus_t JSON_Iterate( const char * buf,
+ size_t max,
+ size_t * start,
+ size_t * next,
+ JSONPair_t * outPair )
+{
+ JSONStatus_t ret;
+ size_t key, keyLength, value, valueLength;
+
+ if( ( buf == NULL ) || ( start == NULL ) || ( next == NULL ) ||
+ ( outPair == NULL ) )
+ {
+ ret = JSONNullParameter;
+ }
+ else if( ( max == 0U ) || ( *start >= max ) || ( *next > max ) )
+ {
+ ret = JSONBadParameter;
+ }
+ else
+ {
+ skipSpace( buf, start, max );
+
+ if( *next <= *start )
+ {
+ *next = *start + 1U;
+ skipSpace( buf, next, max );
+ }
+
+ ret = iterate( buf, max, start, next, &key, &keyLength,
+ &value, &valueLength );
+ }
+
+ if( ret == JSONSuccess )
+ {
+ JSONTypes_t t = getType( buf[ value ] );
+
+ if( t == JSONString )
+ {
+ /* strip the surrounding quotes */
+ value++;
+ valueLength -= 2U;
+ }
+
+ outPair->key = ( key == 0U ) ? NULL : &buf[ key ];
+ outPair->keyLength = keyLength;
+ outPair->value = &buf[ value ];
+ outPair->valueLength = valueLength;
+ outPair->jsonType = t;
+ }
+
+ return ret;
+}
diff --git a/project/coreMQTT/coreJSON/core_json.h b/project/coreMQTT/coreJSON/core_json.h
new file mode 100644
index 0000000..102a24f
--- /dev/null
+++ b/project/coreMQTT/coreJSON/core_json.h
@@ -0,0 +1,339 @@
+/*
+ * coreJSON v3.2.0
+ * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_json.h
+ * @brief Include this header file to use coreJSON in your application.
+ */
+
+#ifndef CORE_JSON_H_
+#define CORE_JSON_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+/* *INDENT-ON* */
+
+/**
+ * @ingroup json_enum_types
+ * @brief Return codes from coreJSON library functions.
+ */
+typedef enum
+{
+ JSONPartial = 0, /**< @brief JSON document is valid so far but incomplete. */
+ JSONSuccess, /**< @brief JSON document is valid and complete. */
+ JSONIllegalDocument, /**< @brief JSON document is invalid or malformed. */
+ JSONMaxDepthExceeded, /**< @brief JSON document has nesting that exceeds JSON_MAX_DEPTH. */
+ JSONNotFound, /**< @brief Query key could not be found in the JSON document. */
+ JSONNullParameter, /**< @brief Pointer parameter passed to a function is NULL. */
+ JSONBadParameter /**< @brief Query key is empty, or any subpart is empty, or max is 0. */
+} JSONStatus_t;
+
+/**
+ * @brief Parse a buffer to determine if it contains a valid JSON document.
+ *
+ * @param[in] buf The buffer to parse.
+ * @param[in] max The size of the buffer.
+ *
+ * @note The maximum nesting depth may be specified by defining the macro
+ * JSON_MAX_DEPTH. The default is 32 of sizeof(char).
+ *
+ * @note By default, a valid JSON document may contain a single element
+ * (e.g., string, boolean, number). To require that a valid document
+ * contain an object or array, define JSON_VALIDATE_COLLECTIONS_ONLY.
+ *
+ * @return #JSONSuccess if the buffer contents are valid JSON;
+ * #JSONNullParameter if buf is NULL;
+ * #JSONBadParameter if max is 0;
+ * #JSONIllegalDocument if the buffer contents are NOT valid JSON;
+ * #JSONMaxDepthExceeded if object and array nesting exceeds a threshold;
+ * #JSONPartial if the buffer contents are potentially valid but incomplete.
+ *
+ * <b>Example</b>
+ * @code{c}
+ * // Variables used in this example.
+ * JSONStatus_t result;
+ * char buffer[] = "{\"foo\":\"abc\",\"bar\":{\"foo\":\"xyz\"}}";
+ * size_t bufferLength = sizeof( buffer ) - 1;
+ *
+ * result = JSON_Validate( buffer, bufferLength );
+ *
+ * // JSON document is valid.
+ * assert( result == JSONSuccess );
+ * @endcode
+ */
+/* @[declare_json_validate] */
+JSONStatus_t JSON_Validate( const char * buf,
+ size_t max );
+/* @[declare_json_validate] */
+
+/**
+ * @brief Find a key or array index in a JSON document and output the
+ * pointer @p outValue to its value.
+ *
+ * Any value may also be an object or an array to a maximum depth. A search
+ * may descend through nested objects or arrays when the query contains matching
+ * key strings or array indexes joined by a separator.
+ *
+ * For example, if the provided buffer contains <code>{"foo":"abc","bar":{"foo":"xyz"}}</code>,
+ * then a search for 'foo' would output <code>abc</code>, 'bar' would output
+ * <code>{"foo":"xyz"}</code>, and a search for 'bar.foo' would output
+ * <code>xyz</code>.
+ *
+ * If the provided buffer contains <code>[123,456,{"foo":"abc","bar":[88,99]}]</code>,
+ * then a search for '[1]' would output <code>456</code>, '[2].foo' would output
+ * <code>abc</code>, and '[2].bar[0]' would output <code>88</code>.
+ *
+ * On success, the pointer @p outValue points to a location in buf. No null
+ * termination is done for the value. For valid JSON it is safe to place
+ * a null character at the end of the value, so long as the character
+ * replaced is put back before running another search.
+ *
+ * @param[in] buf The buffer to search.
+ * @param[in] max size of the buffer.
+ * @param[in] query The object keys and array indexes to search for.
+ * @param[in] queryLength Length of the key.
+ * @param[out] outValue A pointer to receive the address of the value found.
+ * @param[out] outValueLength A pointer to receive the length of the value found.
+ *
+ * @note The maximum nesting depth may be specified by defining the macro
+ * JSON_MAX_DEPTH. The default is 32 of sizeof(char).
+ *
+ * @note JSON_Search() performs validation, but stops upon finding a matching
+ * key and its value. To validate the entire JSON document, use JSON_Validate().
+ *
+ * @return #JSONSuccess if the query is matched and the value output;
+ * #JSONNullParameter if any pointer parameters are NULL;
+ * #JSONBadParameter if the query is empty, or the portion after a separator is empty,
+ * or max is 0, or an index is too large to convert to a signed 32-bit integer;
+ * #JSONNotFound if the query has no match.
+ *
+ * <b>Example</b>
+ * @code{c}
+ * // Variables used in this example.
+ * JSONStatus_t result;
+ * char buffer[] = "{\"foo\":\"abc\",\"bar\":{\"foo\":\"xyz\"}}";
+ * size_t bufferLength = sizeof( buffer ) - 1;
+ * char query[] = "bar.foo";
+ * size_t queryLength = sizeof( query ) - 1;
+ * char * value;
+ * size_t valueLength;
+ *
+ * // Calling JSON_Validate() is not necessary if the document is guaranteed to be valid.
+ * result = JSON_Validate( buffer, bufferLength );
+ *
+ * if( result == JSONSuccess )
+ * {
+ * result = JSON_Search( buffer, bufferLength, query, queryLength,
+ * &value, &valueLength );
+ * }
+ *
+ * if( result == JSONSuccess )
+ * {
+ * // The pointer "value" will point to a location in the "buffer".
+ * char save = value[ valueLength ];
+ * // After saving the character, set it to a null byte for printing.
+ * value[ valueLength ] = '\0';
+ * // "Found: bar.foo -> xyz" will be printed.
+ * printf( "Found: %s -> %s\n", query, value );
+ * // Restore the original character.
+ * value[ valueLength ] = save;
+ * }
+ * @endcode
+ *
+ * @note The maximum index value is ~2 billion ( 2^31 - 9 ).
+ */
+/* @[declare_json_search] */
+#define JSON_Search( buf, max, query, queryLength, outValue, outValueLength ) \
+ JSON_SearchT( buf, max, query, queryLength, outValue, outValueLength, NULL )
+/* @[declare_json_search] */
+
+/**
+ * @brief The largest value usable as an array index in a query
+ * for JSON_Search(), ~2 billion.
+ */
+#define MAX_INDEX_VALUE ( 0x7FFFFFF7 ) /* 2^31 - 9 */
+
+/**
+ * @ingroup json_enum_types
+ * @brief Value types from the JSON standard.
+ */
+typedef enum
+{
+ JSONInvalid = 0, /**< @brief Not a valid JSON type. */
+ JSONString, /**< @brief A quote delimited sequence of Unicode characters. */
+ JSONNumber, /**< @brief A rational number. */
+ JSONTrue, /**< @brief The literal value true. */
+ JSONFalse, /**< @brief The literal value false. */
+ JSONNull, /**< @brief The literal value null. */
+ JSONObject, /**< @brief A collection of zero or more key-value pairs. */
+ JSONArray /**< @brief A collection of zero or more values. */
+} JSONTypes_t;
+
+/**
+ * @brief Same as JSON_Search(), but also outputs a type for the value found
+ *
+ * See @ref JSON_Search for documentation of common behavior.
+ *
+ * @param[in] buf The buffer to search.
+ * @param[in] max size of the buffer.
+ * @param[in] query The object keys and array indexes to search for.
+ * @param[in] queryLength Length of the key.
+ * @param[out] outValue A pointer to receive the address of the value found.
+ * @param[out] outValueLength A pointer to receive the length of the value found.
+ * @param[out] outType An enum indicating the JSON-specific type of the value.
+ */
+/* @[declare_json_searcht] */
+JSONStatus_t JSON_SearchT( char * buf,
+ size_t max,
+ const char * query,
+ size_t queryLength,
+ char ** outValue,
+ size_t * outValueLength,
+ JSONTypes_t * outType );
+/* @[declare_json_searcht] */
+
+/**
+ * @brief Same as JSON_SearchT(), but with const qualified buf and outValue arguments.
+ *
+ * See @ref JSON_Search for documentation of common behavior.
+ *
+ * @param[in] buf The buffer to search.
+ * @param[in] max size of the buffer.
+ * @param[in] query The object keys and array indexes to search for.
+ * @param[in] queryLength Length of the key.
+ * @param[out] outValue A pointer to receive the address of the value found.
+ * @param[out] outValueLength A pointer to receive the length of the value found.
+ * @param[out] outType An enum indicating the JSON-specific type of the value.
+ */
+/* @[declare_json_searchconst] */
+JSONStatus_t JSON_SearchConst( const char * buf,
+ size_t max,
+ const char * query,
+ size_t queryLength,
+ const char ** outValue,
+ size_t * outValueLength,
+ JSONTypes_t * outType );
+/* @[declare_json_searchconst] */
+
+/**
+ * @ingroup json_struct_types
+ * @brief Structure to represent a key-value pair.
+ */
+typedef struct
+{
+ const char * key; /**< @brief Pointer to the code point sequence for key. */
+ size_t keyLength; /**< @brief Length of the code point sequence for key. */
+ const char * value; /**< @brief Pointer to the code point sequence for value. */
+ size_t valueLength; /**< @brief Length of the code point sequence for value. */
+ JSONTypes_t jsonType; /**< @brief JSON-specific type of the value. */
+} JSONPair_t;
+
+/**
+ * @brief Output the next key-value pair or value from a collection.
+ *
+ * This function may be used in a loop to output each key-value pair from an object,
+ * or each value from an array. For the first invocation, the integers pointed to by
+ * start and next should be initialized to 0. These will be updated by the function.
+ * If another key-value pair or value is present, the output structure is populated
+ * and #JSONSuccess is returned; otherwise the structure is unchanged and #JSONNotFound
+ * is returned.
+ *
+ * @param[in] buf The buffer to search.
+ * @param[in] max size of the buffer.
+ * @param[in,out] start The index at which the collection begins.
+ * @param[in,out] next The index at which to seek the next value.
+ * @param[out] outPair A pointer to receive the next key-value pair.
+ *
+ * @note This function expects a valid JSON document; run JSON_Validate() first.
+ *
+ * @note For an object, the outPair structure will reference a key and its value.
+ * For an array, only the value will be referenced (i.e., outPair.key will be NULL).
+ *
+ * @return #JSONSuccess if a value is output;
+ * #JSONIllegalDocument if the buffer does not contain a collection;
+ * #JSONNotFound if there are no further values in the collection.
+ *
+ * <b>Example</b>
+ * @code{c}
+ * // Variables used in this example.
+ * static char * json_types[] =
+ * {
+ * "invalid",
+ * "string",
+ * "number",
+ * "true",
+ * "false",
+ * "null",
+ * "object",
+ * "array"
+ * };
+ *
+ * void show( const char * json,
+ * size_t length )
+ * {
+ * size_t start = 0, next = 0;
+ * JSONPair_t pair = { 0 };
+ * JSONStatus_t result;
+ *
+ * result = JSON_Validate( json, length );
+ * if( result == JSONSuccess )
+ * {
+ * result = JSON_Iterate( json, length, &start, &next, &pair );
+ * }
+ *
+ * while( result == JSONSuccess )
+ * {
+ * if( pair.key != NULL )
+ * {
+ * printf( "key: %.*s\t", ( int ) pair.keyLength, pair.key );
+ * }
+ *
+ * printf( "value: (%s) %.*s\n", json_types[ pair.jsonType ],
+ * ( int ) pair.valueLength, pair.value );
+ *
+ * result = JSON_Iterate( json, length, &start, &next, &pair );
+ * }
+ * }
+ * @endcode
+ */
+/* @[declare_json_iterate] */
+JSONStatus_t JSON_Iterate( const char * buf,
+ size_t max,
+ size_t * start,
+ size_t * next,
+ JSONPair_t * outPair );
+/* @[declare_json_iterate] */
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ }
+#endif
+/* *INDENT-ON* */
+
+#endif /* ifndef CORE_JSON_H_ */
diff --git a/project/coreMQTT/coreJSON/makefile b/project/coreMQTT/coreJSON/makefile
new file mode 100644
index 0000000..b6ece0e
--- /dev/null
+++ b/project/coreMQTT/coreJSON/makefile
@@ -0,0 +1,35 @@
+#********************************************************************************
+# Copyright: (C) 2023 LingYun IoT System Studio
+# All rights reserved.
+#
+# Filename: Makefile
+# Description: This file used compile all the source code to static library
+#
+# Version: 1.0.0(11/08/23)
+# Author: Guo Wenxue <guowenxue@gmail.com>
+# ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+#
+#*******************************************************************************
+
+PWD=$(shell pwd )
+
+BUILD_ARCH=$(shell uname -m)
+ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
+ CROSS_COMPILE?=arm-linux-gnueabihf-
+endif
+
+LIBNAME=$(shell basename ${PWD} )
+TOPDIR=$(shell dirname ${PWD} )
+CFLAGS+=-D_GNU_SOURCE
+
+all: clean
+ @rm -f *.o
+ @${CROSS_COMPILE}gcc ${CFLAGS} -I${TOPDIR} -c *.c
+ ${CROSS_COMPILE}ar -rcs lib${LIBNAME}.a *.o
+
+clean:
+ @rm -f *.o
+ @rm -f *.a
+
+distclean:
+ @make clean
diff --git a/project/coreMQTT/coreMQTT/README.md b/project/coreMQTT/coreMQTT/README.md
new file mode 100644
index 0000000..16355a1
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/README.md
@@ -0,0 +1,35 @@
+## coreMQTT Client Library
+
+This repository contains the coreMQTT library that has been optimized for a low memory footprint. The coreMQTT library is compliant with the [MQTT 3.1.1](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html) standard. It has no dependencies on any additional libraries other than the standard C library, a customer-implemented network transport interface, and *optionally* a user-implemented platform time function. This library is distributed under the [MIT Open Source License](LICENSE).
+
+This library has gone through code quality checks including verification that no function has a [GNU Complexity](https://www.gnu.org/software/complexity/manual/complexity.html) score over 8, and checks against deviations from mandatory rules in the [MISRA coding standard](https://www.misra.org.uk). Deviations from the MISRA C:2012 guidelines are documented under [MISRA Deviations](MISRA.md). This library has also undergone both static code analysis from [Coverity static analysis](https://scan.coverity.com/), and validation of memory safety through the [CBMC automated reasoning tool](https://www.cprover.org/cbmc/).
+
+See memory requirements for this library [here](./docs/doxygen/include/size_table.md).
+
+**coreMQTT v2.1.1 [source code](https://github.com/FreeRTOS/coreMQTT/tree/v2.1.1/source) is part of the [FreeRTOS 202210.01 LTS](https://github.com/FreeRTOS/FreeRTOS-LTS/tree/202210.01-LTS) release.**
+
+## MQTT Config File
+
+The MQTT client library exposes build configuration macros that are required for building the library.
+A list of all the configurations and their default values are defined in [core_mqtt_config_defaults.h](source/include/core_mqtt_config_defaults.h).
+To provide custom values for the configuration macros, a custom config file named `core_mqtt_config.h` can be
+provided by the application to the library.
+
+By default, a `core_mqtt_config.h` custom config is required to build the library. To disable this requirement
+and build the library with default configuration values, provide `MQTT_DO_NOT_USE_CUSTOM_CONFIG` as a compile time preprocessor macro.
+
+**Thus, the MQTT library can be built by either**:
+* Defining a `core_mqtt_config.h` file in the application, and adding it to the include directories list of the library
+**OR**
+* Defining the `MQTT_DO_NOT_USE_CUSTOM_CONFIG` preprocessor macro for the library build.
+
+## Documentation
+
+For pre-generated documentation, please see the documentation linked in the locations below:
+
+| Location |
+| :-: |
+| [FreeRTOS.org](https://freertos.org/Documentation/api-ref/coreMQTT/docs/doxygen/output/html/index.html) |
+
+Note that the latest included version of coreMQTT may differ across repositories.
+
diff --git a/project/coreMQTT/coreMQTT/core_mqtt.c b/project/coreMQTT/coreMQTT/core_mqtt.c
new file mode 100644
index 0000000..633f61b
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/core_mqtt.c
@@ -0,0 +1,3314 @@
+/*
+ * coreMQTT v2.1.1
+ * Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_mqtt.c
+ * @brief Implements the user-facing functions in core_mqtt.h.
+ */
+#include <string.h>
+#include <assert.h>
+
+#include "core_mqtt.h"
+#include "core_mqtt_state.h"
+
+/* Include config defaults header to get default values of configs. */
+#include "core_mqtt_config_defaults.h"
+
+#include "core_mqtt_default_logging.h"
+
+#ifndef MQTT_PRE_SEND_HOOK
+
+/**
+ * @brief Hook called before a 'send' operation is executed.
+ */
+ #define MQTT_PRE_SEND_HOOK( pContext )
+#endif /* !MQTT_PRE_SEND_HOOK */
+
+#ifndef MQTT_POST_SEND_HOOK
+
+/**
+ * @brief Hook called after the 'send' operation is complete.
+ */
+ #define MQTT_POST_SEND_HOOK( pContext )
+#endif /* !MQTT_POST_SEND_HOOK */
+
+#ifndef MQTT_PRE_STATE_UPDATE_HOOK
+
+/**
+ * @brief Hook called just before an update to the MQTT state is made.
+ */
+ #define MQTT_PRE_STATE_UPDATE_HOOK( pContext )
+#endif /* !MQTT_PRE_STATE_UPDATE_HOOK */
+
+#ifndef MQTT_POST_STATE_UPDATE_HOOK
+
+/**
+ * @brief Hook called just after an update to the MQTT state has
+ * been made.
+ */
+ #define MQTT_POST_STATE_UPDATE_HOOK( pContext )
+#endif /* !MQTT_POST_STATE_UPDATE_HOOK */
+
+/*-----------------------------------------------------------*/
+
+/**
+ * @brief Sends provided buffer to network using transport send.
+ *
+ * @brief param[in] pContext Initialized MQTT context.
+ * @brief param[in] pBufferToSend Buffer to be sent to network.
+ * @brief param[in] bytesToSend Number of bytes to be sent.
+ *
+ * @note This operation may call the transport send function
+ * repeatedly to send bytes over the network until either:
+ * 1. The requested number of bytes @a bytesToSend have been sent.
+ * OR
+ * 2. MQTT_SEND_TIMEOUT_MS milliseconds have gone by since entering this
+ * function.
+ * OR
+ * 3. There is an error in sending data over the network.
+ *
+ * @return Total number of bytes sent, or negative value on network error.
+ */
+static int32_t sendBuffer( MQTTContext_t * pContext,
+ const uint8_t * pBufferToSend,
+ size_t bytesToSend );
+
+/**
+ * @brief Sends MQTT connect without copying the users data into any buffer.
+ *
+ * @brief param[in] pContext Initialized MQTT context.
+ * @brief param[in] pConnectInfo MQTT CONNECT packet information.
+ * @brief param[in] pWillInfo Last Will and Testament. Pass NULL if Last Will and
+ * Testament is not used.
+ * @brief param[in] remainingLength the length of the connect packet.
+ *
+ * @note This operation may call the transport send function
+ * repeatedly to send bytes over the network until either:
+ * 1. The requested number of bytes @a remainingLength have been sent.
+ * OR
+ * 2. MQTT_SEND_TIMEOUT_MS milliseconds have gone by since entering this
+ * function.
+ * OR
+ * 3. There is an error in sending data over the network.
+ *
+ * @return #MQTTSendFailed or #MQTTSuccess.
+ */
+static MQTTStatus_t sendConnectWithoutCopy( MQTTContext_t * pContext,
+ const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ size_t remainingLength );
+
+/**
+ * @brief Sends the vector array passed through the parameters over the network.
+ *
+ * @note The preference is given to 'writev' function if it is present in the
+ * transport interface. Otherwise, a send call is made repeatedly to achieve the
+ * result.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] pIoVec The vector array to be sent.
+ * @param[in] ioVecCount The number of elements in the array.
+ *
+ * @note This operation may call the transport send or writev functions
+ * repeatedly to send bytes over the network until either:
+ * 1. The requested number of bytes have been sent.
+ * OR
+ * 2. MQTT_SEND_TIMEOUT_MS milliseconds have gone by since entering this
+ * function.
+ * OR
+ * 3. There is an error in sending data over the network.
+ *
+ * @return The total number of bytes sent or the error code as received from the
+ * transport interface.
+ */
+static int32_t sendMessageVector( MQTTContext_t * pContext,
+ TransportOutVector_t * pIoVec,
+ size_t ioVecCount );
+
+/**
+ * @brief Add a string and its length after serializing it in a manner outlined by
+ * the MQTT specification.
+ *
+ * @param[in] serailizedLength Array of two bytes to which the vector will point.
+ * The array must remain in scope until the message has been sent.
+ * @param[in] string The string to be serialized.
+ * @param[in] length The length of the string to be serialized.
+ * @param[in] iterator The iterator pointing to the first element in the
+ * transport interface IO array.
+ * @param[out] updatedLength This parameter will be added to with the number of
+ * bytes added to the vector.
+ *
+ * @return The number of vectors added.
+ */
+static size_t addEncodedStringToVector( uint8_t serailizedLength[ 2 ],
+ const char * const string,
+ uint16_t length,
+ TransportOutVector_t * iterator,
+ size_t * updatedLength );
+
+/**
+ * @brief Send MQTT SUBSCRIBE message without copying the user data into a buffer and
+ * directly sending it.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] pSubscriptionList List of MQTT subscription info.
+ * @param[in] subscriptionCount The count of elements in the list.
+ * @param[in] packetId The packet ID of the subscribe packet
+ * @param[in] remainingLength The remaining length of the subscribe packet.
+ *
+ * @return #MQTTSuccess or #MQTTSendFailed.
+ */
+static MQTTStatus_t sendSubscribeWithoutCopy( MQTTContext_t * pContext,
+ const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId,
+ size_t remainingLength );
+
+/**
+ * @brief Send MQTT UNSUBSCRIBE message without copying the user data into a buffer and
+ * directly sending it.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] pSubscriptionList MQTT subscription info.
+ * @param[in] subscriptionCount The count of elements in the list.
+ * @param[in] packetId The packet ID of the unsubscribe packet.
+ * @param[in] remainingLength The remaining length of the unsubscribe packet.
+ *
+ * @return #MQTTSuccess or #MQTTSendFailed.
+ */
+static MQTTStatus_t sendUnsubscribeWithoutCopy( MQTTContext_t * pContext,
+ const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId,
+ size_t remainingLength );
+
+/**
+ * @brief Calculate the interval between two millisecond timestamps, including
+ * when the later value has overflowed.
+ *
+ * @note In C, the operands are promoted to signed integers in subtraction.
+ * Using this function avoids the need to cast the result of subtractions back
+ * to uint32_t.
+ *
+ * @param[in] later The later time stamp, in milliseconds.
+ * @param[in] start The earlier time stamp, in milliseconds.
+ *
+ * @return later - start.
+ */
+static uint32_t calculateElapsedTime( uint32_t later,
+ uint32_t start );
+
+/**
+ * @brief Convert a byte indicating a publish ack type to an #MQTTPubAckType_t.
+ *
+ * @param[in] packetType First byte of fixed header.
+ *
+ * @return Type of ack.
+ */
+static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType );
+
+/**
+ * @brief Receive bytes into the network buffer.
+ *
+ * @param[in] pContext Initialized MQTT Context.
+ * @param[in] bytesToRecv Number of bytes to receive.
+ *
+ * @note This operation calls the transport receive function
+ * repeatedly to read bytes from the network until either:
+ * 1. The requested number of bytes @a bytesToRecv are read.
+ * OR
+ * 2. No data is received from the network for MQTT_RECV_POLLING_TIMEOUT_MS duration.
+ *
+ * OR
+ * 3. There is an error in reading from the network.
+ *
+ *
+ * @return Number of bytes received, or negative number on network error.
+ */
+static int32_t recvExact( const MQTTContext_t * pContext,
+ size_t bytesToRecv );
+
+/**
+ * @brief Discard a packet from the transport interface.
+ *
+ * @param[in] pContext MQTT Connection context.
+ * @param[in] remainingLength Remaining length of the packet to dump.
+ * @param[in] timeoutMs Time remaining to discard the packet.
+ *
+ * @return #MQTTRecvFailed or #MQTTNoDataAvailable.
+ */
+static MQTTStatus_t discardPacket( const MQTTContext_t * pContext,
+ size_t remainingLength,
+ uint32_t timeoutMs );
+
+/**
+ * @brief Discard a packet from the MQTT buffer and the transport interface.
+ *
+ * @param[in] pContext MQTT Connection context.
+ * @param[in] pPacketInfo Information struct of the packet to be discarded.
+ *
+ * @return #MQTTRecvFailed or #MQTTNoDataAvailable.
+ */
+static MQTTStatus_t discardStoredPacket( MQTTContext_t * pContext,
+ const MQTTPacketInfo_t * pPacketInfo );
+
+/**
+ * @brief Receive a packet from the transport interface.
+ *
+ * @param[in] pContext MQTT Connection context.
+ * @param[in] incomingPacket packet struct with remaining length.
+ * @param[in] remainingTimeMs Time remaining to receive the packet.
+ *
+ * @return #MQTTSuccess or #MQTTRecvFailed.
+ */
+static MQTTStatus_t receivePacket( const MQTTContext_t * pContext,
+ MQTTPacketInfo_t incomingPacket,
+ uint32_t remainingTimeMs );
+
+/**
+ * @brief Get the correct ack type to send.
+ *
+ * @param[in] state Current state of publish.
+ *
+ * @return Packet Type byte of PUBACK, PUBREC, PUBREL, or PUBCOMP if one of
+ * those should be sent, else 0.
+ */
+static uint8_t getAckTypeToSend( MQTTPublishState_t state );
+
+/**
+ * @brief Send acks for received QoS 1/2 publishes.
+ *
+ * @param[in] pContext MQTT Connection context.
+ * @param[in] packetId packet ID of original PUBLISH.
+ * @param[in] publishState Current publish state in record.
+ *
+ * @return #MQTTSuccess, #MQTTIllegalState or #MQTTSendFailed.
+ */
+static MQTTStatus_t sendPublishAcks( MQTTContext_t * pContext,
+ uint16_t packetId,
+ MQTTPublishState_t publishState );
+
+/**
+ * @brief Send a keep alive PINGREQ if the keep alive interval has elapsed.
+ *
+ * @param[in] pContext Initialized MQTT Context.
+ *
+ * @return #MQTTKeepAliveTimeout if a PINGRESP is not received in time,
+ * #MQTTSendFailed if the PINGREQ cannot be sent, or #MQTTSuccess.
+ */
+static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext );
+
+/**
+ * @brief Handle received MQTT PUBLISH packet.
+ *
+ * @param[in] pContext MQTT Connection context.
+ * @param[in] pIncomingPacket Incoming packet.
+ *
+ * @return MQTTSuccess, MQTTIllegalState or deserialization error.
+ */
+static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext,
+ MQTTPacketInfo_t * pIncomingPacket );
+
+/**
+ * @brief Handle received MQTT publish acks.
+ *
+ * @param[in] pContext MQTT Connection context.
+ * @param[in] pIncomingPacket Incoming packet.
+ *
+ * @return MQTTSuccess, MQTTIllegalState, or deserialization error.
+ */
+static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext,
+ MQTTPacketInfo_t * pIncomingPacket );
+
+/**
+ * @brief Handle received MQTT ack.
+ *
+ * @param[in] pContext MQTT Connection context.
+ * @param[in] pIncomingPacket Incoming packet.
+ * @param[in] manageKeepAlive Flag indicating if PINGRESPs should not be given
+ * to the application
+ *
+ * @return MQTTSuccess, MQTTIllegalState, or deserialization error.
+ */
+static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext,
+ MQTTPacketInfo_t * pIncomingPacket,
+ bool manageKeepAlive );
+
+/**
+ * @brief Run a single iteration of the receive loop.
+ *
+ * @param[in] pContext MQTT Connection context.
+ * @param[in] manageKeepAlive Flag indicating if keep alive should be handled.
+ *
+ * @return #MQTTRecvFailed if a network error occurs during reception;
+ * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ;
+ * #MQTTBadResponse if an invalid packet is received;
+ * #MQTTKeepAliveTimeout if the server has not sent a PINGRESP before
+ * #MQTT_PINGRESP_TIMEOUT_MS milliseconds;
+ * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an
+ * invalid transition for the internal state machine;
+ * #MQTTSuccess on success.
+ */
+static MQTTStatus_t receiveSingleIteration( MQTTContext_t * pContext,
+ bool manageKeepAlive );
+
+/**
+ * @brief Validates parameters of #MQTT_Subscribe or #MQTT_Unsubscribe.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] pSubscriptionList List of MQTT subscription info.
+ * @param[in] subscriptionCount The number of elements in pSubscriptionList.
+ * @param[in] packetId Packet identifier.
+ *
+ * @return #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ */
+static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * pContext,
+ const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId );
+
+/**
+ * @brief Receives a CONNACK MQTT packet.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] timeoutMs Timeout for waiting for CONNACK packet.
+ * @param[in] cleanSession Clean session flag set by application.
+ * @param[out] pIncomingPacket List of MQTT subscription info.
+ * @param[out] pSessionPresent Whether a previous session was present.
+ * Only relevant if not establishing a clean session.
+ *
+ * @return #MQTTBadResponse if a bad response is received;
+ * #MQTTNoDataAvailable if no data available for transport recv;
+ * ##MQTTRecvFailed if transport recv failed;
+ * #MQTTSuccess otherwise.
+ */
+static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext,
+ uint32_t timeoutMs,
+ bool cleanSession,
+ MQTTPacketInfo_t * pIncomingPacket,
+ bool * pSessionPresent );
+
+/**
+ * @brief Resends pending acks for a re-established MQTT session, or
+ * clears existing state records for a clean session.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] sessionPresent Session present flag received from the MQTT broker.
+ *
+ * @return #MQTTSendFailed if transport send during resend failed;
+ * #MQTTSuccess otherwise.
+ */
+static MQTTStatus_t handleSessionResumption( MQTTContext_t * pContext,
+ bool sessionPresent );
+
+
+/**
+ * @brief Send the publish packet without copying the topic string and payload in
+ * the buffer.
+ *
+ * @brief param[in] pContext Initialized MQTT context.
+ * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters.
+ * @brief param[in] pMqttHeader the serialized MQTT header with the header byte;
+ * the encoded length of the packet; and the encoded length of the topic string.
+ * @brief param[in] headerSize Size of the serialized PUBLISH header.
+ * @brief param[in] packetId Packet Id of the publish packet.
+ *
+ * @return #MQTTSendFailed if transport send during resend failed;
+ * #MQTTSuccess otherwise.
+ */
+static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext,
+ const MQTTPublishInfo_t * pPublishInfo,
+ const uint8_t * pMqttHeader,
+ size_t headerSize,
+ uint16_t packetId );
+
+/**
+ * @brief Function to validate #MQTT_Publish parameters.
+ *
+ * @brief param[in] pContext Initialized MQTT context.
+ * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters.
+ * @brief param[in] packetId Packet Id for the MQTT PUBLISH packet.
+ *
+ * @return #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ */
+static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext,
+ const MQTTPublishInfo_t * pPublishInfo,
+ uint16_t packetId );
+
+/**
+ * @brief Performs matching for special cases when a topic filter ends
+ * with a wildcard character.
+ *
+ * When the topic name has been consumed but there are remaining characters to
+ * to match in topic filter, this function handles the following 2 cases:
+ * - When the topic filter ends with "/+" or "/#" characters, but the topic
+ * name only ends with '/'.
+ * - When the topic filter ends with "/#" characters, but the topic name
+ * ends at the parent level.
+ *
+ * @note This function ASSUMES that the topic name been consumed in linear
+ * matching with the topic filer, but the topic filter has remaining characters
+ * to be matched.
+ *
+ * @param[in] pTopicFilter The topic filter containing the wildcard.
+ * @param[in] topicFilterLength Length of the topic filter being examined.
+ * @param[in] filterIndex Index of the topic filter being examined.
+ *
+ * @return Returns whether the topic filter and the topic name match.
+ */
+static bool matchEndWildcardsSpecialCases( const char * pTopicFilter,
+ uint16_t topicFilterLength,
+ uint16_t filterIndex );
+
+/**
+ * @brief Attempt to match topic name with a topic filter starting with a wildcard.
+ *
+ * If the topic filter starts with a '+' (single-level) wildcard, the function
+ * advances the @a pNameIndex by a level in the topic name.
+ * If the topic filter starts with a '#' (multi-level) wildcard, the function
+ * concludes that both the topic name and topic filter match.
+ *
+ * @param[in] pTopicName The topic name to match.
+ * @param[in] topicNameLength Length of the topic name.
+ * @param[in] pTopicFilter The topic filter to match.
+ * @param[in] topicFilterLength Length of the topic filter.
+ * @param[in,out] pNameIndex Current index in the topic name being examined. It is
+ * advanced by one level for `+` wildcards.
+ * @param[in, out] pFilterIndex Current index in the topic filter being examined.
+ * It is advanced to position of '/' level separator for '+' wildcard.
+ * @param[out] pMatch Whether the topic filter and topic name match.
+ *
+ * @return `true` if the caller of this function should exit; `false` if the
+ * caller should continue parsing the topics.
+ */
+static bool matchWildcards( const char * pTopicName,
+ uint16_t topicNameLength,
+ const char * pTopicFilter,
+ uint16_t topicFilterLength,
+ uint16_t * pNameIndex,
+ uint16_t * pFilterIndex,
+ bool * pMatch );
+
+/**
+ * @brief Match a topic name and topic filter allowing the use of wildcards.
+ *
+ * @param[in] pTopicName The topic name to check.
+ * @param[in] topicNameLength Length of the topic name.
+ * @param[in] pTopicFilter The topic filter to check.
+ * @param[in] topicFilterLength Length of topic filter.
+ *
+ * @return `true` if the topic name and topic filter match; `false` otherwise.
+ */
+static bool matchTopicFilter( const char * pTopicName,
+ uint16_t topicNameLength,
+ const char * pTopicFilter,
+ uint16_t topicFilterLength );
+
+/*-----------------------------------------------------------*/
+
+static bool matchEndWildcardsSpecialCases( const char * pTopicFilter,
+ uint16_t topicFilterLength,
+ uint16_t filterIndex )
+{
+ bool matchFound = false;
+
+ assert( pTopicFilter != NULL );
+ assert( topicFilterLength != 0U );
+
+ /* Check if the topic filter has 2 remaining characters and it ends in
+ * "/#". This check handles the case to match filter "sport/#" with topic
+ * "sport". The reason is that the '#' wildcard represents the parent and
+ * any number of child levels in the topic name.*/
+ if( ( topicFilterLength >= 3U ) &&
+ ( filterIndex == ( topicFilterLength - 3U ) ) &&
+ ( pTopicFilter[ filterIndex + 1U ] == '/' ) &&
+ ( pTopicFilter[ filterIndex + 2U ] == '#' ) )
+
+ {
+ matchFound = true;
+ }
+
+ /* Check if the next character is "#" or "+" and the topic filter ends in
+ * "/#" or "/+". This check handles the cases to match:
+ *
+ * - Topic filter "sport/+" with topic "sport/".
+ * - Topic filter "sport/#" with topic "sport/".
+ */
+ if( ( filterIndex == ( topicFilterLength - 2U ) ) &&
+ ( pTopicFilter[ filterIndex ] == '/' ) )
+ {
+ /* Check that the last character is a wildcard. */
+ matchFound = ( pTopicFilter[ filterIndex + 1U ] == '+' ) ||
+ ( pTopicFilter[ filterIndex + 1U ] == '#' );
+ }
+
+ return matchFound;
+}
+
+/*-----------------------------------------------------------*/
+
+static bool matchWildcards( const char * pTopicName,
+ uint16_t topicNameLength,
+ const char * pTopicFilter,
+ uint16_t topicFilterLength,
+ uint16_t * pNameIndex,
+ uint16_t * pFilterIndex,
+ bool * pMatch )
+{
+ bool shouldStopMatching = false;
+ bool locationIsValidForWildcard;
+
+ assert( pTopicName != NULL );
+ assert( topicNameLength != 0U );
+ assert( pTopicFilter != NULL );
+ assert( topicFilterLength != 0U );
+ assert( pNameIndex != NULL );
+ assert( pFilterIndex != NULL );
+ assert( pMatch != NULL );
+
+ /* Wild card in a topic filter is only valid either at the starting position
+ * or when it is preceded by a '/'.*/
+ locationIsValidForWildcard = ( *pFilterIndex == 0u ) ||
+ ( pTopicFilter[ *pFilterIndex - 1U ] == '/' );
+
+ if( ( pTopicFilter[ *pFilterIndex ] == '+' ) && ( locationIsValidForWildcard == true ) )
+ {
+ bool nextLevelExistsInTopicName = false;
+ bool nextLevelExistsinTopicFilter = false;
+
+ /* Move topic name index to the end of the current level. The end of the
+ * current level is identified by the last character before the next level
+ * separator '/'. */
+ while( *pNameIndex < topicNameLength )
+ {
+ /* Exit the loop if we hit the level separator. */
+ if( pTopicName[ *pNameIndex ] == '/' )
+ {
+ nextLevelExistsInTopicName = true;
+ break;
+ }
+
+ ( *pNameIndex )++;
+ }
+
+ /* Determine if the topic filter contains a child level after the current level
+ * represented by the '+' wildcard. */
+ if( ( *pFilterIndex < ( topicFilterLength - 1U ) ) &&
+ ( pTopicFilter[ *pFilterIndex + 1U ] == '/' ) )
+ {
+ nextLevelExistsinTopicFilter = true;
+ }
+
+ /* If the topic name contains a child level but the topic filter ends at
+ * the current level, then there does not exist a match. */
+ if( ( nextLevelExistsInTopicName == true ) &&
+ ( nextLevelExistsinTopicFilter == false ) )
+ {
+ *pMatch = false;
+ shouldStopMatching = true;
+ }
+
+ /* If the topic name and topic filter have child levels, then advance the
+ * filter index to the level separator in the topic filter, so that match
+ * can be performed in the next level.
+ * Note: The name index already points to the level separator in the topic
+ * name. */
+ else if( nextLevelExistsInTopicName == true )
+ {
+ ( *pFilterIndex )++;
+ }
+ else
+ {
+ /* If we have reached here, the the loop terminated on the
+ * ( *pNameIndex < topicNameLength) condition, which means that have
+ * reached past the end of the topic name, and thus, we decrement the
+ * index to the last character in the topic name.*/
+ ( *pNameIndex )--;
+ }
+ }
+
+ /* '#' matches everything remaining in the topic name. It must be the
+ * last character in a topic filter. */
+ else if( ( pTopicFilter[ *pFilterIndex ] == '#' ) &&
+ ( *pFilterIndex == ( topicFilterLength - 1U ) ) &&
+ ( locationIsValidForWildcard == true ) )
+ {
+ /* Subsequent characters don't need to be checked for the
+ * multi-level wildcard. */
+ *pMatch = true;
+ shouldStopMatching = true;
+ }
+ else
+ {
+ /* Any character mismatch other than '+' or '#' means the topic
+ * name does not match the topic filter. */
+ *pMatch = false;
+ shouldStopMatching = true;
+ }
+
+ return shouldStopMatching;
+}
+
+/*-----------------------------------------------------------*/
+
+static bool matchTopicFilter( const char * pTopicName,
+ uint16_t topicNameLength,
+ const char * pTopicFilter,
+ uint16_t topicFilterLength )
+{
+ bool matchFound = false, shouldStopMatching = false;
+ uint16_t nameIndex = 0, filterIndex = 0;
+
+ assert( pTopicName != NULL );
+ assert( topicNameLength != 0 );
+ assert( pTopicFilter != NULL );
+ assert( topicFilterLength != 0 );
+
+ while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) )
+ {
+ /* Check if the character in the topic name matches the corresponding
+ * character in the topic filter string. */
+ if( pTopicName[ nameIndex ] == pTopicFilter[ filterIndex ] )
+ {
+ /* If the topic name has been consumed but the topic filter has not
+ * been consumed, match for special cases when the topic filter ends
+ * with wildcard character. */
+ if( nameIndex == ( topicNameLength - 1U ) )
+ {
+ matchFound = matchEndWildcardsSpecialCases( pTopicFilter,
+ topicFilterLength,
+ filterIndex );
+ }
+ }
+ else
+ {
+ /* Check for matching wildcards. */
+ shouldStopMatching = matchWildcards( pTopicName,
+ topicNameLength,
+ pTopicFilter,
+ topicFilterLength,
+ &nameIndex,
+ &filterIndex,
+ &matchFound );
+ }
+
+ if( ( matchFound == true ) || ( shouldStopMatching == true ) )
+ {
+ break;
+ }
+
+ /* Increment indexes. */
+ nameIndex++;
+ filterIndex++;
+ }
+
+ if( matchFound == false )
+ {
+ /* If the end of both strings has been reached, they match. This represents the
+ * case when the topic filter contains the '+' wildcard at a non-starting position.
+ * For example, when matching either of "sport/+/player" OR "sport/hockey/+" topic
+ * filters with "sport/hockey/player" topic name. */
+ matchFound = ( nameIndex == topicNameLength ) &&
+ ( filterIndex == topicFilterLength );
+ }
+
+ return matchFound;
+}
+
+/*-----------------------------------------------------------*/
+
+static int32_t sendMessageVector( MQTTContext_t * pContext,
+ TransportOutVector_t * pIoVec,
+ size_t ioVecCount )
+{
+ int32_t sendResult;
+ uint32_t timeoutMs;
+ TransportOutVector_t * pIoVectIterator;
+ size_t vectorsToBeSent = ioVecCount;
+ size_t bytesToSend = 0U;
+ int32_t bytesSentOrError = 0;
+
+ assert( pContext != NULL );
+ assert( pIoVec != NULL );
+ assert( pContext->getTime != NULL );
+ /* Send must always be defined */
+ assert( pContext->transportInterface.send != NULL );
+
+ /* Count the total number of bytes to be sent as outlined in the vector. */
+ for( pIoVectIterator = pIoVec; pIoVectIterator <= &( pIoVec[ ioVecCount - 1U ] ); pIoVectIterator++ )
+ {
+ bytesToSend += pIoVectIterator->iov_len;
+ }
+
+ /* Reset the iterator to point to the first entry in the array. */
+ pIoVectIterator = pIoVec;
+
+ /* Set the timeout. */
+ timeoutMs = pContext->getTime() + MQTT_SEND_TIMEOUT_MS;
+
+ while( ( bytesSentOrError < ( int32_t ) bytesToSend ) && ( bytesSentOrError >= 0 ) )
+ {
+ if( pContext->transportInterface.writev != NULL )
+ {
+ sendResult = pContext->transportInterface.writev( pContext->transportInterface.pNetworkContext,
+ pIoVectIterator,
+ vectorsToBeSent );
+ }
+ else
+ {
+ sendResult = pContext->transportInterface.send( pContext->transportInterface.pNetworkContext,
+ pIoVectIterator->iov_base,
+ pIoVectIterator->iov_len );
+ }
+
+ if( sendResult > 0 )
+ {
+ /* It is a bug in the application's transport send implementation if
+ * more bytes than expected are sent. */
+ assert( sendResult <= ( ( int32_t ) bytesToSend - bytesSentOrError ) );
+
+ bytesSentOrError += sendResult;
+
+ /* Set last transmission time. */
+ pContext->lastPacketTxTime = pContext->getTime();
+
+ LogDebug( ( "sendMessageVector: Bytes Sent=%ld, Bytes Remaining=%lu",
+ ( long int ) sendResult,
+ ( unsigned long ) ( bytesToSend - ( size_t ) bytesSentOrError ) ) );
+ }
+ else if( sendResult < 0 )
+ {
+ bytesSentOrError = sendResult;
+ LogError( ( "sendMessageVector: Unable to send packet: Network Error." ) );
+ }
+ else
+ {
+ /* MISRA Empty body */
+ }
+
+ /* Check for timeout. */
+ if( pContext->getTime() >= timeoutMs )
+ {
+ LogError( ( "sendMessageVector: Unable to send packet: Timed out." ) );
+ break;
+ }
+
+ /* Update the send pointer to the correct vector and offset. */
+ while( ( pIoVectIterator <= &( pIoVec[ ioVecCount - 1U ] ) ) &&
+ ( sendResult >= ( int32_t ) pIoVectIterator->iov_len ) )
+ {
+ sendResult -= ( int32_t ) pIoVectIterator->iov_len;
+ pIoVectIterator++;
+ /* Update the number of vector which are yet to be sent. */
+ vectorsToBeSent--;
+ }
+
+ /* Some of the bytes from this vector were sent as well, update the length
+ * and the pointer to data in this vector. */
+ if( ( sendResult > 0 ) &&
+ ( pIoVectIterator <= &( pIoVec[ ioVecCount - 1U ] ) ) )
+ {
+ pIoVectIterator->iov_base = ( const void * ) &( ( ( const uint8_t * ) pIoVectIterator->iov_base )[ sendResult ] );
+ pIoVectIterator->iov_len -= ( size_t ) sendResult;
+ }
+ }
+
+ return bytesSentOrError;
+}
+
+static int32_t sendBuffer( MQTTContext_t * pContext,
+ const uint8_t * pBufferToSend,
+ size_t bytesToSend )
+{
+ int32_t sendResult;
+ uint32_t timeoutMs;
+ int32_t bytesSentOrError = 0;
+ const uint8_t * pIndex = pBufferToSend;
+
+ assert( pContext != NULL );
+ assert( pContext->getTime != NULL );
+ assert( pContext->transportInterface.send != NULL );
+ assert( pIndex != NULL );
+
+ /* Set the timeout. */
+ timeoutMs = pContext->getTime() + MQTT_SEND_TIMEOUT_MS;
+
+ while( ( bytesSentOrError < ( int32_t ) bytesToSend ) && ( bytesSentOrError >= 0 ) )
+ {
+ sendResult = pContext->transportInterface.send( pContext->transportInterface.pNetworkContext,
+ pIndex,
+ bytesToSend - ( size_t ) bytesSentOrError );
+
+ if( sendResult > 0 )
+ {
+ /* It is a bug in the application's transport send implementation if
+ * more bytes than expected are sent. */
+ assert( sendResult <= ( ( int32_t ) bytesToSend - bytesSentOrError ) );
+
+ bytesSentOrError += sendResult;
+ pIndex = &pIndex[ sendResult ];
+
+ /* Set last transmission time. */
+ pContext->lastPacketTxTime = pContext->getTime();
+
+ LogDebug( ( "sendBuffer: Bytes Sent=%ld, Bytes Remaining=%lu",
+ ( long int ) sendResult,
+ ( unsigned long ) ( bytesToSend - ( size_t ) bytesSentOrError ) ) );
+ }
+ else if( sendResult < 0 )
+ {
+ bytesSentOrError = sendResult;
+ LogError( ( "sendBuffer: Unable to send packet: Network Error." ) );
+ }
+ else
+ {
+ /* MISRA Empty body */
+ }
+
+ /* Check for timeout. */
+ if( pContext->getTime() >= timeoutMs )
+ {
+ LogError( ( "sendBuffer: Unable to send packet: Timed out." ) );
+ break;
+ }
+ }
+
+ return bytesSentOrError;
+}
+
+/*-----------------------------------------------------------*/
+
+static uint32_t calculateElapsedTime( uint32_t later,
+ uint32_t start )
+{
+ return later - start;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType )
+{
+ MQTTPubAckType_t ackType = MQTTPuback;
+
+ switch( packetType )
+ {
+ case MQTT_PACKET_TYPE_PUBACK:
+ ackType = MQTTPuback;
+ break;
+
+ case MQTT_PACKET_TYPE_PUBREC:
+ ackType = MQTTPubrec;
+ break;
+
+ case MQTT_PACKET_TYPE_PUBREL:
+ ackType = MQTTPubrel;
+ break;
+
+ case MQTT_PACKET_TYPE_PUBCOMP:
+ default:
+
+ /* This function is only called after checking the type is one of
+ * the above four values, so packet type must be PUBCOMP here. */
+ assert( packetType == MQTT_PACKET_TYPE_PUBCOMP );
+ ackType = MQTTPubcomp;
+ break;
+ }
+
+ return ackType;
+}
+
+/*-----------------------------------------------------------*/
+
+static int32_t recvExact( const MQTTContext_t * pContext,
+ size_t bytesToRecv )
+{
+ uint8_t * pIndex = NULL;
+ size_t bytesRemaining = bytesToRecv;
+ int32_t totalBytesRecvd = 0, bytesRecvd;
+ uint32_t lastDataRecvTimeMs = 0U, timeSinceLastRecvMs = 0U;
+ TransportRecv_t recvFunc = NULL;
+ MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL;
+ bool receiveError = false;
+
+ assert( pContext != NULL );
+ assert( bytesToRecv <= pContext->networkBuffer.size );
+ assert( pContext->getTime != NULL );
+ assert( pContext->transportInterface.recv != NULL );
+ assert( pContext->networkBuffer.pBuffer != NULL );
+
+ pIndex = pContext->networkBuffer.pBuffer;
+ recvFunc = pContext->transportInterface.recv;
+ getTimeStampMs = pContext->getTime;
+
+ /* Part of the MQTT packet has been read before calling this function. */
+ lastDataRecvTimeMs = getTimeStampMs();
+
+ while( ( bytesRemaining > 0U ) && ( receiveError == false ) )
+ {
+ bytesRecvd = recvFunc( pContext->transportInterface.pNetworkContext,
+ pIndex,
+ bytesRemaining );
+
+ if( bytesRecvd < 0 )
+ {
+ LogError( ( "Network error while receiving packet: ReturnCode=%ld.",
+ ( long int ) bytesRecvd ) );
+ totalBytesRecvd = bytesRecvd;
+ receiveError = true;
+ }
+ else if( bytesRecvd > 0 )
+ {
+ /* Reset the starting time as we have received some data from the network. */
+ lastDataRecvTimeMs = getTimeStampMs();
+
+ /* It is a bug in the application's transport receive implementation
+ * if more bytes than expected are received. To avoid a possible
+ * overflow in converting bytesRemaining from unsigned to signed,
+ * this assert must exist after the check for bytesRecvd being
+ * negative. */
+ assert( ( size_t ) bytesRecvd <= bytesRemaining );
+
+ bytesRemaining -= ( size_t ) bytesRecvd;
+ totalBytesRecvd += ( int32_t ) bytesRecvd;
+ /* Increment the index. */
+ pIndex = &pIndex[ bytesRecvd ];
+ LogDebug( ( "BytesReceived=%ld, BytesRemaining=%lu, TotalBytesReceived=%ld.",
+ ( long int ) bytesRecvd,
+ ( unsigned long ) bytesRemaining,
+ ( long int ) totalBytesRecvd ) );
+ }
+ else
+ {
+ /* No bytes were read from the network. */
+ timeSinceLastRecvMs = calculateElapsedTime( getTimeStampMs(), lastDataRecvTimeMs );
+
+ /* Check for timeout if we have been waiting to receive any byte on the network. */
+ if( timeSinceLastRecvMs >= MQTT_RECV_POLLING_TIMEOUT_MS )
+ {
+ LogError( ( "Unable to receive packet: Timed out in transport recv." ) );
+ receiveError = true;
+ }
+ }
+ }
+
+ return totalBytesRecvd;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t discardPacket( const MQTTContext_t * pContext,
+ size_t remainingLength,
+ uint32_t timeoutMs )
+{
+ MQTTStatus_t status = MQTTRecvFailed;
+ int32_t bytesReceived = 0;
+ size_t bytesToReceive = 0U;
+ uint32_t totalBytesReceived = 0U;
+ uint32_t entryTimeMs = 0U;
+ uint32_t elapsedTimeMs = 0U;
+ MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL;
+ bool receiveError = false;
+
+ assert( pContext != NULL );
+ assert( pContext->getTime != NULL );
+
+ bytesToReceive = pContext->networkBuffer.size;
+ getTimeStampMs = pContext->getTime;
+
+ entryTimeMs = getTimeStampMs();
+
+ while( ( totalBytesReceived < remainingLength ) && ( receiveError == false ) )
+ {
+ if( ( remainingLength - totalBytesReceived ) < bytesToReceive )
+ {
+ bytesToReceive = remainingLength - totalBytesReceived;
+ }
+
+ bytesReceived = recvExact( pContext, bytesToReceive );
+
+ if( bytesReceived != ( int32_t ) bytesToReceive )
+ {
+ LogError( ( "Receive error while discarding packet."
+ "ReceivedBytes=%ld, ExpectedBytes=%lu.",
+ ( long int ) bytesReceived,
+ ( unsigned long ) bytesToReceive ) );
+ receiveError = true;
+ }
+ else
+ {
+ totalBytesReceived += ( uint32_t ) bytesReceived;
+
+ elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs );
+
+ /* Check for timeout. */
+ if( elapsedTimeMs >= timeoutMs )
+ {
+ LogError( ( "Time expired while discarding packet." ) );
+ receiveError = true;
+ }
+ }
+ }
+
+ if( totalBytesReceived == remainingLength )
+ {
+ LogError( ( "Dumped packet. DumpedBytes=%lu.",
+ ( unsigned long ) totalBytesReceived ) );
+ /* Packet dumped, so no data is available. */
+ status = MQTTNoDataAvailable;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t discardStoredPacket( MQTTContext_t * pContext,
+ const MQTTPacketInfo_t * pPacketInfo )
+{
+ MQTTStatus_t status = MQTTRecvFailed;
+ int32_t bytesReceived = 0;
+ size_t bytesToReceive = 0U;
+ uint32_t totalBytesReceived = 0U;
+ bool receiveError = false;
+ size_t mqttPacketSize = 0;
+ size_t remainingLength;
+
+ assert( pContext != NULL );
+ assert( pPacketInfo != NULL );
+
+ mqttPacketSize = pPacketInfo->remainingLength + pPacketInfo->headerLength;
+
+ /* Assert that the packet being discarded is bigger than the
+ * receive buffer. */
+ assert( mqttPacketSize > pContext->networkBuffer.size );
+
+ /* Discard these many bytes at a time. */
+ bytesToReceive = pContext->networkBuffer.size;
+
+ /* Number of bytes depicted by 'index' have already been received. */
+ remainingLength = mqttPacketSize - pContext->index;
+
+ while( ( totalBytesReceived < remainingLength ) && ( receiveError == false ) )
+ {
+ if( ( remainingLength - totalBytesReceived ) < bytesToReceive )
+ {
+ bytesToReceive = remainingLength - totalBytesReceived;
+ }
+
+ bytesReceived = recvExact( pContext, bytesToReceive );
+
+ if( bytesReceived != ( int32_t ) bytesToReceive )
+ {
+ LogError( ( "Receive error while discarding packet."
+ "ReceivedBytes=%ld, ExpectedBytes=%lu.",
+ ( long int ) bytesReceived,
+ ( unsigned long ) bytesToReceive ) );
+ receiveError = true;
+ }
+ else
+ {
+ totalBytesReceived += ( uint32_t ) bytesReceived;
+ }
+ }
+
+ if( totalBytesReceived == remainingLength )
+ {
+ LogError( ( "Dumped packet. DumpedBytes=%lu.",
+ ( unsigned long ) totalBytesReceived ) );
+ /* Packet dumped, so no data is available. */
+ status = MQTTNoDataAvailable;
+ }
+
+ /* Clear the buffer */
+ ( void ) memset( pContext->networkBuffer.pBuffer,
+ 0,
+ pContext->networkBuffer.size );
+
+ /* Reset the index. */
+ pContext->index = 0;
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t receivePacket( const MQTTContext_t * pContext,
+ MQTTPacketInfo_t incomingPacket,
+ uint32_t remainingTimeMs )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ int32_t bytesReceived = 0;
+ size_t bytesToReceive = 0U;
+
+ assert( pContext != NULL );
+ assert( pContext->networkBuffer.pBuffer != NULL );
+
+ if( incomingPacket.remainingLength > pContext->networkBuffer.size )
+ {
+ LogError( ( "Incoming packet will be dumped: "
+ "Packet length exceeds network buffer size."
+ "PacketSize=%lu, NetworkBufferSize=%lu.",
+ ( unsigned long ) incomingPacket.remainingLength,
+ ( unsigned long ) pContext->networkBuffer.size ) );
+ status = discardPacket( pContext,
+ incomingPacket.remainingLength,
+ remainingTimeMs );
+ }
+ else
+ {
+ bytesToReceive = incomingPacket.remainingLength;
+ bytesReceived = recvExact( pContext, bytesToReceive );
+
+ if( bytesReceived == ( int32_t ) bytesToReceive )
+ {
+ /* Receive successful, bytesReceived == bytesToReceive. */
+ LogDebug( ( "Packet received. ReceivedBytes=%ld.",
+ ( long int ) bytesReceived ) );
+ }
+ else
+ {
+ LogError( ( "Packet reception failed. ReceivedBytes=%ld, "
+ "ExpectedBytes=%lu.",
+ ( long int ) bytesReceived,
+ ( unsigned long ) bytesToReceive ) );
+ status = MQTTRecvFailed;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static uint8_t getAckTypeToSend( MQTTPublishState_t state )
+{
+ uint8_t packetTypeByte = 0U;
+
+ switch( state )
+ {
+ case MQTTPubAckSend:
+ packetTypeByte = MQTT_PACKET_TYPE_PUBACK;
+ break;
+
+ case MQTTPubRecSend:
+ packetTypeByte = MQTT_PACKET_TYPE_PUBREC;
+ break;
+
+ case MQTTPubRelSend:
+ packetTypeByte = MQTT_PACKET_TYPE_PUBREL;
+ break;
+
+ case MQTTPubCompSend:
+ packetTypeByte = MQTT_PACKET_TYPE_PUBCOMP;
+ break;
+
+ case MQTTPubAckPending:
+ case MQTTPubCompPending:
+ case MQTTPubRecPending:
+ case MQTTPubRelPending:
+ case MQTTPublishDone:
+ case MQTTPublishSend:
+ case MQTTStateNull:
+ default:
+ /* Take no action for states that do not require sending an ack. */
+ break;
+ }
+
+ return packetTypeByte;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t sendPublishAcks( MQTTContext_t * pContext,
+ uint16_t packetId,
+ MQTTPublishState_t publishState )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ MQTTPublishState_t newState = MQTTStateNull;
+ int32_t sendResult = 0;
+ uint8_t packetTypeByte = 0U;
+ MQTTPubAckType_t packetType;
+ MQTTFixedBuffer_t localBuffer;
+ uint8_t pubAckPacket[ MQTT_PUBLISH_ACK_PACKET_SIZE ];
+
+ localBuffer.pBuffer = pubAckPacket;
+ localBuffer.size = MQTT_PUBLISH_ACK_PACKET_SIZE;
+
+ assert( pContext != NULL );
+
+ packetTypeByte = getAckTypeToSend( publishState );
+
+ if( packetTypeByte != 0U )
+ {
+ packetType = getAckFromPacketType( packetTypeByte );
+
+ status = MQTT_SerializeAck( &localBuffer,
+ packetTypeByte,
+ packetId );
+
+ if( status == MQTTSuccess )
+ {
+ MQTT_PRE_SEND_HOOK( pContext );
+
+ /* Here, we are not using the vector approach for efficiency. There is just one buffer
+ * to be sent which can be achieved with a normal send call. */
+ sendResult = sendBuffer( pContext,
+ localBuffer.pBuffer,
+ MQTT_PUBLISH_ACK_PACKET_SIZE );
+
+ MQTT_POST_SEND_HOOK( pContext );
+ }
+
+ if( sendResult == ( int32_t ) MQTT_PUBLISH_ACK_PACKET_SIZE )
+ {
+ pContext->controlPacketSent = true;
+
+ MQTT_PRE_STATE_UPDATE_HOOK( pContext );
+
+ status = MQTT_UpdateStateAck( pContext,
+ packetId,
+ packetType,
+ MQTT_SEND,
+ &newState );
+
+ MQTT_POST_STATE_UPDATE_HOOK( pContext );
+
+ if( status != MQTTSuccess )
+ {
+ LogError( ( "Failed to update state of publish %hu.",
+ ( unsigned short ) packetId ) );
+ }
+ }
+ else
+ {
+ LogError( ( "Failed to send ACK packet: PacketType=%02x, SentBytes=%ld, "
+ "PacketSize=%lu.",
+ ( unsigned int ) packetTypeByte, ( long int ) sendResult,
+ MQTT_PUBLISH_ACK_PACKET_SIZE ) );
+ status = MQTTSendFailed;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ uint32_t now = 0U;
+ uint32_t packetTxTimeoutMs = 0U;
+
+ assert( pContext != NULL );
+ assert( pContext->getTime != NULL );
+
+ now = pContext->getTime();
+
+ packetTxTimeoutMs = 1000U * ( uint32_t ) pContext->keepAliveIntervalSec;
+
+ if( PACKET_TX_TIMEOUT_MS < packetTxTimeoutMs )
+ {
+ packetTxTimeoutMs = PACKET_TX_TIMEOUT_MS;
+ }
+
+ /* If keep alive interval is 0, it is disabled. */
+ if( pContext->waitingForPingResp == true )
+ {
+ /* Has time expired? */
+ if( calculateElapsedTime( now, pContext->pingReqSendTimeMs ) >
+ MQTT_PINGRESP_TIMEOUT_MS )
+ {
+ status = MQTTKeepAliveTimeout;
+ }
+ }
+ else
+ {
+ if( ( packetTxTimeoutMs != 0U ) && ( calculateElapsedTime( now, pContext->lastPacketTxTime ) >= packetTxTimeoutMs ) )
+ {
+ status = MQTT_Ping( pContext );
+ }
+ else
+ {
+ const uint32_t timeElapsed = calculateElapsedTime( now, pContext->lastPacketRxTime );
+
+ if( ( timeElapsed != 0U ) && ( timeElapsed >= PACKET_RX_TIMEOUT_MS ) )
+ {
+ status = MQTT_Ping( pContext );
+ }
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext,
+ MQTTPacketInfo_t * pIncomingPacket )
+{
+ MQTTStatus_t status = MQTTBadParameter;
+ MQTTPublishState_t publishRecordState = MQTTStateNull;
+ uint16_t packetIdentifier = 0U;
+ MQTTPublishInfo_t publishInfo;
+ MQTTDeserializedInfo_t deserializedInfo;
+ bool duplicatePublish = false;
+
+ assert( pContext != NULL );
+ assert( pIncomingPacket != NULL );
+ assert( pContext->appCallback != NULL );
+
+ status = MQTT_DeserializePublish( pIncomingPacket, &packetIdentifier, &publishInfo );
+ LogInfo( ( "De-serialized incoming PUBLISH packet: DeserializerResult=%s.",
+ MQTT_Status_strerror( status ) ) );
+
+ if( ( status == MQTTSuccess ) &&
+ ( pContext->incomingPublishRecords == NULL ) &&
+ ( publishInfo.qos > MQTTQoS0 ) )
+ {
+ LogError( ( "Incoming publish has QoS > MQTTQoS0 but incoming "
+ "publish records have not been initialized. Dropping the "
+ "incoming publish. Please call MQTT_InitStatefulQoS to enable "
+ "use of QoS1 and QoS2 publishes." ) );
+ status = MQTTRecvFailed;
+ }
+
+ if( status == MQTTSuccess )
+ {
+ MQTT_PRE_STATE_UPDATE_HOOK( pContext );
+
+ status = MQTT_UpdateStatePublish( pContext,
+ packetIdentifier,
+ MQTT_RECEIVE,
+ publishInfo.qos,
+ &publishRecordState );
+
+ MQTT_POST_STATE_UPDATE_HOOK( pContext );
+
+ if( status == MQTTSuccess )
+ {
+ LogInfo( ( "State record updated. New state=%s.",
+ MQTT_State_strerror( publishRecordState ) ) );
+ }
+
+ /* Different cases in which an incoming publish with duplicate flag is
+ * handled are as listed below.
+ * 1. No collision - This is the first instance of the incoming publish
+ * packet received or an earlier received packet state is lost. This
+ * will be handled as a new incoming publish for both QoS1 and QoS2
+ * publishes.
+ * 2. Collision - The incoming packet was received before and a state
+ * record is present in the state engine. For QoS1 and QoS2 publishes
+ * this case can happen at 2 different cases and handling is
+ * different.
+ * a. QoS1 - If a PUBACK is not successfully sent for the incoming
+ * publish due to a connection issue, it can result in broker
+ * sending out a duplicate publish with dup flag set, when a
+ * session is reestablished. It can result in a collision in
+ * state engine. This will be handled by processing the incoming
+ * publish as a new publish ignoring the
+ * #MQTTStateCollision status from the state engine. The publish
+ * data is not passed to the application.
+ * b. QoS2 - If a PUBREC is not successfully sent for the incoming
+ * publish or the PUBREC sent is not successfully received by the
+ * broker due to a connection issue, it can result in broker
+ * sending out a duplicate publish with dup flag set, when a
+ * session is reestablished. It can result in a collision in
+ * state engine. This will be handled by ignoring the
+ * #MQTTStateCollision status from the state engine. The publish
+ * data is not passed to the application. */
+ else if( status == MQTTStateCollision )
+ {
+ status = MQTTSuccess;
+ duplicatePublish = true;
+
+ /* Calculate the state for the ack packet that needs to be sent out
+ * for the duplicate incoming publish. */
+ publishRecordState = MQTT_CalculateStatePublish( MQTT_RECEIVE,
+ publishInfo.qos );
+
+ LogDebug( ( "Incoming publish packet with packet id %hu already exists.",
+ ( unsigned short ) packetIdentifier ) );
+
+ if( publishInfo.dup == false )
+ {
+ LogError( ( "DUP flag is 0 for duplicate packet (MQTT-3.3.1.-1)." ) );
+ }
+ }
+ else
+ {
+ LogError( ( "Error in updating publish state for incoming publish with packet id %hu."
+ " Error is %s",
+ ( unsigned short ) packetIdentifier,
+ MQTT_Status_strerror( status ) ) );
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Set fields of deserialized struct. */
+ deserializedInfo.packetIdentifier = packetIdentifier;
+ deserializedInfo.pPublishInfo = &publishInfo;
+ deserializedInfo.deserializationResult = status;
+
+ /* Invoke application callback to hand the buffer over to application
+ * before sending acks.
+ * Application callback will be invoked for all publishes, except for
+ * duplicate incoming publishes. */
+ if( duplicatePublish == false )
+ {
+ pContext->appCallback( pContext,
+ pIncomingPacket,
+ &deserializedInfo );
+ }
+
+ /* Send PUBACK or PUBREC if necessary. */
+ status = sendPublishAcks( pContext,
+ packetIdentifier,
+ publishRecordState );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext,
+ MQTTPacketInfo_t * pIncomingPacket )
+{
+ MQTTStatus_t status = MQTTBadResponse;
+ MQTTPublishState_t publishRecordState = MQTTStateNull;
+ uint16_t packetIdentifier;
+ MQTTPubAckType_t ackType;
+ MQTTEventCallback_t appCallback;
+ MQTTDeserializedInfo_t deserializedInfo;
+
+ assert( pContext != NULL );
+ assert( pIncomingPacket != NULL );
+ assert( pContext->appCallback != NULL );
+
+ appCallback = pContext->appCallback;
+
+ ackType = getAckFromPacketType( pIncomingPacket->type );
+ status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL );
+ LogInfo( ( "Ack packet deserialized with result: %s.",
+ MQTT_Status_strerror( status ) ) );
+
+ if( status == MQTTSuccess )
+ {
+ MQTT_PRE_STATE_UPDATE_HOOK( pContext );
+
+ status = MQTT_UpdateStateAck( pContext,
+ packetIdentifier,
+ ackType,
+ MQTT_RECEIVE,
+ &publishRecordState );
+
+ MQTT_POST_STATE_UPDATE_HOOK( pContext );
+
+ if( status == MQTTSuccess )
+ {
+ LogInfo( ( "State record updated. New state=%s.",
+ MQTT_State_strerror( publishRecordState ) ) );
+ }
+ else
+ {
+ LogError( ( "Updating the state engine for packet id %hu"
+ " failed with error %s.",
+ ( unsigned short ) packetIdentifier,
+ MQTT_Status_strerror( status ) ) );
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Set fields of deserialized struct. */
+ deserializedInfo.packetIdentifier = packetIdentifier;
+ deserializedInfo.deserializationResult = status;
+ deserializedInfo.pPublishInfo = NULL;
+
+ /* Invoke application callback to hand the buffer over to application
+ * before sending acks. */
+ appCallback( pContext, pIncomingPacket, &deserializedInfo );
+
+ /* Send PUBREL or PUBCOMP if necessary. */
+ status = sendPublishAcks( pContext,
+ packetIdentifier,
+ publishRecordState );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext,
+ MQTTPacketInfo_t * pIncomingPacket,
+ bool manageKeepAlive )
+{
+ MQTTStatus_t status = MQTTBadResponse;
+ uint16_t packetIdentifier = MQTT_PACKET_ID_INVALID;
+ MQTTDeserializedInfo_t deserializedInfo;
+
+ /* We should always invoke the app callback unless we receive a PINGRESP
+ * and are managing keep alive, or if we receive an unknown packet. We
+ * initialize this to false since the callback must be invoked before
+ * sending any PUBREL or PUBCOMP. However, for other cases, we invoke it
+ * at the end to reduce the complexity of this function. */
+ bool invokeAppCallback = false;
+ MQTTEventCallback_t appCallback = NULL;
+
+ assert( pContext != NULL );
+ assert( pIncomingPacket != NULL );
+ assert( pContext->appCallback != NULL );
+
+ appCallback = pContext->appCallback;
+
+ LogDebug( ( "Received packet of type %02x.",
+ ( unsigned int ) pIncomingPacket->type ) );
+
+ switch( pIncomingPacket->type )
+ {
+ case MQTT_PACKET_TYPE_PUBACK:
+ case MQTT_PACKET_TYPE_PUBREC:
+ case MQTT_PACKET_TYPE_PUBREL:
+ case MQTT_PACKET_TYPE_PUBCOMP:
+
+ /* Handle all the publish acks. The app callback is invoked here. */
+ status = handlePublishAcks( pContext, pIncomingPacket );
+
+ break;
+
+ case MQTT_PACKET_TYPE_PINGRESP:
+ status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL );
+ invokeAppCallback = ( status == MQTTSuccess ) && !manageKeepAlive;
+
+ if( ( status == MQTTSuccess ) && ( manageKeepAlive == true ) )
+ {
+ pContext->waitingForPingResp = false;
+ }
+
+ break;
+
+ case MQTT_PACKET_TYPE_SUBACK:
+ case MQTT_PACKET_TYPE_UNSUBACK:
+ /* Deserialize and give these to the app provided callback. */
+ status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL );
+ invokeAppCallback = ( status == MQTTSuccess ) || ( status == MQTTServerRefused );
+ break;
+
+ default:
+ /* Bad response from the server. */
+ LogError( ( "Unexpected packet type from server: PacketType=%02x.",
+ ( unsigned int ) pIncomingPacket->type ) );
+ status = MQTTBadResponse;
+ break;
+ }
+
+ if( invokeAppCallback == true )
+ {
+ /* Set fields of deserialized struct. */
+ deserializedInfo.packetIdentifier = packetIdentifier;
+ deserializedInfo.deserializationResult = status;
+ deserializedInfo.pPublishInfo = NULL;
+ appCallback( pContext, pIncomingPacket, &deserializedInfo );
+ /* In case a SUBACK indicated refusal, reset the status to continue the loop. */
+ status = MQTTSuccess;
+ }
+
+ return status;
+}
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t receiveSingleIteration( MQTTContext_t * pContext,
+ bool manageKeepAlive )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ MQTTPacketInfo_t incomingPacket = { 0 };
+ int32_t recvBytes;
+ size_t totalMQTTPacketLength = 0;
+
+ assert( pContext != NULL );
+ assert( pContext->networkBuffer.pBuffer != NULL );
+
+ /* Read as many bytes as possible into the network buffer. */
+ recvBytes = pContext->transportInterface.recv( pContext->transportInterface.pNetworkContext,
+ &( pContext->networkBuffer.pBuffer[ pContext->index ] ),
+ pContext->networkBuffer.size - pContext->index );
+
+ if( recvBytes < 0 )
+ {
+ /* The receive function has failed. Bubble up the error up to the user. */
+ status = MQTTRecvFailed;
+ }
+ else if( ( recvBytes == 0 ) && ( pContext->index == 0U ) )
+ {
+ /* No more bytes available since the last read and neither is anything in
+ * the buffer. */
+ status = MQTTNoDataAvailable;
+ }
+
+ /* Either something was received, or there is still data to be processed in the
+ * buffer, or both. */
+ else
+ {
+ /* Update the number of bytes in the MQTT fixed buffer. */
+ pContext->index += ( size_t ) recvBytes;
+
+ status = MQTT_ProcessIncomingPacketTypeAndLength( pContext->networkBuffer.pBuffer,
+ &pContext->index,
+ &incomingPacket );
+
+ totalMQTTPacketLength = incomingPacket.remainingLength + incomingPacket.headerLength;
+ }
+
+ /* No data was received, check for keep alive timeout. */
+ if( recvBytes == 0 )
+ {
+ if( manageKeepAlive == true )
+ {
+ /* Keep the copy of the status to be reset later. */
+ MQTTStatus_t statusCopy = status;
+
+ /* Assign status so an error can be bubbled up to application,
+ * but reset it on success. */
+ status = handleKeepAlive( pContext );
+
+ if( status == MQTTSuccess )
+ {
+ /* Reset the status. */
+ status = statusCopy;
+ }
+ else
+ {
+ LogError( ( "Handling of keep alive failed. Status=%s",
+ MQTT_Status_strerror( status ) ) );
+ }
+ }
+ }
+
+ /* Check whether there is data available before processing the packet further. */
+ if( ( status == MQTTNeedMoreBytes ) || ( status == MQTTNoDataAvailable ) )
+ {
+ /* Do nothing as there is nothing to be processed right now. The proper
+ * error code will be bubbled up to the user. */
+ }
+ /* Any other error code. */
+ else if( status != MQTTSuccess )
+ {
+ LogError( ( "Call to receiveSingleIteration failed. Status=%s",
+ MQTT_Status_strerror( status ) ) );
+ }
+ /* If the MQTT Packet size is bigger than the buffer itself. */
+ else if( totalMQTTPacketLength > pContext->networkBuffer.size )
+ {
+ /* Discard the packet from the receive buffer and drain the pending
+ * data from the socket buffer. */
+ status = discardStoredPacket( pContext,
+ &incomingPacket );
+ }
+ /* If the total packet is of more length than the bytes we have available. */
+ else if( totalMQTTPacketLength > pContext->index )
+ {
+ status = MQTTNeedMoreBytes;
+ }
+ else
+ {
+ /* MISRA else. */
+ }
+
+ /* Handle received packet. If incomplete data was read then this will not execute. */
+ if( status == MQTTSuccess )
+ {
+ incomingPacket.pRemainingData = &pContext->networkBuffer.pBuffer[ incomingPacket.headerLength ];
+
+ /* PUBLISH packets allow flags in the lower four bits. For other
+ * packet types, they are reserved. */
+ if( ( incomingPacket.type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH )
+ {
+ status = handleIncomingPublish( pContext, &incomingPacket );
+ }
+ else
+ {
+ status = handleIncomingAck( pContext, &incomingPacket, manageKeepAlive );
+ }
+
+ /* Update the index to reflect the remaining bytes in the buffer. */
+ pContext->index -= totalMQTTPacketLength;
+
+ /* Move the remaining bytes to the front of the buffer. */
+ ( void ) memmove( pContext->networkBuffer.pBuffer,
+ &( pContext->networkBuffer.pBuffer[ totalMQTTPacketLength ] ),
+ pContext->index );
+ }
+
+ if( status == MQTTNoDataAvailable )
+ {
+ /* No data available is not an error. Reset to MQTTSuccess so the
+ * return code will indicate success. */
+ status = MQTTSuccess;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * pContext,
+ const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ size_t iterator;
+
+ /* Validate all the parameters. */
+ if( ( pContext == NULL ) || ( pSubscriptionList == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pContext=%p, "
+ "pSubscriptionList=%p.",
+ ( void * ) pContext,
+ ( void * ) pSubscriptionList ) );
+ status = MQTTBadParameter;
+ }
+ else if( subscriptionCount == 0UL )
+ {
+ LogError( ( "Subscription count is 0." ) );
+ status = MQTTBadParameter;
+ }
+ else if( packetId == 0U )
+ {
+ LogError( ( "Packet Id for subscription packet is 0." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ if( pContext->incomingPublishRecords == NULL )
+ {
+ for( iterator = 0; iterator < subscriptionCount; iterator++ )
+ {
+ if( pSubscriptionList->qos > MQTTQoS0 )
+ {
+ LogError( ( "The incoming publish record list is not "
+ "initialised for QoS1/QoS2 records. Please call "
+ " MQTT_InitStatefulQoS to enable use of QoS1 and "
+ " QoS2 packets." ) );
+ status = MQTTBadParameter;
+ break;
+ }
+ }
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static size_t addEncodedStringToVector( uint8_t serailizedLength[ 2 ],
+ const char * const string,
+ uint16_t length,
+ TransportOutVector_t * iterator,
+ size_t * updatedLength )
+{
+ size_t packetLength = 0U;
+ const size_t seralizedLengthFieldSize = 2U;
+ TransportOutVector_t * pLocalIterator = iterator;
+ /* This function always adds 2 vectors. */
+ size_t vectorsAdded = 0U;
+
+ /* When length is non-zero, the string must be non-NULL. */
+ assert( ( length != 0U ) == ( string != NULL ) );
+
+ serailizedLength[ 0 ] = ( ( uint8_t ) ( ( length ) >> 8 ) );
+ serailizedLength[ 1 ] = ( ( uint8_t ) ( ( length ) & 0x00ffU ) );
+
+ /* Add the serialized length of the string first. */
+ pLocalIterator[ 0 ].iov_base = serailizedLength;
+ pLocalIterator[ 0 ].iov_len = seralizedLengthFieldSize;
+ vectorsAdded++;
+ packetLength = seralizedLengthFieldSize;
+
+ /* Sometimes the string can be NULL that is, of 0 length. In that case,
+ * only the length field should be encoded in the vector. */
+ if( ( string != NULL ) && ( length != 0U ) )
+ {
+ /* Then add the pointer to the string itself. */
+ pLocalIterator[ 1 ].iov_base = string;
+ pLocalIterator[ 1 ].iov_len = length;
+ vectorsAdded++;
+ packetLength += length;
+ }
+
+ ( *updatedLength ) = ( *updatedLength ) + packetLength;
+
+ return vectorsAdded;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t sendSubscribeWithoutCopy( MQTTContext_t * pContext,
+ const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId,
+ size_t remainingLength )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ uint8_t subscribeheader[ 7 ];
+ uint8_t * pIndex;
+ TransportOutVector_t pIoVector[ MQTT_SUB_UNSUB_MAX_VECTORS ];
+ TransportOutVector_t * pIterator;
+ uint8_t serializedTopicFieldLength[ MQTT_SUB_UNSUB_MAX_VECTORS ][ 2 ];
+ size_t totalPacketLength = 0U;
+ size_t ioVectorLength = 0U;
+ size_t subscriptionsSent = 0U;
+ /* For subscribe, only three vector slots are required per topic string. */
+ const size_t subscriptionStringVectorSlots = 3U;
+ size_t vectorsAdded;
+ size_t topicFieldLengthIndex;
+
+ /* The vector array should be at least three element long as the topic
+ * string needs these many vector elements to be stored. */
+ assert( MQTT_SUB_UNSUB_MAX_VECTORS >= subscriptionStringVectorSlots );
+
+ pIndex = subscribeheader;
+ pIterator = pIoVector;
+
+ pIndex = MQTT_SerializeSubscribeHeader( remainingLength,
+ pIndex,
+ packetId );
+
+ /* The header is to be sent first. */
+ pIterator->iov_base = subscribeheader;
+ /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-182 */
+ /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-108 */
+ /* coverity[misra_c_2012_rule_18_2_violation] */
+ /* coverity[misra_c_2012_rule_10_8_violation] */
+ pIterator->iov_len = ( size_t ) ( pIndex - subscribeheader );
+ totalPacketLength += pIterator->iov_len;
+ pIterator++;
+ ioVectorLength++;
+
+ while( ( status == MQTTSuccess ) && ( subscriptionsSent < subscriptionCount ) )
+ {
+ /* Reset the index for next iteration. */
+ topicFieldLengthIndex = 0;
+
+ /* Check whether the subscription topic (with QoS) will fit in the
+ * given vector. */
+ while( ( ioVectorLength <= ( MQTT_SUB_UNSUB_MAX_VECTORS - subscriptionStringVectorSlots ) ) &&
+ ( subscriptionsSent < subscriptionCount ) )
+ {
+ /* The topic filter gets sent next. */
+ vectorsAdded = addEncodedStringToVector( serializedTopicFieldLength[ topicFieldLengthIndex ],
+ pSubscriptionList[ subscriptionsSent ].pTopicFilter,
+ pSubscriptionList[ subscriptionsSent ].topicFilterLength,
+ pIterator,
+ &totalPacketLength );
+
+ /* Update the pointer after the above operation. */
+ pIterator = &pIterator[ vectorsAdded ];
+
+ /* Lastly, the QoS gets sent. */
+ pIterator->iov_base = &( pSubscriptionList[ subscriptionsSent ].qos );
+ pIterator->iov_len = 1U;
+ totalPacketLength += pIterator->iov_len;
+
+ /* Increment the pointer. */
+ pIterator++;
+
+ /* Two slots get used by the topic string length and topic string.
+ * One slot gets used by the quality of service. */
+ ioVectorLength += vectorsAdded + 1U;
+
+ subscriptionsSent++;
+
+ /* The index needs to be updated for next iteration. */
+ topicFieldLengthIndex++;
+ }
+
+ if( sendMessageVector( pContext,
+ pIoVector,
+ ioVectorLength ) != ( int32_t ) totalPacketLength )
+ {
+ status = MQTTSendFailed;
+ }
+
+ /* Update the iterator for the next potential loop iteration. */
+ pIterator = pIoVector;
+ /* Reset the vector length for the next potential loop iteration. */
+ ioVectorLength = 0U;
+ /* Reset the packet length for the next potential loop iteration. */
+ totalPacketLength = 0U;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t sendUnsubscribeWithoutCopy( MQTTContext_t * pContext,
+ const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId,
+ size_t remainingLength )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ uint8_t unsubscribeheader[ 7 ];
+ uint8_t * pIndex;
+ TransportOutVector_t pIoVector[ MQTT_SUB_UNSUB_MAX_VECTORS ];
+ TransportOutVector_t * pIterator;
+ uint8_t serializedTopicFieldLength[ MQTT_SUB_UNSUB_MAX_VECTORS ][ 2 ];
+ size_t totalPacketLength = 0U;
+ size_t unsubscriptionsSent = 0U;
+ size_t ioVectorLength = 0U;
+ /* For unsubscribe, only two vector slots are required per topic string. */
+ const size_t unsubscribeStringVectorSlots = 2U;
+ size_t vectorsAdded;
+ size_t topicFieldLengthIndex;
+
+ /* The vector array should be at least three element long as the topic
+ * string needs these many vector elements to be stored. */
+ assert( MQTT_SUB_UNSUB_MAX_VECTORS >= unsubscribeStringVectorSlots );
+
+ pIndex = unsubscribeheader;
+ pIterator = pIoVector;
+
+ pIndex = MQTT_SerializeUnsubscribeHeader( remainingLength,
+ pIndex,
+ packetId );
+
+ /* The header is to be sent first. */
+ pIterator->iov_base = unsubscribeheader;
+ /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-182 */
+ /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-108 */
+ /* coverity[misra_c_2012_rule_18_2_violation] */
+ /* coverity[misra_c_2012_rule_10_8_violation] */
+ pIterator->iov_len = ( size_t ) ( pIndex - unsubscribeheader );
+ totalPacketLength += pIterator->iov_len;
+ pIterator++;
+ ioVectorLength++;
+
+ while( ( status == MQTTSuccess ) && ( unsubscriptionsSent < subscriptionCount ) )
+ {
+ /* Reset the index for next iteration. */
+ topicFieldLengthIndex = 0;
+
+ /* Check whether the subscription topic will fit in the given vector. */
+ while( ( ioVectorLength <= ( MQTT_SUB_UNSUB_MAX_VECTORS - unsubscribeStringVectorSlots ) ) &&
+ ( unsubscriptionsSent < subscriptionCount ) )
+ {
+ /* The topic filter gets sent next. */
+ vectorsAdded = addEncodedStringToVector( serializedTopicFieldLength[ topicFieldLengthIndex ],
+ pSubscriptionList[ unsubscriptionsSent ].pTopicFilter,
+ pSubscriptionList[ unsubscriptionsSent ].topicFilterLength,
+ pIterator,
+ &totalPacketLength );
+
+ /* Update the iterator to point to the next empty location. */
+ pIterator = &pIterator[ vectorsAdded ];
+ /* Update the total count based on how many vectors were added. */
+ ioVectorLength += vectorsAdded;
+
+ unsubscriptionsSent++;
+
+ /* Update the index for next iteration. */
+ topicFieldLengthIndex++;
+ }
+
+ if( sendMessageVector( pContext, pIoVector, ioVectorLength ) != ( int32_t ) totalPacketLength )
+ {
+ status = MQTTSendFailed;
+ }
+
+ /* Update the iterator for the next potential loop iteration. */
+ pIterator = pIoVector;
+ /* Reset the vector length for the next potential loop iteration. */
+ ioVectorLength = 0U;
+ /* Reset the packet length for the next potential loop iteration. */
+ totalPacketLength = 0U;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext,
+ const MQTTPublishInfo_t * pPublishInfo,
+ const uint8_t * pMqttHeader,
+ size_t headerSize,
+ uint16_t packetId )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ uint8_t serializedPacketID[ 2 ];
+ TransportOutVector_t pIoVector[ 4 ];
+ size_t ioVectorLength;
+ size_t totalMessageLength;
+ const size_t packetIDLength = 2U;
+
+ /* The header is sent first. */
+ pIoVector[ 0U ].iov_base = pMqttHeader;
+ pIoVector[ 0U ].iov_len = headerSize;
+ totalMessageLength = headerSize;
+
+ /* Then the topic name has to be sent. */
+ pIoVector[ 1U ].iov_base = pPublishInfo->pTopicName;
+ pIoVector[ 1U ].iov_len = pPublishInfo->topicNameLength;
+ totalMessageLength += pPublishInfo->topicNameLength;
+
+ /* The next field's index should be 2 as the first two fields
+ * have been filled in. */
+ ioVectorLength = 2U;
+
+ if( pPublishInfo->qos > MQTTQoS0 )
+ {
+ /* Encode the packet ID. */
+ serializedPacketID[ 0 ] = ( ( uint8_t ) ( ( packetId ) >> 8 ) );
+ serializedPacketID[ 1 ] = ( ( uint8_t ) ( ( packetId ) & 0x00ffU ) );
+
+ pIoVector[ ioVectorLength ].iov_base = serializedPacketID;
+ pIoVector[ ioVectorLength ].iov_len = packetIDLength;
+
+ ioVectorLength++;
+ totalMessageLength += packetIDLength;
+ }
+
+ /* Publish packets are allowed to contain no payload. */
+ if( pPublishInfo->payloadLength > 0U )
+ {
+ pIoVector[ ioVectorLength ].iov_base = pPublishInfo->pPayload;
+ pIoVector[ ioVectorLength ].iov_len = pPublishInfo->payloadLength;
+
+ ioVectorLength++;
+ totalMessageLength += pPublishInfo->payloadLength;
+ }
+
+ if( sendMessageVector( pContext, pIoVector, ioVectorLength ) != ( int32_t ) totalMessageLength )
+ {
+ status = MQTTSendFailed;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t sendConnectWithoutCopy( MQTTContext_t * pContext,
+ const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ size_t remainingLength )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ TransportOutVector_t * iterator;
+ size_t ioVectorLength = 0U;
+ size_t totalMessageLength = 0U;
+ int32_t bytesSentOrError;
+
+ /* Connect packet header can be of maximum 15 bytes. */
+ uint8_t connectPacketHeader[ 15 ];
+ uint8_t * pIndex = connectPacketHeader;
+ TransportOutVector_t pIoVector[ 11 ];
+ uint8_t serializedClientIDLength[ 2 ];
+ uint8_t serializedTopicLength[ 2 ];
+ uint8_t serializedPayloadLength[ 2 ];
+ uint8_t serializedUsernameLength[ 2 ];
+ uint8_t serializedPasswordLength[ 2 ];
+ size_t vectorsAdded;
+
+ iterator = pIoVector;
+
+ /* Validate arguments. */
+ if( ( pWillInfo != NULL ) && ( pWillInfo->pTopicName == NULL ) )
+ {
+ LogError( ( "pWillInfo->pTopicName cannot be NULL if Will is present." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ pIndex = MQTT_SerializeConnectFixedHeader( pIndex,
+ pConnectInfo,
+ pWillInfo,
+ remainingLength );
+
+ assert( ( pIndex - connectPacketHeader ) <= 15 );
+
+ /* The header gets sent first. */
+ iterator->iov_base = connectPacketHeader;
+ /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-182 */
+ /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-108 */
+ /* coverity[misra_c_2012_rule_18_2_violation] */
+ /* coverity[misra_c_2012_rule_10_8_violation] */
+ iterator->iov_len = ( size_t ) ( pIndex - connectPacketHeader );
+ totalMessageLength += iterator->iov_len;
+ iterator++;
+ ioVectorLength++;
+
+ /* Serialize the client ID. */
+ vectorsAdded = addEncodedStringToVector( serializedClientIDLength,
+ pConnectInfo->pClientIdentifier,
+ pConnectInfo->clientIdentifierLength,
+ iterator,
+ &totalMessageLength );
+
+ /* Update the iterator to point to the next empty slot. */
+ iterator = &iterator[ vectorsAdded ];
+ ioVectorLength += vectorsAdded;
+
+ if( pWillInfo != NULL )
+ {
+ /* Serialize the topic. */
+ vectorsAdded = addEncodedStringToVector( serializedTopicLength,
+ pWillInfo->pTopicName,
+ pWillInfo->topicNameLength,
+ iterator,
+ &totalMessageLength );
+
+ /* Update the iterator to point to the next empty slot. */
+ iterator = &iterator[ vectorsAdded ];
+ ioVectorLength += vectorsAdded;
+
+
+ /* Serialize the payload. Payload of last will and testament can be NULL. */
+ vectorsAdded = addEncodedStringToVector( serializedPayloadLength,
+ pWillInfo->pPayload,
+ ( uint16_t ) pWillInfo->payloadLength,
+ iterator,
+ &totalMessageLength );
+
+ /* Update the iterator to point to the next empty slot. */
+ iterator = &iterator[ vectorsAdded ];
+ ioVectorLength += vectorsAdded;
+ }
+
+ /* Encode the user name if provided. */
+ if( pConnectInfo->pUserName != NULL )
+ {
+ /* Serialize the user name string. */
+ vectorsAdded = addEncodedStringToVector( serializedUsernameLength,
+ pConnectInfo->pUserName,
+ pConnectInfo->userNameLength,
+ iterator,
+ &totalMessageLength );
+
+ /* Update the iterator to point to the next empty slot. */
+ iterator = &iterator[ vectorsAdded ];
+ ioVectorLength += vectorsAdded;
+ }
+
+ /* Encode the password if provided. */
+ if( pConnectInfo->pPassword != NULL )
+ {
+ /* Serialize the user name string. */
+ vectorsAdded = addEncodedStringToVector( serializedPasswordLength,
+ pConnectInfo->pPassword,
+ pConnectInfo->passwordLength,
+ iterator,
+ &totalMessageLength );
+ /* Update the iterator to point to the next empty slot. */
+ iterator = &iterator[ vectorsAdded ];
+ ioVectorLength += vectorsAdded;
+ }
+
+ bytesSentOrError = sendMessageVector( pContext, pIoVector, ioVectorLength );
+
+ if( bytesSentOrError != ( int32_t ) totalMessageLength )
+ {
+ status = MQTTSendFailed;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext,
+ uint32_t timeoutMs,
+ bool cleanSession,
+ MQTTPacketInfo_t * pIncomingPacket,
+ bool * pSessionPresent )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ MQTTGetCurrentTimeFunc_t getTimeStamp = NULL;
+ uint32_t entryTimeMs = 0U, remainingTimeMs = 0U, timeTakenMs = 0U;
+ bool breakFromLoop = false;
+ uint16_t loopCount = 0U;
+
+ assert( pContext != NULL );
+ assert( pIncomingPacket != NULL );
+ assert( pContext->getTime != NULL );
+
+ getTimeStamp = pContext->getTime;
+
+ /* Get the entry time for the function. */
+ entryTimeMs = getTimeStamp();
+
+ do
+ {
+ /* Transport read for incoming CONNACK packet type and length.
+ * MQTT_GetIncomingPacketTypeAndLength is a blocking call and it is
+ * returned after a transport receive timeout, an error, or a successful
+ * receive of packet type and length. */
+ status = MQTT_GetIncomingPacketTypeAndLength( pContext->transportInterface.recv,
+ pContext->transportInterface.pNetworkContext,
+ pIncomingPacket );
+
+ /* The loop times out based on 2 conditions.
+ * 1. If timeoutMs is greater than 0:
+ * Loop times out based on the timeout calculated by getTime()
+ * function.
+ * 2. If timeoutMs is 0:
+ * Loop times out based on the maximum number of retries config
+ * MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT. This config will control
+ * maximum the number of retry attempts to read the CONNACK packet.
+ * A value of 0 for the config will try once to read CONNACK. */
+ if( timeoutMs > 0U )
+ {
+ breakFromLoop = calculateElapsedTime( getTimeStamp(), entryTimeMs ) >= timeoutMs;
+ }
+ else
+ {
+ breakFromLoop = loopCount >= MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT;
+ loopCount++;
+ }
+
+ /* Loop until there is data to read or if we have exceeded the timeout/retries. */
+ } while( ( status == MQTTNoDataAvailable ) && ( breakFromLoop == false ) );
+
+ if( status == MQTTSuccess )
+ {
+ /* Time taken in this function so far. */
+ timeTakenMs = calculateElapsedTime( getTimeStamp(), entryTimeMs );
+
+ if( timeTakenMs < timeoutMs )
+ {
+ /* Calculate remaining time for receiving the remainder of
+ * the packet. */
+ remainingTimeMs = timeoutMs - timeTakenMs;
+ }
+
+ /* Reading the remainder of the packet by transport recv.
+ * Attempt to read once even if the timeout has expired.
+ * Invoking receivePacket with remainingTime as 0 would attempt to
+ * recv from network once. If using retries, the remainder of the
+ * CONNACK packet is tried to be read only once. Reading once would be
+ * good as the packet type and remaining length was already read. Hence,
+ * the probability of the remaining 2 bytes available to read is very high. */
+ if( pIncomingPacket->type == MQTT_PACKET_TYPE_CONNACK )
+ {
+ status = receivePacket( pContext,
+ *pIncomingPacket,
+ remainingTimeMs );
+ }
+ else
+ {
+ LogError( ( "Incorrect packet type %X received while expecting"
+ " CONNACK(%X).",
+ ( unsigned int ) pIncomingPacket->type,
+ MQTT_PACKET_TYPE_CONNACK ) );
+ status = MQTTBadResponse;
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Update the packet info pointer to the buffer read. */
+ pIncomingPacket->pRemainingData = pContext->networkBuffer.pBuffer;
+
+ /* Deserialize CONNACK. */
+ status = MQTT_DeserializeAck( pIncomingPacket, NULL, pSessionPresent );
+ }
+
+ /* If a clean session is requested, a session present should not be set by
+ * broker. */
+ if( status == MQTTSuccess )
+ {
+ if( ( cleanSession == true ) && ( *pSessionPresent == true ) )
+ {
+ LogError( ( "Unexpected session present flag in CONNACK response from broker."
+ " CONNECT request with clean session was made with broker." ) );
+ status = MQTTBadResponse;
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ LogDebug( ( "Received MQTT CONNACK successfully from broker." ) );
+ }
+ else
+ {
+ LogError( ( "CONNACK recv failed with status = %s.",
+ MQTT_Status_strerror( status ) ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t handleSessionResumption( MQTTContext_t * pContext,
+ bool sessionPresent )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER;
+ uint16_t packetId = MQTT_PACKET_ID_INVALID;
+ MQTTPublishState_t state = MQTTStateNull;
+
+ assert( pContext != NULL );
+
+ /* Reset the index and clear the buffer when a new session is established. */
+ pContext->index = 0;
+ ( void ) memset( pContext->networkBuffer.pBuffer, 0, pContext->networkBuffer.size );
+
+ if( sessionPresent == true )
+ {
+ /* Get the next packet ID for which a PUBREL need to be resent. */
+ packetId = MQTT_PubrelToResend( pContext, &cursor, &state );
+
+ /* Resend all the PUBREL acks after session is reestablished. */
+ while( ( packetId != MQTT_PACKET_ID_INVALID ) &&
+ ( status == MQTTSuccess ) )
+ {
+ status = sendPublishAcks( pContext, packetId, state );
+
+ packetId = MQTT_PubrelToResend( pContext, &cursor, &state );
+ }
+ }
+ else
+ {
+ /* Clear any existing records if a new session is established. */
+ if( pContext->outgoingPublishRecordMaxCount > 0U )
+ {
+ ( void ) memset( pContext->outgoingPublishRecords,
+ 0x00,
+ pContext->outgoingPublishRecordMaxCount * sizeof( *pContext->outgoingPublishRecords ) );
+ }
+
+ if( pContext->incomingPublishRecordMaxCount > 0U )
+ {
+ ( void ) memset( pContext->incomingPublishRecords,
+ 0x00,
+ pContext->incomingPublishRecordMaxCount * sizeof( *pContext->incomingPublishRecords ) );
+ }
+ }
+
+ return status;
+}
+
+static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext,
+ const MQTTPublishInfo_t * pPublishInfo,
+ uint16_t packetId )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ /* Validate arguments. */
+ if( ( pContext == NULL ) || ( pPublishInfo == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pContext=%p, "
+ "pPublishInfo=%p.",
+ ( void * ) pContext,
+ ( void * ) pPublishInfo ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) )
+ {
+ LogError( ( "Packet Id is 0 for PUBLISH with QoS=%u.",
+ ( unsigned int ) pPublishInfo->qos ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pPublishInfo->payloadLength > 0U ) && ( pPublishInfo->pPayload == NULL ) )
+ {
+ LogError( ( "A nonzero payload length requires a non-NULL payload: "
+ "payloadLength=%lu, pPayload=%p.",
+ ( unsigned long ) pPublishInfo->payloadLength,
+ pPublishInfo->pPayload ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pContext->outgoingPublishRecords == NULL ) && ( pPublishInfo->qos > MQTTQoS0 ) )
+ {
+ LogError( ( "Trying to publish a QoS > MQTTQoS0 packet when outgoing publishes "
+ "for QoS1/QoS2 have not been enabled. Please, call MQTT_InitStatefulQoS "
+ "to initialize and enable the use of QoS1/QoS2 publishes." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* MISRA else */
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_Init( MQTTContext_t * pContext,
+ const TransportInterface_t * pTransportInterface,
+ MQTTGetCurrentTimeFunc_t getTimeFunction,
+ MQTTEventCallback_t userCallback,
+ const MQTTFixedBuffer_t * pNetworkBuffer )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ /* Validate arguments. */
+ if( ( pContext == NULL ) || ( pTransportInterface == NULL ) ||
+ ( pNetworkBuffer == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pContext=%p, "
+ "pTransportInterface=%p, "
+ "pNetworkBuffer=%p",
+ ( void * ) pContext,
+ ( void * ) pTransportInterface,
+ ( void * ) pNetworkBuffer ) );
+ status = MQTTBadParameter;
+ }
+ else if( getTimeFunction == NULL )
+ {
+ LogError( ( "Invalid parameter: getTimeFunction is NULL" ) );
+ status = MQTTBadParameter;
+ }
+ else if( userCallback == NULL )
+ {
+ LogError( ( "Invalid parameter: userCallback is NULL" ) );
+ status = MQTTBadParameter;
+ }
+ else if( pTransportInterface->recv == NULL )
+ {
+ LogError( ( "Invalid parameter: pTransportInterface->recv is NULL" ) );
+ status = MQTTBadParameter;
+ }
+ else if( pTransportInterface->send == NULL )
+ {
+ LogError( ( "Invalid parameter: pTransportInterface->send is NULL" ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ ( void ) memset( pContext, 0x00, sizeof( MQTTContext_t ) );
+
+ pContext->connectStatus = MQTTNotConnected;
+ pContext->transportInterface = *pTransportInterface;
+ pContext->getTime = getTimeFunction;
+ pContext->appCallback = userCallback;
+ pContext->networkBuffer = *pNetworkBuffer;
+
+ /* Zero is not a valid packet ID per MQTT spec. Start from 1. */
+ pContext->nextPacketId = 1;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_InitStatefulQoS( MQTTContext_t * pContext,
+ MQTTPubAckInfo_t * pOutgoingPublishRecords,
+ size_t outgoingPublishCount,
+ MQTTPubAckInfo_t * pIncomingPublishRecords,
+ size_t incomingPublishCount )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( pContext == NULL )
+ {
+ LogError( ( "Argument cannot be NULL: pContext=%p\n",
+ ( void * ) pContext ) );
+ status = MQTTBadParameter;
+ }
+
+ /* Check whether the arguments make sense. Not equal here behaves
+ * like an exclusive-or operator for boolean values. */
+ else if( ( outgoingPublishCount == 0U ) !=
+ ( pOutgoingPublishRecords == NULL ) )
+ {
+ LogError( ( "Arguments do not match: pOutgoingPublishRecords=%p, "
+ "outgoingPublishCount=%lu",
+ ( void * ) pOutgoingPublishRecords,
+ outgoingPublishCount ) );
+ status = MQTTBadParameter;
+ }
+
+ /* Check whether the arguments make sense. Not equal here behaves
+ * like an exclusive-or operator for boolean values. */
+ else if( ( incomingPublishCount == 0U ) !=
+ ( pIncomingPublishRecords == NULL ) )
+ {
+ LogError( ( "Arguments do not match: pIncomingPublishRecords=%p, "
+ "incomingPublishCount=%lu",
+ ( void * ) pIncomingPublishRecords,
+ incomingPublishCount ) );
+ status = MQTTBadParameter;
+ }
+ else if( pContext->appCallback == NULL )
+ {
+ LogError( ( "MQTT_InitStatefulQoS must be called only after MQTT_Init has"
+ " been called succesfully.\n" ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ pContext->incomingPublishRecordMaxCount = incomingPublishCount;
+ pContext->incomingPublishRecords = pIncomingPublishRecords;
+ pContext->outgoingPublishRecordMaxCount = outgoingPublishCount;
+ pContext->outgoingPublishRecords = pOutgoingPublishRecords;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_CancelCallback( const MQTTContext_t * pContext,
+ uint16_t packetId )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( pContext == NULL )
+ {
+ LogWarn( ( "pContext is NULL\n" ) );
+ status = MQTTBadParameter;
+ }
+ else if( pContext->outgoingPublishRecords == NULL )
+ {
+ LogError( ( "QoS1/QoS2 is not initialized for use. Please, "
+ "call MQTT_InitStatefulQoS to enable QoS1 and QoS2 "
+ "publishes.\n" ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ MQTT_PRE_STATE_UPDATE_HOOK( pContext );
+
+ status = MQTT_RemoveStateRecord( pContext,
+ packetId );
+
+ MQTT_POST_STATE_UPDATE_HOOK( pContext );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext,
+ const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ uint32_t timeoutMs,
+ bool * pSessionPresent )
+{
+ size_t remainingLength = 0UL, packetSize = 0UL;
+ MQTTStatus_t status = MQTTSuccess;
+ MQTTPacketInfo_t incomingPacket = { 0 };
+
+ incomingPacket.type = ( uint8_t ) 0;
+
+ if( ( pContext == NULL ) || ( pConnectInfo == NULL ) || ( pSessionPresent == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pContext=%p, "
+ "pConnectInfo=%p, pSessionPresent=%p.",
+ ( void * ) pContext,
+ ( void * ) pConnectInfo,
+ ( void * ) pSessionPresent ) );
+ status = MQTTBadParameter;
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Get MQTT connect packet size and remaining length. */
+ status = MQTT_GetConnectPacketSize( pConnectInfo,
+ pWillInfo,
+ &remainingLength,
+ &packetSize );
+ LogDebug( ( "CONNECT packet size is %lu and remaining length is %lu.",
+ ( unsigned long ) packetSize,
+ ( unsigned long ) remainingLength ) );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ MQTT_PRE_SEND_HOOK( pContext );
+
+ status = sendConnectWithoutCopy( pContext,
+ pConnectInfo,
+ pWillInfo,
+ remainingLength );
+
+ MQTT_POST_SEND_HOOK( pContext );
+ }
+
+ /* Read CONNACK from transport layer. */
+ if( status == MQTTSuccess )
+ {
+ status = receiveConnack( pContext,
+ timeoutMs,
+ pConnectInfo->cleanSession,
+ &incomingPacket,
+ pSessionPresent );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Resend PUBRELs when reestablishing a session, or clear records for new sessions. */
+ status = handleSessionResumption( pContext, *pSessionPresent );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ LogInfo( ( "MQTT connection established with the broker." ) );
+ pContext->connectStatus = MQTTConnected;
+ /* Initialize keep-alive fields after a successful connection. */
+ pContext->keepAliveIntervalSec = pConnectInfo->keepAliveSeconds;
+ pContext->waitingForPingResp = false;
+ pContext->pingReqSendTimeMs = 0U;
+ }
+ else
+ {
+ LogError( ( "MQTT connection failed with status = %s.",
+ MQTT_Status_strerror( status ) ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext,
+ const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId )
+{
+ size_t remainingLength = 0UL, packetSize = 0UL;
+
+ /* Validate arguments. */
+ MQTTStatus_t status = validateSubscribeUnsubscribeParams( pContext,
+ pSubscriptionList,
+ subscriptionCount,
+ packetId );
+
+ if( status == MQTTSuccess )
+ {
+ /* Get the remaining length and packet size.*/
+ status = MQTT_GetSubscribePacketSize( pSubscriptionList,
+ subscriptionCount,
+ &remainingLength,
+ &packetSize );
+ LogDebug( ( "SUBSCRIBE packet size is %lu and remaining length is %lu.",
+ ( unsigned long ) packetSize,
+ ( unsigned long ) remainingLength ) );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ MQTT_PRE_SEND_HOOK( pContext );
+
+ /* Send MQTT SUBSCRIBE packet. */
+ status = sendSubscribeWithoutCopy( pContext,
+ pSubscriptionList,
+ subscriptionCount,
+ packetId,
+ remainingLength );
+
+ MQTT_POST_SEND_HOOK( pContext );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext,
+ const MQTTPublishInfo_t * pPublishInfo,
+ uint16_t packetId )
+{
+ size_t headerSize = 0UL;
+ size_t remainingLength = 0UL;
+ size_t packetSize = 0UL;
+ MQTTPublishState_t publishStatus = MQTTStateNull;
+ bool stateUpdateHookExecuted = false;
+
+ /* 1 header byte + 4 bytes (maximum) required for encoding the length +
+ * 2 bytes for topic string. */
+ uint8_t mqttHeader[ 7 ];
+
+ /* Validate arguments. */
+ MQTTStatus_t status = validatePublishParams( pContext, pPublishInfo, packetId );
+
+ if( status == MQTTSuccess )
+ {
+ /* Get the remaining length and packet size.*/
+ status = MQTT_GetPublishPacketSize( pPublishInfo,
+ &remainingLength,
+ &packetSize );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ status = MQTT_SerializePublishHeaderWithoutTopic( pPublishInfo,
+ remainingLength,
+ mqttHeader,
+ &headerSize );
+ }
+
+ if( ( status == MQTTSuccess ) && ( pPublishInfo->qos > MQTTQoS0 ) )
+ {
+ MQTT_PRE_STATE_UPDATE_HOOK( pContext );
+
+ /* Set the flag so that the corresponding hook can be called later. */
+ stateUpdateHookExecuted = true;
+
+ status = MQTT_ReserveState( pContext,
+ packetId,
+ pPublishInfo->qos );
+
+ /* State already exists for a duplicate packet.
+ * If a state doesn't exist, it will be handled as a new publish in
+ * state engine. */
+ if( ( status == MQTTStateCollision ) && ( pPublishInfo->dup == true ) )
+ {
+ status = MQTTSuccess;
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Take the mutex as multiple send calls are required for sending this
+ * packet. */
+ MQTT_PRE_SEND_HOOK( pContext );
+
+ status = sendPublishWithoutCopy( pContext,
+ pPublishInfo,
+ mqttHeader,
+ headerSize,
+ packetId );
+
+ /* Give the mutex away for the next taker. */
+ MQTT_POST_SEND_HOOK( pContext );
+ }
+
+ if( ( status == MQTTSuccess ) &&
+ ( pPublishInfo->qos > MQTTQoS0 ) )
+ {
+ /* Update state machine after PUBLISH is sent.
+ * Only to be done for QoS1 or QoS2. */
+ status = MQTT_UpdateStatePublish( pContext,
+ packetId,
+ MQTT_SEND,
+ pPublishInfo->qos,
+ &publishStatus );
+
+ if( status != MQTTSuccess )
+ {
+ LogError( ( "Update state for publish failed with status %s."
+ " However PUBLISH packet was sent to the broker."
+ " Any further handling of ACKs for the packet Id"
+ " will fail.",
+ MQTT_Status_strerror( status ) ) );
+ }
+ }
+
+ if( stateUpdateHookExecuted == true )
+ {
+ /* Regardless of the status, if the mutex was taken due to the
+ * packet being of QoS > QoS0, then it should be relinquished. */
+ MQTT_POST_STATE_UPDATE_HOOK( pContext );
+ }
+
+ if( status != MQTTSuccess )
+ {
+ LogError( ( "MQTT PUBLISH failed with status %s.",
+ MQTT_Status_strerror( status ) ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext )
+{
+ int32_t sendResult = 0;
+ MQTTStatus_t status = MQTTSuccess;
+ size_t packetSize = 0U;
+ /* MQTT ping packets are of fixed length. */
+ uint8_t pingreqPacket[ 2U ];
+ MQTTFixedBuffer_t localBuffer;
+
+ localBuffer.pBuffer = pingreqPacket;
+ localBuffer.size = 2U;
+
+ if( pContext == NULL )
+ {
+ LogError( ( "pContext is NULL." ) );
+ status = MQTTBadParameter;
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Get MQTT PINGREQ packet size. */
+ status = MQTT_GetPingreqPacketSize( &packetSize );
+
+ if( status == MQTTSuccess )
+ {
+ LogDebug( ( "MQTT PINGREQ packet size is %lu.",
+ ( unsigned long ) packetSize ) );
+ }
+ else
+ {
+ LogError( ( "Failed to get the PINGREQ packet size." ) );
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Serialize MQTT PINGREQ. */
+ status = MQTT_SerializePingreq( &localBuffer );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Take the mutex as the send call should not be interrupted in
+ * between. */
+ MQTT_PRE_SEND_HOOK( pContext );
+
+ /* Send the serialized PINGREQ packet to transport layer.
+ * Here, we do not use the vectored IO approach for efficiency as the
+ * Ping packet does not have numerous fields which need to be copied
+ * from the user provided buffers. Thus it can be sent directly. */
+ sendResult = sendBuffer( pContext,
+ localBuffer.pBuffer,
+ 2U );
+
+ /* Give the mutex away. */
+ MQTT_POST_SEND_HOOK( pContext );
+
+ /* It is an error to not send the entire PINGREQ packet. */
+ if( sendResult < ( int32_t ) packetSize )
+ {
+ LogError( ( "Transport send failed for PINGREQ packet." ) );
+ status = MQTTSendFailed;
+ }
+ else
+ {
+ pContext->pingReqSendTimeMs = pContext->lastPacketTxTime;
+ pContext->waitingForPingResp = true;
+ LogDebug( ( "Sent %ld bytes of PINGREQ packet.",
+ ( long int ) sendResult ) );
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext,
+ const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId )
+{
+ size_t remainingLength = 0UL, packetSize = 0UL;
+
+ /* Validate arguments. */
+ MQTTStatus_t status = validateSubscribeUnsubscribeParams( pContext,
+ pSubscriptionList,
+ subscriptionCount,
+ packetId );
+
+ if( status == MQTTSuccess )
+ {
+ /* Get the remaining length and packet size.*/
+ status = MQTT_GetUnsubscribePacketSize( pSubscriptionList,
+ subscriptionCount,
+ &remainingLength,
+ &packetSize );
+ LogDebug( ( "UNSUBSCRIBE packet size is %lu and remaining length is %lu.",
+ ( unsigned long ) packetSize,
+ ( unsigned long ) remainingLength ) );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Take the mutex because the below call should not be interrupted. */
+ MQTT_PRE_SEND_HOOK( pContext );
+
+ status = sendUnsubscribeWithoutCopy( pContext,
+ pSubscriptionList,
+ subscriptionCount,
+ packetId,
+ remainingLength );
+
+ /* Give the mutex away. */
+ MQTT_POST_SEND_HOOK( pContext );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext )
+{
+ size_t packetSize = 0U;
+ int32_t sendResult = 0;
+ MQTTStatus_t status = MQTTSuccess;
+ MQTTFixedBuffer_t localBuffer;
+ uint8_t disconnectPacket[ 2U ];
+
+ localBuffer.pBuffer = disconnectPacket;
+ localBuffer.size = 2U;
+
+ /* Validate arguments. */
+ if( pContext == NULL )
+ {
+ LogError( ( "pContext cannot be NULL." ) );
+ status = MQTTBadParameter;
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Get MQTT DISCONNECT packet size. */
+ status = MQTT_GetDisconnectPacketSize( &packetSize );
+ LogDebug( ( "MQTT DISCONNECT packet size is %lu.",
+ ( unsigned long ) packetSize ) );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Serialize MQTT DISCONNECT packet. */
+ status = MQTT_SerializeDisconnect( &localBuffer );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Take the mutex because the below call should not be interrupted. */
+ MQTT_PRE_SEND_HOOK( pContext );
+
+ /* Here we do not use vectors as the disconnect packet has fixed fields
+ * which do not reside in user provided buffers. Thus, it can be sent
+ * using a simple send call. */
+ sendResult = sendBuffer( pContext,
+ localBuffer.pBuffer,
+ packetSize );
+
+ /* Give the mutex away. */
+ MQTT_POST_SEND_HOOK( pContext );
+
+ if( sendResult < ( int32_t ) packetSize )
+ {
+ LogError( ( "Transport send failed for DISCONNECT packet." ) );
+ status = MQTTSendFailed;
+ }
+ else
+ {
+ LogDebug( ( "Sent %ld bytes of DISCONNECT packet.",
+ ( long int ) sendResult ) );
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ LogInfo( ( "Disconnected from the broker." ) );
+ pContext->connectStatus = MQTTNotConnected;
+
+ /* Reset the index and clean the buffer on a successful disconnect. */
+ pContext->index = 0;
+ ( void ) memset( pContext->networkBuffer.pBuffer, 0, pContext->networkBuffer.size );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext )
+{
+ MQTTStatus_t status = MQTTBadParameter;
+
+ if( pContext == NULL )
+ {
+ LogError( ( "Invalid input parameter: MQTT Context cannot be NULL." ) );
+ }
+ else if( pContext->getTime == NULL )
+ {
+ LogError( ( "Invalid input parameter: MQTT Context must have valid getTime." ) );
+ }
+ else if( pContext->networkBuffer.pBuffer == NULL )
+ {
+ LogError( ( "Invalid input parameter: The MQTT context's networkBuffer must not be NULL." ) );
+ }
+ else
+ {
+ pContext->controlPacketSent = false;
+ status = receiveSingleIteration( pContext, true );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext )
+{
+ MQTTStatus_t status = MQTTBadParameter;
+
+ if( pContext == NULL )
+ {
+ LogError( ( "Invalid input parameter: MQTT Context cannot be NULL." ) );
+ }
+ else if( pContext->getTime == NULL )
+ {
+ LogError( ( "Invalid input parameter: MQTT Context must have a valid getTime function." ) );
+ }
+ else if( pContext->networkBuffer.pBuffer == NULL )
+ {
+ LogError( ( "Invalid input parameter: MQTT context's networkBuffer must not be NULL." ) );
+ }
+ else
+ {
+ status = receiveSingleIteration( pContext, false );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+uint16_t MQTT_GetPacketId( MQTTContext_t * pContext )
+{
+ uint16_t packetId = 0U;
+
+ if( pContext != NULL )
+ {
+ MQTT_PRE_STATE_UPDATE_HOOK( pContext );
+
+ packetId = pContext->nextPacketId;
+
+ /* A packet ID of zero is not a valid packet ID. When the max ID
+ * is reached the next one should start at 1. */
+ if( pContext->nextPacketId == ( uint16_t ) UINT16_MAX )
+ {
+ pContext->nextPacketId = 1;
+ }
+ else
+ {
+ pContext->nextPacketId++;
+ }
+
+ MQTT_POST_STATE_UPDATE_HOOK( pContext );
+ }
+
+ return packetId;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_MatchTopic( const char * pTopicName,
+ const uint16_t topicNameLength,
+ const char * pTopicFilter,
+ const uint16_t topicFilterLength,
+ bool * pIsMatch )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ bool topicFilterStartsWithWildcard = false;
+ bool matchStatus = false;
+
+ if( ( pTopicName == NULL ) || ( topicNameLength == 0u ) )
+ {
+ LogError( ( "Invalid paramater: Topic name should be non-NULL and its "
+ "length should be > 0: TopicName=%p, TopicNameLength=%hu",
+ ( void * ) pTopicName,
+ ( unsigned short ) topicNameLength ) );
+
+ status = MQTTBadParameter;
+ }
+ else if( ( pTopicFilter == NULL ) || ( topicFilterLength == 0u ) )
+ {
+ LogError( ( "Invalid paramater: Topic filter should be non-NULL and "
+ "its length should be > 0: TopicName=%p, TopicFilterLength=%hu",
+ ( void * ) pTopicFilter,
+ ( unsigned short ) topicFilterLength ) );
+ status = MQTTBadParameter;
+ }
+ else if( pIsMatch == NULL )
+ {
+ LogError( ( "Invalid paramater: Output parameter, pIsMatch, is NULL" ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Check for an exact match if the incoming topic name and the registered
+ * topic filter length match. */
+ if( topicNameLength == topicFilterLength )
+ {
+ matchStatus = strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0;
+ }
+
+ if( matchStatus == false )
+ {
+ /* If an exact match was not found, match against wildcard characters in
+ * topic filter.*/
+
+ /* Determine if topic filter starts with a wildcard. */
+ topicFilterStartsWithWildcard = ( pTopicFilter[ 0 ] == '+' ) ||
+ ( pTopicFilter[ 0 ] == '#' );
+
+ /* Note: According to the MQTT 3.1.1 specification, incoming PUBLISH topic names
+ * starting with "$" character cannot be matched against topic filter starting with
+ * a wildcard, i.e. for example, "$SYS/sport" cannot be matched with "#" or
+ * "+/sport" topic filters. */
+ if( !( ( pTopicName[ 0 ] == '$' ) && ( topicFilterStartsWithWildcard == true ) ) )
+ {
+ matchStatus = matchTopicFilter( pTopicName, topicNameLength, pTopicFilter, topicFilterLength );
+ }
+ }
+
+ /* Update the output parameter with the match result. */
+ *pIsMatch = matchStatus;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket,
+ uint8_t ** pPayloadStart,
+ size_t * pPayloadSize )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( pSubackPacket == NULL )
+ {
+ LogError( ( "Invalid parameter: pSubackPacket is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( pPayloadStart == NULL )
+ {
+ LogError( ( "Invalid parameter: pPayloadStart is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( pPayloadSize == NULL )
+ {
+ LogError( ( "Invalid parameter: pPayloadSize is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( pSubackPacket->type != MQTT_PACKET_TYPE_SUBACK )
+ {
+ LogError( ( "Invalid parameter: Input packet is not a SUBACK packet: "
+ "ExpectedType=%02x, InputType=%02x",
+ ( int ) MQTT_PACKET_TYPE_SUBACK,
+ ( int ) pSubackPacket->type ) );
+ status = MQTTBadParameter;
+ }
+ else if( pSubackPacket->pRemainingData == NULL )
+ {
+ LogError( ( "Invalid parameter: pSubackPacket->pRemainingData is NULL" ) );
+ status = MQTTBadParameter;
+ }
+
+ /* A SUBACK must have a remaining length of at least 3 to accommodate the
+ * packet identifier and at least 1 return code. */
+ else if( pSubackPacket->remainingLength < 3U )
+ {
+ LogError( ( "Invalid parameter: Packet remaining length is invalid: "
+ "Should be greater than 2 for SUBACK packet: InputRemainingLength=%lu",
+ ( unsigned long ) pSubackPacket->remainingLength ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* According to the MQTT 3.1.1 protocol specification, the "Remaining Length" field is a
+ * length of the variable header (2 bytes) plus the length of the payload.
+ * Therefore, we add 2 positions for the starting address of the payload, and
+ * subtract 2 bytes from the remaining length for the length of the payload.*/
+ *pPayloadStart = &pSubackPacket->pRemainingData[ sizeof( uint16_t ) ];
+ *pPayloadSize = pSubackPacket->remainingLength - sizeof( uint16_t );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+const char * MQTT_Status_strerror( MQTTStatus_t status )
+{
+ const char * str = NULL;
+
+ switch( status )
+ {
+ case MQTTSuccess:
+ str = "MQTTSuccess";
+ break;
+
+ case MQTTBadParameter:
+ str = "MQTTBadParameter";
+ break;
+
+ case MQTTNoMemory:
+ str = "MQTTNoMemory";
+ break;
+
+ case MQTTSendFailed:
+ str = "MQTTSendFailed";
+ break;
+
+ case MQTTRecvFailed:
+ str = "MQTTRecvFailed";
+ break;
+
+ case MQTTBadResponse:
+ str = "MQTTBadResponse";
+ break;
+
+ case MQTTServerRefused:
+ str = "MQTTServerRefused";
+ break;
+
+ case MQTTNoDataAvailable:
+ str = "MQTTNoDataAvailable";
+ break;
+
+ case MQTTIllegalState:
+ str = "MQTTIllegalState";
+ break;
+
+ case MQTTStateCollision:
+ str = "MQTTStateCollision";
+ break;
+
+ case MQTTKeepAliveTimeout:
+ str = "MQTTKeepAliveTimeout";
+ break;
+
+ default:
+ str = "Invalid MQTT Status code";
+ break;
+ }
+
+ return str;
+}
+
+/*-----------------------------------------------------------*/
diff --git a/project/coreMQTT/coreMQTT/core_mqtt.h b/project/coreMQTT/coreMQTT/core_mqtt.h
new file mode 100644
index 0000000..a9f14c5
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/core_mqtt.h
@@ -0,0 +1,1015 @@
+/*
+ * coreMQTT v2.1.1
+ * Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_mqtt.h
+ * @brief User-facing functions of the MQTT 3.1.1 library.
+ */
+#ifndef CORE_MQTT_H
+#define CORE_MQTT_H
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+/* *INDENT-ON* */
+
+/* Include MQTT serializer library. */
+#include "core_mqtt_serializer.h"
+
+/* Include transport interface. */
+#include "transport_interface.h"
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * The current version of this library.
+ */
+#define MQTT_LIBRARY_VERSION "v2.1.1"
+/** @endcond */
+
+/**
+ * @ingroup mqtt_constants
+ * @brief Invalid packet identifier.
+ *
+ * Zero is an invalid packet identifier as per MQTT v3.1.1 spec.
+ */
+#define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U )
+
+/* Structures defined in this file. */
+struct MQTTPubAckInfo;
+struct MQTTContext;
+struct MQTTDeserializedInfo;
+
+/**
+ * @ingroup mqtt_callback_types
+ * @brief Application provided function to query the time elapsed since a given
+ * epoch in milliseconds.
+ *
+ * @note The timer should be a monotonic timer. It just needs to provide an
+ * incrementing count of milliseconds elapsed since a given epoch.
+ *
+ * @return The time elapsed in milliseconds.
+ */
+typedef uint32_t (* MQTTGetCurrentTimeFunc_t )( void );
+
+/**
+ * @ingroup mqtt_callback_types
+ * @brief Application callback for receiving incoming publishes and incoming
+ * acks.
+ *
+ * @note This callback will be called only if packets are deserialized with a
+ * result of #MQTTSuccess or #MQTTServerRefused. The latter can be obtained
+ * when deserializing a SUBACK, indicating a broker's rejection of a subscribe.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] pPacketInfo Information on the type of incoming MQTT packet.
+ * @param[in] pDeserializedInfo Deserialized information from incoming packet.
+ */
+typedef void (* MQTTEventCallback_t )( struct MQTTContext * pContext,
+ struct MQTTPacketInfo * pPacketInfo,
+ struct MQTTDeserializedInfo * pDeserializedInfo );
+
+/**
+ * @ingroup mqtt_enum_types
+ * @brief Values indicating if an MQTT connection exists.
+ */
+typedef enum MQTTConnectionStatus
+{
+ MQTTNotConnected, /**< @brief MQTT Connection is inactive. */
+ MQTTConnected /**< @brief MQTT Connection is active. */
+} MQTTConnectionStatus_t;
+
+/**
+ * @ingroup mqtt_enum_types
+ * @brief The state of QoS 1 or QoS 2 MQTT publishes, used in the state engine.
+ */
+typedef enum MQTTPublishState
+{
+ MQTTStateNull = 0, /**< @brief An empty state with no corresponding PUBLISH. */
+ MQTTPublishSend, /**< @brief The library will send an outgoing PUBLISH packet. */
+ MQTTPubAckSend, /**< @brief The library will send a PUBACK for a received PUBLISH. */
+ MQTTPubRecSend, /**< @brief The library will send a PUBREC for a received PUBLISH. */
+ MQTTPubRelSend, /**< @brief The library will send a PUBREL for a received PUBREC. */
+ MQTTPubCompSend, /**< @brief The library will send a PUBCOMP for a received PUBREL. */
+ MQTTPubAckPending, /**< @brief The library is awaiting a PUBACK for an outgoing PUBLISH. */
+ MQTTPubRecPending, /**< @brief The library is awaiting a PUBREC for an outgoing PUBLISH. */
+ MQTTPubRelPending, /**< @brief The library is awaiting a PUBREL for an incoming PUBLISH. */
+ MQTTPubCompPending, /**< @brief The library is awaiting a PUBCOMP for an outgoing PUBLISH. */
+ MQTTPublishDone /**< @brief The PUBLISH has been completed. */
+} MQTTPublishState_t;
+
+/**
+ * @ingroup mqtt_enum_types
+ * @brief Packet types used in acknowledging QoS 1 or QoS 2 publishes.
+ */
+typedef enum MQTTPubAckType
+{
+ MQTTPuback, /**< @brief PUBACKs are sent in response to a QoS 1 PUBLISH. */
+ MQTTPubrec, /**< @brief PUBRECs are sent in response to a QoS 2 PUBLISH. */
+ MQTTPubrel, /**< @brief PUBRELs are sent in response to a PUBREC. */
+ MQTTPubcomp /**< @brief PUBCOMPs are sent in response to a PUBREL. */
+} MQTTPubAckType_t;
+
+/**
+ * @ingroup mqtt_enum_types
+ * @brief The status codes in the SUBACK response to a subscription request.
+ */
+typedef enum MQTTSubAckStatus
+{
+ MQTTSubAckSuccessQos0 = 0x00, /**< @brief Success with a maximum delivery at QoS 0. */
+ MQTTSubAckSuccessQos1 = 0x01, /**< @brief Success with a maximum delivery at QoS 1. */
+ MQTTSubAckSuccessQos2 = 0x02, /**< @brief Success with a maximum delivery at QoS 2. */
+ MQTTSubAckFailure = 0x80 /**< @brief Failure. */
+} MQTTSubAckStatus_t;
+
+/**
+ * @ingroup mqtt_struct_types
+ * @brief An element of the state engine records for QoS 1 or Qos 2 publishes.
+ */
+typedef struct MQTTPubAckInfo
+{
+ uint16_t packetId; /**< @brief The packet ID of the original PUBLISH. */
+ MQTTQoS_t qos; /**< @brief The QoS of the original PUBLISH. */
+ MQTTPublishState_t publishState; /**< @brief The current state of the publish process. */
+} MQTTPubAckInfo_t;
+
+/**
+ * @ingroup mqtt_struct_types
+ * @brief A struct representing an MQTT connection.
+ */
+typedef struct MQTTContext
+{
+ /**
+ * @brief State engine records for outgoing publishes.
+ */
+ MQTTPubAckInfo_t * outgoingPublishRecords;
+
+ /**
+ * @brief State engine records for incoming publishes.
+ */
+ MQTTPubAckInfo_t * incomingPublishRecords;
+
+ /**
+ * @brief The maximum number of outgoing publish records.
+ */
+ size_t outgoingPublishRecordMaxCount;
+
+ /**
+ * @brief The maximum number of incoming publish records.
+ */
+ size_t incomingPublishRecordMaxCount;
+
+ /**
+ * @brief The transport interface used by the MQTT connection.
+ */
+ TransportInterface_t transportInterface;
+
+ /**
+ * @brief The buffer used in sending and receiving packets from the network.
+ */
+ MQTTFixedBuffer_t networkBuffer;
+
+ /**
+ * @brief The next available ID for outgoing MQTT packets.
+ */
+ uint16_t nextPacketId;
+
+ /**
+ * @brief Whether the context currently has a connection to the broker.
+ */
+ MQTTConnectionStatus_t connectStatus;
+
+ /**
+ * @brief Function used to get millisecond timestamps.
+ */
+ MQTTGetCurrentTimeFunc_t getTime;
+
+ /**
+ * @brief Callback function used to give deserialized MQTT packets to the application.
+ */
+ MQTTEventCallback_t appCallback;
+
+ /**
+ * @brief Timestamp of the last packet sent by the library.
+ */
+ uint32_t lastPacketTxTime;
+
+ /**
+ * @brief Timestamp of the last packet received by the library.
+ */
+ uint32_t lastPacketRxTime;
+
+ /**
+ * @brief Whether the library sent a packet during a call of #MQTT_ProcessLoop or
+ * #MQTT_ReceiveLoop.
+ */
+ bool controlPacketSent;
+
+ /**
+ * @brief Index to keep track of the number of bytes received in network buffer.
+ */
+ size_t index;
+
+ /* Keep alive members. */
+ uint16_t keepAliveIntervalSec; /**< @brief Keep Alive interval. */
+ uint32_t pingReqSendTimeMs; /**< @brief Timestamp of the last sent PINGREQ. */
+ bool waitingForPingResp; /**< @brief If the library is currently awaiting a PINGRESP. */
+} MQTTContext_t;
+
+/**
+ * @ingroup mqtt_struct_types
+ * @brief Struct to hold deserialized packet information for an #MQTTEventCallback_t
+ * callback.
+ */
+typedef struct MQTTDeserializedInfo
+{
+ uint16_t packetIdentifier; /**< @brief Packet ID of deserialized packet. */
+ MQTTPublishInfo_t * pPublishInfo; /**< @brief Pointer to deserialized publish info. */
+ MQTTStatus_t deserializationResult; /**< @brief Return code of deserialization. */
+} MQTTDeserializedInfo_t;
+
+/**
+ * @brief Initialize an MQTT context.
+ *
+ * This function must be called on an #MQTTContext_t before any other function.
+ *
+ * @note The #MQTTGetCurrentTimeFunc_t function for querying time must be defined. If
+ * there is no time implementation, it is the responsibility of the application
+ * to provide a dummy function to always return 0, provide 0 timeouts for
+ * all calls to #MQTT_Connect, #MQTT_ProcessLoop, and #MQTT_ReceiveLoop and configure
+ * the #MQTT_RECV_POLLING_TIMEOUT_MS and #MQTT_SEND_TIMEOUT_MS configurations
+ * to be 0. This will result in loop functions running for a single iteration, and
+ * #MQTT_Connect relying on #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT to receive the CONNACK packet.
+ *
+ * @param[in] pContext The context to initialize.
+ * @param[in] pTransportInterface The transport interface to use with the context.
+ * @param[in] getTimeFunction The time utility function which can return the amount of time
+ * (in milliseconds) elapsed since a given epoch. This function will be used to ensure that
+ * timeouts in the API calls are met and keep-alive messages are sent on time.
+ * @param[in] userCallback The user callback to use with the context to notify about incoming
+ * packet events.
+ * @param[in] pNetworkBuffer Network buffer provided for the context. This buffer will be used
+ * to receive incoming messages from the broker. This buffer must remain valid and in scope
+ * for the entire lifetime of the @p pContext and must not be used by another context and/or
+ * application.
+ *
+ * @return #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Function for obtaining a timestamp.
+ * uint32_t getTimeStampMs();
+ * // Callback function for receiving packets.
+ * void eventCallback(
+ * MQTTContext_t * pContext,
+ * MQTTPacketInfo_t * pPacketInfo,
+ * MQTTDeserializedInfo_t * pDeserializedInfo
+ * );
+ * // Network send.
+ * int32_t networkSend( NetworkContext_t * pContext, const void * pBuffer, size_t bytes );
+ * // Network receive.
+ * int32_t networkRecv( NetworkContext_t * pContext, void * pBuffer, size_t bytes );
+ *
+ * MQTTContext_t mqttContext;
+ * TransportInterface_t transport;
+ * MQTTFixedBuffer_t fixedBuffer;
+ * // Create a globally accessible buffer which remains in scope for the entire duration
+ * // of the MQTT context.
+ * uint8_t buffer[ 1024 ];
+ *
+ * // Clear context.
+ * memset( ( void * ) &mqttContext, 0x00, sizeof( MQTTContext_t ) );
+ *
+ * // Set transport interface members.
+ * transport.pNetworkContext = &someTransportContext;
+ * transport.send = networkSend;
+ * transport.recv = networkRecv;
+ *
+ * // Set buffer members.
+ * fixedBuffer.pBuffer = buffer;
+ * fixedBuffer.size = 1024;
+ *
+ * status = MQTT_Init( &mqttContext, &transport, getTimeStampMs, eventCallback, &fixedBuffer );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // Do something with mqttContext. The transport and fixedBuffer structs were
+ * // copied into the context, so the original structs do not need to stay in scope.
+ * // However, the memory pointed to by the fixedBuffer.pBuffer must remain in scope.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_init] */
+MQTTStatus_t MQTT_Init( MQTTContext_t * pContext,
+ const TransportInterface_t * pTransportInterface,
+ MQTTGetCurrentTimeFunc_t getTimeFunction,
+ MQTTEventCallback_t userCallback,
+ const MQTTFixedBuffer_t * pNetworkBuffer );
+/* @[declare_mqtt_init] */
+
+/**
+ * @brief Initialize an MQTT context for QoS > 0.
+ *
+ * This function must be called on an #MQTTContext_t after MQTT_Init and before any other function.
+ *
+ * @param[in] pContext The context to initialize.
+ * @param[in] pOutgoingPublishRecords Pointer to memory which will be used to store state of outgoing
+ * publishes.
+ * @param[in] outgoingPublishCount Maximum number of records which can be kept in the memory
+ * pointed to by @p pOutgoingPublishRecords.
+ * @param[in] pIncomingPublishRecords Pointer to memory which will be used to store state of incoming
+ * publishes.
+ * @param[in] incomingPublishCount Maximum number of records which can be kept in the memory
+ * pointed to by @p pIncomingPublishRecords.
+ *
+ * @return #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Function for obtaining a timestamp.
+ * uint32_t getTimeStampMs();
+ * // Callback function for receiving packets.
+ * void eventCallback(
+ * MQTTContext_t * pContext,
+ * MQTTPacketInfo_t * pPacketInfo,
+ * MQTTDeserializedInfo_t * pDeserializedInfo
+ * );
+ * // Network send.
+ * int32_t networkSend( NetworkContext_t * pContext, const void * pBuffer, size_t bytes );
+ * // Network receive.
+ * int32_t networkRecv( NetworkContext_t * pContext, void * pBuffer, size_t bytes );
+ *
+ * MQTTContext_t mqttContext;
+ * TransportInterface_t transport;
+ * MQTTFixedBuffer_t fixedBuffer;
+ * uint8_t buffer[ 1024 ];
+ * const size_t outgoingPublishCount = 30;
+ * MQTTPubAckInfo_t outgoingPublishes[ outgoingPublishCount ];
+ *
+ * // Clear context.
+ * memset( ( void * ) &mqttContext, 0x00, sizeof( MQTTContext_t ) );
+ *
+ * // Set transport interface members.
+ * transport.pNetworkContext = &someTransportContext;
+ * transport.send = networkSend;
+ * transport.recv = networkRecv;
+ *
+ * // Set buffer members.
+ * fixedBuffer.pBuffer = buffer;
+ * fixedBuffer.size = 1024;
+ *
+ * status = MQTT_Init( &mqttContext, &transport, getTimeStampMs, eventCallback, &fixedBuffer );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // We do not expect any incoming publishes in this example, therefore the incoming
+ * // publish pointer is NULL and the count is zero.
+ * status = MQTT_InitStatefulQoS( &mqttContext, outgoingPublishes, outgoingPublishCount, NULL, 0 );
+ *
+ * // Now QoS1 and/or QoS2 publishes can be sent with this context.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_initstatefulqos] */
+MQTTStatus_t MQTT_InitStatefulQoS( MQTTContext_t * pContext,
+ MQTTPubAckInfo_t * pOutgoingPublishRecords,
+ size_t outgoingPublishCount,
+ MQTTPubAckInfo_t * pIncomingPublishRecords,
+ size_t incomingPublishCount );
+/* @[declare_mqtt_initstatefulqos] */
+
+/**
+ * @brief Establish an MQTT session.
+ *
+ * This function will send MQTT CONNECT packet and receive a CONNACK packet. The
+ * send and receive from the network is done through the transport interface.
+ *
+ * The maximum time this function waits for a CONNACK is decided in one of the
+ * following ways:
+ * 1. If @p timeoutMs is greater than 0:
+ * #MQTTContext_t.getTime is used to ensure that the function does not wait
+ * more than @p timeoutMs for CONNACK.
+ * 2. If @p timeoutMs is 0:
+ * The network receive for CONNACK is retried up to the number of times
+ * configured by #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT.
+ *
+ * @note If a dummy #MQTTGetCurrentTimeFunc_t was passed to #MQTT_Init, then a
+ * timeout value of 0 MUST be passed to the API, and the #MQTT_RECV_POLLING_TIMEOUT_MS
+ * and #MQTT_SEND_TIMEOUT_MS timeout configurations MUST be set to 0.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] pConnectInfo MQTT CONNECT packet information.
+ * @param[in] pWillInfo Last Will and Testament. Pass NULL if Last Will and
+ * Testament is not used.
+ * @param[in] timeoutMs Maximum time in milliseconds to wait for a CONNACK packet.
+ * A zero timeout makes use of the retries for receiving CONNACK as configured with
+ * #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT.
+ * @param[out] pSessionPresent This value will be set to true if a previous session
+ * was present; otherwise it will be set to false. It is only relevant if not
+ * establishing a clean session.
+ *
+ * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to
+ * hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSendFailed if transport send failed;
+ * #MQTTRecvFailed if transport receive failed for CONNACK;
+ * #MQTTNoDataAvailable if no data available to receive in transport until
+ * the @p timeoutMs for CONNACK;
+ * #MQTTSuccess otherwise.
+ *
+ * @note This API may spend more time than provided in the timeoutMS parameters in
+ * certain conditions as listed below:
+ *
+ * 1. Timeouts are incorrectly configured - If the timeoutMS is less than the
+ * transport receive timeout and if a CONNACK packet is not received within
+ * the transport receive timeout, the API will spend the transport receive
+ * timeout (which is more time than the timeoutMs). It is the case of incorrect
+ * timeout configuration as the timeoutMs parameter passed to this API must be
+ * greater than the transport receive timeout. Please refer to the transport
+ * interface documentation for more details about timeout configurations.
+ *
+ * 2. Partial CONNACK packet is received right before the expiry of the timeout - It
+ * is possible that first two bytes of CONNACK packet (packet type and remaining
+ * length) are received right before the expiry of the timeoutMS. In that case,
+ * the API makes one more network receive call in an attempt to receive the remaining
+ * 2 bytes. In the worst case, it can happen that the remaining 2 bytes are never
+ * received and this API will end up spending timeoutMs + transport receive timeout.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTConnectInfo_t connectInfo = { 0 };
+ * MQTTPublishInfo_t willInfo = { 0 };
+ * bool sessionPresent;
+ * // This is assumed to have been initialized before calling this function.
+ * MQTTContext_t * pContext;
+ *
+ * // True for creating a new session with broker, false if we want to resume an old one.
+ * connectInfo.cleanSession = true;
+ * // Client ID must be unique to broker. This field is required.
+ * connectInfo.pClientIdentifier = "someClientID";
+ * connectInfo.clientIdentifierLength = strlen( connectInfo.pClientIdentifier );
+ *
+ * // The following fields are optional.
+ * // Value for keep alive.
+ * connectInfo.keepAliveSeconds = 60;
+ * // Optional username and password.
+ * connectInfo.pUserName = "someUserName";
+ * connectInfo.userNameLength = strlen( connectInfo.pUserName );
+ * connectInfo.pPassword = "somePassword";
+ * connectInfo.passwordLength = strlen( connectInfo.pPassword );
+ *
+ * // The last will and testament is optional, it will be published by the broker
+ * // should this client disconnect without sending a DISCONNECT packet.
+ * willInfo.qos = MQTTQoS0;
+ * willInfo.pTopicName = "/lwt/topic/name";
+ * willInfo.topicNameLength = strlen( willInfo.pTopicName );
+ * willInfo.pPayload = "LWT Message";
+ * willInfo.payloadLength = strlen( "LWT Message" );
+ *
+ * // Send the connect packet. Use 100 ms as the timeout to wait for the CONNACK packet.
+ * status = MQTT_Connect( pContext, &connectInfo, &willInfo, 100, &sessionPresent );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // Since we requested a clean session, this must be false
+ * assert( sessionPresent == false );
+ *
+ * // Do something with the connection.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_connect] */
+MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext,
+ const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ uint32_t timeoutMs,
+ bool * pSessionPresent );
+/* @[declare_mqtt_connect] */
+
+/**
+ * @brief Sends MQTT SUBSCRIBE for the given list of topic filters to
+ * the broker.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] pSubscriptionList Array of MQTT subscription info.
+ * @param[in] subscriptionCount The number of elements in @ pSubscriptionList
+ * array.
+ * @param[in] packetId Packet ID generated by #MQTT_GetPacketId.
+ *
+ * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to
+ * hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSendFailed if transport write failed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
+ * uint16_t packetId;
+ * // This context is assumed to be initialized and connected.
+ * MQTTContext_t * pContext;
+ * // This is assumed to be a list of filters we want to subscribe to.
+ * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ];
+ *
+ * // Set each subscription.
+ * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ )
+ * {
+ * subscriptionList[ i ].qos = MQTTQoS0;
+ * // Each subscription needs a topic filter.
+ * subscriptionList[ i ].pTopicFilter = filters[ i ];
+ * subscriptionList[ i ].topicFilterLength = strlen( filters[ i ] );
+ * }
+ *
+ * // Obtain a new packet id for the subscription.
+ * packetId = MQTT_GetPacketId( pContext );
+ *
+ * status = MQTT_Subscribe( pContext, &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, packetId );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // We must now call MQTT_ReceiveLoop() or MQTT_ProcessLoop() to receive the SUBACK.
+ * // If the broker accepts the subscription we can now receive publishes
+ * // on the requested topics.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_subscribe] */
+MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext,
+ const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId );
+/* @[declare_mqtt_subscribe] */
+
+/**
+ * @brief Publishes a message to the given topic name.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
+ * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
+ *
+ * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSendFailed if transport write failed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTPublishInfo_t publishInfo;
+ * uint16_t packetId;
+ * // This context is assumed to be initialized and connected.
+ * MQTTContext_t * pContext;
+ *
+ * // QoS of publish.
+ * publishInfo.qos = MQTTQoS1;
+ * publishInfo.pTopicName = "/some/topic/name";
+ * publishInfo.topicNameLength = strlen( publishInfo.pTopicName );
+ * publishInfo.pPayload = "Hello World!";
+ * publishInfo.payloadLength = strlen( "Hello World!" );
+ *
+ * // Packet ID is needed for QoS > 0.
+ * packetId = MQTT_GetPacketId( pContext );
+ *
+ * status = MQTT_Publish( pContext, &publishInfo, packetId );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // Since the QoS is > 0, we will need to call MQTT_ReceiveLoop()
+ * // or MQTT_ProcessLoop() to process the publish acknowledgments.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_publish] */
+MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext,
+ const MQTTPublishInfo_t * pPublishInfo,
+ uint16_t packetId );
+/* @[declare_mqtt_publish] */
+
+/**
+ * @brief Cancels an outgoing publish callback (only for QoS > QoS0) by
+ * removing it from the pending ACK list.
+ *
+ * @note This cannot cancel the actual publish as that might have already
+ * been sent to the broker. This only removes the details of the given packet
+ * ID from the list of unACKed packet. That allows the caller to free any memory
+ * associated with the publish payload, topic string etc. Also, after this API
+ * call, the user provided callback will not be invoked when the ACK packet is
+ * received.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] packetId packet ID corresponding to the outstanding publish.
+ *
+ * @return #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ */
+/* @[declare_mqtt_cancelcallback] */
+MQTTStatus_t MQTT_CancelCallback( const MQTTContext_t * pContext,
+ uint16_t packetId );
+/* @[declare_mqtt_cancelcallback] */
+
+/**
+ * @brief Sends an MQTT PINGREQ to broker.
+ *
+ * @param[in] pContext Initialized and connected MQTT context.
+ *
+ * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSendFailed if transport write failed;
+ * #MQTTSuccess otherwise.
+ */
+/* @[declare_mqtt_ping] */
+MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext );
+/* @[declare_mqtt_ping] */
+
+/**
+ * @brief Sends MQTT UNSUBSCRIBE for the given list of topic filters to
+ * the broker.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ * @param[in] pSubscriptionList List of MQTT subscription info.
+ * @param[in] subscriptionCount The number of elements in pSubscriptionList.
+ * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
+ *
+ * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to
+ * hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSendFailed if transport write failed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTSubscribeInfo_t unsubscribeList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
+ * uint16_t packetId;
+ * // This context is assumed to be initialized and connected.
+ * MQTTContext_t * pContext;
+ * // This is assumed to be a list of filters we want to unsubscribe from.
+ * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ];
+ *
+ * // Set information for each unsubscribe request.
+ * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ )
+ * {
+ * unsubscribeList[ i ].pTopicFilter = filters[ i ];
+ * unsubscribeList[ i ].topicFilterLength = strlen( filters[ i ] );
+ *
+ * // The QoS field of MQTT_SubscribeInfo_t is unused for unsubscribing.
+ * }
+ *
+ * // Obtain a new packet id for the unsubscribe request.
+ * packetId = MQTT_GetPacketId( pContext );
+ *
+ * status = MQTT_Unsubscribe( pContext, &unsubscribeList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, packetId );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // We must now call MQTT_ReceiveLoop() or MQTT_ProcessLoop() to receive the UNSUBACK.
+ * // After this the broker should no longer send publishes for these topics.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_unsubscribe] */
+MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext,
+ const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId );
+/* @[declare_mqtt_unsubscribe] */
+
+/**
+ * @brief Disconnect an MQTT session.
+ *
+ * @param[in] pContext Initialized and connected MQTT context.
+ *
+ * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to
+ * hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSendFailed if transport send failed;
+ * #MQTTSuccess otherwise.
+ */
+/* @[declare_mqtt_disconnect] */
+MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext );
+/* @[declare_mqtt_disconnect] */
+
+/**
+ * @brief Loop to receive packets from the transport interface. Handles keep
+ * alive.
+ *
+ * @note If a dummy timer function, #MQTTGetCurrentTimeFunc_t, is passed to the library,
+ * then the keep-alive mechanism is not supported by the #MQTT_ProcessLoop API.
+ * In that case, the #MQTT_ReceiveLoop API function should be used instead.
+ *
+ * @param[in] pContext Initialized and connected MQTT context.
+ *
+ * @note Calling this function blocks the calling context for a time period that
+ * depends on the passed the configuration macros, #MQTT_RECV_POLLING_TIMEOUT_MS
+ * and #MQTT_SEND_TIMEOUT_MS, and the underlying transport interface implementation
+ * timeouts, unless an error occurs. The blocking period also depends on the execution time of the
+ * #MQTTEventCallback_t callback supplied to the library. It is recommended that the supplied
+ * #MQTTEventCallback_t callback does not contain blocking operations to prevent potential
+ * non-deterministic blocking period of the #MQTT_ProcessLoop API call.
+ *
+ * @return #MQTTBadParameter if context is NULL;
+ * #MQTTRecvFailed if a network error occurs during reception;
+ * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ;
+ * #MQTTBadResponse if an invalid packet is received;
+ * #MQTTKeepAliveTimeout if the server has not sent a PINGRESP before
+ * #MQTT_PINGRESP_TIMEOUT_MS milliseconds;
+ * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an
+ * invalid transition for the internal state machine;
+ * #MQTTNeedMoreBytes if MQTT_ProcessLoop has received
+ * incomplete data; it should be called again (probably after a delay);
+ * #MQTTSuccess on success.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * // This context is assumed to be initialized and connected.
+ * MQTTContext_t * pContext;
+ *
+ * while( true )
+ * {
+ * status = MQTT_ProcessLoop( pContext );
+ *
+ * if( status != MQTTSuccess && status != MQTTNeedMoreBytes )
+ * {
+ * // Determine the error. It's possible we might need to disconnect
+ * // the underlying transport connection.
+ * }
+ * else
+ * {
+ * // Other application functions.
+ * }
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_processloop] */
+MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext );
+/* @[declare_mqtt_processloop] */
+
+/**
+ * @brief Loop to receive packets from the transport interface. Does not handle
+ * keep alive.
+ *
+ * @note If a dummy #MQTTGetCurrentTimeFunc_t was passed to #MQTT_Init, then the
+ * #MQTT_RECV_POLLING_TIMEOUT_MS and #MQTT_SEND_TIMEOUT_MS timeout configurations
+ * MUST be set to 0.
+ *
+ * @param[in] pContext Initialized and connected MQTT context.
+ *
+ * @note Calling this function blocks the calling context for a time period that
+ * depends on the the configuration macros, #MQTT_RECV_POLLING_TIMEOUT_MS and
+ * #MQTT_SEND_TIMEOUT_MS, and the underlying transport interface implementation
+ * timeouts, unless an error occurs. The blocking period also depends on the execution time of the
+ * #MQTTEventCallback_t callback supplied to the library. It is recommended that the supplied
+ * #MQTTEventCallback_t callback does not contain blocking operations to prevent potential
+ * non-deterministic blocking period of the #MQTT_ReceiveLoop API call.
+ *
+ * @return #MQTTBadParameter if context is NULL;
+ * #MQTTRecvFailed if a network error occurs during reception;
+ * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ;
+ * #MQTTBadResponse if an invalid packet is received;
+ * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an
+ * invalid transition for the internal state machine;
+ * #MQTTNeedMoreBytes if MQTT_ReceiveLoop has received
+ * incomplete data; it should be called again (probably after a delay);
+ * #MQTTSuccess on success.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * uint32_t keepAliveMs = 60 * 1000;
+ * // This context is assumed to be initialized and connected.
+ * MQTTContext_t * pContext;
+ *
+ * while( true )
+ * {
+ * status = MQTT_ReceiveLoop( pContext );
+ *
+ * if( status != MQTTSuccess && status != MQTTNeedMoreBytes )
+ * {
+ * // Determine the error. It's possible we might need to disconnect
+ * // the underlying transport connection.
+ * }
+ * else
+ * {
+ * // Since this function does not send pings, the application may need
+ * // to in order to comply with keep alive.
+ * if( ( pContext->getTime() - pContext->lastPacketTxTime ) > keepAliveMs )
+ * {
+ * status = MQTT_Ping( pContext );
+ * }
+ *
+ * // Other application functions.
+ * }
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_receiveloop] */
+MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext );
+/* @[declare_mqtt_receiveloop] */
+
+/**
+ * @brief Get a packet ID that is valid according to the MQTT 3.1.1 spec.
+ *
+ * @param[in] pContext Initialized MQTT context.
+ *
+ * @return A non-zero number.
+ */
+/* @[declare_mqtt_getpacketid] */
+uint16_t MQTT_GetPacketId( MQTTContext_t * pContext );
+/* @[declare_mqtt_getpacketid] */
+
+/**
+ * @brief A utility function that determines whether the passed topic filter and
+ * topic name match according to the MQTT 3.1.1 protocol specification.
+ *
+ * @param[in] pTopicName The topic name to check.
+ * @param[in] topicNameLength Length of the topic name.
+ * @param[in] pTopicFilter The topic filter to check.
+ * @param[in] topicFilterLength Length of topic filter.
+ * @param[out] pIsMatch If the match is performed without any error, that is if the
+ * return value is MQTTSuccess, then and only then the value in this parameter is valid
+ * and updated. In such a case, if the topic filter and the topic name match, then this
+ * value is set to true; otherwise if there is no match then it is set to false.
+ *
+ * @note The API assumes that the passed topic name is valid to meet the
+ * requirements of the MQTT 3.1.1 specification. Invalid topic names (for example,
+ * containing wildcard characters) should not be passed to the function.
+ * Also, the API checks validity of topic filter for wildcard characters ONLY if
+ * the passed topic name and topic filter do not have an exact string match.
+ *
+ * @return Returns one of the following:
+ * - #MQTTBadParameter, if any of the input parameters is invalid.
+ * - #MQTTSuccess, if the matching operation was performed.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * const char * pTopic = "topic/match/1";
+ * const char * pFilter = "topic/#";
+ * MQTTStatus_t status = MQTTSuccess;
+ * bool match = false;
+ *
+ * status = MQTT_MatchTopic( pTopic, strlen( pTopic ), pFilter, strlen( pFilter ), &match );
+ * // Our parameters were valid, so this will return success.
+ * assert( status == MQTTSuccess );
+ *
+ * // For this specific example, we already know this value is true. This
+ * // check is placed here as an example for use with variable topic names.
+ * if( match )
+ * {
+ * // Application can decide what to do with the matching topic name.
+ * }
+ * @endcode
+ */
+MQTTStatus_t MQTT_MatchTopic( const char * pTopicName,
+ const uint16_t topicNameLength,
+ const char * pTopicFilter,
+ const uint16_t topicFilterLength,
+ bool * pIsMatch );
+
+/**
+ * @brief Parses the payload of an MQTT SUBACK packet that contains status codes
+ * corresponding to topic filter subscription requests from the original
+ * subscribe packet.
+ *
+ * Each return code in the SUBACK packet corresponds to a topic filter in the
+ * SUBSCRIBE Packet being acknowledged.
+ * The status codes can be one of the following:
+ * - 0x00 - Success - Maximum QoS 0
+ * - 0x01 - Success - Maximum QoS 1
+ * - 0x02 - Success - Maximum QoS 2
+ * - 0x80 - Failure
+ * Refer to #MQTTSubAckStatus_t for the status codes.
+ *
+ * @param[in] pSubackPacket The SUBACK packet whose payload is to be parsed.
+ * @param[out] pPayloadStart This is populated with the starting address
+ * of the payload (or return codes for topic filters) in the SUBACK packet.
+ * @param[out] pPayloadSize This is populated with the size of the payload
+ * in the SUBACK packet. It represents the number of topic filters whose
+ * SUBACK status is present in the packet.
+ *
+ * @return Returns one of the following:
+ * - #MQTTBadParameter if the input SUBACK packet is invalid.
+ * - #MQTTSuccess if parsing the payload was successful.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Global variable used in this example.
+ * // This is assumed to be the subscription list in the original SUBSCRIBE packet.
+ * MQTTSubscribeInfo_t pSubscribes[ NUMBER_OF_SUBSCRIPTIONS ];
+ *
+ * // MQTT_GetSubAckStatusCodes is intended to be used from the application
+ * // callback that is called by the library in MQTT_ProcessLoop or MQTT_ReceiveLoop.
+ * void eventCallback(
+ * MQTTContext_t * pContext,
+ * MQTTPacketInfo_t * pPacketInfo,
+ * MQTTDeserializedInfo_t * pDeserializedInfo
+ * )
+ * {
+ * MQTTStatus_t status = MQTTSuccess;
+ * uint8_t * pCodes;
+ * size_t numCodes;
+ *
+ * if( pPacketInfo->type == MQTT_PACKET_TYPE_SUBACK )
+ * {
+ * status = MQTT_GetSubAckStatusCodes( pPacketInfo, &pCodes, &numCodes );
+ *
+ * // Since the pointers to the payload and payload size are not NULL, and
+ * // we use the packet info struct passed to the app callback (verified
+ * // to be valid by the library), this function must return success.
+ * assert( status == MQTTSuccess );
+ * // The server must send a response code for each topic filter in the
+ * // original SUBSCRIBE packet.
+ * assert( numCodes == NUMBER_OF_SUBSCRIPTIONS );
+ *
+ * for( int i = 0; i < numCodes; i++ )
+ * {
+ * // The only failure code is 0x80 = MQTTSubAckFailure.
+ * if( pCodes[ i ] == MQTTSubAckFailure )
+ * {
+ * // The subscription failed, we may want to retry the
+ * // subscription in pSubscribes[ i ] outside of this callback.
+ * }
+ * else
+ * {
+ * // The subscription was granted, but the maximum QoS may be
+ * // lower than what was requested. We can verify the granted QoS.
+ * if( pSubscribes[ i ].qos != pCodes[ i ] )
+ * {
+ * LogWarn( (
+ * "Requested QoS %u, but granted QoS %u for %s",
+ * pSubscribes[ i ].qos, pCodes[ i ], pSubscribes[ i ].pTopicFilter
+ * ) );
+ * }
+ * }
+ * }
+ * }
+ * // Handle other packet types.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_getsubackstatuscodes] */
+MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket,
+ uint8_t ** pPayloadStart,
+ size_t * pPayloadSize );
+/* @[declare_mqtt_getsubackstatuscodes] */
+
+/**
+ * @brief Error code to string conversion for MQTT statuses.
+ *
+ * @param[in] status The status to convert to a string.
+ *
+ * @return The string representation of the status.
+ */
+/* @[declare_mqtt_status_strerror] */
+const char * MQTT_Status_strerror( MQTTStatus_t status );
+/* @[declare_mqtt_status_strerror] */
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ }
+#endif
+/* *INDENT-ON* */
+
+#endif /* ifndef CORE_MQTT_H */
diff --git a/project/coreMQTT/coreMQTT/core_mqtt_config_defaults.h b/project/coreMQTT/coreMQTT/core_mqtt_config_defaults.h
new file mode 100644
index 0000000..35b42d5
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/core_mqtt_config_defaults.h
@@ -0,0 +1,204 @@
+/*
+ * coreMQTT v2.1.1
+ * Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_mqtt_config_defaults.h
+ * @brief This represents the default values for the configuration macros
+ * for the MQTT library.
+ *
+ * @note This file SHOULD NOT be modified. If custom values are needed for
+ * any configuration macro, a core_mqtt_config.h file should be provided to
+ * the MQTT library to override the default values defined in this file.
+ * To use the custom config file, the MQTT_DO_NOT_USE_CUSTOM_CONFIG preprocessor
+ * macro SHOULD NOT be set.
+ */
+
+#ifndef CORE_MQTT_CONFIG_DEFAULTS_H_
+#define CORE_MQTT_CONFIG_DEFAULTS_H_
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+/* *INDENT-ON* */
+
+/* MQTT_DO_NOT_USE_CUSTOM_CONFIG allows building the MQTT library
+ * without a custom config. If a custom config is provided, the
+ * MQTT_DO_NOT_USE_CUSTOM_CONFIG macro should not be defined. */
+#ifndef MQTT_DO_NOT_USE_CUSTOM_CONFIG
+/* Include custom config file before other headers. */
+ #include "core_mqtt_config.h"
+#endif
+
+/* The macro definition for MQTT_DO_NOT_USE_CUSTOM_CONFIG is for Doxygen
+ * documentation only. */
+
+/**
+ * @brief Define this macro to build the MQTT library without the custom config
+ * file core_mqtt_config.h.
+ *
+ * Without the custom config, the MQTT library builds with
+ * default values of config macros defined in core_mqtt_config_defaults.h file.
+ *
+ * If a custom config is provided, then MQTT_DO_NOT_USE_CUSTOM_CONFIG should not
+ * be defined.
+ */
+#ifdef DOXYGEN
+ #define MQTT_DO_NOT_USE_CUSTOM_CONFIG
+#endif
+
+/**
+ * @ingroup mqtt_constants
+ * @brief Maximum number of vectors in subscribe and unsubscribe packet.
+ */
+#ifndef MQTT_SUB_UNSUB_MAX_VECTORS
+ #define MQTT_SUB_UNSUB_MAX_VECTORS ( 4U )
+#endif
+
+/**
+ * @brief The number of retries for receiving CONNACK.
+ *
+ * The MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT will be used only when the
+ * timeoutMs parameter of #MQTT_Connect is passed as 0 . The transport
+ * receive for CONNACK will be retried MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT
+ * times before timing out. A value of 0 for this config will cause the
+ * transport receive for CONNACK to be invoked only once.
+ *
+ * <b>Possible values:</b> Any positive 16 bit integer. <br>
+ * <b>Default value:</b> `5`
+ */
+#ifndef MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT
+/* Default value for the CONNACK receive retries. */
+ #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT ( 5U )
+#endif
+
+/**
+ * @brief Maximum number of milliseconds to wait for a ping response to a ping
+ * request as part of the keep-alive mechanism.
+ *
+ * If a ping response is not received before this timeout, then
+ * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout.
+ *
+ * @note If this value is more than half of the keep alive interval, and the
+ * server does not receive the previous ping request, then it is likely that the
+ * server will disconnect the client before #MQTTKeepAliveTimeout can be returned.
+ *
+ * @note If a dummy implementation of the #MQTTGetCurrentTimeFunc_t timer function,
+ * is supplied to the library, then the keep-alive mechanism is not supported by the
+ * #MQTT_ProcessLoop API function. In that case, the value of #MQTT_PINGRESP_TIMEOUT_MS
+ * is irrelevant to the behavior of the library.
+ *
+ * <b>Possible values:</b> Any positive integer up to SIZE_MAX. <br>
+ * <b>Default value:</b> `5000`
+ */
+#ifndef MQTT_PINGRESP_TIMEOUT_MS
+/* Wait 5 seconds by default for a ping response. */
+ #define MQTT_PINGRESP_TIMEOUT_MS ( 5000U )
+#endif
+
+/**
+ * @brief Maximum number of milliseconds of TX inactivity to wait
+ * before initiating a PINGREQ
+ *
+ * @note If this value is less than the keep alive interval than
+ * it will be used instead.
+ *
+ * <b>Possible values:</b> Any positive integer up to SIZE_MAX. <br>
+ * <b>Default value:</b> '30000'
+ */
+#ifndef PACKET_TX_TIMEOUT_MS
+ #define PACKET_TX_TIMEOUT_MS ( 30000U )
+#endif
+
+/**
+ * @brief Maximum number of milliseconds of RX inactivity to wait
+ * before initiating a PINGREQ
+ *
+ * <b>Possible values:</b> Any positive integer up to SIZE_MAX. <br>
+ * <b>Default value:</b> '30000'
+ *
+ */
+#ifndef PACKET_RX_TIMEOUT_MS
+ #define PACKET_RX_TIMEOUT_MS ( 30000U )
+#endif
+
+/**
+ * @brief The maximum duration between non-empty network reads while
+ * receiving an MQTT packet via the #MQTT_ProcessLoop or #MQTT_ReceiveLoop
+ * API functions.
+ *
+ * When an incoming MQTT packet is detected, the transport receive function
+ * may be called multiple times until all of the expected number of bytes of the
+ * packet are received. This timeout represents the maximum polling duration that
+ * is allowed without any data reception from the network for the incoming packet.
+ *
+ * If the timeout expires, the #MQTT_ProcessLoop and #MQTT_ReceiveLoop functions
+ * return #MQTTRecvFailed.
+ *
+ * @note If a dummy implementation of the #MQTTGetCurrentTimeFunc_t timer function,
+ * is supplied to the library, then #MQTT_RECV_POLLING_TIMEOUT_MS MUST be set to 0.
+ *
+ * <b>Possible values:</b> Any positive 32 bit integer. Recommended to use a
+ * small timeout value. <br>
+ * <b>Default value:</b> `10`
+ *
+ */
+#ifndef MQTT_RECV_POLLING_TIMEOUT_MS
+ #define MQTT_RECV_POLLING_TIMEOUT_MS ( 10U )
+#endif
+
+/**
+ * @brief The maximum duration allowed to send an MQTT packet over the transport
+ * interface.
+ *
+ * When sending an MQTT packet, the transport send or writev functions may be
+ * called multiple times until all of the required number of bytes are sent.
+ * This timeout represents the maximum duration that is allowed to send the MQTT
+ * packet while calling the transport send or writev functions.
+ *
+ * If the timeout expires, #MQTTSendFailed will be returned by the public API
+ * functions.
+ *
+ * @note If a dummy implementation of the #MQTTGetCurrentTimeFunc_t timer function,
+ * is supplied to the library, then #MQTT_SEND_TIMEOUT_MS MUST be set to 0.
+ *
+ * <b>Possible values:</b> Any positive 32 bit integer. <br>
+ * <b>Default value:</b> `20000`
+ *
+ */
+#ifndef MQTT_SEND_TIMEOUT_MS
+ #define MQTT_SEND_TIMEOUT_MS ( 20000U )
+#endif
+
+#ifdef MQTT_SEND_RETRY_TIMEOUT_MS
+ #error MQTT_SEND_RETRY_TIMEOUT_MS is deprecated. Instead use MQTT_SEND_TIMEOUT_MS.
+#endif
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ }
+#endif
+/* *INDENT-ON* */
+
+#endif /* ifndef CORE_MQTT_CONFIG_DEFAULTS_H_ */
diff --git a/project/coreMQTT/coreMQTT/core_mqtt_default_logging.h b/project/coreMQTT/coreMQTT/core_mqtt_default_logging.h
new file mode 100644
index 0000000..dcacf90
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/core_mqtt_default_logging.h
@@ -0,0 +1,132 @@
+/*
+ * coreMQTT v2.1.1
+ * Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_mqtt_default_logging.h
+ * @brief This represents the default values for the logging macros for the MQTT
+ * library.
+ *
+ * @note This file SHOULD NOT be modified. If custom values are needed for
+ * any configuration macro, a core_mqtt_config.h file should be provided to
+ * the MQTT library to override the default values defined in this file.
+ * To use the custom config file, the MQTT_DO_NOT_USE_CUSTOM_CONFIG preprocessor
+ * macro SHOULD NOT be set.
+ */
+
+#ifndef CORE_MQTT_DEFAULT_LOGGING_H_
+#define CORE_MQTT_DEFAULT_LOGGING_H_
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+/* *INDENT-ON* */
+
+/**
+ * @brief Macro that is called in the MQTT library for logging "Error" level
+ * messages.
+ *
+ * To enable error level logging in the MQTT library, this macro should be mapped to the
+ * application-specific logging implementation that supports error logging.
+ *
+ * @note This logging macro is called in the MQTT library with parameters wrapped in
+ * double parentheses to be ISO C89/C90 standard compliant. For a reference
+ * POSIX implementation of the logging macros, refer to core_mqtt_config.h files, and the
+ * logging-stack in demos folder of the
+ * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C).
+ *
+ * <b>Default value</b>: Error logging is turned off, and no code is generated for calls
+ * to the macro in the MQTT library on compilation.
+ */
+#ifndef LogError
+ #define LogError( message )
+#endif
+
+/**
+ * @brief Macro that is called in the MQTT library for logging "Warning" level
+ * messages.
+ *
+ * To enable warning level logging in the MQTT library, this macro should be mapped to the
+ * application-specific logging implementation that supports warning logging.
+ *
+ * @note This logging macro is called in the MQTT library with parameters wrapped in
+ * double parentheses to be ISO C89/C90 standard compliant. For a reference
+ * POSIX implementation of the logging macros, refer to core_mqtt_config.h files, and the
+ * logging-stack in demos folder of the
+ * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
+ *
+ * <b>Default value</b>: Warning logs are turned off, and no code is generated for calls
+ * to the macro in the MQTT library on compilation.
+ */
+#ifndef LogWarn
+ #define LogWarn( message )
+#endif
+
+/**
+ * @brief Macro that is called in the MQTT library for logging "Info" level
+ * messages.
+ *
+ * To enable info level logging in the MQTT library, this macro should be mapped to the
+ * application-specific logging implementation that supports info logging.
+ *
+ * @note This logging macro is called in the MQTT library with parameters wrapped in
+ * double parentheses to be ISO C89/C90 standard compliant. For a reference
+ * POSIX implementation of the logging macros, refer to core_mqtt_config.h files, and the
+ * logging-stack in demos folder of the
+ * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
+ *
+ * <b>Default value</b>: Info logging is turned off, and no code is generated for calls
+ * to the macro in the MQTT library on compilation.
+ */
+#ifndef LogInfo
+ #define LogInfo( message )
+#endif
+
+/**
+ * @brief Macro that is called in the MQTT library for logging "Debug" level
+ * messages.
+ *
+ * To enable debug level logging from MQTT library, this macro should be mapped to the
+ * application-specific logging implementation that supports debug logging.
+ *
+ * @note This logging macro is called in the MQTT library with parameters wrapped in
+ * double parentheses to be ISO C89/C90 standard compliant. For a reference
+ * POSIX implementation of the logging macros, refer to core_mqtt_config.h files, and the
+ * logging-stack in demos folder of the
+ * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
+ *
+ * <b>Default value</b>: Debug logging is turned off, and no code is generated for calls
+ * to the macro in the MQTT library on compilation.
+ */
+#ifndef LogDebug
+ #define LogDebug( message )
+#endif
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ }
+#endif
+/* *INDENT-ON* */
+
+#endif /* ifndef CORE_MQTT_DEFAULT_LOGGING_H_ */
diff --git a/project/coreMQTT/coreMQTT/core_mqtt_serializer.c b/project/coreMQTT/coreMQTT/core_mqtt_serializer.c
new file mode 100644
index 0000000..3e2b427
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/core_mqtt_serializer.c
@@ -0,0 +1,2684 @@
+/*
+ * coreMQTT v2.1.1
+ * Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_mqtt_serializer.c
+ * @brief Implements the user-facing functions in core_mqtt_serializer.h.
+ */
+#include <string.h>
+#include <assert.h>
+
+#include "core_mqtt_serializer.h"
+
+/* Include config defaults header to get default values of configs. */
+#include "core_mqtt_config_defaults.h"
+
+#include "core_mqtt_default_logging.h"
+
+/**
+ * @brief MQTT protocol version 3.1.1.
+ */
+#define MQTT_VERSION_3_1_1 ( ( uint8_t ) 4U )
+
+/**
+ * @brief Size of the fixed and variable header of a CONNECT packet.
+ */
+#define MQTT_PACKET_CONNECT_HEADER_SIZE ( 10UL )
+
+/* MQTT CONNECT flags. */
+#define MQTT_CONNECT_FLAG_CLEAN ( 1 ) /**< @brief Clean session. */
+#define MQTT_CONNECT_FLAG_WILL ( 2 ) /**< @brief Will present. */
+#define MQTT_CONNECT_FLAG_WILL_QOS1 ( 3 ) /**< @brief Will QoS 1. */
+#define MQTT_CONNECT_FLAG_WILL_QOS2 ( 4 ) /**< @brief Will QoS 2. */
+#define MQTT_CONNECT_FLAG_WILL_RETAIN ( 5 ) /**< @brief Will retain. */
+#define MQTT_CONNECT_FLAG_PASSWORD ( 6 ) /**< @brief Password present. */
+#define MQTT_CONNECT_FLAG_USERNAME ( 7 ) /**< @brief User name present. */
+
+/*
+ * Positions of each flag in the first byte of an MQTT PUBLISH packet's
+ * fixed header.
+ */
+#define MQTT_PUBLISH_FLAG_RETAIN ( 0 ) /**< @brief MQTT PUBLISH retain flag. */
+#define MQTT_PUBLISH_FLAG_QOS1 ( 1 ) /**< @brief MQTT PUBLISH QoS1 flag. */
+#define MQTT_PUBLISH_FLAG_QOS2 ( 2 ) /**< @brief MQTT PUBLISH QoS2 flag. */
+#define MQTT_PUBLISH_FLAG_DUP ( 3 ) /**< @brief MQTT PUBLISH duplicate flag. */
+
+/**
+ * @brief The size of MQTT DISCONNECT packets, per MQTT spec.
+ */
+#define MQTT_DISCONNECT_PACKET_SIZE ( 2UL )
+
+/**
+ * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec.
+ */
+#define MQTT_PACKET_PINGREQ_SIZE ( 2UL )
+
+/**
+ * @brief The Remaining Length field of MQTT disconnect packets, per MQTT spec.
+ */
+#define MQTT_DISCONNECT_REMAINING_LENGTH ( ( uint8_t ) 0 )
+
+/*
+ * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec.
+ */
+#define MQTT_PACKET_CONNACK_REMAINING_LENGTH ( ( uint8_t ) 2U ) /**< @brief A CONNACK packet always has a "Remaining length" of 2. */
+#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */
+
+/*
+ * UNSUBACK, PUBACK, PUBREC, PUBREL, and PUBCOMP always have a remaining length
+ * of 2.
+ */
+#define MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ( ( uint8_t ) 2 ) /**< @brief PUBACK, PUBREC, PUBREl, PUBCOMP, UNSUBACK Remaining length. */
+#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH ( 0U ) /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */
+
+/**
+ * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT
+ * packet is this value, 256 MB.
+ */
+#define MQTT_MAX_REMAINING_LENGTH ( 268435455UL )
+
+/**
+ * @brief Set a bit in an 8-bit unsigned integer.
+ */
+#define UINT8_SET_BIT( x, position ) ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) )
+
+/**
+ * @brief Macro for checking if a bit is set in a 1-byte unsigned int.
+ *
+ * @param[in] x The unsigned int to check.
+ * @param[in] position Which bit to check.
+ */
+#define UINT8_CHECK_BIT( x, position ) ( ( ( x ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) )
+
+/**
+ * @brief Get the high byte of a 16-bit unsigned integer.
+ */
+#define UINT16_HIGH_BYTE( x ) ( ( uint8_t ) ( ( x ) >> 8 ) )
+
+/**
+ * @brief Get the low byte of a 16-bit unsigned integer.
+ */
+#define UINT16_LOW_BYTE( x ) ( ( uint8_t ) ( ( x ) & 0x00ffU ) )
+
+/**
+ * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes.
+ *
+ * @param[in] ptr A uint8_t* that points to the high byte.
+ */
+#define UINT16_DECODE( ptr ) \
+ ( uint16_t ) ( ( ( ( uint16_t ) ptr[ 0 ] ) << 8 ) | \
+ ( ( uint16_t ) ptr[ 1 ] ) )
+
+/**
+ * @brief A value that represents an invalid remaining length.
+ *
+ * This value is greater than what is allowed by the MQTT specification.
+ */
+#define MQTT_REMAINING_LENGTH_INVALID ( ( size_t ) 268435456 )
+
+/**
+ * @brief The minimum remaining length for a QoS 0 PUBLISH.
+ *
+ * Includes two bytes for topic name length and one byte for topic name.
+ */
+#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 ( 3U )
+
+/*-----------------------------------------------------------*/
+
+
+/**
+ * @brief MQTT Subscription packet types.
+ */
+typedef enum MQTTSubscriptionType
+{
+ MQTT_SUBSCRIBE, /**< @brief The type is a SUBSCRIBE packet. */
+ MQTT_UNSUBSCRIBE /**< @brief The type is a UNSUBSCRIBE packet. */
+} MQTTSubscriptionType_t;
+
+/*-----------------------------------------------------------*/
+
+/**
+ * @brief Serializes MQTT PUBLISH packet into the buffer provided.
+ *
+ * This function serializes MQTT PUBLISH packet into #MQTTFixedBuffer_t.pBuffer.
+ * Copy of the payload into the buffer is done as part of the serialization
+ * only if @p serializePayload is true.
+ *
+ * @brief param[in] pPublishInfo Publish information.
+ * @brief param[in] remainingLength Remaining length of the PUBLISH packet.
+ * @brief param[in] packetIdentifier Packet identifier of PUBLISH packet.
+ * @brief param[in, out] pFixedBuffer Buffer to which PUBLISH packet will be
+ * serialized.
+ * @brief param[in] serializePayload Copy payload to the serialized buffer
+ * only if true. Only PUBLISH header will be serialized if false.
+ *
+ * @return Total number of bytes sent; -1 if there is an error.
+ */
+static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo,
+ size_t remainingLength,
+ uint16_t packetIdentifier,
+ const MQTTFixedBuffer_t * pFixedBuffer,
+ bool serializePayload );
+
+/**
+ * @brief Calculates the packet size and remaining length of an MQTT
+ * PUBLISH packet.
+ *
+ * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
+ * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet.
+ * @param[out] pPacketSize The total size of the MQTT PUBLISH packet.
+ *
+ * @return false if the packet would exceed the size allowed by the
+ * MQTT spec; true otherwise.
+ */
+static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo,
+ size_t * pRemainingLength,
+ size_t * pPacketSize );
+
+/**
+ * @brief Calculates the packet size and remaining length of an MQTT
+ * SUBSCRIBE or UNSUBSCRIBE packet.
+ *
+ * @param[in] pSubscriptionList List of MQTT subscription info.
+ * @param[in] subscriptionCount The number of elements in pSubscriptionList.
+ * @param[out] pRemainingLength The Remaining Length of the MQTT SUBSCRIBE or
+ * UNSUBSCRIBE packet.
+ * @param[out] pPacketSize The total size of the MQTT MQTT SUBSCRIBE or
+ * UNSUBSCRIBE packet.
+ * @param[in] subscriptionType #MQTT_SUBSCRIBE or #MQTT_UNSUBSCRIBE.
+ *
+ * #MQTTBadParameter if the packet would exceed the size allowed by the
+ * MQTT spec or a subscription is empty; #MQTTSuccess otherwise.
+ */
+static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ size_t * pRemainingLength,
+ size_t * pPacketSize,
+ MQTTSubscriptionType_t subscriptionType );
+
+/**
+ * @brief Validates parameters of #MQTT_SerializeSubscribe or
+ * #MQTT_SerializeUnsubscribe.
+ *
+ * @param[in] pSubscriptionList List of MQTT subscription info.
+ * @param[in] subscriptionCount The number of elements in pSubscriptionList.
+ * @param[in] packetId Packet identifier.
+ * @param[in] remainingLength Remaining length of the packet.
+ * @param[in] pFixedBuffer Buffer for packet serialization.
+ *
+ * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ */
+static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer );
+
+/**
+ * @brief Serialize an MQTT CONNECT packet in the given buffer.
+ *
+ * @param[in] pConnectInfo MQTT CONNECT packet parameters.
+ * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used.
+ * @param[in] remainingLength Remaining Length of MQTT CONNECT packet.
+ * @param[out] pFixedBuffer Buffer for packet serialization.
+ */
+static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer );
+
+/**
+ * @brief Prints the appropriate message for the CONNACK response code if logs
+ * are enabled.
+ *
+ * @param[in] responseCode MQTT standard CONNACK response code.
+ */
+static void logConnackResponse( uint8_t responseCode );
+
+/**
+ * @brief Encodes the remaining length of the packet using the variable length
+ * encoding scheme provided in the MQTT v3.1.1 specification.
+ *
+ * @param[out] pDestination The destination buffer to store the encoded remaining
+ * length.
+ * @param[in] length The remaining length to encode.
+ *
+ * @return The location of the byte following the encoded value.
+ */
+static uint8_t * encodeRemainingLength( uint8_t * pDestination,
+ size_t length );
+
+/**
+ * @brief Retrieve the size of the remaining length if it were to be encoded.
+ *
+ * @param[in] length The remaining length to be encoded.
+ *
+ * @return The size of the remaining length if it were to be encoded.
+ */
+static size_t remainingLengthEncodedSize( size_t length );
+
+/**
+ * @brief Encode a string whose size is at maximum 16 bits in length.
+ *
+ * @param[out] pDestination Destination buffer for the encoding.
+ * @param[in] pSource The source string to encode.
+ * @param[in] sourceLength The length of the source string to encode.
+ *
+ * @return A pointer to the end of the encoded string.
+ */
+static uint8_t * encodeString( uint8_t * pDestination,
+ const char * pSource,
+ uint16_t sourceLength );
+
+/**
+ * @brief Retrieves and decodes the Remaining Length from the network interface
+ * by reading a single byte at a time.
+ *
+ * @param[in] recvFunc Network interface receive function.
+ * @param[in] pNetworkContext Network interface context to the receive function.
+ *
+ * @return The Remaining Length of the incoming packet.
+ */
+static size_t getRemainingLength( TransportRecv_t recvFunc,
+ NetworkContext_t * pNetworkContext );
+
+/**
+ * @brief Retrieves, decodes and stores the Remaining Length from the network
+ * interface by reading a single byte at a time.
+ *
+ * @param[in] pBuffer The buffer holding the raw data to be processed
+ * @param[in] pIndex Pointer to the index within the buffer to marking the end of raw data
+ * available.
+ * @param[in] pIncomingPacket Structure used to hold the fields of the
+ * incoming packet.
+ *
+ * @return MQTTNeedMoreBytes is returned to show that the incoming
+ * packet is not yet fully received and decoded. Otherwise, MQTTSuccess
+ * shows that processing of the packet was successful.
+ */
+static MQTTStatus_t processRemainingLength( const uint8_t * pBuffer,
+ const size_t * pIndex,
+ MQTTPacketInfo_t * pIncomingPacket );
+
+/**
+ * @brief Check if an incoming packet type is valid.
+ *
+ * @param[in] packetType The packet type to check.
+ *
+ * @return `true` if the packet type is valid; `false` otherwise.
+ */
+static bool incomingPacketValid( uint8_t packetType );
+
+/**
+ * @brief Check the remaining length of an incoming PUBLISH packet against some
+ * value for QoS 0, or for QoS 1 and 2.
+ *
+ * The remaining length for a QoS 1 and 2 packet will always be two greater than
+ * for a QoS 0.
+ *
+ * @param[in] remainingLength Remaining length of the PUBLISH packet.
+ * @param[in] qos The QoS of the PUBLISH.
+ * @param[in] qos0Minimum Minimum possible remaining length for a QoS 0 PUBLISH.
+ *
+ * @return #MQTTSuccess or #MQTTBadResponse.
+ */
+static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength,
+ MQTTQoS_t qos,
+ size_t qos0Minimum );
+
+/**
+ * @brief Process the flags of an incoming PUBLISH packet.
+ *
+ * @param[in] publishFlags Flags of an incoming PUBLISH.
+ * @param[in, out] pPublishInfo Pointer to #MQTTPublishInfo_t struct where
+ * output will be written.
+ *
+ * @return #MQTTSuccess or #MQTTBadResponse.
+ */
+static MQTTStatus_t processPublishFlags( uint8_t publishFlags,
+ MQTTPublishInfo_t * pPublishInfo );
+
+/**
+ * @brief Deserialize a CONNACK packet.
+ *
+ * Converts the packet from a stream of bytes to an #MQTTStatus_t.
+ *
+ * @param[in] pConnack Pointer to an MQTT packet struct representing a
+ * CONNACK.
+ * @param[out] pSessionPresent Whether a previous session was present.
+ *
+ * @return #MQTTSuccess if CONNACK specifies that CONNECT was accepted;
+ * #MQTTServerRefused if CONNACK specifies that CONNECT was rejected;
+ * #MQTTBadResponse if the CONNACK packet doesn't follow MQTT spec.
+ */
+static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * pConnack,
+ bool * pSessionPresent );
+
+/**
+ * @brief Decode the status bytes of a SUBACK packet to a #MQTTStatus_t.
+ *
+ * @param[in] statusCount Number of status bytes in the SUBACK.
+ * @param[in] pStatusStart The first status byte in the SUBACK.
+ *
+ * @return #MQTTSuccess, #MQTTServerRefused, or #MQTTBadResponse.
+ */
+static MQTTStatus_t readSubackStatus( size_t statusCount,
+ const uint8_t * pStatusStart );
+
+/**
+ * @brief Deserialize a SUBACK packet.
+ *
+ * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts
+ * the packet identifier.
+ *
+ * @param[in] pSuback Pointer to an MQTT packet struct representing a SUBACK.
+ * @param[out] pPacketIdentifier Packet ID of the SUBACK.
+ *
+ * @return #MQTTSuccess if SUBACK is valid; #MQTTBadResponse if SUBACK packet
+ * doesn't follow the MQTT spec.
+ */
+static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * pSuback,
+ uint16_t * pPacketIdentifier );
+
+/**
+ * @brief Deserialize a PUBLISH packet received from the server.
+ *
+ * Converts the packet from a stream of bytes to an #MQTTPublishInfo_t and
+ * extracts the packet identifier. Also prints out debug log messages about the
+ * packet.
+ *
+ * @param[in] pIncomingPacket Pointer to an MQTT packet struct representing a
+ * PUBLISH.
+ * @param[out] pPacketId Packet identifier of the PUBLISH.
+ * @param[out] pPublishInfo Pointer to #MQTTPublishInfo_t where output is
+ * written.
+ *
+ * @return #MQTTSuccess if PUBLISH is valid; #MQTTBadResponse
+ * if the PUBLISH packet doesn't follow MQTT spec.
+ */
+static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket,
+ uint16_t * pPacketId,
+ MQTTPublishInfo_t * pPublishInfo );
+
+/**
+ * @brief Deserialize an UNSUBACK, PUBACK, PUBREC, PUBREL, or PUBCOMP packet.
+ *
+ * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts
+ * the packet identifier.
+ *
+ * @param[in] pAck Pointer to the MQTT packet structure representing the packet.
+ * @param[out] pPacketIdentifier Packet ID of the ack type packet.
+ *
+ * @return #MQTTSuccess if UNSUBACK, PUBACK, PUBREC, PUBREL, or PUBCOMP is valid;
+ * #MQTTBadResponse if the packet doesn't follow the MQTT spec.
+ */
+static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * pAck,
+ uint16_t * pPacketIdentifier );
+
+/**
+ * @brief Deserialize a PINGRESP packet.
+ *
+ * Converts the packet from a stream of bytes to an #MQTTStatus_t.
+ *
+ * @param[in] pPingresp Pointer to an MQTT packet struct representing a PINGRESP.
+ *
+ * @return #MQTTSuccess if PINGRESP is valid; #MQTTBadResponse if the PINGRESP
+ * packet doesn't follow MQTT spec.
+ */
+static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * pPingresp );
+
+/*-----------------------------------------------------------*/
+
+static size_t remainingLengthEncodedSize( size_t length )
+{
+ size_t encodedSize;
+
+ /* Determine how many bytes are needed to encode length.
+ * The values below are taken from the MQTT 3.1.1 spec. */
+
+ /* 1 byte is needed to encode lengths between 0 and 127. */
+ if( length < 128U )
+ {
+ encodedSize = 1U;
+ }
+ /* 2 bytes are needed to encode lengths between 128 and 16,383. */
+ else if( length < 16384U )
+ {
+ encodedSize = 2U;
+ }
+ /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */
+ else if( length < 2097152U )
+ {
+ encodedSize = 3U;
+ }
+ /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */
+ else
+ {
+ encodedSize = 4U;
+ }
+
+ LogDebug( ( "Encoded size for length %lu is %lu bytes.",
+ ( unsigned long ) length,
+ ( unsigned long ) encodedSize ) );
+
+ return encodedSize;
+}
+
+/*-----------------------------------------------------------*/
+
+static uint8_t * encodeRemainingLength( uint8_t * pDestination,
+ size_t length )
+{
+ uint8_t lengthByte;
+ uint8_t * pLengthEnd = NULL;
+ size_t remainingLength = length;
+
+ assert( pDestination != NULL );
+
+ pLengthEnd = pDestination;
+
+ /* This algorithm is copied from the MQTT v3.1.1 spec. */
+ do
+ {
+ lengthByte = ( uint8_t ) ( remainingLength % 128U );
+ remainingLength = remainingLength / 128U;
+
+ /* Set the high bit of this byte, indicating that there's more data. */
+ if( remainingLength > 0U )
+ {
+ UINT8_SET_BIT( lengthByte, 7 );
+ }
+
+ /* Output a single encoded byte. */
+ *pLengthEnd = lengthByte;
+ pLengthEnd++;
+ } while( remainingLength > 0U );
+
+ return pLengthEnd;
+}
+
+/*-----------------------------------------------------------*/
+
+static uint8_t * encodeString( uint8_t * pDestination,
+ const char * pSource,
+ uint16_t sourceLength )
+{
+ uint8_t * pBuffer = NULL;
+
+ /* Typecast const char * typed source buffer to const uint8_t *.
+ * This is to use same type buffers in memcpy. */
+ const uint8_t * pSourceBuffer = ( const uint8_t * ) pSource;
+
+ assert( pDestination != NULL );
+
+ pBuffer = pDestination;
+
+ /* The first byte of a UTF-8 string is the high byte of the string length. */
+ *pBuffer = UINT16_HIGH_BYTE( sourceLength );
+ pBuffer++;
+
+ /* The second byte of a UTF-8 string is the low byte of the string length. */
+ *pBuffer = UINT16_LOW_BYTE( sourceLength );
+ pBuffer++;
+
+ /* Copy the string into pBuffer. */
+ if( pSourceBuffer != NULL )
+ {
+ ( void ) memcpy( pBuffer, pSourceBuffer, sourceLength );
+ }
+
+ /* Return the pointer to the end of the encoded string. */
+ pBuffer = &pBuffer[ sourceLength ];
+
+ return pBuffer;
+}
+
+/*-----------------------------------------------------------*/
+
+static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo,
+ size_t * pRemainingLength,
+ size_t * pPacketSize )
+{
+ bool status = true;
+ size_t packetSize = 0, payloadLimit = 0;
+
+ assert( pPublishInfo != NULL );
+ assert( pRemainingLength != NULL );
+ assert( pPacketSize != NULL );
+
+ /* The variable header of a PUBLISH packet always contains the topic name.
+ * The first 2 bytes of UTF-8 string contains length of the string.
+ */
+ packetSize += pPublishInfo->topicNameLength + sizeof( uint16_t );
+
+ /* The variable header of a QoS 1 or 2 PUBLISH packet contains a 2-byte
+ * packet identifier. */
+ if( pPublishInfo->qos > MQTTQoS0 )
+ {
+ packetSize += sizeof( uint16_t );
+ }
+
+ /* Calculate the maximum allowed size of the payload for the given parameters.
+ * This calculation excludes the "Remaining length" encoding, whose size is not
+ * yet known. */
+ payloadLimit = MQTT_MAX_REMAINING_LENGTH - packetSize - 1U;
+
+ /* Ensure that the given payload fits within the calculated limit. */
+ if( pPublishInfo->payloadLength > payloadLimit )
+ {
+ LogError( ( "PUBLISH payload length of %lu cannot exceed "
+ "%lu so as not to exceed the maximum "
+ "remaining length of MQTT 3.1.1 packet( %lu ).",
+ ( unsigned long ) pPublishInfo->payloadLength,
+ ( unsigned long ) payloadLimit,
+ MQTT_MAX_REMAINING_LENGTH ) );
+ status = false;
+ }
+ else
+ {
+ /* Add the length of the PUBLISH payload. At this point, the "Remaining length"
+ * has been calculated. */
+ packetSize += pPublishInfo->payloadLength;
+
+ /* Now that the "Remaining length" is known, recalculate the payload limit
+ * based on the size of its encoding. */
+ payloadLimit -= remainingLengthEncodedSize( packetSize );
+
+ /* Check that the given payload fits within the size allowed by MQTT spec. */
+ if( pPublishInfo->payloadLength > payloadLimit )
+ {
+ LogError( ( "PUBLISH payload length of %lu cannot exceed "
+ "%lu so as not to exceed the maximum "
+ "remaining length of MQTT 3.1.1 packet( %lu ).",
+ ( unsigned long ) pPublishInfo->payloadLength,
+ ( unsigned long ) payloadLimit,
+ MQTT_MAX_REMAINING_LENGTH ) );
+ status = false;
+ }
+ else
+ {
+ /* Set the "Remaining length" output parameter and calculate the full
+ * size of the PUBLISH packet. */
+ *pRemainingLength = packetSize;
+
+ packetSize += 1U + remainingLengthEncodedSize( packetSize );
+ *pPacketSize = packetSize;
+ }
+ }
+
+ LogDebug( ( "PUBLISH packet remaining length=%lu and packet size=%lu.",
+ ( unsigned long ) *pRemainingLength,
+ ( unsigned long ) *pPacketSize ) );
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_SerializePublishHeaderWithoutTopic( const MQTTPublishInfo_t * pPublishInfo,
+ size_t remainingLength,
+ uint8_t * pBuffer,
+ size_t * headerSize )
+{
+ size_t headerLength;
+ uint8_t * pIndex;
+ MQTTStatus_t status = MQTTSuccess;
+
+ /* The first byte of a PUBLISH packet contains the packet type and flags. */
+ uint8_t publishFlags = MQTT_PACKET_TYPE_PUBLISH;
+
+ /* Get the start address of the buffer. */
+ pIndex = pBuffer;
+
+ /* Length of serialized packet = First byte
+ * + Length of encoded remaining length
+ * + Encoded topic length. */
+ headerLength = 1U + remainingLengthEncodedSize( remainingLength ) + 2U;
+
+ if( pPublishInfo->qos == MQTTQoS1 )
+ {
+ LogDebug( ( "Adding QoS as QoS1 in PUBLISH flags." ) );
+ UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 );
+ }
+ else if( pPublishInfo->qos == MQTTQoS2 )
+ {
+ LogDebug( ( "Adding QoS as QoS2 in PUBLISH flags." ) );
+ UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 );
+ }
+ else
+ {
+ /* Empty else MISRA 15.7 */
+ }
+
+ if( pPublishInfo->retain == true )
+ {
+ LogDebug( ( "Adding retain bit in PUBLISH flags." ) );
+ UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN );
+ }
+
+ if( pPublishInfo->dup == true )
+ {
+ LogDebug( ( "Adding dup bit in PUBLISH flags." ) );
+ UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP );
+ }
+
+ *pIndex = publishFlags;
+ pIndex++;
+
+ /* The "Remaining length" is encoded from the second byte. */
+ pIndex = encodeRemainingLength( pIndex, remainingLength );
+
+ /* The first byte of a UTF-8 string is the high byte of the string length. */
+ *pIndex = UINT16_HIGH_BYTE( pPublishInfo->topicNameLength );
+ pIndex++;
+
+ /* The second byte of a UTF-8 string is the low byte of the string length. */
+ *pIndex = UINT16_LOW_BYTE( pPublishInfo->topicNameLength );
+ pIndex++;
+
+ *headerSize = headerLength;
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo,
+ size_t remainingLength,
+ uint16_t packetIdentifier,
+ const MQTTFixedBuffer_t * pFixedBuffer,
+ bool serializePayload )
+{
+ uint8_t * pIndex = NULL;
+ const uint8_t * pPayloadBuffer = NULL;
+
+ /* The first byte of a PUBLISH packet contains the packet type and flags. */
+ uint8_t publishFlags = MQTT_PACKET_TYPE_PUBLISH;
+
+ assert( pPublishInfo != NULL );
+ assert( pFixedBuffer != NULL );
+ assert( pFixedBuffer->pBuffer != NULL );
+ /* Packet Id should be non zero for Qos 1 and Qos 2. */
+ assert( ( pPublishInfo->qos == MQTTQoS0 ) || ( packetIdentifier != 0U ) );
+ /* Duplicate flag should be set only for Qos 1 or Qos 2. */
+ assert( ( pPublishInfo->dup != true ) || ( pPublishInfo->qos != MQTTQoS0 ) );
+
+ /* Get the start address of the buffer. */
+ pIndex = pFixedBuffer->pBuffer;
+
+ if( pPublishInfo->qos == MQTTQoS1 )
+ {
+ LogDebug( ( "Adding QoS as QoS1 in PUBLISH flags." ) );
+ UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 );
+ }
+ else if( pPublishInfo->qos == MQTTQoS2 )
+ {
+ LogDebug( ( "Adding QoS as QoS2 in PUBLISH flags." ) );
+ UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 );
+ }
+ else
+ {
+ /* Empty else MISRA 15.7 */
+ }
+
+ if( pPublishInfo->retain == true )
+ {
+ LogDebug( ( "Adding retain bit in PUBLISH flags." ) );
+ UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN );
+ }
+
+ if( pPublishInfo->dup == true )
+ {
+ LogDebug( ( "Adding dup bit in PUBLISH flags." ) );
+ UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP );
+ }
+
+ *pIndex = publishFlags;
+ pIndex++;
+
+ /* The "Remaining length" is encoded from the second byte. */
+ pIndex = encodeRemainingLength( pIndex, remainingLength );
+
+ /* The topic name is placed after the "Remaining length". */
+ pIndex = encodeString( pIndex,
+ pPublishInfo->pTopicName,
+ pPublishInfo->topicNameLength );
+
+ /* A packet identifier is required for QoS 1 and 2 messages. */
+ if( pPublishInfo->qos > MQTTQoS0 )
+ {
+ LogDebug( ( "Adding packet Id in PUBLISH packet." ) );
+ /* Place the packet identifier into the PUBLISH packet. */
+ *pIndex = UINT16_HIGH_BYTE( packetIdentifier );
+ pIndex[ 1U ] = UINT16_LOW_BYTE( packetIdentifier );
+ pIndex = &pIndex[ 2U ];
+ }
+
+ /* The payload is placed after the packet identifier.
+ * Payload is copied over only if required by the flag serializePayload.
+ * This will help reduce an unnecessary copy of the payload into the buffer.
+ */
+ if( ( pPublishInfo->payloadLength > 0U ) &&
+ ( serializePayload == true ) )
+ {
+ LogDebug( ( "Copying PUBLISH payload of length =%lu to buffer",
+ ( unsigned long ) pPublishInfo->payloadLength ) );
+
+ /* Typecast const void * typed payload buffer to const uint8_t *.
+ * This is to use same type buffers in memcpy. */
+ pPayloadBuffer = ( const uint8_t * ) pPublishInfo->pPayload;
+
+ ( void ) memcpy( pIndex, pPayloadBuffer, pPublishInfo->payloadLength );
+ /* Move the index to after the payload. */
+ pIndex = &pIndex[ pPublishInfo->payloadLength ];
+ }
+
+ /* Ensure that the difference between the end and beginning of the buffer
+ * is less than the buffer size. */
+ assert( ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) <= pFixedBuffer->size );
+}
+
+static size_t getRemainingLength( TransportRecv_t recvFunc,
+ NetworkContext_t * pNetworkContext )
+{
+ size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0;
+ uint8_t encodedByte = 0;
+ int32_t bytesReceived = 0;
+
+ /* This algorithm is copied from the MQTT v3.1.1 spec. */
+ do
+ {
+ if( multiplier > 2097152U ) /* 128 ^ 3 */
+ {
+ remainingLength = MQTT_REMAINING_LENGTH_INVALID;
+ }
+ else
+ {
+ bytesReceived = recvFunc( pNetworkContext, &encodedByte, 1U );
+
+ if( bytesReceived == 1 )
+ {
+ remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier;
+ multiplier *= 128U;
+ bytesDecoded++;
+ }
+ else
+ {
+ remainingLength = MQTT_REMAINING_LENGTH_INVALID;
+ }
+ }
+
+ if( remainingLength == MQTT_REMAINING_LENGTH_INVALID )
+ {
+ break;
+ }
+ } while( ( encodedByte & 0x80U ) != 0U );
+
+ /* Check that the decoded remaining length conforms to the MQTT specification. */
+ if( remainingLength != MQTT_REMAINING_LENGTH_INVALID )
+ {
+ expectedSize = remainingLengthEncodedSize( remainingLength );
+
+ if( bytesDecoded != expectedSize )
+ {
+ remainingLength = MQTT_REMAINING_LENGTH_INVALID;
+ }
+ }
+
+ return remainingLength;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t processRemainingLength( const uint8_t * pBuffer,
+ const size_t * pIndex,
+ MQTTPacketInfo_t * pIncomingPacket )
+{
+ size_t remainingLength = 0;
+ size_t multiplier = 1;
+ size_t bytesDecoded = 0;
+ size_t expectedSize = 0;
+ uint8_t encodedByte = 0;
+ MQTTStatus_t status = MQTTSuccess;
+
+ /* This algorithm is copied from the MQTT v3.1.1 spec. */
+ do
+ {
+ if( multiplier > 2097152U ) /* 128 ^ 3 */
+ {
+ remainingLength = MQTT_REMAINING_LENGTH_INVALID;
+
+ LogError( ( "Invalid remaining length in the packet.\n" ) );
+
+ status = MQTTBadResponse;
+ }
+ else
+ {
+ if( *pIndex > ( bytesDecoded + 1U ) )
+ {
+ /* Get the next byte. It is at the next position after the bytes
+ * decoded till now since the header of one byte was read before. */
+ encodedByte = pBuffer[ bytesDecoded + 1U ];
+
+ remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier;
+ multiplier *= 128U;
+ bytesDecoded++;
+ }
+ else
+ {
+ status = MQTTNeedMoreBytes;
+ }
+ }
+
+ /* If the response is incorrect, or no more data is available, then
+ * break out of the loop. */
+ if( ( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) ||
+ ( status != MQTTSuccess ) )
+ {
+ break;
+ }
+ } while( ( encodedByte & 0x80U ) != 0U );
+
+ if( status == MQTTSuccess )
+ {
+ /* Check that the decoded remaining length conforms to the MQTT specification. */
+ expectedSize = remainingLengthEncodedSize( remainingLength );
+
+ if( bytesDecoded != expectedSize )
+ {
+ LogError( ( "Expected and actual length of decoded bytes do not match.\n" ) );
+ status = MQTTBadResponse;
+ }
+ else
+ {
+ pIncomingPacket->remainingLength = remainingLength;
+ pIncomingPacket->headerLength = bytesDecoded + 1U;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static bool incomingPacketValid( uint8_t packetType )
+{
+ bool status = false;
+
+ /* Check packet type. Mask out lower bits to ignore flags. */
+ switch( packetType & 0xF0U )
+ {
+ /* Valid incoming packet types. */
+ case MQTT_PACKET_TYPE_CONNACK:
+ case MQTT_PACKET_TYPE_PUBLISH:
+ case MQTT_PACKET_TYPE_PUBACK:
+ case MQTT_PACKET_TYPE_PUBREC:
+ case MQTT_PACKET_TYPE_PUBCOMP:
+ case MQTT_PACKET_TYPE_SUBACK:
+ case MQTT_PACKET_TYPE_UNSUBACK:
+ case MQTT_PACKET_TYPE_PINGRESP:
+ status = true;
+ break;
+
+ case ( MQTT_PACKET_TYPE_PUBREL & 0xF0U ):
+
+ /* The second bit of a PUBREL must be set. */
+ if( ( packetType & 0x02U ) > 0U )
+ {
+ status = true;
+ }
+
+ break;
+
+ /* Any other packet type is invalid. */
+ default:
+ LogWarn( ( "Incoming packet invalid: Packet type=%u.",
+ ( unsigned int ) packetType ) );
+ break;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength,
+ MQTTQoS_t qos,
+ size_t qos0Minimum )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ /* Sanity checks for "Remaining length". */
+ if( qos == MQTTQoS0 )
+ {
+ /* Check that the "Remaining length" is greater than the minimum. */
+ if( remainingLength < qos0Minimum )
+ {
+ LogError( ( "QoS 0 PUBLISH cannot have a remaining length less than %lu.",
+ ( unsigned long ) qos0Minimum ) );
+
+ status = MQTTBadResponse;
+ }
+ }
+ else
+ {
+ /* Check that the "Remaining length" is greater than the minimum. For
+ * QoS 1 or 2, this will be two bytes greater than for QoS 0 due to the
+ * packet identifier. */
+ if( remainingLength < ( qos0Minimum + 2U ) )
+ {
+ LogError( ( "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.",
+ ( unsigned long ) ( qos0Minimum + 2U ) ) );
+
+ status = MQTTBadResponse;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t processPublishFlags( uint8_t publishFlags,
+ MQTTPublishInfo_t * pPublishInfo )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ assert( pPublishInfo != NULL );
+
+ /* Check for QoS 2. */
+ if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) )
+ {
+ /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */
+ if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) )
+ {
+ LogError( ( "Bad QoS: 3." ) );
+
+ status = MQTTBadResponse;
+ }
+ else
+ {
+ pPublishInfo->qos = MQTTQoS2;
+ }
+ }
+ /* Check for QoS 1. */
+ else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) )
+ {
+ pPublishInfo->qos = MQTTQoS1;
+ }
+ /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */
+ else
+ {
+ pPublishInfo->qos = MQTTQoS0;
+ }
+
+ if( status == MQTTSuccess )
+ {
+ LogDebug( ( "QoS is %d.", ( int ) pPublishInfo->qos ) );
+
+ /* Parse the Retain bit. */
+ pPublishInfo->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN );
+
+ LogDebug( ( "Retain bit is %d.", ( int ) pPublishInfo->retain ) );
+
+ /* Parse the DUP bit. */
+ pPublishInfo->dup = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP );
+
+ LogDebug( ( "DUP bit is %d.", ( int ) pPublishInfo->dup ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static void logConnackResponse( uint8_t responseCode )
+{
+ const char * const pConnackResponses[ 6 ] =
+ {
+ "Connection accepted.", /* 0 */
+ "Connection refused: unacceptable protocol version.", /* 1 */
+ "Connection refused: identifier rejected.", /* 2 */
+ "Connection refused: server unavailable", /* 3 */
+ "Connection refused: bad user name or password.", /* 4 */
+ "Connection refused: not authorized." /* 5 */
+ };
+
+ /* Avoid unused parameter warning when assert and logs are disabled. */
+ ( void ) responseCode;
+ ( void ) pConnackResponses;
+
+ assert( responseCode <= 5U );
+
+ if( responseCode == 0u )
+ {
+ /* Log at Debug level for a success CONNACK response. */
+ LogDebug( ( "%s", pConnackResponses[ 0 ] ) );
+ }
+ else
+ {
+ /* Log an error based on the CONNACK response code. */
+ LogError( ( "%s", pConnackResponses[ responseCode ] ) );
+ }
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * pConnack,
+ bool * pSessionPresent )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ const uint8_t * pRemainingData = NULL;
+
+ assert( pConnack != NULL );
+ assert( pSessionPresent != NULL );
+ pRemainingData = pConnack->pRemainingData;
+
+ /* According to MQTT 3.1.1, the second byte of CONNACK must specify a
+ * "Remaining length" of 2. */
+ if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH )
+ {
+ LogError( ( "CONNACK does not have remaining length of %u.",
+ ( unsigned int ) MQTT_PACKET_CONNACK_REMAINING_LENGTH ) );
+
+ status = MQTTBadResponse;
+ }
+
+ /* Check the reserved bits in CONNACK. The high 7 bits of the third byte
+ * in CONNACK must be 0. */
+ else if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U )
+ {
+ LogError( ( "Reserved bits in CONNACK incorrect." ) );
+
+ status = MQTTBadResponse;
+ }
+ else
+ {
+ /* Determine if the "Session Present" bit is set. This is the lowest bit of
+ * the third byte in CONNACK. */
+ if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK )
+ == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK )
+ {
+ LogDebug( ( "CONNACK session present bit set." ) );
+ *pSessionPresent = true;
+
+ /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the
+ * "Session Present" bit is set. */
+ if( pRemainingData[ 1 ] != 0U )
+ {
+ LogError( ( "Session Present bit is set, but connect return code in CONNACK is %u (nonzero).",
+ ( unsigned int ) pRemainingData[ 1 ] ) );
+ status = MQTTBadResponse;
+ }
+ }
+ else
+ {
+ LogDebug( ( "CONNACK session present bit not set." ) );
+ *pSessionPresent = false;
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */
+ if( pRemainingData[ 1 ] > 5U )
+ {
+ LogError( ( "CONNACK response %u is invalid.",
+ ( unsigned int ) pRemainingData[ 1 ] ) );
+
+ status = MQTTBadResponse;
+ }
+ else
+ {
+ /* Print the appropriate message for the CONNACK response code if logs are
+ * enabled. */
+ logConnackResponse( pRemainingData[ 1 ] );
+
+ /* A nonzero CONNACK response code means the connection was refused. */
+ if( pRemainingData[ 1 ] > 0U )
+ {
+ status = MQTTServerRefused;
+ }
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ size_t * pRemainingLength,
+ size_t * pPacketSize,
+ MQTTSubscriptionType_t subscriptionType )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ size_t i = 0, packetSize = 0;
+
+ assert( pSubscriptionList != NULL );
+ assert( subscriptionCount != 0U );
+ assert( pRemainingLength != NULL );
+ assert( pPacketSize != NULL );
+
+ /* The variable header of a subscription packet consists of a 2-byte packet
+ * identifier. */
+ packetSize += sizeof( uint16_t );
+
+ /* Sum the lengths of all subscription topic filters; add 1 byte for each
+ * subscription's QoS if type is MQTT_SUBSCRIBE. */
+ for( i = 0; i < subscriptionCount; i++ )
+ {
+ /* Add the length of the topic filter. MQTT strings are prepended
+ * with 2 byte string length field. Hence 2 bytes are added to size. */
+ packetSize += pSubscriptionList[ i ].topicFilterLength + sizeof( uint16_t );
+
+ /* Only SUBSCRIBE packets include the QoS. */
+ if( subscriptionType == MQTT_SUBSCRIBE )
+ {
+ packetSize += 1U;
+ }
+
+ /* Validate each topic filter. */
+ if( ( pSubscriptionList[ i ].topicFilterLength == 0U ) ||
+ ( pSubscriptionList[ i ].pTopicFilter == NULL ) )
+ {
+ status = MQTTBadParameter;
+ LogError( ( "Subscription #%lu in %sSUBSCRIBE packet cannot be empty.",
+ ( unsigned long ) i,
+ ( subscriptionType == MQTT_SUBSCRIBE ) ? "" : "UN" ) );
+ /* It is not necessary to break as an error status has already been set. */
+ }
+ }
+
+ /* At this point, the "Remaining length" has been calculated. Return error
+ * if the "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise,
+ * set the output parameter.*/
+ if( packetSize > MQTT_MAX_REMAINING_LENGTH )
+ {
+ LogError( ( "Subscription packet length of %lu exceeds"
+ "the MQTT 3.1.1 maximum packet length of %lu.",
+ ( unsigned long ) packetSize,
+ MQTT_MAX_REMAINING_LENGTH ) );
+ status = MQTTBadParameter;
+ }
+
+ if( status == MQTTSuccess )
+ {
+ *pRemainingLength = packetSize;
+
+ /* Calculate the full size of the subscription packet by adding
+ * number of bytes required to encode the "Remaining length" field
+ * plus 1 byte for the "Packet type" field. */
+ packetSize += 1U + remainingLengthEncodedSize( packetSize );
+
+ /*Set the pPacketSize output parameter. */
+ *pPacketSize = packetSize;
+ }
+
+ LogDebug( ( "Subscription packet remaining length=%lu and packet size=%lu.",
+ ( unsigned long ) *pRemainingLength,
+ ( unsigned long ) *pPacketSize ) );
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t readSubackStatus( size_t statusCount,
+ const uint8_t * pStatusStart )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ uint8_t subscriptionStatus = 0;
+ size_t i = 0;
+
+ assert( pStatusStart != NULL );
+
+ /* Iterate through each status byte in the SUBACK packet. */
+ for( i = 0; i < statusCount; i++ )
+ {
+ /* Read a single status byte in SUBACK. */
+ subscriptionStatus = pStatusStart[ i ];
+
+ /* MQTT 3.1.1 defines the following values as status codes. */
+ switch( subscriptionStatus )
+ {
+ case 0x00:
+ case 0x01:
+ case 0x02:
+
+ LogDebug( ( "Topic filter %lu accepted, max QoS %u.",
+ ( unsigned long ) i,
+ ( unsigned int ) subscriptionStatus ) );
+ break;
+
+ case 0x80:
+
+ LogWarn( ( "Topic filter %lu refused.", ( unsigned long ) i ) );
+
+ /* Application should remove subscription from the list */
+ status = MQTTServerRefused;
+
+ break;
+
+ default:
+ LogError( ( "Bad SUBSCRIBE status %u.",
+ ( unsigned int ) subscriptionStatus ) );
+
+ status = MQTTBadResponse;
+
+ break;
+ }
+
+ /* Stop parsing the subscription statuses if a bad response was received. */
+ if( status == MQTTBadResponse )
+ {
+ break;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * pSuback,
+ uint16_t * pPacketIdentifier )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ size_t remainingLength;
+ const uint8_t * pVariableHeader = NULL;
+
+ assert( pSuback != NULL );
+ assert( pPacketIdentifier != NULL );
+
+ remainingLength = pSuback->remainingLength;
+ pVariableHeader = pSuback->pRemainingData;
+
+ /* A SUBACK must have a remaining length of at least 3 to accommodate the
+ * packet identifier and at least 1 return code. */
+ if( remainingLength < 3U )
+ {
+ LogError( ( "SUBACK cannot have a remaining length less than 3." ) );
+ status = MQTTBadResponse;
+ }
+ else
+ {
+ /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */
+ *pPacketIdentifier = UINT16_DECODE( pVariableHeader );
+
+ LogDebug( ( "Packet identifier %hu.",
+ ( unsigned short ) *pPacketIdentifier ) );
+
+ if( *pPacketIdentifier == 0U )
+ {
+ status = MQTTBadResponse;
+ }
+ else
+ {
+ status = readSubackStatus( remainingLength - sizeof( uint16_t ),
+ &pVariableHeader[ sizeof( uint16_t ) ] );
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ size_t packetSize = 0;
+
+ /* Validate all the parameters. */
+ if( ( pFixedBuffer == NULL ) || ( pSubscriptionList == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, "
+ "pSubscriptionList=%p.",
+ ( void * ) pFixedBuffer,
+ ( void * ) pSubscriptionList ) );
+ status = MQTTBadParameter;
+ }
+ /* A buffer must be configured for serialization. */
+ else if( pFixedBuffer->pBuffer == NULL )
+ {
+ LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( subscriptionCount == 0U )
+ {
+ LogError( ( "Subscription count is 0." ) );
+ status = MQTTBadParameter;
+ }
+ else if( packetId == 0U )
+ {
+ LogError( ( "Packet Id for subscription packet is 0." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* The serialized packet size = First byte
+ * + length of encoded size of remaining length
+ * + remaining length. */
+ packetSize = 1U + remainingLengthEncodedSize( remainingLength )
+ + remainingLength;
+
+ if( packetSize > pFixedBuffer->size )
+ {
+ LogError( ( "Buffer size of %lu is not sufficient to hold "
+ "serialized packet of size of %lu.",
+ ( unsigned long ) pFixedBuffer->size,
+ ( unsigned long ) packetSize ) );
+ status = MQTTNoMemory;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket,
+ uint16_t * pPacketId,
+ MQTTPublishInfo_t * pPublishInfo )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ const uint8_t * pVariableHeader, * pPacketIdentifierHigh = NULL;
+
+ assert( pIncomingPacket != NULL );
+ assert( pPacketId != NULL );
+ assert( pPublishInfo != NULL );
+ assert( pIncomingPacket->pRemainingData != NULL );
+
+ pVariableHeader = pIncomingPacket->pRemainingData;
+ /* The flags are the lower 4 bits of the first byte in PUBLISH. */
+ status = processPublishFlags( ( pIncomingPacket->type & 0x0FU ), pPublishInfo );
+
+ if( status == MQTTSuccess )
+ {
+ /* Sanity checks for "Remaining length". A QoS 0 PUBLISH must have a remaining
+ * length of at least 3 to accommodate topic name length (2 bytes) and topic
+ * name (at least 1 byte). A QoS 1 or 2 PUBLISH must have a remaining length of
+ * at least 5 for the packet identifier in addition to the topic name length and
+ * topic name. */
+ status = checkPublishRemainingLength( pIncomingPacket->remainingLength,
+ pPublishInfo->qos,
+ MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Extract the topic name starting from the first byte of the variable header.
+ * The topic name string starts at byte 3 in the variable header. */
+ pPublishInfo->topicNameLength = UINT16_DECODE( pVariableHeader );
+
+ /* Sanity checks for topic name length and "Remaining length". The remaining
+ * length must be at least as large as the variable length header. */
+ status = checkPublishRemainingLength( pIncomingPacket->remainingLength,
+ pPublishInfo->qos,
+ pPublishInfo->topicNameLength + sizeof( uint16_t ) );
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Parse the topic. */
+ pPublishInfo->pTopicName = ( const char * ) ( &pVariableHeader[ sizeof( uint16_t ) ] );
+ LogDebug( ( "Topic name length: %hu.", ( unsigned short ) pPublishInfo->topicNameLength ) );
+
+ /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet
+ * identifier starts immediately after the topic name. */
+ pPacketIdentifierHigh = ( const uint8_t * ) ( &pPublishInfo->pTopicName[ pPublishInfo->topicNameLength ] );
+
+ if( pPublishInfo->qos > MQTTQoS0 )
+ {
+ *pPacketId = UINT16_DECODE( pPacketIdentifierHigh );
+
+ LogDebug( ( "Packet identifier %hu.",
+ ( unsigned short ) *pPacketId ) );
+
+ /* Advance pointer two bytes to start of payload as in the QoS 0 case. */
+ pPacketIdentifierHigh = &pPacketIdentifierHigh[ sizeof( uint16_t ) ];
+
+ /* Packet identifier cannot be 0. */
+ if( *pPacketId == 0U )
+ {
+ LogError( ( "Packet identifier cannot be 0." ) );
+ status = MQTTBadResponse;
+ }
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain
+ * a packet identifier, but QoS 0 PUBLISH packets do not. */
+ pPublishInfo->payloadLength = pIncomingPacket->remainingLength - pPublishInfo->topicNameLength - sizeof( uint16_t );
+
+ if( pPublishInfo->qos != MQTTQoS0 )
+ {
+ /* Two more bytes for the packet identifier. */
+ pPublishInfo->payloadLength -= sizeof( uint16_t );
+ }
+
+ /* Set payload if it exists. */
+ pPublishInfo->pPayload = ( pPublishInfo->payloadLength != 0U ) ? pPacketIdentifierHigh : NULL;
+
+ LogDebug( ( "Payload length %lu.",
+ ( unsigned long ) pPublishInfo->payloadLength ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * pAck,
+ uint16_t * pPacketIdentifier )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ assert( pAck != NULL );
+ assert( pPacketIdentifier != NULL );
+
+ /* Check that the "Remaining length" of the received ACK is 2. */
+ if( pAck->remainingLength != MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH )
+ {
+ LogError( ( "ACK does not have remaining length of %u.",
+ ( unsigned int ) MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ) );
+
+ status = MQTTBadResponse;
+ }
+ else
+ {
+ /* Extract the packet identifier (third and fourth bytes) from ACK. */
+ *pPacketIdentifier = UINT16_DECODE( pAck->pRemainingData );
+
+ LogDebug( ( "Packet identifier %hu.",
+ ( unsigned short ) *pPacketIdentifier ) );
+
+ /* Packet identifier cannot be 0. */
+ if( *pPacketIdentifier == 0U )
+ {
+ LogError( ( "Packet identifier cannot be 0." ) );
+ status = MQTTBadResponse;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * pPingresp )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ assert( pPingresp != NULL );
+
+ /* Check the "Remaining length" (second byte) of the received PINGRESP is 0. */
+ if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH )
+ {
+ LogError( ( "PINGRESP does not have remaining length of %u.",
+ MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) );
+
+ status = MQTTBadResponse;
+ }
+
+ return status;
+}
+
+uint8_t * MQTT_SerializeConnectFixedHeader( uint8_t * pIndex,
+ const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ size_t remainingLength )
+{
+ uint8_t * pIndexLocal = pIndex;
+ uint8_t connectFlags = 0U;
+
+ /* The first byte in the CONNECT packet is the control packet type. */
+ *pIndexLocal = MQTT_PACKET_TYPE_CONNECT;
+ pIndexLocal++;
+
+ /* The remaining length of the CONNECT packet is encoded starting from the
+ * second byte. The remaining length does not include the length of the fixed
+ * header or the encoding of the remaining length. */
+ pIndexLocal = encodeRemainingLength( pIndexLocal, remainingLength );
+
+ /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable
+ * header. This string is 4 bytes long. */
+ pIndexLocal = encodeString( pIndexLocal, "MQTT", 4 );
+
+ /* The MQTT protocol version is the second field of the variable header. */
+ *pIndexLocal = MQTT_VERSION_3_1_1;
+ pIndexLocal++;
+
+ /* Set the clean session flag if needed. */
+ if( pConnectInfo->cleanSession == true )
+ {
+ UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN );
+ }
+
+ /* Set the flags for username and password if provided. */
+ if( pConnectInfo->pUserName != NULL )
+ {
+ UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME );
+ }
+
+ if( pConnectInfo->pPassword != NULL )
+ {
+ UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD );
+ }
+
+ /* Set will flag if a Last Will and Testament is provided. */
+ if( pWillInfo != NULL )
+ {
+ UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL );
+
+ /* Flags only need to be changed for Will QoS 1 or 2. */
+ if( pWillInfo->qos == MQTTQoS1 )
+ {
+ UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 );
+ }
+ else if( pWillInfo->qos == MQTTQoS2 )
+ {
+ UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 );
+ }
+ else
+ {
+ /* Empty else MISRA 15.7 */
+ }
+
+ if( pWillInfo->retain == true )
+ {
+ UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN );
+ }
+ }
+
+ *pIndexLocal = connectFlags;
+ pIndexLocal++;
+
+ /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */
+ pIndexLocal[ 0 ] = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds );
+ pIndexLocal[ 1 ] = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds );
+ pIndexLocal = &pIndexLocal[ 2 ];
+
+ return pIndexLocal;
+}
+/*-----------------------------------------------------------*/
+
+static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer )
+{
+ uint8_t * pIndex = NULL;
+
+ assert( pConnectInfo != NULL );
+ assert( pFixedBuffer != NULL );
+ assert( pFixedBuffer->pBuffer != NULL );
+
+ pIndex = pFixedBuffer->pBuffer;
+
+ /* Serialize the header. */
+ pIndex = MQTT_SerializeConnectFixedHeader( pIndex,
+ pConnectInfo,
+ pWillInfo,
+ remainingLength );
+
+ /* Write the client identifier into the CONNECT packet. */
+ pIndex = encodeString( pIndex,
+ pConnectInfo->pClientIdentifier,
+ pConnectInfo->clientIdentifierLength );
+
+ /* Write the will topic name and message into the CONNECT packet if provided. */
+ if( pWillInfo != NULL )
+ {
+ pIndex = encodeString( pIndex,
+ pWillInfo->pTopicName,
+ pWillInfo->topicNameLength );
+
+ pIndex = encodeString( pIndex,
+ pWillInfo->pPayload,
+ ( uint16_t ) pWillInfo->payloadLength );
+ }
+
+ /* Encode the user name if provided. */
+ if( pConnectInfo->pUserName != NULL )
+ {
+ pIndex = encodeString( pIndex, pConnectInfo->pUserName, pConnectInfo->userNameLength );
+ }
+
+ /* Encode the password if provided. */
+ if( pConnectInfo->pPassword != NULL )
+ {
+ pIndex = encodeString( pIndex, pConnectInfo->pPassword, pConnectInfo->passwordLength );
+ }
+
+ LogDebug( ( "Length of serialized CONNECT packet is %lu.",
+ ( ( unsigned long ) ( pIndex - pFixedBuffer->pBuffer ) ) ) );
+
+ /* Ensure that the difference between the end and beginning of the buffer
+ * is less than the buffer size. */
+ assert( ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) <= pFixedBuffer->size );
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ size_t * pRemainingLength,
+ size_t * pPacketSize )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ size_t remainingLength;
+
+ /* The CONNECT packet will always include a 10-byte variable header. */
+ size_t connectPacketSize = MQTT_PACKET_CONNECT_HEADER_SIZE;
+
+ /* Validate arguments. */
+ if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) ||
+ ( pPacketSize == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pConnectInfo=%p, "
+ "pRemainingLength=%p, pPacketSize=%p.",
+ ( void * ) pConnectInfo,
+ ( void * ) pRemainingLength,
+ ( void * ) pPacketSize ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) )
+ {
+ LogError( ( "Mqtt_GetConnectPacketSize() client identifier must be set." ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pWillInfo != NULL ) && ( pWillInfo->payloadLength > ( size_t ) UINT16_MAX ) )
+ {
+ /* The MQTTPublishInfo_t is reused for the will message. The payload
+ * length for any other message could be larger than 65,535, but
+ * the will message length is required to be represented in 2 bytes.
+ * By bounding the payloadLength of the will message, the CONNECT
+ * packet will never be larger than 327699 bytes. */
+ LogError( ( "The Will Message length must not exceed %d. "
+ "pWillInfo->payloadLength=%lu.",
+ UINT16_MAX,
+ ( unsigned long ) pWillInfo->payloadLength ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Add the length of the client identifier. */
+ connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t );
+
+ /* Add the lengths of the will message and topic name if provided. */
+ if( pWillInfo != NULL )
+ {
+ connectPacketSize += pWillInfo->topicNameLength + sizeof( uint16_t ) +
+ pWillInfo->payloadLength + sizeof( uint16_t );
+ }
+
+ /* Add the lengths of the user name and password if provided. */
+ if( pConnectInfo->pUserName != NULL )
+ {
+ connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t );
+ }
+
+ if( pConnectInfo->pPassword != NULL )
+ {
+ connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t );
+ }
+
+ /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has
+ * been calculated. */
+ remainingLength = connectPacketSize;
+
+ /* Calculate the full size of the MQTT CONNECT packet by adding the size of
+ * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */
+ connectPacketSize += 1U + remainingLengthEncodedSize( connectPacketSize );
+
+ /* The connectPacketSize calculated from this function's parameters is
+ * guaranteed to be less than the maximum MQTT CONNECT packet size, which
+ * is 327700. If the maximum client identifier length, the maximum will
+ * message topic length, the maximum will topic payload length, the
+ * maximum username length, and the maximum password length are all present
+ * in the MQTT CONNECT packet, the total size will be calculated to be
+ * 327699:
+ * (variable length header)10 +
+ * (maximum client identifier length) 65535 + (encoded length) 2 +
+ * (maximum will message topic name length) 65535 + (encoded length)2 +
+ * (maximum will message payload length) 65535 + 2 +
+ * (maximum username length) 65535 + (encoded length) 2 +
+ * (maximum password length) 65535 + (encoded length) 2 +
+ * (packet type field length) 1 +
+ * (CONNECT packet encoded length) 3 = 327699 */
+
+ *pRemainingLength = remainingLength;
+ *pPacketSize = connectPacketSize;
+
+ LogDebug( ( "CONNECT packet remaining length=%lu and packet size=%lu.",
+ ( unsigned long ) *pRemainingLength,
+ ( unsigned long ) *pPacketSize ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ size_t connectPacketSize = 0;
+
+ /* Validate arguments. */
+ if( ( pConnectInfo == NULL ) || ( pFixedBuffer == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pConnectInfo=%p, "
+ "pFixedBuffer=%p.",
+ ( void * ) pConnectInfo,
+ ( void * ) pFixedBuffer ) );
+ status = MQTTBadParameter;
+ }
+ /* A buffer must be configured for serialization. */
+ else if( pFixedBuffer->pBuffer == NULL )
+ {
+ LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pWillInfo != NULL ) && ( pWillInfo->pTopicName == NULL ) )
+ {
+ LogError( ( "pWillInfo->pTopicName cannot be NULL if Will is present." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Calculate CONNECT packet size. Overflow in in this addition is not checked
+ * because it is part of the API contract to call Mqtt_GetConnectPacketSize()
+ * before this function. */
+ connectPacketSize = remainingLength + remainingLengthEncodedSize( remainingLength ) + 1U;
+
+ /* Check that the full packet size fits within the given buffer. */
+ if( connectPacketSize > pFixedBuffer->size )
+ {
+ LogError( ( "Buffer size of %lu is not sufficient to hold "
+ "serialized CONNECT packet of size of %lu.",
+ ( unsigned long ) pFixedBuffer->size,
+ ( unsigned long ) connectPacketSize ) );
+ status = MQTTNoMemory;
+ }
+ else
+ {
+ serializeConnectPacket( pConnectInfo,
+ pWillInfo,
+ remainingLength,
+ pFixedBuffer );
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ size_t * pRemainingLength,
+ size_t * pPacketSize )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ /* Validate parameters. */
+ if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) ||
+ ( pPacketSize == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pSubscriptionList=%p, "
+ "pRemainingLength=%p, pPacketSize=%p.",
+ ( void * ) pSubscriptionList,
+ ( void * ) pRemainingLength,
+ ( void * ) pPacketSize ) );
+ status = MQTTBadParameter;
+ }
+ else if( subscriptionCount == 0U )
+ {
+ LogError( ( "subscriptionCount is 0." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Calculate the MQTT SUBSCRIBE packet size. */
+ status = calculateSubscriptionPacketSize( pSubscriptionList,
+ subscriptionCount,
+ pRemainingLength,
+ pPacketSize,
+ MQTT_SUBSCRIBE );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+uint8_t * MQTT_SerializeSubscribeHeader( size_t remainingLength,
+ uint8_t * pIndex,
+ uint16_t packetId )
+{
+ uint8_t * pIterator = pIndex;
+
+ /* The first byte in SUBSCRIBE is the packet type. */
+ *pIterator = MQTT_PACKET_TYPE_SUBSCRIBE;
+ pIterator++;
+
+ /* Encode the "Remaining length" starting from the second byte. */
+ pIterator = encodeRemainingLength( pIterator, remainingLength );
+
+ /* Place the packet identifier into the SUBSCRIBE packet. */
+ pIterator[ 0 ] = UINT16_HIGH_BYTE( packetId );
+ pIterator[ 1 ] = UINT16_LOW_BYTE( packetId );
+ /* Advance the pointer. */
+ pIterator = &pIterator[ 2 ];
+
+ return pIterator;
+}
+
+/*-----------------------------------------------------------*/
+
+uint8_t * MQTT_SerializeUnsubscribeHeader( size_t remainingLength,
+ uint8_t * pIndex,
+ uint16_t packetId )
+{
+ uint8_t * pIterator = pIndex;
+
+ /* The first byte in UNSUBSCRIBE is the packet type. */
+ *pIterator = MQTT_PACKET_TYPE_UNSUBSCRIBE;
+ pIterator++;
+
+ /* Encode the "Remaining length" starting from the second byte. */
+ pIterator = encodeRemainingLength( pIterator, remainingLength );
+
+ /* Place the packet identifier into the SUBSCRIBE packet. */
+ pIterator[ 0 ] = UINT16_HIGH_BYTE( packetId );
+ pIterator[ 1 ] = UINT16_LOW_BYTE( packetId );
+ /* Increment the pointer. */
+ pIterator = &pIterator[ 2 ];
+
+ return pIterator;
+}
+
+MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer )
+{
+ size_t i = 0;
+ uint8_t * pIndex = NULL;
+
+ /* Validate all the parameters. */
+ MQTTStatus_t status =
+ validateSubscriptionSerializeParams( pSubscriptionList,
+ subscriptionCount,
+ packetId,
+ remainingLength,
+ pFixedBuffer );
+
+ if( status == MQTTSuccess )
+ {
+ pIndex = pFixedBuffer->pBuffer;
+
+ pIndex = MQTT_SerializeSubscribeHeader( remainingLength,
+ pIndex,
+ packetId );
+
+ /* Serialize each subscription topic filter and QoS. */
+ for( i = 0; i < subscriptionCount; i++ )
+ {
+ pIndex = encodeString( pIndex,
+ pSubscriptionList[ i ].pTopicFilter,
+ pSubscriptionList[ i ].topicFilterLength );
+
+ /* Place the QoS in the SUBSCRIBE packet. */
+ *pIndex = ( uint8_t ) ( pSubscriptionList[ i ].qos );
+ pIndex++;
+ }
+
+ LogDebug( ( "Length of serialized SUBSCRIBE packet is %lu.",
+ ( ( unsigned long ) ( pIndex - pFixedBuffer->pBuffer ) ) ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ size_t * pRemainingLength,
+ size_t * pPacketSize )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ /* Validate parameters. */
+ if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) ||
+ ( pPacketSize == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pSubscriptionList=%p, "
+ "pRemainingLength=%p, pPacketSize=%p.",
+ ( void * ) pSubscriptionList,
+ ( void * ) pRemainingLength,
+ ( void * ) pPacketSize ) );
+ status = MQTTBadParameter;
+ }
+ else if( subscriptionCount == 0U )
+ {
+ LogError( ( "Subscription count is 0." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Calculate the MQTT UNSUBSCRIBE packet size. */
+ status = calculateSubscriptionPacketSize( pSubscriptionList,
+ subscriptionCount,
+ pRemainingLength,
+ pPacketSize,
+ MQTT_UNSUBSCRIBE );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ size_t i = 0;
+ uint8_t * pIndex = NULL;
+
+ /* Validate all the parameters. */
+ status = validateSubscriptionSerializeParams( pSubscriptionList,
+ subscriptionCount,
+ packetId,
+ remainingLength,
+ pFixedBuffer );
+
+ if( status == MQTTSuccess )
+ {
+ /* Get the start of the buffer to the iterator variable. */
+ pIndex = pFixedBuffer->pBuffer;
+
+ pIndex = MQTT_SerializeUnsubscribeHeader( remainingLength, pIndex, packetId );
+
+ /* Serialize each subscription topic filter. */
+ for( i = 0; i < subscriptionCount; i++ )
+ {
+ pIndex = encodeString( pIndex,
+ pSubscriptionList[ i ].pTopicFilter,
+ pSubscriptionList[ i ].topicFilterLength );
+ }
+
+ LogDebug( ( "Length of serialized UNSUBSCRIBE packet is %lu.",
+ ( ( unsigned long ) ( pIndex - pFixedBuffer->pBuffer ) ) ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo,
+ size_t * pRemainingLength,
+ size_t * pPacketSize )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pPublishInfo=%p, "
+ "pRemainingLength=%p, pPacketSize=%p.",
+ ( void * ) pPublishInfo,
+ ( void * ) pRemainingLength,
+ ( void * ) pPacketSize ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) )
+ {
+ LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, "
+ "topicNameLength=%hu.",
+ ( void * ) pPublishInfo->pTopicName,
+ ( unsigned short ) pPublishInfo->topicNameLength ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Calculate the "Remaining length" field and total packet size. If it exceeds
+ * what is allowed in the MQTT standard, return an error. */
+ if( calculatePublishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false )
+ {
+ LogError( ( "PUBLISH packet remaining length exceeds %lu, which is the "
+ "maximum size allowed by MQTT 3.1.1.",
+ MQTT_MAX_REMAINING_LENGTH ) );
+ status = MQTTBadParameter;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo,
+ uint16_t packetId,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ size_t packetSize = 0;
+
+ if( ( pFixedBuffer == NULL ) || ( pPublishInfo == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, "
+ "pPublishInfo=%p.",
+ ( void * ) pFixedBuffer,
+ ( void * ) pPublishInfo ) );
+ status = MQTTBadParameter;
+ }
+ /* A buffer must be configured for serialization. */
+ else if( pFixedBuffer->pBuffer == NULL )
+ {
+ LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) );
+ status = MQTTBadParameter;
+ }
+
+ /* For serializing a publish, if there exists a payload, then the buffer
+ * cannot be NULL. */
+ else if( ( pPublishInfo->payloadLength > 0U ) && ( pPublishInfo->pPayload == NULL ) )
+ {
+ LogError( ( "A nonzero payload length requires a non-NULL payload: "
+ "payloadLength=%lu, pPayload=%p.",
+ ( unsigned long ) pPublishInfo->payloadLength,
+ pPublishInfo->pPayload ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) )
+ {
+ LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, "
+ "topicNameLength=%hu.",
+ ( void * ) pPublishInfo->pTopicName,
+ ( unsigned short ) pPublishInfo->topicNameLength ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) )
+ {
+ LogError( ( "Packet ID is 0 for PUBLISH with QoS=%u.",
+ ( unsigned int ) pPublishInfo->qos ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pPublishInfo->dup == true ) && ( pPublishInfo->qos == MQTTQoS0 ) )
+ {
+ LogError( ( "Duplicate flag is set for PUBLISH with Qos 0." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Length of serialized packet = First byte
+ * + Length of encoded remaining length
+ * + Remaining length. */
+ packetSize = 1U + remainingLengthEncodedSize( remainingLength )
+ + remainingLength;
+ }
+
+ if( ( status == MQTTSuccess ) && ( packetSize > pFixedBuffer->size ) )
+ {
+ LogError( ( "Buffer size of %lu is not sufficient to hold "
+ "serialized PUBLISH packet of size of %lu.",
+ ( unsigned long ) pFixedBuffer->size,
+ ( unsigned long ) packetSize ) );
+ status = MQTTNoMemory;
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Serialize publish with header and payload. */
+ serializePublishCommon( pPublishInfo,
+ remainingLength,
+ packetId,
+ pFixedBuffer,
+ true );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo,
+ uint16_t packetId,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer,
+ size_t * pHeaderSize )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ size_t packetSize = 0;
+
+ if( ( pFixedBuffer == NULL ) || ( pPublishInfo == NULL ) ||
+ ( pHeaderSize == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, "
+ "pPublishInfo=%p, pHeaderSize=%p.",
+ ( void * ) pFixedBuffer,
+ ( void * ) pPublishInfo,
+ ( void * ) pHeaderSize ) );
+ status = MQTTBadParameter;
+ }
+ /* A buffer must be configured for serialization. */
+ else if( pFixedBuffer->pBuffer == NULL )
+ {
+ LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) )
+ {
+ LogError( ( "Invalid topic name for publish: pTopicName=%p, "
+ "topicNameLength=%hu.",
+ ( void * ) pPublishInfo->pTopicName,
+ ( unsigned short ) pPublishInfo->topicNameLength ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) )
+ {
+ LogError( ( "Packet Id is 0 for publish with QoS=%hu.",
+ ( unsigned short ) pPublishInfo->qos ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pPublishInfo->dup == true ) && ( pPublishInfo->qos == MQTTQoS0 ) )
+ {
+ LogError( ( "Duplicate flag is set for PUBLISH with Qos 0." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Length of serialized packet = First byte
+ * + Length of encoded remaining length
+ * + Remaining length
+ * - Payload Length.
+ */
+ packetSize = 1U + remainingLengthEncodedSize( remainingLength )
+ + remainingLength
+ - pPublishInfo->payloadLength;
+ }
+
+ if( ( status == MQTTSuccess ) && ( packetSize > pFixedBuffer->size ) )
+ {
+ LogError( ( "Buffer size of %lu is not sufficient to hold "
+ "serialized PUBLISH header packet of size of %lu.",
+ ( unsigned long ) pFixedBuffer->size,
+ ( unsigned long ) ( packetSize - pPublishInfo->payloadLength ) ) );
+ status = MQTTNoMemory;
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Serialize publish without copying the payload. */
+ serializePublishCommon( pPublishInfo,
+ remainingLength,
+ packetId,
+ pFixedBuffer,
+ false );
+
+ /* Header size is the same as calculated packet size. */
+ *pHeaderSize = packetSize;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer,
+ uint8_t packetType,
+ uint16_t packetId )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( pFixedBuffer == NULL )
+ {
+ LogError( ( "Provided buffer is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( pFixedBuffer->pBuffer == NULL )
+ {
+ LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) );
+ status = MQTTBadParameter;
+ }
+ /* The buffer must be able to fit 4 bytes for the packet. */
+ else if( pFixedBuffer->size < MQTT_PUBLISH_ACK_PACKET_SIZE )
+ {
+ LogError( ( "Insufficient memory for packet." ) );
+ status = MQTTNoMemory;
+ }
+ else if( packetId == 0U )
+ {
+ LogError( ( "Packet ID cannot be 0." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ switch( packetType )
+ {
+ /* Only publish acks are serialized by the client. */
+ case MQTT_PACKET_TYPE_PUBACK:
+ case MQTT_PACKET_TYPE_PUBREC:
+ case MQTT_PACKET_TYPE_PUBREL:
+ case MQTT_PACKET_TYPE_PUBCOMP:
+ pFixedBuffer->pBuffer[ 0 ] = packetType;
+ pFixedBuffer->pBuffer[ 1 ] = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH;
+ pFixedBuffer->pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetId );
+ pFixedBuffer->pBuffer[ 3 ] = UINT16_LOW_BYTE( packetId );
+ break;
+
+ default:
+ LogError( ( "Packet type is not a publish ACK: Packet type=%02x",
+ ( unsigned int ) packetType ) );
+ status = MQTTBadParameter;
+ break;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( pPacketSize == NULL )
+ {
+ LogError( ( "pPacketSize is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* MQTT DISCONNECT packets always have the same size. */
+ *pPacketSize = MQTT_DISCONNECT_PACKET_SIZE;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ /* Validate arguments. */
+ if( pFixedBuffer == NULL )
+ {
+ LogError( ( "pFixedBuffer cannot be NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( pFixedBuffer->pBuffer == NULL )
+ {
+ LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Empty else MISRA 15.7 */
+ }
+
+ if( status == MQTTSuccess )
+ {
+ if( pFixedBuffer->size < MQTT_DISCONNECT_PACKET_SIZE )
+ {
+ LogError( ( "Buffer size of %lu is not sufficient to hold "
+ "serialized DISCONNECT packet of size of %lu.",
+ ( unsigned long ) pFixedBuffer->size,
+ MQTT_DISCONNECT_PACKET_SIZE ) );
+ status = MQTTNoMemory;
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT;
+ pFixedBuffer->pBuffer[ 1 ] = MQTT_DISCONNECT_REMAINING_LENGTH;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( pPacketSize == NULL )
+ {
+ LogError( ( "pPacketSize is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* MQTT PINGREQ packets always have the same size. */
+ *pPacketSize = MQTT_PACKET_PINGREQ_SIZE;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( pFixedBuffer == NULL )
+ {
+ LogError( ( "pFixedBuffer is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( pFixedBuffer->pBuffer == NULL )
+ {
+ LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Empty else MISRA 15.7 */
+ }
+
+ if( status == MQTTSuccess )
+ {
+ if( pFixedBuffer->size < MQTT_PACKET_PINGREQ_SIZE )
+ {
+ LogError( ( "Buffer size of %lu is not sufficient to hold "
+ "serialized PINGREQ packet of size of %lu.",
+ ( unsigned long ) pFixedBuffer->size,
+ MQTT_PACKET_PINGREQ_SIZE ) );
+ status = MQTTNoMemory;
+ }
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Ping request packets are always the same. */
+ pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_PINGREQ;
+ pFixedBuffer->pBuffer[ 1 ] = 0x00;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket,
+ uint16_t * pPacketId,
+ MQTTPublishInfo_t * pPublishInfo )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( ( pIncomingPacket == NULL ) || ( pPacketId == NULL ) || ( pPublishInfo == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pIncomingPacket=%p, "
+ "pPacketId=%p, pPublishInfo=%p",
+ ( void * ) pIncomingPacket,
+ ( void * ) pPacketId,
+ ( void * ) pPublishInfo ) );
+ status = MQTTBadParameter;
+ }
+ else if( ( pIncomingPacket->type & 0xF0U ) != MQTT_PACKET_TYPE_PUBLISH )
+ {
+ LogError( ( "Packet is not publish. Packet type: %02x.",
+ ( unsigned int ) pIncomingPacket->type ) );
+ status = MQTTBadParameter;
+ }
+ else if( pIncomingPacket->pRemainingData == NULL )
+ {
+ LogError( ( "Argument cannot be NULL: "
+ "pIncomingPacket->pRemainingData is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ status = deserializePublish( pIncomingPacket, pPacketId, pPublishInfo );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket,
+ uint16_t * pPacketId,
+ bool * pSessionPresent )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( pIncomingPacket == NULL )
+ {
+ LogError( ( "pIncomingPacket cannot be NULL." ) );
+ status = MQTTBadParameter;
+ }
+
+ /* Pointer for packet identifier cannot be NULL for packets other than
+ * CONNACK and PINGRESP. */
+ else if( ( pPacketId == NULL ) &&
+ ( ( pIncomingPacket->type != MQTT_PACKET_TYPE_CONNACK ) &&
+ ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) ) )
+ {
+ LogError( ( "pPacketId cannot be NULL for packet type %02x.",
+ ( unsigned int ) pIncomingPacket->type ) );
+ status = MQTTBadParameter;
+ }
+ /* Pointer for session present cannot be NULL for CONNACK. */
+ else if( ( pSessionPresent == NULL ) &&
+ ( pIncomingPacket->type == MQTT_PACKET_TYPE_CONNACK ) )
+ {
+ LogError( ( "pSessionPresent cannot be NULL for CONNACK packet." ) );
+ status = MQTTBadParameter;
+ }
+
+ /* Pointer for remaining data cannot be NULL for packets other
+ * than PINGRESP. */
+ else if( ( pIncomingPacket->pRemainingData == NULL ) &&
+ ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) )
+ {
+ LogError( ( "Remaining data of incoming packet is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Make sure response packet is a valid ack. */
+ switch( pIncomingPacket->type )
+ {
+ case MQTT_PACKET_TYPE_CONNACK:
+ status = deserializeConnack( pIncomingPacket, pSessionPresent );
+ break;
+
+ case MQTT_PACKET_TYPE_SUBACK:
+ status = deserializeSuback( pIncomingPacket, pPacketId );
+ break;
+
+ case MQTT_PACKET_TYPE_PINGRESP:
+ status = deserializePingresp( pIncomingPacket );
+ break;
+
+ case MQTT_PACKET_TYPE_UNSUBACK:
+ case MQTT_PACKET_TYPE_PUBACK:
+ case MQTT_PACKET_TYPE_PUBREC:
+ case MQTT_PACKET_TYPE_PUBREL:
+ case MQTT_PACKET_TYPE_PUBCOMP:
+ status = deserializeSimpleAck( pIncomingPacket, pPacketId );
+ break;
+
+ /* Any other packet type is invalid. */
+ default:
+ LogError( ( "IotMqtt_DeserializeResponse() called with unknown packet type:(%02x).",
+ ( unsigned int ) pIncomingPacket->type ) );
+ status = MQTTBadResponse;
+ break;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc,
+ NetworkContext_t * pNetworkContext,
+ MQTTPacketInfo_t * pIncomingPacket )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ int32_t bytesReceived = 0;
+
+ if( pIncomingPacket == NULL )
+ {
+ LogError( ( "Invalid parameter: pIncomingPacket is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Read a single byte. */
+ bytesReceived = readFunc( pNetworkContext,
+ &( pIncomingPacket->type ),
+ 1U );
+ }
+
+ if( bytesReceived == 1 )
+ {
+ /* Check validity. */
+ if( incomingPacketValid( pIncomingPacket->type ) == true )
+ {
+ pIncomingPacket->remainingLength = getRemainingLength( readFunc,
+ pNetworkContext );
+
+ if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID )
+ {
+ LogError( ( "Incoming packet remaining length invalid." ) );
+ status = MQTTBadResponse;
+ }
+ }
+ else
+ {
+ LogError( ( "Incoming packet invalid: Packet type=%u.",
+ ( unsigned int ) pIncomingPacket->type ) );
+ status = MQTTBadResponse;
+ }
+ }
+ else if( ( status != MQTTBadParameter ) && ( bytesReceived == 0 ) )
+ {
+ status = MQTTNoDataAvailable;
+ }
+
+ /* If the input packet was valid, then any other number of bytes received is
+ * a failure. */
+ else if( status != MQTTBadParameter )
+ {
+ LogError( ( "A single byte was not read from the transport: "
+ "transportStatus=%ld.",
+ ( long int ) bytesReceived ) );
+ status = MQTTRecvFailed;
+ }
+ else
+ {
+ /* Empty else MISRA 15.7 */
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_ProcessIncomingPacketTypeAndLength( const uint8_t * pBuffer,
+ const size_t * pIndex,
+ MQTTPacketInfo_t * pIncomingPacket )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( pIncomingPacket == NULL )
+ {
+ LogError( ( "Invalid parameter: pIncomingPacket is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( pIndex == NULL )
+ {
+ LogError( ( "Invalid parameter: pIndex is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ else if( pBuffer == NULL )
+ {
+ LogError( ( "Invalid parameter: pBuffer is NULL." ) );
+ status = MQTTBadParameter;
+ }
+ /* There should be at least one byte in the buffer */
+ else if( *pIndex < 1U )
+ {
+ /* No data is available. There are 0 bytes received from the network
+ * receive function. */
+ status = MQTTNoDataAvailable;
+ }
+ else
+ {
+ /* At least one byte is present which should be deciphered. */
+ pIncomingPacket->type = pBuffer[ 0 ];
+ }
+
+ if( status == MQTTSuccess )
+ {
+ /* Check validity. */
+ if( incomingPacketValid( pIncomingPacket->type ) == true )
+ {
+ status = processRemainingLength( pBuffer,
+ pIndex,
+ pIncomingPacket );
+ }
+ else
+ {
+ LogError( ( "Incoming packet invalid: Packet type=%u.",
+ ( unsigned int ) pIncomingPacket->type ) );
+ status = MQTTBadResponse;
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
diff --git a/project/coreMQTT/coreMQTT/core_mqtt_serializer.h b/project/coreMQTT/coreMQTT/core_mqtt_serializer.h
new file mode 100644
index 0000000..d1b179e
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/core_mqtt_serializer.h
@@ -0,0 +1,1306 @@
+/*
+ * coreMQTT v2.1.1
+ * Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_mqtt_serializer.h
+ * @brief User-facing functions for serializing and deserializing MQTT 3.1.1
+ * packets. This header should be included for building a lighter weight MQTT
+ * client than the managed CSDK MQTT library API in core_mqtt.h, by using the
+ * serializer and de-serializer functions exposed in this file's API.
+ */
+#ifndef CORE_MQTT_SERIALIZER_H
+#define CORE_MQTT_SERIALIZER_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+/* *INDENT-ON */
+
+/* MQTT_DO_NOT_USE_CUSTOM_CONFIG allows building the MQTT library
+ * without a custom config. If a custom config is provided, the
+ * MQTT_DO_NOT_USE_CUSTOM_CONFIG macro should not be defined. */
+#ifndef MQTT_DO_NOT_USE_CUSTOM_CONFIG
+ /* Include custom config file before other headers. */
+ #include "core_mqtt_config.h"
+#endif
+
+/* Include config defaults header to get default values of configs not
+ * defined in core_mqtt_config.h file. */
+#include "core_mqtt_config_defaults.h"
+
+#include "transport_interface.h"
+
+/* MQTT packet types. */
+
+/**
+ * @addtogroup mqtt_constants
+ * @{
+ */
+#define MQTT_PACKET_TYPE_CONNECT ( ( uint8_t ) 0x10U ) /**< @brief CONNECT (client-to-server). */
+#define MQTT_PACKET_TYPE_CONNACK ( ( uint8_t ) 0x20U ) /**< @brief CONNACK (server-to-client). */
+#define MQTT_PACKET_TYPE_PUBLISH ( ( uint8_t ) 0x30U ) /**< @brief PUBLISH (bidirectional). */
+#define MQTT_PACKET_TYPE_PUBACK ( ( uint8_t ) 0x40U ) /**< @brief PUBACK (bidirectional). */
+#define MQTT_PACKET_TYPE_PUBREC ( ( uint8_t ) 0x50U ) /**< @brief PUBREC (bidirectional). */
+#define MQTT_PACKET_TYPE_PUBREL ( ( uint8_t ) 0x62U ) /**< @brief PUBREL (bidirectional). */
+#define MQTT_PACKET_TYPE_PUBCOMP ( ( uint8_t ) 0x70U ) /**< @brief PUBCOMP (bidirectional). */
+#define MQTT_PACKET_TYPE_SUBSCRIBE ( ( uint8_t ) 0x82U ) /**< @brief SUBSCRIBE (client-to-server). */
+#define MQTT_PACKET_TYPE_SUBACK ( ( uint8_t ) 0x90U ) /**< @brief SUBACK (server-to-client). */
+#define MQTT_PACKET_TYPE_UNSUBSCRIBE ( ( uint8_t ) 0xA2U ) /**< @brief UNSUBSCRIBE (client-to-server). */
+#define MQTT_PACKET_TYPE_UNSUBACK ( ( uint8_t ) 0xB0U ) /**< @brief UNSUBACK (server-to-client). */
+#define MQTT_PACKET_TYPE_PINGREQ ( ( uint8_t ) 0xC0U ) /**< @brief PINGREQ (client-to-server). */
+#define MQTT_PACKET_TYPE_PINGRESP ( ( uint8_t ) 0xD0U ) /**< @brief PINGRESP (server-to-client). */
+#define MQTT_PACKET_TYPE_DISCONNECT ( ( uint8_t ) 0xE0U ) /**< @brief DISCONNECT (client-to-server). */
+/** @} */
+
+/**
+ * @ingroup mqtt_constants
+ * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets, per MQTT spec.
+ */
+#define MQTT_PUBLISH_ACK_PACKET_SIZE ( 4UL )
+
+/* Structures defined in this file. */
+struct MQTTFixedBuffer;
+struct MQTTConnectInfo;
+struct MQTTSubscribeInfo;
+struct MQTTPublishInfo;
+struct MQTTPacketInfo;
+
+/**
+ * @ingroup mqtt_enum_types
+ * @brief Return codes from MQTT functions.
+ */
+typedef enum MQTTStatus
+{
+ MQTTSuccess = 0, /**< Function completed successfully. */
+ MQTTBadParameter, /**< At least one parameter was invalid. */
+ MQTTNoMemory, /**< A provided buffer was too small. */
+ MQTTSendFailed, /**< The transport send function failed. */
+ MQTTRecvFailed, /**< The transport receive function failed. */
+ MQTTBadResponse, /**< An invalid packet was received from the server. */
+ MQTTServerRefused, /**< The server refused a CONNECT or SUBSCRIBE. */
+ MQTTNoDataAvailable, /**< No data available from the transport interface. */
+ MQTTIllegalState, /**< An illegal state in the state record. */
+ MQTTStateCollision, /**< A collision with an existing state record entry. */
+ MQTTKeepAliveTimeout, /**< Timeout while waiting for PINGRESP. */
+ MQTTNeedMoreBytes /**< MQTT_ProcessLoop/MQTT_ReceiveLoop has received
+ incomplete data; it should be called again (probably after
+ a delay). */
+} MQTTStatus_t;
+
+/**
+ * @ingroup mqtt_enum_types
+ * @brief MQTT Quality of Service values.
+ */
+typedef enum MQTTQoS
+{
+ MQTTQoS0 = 0, /**< Delivery at most once. */
+ MQTTQoS1 = 1, /**< Delivery at least once. */
+ MQTTQoS2 = 2 /**< Delivery exactly once. */
+} MQTTQoS_t;
+
+/**
+ * @ingroup mqtt_struct_types
+ * @brief Buffer passed to MQTT library.
+ *
+ * These buffers are not copied and must remain in scope for the duration of the
+ * MQTT operation.
+ */
+typedef struct MQTTFixedBuffer
+{
+ uint8_t * pBuffer; /**< @brief Pointer to buffer. */
+ size_t size; /**< @brief Size of buffer. */
+} MQTTFixedBuffer_t;
+
+/**
+ * @ingroup mqtt_struct_types
+ * @brief MQTT CONNECT packet parameters.
+ */
+typedef struct MQTTConnectInfo
+{
+ /**
+ * @brief Whether to establish a new, clean session or resume a previous session.
+ */
+ bool cleanSession;
+
+ /**
+ * @brief MQTT keep alive period.
+ */
+ uint16_t keepAliveSeconds;
+
+ /**
+ * @brief MQTT client identifier. Must be unique per client.
+ */
+ const char * pClientIdentifier;
+
+ /**
+ * @brief Length of the client identifier.
+ */
+ uint16_t clientIdentifierLength;
+
+ /**
+ * @brief MQTT user name. Set to NULL if not used.
+ */
+ const char * pUserName;
+
+ /**
+ * @brief Length of MQTT user name. Set to 0 if not used.
+ */
+ uint16_t userNameLength;
+
+ /**
+ * @brief MQTT password. Set to NULL if not used.
+ */
+ const char * pPassword;
+
+ /**
+ * @brief Length of MQTT password. Set to 0 if not used.
+ */
+ uint16_t passwordLength;
+} MQTTConnectInfo_t;
+
+/**
+ * @ingroup mqtt_struct_types
+ * @brief MQTT SUBSCRIBE packet parameters.
+ */
+typedef struct MQTTSubscribeInfo
+{
+ /**
+ * @brief Quality of Service for subscription.
+ */
+ MQTTQoS_t qos;
+
+ /**
+ * @brief Topic filter to subscribe to.
+ */
+ const char * pTopicFilter;
+
+ /**
+ * @brief Length of subscription topic filter.
+ */
+ uint16_t topicFilterLength;
+} MQTTSubscribeInfo_t;
+
+/**
+ * @ingroup mqtt_struct_types
+ * @brief MQTT PUBLISH packet parameters.
+ */
+typedef struct MQTTPublishInfo
+{
+ /**
+ * @brief Quality of Service for message.
+ */
+ MQTTQoS_t qos;
+
+ /**
+ * @brief Whether this is a retained message.
+ */
+ bool retain;
+
+ /**
+ * @brief Whether this is a duplicate publish message.
+ */
+ bool dup;
+
+ /**
+ * @brief Topic name on which the message is published.
+ */
+ const char * pTopicName;
+
+ /**
+ * @brief Length of topic name.
+ */
+ uint16_t topicNameLength;
+
+ /**
+ * @brief Message payload.
+ */
+ const void * pPayload;
+
+ /**
+ * @brief Message payload length.
+ */
+ size_t payloadLength;
+} MQTTPublishInfo_t;
+
+/**
+ * @ingroup mqtt_struct_types
+ * @brief MQTT incoming packet parameters.
+ */
+typedef struct MQTTPacketInfo
+{
+ /**
+ * @brief Type of incoming MQTT packet.
+ */
+ uint8_t type;
+
+ /**
+ * @brief Remaining serialized data in the MQTT packet.
+ */
+ uint8_t * pRemainingData;
+
+ /**
+ * @brief Length of remaining serialized data.
+ */
+ size_t remainingLength;
+
+ /**
+ * @brief The length of the MQTT header including the type and length.
+ */
+ size_t headerLength;
+} MQTTPacketInfo_t;
+
+/**
+ * @brief Get the size and Remaining Length of an MQTT CONNECT packet.
+ *
+ * This function must be called before #MQTT_SerializeConnect in order to get
+ * the size of the MQTT CONNECT packet that is generated from #MQTTConnectInfo_t
+ * and optional #MQTTPublishInfo_t. The size of the #MQTTFixedBuffer_t supplied
+ * to #MQTT_SerializeConnect must be at least @p pPacketSize. The provided
+ * @p pConnectInfo and @p pWillInfo are valid for serialization with
+ * #MQTT_SerializeConnect only if this function returns #MQTTSuccess. The
+ * remaining length returned in @p pRemainingLength and the packet size returned
+ * in @p pPacketSize are valid only if this function returns #MQTTSuccess.
+ *
+ * @param[in] pConnectInfo MQTT CONNECT packet parameters.
+ * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used.
+ * @param[out] pRemainingLength The Remaining Length of the MQTT CONNECT packet.
+ * @param[out] pPacketSize The total size of the MQTT CONNECT packet.
+ *
+ * @return #MQTTBadParameter if the packet would exceed the size allowed by the
+ * MQTT spec; #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTConnectInfo_t connectInfo = { 0 };
+ * MQTTPublishInfo_t willInfo = { 0 };
+ * size_t remainingLength = 0, packetSize = 0;
+ *
+ * // Initialize the connection info, the details are out of scope for this example.
+ * initializeConnectInfo( &connectInfo );
+ *
+ * // Initialize the optional will info, the details are out of scope for this example.
+ * initializeWillInfo( &willInfo );
+ *
+ * // Get the size requirement for the connect packet.
+ * status = MQTT_GetConnectPacketSize(
+ * &connectInfo, &willInfo, &remainingLength, &packetSize
+ * );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The application should allocate or use a static #MQTTFixedBuffer_t
+ * // of size >= packetSize to serialize the connect request.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_getconnectpacketsize] */
+MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ size_t * pRemainingLength,
+ size_t * pPacketSize );
+/* @[declare_mqtt_getconnectpacketsize] */
+
+/**
+ * @brief Serialize an MQTT CONNECT packet in the given fixed buffer @p pFixedBuffer.
+ *
+ * #MQTT_GetConnectPacketSize should be called with @p pConnectInfo and
+ * @p pWillInfo before invoking this function to get the size of the required
+ * #MQTTFixedBuffer_t and @p remainingLength. The @p remainingLength must be
+ * the same as returned by #MQTT_GetConnectPacketSize. The #MQTTFixedBuffer_t
+ * must be at least as large as the size returned by #MQTT_GetConnectPacketSize.
+ *
+ * @param[in] pConnectInfo MQTT CONNECT packet parameters.
+ * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used.
+ * @param[in] remainingLength Remaining Length provided by #MQTT_GetConnectPacketSize.
+ * @param[out] pFixedBuffer Buffer for packet serialization.
+ *
+ * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTConnectInfo_t connectInfo = { 0 };
+ * MQTTPublishInfo_t willInfo = { 0 };
+ * MQTTFixedBuffer_t fixedBuffer;
+ * uint8_t buffer[ BUFFER_SIZE ];
+ * size_t remainingLength = 0, packetSize = 0;
+ *
+ * fixedBuffer.pBuffer = buffer;
+ * fixedBuffer.size = BUFFER_SIZE;
+ *
+ * // Assume connectInfo and willInfo are initialized. Get the size requirement for
+ * // the connect packet.
+ * status = MQTT_GetConnectPacketSize(
+ * &connectInfo, &willInfo, &remainingLength, &packetSize
+ * );
+ * assert( status == MQTTSuccess );
+ * assert( packetSize <= BUFFER_SIZE );
+ *
+ * // Serialize the connect packet into the fixed buffer.
+ * status = MQTT_SerializeConnect( &connectInfo, &willInfo, remainingLength, &fixedBuffer );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The connect packet can now be sent to the broker.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_serializeconnect] */
+MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer );
+/* @[declare_mqtt_serializeconnect] */
+
+/**
+ * @brief Get packet size and Remaining Length of an MQTT SUBSCRIBE packet.
+ *
+ * This function must be called before #MQTT_SerializeSubscribe in order to get
+ * the size of the MQTT SUBSCRIBE packet that is generated from the list of
+ * #MQTTSubscribeInfo_t. The size of the #MQTTFixedBuffer_t supplied
+ * to #MQTT_SerializeSubscribe must be at least @p pPacketSize. The provided
+ * @p pSubscriptionList is valid for serialization with #MQTT_SerializeSubscribe
+ * only if this function returns #MQTTSuccess. The remaining length returned in
+ * @p pRemainingLength and the packet size returned in @p pPacketSize are valid
+ * only if this function returns #MQTTSuccess.
+ *
+ * @param[in] pSubscriptionList List of MQTT subscription info.
+ * @param[in] subscriptionCount The number of elements in pSubscriptionList.
+ * @param[out] pRemainingLength The Remaining Length of the MQTT SUBSCRIBE packet.
+ * @param[out] pPacketSize The total size of the MQTT SUBSCRIBE packet.
+ *
+ * @return #MQTTBadParameter if the packet would exceed the size allowed by the
+ * MQTT spec; #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
+ * size_t remainingLength = 0, packetSize = 0;
+ * // This is assumed to be a list of filters we want to subscribe to.
+ * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ];
+ *
+ * // Set each subscription.
+ * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ )
+ * {
+ * subscriptionList[ i ].qos = MQTTQoS0;
+ * // Each subscription needs a topic filter.
+ * subscriptionList[ i ].pTopicFilter = filters[ i ];
+ * subscriptionList[ i ].topicFilterLength = strlen( filters[ i ] );
+ * }
+ *
+ * // Get the size requirement for the subscribe packet.
+ * status = MQTT_GetSubscribePacketSize(
+ * &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize
+ * );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The application should allocate or use a static #MQTTFixedBuffer_t
+ * // of size >= packetSize to serialize the subscribe request.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_getsubscribepacketsize] */
+MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ size_t * pRemainingLength,
+ size_t * pPacketSize );
+/* @[declare_mqtt_getsubscribepacketsize] */
+
+/**
+ * @brief Serialize an MQTT SUBSCRIBE packet in the given buffer.
+ *
+ * #MQTT_GetSubscribePacketSize should be called with @p pSubscriptionList
+ * before invoking this function to get the size of the required
+ * #MQTTFixedBuffer_t and @p remainingLength. The @p remainingLength must be
+ * the same as returned by #MQTT_GetSubscribePacketSize. The #MQTTFixedBuffer_t
+ * must be at least as large as the size returned by #MQTT_GetSubscribePacketSize.
+ *
+ * @param[in] pSubscriptionList List of MQTT subscription info.
+ * @param[in] subscriptionCount The number of elements in pSubscriptionList.
+ * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
+ * @param[in] remainingLength Remaining Length provided by #MQTT_GetSubscribePacketSize.
+ * @param[out] pFixedBuffer Buffer for packet serialization.
+ *
+ * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
+ * MQTTFixedBuffer_t fixedBuffer;
+ * uint8_t buffer[ BUFFER_SIZE ];
+ * size_t remainingLength = 0, packetSize = 0;
+ * uint16_t packetId;
+ *
+ * fixedBuffer.pBuffer = buffer;
+ * fixedBuffer.size = BUFFER_SIZE;
+ *
+ * // Function to return a valid, unused packet identifier. The details are out of
+ * // scope for this example.
+ * packetId = getNewPacketId();
+ *
+ * // Assume subscriptionList has been initialized. Get the subscribe packet size.
+ * status = MQTT_GetSubscribePacketSize(
+ * &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize
+ * );
+ * assert( status == MQTTSuccess );
+ * assert( packetSize <= BUFFER_SIZE );
+ *
+ * // Serialize the subscribe packet into the fixed buffer.
+ * status = MQTT_SerializeSubscribe(
+ * &subscriptionList[ 0 ],
+ * NUMBER_OF_SUBSCRIPTIONS,
+ * packetId,
+ * remainingLength,
+ * &fixedBuffer
+ * );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The subscribe packet can now be sent to the broker.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_serializesubscribe] */
+MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer );
+/* @[declare_mqtt_serializesubscribe] */
+
+/**
+ * @brief Get packet size and Remaining Length of an MQTT UNSUBSCRIBE packet.
+ *
+ * This function must be called before #MQTT_SerializeUnsubscribe in order to
+ * get the size of the MQTT UNSUBSCRIBE packet that is generated from the list
+ * of #MQTTSubscribeInfo_t. The size of the #MQTTFixedBuffer_t supplied
+ * to #MQTT_SerializeUnsubscribe must be at least @p pPacketSize. The provided
+ * @p pSubscriptionList is valid for serialization with #MQTT_SerializeUnsubscribe
+ * only if this function returns #MQTTSuccess. The remaining length returned in
+ * @p pRemainingLength and the packet size returned in @p pPacketSize are valid
+ * only if this function returns #MQTTSuccess.
+ *
+ * @param[in] pSubscriptionList List of MQTT subscription info.
+ * @param[in] subscriptionCount The number of elements in pSubscriptionList.
+ * @param[out] pRemainingLength The Remaining Length of the MQTT UNSUBSCRIBE packet.
+ * @param[out] pPacketSize The total size of the MQTT UNSUBSCRIBE packet.
+ *
+ * @return #MQTTBadParameter if the packet would exceed the size allowed by the
+ * MQTT spec; #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
+ * size_t remainingLength = 0, packetSize = 0;
+ *
+ * // Initialize the subscribe info. The details are out of scope for this example.
+ * initializeSubscribeInfo( &subscriptionList[ 0 ] );
+ *
+ * // Get the size requirement for the unsubscribe packet.
+ * status = MQTT_GetUnsubscribePacketSize(
+ * &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize
+ * );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The application should allocate or use a static #MQTTFixedBuffer_t
+ * // of size >= packetSize to serialize the unsubscribe request.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_getunsubscribepacketsize] */
+MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ size_t * pRemainingLength,
+ size_t * pPacketSize );
+/* @[declare_mqtt_getunsubscribepacketsize] */
+
+/**
+ * @brief Serialize an MQTT UNSUBSCRIBE packet in the given buffer.
+ *
+ * #MQTT_GetUnsubscribePacketSize should be called with @p pSubscriptionList
+ * before invoking this function to get the size of the required
+ * #MQTTFixedBuffer_t and @p remainingLength. The @p remainingLength must be
+ * the same as returned by #MQTT_GetUnsubscribePacketSize. The #MQTTFixedBuffer_t
+ * must be at least as large as the size returned by #MQTT_GetUnsubscribePacketSize.
+ *
+ * @param[in] pSubscriptionList List of MQTT subscription info.
+ * @param[in] subscriptionCount The number of elements in pSubscriptionList.
+ * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
+ * @param[in] remainingLength Remaining Length provided by #MQTT_GetUnsubscribePacketSize.
+ * @param[out] pFixedBuffer Buffer for packet serialization.
+ *
+ * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
+ * MQTTFixedBuffer_t fixedBuffer;
+ * uint8_t buffer[ BUFFER_SIZE ];
+ * size_t remainingLength = 0, packetSize = 0;
+ * uint16_t packetId;
+ *
+ * fixedBuffer.pBuffer = buffer;
+ * fixedBuffer.size = BUFFER_SIZE;
+ *
+ * // Function to return a valid, unused packet identifier. The details are out of
+ * // scope for this example.
+ * packetId = getNewPacketId();
+ *
+ * // Assume subscriptionList has been initialized. Get the unsubscribe packet size.
+ * status = MQTT_GetUnsubscribePacketSize(
+ * &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize
+ * );
+ * assert( status == MQTTSuccess );
+ * assert( packetSize <= BUFFER_SIZE );
+ *
+ * // Serialize the unsubscribe packet into the fixed buffer.
+ * status = MQTT_SerializeUnsubscribe(
+ * &subscriptionList[ 0 ],
+ * NUMBER_OF_SUBSCRIPTIONS,
+ * packetId,
+ * remainingLength,
+ * &fixedBuffer
+ * );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The unsubscribe packet can now be sent to the broker.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_serializeunsubscribe] */
+MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptionList,
+ size_t subscriptionCount,
+ uint16_t packetId,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer );
+/* @[declare_mqtt_serializeunsubscribe] */
+
+/**
+ * @brief Get the packet size and remaining length of an MQTT PUBLISH packet.
+ *
+ * This function must be called before #MQTT_SerializePublish in order to get
+ * the size of the MQTT PUBLISH packet that is generated from #MQTTPublishInfo_t.
+ * The size of the #MQTTFixedBuffer_t supplied to #MQTT_SerializePublish must be
+ * at least @p pPacketSize. The provided @p pPublishInfo is valid for
+ * serialization with #MQTT_SerializePublish only if this function returns
+ * #MQTTSuccess. The remaining length returned in @p pRemainingLength and the
+ * packet size returned in @p pPacketSize are valid only if this function
+ * returns #MQTTSuccess.
+ *
+ * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
+ * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet.
+ * @param[out] pPacketSize The total size of the MQTT PUBLISH packet.
+ *
+ * @return #MQTTBadParameter if the packet would exceed the size allowed by the
+ * MQTT spec or if invalid parameters are passed; #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTPublishInfo_t publishInfo = { 0 };
+ * size_t remainingLength = 0, packetSize = 0;
+ *
+ * // Initialize the publish info.
+ * publishInfo.qos = MQTTQoS0;
+ * publishInfo.pTopicName = "/some/topic/name";
+ * publishInfo.topicNameLength = strlen( publishInfo.pTopicName );
+ * publishInfo.pPayload = "Hello World!";
+ * publishInfo.payloadLength = strlen( "Hello World!" );
+ *
+ * // Get the size requirement for the publish packet.
+ * status = MQTT_GetPublishPacketSize(
+ * &publishInfo, &remainingLength, &packetSize
+ * );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The application should allocate or use a static #MQTTFixedBuffer_t
+ * // of size >= packetSize to serialize the publish.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_getpublishpacketsize] */
+MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo,
+ size_t * pRemainingLength,
+ size_t * pPacketSize );
+/* @[declare_mqtt_getpublishpacketsize] */
+
+/**
+ * @brief Serialize an MQTT PUBLISH packet in the given buffer.
+ *
+ * This function will serialize complete MQTT PUBLISH packet into
+ * the given buffer. If the PUBLISH payload can be sent separately,
+ * consider using #MQTT_SerializePublishHeader, which will serialize
+ * only the PUBLISH header into the buffer.
+ *
+ * #MQTT_GetPublishPacketSize should be called with @p pPublishInfo before
+ * invoking this function to get the size of the required #MQTTFixedBuffer_t and
+ * @p remainingLength. The @p remainingLength must be the same as returned by
+ * #MQTT_GetPublishPacketSize. The #MQTTFixedBuffer_t must be at least as large
+ * as the size returned by #MQTT_GetPublishPacketSize.
+ *
+ * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
+ * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
+ * @param[in] remainingLength Remaining Length provided by #MQTT_GetPublishPacketSize.
+ * @param[out] pFixedBuffer Buffer for packet serialization.
+ *
+ * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTPublishInfo_t publishInfo = { 0 };
+ * MQTTFixedBuffer_t fixedBuffer;
+ * uint8_t buffer[ BUFFER_SIZE ];
+ * size_t remainingLength = 0, packetSize = 0;
+ * uint16_t packetId;
+ *
+ * fixedBuffer.pBuffer = buffer;
+ * fixedBuffer.size = BUFFER_SIZE;
+ *
+ * // A packet identifier is unused for QoS 0 publishes. Otherwise, a valid, unused packet
+ * // identifier must be used.
+ * packetId = 0;
+ *
+ * // Assume publishInfo has been initialized. Get publish packet size.
+ * status = MQTT_GetPublishPacketSize(
+ * &publishInfo, &remainingLength, &packetSize
+ * );
+ * assert( status == MQTTSuccess );
+ * assert( packetSize <= BUFFER_SIZE );
+ *
+ * // Serialize the publish packet into the fixed buffer.
+ * status = MQTT_SerializePublish(
+ * &publishInfo,
+ * packetId,
+ * remainingLength,
+ * &fixedBuffer
+ * );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The publish packet can now be sent to the broker.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_serializepublish] */
+MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo,
+ uint16_t packetId,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer );
+/* @[declare_mqtt_serializepublish] */
+
+/**
+ * @brief Serialize an MQTT PUBLISH packet header without the topic string in the
+ * given buffer. This function will add the topic string length to the provided
+ * buffer. This helps reduce an unnecessary copy of the topic string into the
+ * buffer.
+ *
+ * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
+ * @param[in] remainingLength Remaining Length provided by #MQTT_GetPublishPacketSize.
+ * @param[out] pBuffer Buffer for packet serialization.
+ * @param[out] headerSize Size of the serialized MQTT PUBLISH header.
+ *
+ * @return #MQTTSuccess if the serialization is successful. Otherwise, #MQTTBadParameter.
+ */
+MQTTStatus_t MQTT_SerializePublishHeaderWithoutTopic( const MQTTPublishInfo_t * pPublishInfo,
+ size_t remainingLength,
+ uint8_t * pBuffer,
+ size_t * headerSize );
+
+/**
+ * @brief Serialize an MQTT PUBLISH packet header in the given buffer.
+ *
+ * This function serializes PUBLISH header in to the given buffer. The payload
+ * for PUBLISH will not be copied over to the buffer. This will help reduce
+ * the memory needed for the buffer and avoid an unwanted copy operation of the
+ * PUBLISH payload into the buffer. If the payload also would need to be part of
+ * the serialized buffer, consider using #MQTT_SerializePublish.
+ *
+ * #MQTT_GetPublishPacketSize should be called with @p pPublishInfo before
+ * invoking this function to get the size of the required #MQTTFixedBuffer_t and
+ * @p remainingLength. The @p remainingLength must be the same as returned by
+ * #MQTT_GetPublishPacketSize. The #MQTTFixedBuffer_t must be at least as large
+ * as the size returned by #MQTT_GetPublishPacketSize.
+ *
+ * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
+ * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
+ * @param[in] remainingLength Remaining Length provided by #MQTT_GetPublishPacketSize.
+ * @param[out] pFixedBuffer Buffer for packet serialization.
+ * @param[out] pHeaderSize Size of the serialized MQTT PUBLISH header.
+ *
+ * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTPublishInfo_t publishInfo = { 0 };
+ * MQTTFixedBuffer_t fixedBuffer;
+ * uint8_t buffer[ BUFFER_SIZE ];
+ * size_t remainingLength = 0, packetSize = 0, headerSize = 0;
+ * uint16_t packetId;
+ * int32_t bytesSent;
+ *
+ * fixedBuffer.pBuffer = buffer;
+ * fixedBuffer.size = BUFFER_SIZE;
+ *
+ * // A packet identifier is unused for QoS 0 publishes. Otherwise, a valid, unused packet
+ * // identifier must be used.
+ * packetId = 0;
+ *
+ * // Assume publishInfo has been initialized. Get the publish packet size.
+ * status = MQTT_GetPublishPacketSize(
+ * &publishInfo, &remainingLength, &packetSize
+ * );
+ * assert( status == MQTTSuccess );
+ * // The payload will not be serialized, so the the fixed buffer does not need to hold it.
+ * assert( ( packetSize - publishInfo.payloadLength ) <= BUFFER_SIZE );
+ *
+ * // Serialize the publish packet header into the fixed buffer.
+ * status = MQTT_SerializePublishHeader(
+ * &publishInfo,
+ * packetId,
+ * remainingLength,
+ * &fixedBuffer,
+ * &headerSize
+ * );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The publish header and payload can now be sent to the broker.
+ * // mqttSocket here is a socket descriptor created and connected to the MQTT
+ * // broker outside of this function.
+ * bytesSent = send( mqttSocket, ( void * ) fixedBuffer.pBuffer, headerSize, 0 );
+ * assert( bytesSent == headerSize );
+ * bytesSent = send( mqttSocket, publishInfo.pPayload, publishInfo.payloadLength, 0 );
+ * assert( bytesSent == publishInfo.payloadLength );
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_serializepublishheader] */
+MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo,
+ uint16_t packetId,
+ size_t remainingLength,
+ const MQTTFixedBuffer_t * pFixedBuffer,
+ size_t * pHeaderSize );
+/* @[declare_mqtt_serializepublishheader] */
+
+/**
+ * @brief Serialize an MQTT PUBACK, PUBREC, PUBREL, or PUBCOMP into the given
+ * buffer.
+ *
+ * @param[out] pFixedBuffer Buffer for packet serialization.
+ * @param[in] packetType Byte of the corresponding packet fixed header per the
+ * MQTT spec.
+ * @param[in] packetId Packet ID of the publish.
+ *
+ * @return #MQTTBadParameter, #MQTTNoMemory, or #MQTTSuccess.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTFixedBuffer_t fixedBuffer;
+ * uint8_t buffer[ BUFFER_SIZE ];
+ * uint16_t packetId;
+ * uint8_t packetType;
+ *
+ * fixedBuffer.pBuffer = buffer;
+ * fixedBuffer.size = BUFFER_SIZE;
+ * // The fixed buffer must be large enough to hold 4 bytes.
+ * assert( BUFFER_SIZE >= MQTT_PUBLISH_ACK_PACKET_SIZE );
+ *
+ * // The packet ID must be the same as the original publish packet.
+ * packetId = publishPacketId;
+ *
+ * // The byte representing a packet of type ACK. This function accepts PUBACK, PUBREC, PUBREL, or PUBCOMP.
+ * packetType = MQTT_PACKET_TYPE_PUBACK;
+ *
+ * // Serialize the publish acknowledgment into the fixed buffer.
+ * status = MQTT_SerializeAck( &fixedBuffer, packetType, packetId );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The publish acknowledgment can now be sent to the broker.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_serializeack] */
+MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer,
+ uint8_t packetType,
+ uint16_t packetId );
+/* @[declare_mqtt_serializeack] */
+
+/**
+ * @brief Get the size of an MQTT DISCONNECT packet.
+ *
+ * @param[out] pPacketSize The size of the MQTT DISCONNECT packet.
+ *
+ * @return #MQTTSuccess, or #MQTTBadParameter if @p pPacketSize is NULL.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * size_t packetSize = 0;
+ *
+ * // Get the size requirement for the disconnect packet.
+ * status = MQTT_GetDisconnectPacketSize( &packetSize );
+ * assert( status == MQTTSuccess );
+ * assert( packetSize == 2 );
+ *
+ * // The application should allocate or use a static #MQTTFixedBuffer_t of
+ * // size >= 2 to serialize the disconnect packet.
+ *
+ * @endcode
+ */
+/* @[declare_mqtt_getdisconnectpacketsize] */
+MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize );
+/* @[declare_mqtt_getdisconnectpacketsize] */
+
+/**
+ * @brief Serialize an MQTT DISCONNECT packet into the given buffer.
+ *
+ * The input #MQTTFixedBuffer_t.size must be at least as large as the size
+ * returned by #MQTT_GetDisconnectPacketSize.
+ *
+ * @param[out] pFixedBuffer Buffer for packet serialization.
+ *
+ * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTFixedBuffer_t fixedBuffer;
+ * uint8_t buffer[ BUFFER_SIZE ];
+ *
+ * fixedBuffer.pBuffer = buffer;
+ * fixedBuffer.size = BUFFER_SIZE;
+ *
+ * // Get the disconnect packet size.
+ * status = MQTT_GetDisconnectPacketSize( &packetSize );
+ * assert( status == MQTTSuccess );
+ * assert( packetSize <= BUFFER_SIZE );
+ *
+ * // Serialize the disconnect into the fixed buffer.
+ * status = MQTT_SerializeDisconnect( &fixedBuffer );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The disconnect packet can now be sent to the broker.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_serializedisconnect] */
+MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer );
+/* @[declare_mqtt_serializedisconnect] */
+
+/**
+ * @brief Get the size of an MQTT PINGREQ packet.
+ *
+ * @param[out] pPacketSize The size of the MQTT PINGREQ packet.
+ *
+ * @return #MQTTSuccess or #MQTTBadParameter if pPacketSize is NULL.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * size_t packetSize = 0;
+ *
+ * // Get the size requirement for the ping request packet.
+ * status = MQTT_GetPingreqPacketSize( &packetSize );
+ * assert( status == MQTTSuccess );
+ * assert( packetSize == 2 );
+ *
+ * // The application should allocate or use a static #MQTTFixedBuffer_t of
+ * // size >= 2 to serialize the ping request.
+ *
+ * @endcode
+ */
+/* @[declare_mqtt_getpingreqpacketsize] */
+MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize );
+/* @[declare_mqtt_getpingreqpacketsize] */
+
+/**
+ * @brief Serialize an MQTT PINGREQ packet into the given buffer.
+ *
+ * The input #MQTTFixedBuffer_t.size must be at least as large as the size
+ * returned by #MQTT_GetPingreqPacketSize.
+ *
+ * @param[out] pFixedBuffer Buffer for packet serialization.
+ *
+ * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
+ * #MQTTBadParameter if invalid parameters are passed;
+ * #MQTTSuccess otherwise.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTFixedBuffer_t fixedBuffer;
+ * uint8_t buffer[ BUFFER_SIZE ];
+ *
+ * fixedBuffer.pBuffer = buffer;
+ * fixedBuffer.size = BUFFER_SIZE;
+ *
+ * // Get the ping request packet size.
+ * status = MQTT_GetPingreqPacketSize( &packetSize );
+ * assert( status == MQTTSuccess );
+ * assert( packetSize <= BUFFER_SIZE );
+ *
+ * // Serialize the ping request into the fixed buffer.
+ * status = MQTT_SerializePingreq( &fixedBuffer );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * // The ping request can now be sent to the broker.
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_serializepingreq] */
+MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer );
+/* @[declare_mqtt_serializepingreq] */
+
+/**
+ * @brief Deserialize an MQTT PUBLISH packet.
+ *
+ * @param[in] pIncomingPacket #MQTTPacketInfo_t containing the buffer.
+ * @param[out] pPacketId The packet ID obtained from the buffer.
+ * @param[out] pPublishInfo Struct containing information about the publish.
+ *
+ * @return #MQTTBadParameter, #MQTTBadResponse, or #MQTTSuccess.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // TransportRecv_t function for reading from the network.
+ * int32_t socket_recv(
+ * NetworkContext_t * pNetworkContext,
+ * void * pBuffer,
+ * size_t bytesToRecv
+ * );
+ * // Some context to be used with the above transport receive function.
+ * NetworkContext_t networkContext;
+ *
+ * // Other variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTPacketInfo_t incomingPacket;
+ * MQTTPublishInfo_t publishInfo = { 0 };
+ * uint16_t packetId;
+ *
+ * int32_t bytesRecvd;
+ * // A buffer to hold remaining data of the incoming packet.
+ * uint8_t buffer[ BUFFER_SIZE ];
+ *
+ * // Populate all fields of the incoming packet.
+ * status = MQTT_GetIncomingPacketTypeAndLength(
+ * socket_recv,
+ * &networkContext,
+ * &incomingPacket
+ * );
+ * assert( status == MQTTSuccess );
+ * assert( incomingPacket.remainingLength <= BUFFER_SIZE );
+ * bytesRecvd = socket_recv(
+ * &networkContext,
+ * ( void * ) buffer,
+ * incomingPacket.remainingLength
+ * );
+ * incomingPacket.pRemainingData = buffer;
+ *
+ * // Deserialize the publish information if the incoming packet is a publish.
+ * if( ( incomingPacket.type & 0xF0 ) == MQTT_PACKET_TYPE_PUBLISH )
+ * {
+ * status = MQTT_DeserializePublish( &incomingPacket, &packetId, &publishInfo );
+ * if( status == MQTTSuccess )
+ * {
+ * // The deserialized publish information can now be used from `publishInfo`.
+ * }
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_deserializepublish] */
+MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket,
+ uint16_t * pPacketId,
+ MQTTPublishInfo_t * pPublishInfo );
+/* @[declare_mqtt_deserializepublish] */
+
+/**
+ * @brief Deserialize an MQTT CONNACK, SUBACK, UNSUBACK, PUBACK, PUBREC, PUBREL,
+ * PUBCOMP, or PINGRESP.
+ *
+ * @param[in] pIncomingPacket #MQTTPacketInfo_t containing the buffer.
+ * @param[out] pPacketId The packet ID of obtained from the buffer. Not used
+ * in CONNACK or PINGRESP.
+ * @param[out] pSessionPresent Boolean flag from a CONNACK indicating present session.
+ *
+ * @return #MQTTBadParameter, #MQTTBadResponse, #MQTTServerRefused, or #MQTTSuccess.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTPacketInfo_t incomingPacket;
+ * // Used for SUBACK, UNSUBACK, PUBACK, PUBREC, PUBREL, and PUBCOMP.
+ * uint16_t packetId;
+ * // Used for CONNACK.
+ * bool sessionPresent;
+ *
+ * // Receive an incoming packet and populate all fields. The details are out of scope
+ * // for this example.
+ * receiveIncomingPacket( &incomingPacket );
+ *
+ * // Deserialize ack information if the incoming packet is not a publish.
+ * if( ( incomingPacket.type & 0xF0 ) != MQTT_PACKET_TYPE_PUBLISH )
+ * {
+ * status = MQTT_DeserializeAck( &incomingPacket, &packetId, &sessionPresent );
+ * if( status == MQTTSuccess )
+ * {
+ * // The packet ID or session present flag information is available. For
+ * // ping response packets, the only information is the status code.
+ * }
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_deserializeack] */
+MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket,
+ uint16_t * pPacketId,
+ bool * pSessionPresent );
+/* @[declare_mqtt_deserializeack] */
+
+/**
+ * @brief Extract the MQTT packet type and length from incoming packet.
+ *
+ * This function must be called for every incoming packet to retrieve the
+ * #MQTTPacketInfo_t.type and #MQTTPacketInfo_t.remainingLength. A
+ * #MQTTPacketInfo_t is not valid until this routine has been invoked.
+ *
+ * @param[in] readFunc Transport layer read function pointer.
+ * @param[in] pNetworkContext The network context pointer provided by the application.
+ * @param[out] pIncomingPacket Pointer to MQTTPacketInfo_t structure. This is
+ * where type, remaining length and packet identifier are stored.
+ *
+ * @return #MQTTSuccess on successful extraction of type and length,
+ * #MQTTBadParameter if @p pIncomingPacket is invalid,
+ * #MQTTRecvFailed on transport receive failure,
+ * #MQTTBadResponse if an invalid packet is read, and
+ * #MQTTNoDataAvailable if there is nothing to read.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // TransportRecv_t function for reading from the network.
+ * int32_t socket_recv(
+ * NetworkContext_t * pNetworkContext,
+ * void * pBuffer,
+ * size_t bytesToRecv
+ * );
+ * // Some context to be used with above transport receive function.
+ * NetworkContext_t networkContext;
+ *
+ * // Struct to hold the incoming packet information.
+ * MQTTPacketInfo_t incomingPacket;
+ * MQTTStatus_t status = MQTTSuccess;
+ * int32_t bytesRecvd;
+ * // Buffer to hold the remaining data of the incoming packet.
+ * uint8_t buffer[ BUFFER_SIZE ];
+ *
+ * // Loop until data is available to be received.
+ * do{
+ * status = MQTT_GetIncomingPacketTypeAndLength(
+ * socket_recv,
+ * &networkContext,
+ * &incomingPacket
+ * );
+ * } while( status == MQTTNoDataAvailable );
+ *
+ * assert( status == MQTTSuccess );
+ *
+ * // Receive the rest of the incoming packet.
+ * assert( incomingPacket.remainingLength <= BUFFER_SIZE );
+ * bytesRecvd = socket_recv(
+ * &networkContext,
+ * ( void * ) buffer,
+ * incomingPacket.remainingLength
+ * );
+ *
+ * // Set the remaining data field.
+ * incomingPacket.pRemainingData = buffer;
+ * @endcode
+ */
+/* @[declare_mqtt_getincomingpackettypeandlength] */
+MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc,
+ NetworkContext_t * pNetworkContext,
+ MQTTPacketInfo_t * pIncomingPacket );
+/* @[declare_mqtt_getincomingpackettypeandlength] */
+
+/**
+ * @brief Extract the MQTT packet type and length from incoming packet.
+ *
+ * This function must be called for every incoming packet to retrieve the
+ * #MQTTPacketInfo_t.type and #MQTTPacketInfo_t.remainingLength. A
+ * #MQTTPacketInfo_t is not valid until this routine has been invoked.
+ *
+ * @param[in] pBuffer The buffer holding the raw data to be processed
+ * @param[in] pIndex Pointer to the index within the buffer to marking the end
+ * of raw data available.
+ * @param[out] pIncomingPacket Structure used to hold the fields of the
+ * incoming packet.
+ *
+ * @return #MQTTSuccess on successful extraction of type and length,
+ * #MQTTBadParameter if @p pIncomingPacket is invalid,
+ * #MQTTBadResponse if an invalid packet is read, and
+ * #MQTTNoDataAvailable if there is nothing to read.
+ */
+ /* @[declare_mqtt_processincomingpackettypeandlength] */
+MQTTStatus_t MQTT_ProcessIncomingPacketTypeAndLength( const uint8_t * pBuffer,
+ const size_t * pIndex,
+ MQTTPacketInfo_t * pIncomingPacket );
+/* @[declare_mqtt_processincomingpackettypeandlength] */
+
+/**
+ * @fn uint8_t * MQTT_SerializeConnectFixedHeader( uint8_t * pIndex, const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, size_t remainingLength );
+ * @brief Serialize the fixed part of the connect packet header.
+ *
+ * @param[out] pIndex Pointer to the buffer where the header is to
+ * be serialized.
+ * @param[in] pConnectInfo The connect information.
+ * @param[in] pWillInfo The last will and testament information.
+ * @param[in] remainingLength The remaining length of the packet to be
+ * serialized.
+ *
+ * @return A pointer to the end of the encoded string.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+uint8_t * MQTT_SerializeConnectFixedHeader( uint8_t * pIndex,
+ const MQTTConnectInfo_t * pConnectInfo,
+ const MQTTPublishInfo_t * pWillInfo,
+ size_t remainingLength );
+/** @endcond */
+
+/**
+ * @fn uint8_t * MQTT_SerializeSubscribeHeader( size_t remainingLength, uint8_t * pIndex, uint16_t packetId );
+ * @brief Serialize the fixed part of the subscribe packet header.
+ *
+ * @param[in] remainingLength The remaining length of the packet to be
+ * serialized.
+ * @param[in] pIndex Pointer to the buffer where the header is to
+ * be serialized.
+ * @param[in] packetId The packet ID to be serialized.
+ *
+ * @return A pointer to the end of the encoded string.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+uint8_t * MQTT_SerializeSubscribeHeader( size_t remainingLength,
+ uint8_t * pIndex,
+ uint16_t packetId );
+/** @endcond */
+
+/**
+ * @fn uint8_t * MQTT_SerializeUnsubscribeHeader( size_t remainingLength, uint8_t * pIndex, uint16_t packetId );
+ * @brief Serialize the fixed part of the unsubscribe packet header.
+ *
+ * @param[in] remainingLength The remaining length of the packet to be
+ * serialized.
+ * @param[in] pIndex Pointer to the buffer where the header is to
+ * be serialized.
+ * @param[in] packetId The packet ID to be serialized.
+ *
+ * @return A pointer to the end of the encoded string.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+uint8_t * MQTT_SerializeUnsubscribeHeader( size_t remainingLength,
+ uint8_t * pIndex,
+ uint16_t packetId );
+/** @endcond */
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ }
+#endif
+/* *INDENT-ON* */
+
+#endif /* ifndef CORE_MQTT_SERIALIZER_H */
diff --git a/project/coreMQTT/coreMQTT/core_mqtt_state.c b/project/coreMQTT/coreMQTT/core_mqtt_state.c
new file mode 100644
index 0000000..8ad3f2b
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/core_mqtt_state.c
@@ -0,0 +1,1214 @@
+/*
+ * coreMQTT v2.1.1
+ * Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_mqtt_state.c
+ * @brief Implements the functions in core_mqtt_state.h.
+ */
+#include <assert.h>
+#include <string.h>
+#include "core_mqtt_state.h"
+
+/* Include config defaults header to get default values of configs. */
+#include "core_mqtt_config_defaults.h"
+
+#include "core_mqtt_default_logging.h"
+
+/*-----------------------------------------------------------*/
+
+/**
+ * @brief A global static variable used to generate the macro
+ * #MQTT_INVALID_STATE_COUNT of size_t length.
+ */
+static const size_t ZERO_SIZE_T = 0U;
+
+/**
+ * @brief This macro depicts the invalid value for the state publishes.
+ */
+#define MQTT_INVALID_STATE_COUNT ( ~ZERO_SIZE_T )
+
+/**
+ * @brief Create a 16-bit bitmap with bit set at specified position.
+ *
+ * @param[in] position The position at which the bit need to be set.
+ */
+#define UINT16_BITMAP_BIT_SET_AT( position ) ( ( uint16_t ) 0x01U << ( ( uint16_t ) position ) )
+
+/**
+ * @brief Set a bit in an 16-bit unsigned integer.
+ *
+ * @param[in] x The 16-bit unsigned integer to set a bit.
+ * @param[in] position The position at which the bit need to be set.
+ */
+#define UINT16_SET_BIT( x, position ) ( ( x ) = ( uint16_t ) ( ( x ) | ( UINT16_BITMAP_BIT_SET_AT( position ) ) ) )
+
+/**
+ * @brief Macro for checking if a bit is set in a 16-bit unsigned integer.
+ *
+ * @param[in] x The unsigned 16-bit integer to check.
+ * @param[in] position Which bit to check.
+ */
+#define UINT16_CHECK_BIT( x, position ) ( ( ( x ) & ( UINT16_BITMAP_BIT_SET_AT( position ) ) ) == ( UINT16_BITMAP_BIT_SET_AT( position ) ) )
+
+/*-----------------------------------------------------------*/
+
+/**
+ * @brief Test if a transition to new state is possible, when dealing with PUBLISHes.
+ *
+ * @param[in] currentState The current state.
+ * @param[in] newState State to transition to.
+ * @param[in] opType Reserve, Send, or Receive.
+ * @param[in] qos 0, 1, or 2.
+ *
+ * @note This function does not validate the current state, or the new state
+ * based on either the operation type or QoS. It assumes the new state is valid
+ * given the opType and QoS, which will be the case if calculated by
+ * MQTT_CalculateStatePublish().
+ *
+ * @return `true` if transition is possible, else `false`
+ */
+static bool validateTransitionPublish( MQTTPublishState_t currentState,
+ MQTTPublishState_t newState,
+ MQTTStateOperation_t opType,
+ MQTTQoS_t qos );
+
+/**
+ * @brief Test if a transition to a new state is possible, when dealing with acks.
+ *
+ * @param[in] currentState The current state.
+ * @param[in] newState State to transition to.
+ *
+ * @return `true` if transition is possible, else `false`.
+ */
+static bool validateTransitionAck( MQTTPublishState_t currentState,
+ MQTTPublishState_t newState );
+
+/**
+ * @brief Test if the publish corresponding to an ack is outgoing or incoming.
+ *
+ * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP.
+ * @param[in] opType Send, or Receive.
+ *
+ * @return `true` if corresponds to outgoing publish, else `false`.
+ */
+static bool isPublishOutgoing( MQTTPubAckType_t packetType,
+ MQTTStateOperation_t opType );
+
+/**
+ * @brief Find a packet ID in the state record.
+ *
+ * @param[in] records State record array.
+ * @param[in] recordCount Length of record array.
+ * @param[in] packetId packet ID to search for.
+ * @param[out] pQos QoS retrieved from record.
+ * @param[out] pCurrentState state retrieved from record.
+ *
+ * @return index of the packet id in the record if it exists, else the record length.
+ */
+static size_t findInRecord( const MQTTPubAckInfo_t * records,
+ size_t recordCount,
+ uint16_t packetId,
+ MQTTQoS_t * pQos,
+ MQTTPublishState_t * pCurrentState );
+
+/**
+ * @brief Compact records.
+ *
+ * Records are arranged in the relative order to maintain message ordering.
+ * This will lead to fragmentation and this function will help in defragmenting
+ * the records array.
+ *
+ * @param[in] records State record array.
+ * @param[in] recordCount Length of record array.
+ */
+static void compactRecords( MQTTPubAckInfo_t * records,
+ size_t recordCount );
+
+/**
+ * @brief Store a new entry in the state record.
+ *
+ * @param[in] records State record array.
+ * @param[in] recordCount Length of record array.
+ * @param[in] packetId Packet ID of new entry.
+ * @param[in] qos QoS of new entry.
+ * @param[in] publishState State of new entry.
+ *
+ * @return #MQTTSuccess, #MQTTNoMemory, or #MQTTStateCollision.
+ */
+static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records,
+ size_t recordCount,
+ uint16_t packetId,
+ MQTTQoS_t qos,
+ MQTTPublishState_t publishState );
+
+/**
+ * @brief Update and possibly delete an entry in the state record.
+ *
+ * @param[in] records State record array.
+ * @param[in] recordIndex index of record to update.
+ * @param[in] newState New state to update.
+ * @param[in] shouldDelete Whether an existing entry should be deleted.
+ */
+static void updateRecord( MQTTPubAckInfo_t * records,
+ size_t recordIndex,
+ MQTTPublishState_t newState,
+ bool shouldDelete );
+
+/**
+ * @brief Get the packet ID and index of an outgoing publish in specified
+ * states.
+ *
+ * @param[in] pMqttContext Initialized MQTT context.
+ * @param[in] searchStates The states to search for in 2-byte bit map.
+ * @param[in,out] pCursor Index at which to start searching.
+ *
+ * @return Packet ID of the outgoing publish.
+ */
+static uint16_t stateSelect( const MQTTContext_t * pMqttContext,
+ uint16_t searchStates,
+ MQTTStateCursor_t * pCursor );
+
+/**
+ * @brief Update the state records for an ACK after state transition
+ * validations.
+ *
+ * @param[in] records State records pointer.
+ * @param[in] maxRecordCount The maximum number of records.
+ * @param[in] recordIndex Index at which the record is stored.
+ * @param[in] packetId Packet id of the packet.
+ * @param[in] currentState Current state of the publish record.
+ * @param[in] newState New state of the publish.
+ *
+ * @return #MQTTIllegalState, or #MQTTSuccess.
+ */
+static MQTTStatus_t updateStateAck( MQTTPubAckInfo_t * records,
+ size_t maxRecordCount,
+ size_t recordIndex,
+ uint16_t packetId,
+ MQTTPublishState_t currentState,
+ MQTTPublishState_t newState );
+
+/**
+ * @brief Update the state record for a PUBLISH packet after validating
+ * the state transitions.
+ *
+ * @param[in] pMqttContext Initialized MQTT context.
+ * @param[in] recordIndex Index in state records at which publish record exists.
+ * @param[in] packetId ID of the PUBLISH packet.
+ * @param[in] opType Send or Receive.
+ * @param[in] qos 0, 1, or 2.
+ * @param[in] currentState Current state of the publish record.
+ * @param[in] newState New state of the publish record.
+ *
+ * @return #MQTTIllegalState, #MQTTStateCollision or #MQTTSuccess.
+ */
+static MQTTStatus_t updateStatePublish( const MQTTContext_t * pMqttContext,
+ size_t recordIndex,
+ uint16_t packetId,
+ MQTTStateOperation_t opType,
+ MQTTQoS_t qos,
+ MQTTPublishState_t currentState,
+ MQTTPublishState_t newState );
+
+/*-----------------------------------------------------------*/
+
+static bool validateTransitionPublish( MQTTPublishState_t currentState,
+ MQTTPublishState_t newState,
+ MQTTStateOperation_t opType,
+ MQTTQoS_t qos )
+{
+ bool isValid = false;
+
+ switch( currentState )
+ {
+ case MQTTStateNull:
+
+ /* Transitions from null occur when storing a new entry into the record. */
+ if( opType == MQTT_RECEIVE )
+ {
+ isValid = ( newState == MQTTPubAckSend ) || ( newState == MQTTPubRecSend );
+ }
+
+ break;
+
+ case MQTTPublishSend:
+
+ /* Outgoing publish. All such publishes start in this state due to
+ * the reserve operation. */
+ switch( qos )
+ {
+ case MQTTQoS1:
+ isValid = newState == MQTTPubAckPending;
+ break;
+
+ case MQTTQoS2:
+ isValid = newState == MQTTPubRecPending;
+ break;
+
+ case MQTTQoS0:
+ default:
+ /* QoS 0 is checked before calling this function. */
+ break;
+ }
+
+ break;
+
+ /* Below cases are for validating the resends of publish when a session is
+ * reestablished. */
+ case MQTTPubAckPending:
+
+ /* When a session is reestablished, outgoing QoS1 publishes in state
+ * #MQTTPubAckPending can be resent. The state remains the same. */
+ isValid = newState == MQTTPubAckPending;
+
+ break;
+
+ case MQTTPubRecPending:
+
+ /* When a session is reestablished, outgoing QoS2 publishes in state
+ * #MQTTPubRecPending can be resent. The state remains the same. */
+ isValid = newState == MQTTPubRecPending;
+
+ break;
+
+ case MQTTPubAckSend:
+ case MQTTPubCompPending:
+ case MQTTPubCompSend:
+ case MQTTPubRecSend:
+ case MQTTPubRelPending:
+ case MQTTPubRelSend:
+ case MQTTPublishDone:
+ default:
+ /* For a PUBLISH, we should not start from any other state. */
+ break;
+ }
+
+ return isValid;
+}
+
+/*-----------------------------------------------------------*/
+
+static bool validateTransitionAck( MQTTPublishState_t currentState,
+ MQTTPublishState_t newState )
+{
+ bool isValid = false;
+
+ switch( currentState )
+ {
+ case MQTTPubAckSend:
+ /* Incoming publish, QoS 1. */
+ case MQTTPubAckPending:
+ /* Outgoing publish, QoS 1. */
+ isValid = newState == MQTTPublishDone;
+ break;
+
+ case MQTTPubRecSend:
+ /* Incoming publish, QoS 2. */
+ isValid = newState == MQTTPubRelPending;
+ break;
+
+ case MQTTPubRelPending:
+
+ /* Incoming publish, QoS 2.
+ * There are 2 valid transitions possible.
+ * 1. MQTTPubRelPending -> MQTTPubCompSend : A PUBREL ack is received
+ * when publish record state is MQTTPubRelPending. This is the
+ * normal state transition without any connection interruptions.
+ * 2. MQTTPubRelPending -> MQTTPubRelPending : Receiving a duplicate
+ * QoS2 publish can result in a transition to the same state.
+ * This can happen in the below state transition.
+ * 1. Incoming publish received.
+ * 2. PUBREC ack sent and state is now MQTTPubRelPending.
+ * 3. TCP connection failure and broker didn't receive the PUBREC.
+ * 4. Reestablished MQTT session.
+ * 5. MQTT broker resent the un-acked publish.
+ * 6. Publish is received when publish record state is in
+ * MQTTPubRelPending.
+ * 7. Sending out a PUBREC will result in this transition
+ * to the same state. */
+ isValid = ( newState == MQTTPubCompSend ) ||
+ ( newState == MQTTPubRelPending );
+ break;
+
+ case MQTTPubCompSend:
+
+ /* Incoming publish, QoS 2.
+ * There are 2 valid transitions possible.
+ * 1. MQTTPubCompSend -> MQTTPublishDone : A PUBCOMP ack is sent
+ * after receiving a PUBREL from broker. This is the
+ * normal state transition without any connection interruptions.
+ * 2. MQTTPubCompSend -> MQTTPubCompSend : Receiving a duplicate PUBREL
+ * can result in a transition to the same state.
+ * This can happen in the below state transition.
+ * 1. A TCP connection failure happened before sending a PUBCOMP
+ * for an incoming PUBREL.
+ * 2. Reestablished an MQTT session.
+ * 3. MQTT broker resent the un-acked PUBREL.
+ * 4. Receiving the PUBREL again will result in this transition
+ * to the same state. */
+ isValid = ( newState == MQTTPublishDone ) ||
+ ( newState == MQTTPubCompSend );
+ break;
+
+ case MQTTPubRecPending:
+ /* Outgoing publish, Qos 2. */
+ isValid = newState == MQTTPubRelSend;
+ break;
+
+ case MQTTPubRelSend:
+ /* Outgoing publish, Qos 2. */
+ isValid = newState == MQTTPubCompPending;
+ break;
+
+ case MQTTPubCompPending:
+
+ /* Outgoing publish, Qos 2.
+ * There are 2 valid transitions possible.
+ * 1. MQTTPubCompPending -> MQTTPublishDone : A PUBCOMP is received.
+ * This marks the complete state transition for the publish packet.
+ * This is the normal state transition without any connection
+ * interruptions.
+ * 2. MQTTPubCompPending -> MQTTPubCompPending : Resending a PUBREL for
+ * packets in state #MQTTPubCompPending can result in this
+ * transition to the same state.
+ * This can happen in the below state transition.
+ * 1. A TCP connection failure happened before receiving a PUBCOMP
+ * for an outgoing PUBREL.
+ * 2. An MQTT session is reestablished.
+ * 3. Resending the un-acked PUBREL results in this transition
+ * to the same state. */
+ isValid = ( newState == MQTTPublishDone ) ||
+ ( newState == MQTTPubCompPending );
+ break;
+
+ case MQTTPublishDone:
+ /* Done state should transition to invalid since it will be removed from the record. */
+ case MQTTPublishSend:
+ /* If an ack was sent/received we shouldn't have been in this state. */
+ case MQTTStateNull:
+ /* If an ack was sent/received the record should exist. */
+ default:
+ /* Invalid. */
+ break;
+ }
+
+ return isValid;
+}
+
+/*-----------------------------------------------------------*/
+
+static bool isPublishOutgoing( MQTTPubAckType_t packetType,
+ MQTTStateOperation_t opType )
+{
+ bool isOutgoing = false;
+
+ switch( packetType )
+ {
+ case MQTTPuback:
+ case MQTTPubrec:
+ case MQTTPubcomp:
+ isOutgoing = opType == MQTT_RECEIVE;
+ break;
+
+ case MQTTPubrel:
+ isOutgoing = opType == MQTT_SEND;
+ break;
+
+ default:
+ /* No other ack type. */
+ break;
+ }
+
+ return isOutgoing;
+}
+
+/*-----------------------------------------------------------*/
+
+static size_t findInRecord( const MQTTPubAckInfo_t * records,
+ size_t recordCount,
+ uint16_t packetId,
+ MQTTQoS_t * pQos,
+ MQTTPublishState_t * pCurrentState )
+{
+ size_t index = 0;
+
+ assert( packetId != MQTT_PACKET_ID_INVALID );
+
+ *pCurrentState = MQTTStateNull;
+
+ for( index = 0; index < recordCount; index++ )
+ {
+ if( records[ index ].packetId == packetId )
+ {
+ *pQos = records[ index ].qos;
+ *pCurrentState = records[ index ].publishState;
+ break;
+ }
+ }
+
+ if( index == recordCount )
+ {
+ index = MQTT_INVALID_STATE_COUNT;
+ }
+
+ return index;
+}
+
+/*-----------------------------------------------------------*/
+
+static void compactRecords( MQTTPubAckInfo_t * records,
+ size_t recordCount )
+{
+ size_t index = 0;
+ size_t emptyIndex = MQTT_INVALID_STATE_COUNT;
+
+ assert( records != NULL );
+
+ /* Find the empty spots and fill those with non empty values. */
+ for( ; index < recordCount; index++ )
+ {
+ /* Find the first empty spot. */
+ if( records[ index ].packetId == MQTT_PACKET_ID_INVALID )
+ {
+ if( emptyIndex == MQTT_INVALID_STATE_COUNT )
+ {
+ emptyIndex = index;
+ }
+ }
+ else
+ {
+ if( emptyIndex != MQTT_INVALID_STATE_COUNT )
+ {
+ /* Copy over the contents at non empty index to empty index. */
+ records[ emptyIndex ].packetId = records[ index ].packetId;
+ records[ emptyIndex ].qos = records[ index ].qos;
+ records[ emptyIndex ].publishState = records[ index ].publishState;
+
+ /* Mark the record at current non empty index as invalid. */
+ records[ index ].packetId = MQTT_PACKET_ID_INVALID;
+ records[ index ].qos = MQTTQoS0;
+ records[ index ].publishState = MQTTStateNull;
+
+ /* Advance the emptyIndex. */
+ emptyIndex++;
+ }
+ }
+ }
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records,
+ size_t recordCount,
+ uint16_t packetId,
+ MQTTQoS_t qos,
+ MQTTPublishState_t publishState )
+{
+ MQTTStatus_t status = MQTTNoMemory;
+ int32_t index = 0;
+ size_t availableIndex = recordCount;
+ bool validEntryFound = false;
+
+ assert( packetId != MQTT_PACKET_ID_INVALID );
+ assert( qos != MQTTQoS0 );
+
+ /* Check if we have to compact the records. This is known by checking if
+ * the last spot in the array is filled. */
+ if( records[ recordCount - 1U ].packetId != MQTT_PACKET_ID_INVALID )
+ {
+ compactRecords( records, recordCount );
+ }
+
+ /* Start from end so first available index will be populated.
+ * Available index is always found after the last element in the records.
+ * This is to make sure the relative order of the records in order to meet
+ * the message ordering requirement of MQTT spec 3.1.1. */
+ for( index = ( ( int32_t ) recordCount - 1 ); index >= 0; index-- )
+ {
+ /* Available index is only found after packet at the highest index. */
+ if( records[ index ].packetId == MQTT_PACKET_ID_INVALID )
+ {
+ if( validEntryFound == false )
+ {
+ availableIndex = ( size_t ) index;
+ }
+ }
+ else
+ {
+ /* A non-empty spot found in the records. */
+ validEntryFound = true;
+
+ if( records[ index ].packetId == packetId )
+ {
+ /* Collision. */
+ LogError( ( "Collision when adding PacketID=%u at index=%d.",
+ ( unsigned int ) packetId,
+ ( int ) index ) );
+
+ status = MQTTStateCollision;
+ availableIndex = recordCount;
+ break;
+ }
+ }
+ }
+
+ if( availableIndex < recordCount )
+ {
+ records[ availableIndex ].packetId = packetId;
+ records[ availableIndex ].qos = qos;
+ records[ availableIndex ].publishState = publishState;
+ status = MQTTSuccess;
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static void updateRecord( MQTTPubAckInfo_t * records,
+ size_t recordIndex,
+ MQTTPublishState_t newState,
+ bool shouldDelete )
+{
+ assert( records != NULL );
+
+ if( shouldDelete == true )
+ {
+ /* Mark the record as invalid. */
+ records[ recordIndex ].packetId = MQTT_PACKET_ID_INVALID;
+ records[ recordIndex ].qos = MQTTQoS0;
+ records[ recordIndex ].publishState = MQTTStateNull;
+ }
+ else
+ {
+ records[ recordIndex ].publishState = newState;
+ }
+}
+
+/*-----------------------------------------------------------*/
+
+static uint16_t stateSelect( const MQTTContext_t * pMqttContext,
+ uint16_t searchStates,
+ MQTTStateCursor_t * pCursor )
+{
+ uint16_t packetId = MQTT_PACKET_ID_INVALID;
+ uint16_t outgoingStates = 0U;
+ const MQTTPubAckInfo_t * records = NULL;
+ size_t maxCount;
+ bool stateCheck = false;
+
+ assert( pMqttContext != NULL );
+ assert( searchStates != 0U );
+ assert( pCursor != NULL );
+
+ /* Create a bit map with all the outgoing publish states. */
+ UINT16_SET_BIT( outgoingStates, MQTTPublishSend );
+ UINT16_SET_BIT( outgoingStates, MQTTPubAckPending );
+ UINT16_SET_BIT( outgoingStates, MQTTPubRecPending );
+ UINT16_SET_BIT( outgoingStates, MQTTPubRelSend );
+ UINT16_SET_BIT( outgoingStates, MQTTPubCompPending );
+
+ /* Only outgoing publish records need to be searched. */
+ assert( ( outgoingStates & searchStates ) > 0U );
+ assert( ( ~outgoingStates & searchStates ) == 0U );
+
+ records = pMqttContext->outgoingPublishRecords;
+ maxCount = pMqttContext->outgoingPublishRecordMaxCount;
+
+ while( *pCursor < maxCount )
+ {
+ /* Check if any of the search states are present. */
+ stateCheck = UINT16_CHECK_BIT( searchStates, records[ *pCursor ].publishState );
+
+ if( stateCheck == true )
+ {
+ packetId = records[ *pCursor ].packetId;
+ ( *pCursor )++;
+ break;
+ }
+
+ ( *pCursor )++;
+ }
+
+ return packetId;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType,
+ MQTTStateOperation_t opType,
+ MQTTQoS_t qos )
+{
+ MQTTPublishState_t calculatedState = MQTTStateNull;
+ /* There are more QoS2 cases than QoS1, so initialize to that. */
+ bool qosValid = qos == MQTTQoS2;
+
+ switch( packetType )
+ {
+ case MQTTPuback:
+ qosValid = qos == MQTTQoS1;
+ calculatedState = MQTTPublishDone;
+ break;
+
+ case MQTTPubrec:
+
+ /* Incoming publish: send PUBREC, PUBREL pending.
+ * Outgoing publish: receive PUBREC, send PUBREL. */
+ calculatedState = ( opType == MQTT_SEND ) ? MQTTPubRelPending : MQTTPubRelSend;
+ break;
+
+ case MQTTPubrel:
+
+ /* Incoming publish: receive PUBREL, send PUBCOMP.
+ * Outgoing publish: send PUBREL, PUBCOMP pending. */
+ calculatedState = ( opType == MQTT_SEND ) ? MQTTPubCompPending : MQTTPubCompSend;
+ break;
+
+ case MQTTPubcomp:
+ calculatedState = MQTTPublishDone;
+ break;
+
+ default:
+ /* No other ack type. */
+ break;
+ }
+
+ /* Sanity check, make sure ack and QoS agree. */
+ if( qosValid == false )
+ {
+ calculatedState = MQTTStateNull;
+ }
+
+ return calculatedState;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t updateStateAck( MQTTPubAckInfo_t * records,
+ size_t maxRecordCount,
+ size_t recordIndex,
+ uint16_t packetId,
+ MQTTPublishState_t currentState,
+ MQTTPublishState_t newState )
+{
+ MQTTStatus_t status = MQTTIllegalState;
+ bool shouldDeleteRecord = false;
+ bool isTransitionValid = false;
+
+ assert( records != NULL );
+
+ /* Record to be deleted if the state transition is completed or if a PUBREC
+ * is received for an outgoing QoS2 publish. When a PUBREC is received,
+ * record is deleted and added back to the end of the records to maintain
+ * ordering for PUBRELs. */
+ shouldDeleteRecord = ( newState == MQTTPublishDone ) || ( newState == MQTTPubRelSend );
+ isTransitionValid = validateTransitionAck( currentState, newState );
+
+ if( isTransitionValid == true )
+ {
+ status = MQTTSuccess;
+
+ /* Update record for acks. When sending or receiving acks for packets that
+ * are resent during a session reestablishment, the new state and
+ * current state can be the same. No update of record required in that case. */
+ if( currentState != newState )
+ {
+ updateRecord( records,
+ recordIndex,
+ newState,
+ shouldDeleteRecord );
+
+ /* For QoS2 messages, in order to preserve the message ordering, when
+ * a PUBREC is received for an outgoing publish, the record should be
+ * moved to the last. This move will help preserve the order in which
+ * a PUBREL needs to be resent in case of a session reestablishment. */
+ if( newState == MQTTPubRelSend )
+ {
+ status = addRecord( records,
+ maxRecordCount,
+ packetId,
+ MQTTQoS2,
+ MQTTPubRelSend );
+ }
+ }
+ }
+ else
+ {
+ /* Invalid state transition. */
+ LogError( ( "Invalid transition from state %s to state %s.",
+ MQTT_State_strerror( currentState ),
+ MQTT_State_strerror( newState ) ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+static MQTTStatus_t updateStatePublish( const MQTTContext_t * pMqttContext,
+ size_t recordIndex,
+ uint16_t packetId,
+ MQTTStateOperation_t opType,
+ MQTTQoS_t qos,
+ MQTTPublishState_t currentState,
+ MQTTPublishState_t newState )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ bool isTransitionValid = false;
+
+ assert( pMqttContext != NULL );
+ assert( packetId != MQTT_PACKET_ID_INVALID );
+ assert( qos != MQTTQoS0 );
+
+ /* This will always succeed for an incoming publish. This is due to the fact
+ * that the passed in currentState must be MQTTStateNull, since
+ * #MQTT_UpdateStatePublish does not perform a lookup for receives. */
+ isTransitionValid = validateTransitionPublish( currentState, newState, opType, qos );
+
+ if( isTransitionValid == true )
+ {
+ /* addRecord will check for collisions. */
+ if( opType == MQTT_RECEIVE )
+ {
+ status = addRecord( pMqttContext->incomingPublishRecords,
+ pMqttContext->incomingPublishRecordMaxCount,
+ packetId,
+ qos,
+ newState );
+ }
+ /* Send operation. */
+ else
+ {
+ /* Skip updating record when publish is resend and no state
+ * update is required. */
+ if( currentState != newState )
+ {
+ updateRecord( pMqttContext->outgoingPublishRecords,
+ recordIndex,
+ newState,
+ false );
+ }
+ }
+ }
+ else
+ {
+ status = MQTTIllegalState;
+ LogError( ( "Invalid transition from state %s to state %s.",
+ MQTT_State_strerror( currentState ),
+ MQTT_State_strerror( newState ) ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_ReserveState( const MQTTContext_t * pMqttContext,
+ uint16_t packetId,
+ MQTTQoS_t qos )
+{
+ MQTTStatus_t status = MQTTSuccess;
+
+ if( qos == MQTTQoS0 )
+ {
+ status = MQTTSuccess;
+ }
+ else if( ( packetId == MQTT_PACKET_ID_INVALID ) || ( pMqttContext == NULL ) )
+ {
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Collisions are detected when adding the record. */
+ status = addRecord( pMqttContext->outgoingPublishRecords,
+ pMqttContext->outgoingPublishRecordMaxCount,
+ packetId,
+ qos,
+ MQTTPublishSend );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType,
+ MQTTQoS_t qos )
+{
+ MQTTPublishState_t calculatedState = MQTTStateNull;
+
+ switch( qos )
+ {
+ case MQTTQoS0:
+ calculatedState = MQTTPublishDone;
+ break;
+
+ case MQTTQoS1:
+ calculatedState = ( opType == MQTT_SEND ) ? MQTTPubAckPending : MQTTPubAckSend;
+ break;
+
+ case MQTTQoS2:
+ calculatedState = ( opType == MQTT_SEND ) ? MQTTPubRecPending : MQTTPubRecSend;
+ break;
+
+ default:
+ /* No other QoS values. */
+ break;
+ }
+
+ return calculatedState;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_UpdateStatePublish( const MQTTContext_t * pMqttContext,
+ uint16_t packetId,
+ MQTTStateOperation_t opType,
+ MQTTQoS_t qos,
+ MQTTPublishState_t * pNewState )
+{
+ MQTTPublishState_t newState = MQTTStateNull;
+ MQTTPublishState_t currentState = MQTTStateNull;
+ MQTTStatus_t mqttStatus = MQTTSuccess;
+ size_t recordIndex = MQTT_INVALID_STATE_COUNT;
+ MQTTQoS_t foundQoS = MQTTQoS0;
+
+ if( ( pMqttContext == NULL ) || ( pNewState == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pMqttContext=%p, pNewState=%p",
+ ( void * ) pMqttContext,
+ ( void * ) pNewState ) );
+
+ mqttStatus = MQTTBadParameter;
+ }
+ else if( qos == MQTTQoS0 )
+ {
+ /* QoS 0 publish. Do nothing. */
+ *pNewState = MQTTPublishDone;
+ }
+ else if( packetId == MQTT_PACKET_ID_INVALID )
+ {
+ /* Publishes > QoS 0 need a valid packet ID. */
+ mqttStatus = MQTTBadParameter;
+ }
+ else if( opType == MQTT_SEND )
+ {
+ /* Search record for entry so we can check QoS. */
+ recordIndex = findInRecord( pMqttContext->outgoingPublishRecords,
+ pMqttContext->outgoingPublishRecordMaxCount,
+ packetId,
+ &foundQoS,
+ ¤tState );
+
+ if( ( recordIndex == MQTT_INVALID_STATE_COUNT ) || ( foundQoS != qos ) )
+ {
+ /* Entry should match with supplied QoS. */
+ mqttStatus = MQTTBadParameter;
+ }
+ }
+ else
+ {
+ /* QoS 1 or 2 receive. Nothing to be done. */
+ }
+
+ if( ( qos != MQTTQoS0 ) && ( mqttStatus == MQTTSuccess ) )
+ {
+ newState = MQTT_CalculateStatePublish( opType, qos );
+ /* Validate state transition and update state records. */
+ mqttStatus = updateStatePublish( pMqttContext,
+ recordIndex,
+ packetId,
+ opType,
+ qos,
+ currentState,
+ newState );
+
+ /* Update output parameter on success. */
+ if( mqttStatus == MQTTSuccess )
+ {
+ *pNewState = newState;
+ }
+ }
+
+ return mqttStatus;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_RemoveStateRecord( const MQTTContext_t * pMqttContext,
+ uint16_t packetId )
+{
+ MQTTStatus_t status = MQTTSuccess;
+ MQTTPubAckInfo_t * records;
+ size_t recordIndex;
+ /* Current state is updated by the findInRecord function. */
+ MQTTPublishState_t currentState;
+ MQTTQoS_t qos = MQTTQoS0;
+
+
+ if( ( pMqttContext == NULL ) || ( ( pMqttContext->outgoingPublishRecords == NULL ) ) )
+ {
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ records = pMqttContext->outgoingPublishRecords;
+
+ recordIndex = findInRecord( records,
+ pMqttContext->outgoingPublishRecordMaxCount,
+ packetId,
+ &qos,
+ ¤tState );
+
+ if( currentState == MQTTStateNull )
+ {
+ status = MQTTBadParameter;
+ }
+ else if( ( qos != MQTTQoS1 ) && ( qos != MQTTQoS2 ) )
+ {
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ /* Delete the record. */
+ updateRecord( records,
+ recordIndex,
+ MQTTStateNull,
+ true );
+ }
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+MQTTStatus_t MQTT_UpdateStateAck( const MQTTContext_t * pMqttContext,
+ uint16_t packetId,
+ MQTTPubAckType_t packetType,
+ MQTTStateOperation_t opType,
+ MQTTPublishState_t * pNewState )
+{
+ MQTTPublishState_t newState = MQTTStateNull;
+ MQTTPublishState_t currentState = MQTTStateNull;
+ bool isOutgoingPublish = isPublishOutgoing( packetType, opType );
+ MQTTQoS_t qos = MQTTQoS0;
+ size_t maxRecordCount = MQTT_INVALID_STATE_COUNT;
+ size_t recordIndex = MQTT_INVALID_STATE_COUNT;
+
+ MQTTPubAckInfo_t * records = NULL;
+ MQTTStatus_t status = MQTTBadResponse;
+
+ if( ( pMqttContext == NULL ) || ( pNewState == NULL ) )
+ {
+ LogError( ( "Argument cannot be NULL: pMqttContext=%p, pNewState=%p.",
+ ( void * ) pMqttContext,
+ ( void * ) pNewState ) );
+ status = MQTTBadParameter;
+ }
+ else if( packetId == MQTT_PACKET_ID_INVALID )
+ {
+ LogError( ( "Packet ID must be nonzero." ) );
+ status = MQTTBadParameter;
+ }
+ else if( packetType > MQTTPubcomp )
+ {
+ LogError( ( "Invalid packet type %u.", ( unsigned int ) packetType ) );
+ status = MQTTBadParameter;
+ }
+ else
+ {
+ if( isOutgoingPublish == true )
+ {
+ records = pMqttContext->outgoingPublishRecords;
+ maxRecordCount = pMqttContext->outgoingPublishRecordMaxCount;
+ }
+ else
+ {
+ records = pMqttContext->incomingPublishRecords;
+ maxRecordCount = pMqttContext->incomingPublishRecordMaxCount;
+ }
+
+ recordIndex = findInRecord( records,
+ maxRecordCount,
+ packetId,
+ &qos,
+ ¤tState );
+ }
+
+ if( recordIndex != MQTT_INVALID_STATE_COUNT )
+ {
+ newState = MQTT_CalculateStateAck( packetType, opType, qos );
+
+ /* Validate state transition and update state record. */
+ status = updateStateAck( records,
+ maxRecordCount,
+ recordIndex,
+ packetId,
+ currentState,
+ newState );
+
+ /* Update the output parameter. */
+ if( status == MQTTSuccess )
+ {
+ *pNewState = newState;
+ }
+ }
+ else
+ {
+ LogError( ( "No matching record found for publish: PacketId=%u.",
+ ( unsigned int ) packetId ) );
+ }
+
+ return status;
+}
+
+/*-----------------------------------------------------------*/
+
+uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext,
+ MQTTStateCursor_t * pCursor,
+ MQTTPublishState_t * pState )
+{
+ uint16_t packetId = MQTT_PACKET_ID_INVALID;
+ uint16_t searchStates = 0U;
+
+ /* Validate arguments. */
+ if( ( pMqttContext == NULL ) || ( pCursor == NULL ) || ( pState == NULL ) )
+ {
+ LogError( ( "Arguments cannot be NULL pMqttContext=%p, pCursor=%p"
+ " pState=%p.",
+ ( void * ) pMqttContext,
+ ( void * ) pCursor,
+ ( void * ) pState ) );
+ }
+ else
+ {
+ /* PUBREL for packets in state #MQTTPubCompPending and #MQTTPubRelSend
+ * would need to be resent when a session is reestablished.*/
+ UINT16_SET_BIT( searchStates, MQTTPubCompPending );
+ UINT16_SET_BIT( searchStates, MQTTPubRelSend );
+ packetId = stateSelect( pMqttContext, searchStates, pCursor );
+
+ /* The state needs to be in #MQTTPubRelSend for sending PUBREL. */
+ if( packetId != MQTT_PACKET_ID_INVALID )
+ {
+ *pState = MQTTPubRelSend;
+ }
+ }
+
+ return packetId;
+}
+
+/*-----------------------------------------------------------*/
+
+uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext,
+ MQTTStateCursor_t * pCursor )
+{
+ uint16_t packetId = MQTT_PACKET_ID_INVALID;
+ uint16_t searchStates = 0U;
+
+ /* Validate arguments. */
+ if( ( pMqttContext == NULL ) || ( pCursor == NULL ) )
+ {
+ LogError( ( "Arguments cannot be NULL pMqttContext=%p, pCursor=%p",
+ ( void * ) pMqttContext,
+ ( void * ) pCursor ) );
+ }
+ else
+ {
+ /* Packets in state #MQTTPublishSend, #MQTTPubAckPending and
+ * #MQTTPubRecPending would need to be resent when a session is
+ * reestablished. */
+ UINT16_SET_BIT( searchStates, MQTTPublishSend );
+ UINT16_SET_BIT( searchStates, MQTTPubAckPending );
+ UINT16_SET_BIT( searchStates, MQTTPubRecPending );
+
+ packetId = stateSelect( pMqttContext, searchStates, pCursor );
+ }
+
+ return packetId;
+}
+
+/*-----------------------------------------------------------*/
+
+const char * MQTT_State_strerror( MQTTPublishState_t state )
+{
+ const char * str = NULL;
+
+ switch( state )
+ {
+ case MQTTStateNull:
+ str = "MQTTStateNull";
+ break;
+
+ case MQTTPublishSend:
+ str = "MQTTPublishSend";
+ break;
+
+ case MQTTPubAckSend:
+ str = "MQTTPubAckSend";
+ break;
+
+ case MQTTPubRecSend:
+ str = "MQTTPubRecSend";
+ break;
+
+ case MQTTPubRelSend:
+ str = "MQTTPubRelSend";
+ break;
+
+ case MQTTPubCompSend:
+ str = "MQTTPubCompSend";
+ break;
+
+ case MQTTPubAckPending:
+ str = "MQTTPubAckPending";
+ break;
+
+ case MQTTPubRecPending:
+ str = "MQTTPubRecPending";
+ break;
+
+ case MQTTPubRelPending:
+ str = "MQTTPubRelPending";
+ break;
+
+ case MQTTPubCompPending:
+ str = "MQTTPubCompPending";
+ break;
+
+ case MQTTPublishDone:
+ str = "MQTTPublishDone";
+ break;
+
+ default:
+ /* Invalid state received. */
+ str = "Invalid MQTT State";
+ break;
+ }
+
+ return str;
+}
+
+/*-----------------------------------------------------------*/
diff --git a/project/coreMQTT/coreMQTT/core_mqtt_state.h b/project/coreMQTT/coreMQTT/core_mqtt_state.h
new file mode 100644
index 0000000..0644489
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/core_mqtt_state.h
@@ -0,0 +1,310 @@
+/*
+ * coreMQTT v2.1.1
+ * Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_mqtt_state.h
+ * @brief Function to keep state of MQTT PUBLISH packet deliveries.
+ */
+#ifndef CORE_MQTT_STATE_H
+#define CORE_MQTT_STATE_H
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+/* *INDENT-ON* */
+
+#include "core_mqtt.h"
+
+/**
+ * @ingroup mqtt_constants
+ * @brief Initializer value for an #MQTTStateCursor_t, indicating a search
+ * should start at the beginning of a state record array
+ */
+#define MQTT_STATE_CURSOR_INITIALIZER ( ( size_t ) 0 )
+
+/**
+ * @ingroup mqtt_basic_types
+ * @brief Cursor for iterating through state records.
+ */
+typedef size_t MQTTStateCursor_t;
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this section, this enum is private.
+ *
+ * @brief Value indicating either send or receive.
+ */
+typedef enum MQTTStateOperation
+{
+ MQTT_SEND,
+ MQTT_RECEIVE
+} MQTTStateOperation_t;
+/** @endcond */
+
+/**
+ * @fn MQTTStatus_t MQTT_ReserveState( const MQTTContext_t * pMqttContext, uint16_t packetId, MQTTQoS_t qos );
+ * @brief Reserve an entry for an outgoing QoS 1 or Qos 2 publish.
+ *
+ * @param[in] pMqttContext Initialized MQTT context.
+ * @param[in] packetId The ID of the publish packet.
+ * @param[in] qos 1 or 2.
+ *
+ * @return MQTTSuccess, MQTTNoMemory, or MQTTStateCollision.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+MQTTStatus_t MQTT_ReserveState( const MQTTContext_t * pMqttContext,
+ uint16_t packetId,
+ MQTTQoS_t qos );
+/** @endcond */
+
+/**
+ * @fn MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, MQTTQoS_t qos )
+ * @brief Calculate the new state for a publish from its qos and operation type.
+ *
+ * @param[in] opType Send or Receive.
+ * @param[in] qos 0, 1, or 2.
+ *
+ * @return The calculated state.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType,
+ MQTTQoS_t qos );
+/** @endcond */
+
+/**
+ * @fn MQTTStatus_t MQTT_UpdateStatePublish( const MQTTContext_t * pMqttContext, uint16_t packetId, MQTTStateOperation_t opType, MQTTQoS_t qos, MQTTPublishState_t * pNewState );
+ * @brief Update the state record for a PUBLISH packet.
+ *
+ * @param[in] pMqttContext Initialized MQTT context.
+ * @param[in] packetId ID of the PUBLISH packet.
+ * @param[in] opType Send or Receive.
+ * @param[in] qos 0, 1, or 2.
+ * @param[out] pNewState Updated state of the publish.
+ *
+ * @return #MQTTBadParameter, #MQTTIllegalState, #MQTTStateCollision or
+ * #MQTTSuccess.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+MQTTStatus_t MQTT_UpdateStatePublish( const MQTTContext_t * pMqttContext,
+ uint16_t packetId,
+ MQTTStateOperation_t opType,
+ MQTTQoS_t qos,
+ MQTTPublishState_t * pNewState );
+/** @endcond */
+
+/**
+ * @fn MQTTStatus_t MQTT_RemoveStateRecord( const MQTTContext_t * pMqttContext, uint16_t packetId );
+ * @brief Remove the state record for a PUBLISH packet.
+ *
+ * @param[in] pMqttContext Initialized MQTT context.
+ * @param[in] packetId ID of the PUBLISH packet.
+ *
+ * @return #MQTTBadParameter or #MQTTSuccess.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+MQTTStatus_t MQTT_RemoveStateRecord( const MQTTContext_t * pMqttContext,
+ uint16_t packetId );
+/** @endcond */
+
+/**
+ * @fn MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTQoS_t qos );
+ * @brief Calculate the state from a PUBACK, PUBREC, PUBREL, or PUBCOMP.
+ *
+ * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP.
+ * @param[in] opType Send or Receive.
+ * @param[in] qos 1 or 2.
+ *
+ * @return The calculated state.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType,
+ MQTTStateOperation_t opType,
+ MQTTQoS_t qos );
+/** @endcond */
+
+/**
+ * @fn MQTTStatus_t MQTT_UpdateStateAck( const MQTTContext_t * pMqttContext, uint16_t packetId, MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTPublishState_t * pNewState );
+ * @brief Update the state record for an ACKed publish.
+ *
+ * @param[in] pMqttContext Initialized MQTT context.
+ * @param[in] packetId ID of the ack packet.
+ * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP.
+ * @param[in] opType Send or Receive.
+ * @param[out] pNewState Updated state of the publish.
+ *
+ * @return #MQTTBadParameter if an invalid parameter is passed;
+ * #MQTTBadResponse if the packet from the network is not found in the records;
+ * #MQTTIllegalState if the requested update would result in an illegal transition;
+ * #MQTTSuccess otherwise.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+MQTTStatus_t MQTT_UpdateStateAck( const MQTTContext_t * pMqttContext,
+ uint16_t packetId,
+ MQTTPubAckType_t packetType,
+ MQTTStateOperation_t opType,
+ MQTTPublishState_t * pNewState );
+/** @endcond */
+
+/**
+ * @fn uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, MQTTStateCursor_t * pCursor, MQTTPublishState_t * pState );
+ * @brief Get the packet ID of next pending PUBREL ack to be resent.
+ *
+ * This function will need to be called to get the packet for which a PUBREL
+ * need to be sent when a session is reestablished. Calling this function
+ * repeatedly until packet id is 0 will give all the packets for which
+ * a PUBREL need to be resent in the correct order.
+ *
+ * @param[in] pMqttContext Initialized MQTT context.
+ * @param[in,out] pCursor Index at which to start searching.
+ * @param[out] pState State indicating that PUBREL packet need to be sent.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext,
+ MQTTStateCursor_t * pCursor,
+ MQTTPublishState_t * pState );
+/** @endcond */
+
+/**
+ * @brief Get the packet ID of next pending publish to be resent.
+ *
+ * This function will need to be called to get the packet for which a publish
+ * need to be sent when a session is reestablished. Calling this function
+ * repeatedly until packet id is 0 will give all the packets for which
+ * a publish need to be resent in the correct order.
+ *
+ * @param[in] pMqttContext Initialized MQTT context.
+ * @param[in,out] pCursor Index at which to start searching.
+ *
+ * <b>Example</b>
+ * @code{c}
+ *
+ * // For this example assume this function returns an outgoing unacknowledged
+ * // QoS 1 or 2 publish from its packet identifier.
+ * MQTTPublishInfo_t * getPublish( uint16_t packetID );
+ *
+ * // Variables used in this example.
+ * MQTTStatus_t status;
+ * MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER;
+ * bool sessionPresent;
+ * uint16_t packetID;
+ * MQTTPublishInfo_t * pResendPublish = NULL;
+ * MQTTConnectInfo_t connectInfo = { 0 };
+ *
+ * // This is assumed to have been initialized before the call to MQTT_Connect().
+ * MQTTContext_t * pContext;
+ *
+ * // Set clean session to false to attempt session resumption.
+ * connectInfo.cleanSession = false;
+ * connectInfo.pClientIdentifier = "someClientID";
+ * connectInfo.clientIdentifierLength = strlen( connectInfo.pClientIdentifier );
+ * connectInfo.keepAliveSeconds = 60;
+ * // Optional connect parameters are not relevant to this example.
+ *
+ * // Create an MQTT connection. Use 100 milliseconds as a timeout.
+ * status = MQTT_Connect( pContext, &connectInfo, NULL, 100, &sessionPresent );
+ *
+ * if( status == MQTTSuccess )
+ * {
+ * if( sessionPresent )
+ * {
+ * // Loop while packet ID is nonzero.
+ * while( ( packetID = MQTT_PublishToResend( pContext, &cursor ) ) != 0 )
+ * {
+ * // Assume this function will succeed.
+ * pResendPublish = getPublish( packetID );
+ * // Set DUP flag.
+ * pResendPublish->dup = true;
+ * status = MQTT_Publish( pContext, pResendPublish, packetID );
+ *
+ * if( status != MQTTSuccess )
+ * {
+ * // Application can decide how to handle a failure.
+ * }
+ * }
+ * }
+ * else
+ * {
+ * // The broker did not resume a session, so we can clean up the
+ * // list of outgoing publishes.
+ * }
+ * }
+ * @endcode
+ */
+/* @[declare_mqtt_publishtoresend] */
+uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext,
+ MQTTStateCursor_t * pCursor );
+/* @[declare_mqtt_publishtoresend] */
+
+/**
+ * @fn const char * MQTT_State_strerror( MQTTPublishState_t state );
+ * @brief State to string conversion for state engine.
+ *
+ * @param[in] state The state to convert to a string.
+ *
+ * @return The string representation of the state.
+ */
+
+/**
+ * @cond DOXYGEN_IGNORE
+ * Doxygen should ignore this definition, this function is private.
+ */
+const char * MQTT_State_strerror( MQTTPublishState_t state );
+/** @endcond */
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ }
+#endif
+/* *INDENT-ON* */
+
+#endif /* ifndef CORE_MQTT_STATE_H */
diff --git a/project/coreMQTT/coreMQTT/makefile b/project/coreMQTT/coreMQTT/makefile
new file mode 100644
index 0000000..b6ece0e
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/makefile
@@ -0,0 +1,35 @@
+#********************************************************************************
+# Copyright: (C) 2023 LingYun IoT System Studio
+# All rights reserved.
+#
+# Filename: Makefile
+# Description: This file used compile all the source code to static library
+#
+# Version: 1.0.0(11/08/23)
+# Author: Guo Wenxue <guowenxue@gmail.com>
+# ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+#
+#*******************************************************************************
+
+PWD=$(shell pwd )
+
+BUILD_ARCH=$(shell uname -m)
+ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
+ CROSS_COMPILE?=arm-linux-gnueabihf-
+endif
+
+LIBNAME=$(shell basename ${PWD} )
+TOPDIR=$(shell dirname ${PWD} )
+CFLAGS+=-D_GNU_SOURCE
+
+all: clean
+ @rm -f *.o
+ @${CROSS_COMPILE}gcc ${CFLAGS} -I${TOPDIR} -c *.c
+ ${CROSS_COMPILE}ar -rcs lib${LIBNAME}.a *.o
+
+clean:
+ @rm -f *.o
+ @rm -f *.a
+
+distclean:
+ @make clean
diff --git a/project/coreMQTT/coreMQTT/transport_interface.h b/project/coreMQTT/coreMQTT/transport_interface.h
new file mode 100644
index 0000000..e86443d
--- /dev/null
+++ b/project/coreMQTT/coreMQTT/transport_interface.h
@@ -0,0 +1,316 @@
+/*
+ * coreMQTT v2.1.1
+ * Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file transport_interface.h
+ * @brief Transport interface definitions to send and receive data over the
+ * network.
+ */
+#ifndef TRANSPORT_INTERFACE_H_
+#define TRANSPORT_INTERFACE_H_
+
+#include <stdint.h>
+#include <stddef.h>
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+/* *INDENT-ON* */
+
+/**
+ * @transportpage
+ * @brief The transport interface definition.
+ *
+ * @transportsectionoverview
+ *
+ * The transport interface is a set of APIs that must be implemented using an
+ * external transport layer protocol. The transport interface is defined in
+ * @ref transport_interface.h. This interface allows protocols like MQTT and
+ * HTTP to send and receive data over the transport layer. This
+ * interface does not handle connection and disconnection to the server of
+ * interest. The connection, disconnection, and other transport settings, like
+ * timeout and TLS setup, must be handled in the user application.
+ * <br>
+ *
+ * The functions that must be implemented are:<br>
+ * - [Transport Receive](@ref TransportRecv_t)
+ * - [Transport Send](@ref TransportSend_t)
+ *
+ * Each of the functions above take in an opaque context @ref NetworkContext_t.
+ * The functions above and the context are also grouped together in the
+ * @ref TransportInterface_t structure:<br><br>
+ * @snippet this define_transportinterface
+ * <br>
+ *
+ * @transportsectionimplementation
+ *
+ * The following steps give guidance on implementing the transport interface:
+ *
+ * -# Implementing @ref NetworkContext_t<br><br>
+ * @snippet this define_networkcontext
+ * <br>
+ * @ref NetworkContext_t is the incomplete type <b>struct NetworkContext</b>.
+ * The implemented struct NetworkContext must contain all of the information
+ * that is needed to receive and send data with the @ref TransportRecv_t
+ * and the @ref TransportSend_t implementations.<br>
+ * In the case of TLS over TCP, struct NetworkContext is typically implemented
+ * with the TCP socket context and a TLS context.<br><br>
+ * <b>Example code:</b>
+ * @code{c}
+ * struct NetworkContext
+ * {
+ * struct MyTCPSocketContext tcpSocketContext;
+ * struct MyTLSContext tlsContext;
+ * };
+ * @endcode
+ * <br>
+ * -# Implementing @ref TransportRecv_t<br><br>
+ * @snippet this define_transportrecv
+ * <br>
+ * This function is expected to populate a buffer, with bytes received from the
+ * transport, and return the number of bytes placed in the buffer.
+ * In the case of TLS over TCP, @ref TransportRecv_t is typically implemented by
+ * calling the TLS layer function to receive data. In case of plaintext TCP
+ * without TLS, it is typically implemented by calling the TCP layer receive
+ * function. @ref TransportRecv_t may be invoked multiple times by the protocol
+ * library, if fewer bytes than were requested to receive are returned.
+ * <br><br>
+ * <b>Example code:</b>
+ * @code{c}
+ * int32_t myNetworkRecvImplementation( NetworkContext_t * pNetworkContext,
+ * void * pBuffer,
+ * size_t bytesToRecv )
+ * {
+ * int32_t bytesReceived = 0;
+ * bool callTlsRecvFunc = true;
+ *
+ * // For a single byte read request, check if data is available on the network.
+ * if( bytesToRecv == 1 )
+ * {
+ * // If no data is available on the network, do not call TLSRecv
+ * // to avoid blocking for socket timeout.
+ * if( TLSRecvCount( pNetworkContext->tlsContext ) == 0 )
+ * {
+ * callTlsRecvFunc = false;
+ * }
+ * }
+ *
+ * if( callTlsRecvFunc == true )
+ * {
+ * bytesReceived = TLSRecv( pNetworkContext->tlsContext,
+ * pBuffer,
+ * bytesToRecv,
+ * MY_SOCKET_TIMEOUT );
+ * if( bytesReceived < 0 )
+ * {
+ * // If the error code represents a timeout, then the return
+ * // code should be translated to zero so that the caller
+ * // can retry the read operation.
+ * if( bytesReceived == MY_SOCKET_ERROR_TIMEOUT )
+ * {
+ * bytesReceived = 0;
+ * }
+ * }
+ * // Handle other cases.
+ * }
+ * return bytesReceived;
+ * }
+ * @endcode
+ * <br>
+ * -# Implementing @ref TransportSend_t<br><br>
+ * @snippet this define_transportsend
+ * <br>
+ * This function is expected to send the bytes, in the given buffer over the
+ * transport, and return the number of bytes sent.
+ * In the case of TLS over TCP, @ref TransportSend_t is typically implemented by
+ * calling the TLS layer function to send data. In case of plaintext TCP
+ * without TLS, it is typically implemented by calling the TCP layer send
+ * function. @ref TransportSend_t may be invoked multiple times by the protocol
+ * library, if fewer bytes than were requested to send are returned.
+ * <br><br>
+ * <b>Example code:</b>
+ * @code{c}
+ * int32_t myNetworkSendImplementation( NetworkContext_t * pNetworkContext,
+ * const void * pBuffer,
+ * size_t bytesToSend )
+ * {
+ * int32_t bytesSent = 0;
+ * bytesSent = TLSSend( pNetworkContext->tlsContext,
+ * pBuffer,
+ * bytesToSend,
+ * MY_SOCKET_TIMEOUT );
+ *
+ * // If underlying TCP buffer is full, set the return value to zero
+ * // so that caller can retry the send operation.
+ * if( bytesSent == MY_SOCKET_ERROR_BUFFER_FULL )
+ * {
+ * bytesSent = 0;
+ * }
+ * else if( bytesSent < 0 )
+ * {
+ * // Handle socket error.
+ * }
+ * // Handle other cases.
+ *
+ * return bytesSent;
+ * }
+ * @endcode
+ */
+
+/**
+ * @transportstruct
+ * @typedef NetworkContext_t
+ * @brief The NetworkContext is an incomplete type. An implementation of this
+ * interface must define struct NetworkContext for the system requirements.
+ * This context is passed into the network interface functions.
+ */
+/* @[define_networkcontext] */
+struct NetworkContext;
+typedef struct NetworkContext NetworkContext_t;
+/* @[define_networkcontext] */
+
+/**
+ * @transportcallback
+ * @brief Transport interface for receiving data on the network.
+ *
+ * @note It is RECOMMENDED that the transport receive implementation
+ * does NOT block when requested to read a single byte. A single byte
+ * read request can be made by the caller to check whether there is a
+ * new frame available on the network for reading.
+ * However, the receive implementation MAY block for a timeout period when
+ * it is requested to read more than 1 byte. This is because once the caller
+ * is aware that a new frame is available to read on the network, then
+ * the likelihood of reading more than one byte over the network becomes high.
+ *
+ * @param[in] pNetworkContext Implementation-defined network context.
+ * @param[in] pBuffer Buffer to receive the data into.
+ * @param[in] bytesToRecv Number of bytes requested from the network.
+ *
+ * @return The number of bytes received or a negative value to indicate
+ * error.
+ *
+ * @note If no data is available on the network to read and no error
+ * has occurred, zero MUST be the return value. A zero return value
+ * SHOULD represent that the read operation can be retried by calling
+ * the API function. Zero MUST NOT be returned if a network disconnection
+ * has occurred.
+ */
+/* @[define_transportrecv] */
+typedef int32_t ( * TransportRecv_t )( NetworkContext_t * pNetworkContext,
+ void * pBuffer,
+ size_t bytesToRecv );
+/* @[define_transportrecv] */
+
+/**
+ * @transportcallback
+ * @brief Transport interface for sending data over the network.
+ *
+ * @param[in] pNetworkContext Implementation-defined network context.
+ * @param[in] pBuffer Buffer containing the bytes to send over the network stack.
+ * @param[in] bytesToSend Number of bytes to send over the network.
+ *
+ * @return The number of bytes sent or a negative value to indicate error.
+ *
+ * @note If no data is transmitted over the network due to a full TX buffer and
+ * no network error has occurred, this MUST return zero as the return value.
+ * A zero return value SHOULD represent that the send operation can be retried
+ * by calling the API function. Zero MUST NOT be returned if a network disconnection
+ * has occurred.
+ */
+/* @[define_transportsend] */
+typedef int32_t ( * TransportSend_t )( NetworkContext_t * pNetworkContext,
+ const void * pBuffer,
+ size_t bytesToSend );
+/* @[define_transportsend] */
+
+/**
+ * @brief Transport vector structure for sending multiple messages.
+ */
+typedef struct TransportOutVector
+{
+ /**
+ * @brief Base address of data.
+ */
+ const void * iov_base;
+
+ /**
+ * @brief Length of data in buffer.
+ */
+ size_t iov_len;
+} TransportOutVector_t;
+
+/**
+ * @transportcallback
+ * @brief Transport interface function for "vectored" / scatter-gather based
+ * writes. This function is expected to iterate over the list of vectors pIoVec
+ * having ioVecCount entries containing portions of one MQTT message at a maximum.
+ * If the proper functionality is available, then the data in the list should be
+ * copied to the underlying TCP buffer before flushing the buffer. Implementing it
+ * in this fashion will lead to sending of fewer TCP packets for all the values
+ * in the list.
+ *
+ * @note If the proper write functionality is not present for a given device/IP-stack,
+ * then there is no strict requirement to implement write. Only the send and recv
+ * interfaces must be defined for the application to work properly.
+ *
+ * @param[in] pNetworkContext Implementation-defined network context.
+ * @param[in] pIoVec An array of TransportIoVector_t structs.
+ * @param[in] ioVecCount Number of TransportIoVector_t in pIoVec.
+ *
+ * @return The number of bytes written or a negative value to indicate error.
+ *
+ * @note If no data is written to the buffer due to the buffer being full this MUST
+ * return zero as the return value.
+ * A zero return value SHOULD represent that the write operation can be retried
+ * by calling the API function. Zero MUST NOT be returned if a network disconnection
+ * has occurred.
+ */
+/* @[define_transportwritev] */
+typedef int32_t ( * TransportWritev_t )( NetworkContext_t * pNetworkContext,
+ TransportOutVector_t * pIoVec,
+ size_t ioVecCount );
+/* @[define_transportwritev] */
+
+/**
+ * @transportstruct
+ * @brief The transport layer interface.
+ */
+/* @[define_transportinterface] */
+typedef struct TransportInterface
+{
+ TransportRecv_t recv; /**< Transport receive function pointer. */
+ TransportSend_t send; /**< Transport send function pointer. */
+ TransportWritev_t writev; /**< Transport writev function pointer. */
+ NetworkContext_t * pNetworkContext; /**< Implementation-defined network context. */
+} TransportInterface_t;
+/* @[define_transportinterface] */
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ }
+#endif
+/* *INDENT-ON* */
+
+#endif /* ifndef TRANSPORT_INTERFACE_H_ */
diff --git a/project/coreMQTT/coreSNTP/README.md b/project/coreMQTT/coreSNTP/README.md
new file mode 100644
index 0000000..597e271
--- /dev/null
+++ b/project/coreMQTT/coreSNTP/README.md
@@ -0,0 +1,28 @@
+## coreSNTP Library
+
+This repository contains the coreSNTP library, a client library to use Simple Network Time Protocol (SNTP) to synchronize device clocks with internet time. This library implements the SNTPv4 specification defined in [RFC 4330](https://tools.ietf.org/html/rfc4330).
+
+An SNTP client can request time from both NTP and SNTP servers. According to the SNTPv4 specification, "_To an NTP or SNTP server, NTP and SNTP clients are indistinguishable; to an NTP or SNTP client, NTP and SNTP servers are indistinguishable._", thereby, allowing SNTP clients to request time from NTP servers.
+
+**coreSNTP v1.2.0 [source code](https://github.com/FreeRTOS/coreSNTP/tree/v1.2.0/source) is part of the [FreeRTOS 202210.00 LTS](https://github.com/FreeRTOS/FreeRTOS-LTS/tree/202210.00-LTS) release.**
+
+## Documentation
+
+The API reference documentation for the coreSNTP library version released in [FreeRTOS/FreeRTOS](https://github.com/FreeRTOS/FreeRTOS) can be viewed from the [freertos.org website](https://freertos.org/coresntp/index.html).
+
+## Cloning this repository
+This repo uses [Git Submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) to bring in dependent components.
+
+To clone using HTTPS:
+```
+git clone https://github.com/FreeRTOS/coreSNTP.git --recurse-submodules
+```
+Using SSH:
+```
+git clone git@github.com:FreeRTOS/coreSNTP.git --recurse-submodules
+```
+
+If you have downloaded the repo without using the `--recurse-submodules` argument, you need to run:
+```
+git submodule update --init --recursive
+```
diff --git a/project/coreMQTT/coreSNTP/core_sntp_client.c b/project/coreMQTT/coreSNTP/core_sntp_client.c
new file mode 100644
index 0000000..6b4d8d9
--- /dev/null
+++ b/project/coreMQTT/coreSNTP/core_sntp_client.c
@@ -0,0 +1,959 @@
+/*
+ * coreSNTP v1.2.0
+ * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_sntp_client.c
+ * @brief Implementation of the client API of the coreSNTP library.
+ */
+
+/* Standard includes. */
+#include <assert.h>
+#include <string.h>
+
+/* SNTP client library API include. */
+#include "core_sntp_client.h"
+
+#include "core_sntp_config_defaults.h"
+
+/**
+ * @brief Utility to convert fractions part of SNTP timestamp to milliseconds.
+ *
+ * @param[in] fractions The fractions value in an SNTP timestamp.
+ */
+#define FRACTIONS_TO_MS( fractions ) \
+ ( fractions / ( SNTP_FRACTION_VALUE_PER_MICROSECOND * 1000U ) )
+
+SntpStatus_t Sntp_Init( SntpContext_t * pContext,
+ const SntpServerInfo_t * pTimeServers,
+ size_t numOfServers,
+ uint32_t serverResponseTimeoutMs,
+ uint8_t * pNetworkBuffer,
+ size_t bufferSize,
+ SntpResolveDns_t resolveDnsFunc,
+ SntpGetTime_t getSystemTimeFunc,
+ SntpSetTime_t setSystemTimeFunc,
+ const UdpTransportInterface_t * pTransportIntf,
+ const SntpAuthenticationInterface_t * pAuthIntf )
+{
+ SntpStatus_t status = SntpSuccess;
+
+ /* Validate pointer parameters are not NULL. */
+ if( ( pContext == NULL ) || ( pTimeServers == NULL ) ||
+ ( pNetworkBuffer == NULL ) || ( resolveDnsFunc == NULL ) ||
+ ( getSystemTimeFunc == NULL ) || ( setSystemTimeFunc == NULL ) ||
+ ( pTransportIntf == NULL ) )
+ {
+ LogError( ( "Invalid parameter: Pointer parameters (except pAuthIntf) cannot be NULL" ) );
+
+ status = SntpErrorBadParameter;
+ }
+ /* Validate the length of the servers list.*/
+ else if( numOfServers == 0U )
+ {
+ LogError( ( "Invalid parameter: Size of server list cannot be zero" ) );
+ status = SntpErrorBadParameter;
+ }
+ /* Validate that the UDP transport interface functions are valid. */
+ else if( ( pTransportIntf->recvFrom == NULL ) || ( pTransportIntf->sendTo == NULL ) )
+ {
+ LogError( ( "Invalid parameter: Function members of UDP transport interface cannot be NULL" ) );
+ status = SntpErrorBadParameter;
+ }
+
+ /* If an authentication interface is provided, validate that its function pointer
+ * members are valid. */
+ else if( ( pAuthIntf != NULL ) &&
+ ( ( pAuthIntf->generateClientAuth == NULL ) ||
+ ( pAuthIntf->validateServerAuth == NULL ) ) )
+ {
+ LogError( ( "Invalid parameter: Function members of authentication interface cannot be NULL" ) );
+ status = SntpErrorBadParameter;
+ }
+ else if( bufferSize < SNTP_PACKET_BASE_SIZE )
+ {
+ LogError( ( "Cannot initialize context: Passed network buffer size is less than %u bytes: "
+ "bufferSize=%lu", SNTP_PACKET_BASE_SIZE, ( unsigned long ) bufferSize ) );
+ status = SntpErrorBufferTooSmall;
+ }
+ else
+ {
+ /* Reset the context memory to zero. */
+ ( void ) memset( pContext, 0, sizeof( SntpContext_t ) );
+
+ /* Set the members of the context with passed parameters. */
+ pContext->pTimeServers = pTimeServers;
+ pContext->numOfServers = numOfServers;
+
+ pContext->responseTimeoutMs = serverResponseTimeoutMs;
+
+ pContext->pNetworkBuffer = pNetworkBuffer;
+ pContext->bufferSize = bufferSize;
+
+ pContext->resolveDnsFunc = resolveDnsFunc;
+ pContext->getTimeFunc = getSystemTimeFunc;
+ pContext->setTimeFunc = setSystemTimeFunc;
+
+ /* Copy contents of UDP transport interface to context. */
+ ( void ) memcpy( &pContext->networkIntf, pTransportIntf, sizeof( UdpTransportInterface_t ) );
+
+ /* If authentication interface has been passed, copy its contents to the context. */
+ if( pAuthIntf != NULL )
+ {
+ ( void ) memcpy( &pContext->authIntf, pAuthIntf, sizeof( SntpAuthenticationInterface_t ) );
+ }
+
+ /* Initialize the packet size member to the standard minimum SNTP packet size.*/
+ pContext->sntpPacketSize = SNTP_PACKET_BASE_SIZE;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Utility to calculate the difference in milliseconds between 2
+ * SNTP timestamps.
+ *
+ * @param[in] pCurrentTime The more recent timestamp.
+ * @param[in] pOlderTime The older timestamp.
+ *
+ * @note This functions supports the edge case of SNTP timestamp overflow
+ * when @p pCurrentTime represents time in NTP era 1 (i.e. time since 7 Feb 2036)
+ * and the @p OlderTime represents time in NTP era 0 (i.e. time since 1st Jan 1900).
+ *
+ * @return Returns the calculated time duration between the two timestamps.
+ *
+ * @note This function returns the calculated time difference as unsigned 64 bit
+ * to avoid integer overflow when converting time difference between the seconds part
+ * of the timestamps, which are 32 bits wide, to milliseconds.
+ */
+static uint64_t calculateElapsedTimeMs( const SntpTimestamp_t * pCurrentTime,
+ const SntpTimestamp_t * pOlderTime )
+{
+ uint64_t timeDiffMs = 0UL;
+ uint32_t timeDiffSec = 0U;
+
+ assert( pCurrentTime != NULL );
+ assert( pOlderTime != NULL );
+
+ /* Detect if SNTP time has overflown between the 2 timestamps. */
+ if( pCurrentTime->seconds < pOlderTime->seconds )
+ {
+ /* Handle the SNTP time overflow by calculating the actual time
+ * duration from pOlderTime, that exists in NTP era 0, to pCurrentTime,
+ * that exists in NTP era 1. */
+ timeDiffSec = ( UINT32_MAX - pOlderTime->seconds ) + /* Time in NTP era 0. */
+ 1U + /* Epoch time in NTP era 1, i.e. 7 Feb 2036 6h:14m:28s. */
+ pCurrentTime->seconds; /* Time in NTP era 1. */
+
+ timeDiffMs = ( uint64_t ) timeDiffSec * 1000UL;
+ }
+ else
+ {
+ timeDiffSec = ( pCurrentTime->seconds - pOlderTime->seconds );
+ timeDiffMs = ( uint64_t ) timeDiffSec * 1000UL;
+ }
+
+ if( pCurrentTime->fractions > pOlderTime->fractions )
+ {
+ timeDiffMs += ( ( uint64_t ) pCurrentTime->fractions - ( uint64_t ) pOlderTime->fractions ) /
+ ( SNTP_FRACTION_VALUE_PER_MICROSECOND * 1000UL );
+ }
+ else
+ {
+ timeDiffMs -= ( ( uint64_t ) pOlderTime->fractions - ( uint64_t ) pCurrentTime->fractions ) /
+ ( SNTP_FRACTION_VALUE_PER_MICROSECOND * 1000UL );
+ }
+
+ return timeDiffMs;
+}
+
+/**
+ * @brief Validates the content of the SNTP context passed to the APIs to
+ * check whether it represents an initialized context.
+ *
+ * @param[in] pContext The SNTP context to validate.
+ *
+ * @return Returns one of the following:
+ * - #SntpSuccess if the context is verified to be initialized.
+ * - #SntpErrorBadParameter if the context is NULL.
+ * - #SntpErrorContextNotInitialized if the context is validated to be initialized.
+ */
+static SntpStatus_t validateContext( const SntpContext_t * pContext )
+{
+ SntpStatus_t status = SntpSuccess;
+
+ /* Check if the context parameter is invalid. */
+ if( pContext == NULL )
+ {
+ status = SntpErrorBadParameter;
+ LogError( ( "Invalid context parameter: Context is NULL" ) );
+ }
+
+ /* Validate pointer parameters are not NULL. */
+ else if( ( pContext->pTimeServers == NULL ) || ( pContext->pNetworkBuffer == NULL ) ||
+ ( pContext->resolveDnsFunc == NULL ) ||
+ ( pContext->getTimeFunc == NULL ) || ( pContext->setTimeFunc == NULL ) )
+ {
+ status = SntpErrorContextNotInitialized;
+ }
+
+ /* Validate the size of the configured servers list, network buffer size and the state
+ * variable for the SNTP packet size.*/
+ else if( ( pContext->numOfServers == 0U ) || ( pContext->bufferSize < SNTP_PACKET_BASE_SIZE ) ||
+ ( pContext->sntpPacketSize < SNTP_PACKET_BASE_SIZE ) )
+ {
+ status = SntpErrorContextNotInitialized;
+ }
+ /* Validate that the UDP transport interface functions are valid. */
+ else if( ( pContext->networkIntf.recvFrom == NULL ) || ( pContext->networkIntf.sendTo == NULL ) )
+ {
+ status = SntpErrorContextNotInitialized;
+ }
+
+ /* If an authentication interface is provided, validate that both its function pointer
+ * members are valid. */
+ else if( ( ( pContext->authIntf.generateClientAuth != NULL ) && ( pContext->authIntf.validateServerAuth == NULL ) ) ||
+ ( ( pContext->authIntf.generateClientAuth == NULL ) && ( pContext->authIntf.validateServerAuth != NULL ) ) )
+ {
+ status = SntpErrorContextNotInitialized;
+ }
+ else
+ {
+ status = SntpSuccess;
+ }
+
+ if( status == SntpErrorContextNotInitialized )
+ {
+ LogError( ( "Invalid context parameter: Context is not initialized with Sntp_Init" ) );
+ }
+
+ return status;
+}
+
+/**
+ * @brief Sends SNTP request packet to the passed server over the network
+ * using transport interface's send function.
+ *
+ * @note For the case of zero byte transmissions over the network, this function
+ * repeatedly retries the send operation by calling the transport interface
+ * until either:
+ * 1. The requested number of bytes @p packetSize have been sent.
+ * OR
+ * 2. There is an error in sending data over the network.
+ *
+ * @note This function treats partial data transmissions as error as UDP
+ * transport protocol does not support partial sends.
+ *
+ * @param[in] pNetworkIntf The UDP transport interface to use for
+ * sending data over the network.
+ * @param[in] timeServer The IPv4 address of the server to send the
+ * SNTP request packet to.
+ * @param[in] serverPort The port of the @p timeServer to send the
+ * request to.
+ * @param[in] getTimeFunc The function to query system time for
+ * tracking retry time period of no data transmissions.
+ * @param[in] pPacket The buffer containing the SNTP packet data
+ * to send over the network.
+ * @param[in] packetSize The size of data in the SNTP request packet.
+ * @param[in] timeoutMs The timeout period for retry attempts of sending
+ * SNTP request packet over the network.
+ *
+ * @return Returns #SntpSuccess on successful transmission of the entire
+ * SNTP request packet over the network; #SntpErrorNetworkFailure
+ * to indicate failure from transport interface; #SntpErrorSendTimeout if
+ * time request could not be sent over the network within the @p timeoutMs
+ * duration.
+ */
+static SntpStatus_t sendSntpPacket( const UdpTransportInterface_t * pNetworkIntf,
+ uint32_t timeServer,
+ uint16_t serverPort,
+ SntpGetTime_t getTimeFunc,
+ const uint8_t * pPacket,
+ uint16_t packetSize,
+ uint32_t timeoutMs )
+{
+ const uint8_t * pIndex = pPacket;
+ int32_t bytesSent = 0;
+ SntpTimestamp_t lastSendTime;
+ bool shouldRetry = false;
+ SntpStatus_t status = SntpErrorSendTimeout;
+
+ assert( pPacket != NULL );
+ assert( getTimeFunc != NULL );
+ assert( pNetworkIntf != NULL );
+ assert( packetSize >= SNTP_PACKET_BASE_SIZE );
+
+ /* Record the starting time of attempting to send data. This begins the retry timeout
+ * window. */
+ getTimeFunc( &lastSendTime );
+
+ /* Loop until the entire packet is sent. */
+ do
+ {
+ /* Reset flag for retrying send operation for the iteration. If request packet cannot be
+ * sent and timeout has not occurred, the flag will be set later for the next iteration. */
+ shouldRetry = false;
+
+ bytesSent = pNetworkIntf->sendTo( pNetworkIntf->pUserContext,
+ timeServer,
+ serverPort,
+ pIndex,
+ packetSize );
+
+ if( bytesSent < 0 )
+ {
+ LogError( ( "Unable to send request packet: Transport send failed. "
+ "ErrorCode=%ld.", ( long int ) bytesSent ) );
+ status = SntpErrorNetworkFailure;
+ }
+ else if( bytesSent == 0 )
+ {
+ /* No bytes were sent over the network. Retry send if we have not timed out. */
+
+ SntpTimestamp_t currentTime;
+ uint64_t elapsedTimeMs;
+
+ getTimeFunc( ¤tTime );
+
+ /* Calculate time elapsed since last data was sent over network. */
+ elapsedTimeMs = calculateElapsedTimeMs( ¤tTime, &lastSendTime );
+
+ /* Check for timeout if we have been waiting to send any data over the network. */
+ if( elapsedTimeMs >= timeoutMs )
+ {
+ LogError( ( "Unable to send request packet: Timed out retrying send: "
+ "SendRetryTimeout=%ums", timeoutMs ) );
+ status = SntpErrorSendTimeout;
+ }
+ else
+ {
+ shouldRetry = true;
+ }
+ }
+
+ /* Partial sends are not supported by UDP, which only supports sending the entire datagram as a whole.
+ * Thus, if the transport send function returns status representing partial send, it will be treated as failure. */
+ else if( bytesSent != ( int32_t ) packetSize )
+ {
+ LogError( ( "Unable to send request packet: Transport send returned unexpected bytes sent. "
+ "ReturnCode=%ld, ExpectedCode=%u", ( long int ) bytesSent, packetSize ) );
+
+ status = SntpErrorNetworkFailure;
+ }
+ else
+ {
+ /* The time request packet has been sent over the network. */
+ status = SntpSuccess;
+ }
+ } while( shouldRetry == true );
+
+ return status;
+}
+
+/**
+ * @brief Adds client authentication data to SNTP request packet by calling the
+ * authentication interface.
+ *
+ * @param[in] pContext The SNTP context.
+ *
+ * @return Returns one of the following:
+ * - #SntpSuccess if the interface function successfully appends client
+ * authentication data.
+ * - #SntpErrorAuthFailure when the interface returns either an error OR an
+ * incorrect size of the client authentication data.
+ */
+static SntpStatus_t addClientAuthentication( SntpContext_t * pContext )
+{
+ SntpStatus_t status = SntpSuccess;
+ uint16_t authDataSize = 0U;
+
+ assert( pContext != NULL );
+ assert( pContext->authIntf.generateClientAuth != NULL );
+ assert( pContext->currentServerIndex <= pContext->numOfServers );
+
+ status = pContext->authIntf.generateClientAuth( pContext->authIntf.pAuthContext,
+ &pContext->pTimeServers[ pContext->currentServerIndex ],
+ pContext->pNetworkBuffer,
+ pContext->bufferSize,
+ &authDataSize );
+
+ if( status != SntpSuccess )
+ {
+ LogError( ( "Unable to send time request: Client authentication function failed: "
+ "RetStatus=%s", Sntp_StatusToStr( status ) ) );
+ }
+
+ /* Sanity check that the returned authentication data size fits in the remaining space
+ * of the request buffer besides the first #SNTP_PACKET_BASE_SIZE bytes. */
+ else if( authDataSize > ( pContext->bufferSize - SNTP_PACKET_BASE_SIZE ) )
+ {
+ LogError( ( "Unable to send time request: Invalid authentication code size: "
+ "AuthCodeSize=%lu, NetworkBufferSize=%lu",
+ ( unsigned long ) authDataSize, ( unsigned long ) pContext->bufferSize ) );
+ status = SntpErrorAuthFailure;
+ }
+ else
+ {
+ /* With the authentication data added. calculate total SNTP request packet size. The same
+ * size would be expected in the SNTP response from server. */
+ pContext->sntpPacketSize = SNTP_PACKET_BASE_SIZE + authDataSize;
+
+ LogInfo( ( "Appended client authentication code to SNTP request packet:"
+ " AuthCodeSize=%lu, TotalPacketSize=%lu",
+ ( unsigned long ) authDataSize,
+ ( unsigned long ) pContext->sntpPacketSize ) );
+ }
+
+ return status;
+}
+
+SntpStatus_t Sntp_SendTimeRequest( SntpContext_t * pContext,
+ uint32_t randomNumber,
+ uint32_t blockTimeMs )
+{
+ SntpStatus_t status = SntpSuccess;
+
+ /* Validate the context parameter. */
+ status = validateContext( pContext );
+
+ if( status == SntpSuccess )
+ {
+ const SntpServerInfo_t * pServer = NULL;
+
+ /* Set local variable for the currently indexed server to use for time
+ * query. */
+ pServer = &pContext->pTimeServers[ pContext->currentServerIndex ];
+
+ LogDebug( ( "Using server %.*s for time query", ( int ) pServer->serverNameLen, pServer->pServerName ) );
+
+ /* Perform DNS resolution of the currently indexed server in the list
+ * of configured servers. */
+ if( pContext->resolveDnsFunc( pServer, &pContext->currentServerAddr ) == false )
+ {
+ LogError( ( "Unable to send time request: DNS resolution failed: Server=%.*s",
+ ( int ) pServer->serverNameLen, pServer->pServerName ) );
+
+ status = SntpErrorDnsFailure;
+ }
+ else
+ {
+ LogDebug( ( "Server DNS resolved: Address=0x%08X", pContext->currentServerAddr ) );
+ }
+
+ if( status == SntpSuccess )
+ {
+ /* Obtain current system time to generate SNTP request packet. */
+ pContext->getTimeFunc( &pContext->lastRequestTime );
+
+ LogDebug( ( "Obtained current time for SNTP request packet: Time=%us %ums",
+ pContext->lastRequestTime.seconds, FRACTIONS_TO_MS( pContext->lastRequestTime.fractions ) ) );
+
+ /* Generate SNTP request packet with the current system time and
+ * the passed random number. */
+ status = Sntp_SerializeRequest( &pContext->lastRequestTime,
+ randomNumber,
+ pContext->pNetworkBuffer,
+ pContext->bufferSize );
+
+ /* The serialization should be successful as all parameter validation has
+ * been done before. */
+ assert( status == SntpSuccess );
+ }
+
+ /* If an authentication interface has been configured, call the function to append client
+ * authentication data to SNTP request buffer. */
+ if( ( status == SntpSuccess ) && ( pContext->authIntf.generateClientAuth != NULL ) )
+ {
+ status = addClientAuthentication( pContext );
+ }
+
+ if( status == SntpSuccess )
+ {
+ LogInfo( ( "Sending serialized SNTP request packet to the server: Addr=%u, Port=%u",
+ pContext->currentServerAddr,
+ pContext->pTimeServers[ pContext->currentServerIndex ].port ) );
+
+ /* Send the request packet over the network to the time server. */
+ status = sendSntpPacket( &pContext->networkIntf,
+ pContext->currentServerAddr,
+ pContext->pTimeServers[ pContext->currentServerIndex ].port,
+ pContext->getTimeFunc,
+ pContext->pNetworkBuffer,
+ pContext->sntpPacketSize,
+ blockTimeMs );
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Utility to update the SNTP context to rotate the server of use for subsequent
+ * time request(s).
+ *
+ * @note If there is no next server remaining, after the current server's index, in the list of
+ * configured servers, the server rotation algorithm wraps around to the first server in the list.
+ * The wrap around is done so that an application using the library for a long-running SNTP client
+ * functionality (like a daemon task) does not become dysfunctional after all configured time
+ * servers have been used up. Time synchronization can be a critical functionality for a system
+ * and the wrap around logic ensures that the SNTP client continues to function in such a case.
+ *
+ * @note Server rotation is performed ONLY when either of:
+ * - The current server responds with a rejection for time request.
+ * OR
+ * - The current server response wait has timed out.
+ */
+static void rotateServerForNextTimeQuery( SntpContext_t * pContext )
+{
+ size_t nextServerIndex = ( pContext->currentServerIndex + 1U ) % pContext->numOfServers;
+
+ LogInfo( ( "Rotating server for next time query: PreviousServer=%.*s, NextServer=%.*s",
+ ( int ) pContext->pTimeServers[ pContext->currentServerIndex ].serverNameLen,
+ pContext->pTimeServers[ pContext->currentServerIndex ].pServerName,
+ ( int ) pContext->pTimeServers[ nextServerIndex ].serverNameLen,
+ pContext->pTimeServers[ nextServerIndex ].pServerName ) );
+
+ pContext->currentServerIndex = nextServerIndex;
+}
+
+
+/**
+ * @brief This function attempts to receive the SNTP response packet from a server.
+ *
+ * @note This function treats reads of data sizes less than the expected server response packet,
+ * as an error as UDP does not support partial reads. Such a scenario can exist either due:
+ * - An error in the server sending its response with smaller packet size than the request packet OR
+ * - A malicious attacker spoofing or modifying server response OR
+ * - An error in the UDP transport interface implementation for read operation.
+ *
+ * @param[in] pTransportIntf The UDP transport interface to use for receiving data from
+ * the network.
+ * @param[in] timeServer The server to read the response from the network.
+ * @param[in] serverPort The port of the server to read the response from.
+ * @param[in, out] pBuffer This will be filled with the server response read from the
+ * network.
+ * @param[in] responseSize The size of server response to read from the network.
+ *
+ * @return It returns one of the following:
+ * - #SntpSuccess if an SNTP response packet is received from the network.
+ * - #SntpNoResponseReceived if a server response is not received from the network.
+ * - #SntpErrorNetworkFailure if there is an internal failure in reading from the network
+ * in the user-defined transport interface.
+ */
+static SntpStatus_t receiveSntpResponse( const UdpTransportInterface_t * pTransportIntf,
+ uint32_t timeServer,
+ uint16_t serverPort,
+ uint8_t * pBuffer,
+ uint16_t responseSize )
+{
+ SntpStatus_t status = SntpNoResponseReceived;
+ int32_t bytesRead = 0;
+
+ assert( pTransportIntf != NULL );
+ assert( pTransportIntf->recvFrom != NULL );
+ assert( pBuffer != NULL );
+ assert( responseSize >= SNTP_PACKET_BASE_SIZE );
+
+ bytesRead = pTransportIntf->recvFrom( pTransportIntf->pUserContext,
+ timeServer,
+ serverPort,
+ pBuffer,
+ responseSize );
+
+ /* Negative return code indicates error. */
+ if( bytesRead < 0 )
+ {
+ status = SntpErrorNetworkFailure;
+ LogError( ( "Unable to receive server response: Transport receive failed: Code=%ld",
+ ( long int ) bytesRead ) );
+ }
+ /* If the packet was not available on the network, check whether we can retry. */
+ else if( bytesRead == 0 )
+ {
+ status = SntpNoResponseReceived;
+ }
+
+ /* Partial reads are not supported by UDP, which only supports receiving the entire datagram as a whole.
+ * Thus, if the transport receive function returns reception of partial data, it will be treated as failure. */
+ else if( bytesRead != ( int32_t ) responseSize )
+ {
+ LogError( ( "Failed to receive server response: Transport recv returned less than expected bytes."
+ "ExpectedBytes=%u, ReadBytes=%ld", responseSize, ( long int ) bytesRead ) );
+ status = SntpErrorNetworkFailure;
+ }
+ else
+ {
+ LogDebug( ( "Received server response: PacketSize=%ld", ( long int ) bytesRead ) );
+ status = SntpSuccess;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Processes the response from a server by de-serializing the SNTP packet to
+ * validate the server (if an authentication interface has been configured), determine
+ * whether server has accepted or rejected the time request, and update the system clock
+ * if the server responded positively with time.
+ *
+ * @param[in] pContext The SNTP context representing the SNTP client.
+ * @param[in] pResponseRxTime The time of receiving the server response from the network.
+ *
+ * @return It returns one of the following:
+ * - #SntpSuccess if the server response is successfully de-serialized and system clock
+ * updated.
+ * - #SntpErrorAuthFailure if there is internal failure in user-defined authentication
+ * interface when validating server from the response.
+ * - #SntpServerNotAuthenticated if the server failed authenticated check in the user-defined
+ * interface.
+ * - #SntpRejectedResponse if the server has rejected the time request in its response.
+ * - #SntpInvalidResponse if the server response failed sanity checks.
+ */
+static SntpStatus_t processServerResponse( SntpContext_t * pContext,
+ const SntpTimestamp_t * pResponseRxTime )
+{
+ SntpStatus_t status = SntpSuccess;
+ const SntpServerInfo_t * pServer = &pContext->pTimeServers[ pContext->currentServerIndex ];
+
+ assert( pContext != NULL );
+ assert( pResponseRxTime != NULL );
+
+ if( pContext->authIntf.validateServerAuth != NULL )
+ {
+ /* Verify the server from the authentication data in the SNTP response packet. */
+ status = pContext->authIntf.validateServerAuth( pContext->authIntf.pAuthContext,
+ pServer,
+ pContext->pNetworkBuffer,
+ pContext->sntpPacketSize );
+ assert( ( status == SntpSuccess ) || ( status == SntpErrorAuthFailure ) ||
+ ( status == SntpServerNotAuthenticated ) );
+
+ if( status != SntpSuccess )
+ {
+ LogError( ( "Unable to use server response: Server authentication function failed: "
+ "ReturnStatus=%s", Sntp_StatusToStr( status ) ) );
+ }
+ else
+ {
+ LogDebug( ( "Server response has been validated: Server=%.*s", ( int ) pServer->serverNameLen, pServer->pServerName ) );
+ }
+ }
+
+ if( status == SntpSuccess )
+ {
+ SntpResponseData_t parsedResponse;
+
+ /* De-serialize response packet to determine whether the server accepted or rejected
+ * the request for time. Also, calculate the system clock offset if the server responded
+ * with time. */
+ status = Sntp_DeserializeResponse( &pContext->lastRequestTime,
+ pResponseRxTime,
+ pContext->pNetworkBuffer,
+ pContext->sntpPacketSize,
+ &parsedResponse );
+
+ /* We do not expect the following errors to be returned as the context
+ * has been validated in the Sntp_ReceiveTimeResponse API. */
+ assert( status != SntpErrorBadParameter );
+ assert( status != SntpErrorBufferTooSmall );
+
+ if( ( status == SntpRejectedResponseChangeServer ) ||
+ ( status == SntpRejectedResponseRetryWithBackoff ) ||
+ ( status == SntpRejectedResponseOtherCode ) )
+ {
+ /* Server has rejected the time request. Thus, we will rotate to the next time server
+ * in the list. */
+ rotateServerForNextTimeQuery( pContext );
+
+ LogError( ( "Unable to use server response: Server has rejected request for time: RejectionCode=%.*s",
+ ( int ) SNTP_KISS_OF_DEATH_CODE_LENGTH, ( char * ) &parsedResponse.rejectedResponseCode ) );
+ status = SntpRejectedResponse;
+ }
+ else if( status == SntpInvalidResponse )
+ {
+ LogError( ( "Unable to use server response: Server response failed sanity checks." ) );
+ }
+ else
+ {
+ /* Server has responded successfully with time, and we have calculated the clock offset
+ * of system clock relative to the server.*/
+ LogDebug( ( "Updating system time: ServerTime=%u %ums ClockOffset=%lums",
+ parsedResponse.serverTime.seconds, FRACTIONS_TO_MS( parsedResponse.serverTime.fractions ),
+ parsedResponse.clockOffsetMs ) );
+
+ /* Update the system clock with the calculated offset. */
+ pContext->setTimeFunc( pServer, &parsedResponse.serverTime,
+ parsedResponse.clockOffsetMs, parsedResponse.leapSecondType );
+
+ status = SntpSuccess;
+ }
+ }
+
+ /* Reset the last request time state in context to protect against replay attacks.
+ * Note: The last request time is not cleared when a rejection response packet is received and the client does
+ * has not authenticated server from the response. This is because clearing of the state causes the coreSNTP
+ * library to discard any subsequent server response packets (as the "originate timestamp" of those packets will
+ * not match the last request time value of the context), and thus, an attacker can cause Denial of Service
+ * attacks by spoofing server response before the actual server is able to respond.
+ */
+ if( ( status == SntpSuccess ) ||
+ ( ( pContext->authIntf.validateServerAuth != NULL ) && ( status == SntpRejectedResponse ) ) )
+ {
+ /* In the attack of SNTP request packet being replayed, the replayed request packet is serviced by
+ * SNTP/NTP server with SNTP response (as servers are stateless) and client receives the response
+ * containing new values of server timestamps but the stale value of "originate timestamp".
+ * To prevent the coreSNTP library from servicing such a server response (associated with the replayed
+ * SNTP request packet), the last request timestamp state is cleared in the context after receiving the
+ * first valid server response. Therefore, any subsequent server response(s) from replayed client request
+ * packets can be invalidated due to the "originate timestamp" not matching the last request time stored
+ * in the context.
+ * Note: If an attacker spoofs a server response with a zero "originate timestamp" after the coreSNTP
+ * library (i.e. the SNTP client) has cleared the internal state to zero, the spoofed packet will be
+ * discarded as the coreSNTP serializer does not accept server responses with zero value for timestamps.
+ */
+ pContext->lastRequestTime.seconds = 0U;
+ pContext->lastRequestTime.fractions = 0U;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Determines whether a retry attempt should be made to receive server response packet from the network
+ * depending on the timing constraints of server response timeout, @p responseTimeoutMs, and the block time
+ * period, @p blockTimeMs, passed. If neither of the time windows have expired, the function determines that the
+ * read operation can be re-tried.
+ *
+ * @param[in] pCurrentTime The current time in the system used for calculating elapsed time windows.
+ * @param[in] pReadStartTime The time of the first read attempt in the current set of read tries occurring
+ * from the Sntp_ReceiveTimeRequest API call by the application. This time is used for calculating the elapsed
+ * time to determine whether the block time has expired.
+ * @param[in] pRequestTime The time of sending the SNTP request to the server for which the response is
+ * awaited. This time is used for calculating total elapsed elapsed time of waiting for server response to
+ * determine if a server response timeout has occurred.
+ * @param[in] responseTimeoutMs The server response timeout configuration.
+ * @param[in] blockTimeMs The maximum block time of waiting for server response across read tries in the current
+ * call made by application to Sntp_ReceiveTimeResponse API.
+ * @param[out] pHasResponseTimedOut This will be populated with state to indicate whether the wait for server
+ * response has timed out.
+ *
+ * @return Returns true for retrying read operation of server response; false on either server response timeout
+ * OR completion of block time window.
+ */
+static bool decideAboutReadRetry( const SntpTimestamp_t * pCurrentTime,
+ const SntpTimestamp_t * pReadStartTime,
+ const SntpTimestamp_t * pRequestTime,
+ uint32_t responseTimeoutMs,
+ uint32_t blockTimeMs,
+ bool * pHasResponseTimedOut )
+{
+ uint64_t timeSinceRequestMs = 0UL;
+ uint64_t timeElapsedInReadAttempts = 0UL;
+ bool shouldRetry = false;
+
+ assert( pCurrentTime != NULL );
+ assert( pReadStartTime != NULL );
+ assert( pRequestTime != NULL );
+ assert( pHasResponseTimedOut != NULL );
+
+ /* Calculate time elapsed since the time request was sent to the server
+ * to determine whether the server response has timed out. */
+ timeSinceRequestMs = calculateElapsedTimeMs( pCurrentTime, pRequestTime );
+
+ /* Calculate the time elapsed across all the read attempts so far to determine
+ * whether the block time window for reading server response has expired. */
+ timeElapsedInReadAttempts = calculateElapsedTimeMs( pCurrentTime, pReadStartTime );
+
+ /* Check whether a response timeout has occurred to inform whether we should
+ * wait for server response anymore. */
+ if( timeSinceRequestMs >= ( uint64_t ) responseTimeoutMs )
+ {
+ shouldRetry = false;
+ *pHasResponseTimedOut = true;
+
+ LogError( ( "Unable to receive response: Server response has timed out: "
+ "RequestTime=%us %ums, TimeoutDuration=%ums, ElapsedTime=%lu",
+ pRequestTime->seconds, FRACTIONS_TO_MS( pRequestTime->fractions ),
+ responseTimeoutMs, timeSinceRequestMs ) );
+ }
+ /* Check whether the block time window has expired to determine whether read can be retried. */
+ else if( timeElapsedInReadAttempts >= ( uint64_t ) blockTimeMs )
+ {
+ shouldRetry = false;
+ LogDebug( ( "Did not receive server response: Read block time has expired: "
+ "BlockTime=%ums, ResponseWaitElapsedTime=%lums",
+ blockTimeMs, timeSinceRequestMs ) );
+ }
+ else
+ {
+ shouldRetry = true;
+ LogDebug( ( "Did not receive server response: Retrying read: "
+ "BlockTime=%ums, ResponseWaitElapsedTime=%lums, ResponseTimeout=%u",
+ blockTimeMs, timeSinceRequestMs, responseTimeoutMs ) );
+ }
+
+ return shouldRetry;
+}
+
+SntpStatus_t Sntp_ReceiveTimeResponse( SntpContext_t * pContext,
+ uint32_t blockTimeMs )
+{
+ SntpStatus_t status = SntpNoResponseReceived;
+ bool hasResponseTimedOut = false;
+
+ /* Validate the context parameter. */
+ status = validateContext( pContext );
+
+ if( status == SntpSuccess )
+ {
+ SntpTimestamp_t startTime, currentTime;
+ const SntpTimestamp_t * pRequestTime = &pContext->lastRequestTime;
+ bool shouldRetry = false;
+
+ /* Record time before read attempts so that it can be used as base time for
+ * for tracking the block time window across read retries. */
+ pContext->getTimeFunc( &startTime );
+
+ do
+ {
+ /* Reset the retry read operation flag. If the server response is not received in the current iteration's read
+ * attempt and the wait has not timed out, the flag will be set to perform a retry. */
+ shouldRetry = false;
+
+ /* Make an attempt to read the server response from the network. */
+ status = receiveSntpResponse( &pContext->networkIntf,
+ pContext->currentServerAddr,
+ pContext->pTimeServers[ pContext->currentServerIndex ].port,
+ pContext->pNetworkBuffer,
+ pContext->sntpPacketSize );
+
+ /* If the server response is received, deserialize it, validate the server
+ * (if authentication interface is provided), and update system time with
+ * the calculated clock offset. */
+ if( status == SntpSuccess )
+ {
+ /* Get current time to de-serialize the receive server response packet. */
+ pContext->getTimeFunc( ¤tTime );
+
+ status = processServerResponse( pContext, ¤tTime );
+ }
+ else if( status == SntpNoResponseReceived )
+ {
+ /* Get current time to determine whether another attempt for reading the packet can
+ * be made. */
+ pContext->getTimeFunc( ¤tTime );
+
+ /* Set the flag to retry read of server response from the network. */
+ shouldRetry = decideAboutReadRetry( ¤tTime,
+ &startTime,
+ pRequestTime,
+ pContext->responseTimeoutMs,
+ blockTimeMs,
+ &hasResponseTimedOut );
+ }
+ else
+ {
+ /* Empty else marker. */
+ }
+ } while( shouldRetry == true );
+
+ /* If the wait for server response to the time request has timed out, rotate the server of use in the
+ * context for subsequent time request(s). Also, update the return status to indicate response timeout. */
+ if( hasResponseTimedOut == true )
+ {
+ status = SntpErrorResponseTimeout;
+
+ /* Rotate server to the next in the list of configured servers in the context. */
+ rotateServerForNextTimeQuery( pContext );
+ }
+ }
+
+ return status;
+}
+
+const char * Sntp_StatusToStr( SntpStatus_t status )
+{
+ const char * pString = NULL;
+
+ switch( status )
+ {
+ case SntpSuccess:
+ pString = "SntpSuccess";
+ break;
+
+ case SntpErrorBadParameter:
+ pString = "SntpErrorBadParameter";
+ break;
+
+ case SntpRejectedResponseChangeServer:
+ pString = "SntpRejectedResponseChangeServer";
+ break;
+
+ case SntpRejectedResponseRetryWithBackoff:
+ pString = "SntpRejectedResponseRetryWithBackoff";
+ break;
+
+ case SntpRejectedResponseOtherCode:
+ pString = "SntpRejectedResponseOtherCode";
+ break;
+
+ case SntpErrorBufferTooSmall:
+ pString = "SntpErrorBufferTooSmall";
+ break;
+
+ case SntpInvalidResponse:
+ pString = "SntpInvalidResponse";
+ break;
+
+ case SntpZeroPollInterval:
+ pString = "SntpZeroPollInterval";
+ break;
+
+ case SntpErrorTimeNotSupported:
+ pString = "SntpErrorTimeNotSupported";
+ break;
+
+ case SntpErrorDnsFailure:
+ pString = "SntpErrorDnsFailure";
+ break;
+
+ case SntpErrorNetworkFailure:
+ pString = "SntpErrorNetworkFailure";
+ break;
+
+ case SntpServerNotAuthenticated:
+ pString = "SntpServerNotAuthenticated";
+ break;
+
+ case SntpErrorAuthFailure:
+ pString = "SntpErrorAuthFailure";
+ break;
+
+ default:
+ pString = "Invalid status code!";
+ break;
+ }
+
+ return pString;
+}
diff --git a/project/coreMQTT/coreSNTP/core_sntp_client.h b/project/coreMQTT/coreSNTP/core_sntp_client.h
new file mode 100644
index 0000000..829aed5
--- /dev/null
+++ b/project/coreMQTT/coreSNTP/core_sntp_client.h
@@ -0,0 +1,655 @@
+/*
+ * coreSNTP v1.2.0
+ * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_sntp_client.h
+ * @brief API of an SNTPv4 client library that can send time requests and receive time response to/from
+ * SNTP/NTP servers. The library follows the Best Practices suggested in the the SNTPv4 specification,
+ * [RFC 4330](https://tools.ietf.org/html/rfc4330).
+ * The library can be used to run an SNTP client in a dedicated deamon task to periodically synchronize
+ * time from the Internet.
+ */
+
+#ifndef CORE_SNTP_CLIENT_H_
+#define CORE_SNTP_CLIENT_H_
+
+/* Standard include. */
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+/* Include coreSNTP Serializer header. */
+#include "core_sntp_serializer.h"
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+/* *INDENT-ON* */
+
+/**
+ * @ingroup sntp_constants
+ * @brief The default UDP port supported by SNTP/NTP servers for client-server
+ * communication.
+ *
+ * @note It is possible for a server to use a different port number than
+ * the default port when using the Network Time Security protocol as the security
+ * mechanism for SNTP communication. For more information, refer to Section 4.1.8
+ * of [RFC 8915](https://tools.ietf.org/html/rfc8915).
+ */
+#define SNTP_DEFAULT_SERVER_PORT ( 123U )
+
+/**
+ * @ingroup sntp_struct_types
+ * @brief Structure representing information for a time server.
+ */
+typedef struct SntpServerInfo
+{
+ const char * pServerName; /**<@brief The time server name. */
+ size_t serverNameLen; /**<@brief The length of the server name.*/
+ uint16_t port; /**<@brief The UDP port supported by the server
+ * for SNTP/NTP communication. */
+} SntpServerInfo_t;
+
+/**
+ * @ingroup sntp_callback_types
+ * @brief Interface for user-defined function to resolve time server domain-name
+ * to an IPv4 address.
+ * The SNTP client library attempts to resolve the DNS of the time-server being
+ * used every time the @ref Sntp_SendTimeRequest API is called.
+ *
+ * @param[in] pServerAddr The time-server whose IPv4 address is to be resolved.
+ * @param[out] pIpV4Addr This should be filled with the resolved IPv4 address.
+ * of @p pTimeServer.
+ *
+ * @return `true` if DNS resolution is successful; otherwise `false` to represent
+ * failure.
+ */
+typedef bool ( * SntpResolveDns_t )( const SntpServerInfo_t * pServerAddr,
+ uint32_t * pIpV4Addr );
+
+/**
+ * @ingroup sntp_callback_types
+ * @brief Interface for user-defined function to obtain the current system time
+ * in SNTP timestamp format.
+ *
+ * @note If your platform follows UNIX representation of time, the
+ * #SNTP_TIME_AT_UNIX_EPOCH_SECS and #SNTP_FRACTION_VALUE_PER_MICROSECOND macros
+ * can be used to convert UNIX time to SNTP timestamp.
+ *
+ * @param[out] pCurrentTime This should be filled with the current system time
+ * in SNTP timestamp format.
+ *
+ * @return `true` if obtaining system time is successful; otherwise `false` to
+ * represent failure.
+ */
+typedef void ( * SntpGetTime_t )( SntpTimestamp_t * pCurrentTime );
+
+/**
+ * @ingroup sntp_callback_types
+ * @brief Interface for user-defined function to update the system clock time
+ * so that it is synchronized the time server used for getting current time.
+ *
+ * @param[in] pTimeServer The time server used to request time.
+ * @param[in] pServerTime The current time returned by the @p pTimeServer.
+ * @param[in] clockOffsetMs The calculated clock offset (in milliseconds) of the
+ * system relative to the server time.
+ * @param[in] leapSecondInfo Information about whether there is about an upcoming
+ * leap second adjustment of insertion or deletion in the last minute before midnight
+ * on the last day of the current month. For more information on leap seconds, refer
+ * to https://www.nist.gov/pml/time-and-frequency-division/leap-seconds-faqs. Depending
+ * on the accuracy requirements of the system clock, the user can choose to account
+ * for the leap second or ignore it in their system clock update logic.
+ *
+ * @note If the @p clockOffsetMs is positive, then the system time is BEHIND the server time,
+ * and if the @p clockOffsetMs, the system time is AHEAD of the server time. To correct the
+ * system time, the user can use either of "step", "slew" OR combination of the two clock
+ * discipline methodologies depending on the application needs.
+ * If the application requires a smooth time continuum of system time, then the "slew"
+ * discipline methodology can be used with the clock offset value, @p clockOffSetMs, to correct
+ * the system clock gradually with a "slew rate".
+ * If the application can accept sudden jump in time (forward or backward), then
+ * the "step" discipline methodology can be used to directly update the system
+ * clock with the current server time, @p pServerTime, every time the coreSNTP library
+ * calls the interface.
+ */
+typedef void ( * SntpSetTime_t )( const SntpServerInfo_t * pTimeServer,
+ const SntpTimestamp_t * pServerTime,
+ int64_t clockOffsetMs,
+ SntpLeapSecondInfo_t leapSecondInfo );
+
+/**
+ * @ingroup sntp_struct_types
+ * @typedef NetworkContext_t
+ * @brief A user-defined type for context that is passed to the transport interface functions.
+ * It MUST be defined by the user to use the library.
+ * It is of incomplete type to allow user to define to the needs of their transport
+ * interface implementation.
+ */
+struct NetworkContext;
+typedef struct NetworkContext NetworkContext_t;
+
+/**
+ * @ingroup sntp_callback_types
+ * @brief Interface for user-defined function to send time request as a single datagram
+ * to server on the network over User Datagram Protocol (UDP).
+ *
+ * @note It is RECOMMENDED that the send operation is non-blocking, i.e. it
+ * SHOULD immediately return when the entire UDP data cannot be sent over the
+ * network. In such a case, the coreSNTP library will re-try send operation
+ * for a maximum period of blocking time passed to the @ref Sntp_SendTimeRequest API.
+ *
+ * @note If the size of the SNTP packet exceeds the Maximum Transmission Unit (MTU)
+ * supported by the network interface of the device, the user-defined implementation
+ * MAY either support fragmentation of UDP packets OR use a size for authentication data
+ * that allows the SNTP packet to fit within the MTU required size threshold. (Note that
+ * the size of SNTP packet is #SNTP_PACKET_BASE_SIZE + authentication data.)
+ *
+ * @param[in,out] pNetworkContext The user defined NetworkContext_t which
+ * is opaque to the coreSNTP library.
+ * @param[in] serverAddr The IPv4 address of the time server to send the data to.
+ * @param[in] serverPort The port of the server to send data to.
+ * @param[in] pBuffer The buffer containing the data to send over the network.
+ * @param[in] bytesToSend The size of data in @p pBuffer to send.
+ *
+ * @return The function SHOULD return one of the following integer codes:
+ * - @p bytesToSend when all requested data is successfully transmitted over the
+ * network.
+ * - 0 when no data could be sent over the network (due to network buffer being
+ * full, for example), and the send operation can be retried.
+ * - < 0 when the send operation failed to send any data due to an internal error,
+ * and operation cannot be retried.
+ */
+typedef int32_t ( * UdpTransportSendTo_t )( NetworkContext_t * pNetworkContext,
+ uint32_t serverAddr,
+ uint16_t serverPort,
+ const void * pBuffer,
+ uint16_t bytesToSend );
+
+/**
+ * @ingroup sntp_callback_types
+ * @brief Interface for user-defined function to receive the server response, to a time
+ * request (sent through the @ref UdpTransportSendTo_t function), from the network over
+ * User Datagram Protocol (UDP).
+ *
+ * @note It is RECOMMENDED that the read operation is non-blocking, i.e. it
+ * SHOULD immediately return when no data is available on the network.
+ * In such a case, the coreSNTP library will re-try send operation for a maximum period
+ * of blocking time passed through the @ref Sntp_ReceiveTimeResponse API.
+ *
+ * @note If the size of the SNTP response packet from the server exceeds the
+ * Maximum Transmission Unit (MTU) supported by the network interface of the device,
+ * the user-defined implementation of the interface MAY either support receiving and
+ * assembling fragmented UDP packets OR use an authentication data size that allows
+ * SNTP packet to fit within the MTU required packet size threshold. (Note that
+ * the size of SNTP packet is #SNTP_PACKET_BASE_SIZE + authentication data.)
+ *
+ * @param[in,out] pNetworkContext The user defined NetworkContext_t which
+ * is opaque to the coreSNTP library.
+ * @param[in] serverAddr The IPv4 address of the time server to receive data from.
+ * @param[in] serverPort The port of the server to receive data from.
+ * @param[out] pBuffer This SHOULD be filled with data received from the network.
+ * @param[in] bytesToRecv The expected number of bytes to receive from the
+ * server.
+ *
+ * @return The function SHOULD return one of the following integer codes:
+ * - @p bytesToRecv value if all the requested number of bytes are received
+ * from the network.
+ * - ZERO when no data is available on the network, and the operation can be
+ * retried.
+ * - < 0 when the read operation failed due to internal error, and operation cannot
+ * be retried.
+ */
+typedef int32_t ( * UdpTransportRecvFrom_t )( NetworkContext_t * pNetworkContext,
+ uint32_t serverAddr,
+ uint16_t serverPort,
+ void * pBuffer,
+ uint16_t bytesToRecv );
+
+/**
+ * @ingroup sntp_struct_types
+ * @brief Struct representing the UDP transport interface for user-defined functions
+ * that coreSNTP library depends on for performing read/write network operations.
+ */
+typedef struct UdpTransportIntf
+{
+ NetworkContext_t * pUserContext; /**<@brief The user-defined context for storing
+ * network socket information. */
+ UdpTransportSendTo_t sendTo; /**<@brief The user-defined UDP send function. */
+ UdpTransportRecvFrom_t recvFrom; /**<@brief The user-defined UDP receive function. */
+} UdpTransportInterface_t;
+
+/**
+ * @ingroup sntp_struct_types
+ * @typedef SntpAuthContext_t
+ * @brief A user-defined type for context that is passed to the authentication interface functions.
+ * It MUST be defined by the user to use the library.
+ * It is of incomplete type to allow user to defined to the the needs of their authentication
+ * interface implementation.
+ */
+struct SntpAuthContext;
+typedef struct SntpAuthContext SntpAuthContext_t;
+
+/**
+ * @ingroup sntp_callback_types
+ * @brief Interface for user-defined function to generate and append
+ * authentication code in an SNTP request buffer for the SNTP client to be
+ * authenticated by the time server, if a security mechanism is used.
+ *
+ * The user can choose to implement with any security mechanism, symmetric
+ * key-based (like AES-CMAC) or asymmetric key-based (like Network Time Security),
+ * depending on the security mechanism supported by the time server being used
+ * to synchronize time with.
+ *
+ * @note The function SHOULD generate the authentication data for the first
+ * #SNTP_PACKET_BASE_SIZE bytes of SNTP request packet present in the passed buffer
+ * @p pBuffer, and fill the generated authentication data after #SNTP_PACKET_BASE_SIZE
+ * bytes in the buffer.
+ *
+ * @param[in,out] pContext The user defined NetworkContext_t which
+ * is opaque to the coreSNTP library.
+ * @param[in] pTimeServer The time server being used to request time from.
+ * This parameter is useful to choose the security mechanism when multiple time
+ * servers are configured in the library, and they require different security
+ * mechanisms or authentication credentials to use.
+ * @param[in, out] pBuffer This buffer SHOULD be filled with the authentication
+ * code generated from the #SNTP_PACKET_BASE_SIZE bytes of SNTP request data
+ * present in it.
+ * @param[in] bufferSize The maximum amount of data that can be held by the buffer,
+ * @p pBuffer.
+ * @param[out] pAuthCodeSize This should be filled with size of the authentication
+ * data appended to the SNTP request buffer, @p pBuffer. This value plus
+ * #SNTP_PACKET_BASE_SIZE should not exceed the buffer size, @p bufferSize.
+ *
+ * @return The function SHOULD return one of the following integer codes:
+ * - #SntpSuccess when the authentication data is successfully appended to @p pBuffer.
+ * - #SntpErrorBufferTooSmall when the user-supplied buffer (to the SntpContext_t through
+ * @ref Sntp_Init) is not large enough to hold authentication data.
+ * - #SntpErrorAuthFailure for failure to generate authentication data due to internal
+ * error.
+ */
+typedef SntpStatus_t (* SntpGenerateAuthCode_t )( SntpAuthContext_t * pContext,
+ const SntpServerInfo_t * pTimeServer,
+ void * pBuffer,
+ size_t bufferSize,
+ uint16_t * pAuthCodeSize );
+
+/**
+ * @ingroup sntp_callback_types
+ * @brief Interface for user-defined function to authenticate server by validating
+ * the authentication code present in its SNTP response to a time request, if
+ * a security mechanism is supported by the server.
+ *
+ * The user can choose to implement with any security mechanism, symmetric
+ * key-based (like AES-CMAC) or asymmetric key-based (like Network Time Security),
+ * depending on the security mechanism supported by the time server being used
+ * to synchronize time with.
+ *
+ * @note In an SNTP response, the authentication code is present only after the
+ * first #SNTP_PACKET_BASE_SIZE bytes. Depending on the security mechanism used,
+ * the first #SNTP_PACKET_BASE_SIZE bytes MAY be used in validating the
+ * authentication data sent by the server.
+ *
+ * @param[in,out] pContext The user defined NetworkContext_t which
+ * is opaque to the coreSNTP library.
+ * @param[in] pTimeServer The time server that has to be authenticated from its
+ * SNTP response.
+ * This parameter is useful to choose the security mechanism when multiple time
+ * servers are configured in the library, and they require different security
+ * mechanisms or authentication credentials to use.
+ * @param[in] pResponseData The SNTP response from the server that contains the
+ * authentication code after the first #SNTP_PACKET_BASE_SIZE bytes.
+ * @param[in] responseSize The total size of the response from the server.
+ *
+ * @return The function SHOULD return one of the following integer codes:
+ * - #SntpSuccess when the server is successfully authenticated.
+ * - #SntpServerNotAuthenticated when server could not be authenticated.
+ * - #SntpErrorAuthFailure for failure to authenticate server due to internal
+ * error.
+ */
+typedef SntpStatus_t (* SntpValidateServerAuth_t )( SntpAuthContext_t * pContext,
+ const SntpServerInfo_t * pTimeServer,
+ const void * pResponseData,
+ uint16_t responseSize );
+
+/**
+ * @ingroup sntp_struct_types
+ * @brief Struct representing the authentication interface for securely
+ * communicating with time servers.
+ *
+ * @note Using a security mechanism is OPTIONAL for using the coreSNTP
+ * library i.e. a user does not need to define the authentication interface
+ * if they are not using a security mechanism for SNTP communication.
+ */
+typedef struct SntpAuthenticationIntf
+{
+ /**
+ *@brief The user-defined context for storing information like
+ * key credentials required for cryptographic operations in the
+ * security mechanism used for communicating with server.
+ */
+ SntpAuthContext_t * pAuthContext;
+
+ /**
+ * @brief The user-defined function for appending client authentication data.
+ * */
+ SntpGenerateAuthCode_t generateClientAuth;
+
+ /**
+ * @brief The user-defined function for authenticating server from its SNTP
+ * response.
+ */
+ SntpValidateServerAuth_t validateServerAuth;
+} SntpAuthenticationInterface_t;
+
+/**
+ * @ingroup sntp_struct_types
+ * @brief Structure for a context that stores state for managing a long-running
+ * SNTP client that periodically polls time and synchronizes system clock.
+ */
+typedef struct SntpContext
+{
+ /**
+ * @brief List of time servers in decreasing priority order configured
+ * for the SNTP client.
+ * Only a single server is configured for use at a time across polling
+ * attempts until the server rejects a time request or there is a response
+ * timeout, after which, the next server in the list is used for subsequent
+ * polling requests.
+ */
+ const SntpServerInfo_t * pTimeServers;
+
+ /**
+ * @brief Number of servers configured for use.
+ */
+ size_t numOfServers;
+
+ /**
+ * @brief The index for the currently configured time server for time querying
+ * from the list of time servers in @ref pTimeServers.
+ */
+ size_t currentServerIndex;
+
+ /**
+ * @brief The user-supplied buffer for storing network data of both SNTP requests
+ * and SNTP response.
+ */
+ uint8_t * pNetworkBuffer;
+
+ /**
+ * @brief The size of the network buffer.
+ */
+ size_t bufferSize;
+
+ /**
+ * @brief The user-supplied function for resolving DNS name of time servers.
+ */
+ SntpResolveDns_t resolveDnsFunc;
+
+ /**
+ * @brief The user-supplied function for obtaining the current system time.
+ */
+ SntpGetTime_t getTimeFunc;
+
+ /**
+ * @brief The user-supplied function for correcting system time after receiving
+ * time from a server.
+ */
+ SntpSetTime_t setTimeFunc;
+
+ /**
+ * @brief The user-defined interface for performing User Datagram Protocol (UDP)
+ * send and receive network operations.
+ */
+ UdpTransportInterface_t networkIntf;
+
+ /**
+ * @brief The user-defined interface for incorporating security mechanism of
+ * adding client authentication in SNTP request as well as authenticating server
+ * from SNTP response.
+ *
+ * @note If the application will not use security mechanism for any of the
+ * configured servers, then this interface can be undefined.
+ */
+ SntpAuthenticationInterface_t authIntf;
+
+ /**
+ * @brief Cache of the resolved Ipv4 address of the current server being used for
+ * time synchronization.
+ * As a Best Practice functionality, the client library attempts to resolve the
+ * DNS of the time-server every time the @ref Sntp_SendTimeRequest API is called.
+ */
+ uint32_t currentServerAddr;
+
+ /**
+ * @brief Cache of the timestamp of sending the last time request to a server
+ * for replay attack protection by checking that the server response contains
+ * the same timestamp in its "originate timestamp" field.
+ */
+ SntpTimestamp_t lastRequestTime;
+
+ /**
+ * @brief State member for storing the size of the SNTP packet that includes
+ * both #SNTP_PACKET_BASE_SIZE bytes plus any authentication data, if a security
+ * mechanism is used.
+ * This value is used for expecting the same size for an SNTP response
+ * from the server.
+ */
+ uint16_t sntpPacketSize;
+
+ /**
+ * @brief The timeout duration (in milliseconds) for receiving a response, through
+ * @ref Sntp_ReceiveTimeResponse API, from a server after the request for time is
+ * sent to it through @ref Sntp_SendTimeRequest API.
+ */
+ uint32_t responseTimeoutMs;
+} SntpContext_t;
+
+/**
+ * @brief Initializes a context for SNTP client communication with SNTP/NTP
+ * servers.
+ *
+ * @param[out] pContext The user-supplied memory for the context that will be
+ * initialized to represent an SNTP client.
+ * @param[in] pTimeServers The list of decreasing order of priority of time
+ * servers that should be used by the SNTP client. This list MUST stay in
+ * scope for all the time of use of the context.
+ * @param[in] numOfServers The number of servers in the list, @p pTimeServers.
+ * @param[in] serverResponseTimeoutMs The timeout duration (in milliseconds) for
+ * receiving server response for time requests. The same timeout value is used for
+ * each server in the @p pTimeServers list.
+ * @param[in] pNetworkBuffer The user-supplied memory that will be used for
+ * storing network data for SNTP client-server communication. The buffer
+ * MUST stay in scope for all the time of use of the context.
+ * @param[in] bufferSize The size of the passed buffer @p pNetworkBuffer. The buffer
+ * SHOULD be appropriately sized for storing an entire SNTP packet which includes
+ * both #SNTP_PACKET_BASE_SIZE bytes of standard SNTP packet size, and space for
+ * authentication data, if security mechanism is used to communicate with any of
+ * the time servers configured for use.
+ * @param[in] resolveDnsFunc The user-defined function for DNS resolution of time
+ * server.
+ * @param[in] getSystemTimeFunc The user-defined function for querying system
+ * time.
+ * @param[in] setSystemTimeFunc The user-defined function for correcting system
+ * time for every successful time response received from a server.
+ * @param[in] pTransportIntf The user-defined function for performing network
+ * send/recv operations over UDP.
+ * @param[in] pAuthIntf The user-defined interface for generating client authentication
+ * in SNTP requests and authenticating servers in SNTP responses, if security mechanism
+ * is used in SNTP communication with server(s). If security mechanism is not used in
+ * communication with any of the configured servers (in @p pTimeServers), then the
+ * @ref SntpAuthenticationInterface_t does not need to be defined and this parameter
+ * can be NULL.
+ *
+ * @return This function returns one of the following:
+ * - #SntpSuccess if the context is initialized.
+ * - #SntpErrorBadParameter if any of the passed parameters in invalid.
+ * - #SntpErrorBufferTooSmall if the buffer does not have the minimum size
+ * required for a valid SNTP response packet.
+ */
+/* @[define_sntp_init] */
+SntpStatus_t Sntp_Init( SntpContext_t * pContext,
+ const SntpServerInfo_t * pTimeServers,
+ size_t numOfServers,
+ uint32_t serverResponseTimeoutMs,
+ uint8_t * pNetworkBuffer,
+ size_t bufferSize,
+ SntpResolveDns_t resolveDnsFunc,
+ SntpGetTime_t getSystemTimeFunc,
+ SntpSetTime_t setSystemTimeFunc,
+ const UdpTransportInterface_t * pTransportIntf,
+ const SntpAuthenticationInterface_t * pAuthIntf );
+/* @[define_sntp_init] */
+
+
+/**
+ * @brief Sends a request for time from the currently configured server (in the
+ * context).
+ * If the user has provided an authentication interface, the client
+ * authentication code is appended to the request before sending over the network
+ * by calling the @ref SntpGenerateAuthCode_t function of the
+ * @ref SntpAuthenticationInterface_t.
+ *
+ * @note This function will ONLY send a request if there is a server available
+ * in the configured list that has not rejected an earlier request for time.
+ * This adheres to the Best Practice functionality specified in [Section 10 Point 8
+ * of SNTPv4 specification](https://tools.ietf.org/html/rfc4330#section-10).
+ *
+ * @param[in] pContext The context representing an SNTPv4 client.
+ * @param[in] randomNumber A random number serializing the SNTP request packet
+ * to protect against spoofing attacks by attackers that are off the network path
+ * of the SNTP client-server communication. This mechanism is suggested by SNTPv4
+ * specification in [RFC 4330 Section 3](https://tools.ietf.org/html/rfc4330#section-3).
+ * @param[in] blockTimeMs The maximum duration of time (in milliseconds) the function will
+ * block on attempting to send time request to the server over the network. If a zero
+ * block time value is provided, then the function will attempt to send the packet ONLY
+ * once.
+ *
+ * @note It is RECOMMENDED that a True Random Number Generator is used to generate the
+ * random number by using a hardware module, like Hardware Security Module (HSM), Secure Element,
+ * etc, as the entropy source.
+ *
+ * @return The API function returns one of the following:
+ * - #SntpSuccess if a time request is successfully sent to the currently configured
+ * time server in the context.
+ * - #SntpErrorBadParameter if an invalid context is passed to the function.
+ * - #SntpErrorContextNotInitialized if an uninitialized or invalid context is passed
+ * to the function.
+ * - #SntpErrorDnsFailure if there is failure in the user-defined function for
+ * DNS resolution of the time server.
+ * - #SntpErrorNetworkFailure if the SNTP request could not be sent over the network
+ * through the user-defined transport interface.
+ * - #SntpErrorAuthFailure if there was a failure in generating the client
+ * authentication code in the user-defined authentication interface.
+ * - #SntpErrorSendTimeout if the time request packet could not be sent over the
+ * network for the entire @p blockTimeMs duration.
+ */
+/* @[define_sntp_sendtimerequest] */
+SntpStatus_t Sntp_SendTimeRequest( SntpContext_t * pContext,
+ uint32_t randomNumber,
+ uint32_t blockTimeMs );
+/* @[define_sntp_sendtimerequest] */
+
+
+/**
+ * @brief Receives a time response from the server that has been requested for time with
+ * the @ref Sntp_SendTimeRequest API function.
+ * Once an accepted response containing time from server is received, this function calls
+ * the user-defined @ref SntpSetTime_t function to update the system time.
+ *
+ * @note If the user has provided an authentication interface to the client
+ * (through @ref Sntp_Init), the server response is authenticated by calling the
+ * @ref SntpValidateServerAuth_t function of the @ref SntpAuthenticationInterface_t interface.
+ *
+ * @note On receiving a successful server response containing server time,
+ * this API calculates the clock offset value of the system clock relative to the
+ * server before calling the user-defined @ref SntpSetTime_t function for updating
+ * the system time.
+ *
+ * @note For correct calculation of clock-offset, the server and client times MUST be within
+ * ~68 years (or 2^31 seconds) of each other. In the special case when the server and client
+ * times are exactly 2^31 seconds apart, the library ASSUMES that the server time is ahead
+ * of the client time, and returns the positive clock-offset value of INT32_MAX seconds.
+ *
+ * @note This API will rotate the server of use in the library for the next time request
+ * (through the @ref Sntp_SendTimeRequest) if either of following events occur:
+ * - The server has responded with a rejection for the time request.
+ * OR
+ * - The server response wait has timed out.
+ * If all the servers configured in the context have been used, the API will rotate server for
+ * time query back to the first server in the list which will be used in next time request.
+ *
+ * @param[in] pContext The context representing an SNTPv4 client.
+ * @param[in] blockTimeMs The maximum duration of time (in milliseconds) the function will
+ * block on receiving a response from the server unless either the response is received
+ * OR a response timeout occurs.
+ *
+ * @note This function can be called multiple times with zero or small blocking times
+ * to poll whether server response is received until either the response response is
+ * received from the server OR a response timeout has occurred.
+ *
+ * @return This API functions returns one of the following:
+ * - #SntpSuccess if a successful server response is received.
+ * - #SntpErrorContextNotInitialized if an uninitialized or invalid context is passed
+ * to the function.
+ * - #SntpErrorBadParameter if an invalid context is passed to the function.
+ * - #SntpErrorNetworkFailure if there is a failure in the user-defined transport
+ * - #SntpErrorAuthFailure if an internal error occurs in the user-defined
+ * authentication interface when validating the server response.
+ * - #SntpServerNotAuthenticated when the server could not be authenticated from
+ * its response with the user-defined authentication interface.
+ * - #SntpInvalidResponse if the server response fails sanity checks expected in an
+ * SNTP response packet.
+ * - #SntpErrorResponseTimeout if a timeout has occurred in receiving response from
+ * the server.
+ * - #SntpRejectedResponse if the server responded with a rejection for the time
+ * request.
+ */
+/* @[define_sntp_receivetimeresponse] */
+SntpStatus_t Sntp_ReceiveTimeResponse( SntpContext_t * pContext,
+ uint32_t blockTimeMs );
+/* @[define_sntp_receivetimeresponse] */
+
+/**
+ * @brief Converts @ref SntpStatus_t to its equivalent
+ * string.
+ *
+ * @note The returned string MUST NOT be modified.
+ *
+ * @param[in] status The status to convert to a string.
+ *
+ * @return The string representation of the status
+ * code.
+ */
+/* @[define_sntp_statustostr] */
+const char * Sntp_StatusToStr( SntpStatus_t status );
+/* @[define_sntp_statustostr] */
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ }
+#endif
+/* *INDENT-ON* */
+
+#endif /* ifndef CORE_SNTP_CLIENT_H_ */
diff --git a/project/coreMQTT/coreSNTP/core_sntp_config_defaults.h b/project/coreMQTT/coreSNTP/core_sntp_config_defaults.h
new file mode 100644
index 0000000..162a966
--- /dev/null
+++ b/project/coreMQTT/coreSNTP/core_sntp_config_defaults.h
@@ -0,0 +1,144 @@
+/*
+ * coreSNTP v1.2.0
+ * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_sntp_config_defaults.h
+ * @brief This file represents the default values for the configuration macros
+ * of the coreSNTP library.
+ *
+ * @note This file SHOULD NOT be modified. If custom values are needed for
+ * any configuration macro, a core_sntp_config.h file should be provided to
+ * the SNTP library to override the default values defined in this file.
+ * To build the library with the core_sntp_config.h file, make sure to
+ * not set the SNTP_DO_NOT_USE_CUSTOM_CONFIG preprocessor macro.
+ */
+
+#ifndef CORE_SNTP_CONFIG_DEFAULTS_H_
+#define CORE_SNTP_CONFIG_DEFAULTS_H_
+
+/* The macro definition for SNTP_DO_NOT_USE_CUSTOM_CONFIG is for Doxygen
+ * documentation only. */
+
+/**
+ * @brief Define this macro to build the SNTP library without the custom config
+ * file core_sntp_config.h.
+ *
+ * Without the custom config, the SNTP library builds with
+ * default values of config macros defined in core_sntp_config_defaults.h file.
+ *
+ * If a custom config is provided, then SNTP_DO_NOT_USE_CUSTOM_CONFIG should not
+ * be defined.
+ */
+#ifdef DOXYGEN
+ #define SNTP_DO_NOT_USE_CUSTOM_CONFIG
+#endif
+
+/* SNTP_DO_NOT_USE_CUSTOM_CONFIG allows building the SNTP library
+ * without a custom config. If a custom config is provided, the
+ * SNTP_DO_NOT_USE_CUSTOM_CONFIG macro should not be defined. */
+#ifndef SNTP_DO_NOT_USE_CUSTOM_CONFIG
+ #include "core_sntp_config.h"
+#endif
+
+/**
+ * @brief Macro that is called in the SNTP library for logging "Error" level
+ * messages.
+ *
+ * To enable error level logging in the SNTP library, this macro should be mapped to the
+ * application-specific logging implementation that supports error logging.
+ *
+ * @note This logging macro is called in the SNTP library with parameters wrapped in
+ * double parentheses to be ISO C89/C90 standard compliant. For a reference
+ * POSIX implementation of the logging macros, refer to core_sntp_config.h files, and the
+ * logging-stack in demos folder of the
+ * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C).
+ *
+ * <b>Default value</b>: Error logging is turned off, and no code is generated for calls
+ * to the macro in the SNTP library on compilation.
+ */
+#ifndef LogError
+ #define LogError( message )
+#endif
+
+/**
+ * @brief Macro that is called in the SNTP library for logging "Warning" level
+ * messages.
+ *
+ * To enable warning level logging in the SNTP library, this macro should be mapped to the
+ * application-specific logging implementation that supports warning logging.
+ *
+ * @note This logging macro is called in the SNTP library with parameters wrapped in
+ * double parentheses to be ISO C89/C90 standard compliant. For a reference
+ * POSIX implementation of the logging macros, refer to core_sntp_config.h files, and the
+ * logging-stack in demos folder of the
+ * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
+ *
+ * <b>Default value</b>: Warning logs are turned off, and no code is generated for calls
+ * to the macro in the SNTP library on compilation.
+ */
+#ifndef LogWarn
+ #define LogWarn( message )
+#endif
+
+/**
+ * @brief Macro that is called in the SNTP library for logging "Info" level
+ * messages.
+ *
+ * To enable info level logging in the SNTP library, this macro should be mapped to the
+ * application-specific logging implementation that supports info logging.
+ *
+ * @note This logging macro is called in the SNTP library with parameters wrapped in
+ * double parentheses to be ISO C89/C90 standard compliant. For a reference
+ * POSIX implementation of the logging macros, refer to core_sntp_config.h files, and the
+ * logging-stack in demos folder of the
+ * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
+ *
+ * <b>Default value</b>: Info logging is turned off, and no code is generated for calls
+ * to the macro in the SNTP library on compilation.
+ */
+#ifndef LogInfo
+ #define LogInfo( message )
+#endif
+
+/**
+ * @brief Macro that is called in the SNTP library for logging "Debug" level
+ * messages.
+ *
+ * To enable debug level logging from SNTP library, this macro should be mapped to the
+ * application-specific logging implementation that supports debug logging.
+ *
+ * @note This logging macro is called in the SNTP library with parameters wrapped in
+ * double parentheses to be ISO C89/C90 standard compliant. For a reference
+ * POSIX implementation of the logging macros, refer to core_sntp_config.h files, and the
+ * logging-stack in demos folder of the
+ * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
+ *
+ * <b>Default value</b>: Debug logging is turned off, and no code is generated for calls
+ * to the macro in the SNTP library on compilation.
+ */
+#ifndef LogDebug
+ #define LogDebug( message )
+#endif
+
+#endif /* ifndef CORE_SNTP_CONFIG_DEFAULTS_H_ */
diff --git a/project/coreMQTT/coreSNTP/core_sntp_serializer.c b/project/coreMQTT/coreSNTP/core_sntp_serializer.c
new file mode 100644
index 0000000..14fffed
--- /dev/null
+++ b/project/coreMQTT/coreSNTP/core_sntp_serializer.c
@@ -0,0 +1,853 @@
+/*
+ * coreSNTP v1.2.0
+ * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_sntp_serializer.c
+ * @brief Implementation of the Serializer API of the coreSNTP library.
+ */
+
+/* Standard includes. */
+#include <string.h>
+#include <stdbool.h>
+#include <assert.h>
+
+/* Include API header. */
+#include "core_sntp_serializer.h"
+
+#include "core_sntp_config_defaults.h"
+
+/**
+ * @brief The version of SNTP supported by the coreSNTP library by complying
+ * with the SNTPv4 specification defined in [RFC 4330](https://tools.ietf.org/html/rfc4330).
+ */
+#define SNTP_VERSION ( 4U )
+
+/**
+ * @brief The bit mask for the "Mode" information in the first byte of an SNTP packet.
+ * The "Mode" field occupies bits 0-2 of the byte.
+ * @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4)
+ * for more information.
+ */
+#define SNTP_MODE_BITS_MASK ( 0x07U )
+
+/**
+ * @brief The value indicating a "client" in the "Mode" field of an SNTP packet.
+ * @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4)
+ * for more information.
+ */
+#define SNTP_MODE_CLIENT ( 3U )
+
+/**
+ * @brief The value indicating a "server" in the "Mode" field of an SNTP packet.
+ * @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4)
+ * for more information.
+ */
+#define SNTP_MODE_SERVER ( 4U )
+
+/**
+ * @brief The position of the least significant bit of the "Leap Indicator" field
+ * in first byte of an SNTP packet. The "Leap Indicator" field occupies bits 6-7 of the byte.
+ * @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4)
+ * for more information.
+ */
+#define SNTP_LEAP_INDICATOR_LSB_POSITION ( 6 )
+
+/**
+ * @brief Value of Stratum field in SNTP packet representing a Kiss-o'-Death message
+ * from server.
+ */
+#define SNTP_KISS_OF_DEATH_STRATUM ( 0U )
+
+/**
+ * @brief The position of least significant bit of the "Version" information
+ * in the first byte of an SNTP packet. "Version" field occupies bits 3-5 of
+ * the byte.
+ * @note Refer to the [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4)
+ * for more information.
+ */
+#define SNTP_VERSION_LSB_POSITION ( 3 )
+
+/**
+ * @brief The integer value of the Kiss-o'-Death ASCII code, "DENY", used
+ * for comparison with data in an SNTP response.
+ * @note Refer to [RFC 4330 Section 8](https://tools.ietf.org/html/rfc4330#section-8)
+ * for more information.
+ */
+#define KOD_CODE_DENY_UINT_VALUE ( 0x44454e59U )
+
+/**
+ * @brief The integer value of the Kiss-o'-Death ASCII code, "RSTR", used
+ * for comparison with data in an SNTP response.
+ * @note Refer to [RFC 4330 Section 8](https://tools.ietf.org/html/rfc4330#section-8)
+ * for more information.
+ */
+#define KOD_CODE_RSTR_UINT_VALUE ( 0x52535452U )
+
+/**
+ * @brief The integer value of the Kiss-o'-Death ASCII code, "RATE", used
+ * for comparison with data in an SNTP response.
+ * @note Refer to [RFC 4330 Section 8](https://tools.ietf.org/html/rfc4330#section-8)
+ * for more information.
+ */
+#define KOD_CODE_RATE_UINT_VALUE ( 0x52415445U )
+
+/**
+ * @brief Macro to represent exactly half of the total number of seconds in an NTP era.
+ * As 32 bits are used to represent the "seconds" part of an SNTP timestamp, the half of
+ * the total range of seconds in an NTP era is 2^31 seconds, which represents ~68 years.
+ *
+ * @note This macro represents the edge case of calculating the client system clock-offset
+ * relative to server time as the library ASSUMES that the client and server times are within
+ * 2^31 seconds apart in duration. This is done to support calculating clock-offset for the
+ * cases when server and client systems are in adjacent NTP eras, which can occur when NTP time
+ * wraps around in 2036, and the relative NTP era presence of client and server times is
+ * determined based on comparing first order difference values between all possible NTP era
+ * configurations of the systems.
+ */
+#define CLOCK_OFFSET_MAX_TIME_DIFFERENCE ( ( ( ( int64_t ) INT32_MAX + 1 ) * 1000 ) )
+
+/**
+ * @brief Macro to represent the total number of milliseconds that are represented in an
+ * NTP era period. This macro represents a duration of ~136 years.
+ *
+ * @note Rationale for calculation: The "seconds" part of an NTP timestamp is represented in
+ * unsigned 32 bit width, thus, the total range of seconds it represents is 2^32,
+ * i.e. (UINT32_MAX + 1).
+ */
+#define TOTAL_MILLISECONDS_IN_NTP_ERA ( ( ( int64_t ) UINT32_MAX + 1 ) * 1000 )
+
+/**
+ * @brief Structure representing an SNTP packet header.
+ * For more information on SNTP packet format, refer to
+ * [RFC 4330 Section 4](https://tools.ietf.org/html/rfc4330#section-4).
+ *
+ * @note This does not include extension fields for authentication data
+ * for secure SNTP communication. Authentication data follows the
+ * packet header represented by this structure.
+ */
+typedef struct SntpPacket
+{
+ /**
+ * @brief Bits 6-7 leap indicator, bits 3-5 are version number, bits 0-2 are mode
+ */
+ uint8_t leapVersionMode;
+
+ /**
+ * @brief stratum
+ */
+ uint8_t stratum;
+
+ /**
+ * @brief poll interval
+ */
+ uint8_t poll;
+
+ /**
+ * @brief precision
+ */
+ uint8_t precision;
+
+ /**
+ * @brief root delay
+ */
+ uint32_t rootDelay;
+
+ /**
+ * @brief root dispersion
+ */
+ uint32_t rootDispersion;
+
+ /**
+ * @brief reference ID
+ */
+ uint32_t refId;
+
+ /**
+ * @brief reference time
+ */
+ SntpTimestamp_t refTime;
+
+ /**
+ * @brief origin timestamp
+ */
+ SntpTimestamp_t originTime;
+
+ /**
+ * @brief receive timestamp
+ */
+ SntpTimestamp_t receiveTime;
+
+ /**
+ * @brief transmit timestamp
+ */
+ SntpTimestamp_t transmitTime;
+} SntpPacket_t;
+
+/**
+ * @brief Utility macro to fill 32-bit integer in word-sized
+ * memory in network byte (or Big Endian) order.
+ *
+ * @param[out] pWordMemory Pointer to the word-sized memory in which
+ * the 32-bit integer will be filled.
+ * @param[in] data The 32-bit integer to fill in the @p wordMemory
+ * in network byte order.
+ *
+ * @note This utility ensures that data is filled in memory
+ * in expected network byte order, as an assignment operation
+ * (like *pWordMemory = word) can cause undesired side-effect
+ * of network-byte ordering getting reversed on Little Endian platforms.
+ */
+static void fillWordMemoryInNetworkOrder( uint32_t * pWordMemory,
+ uint32_t data )
+{
+ assert( pWordMemory != NULL );
+
+ *( ( uint8_t * ) pWordMemory ) = ( uint8_t ) ( data >> 24 );
+ *( ( uint8_t * ) pWordMemory + 1 ) = ( uint8_t ) ( ( data >> 16 ) & 0x000000FFU );
+ *( ( uint8_t * ) pWordMemory + 2 ) = ( uint8_t ) ( ( data >> 8 ) & 0x000000FFU );
+ *( ( uint8_t * ) pWordMemory + 3 ) = ( uint8_t ) ( ( data ) & 0x000000FFU );
+}
+
+/**
+ * @brief Utility macro to generate a 32-bit integer from memory containing
+ * integer in network (or Big Endian) byte order.
+ * @param[in] ptr Pointer to the memory containing 32-bit integer in network
+ * byte order.
+ *
+ * @return The host representation of the 32-bit integer in the passed word
+ * memory.
+ */
+static uint32_t readWordFromNetworkByteOrderMemory( const uint32_t * ptr )
+{
+ const uint8_t * pMemStartByte = ( const uint8_t * ) ptr;
+
+ assert( ptr != NULL );
+
+ return ( uint32_t ) ( ( ( uint32_t ) *( pMemStartByte ) << 24 ) |
+ ( 0x00FF0000U & ( ( uint32_t ) *( pMemStartByte + 1 ) << 16 ) ) |
+ ( 0x0000FF00U & ( ( uint32_t ) *( pMemStartByte + 2 ) << 8 ) ) |
+ ( ( uint32_t ) *( pMemStartByte + 3 ) ) );
+}
+
+/**
+ * @brief Utility to return absolute (or positively signed) value of an signed
+ * 64 bit integer.
+ *
+ * @param[in] value The integer to return the absolute value of.
+ *
+ * @return The absolute value of @p value.
+ */
+static int64_t absoluteOf( int64_t value )
+{
+ return ( value >= 0 ) ? value : ( ( int64_t ) 0 - value );
+}
+
+/**
+ * @brief Utility to determine whether a timestamp represents a zero
+ * timestamp value.
+ *
+ * @note This utility is used to determine whether a timestamp value is
+ * invalid. According to the SNTPv4 specification, a zero timestamp value
+ * is considered invalid.
+ *
+ * @param[in] pTime The timestamp whose value is to be inspected for
+ * zero value.
+ *
+ * @return `true` if the timestamp is zero; otherwise `false`.
+ */
+static bool isZeroTimestamp( const SntpTimestamp_t * pTime )
+{
+ bool isSecondsZero = ( pTime->seconds == 0U ) ? true : false;
+ bool isFractionsZero = ( pTime->fractions == 0U ) ? true : false;
+
+ return( isSecondsZero && isFractionsZero );
+}
+
+/**
+ * @brief Utility to convert the "fractions" part of an SNTP timestamp to milliseconds
+ * duration of time.
+ * @param[in] fractions The fractions value.
+ *
+ * @return The milliseconds equivalent of the @p fractions value.
+ */
+static uint32_t fractionsToMs( uint32_t fractions )
+{
+ return( fractions / ( 1000U * SNTP_FRACTION_VALUE_PER_MICROSECOND ) );
+}
+
+/**
+ * @brief Utility to safely calculate difference between server and client timestamps and
+ * return the difference in the resolution of milliseconds as a signed 64 bit integer.
+ * The calculated value represents the effective subtraction as ( @p serverTimeSec - @p clientTimeSec ).
+ *
+ * @note This utility SUPPORTS the cases of server and client timestamps being in different NTP eras,
+ * and ASSUMES that the server and client systems are within 68 years of each other.
+ * To handle the case of different NTP eras, this function calculates difference values for all
+ * possible combinations of NTP eras of server and client times (i.e. 1. both timestamps in same era,
+ * 2. server timestamp one era ahead, and 3. client timestamp being one era ahead), and determines
+ * the NTP era configuration by choosing the difference value of the smallest absolute value.
+ *
+ * @param[in] pServerTime The server timestamp.
+ * @param[in] pClientTime The client timestamp.
+ *
+ * @return The calculated difference between server and client times as a signed 64 bit integer.
+ */
+static int64_t safeTimeDifference( const SntpTimestamp_t * pServerTime,
+ const SntpTimestamp_t * pClientTime )
+{
+ int64_t eraAdjustedDiff = 0;
+
+ /* Convert the timestamps into 64 bit signed integer values of milliseconds. */
+ int64_t serverTime = ( ( int64_t ) pServerTime->seconds * 1000 ) + ( int64_t ) fractionsToMs( pServerTime->fractions );
+ int64_t clientTime = ( ( int64_t ) pClientTime->seconds * 1000 ) + ( int64_t ) fractionsToMs( pClientTime->fractions );
+
+ /* The difference between the 2 timestamps is calculated by determining the whether the timestamps
+ * are present in the same NTP era or adjacent NTP eras (i.e. the NTP timestamp overflow case). */
+
+ /* First, calculate the first order time difference assuming that server and client times
+ * are in the same NTP era. */
+ int64_t diffWithNoEraAdjustment = serverTime - clientTime;
+
+ /* Store the absolute value of the time difference which will be used for comparison with
+ * different cases of relative NTP era configuration of client and server times. */
+ int64_t absSameEraDiff = absoluteOf( diffWithNoEraAdjustment );
+
+ /* If the absolute difference value is 2^31 seconds, it means that the server and client times are
+ * away by exactly half the range of SNTP timestamp "second" values representable in unsigned 32 bits.
+ * In such a case, irrespective of whether the 2 systems exist in the same or adjacent NTP eras, the
+ * time difference calculated between the systems will ALWAYS yield the same value when viewed from
+ * all NTP era configurations of the times.
+ * For such a case, we will ASSUME that the server time is AHEAD of client time, and thus, generate
+ * a positive clock-offset value.
+ */
+ if( absSameEraDiff == CLOCK_OFFSET_MAX_TIME_DIFFERENCE )
+ {
+ /* It does not matter whether server and client are in the same era for this
+ * special case as the difference value for both same and adjacent eras will yield
+ * the same absolute value of 2^31.*/
+ eraAdjustedDiff = CLOCK_OFFSET_MAX_TIME_DIFFERENCE;
+ }
+ else
+ {
+ /* Determine if server time belongs to an NTP era different than the client time, and accordingly
+ * choose the 64 bit representation of the first order difference to account for the era.
+ * The logic for determining the relative era presence of the timestamps is by calculating the
+ * first order difference (of "Server Time - Client Time") for all the 3 different era combinations
+ * (1. both timestamps in same era, 2. server time one era ahead, 3. client time one era ahead)
+ * and choosing the NTP era configuration that has the smallest first order difference value.
+ */
+ int64_t diffWithServerEraAdjustment = serverTime + TOTAL_MILLISECONDS_IN_NTP_ERA -
+ clientTime; /* This helps determine whether server is an
+ * era ahead of client time. */
+ int64_t diffWithClientEraAdjustment = serverTime -
+ ( TOTAL_MILLISECONDS_IN_NTP_ERA + clientTime ); /* This helps determine whether server is an
+ * era behind of client time. */
+
+ /* Store the absolute value equivalents of all the time difference configurations
+ * for easier comparison to smallest value from them. */
+ int64_t absServerEraAheadDiff = absoluteOf( diffWithServerEraAdjustment );
+ int64_t absClientEraAheadDiff = absoluteOf( diffWithClientEraAdjustment );
+
+ /* Determine the correct relative era of client and server times by checking which era
+ * configuration of difference value represents the least difference. */
+ if( ( absSameEraDiff <= absServerEraAheadDiff ) && ( absSameEraDiff <= absClientEraAheadDiff ) )
+ {
+ /* Both server and client times are in the same era. */
+ eraAdjustedDiff = diffWithNoEraAdjustment;
+ }
+ /* Check if server time is an NTP era ahead of client time. */
+ else if( absServerEraAheadDiff < absSameEraDiff )
+ {
+ /* Server time is in NTP era 1 while client time is in NTP era 0. */
+ eraAdjustedDiff = diffWithServerEraAdjustment;
+ }
+ /* Now, we know that the client time is an era ahead of server time. */
+ else
+ {
+ /* Server time is in NTP era 0 while client time is in NTP era 1. */
+ eraAdjustedDiff = diffWithClientEraAdjustment;
+ }
+ }
+
+ return eraAdjustedDiff;
+}
+
+/**
+ * @brief Utility to calculate clock offset of system relative to the
+ * server using the on-wire protocol specified in the NTPv4 specification.
+ * For more information on on-wire protocol, refer to
+ * [RFC 5905 Section 8](https://tools.ietf.org/html/rfc5905#section-8).
+ *
+ * @note The following diagram explains the calculation of the clock
+ * offset:
+ *
+ * T2 T3
+ * --------------------------------- <----- *SNTP/NTP server*
+ * /\ \
+ * / \
+ * Request* / \ *Response*
+ * / \/
+ * --------------------------------- <----- *SNTP client*
+ * T1 T4
+ *
+ * The four most recent timestamps, T1 through T4, are used to compute
+ * the clock offset of SNTP client relative to the server where:
+ *
+ * T1 = Client Request Transmit Time
+ * T2 = Server Receive Time (of client request)
+ * T3 = Server Response Transmit Time
+ * T4 = Client Receive Time (of server response)
+ *
+ * Clock Offset = T(NTP/SNTP server) - T(SNTP client)
+ * = [( T2 - T1 ) + ( T3 - T4 )]
+ * ---------------------------
+ * 2
+ *
+ * @note Both NTPv4 and SNTPv4 specifications suggest calculating the
+ * clock offset value, if possible. As the timestamp format uses 64 bit
+ * integer and there exist 2 orders of arithmetic calculations on the
+ * timestamp values (subtraction followed by addition as shown in the
+ * diagram above), the clock offset for the system can be calculated
+ * ONLY if the value can be represented in 62 significant bits and 2 sign
+ * bits i.e. if the system clock is within 34 years (in the future or past)
+ * of the server time.
+ *
+ * @param[in] pClientTxTime The system time of sending the SNTP request.
+ * This is the same as "T1" in the above diagram.
+ * @param[in] pServerRxTime The server time of receiving the SNTP request
+ * packet from the client. This is the same as "T2" in the above diagram.
+ * @param[in] pServerTxTime The server time of sending the SNTP response
+ * packet. This is the same as "T3" in the above diagram.
+ * @param[in] pClientRxTime The system time of receiving the SNTP response
+ * from the server. This is the same as "T4" in the above diagram.
+ * @param[out] pClockOffset This will be filled with the calculated offset value
+ * of the system clock relative to the server time with the assumption that the
+ * system clock is within 68 years of server time.
+ */
+static void calculateClockOffset( const SntpTimestamp_t * pClientTxTime,
+ const SntpTimestamp_t * pServerRxTime,
+ const SntpTimestamp_t * pServerTxTime,
+ const SntpTimestamp_t * pClientRxTime,
+ int64_t * pClockOffset )
+{
+ /* Variable for storing the first-order difference between timestamps. */
+ int64_t firstOrderDiffSend = 0;
+ int64_t firstOrderDiffRecv = 0;
+
+ assert( pClientTxTime != NULL );
+ assert( pServerRxTime != NULL );
+ assert( pServerTxTime != NULL );
+ assert( pClientRxTime != NULL );
+ assert( pClockOffset != NULL );
+
+ /* Perform first order difference of timestamps on the network send path i.e. T2 - T1.
+ * Note: The calculated difference value will always represent years in the range of
+ *[-68 years, +68 years]. */
+ firstOrderDiffSend = safeTimeDifference( pServerRxTime, pClientTxTime );
+
+ /* Perform first order difference of timestamps on the network receive path i.e. T3 - T4.
+ * Note: The calculated difference value will always represent years in the range of
+ *[-68 years, +68 years]. */
+ firstOrderDiffRecv = safeTimeDifference( pServerTxTime, pClientRxTime );
+
+ /* Now calculate the system clock-offset relative to server time as the average of the
+ * first order difference of timestamps in both directions of network path.
+ * Note: This will ALWAYS represent offset in the range of [-68 years, +68 years]. */
+ *pClockOffset = ( firstOrderDiffSend + firstOrderDiffRecv ) / 2;
+}
+
+/**
+ * @brief Parse a SNTP response packet by determining whether it is a rejected
+ * or accepted response to an SNTP request, and accordingly, populate the
+ * @p pParsedResponse parameter with the parsed data.
+ *
+ * @note If the server has rejected the request with the a Kiss-o'-Death message,
+ * then this function will set the associated rejection code in the output parameter
+ * while setting the remaining members to zero.
+ * If the server has accepted the time request, then the function will set the
+ * rejectedResponseCode member of the output parameter to #SNTP_KISS_OF_DEATH_CODE_NONE,
+ * and set the other the members with appropriate data extracted from the response
+ * packet.
+ *
+ * @param[in] pResponsePacket The SNTP response packet from server to parse.
+ * @param[in] pRequestTxTime The system time (in SNTP timestamp format) of
+ * sending the SNTP request to server.
+ * @param[in] pResponseRxTime The system time (in SNTP timestamp format) of
+ * receiving the SNTP response from server.
+ * @param[out] pParsedResponse The parameter that will be populated with data
+ * parsed from the response packet, @p pResponsePacket.
+ *
+ * @return This function returns one of the following:
+ * - #SntpSuccess if the server response does not represent a Kiss-o'-Death
+ * message.
+ * - #SntpRejectedResponseChangeServer if the server rejected with a code
+ * indicating that client cannot be retry requests to it.
+ * - #SntpRejectedResponseRetryWithBackoff if the server rejected with a code
+ * indicating that client should back-off before retrying request.
+ * - #SntpRejectedResponseOtherCode if the server rejected with a code
+ * other than "DENY", "RSTR" and "RATE".
+ */
+static SntpStatus_t parseValidSntpResponse( const SntpPacket_t * pResponsePacket,
+ const SntpTimestamp_t * pRequestTxTime,
+ const SntpTimestamp_t * pResponseRxTime,
+ SntpResponseData_t * pParsedResponse )
+{
+ SntpStatus_t status = SntpSuccess;
+
+ assert( pResponsePacket != NULL );
+ assert( pResponseRxTime != NULL );
+ assert( pParsedResponse != NULL );
+
+ /* Clear the output parameter memory to zero. */
+ ( void ) memset( pParsedResponse, 0, sizeof( *pParsedResponse ) );
+
+ /* Determine if the server has accepted or rejected the request for time. */
+ if( pResponsePacket->stratum == SNTP_KISS_OF_DEATH_STRATUM )
+ {
+ /* Server has sent a Kiss-o'-Death message i.e. rejected the request. */
+
+ /* Extract the kiss-code sent by the server from the "Reference ID" field
+ * of the SNTP packet. */
+ pParsedResponse->rejectedResponseCode =
+ readWordFromNetworkByteOrderMemory( &pResponsePacket->refId );
+
+ /* Determine the return code based on the Kiss-o'-Death code. */
+ switch( pParsedResponse->rejectedResponseCode )
+ {
+ case KOD_CODE_DENY_UINT_VALUE:
+ case KOD_CODE_RSTR_UINT_VALUE:
+ status = SntpRejectedResponseChangeServer;
+ break;
+
+ case KOD_CODE_RATE_UINT_VALUE:
+ status = SntpRejectedResponseRetryWithBackoff;
+ break;
+
+ default:
+ status = SntpRejectedResponseOtherCode;
+ break;
+ }
+ }
+ else
+ {
+ /* Server has responded successfully to the time request. */
+
+ SntpTimestamp_t serverRxTime;
+
+ /* Map of integer value to SntpLeapSecondInfo_t enumeration type for the
+ * "Leap Indicator" field in the first byte of an SNTP packet.
+ * Note: This map is used to not violate MISRA Rule 10.5 when directly
+ * converting an integer to enumeration type.
+ */
+ const SntpLeapSecondInfo_t leapIndicatorTypeMap[] =
+ {
+ NoLeapSecond,
+ LastMinuteHas61Seconds,
+ LastMinuteHas59Seconds,
+ AlarmServerNotSynchronized
+ };
+
+ /* Set the Kiss-o'-Death code value to NULL as server has responded favorably
+ * to the time request. */
+ pParsedResponse->rejectedResponseCode = SNTP_KISS_OF_DEATH_CODE_NONE;
+
+ /* Fill the output parameter with the server time which is the
+ * "transmit" time in the response packet. */
+ pParsedResponse->serverTime.seconds =
+ readWordFromNetworkByteOrderMemory( &pResponsePacket->transmitTime.seconds );
+ pParsedResponse->serverTime.fractions =
+ readWordFromNetworkByteOrderMemory( &pResponsePacket->transmitTime.fractions );
+
+ /* Extract information of any upcoming leap second from the response. */
+ pParsedResponse->leapSecondType = leapIndicatorTypeMap[
+ ( pResponsePacket->leapVersionMode >> SNTP_LEAP_INDICATOR_LSB_POSITION ) ];
+
+ /* Store the "receive" time in SNTP response packet in host order. */
+ serverRxTime.seconds =
+ readWordFromNetworkByteOrderMemory( &pResponsePacket->receiveTime.seconds );
+ serverRxTime.fractions =
+ readWordFromNetworkByteOrderMemory( &pResponsePacket->receiveTime.fractions );
+
+ /* Calculate system clock offset relative to server time, if possible, within
+ * the 64 bit integer width of the SNTP timestamp. */
+ calculateClockOffset( pRequestTxTime,
+ &serverRxTime,
+ &pParsedResponse->serverTime,
+ pResponseRxTime,
+ &pParsedResponse->clockOffsetMs );
+ }
+
+ return status;
+}
+
+
+SntpStatus_t Sntp_SerializeRequest( SntpTimestamp_t * pRequestTime,
+ uint32_t randomNumber,
+ void * pBuffer,
+ size_t bufferSize )
+{
+ SntpStatus_t status = SntpSuccess;
+
+ if( pRequestTime == NULL )
+ {
+ status = SntpErrorBadParameter;
+ }
+ else if( pBuffer == NULL )
+ {
+ status = SntpErrorBadParameter;
+ }
+ else if( bufferSize < SNTP_PACKET_BASE_SIZE )
+ {
+ status = SntpErrorBufferTooSmall;
+ }
+
+ /* Zero timestamps for client request time is not allowed to protect against
+ * attack spoofing server response containing zero value for "originate timestamp".
+ * Note: In SNTP/NTP communication, the "originate timestamp" of a valid server response
+ * matches the "transmit timestamp" in corresponding client request packet. */
+ else if( isZeroTimestamp( pRequestTime ) == true )
+ {
+ status = SntpErrorBadParameter;
+ }
+ else
+ {
+ /* MISRA Ref 11.5.1 [Void pointer assignment] */
+ /* More details at: https://github.com/FreeRTOS/coreSNTP/blob/main/MISRA.md#rule-115 */
+ /* coverity[misra_c_2012_rule_11_5_violation] */
+ SntpPacket_t * pRequestPacket = ( SntpPacket_t * ) pBuffer;
+
+ /* Fill the buffer with zero as most fields are zero for a standard SNTP
+ * request packet.*/
+ ( void ) memset( pBuffer, 0, sizeof( SntpPacket_t ) );
+
+ /* Set the first byte of the request packet for "Version" and "Mode" fields */
+ pRequestPacket->leapVersionMode = 0U /* Leap Indicator */ |
+ ( SNTP_VERSION << SNTP_VERSION_LSB_POSITION ) /* Version Number */ |
+ SNTP_MODE_CLIENT /* Mode */;
+
+
+ /* Add passed random number to non-significant bits of the fractions part
+ * of the transmit timestamp.
+ * This is suggested by the SNTPv4 (and NTPv4) specification(s)
+ * to protect against replay attacks. Refer to RFC 4330 Section 3 for
+ * more information.
+ * Adding random bits to the least significant 16 bits of the fractions
+ * part of the timestamp affects only ~15 microseconds of information
+ * (calculated as 0xFFFF * 232 picoseconds).
+ */
+ pRequestTime->fractions = ( pRequestTime->fractions
+ | ( randomNumber >> 16 ) );
+
+ /* Update the request buffer with request timestamp in network byte order. */
+ fillWordMemoryInNetworkOrder( &pRequestPacket->transmitTime.seconds,
+ pRequestTime->seconds );
+ fillWordMemoryInNetworkOrder( &pRequestPacket->transmitTime.fractions,
+ pRequestTime->fractions );
+ }
+
+ return status;
+}
+
+
+SntpStatus_t Sntp_DeserializeResponse( const SntpTimestamp_t * pRequestTime,
+ const SntpTimestamp_t * pResponseRxTime,
+ const void * pResponseBuffer,
+ size_t bufferSize,
+ SntpResponseData_t * pParsedResponse )
+{
+ SntpStatus_t status = SntpSuccess;
+ /* MISRA Ref 11.5.1 [Void pointer assignment] */
+ /* More details at: https://github.com/FreeRTOS/coreSNTP/blob/main/MISRA.md#rule-115 */
+ /* coverity[misra_c_2012_rule_11_5_violation] */
+ const SntpPacket_t * pResponsePacket = ( const SntpPacket_t * ) pResponseBuffer;
+
+ if( ( pRequestTime == NULL ) || ( pResponseRxTime == NULL ) ||
+ ( pResponseBuffer == NULL ) || ( pParsedResponse == NULL ) )
+ {
+ status = SntpErrorBadParameter;
+ }
+ else if( bufferSize < SNTP_PACKET_BASE_SIZE )
+ {
+ status = SntpErrorBufferTooSmall;
+ }
+
+ /* Zero timestamps for client request time is not allowed to protect against
+ * attack spoofing server response containing zero value for "originate timestamp".
+ * Note: In SNTP/NTP communication, the "originate timestamp" of a valid server response
+ * matches the "transmit timestamp" in corresponding client request packet. */
+ else if( isZeroTimestamp( pRequestTime ) == true )
+ {
+ status = SntpErrorBadParameter;
+ }
+ /* Check if the packet represents a server in the "Mode" field. */
+ else if( ( pResponsePacket->leapVersionMode & SNTP_MODE_BITS_MASK ) != SNTP_MODE_SERVER )
+ {
+ status = SntpInvalidResponse;
+ }
+
+ /* Check if any of the timestamps in the response packet are zero, which is invalid.
+ * Note: This is done to protect against a nuanced server spoofing attack where if the
+ * SNTP client resets its internal state of "Client transmit timestamp" (OR "originate
+ * timestamp" from server perspective) to zero as a protection against replay attack, an
+ * an attacker with this knowledge of the client operation can spoof a server response
+ * containing the "originate timestamp" as zero. Thus, to protect against such attack,
+ * a server response packet with any zero timestamp is rejected. */
+ else if( ( isZeroTimestamp( &pResponsePacket->originTime ) == true ) ||
+ ( isZeroTimestamp( &pResponsePacket->receiveTime ) == true ) ||
+ ( isZeroTimestamp( &pResponsePacket->transmitTime ) == true ) )
+ {
+ status = SntpInvalidResponse;
+ }
+
+
+ /* Validate that the server has sent the client's request timestamp in the
+ * "originate" timestamp field of the response. */
+ else if( ( pRequestTime->seconds !=
+ readWordFromNetworkByteOrderMemory( &pResponsePacket->originTime.seconds ) ) ||
+ ( pRequestTime->fractions !=
+ readWordFromNetworkByteOrderMemory( &pResponsePacket->originTime.fractions ) ) )
+
+ {
+ status = SntpInvalidResponse;
+ }
+ else
+ {
+ /* As the response packet is valid, parse more information from it and
+ * populate the output parameter. */
+
+ status = parseValidSntpResponse( pResponsePacket,
+ pRequestTime,
+ pResponseRxTime,
+ pParsedResponse );
+ }
+
+ return status;
+}
+
+SntpStatus_t Sntp_CalculatePollInterval( uint16_t clockFreqTolerance,
+ uint16_t desiredAccuracy,
+ uint32_t * pPollInterval )
+{
+ SntpStatus_t status = SntpSuccess;
+
+ if( ( clockFreqTolerance == 0U ) || ( desiredAccuracy == 0U ) || ( pPollInterval == NULL ) )
+ {
+ status = SntpErrorBadParameter;
+ }
+ else
+ {
+ uint32_t exactIntervalForAccuracy = 0U;
+ uint8_t log2PollInterval = 0U;
+
+ /* Calculate the poll interval required for achieving the exact desired clock accuracy
+ * with the following formulae:
+ *
+ * System Clock Drift Rate ( microseconds / second ) = Clock Frequency Tolerance (in PPM )
+ * Maximum Clock Drift ( milliseconds ) = Desired Accuracy ( milliseconds )
+ *
+ * Poll Interval ( seconds ) = Maximum Clock Drift
+ * ---------------------------
+ * System Clock Drift Rate
+ *
+ * = Maximum Drift ( milliseconds ) * 1000 ( microseconds / millisecond )
+ * ------------------------------------------------------------------------
+ * System Clock Drift Rate ( microseconds / second )
+ *
+ * = Desired Accuracy * 1000
+ * ------------------------------
+ * Clock Frequency Tolerance
+ */
+ exactIntervalForAccuracy = ( ( uint32_t ) desiredAccuracy * 1000U ) / clockFreqTolerance;
+
+ /* Check if calculated poll interval value falls in the supported range of seconds. */
+ if( exactIntervalForAccuracy == 0U )
+ {
+ /* Poll interval value is less than 1 second, and is not supported by the function. */
+ status = SntpZeroPollInterval;
+ }
+ else
+ {
+ /* To calculate the log 2 value of the exact poll interval value, first determine the highest
+ * bit set in the value. */
+ while( exactIntervalForAccuracy != 0U )
+ {
+ log2PollInterval++;
+ exactIntervalForAccuracy /= 2U;
+ }
+
+ /* Convert the highest bit in the exact poll interval value to the nearest integer
+ * value lower or equal to the log2 of the exact poll interval value. */
+ log2PollInterval--;
+
+ /* Calculate the poll interval as the closest exponent of 2 value that achieves
+ * equal or higher accuracy than the desired accuracy. */
+ *pPollInterval = ( ( ( uint32_t ) 1U ) << log2PollInterval );
+ }
+ }
+
+ return status;
+}
+
+SntpStatus_t Sntp_ConvertToUnixTime( const SntpTimestamp_t * pSntpTime,
+ uint32_t * pUnixTimeSecs,
+ uint32_t * pUnixTimeMicrosecs )
+{
+ SntpStatus_t status = SntpSuccess;
+
+ if( ( pSntpTime == NULL ) || ( pUnixTimeSecs == NULL ) || ( pUnixTimeMicrosecs == NULL ) )
+ {
+ status = SntpErrorBadParameter;
+ }
+ /* Check if passed time does not lie in the [UNIX epoch in 1970, UNIX time overflow in 2038] time range. */
+ else if( ( pSntpTime->seconds > SNTP_TIME_AT_LARGEST_UNIX_TIME_SECS ) &&
+ ( pSntpTime->seconds < SNTP_TIME_AT_UNIX_EPOCH_SECS ) )
+ {
+ /* The SNTP timestamp is outside the supported time range for conversion. */
+ status = SntpErrorTimeNotSupported;
+ }
+ else
+ {
+ /* Handle case when timestamp represents date in SNTP era 1
+ * (i.e. time from 7 Feb 2036 6:28:16 UTC onwards). */
+ if( pSntpTime->seconds <= SNTP_TIME_AT_LARGEST_UNIX_TIME_SECS )
+ {
+ /* Unix Time ( seconds ) = Seconds Duration in
+ * [UNIX epoch, SNTP Era 1 Epoch Time]
+ * +
+ * Sntp Time since Era 1 Epoch
+ */
+ *pUnixTimeSecs = UNIX_TIME_SECS_AT_SNTP_ERA_1_SMALLEST_TIME + pSntpTime->seconds;
+ }
+ /* Handle case when SNTP timestamp is in SNTP era 1 time range. */
+ else
+ {
+ *pUnixTimeSecs = pSntpTime->seconds - SNTP_TIME_AT_UNIX_EPOCH_SECS;
+ }
+
+ /* Convert SNTP fractions to microseconds for UNIX time. */
+ *pUnixTimeMicrosecs = pSntpTime->fractions / SNTP_FRACTION_VALUE_PER_MICROSECOND;
+ }
+
+ return status;
+}
diff --git a/project/coreMQTT/coreSNTP/core_sntp_serializer.h b/project/coreMQTT/coreSNTP/core_sntp_serializer.h
new file mode 100644
index 0000000..55215f1
--- /dev/null
+++ b/project/coreMQTT/coreSNTP/core_sntp_serializer.h
@@ -0,0 +1,535 @@
+/*
+ * coreSNTP v1.2.0
+ * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @file core_sntp_serializer.h
+ * @brief API for serializing SNTP request packets and, and de-serializing SNTP
+ * response packets.
+ * This API layer adheres to the SNTPv4 specification defined in
+ * [RFC 4330](https://tools.ietf.org/html/rfc4330).
+ */
+
+#ifndef CORE_SNTP_SERIALIZER_H_
+#define CORE_SNTP_SERIALIZER_H_
+
+/* Standard include. */
+#include <stdint.h>
+#include <stddef.h>
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ extern "C" {
+#endif
+/* *INDENT-ON* */
+
+/**
+ * @ingroup sntp_constants
+ * @brief The base packet size of request and response of the (S)NTP protocol.
+ * @note This is the packet size without any authentication headers for security
+ * mechanism. If the application uses a security mechanism for communicating with
+ * an (S)NTP server, it can add authentication data after the SNTP packet is
+ * serialized with the @ref Sntp_SerializeRequest API function.
+ */
+#define SNTP_PACKET_BASE_SIZE ( 48U )
+
+/**
+ * @ingroup sntp_constants
+ * @brief Number of SNTP timestamp fractions in 1 microsecond.
+ *
+ * The fraction's part of an SNTP timestamp is 32-bits wide, thereby, giving a
+ * resolution of 2^(-32) seconds ~ 232 picoseconds.
+ *
+ * @note The application can use this value to convert microseconds part of system
+ * time into SNTP timestamp fractions. For example, if the microseconds
+ * part of system time is n microseconds, the fractions value to be used for the
+ * SNTP timestamp part will be n * SNTP_FRACTION_VALUE_PER_MICROSECOND.
+ */
+#define SNTP_FRACTION_VALUE_PER_MICROSECOND ( 4295U )
+
+/**
+ * @ingroup sntp_constants
+ * @brief The seconds part of SNTP time at the UNIX epoch time, which represents
+ * an offset of 70 years (in seconds) between SNTP epoch and UNIX epoch time.
+ * SNTP uses 1st Jan 1900 UTC as the epoch time, whereas UNIX standard uses
+ * 1st Jan 1970 UTC as the epoch time, thereby, causing an offset of 70 years
+ * between them.
+ *
+ * Difference of 70 years = ((70 * 365) + 17 leap days) * 24 * 3600 seconds
+ *
+ * @note If your system follows UNIX time, the application can use this value to
+ * convert seconds part of a system time to seconds part of the equivalent SNTP
+ * time. For example, if the seconds part of system time is n seconds, the seconds
+ * value to be used for the SNTP timestamp will be n + SNTP_TO_UNIX_OFFSET_SECS.
+ */
+#define SNTP_TIME_AT_UNIX_EPOCH_SECS ( 2208988800U )
+
+/**
+ * @ingroup sntp_constants
+ * @brief The seconds value of SNTP timestamp for the largest UNIX time when
+ * using signed 32-bit integer for seconds.
+ * The largest time representable with a 32-bit signed integer in UNIX time
+ * is 19 Jan 2038 3h 14m 7s UTC. However, as the SNTP time overflows at
+ * 7 Feb 2036 6h 28m 16s UTC, therefore, the SNTP time for the largest UNIX time
+ * represents the time duration between the 2 timestamps.
+ *
+ * SNTP Time at Largest Time Duration in the range
+ * Signed 32-bit UNIX time = [7 Feb 2036 6:28:16, 19 Jan 2038 3:14:07]
+ */
+#define SNTP_TIME_AT_LARGEST_UNIX_TIME_SECS ( 61505151U )
+
+/**
+ * @ingroup sntp_constants
+ * @brief The UNIX time (in seconds) at the smallest SNTP time in era 1,
+ * i.e. UNIX time at 7 Feb 2036 6:28:16 UTC/
+ *
+ * Time Duration = 7 Feb 6:28:16 UTC (SNTP Era 1 Epoch) -
+ * 1 Jan 1970 0:0:0 UTC (UNIX epoch)
+ * = 66 years, 37 days, 6 hours, 28 minutes and 16 seconds
+ * = ((66 * 365) + 16 leap days) * 24 * 3600) + (6 * 3600)
+ * + (28 * 60) + 16
+ */
+#define UNIX_TIME_SECS_AT_SNTP_ERA_1_SMALLEST_TIME ( 2085978496U )
+
+/**
+ * @ingroup sntp_constants
+ * @brief The fixed-length of any Kiss-o'-Death message ASCII code sent
+ * in an SNTP server response.
+ * @note An SNTP server sends a Kiss-o'-Death message to reject a time request
+ * from the client. For more information on the Kiss-o'-Death codes, refer to the
+ * [SNTPv4 specification Section 8](https://tools.ietf.org/html/rfc4330#section-8).
+ */
+#define SNTP_KISS_OF_DEATH_CODE_LENGTH ( 4U )
+
+/**
+ * @ingroup sntp_constants
+ * @brief The value for the #SntpResponseData_t.rejectedResponseCode member
+ * when that the server response packet does not contain a Kiss-o'-Death
+ * message, and therefore, does not have a "kiss code".
+ * The server sends a "kiss-code" only when it rejects an SNTP request
+ * with a Kiss-o'-Death message.
+ */
+#define SNTP_KISS_OF_DEATH_CODE_NONE ( 0U )
+
+/**
+ * @ingroup sntp_enum_types
+ * @brief Enumeration of status codes that can be returned
+ * by the coreSNTP Library API.
+ */
+typedef enum SntpStatus
+{
+ /**
+ * @brief Successful operation of an SNTP API.
+ */
+ SntpSuccess,
+
+ /**
+ * @brief Invalid parameter passed to an API function.
+ */
+ SntpErrorBadParameter,
+
+ /**
+ * @brief Server sent a Kiss-o'-Death message to reject the request for time.
+ * This status can be returned by the @ref Sntp_ReceiveTimeResponse API.
+ */
+ SntpRejectedResponse,
+
+ /**
+ * @brief Server sent a Kiss-o'-Death message with non-retryable code (i.e. DENY or RSTR).
+ */
+ SntpRejectedResponseChangeServer,
+
+ /**
+ * @brief Server sent a Kiss-o'-Death message with a RATE code, which means that
+ * client should back-off before retrying.
+ */
+ SntpRejectedResponseRetryWithBackoff,
+
+ /**
+ * @brief Server sent a Kiss-o'-Death message with a code, specific to the server.
+ * Application can inspect the ASCII kiss-code from @ref Sntp_DeserializeResponse API.
+ */
+ SntpRejectedResponseOtherCode,
+
+ /**
+ * @brief Application provided insufficient buffer space for serializing
+ * or de-serializing an SNTP packet.
+ * The minimum size of an SNTP packet is #SNTP_PACKET_BASE_SIZE
+ * bytes. */
+ SntpErrorBufferTooSmall,
+
+ /**
+ * @brief Server response failed validation checks for expected data in SNTP packet.
+ */
+ SntpInvalidResponse,
+
+ /**
+ * @brief Poll interval value is under 1 second which cannot be calculated
+ * by @ref Sntp_CalculatePollInterval.
+ */
+ SntpZeroPollInterval,
+
+ /**
+ * @brief SNTP timestamp cannot be converted to UNIX time as time does not lie
+ * in time range supported by Sntp_ConvertToUnixTime.
+ */
+ SntpErrorTimeNotSupported,
+
+ /**
+ * @brief The user-defined DNS resolution interface, @ref SntpResolveDns_t, failed to resolve
+ * address for a time server. This status is returned by the @ref Sntp_SendTimeRequest API.
+ */
+ SntpErrorDnsFailure,
+
+ /**
+ * @brief Networking operation of sending or receiving SNTP packet through the user-defined UDP
+ * transport interface, @ref UdpTransportInterface_t, failed.
+ * This status is returned by either of @ref Sntp_SendTimeRequest OR @ref Sntp_ReceiveTimeResponse
+ * APIs.
+ */
+ SntpErrorNetworkFailure,
+
+ /**
+ * @brief Time server is not authenticated from the authentication data in its response.
+ * This status can be returned by the user-supplied definition of the
+ * @ref SntpValidateServerAuth_t authentication interface.
+ */
+ SntpServerNotAuthenticated,
+
+ /**
+ * @brief Failure from the user-supplied authentication interface, @ref SntpAuthenticationInterface_t,
+ * in either generating authentication data for SNTP request OR validating the authentication
+ * data in SNTP response from server.
+ */
+ SntpErrorAuthFailure,
+
+ /**
+ * @brief A timeout occurred in sending time request packet over the network to a server through the
+ * @ref Sntp_SendTimeRequest API.
+ */
+ SntpErrorSendTimeout,
+
+ /**
+ * @brief A timeout has occurred in receiving server response with the @ref Sntp_ReceiveTimeResponse
+ * API.
+ */
+ SntpErrorResponseTimeout,
+
+ /**
+ * @brief No SNTP packet for server response is received from the network by the
+ * @ref Sntp_ReceiveTimeResponse API.
+ */
+ SntpNoResponseReceived,
+
+ /**
+ * @brief The SNTP context passed to @ref Sntp_SendTimeRequest or @ref Sntp_ReceiveTimeResponse APIs is
+ * is uninitialized.
+ */
+ SntpErrorContextNotInitialized
+} SntpStatus_t;
+
+/**
+ * @ingroup sntp_enum_types
+ * @brief Enumeration for leap second information that an SNTP server can
+ * send its response to a time request. An SNTP server sends information about
+ * whether there is an upcoming leap second adjustment in the last day of the
+ * current month.
+ *
+ * @note A leap second is an adjustment made in atomic clock time because Earth's rotation
+ * can be inconsistent. Leap seconds are usually incorporated as an extra second insertion
+ * or second deletion in the last minute before midnight i.e. in the minute of 23h:59m UTC
+ * on the last day of June or December. For more information on leap seconds, refer to
+ * https://www.nist.gov/pml/time-and-frequency-division/leap-seconds-faqs.
+ */
+typedef enum SntpLeapSecondInfo
+{
+ NoLeapSecond = 0x00, /** <@brief There is no upcoming leap second adjustment. */
+ LastMinuteHas61Seconds = 0x01, /** <@brief A leap second should be inserted in the last minute before midnight. */
+ LastMinuteHas59Seconds = 0x02, /** <@brief A leap second should be deleted from the last minute before midnight. */
+ AlarmServerNotSynchronized = 0x03 /** <@brief An alarm condition meaning that server's time is not synchronized
+ * to an upstream NTP (or SNTP) server. */
+} SntpLeapSecondInfo_t;
+
+/**
+ * @ingroup sntp_struct_types
+ * @brief Structure representing an SNTP timestamp.
+ *
+ * @note The SNTP timestamp uses 1st January 1900 0h 0m 0s Coordinated Universal Time (UTC)
+ * as the primary epoch i.e. the timestamp represents current time as the amount of time since
+ * the epoch time.
+ * Refer to the [SNTPv4 specification](https://tools.ietf.org/html/rfc4330#section-3) for more
+ * information of the SNTP timestamp format.
+ */
+typedef struct SntpTimestamp
+{
+ uint32_t seconds; /**< @brief Number of seconds since epoch time. */
+ uint32_t fractions; /**< @brief The fractions part of the SNTP timestamp with resolution
+ * of 2^(-32) ~ 232 picoseconds. */
+} SntpTimestamp_t;
+
+/**
+ * @ingroup sntp_struct_types
+ * @brief Structure representing data parsed from an SNTP response from server
+ * as well as data of arithmetic calculations derived from the response.
+ */
+typedef struct SntpResponse
+{
+ /**
+ * @brief The timestamp sent by the server.
+ */
+ SntpTimestamp_t serverTime;
+
+ /**
+ * @brief The information of an upcoming leap second in the
+ * server response.
+ */
+ SntpLeapSecondInfo_t leapSecondType;
+
+ /**
+ * @brief If a server responded with Kiss-o'-Death message to reject
+ * time request, this is the fixed length ASCII code sequence for the
+ * rejection.
+ *
+ * The Kiss-o'-Death code is always #SNTP_KISS_OF_DEATH_CODE_LENGTH
+ * bytes long.
+ *
+ * @note If the server does not send a Kiss-o'-Death message in its
+ * response, this value will be #SNTP_KISS_OF_DEATH_CODE_NONE.
+ */
+ uint32_t rejectedResponseCode;
+
+ /**
+ * @brief The offset (in milliseconds) of the system clock relative to the server time
+ * calculated from timestamps in the client SNTP request and server SNTP response packets.
+ * If the the system time is BEHIND the server time, then the clock-offset value is > 0.
+ * If the system time is AHEAD of the server time, then the clock-offset value is < 0.
+ *
+ * @note This information can be used to synchronize the system clock with a "slew",
+ * "step" OR combination of the two clock correction methodologies depending on the degree
+ * of system clock drift (represented by the clock-offset) and the application's
+ * tolerance for system clock error.
+ *
+ * @note The library calculates the clock-offset value using the On-Wire
+ * protocol suggested by the NTPv4 specification. For more information,
+ * refer to https://tools.ietf.org/html/rfc5905#section-8.
+ *
+ * @note The library ASSUMES that the server and client systems are within
+ * ~68 years of each other clock, whether in the same NTP era or across adjacent
+ * NTP eras. Thus, the client and system times MUST be within ~68 years (or
+ * 2^31 seconds exactly) of each other for correct calculation of clock-offset.
+ *
+ * @note When the server and client times are exactly 2^31 (or INT32_MAX + 1 )
+ * seconds apart, the library ASSUMES that the server time is ahead of the client
+ * time, and return the clock-offset value of INT32_MAX.
+ */
+ int64_t clockOffsetMs;
+} SntpResponseData_t;
+
+
+/**
+ * @brief Serializes an SNTP request packet to use for querying a
+ * time server.
+ *
+ * This function will fill only #SNTP_PACKET_BASE_SIZE bytes of data in the
+ * passed buffer.
+ *
+ * @param[in, out] pRequestTime The current time of the system, expressed as time
+ * since the SNTP epoch (i.e. 0h of 1st Jan 1900 ). This time will be serialized
+ * in the SNTP request packet. The function will use this parameter to return the
+ * timestamp serialized in the SNTP request. To protect against attacks spoofing
+ * server responses, the timestamp MUST NOT be zero in value.
+ * @param[in] randomNumber A random number (generated by a True Random Generator)
+ * for use in the SNTP request packet to protect against replay attacks as suggested
+ * by SNTPv4 specification. For more information, refer to
+ * [RFC 4330 Section 3](https://tools.ietf.org/html/rfc4330#section-3).
+ * @param[out] pBuffer The buffer that will be populated with the serialized
+ * SNTP request packet.
+ * @param[in] bufferSize The size of the @p pBuffer buffer. It should be at least
+ * #SNTP_PACKET_BASE_SIZE bytes in size.
+ *
+ * @note It is recommended to use a True Random Generator (TRNG) to generate
+ * the random number.
+ * @note The application MUST save the @p pRequestTime value for de-serializing
+ * the server response with @ref Sntp_DeserializeResponse API.
+ *
+ * @return This function returns one of the following:
+ * - #SntpSuccess when serialization operation is successful.
+ * - #SntpErrorBadParameter if an invalid parameter is passed.
+ * - #SntpErrorBufferTooSmall if the buffer does not have the minimum size
+ * for serializing an SNTP request packet.
+ */
+/* @[define_sntp_serializerequest] */
+SntpStatus_t Sntp_SerializeRequest( SntpTimestamp_t * pRequestTime,
+ uint32_t randomNumber,
+ void * pBuffer,
+ size_t bufferSize );
+/* @[define_sntp_serializerequest] */
+
+/**
+ * @brief De-serializes an SNTP packet received from a server as a response
+ * to a SNTP request.
+ *
+ * This function will parse only the #SNTP_PACKET_BASE_SIZE bytes of data
+ * in the passed buffer.
+ *
+ * @note If the server has sent a Kiss-o'-Death message to reject the associated
+ * time request, the API function will return the appropriate return code and,
+ * also, provide the ASCII code (of fixed length, #SNTP_KISS_OF_DEATH_CODE_LENGTH bytes)
+ * in the #SntpResponseData_t.rejectedResponseCode member of @p pParsedResponse parameter,
+ * parsed from the response packet.
+ * The application SHOULD respect the server rejection and take appropriate action
+ * based on the rejection code.
+ * If the server response represents an accepted SNTP client request, then the API
+ * function will set the #SntpResponseData_t.rejectedResponseCode member of
+ * @p pParsedResponse parameter to #SNTP_KISS_OF_DEATH_CODE_NONE.
+ *
+ * @note If the server has positively responded with its clock time, then this API
+ * function will calculate the clock-offset. For the clock-offset to be correctly
+ * calculated, the system clock MUST be within ~68 years (or 2^31 seconds) of the server
+ * time mentioned. This function supports clock-offset calculation when server and client
+ * timestamps are in adjacent NTP eras, with one system is in NTP era 0 (i.e. before 7 Feb 2036
+ * 6h:28m:14s UTC) and another system in NTP era 1 (on or after 7 Feb 2036 6h:28m:14s UTC).
+ *
+ * @note In the special case when the server and client times are exactly 2^31 seconds apart,
+ * the library ASSUMES that the server time is ahead of the client time, and returns the
+ * positive clock-offset value of INT32_MAX seconds.
+ *
+ * @param[in] pRequestTime The system time used in the SNTP request packet
+ * that is associated with the server response. This MUST be the same as the
+ * time returned by the @ref Sntp_SerializeRequest API. To protect against attacks
+ * spoofing server responses, this timestamp MUST NOT be zero in value.
+ * @param[in] pResponseRxTime The time of the system, expressed as time since the
+ * SNTP epoch (i.e. 0h of 1st Jan 1900 ), at receiving SNTP response from server.
+ * This time will be used to calculate system clock offset relative to server.
+ * @param[in] pResponseBuffer The buffer containing the SNTP response from the
+ * server.
+ * @param[in] bufferSize The size of the @p pResponseBuffer containing the SNTP
+ * response. It MUST be at least #SNTP_PACKET_BASE_SIZE bytes
+ * long for a valid SNTP response.
+ * @param[out] pParsedResponse The information parsed from the SNTP response packet.
+ * If possible to calculate without overflow, it also contains the system clock
+ * offset relative to the server time.
+ *
+ * @return This function returns one of the following:
+ * - #SntpSuccess if the de-serialization operation is successful.
+ * - #SntpErrorBadParameter if an invalid parameter is passed.
+ * - #SntpErrorBufferTooSmall if the buffer does not have the minimum size
+ * required for a valid SNTP response packet.
+ * - #SntpInvalidResponse if the response fails sanity checks expected in an
+ * SNTP response.
+ * - #SntpRejectedResponseChangeServer if the server rejected with a code
+ * indicating that client cannot be retry requests to it.
+ * - #SntpRejectedResponseRetryWithBackoff if the server rejected with a code
+ * indicating that client should back-off before retrying request.
+ * - #SntpRejectedResponseOtherCode if the server rejected with a code that
+ * application can inspect in the @p pParsedResponse parameter.
+ */
+/* @[define_sntp_deserializeresponse] */
+SntpStatus_t Sntp_DeserializeResponse( const SntpTimestamp_t * pRequestTime,
+ const SntpTimestamp_t * pResponseRxTime,
+ const void * pResponseBuffer,
+ size_t bufferSize,
+ SntpResponseData_t * pParsedResponse );
+/* @[define_sntp_deserializeresponse] */
+
+/**
+ * @brief Utility to calculate the poll interval of sending periodic time queries
+ * to servers to achieve a desired system clock accuracy for a given
+ * frequency tolerance of the system clock.
+ *
+ * For example, from the SNTPv4 specification, "if the frequency tolerance
+ * is 200 parts per million (PPM) and the required accuracy is one minute,
+ * the maximum timeout is about 3.5 days". In this example, the system
+ * clock frequency tolerance is 200 PPM and the desired accuracy is
+ * 60000 milliseconds (or 1 minute) for which this API function
+ * will return the maximum poll interval value as 2^18 seconds (or ~3 days).
+ *
+ * @note The poll interval returned is a power of 2, which is the
+ * standard way to represent the value. According to the SNTPv4 specification
+ * Best Practices, an SNTP client SHOULD NOT have a poll interval less than 15 seconds.
+ * https://tools.ietf.org/html/rfc4330#section-10. This API function DOES NOT
+ * support poll interval calculation less than 1 second.
+ *
+ * @param[in] clockFreqTolerance The frequency tolerance of system clock
+ * in parts per million (PPM) units. This parameter MUST be non-zero.
+ * @param[in] desiredAccuracy The acceptable maximum drift, in milliseconds,
+ * for the system clock. The maximum value (0xFFFF) represents ~1 minute of
+ * desired clock accuracy. This parameter MUST be non-zero.
+ * @param[out] pPollInterval This is filled with the poll interval, in seconds
+ * calculated as the closest power of 2 value that will achieve either the
+ * exact desired or higher clock accuracy @p desiredAccuracy, for the given clock
+ * frequency tolerance, @p clockFreqTolerance.
+ *
+ * @return Returns one of the following:
+ * - #SntpSuccess if calculation is successful.
+ * - #SntpErrorBadParameter for an invalid parameter passed to the function.
+ * - #SntpZeroPollInterval if calculated poll interval is less than 1 second.
+ */
+/* @[define_sntp_calculatepollinterval] */
+SntpStatus_t Sntp_CalculatePollInterval( uint16_t clockFreqTolerance,
+ uint16_t desiredAccuracy,
+ uint32_t * pPollInterval );
+/* @[define_sntp_calculatepollinterval] */
+
+
+/**
+ * @brief Utility to convert SNTP timestamp (that uses 1st Jan 1900 as the epoch) to
+ * UNIX timestamp (that uses 1st Jan 1970 as the epoch).
+ *
+ * @note This function can ONLY handle conversions of SNTP timestamps that lie in the
+ * range from 1st Jan 1970 0h 0m 0s, the UNIX epoch time, to 19th Jan 2038 3h 14m 7s,
+ * the maximum UNIX time that can be represented in a signed 32 bit integer. (The
+ * limitation is to support systems that use signed 32-bit integer to represent the
+ * seconds part of the UNIX time.)
+ *
+ * @note This function supports overflow of the SNTP timestamp (from the 7 Feb 2036
+ * 6h 28m 16s time, i.e. SNTP era 1) by treating the timestamps with seconds part
+ * in the range [0, 61,505,152] seconds where the upper limit represents the UNIX
+ * overflow time (i.e. 19 Jan 2038 3h 14m 7s) for systems that use signed 32-bit
+ * integer to represent time.
+ *
+ * @param[in] pSntpTime The SNTP timestamp to convert to UNIX time.
+ * @param[out] pUnixTimeSecs This will be filled with the seconds part of the
+ * UNIX time equivalent of the SNTP time, @p pSntpTime.
+ * @param[out] pUnixTimeMicrosecs This will be filled with the microseconds part
+ * of the UNIX time equivalent of the SNTP time, @p pSntpTime.
+ *
+ * @return Returns one of the following:
+ * - #SntpSuccess if conversion to UNIX time is successful
+ * - #SntpErrorBadParameter if any of the passed parameters are NULL.
+ * - #SntpErrorTimeNotSupported if the passed SNTP time does not lie in the
+ * supported time range.
+ */
+/* @[define_sntp_converttounixtime] */
+SntpStatus_t Sntp_ConvertToUnixTime( const SntpTimestamp_t * pSntpTime,
+ uint32_t * pUnixTimeSecs,
+ uint32_t * pUnixTimeMicrosecs );
+/* @[define_sntp_converttounixtime] */
+
+/* *INDENT-OFF* */
+#ifdef __cplusplus
+ }
+#endif
+/* *INDENT-ON* */
+
+#endif /* ifndef CORE_SNTP_SERIALIZER_H_ */
diff --git a/project/coreMQTT/coreSNTP/makefile b/project/coreMQTT/coreSNTP/makefile
new file mode 100644
index 0000000..b6ece0e
--- /dev/null
+++ b/project/coreMQTT/coreSNTP/makefile
@@ -0,0 +1,35 @@
+#********************************************************************************
+# Copyright: (C) 2023 LingYun IoT System Studio
+# All rights reserved.
+#
+# Filename: Makefile
+# Description: This file used compile all the source code to static library
+#
+# Version: 1.0.0(11/08/23)
+# Author: Guo Wenxue <guowenxue@gmail.com>
+# ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+#
+#*******************************************************************************
+
+PWD=$(shell pwd )
+
+BUILD_ARCH=$(shell uname -m)
+ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
+ CROSS_COMPILE?=arm-linux-gnueabihf-
+endif
+
+LIBNAME=$(shell basename ${PWD} )
+TOPDIR=$(shell dirname ${PWD} )
+CFLAGS+=-D_GNU_SOURCE
+
+all: clean
+ @rm -f *.o
+ @${CROSS_COMPILE}gcc ${CFLAGS} -I${TOPDIR} -c *.c
+ ${CROSS_COMPILE}ar -rcs lib${LIBNAME}.a *.o
+
+clean:
+ @rm -f *.o
+ @rm -f *.a
+
+distclean:
+ @make clean
diff --git a/project/coreMQTT/etc/mqttd.conf b/project/coreMQTT/etc/mqttd.conf
new file mode 100644
index 0000000..50fcffa
--- /dev/null
+++ b/project/coreMQTT/etc/mqttd.conf
@@ -0,0 +1,69 @@
+[common]
+devid="RPi3B#01"
+
+# 树莓派连接的外设信息,0:禁用或未连接 其他: 使能或相关硬件连接的Pin管脚(wPI模式)
+[hardware]
+
+# 是否使能继电器模块,0:禁用 1:使能
+relay=1
+
+# 是否使能 PWM 蜂鸣器模块,0:禁用 N:Beep 次数
+beep=3
+
+# 是否使能 RGB 三色灯模块,0:禁用 1:使能
+rgbled=1
+
+# 是否使能 DS18b20 温度传感器模块,0:禁用 1:使能
+ds18b20=1
+
+# 是否使能 SHT2X 温湿度传感器模块,0:禁用 1:使能
+sht2x=1
+
+# 是否使能 TSL2561 光强传感器模块,0:禁用 1:使能
+lux=1
+
+[logger]
+
+# 日志记录文件
+file=/tmp/mqttd.log
+
+# 日志级别: 0:ERROR 1:WARN 2:INFO 3:DEBUG 4:TRACE
+level=2
+
+# 日志回滚大小
+size=1024
+
+
+[broker]
+
+# broker 服务器地址和端口号
+hostname="main.iot-yun.club"
+port=10883
+
+# broker 认证连接的用户名和密码
+username="lingyun"
+password="lingyun"
+
+# broker给subsciber和publisher发送PING报文保持 keepalive 的时间周期,单位是秒
+keepalive=30
+
+# Qos0: 发送者只发送一次消息,不进行重试,Broker不会返回确认消息。在Qos0情况下,Broker可能没有接受到消息
+# Qos1: 发送者最少发送一次消息,确保消息到达Broker,Broker需要返回确认消息PUBACK。在Qos1情况下,Broker可能接受到重复消息
+# Qos2: Qos2使用两阶段确认来保证消息的不丢失和不重复。在Qos2情况下,Broker肯定会收到消息,且只收到一次
+
+[publisher]
+
+# mosquitto_sub -h main.iot-yun.club -p 10883 -u lingyun -P lingyun -t \$Sys/Studio/Uplink
+pubTopic="$Sys/Studio/Uplink"
+pubQos=0
+
+# Publisher上报传感器数据的周期,单位是秒
+interval=60
+
+[subsciber]
+
+# mosquitto_pub -h main.iot-yun.club -p 10883 -u lingyun -P lingyun -t \$Sys/Studio/Downlink -m '{"id":"RPi3B#01", "light":"on", "buzzer":"on"}'
+# mosquitto_pub -h main.iot-yun.club -p 10883 -u lingyun -P lingyun -t \$Sys/Studio/Downlink -m '{"id":"RPi3B#01", "leds":[{"red":"on","green":"off","blue":"on"}]}'
+subTopic="$Sys/Studio/Downlink"
+subQos=0
+
diff --git a/project/coreMQTT/main.c b/project/coreMQTT/main.c
new file mode 100644
index 0000000..31d8fd7
--- /dev/null
+++ b/project/coreMQTT/main.c
@@ -0,0 +1,458 @@
+/*********************************************************************************
+ * Copyright: (C) 2019 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: main.c
+ * Description: This file
+ *
+ * Version: 1.0.0(29/01/19)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "29/01/19 15:34:41"
+ *
+ ********************************************************************************/
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <string.h>
+#include <errno.h>
+
+#include <mosquitto.h>
+#include <cjson/cJSON.h>
+
+#include "logger.h"
+#include "util_proc.h"
+#include "modules.h"
+#include "conf.h"
+
+#define PROG_VERSION "v1.0.0"
+#define DAEMON_PIDFILE "/tmp/.mqtt.pid"
+
+void *mqtt_sub_worker(void *args);
+void *mqtt_pub_worker(void *args);
+
+static void program_usage(char *progname)
+{
+
+ printf("Usage: %s [OPTION]...\n", progname);
+ printf(" %s is LingYun studio MQTT daemon program running on RaspberryPi\n", progname);
+
+ printf("\nMandatory arguments to long options are mandatory for short options too:\n");
+ printf(" -d[debug ] Running in debug mode\n");
+ printf(" -c[conf ] Specify configure file\n");
+ printf(" -h[help ] Display this help information\n");
+ printf(" -v[version ] Display the program version\n");
+
+ printf("\n%s version %s\n", progname, PROG_VERSION);
+ return;
+}
+
+int main (int argc, char **argv)
+{
+ int daemon = 1;
+ pthread_t tid;
+ mqtt_ctx_t ctx;
+ char *conf_file=NULL;
+ int debug = 0;
+ int opt;
+ char *progname=NULL;
+
+ struct option long_options[] = {
+ {"conf", required_argument, NULL, 'c'},
+ {"debug", no_argument, NULL, 'd'},
+ {"version", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ progname = (char *)basename(argv[0]);
+
+ /* Parser the command line parameters */
+ while ((opt = getopt_long(argc, argv, "c:dvh", long_options, NULL)) != -1)
+ {
+ switch (opt)
+ {
+ case 'c': /* Set configure file */
+ conf_file = optarg;
+ break;
+
+ case 'd': /* Set debug running */
+ daemon = 0;
+ debug = 1;
+ break;
+
+ case 'v': /* Get software version */
+ printf("%s version %s\n", progname, PROG_VERSION);
+ return 0;
+
+ case 'h': /* Get help information */
+ program_usage(progname);
+ return 0;
+
+ default:
+ break;
+ }
+
+ }
+
+ if( !conf_file )
+ debug = 1;
+
+ if( mqttd_parser_conf(conf_file, &ctx, debug)<0 )
+ {
+ fprintf(stderr, "Parser mqtted configure file failure\n");
+ return -2;
+ }
+
+ install_default_signal();
+
+ if( check_set_program_running(daemon, DAEMON_PIDFILE) < 0 )
+ goto cleanup;
+
+ mosquitto_lib_init();
+
+ if( thread_start(&tid, mqtt_sub_worker, &ctx ) < 0 )
+ {
+ log_error("Start MQTT subsciber worker thread failure\n");
+ goto cleanup;
+ }
+ log_info("Start MQTT subsciber worker thread ok\n");
+
+ if( thread_start(&tid, mqtt_pub_worker, &ctx) < 0 )
+ {
+ log_error("Start MQTT publisher worker thread failure\n");
+ goto cleanup;
+ }
+ log_info("Start MQTT publisher worker thread ok\n");
+
+ while( ! g_signal.stop )
+ {
+ msleep(1000);
+ }
+
+cleanup:
+ mosquitto_lib_cleanup();
+ log_close();
+
+ return 0;
+} /* ----- End of main() ----- */
+
+void pub_connect_callback(struct mosquitto *mosq, void *userdata, int result)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)userdata;
+ int rv = 0;
+ char msg[128];
+ float temp = 0.0; /* temperature */
+ float rh = 0.0; /* relative humidity */
+ int retain = 0;
+
+ if( result )
+ {
+ log_error("Publisher connect to broker server[%s:%d] failed, rv=%d\n", ctx->host, ctx->port, result);
+ return ;
+ }
+
+ log_info("Publisher connect to broker server[%s:%d] successfully\n", ctx->host, ctx->port);
+ log_debug("Publish topic '%s'\n", ctx->pubTopic);
+
+ if( ctx->hwconf.ds18b20 )
+ {
+ memset(msg, 0, sizeof(msg));
+
+ log_debug("DS18B20 temperature sensor enabled, start broadcast it\n");
+
+ if( ds18b20_get_temperature(&temp) < 0 )
+ snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"error\" }", ctx->devid);
+ else
+ snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"%.2f\" }", ctx->devid, temp);
+
+ rv = mosquitto_publish(mosq, NULL, ctx->pubTopic, strlen(msg), msg, ctx->pubQos, retain);
+ if( rv )
+ {
+ log_error("Publisher broadcast message '%s' failure: %d\n", msg, rv);
+ }
+ else
+ {
+ log_info("Publisher broadcast message '%s' ok\n", msg);
+ }
+ }
+
+ if( ctx->hwconf.sht2x )
+ {
+ memset(msg, 0, sizeof(msg));
+
+ log_debug("SHT2X temperature and humidity sensor enabled, start broadcast it\n");
+
+ if( sht2x_get_temp_humidity(&temp, &rh) < 0 )
+ snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"error\", \"RH\":\"error\" }", ctx->devid);
+ else
+ snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"%.2f\", \"RH\":\"%.2f\" }", ctx->devid, temp, rh);
+
+ rv = mosquitto_publish(mosq, NULL, ctx->pubTopic, strlen(msg), msg, ctx->pubQos, retain);
+ if( rv )
+ {
+ log_error("Publisher broadcast message '%s' failure: %d\n", msg, rv);
+ }
+ else
+ {
+ log_info("Publisher broadcast message '%s' ok\n", msg);
+ }
+ }
+
+ log_info("Publisher broadcast over and disconnect broker now\n", ctx->host, ctx->port);
+ mosquitto_disconnect(mosq);
+
+ return ;
+}
+
+
+void *mqtt_pub_worker(void *args)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)args;
+ struct mosquitto *mosq;
+ bool session = true;
+
+
+ mosq = mosquitto_new(NULL, session, ctx);
+ if( !mosq )
+ {
+ log_error("mosquitto_new failure\n");
+ return NULL;
+ }
+
+ /* set connnect to broker username and password */
+ if( strlen(ctx->uid)> 0 && strlen(ctx->pwd)> 0 )
+ mosquitto_username_pw_set(mosq, ctx->uid, ctx->pwd);
+
+ /* set callback functions */
+ mosquitto_connect_callback_set(mosq, pub_connect_callback);
+
+ while( !g_signal.stop )
+ {
+ /* connect to MQTT broker */
+ if( mosquitto_connect(mosq, ctx->host, ctx->port, ctx->keepalive) )
+ {
+ log_error("Publisher connect to broker[%s:%d] failure: %s\n", ctx->host, ctx->port, strerror(errno));
+ msleep(1000);
+ continue;
+ }
+
+ /* -1: use default timeout 1000ms 1: unused */
+ mosquitto_loop_forever(mosq, -1, 1);
+
+ /* Publisher broadcast sensors data message interval time, unit seconds */
+ sleep( ctx->interval );
+ }
+
+ mosquitto_destroy(mosq);
+ return NULL;
+}
+
+void sub_connect_callback(struct mosquitto *mosq, void *userdata, int result)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)userdata;
+
+ if( result )
+ {
+ log_error("Subscriber connect to broker server failed, rv=%d\n", result);
+ return ;
+ }
+
+ log_info("Subscriber connect to broker server[%s:%d] successfully\n", ctx->host, ctx->port);
+ mosquitto_subscribe(mosq, NULL, ctx->subTopic, ctx->subQos);
+}
+
+void sub_disconnect_callback(struct mosquitto *mosq, void *userdata, int result)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)userdata;
+
+ log_warn("Subscriber disconnect to broker server[%s:%d], reason=%d\n", ctx->host, ctx->port, result);
+}
+
+static inline void mqtt_turn_led(int which, char *cmd)
+{
+ if( strcasestr(cmd, "on") )
+ turn_led(which, ON);
+ else if( strcasestr(cmd, "off") )
+ turn_led(which, OFF);
+}
+
+void proc_json_items(cJSON *root, mqtt_ctx_t *ctx)
+{
+ int i;
+ char *value;
+ cJSON *item;
+ cJSON *array;
+ hwconf_t *hwconf = &ctx->hwconf;
+
+ if( !root )
+ {
+ log_error("Invalid input arguments $root\n");
+ return ;
+ }
+
+ for( i=0; i<cJSON_GetArraySize(root); i++ )
+ {
+ item = cJSON_GetArrayItem(root, i);
+ if( !item )
+ break;
+
+ /* if item is cJSON_Object, then recursive call proc_json */
+ if( cJSON_Object == item->type )
+ {
+ proc_json_items(item, ctx);
+ }
+ else if( cJSON_Array == item->type )
+ {
+ /* RGB colors led control: {"id":"RPi3B#01", "leds":[{"red":"on","green":"off","blue":"on"}]} */
+ if( hwconf->led && !strcasecmp(item->string, "leds") )
+ {
+ array = cJSON_GetArrayItem(item, 0);
+ if( NULL != array )
+ {
+ cJSON *led_item;
+
+ if( NULL != (led_item=cJSON_GetObjectItem(array , "red")) )
+ {
+ log_info("turn red led '%s'\n", led_item->valuestring);
+ mqtt_turn_led(LED_R, led_item->valuestring);
+ }
+
+ if( NULL != (led_item=cJSON_GetObjectItem(array , "green")) )
+ {
+ log_info("turn green led '%s'\n", led_item->valuestring);
+ mqtt_turn_led(LED_G, led_item->valuestring);
+ }
+
+ if( NULL != (led_item=cJSON_GetObjectItem(array , "blue")) )
+ {
+ log_info("turn blue led '%s'\n", led_item->valuestring);
+ mqtt_turn_led(LED_B, led_item->valuestring);
+ }
+ }
+ }
+ }
+ else
+ {
+ value = cJSON_Print(item);
+
+ /* light controled by relay: {"id":"RPi3B#01", "light":"on"} */
+ if( hwconf->relay && !strcasecmp(item->string, "light") )
+ {
+ if( strcasestr(value, "on") )
+ {
+ log_info("Turn light on\n");
+ turn_relay(RELAY1, ON);
+ }
+ else if( strcasestr(value, "off") )
+ {
+ log_info("Turn light off\n");
+ turn_relay(RELAY1, OFF);
+ }
+ }
+
+ /* buzzer controled : {"id":"RPi3B#01", "buzzer":"on"} */
+ if( hwconf->beeper && !strcasecmp(item->string, "buzzer") )
+ {
+ if( strcasestr(value, "on") )
+ {
+ turn_beep(3);
+ }
+ }
+
+ free(value); /* must free it, or it will result memory leak */
+ }
+ }
+
+}
+
+void sub_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)userdata;
+
+ cJSON *root = NULL;
+ cJSON *item;
+ char *value;
+
+ if ( !message->payloadlen )
+ {
+ log_error("%s (null)\n", message->topic);
+ return ;
+ }
+
+ log_debug("Subscriber receive message: '%s'\n", message->payload);
+
+ root = cJSON_Parse(message->payload);
+ if( !root )
+ {
+ log_error("cJSON_Parse parser failure: %s\n", cJSON_GetErrorPtr());
+ return ;
+ }
+
+ item = cJSON_GetObjectItem(root, "id");
+ if( !item )
+ {
+ log_error("cJSON_Parse get ID failure: %s\n", cJSON_GetErrorPtr());
+ goto cleanup;
+ }
+
+ value = cJSON_PrintUnformatted(item);
+ if( strcasecmp(value, ctx->devid) )
+ {
+ free(value);
+ goto cleanup;
+ }
+
+ free(value);
+ log_info("Subscriber receive message: '%s'\n", message->payload);
+
+ proc_json_items(root, ctx);
+
+cleanup:
+ cJSON_Delete(root); /* must delete it, or it will result memory leak */
+ return ;
+}
+
+
+void *mqtt_sub_worker(void *args)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)args;
+ struct mosquitto *mosq;
+ bool session = true;
+
+ mosq = mosquitto_new(NULL, session, ctx);
+ if( !mosq )
+ {
+ log_error("mosquitto_new failure\n");
+ return NULL;
+ }
+
+ /* set connnect to broker username and password */
+ if( strlen(ctx->uid)> 0 && strlen(ctx->pwd)> 0 )
+ mosquitto_username_pw_set(mosq, ctx->uid, ctx->pwd);
+
+ /* set callback functions */
+ mosquitto_connect_callback_set(mosq, sub_connect_callback);
+ mosquitto_disconnect_callback_set(mosq, sub_disconnect_callback);
+ mosquitto_message_callback_set(mosq, sub_message_callback);
+
+ while( !g_signal.stop )
+ {
+ /* connect to MQTT broker */
+ if( mosquitto_connect(mosq, ctx->host, ctx->port, ctx->keepalive) )
+ {
+ log_error("Subscriber connect to broker[%s:%d] failure: %s\n", ctx->host, ctx->port, strerror(errno));
+ msleep(1000);
+ continue;
+ }
+
+ /* -1: use default timeout 1000ms 1: unused */
+ mosquitto_loop_forever(mosq, -1, 1);
+ }
+
+ mosquitto_destroy(mosq);
+ return NULL;
+}
+
diff --git a/project/coreMQTT/makefile b/project/coreMQTT/makefile
new file mode 100644
index 0000000..49a790f
--- /dev/null
+++ b/project/coreMQTT/makefile
@@ -0,0 +1,69 @@
+#********************************************************************************
+# Copyright: (C) 2023 LingYun IoT System Studio
+# All rights reserved.
+#
+# Filename: Makefile
+# Description: This file is the project top Makefie
+#
+# Version: 1.0.0(11/08/23)
+# Author: Guo Wenxue <guowenxue@gmail.com>
+# ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+#
+#*******************************************************************************
+
+PRJ_PATH=$(shell pwd)
+APP_NAME = mqttd
+
+BUILD_ARCH=$(shell uname -m)
+ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
+ CROSS_COMPILE=arm-linux-gnueabihf-
+endif
+
+# C source files in top-level directory
+SRCFILES = $(wildcard *.c)
+
+# common CFLAGS for our source code
+CFLAGS = -Wall -Wshadow -Wundef -Wmaybe-uninitialized -D_GNU_SOURCE
+
+# C source file in sub-directory
+SRCS=booster modules coreJSON coreMQTT coreSNTP
+SRCS_PATH=$(patsubst %,${PRJ_PATH}/%,$(SRCS))
+CFLAGS+=$(patsubst %,-I%,$(SRCS_PATH))
+LDFLAGS+=$(patsubst %,-L%,$(SRCS_PATH))
+LIBS=$(patsubst %,-l%,$(SRCS))
+LDFLAGS+=${LIBS}
+
+# Open source libraries
+CFLAGS+=-I ${PRJ_PATH}/openlibs/install/include
+LDFLAGS+=-L ${PRJ_PATH}/openlibs/install/lib
+
+# libraries
+libs=openlibs ${SRCS}
+LDFLAGS+=-lmosquitto -lcjson -lssl -lcrypto -lgpiod
+
+LDFLAGS+=-lpthread
+
+all: entry subdir
+ ${CROSS_COMPILE}gcc ${CFLAGS} ${SRCFILES} -o ${APP_NAME} ${LDFLAGS}
+ @make install
+
+entry:
+ @echo "Building ${APP_NAME} on ${BUILD_ARCH}"
+
+subdir:
+ @for dir in ${libs} ; do if [ ! -e $${dir} ] ; then ln -s ../$${dir}; fi; done
+ @for dir in ${libs} ; do make -C $${dir} ; done
+
+install:
+ cp ${APP_NAME} /tftp
+
+clean:
+ @for dir in ${SRCS} ; do if [ -e $${dir} ] ; then make clean -C $${dir}; fi; done
+ @rm -f ${APP_NAME}
+
+distclean:
+ @for dir in ${libs} ; do if [ -e $${dir} ] ; then make clean -C $${dir}; fi; done
+ @for dir in ${libs} ; do if [ -e $${dir} ] ; then unlink $${dir}; fi; done
+ @rm -f ${APP_NAME}
+ @rm -f cscope.* tags
+
diff --git a/project/gpsd/gpsd.c b/project/gpsd/gpsd.c
new file mode 100644
index 0000000..61356f1
--- /dev/null
+++ b/project/gpsd/gpsd.c
@@ -0,0 +1,245 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: gpsd.c
+ * Description: This file is GPS location parser program
+ *
+ * Version: 1.0.0(07/10/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "07/10/23 09:49:55"
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "logger.h"
+#include "comport.h"
+#include "util_proc.h"
+
+typedef struct gps_fix_s
+{
+ char time[32]; /* GPS UTC time, formt: hhmmss */
+ char status; /* A: Valid V:Invalid */
+ float latitude; /* Latitude */
+ char lat; /* N: North S: South */
+ float longitude; /* Longitude */
+ char lon; /* E: East W: West */
+ float speed; /* Speed over ground, meters/sec */
+ float track; /* Course made good (relative to true north) */
+} gps_fix_t;
+
+
+int proc_gprmc(char *buf, int size, gps_fix_t *info);
+
+static inline void banner(const char *progname)
+{
+ printf("%s program Version v1.0.0 Build on (%s)\n", progname, __DATE__);
+ printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
+}
+
+static void program_usage(const char *progname)
+{
+ banner(progname);
+
+ printf("Usage: %s [OPTION]...\n", progname);
+ printf(" %s is a MQTT subscribe daemon program to control relay. \n", progname);
+
+ printf("\nMandatory arguments to long options are mandatory for short options too:\n");
+ printf(" -D[device ] Set GPS connect serial port device\n");
+ printf(" -d[debug ] Running in debug mode\n");
+ printf(" -l[level ] Set the log level as [0..%d]\n", LOG_LEVEL_MAX-1);
+ printf(" -h[help ] Display this help information\n");
+ printf(" -v[version ] Display the program version\n");
+
+ return;
+}
+
+int main (int argc, char **argv)
+{
+ const char *progname=NULL;
+ int opt = 0;
+ int rv = 0;
+ int debug = 0;
+ char pid_file[64] = { 0 }; /* The file used to record the PID */
+ char *log_file = "/tmp/gpsd.log";
+ int log_level = LOG_LEVEL_INFO;
+ int log_size = 10;
+
+ char buf[4096];
+ char *dev=NULL;
+ comport_t comport;
+ gps_fix_t info;
+
+ struct option long_options[] = {
+ {"device", required_argument, NULL, 'D'},
+ {"debug", no_argument, NULL, 'd'},
+ {"level", required_argument, NULL, 'l'},
+ {"version", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ progname = basename(argv[0]);
+ /* Parser the command line parameters */
+ while ((opt = getopt_long(argc, argv, "D:dl:vh", long_options, NULL)) != -1)
+ {
+ switch (opt)
+ {
+ case 'D': /* Set serial port device */
+ dev = optarg;
+ break;
+
+ case 'd': /* Set debug running */
+ debug = 1;
+ log_file = "console";
+ log_level = LOG_LEVEL_DEBUG;
+ log_size = ROLLBACK_NONE;
+ break;
+
+ case 'l': /* Set the log level */
+ rv = atoi(optarg);
+ log_level = rv>LOG_LEVEL_MAX ? LOG_LEVEL_MAX-1 : rv;
+ break;
+
+ case 'v': /* Get software version */
+ banner(progname); /* Defined in version.h */
+ return EXIT_SUCCESS;
+
+ case 'h': /* Get help information */
+ program_usage(progname);
+ return 0;
+
+ default:
+ break;
+ } /* end of "switch(opt)" */
+ }
+
+ if( !dev )
+ {
+ program_usage(progname);
+ return 0;
+ }
+
+ if( (rv=log_open(log_file, log_level, log_size, LOG_LOCK_DISABLE)) < 0 )
+ {
+ fprintf(stderr, "open logger failed, rv=%d\n", rv);
+ return 1;
+ }
+
+ if( !debug )
+ {
+ snprintf(pid_file, sizeof(pid_file), "/var/run/%s.pid", progname);
+ log_info("check program running in daemon or not by pidfile [%s]\n", pid_file);
+ if( check_daemon_running(pid_file) )
+ {
+ printf("Programe already running, exit now.\n");
+ return 3;
+ }
+
+ if( set_daemon_running(pid_file) < 0 )
+ {
+ printf("Programe already running, exit now.\n");
+ return 3;
+ }
+ }
+
+ if( (rv=comport_open(&comport, dev, 4800, "8N1N")) < 0 )
+ {
+ log_error("Open serial port \"%s\" failed, rv=%d\n", dev, rv);
+ return 2;
+ }
+ log_info("Open serial port \"%s\" successfully\n", dev);
+
+ while(1)
+ {
+ memset(buf, 0, sizeof(buf));
+ rv = comport_recv(&comport, buf, sizeof(buf), 3000);
+ if( rv > 0 )
+ {
+ proc_gprmc(buf, strlen(buf), &info);
+ }
+ }
+
+ comport_close(&comport);
+ return 0;
+}
+
+/*
+ * $GPRMC,024216.000,A,3029.6651,N,11423.6251,E,0.08, 156.95,100723,,,A*65
+ * 1 024216.000 UTC time format: HHMMSS, 10:42:16(BeiJing)
+ * 2 A Status of Fix:
+ * A = Autonomous, valid;
+ * D = Differential, valid;
+ * V = invalid
+ *
+ * 3,4 3029.6651,N Latitude format: ddmm.mmmm, Latitude 30 deg. 29.6651 min North
+ * 5,6 11423.6251,E Longitude: dddmm.mmmm, Longitude 114 deg. 23.6251 min East
+ * 7 0.08 Speed over ground, Knots
+ * 8 156.95 Course Made Good, True north
+ * 9 100723 Date of fix ddmmyy. 2023-07-10
+ * 10,11 ,, Magnetic variation
+ * 12 A FAA mode indicator (NMEA 2.3 and later)
+ * A=autonomous,
+ * D=differential,
+ * E=Estimated,
+ * M=Manual input mode,
+ * N=not valid,
+ * S=Simulator,
+ * V=Valid
+ * 13 *68 mandatory nmea_checksum
+ */
+
+#define DD(s) ((int)((s)[0]-'0')*10+(int)((s)[1]-'0'))
+int proc_gprmc(char *buf, int size, gps_fix_t *info)
+{
+ char *ptr_start=NULL;
+ char *ptr_end=NULL;
+ char hhmmss[12]={0x0};
+ char ddmmyy[12]={0x0};
+
+ if( !buf || size<=0 || !info )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ log_trace("GPS receive raw data\n:%s\n", buf);
+
+ if( !(ptr_start=strstr(buf, "$GPRMC")) )
+ {
+ log_warn("$GPRMC keywords not matched\n");
+ return -2;
+ }
+
+ if( !(ptr_end=strchr(ptr_start, 0x0D)) )
+ {
+ log_warn("$GPRMC data not integrated\n");
+ }
+
+ *ptr_end = '\0';
+
+ memset(info, 0, sizeof(*info));
+ sscanf(ptr_start, "$GPRMC,%[^,],%c,%f,%c,%f,%c,%f,%f,%[^,]",
+ hhmmss, &info->status, &info->latitude, &info->lat,
+ &info->longitude, &info->lon, &info->speed, &info->track, ddmmyy);
+
+ snprintf(info->time, sizeof(info->time), "%04d-%02d-%02d %02d:%02d:%02d",
+ DD(ddmmyy+4)+2000, DD(ddmmyy+2), DD(ddmmyy),
+ DD(hhmmss)+8, DD(hhmmss+2), DD(hhmmss+4));
+
+ if( info->status == 'A' )
+ {
+ log_info("NMEA0183: %s lat=%.2f(%c) lon=%.2f(%c) speed=%.2f, track=%.2f\n",
+ info->time, info->latitude, info->lat,
+ info->longitude, info->lon, info->speed, info->track);
+ }
+ else if( info->status == 'V' )
+ {
+ log_info("NMEA0183: Invalid data\n");
+ }
+
+ return 0;
+}
diff --git a/project/gpsd/makefile b/project/gpsd/makefile
new file mode 100644
index 0000000..6e48675
--- /dev/null
+++ b/project/gpsd/makefile
@@ -0,0 +1,62 @@
+#********************************************************************************
+# Copyright: (C) 2023 LingYun IoT System Studio
+# All rights reserved.
+#
+# Filename: Makefile
+# Description: This file is the project top Makefie
+#
+# Version: 1.0.0(11/08/23)
+# Author: Guo Wenxue <guowenxue@gmail.com>
+# ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+#
+#*******************************************************************************
+
+PRJ_PATH=$(shell pwd)
+APP_NAME = gpsd
+INST_PATH= /tftp
+
+BUILD_ARCH=$(shell uname -m)
+ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
+ CROSS_COMPILE=arm-linux-gnueabihf-
+endif
+
+# C source files in top-level directory
+SRCFILES = $(wildcard *.c)
+
+# common CFLAGS for our source code
+CFLAGS = -Wall -Wshadow -Wundef -Wmaybe-uninitialized -D_GNU_SOURCE
+
+# C source file in sub-directory
+DIRS= booster
+DIRS_PATH=$(patsubst %,${PRJ_PATH}/%,$(DIRS))
+CFLAGS+=$(patsubst %,-I%,$(DIRS_PATH))
+LDFLAGS+=$(patsubst %,-L%,$(DIRS_PATH))
+LIBS=$(patsubst %,-l%,$(DIRS))
+LDFLAGS+=${LIBS}
+
+LDFLAGS+=-lpthread
+
+.PHONY:libs
+all: entry modules binary
+
+entry:
+ @echo "Building ${APP_NAME} on ${BUILD_ARCH}"
+ @if [ ! -e booster ] ; then ln -s ../booster; fi
+
+modules:
+ @set -e; for d in ${DIRS}; do $(MAKE) CROSS_COMPILE=${CROSS_COMPILE} CFLAGS="${CFLAGS}" -C $${d}; done
+
+binary: ${SRCFILES}
+ $(CROSS_COMPILE)gcc $(CFLAGS) -o ${APP_NAME} $^ ${LDFLAGS}
+ @echo " Compile over"
+
+clean:
+ set -e; for d in ${DIRS}; do $(MAKE) clean -C $${d}; done
+ @rm -f *.o $(APP_NAME)
+
+distclean: clean
+ @rm -rf cscope* tags
+ @rm -f booster
+
+install:
+ cp ${APP_NAME} ${INST_PATH}
diff --git a/project/modules/ds18b20.c b/project/modules/ds18b20.c
new file mode 100644
index 0000000..5f279fe
--- /dev/null
+++ b/project/modules/ds18b20.c
@@ -0,0 +1,119 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: ds18b20.c
+ * Description: This file is temperature sensor DS18B20 code
+ *
+ * Version: 1.0.0(2023/8/10)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "2023/8/10 12:13:26"
+ *
+ * Pin connection:
+ *
+ * DS18B20 Module Raspberry Pi Board
+ * VCC <-----> #Pin1(3.3V)
+ * DQ <-----> #Pin7(BCM GPIO4)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ *
+ * dtoverlay=w1-gpio-pullup,gpiopin=4
+ *
+ ********************************************************************************/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+#include "logger.h"
+
+/* File Content:
+ pi@raspberrypi:~/guowenxue $ cat /sys/bus/w1/devices/28-041731f7c0ff/w1_slave
+ 3a 01 4b 46 7f ff 0c 10 a5 : crc=a5 YES
+ 3a 01 4b 46 7f ff 0c 10 a5 t=19625
+ */
+
+int ds18b20_get_temperature(float *temp)
+{
+ char w1_path[50] = "/sys/bus/w1/devices/";
+ char chip[20];
+ char buf[128];
+ DIR *dirp;
+ struct dirent *direntp;
+ int fd =-1;
+ char *ptr;
+ float value;
+ int found = 0;
+
+ if( !temp )
+ {
+ return -1;
+ }
+
+ /*+-------------------------------------------------------------------+
+ *| open dierectory /sys/bus/w1/devices to get chipset Serial Number |
+ *+-------------------------------------------------------------------+*/
+ if((dirp = opendir(w1_path)) == NULL)
+ {
+ log_error("opendir error: %s\n", strerror(errno));
+ return -2;
+ }
+
+ while((direntp = readdir(dirp)) != NULL)
+ {
+ if(strstr(direntp->d_name,"28-"))
+ {
+ /* find and get the chipset SN filename */
+ strcpy(chip,direntp->d_name);
+ found = 1;
+ break;
+ }
+ }
+ closedir(dirp);
+
+ if( !found )
+ {
+ log_error("Can not find ds18b20 in %s\n", w1_path);
+ return -3;
+ }
+
+ /* get DS18B20 sample file full path: /sys/bus/w1/devices/28-xxxx/w1_slave */
+ strncat(w1_path, chip, sizeof(w1_path)-strlen(w1_path));
+ strncat(w1_path, "/w1_slave", sizeof(w1_path)-strlen(w1_path));
+
+ /* open file /sys/bus/w1/devices/28-xxxx/w1_slave to get temperature */
+ if( (fd=open(w1_path, O_RDONLY)) < 0 )
+ {
+ log_error("open %s error: %s\n", w1_path, strerror(errno));
+ return -4;
+ }
+
+ if(read(fd, buf, sizeof(buf)) < 0)
+ {
+ log_error("read %s error: %s\n", w1_path, strerror(errno));
+ return -5;
+ }
+
+ ptr = strstr(buf, "t=");
+ if( !ptr )
+ {
+ log_error("ERROR: Can not get temperature\n");
+ return -6;
+ }
+
+ ptr+=2;
+
+ /* convert string value to float value */
+ *temp = atof(ptr)/1000;
+
+ close(fd);
+
+ return 0;
+}
diff --git a/project/modules/ds18b20.h b/project/modules/ds18b20.h
new file mode 100644
index 0000000..e756134
--- /dev/null
+++ b/project/modules/ds18b20.h
@@ -0,0 +1,31 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: ds18b20.h
+ * Description: This file is temperature sensor DS18B20 code
+ *
+ * Version: 1.0.0(2023/8/10)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "2023/8/10 12:13:26"
+ *
+ * Pin connection:
+ *
+ * DS18B20 Module Raspberry Pi Board
+ * VCC <-----> #Pin1(3.3V)
+ * DQ <-----> #Pin7(BCM GPIO4)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ *
+ * dtoverlay=w1-gpio-pullup,gpiopin=4
+ *
+ ********************************************************************************/
+
+#ifndef _DS18B20_H_
+#define _DS18B20_H_
+
+extern int ds18b20_get_temperature(float *temp);
+
+#endif /* ----- #ifndef _DS18B20_H_ ----- */
+
diff --git a/project/modules/leds.c b/project/modules/leds.c
new file mode 100644
index 0000000..2e46182
--- /dev/null
+++ b/project/modules/leds.c
@@ -0,0 +1,162 @@
+/*********************************************************************************
+ * Copyright: (C) 2021 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: led.c
+ * Description: This file is used to control RGB 3-colors LED
+ * compatible with libgpiod-1.x.x, not compatible with 2.x.x
+ *
+ *
+ * Pin connection:
+ * RGB Led Module Raspberry Pi Board
+ * R <-----> #Pin33(BCM GPIO13)
+ * G <-----> #Pin35(BCM GPIO19)
+ * B <-----> #Pin37(BCM GPIO26)
+ * GND <-----> GND
+ *
+ * System install:
+ * sudo apt install -y libgpiod-dev gpiod
+ *
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <gpiod.h>
+#include "logger.h"
+#include "leds.h"
+
+#define DELAY 500
+
+static led_info_t leds_info[LED_CNT] =
+{
+ {"red", 13, 1, NULL },
+ {"green", 19, 1, NULL },
+ {"blue", 26, 1, NULL },
+};
+
+int init_led(led_ctx_t *ctx, int which)
+{
+ int i, rv;
+ led_info_t *led;
+
+ if( !ctx )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ ctx->leds = leds_info;
+ ctx->count = LED_CNT;
+
+ ctx->chip = gpiod_chip_open_by_name("gpiochip0");
+ if( !ctx->chip )
+ {
+ log_error("open gpiochip failure, maybe you need running as root\n");
+ return -2;
+ }
+
+ for(i=0; i<ctx->count; i++)
+ {
+ if( which == i )
+ {
+ led = &ctx->leds[i];
+
+ led->line = gpiod_chip_get_line(ctx->chip, led->gpio);
+ if( !led->line )
+ {
+ log_error("open gpioline for %s[%d] failed\n", led->name, led->gpio);
+ rv = -3;
+ goto failed;
+ }
+
+ rv = gpiod_line_request_output(led->line, led->name, !led->active);
+ if( rv )
+ {
+ log_error("request gpio output for %5s[%d] failed: %s\n", led->name, led->gpio, strerror(errno));
+ rv = -4;
+ goto failed;
+ }
+
+ log_debug("request %5s led[%d] for gpio output okay\n", led->name, led->gpio);
+ }
+ }
+
+ return 0;
+
+failed:
+ term_led(ctx, which);
+ return rv;
+}
+
+int term_led(led_ctx_t *ctx, int which)
+{
+ int i;
+ led_info_t *led;
+
+ log_warn("terminate RGB Led gpios\n");
+
+ if( !ctx )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ if( !ctx->chip )
+ return 0;
+
+ for(i=0; i<ctx->count; i++)
+ {
+ if( which == i )
+ {
+ led = &ctx->leds[i];
+
+ if( led->line )
+ gpiod_line_release(led->line);
+ }
+ }
+
+ gpiod_chip_close(ctx->chip);
+ return 0;
+}
+
+int turn_led(int which, int cmd)
+{
+ int rv = 0;
+ led_ctx_t ctx;
+ led_info_t *led;
+
+ if( which<0 || which>=LED_CNT )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ if( (rv=init_led(&ctx, which)) < 0 )
+ {
+ log_error("Initial RGB leds failure, rv=%d\n", rv);
+ return -2;
+ }
+
+ led = &ctx.leds[which];
+
+ if( OFF == cmd )
+ {
+ gpiod_line_set_value(led->line, !led->active);
+ }
+ else
+ {
+ gpiod_line_set_value(led->line, led->active);
+ }
+
+ term_led(&ctx, which);
+ return 0;
+}
diff --git a/project/modules/leds.h b/project/modules/leds.h
new file mode 100644
index 0000000..ddf1518
--- /dev/null
+++ b/project/modules/leds.h
@@ -0,0 +1,58 @@
+/*********************************************************************************
+ * Copyright: (C) 2021 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: led.h
+ * Description: This file is used to control RGB 3-colors LED
+ *
+ *
+ * Pin connection:
+ * RGB Led Module Raspberry Pi Board
+ * R <-----> #Pin33(BCM GPIO13)
+ * G <-----> #Pin35(BCM GPIO19)
+ * B <-----> #Pin37(BCM GPIO26)
+ * GND <-----> GND
+ *
+ * System install:
+ * sudo apt install -y libgpiod-dev gpiod
+ *
+ *
+ ********************************************************************************/
+
+#ifndef _LEDS_H_
+#define _LEDS_H_
+
+#define ON 1
+#define OFF 0
+
+/* Three LEDs code */
+enum
+{
+ LED_R = 0,
+ LED_G,
+ LED_B,
+ LED_CNT,
+};
+
+/* Three LEDs hardware information */
+typedef struct led_info_s
+{
+ const char *name; /* RGB 3-color LED name */
+ int gpio; /* RGB 3-color LED BCM pin number */
+ int active;/* RGB 3-color LED active GPIO level: 0->low 1->high */
+ struct gpiod_line *line; /* libgpiod line */
+} led_info_t;
+
+/* Three LEDs API context */
+typedef struct led_ctx_s
+{
+ struct gpiod_chip *chip;
+ led_info_t *leds;
+ int count;
+} led_ctx_t;
+
+extern int init_led(led_ctx_t *ctx, int which);
+extern int term_led(led_ctx_t *ctx, int which);
+extern int turn_led(int which, int cmd);
+
+#endif /* ----- #ifndef _LEDS_H_ ----- */
diff --git a/project/modules/makefile b/project/modules/makefile
new file mode 100644
index 0000000..1b2a503
--- /dev/null
+++ b/project/modules/makefile
@@ -0,0 +1,38 @@
+#********************************************************************************
+# Copyright: (C) 2023 LingYun IoT System Studio
+# All rights reserved.
+#
+# Filename: Makefile
+# Description: This file used compile all the source code to static library
+#
+# Version: 1.0.0(11/08/23)
+# Author: Guo Wenxue <guowenxue@gmail.com>
+# ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+#
+#*******************************************************************************
+
+PWD=$(shell pwd)
+LIBNAME=$(shell basename ${PWD} )
+TOPDIR=$(shell dirname ${PWD} )
+
+BUILD_ARCH=$(shell uname -m)
+ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
+ CROSS_COMPILE?=arm-linux-gnueabihf-
+endif
+
+OPENLIBS_INCPATH=${TOPDIR}/openlibs/install/include
+OPENLIBS_LIBPATH=${TOPDIR}/openlibs/install/lib
+
+CFLAGS+=-I${OPENLIBS_INCPATH} -I${TOPDIR}/booster
+
+all: clean
+ @rm -f *.o
+ ${CROSS_COMPILE}gcc ${CFLAGS} -c *.c
+ ${CROSS_COMPILE}ar -rcs lib${LIBNAME}.a *.o
+
+clean:
+ @rm -f *.o
+ @rm -f *.a
+
+distclean:
+ @make clean
diff --git a/project/modules/modules.h b/project/modules/modules.h
new file mode 100644
index 0000000..2e2c32c
--- /dev/null
+++ b/project/modules/modules.h
@@ -0,0 +1,24 @@
+/********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: modules.h
+ * Description: This file
+ *
+ * Version: 1.0.0(08/17/2023)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "08/17/2023 09:27:25 PM"
+ *
+ ********************************************************************************/
+
+#ifndef _MODULES_H_
+#define _MODULES_H_
+
+#include "ds18b20.h"
+#include "leds.h"
+#include "pwm.h"
+#include "relay.h"
+#include "sht20.h"
+#include "tsl2561.h"
+
+#endif /* ----- #ifndef _MODULES_H_ ----- */
diff --git a/project/modules/pwm.c b/project/modules/pwm.c
new file mode 100644
index 0000000..922ccbf
--- /dev/null
+++ b/project/modules/pwm.c
@@ -0,0 +1,213 @@
+/*********************************************************************************
+ * Copyright: (C) 2021 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: pwm.c
+ * Description: This file is used to control PWM buzzer/Led
+ *
+ * Pin connection:
+ * PWM Module Raspberry Pi Board
+ * VCC <-----> 5V
+ * buzzer <-----> #Pin32(BCM GPIO12)
+ * Led <-----> #Pin33(BCM GPIO13)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ *
+ * dtoverlay=pwm,pin=12,func=4 (Buzzer)
+ * dtoverlay=pwm,pin=13,func=4 (Led)
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#include <getopt.h>
+#include <libgen.h>
+
+#include "logger.h"
+#include "util_proc.h"
+#include "pwm.h"
+
+/* check PWM $channel export or not */
+int check_pwm(int channel)
+{
+ char path[256];
+
+ /* check /sys/class/pwm/pwmchip0/pwmN exist or not */
+ snprintf(path, sizeof(path), "%s/pwm%d", PWMCHIP_PATH, channel);
+ return access(path, F_OK) ? 0 : 1;
+}
+
+/* export($export=1)/unexport($export=0) PWM $channel */
+int export_pwm(int channel, int export)
+{
+ int fd;
+ char buf[32];
+ char path[256];
+
+ if( export && check_pwm(channel) )
+ return 0; /* export already when export */
+ else if( !export && !check_pwm(channel) )
+ return 0; /* unexport already when unexport */
+
+ /* export PWM channel by echo N > /sys/class/pwm/pwmchip0/export */
+ snprintf(path, sizeof(path), "%s/%s", PWMCHIP_PATH, export?"export":"unexport");
+ if( (fd=open(path, O_WRONLY)) < 0 )
+ {
+ log_error("open '%s' failed: %s\n", path, strerror(errno));
+ return -3;
+ }
+
+ snprintf(buf, sizeof(buf), "%d", channel);
+ write(fd, buf, strlen(buf));
+
+ msleep(100);
+
+ if( export && check_pwm(channel) )
+ return 0; /* export already when export */
+ else if( !export && !check_pwm(channel) )
+ return 0; /* unexport already when unexport */
+
+ return -4;
+}
+
+/* configure PWM $channel */
+int config_pwm(int channel, int freq, int duty)
+{
+ int fd;
+ char buf[32];
+ char path[256];
+ int period;
+ int duty_cycle;
+
+ if( !check_pwm(channel) )
+ return -2;
+
+ /* open PWM period file and write period in ns */
+ snprintf(path, sizeof(path), "%s/pwm%d/period", PWMCHIP_PATH, channel);
+ if( (fd=open(path, O_WRONLY)) < 0 )
+ {
+ log_error("open '%s' failed: %s\n", path, strerror(errno));
+ return -3;
+ }
+ period = 1000000000/freq;
+ snprintf(buf, sizeof(buf), "%d", period);
+ write(fd, buf, strlen(buf));
+
+ /* open PWM duty_cycle file and write duty */
+ snprintf(path, sizeof(path), "%s/pwm%d/duty_cycle", PWMCHIP_PATH, channel);
+ if( (fd=open(path, O_WRONLY)) < 0 )
+ {
+ log_error("open '%s' failed: %s\n", path, strerror(errno));
+ return -3;
+ }
+ duty_cycle = (period*duty) / 100;
+ snprintf(buf, sizeof(buf), "%d", duty_cycle);
+ write(fd, buf, strlen(buf));
+
+ return 0;
+}
+
+int init_pwm(int channel, int freq, int duty)
+{
+ int rv;
+ char buf[32];
+ char path[256];
+
+ if( (rv=export_pwm(channel, 1)) )
+ {
+ log_error("export PWM channel[%d] failed, rv=%d\n", channel, rv);
+ return rv;
+ }
+
+ if( (rv=config_pwm(channel, freq, duty)) )
+ {
+ log_error("config PWM channel[%d] failed, rv=%d\n", channel, rv);
+ return rv;
+ }
+
+ log_debug("config pwm%d with freq[%d] duty[%d] okay\n", channel, freq, duty);
+
+ return 0;
+}
+
+int turn_pwm(int channel, int status)
+{
+ int fd;
+ char buf[32];
+ char path[256];
+
+ if( !check_pwm(channel) )
+ return -1;
+
+ /* open PWM enable file and enable(1)/disable(0) it */
+ snprintf(path, sizeof(path), "%s/pwm%d/enable", PWMCHIP_PATH, channel);
+ if( (fd=open(path, O_WRONLY)) < 0 )
+ {
+ log_error("open '%s' failed: %s\n", path, strerror(errno));
+ return -3;
+ }
+ snprintf(buf, sizeof(buf), "%d", status?1:0);
+ write(fd, buf, strlen(buf));
+
+ log_debug("pwm[%d] %s\n", channel, status?"enable":"disable");
+
+ return 0;
+}
+
+int term_pwm(int channel)
+{
+ if( !check_pwm(channel) )
+ return 0;
+
+ turn_pwm(channel, DISABLE);
+ export_pwm(channel, 0);
+
+ return 0;
+}
+
+int turn_beep(int times)
+{
+ int rv;
+
+ /* stop beeper beep */
+ if(times == 0)
+ {
+ log_debug("Stop beeper\n");
+ term_pwm(CHN_BEEPER);
+ return 0;
+ }
+
+ rv = init_pwm(CHN_BEEPER, FRQ_BEEPER, 50);
+ if(rv < 0)
+ {
+ log_error("Initial beeper pwm[%d] failed, rv=%d\n", CHN_BEEPER);
+ return -2;
+ }
+
+ /* turn beeper beep ceaselessly */
+ if( times < 0)
+ {
+ turn_pwm(CHN_BEEPER, ENABLE);
+ return 0;
+ }
+
+ while( times-- )
+ {
+ turn_pwm(CHN_BEEPER, ENABLE);
+ msleep(800);
+
+ turn_pwm(CHN_BEEPER, DISABLE);
+ msleep(800);
+ }
+
+ term_pwm(CHN_BEEPER);
+ return 0;
+}
diff --git a/project/modules/pwm.h b/project/modules/pwm.h
new file mode 100644
index 0000000..1e07f6a
--- /dev/null
+++ b/project/modules/pwm.h
@@ -0,0 +1,44 @@
+/*********************************************************************************
+ * Copyright: (C) 2021 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: pwm.h
+ * Description: This file is used to control PWM buzzer/Led
+ *
+ * Pin connection:
+ * PWM Module Raspberry Pi Board
+ * VCC <-----> 5V
+ * buzzer <-----> #Pin32(BCM GPIO12)
+ * Led <-----> #Pin33(BCM GPIO13)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ *
+ * dtoverlay=pwm,pin=12,func=4 (Buzzer)
+ * dtoverlay=pwm,pin=13,func=4 (Led)
+ *
+ ********************************************************************************/
+
+
+#ifndef _PWM_H_
+#define _PWM_H_
+
+#define PWMCHIP_PATH "/sys/class/pwm/pwmchip0"
+
+#define ENABLE 1
+#define DISABLE 0
+
+#define CHN_BEEPER 0
+#define FRQ_BEEPER 2700
+
+#define CHN_RGBLED 1
+#define FRQ_RGBLED 100
+
+extern int init_pwm(int channel, int freq, int duty);
+extern int turn_pwm(int channel, int status);
+extern int term_pwm(int channel);
+
+extern int turn_beep(int times);
+
+#endif /* ----- #ifndef _PWM_H_ ----- */
+
diff --git a/project/modules/relay.c b/project/modules/relay.c
new file mode 100644
index 0000000..35695ae
--- /dev/null
+++ b/project/modules/relay.c
@@ -0,0 +1,156 @@
+/*********************************************************************************
+ * Copyright: (C) 2021 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: relay.c
+ * Description: This file is used to control Relay
+ *
+ *
+ * Pin connection:
+ * Relay Module Raspberry Pi Board
+ * VCC <-----> 5V
+ * I <-----> #Pin16(BCM GPIO23)
+ * GND <-----> GND
+ *
+ * System install:
+ * sudo apt install -y libgpiod-dev gpiod
+ *
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#include <getopt.h>
+#include <libgen.h>
+
+#include <gpiod.h>
+#include "logger.h"
+#include "relay.h"
+
+#define DELAY 500
+
+static relay_info_t relay_info[RELAY_CNT] =
+{
+ {"relay1", 23, 1, NULL },
+};
+
+int init_relay(relay_ctx_t *ctx)
+{
+ int i, rv;
+ relay_info_t *relay;
+
+ if( !ctx )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ ctx->relay = relay_info;
+ ctx->count = RELAY_CNT;
+
+ ctx->chip = gpiod_chip_open_by_name("gpiochip0");
+ if( !ctx->chip )
+ {
+ log_error("open gpiochip failure, maybe you need running as root\n");
+ return -2;
+ }
+
+
+ for(i=0; i<ctx->count; i++)
+ {
+ relay = &ctx->relay[i];
+
+ relay->line = gpiod_chip_get_line(ctx->chip, relay->gpio);
+ if( !relay->line )
+ {
+ log_error("open gpioline for %s[%d] failed\n", relay->name, relay->gpio);
+ rv = -3;
+ goto failed;
+ }
+
+ rv = gpiod_line_request_output(relay->line, relay->name, !relay->active);
+ if( rv )
+ {
+ log_error("request gpio output for %5s[%d] failed\n", relay->name, relay->gpio);
+ rv = -4;
+ goto failed;
+ }
+
+ log_debug("request %s[%d] for gpio output okay\n", relay->name, relay->gpio);
+ }
+
+ return 0;
+
+failed:
+ term_relay(ctx);
+ return rv;
+}
+
+int term_relay(relay_ctx_t *ctx)
+{
+ int i;
+ relay_info_t *relay;
+
+ if( !ctx )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ if( !ctx->chip )
+ return 0;
+
+ for(i=0; i<ctx->count; i++)
+ {
+ relay = &ctx->relay[i];
+
+ if( relay->line )
+ gpiod_line_release(relay->line);
+ }
+
+ gpiod_chip_close(ctx->chip);
+ return 0;
+}
+
+
+int turn_relay(int which, int cmd)
+{
+ int rv = 0;
+ relay_info_t *relay;
+ relay_ctx_t ctx;
+
+ if( which<0 || which>=RELAY_CNT )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ if( (rv=init_relay(&ctx)) < 0 )
+ {
+ log_error("Initial relay failure, rv=%d\n", rv);
+ return -2;
+ }
+
+ relay = &ctx.relay[which];
+
+ if( OFF == cmd )
+ {
+ gpiod_line_set_value(relay->line, !relay->active);
+ }
+ else
+ {
+ gpiod_line_set_value(relay->line, relay->active);
+ }
+
+ term_relay(&ctx);
+
+ return 0;
+}
+
diff --git a/project/modules/relay.h b/project/modules/relay.h
new file mode 100644
index 0000000..c1d8c1d
--- /dev/null
+++ b/project/modules/relay.h
@@ -0,0 +1,56 @@
+/*********************************************************************************
+ * Copyright: (C) 2021 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: relay.c
+ * Description: This file is used to control Relay
+ *
+ *
+ * Pin connection:
+ * Relay Module Raspberry Pi Board
+ * VCC <-----> 5V
+ * I <-----> #Pin16(BCM GPIO23)
+ * GND <-----> GND
+ *
+ * System install:
+ * sudo apt install -y libgpiod-dev gpiod
+ *
+ *
+ ********************************************************************************/
+
+#ifndef _RELAY_H_
+#define _RELAY_H_
+
+#define ON 1
+#define OFF 0
+
+/* relay code */
+enum
+{
+ RELAY1 = 0,
+ RELAY_CNT,
+};
+
+/* Relay hardware information */
+typedef struct relay_info_s
+{
+ const char *name; /* Relay name */
+ int gpio; /* Relay BCM pin number */
+ int active;/* Relay active GPIO level: 0->low 1->high */
+ struct gpiod_line *line; /* libgpiod line */
+} relay_info_t;
+
+/* Relay API context */
+typedef struct relay_ctx_s
+{
+ struct gpiod_chip *chip;
+ relay_info_t *relay;
+ int count;
+} relay_ctx_t;
+
+extern int init_relay(relay_ctx_t *ctx);
+extern int term_relay(relay_ctx_t *ctx);
+extern int turn_relay(int which, int cmd);
+
+#endif /* ----- #ifndef _RELAY_H_ ----- */
+
diff --git a/project/modules/sht20.c b/project/modules/sht20.c
new file mode 100644
index 0000000..22a2ac6
--- /dev/null
+++ b/project/modules/sht20.c
@@ -0,0 +1,244 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: sht20.c
+ * Description: This file is temperature and relative humidity sensor SHT20 code
+ *
+ * Version: 1.0.0(10/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ * STH20 Module Raspberry Pi Board
+ * VCC <-----> #Pin1(3.3V)
+ * SDA1 <-----> #Pin3(SDA, BCM GPIO2)
+ * SCL1 <-----> #Pin5(SCL, BCM GPIO3)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ * dtoverlay=i2c1,pins_2_3
+ *
+ ********************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+
+#include "logger.h"
+#include "util_proc.h"
+#include "sht20.h"
+
+int i2c_write(int fd, uint8_t slave_addr, uint8_t *data, int len);
+int i2c_read(int fd, uint8_t slave_addr, uint8_t *buf, int size);
+int sht20_checksum(uint8_t *data, int len, int8_t checksum);
+
+int sht2x_get_temp_humidity(float *temp, float *rh)
+{
+ int fd;
+ int rv = 0;
+ char *dev = SHT20_I2CDEV;
+ uint8_t buf[4];
+
+ if( !temp || !rh )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ /*+--------------------------------+
+ *| open /dev/i2c-x device |
+ *+--------------------------------+*/
+ if( (fd=open(dev, O_RDWR)) < 0)
+ {
+ log_error("i2c device '%s' open failed: %s\n", dev, strerror(errno));
+ return -2;
+ }
+
+ /*+--------------------------------+
+ *| software reset SHT20 sensor |
+ *+--------------------------------+*/
+
+ buf[0] = 0xFE;
+ i2c_write(fd, SHT20_I2CADDR, buf, 1);
+
+ msleep(50);
+
+
+ /*+--------------------------------+
+ *| trigger temperature measure |
+ *+--------------------------------+*/
+
+ buf[0] = 0xF3;
+ i2c_write(fd, SHT20_I2CADDR, buf, 1);
+
+ msleep(85); /* datasheet: typ=66, max=85 */
+
+ memset(buf, 0, sizeof(buf));
+ i2c_read(fd, SHT20_I2CADDR, buf, 3);
+ log_dump(LOG_LEVEL_DEBUG, "Temperature sample data: ", buf, 3);
+
+ if( !sht20_checksum(buf, 2, buf[2]) )
+ {
+ rv = -3;
+ log_error("Temperature sample data CRC checksum failure.\n");
+ goto cleanup;
+ }
+
+ *temp = 175.72 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;
+
+
+ /*+--------------------------------+
+ *| trigger humidity measure |
+ *+--------------------------------+*/
+
+ buf[0] = 0xF5;
+ i2c_write(fd, SHT20_I2CADDR, buf, 1);
+
+ msleep(29); /* datasheet: typ=22, max=29 */
+
+ memset(buf, 0, sizeof(buf));
+ i2c_read(fd, SHT20_I2CADDR, buf, 3);
+ log_dump(LOG_LEVEL_DEBUG, "Relative humidity sample data: ", buf, 3);
+
+ if( !sht20_checksum(buf, 2, buf[2]) )
+ {
+ rv = -4;
+ log_error("Relative humidity sample data CRC checksum failure.\n");
+ goto cleanup;
+ }
+
+ *rh = 125 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 6;
+
+ /*+--------------------------------+
+ *| print the measure result |
+ *+--------------------------------+*/
+
+ log_debug("Temperature=%lf 'C relative humidity=%lf%%\n", *temp, *rh);
+
+cleanup:
+ close(fd);
+ return rv;
+}
+
+int sht20_checksum(uint8_t *data, int len, int8_t checksum)
+{
+ int8_t crc = 0;
+ int8_t bit;
+ int8_t byteCtr;
+
+ //calculates 8-Bit checksum with given polynomial: x^8 + x^5 + x^4 + 1
+ for (byteCtr = 0; byteCtr < len; ++byteCtr)
+ {
+ crc ^= (data[byteCtr]);
+ for ( bit = 8; bit > 0; --bit)
+ {
+ /* x^8 + x^5 + x^4 + 1 = 0001 0011 0001 = 0x131 */
+ if (crc & 0x80) crc = (crc << 1) ^ 0x131;
+ else crc = (crc << 1);
+ }
+ }
+
+ if (crc != checksum)
+ return 0;
+ else
+ return 1;
+}
+
+int i2c_write(int fd, uint8_t slave_addr, uint8_t *data, int len)
+{
+ struct i2c_rdwr_ioctl_data i2cdata;
+ int rv = 0;
+
+ if( !data || len<= 0)
+ {
+ log_error("%s() invalid input arguments!\n", __func__);
+ return -1;
+ }
+
+ i2cdata.nmsgs = 1;
+ i2cdata.msgs = malloc( sizeof(struct i2c_msg)*i2cdata.nmsgs );
+ if ( !i2cdata.msgs )
+ {
+ log_error("%s() msgs malloc failed!\n", __func__);
+ return -2;
+ }
+
+ i2cdata.msgs[0].addr = slave_addr;
+ i2cdata.msgs[0].flags = 0; //write
+ i2cdata.msgs[0].len = len;
+ i2cdata.msgs[0].buf = malloc(len);
+ if( !i2cdata.msgs[0].buf )
+ {
+ log_error("%s() msgs malloc failed!\n", __func__);
+ rv = -3;
+ goto cleanup;
+ }
+ memcpy(i2cdata.msgs[0].buf, data, len);
+
+
+ if( ioctl(fd, I2C_RDWR, &i2cdata)<0 )
+ {
+ log_error("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ rv = -4;
+ goto cleanup;
+ }
+
+cleanup:
+ if( i2cdata.msgs[0].buf )
+ free(i2cdata.msgs[0].buf);
+
+ if( i2cdata.msgs )
+ free(i2cdata.msgs);
+
+ return rv;
+}
+
+int i2c_read(int fd, uint8_t slave_addr, uint8_t *buf, int size)
+{
+ struct i2c_rdwr_ioctl_data i2cdata;
+ int rv = 0;
+
+ if( !buf || size<= 0)
+ {
+ log_error("%s() invalid input arguments!\n", __func__);
+ return -1;
+ }
+
+ i2cdata.nmsgs = 1;
+ i2cdata.msgs = malloc( sizeof(struct i2c_msg)*i2cdata.nmsgs );
+ if ( !i2cdata.msgs )
+ {
+ log_error("%s() msgs malloc failed!\n", __func__);
+ return -2;
+ }
+
+ i2cdata.msgs[0].addr = slave_addr;
+ i2cdata.msgs[0].flags = I2C_M_RD; //read
+ i2cdata.msgs[0].len = size;
+ i2cdata.msgs[0].buf = buf;
+ memset(buf, 0, size);
+
+ if( ioctl(fd, I2C_RDWR, &i2cdata)<0 )
+ {
+ log_error("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ rv = -4;
+ }
+
+ free( i2cdata.msgs );
+ return rv;
+}
diff --git a/project/modules/sht20.h b/project/modules/sht20.h
new file mode 100644
index 0000000..6c9f9f9
--- /dev/null
+++ b/project/modules/sht20.h
@@ -0,0 +1,34 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: sht20.c
+ * Description: This file is temperature and relative humidity sensor SHT20 code
+ *
+ * Version: 1.0.0(10/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ * STH20 Module Raspberry Pi Board
+ * VCC <-----> #Pin1(3.3V)
+ * SDA <-----> #Pin3(SDA, BCM GPIO2)
+ * SCL <-----> #Pin5(SCL, BCM GPIO3)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ * dtoverlay=i2c1,pins_2_3
+ *
+ ********************************************************************************/
+
+
+#ifndef _SHT20_H_
+#define _SHT20_H_
+
+#define SHT20_I2CDEV "/dev/i2c-1"
+#define SHT20_I2CADDR 0x40
+
+extern int sht2x_get_temp_humidity(float *temp, float *rh);
+
+#endif /* ----- #ifndef _SHT20_H_ ----- */
+
diff --git a/project/modules/tsl2561.c b/project/modules/tsl2561.c
new file mode 100644
index 0000000..4148d74
--- /dev/null
+++ b/project/modules/tsl2561.c
@@ -0,0 +1,209 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: tsl2561.c
+ * Description: This file is the Lux sensor TSL2561 code
+ *
+ * Version: 1.0.0(10/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ * STH20 Module Raspberry Pi Board
+ * VCC <-----> #Pin1(3.3V)
+ * SDA0 <-----> #Pin27(SDA, BCM GPIO0)
+ * SCL0 <-----> #Pin28(SCL, BCM GPIO1)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ * dtoverlay=i2c0,pins_0_1
+ *
+ ********************************************************************************/
+
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+#include <time.h>
+#include <errno.h>
+#include <libgen.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "util_proc.h"
+#include "logger.h"
+#include "tsl2561.h"
+
+
+#define CONTROL_REG 0x80
+#define REG_COUNT 4
+
+#define POWER_UP 0x03
+#define POWER_DOWN 0x00
+
+#define OFF 0
+#define ON 1
+
+/* Register Address */
+enum
+{
+ /* Channel_0 = DATA0HIGH<<8 + DATA0LOW */
+ DATA0LOW = 0x8C,
+ DATA0HIGH,
+
+ /* Channel_1 = DATA1HIGH<<8 + DATA1LOW */
+ DATA1LOW,
+ DATA1HIGH,
+};
+
+static const int regs_addr[REG_COUNT]={DATA0LOW, DATA0HIGH, DATA1LOW, DATA1HIGH};
+
+void tsl2561_power(int fd, int cmd)
+{
+ struct i2c_msg msg;
+ struct i2c_rdwr_ioctl_data data;
+ unsigned char buf[2];
+
+ msg.addr= TSL2561_I2CADDR;
+ msg.flags=0; /* write */
+ msg.len= 1;
+ msg.buf= buf;
+
+ data.nmsgs= 1;
+ data.msgs= &msg;
+
+ msg.buf[0]=CONTROL_REG;
+ if( ioctl(fd, I2C_RDWR, &data) < 0 )
+ {
+ log_error("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ return ;
+ }
+
+
+ if( cmd )
+ msg.buf[0]=POWER_UP;
+ else
+ msg.buf[0]=POWER_DOWN;
+
+ if( ioctl(fd, I2C_RDWR, &data) < 0 )
+ {
+ log_error("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ return ;
+ }
+
+ return ;
+}
+
+int tsl2561_readreg(int fd, unsigned char regaddr, unsigned char *regval)
+{
+ struct i2c_msg msg;
+ struct i2c_rdwr_ioctl_data data;
+ unsigned char buf[2];
+
+ msg.addr= TSL2561_I2CADDR;
+ msg.flags=0; /* write */
+ msg.len= 1;
+ msg.buf= buf;
+ msg.buf[0] = regaddr;
+
+ data.nmsgs= 1;
+ data.msgs= &msg;
+
+ if( ioctl(fd, I2C_RDWR, &data) < 0 )
+ {
+ log_error("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ msg.addr= TSL2561_I2CADDR;
+ msg.flags=I2C_M_RD; /* read */
+ msg.len= 1;
+ msg.buf= buf;
+
+ data.nmsgs= 1;
+ data.msgs= &msg;
+
+ if( ioctl(fd, I2C_RDWR, &data) < 0 )
+ {
+ log_error("%s() ioctl failure: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ *regval = msg.buf[0];
+ return 0;
+}
+
+int tsl2561_get_lux(float *lux)
+{
+ int i, fd;
+ int rv = 0;
+ char *dev = TSL2561_I2CDEV;
+ float div = 0.0;
+
+ unsigned char reg_data[REG_COUNT];
+ unsigned char buf;
+ int chn0_data = 0;
+ int chn1_data = 0;
+
+ if( !lux )
+ {
+ log_error("Invalid input arguments\n");
+ return -1;
+ }
+
+ if( (fd=open(dev, O_RDWR)) < 0)
+ {
+ log_error("i2c device '%s' open failed: %s\n", dev, strerror(errno));
+ return -2;
+ }
+
+ tsl2561_power(fd, ON);
+
+ msleep(410); /* t(CONV) MAX 400ms */
+
+ /* Read register Channel0 and channel1 data from register */
+ for(i=0; i<REG_COUNT; i++)
+ {
+ tsl2561_readreg(fd, regs_addr[i], ®_data[i]);
+ }
+
+ chn0_data = reg_data[1]*256 + reg_data[0]; /* Channel0 = DATA0HIGH<<8 + DATA0LOW */
+ chn1_data = reg_data[3]*256 + reg_data[2]; /* channel1 = DATA1HIGH<<8 + DATA1LOW */
+
+ if( chn0_data<=0 || chn1_data<0 )
+ {
+ rv = -2;
+ goto OUT;
+ }
+
+ div = (float)chn1_data / (float)chn0_data;
+
+ if( div>0 && div<=0.5 )
+ *lux = 0.304*chn0_data-0.062*chn0_data*pow(div,1.4);
+
+ else if( div>0.5 && div<=0.61 )
+ *lux = 0.0224*chn0_data-0.031*chn1_data;
+
+ else if( div>0.61 && div<=0.8 )
+ *lux = 0.0128*chn0_data-0.0153*chn1_data;
+
+ else if( div>0.8 && div<=1.3 )
+ *lux = 0.00146*chn0_data-0.00112*chn1_data;
+
+ else if( div>1.3 )
+ *lux = 0.0;
+
+OUT:
+ tsl2561_power(fd, OFF);
+ return rv;
+}
diff --git a/project/modules/tsl2561.h b/project/modules/tsl2561.h
new file mode 100644
index 0000000..b73ff4a
--- /dev/null
+++ b/project/modules/tsl2561.h
@@ -0,0 +1,35 @@
+/*********************************************************************************
+ * Copyright: (C) 2023 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: tsl2561.h
+ * Description: This file is the Lux sensor TSL2561 code
+ *
+ * Version: 1.0.0(10/08/23)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "10/08/23 17:52:00"
+ *
+ * Pin connection:
+ * STH20 Module Raspberry Pi Board
+ * VCC <-----> #Pin1(3.3V)
+ * SDA0 <-----> #Pin27(SDA, BCM GPIO0)
+ * SCL0 <-----> #Pin28(SCL, BCM GPIO1)
+ * GND <-----> GND
+ *
+ * /boot/config.txt:
+ * dtoverlay=i2c0,pins_0_1
+ *
+ ********************************************************************************/
+
+
+#ifndef _TSL2561_H_
+#define _TSL2561_H_
+
+#define TSL2561_I2CDEV "/dev/i2c-0"
+#define TSL2561_I2CADDR 0x39
+
+extern int tsl2561_get_lux(float *lux);
+
+#endif /* ----- #ifndef _TSL2561_H_ ----- */
+
+
diff --git a/project/mosquitto/.gitignore b/project/mosquitto/.gitignore
new file mode 100644
index 0000000..d2ecef2
--- /dev/null
+++ b/project/mosquitto/.gitignore
@@ -0,0 +1,9 @@
+# git ignore files/folders in the list
+
+# ignore folders
+install/
+
+# ignore files
+*.so*
+*.o
+*.a
diff --git a/project/mosquitto/conf.c b/project/mosquitto/conf.c
new file mode 100644
index 0000000..4c0ee20
--- /dev/null
+++ b/project/mosquitto/conf.c
@@ -0,0 +1,200 @@
+/*********************************************************************************
+ * Copyright: (C) 2019 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: conf.c
+ * Description: This file is mqttd configure file parser function
+ *
+ * Version: 1.0.0(2019年06月25日)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "2019年06月25日 22时23分55秒"
+ *
+ ********************************************************************************/
+#include "conf.h"
+#include "logger.h"
+#include "iniparser.h"
+
+
+int mqttd_parser_conf(const char *conf_file, mqtt_ctx_t *ctx, int debug)
+{
+ dictionary *ini;
+ const char *str;
+ int val;
+ int rv = 0;
+
+ if( !conf_file || !ctx )
+ {
+ fprintf(stderr, "%s() Invalid input arguments\n", __func__);
+ return -1;
+ }
+
+ memset(ctx, 0, sizeof(*ctx));
+
+ ini = iniparser_load(conf_file);
+ if( !ini )
+ {
+ fprintf(stderr, "ERROR: Configure file '%s' load failed\n", conf_file);
+ return -2;
+ }
+
+ /*+------------------------------------------------------+
+ *| parser logger settings and start logger system |
+ *+------------------------------------------------------+*/
+ if( !debug )
+ {
+ str = iniparser_getstring(ini, "logger:file", "/tmp/mqttd.log");
+ strncpy(ctx->logfile, str, sizeof(ctx->logfile));
+ ctx->logsize = iniparser_getint(ini, "logger:size", 1024);
+ ctx->loglevel = iniparser_getint(ini, "logger:level", LOG_LEVEL_INFO);
+ }
+ else
+ {
+ strncpy(ctx->logfile, "console", sizeof(ctx->logfile));
+ ctx->loglevel = LOG_LEVEL_DEBUG;
+ ctx->logsize = 0;
+ }
+
+ if( log_open(ctx->logfile, ctx->loglevel, ctx->logsize, LOG_LOCK_DISABLE) < 0 )
+ {
+ fprintf(stderr, "Logger system initialise failure\n");
+ return -2;
+ }
+
+ log_info("Logger system initialise ok\n");
+
+
+ /*+------------------------------------------------------+
+ *| parser production ID |
+ *+------------------------------------------------------+*/
+
+ if( !(str=iniparser_getstring(ini, "common:devid", NULL)) )
+ {
+ log_error("ERROR: Parser device ID failure\n");
+ rv = -3;
+ goto cleanup;
+ }
+ /* cJSON parser ID will get "" */
+ snprintf(ctx->devid, sizeof(ctx->devid), "\"%s\"", str);
+ log_info("Parser device ID [%s]\n", ctx->devid);
+
+
+ /*+------------------------------------------------------+
+ *| parser hardware module configuration |
+ *+------------------------------------------------------+*/
+
+ /* relay */
+ ctx->hwconf.relay=iniparser_getint(ini, "hardware:relay", 0);
+ if( !ctx->hwconf.relay )
+ log_warn("Parser relay module disabled\n");
+ else
+ log_info("Parser relay module enabled\n");
+
+ /* RGB 3-colors LED */
+ ctx->hwconf.led=iniparser_getint(ini, "hardware:rgbled", 0);
+ if( !ctx->hwconf.led )
+ log_warn("Parser RGB 3-colors Led module disabled\n");
+ else
+ log_info("Parser RGB 3-colors Led module enabled\n");
+
+ /* beeper */
+ ctx->hwconf.beeper=iniparser_getint(ini, "hardware:beep", 0);
+ if( !ctx->hwconf.beeper )
+ log_warn("Parser beeper module disabled\n");
+ else
+ log_info("Parser beeper module enabled\n");
+
+ /* DS18B20 temperature module */
+ ctx->hwconf.ds18b20=iniparser_getint(ini, "hardware:ds18b20", 0);
+ if( !ctx->hwconf.ds18b20 )
+ log_warn("Parser DS18B20 temperature module disabled\n");
+ else
+ log_info("Parser DS18B20 temperature module enabled\n");
+
+ /* SHT20 temperature and hummidity module */
+ ctx->hwconf.sht2x=iniparser_getint(ini, "hardware:sht2x", 0);
+ if( !ctx->hwconf.sht2x )
+ log_warn("Parser SHT2X temperature and hummidity module disabled\n");
+ else
+ log_info("Parser SHT2X temperature and hummidity module enabled\n");
+
+ /* TSL2561 light intensity sensor module */
+ ctx->hwconf.tsl2561=iniparser_getint(ini, "hardware:tsl2561", 0);
+ if( !ctx->hwconf.tsl2561 )
+ log_warn("Parser TSL2561 light intensity sensor module disabled\n");
+ else
+ log_info("Parser TSL2561 light intensity sensor module enabled\n");
+
+ /*+------------------------------------------------------+
+ *| parser broker settings |
+ *+------------------------------------------------------+*/
+
+ if( !(str=iniparser_getstring(ini, "broker:hostname", NULL)) )
+ {
+ log_error("ERROR: Parser MQTT broker server hostname failure\n");
+ rv = -4;
+ goto cleanup;
+ }
+ strncpy(ctx->host, str, sizeof(ctx->host) );
+
+ if( (val=iniparser_getint(ini, "broker:port", -1)) < 0 )
+ {
+ log_error("ERROR: Parser MQTT broker server port failure\n");
+ rv = -5;
+ goto cleanup;
+ }
+ ctx->port = val;
+ log_info("Parser MQTT broker server [%s:%d]\n", ctx->host, ctx->port);
+
+ str=iniparser_getstring(ini, "broker:username", NULL);
+ strncpy(ctx->uid, str, sizeof(ctx->uid) );
+
+ str=iniparser_getstring(ini, "broker:password", NULL);
+ strncpy(ctx->pwd, str, sizeof(ctx->pwd) );
+
+ if( ctx->uid && ctx->pwd )
+ log_info("Parser broker author by [%s:%s]\n", ctx->uid, ctx->pwd);
+
+ ctx->keepalive = iniparser_getint(ini, "broker:keepalive", DEF_KEEPALIVE);
+ log_info("Parser broker keepalive timeout [%d] seconds\n", ctx->keepalive);
+
+ /*+------------------------------------------------------+
+ *| parser publisher settings |
+ *+------------------------------------------------------+*/
+
+ if( !(str=iniparser_getstring(ini, "publisher:pubTopic", NULL)) )
+ {
+ log_error("ERROR: Parser MQTT broker publisher topic failure\n");
+ rv = -6;
+ goto cleanup;
+ }
+ strncpy(ctx->pubTopic, str, sizeof(ctx->pubTopic) );
+
+ ctx->pubQos = iniparser_getint(ini, "publisher:pubQos", DEF_QOS);
+ ctx->interval = iniparser_getint(ini, "publisher:interval", DEF_PUBINTERVAL);
+ log_info("Parser publisher topic \"%s\" with Qos[%d] interval[%d]\n", ctx->pubTopic, ctx->pubQos, ctx->interval);
+
+ /*+------------------------------------------------------+
+ *| parser subscriber settings |
+ *+------------------------------------------------------+*/
+
+ if( !(str=iniparser_getstring(ini, "subsciber:subTopic", NULL)) )
+ {
+ log_error("ERROR: Parser MQTT broker publisher topic failure\n");
+ rv = -7;
+ goto cleanup;
+ }
+ strncpy(ctx->subTopic, str, sizeof(ctx->subTopic) );
+
+ ctx->subQos = iniparser_getint(ini, "subsciber:subQos", DEF_QOS);
+ log_info("Parser subscriber topic \"%s\" with Qos[%d]\n", ctx->subTopic, ctx->subQos);
+
+cleanup:
+ if( ini )
+ iniparser_freedict(ini);
+
+ if( rv )
+ log_close();
+
+ return rv;
+}
+
diff --git a/project/mosquitto/conf.h b/project/mosquitto/conf.h
new file mode 100644
index 0000000..89d3a66
--- /dev/null
+++ b/project/mosquitto/conf.h
@@ -0,0 +1,71 @@
+/*********************************************************************************
+ * Copyright: (C) 2019 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: conf.h
+ * Description: This file is mqttd configure file parser function
+ *
+ * Version: 1.0.0(2019年06月25日)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "2019年06月25日 22时23分55秒"
+ *
+ ********************************************************************************/
+#ifndef __CONF_H_
+#define __CONF_H_
+
+enum
+{
+ Qos0, /* 发送者只发送一次消息,不进行重试,Broker不会返回确认消息。在Qos0情况下,Broker可能没有接受到消息 */
+ Qos1, /* 发送者最少发送一次消息,确保消息到达Broker,Broker需要返回确认消息PUBACK。在Qos1情况下,Broker可能接受到重复消息 */
+ Qos2, /* Qos2使用两阶段确认来保证消息的不丢失和不重复。在Qos2情况下,Broker肯定会收到消息,且只收到一次 */
+};
+
+#define DEF_KEEPALIVE 30
+#define DEF_PUBINTERVAL 120
+#define DEF_QOS Qos2
+
+typedef struct hwconf_s
+{
+ int relay; /* relay aviable or not. 0:Disable 1: Enable */
+ int led; /* RGB led aviable or not. 0:Disable 1: Enable */
+ int beeper; /* beeper aviable or not. 0:Disable 1: Enable */
+ int ds18b20; /* DS1B820 aviable or not. 0:Disable 1: Enable */
+ int sht2x; /* SHT20 aviable or not. 0:Disable 1: Enable */
+ int tsl2561; /* TSL2561 aviable or not. 0:Disable 1: Enable */
+} hwconf_t;
+
+
+typedef struct mqtt_ctx_s
+{
+ char devid[32]; /* device ID */
+
+ /* hardware configuration */
+ hwconf_t hwconf;
+
+ /* logger settings */
+ char logfile[128]; /* logger record file */
+ int loglevel; /* logger level */
+ int logsize; /* logger file maxsize, oversize will rollback */
+
+ /* Broker settings */
+ char host[128]; /* MQTT broker server name */
+ int port; /* MQTT broker listen port */
+ char uid[64]; /* username */
+ char pwd[64]; /* password */
+ int keepalive; /* MQTT broker send PING message to subsciber/publisher keepalive timeout<seconds> */
+
+ /* Publisher settings */
+ char pubTopic[256]; /* Publisher topic */
+ int pubQos; /* Publisher Qos */
+ int interval ; /* Publish sensor data interval time, unit seconds */
+
+ /* Subscriber settings */
+ char subTopic[256]; /* Subscriber topic */
+ int subQos; /* Subscriber Qos */
+} mqtt_ctx_t;
+
+
+extern int mqttd_parser_conf(const char *conf_file, mqtt_ctx_t *ctx, int debug);
+
+#endif /* ----- #ifndef _CONF_H_ ----- */
+
diff --git a/project/mosquitto/etc/mqttd.conf b/project/mosquitto/etc/mqttd.conf
new file mode 100644
index 0000000..50fcffa
--- /dev/null
+++ b/project/mosquitto/etc/mqttd.conf
@@ -0,0 +1,69 @@
+[common]
+devid="RPi3B#01"
+
+# 树莓派连接的外设信息,0:禁用或未连接 其他: 使能或相关硬件连接的Pin管脚(wPI模式)
+[hardware]
+
+# 是否使能继电器模块,0:禁用 1:使能
+relay=1
+
+# 是否使能 PWM 蜂鸣器模块,0:禁用 N:Beep 次数
+beep=3
+
+# 是否使能 RGB 三色灯模块,0:禁用 1:使能
+rgbled=1
+
+# 是否使能 DS18b20 温度传感器模块,0:禁用 1:使能
+ds18b20=1
+
+# 是否使能 SHT2X 温湿度传感器模块,0:禁用 1:使能
+sht2x=1
+
+# 是否使能 TSL2561 光强传感器模块,0:禁用 1:使能
+lux=1
+
+[logger]
+
+# 日志记录文件
+file=/tmp/mqttd.log
+
+# 日志级别: 0:ERROR 1:WARN 2:INFO 3:DEBUG 4:TRACE
+level=2
+
+# 日志回滚大小
+size=1024
+
+
+[broker]
+
+# broker 服务器地址和端口号
+hostname="main.iot-yun.club"
+port=10883
+
+# broker 认证连接的用户名和密码
+username="lingyun"
+password="lingyun"
+
+# broker给subsciber和publisher发送PING报文保持 keepalive 的时间周期,单位是秒
+keepalive=30
+
+# Qos0: 发送者只发送一次消息,不进行重试,Broker不会返回确认消息。在Qos0情况下,Broker可能没有接受到消息
+# Qos1: 发送者最少发送一次消息,确保消息到达Broker,Broker需要返回确认消息PUBACK。在Qos1情况下,Broker可能接受到重复消息
+# Qos2: Qos2使用两阶段确认来保证消息的不丢失和不重复。在Qos2情况下,Broker肯定会收到消息,且只收到一次
+
+[publisher]
+
+# mosquitto_sub -h main.iot-yun.club -p 10883 -u lingyun -P lingyun -t \$Sys/Studio/Uplink
+pubTopic="$Sys/Studio/Uplink"
+pubQos=0
+
+# Publisher上报传感器数据的周期,单位是秒
+interval=60
+
+[subsciber]
+
+# mosquitto_pub -h main.iot-yun.club -p 10883 -u lingyun -P lingyun -t \$Sys/Studio/Downlink -m '{"id":"RPi3B#01", "light":"on", "buzzer":"on"}'
+# mosquitto_pub -h main.iot-yun.club -p 10883 -u lingyun -P lingyun -t \$Sys/Studio/Downlink -m '{"id":"RPi3B#01", "leds":[{"red":"on","green":"off","blue":"on"}]}'
+subTopic="$Sys/Studio/Downlink"
+subQos=0
+
diff --git a/project/mosquitto/main.c b/project/mosquitto/main.c
new file mode 100644
index 0000000..31d8fd7
--- /dev/null
+++ b/project/mosquitto/main.c
@@ -0,0 +1,458 @@
+/*********************************************************************************
+ * Copyright: (C) 2019 LingYun IoT System Studio
+ * All rights reserved.
+ *
+ * Filename: main.c
+ * Description: This file
+ *
+ * Version: 1.0.0(29/01/19)
+ * Author: Guo Wenxue <guowenxue@gmail.com>
+ * ChangeLog: 1, Release initial version on "29/01/19 15:34:41"
+ *
+ ********************************************************************************/
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <string.h>
+#include <errno.h>
+
+#include <mosquitto.h>
+#include <cjson/cJSON.h>
+
+#include "logger.h"
+#include "util_proc.h"
+#include "modules.h"
+#include "conf.h"
+
+#define PROG_VERSION "v1.0.0"
+#define DAEMON_PIDFILE "/tmp/.mqtt.pid"
+
+void *mqtt_sub_worker(void *args);
+void *mqtt_pub_worker(void *args);
+
+static void program_usage(char *progname)
+{
+
+ printf("Usage: %s [OPTION]...\n", progname);
+ printf(" %s is LingYun studio MQTT daemon program running on RaspberryPi\n", progname);
+
+ printf("\nMandatory arguments to long options are mandatory for short options too:\n");
+ printf(" -d[debug ] Running in debug mode\n");
+ printf(" -c[conf ] Specify configure file\n");
+ printf(" -h[help ] Display this help information\n");
+ printf(" -v[version ] Display the program version\n");
+
+ printf("\n%s version %s\n", progname, PROG_VERSION);
+ return;
+}
+
+int main (int argc, char **argv)
+{
+ int daemon = 1;
+ pthread_t tid;
+ mqtt_ctx_t ctx;
+ char *conf_file=NULL;
+ int debug = 0;
+ int opt;
+ char *progname=NULL;
+
+ struct option long_options[] = {
+ {"conf", required_argument, NULL, 'c'},
+ {"debug", no_argument, NULL, 'd'},
+ {"version", no_argument, NULL, 'v'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ progname = (char *)basename(argv[0]);
+
+ /* Parser the command line parameters */
+ while ((opt = getopt_long(argc, argv, "c:dvh", long_options, NULL)) != -1)
+ {
+ switch (opt)
+ {
+ case 'c': /* Set configure file */
+ conf_file = optarg;
+ break;
+
+ case 'd': /* Set debug running */
+ daemon = 0;
+ debug = 1;
+ break;
+
+ case 'v': /* Get software version */
+ printf("%s version %s\n", progname, PROG_VERSION);
+ return 0;
+
+ case 'h': /* Get help information */
+ program_usage(progname);
+ return 0;
+
+ default:
+ break;
+ }
+
+ }
+
+ if( !conf_file )
+ debug = 1;
+
+ if( mqttd_parser_conf(conf_file, &ctx, debug)<0 )
+ {
+ fprintf(stderr, "Parser mqtted configure file failure\n");
+ return -2;
+ }
+
+ install_default_signal();
+
+ if( check_set_program_running(daemon, DAEMON_PIDFILE) < 0 )
+ goto cleanup;
+
+ mosquitto_lib_init();
+
+ if( thread_start(&tid, mqtt_sub_worker, &ctx ) < 0 )
+ {
+ log_error("Start MQTT subsciber worker thread failure\n");
+ goto cleanup;
+ }
+ log_info("Start MQTT subsciber worker thread ok\n");
+
+ if( thread_start(&tid, mqtt_pub_worker, &ctx) < 0 )
+ {
+ log_error("Start MQTT publisher worker thread failure\n");
+ goto cleanup;
+ }
+ log_info("Start MQTT publisher worker thread ok\n");
+
+ while( ! g_signal.stop )
+ {
+ msleep(1000);
+ }
+
+cleanup:
+ mosquitto_lib_cleanup();
+ log_close();
+
+ return 0;
+} /* ----- End of main() ----- */
+
+void pub_connect_callback(struct mosquitto *mosq, void *userdata, int result)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)userdata;
+ int rv = 0;
+ char msg[128];
+ float temp = 0.0; /* temperature */
+ float rh = 0.0; /* relative humidity */
+ int retain = 0;
+
+ if( result )
+ {
+ log_error("Publisher connect to broker server[%s:%d] failed, rv=%d\n", ctx->host, ctx->port, result);
+ return ;
+ }
+
+ log_info("Publisher connect to broker server[%s:%d] successfully\n", ctx->host, ctx->port);
+ log_debug("Publish topic '%s'\n", ctx->pubTopic);
+
+ if( ctx->hwconf.ds18b20 )
+ {
+ memset(msg, 0, sizeof(msg));
+
+ log_debug("DS18B20 temperature sensor enabled, start broadcast it\n");
+
+ if( ds18b20_get_temperature(&temp) < 0 )
+ snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"error\" }", ctx->devid);
+ else
+ snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"%.2f\" }", ctx->devid, temp);
+
+ rv = mosquitto_publish(mosq, NULL, ctx->pubTopic, strlen(msg), msg, ctx->pubQos, retain);
+ if( rv )
+ {
+ log_error("Publisher broadcast message '%s' failure: %d\n", msg, rv);
+ }
+ else
+ {
+ log_info("Publisher broadcast message '%s' ok\n", msg);
+ }
+ }
+
+ if( ctx->hwconf.sht2x )
+ {
+ memset(msg, 0, sizeof(msg));
+
+ log_debug("SHT2X temperature and humidity sensor enabled, start broadcast it\n");
+
+ if( sht2x_get_temp_humidity(&temp, &rh) < 0 )
+ snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"error\", \"RH\":\"error\" }", ctx->devid);
+ else
+ snprintf(msg, sizeof(msg), "{ \"id\":%s, \"temp\":\"%.2f\", \"RH\":\"%.2f\" }", ctx->devid, temp, rh);
+
+ rv = mosquitto_publish(mosq, NULL, ctx->pubTopic, strlen(msg), msg, ctx->pubQos, retain);
+ if( rv )
+ {
+ log_error("Publisher broadcast message '%s' failure: %d\n", msg, rv);
+ }
+ else
+ {
+ log_info("Publisher broadcast message '%s' ok\n", msg);
+ }
+ }
+
+ log_info("Publisher broadcast over and disconnect broker now\n", ctx->host, ctx->port);
+ mosquitto_disconnect(mosq);
+
+ return ;
+}
+
+
+void *mqtt_pub_worker(void *args)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)args;
+ struct mosquitto *mosq;
+ bool session = true;
+
+
+ mosq = mosquitto_new(NULL, session, ctx);
+ if( !mosq )
+ {
+ log_error("mosquitto_new failure\n");
+ return NULL;
+ }
+
+ /* set connnect to broker username and password */
+ if( strlen(ctx->uid)> 0 && strlen(ctx->pwd)> 0 )
+ mosquitto_username_pw_set(mosq, ctx->uid, ctx->pwd);
+
+ /* set callback functions */
+ mosquitto_connect_callback_set(mosq, pub_connect_callback);
+
+ while( !g_signal.stop )
+ {
+ /* connect to MQTT broker */
+ if( mosquitto_connect(mosq, ctx->host, ctx->port, ctx->keepalive) )
+ {
+ log_error("Publisher connect to broker[%s:%d] failure: %s\n", ctx->host, ctx->port, strerror(errno));
+ msleep(1000);
+ continue;
+ }
+
+ /* -1: use default timeout 1000ms 1: unused */
+ mosquitto_loop_forever(mosq, -1, 1);
+
+ /* Publisher broadcast sensors data message interval time, unit seconds */
+ sleep( ctx->interval );
+ }
+
+ mosquitto_destroy(mosq);
+ return NULL;
+}
+
+void sub_connect_callback(struct mosquitto *mosq, void *userdata, int result)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)userdata;
+
+ if( result )
+ {
+ log_error("Subscriber connect to broker server failed, rv=%d\n", result);
+ return ;
+ }
+
+ log_info("Subscriber connect to broker server[%s:%d] successfully\n", ctx->host, ctx->port);
+ mosquitto_subscribe(mosq, NULL, ctx->subTopic, ctx->subQos);
+}
+
+void sub_disconnect_callback(struct mosquitto *mosq, void *userdata, int result)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)userdata;
+
+ log_warn("Subscriber disconnect to broker server[%s:%d], reason=%d\n", ctx->host, ctx->port, result);
+}
+
+static inline void mqtt_turn_led(int which, char *cmd)
+{
+ if( strcasestr(cmd, "on") )
+ turn_led(which, ON);
+ else if( strcasestr(cmd, "off") )
+ turn_led(which, OFF);
+}
+
+void proc_json_items(cJSON *root, mqtt_ctx_t *ctx)
+{
+ int i;
+ char *value;
+ cJSON *item;
+ cJSON *array;
+ hwconf_t *hwconf = &ctx->hwconf;
+
+ if( !root )
+ {
+ log_error("Invalid input arguments $root\n");
+ return ;
+ }
+
+ for( i=0; i<cJSON_GetArraySize(root); i++ )
+ {
+ item = cJSON_GetArrayItem(root, i);
+ if( !item )
+ break;
+
+ /* if item is cJSON_Object, then recursive call proc_json */
+ if( cJSON_Object == item->type )
+ {
+ proc_json_items(item, ctx);
+ }
+ else if( cJSON_Array == item->type )
+ {
+ /* RGB colors led control: {"id":"RPi3B#01", "leds":[{"red":"on","green":"off","blue":"on"}]} */
+ if( hwconf->led && !strcasecmp(item->string, "leds") )
+ {
+ array = cJSON_GetArrayItem(item, 0);
+ if( NULL != array )
+ {
+ cJSON *led_item;
+
+ if( NULL != (led_item=cJSON_GetObjectItem(array , "red")) )
+ {
+ log_info("turn red led '%s'\n", led_item->valuestring);
+ mqtt_turn_led(LED_R, led_item->valuestring);
+ }
+
+ if( NULL != (led_item=cJSON_GetObjectItem(array , "green")) )
+ {
+ log_info("turn green led '%s'\n", led_item->valuestring);
+ mqtt_turn_led(LED_G, led_item->valuestring);
+ }
+
+ if( NULL != (led_item=cJSON_GetObjectItem(array , "blue")) )
+ {
+ log_info("turn blue led '%s'\n", led_item->valuestring);
+ mqtt_turn_led(LED_B, led_item->valuestring);
+ }
+ }
+ }
+ }
+ else
+ {
+ value = cJSON_Print(item);
+
+ /* light controled by relay: {"id":"RPi3B#01", "light":"on"} */
+ if( hwconf->relay && !strcasecmp(item->string, "light") )
+ {
+ if( strcasestr(value, "on") )
+ {
+ log_info("Turn light on\n");
+ turn_relay(RELAY1, ON);
+ }
+ else if( strcasestr(value, "off") )
+ {
+ log_info("Turn light off\n");
+ turn_relay(RELAY1, OFF);
+ }
+ }
+
+ /* buzzer controled : {"id":"RPi3B#01", "buzzer":"on"} */
+ if( hwconf->beeper && !strcasecmp(item->string, "buzzer") )
+ {
+ if( strcasestr(value, "on") )
+ {
+ turn_beep(3);
+ }
+ }
+
+ free(value); /* must free it, or it will result memory leak */
+ }
+ }
+
+}
+
+void sub_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)userdata;
+
+ cJSON *root = NULL;
+ cJSON *item;
+ char *value;
+
+ if ( !message->payloadlen )
+ {
+ log_error("%s (null)\n", message->topic);
+ return ;
+ }
+
+ log_debug("Subscriber receive message: '%s'\n", message->payload);
+
+ root = cJSON_Parse(message->payload);
+ if( !root )
+ {
+ log_error("cJSON_Parse parser failure: %s\n", cJSON_GetErrorPtr());
+ return ;
+ }
+
+ item = cJSON_GetObjectItem(root, "id");
+ if( !item )
+ {
+ log_error("cJSON_Parse get ID failure: %s\n", cJSON_GetErrorPtr());
+ goto cleanup;
+ }
+
+ value = cJSON_PrintUnformatted(item);
+ if( strcasecmp(value, ctx->devid) )
+ {
+ free(value);
+ goto cleanup;
+ }
+
+ free(value);
+ log_info("Subscriber receive message: '%s'\n", message->payload);
+
+ proc_json_items(root, ctx);
+
+cleanup:
+ cJSON_Delete(root); /* must delete it, or it will result memory leak */
+ return ;
+}
+
+
+void *mqtt_sub_worker(void *args)
+{
+ mqtt_ctx_t *ctx = (mqtt_ctx_t *)args;
+ struct mosquitto *mosq;
+ bool session = true;
+
+ mosq = mosquitto_new(NULL, session, ctx);
+ if( !mosq )
+ {
+ log_error("mosquitto_new failure\n");
+ return NULL;
+ }
+
+ /* set connnect to broker username and password */
+ if( strlen(ctx->uid)> 0 && strlen(ctx->pwd)> 0 )
+ mosquitto_username_pw_set(mosq, ctx->uid, ctx->pwd);
+
+ /* set callback functions */
+ mosquitto_connect_callback_set(mosq, sub_connect_callback);
+ mosquitto_disconnect_callback_set(mosq, sub_disconnect_callback);
+ mosquitto_message_callback_set(mosq, sub_message_callback);
+
+ while( !g_signal.stop )
+ {
+ /* connect to MQTT broker */
+ if( mosquitto_connect(mosq, ctx->host, ctx->port, ctx->keepalive) )
+ {
+ log_error("Subscriber connect to broker[%s:%d] failure: %s\n", ctx->host, ctx->port, strerror(errno));
+ msleep(1000);
+ continue;
+ }
+
+ /* -1: use default timeout 1000ms 1: unused */
+ mosquitto_loop_forever(mosq, -1, 1);
+ }
+
+ mosquitto_destroy(mosq);
+ return NULL;
+}
+
diff --git a/project/mosquitto/makefile b/project/mosquitto/makefile
new file mode 100644
index 0000000..c716ebb
--- /dev/null
+++ b/project/mosquitto/makefile
@@ -0,0 +1,68 @@
+#********************************************************************************
+# Copyright: (C) 2023 LingYun IoT System Studio
+# All rights reserved.
+#
+# Filename: Makefile
+# Description: This file is the project top Makefie
+#
+# Version: 1.0.0(11/08/23)
+# Author: Guo Wenxue <guowenxue@gmail.com>
+# ChangeLog: 1, Release initial version on "11/08/23 16:18:43"
+#
+#*******************************************************************************
+
+PRJ_PATH=$(shell pwd)
+APP_NAME = mqttd
+
+BUILD_ARCH=$(shell uname -m)
+ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
+ CROSS_COMPILE=arm-linux-gnueabihf-
+endif
+
+# C source files in top-level directory
+SRCFILES = $(wildcard *.c)
+
+# common CFLAGS for our source code
+CFLAGS = -Wall -Wshadow -Wundef -Wmaybe-uninitialized -D_GNU_SOURCE
+
+# C source file in sub-directory
+SRCS=booster modules
+SRCS_PATH=$(patsubst %,${PRJ_PATH}/%,$(SRCS))
+CFLAGS+=$(patsubst %,-I%,$(SRCS_PATH))
+LDFLAGS+=$(patsubst %,-L%,$(SRCS_PATH))
+LIBS=$(patsubst %,-l%,$(SRCS))
+LDFLAGS+=${LIBS}
+
+# Open source libraries
+CFLAGS+=-I ${PRJ_PATH}/openlibs/install/include
+LDFLAGS+=-L ${PRJ_PATH}/openlibs/install/lib
+
+# libraries
+libs=openlibs ${SRCS}
+LDFLAGS+=-lmosquitto -lcjson -lssl -lcrypto -lgpiod
+
+LDFLAGS+=-lpthread
+
+all: entry subdir
+ ${CROSS_COMPILE}gcc ${CFLAGS} ${SRCFILES} -o ${APP_NAME} ${LDFLAGS}
+
+entry:
+ @echo "Building ${APP_NAME} on ${BUILD_ARCH}"
+
+subdir:
+ @for dir in ${libs} ; do if [ ! -e $${dir} ] ; then ln -s ../$${dir}; fi; done
+ @for dir in ${libs} ; do make -C $${dir} ; done
+
+install:
+ cp ${APP_NAME} /tftp
+
+clean:
+ @for dir in ${SRCS} ; do if [ -e $${dir} ] ; then make clean -C $${dir}; fi; done
+ @rm -f ${APP_NAME}
+
+distclean:
+ @for dir in ${libs} ; do if [ -e $${dir} ] ; then make clean -C $${dir}; fi; done
+ @for dir in ${libs} ; do if [ -e $${dir} ] ; then unlink $${dir}; fi; done
+ @rm -f ${APP_NAME}
+ @rm -f cscope.* tags
+
diff --git a/project/openlibs/cjson/build.sh b/project/openlibs/cjson/build.sh
new file mode 100755
index 0000000..1666b33
--- /dev/null
+++ b/project/openlibs/cjson/build.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+
+# library name and version
+# Official: https://github.com/DaveGamble/cJSON
+LIB_NAME=cJSON-1.7.15
+PACK_SUFIX=tar.gz
+
+# LingYun source code FTP server
+LY_FTP=http://main.iot-yun.club:2211/src/
+
+# library download URL address
+LIB_URL=$LY_FTP
+
+# Cross compiler for cross compile on Linux server
+CROSS_COMPILE=arm-linux-gnueabihf-
+
+# compile jobs
+JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
+
+# this project absolute path
+PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
+
+# top project absolute path
+TOP_PATH=$(realpath $PRJ_PATH/..)
+
+# binaries install path
+PREFIX_PATH=$TOP_PATH/install
+BIN_PATH=$PREFIX_PATH/bin
+LIB_PATH=$PREFIX_PATH/lib
+INC_PATH=$PREFIX_PATH/include
+
+# check installed or not file
+INST_FILE=$PREFIX_PATH/lib/libcjson.so
+
+# shell script will exit once get command error
+set -e
+
+#+-------------------------+
+#| Shell script functions |
+#+-------------------------+
+
+function pr_error() {
+ echo -e "\033[40;31m $1 \033[0m"
+}
+
+function pr_warn() {
+ echo -e "\033[40;33m $1 \033[0m"
+}
+
+function pr_info() {
+ echo -e "\033[40;32m $1 \033[0m"
+}
+
+function check_result()
+{
+ if [ $? != 0 ] ; then
+ pr_error $1
+ fi
+}
+# decompress a packet to destination path
+function do_unpack()
+{
+ tarball=$1
+ dstpath=`pwd`
+
+ if [[ $# == 2 ]] ; then
+ dstpath=$2
+ fi
+
+ pr_info "decompress $tarball => $dstpath"
+
+ mkdir -p $dstpath
+ case $tarball in
+ *.tar.gz)
+ tar -xzf $tarball -C $dstpath
+ ;;
+
+ *.tar.bz2)
+ tar -xjf $tarball -C $dstpath
+ ;;
+
+ *.tar.xz)
+ tar -xJf $tarball -C $dstpath
+ ;;
+
+ *.tar.zst)
+ tar -I zstd -xf $tarball -C $dstpath
+ ;;
+
+ *.tar)
+ tar -xf $tarball -C $dstpath
+ ;;
+
+ *.zip)
+ unzip -qo $tarball -d $dstpath
+ ;;
+
+ *)
+ pr_error "decompress Unsupport packet: $tarball"
+ return 1;
+ ;;
+ esac
+}
+
+function do_export()
+{
+ BUILD_ARCH=$(uname -m)
+ if [[ $BUILD_ARCH =~ "arm" ]] ; then
+ pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
+ return ;
+ fi
+
+ pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
+
+ # export cross toolchain
+ export CC=${CROSS_COMPILE}gcc
+ export CXX=${CROSS_COMPILE}g++
+ export AS=${CROSS_COMPILE}as
+ export AR=${CROSS_COMPILE}ar
+ export LD=${CROSS_COMPILE}ld
+ export NM=${CROSS_COMPILE}nm
+ export RANLIB=${CROSS_COMPILE}ranlib
+ export OBJDUMP=${CROSS_COMPILE}objdump
+ export STRIP=${CROSS_COMPILE}strip
+
+ # export cross configure
+ export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
+
+ # Clear LDFLAGS and CFLAGS
+ export LDFLAGS=
+ export CFLAGS=
+}
+
+function do_fetch()
+{
+ if [ -e ${INST_FILE} ] ; then
+ pr_warn "$LIB_NAME compile and installed alredy"
+ exit ;
+ fi
+
+ if [ -d $LIB_NAME ] ; then
+ pr_warn "$LIB_NAME fetch already"
+ return ;
+ fi
+
+ if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
+ wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
+ check_result "ERROR: download ${LIB_NAME} failure"
+ fi
+
+ do_unpack ${LIB_NAME}.${PACK_SUFIX}
+}
+
+function do_build()
+{
+ cd $LIB_NAME
+
+ do_export
+
+ sed -i "s|^CC =.*|CC = ${CROSS_COMPILE}gcc -std=c89|" Makefile
+
+ make && make PREFIX=$PREFIX_PATH install
+ check_result "ERROR: compile ${LIB_NAME} failure"
+
+ cp libcjson.a $PREFIX_PATH/lib
+}
+
+function do_clean()
+{
+ rm -rf *${LIB_NAME}*
+}
+
+if [[ $# == 1 && $1 == -c ]] ;then
+ pr_warn "start clean ${LIB_NAME}"
+ do_clean
+ exit;
+fi
+
+do_fetch
+
+do_build
+
diff --git a/project/openlibs/libevent/build.sh b/project/openlibs/libevent/build.sh
new file mode 100755
index 0000000..55e452d
--- /dev/null
+++ b/project/openlibs/libevent/build.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+
+# library name and version
+# Official: https://github.com/libevent/libevent/releases/download/
+LIB_NAME=libevent-2.1.11-stable
+PACK_SUFIX=tar.gz
+
+# LingYun source code FTP server
+LY_FTP=http://main.iot-yun.club:2211/src/
+
+# library download URL address
+LIB_URL=$LY_FTP
+
+# Cross compiler for cross compile on Linux server
+CROSS_COMPILE=arm-linux-gnueabihf-
+
+# compile jobs
+JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
+
+# this project absolute path
+PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
+
+# top project absolute path
+TOP_PATH=$(realpath $PRJ_PATH/..)
+
+# binaries install path
+PREFIX_PATH=$TOP_PATH/install
+BIN_PATH=$PREFIX_PATH/bin
+LIB_PATH=$PREFIX_PATH/lib
+INC_PATH=$PREFIX_PATH/include
+
+# check installed or not file
+INST_FILE=$PREFIX_PATH/lib/libevent.so
+
+# shell script will exit once get command error
+set -e
+
+#+-------------------------+
+#| Shell script functions |
+#+-------------------------+
+
+function pr_error() {
+ echo -e "\033[40;31m $1 \033[0m"
+}
+
+function pr_warn() {
+ echo -e "\033[40;33m $1 \033[0m"
+}
+
+function pr_info() {
+ echo -e "\033[40;32m $1 \033[0m"
+}
+
+function check_result()
+{
+ if [ $? != 0 ] ; then
+ pr_error $1
+ fi
+}
+# decompress a packet to destination path
+function do_unpack()
+{
+ tarball=$1
+ dstpath=`pwd`
+
+ if [[ $# == 2 ]] ; then
+ dstpath=$2
+ fi
+
+ pr_info "decompress $tarball => $dstpath"
+
+ mkdir -p $dstpath
+ case $tarball in
+ *.tar.gz)
+ tar -xzf $tarball -C $dstpath
+ ;;
+
+ *.tar.bz2)
+ tar -xjf $tarball -C $dstpath
+ ;;
+
+ *.tar.xz)
+ tar -xJf $tarball -C $dstpath
+ ;;
+
+ *.tar.zst)
+ tar -I zstd -xf $tarball -C $dstpath
+ ;;
+
+ *.tar)
+ tar -xf $tarball -C $dstpath
+ ;;
+
+ *.zip)
+ unzip -qo $tarball -d $dstpath
+ ;;
+
+ *)
+ pr_error "decompress Unsupport packet: $tarball"
+ return 1;
+ ;;
+ esac
+}
+
+function do_export()
+{
+ BUILD_ARCH=$(uname -m)
+ if [[ $BUILD_ARCH =~ "arm" ]] ; then
+ pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
+ return ;
+ fi
+
+ pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
+
+ # export cross toolchain
+ export CC=${CROSS_COMPILE}gcc
+ export CXX=${CROSS_COMPILE}g++
+ export AS=${CROSS_COMPILE}as
+ export AR=${CROSS_COMPILE}ar
+ export LD=${CROSS_COMPILE}ld
+ export NM=${CROSS_COMPILE}nm
+ export RANLIB=${CROSS_COMPILE}ranlib
+ export OBJDUMP=${CROSS_COMPILE}objdump
+ export STRIP=${CROSS_COMPILE}strip
+
+ # export cross configure
+ export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
+
+ # Clear LDFLAGS and CFLAGS
+ export LDFLAGS=
+ export CFLAGS=
+}
+
+function do_fetch()
+{
+ if [ -e ${INST_FILE} ] ; then
+ pr_warn "$LIB_NAME compile and installed alredy"
+ exit ;
+ fi
+
+ if [ -d $LIB_NAME ] ; then
+ pr_warn "$LIB_NAME fetch already"
+ return ;
+ fi
+
+ if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
+ wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
+ check_result "ERROR: download ${LIB_NAME} failure"
+ fi
+
+ do_unpack ${LIB_NAME}.${PACK_SUFIX}
+}
+
+function do_build()
+{
+ cd $LIB_NAME
+
+ do_export
+
+ ./configure --prefix=${PREFIX_PATH} ${CONFIG_CROSS} --enable-static \
+ --enable-thread-support --enable-openssl --enable-function-sections
+ check_result "ERROR: configure ${LIB_NAME} failure"
+
+ make && make install
+ check_result "ERROR: compile ${LIB_NAME} failure"
+}
+
+function do_clean()
+{
+ rm -rf *${LIB_NAME}*
+}
+
+if [[ $# == 1 && $1 == -c ]] ;then
+ pr_warn "start clean ${LIB_NAME}"
+ do_clean
+ exit;
+fi
+
+do_fetch
+
+do_build
+
diff --git a/project/openlibs/libgpiod/build.sh b/project/openlibs/libgpiod/build.sh
new file mode 100755
index 0000000..d703b78
--- /dev/null
+++ b/project/openlibs/libgpiod/build.sh
@@ -0,0 +1,185 @@
+#!/bin/bash
+
+# library name and version
+# Official: https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
+LIB_NAME=libgpiod-1.6.4
+PACK_SUFIX=tar.gz
+
+# LingYun source code FTP server
+LY_FTP=http://main.iot-yun.club:2211/src/
+
+# library download URL address
+LIB_URL=$LY_FTP
+
+# Cross compiler for cross compile on Linux server
+CROSS_COMPILE=arm-linux-gnueabihf-
+
+# compile jobs
+JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
+
+# this project absolute path
+PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
+
+# top project absolute path
+TOP_PATH=$(realpath $PRJ_PATH/..)
+
+# binaries install path
+PREFIX_PATH=$TOP_PATH/install
+BIN_PATH=$PREFIX_PATH/bin
+LIB_PATH=$PREFIX_PATH/lib
+INC_PATH=$PREFIX_PATH/include
+
+# check installed or not file
+INST_FILE=$PREFIX_PATH/lib/libgpiod.so
+
+# shell script will exit once get command error
+set -e
+
+#+-------------------------+
+#| Shell script functions |
+#+-------------------------+
+
+function pr_error() {
+ echo -e "\033[40;31m $1 \033[0m"
+}
+
+function pr_warn() {
+ echo -e "\033[40;33m $1 \033[0m"
+}
+
+function pr_info() {
+ echo -e "\033[40;32m $1 \033[0m"
+}
+
+function check_result()
+{
+ if [ $? != 0 ] ; then
+ pr_error $1
+ fi
+}
+# decompress a packet to destination path
+function do_unpack()
+{
+ tarball=$1
+ dstpath=`pwd`
+
+ if [[ $# == 2 ]] ; then
+ dstpath=$2
+ fi
+
+ pr_info "decompress $tarball => $dstpath"
+
+ mkdir -p $dstpath
+ case $tarball in
+ *.tar.gz)
+ tar -xzf $tarball -C $dstpath
+ ;;
+
+ *.tar.bz2)
+ tar -xjf $tarball -C $dstpath
+ ;;
+
+ *.tar.xz)
+ tar -xJf $tarball -C $dstpath
+ ;;
+
+ *.tar.zst)
+ tar -I zstd -xf $tarball -C $dstpath
+ ;;
+
+ *.tar)
+ tar -xf $tarball -C $dstpath
+ ;;
+
+ *.zip)
+ unzip -qo $tarball -d $dstpath
+ ;;
+
+ *)
+ pr_error "decompress Unsupport packet: $tarball"
+ return 1;
+ ;;
+ esac
+}
+
+function do_export()
+{
+ BUILD_ARCH=$(uname -m)
+ if [[ $BUILD_ARCH =~ "arm" ]] ; then
+ pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
+ return ;
+ fi
+
+ pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
+
+ # export cross toolchain
+ export CC=${CROSS_COMPILE}gcc
+ export CXX=${CROSS_COMPILE}g++
+ export AS=${CROSS_COMPILE}as
+ export AR=${CROSS_COMPILE}ar
+ export LD=${CROSS_COMPILE}ld
+ export NM=${CROSS_COMPILE}nm
+ export RANLIB=${CROSS_COMPILE}ranlib
+ export OBJDUMP=${CROSS_COMPILE}objdump
+ export STRIP=${CROSS_COMPILE}strip
+
+ # export cross configure
+ export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
+
+ # Clear LDFLAGS and CFLAGS
+ export LDFLAGS=
+ export CFLAGS=
+}
+
+function do_fetch()
+{
+ if [ -e ${INST_FILE} ] ; then
+ pr_warn "$LIB_NAME compile and installed alredy"
+ exit ;
+ fi
+
+ if [ -d $LIB_NAME ] ; then
+ pr_warn "$LIB_NAME fetch already"
+ return ;
+ fi
+
+ if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
+ wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
+ check_result "ERROR: download ${LIB_NAME} failure"
+ fi
+
+ do_unpack ${LIB_NAME}.${PACK_SUFIX}
+}
+
+function do_build()
+{
+ cd $LIB_NAME
+
+ ./autogen.sh
+
+ do_export
+
+ echo "ac_cv_func_malloc_0_nonnull=yes" > arm-linux.cache
+ ./configure --prefix=${PREFIX_PATH} ${CONFIG_CROSS} --enable-static \
+ --cache-file=arm-linux.cache --enable-tools
+ check_result "ERROR: configure ${LIB_NAME} failure"
+
+ make -j ${JOBS} && make install
+ check_result "ERROR: compile ${LIB_NAME} failure"
+}
+
+function do_clean()
+{
+ rm -rf *${LIB_NAME}*
+}
+
+if [[ $# == 1 && $1 == -c ]] ;then
+ pr_warn "start clean ${LIB_NAME}"
+ do_clean
+ exit;
+fi
+
+do_fetch
+
+do_build
+
diff --git a/project/openlibs/makefile b/project/openlibs/makefile
new file mode 100644
index 0000000..7f782e8
--- /dev/null
+++ b/project/openlibs/makefile
@@ -0,0 +1,10 @@
+
+PRJ_PATH=$(shell pwd)
+libs=libgpiod cjson openssl mosquitto libevent
+
+all:
+ for dir in ${libs} ; do cd ${PRJ_PATH}/$${dir} && ./build.sh ; done
+
+clean:
+ rm -rf install
+ for dir in ${libs} ; do cd ${PRJ_PATH}/$${dir} && ./build.sh -c ; done
diff --git a/project/openlibs/mosquitto/build.sh b/project/openlibs/mosquitto/build.sh
new file mode 100755
index 0000000..d42acea
--- /dev/null
+++ b/project/openlibs/mosquitto/build.sh
@@ -0,0 +1,188 @@
+#!/bin/bash
+
+# library name and version
+# Official: https://mosquitto.org/
+LIB_NAME=mosquitto-2.0.15
+PACK_SUFIX=tar.gz
+
+# LingYun source code FTP server
+LY_FTP=http://main.iot-yun.club:2211/src/
+
+# library download URL address
+LIB_URL=$LY_FTP
+
+# Cross compiler for cross compile on Linux server
+CROSS_COMPILE=arm-linux-gnueabihf-
+
+# compile jobs
+JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
+
+# this project absolute path
+PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
+
+# top project absolute path
+TOP_PATH=$(realpath $PRJ_PATH/..)
+
+# binaries install path
+PREFIX_PATH=$TOP_PATH/install
+BIN_PATH=$PREFIX_PATH/bin
+LIB_PATH=$PREFIX_PATH/lib
+INC_PATH=$PREFIX_PATH/include
+
+# check installed or not file
+INST_FILE=$PREFIX_PATH/bin/mosquitto
+
+# shell script will exit once get command error
+set -e
+
+#+-------------------------+
+#| Shell script functions |
+#+-------------------------+
+
+function pr_error() {
+ echo -e "\033[40;31m $1 \033[0m"
+}
+
+function pr_warn() {
+ echo -e "\033[40;33m $1 \033[0m"
+}
+
+function pr_info() {
+ echo -e "\033[40;32m $1 \033[0m"
+}
+
+function check_result()
+{
+ if [ $? != 0 ] ; then
+ pr_error $1
+ fi
+}
+# decompress a packet to destination path
+function do_unpack()
+{
+ tarball=$1
+ dstpath=`pwd`
+
+ if [[ $# == 2 ]] ; then
+ dstpath=$2
+ fi
+
+ pr_info "decompress $tarball => $dstpath"
+
+ mkdir -p $dstpath
+ case $tarball in
+ *.tar.gz)
+ tar -xzf $tarball -C $dstpath
+ ;;
+
+ *.tar.bz2)
+ tar -xjf $tarball -C $dstpath
+ ;;
+
+ *.tar.xz)
+ tar -xJf $tarball -C $dstpath
+ ;;
+
+ *.tar.zst)
+ tar -I zstd -xf $tarball -C $dstpath
+ ;;
+
+ *.tar)
+ tar -xf $tarball -C $dstpath
+ ;;
+
+ *.zip)
+ unzip -qo $tarball -d $dstpath
+ ;;
+
+ *)
+ pr_error "decompress Unsupport packet: $tarball"
+ return 1;
+ ;;
+ esac
+}
+
+function do_export()
+{
+ BUILD_ARCH=$(uname -m)
+ if [[ $BUILD_ARCH =~ "arm" ]] ; then
+ pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
+ return ;
+ fi
+
+ pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
+
+ # export cross toolchain
+ export CC=${CROSS_COMPILE}gcc
+ export CXX=${CROSS_COMPILE}g++
+ export AS=${CROSS_COMPILE}as
+ export AR=${CROSS_COMPILE}ar
+ export LD=${CROSS_COMPILE}ld
+ export NM=${CROSS_COMPILE}nm
+ export RANLIB=${CROSS_COMPILE}ranlib
+ export OBJDUMP=${CROSS_COMPILE}objdump
+ export STRIP=${CROSS_COMPILE}strip
+
+ # export cross configure
+ export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
+
+ # Clear LDFLAGS and CFLAGS
+ export LDFLAGS=
+ export CFLAGS=
+}
+
+function do_fetch()
+{
+ if [ -e ${INST_FILE} ] ; then
+ pr_warn "$LIB_NAME compile and installed alredy"
+ exit ;
+ fi
+
+ if [ -d $LIB_NAME ] ; then
+ pr_warn "$LIB_NAME fetch already"
+ return ;
+ fi
+
+ if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
+ wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
+ check_result "ERROR: download ${LIB_NAME} failure"
+ fi
+
+ do_unpack ${LIB_NAME}.${PACK_SUFIX}
+}
+
+function do_build()
+{
+ cd $LIB_NAME
+
+ do_export
+
+ export CFLAGS=-I${PREFIX_PATH}/include
+ export LDFLAGS="-L${PREFIX_PATH}/lib -lcrypto -lssl -ldl -lpthread"
+ export DESTDIR=${PREFIX_PATH}
+
+ make WITH_UUID=no WITH_STATIC_LIBRARIES=yes
+ check_result "ERROR: compile ${LIB_NAME} failure"
+
+ make DESTDIR=${PREFIX_PATH} prefix=/ install
+ check_result "ERROR: compile ${LIB_NAME} failure"
+
+ install -m 755 src/mosquitto $BIN_PATH
+ install -m 644 lib/libmosquitto.a $LIB_PATH
+}
+
+function do_clean()
+{
+ rm -rf *${LIB_NAME}*
+}
+
+if [[ $# == 1 && $1 == -c ]] ;then
+ pr_warn "start clean ${LIB_NAME}"
+ do_clean
+ exit;
+fi
+
+do_fetch
+
+do_build
+
diff --git a/project/openlibs/openssl/build.sh b/project/openlibs/openssl/build.sh
new file mode 100755
index 0000000..244da93
--- /dev/null
+++ b/project/openlibs/openssl/build.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+
+# library name and version
+# Official: https://www.openssl.org/
+LIB_NAME=openssl-1.1.1v
+PACK_SUFIX=tar.gz
+
+# LingYun source code FTP server
+LY_FTP=http://main.iot-yun.club:2211/src/
+
+# library download URL address
+LIB_URL=$LY_FTP
+
+# Cross compiler for cross compile on Linux server
+CROSS_COMPILE=arm-linux-gnueabihf-
+
+# compile jobs
+JOBS=`cat /proc/cpuinfo |grep "processor"|wc -l`
+
+# this project absolute path
+PRJ_PATH=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)
+
+# top project absolute path
+TOP_PATH=$(realpath $PRJ_PATH/..)
+
+# binaries install path
+PREFIX_PATH=$TOP_PATH/install
+BIN_PATH=$PREFIX_PATH/bin
+LIB_PATH=$PREFIX_PATH/lib
+INC_PATH=$PREFIX_PATH/include
+
+# check installed or not file
+INST_FILE=$PREFIX_PATH/bin/openssl
+
+# shell script will exit once get command error
+set -e
+
+#+-------------------------+
+#| Shell script functions |
+#+-------------------------+
+
+function pr_error() {
+ echo -e "\033[40;31m $1 \033[0m"
+}
+
+function pr_warn() {
+ echo -e "\033[40;33m $1 \033[0m"
+}
+
+function pr_info() {
+ echo -e "\033[40;32m $1 \033[0m"
+}
+
+function check_result()
+{
+ if [ $? != 0 ] ; then
+ pr_error $1
+ fi
+}
+# decompress a packet to destination path
+function do_unpack()
+{
+ tarball=$1
+ dstpath=`pwd`
+
+ if [[ $# == 2 ]] ; then
+ dstpath=$2
+ fi
+
+ pr_info "decompress $tarball => $dstpath"
+
+ mkdir -p $dstpath
+ case $tarball in
+ *.tar.gz)
+ tar -xzf $tarball -C $dstpath
+ ;;
+
+ *.tar.bz2)
+ tar -xjf $tarball -C $dstpath
+ ;;
+
+ *.tar.xz)
+ tar -xJf $tarball -C $dstpath
+ ;;
+
+ *.tar.zst)
+ tar -I zstd -xf $tarball -C $dstpath
+ ;;
+
+ *.tar)
+ tar -xf $tarball -C $dstpath
+ ;;
+
+ *.zip)
+ unzip -qo $tarball -d $dstpath
+ ;;
+
+ *)
+ pr_error "decompress Unsupport packet: $tarball"
+ return 1;
+ ;;
+ esac
+}
+
+function do_export()
+{
+ BUILD_ARCH=$(uname -m)
+ if [[ $BUILD_ARCH =~ "arm" ]] ; then
+ pr_warn "local($BUILD_ARCH) compile $LIB_NAME"
+ return ;
+ fi
+
+ pr_warn "cross(${CROSS_COMPILE}) compile $LIB_NAME"
+
+ # export cross toolchain
+ export CC=${CROSS_COMPILE}gcc
+ export CXX=${CROSS_COMPILE}g++
+ export AS=${CROSS_COMPILE}as
+ export AR=${CROSS_COMPILE}ar
+ export LD=${CROSS_COMPILE}ld
+ export NM=${CROSS_COMPILE}nm
+ export RANLIB=${CROSS_COMPILE}ranlib
+ export OBJDUMP=${CROSS_COMPILE}objdump
+ export STRIP=${CROSS_COMPILE}strip
+
+ # export cross configure
+ export CONFIG_CROSS=" --build=i686-pc-linux --host=arm-linux "
+
+ # Clear LDFLAGS and CFLAGS
+ export LDFLAGS=
+ export CFLAGS=
+}
+
+function do_fetch()
+{
+ if [ -e ${INST_FILE} ] ; then
+ pr_warn "$LIB_NAME compile and installed alredy"
+ exit ;
+ fi
+
+ if [ -d $LIB_NAME ] ; then
+ pr_warn "$LIB_NAME fetch already"
+ return ;
+ fi
+
+ if [ ! -f ${LIB_NAME}.${PACK_SUFIX} ] ; then
+ wget ${LIB_URL}/${LIB_NAME}.${PACK_SUFIX}
+ check_result "ERROR: download ${LIB_NAME} failure"
+ fi
+
+ do_unpack ${LIB_NAME}.${PACK_SUFIX}
+}
+
+function do_build()
+{
+ cd $LIB_NAME
+
+ do_export
+
+ CROSS_COMPILE=${CROSSTOOL} ./Configure linux-armv4 \
+ threads -shared -no-zlib --prefix=$PREFIX_PATH --openssldir=$PREFIX_PATH
+ check_result "ERROR: configure ${LIB_NAME} failure"
+
+ make -j ${JOBS} && make install
+ check_result "ERROR: compile ${LIB_NAME} failure"
+}
+
+function do_clean()
+{
+ rm -rf *${LIB_NAME}*
+}
+
+if [[ $# == 1 && $1 == -c ]] ;then
+ pr_warn "start clean ${LIB_NAME}"
+ do_clean
+ exit;
+fi
+
+do_fetch
+
+do_build
+
--
Gitblit v1.9.1