Add modules and MQTT project
Signed-off-by: guowenxue <guowenxue@gmail.com>
New file |
| | |
| | | # git ignore files/folders in the list |
| | | |
| | | # ignore folders |
| | | install/ |
| | | |
| | | # ignore files |
| | | *.so* |
| | | *.o |
| | | *.a |
| | | cscope.* |
| | | tags |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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 ; |
| | | } |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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 ; |
| | | } |
| | | |
New file |
| | |
| | | #******************************************************************************** |
| | | # 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} |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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 ; |
| | | } |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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 ; |
| | | } |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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 ; |
| | | } |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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 ; |
| | | } |
New file |
| | |
| | | ## booster |
| | | |
| | | LingYun embedded C program basic library |
| | | |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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_ ----- */ |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| | | |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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_ ----- */ |
| | | |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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 |
New file |
| | |
| | | /*-------------------------------------------------------------------------*/ |
| | | /** |
| | | @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 ; |
| | | } |
New file |
| | |
| | | |
| | | /*-------------------------------------------------------------------------*/ |
| | | /** |
| | | @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 |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| | | |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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_ ----- */ |
New file |
| | |
| | | |
| | | /*-------------------------------------------------------------------------*/ |
| | | /** |
| | | @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); |
| | | } |
New file |
| | |
| | | |
| | | /*-------------------------------------------------------------------------*/ |
| | | /** |
| | | @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 |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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 |
| | | |
| | | |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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); |
| | | |
| | | } |
| | | } |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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 |
New file |
| | |
| | | #******************************************************************************** |
| | | # 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 |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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)]; |
| | | } |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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_ ----- */ |
| | | |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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); |
| | | } |
| | | |
| | | |
New file |
| | |
| | | /******************************************************************************** |
| | | * 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 |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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; |
| | | } |
| | | |
New file |
| | |
| | | /********************************************************************************* |
| | | * 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_ ----- */ |
| | | |
New file |
| | |
| | | ## 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. |
| | | |
New file |
| | |
| | | /* |
| | | * 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; |
| | | } |
New file |
| | |
| | | /* |
| | | * 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_ */ |
New file |
| | |
| | | #******************************************************************************** |
| | | # 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 |
New file |
| | |
| | | ## 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. |
| | | |
New file |
| | |
| | | /* |
| | | * 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; |
| | | } |
| | | |
| | | /*-----------------------------------------------------------*/ |
New file |
| | |
| | | /* |
| | | * 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 */ |
New file |
| | |
| | | /* |
| | | * 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_ */ |
New file |
| | |
| | | /* |
| | | * 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_ */ |
New file |
| | |
| | | /* |
| | | * 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; |
| | | } |
| | | |
| | | /*-----------------------------------------------------------*/ |
New file |
| | |
| | | /* |
| | | * 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 */ |
New file |
| | |
| | | /* |
| | | * 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; |
| | | } |
| | | |
| | | /*-----------------------------------------------------------*/ |
New file |
| | |
| | | /* |
| | | * 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 */ |
project/coreMQTT/coreMQTT/makefile
project/coreMQTT/coreMQTT/transport_interface.h
project/coreMQTT/coreSNTP/README.md
project/coreMQTT/coreSNTP/core_sntp_client.c
project/coreMQTT/coreSNTP/core_sntp_client.h
project/coreMQTT/coreSNTP/core_sntp_config_defaults.h
project/coreMQTT/coreSNTP/core_sntp_serializer.c
project/coreMQTT/coreSNTP/core_sntp_serializer.h
project/coreMQTT/coreSNTP/makefile
project/coreMQTT/etc/mqttd.conf
project/coreMQTT/main.c
project/coreMQTT/makefile
project/gpsd/gpsd.c
project/gpsd/makefile
project/modules/ds18b20.c
project/modules/ds18b20.h
project/modules/leds.c
project/modules/leds.h
project/modules/makefile
project/modules/modules.h
project/modules/pwm.c
project/modules/pwm.h
project/modules/relay.c
project/modules/relay.h
project/modules/sht20.c
project/modules/sht20.h
project/modules/tsl2561.c
project/modules/tsl2561.h
project/mosquitto/.gitignore
project/mosquitto/conf.c
project/mosquitto/conf.h
project/mosquitto/etc/mqttd.conf
project/mosquitto/main.c
project/mosquitto/makefile
project/openlibs/cjson/build.sh
project/openlibs/libevent/build.sh
project/openlibs/libgpiod/build.sh
project/openlibs/makefile
project/openlibs/mosquitto/build.sh
project/openlibs/openssl/build.sh |