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