RaspberrPi project source code
guowenxue
2023-08-26 d6b4a750258b34c79e3c643595a0ae1cb0e18bed
Add modules and MQTT project

Signed-off-by: guowenxue <guowenxue@gmail.com>
85 files added
27671 ■■■■■ changed files
.gitignore 11 ●●●●● patch | view | raw | blame | history
modules/ds18b20.c 134 ●●●●● patch | view | raw | blame | history
modules/infrared.c 274 ●●●●● patch | view | raw | blame | history
modules/leds.c 260 ●●●●● patch | view | raw | blame | history
modules/makefile 34 ●●●●● patch | view | raw | blame | history
modules/pwm.c 311 ●●●●● patch | view | raw | blame | history
modules/relay.c 286 ●●●●● patch | view | raw | blame | history
modules/sht20.c 331 ●●●●● patch | view | raw | blame | history
modules/tsl2561.c 337 ●●●●● patch | view | raw | blame | history
project/booster/README.md 4 ●●●● patch | view | raw | blame | history
project/booster/at-esp32.c 388 ●●●●● patch | view | raw | blame | history
project/booster/at-esp32.h 140 ●●●●● patch | view | raw | blame | history
project/booster/atcmd.c 300 ●●●●● patch | view | raw | blame | history
project/booster/atcmd.h 93 ●●●●● patch | view | raw | blame | history
project/booster/comport.c 520 ●●●●● patch | view | raw | blame | history
project/booster/comport.h 74 ●●●●● patch | view | raw | blame | history
project/booster/dictionary.c 381 ●●●●● patch | view | raw | blame | history
project/booster/dictionary.h 174 ●●●●● patch | view | raw | blame | history
project/booster/esp32.c 166 ●●●●● patch | view | raw | blame | history
project/booster/esp32.h 35 ●●●●● patch | view | raw | blame | history
project/booster/iniparser.c 837 ●●●●● patch | view | raw | blame | history
project/booster/iniparser.h 359 ●●●●● patch | view | raw | blame | history
project/booster/list.h 723 ●●●●● patch | view | raw | blame | history
project/booster/logger.c 279 ●●●●● patch | view | raw | blame | history
project/booster/logger.h 68 ●●●●● patch | view | raw | blame | history
project/booster/makefile 35 ●●●●● patch | view | raw | blame | history
project/booster/ringbuf.c 107 ●●●●● patch | view | raw | blame | history
project/booster/ringbuf.h 57 ●●●●● patch | view | raw | blame | history
project/booster/util_proc.c 432 ●●●●● patch | view | raw | blame | history
project/booster/util_proc.h 89 ●●●●● patch | view | raw | blame | history
project/coreMQTT/conf.c 200 ●●●●● patch | view | raw | blame | history
project/coreMQTT/conf.h 71 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreJSON/README.md 65 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreJSON/core_json.c 1818 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreJSON/core_json.h 339 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreJSON/makefile 35 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/README.md 35 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/core_mqtt.c 3314 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/core_mqtt.h 1015 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/core_mqtt_config_defaults.h 204 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/core_mqtt_default_logging.h 132 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/core_mqtt_serializer.c 2684 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/core_mqtt_serializer.h 1306 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/core_mqtt_state.c 1214 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/core_mqtt_state.h 310 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/makefile 35 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreMQTT/transport_interface.h 316 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreSNTP/README.md 28 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreSNTP/core_sntp_client.c 959 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreSNTP/core_sntp_client.h 655 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreSNTP/core_sntp_config_defaults.h 144 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreSNTP/core_sntp_serializer.c 853 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreSNTP/core_sntp_serializer.h 535 ●●●●● patch | view | raw | blame | history
project/coreMQTT/coreSNTP/makefile 35 ●●●●● patch | view | raw | blame | history
project/coreMQTT/etc/mqttd.conf 69 ●●●●● patch | view | raw | blame | history
project/coreMQTT/main.c 458 ●●●●● patch | view | raw | blame | history
project/coreMQTT/makefile 69 ●●●●● patch | view | raw | blame | history
project/gpsd/gpsd.c 245 ●●●●● patch | view | raw | blame | history
project/gpsd/makefile 62 ●●●●● patch | view | raw | blame | history
project/modules/ds18b20.c 119 ●●●●● patch | view | raw | blame | history
project/modules/ds18b20.h 31 ●●●●● patch | view | raw | blame | history
project/modules/leds.c 162 ●●●●● patch | view | raw | blame | history
project/modules/leds.h 58 ●●●●● patch | view | raw | blame | history
project/modules/makefile 38 ●●●●● patch | view | raw | blame | history
project/modules/modules.h 24 ●●●●● patch | view | raw | blame | history
project/modules/pwm.c 213 ●●●●● patch | view | raw | blame | history
project/modules/pwm.h 44 ●●●●● patch | view | raw | blame | history
project/modules/relay.c 156 ●●●●● patch | view | raw | blame | history
project/modules/relay.h 56 ●●●●● patch | view | raw | blame | history
project/modules/sht20.c 244 ●●●●● patch | view | raw | blame | history
project/modules/sht20.h 34 ●●●●● patch | view | raw | blame | history
project/modules/tsl2561.c 209 ●●●●● patch | view | raw | blame | history
project/modules/tsl2561.h 35 ●●●●● patch | view | raw | blame | history
project/mosquitto/.gitignore 9 ●●●●● patch | view | raw | blame | history
project/mosquitto/conf.c 200 ●●●●● patch | view | raw | blame | history
project/mosquitto/conf.h 71 ●●●●● patch | view | raw | blame | history
project/mosquitto/etc/mqttd.conf 69 ●●●●● patch | view | raw | blame | history
project/mosquitto/main.c 458 ●●●●● patch | view | raw | blame | history
project/mosquitto/makefile 68 ●●●●● patch | view | raw | blame | history
project/openlibs/cjson/build.sh 182 ●●●●● patch | view | raw | blame | history
project/openlibs/libevent/build.sh 182 ●●●●● patch | view | raw | blame | history
project/openlibs/libgpiod/build.sh 185 ●●●●● patch | view | raw | blame | history
project/openlibs/makefile 10 ●●●●● patch | view | raw | blame | history
project/openlibs/mosquitto/build.sh 188 ●●●●● patch | view | raw | blame | history
project/openlibs/openssl/build.sh 182 ●●●●● patch | view | raw | blame | history
.gitignore
New file
@@ -0,0 +1,11 @@
# git ignore files/folders in the list
# ignore folders
install/
# ignore files
*.so*
*.o
*.a
cscope.*
tags
modules/ds18b20.c
New file
@@ -0,0 +1,134 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  ds18b20.c
 *    Description:  This file is temperature sensor DS18B20 code
 *
 *        Version:  1.0.0(2023/8/10)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2023/8/10 12:13:26"
 *
 * Pin connection:
 *
 *               DS18B20 Module          Raspberry Pi Board
 *                   VCC      <----->      #Pin1(3.3V)
 *                   DQ       <----->      #Pin7(BCM GPIO4)
 *                   GND      <----->      GND
 *
 * /boot/config.txt:
 *
 *          dtoverlay=w1-gpio-pullup,gpiopin=4
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <errno.h>
int ds18b20_get_temperature(float *temp);
int main(int argc, char *argv[])
{
    float       temp;
    if( ds18b20_get_temperature(&temp) < 0 )
    {
        printf("ERROR: ds18b20 get temprature failure\n");
        return 1;
    }
    printf("DS18B20 get temperature: %f ℃\n", temp);
    return 0;
}
/* File Content:
   pi@raspberrypi:~/guowenxue $ cat /sys/bus/w1/devices/28-041731f7c0ff/w1_slave
   3a 01 4b 46 7f ff 0c 10 a5 : crc=a5 YES
   3a 01 4b 46 7f ff 0c 10 a5 t=19625
   */
int ds18b20_get_temperature(float *temp)
{
    char            w1_path[50] = "/sys/bus/w1/devices/";
    char            chip[20];
    char            buf[128];
    DIR            *dirp;
    struct dirent  *direntp;
    int             fd =-1;
    char           *ptr;
    float           value;
    int             found = 0;
    if( !temp )
    {
        return -1;
    }
    /*+-------------------------------------------------------------------+
     *|  open dierectory /sys/bus/w1/devices to get chipset Serial Number |
     *+-------------------------------------------------------------------+*/
    if((dirp = opendir(w1_path)) == NULL)
    {
        printf("opendir error: %s\n", strerror(errno));
        return -2;
    }
    while((direntp = readdir(dirp)) != NULL)
    {
        if(strstr(direntp->d_name,"28-"))
        {
            /* find and get the chipset SN filename */
            strcpy(chip,direntp->d_name);
            found = 1;
            break;
        }
    }
    closedir(dirp);
    if( !found )
    {
        printf("Can not find ds18b20 in %s\n", w1_path);
        return -3;
    }
    /* get DS18B20 sample file full path: /sys/bus/w1/devices/28-xxxx/w1_slave */
    strncat(w1_path, chip, sizeof(w1_path)-strlen(w1_path));
    strncat(w1_path, "/w1_slave", sizeof(w1_path)-strlen(w1_path));
    /* open file /sys/bus/w1/devices/28-xxxx/w1_slave to get temperature */
    if( (fd=open(w1_path, O_RDONLY)) < 0 )
    {
        printf("open %s error: %s\n", w1_path, strerror(errno));
        return -4;
    }
    if(read(fd, buf, sizeof(buf)) < 0)
    {
        printf("read %s error: %s\n", w1_path, strerror(errno));
        return -5;
    }
    ptr = strstr(buf, "t=");
    if( !ptr )
    {
        printf("ERROR: Can not get temperature\n");
        return -6;
    }
    ptr+=2;
    /* convert string value to float value */
    *temp = atof(ptr)/1000;
    close(fd);
    return 0;
}
modules/infrared.c
New file
@@ -0,0 +1,274 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  led.c
 *    Description:  This file is HC-SR501 infrared sensor code
 *
 *
 * Pin connection:
 *               HC-SR501 Module           Raspberry Pi Board
 *                  VCC       <----->      5V
 *                  I/O       <----->      #Pin18(BCM GPIO24)
 *                  GND       <----->      GND
 *
 * System install:
 *                  sudo apt install -y libgpiod-dev gpiod
 *
 *
 ********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>
#include <libgen.h>
#include <gpiod.h>
/* infrared code */
enum
{
    IR1 = 0,
    IR_CNT,
};
/* infrared hardware information */
typedef struct ir_info_s
{
    const char         *name;  /* infrared name  */
    int                 gpio;  /* infrared BCM pin number */
    int                 active;/* infrared active GPIO level: 0->low 1->high */
    struct gpiod_line  *line;  /* libgpiod line */
} ir_info_t;
static ir_info_t    ir_info[IR_CNT] =
{
    {"IR1", 23, 1, NULL },
};
/* infrared API context */
typedef struct ir_ctx_s
{
    struct gpiod_chip   *chip;
    ir_info_t           *ir;
    int                  count;
} ir_ctx_t;
int init_infrared(ir_ctx_t *ctx);
int term_infrared(ir_ctx_t *ctx);
int detect_infrared(ir_ctx_t *ctx, int which);
static inline void msleep(unsigned long ms);
static inline void banner(const char *progname)
{
    printf("%s program Version v1.0.0\n", progname);
    printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
}
static void program_usage(const char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" This is infrared detect program. \n");
    printf(" -d[device  ]  Specify infrared device, such as 0\n");
    printf(" -h[help    ]  Display this help information\n");
    printf(" -v[version ]  Display the program version\n");
    printf("\n");
    banner(progname);
    return;
}
int g_stop = 0;
void sig_handler(int signum)
{
    switch( signum )
    {
        case SIGINT:
        case SIGTERM:
            g_stop = 1;
        default:
            break;
    }
    return ;
}
int main(int argc, char **argv)
{
    int             i, rv;
    char           *progname=NULL;
    ir_ctx_t        ir_ctx =
    {
        .chip  = NULL,
        .ir    = ir_info,
        .count = IR_CNT,
    };
    struct option long_options[] = {
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };
    progname = basename(argv[0]);
    /* Parser the command line parameters */
    while ((rv = getopt_long(argc, argv, "vh", long_options, NULL)) != -1)
    {
        switch (rv)
        {
            case 'v':  /* Get software version */
                banner(progname);
                return EXIT_SUCCESS;
            case 'h':  /* Get help information */
                program_usage(progname);
                return 0;
            default:
                break;
        }
    }
    if( (rv=init_infrared(&ir_ctx)) < 0 )
    {
        printf("initial infrared gpio failure, rv=%d\n", rv);
        return 1;
    }
    signal(SIGINT,  sig_handler);
    signal(SIGTERM, sig_handler);
    while( !g_stop )
    {
        for( i=0; i<ir_ctx.count; i++ )
        {
            rv = detect_infrared(&ir_ctx, i);
            printf("Infrared[%d] monitor: %s\n", i, rv ? "Someone is closing!":"No one nearby!");
        }
        sleep(1);
    }
    term_infrared(&ir_ctx);
    return 0;
}
int term_infrared(ir_ctx_t *ctx)
{
    int            i;
    ir_info_t     *ir;
    if( !ctx )
    {
        printf("Invalid input arguments\n");
        return -1;
    }
    if( !ctx->chip )
        return 0;
    for(i=0; i<ctx->count; i++)
    {
        ir = &ctx->ir[i];
        if( ir->line )
            gpiod_line_release(ir->line);
    }
    gpiod_chip_close(ctx->chip);
    return 0;
}
int init_infrared(ir_ctx_t *ctx)
{
    int            i, rv;
    ir_info_t     *ir;
    if( !ctx )
    {
        printf("Invalid input arguments\n");
        return -1;
    }
    ctx->chip = gpiod_chip_open_by_name("gpiochip0");
    if( !ctx->chip )
    {
        printf("open gpiochip failure, maybe you need running as root\n");
        return -2;
    }
    for(i=0; i<ctx->count; i++)
    {
        ir = &ctx->ir[i];
        ir ->line = gpiod_chip_get_line(ctx->chip, ir->gpio);
        if( !ir->line )
        {
            printf("open gpioline for %s[%d] failed\n", ir->name, ir->gpio);
            rv = -3;
            goto failed;
        }
        rv  = gpiod_line_request_input(ir->line, ir->name);
        if( rv )
        {
            printf("request gpio input for %s[%d] failed\n", ir->name, ir->gpio);
            rv = -4;
            goto failed;
        }
    }
    return 0;
failed:
    term_infrared(ctx);
    return rv;
}
int detect_infrared(ir_ctx_t *ctx, int which)
{
    int            rv = 0;
    ir_info_t     *ir;
    if( !ctx || which<0 || which>=ctx->count )
    {
        printf("Invalid input arguments\n");
        return 0;
    }
    ir = &ctx->ir[which];
    return gpiod_line_get_value(ir->line)==ir->active ? 1 : 0;
}
static inline void msleep(unsigned long ms)
{
    struct timespec cSleep;
    unsigned long ulTmp;
    cSleep.tv_sec = ms / 1000;
    if (cSleep.tv_sec == 0)
    {
        ulTmp = ms * 10000;
        cSleep.tv_nsec = ulTmp * 100;
    }
    else
    {
        cSleep.tv_nsec = 0;
    }
    nanosleep(&cSleep, 0);
    return ;
}
modules/leds.c
New file
@@ -0,0 +1,260 @@
/*********************************************************************************
 *      Copyright:  (C) 2021 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  led.c
 *    Description:  This file is used to control RGB 3-colors LED
 *
 *
 * Pin connection:
 *               RGB Led Module           Raspberry Pi Board
 *                   R        <----->      #Pin33(BCM GPIO13)
 *                   G        <----->      #Pin35(BCM GPIO19)
 *                   B        <----->      #Pin37(BCM GPIO26)
 *                  GND       <----->      GND
 *
 * System install:
 *                  sudo apt install -y libgpiod-dev gpiod
 *
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <gpiod.h>
#define DELAY     500
#define ON        1
#define OFF       0
/* Three LEDs code */
enum
{
    LED_R = 0,
    LED_G,
    LED_B,
    LEDCNT,
};
/* Three LEDs hardware information */
typedef struct led_info_s
{
    const char         *name;  /* RGB 3-color LED name  */
    int                 gpio;  /* RGB 3-color LED BCM pin number */
    int                 active;/* RGB 3-color LED active GPIO level: 0->low 1->high */
    struct gpiod_line  *line;  /* libgpiod line */
} led_info_t;
static led_info_t    leds_info[LEDCNT] =
{
    {"red",   13, 1, NULL },
    {"green", 19, 1, NULL },
    {"blue",  26, 1, NULL },
};
/* Three LEDs API context */
typedef struct led_ctx_s
{
    struct gpiod_chip   *chip;
    led_info_t          *leds;
    int                  count;
} led_ctx_t;
int init_led(led_ctx_t *ctx);
int term_led(led_ctx_t *ctx);
int turn_led(led_ctx_t *ctx, int which, int cmd);
static inline void msleep(unsigned long ms);
int g_stop = 0;
void sig_handler(int signum)
{
    switch( signum )
    {
        case SIGINT:
        case SIGTERM:
            g_stop = 1;
        default:
            break;
    }
    return ;
}
int main(int argc, char *argv[])
{
    int                 rv;
    led_ctx_t           led_ctx =
    {
        .chip  = NULL,
        .leds  = leds_info,
        .count = LEDCNT,
    };
    if( (rv=init_led(&led_ctx)) < 0 )
    {
        printf("initial leds gpio failure, rv=%d\n", rv);
        return 1;
    }
    printf("initial RGB Led gpios okay\n", rv);
    signal(SIGINT,  sig_handler);
    signal(SIGTERM, sig_handler);
    while( !g_stop )
    {
        turn_led(&led_ctx, LED_R, ON);
        msleep(DELAY);
        turn_led(&led_ctx, LED_R, OFF);
        msleep(DELAY);
        turn_led(&led_ctx, LED_G, ON);
        msleep(DELAY);
        turn_led(&led_ctx, LED_G, OFF);
        msleep(DELAY);
        turn_led(&led_ctx, LED_B, ON);
        msleep(DELAY);
        turn_led(&led_ctx, LED_B, OFF);
        msleep(DELAY);
    }
    term_led(&led_ctx);
    return 0;
}
int term_led(led_ctx_t *ctx)
{
    int            i;
    led_info_t    *led;
    printf("terminate RGB Led gpios\n");
    if( !ctx )
    {
        printf("Invalid input arguments\n");
        return -1;
    }
    if( !ctx->chip )
        return 0;
    for(i=0; i<ctx->count; i++)
    {
        led = &ctx->leds[i];
        if( led->line )
            gpiod_line_release(led->line);
    }
    gpiod_chip_close(ctx->chip);
    return 0;
}
int init_led(led_ctx_t *ctx)
{
    int            i, rv;
    led_info_t    *led;
    if( !ctx )
    {
        printf("Invalid input arguments\n");
        return -1;
    }
    ctx->chip = gpiod_chip_open_by_name("gpiochip0");
    if( !ctx->chip )
    {
        printf("open gpiochip failure, maybe you need running as root\n");
        return -2;
    }
    for(i=0; i<ctx->count; i++)
    {
        led = &ctx->leds[i];
        led->line = gpiod_chip_get_line(ctx->chip, led->gpio);
        if( !led->line )
        {
            printf("open gpioline for %s[%d] failed\n", led->name, led->gpio);
            rv = -3;
            goto failed;
        }
        rv  = gpiod_line_request_output(led->line, led->name, !led->active);
        if( rv )
        {
            printf("request gpio output for %5s[%d] failed\n", led->name, led->gpio);
            rv = -4;
            goto failed;
        }
        //printf("request %5s led[%d] for gpio output okay\n", led->name, led->gpio);
    }
    return 0;
failed:
    term_led(ctx);
    return rv;
}
int turn_led(led_ctx_t *ctx, int which, int cmd)
{
    int            rv = 0;
    led_info_t    *led;
    if( !ctx || which<0 || which>=ctx->count )
    {
        printf("Invalid input arguments\n");
        return -1;
    }
    led = &ctx->leds[which];
    if( OFF == cmd )
    {
        gpiod_line_set_value(led->line, !led->active);
    }
    else
    {
        gpiod_line_set_value(led->line, led->active);
    }
    return 0;
}
static inline void msleep(unsigned long ms)
{
    struct timespec cSleep;
    unsigned long ulTmp;
    cSleep.tv_sec = ms / 1000;
    if (cSleep.tv_sec == 0)
    {
        ulTmp = ms * 10000;
        cSleep.tv_nsec = ulTmp * 100;
    }
    else
    {
        cSleep.tv_nsec = 0;
    }
    nanosleep(&cSleep, 0);
    return ;
}
modules/makefile
New file
@@ -0,0 +1,34 @@
#********************************************************************************
#      Copyright:  (C) 2023 LingYun IoT System Studio
#                  All rights reserved.
#
#       Filename:  Makefile
#    Description:  This file used to compile all the C file to respective binary,
#                  and it will auto detect cross compile or local compile.
#
#        Version:  1.0.0(11/08/23)
#         Author:  Guo Wenxue <guowenxue@gmail.com>
#      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
#
#*******************************************************************************
BUILD_ARCH=$(shell uname -m)
ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
    CROSS_COMPILE?=arm-linux-gnueabihf-
endif
CC=${CROSS_COMPILE}gcc
LDFLAGS+=-lm -lgpiod
SRCFILES = $(wildcard *.c)
BINARIES=$(SRCFILES:%.c=%)
all: ${BINARIES}
%: %.c
    $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
clean:
    rm -f ${BINARIES}
modules/pwm.c
New file
@@ -0,0 +1,311 @@
/*********************************************************************************
 *      Copyright:  (C) 2021 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  pwm.c
 *    Description:  This file is used to control PWM buzzer/Led
 *
 * Pin connection:
 *               PWM Module              Raspberry Pi Board
 *                  VCC       <----->      5V
 *                 buzzer     <----->      #Pin32(BCM GPIO12)
 *                  Led       <----->      #Pin33(BCM GPIO13)
 *                  GND       <----->      GND
 *
 * /boot/config.txt:
 *
 *          dtoverlay=pwm,pin=12,func=4 (Buzzer)
 *          dtoverlay=pwm,pin=13,func=4 (Led)
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <getopt.h>
#include <libgen.h>
#include <gpiod.h>
#define PWMCHIP_PATH      "/sys/class/pwm/pwmchip0"
#define ENABLE            1
#define DISABLE           0
int init_pwm(int channel, int freq, int duty);
int turn_pwm(int channel, int status);
int term_pwm(int channel);
static inline void msleep(unsigned long ms);
static inline void banner(const char *progname)
{
    printf("%s program Version v1.0.0\n", progname);
    printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
}
static void program_usage(const char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" This is pwm control program. \n");
    printf(" -c[channel ]  Specify PWM channel, such as 0\n");
    printf(" -f[freq    ]  Specify PWM frequency, default 2500(Hz)\n");
    printf(" -d[duty    ]  Specify PWM duty, default 50(50%)\n");
    printf(" -s[status  ]  Specify PWM status: 1->on(default), 0->off\n");
    printf(" -h[help    ]  Display this help information\n");
    printf(" -v[version ]  Display the program version\n");
    printf("\n");
    printf("Example buzzer : %s -c 0 -f 2750 -d 50 -s 1\n", progname);
    printf("Example Led    : %s -c 1 -f 100 -d 50 -s 1\n", progname);
    printf("Example disable: %s -c 0 -s 0\n", progname);
    printf("\n");
    banner(progname);
    return;
}
int main(int argc, char **argv)
{
    int             rv;
    char           *progname=NULL;
    int             channel = -1;
    int             freq = 2500;
    int             duty = 50;
    int             status = ENABLE;
    struct option long_options[] = {
        {"channel", required_argument, NULL, 'c'},
        {"freq", required_argument, NULL, 'f'},
        {"duty", required_argument, NULL, 'd'},
        {"status", required_argument, NULL, 's'},
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };
    progname = basename(argv[0]);
    /* Parser the command line parameters */
    while ((rv = getopt_long(argc, argv, "c:f:d:s:vh", long_options, NULL)) != -1)
    {
        switch (rv)
        {
            case 'c': /*  Set pwm channel, such as 0,1 */
                channel = atoi(optarg);
                break;
            case 'f': /*  Set pwm frequency */
                freq = atoi(optarg);
                break;
            case 'd': /*  Set pwm duty cycle */
                duty = atoi(optarg);
                break;
            case 's': /*  Set pwm status, 0 for disable and 1 for enable */
                status = atoi(optarg);
                break;
            case 'v':  /* Get software version */
                banner(progname);
                return EXIT_SUCCESS;
            case 'h':  /* Get help information */
                program_usage(progname);
                return 0;
            default:
                break;
        }
    }
    if(channel < 0 )
    {
        program_usage(progname);
        return 0;
    }
    if( status )
    {
        if( (rv=init_pwm(channel, freq, duty)) < 0 )
        {
            printf("initial PWM failure, rv=%d\n", rv);
            return 1;
        }
        turn_pwm(channel, ENABLE);
    }
    else
    {
        term_pwm(channel);
    }
    return 0;
}
/* check PWM $channel export or not */
int check_pwm(int channel)
{
    char          path[256];
    /* check /sys/class/pwm/pwmchip0/pwmN exist or not */
    snprintf(path, sizeof(path), "%s/pwm%d", PWMCHIP_PATH, channel);
    return access(path, F_OK) ? 0 : 1;
}
/* export($export=1)/unexport($export=0) PWM $channel */
int export_pwm(int channel, int export)
{
    int           fd;
    char          buf[32];
    char          path[256];
    if( export && check_pwm(channel) )
        return 0; /* export already when export  */
    else if( !export && !check_pwm(channel) )
        return 0; /* unexport already when unexport  */
    /* export PWM channel by echo N > /sys/class/pwm/pwmchip0/export */
    snprintf(path, sizeof(path), "%s/%s", PWMCHIP_PATH, export?"export":"unexport");
    if( (fd=open(path, O_WRONLY)) < 0 )
    {
        printf("open '%s' failed: %s\n", path, strerror(errno));
        return -3;
    }
    snprintf(buf, sizeof(buf), "%d", channel);
    write(fd, buf, strlen(buf));
    msleep(100);
    if( export && check_pwm(channel) )
        return 0; /* export already when export  */
    else if( !export && !check_pwm(channel) )
        return 0; /* unexport already when unexport  */
    return -4;
}
/* configure PWM $channel */
int config_pwm(int channel, int freq, int duty)
{
    int           fd;
    char          buf[32];
    char          path[256];
    int           period;
    int           duty_cycle;
    if( !check_pwm(channel) )
        return -2;
    /* open PWM period file and write period in ns */
    snprintf(path, sizeof(path), "%s/pwm%d/period", PWMCHIP_PATH, channel);
    if( (fd=open(path, O_WRONLY)) < 0 )
    {
        printf("open '%s' failed: %s\n", path, strerror(errno));
        return -3;
    }
    period = 1000000000/freq;
    snprintf(buf, sizeof(buf), "%d", period);
    write(fd, buf, strlen(buf));
    /* open PWM duty_cycle file and write duty */
    snprintf(path, sizeof(path), "%s/pwm%d/duty_cycle", PWMCHIP_PATH, channel);
    if( (fd=open(path, O_WRONLY)) < 0 )
    {
        printf("open '%s' failed: %s\n", path, strerror(errno));
        return -3;
    }
    duty_cycle = (period*duty) / 100;
    snprintf(buf, sizeof(buf), "%d", duty_cycle);
    write(fd, buf, strlen(buf));
    return 0;
}
int init_pwm(int channel, int freq, int duty)
{
    int           rv;
    char          buf[32];
    char          path[256];
    if( (rv=export_pwm(channel, 1)) )
    {
        printf("export PWM channel[%d] failed, rv=%d\n", channel, rv);
        return rv;
    }
    if( (rv=config_pwm(channel, freq, duty)) )
    {
        printf("config PWM channel[%d] failed, rv=%d\n", channel, rv);
        return rv;
    }
    printf("config pwm%d with freq[%d] duty[%d] okay\n", channel, freq, duty);
    return 0;
}
int turn_pwm(int channel, int status)
{
    int           fd;
    char          buf[32];
    char          path[256];
    if( !check_pwm(channel) )
        return -1;
    /* open PWM enable file and enable(1)/disable(0) it */
    snprintf(path, sizeof(path), "%s/pwm%d/enable", PWMCHIP_PATH, channel);
    if( (fd=open(path, O_WRONLY)) < 0 )
    {
        printf("open '%s' failed: %s\n", path, strerror(errno));
        return -3;
    }
    snprintf(buf, sizeof(buf), "%d", status?1:0);
    write(fd, buf, strlen(buf));
    printf("pwm[%d] %s\n", channel, status?"enable":"disable");
    return 0;
}
int term_pwm(int channel)
{
    if( !check_pwm(channel) )
        return 0;
    turn_pwm(channel, DISABLE);
    export_pwm(channel, 0);
    return 0;
}
static inline void msleep(unsigned long ms)
{
    struct timespec cSleep;
    unsigned long ulTmp;
    cSleep.tv_sec = ms / 1000;
    if (cSleep.tv_sec == 0)
    {
        ulTmp = ms * 10000;
        cSleep.tv_nsec = ulTmp * 100;
    }
    else
    {
        cSleep.tv_nsec = 0;
    }
    nanosleep(&cSleep, 0);
    return ;
}
modules/relay.c
New file
@@ -0,0 +1,286 @@
/*********************************************************************************
 *      Copyright:  (C) 2021 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  relay.c
 *    Description:  This file is used to control Relay
 *
 *
 * Pin connection:
 *                 Relay Module           Raspberry Pi Board
 *                  VCC       <----->      5V
 *                   I        <----->      #Pin16(BCM GPIO23)
 *                  GND       <----->      GND
 *
 * System install:
 *                  sudo apt install -y libgpiod-dev gpiod
 *
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <getopt.h>
#include <libgen.h>
#include <gpiod.h>
#define DELAY     500
#define ON        1
#define OFF       0
/* relay code */
enum
{
    RELAY1 = 0,
    RELAY_CNT,
};
/* Relay hardware information */
typedef struct relay_info_s
{
    const char         *name;  /* Relay name  */
    int                 gpio;  /* Relay BCM pin number */
    int                 active;/* Relay active GPIO level: 0->low 1->high */
    struct gpiod_line  *line;  /* libgpiod line */
} relay_info_t;
static relay_info_t    relay_info[RELAY_CNT] =
{
    {"relay1",  23, 1, NULL },
};
/* Relay API context */
typedef struct relay_ctx_s
{
    struct gpiod_chip   *chip;
    relay_info_t        *relay;
    int                  count;
} relay_ctx_t;
int init_relay(relay_ctx_t *ctx);
int term_relay(relay_ctx_t *ctx);
int turn_relay(relay_ctx_t *ctx, int which, int cmd);
static inline void msleep(unsigned long ms);
static inline void banner(const char *progname)
{
    printf("%s program Version v1.0.0\n", progname);
    printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
}
static void program_usage(const char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" This is relay control program. \n");
    printf(" -d[device  ]  Specify relay device, such as 0\n");
    printf(" -s[status  ]  Specify relay status, 0 for open, 1 for close\n");
    printf(" -h[help    ]  Display this help information\n");
    printf(" -v[version ]  Display the program version\n");
    printf("\n");
    banner(progname);
    return;
}
int main(int argc, char **argv)
{
    int             rv;
    char           *progname=NULL;
    int             which = -1;
    int             status = ON;
    relay_ctx_t     relay_ctx =
    {
        .chip  = NULL,
        .relay  = relay_info,
        .count = RELAY_CNT,
    };
    struct option long_options[] = {
        {"device", required_argument, NULL, 'd'},
        {"status", required_argument, NULL, 's'},
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };
    progname = basename(argv[0]);
    /* Parser the command line parameters */
    while ((rv = getopt_long(argc, argv, "d:s:vh", long_options, NULL)) != -1)
    {
        switch (rv)
        {
            case 'd': /*  Set relay number, such as 0...max */
                which = atoi(optarg);
                break;
            case 's': /*  Set relay status, 0 for open and 1 for close */
                status = atoi(optarg);
                break;
            case 'v':  /* Get software version */
                banner(progname);
                return EXIT_SUCCESS;
            case 'h':  /* Get help information */
                program_usage(progname);
                return 0;
            default:
                break;
        }
    }
    if( which<0 || which>=relay_ctx.count )
    {
        printf("ERROR: Invalid relay index [%d], max=%d\n", which, relay_ctx.count-1);
        return 0;
    }
    if( (rv=init_relay(&relay_ctx)) < 0 )
    {
        printf("initial relay gpio failure, rv=%d\n", rv);
        return 1;
    }
    turn_relay(&relay_ctx, which, status);
    term_relay(&relay_ctx);
    return 0;
}
int term_relay(relay_ctx_t *ctx)
{
    int            i;
    relay_info_t  *relay;
    if( !ctx )
    {
        printf("Invalid input arguments\n");
        return -1;
    }
    if( !ctx->chip )
        return 0;
    for(i=0; i<ctx->count; i++)
    {
        relay = &ctx->relay[i];
        if( relay->line )
            gpiod_line_release(relay->line);
    }
    gpiod_chip_close(ctx->chip);
    return 0;
}
int init_relay(relay_ctx_t *ctx)
{
    int            i, rv;
    relay_info_t  *relay;
    if( !ctx )
    {
        printf("Invalid input arguments\n");
        return -1;
    }
    ctx->chip = gpiod_chip_open_by_name("gpiochip0");
    if( !ctx->chip )
    {
        printf("open gpiochip failure, maybe you need running as root\n");
        return -2;
    }
    for(i=0; i<ctx->count; i++)
    {
        relay = &ctx->relay[i];
        relay->line = gpiod_chip_get_line(ctx->chip, relay->gpio);
        if( !relay->line )
        {
            printf("open gpioline for %s[%d] failed\n", relay->name, relay->gpio);
            rv = -3;
            goto failed;
        }
        rv  = gpiod_line_request_output(relay->line, relay->name, !relay->active);
        if( rv )
        {
            printf("request gpio output for %5s[%d] failed\n", relay->name, relay->gpio);
            rv = -4;
            goto failed;
        }
        //printf("request %s[%d] for gpio output okay\n", relay->name, relay->gpio);
    }
    return 0;
failed:
    term_relay(ctx);
    return rv;
}
int turn_relay(relay_ctx_t *ctx, int which, int cmd)
{
    int            rv = 0;
    relay_info_t  *relay;
    if( !ctx || which<0 || which>=ctx->count )
    {
        printf("Invalid input arguments\n");
        return -1;
    }
    relay = &ctx->relay[which];
    if( OFF == cmd )
    {
        gpiod_line_set_value(relay->line, !relay->active);
    }
    else
    {
        gpiod_line_set_value(relay->line, relay->active);
    }
    return 0;
}
static inline void msleep(unsigned long ms)
{
    struct timespec cSleep;
    unsigned long ulTmp;
    cSleep.tv_sec = ms / 1000;
    if (cSleep.tv_sec == 0)
    {
        ulTmp = ms * 10000;
        cSleep.tv_nsec = ulTmp * 100;
    }
    else
    {
        cSleep.tv_nsec = 0;
    }
    nanosleep(&cSleep, 0);
    return ;
}
modules/sht20.c
New file
@@ -0,0 +1,331 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  sht20.c
 *    Description:  This file is temperature and relative humidity sensor SHT20 code
 *
 *        Version:  1.0.0(10/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "10/08/23 17:52:00"
 *
 * Pin connection:
 *               STH20 Module            Raspberry Pi Board
 *                   VCC      <----->      #Pin1(3.3V)
 *                   SDA      <----->      #Pin3(SDA, BCM GPIO2)
 *                   SCL      <----->      #Pin5(SCL, BCM GPIO3)
 *                   GND      <----->      GND
 *
 * /boot/config.txt:
 *                  dtoverlay=i2c1,pins_2_3
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <getopt.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/types.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
int i2c_write(int fd, uint8_t slave_addr, uint8_t *data, int len);
int i2c_read(int fd, uint8_t slave_addr, uint8_t *buf, int size);
int sht20_checksum(uint8_t *data, int len, int8_t checksum);
static inline void msleep(unsigned long ms);
static inline void dump_buf(const char *prompt, uint8_t *buf, int size);
static inline void banner(const char *progname)
{
    printf("%s program Version v1.0.0\n", progname);
    printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
}
static void program_usage(const char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" %s is SHT20 temperature and humidity sensor program. \n", progname);
    printf(" -d[device  ]  Specify I2C device, such as /dev/i2c-1\n");
    printf(" -h[help    ]  Display this help information\n");
    printf(" -v[version ]  Display the program version\n");
    printf("\n");
    banner(progname);
    return;
}
int main(int argc, char **argv)
{
    int             fd, rv;
    float           temp, rh;
    char           *dev = "/dev/i2c-1";
    char           *progname=NULL;
    uint8_t         buf[4];
    struct option long_options[] = {
        {"device", required_argument, NULL, 'd'},
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };
    progname = basename(argv[0]);
    /* Parser the command line parameters */
    while ((rv = getopt_long(argc, argv, "d:vh", long_options, NULL)) != -1)
    {
        switch (rv)
        {
            case 'd': /*  Set I2C device path: /dev/i2c-1 */
                dev = optarg;
                break;
            case 'v':  /*  Get software version */
                banner(progname);
                return EXIT_SUCCESS;
            case 'h':  /*  Get help information */
                program_usage(progname);
                return 0;
            default:
                break;
        }
    }
    /*+--------------------------------+
     *|     open /dev/i2c-x device     |
     *+--------------------------------+*/
    if( (fd=open(dev, O_RDWR)) < 0)
    {
        printf("i2c device '%s' open failed: %s\n", dev, strerror(errno));
        return -1;
    }
    /*+--------------------------------+
     *|   software reset SHT20 sensor  |
     *+--------------------------------+*/
    buf[0] = 0xFE;
    i2c_write(fd, 0x40, buf, 1);
    msleep(50);
    /*+--------------------------------+
     *|   trigger temperature measure  |
     *+--------------------------------+*/
    buf[0] = 0xF3;
    i2c_write(fd, 0x40, buf, 1);
    msleep(85); /* datasheet: typ=66, max=85 */
    memset(buf, 0, sizeof(buf));
    i2c_read(fd, 0x40, buf, 3);
    dump_buf("Temperature sample data: ", buf, 3);
    if( !sht20_checksum(buf, 2, buf[2]) )
    {
        printf("Temperature sample data CRC checksum failure.\n");
        goto cleanup;
    }
    temp = 175.72 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;
    /*+--------------------------------+
     *|    trigger humidity measure    |
     *+--------------------------------+*/
    buf[0] = 0xF5;
    i2c_write(fd, 0x40, buf, 1);
    msleep(29); /* datasheet: typ=22, max=29 */
    memset(buf, 0, sizeof(buf));
    i2c_read(fd, 0x40, buf, 3);
    dump_buf("Relative humidity sample data: ", buf, 3);
    if( !sht20_checksum(buf, 2, buf[2]) )
    {
        printf("Relative humidity sample data CRC checksum failure.\n");
        goto cleanup;
    }
    rh = 125 * (((((int) buf[0]) << 8) + buf[1]) / 65536.0) - 6;
    /*+--------------------------------+
     *|    print the measure result    |
     *+--------------------------------+*/
    printf("Temperature=%lf 'C relative humidity=%lf%%\n", temp, rh);
cleanup:
    close(fd);
    return 0;
}
int sht20_checksum(uint8_t *data, int len, int8_t checksum)
{
    int8_t crc = 0;
    int8_t bit;
    int8_t byteCtr;
    //calculates 8-Bit checksum with given polynomial: x^8 + x^5 + x^4 + 1
    for (byteCtr = 0; byteCtr < len; ++byteCtr)
    {
        crc ^= (data[byteCtr]);
        for ( bit = 8; bit > 0; --bit)
        {
            /* x^8 + x^5 + x^4 + 1 = 0001 0011 0001 = 0x131 */
            if (crc & 0x80) crc = (crc << 1) ^ 0x131;
            else crc = (crc << 1);
        }
    }
    if (crc != checksum)
        return 0;
    else
        return 1;
}
int i2c_write(int fd, uint8_t slave_addr, uint8_t *data, int len)
{
    struct i2c_rdwr_ioctl_data i2cdata;
    int rv = 0;
    if( !data || len<= 0)
    {
        printf("%s() invalid input arguments!\n", __func__);
        return -1;
    }
    i2cdata.nmsgs = 1;
    i2cdata.msgs = malloc( sizeof(struct i2c_msg)*i2cdata.nmsgs );
    if ( !i2cdata.msgs )
    {
        printf("%s() msgs malloc failed!\n", __func__);
        return -2;
    }
    i2cdata.msgs[0].addr = slave_addr;
    i2cdata.msgs[0].flags = 0; //write
    i2cdata.msgs[0].len = len;
    i2cdata.msgs[0].buf = malloc(len);
    if( !i2cdata.msgs[0].buf )
    {
        printf("%s() msgs malloc failed!\n", __func__);
        rv = -3;
        goto cleanup;
    }
    memcpy(i2cdata.msgs[0].buf, data, len);
    if( ioctl(fd, I2C_RDWR, &i2cdata)<0 )
    {
        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
        rv = -4;
        goto cleanup;
    }
cleanup:
    if( i2cdata.msgs[0].buf )
        free(i2cdata.msgs[0].buf);
    if( i2cdata.msgs )
        free(i2cdata.msgs);
    return rv;
}
int i2c_read(int fd, uint8_t slave_addr, uint8_t *buf, int size)
{
    struct i2c_rdwr_ioctl_data i2cdata;
    int rv = 0;
    if( !buf || size<= 0)
    {
        printf("%s() invalid input arguments!\n", __func__);
        return -1;
    }
    i2cdata.nmsgs = 1;
    i2cdata.msgs = malloc( sizeof(struct i2c_msg)*i2cdata.nmsgs );
    if ( !i2cdata.msgs )
    {
        printf("%s() msgs malloc failed!\n", __func__);
        return -2;
    }
    i2cdata.msgs[0].addr = slave_addr;
    i2cdata.msgs[0].flags = I2C_M_RD; //read
    i2cdata.msgs[0].len = size;
    i2cdata.msgs[0].buf = buf;
    memset(buf, 0, size);
    if( ioctl(fd, I2C_RDWR, &i2cdata)<0 )
    {
        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
        rv = -4;
    }
    free( i2cdata.msgs );
    return rv;
}
static inline void msleep(unsigned long ms)
{
    struct timespec cSleep;
    unsigned long ulTmp;
    cSleep.tv_sec = ms / 1000;
    if (cSleep.tv_sec == 0)
    {
        ulTmp = ms * 10000;
        cSleep.tv_nsec = ulTmp * 100;
    }
    else
    {
        cSleep.tv_nsec = 0;
    }
    nanosleep(&cSleep, 0);
    return ;
}
static inline void dump_buf(const char *prompt, uint8_t *buf, int size)
{
    int          i;
    if( !buf )
    {
        return ;
    }
    if( prompt )
    {
        printf("%-32s ", prompt);
    }
    for(i=0; i<size; i++)
    {
        printf("%02x ", buf[i]);
    }
    printf("\n");
    return ;
}
modules/tsl2561.c
New file
@@ -0,0 +1,337 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  sht20.c
 *    Description:  This file is the Lux sensor TSL2561 code
 *
 *        Version:  1.0.0(10/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "10/08/23 17:52:00"
 *
 * Pin connection:
 *               STH20 Module            Raspberry Pi Board
 *                   VCC      <----->      #Pin1(3.3V)
 *                   SDA      <----->      #Pin27(SDA, BCM GPIO0)
 *                   SCL      <----->      #Pin28(SCL, BCM GPIO1)
 *                   GND      <----->      GND
 *
 * /boot/config.txt:
 *                  dtoverlay=i2c0,pins_0_1
 *
 ********************************************************************************/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <time.h>
#include <errno.h>
#include <libgen.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/types.h>
#include <sys/stat.h>
#define TSL2561_I2C_ADDR                0x39
#define CONTROL_REG                     0x80
#define REG_COUNT                       4
#define POWER_UP                        0x03
#define POWER_DOWN                      0x00
#define OFF                             0
#define ON                              1
/* Register Address  */
enum
{
    /* Channel_0 = DATA0HIGH<<8 + DATA0LOW */
    DATA0LOW = 0x8C,
    DATA0HIGH,
    /* Channel_1 = DATA1HIGH<<8 + DATA1LOW */
    DATA1LOW,
    DATA1HIGH,
};
static const int  regs_addr[REG_COUNT]={DATA0LOW, DATA0HIGH, DATA1LOW, DATA1HIGH};
float tsl2561_get_lux(int fd);
static inline void print_datime(void);
static inline void banner(const char *progname)
{
    printf("%s program Version v1.0.0\n", progname);
    printf("Copyright (C) 2023 LingYun IoT System Studio.\n");
}
static void program_usage(const char *progname)
{
    printf("Usage: %s [OPTION]...\n", progname);
    printf(" %s is TSL2561 Lux sensor program.\n", progname);
    printf(" -d[device  ]  Specify I2C device, such as /dev/i2c-0\n");
    printf(" -h[help    ]  Display this help information\n");
    printf(" -v[version ]  Display the program version\n");
    printf("\n");
    banner(progname);
    return;
}
int main(int argc, char **argv)
{
    int             fd, rv;
    float           lux;
    char           *dev = "/dev/i2c-0";
    char           *progname=NULL;
    struct option long_options[] = {
        {"device", required_argument, NULL, 'd'},
        {"version", no_argument, NULL, 'v'},
        {"help", no_argument, NULL, 'h'},
        {NULL, 0, NULL, 0}
    };
    progname = basename(argv[0]);
    /* Parser the command line parameters */
    while ((rv = getopt_long(argc, argv, "d:vh", long_options, NULL)) != -1)
    {
        switch (rv)
        {
            case 'd': /*  Set I2C device path: /dev/i2c-1 */
                dev = optarg;
                break;
            case 'v':  /*  Get software version */
                banner(progname);
                return EXIT_SUCCESS;
            case 'h':  /*  Get help information */
                program_usage(progname);
                return 0;
            default:
                break;
        }
    }
    /*+--------------------------------+
     *|     open /dev/i2c-x device     |
     *+--------------------------------+*/
    if( (fd=open(dev, O_RDWR)) < 0)
    {
        printf("i2c device '%s' open failed: %s\n", dev, strerror(errno));
        return -1;
    }
    while(1)
    {
        lux = tsl2561_get_lux(fd);
        print_datime();
        printf("TSLl2561 get lux: %.3f\n", lux);
        sleep(1);
    }
    close(fd);
    return 0;
}
static inline void print_datime(void)
{
    time_t         tmp;
    struct tm     *p;
    time(&tmp);
    p=localtime(&tmp);
    printf("%d-%02d-%02d %02d:%02d:%02d\t", (p->tm_year+1900),(p->tm_mon+1),
            p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec);
}
static inline void msleep(unsigned long ms)
{
    struct timespec cSleep;
    unsigned long ulTmp;
    cSleep.tv_sec = ms / 1000;
    if (cSleep.tv_sec == 0)
    {
        ulTmp = ms * 10000;
        cSleep.tv_nsec = ulTmp * 100;
    }
    else
    {
        cSleep.tv_nsec = 0;
    }
    nanosleep(&cSleep, 0);
    return ;
}
void tsl2561_power(int fd, int cmd)
{
    struct i2c_msg               msg;
    struct i2c_rdwr_ioctl_data   data;
    unsigned char                buf[2];
    msg.addr= TSL2561_I2C_ADDR;
    msg.flags=0;  /* write */
    msg.len= 1;
    msg.buf= buf;
    data.nmsgs= 1;
    data.msgs= &msg;
    msg.buf[0]=CONTROL_REG;
    if( ioctl(fd, I2C_RDWR, &data) < 0 )
    {
        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
        return ;
    }
    if( cmd )
        msg.buf[0]=POWER_UP;
    else
        msg.buf[0]=POWER_DOWN;
    if( ioctl(fd, I2C_RDWR, &data) < 0 )
    {
        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
        return ;
    }
    return ;
}
int tsl2561_readreg(int fd, unsigned char regaddr, unsigned char *regval)
{
    struct i2c_msg               msg;
    struct i2c_rdwr_ioctl_data   data;
    unsigned char                buf[2];
    msg.addr= TSL2561_I2C_ADDR;
    msg.flags=0;  /* write */
    msg.len= 1;
    msg.buf= buf;
    msg.buf[0] = regaddr;
    data.nmsgs= 1;
    data.msgs= &msg;
    if( ioctl(fd, I2C_RDWR, &data) < 0 )
    {
        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
        return -1;
    }
    memset(buf, 0, sizeof(buf));
    msg.addr= TSL2561_I2C_ADDR;
    msg.flags=I2C_M_RD;  /* read */
    msg.len= 1;
    msg.buf= buf;
    data.nmsgs= 1;
    data.msgs= &msg;
    if( ioctl(fd, I2C_RDWR, &data) < 0 )
    {
        printf("%s() ioctl failure: %s\n", __func__, strerror(errno));
        return -1;
    }
    *regval = msg.buf[0];
    return 0;
}
float tsl2561_get_lux(int fd)
{
    int                 i;
    unsigned char       reg_data[REG_COUNT];
    unsigned char       buf;
    int                 chn0_data = 0;
    int                 chn1_data = 0;
    float               div = 0.0;
    float               lux = 0.0;
    tsl2561_power(fd, ON);
    msleep(410);  /* t(CONV) MAX 400ms */
    /* Read register Channel0 and channel1 data from register */
    for(i=0; i<REG_COUNT; i++)
    {
        tsl2561_readreg(fd, regs_addr[i], &reg_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 ;
}
project/booster/README.md
New file
@@ -0,0 +1,4 @@
## booster
LingYun embedded C program basic library
project/booster/at-esp32.c
New file
@@ -0,0 +1,388 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  at-esp32.c
 *    Description:  This file is ESP32 AT command low level API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#include "logger.h"
#include "at-esp32.h"
/*+------------------------+
 *|    Baisc AT command    |
 *+------------------------+*/
static inline int check_at_ready(comport_t *comport)
{
    int             times = 6;
    int             ready = 0;
    while( times-- )
    {
        if( 0 == send_atcmd_check_ok(comport, "AT", 500) )
        {
            ready = 1;
            break;
        }
    }
    return ready;
}
/* AT command: AT+RST */
int esp32_reset(comport_t *comport)
{
    int             rv;
    rv = send_atcmd(comport, "AT+RST", 5000, "ready", AT_ERRSTR, NULL, 0);
    if( rv < 0)
    {
        log_error("send AT command to reset ESP32 failed, rv=%d\n", rv);
        return -1;
    }
    if( check_at_ready(comport) )
    {
        log_info("send AT to reset ESP32 and AT command ready\n");
        return 0;
    }
    else
    {
        log_info("send AT to reset ESP32 but AT command not ready\n");
        return -3;
    }
}
/* AT command: AT+RESTORE */
int esp32_restore(comport_t *comport)
{
    int             rv;
    //rv = send_atcmd_check_ok(comport, "AT+RESTORE", 5000);
    rv = send_atcmd(comport, "AT+RESTORE", 5000, "ready", AT_ERRSTR, NULL, 0);
    if( rv < 0)
    {
        log_error("send AT command to restore ESP32 failed, rv=%d\n", rv);
        return -1;
    }
    if( check_at_ready(comport) )
    {
        log_info("send AT command to resstore ESP32 ready\n");
        return 0;
    }
    else
    {
        log_error("send AT command to restore ESP32 but AT not ready\n");
        return -3;
    }
}
/* AT command: ATE1 or ATE0 */
int esp32_set_echo(comport_t *comport, int enable)
{
    char           *at = enable? "ATE1" : "ATE0";
    return send_atcmd_check_ok(comport, at, 500);
}
/* AT command: AT+GMR */
int esp32_get_firmware(comport_t *comport, char *version, int size)
{
    if( !version || size<=0 )
        return -1;
    return send_atcmd_check_value(comport, "AT+GMR", 2000, version, size);
}
/* AT command: AT+SYSSTORE */
int esp32_set_sysstore(comport_t *comport, int enable)
{
    char            at[ATCMD_LEN]={'\0'};
    snprintf(at, sizeof(at), "AT+SYSSTORE=%d", enable?1:0);
    return send_atcmd_check_ok(comport, at, 1000);
}
/*+------------------------+
 *|    WiFi AT command     |
 *+------------------------+*/
/* AT command: AT+CWMODE */
int esp32_set_wmode(comport_t *comport, workmode_t mode, int autoconn)
{
    char            at[ATCMD_LEN]={'\0'};
    snprintf(at, sizeof(at), "AT+CWMODE=%d,%d", mode, autoconn?1:0);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSTAMAC/CIPAPMAC */
int esp32_get_macaddr(comport_t *comport, workmode_t mode, char *mac)
{
    if( !mac )
        return -1;
    if( MODE_SOFTAP == mode )
        return send_atcmd_check_request(comport, "AT+CIPAPMAC?", 3000, mac, MAC_LEN);
    else
        return send_atcmd_check_request(comport, "AT+CIPSTAMAC?", 3000, mac, MAC_LEN);
}
/* AT command: AT+CIPSTA/AT+CIPAP  */
int esp32_set_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway)
{
    char            at[ATCMD_LEN]={'\0'};
    if( !ip || !gateway )
        return -1;
    if( MODE_SOFTAP == mode )
        snprintf(at, sizeof(at), "AT+CIPAP=\"%s\",\"%s\"", ip, gateway);
    else
        snprintf(at, sizeof(at), "AT+CIPSTA=\"%s\",\"%s\"", ip, gateway);
    return send_atcmd_check_ok(comport, at, 2000);
}
/* AT command: AT+CIPSTA/AT+CIPAP  */
int esp32_get_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway)
{
    char           *at = MODE_SOFTAP==mode? "AT+CIPAP?" : "AT+CIPSTA?";
    char            buf[ATCMD_REPLY_LEN];
    int             rv;
    if( !ip || !gateway )
        return -1;
    rv = send_atcmd_check_value(comport, at, 3000, buf, sizeof(buf));
    if( rv < 0)
    {
        log_error("AT command query IP address failed, rv=%d\n", rv);
        return rv;
    }
    rv = parser_request_value(buf, "ip", ip, IP_LEN);
    if( rv < 0 )
    {
        log_error("Parser query IP address failed, rv=%d\n", rv);
        return rv;
    }
    rv = parser_request_value(buf, "gateway", gateway, IP_LEN);
    if( rv < 0 )
    {
        log_error("Parser query gateway address failed, rv=%d\n", rv);
        return rv;
    }
    return 0;
}
/* AT command: AT+CWDHCP */
int esp32_set_dhcp(comport_t *comport, workmode_t mode, int enable)
{
    char            at[ATCMD_LEN]={'\0'};
    snprintf(at, sizeof(at), "AT+CWDHCP=%d,%d", enable?1:0, mode);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CWAUTOCONN */
int esp32_set_autoconn(comport_t *comport, int enable)
{
    char            at[ATCMD_LEN]={'\0'};
    snprintf(at, sizeof(at), "AT+CWAUTOCONN=%d", enable?1:0);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CWLAP */
int esp32_list_ap(comport_t *comport, char *buf, int size)
{
    if( !buf || size<=0 )
        return -1;
    return send_atcmd_check_value(comport, "AT+CWLAP", 8000, buf, size);
}
/* AT command: AT+CWJAP */
int esp32_connect_ap(comport_t *comport, char *ssid, char *pwd)
{
    char            at[ATCMD_LEN]={'\0'};
    if( !ssid || !pwd )
        return -1;
    snprintf(at, sizeof(at), "AT+CWJAP=\"%s\",\"%s\"", ssid, pwd);
    return send_atcmd_check_ok(comport, at, 15000);
}
/* AT command: AT+CWQAP */
int esp32_disconn_ap(comport_t *comport)
{
    return send_atcmd_check_ok(comport, "AT+CWQAP", 5000);
}
/* AT command: AT+CWSTATE?  status value:
 * - 0: ESP32 station has not started any Wi-Fi connection.
 * - 1: ESP32 station has connected to an AP, but does not get an IPv4 address yet.
 * - 2: ESP32 station has connected to an AP, and got an IPv4 address.
 * - 3: ESP32 station is in Wi-Fi connecting or reconnecting state.
 * - 4: ESP32 station is in Wi-Fi disconnected state
 */
int esp32_join_status(comport_t *comport, int *status, char *ssid)
{
    char            buf[ATCMD_REPLY_LEN];
    int             rv;
    if( !status || !ssid )
        return -1;
    rv = send_atcmd_check_request(comport, "AT+CWSTATE?", 3000, buf, sizeof(buf));
    if( rv < 0)
    {
        log_error("AT command query join status failed, rv=%d\n", rv);
        return rv;
    }
    sscanf(buf, "%d,%s", status, ssid);
    return 0;
}
/* AT command:  AT+PING */
int esp32_ping(comport_t *comport, char *host, int timeout)
{
    char            at[ATCMD_LEN]={'\0'};
    if( !host )
        return -1;
    snprintf(at, sizeof(at), "AT+PING=\"%s\"", host);
    return send_atcmd_check_ok(comport, at, timeout);
}
/* AT command:  AT+CWSAP */
int esp32_set_softap(comport_t *comport, char *ssid, char *pwd, int channel, encmode_t encmode)
{
    char            at[ATCMD_LEN]={'\0'};
    if( !ssid || !pwd )
        return -1;
    snprintf(at, sizeof(at), "AT+CWSAP=\"%s\",\"%s\",%d,%d", ssid, pwd, channel, encmode);
    return send_atcmd_check_ok(comport, at, 5000);
}
/* AT command: AT+CWLIF */
int esp32_list_client(comport_t *comport, char *buf, int size)
{
    if( !buf || size<=0 )
        return -1;
    return send_atcmd_check_value(comport, "AT+CWLIF", 8000, buf, size);
}
/*+------------------------+
 *|   Socket AT command    |
 *+------------------------+*/
/* AT command: AT+CIPMUX */
int esp32_set_socket_mux(comport_t *comport, int enable)
{
    char            at[ATCMD_LEN]={'\0'};
    snprintf(at, sizeof(at), "AT+CIPMUX=%d", enable?1:0);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSERVERMAXCONN */
int esp32_set_socket_clients(comport_t *comport, int max)
{
    char            at[ATCMD_LEN]={'\0'};
    if( max <= 0 )
        return -1;
    snprintf(at, sizeof(at), "AT+CIPSERVERMAXCONN=%d", max);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSTO, timeout unit second */
int esp32_set_socket_timeout(comport_t *comport, int timeout)
{
    char            at[ATCMD_LEN]={'\0'};
    if( timeout <= 0 )
        return -1;
    snprintf(at, sizeof(at), "AT+CIPSTO=%d", timeout);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSERVER */
int esp32_set_tcp_server(comport_t *comport, int port)
{
    char            at[ATCMD_LEN]={'\0'};
    if( port <= 0 )
        return -1;
    snprintf(at, sizeof(at), "AT+CIPSERVER=1,%d,\"TCP\"", port);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSERVER */
int esp32_del_tcp_server(comport_t *comport, int port)
{
    char            at[ATCMD_LEN]={'\0'};
    if( port <= 0 )
        return -1;
    snprintf(at, sizeof(at), "AT+CIPSERVER=0,%d,\"TCP\"", port);
    return send_atcmd_check_ok(comport, at, 1500);
}
/* AT command: AT+CIPSTART */
int esp32_set_tcp_client(comport_t *comport, int mux, char *host, int port)
{
    char            at[ATCMD_LEN]={'\0'};
    char            buf[ATCMD_REPLY_LEN]={'\0'};
    int             keepalive = 60; /* unit second */
    int             rv,linkid = 0;
    if( !host || port <= 0 )
        return -1;
    rv = esp32_set_socket_mux(comport, mux);
    if(rv < 0)
        return rv;
    if( mux )
        snprintf(at, sizeof(at), "AT+CIPSTART=1,\"TCP\",\"%s\",%d,%d", host, port, keepalive);
    else
        snprintf(at, sizeof(at), "AT+CIPSTART=\"TCP\",\"%s\",%d,%d", host, port, keepalive);
    rv = send_atcmd_check_value(comport, at, 1500, buf, sizeof(buf));
    if(rv < 0)
        return rv;
    sscanf(buf, "%d,", &linkid);
    return linkid;
}
project/booster/at-esp32.h
New file
@@ -0,0 +1,140 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  at-esp32.h
 *    Description:  This file is ESP32 AT command low level API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#ifndef  _AT_ESP32_H_
#define  _AT_ESP32_H_
#include "atcmd.h"
enum
{
    DISABLE = 0,    /* common disable */
    ENABLE,         /* common enable  */
};
#define MAC_LEN     18 /* 11:22:33:44:55:66 */
#define IP_LEN      16 /* 255.255.255.255 */
/*+------------------------+
 *|    Baisc AT command    |
 *+------------------------+*/
/* AT command: AT+RST */
extern int esp32_reset(comport_t *comport);
/* AT command: AT+RESTORE */
extern int esp32_restore(comport_t *comport);
/* AT command: ATE1 or ATE0 */
extern int esp32_set_echo(comport_t *comport, int enable);
/* AT command: AT+GMR */
extern int esp32_get_firmware(comport_t *comport, char *version, int size);
/* AT command: AT+SYSSTORE */
extern int esp32_set_sysstore(comport_t *comport, int enable);
/*+------------------------+
 *|    WiFi AT command     |
 *+------------------------+*/
typedef enum
{
    MODE_DISABLE=0, /* Wi-Fi RF will be disabled */
    MODE_STATION,   /* Station mode */
    MODE_SOFTAP,    /* SoftAP mode */
    MODE_WDS,       /* Wireless Distribution System: Both Station & SoftAP mode */
} workmode_t;
/* AT command: AT+CWMODE */
extern int esp32_set_wmode(comport_t *comport, workmode_t mode, int autoconn);
/* AT command: AT+CWDHCP */
extern int esp32_set_dhcp(comport_t *comport, workmode_t mode, int enable);
/* AT command: AT+CWAUTOCONN */
extern int esp32_set_autoconn(comport_t *comport, int enable);
/* AT command: AT+CIPSTAMAC/CIPAPMAC */
extern int esp32_get_macaddr(comport_t *comport, workmode_t mode, char *mac);
/* AT command: AT+CIPSTA/CIPAP */
extern int esp32_get_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway);
/* AT command: AT+CIPSTA/AT+CIPAP  */
extern int esp32_set_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway);
/* AT command: AT+CWLAP */
extern int esp32_list_ap(comport_t *comport, char *buf, int size);
/* AT command: AT+CWJAP */
extern int esp32_connect_ap(comport_t *comport, char *ssid, char *pwd);
/* AT command: AT+CWQAP */
extern int esp32_disconn_ap(comport_t *comport);
/* AT command: AT+CWSTATE? , status value:
 * - 0: ESP32 station has not started any Wi-Fi connection.
 * - 1: ESP32 station has connected to an AP, but does not get an IPv4 address yet.
 * - 2: ESP32 station has connected to an AP, and got an IPv4 address.
 * - 3: ESP32 station is in Wi-Fi connecting or reconnecting state.
 * - 4: ESP32 station is in Wi-Fi disconnected state
 */
extern int esp32_join_status(comport_t *comport, int *status, char *ssid);
/* AT command:  AT+PING */
extern int esp32_ping(comport_t *comport, char *host, int timeout);
/* AT command:  AT+CWSAP */
typedef enum
{
    MODE_OPEN,
    /* WEP not support */
    MODE_WPAPSK = 2,
    MODE_WPA2PSK,
    MODE_WPA_WPA2PSK,
} encmode_t;
extern int esp32_set_softap(comport_t *comport, char *ssid, char *pwd, int channel, encmode_t encmode);
/* AT command:  AT+CWLIF */
extern int esp32_list_client(comport_t *comport, char *buf, int size);
/*+------------------------+
 *|   Socket AT command    |
 *+------------------------+*/
/* AT command: CIPMUX */
extern int esp32_set_socket_mux(comport_t *comport, int enable);
/* AT command: AT+CIPSERVERMAXCONN */
extern int esp32_set_socket_clients(comport_t *comport, int max);
/* AT command: AT+CIPSTO, timeout unit second */
extern int esp32_set_socket_timeout(comport_t *comport, int timeout);
/* AT command: AT+CIPSERVER */
extern int esp32_set_tcp_server(comport_t *comport, int port);
/* AT command: AT+CIPSERVER */
extern int esp32_del_tcp_server(comport_t *comport, int port);
/* AT command: AT+CIPSTART */
extern int esp32_set_tcp_client(comport_t *comport, int mux, char *host, int port);
/*+------------------------+
 *|     BLE AT command     |
 *+------------------------+*/
// RFU
#endif   /* ----- #ifndef _AT_ESP32_H_  ----- */
project/booster/atcmd.c
New file
@@ -0,0 +1,300 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  atcmd.c
 *    Description:  This file is lowlevel AT command send and parser API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#include <ctype.h>
#include "logger.h"
#include "atcmd.h"
/*  Description: this function used to send an AT command from serial port and wait for reply message from
 *               the communication module, and it will return once get expet/error string or timeout.
 *
 *    Arugments:
 *         $comport: the serial port which connected to GPRS/3G/4G/NB-IoT/WiFi/BLE/Zigbee/LoRa...
 *              $at: the AT command need to be sent, without "\r\n"
 *         $timeout: wait for module reply for AT command timeout value, unit micro seconds(ms)
 *          $exepct: the expect string reply from communication module
 *           $error: the error string reply from communication module
 *           $reply: the module reply message output buffer
 *            $size: the output buffer ($reply) size
 *
 * Return value: <0: Function error   0: Got error string   1: Got expect string 2: Receive util timeout
 *
 */
int send_atcmd(comport_t *comport, char *at, unsigned long timeout, char *expect, char *error, char *reply, int size)
{
    int                    i, rv = 0;
    int                    res = ATRES_TIMEOUT;
    int                    bytes = 0;
    char                   buf[ATCMD_REPLY_LEN] = {'\0'};
    char                   atcmd[ATCMD_LEN] = {'\0'};
    if( !comport || !at )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    if( comport->fd <= 0 )
    {
        log_error("comport[%s] not opened\n");
        return -2;
    }
    /* flushes both data received but not read, and data written but not transmitted in serial port */
    tcflush(comport->fd, TCIOFLUSH);
    snprintf(atcmd, sizeof(atcmd), "%s%s", at, AT_SUFFIX);
    rv=comport_send( comport, atcmd, strlen(atcmd) );
    if(rv < 0)
    {
        log_error("send AT command \"%s\" to \"%s\" failed, rv=%d\n", at, comport->devname, rv);
        return -3;
    }
    res = ATRES_TIMEOUT;
    memset( buf, 0, sizeof(buf) );
    for(i=0; i<timeout/10; i++)
    {
        if( bytes >= sizeof(buf) )
            break;
        rv=comport_recv( comport, buf+bytes, sizeof(buf)-bytes, 10);
        if(rv < 0)
        {
            log_error("send AT command \'%s\' to \'%s\' failed, rv=%d\n", at, comport->devname, rv);
            return -3;
        }
        bytes += rv;
        if( expect && strstr(buf, expect) )
        {
            log_debug("send AT command \"%s\" and got reply \"OK\"\n", at);
            res = ATRES_EXPECT;
            break;
        }
        if( error && strstr(buf, error) )
        {
            log_debug("send AT command \"%s\" and got reply \"ERROR\"\n", at);
            res = ATRES_ERROR;
            break;
        }
    }
    if( bytes > 0 )
        log_trace("AT command reply:%s", buf);
    if( reply && size>0 )
    {
        bytes = strlen(buf)>size ? size : strlen(buf);
        memset(reply, 0, size);
        strncpy(reply, buf, bytes);
        log_debug("copy out AT command \"%s\" reply message: \n%s", at, reply);
    }
    return res;
}
/*
 *  Description: Send AT command which will only reply by "OK" or "ERROR", such as AT:
 *                 Reply:   \r\nOK\r\n
 * Return Value: 0: OK     -X: Failure
 */
int send_atcmd_check_ok(comport_t *comport, char *at, unsigned long timeout)
{
    int                     rv;
    if( !comport || !at )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    rv=send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, NULL, 0);
    if( ATRES_EXPECT == rv )
    {
        return 0;
    }
    else
    {
        return -2;
    }
}
/*
 *  Description: Send AT command which will reply by a value directly in a single line, such as AT+CGMM:
 *                  Reply:   \r\nEC20F\r\nOK\r\n
 *
 * Return Value: 0: OK     -X: Failure
 */
int send_atcmd_check_value(comport_t *comport, char *at, unsigned long timeout, char *reply, int size)
{
    int                     rv, len;
    char                    buf[ATCMD_REPLY_LEN];
    char                   *ptr_start = buf;
    char                   *ptr_end;
    if( !comport || !at || !reply || size<=0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    rv = send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, buf, ATCMD_REPLY_LEN);
    if( rv <= 0 )
    {
        return -2;
    }
    /* Skip the echo back command line */
    if( !strncmp(buf, at, strlen(at)) )
    {
        ptr_start=strchr(buf, '\n');
        if( !ptr_start )
        {
            log_error("reply message got wrong\n");
            return -3;
        }
        ptr_start++;   /* skip '\n'  */
    }
    /* find end reply string "\r\nOK\r\n"  */
    ptr_end = strstr(ptr_start, AT_OKSTR);
    if( ptr_end )
    {
        len = ptr_end - ptr_start;
    }
    else
    {
        len = strlen(buf) - (ptr_start-buf);
    }
    memset(reply, 0, size);
    len = len>size ? size : len;
    memcpy(reply, ptr_start, len);
    return 0;
}
/*
 *  Description: Parser the $value from $key like "xxx: " line, such as AT+CSQ:
 *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n
 *
 * Return Value: 0: OK     -X: Failure
 */
int parser_request_value(char *buf, char *key, char *value, int size)
{
    char                   *ptr;
    int                    i = 0;
    if( !buf || !key || !value || size<=0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    ptr = strstr(buf, key);
    if( !ptr )
    {
        log_debug("Not found key \"%s\" in %s\n", key, buf);
        return -2;
    }
    ptr=strchr(ptr, ':');  /* found ':' before the value */
    if( !ptr )
    {
        log_debug("Not found ':' before value\n");
        return -3;
    }
    ptr++;   /* skip ':'  */
    if( *ptr == '\"' ) /* skip " */
        ptr++;
    memset(value, 0, size);
    while(*ptr!='\r' && i<size-1)
    {
        if( !isspace(*ptr) && *ptr!='\"') /* skip space,\r,\n ...  */
            value[i++] = *ptr;
        ptr ++;
    }
    ptr++; /* skip  */
    return 0;
}
/*
 *  Description: Send AT command which will reply by the value  with a prefix "+CMD: " line, such as AT+CSQ:
 *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n
 *
 * Return Value: 0: OK     -X: Failure
 */
int send_atcmd_check_request(comport_t *comport, char *at, unsigned long timeout, char *reply, int size)
{
    int                     i = 0;
    int                     rv;
    char                    buf[ATCMD_REPLY_LEN];
    char                   *ptr;
    if( !comport || !at || !reply || size<=0 )
    {
        log_error("Invalid input arguments\n");
        return -1;
    }
    rv = send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, buf, ATCMD_REPLY_LEN);
    if( rv <= 0 )
    {
        return -2;
    }
    ptr=strchr(buf, '+');  /* found '+' before the value */
    if( !ptr )
    {
        log_error("reply message got wrong\n");
        return -3;
    }
    ptr++;   /* skip '+'  */
    ptr=strchr(buf, ':');  /* found ':' before the value */
    if( !ptr )
    {
        log_error("reply message got wrong\n");
        return -3;
    }
    ptr++;   /* skip ':'  */
    if( *ptr == '\"' ) /* skip " */
        ptr++;
    memset(reply, 0, size);
    while(*ptr!='\r' && i<size-1)
    {
        if( !isspace(*ptr) && *ptr!='\"') /* skip space,\r,\n ...  */
            reply[i++] = *ptr;
        ptr ++;
    }
    return 0;
}
project/booster/atcmd.h
New file
@@ -0,0 +1,93 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  atcmd.h
 *    Description:  This file is lowlevel AT command send and parser API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#ifndef  _ATCMD_H_
#define  _ATCMD_H_
#include "comport.h"
/* AT command common reply message max length  */
#define ATCMD_REPLY_LEN          1024 /* Max AT command reply message length */
#define ATCMD_LEN                256 /* Max AT command length */
/* AT command reply message got expect or error string */
#define AT_OKSTR                 "\r\nOK\r\n"     /* expect string always be OK */
#define AT_ERRSTR                "\r\nERROR\r\n"  /* error string always be ERROR */
/* AT command should be end by $AT_SUFFIX */
#define AT_SUFFIX                "\r\n"
/* send_atcmd)( return value status */
enum
{
    ATRES_ERROR,    /* AT command reply got error string, such as "ERROR\r\n" */
    ATRES_EXPECT,   /* AT command reply got expect string, such as "OK\r\n" */
    ATRES_TIMEOUT,  /* AT command not get error/expect string, receive util timeout */
};
/*  Description: this function used to send an AT command from serial port and wait for reply message from
 *               the communication module, and it will return once get expet/error string or timeout.
 *
 *    Arugments:
 *         $comport: the serial port which connected to GPRS/3G/4G/NB-IoT/WiFi/BLE/Zigbee/LoRa...
 *              $at: the AT command need to be sent, without "\r\n"
 *         $timeout: wait for module reply for AT command timeout value, unit micro seconds(ms)
 *          $exepct: the expect string reply from communication module
 *           $error: the error string reply from communication module
 *           $reply: the module reply message output buffer
 *            $size: the output buffer ($reply) size
 *
 * Return value: <0: Function error   0: Got error string   1: Got expect string 2: Receive util timeout
 *
 */
int send_atcmd(comport_t *comport, char *at, unsigned long timeout, char *expect, char *error, char *reply, int size);
/*
 *  Description: Send AT command which will only reply by "OK" or "ERROR", such as AT:
 *                  Reply:   \r\nOK\r\n
 *
 * Return Value:  0: OK     -X: ERROR
 */
int send_atcmd_check_ok(comport_t *comport, char *at, unsigned long timeout);
/*
 *  Description: Send AT command which will reply by a value directly in a single line, such as AT+CGMM:
 *                  Reply:   \r\nEC20F\r\nOK\r\n
 *
 * Return Value:  0: OK     -X: ERROR
 */
int send_atcmd_check_value(comport_t *comport, char *at, unsigned long timeout, char *reply, int size);
/*
 *  Description: Parser the $value from $key like "xxx: " line, such as AT+CSQ:
 *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n
 *
 * Return Value: 0: OK     -X: Failure
 */
int parser_request_value(char *buf, char *key, char *value, int size);
/*
 *  Description: Send AT command which will reply by the value  with a prefix "+CMD: " line, such as AT+CSQ:
 *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n
 *
 * Return Value:  0: OK     -X: ERROR
 */
int send_atcmd_check_request(comport_t *comport, char *at, unsigned long timeout, char *repy, int size);
#endif   /* ----- #ifndef _ATCMD_H_  ----- */
project/booster/comport.c
New file
@@ -0,0 +1,520 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  comport.c
 *    Description:  This file is linux comport common API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#include "comport.h"
#define CONFIG_PRINT_LOGGER
//#define CONFIG_PRINT_STDOUT
#if ( defined CONFIG_PRINT_LOGGER )
#include "logger.h"
#define dbg_print(format,args...) log_error(format, ##args)
#elif ( defined CONFIG_PRINT_STDOUT )
#define dbg_print(format,args...) printf(format, ##args)
#else
#define dbg_print(format,args...) do{} while(0);
#endif
static inline void set_settings(comport_t * comport, const char *settings);
/*
 *  description: Open the serial port
 *
 *   input args: $comport:  corresponding comport point
 *               $dev_name:  The comport device name path, such as '/dev/ttyS3'
 *               $baudrate:  The baudrate, such as 115200
 *               $settings:  The databit,parity,stopbit,flowctrl settings, such as '8N1N'
 *
 * return value: The comport opened file description, <0 means failure
 */
int comport_open(comport_t *comport, const char *devname, long baudrate, const char *settings)
{
    int                         rv = -1;
    struct termios              old_cfg, new_cfg;
    int                         old_flags;
    long                        tmp;
    if( !comport || !devname )
    {
        dbg_print("invalid input arugments\n");
        return -1;
    }
    /*+-----------------------+
     *| open the serial port  |
     *+-----------------------+*/
    memset(comport, 0, sizeof(*comport));
    strncpy(comport->devname, devname, sizeof(comport->devname));
    comport->baudrate = baudrate;
    comport->fd = -1;
    comport->fragsize = CONFIG_DEF_FRAGSIZE;
    set_settings(comport, settings);
    if( !strstr(comport->devname, "tty") )
    {
        dbg_print("comport device \"%s\" is not tty device\n", comport->devname);
        return -2;
    }
    comport->fd = open(comport->devname, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if( comport->fd<0 )
    {
        dbg_print("comport open \"%s\" failed:%s\n", comport->devname, strerror(errno));
        return -3;
    }
    if(   (-1 != (old_flags = fcntl(comport->fd, F_GETFL, 0)))
       && (-1 != fcntl(comport->fd, F_SETFL, old_flags & ~O_NONBLOCK)) )
    {
        /* Flush input and output */
        tcflush(comport->fd, TCIOFLUSH);
    }
    else
    {
        rv = -4;
        goto CleanUp;
    }
    if (0 != tcgetattr(comport->fd, &old_cfg))
    {
        rv = -5;
        goto CleanUp;
    }
    /*+-----------------------+
     *| configure serial port |
     *+-----------------------+*/
    memset(&new_cfg, 0, sizeof(new_cfg));
    new_cfg.c_cflag &= ~CSIZE;
    new_cfg.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    new_cfg.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    new_cfg.c_oflag &= ~(OPOST);
    /* Set the data bit */
    switch (comport->databit)
    {
        case 0x07:
            new_cfg.c_cflag |= CS7;
            break;
        case 0x06:
            new_cfg.c_cflag |= CS6;
            break;
        case 0x05:
            new_cfg.c_cflag |= CS5;
            break;
        default:
            new_cfg.c_cflag |= CS8;
            break;
    }
    /* Set the parity */
    switch (comport->parity)
    {
        case 0x01:    /* Odd */
            new_cfg.c_cflag |= (PARENB | PARODD);
            new_cfg.c_cflag |= (INPCK | ISTRIP);
            break;
        case 0x02:    /* Even */
            new_cfg.c_cflag |= PARENB;
            new_cfg.c_cflag &= ~PARODD;;
            new_cfg.c_cflag |= (INPCK | ISTRIP);
            break;
        case 0x03:
            new_cfg.c_cflag &= ~PARENB;
            new_cfg.c_cflag &= ~CSTOPB;
            break;
        default:
            new_cfg.c_cflag &= ~PARENB;
    }
    /* Set Stop bit */
    if (0x01 != comport->stopbit)
    {
        new_cfg.c_cflag |= CSTOPB;
    }
    else
    {
        new_cfg.c_cflag &= ~CSTOPB;
    }
    /* Set flow control */
    switch (comport->flowctrl)
    {
        case 1:       /* Software control */
        case 3:
            new_cfg.c_cflag &= ~(CRTSCTS);
            new_cfg.c_iflag |= (IXON | IXOFF);
            break;
        case 2:       /* Hardware control */
            new_cfg.c_cflag |= CRTSCTS;
            new_cfg.c_iflag &= ~(IXON | IXOFF);
            break;
        default:      /* NONE */
            new_cfg.c_cflag &= ~(CRTSCTS);
            new_cfg.c_iflag &= ~(IXON | IXOFF);
            break;
    }
    /* Set baudrate */
    switch (comport->baudrate)
    {
        /* Upper is not POSIX(bits/termios-baud.h) */
        case 4000000:
            tmp = B4000000;
            break;
        case 3500000:
            tmp = B3500000;
            break;
        case 3000000:
            tmp = B3000000;
            break;
        case 2500000:
            tmp = B2500000;
            break;
        case 2000000:
            tmp = B2000000;
            break;
        case 1500000:
            tmp = B1500000;
            break;
        case 1152000:
            tmp = B1152000;
            break;
        case 1000000:
            tmp = B1000000;
            break;
        case 921600:
            tmp = B921600;
            break;
        case 576000:
            tmp = B576000;
            break;
        case 500000:
            tmp = B500000;
            break;
        case 460800:
            tmp = B460800;
            break;
        case 230400:
            tmp = B230400;
            break;
        case 115200:
            tmp = B115200;
            break;
        case 57600:
            tmp = B57600;
            break;
        /* Below is POSIX(bits/termios.h) */
        case 38400:
            tmp = B38400;
            break;
        case 19200:
            tmp = B19200;
            break;
        case 9600:
            tmp = B9600;
            break;
        case 4800:
            tmp = B4800;
            break;
        case 2400:
            tmp = B2400;
            break;
        case 1800:
            tmp = B1800;
            break;
        case 1200:
            tmp = B1200;
            break;
        case 600:
            tmp = B600;
            break;
        case 300:
            tmp = B300;
            break;
        case 200:
            tmp = B200;
            break;
        case 150:
            tmp = B150;
            break;
        case 134:
            tmp = B134;
            break;
        case 110:
            tmp = B110;
            break;
        case 75:
            tmp = B75;
            break;
        case 50:
            tmp = B50;
            break;
        default:
            tmp = B115200;
    }
    cfsetispeed(&new_cfg, tmp);
    cfsetispeed(&new_cfg, tmp);
    /* Set the Com port timeout settings */
    new_cfg.c_cc[VMIN] = 0;
    new_cfg.c_cc[VTIME] = 0;
    tcflush(comport->fd, TCIFLUSH);
    if (0 != tcsetattr(comport->fd, TCSANOW, &new_cfg))
    {
        rv = -6;          // Failed to set device com port settings
        goto CleanUp;
    }
    rv = comport->fd;
CleanUp:
    return rv;
}
/*
 *  description: close comport
 *   input args: $comport:  corresponding comport point
 */
void comport_close(comport_t *comport)
{
    if( !comport )
    {
        dbg_print("invalid input arugments\n");
        return ;
    }
    if ( comport->fd >= 0 )
    {
        close(comport->fd);
    }
    comport->fd = -1;
    return ;
}
/*
 *  description: write $data_bytes $data to $comport
 * return value: 0: write ok  <0: write failure
 */
int comport_send(comport_t *comport, char *data, int data_bytes)
{
    char                       *ptr;
    int                         left, bytes = 0;
    int                         rv = 0;
    if( !comport || !data || data_bytes<=0 )
    {
        dbg_print("invalid input arugments\n");
        return -1;
    }
    if( comport->fd < 0 )
    {
        dbg_print("Serail port not opened\n");
        return -2;
    }
    ptr = data;
    left = data_bytes;
    while( left > 0 )
    {
        /* Large data, then slice them to frag and send */
        bytes = left>comport->fragsize ? comport->fragsize : left;
        rv = write(comport->fd, ptr, bytes);
        if( rv<0 )
        {
            rv = -3;
            break;
        }
        left -= rv;
        ptr += rv;
    }
    return rv;
}
/*
 *  description: read data from $comport in $timeout <ms> to $buf no more than $buf_size bytes
 * return value: the actual read data bytes, <0: read failure
 */
int comport_recv(comport_t *comport, char *buf, int buf_size, unsigned long timeout)
{
    fd_set                      rdfds, exfds;
    struct timeval              to, *to_ptr = NULL;
    int                         ret, rv = 0;
    int                         bytes = 0;
    if ( !comport || !buf || buf_size<=0 )
    {
        dbg_print("invalid input arugments\n");
        return -1;
    }
    if ( comport->fd < 0 )
    {
        dbg_print("Serail port not opened\n");
        return -2;
    }
    memset(buf, 0, buf_size);
    FD_ZERO(&rdfds);
    FD_ZERO(&exfds);
    FD_SET(comport->fd, &rdfds);
    FD_SET(comport->fd, &exfds);
    if( TIMEOUT_NONE != timeout )
    {
        to.tv_sec = (time_t) (timeout / 1000);
        to.tv_usec = (long)(1000 * (timeout % 1000));
        to_ptr = &to;
    }
    while( 1 )
    {
        /* check got data arrive or not */
        ret = select(comport->fd+1, &rdfds, 0, &exfds, to_ptr);
        if( ret<0 )
        {
            /* EINTR means catch interrupt signal */
            dbg_print("comport select() failed: %s\n", strerror(errno));
            rv = EINTR==errno ? 0 : -3;
            break;
        }
        else if( 0 == ret ) /* timeout */
        {
            break;
        }
        /* read data from comport */
        ret = read(comport->fd, buf+bytes, buf_size-bytes);
        if(ret <= 0)
        {
            dbg_print("comport read() failed: %s\n", strerror(errno));
            break;
        }
        bytes += ret;
        if( bytes >= buf_size )
            break;
        /* try to read data in 1ms again, if no data arrive it will break */
        to.tv_sec = 0;
        to.tv_usec = 10000;
        to_ptr = &to;
    }
    if( !rv )
        rv = bytes;
    return rv;
}
/**************************************************************************************
 *  Description: Set the comport databit,parity,stopbit,flowctrl into the comport structure
 *   Input Args: comport: the comport_t pointer
 *               settings: The databit/parity/stopbit/flowctrl settings as like "8N1N"
 *  Output Args: NONE
 * Return Value: NONE
 *************************************************************************************/
static inline void set_settings(comport_t * comport, const char *settings)
{
    if( !settings || !comport )
    {
        dbg_print("invalid input arugments\n");
        return ;
    }
    switch (settings[0])        /* data bit */
    {
        case '7':
            comport->databit = 7;
            break;
        case '8':
        default:
            comport->databit = 8;
            break;
    }
    switch (settings[1])        /* parity */
    {
        case 'O':
        case 'o':
            comport->parity = 1;
            break;
        case 'E':
        case 'e':
            comport->parity = 2;
            break;
        case 'S':
        case 's':
            comport->parity = 3;
            break;
        case 'N':
        case 'n':
        default:
            comport->parity = 0;
            break;
    }
    switch (settings[2])        /* stop bit */
    {
        case '0':
            comport->stopbit = 0;
            break;
        case '1':
        default:
            comport->stopbit = 1;
            break;
    }
    switch (settings[3])        /* flow control */
    {
        case 'S':
        case 's':
            comport->flowctrl = 1;
            break;
        case 'H':
        case 'h':
            comport->flowctrl = 2;
            break;
        case 'B':
        case 'b':
            comport->flowctrl = 3;
            break;
        case 'N':
        case 'n':
        default:
            comport->flowctrl = 0;
            break;
    }
}
project/booster/comport.h
New file
@@ -0,0 +1,74 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  comport.c
 *    Description:  This file is linux comport common API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#ifndef  _COMPORT_H_
#define  _COMPORT_H_
#include  <stdio.h>
#include  <stdlib.h>
#include  <unistd.h>
#include  <string.h>
#include  <getopt.h>
#include  <fcntl.h>
#include  <errno.h>
#include  <termios.h>
#include  <sys/stat.h>
#include  <sys/wait.h>
#include  <sys/types.h>
#include  <sys/stat.h>
#include  <sys/select.h>
#define CONFIG_DEF_FRAGSIZE    128
typedef struct comport_s
{
    char           devname[32];
    unsigned char  databit, parity, stopbit, flowctrl;
    long           baudrate;
    int            fd;
    int            fragsize; /* frag size when do large data send */
} comport_t;
/*
 *  description: Open the comport and returned by $comport
 *
 *   input args: $comport:  corresponding comport handler
 *               $devname:  The comport device name path, such as '/dev/ttyS3'
 *               $baudrate: The baudrate, such as 115200
 *               $settings: The databit,parity,stopbit,flowctrl settings, such as '8N1N'
 *
 * return value: The comport opened file description, <0 means failure
 */
extern int  comport_open(comport_t *comport, const char *devname, long baudrate, const char *settings);
/*
 *  description: close comport
 *   input args: $comport:  corresponding comport handler
 */
extern void comport_close(comport_t *comport);
/*
 *  description: write $bytes $data to $comport
 * return value: 0: write ok  <0: write failure
 */
extern int  comport_send(comport_t *comport, char *data, int data_bytes);
/*
 *  description: read data from $comport in $timeout <ms> to $buf no more than $buf_size bytes
 * return value: the actual read data bytes, <0: read failure
 */
#define TIMEOUT_NONE           0
extern int  comport_recv(comport_t *comport, char *buf, int buf_size, unsigned long timeout);
#endif
project/booster/dictionary.c
New file
@@ -0,0 +1,381 @@
/*-------------------------------------------------------------------------*/
/**
   @file    dictionary.c
   @author  N. Devillard
   @brief   Implements a dictionary for string variables.
   @url     https://github.com/ndevilla/iniparser
   This module implements a simple dictionary object, i.e. a list
   of string/string associations. This object is useful to store e.g.
   informations retrieved from a configuration file (ini files).
*/
/*--------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------
                                Includes
 ---------------------------------------------------------------------------*/
#include "dictionary.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/** Maximum value size for integers and doubles. */
#define MAXVALSZ    1024
/** Minimal allocated number of entries in a dictionary */
#define DICTMINSZ   128
/** Invalid key token */
#define DICT_INVALID_KEY    ((char*)-1)
/*---------------------------------------------------------------------------
                            Private functions
 ---------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/**
  @brief    Duplicate a string
  @param    s String to duplicate
  @return   Pointer to a newly allocated string, to be freed with free()
  This is a replacement for strdup(). This implementation is provided
  for systems that do not have it.
 */
/*--------------------------------------------------------------------------*/
static char * xstrdup(const char * s)
{
    char * t ;
    size_t len ;
    if (!s)
        return NULL ;
    len = strlen(s) + 1 ;
    t = (char*) malloc(len) ;
    if (t) {
        memcpy(t, s, len) ;
    }
    return t ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Double the size of the dictionary
  @param    d Dictionary to grow
  @return   This function returns non-zero in case of failure
 */
/*--------------------------------------------------------------------------*/
static int dictionary_grow(dictionary * d)
{
    char        ** new_val ;
    char        ** new_key ;
    unsigned     * new_hash ;
    new_val  = (char**) calloc(d->size * 2, sizeof *d->val);
    new_key  = (char**) calloc(d->size * 2, sizeof *d->key);
    new_hash = (unsigned*) calloc(d->size * 2, sizeof *d->hash);
    if (!new_val || !new_key || !new_hash) {
        /* An allocation failed, leave the dictionary unchanged */
        if (new_val)
            free(new_val);
        if (new_key)
            free(new_key);
        if (new_hash)
            free(new_hash);
        return -1 ;
    }
    /* Initialize the newly allocated space */
    memcpy(new_val, d->val, d->size * sizeof(char *));
    memcpy(new_key, d->key, d->size * sizeof(char *));
    memcpy(new_hash, d->hash, d->size * sizeof(unsigned));
    /* Delete previous data */
    free(d->val);
    free(d->key);
    free(d->hash);
    /* Actually update the dictionary */
    d->size *= 2 ;
    d->val = new_val;
    d->key = new_key;
    d->hash = new_hash;
    return 0 ;
}
/*---------------------------------------------------------------------------
                            Function codes
 ---------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/**
  @brief    Compute the hash key for a string.
  @param    key     Character string to use for key.
  @return   1 unsigned int on at least 32 bits.
  This hash function has been taken from an Article in Dr Dobbs Journal.
  This is normally a collision-free function, distributing keys evenly.
  The key is stored anyway in the struct so that collision can be avoided
  by comparing the key itself in last resort.
 */
/*--------------------------------------------------------------------------*/
unsigned dictionary_hash(const char * key)
{
    size_t      len ;
    unsigned    hash ;
    size_t      i ;
    if (!key)
        return 0 ;
    len = strlen(key);
    for (hash=0, i=0 ; i<len ; i++) {
        hash += (unsigned)key[i] ;
        hash += (hash<<10);
        hash ^= (hash>>6) ;
    }
    hash += (hash <<3);
    hash ^= (hash >>11);
    hash += (hash <<15);
    return hash ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Create a new dictionary object.
  @param    size    Optional initial size of the dictionary.
  @return   1 newly allocated dictionary object.
  This function allocates a new dictionary object of given size and returns
  it. If you do not know in advance (roughly) the number of entries in the
  dictionary, give size=0.
 */
/*-------------------------------------------------------------------------*/
dictionary * dictionary_new(size_t size)
{
    dictionary  *   d ;
    /* If no size was specified, allocate space for DICTMINSZ */
    if (size<DICTMINSZ) size=DICTMINSZ ;
    d = (dictionary*) calloc(1, sizeof *d) ;
    if (d) {
        d->size = size ;
        d->val  = (char**) calloc(size, sizeof *d->val);
        d->key  = (char**) calloc(size, sizeof *d->key);
        d->hash = (unsigned*) calloc(size, sizeof *d->hash);
    }
    return d ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete a dictionary object
  @param    d   dictionary object to deallocate.
  @return   void
  Deallocate a dictionary object and all memory associated to it.
 */
/*--------------------------------------------------------------------------*/
void dictionary_del(dictionary * d)
{
    ssize_t  i ;
    if (d==NULL) return ;
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]!=NULL)
            free(d->key[i]);
        if (d->val[i]!=NULL)
            free(d->val[i]);
    }
    free(d->val);
    free(d->key);
    free(d->hash);
    free(d);
    return ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get a value from a dictionary.
  @param    d       dictionary object to search.
  @param    key     Key to look for in the dictionary.
  @param    def     Default value to return if key not found.
  @return   1 pointer to internally allocated character string.
  This function locates a key in a dictionary and returns a pointer to its
  value, or the passed 'def' pointer if no such key can be found in
  dictionary. The returned character pointer points to data internal to the
  dictionary object, you should not try to free it or modify it.
 */
/*--------------------------------------------------------------------------*/
const char * dictionary_get(const dictionary * d, const char * key, const char * def)
{
    unsigned    hash ;
    ssize_t      i ;
    hash = dictionary_hash(key);
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        /* Compare hash */
        if (hash==d->hash[i]) {
            /* Compare string, to avoid hash collisions */
            if (!strcmp(key, d->key[i])) {
                return d->val[i] ;
            }
        }
    }
    return def ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Set a value in a dictionary.
  @param    d       dictionary object to modify.
  @param    key     Key to modify or add.
  @param    val     Value to add.
  @return   int     0 if Ok, anything else otherwise
  If the given key is found in the dictionary, the associated value is
  replaced by the provided one. If the key cannot be found in the
  dictionary, it is added to it.
  It is Ok to provide a NULL value for val, but NULL values for the dictionary
  or the key are considered as errors: the function will return immediately
  in such a case.
  Notice that if you dictionary_set a variable to NULL, a call to
  dictionary_get will return a NULL value: the variable will be found, and
  its value (NULL) is returned. In other words, setting the variable
  content to NULL is equivalent to deleting the variable from the
  dictionary. It is not possible (in this implementation) to have a key in
  the dictionary without value.
  This function returns non-zero in case of failure.
 */
/*--------------------------------------------------------------------------*/
int dictionary_set(dictionary * d, const char * key, const char * val)
{
    ssize_t         i ;
    unsigned       hash ;
    if (d==NULL || key==NULL) return -1 ;
    /* Compute hash for this key */
    hash = dictionary_hash(key) ;
    /* Find if value is already in dictionary */
    if (d->n>0) {
        for (i=0 ; i<d->size ; i++) {
            if (d->key[i]==NULL)
                continue ;
            if (hash==d->hash[i]) { /* Same hash value */
                if (!strcmp(key, d->key[i])) {   /* Same key */
                    /* Found a value: modify and return */
                    if (d->val[i]!=NULL)
                        free(d->val[i]);
                    d->val[i] = (val ? xstrdup(val) : NULL);
                    /* Value has been modified: return */
                    return 0 ;
                }
            }
        }
    }
    /* Add a new value */
    /* See if dictionary needs to grow */
    if (d->n==d->size) {
        /* Reached maximum size: reallocate dictionary */
        if (dictionary_grow(d) != 0)
            return -1;
    }
    /* Insert key in the first empty slot. Start at d->n and wrap at
       d->size. Because d->n < d->size this will necessarily
       terminate. */
    for (i=d->n ; d->key[i] ; ) {
        if(++i == d->size) i = 0;
    }
    /* Copy key */
    d->key[i]  = xstrdup(key);
    d->val[i]  = (val ? xstrdup(val) : NULL) ;
    d->hash[i] = hash;
    d->n ++ ;
    return 0 ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete a key in a dictionary
  @param    d       dictionary object to modify.
  @param    key     Key to remove.
  @return   void
  This function deletes a key in a dictionary. Nothing is done if the
  key cannot be found.
 */
/*--------------------------------------------------------------------------*/
void dictionary_unset(dictionary * d, const char * key)
{
    unsigned    hash ;
    ssize_t      i ;
    if (key == NULL || d == NULL) {
        return;
    }
    hash = dictionary_hash(key);
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        /* Compare hash */
        if (hash==d->hash[i]) {
            /* Compare string, to avoid hash collisions */
            if (!strcmp(key, d->key[i])) {
                /* Found key */
                break ;
            }
        }
    }
    if (i>=d->size)
        /* Key not found */
        return ;
    free(d->key[i]);
    d->key[i] = NULL ;
    if (d->val[i]!=NULL) {
        free(d->val[i]);
        d->val[i] = NULL ;
    }
    d->hash[i] = 0 ;
    d->n -- ;
    return ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Dump a dictionary to an opened file pointer.
  @param    d   Dictionary to dump
  @param    f   Opened file pointer.
  @return   void
  Dumps a dictionary onto an opened file pointer. Key pairs are printed out
  as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
  output file pointers.
 */
/*--------------------------------------------------------------------------*/
void dictionary_dump(const dictionary * d, FILE * out)
{
    ssize_t  i ;
    if (d==NULL || out==NULL) return ;
    if (d->n<1) {
        fprintf(out, "empty dictionary\n");
        return ;
    }
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]) {
            fprintf(out, "%20s\t[%s]\n",
                    d->key[i],
                    d->val[i] ? d->val[i] : "UNDEF");
        }
    }
    return ;
}
project/booster/dictionary.h
New file
@@ -0,0 +1,174 @@
/*-------------------------------------------------------------------------*/
/**
   @file    dictionary.h
   @author  N. Devillard
   @brief   Implements a dictionary for string variables.
   @url     https://github.com/ndevilla/iniparser
   This module implements a simple dictionary object, i.e. a list
   of string/string associations. This object is useful to store e.g.
   informations retrieved from a configuration file (ini files).
*/
/*--------------------------------------------------------------------------*/
#ifndef _DICTIONARY_H_
#define _DICTIONARY_H_
/*---------------------------------------------------------------------------
                                Includes
 ---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef __cplusplus
extern "C" {
#endif
/*---------------------------------------------------------------------------
                                New types
 ---------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/**
  @brief    Dictionary object
  This object contains a list of string/string associations. Each
  association is identified by a unique string key. Looking up values
  in the dictionary is speeded up by the use of a (hopefully collision-free)
  hash function.
 */
/*-------------------------------------------------------------------------*/
typedef struct _dictionary_ {
    int             n ;     /** Number of entries in dictionary */
    ssize_t         size ;  /** Storage size */
    char        **  val ;   /** List of string values */
    char        **  key ;   /** List of string keys */
    unsigned     *  hash ;  /** List of hash values for keys */
} dictionary ;
/*---------------------------------------------------------------------------
                            Function prototypes
 ---------------------------------------------------------------------------*/
/*-------------------------------------------------------------------------*/
/**
  @brief    Compute the hash key for a string.
  @param    key     Character string to use for key.
  @return   1 unsigned int on at least 32 bits.
  This hash function has been taken from an Article in Dr Dobbs Journal.
  This is normally a collision-free function, distributing keys evenly.
  The key is stored anyway in the struct so that collision can be avoided
  by comparing the key itself in last resort.
 */
/*--------------------------------------------------------------------------*/
unsigned dictionary_hash(const char * key);
/*-------------------------------------------------------------------------*/
/**
  @brief    Create a new dictionary object.
  @param    size    Optional initial size of the dictionary.
  @return   1 newly allocated dictionary object.
  This function allocates a new dictionary object of given size and returns
  it. If you do not know in advance (roughly) the number of entries in the
  dictionary, give size=0.
 */
/*--------------------------------------------------------------------------*/
dictionary * dictionary_new(size_t size);
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete a dictionary object
  @param    d   dictionary object to deallocate.
  @return   void
  Deallocate a dictionary object and all memory associated to it.
 */
/*--------------------------------------------------------------------------*/
void dictionary_del(dictionary * vd);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get a value from a dictionary.
  @param    d       dictionary object to search.
  @param    key     Key to look for in the dictionary.
  @param    def     Default value to return if key not found.
  @return   1 pointer to internally allocated character string.
  This function locates a key in a dictionary and returns a pointer to its
  value, or the passed 'def' pointer if no such key can be found in
  dictionary. The returned character pointer points to data internal to the
  dictionary object, you should not try to free it or modify it.
 */
/*--------------------------------------------------------------------------*/
const char * dictionary_get(const dictionary * d, const char * key, const char * def);
/*-------------------------------------------------------------------------*/
/**
  @brief    Set a value in a dictionary.
  @param    d       dictionary object to modify.
  @param    key     Key to modify or add.
  @param    val     Value to add.
  @return   int     0 if Ok, anything else otherwise
  If the given key is found in the dictionary, the associated value is
  replaced by the provided one. If the key cannot be found in the
  dictionary, it is added to it.
  It is Ok to provide a NULL value for val, but NULL values for the dictionary
  or the key are considered as errors: the function will return immediately
  in such a case.
  Notice that if you dictionary_set a variable to NULL, a call to
  dictionary_get will return a NULL value: the variable will be found, and
  its value (NULL) is returned. In other words, setting the variable
  content to NULL is equivalent to deleting the variable from the
  dictionary. It is not possible (in this implementation) to have a key in
  the dictionary without value.
  This function returns non-zero in case of failure.
 */
/*--------------------------------------------------------------------------*/
int dictionary_set(dictionary * vd, const char * key, const char * val);
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete a key in a dictionary
  @param    d       dictionary object to modify.
  @param    key     Key to remove.
  @return   void
  This function deletes a key in a dictionary. Nothing is done if the
  key cannot be found.
 */
/*--------------------------------------------------------------------------*/
void dictionary_unset(dictionary * d, const char * key);
/*-------------------------------------------------------------------------*/
/**
  @brief    Dump a dictionary to an opened file pointer.
  @param    d   Dictionary to dump
  @param    f   Opened file pointer.
  @return   void
  Dumps a dictionary onto an opened file pointer. Key pairs are printed out
  as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
  output file pointers.
 */
/*--------------------------------------------------------------------------*/
void dictionary_dump(const dictionary * d, FILE * out);
#ifdef __cplusplus
}
#endif
#endif
project/booster/esp32.c
New file
@@ -0,0 +1,166 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  esp32.c
 *    Description:  This file is ESP32 high level logic API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#include "logger.h"
#include "esp32.h"
int esp32_init_module(comport_t *comport)
{
    int             rv;
    char            version[256] = {0};
    if( !comport )
        return -1;
    rv = esp32_reset(comport);
    if( rv < 0)
    {
        log_error("Reset ESP32 WiFi module failed: %d\n", rv);
        return rv;
    }
    esp32_set_echo(comport, DISABLE);
    esp32_set_sysstore(comport, ENABLE);
    rv = esp32_get_firmware(comport, version, sizeof(version));
    if( rv < 0)
    {
        log_error("Query ESP32 firmware version failed: %d\n", rv);
        return rv;
    }
    log_info("ESP32 firmware version:\n%s\n", version);
    return 0;
}
int esp32_setup_softap(comport_t *comport, char *ssid, char *pwd)
{
    esp32_set_wmode(comport, MODE_SOFTAP, ENABLE);
    esp32_set_ipaddr(comport, MODE_SOFTAP, DEF_SOFTAP_IPADDR, DEF_SOFTAP_IPADDR);
    esp32_set_dhcp(comport, MODE_SOFTAP, ENABLE);
    esp32_set_softap(comport, ssid, pwd, 11, MODE_WPA2PSK);
    return 0;
}
int esp32_join_network(comport_t *comport, char *ssid, char *pwd)
{
    int             rv, status = 0;
    int             times=10;
    char            buf[128] = {0};
    if( !comport )
        return -1;
    esp32_join_status(comport, &status, buf);
    if( 2==status && !strcmp(buf, ssid) )
    {
        log_info("ESP32 connected to \"%s\" already\n", ssid);
        return 0;
    }
    esp32_set_wmode(comport, MODE_STATION, ENABLE);
    esp32_set_dhcp(comport, MODE_STATION, ENABLE);
    rv = esp32_connect_ap(comport, ssid, pwd);
    if( rv < 0 )
    {
        log_error("connect to AP \"%s\" failed, rv=%d", ssid, rv);
        return rv;
    }
    while(times--)
    {
        rv = esp32_join_status(comport, &status, buf);
        if( 2 == status )
        {
            log_info("ESP32 connected to \"%s\" already\n", ssid);
            return 0;
        }
        rv = -3;
    }
    return rv;
}
int esp32_check_network(comport_t *comport)
{
    int             rv, times=5;
    char            ip[IP_LEN] = {0};
    char            gateway[IP_LEN] = {0};
    memset(ip, 0, sizeof(ip));
    memset(gateway, 0, sizeof(gateway));
    rv = esp32_get_ipaddr(comport, MODE_STATION, ip, gateway);
    if( rv<0 )
        return rv;
    if( !strlen(ip) || !strlen(gateway) )
        return -3;
    log_info("IP address: %s, netmask: %s\n", ip, gateway);
    while( times-- )
    {
        if( !(rv=esp32_ping(comport, gateway, 5000)) )
        {
            rv = 0;
            break;
        }
    }
    return 0;
}
int esp32_setup_tcp_server(comport_t *comport, int port)
{
    int             rv;
    rv = esp32_set_socket_mux(comport, ENABLE);
    if(rv<0)
        return rv;
    rv = esp32_set_socket_clients(comport, 2);
    if(rv<0)
        return rv;
    rv = esp32_set_tcp_server(comport, port);
    if(rv<0)
        return rv;
    rv = esp32_set_socket_timeout(comport, 600);
    if(rv<0)
        return rv;
    return 0;
}
int esp32_setup_tcp_client(comport_t *comport, char *host, int port)
{
    int             rv;
    rv = esp32_set_tcp_client(comport, DISABLE, host, port);
    if(rv<0)
        return rv;
    return 0;
}
project/booster/esp32.h
New file
@@ -0,0 +1,35 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  esp32.h
 *    Description:  This file is ESP32 high level logic API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#ifndef  _ESP32_H_
#define  _ESP32_H_
#include "at-esp32.h"
#define DEF_SOFTAP_IPADDR    "192.168.8.1"
#define DEF_SOFTAP_SSID      "Router_ESP32"
#define DEF_SOFTAP_PWD       "12345678"
extern int esp32_init_module(comport_t *comport);
extern int esp32_setup_softap(comport_t *comport, char *ssid, char *pwd);
extern int esp32_join_network(comport_t *comport, char *ssid, char *pwd);
extern int esp32_check_network(comport_t *comport);
extern int esp32_setup_tcp_server(comport_t *comport, int port);
extern int esp32_setup_tcp_client(comport_t *comport, char *host, int port);
#endif   /* ----- #ifndef _ESP32_H_  ----- */
project/booster/iniparser.c
New file
@@ -0,0 +1,837 @@
/*-------------------------------------------------------------------------*/
/**
   @file    iniparser.c
   @author  N. Devillard
   @brief   Parser for ini files.
   @url     https://github.com/ndevilla/iniparser
*/
/*--------------------------------------------------------------------------*/
/*---------------------------- Includes ------------------------------------*/
#include <ctype.h>
#include <stdarg.h>
#include "iniparser.h"
/*---------------------------- Defines -------------------------------------*/
#define ASCIILINESZ         (1024)
#define INI_INVALID_KEY     ((char*)-1)
/*---------------------------------------------------------------------------
                        Private to this module
 ---------------------------------------------------------------------------*/
/**
 * This enum stores the status for each parsed line (internal use only).
 */
typedef enum _line_status_ {
    LINE_UNPROCESSED,
    LINE_ERROR,
    LINE_EMPTY,
    LINE_COMMENT,
    LINE_SECTION,
    LINE_VALUE
} line_status ;
/*-------------------------------------------------------------------------*/
/**
  @brief    Convert a string to lowercase.
  @param    in   String to convert.
  @param    out Output buffer.
  @param    len Size of the out buffer.
  @return   ptr to the out buffer or NULL if an error occured.
  This function convert a string into lowercase.
  At most len - 1 elements of the input string will be converted.
 */
/*--------------------------------------------------------------------------*/
static const char * strlwc(const char * in, char *out, unsigned len)
{
    unsigned i ;
    if (in==NULL || out == NULL || len==0) return NULL ;
    i=0 ;
    while (in[i] != '\0' && i < len-1) {
        out[i] = (char)tolower((int)in[i]);
        i++ ;
    }
    out[i] = '\0';
    return out ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Duplicate a string
  @param    s String to duplicate
  @return   Pointer to a newly allocated string, to be freed with free()
  This is a replacement for strdup(). This implementation is provided
  for systems that do not have it.
 */
/*--------------------------------------------------------------------------*/
static char * xstrdup(const char * s)
{
    char * t ;
    size_t len ;
    if (!s)
        return NULL ;
    len = strlen(s) + 1 ;
    t = (char*) malloc(len) ;
    if (t) {
        memcpy(t, s, len) ;
    }
    return t ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Remove blanks at the beginning and the end of a string.
  @param    str  String to parse and alter.
  @return   unsigned New size of the string.
 */
/*--------------------------------------------------------------------------*/
static unsigned strstrip(char * s)
{
    char *last = NULL ;
    char *dest = s;
    if (s==NULL) return 0;
    last = s + strlen(s);
    while (isspace((int)*s) && *s) s++;
    while (last > s) {
        if (!isspace((int)*(last-1)))
            break ;
        last -- ;
    }
    *last = (char)0;
    memmove(dest,s,last - s + 1);
    return last - s;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Default error callback for iniparser: wraps `fprintf(stderr, ...)`.
 */
/*--------------------------------------------------------------------------*/
static int default_error_callback(const char *format, ...)
{
  int ret;
  va_list argptr;
  va_start(argptr, format);
  ret = vfprintf(stderr, format, argptr);
  va_end(argptr);
  return ret;
}
static int (*iniparser_error_callback)(const char*, ...) = default_error_callback;
/*-------------------------------------------------------------------------*/
/**
  @brief    Configure a function to receive the error messages.
  @param    errback  Function to call.
  By default, the error will be printed on stderr. If a null pointer is passed
  as errback the error callback will be switched back to default.
 */
/*--------------------------------------------------------------------------*/
void iniparser_set_error_callback(int (*errback)(const char *, ...))
{
  if (errback) {
    iniparser_error_callback = errback;
  } else {
    iniparser_error_callback = default_error_callback;
  }
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get number of sections in a dictionary
  @param    d   Dictionary to examine
  @return   int Number of sections found in dictionary
  This function returns the number of sections found in a dictionary.
  The test to recognize sections is done on the string stored in the
  dictionary: a section name is given as "section" whereas a key is
  stored as "section:key", thus the test looks for entries that do not
  contain a colon.
  This clearly fails in the case a section name contains a colon, but
  this should simply be avoided.
  This function returns -1 in case of error.
 */
/*--------------------------------------------------------------------------*/
int iniparser_getnsec(const dictionary * d)
{
    int i ;
    int nsec ;
    if (d==NULL) return -1 ;
    nsec=0 ;
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        if (strchr(d->key[i], ':')==NULL) {
            nsec ++ ;
        }
    }
    return nsec ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get name for section n in a dictionary.
  @param    d   Dictionary to examine
  @param    n   Section number (from 0 to nsec-1).
  @return   Pointer to char string
  This function locates the n-th section in a dictionary and returns
  its name as a pointer to a string statically allocated inside the
  dictionary. Do not free or modify the returned string!
  This function returns NULL in case of error.
 */
/*--------------------------------------------------------------------------*/
const char * iniparser_getsecname(const dictionary * d, int n)
{
    int i ;
    int foundsec ;
    if (d==NULL || n<0) return NULL ;
    foundsec=0 ;
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        if (strchr(d->key[i], ':')==NULL) {
            foundsec++ ;
            if (foundsec>n)
                break ;
        }
    }
    if (foundsec<=n) {
        return NULL ;
    }
    return d->key[i] ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Dump a dictionary to an opened file pointer.
  @param    d   Dictionary to dump.
  @param    f   Opened file pointer to dump to.
  @return   void
  This function prints out the contents of a dictionary, one element by
  line, onto the provided file pointer. It is OK to specify @c stderr
  or @c stdout as output files. This function is meant for debugging
  purposes mostly.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dump(const dictionary * d, FILE * f)
{
    int     i ;
    if (d==NULL || f==NULL) return ;
    for (i=0 ; i<d->size ; i++) {
        if (d->key[i]==NULL)
            continue ;
        if (d->val[i]!=NULL) {
            fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
        } else {
            fprintf(f, "[%s]=UNDEF\n", d->key[i]);
        }
    }
    return ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Save a dictionary to a loadable ini file
  @param    d   Dictionary to dump
  @param    f   Opened file pointer to dump to
  @return   void
  This function dumps a given dictionary into a loadable ini file.
  It is Ok to specify @c stderr or @c stdout as output files.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dump_ini(const dictionary * d, FILE * f)
{
    int          i ;
    int          nsec ;
    const char * secname ;
    if (d==NULL || f==NULL) return ;
    nsec = iniparser_getnsec(d);
    if (nsec<1) {
        /* No section in file: dump all keys as they are */
        for (i=0 ; i<d->size ; i++) {
            if (d->key[i]==NULL)
                continue ;
            fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
        }
        return ;
    }
    for (i=0 ; i<nsec ; i++) {
        secname = iniparser_getsecname(d, i) ;
        iniparser_dumpsection_ini(d, secname, f);
    }
    fprintf(f, "\n");
    return ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Save a dictionary section to a loadable ini file
  @param    d   Dictionary to dump
  @param    s   Section name of dictionary to dump
  @param    f   Opened file pointer to dump to
  @return   void
  This function dumps a given section of a given dictionary into a loadable ini
  file.  It is Ok to specify @c stderr or @c stdout as output files.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f)
{
    int     j ;
    char    keym[ASCIILINESZ+1];
    int     seclen ;
    if (d==NULL || f==NULL) return ;
    if (! iniparser_find_entry(d, s)) return ;
    seclen  = (int)strlen(s);
    fprintf(f, "\n[%s]\n", s);
    sprintf(keym, "%s:", s);
    for (j=0 ; j<d->size ; j++) {
        if (d->key[j]==NULL)
            continue ;
        if (!strncmp(d->key[j], keym, seclen+1)) {
            fprintf(f,
                    "%-30s = %s\n",
                    d->key[j]+seclen+1,
                    d->val[j] ? d->val[j] : "");
        }
    }
    fprintf(f, "\n");
    return ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the number of keys in a section of a dictionary.
  @param    d   Dictionary to examine
  @param    s   Section name of dictionary to examine
  @return   Number of keys in section
 */
/*--------------------------------------------------------------------------*/
int iniparser_getsecnkeys(const dictionary * d, const char * s)
{
    int     seclen, nkeys ;
    char    keym[ASCIILINESZ+1];
    int j ;
    nkeys = 0;
    if (d==NULL) return nkeys;
    if (! iniparser_find_entry(d, s)) return nkeys;
    seclen  = (int)strlen(s);
    strlwc(s, keym, sizeof(keym));
    keym[seclen] = ':';
    for (j=0 ; j<d->size ; j++) {
        if (d->key[j]==NULL)
            continue ;
        if (!strncmp(d->key[j], keym, seclen+1))
            nkeys++;
    }
    return nkeys;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the number of keys in a section of a dictionary.
  @param    d    Dictionary to examine
  @param    s    Section name of dictionary to examine
  @param    keys Already allocated array to store the keys in
  @return   The pointer passed as `keys` argument or NULL in case of error
  This function queries a dictionary and finds all keys in a given section.
  The keys argument should be an array of pointers which size has been
  determined by calling `iniparser_getsecnkeys` function prior to this one.
  Each pointer in the returned char pointer-to-pointer is pointing to
  a string allocated in the dictionary; do not free or modify them.
 */
/*--------------------------------------------------------------------------*/
const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys)
{
    int i, j, seclen ;
    char keym[ASCIILINESZ+1];
    if (d==NULL || keys==NULL) return NULL;
    if (! iniparser_find_entry(d, s)) return NULL;
    seclen  = (int)strlen(s);
    strlwc(s, keym, sizeof(keym));
    keym[seclen] = ':';
    i = 0;
    for (j=0 ; j<d->size ; j++) {
        if (d->key[j]==NULL)
            continue ;
        if (!strncmp(d->key[j], keym, seclen+1)) {
            keys[i] = d->key[j];
            i++;
        }
    }
    return keys;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key
  @param    d       Dictionary to search
  @param    key     Key string to look for
  @param    def     Default value to return if key not found.
  @return   pointer to statically allocated character string
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the pointer passed as 'def' is returned.
  The returned char pointer is pointing to a string allocated in
  the dictionary, do not free or modify it.
 */
/*--------------------------------------------------------------------------*/
const char * iniparser_getstring(const dictionary * d, const char * key, const char * def)
{
    const char * lc_key ;
    const char * sval ;
    char tmp_str[ASCIILINESZ+1];
    if (d==NULL || key==NULL)
        return def ;
    lc_key = strlwc(key, tmp_str, sizeof(tmp_str));
    sval = dictionary_get(d, lc_key, def);
    return sval ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to an long int
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   long integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  Supported values for integers include the usual C notation
  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
  are supported. Examples:
  "42"      ->  42
  "042"     ->  34 (octal -> decimal)
  "0x42"    ->  66 (hexa  -> decimal)
  Warning: the conversion may overflow in various ways. Conversion is
  totally outsourced to strtol(), see the associated man page for overflow
  handling.
  Credits: Thanks to A. Becker for suggesting strtol()
 */
/*--------------------------------------------------------------------------*/
long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound)
{
    const char * str ;
    str = iniparser_getstring(d, key, INI_INVALID_KEY);
    if (str==INI_INVALID_KEY) return notfound ;
    return strtol(str, NULL, 0);
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to an int
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  Supported values for integers include the usual C notation
  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
  are supported. Examples:
  "42"      ->  42
  "042"     ->  34 (octal -> decimal)
  "0x42"    ->  66 (hexa  -> decimal)
  Warning: the conversion may overflow in various ways. Conversion is
  totally outsourced to strtol(), see the associated man page for overflow
  handling.
  Credits: Thanks to A. Becker for suggesting strtol()
 */
/*--------------------------------------------------------------------------*/
int iniparser_getint(const dictionary * d, const char * key, int notfound)
{
    return (int)iniparser_getlongint(d, key, notfound);
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to a double
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   double
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
 */
/*--------------------------------------------------------------------------*/
double iniparser_getdouble(const dictionary * d, const char * key, double notfound)
{
    const char * str ;
    str = iniparser_getstring(d, key, INI_INVALID_KEY);
    if (str==INI_INVALID_KEY) return notfound ;
    return atof(str);
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to a boolean
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  A true boolean is found if one of the following is matched:
  - A string starting with 'y'
  - A string starting with 'Y'
  - A string starting with 't'
  - A string starting with 'T'
  - A string starting with '1'
  A false boolean is found if one of the following is matched:
  - A string starting with 'n'
  - A string starting with 'N'
  - A string starting with 'f'
  - A string starting with 'F'
  - A string starting with '0'
  The notfound value returned if no boolean is identified, does not
  necessarily have to be 0 or 1.
 */
/*--------------------------------------------------------------------------*/
int iniparser_getboolean(const dictionary * d, const char * key, int notfound)
{
    int          ret ;
    const char * c ;
    c = iniparser_getstring(d, key, INI_INVALID_KEY);
    if (c==INI_INVALID_KEY) return notfound ;
    if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') {
        ret = 1 ;
    } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') {
        ret = 0 ;
    } else {
        ret = notfound ;
    }
    return ret;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Finds out if a given entry exists in a dictionary
  @param    ini     Dictionary to search
  @param    entry   Name of the entry to look for
  @return   integer 1 if entry exists, 0 otherwise
  Finds out if a given entry exists in the dictionary. Since sections
  are stored as keys with NULL associated values, this is the only way
  of querying for the presence of sections in a dictionary.
 */
/*--------------------------------------------------------------------------*/
int iniparser_find_entry(const dictionary * ini, const char * entry)
{
    int found=0 ;
    if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) {
        found = 1 ;
    }
    return found ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Set an entry in a dictionary.
  @param    ini     Dictionary to modify.
  @param    entry   Entry to modify (entry name)
  @param    val     New value to associate to the entry.
  @return   int 0 if Ok, -1 otherwise.
  If the given entry can be found in the dictionary, it is modified to
  contain the provided value. If it cannot be found, the entry is created.
  It is Ok to set val to NULL.
 */
/*--------------------------------------------------------------------------*/
int iniparser_set(dictionary * ini, const char * entry, const char * val)
{
    char tmp_str[ASCIILINESZ+1];
    return dictionary_set(ini, strlwc(entry, tmp_str, sizeof(tmp_str)), val) ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete an entry in a dictionary
  @param    ini     Dictionary to modify
  @param    entry   Entry to delete (entry name)
  @return   void
  If the given entry can be found, it is deleted from the dictionary.
 */
/*--------------------------------------------------------------------------*/
void iniparser_unset(dictionary * ini, const char * entry)
{
    char tmp_str[ASCIILINESZ+1];
    dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str)));
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Load a single line from an INI file
  @param    input_line  Input line, may be concatenated multi-line input
  @param    section     Output space to store section
  @param    key         Output space to store key
  @param    value       Output space to store value
  @return   line_status value
 */
/*--------------------------------------------------------------------------*/
static line_status iniparser_line(
    const char * input_line,
    char * section,
    char * key,
    char * value)
{
    line_status sta ;
    char * line = NULL;
    size_t      len ;
    line = xstrdup(input_line);
    len = strstrip(line);
    sta = LINE_UNPROCESSED ;
    if (len<1) {
        /* Empty line */
        sta = LINE_EMPTY ;
    } else if (line[0]=='#' || line[0]==';') {
        /* Comment line */
        sta = LINE_COMMENT ;
    } else if (line[0]=='[' && line[len-1]==']') {
        /* Section name */
        sscanf(line, "[%[^]]", section);
        strstrip(section);
        strlwc(section, section, len);
        sta = LINE_SECTION ;
    } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2
           ||  sscanf (line, "%[^=] = '%[^\']'",   key, value) == 2) {
        /* Usual key=value with quotes, with or without comments */
        strstrip(key);
        strlwc(key, key, len);
        /* Don't strip spaces from values surrounded with quotes */
        sta = LINE_VALUE ;
    } else if (sscanf (line, "%[^=] = %[^;#]", key, value) == 2) {
        /* Usual key=value without quotes, with or without comments */
        strstrip(key);
        strlwc(key, key, len);
        strstrip(value);
        /*
         * sscanf cannot handle '' or "" as empty values
         * this is done here
         */
        if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
            value[0]=0 ;
        }
        sta = LINE_VALUE ;
    } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2
           ||  sscanf(line, "%[^=] %[=]", key, value) == 2) {
        /*
         * Special cases:
         * key=
         * key=;
         * key=#
         */
        strstrip(key);
        strlwc(key, key, len);
        value[0]=0 ;
        sta = LINE_VALUE ;
    } else {
        /* Generate syntax error */
        sta = LINE_ERROR ;
    }
    free(line);
    return sta ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Parse an ini file and return an allocated dictionary object
  @param    ininame Name of the ini file to read.
  @return   Pointer to newly allocated dictionary
  This is the parser for ini files. This function is called, providing
  the name of the file to be read. It returns a dictionary object that
  should not be accessed directly, but through accessor functions
  instead.
  The returned dictionary must be freed using iniparser_freedict().
 */
/*--------------------------------------------------------------------------*/
dictionary * iniparser_load(const char * ininame)
{
    FILE * in ;
    char line    [ASCIILINESZ+1] ;
    char section [ASCIILINESZ+1] ;
    char key     [ASCIILINESZ+1] ;
    char tmp     [(ASCIILINESZ * 2) + 2] ;
    char val     [ASCIILINESZ+1] ;
    int  last=0 ;
    int  len ;
    int  lineno=0 ;
    int  errs=0;
    int  mem_err=0;
    dictionary * dict ;
    if ((in=fopen(ininame, "r"))==NULL) {
        iniparser_error_callback("iniparser: cannot open %s\n", ininame);
        return NULL ;
    }
    dict = dictionary_new(0) ;
    if (!dict) {
        fclose(in);
        return NULL ;
    }
    memset(line,    0, ASCIILINESZ);
    memset(section, 0, ASCIILINESZ);
    memset(key,     0, ASCIILINESZ);
    memset(val,     0, ASCIILINESZ);
    last=0 ;
    while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) {
        lineno++ ;
        len = (int)strlen(line)-1;
        if (len<=0)
            continue;
        /* Safety check against buffer overflows */
        if (line[len]!='\n' && !feof(in)) {
            iniparser_error_callback(
              "iniparser: input line too long in %s (%d)\n",
              ininame,
              lineno);
            dictionary_del(dict);
            fclose(in);
            return NULL ;
        }
        /* Get rid of \n and spaces at end of line */
        while ((len>=0) &&
                ((line[len]=='\n') || (isspace(line[len])))) {
            line[len]=0 ;
            len-- ;
        }
        if (len < 0) { /* Line was entirely \n and/or spaces */
            len = 0;
        }
        /* Detect multi-line */
        if (line[len]=='\\') {
            /* Multi-line value */
            last=len ;
            continue ;
        } else {
            last=0 ;
        }
        switch (iniparser_line(line, section, key, val)) {
            case LINE_EMPTY:
            case LINE_COMMENT:
            break ;
            case LINE_SECTION:
            mem_err = dictionary_set(dict, section, NULL);
            break ;
            case LINE_VALUE:
            sprintf(tmp, "%s:%s", section, key);
            mem_err = dictionary_set(dict, tmp, val);
            break ;
            case LINE_ERROR:
            iniparser_error_callback(
              "iniparser: syntax error in %s (%d):\n-> %s\n",
              ininame,
              lineno,
              line);
            errs++ ;
            break;
            default:
            break ;
        }
        memset(line, 0, ASCIILINESZ);
        last=0;
        if (mem_err<0) {
            iniparser_error_callback("iniparser: memory allocation failure\n");
            break ;
        }
    }
    if (errs) {
        dictionary_del(dict);
        dict = NULL ;
    }
    fclose(in);
    return dict ;
}
/*-------------------------------------------------------------------------*/
/**
  @brief    Free all memory associated to an ini dictionary
  @param    d Dictionary to free
  @return   void
  Free all memory associated to an ini dictionary.
  It is mandatory to call this function before the dictionary object
  gets out of the current context.
 */
/*--------------------------------------------------------------------------*/
void iniparser_freedict(dictionary * d)
{
    dictionary_del(d);
}
project/booster/iniparser.h
New file
@@ -0,0 +1,359 @@
/*-------------------------------------------------------------------------*/
/**
   @file    iniparser.h
   @author  N. Devillard
   @brief   Parser for ini files.
   @url     https://github.com/ndevilla/iniparser
*/
/*--------------------------------------------------------------------------*/
#ifndef _INIPARSER_H_
#define _INIPARSER_H_
/*---------------------------------------------------------------------------
                                Includes
 ---------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
 * The following #include is necessary on many Unixes but not Linux.
 * It is not needed for Windows platforms.
 * Uncomment it if needed.
 */
/* #include <unistd.h> */
#include "dictionary.h"
#ifdef __cplusplus
extern "C" {
#endif
/*-------------------------------------------------------------------------*/
/**
  @brief    Configure a function to receive the error messages.
  @param    errback  Function to call.
  By default, the error will be printed on stderr. If a null pointer is passed
  as errback the error callback will be switched back to default.
 */
/*--------------------------------------------------------------------------*/
void iniparser_set_error_callback(int (*errback)(const char *, ...));
/*-------------------------------------------------------------------------*/
/**
  @brief    Get number of sections in a dictionary
  @param    d   Dictionary to examine
  @return   int Number of sections found in dictionary
  This function returns the number of sections found in a dictionary.
  The test to recognize sections is done on the string stored in the
  dictionary: a section name is given as "section" whereas a key is
  stored as "section:key", thus the test looks for entries that do not
  contain a colon.
  This clearly fails in the case a section name contains a colon, but
  this should simply be avoided.
  This function returns -1 in case of error.
 */
/*--------------------------------------------------------------------------*/
int iniparser_getnsec(const dictionary * d);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get name for section n in a dictionary.
  @param    d   Dictionary to examine
  @param    n   Section number (from 0 to nsec-1).
  @return   Pointer to char string
  This function locates the n-th section in a dictionary and returns
  its name as a pointer to a string statically allocated inside the
  dictionary. Do not free or modify the returned string!
  This function returns NULL in case of error.
 */
/*--------------------------------------------------------------------------*/
const char * iniparser_getsecname(const dictionary * d, int n);
/*-------------------------------------------------------------------------*/
/**
  @brief    Save a dictionary to a loadable ini file
  @param    d   Dictionary to dump
  @param    f   Opened file pointer to dump to
  @return   void
  This function dumps a given dictionary into a loadable ini file.
  It is Ok to specify @c stderr or @c stdout as output files.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dump_ini(const dictionary * d, FILE * f);
/*-------------------------------------------------------------------------*/
/**
  @brief    Save a dictionary section to a loadable ini file
  @param    d   Dictionary to dump
  @param    s   Section name of dictionary to dump
  @param    f   Opened file pointer to dump to
  @return   void
  This function dumps a given section of a given dictionary into a loadable ini
  file.  It is Ok to specify @c stderr or @c stdout as output files.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f);
/*-------------------------------------------------------------------------*/
/**
  @brief    Dump a dictionary to an opened file pointer.
  @param    d   Dictionary to dump.
  @param    f   Opened file pointer to dump to.
  @return   void
  This function prints out the contents of a dictionary, one element by
  line, onto the provided file pointer. It is OK to specify @c stderr
  or @c stdout as output files. This function is meant for debugging
  purposes mostly.
 */
/*--------------------------------------------------------------------------*/
void iniparser_dump(const dictionary * d, FILE * f);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the number of keys in a section of a dictionary.
  @param    d   Dictionary to examine
  @param    s   Section name of dictionary to examine
  @return   Number of keys in section
 */
/*--------------------------------------------------------------------------*/
int iniparser_getsecnkeys(const dictionary * d, const char * s);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the number of keys in a section of a dictionary.
  @param    d    Dictionary to examine
  @param    s    Section name of dictionary to examine
  @param    keys Already allocated array to store the keys in
  @return   The pointer passed as `keys` argument or NULL in case of error
  This function queries a dictionary and finds all keys in a given section.
  The keys argument should be an array of pointers which size has been
  determined by calling `iniparser_getsecnkeys` function prior to this one.
  Each pointer in the returned char pointer-to-pointer is pointing to
  a string allocated in the dictionary; do not free or modify them.
 */
/*--------------------------------------------------------------------------*/
const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key
  @param    d       Dictionary to search
  @param    key     Key string to look for
  @param    def     Default value to return if key not found.
  @return   pointer to statically allocated character string
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the pointer passed as 'def' is returned.
  The returned char pointer is pointing to a string allocated in
  the dictionary, do not free or modify it.
 */
/*--------------------------------------------------------------------------*/
const char * iniparser_getstring(const dictionary * d, const char * key, const char * def);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to an int
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  Supported values for integers include the usual C notation
  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
  are supported. Examples:
  - "42"      ->  42
  - "042"     ->  34 (octal -> decimal)
  - "0x42"    ->  66 (hexa  -> decimal)
  Warning: the conversion may overflow in various ways. Conversion is
  totally outsourced to strtol(), see the associated man page for overflow
  handling.
  Credits: Thanks to A. Becker for suggesting strtol()
 */
/*--------------------------------------------------------------------------*/
int iniparser_getint(const dictionary * d, const char * key, int notfound);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to an long int
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  Supported values for integers include the usual C notation
  so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
  are supported. Examples:
  - "42"      ->  42
  - "042"     ->  34 (octal -> decimal)
  - "0x42"    ->  66 (hexa  -> decimal)
  Warning: the conversion may overflow in various ways. Conversion is
  totally outsourced to strtol(), see the associated man page for overflow
  handling.
 */
/*--------------------------------------------------------------------------*/
long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to a double
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   double
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
 */
/*--------------------------------------------------------------------------*/
double iniparser_getdouble(const dictionary * d, const char * key, double notfound);
/*-------------------------------------------------------------------------*/
/**
  @brief    Get the string associated to a key, convert to a boolean
  @param    d Dictionary to search
  @param    key Key string to look for
  @param    notfound Value to return in case of error
  @return   integer
  This function queries a dictionary for a key. A key as read from an
  ini file is given as "section:key". If the key cannot be found,
  the notfound value is returned.
  A true boolean is found if one of the following is matched:
  - A string starting with 'y'
  - A string starting with 'Y'
  - A string starting with 't'
  - A string starting with 'T'
  - A string starting with '1'
  A false boolean is found if one of the following is matched:
  - A string starting with 'n'
  - A string starting with 'N'
  - A string starting with 'f'
  - A string starting with 'F'
  - A string starting with '0'
  The notfound value returned if no boolean is identified, does not
  necessarily have to be 0 or 1.
 */
/*--------------------------------------------------------------------------*/
int iniparser_getboolean(const dictionary * d, const char * key, int notfound);
/*-------------------------------------------------------------------------*/
/**
  @brief    Set an entry in a dictionary.
  @param    ini     Dictionary to modify.
  @param    entry   Entry to modify (entry name)
  @param    val     New value to associate to the entry.
  @return   int     0 if Ok, -1 otherwise.
  If the given entry can be found in the dictionary, it is modified to
  contain the provided value. If it cannot be found, the entry is created.
  It is Ok to set val to NULL.
 */
/*--------------------------------------------------------------------------*/
int iniparser_set(dictionary * ini, const char * entry, const char * val);
/*-------------------------------------------------------------------------*/
/**
  @brief    Delete an entry in a dictionary
  @param    ini     Dictionary to modify
  @param    entry   Entry to delete (entry name)
  @return   void
  If the given entry can be found, it is deleted from the dictionary.
 */
/*--------------------------------------------------------------------------*/
void iniparser_unset(dictionary * ini, const char * entry);
/*-------------------------------------------------------------------------*/
/**
  @brief    Finds out if a given entry exists in a dictionary
  @param    ini     Dictionary to search
  @param    entry   Name of the entry to look for
  @return   integer 1 if entry exists, 0 otherwise
  Finds out if a given entry exists in the dictionary. Since sections
  are stored as keys with NULL associated values, this is the only way
  of querying for the presence of sections in a dictionary.
 */
/*--------------------------------------------------------------------------*/
int iniparser_find_entry(const dictionary * ini, const char * entry) ;
/*-------------------------------------------------------------------------*/
/**
  @brief    Parse an ini file and return an allocated dictionary object
  @param    ininame Name of the ini file to read.
  @return   Pointer to newly allocated dictionary
  This is the parser for ini files. This function is called, providing
  the name of the file to be read. It returns a dictionary object that
  should not be accessed directly, but through accessor functions
  instead.
  The returned dictionary must be freed using iniparser_freedict().
 */
/*--------------------------------------------------------------------------*/
dictionary * iniparser_load(const char * ininame);
/*-------------------------------------------------------------------------*/
/**
  @brief    Free all memory associated to an ini dictionary
  @param    d Dictionary to free
  @return   void
  Free all memory associated to an ini dictionary.
  It is mandatory to call this function before the dictionary object
  gets out of the current context.
 */
/*--------------------------------------------------------------------------*/
void iniparser_freedict(dictionary * d);
#ifdef __cplusplus
}
#endif
#endif
project/booster/list.h
New file
@@ -0,0 +1,723 @@
/*********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  list.h
 *    Description:  This file is copied from Linux kernel, which provide link list API.
 *
 *        Version:  1.0.0(08/09/2020)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "08/09/2020 02:24:34 AM"
 *
 ********************************************************************************/
#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H
#include <linux/stddef.h>
/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:    the pointer to the member.
 * @type:   the type of the container struct this is embedded in.
 * @member: the name of the member within the struct.
 *
 */
#undef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({          \
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
    (type *)( (char *)__mptr - offsetof(type,member) );})
/*
 * Architectures might want to move the poison pointer offset
 * into some well-recognized area such as 0xdead000000000000,
 * that is also not mappable by user-space exploits:
 */
#ifdef CONFIG_ILLEGAL_POINTER_VALUE
# define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL)
#else
# define POISON_POINTER_DELTA 0
#endif
/*
 * These are non-NULL pointers that will result in page faults
 * under normal circumstances, used to verify that nobody uses
 * non-initialized list entries.
 */
#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)
#ifndef ARCH_HAS_PREFETCH
#define ARCH_HAS_PREFETCH
static inline void prefetch(const void *x) {;}
#endif
/*
 * Simple doubly linked list implementation.
 *
 * Some of the internal functions ("__xxx") are useful when
 * manipulating whole lists rather than single entries, as
 * sometimes we already know the next/prev entries and we can
 * generate better code by using them directly rather than
 * using the generic single-entry routines.
 */
struct list_head {
    struct list_head *next, *prev;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}
/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    next->prev = new;
    new->next = next;
    new->prev = prev;
    prev->next = new;
}
/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}
/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}
/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
    next->prev = prev;
    prev->next = next;
}
/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty() on entry does not return true after this, the entry is
 * in an undefined state.
 */
static inline void list_del(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->next = LIST_POISON1;
    entry->prev = LIST_POISON2;
}
/**
 * list_replace - replace old entry by new one
 * @old : the element to be replaced
 * @new : the new element to insert
 *
 * If @old was empty, it will be overwritten.
 */
static inline void list_replace(struct list_head *old,
                struct list_head *new)
{
    new->next = old->next;
    new->next->prev = new;
    new->prev = old->prev;
    new->prev->next = new;
}
static inline void list_replace_init(struct list_head *old,
                    struct list_head *new)
{
    list_replace(old, new);
    INIT_LIST_HEAD(old);
}
/**
 * list_del_init - deletes entry from list and reinitialize it.
 * @entry: the element to delete from the list.
 */
static inline void list_del_init(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    INIT_LIST_HEAD(entry);
}
/**
 * list_move - delete from one list and add as another's head
 * @list: the entry to move
 * @head: the head that will precede our entry
 */
static inline void list_move(struct list_head *list, struct list_head *head)
{
    __list_del(list->prev, list->next);
    list_add(list, head);
}
/**
 * list_move_tail - delete from one list and add as another's tail
 * @list: the entry to move
 * @head: the head that will follow our entry
 */
static inline void list_move_tail(struct list_head *list,
                  struct list_head *head)
{
    __list_del(list->prev, list->next);
    list_add_tail(list, head);
}
/**
 * list_is_last - tests whether @list is the last entry in list @head
 * @list: the entry to test
 * @head: the head of the list
 */
static inline int list_is_last(const struct list_head *list,
                const struct list_head *head)
{
    return list->next == head;
}
/**
 * list_empty - tests whether a list is empty
 * @head: the list to test.
 */
static inline int list_empty(const struct list_head *head)
{
    return head->next == head;
}
/**
 * list_empty_careful - tests whether a list is empty and not being modified
 * @head: the list to test
 *
 * Description:
 * tests whether a list is empty _and_ checks that no other CPU might be
 * in the process of modifying either member (next or prev)
 *
 * NOTE: using list_empty_careful() without synchronization
 * can only be safe if the only activity that can happen
 * to the list entry is list_del_init(). Eg. it cannot be used
 * if another CPU could re-list_add() it.
 */
static inline int list_empty_careful(const struct list_head *head)
{
    struct list_head *next = head->next;
    return (next == head) && (next == head->prev);
}
/**
 * list_is_singular - tests whether a list has just one entry.
 * @head: the list to test.
 */
static inline int list_is_singular(const struct list_head *head)
{
    return !list_empty(head) && (head->next == head->prev);
}
static inline void __list_cut_position(struct list_head *list,
        struct list_head *head, struct list_head *entry)
{
    struct list_head *new_first = entry->next;
    list->next = head->next;
    list->next->prev = list;
    list->prev = entry;
    entry->next = list;
    head->next = new_first;
    new_first->prev = head;
}
/**
 * list_cut_position - cut a list into two
 * @list: a new list to add all removed entries
 * @head: a list with entries
 * @entry: an entry within head, could be the head itself
 *    and if so we won't cut the list
 *
 * This helper moves the initial part of @head, up to and
 * including @entry, from @head to @list. You should
 * pass on @entry an element you know is on @head. @list
 * should be an empty list or a list you do not care about
 * losing its data.
 *
 */
static inline void list_cut_position(struct list_head *list,
        struct list_head *head, struct list_head *entry)
{
    if (list_empty(head))
        return;
    if (list_is_singular(head) &&
        (head->next != entry && head != entry))
        return;
    if (entry == head)
        INIT_LIST_HEAD(list);
    else
        __list_cut_position(list, head, entry);
}
static inline void __list_splice(const struct list_head *list,
                 struct list_head *prev,
                 struct list_head *next)
{
    struct list_head *first = list->next;
    struct list_head *last = list->prev;
    first->prev = prev;
    prev->next = first;
    last->next = next;
    next->prev = last;
}
/**
 * list_splice - join two lists, this is designed for stacks
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice(const struct list_head *list,
                struct list_head *head)
{
    if (!list_empty(list))
        __list_splice(list, head, head->next);
}
/**
 * list_splice_tail - join two lists, each list being a queue
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice_tail(struct list_head *list,
                struct list_head *head)
{
    if (!list_empty(list))
        __list_splice(list, head->prev, head);
}
/**
 * list_splice_init - join two lists and reinitialise the emptied list.
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 *
 * The list at @list is reinitialised
 */
static inline void list_splice_init(struct list_head *list,
                    struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head, head->next);
        INIT_LIST_HEAD(list);
    }
}
/**
 * list_splice_tail_init - join two lists and reinitialise the emptied list
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 *
 * Each of the lists is a queue.
 * The list at @list is reinitialised
 */
static inline void list_splice_tail_init(struct list_head *list,
                     struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head->prev, head);
        INIT_LIST_HEAD(list);
    }
}
/**
 * list_entry - get the struct for this entry
 * @ptr:    the &struct list_head pointer.
 * @type:    the type of the struct this is embedded in.
 * @member:    the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)
/**
 * list_first_entry - get the first element from a list
 * @ptr:    the list head to take the element from.
 * @type:    the type of the struct this is embedded in.
 * @member:    the name of the list_struct within the struct.
 *
 * Note, that list is expected to be not empty.
 */
#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)
/**
 * list_for_each    -    iterate over a list
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:    the head for your list.
 */
#define list_for_each(pos, head) \
    for (pos = (head)->next; prefetch(pos->next), pos != (head); \
        pos = pos->next)
/**
 * __list_for_each    -    iterate over a list
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:    the head for your list.
 *
 * This variant differs from list_for_each() in that it's the
 * simplest possible list iteration code, no prefetching is done.
 * Use this for code that knows the list to be very short (empty
 * or 1 entry) most of the time.
 */
#define __list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)
/**
 * list_for_each_prev    -    iterate over a list backwards
 * @pos:    the &struct list_head to use as a loop cursor.
 * @head:    the head for your list.
 */
#define list_for_each_prev(pos, head) \
    for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \
        pos = pos->prev)
/**
 * list_for_each_safe - iterate over a list safe against removal of list entry
 * @pos:    the &struct list_head to use as a loop cursor.
 * @n:        another &struct list_head to use as temporary storage
 * @head:    the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
    for (pos = (head)->next, n = pos->next; pos != (head); \
        pos = n, n = pos->next)
/**
 * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
 * @pos:    the &struct list_head to use as a loop cursor.
 * @n:        another &struct list_head to use as temporary storage
 * @head:    the head for your list.
 */
#define list_for_each_prev_safe(pos, n, head) \
    for (pos = (head)->prev, n = pos->prev; \
         prefetch(pos->prev), pos != (head); \
         pos = n, n = pos->prev)
/**
 * list_for_each_entry    -    iterate over list of given type
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 */
#define list_for_each_entry(pos, head, member)                \
    for (pos = list_entry((head)->next, typeof(*pos), member);    \
         prefetch(pos->member.next), &pos->member != (head);    \
         pos = list_entry(pos->member.next, typeof(*pos), member))
/**
 * list_for_each_entry_reverse - iterate backwards over list of given type.
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 */
#define list_for_each_entry_reverse(pos, head, member)            \
    for (pos = list_entry((head)->prev, typeof(*pos), member);    \
         prefetch(pos->member.prev), &pos->member != (head);    \
         pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
 * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
 * @pos:    the type * to use as a start point
 * @head:    the head of the list
 * @member:    the name of the list_struct within the struct.
 *
 * Prepares a pos entry for use as a start point in list_for_each_entry_continue().
 */
#define list_prepare_entry(pos, head, member) \
    ((pos) ? : list_entry(head, typeof(*pos), member))
/**
 * list_for_each_entry_continue - continue iteration over list of given type
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Continue to iterate over list of given type, continuing after
 * the current position.
 */
#define list_for_each_entry_continue(pos, head, member)         \
    for (pos = list_entry(pos->member.next, typeof(*pos), member);    \
         prefetch(pos->member.next), &pos->member != (head);    \
         pos = list_entry(pos->member.next, typeof(*pos), member))
/**
 * list_for_each_entry_continue_reverse - iterate backwards from the given point
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Start to iterate over list of given type backwards, continuing after
 * the current position.
 */
#define list_for_each_entry_continue_reverse(pos, head, member)        \
    for (pos = list_entry(pos->member.prev, typeof(*pos), member);    \
         prefetch(pos->member.prev), &pos->member != (head);    \
         pos = list_entry(pos->member.prev, typeof(*pos), member))
/**
 * list_for_each_entry_from - iterate over list of given type from the current point
 * @pos:    the type * to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Iterate over list of given type, continuing from current position.
 */
#define list_for_each_entry_from(pos, head, member)            \
    for (; prefetch(pos->member.next), &pos->member != (head);    \
         pos = list_entry(pos->member.next, typeof(*pos), member))
/**
 * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
 * @pos:    the type * to use as a loop cursor.
 * @n:        another type * to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 */
#define list_for_each_entry_safe(pos, n, head, member)            \
    for (pos = list_entry((head)->next, typeof(*pos), member),    \
        n = list_entry(pos->member.next, typeof(*pos), member);    \
         &pos->member != (head);                    \
         pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
 * list_for_each_entry_safe_continue
 * @pos:    the type * to use as a loop cursor.
 * @n:        another type * to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Iterate over list of given type, continuing after current point,
 * safe against removal of list entry.
 */
#define list_for_each_entry_safe_continue(pos, n, head, member)         \
    for (pos = list_entry(pos->member.next, typeof(*pos), member),        \
        n = list_entry(pos->member.next, typeof(*pos), member);        \
         &pos->member != (head);                        \
         pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
 * list_for_each_entry_safe_from
 * @pos:    the type * to use as a loop cursor.
 * @n:        another type * to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Iterate over list of given type from current point, safe against
 * removal of list entry.
 */
#define list_for_each_entry_safe_from(pos, n, head, member)            \
    for (n = list_entry(pos->member.next, typeof(*pos), member);        \
         &pos->member != (head);                        \
         pos = n, n = list_entry(n->member.next, typeof(*n), member))
/**
 * list_for_each_entry_safe_reverse
 * @pos:    the type * to use as a loop cursor.
 * @n:        another type * to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the list_struct within the struct.
 *
 * Iterate backwards over list of given type, safe against removal
 * of list entry.
 */
#define list_for_each_entry_safe_reverse(pos, n, head, member)        \
    for (pos = list_entry((head)->prev, typeof(*pos), member),    \
        n = list_entry(pos->member.prev, typeof(*pos), member);    \
         &pos->member != (head);                    \
         pos = n, n = list_entry(n->member.prev, typeof(*n), member))
/*
 * Double linked lists with a single pointer list head.
 * Mostly useful for hash tables where the two pointer list head is
 * too wasteful.
 * You lose the ability to access the tail in O(1).
 */
struct hlist_head {
    struct hlist_node *first;
};
struct hlist_node {
    struct hlist_node *next, **pprev;
};
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
    h->next = NULL;
    h->pprev = NULL;
}
static inline int hlist_unhashed(const struct hlist_node *h)
{
    return !h->pprev;
}
static inline int hlist_empty(const struct hlist_head *h)
{
    return !h->first;
}
static inline void __hlist_del(struct hlist_node *n)
{
    struct hlist_node *next = n->next;
    struct hlist_node **pprev = n->pprev;
    *pprev = next;
    if (next)
        next->pprev = pprev;
}
static inline void hlist_del(struct hlist_node *n)
{
    __hlist_del(n);
    n->next = LIST_POISON1;
    n->pprev = LIST_POISON2;
}
static inline void hlist_del_init(struct hlist_node *n)
{
    if (!hlist_unhashed(n)) {
        __hlist_del(n);
        INIT_HLIST_NODE(n);
    }
}
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
    struct hlist_node *first = h->first;
    n->next = first;
    if (first)
        first->pprev = &n->next;
    h->first = n;
    n->pprev = &h->first;
}
/* next must be != NULL */
static inline void hlist_add_before(struct hlist_node *n,
                    struct hlist_node *next)
{
    n->pprev = next->pprev;
    n->next = next;
    next->pprev = &n->next;
    *(n->pprev) = n;
}
static inline void hlist_add_after(struct hlist_node *n,
                    struct hlist_node *next)
{
    next->next = n->next;
    n->next = next;
    next->pprev = &n->next;
    if(next->next)
        next->next->pprev  = &next->next;
}
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
#define hlist_for_each(pos, head) \
    for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \
         pos = pos->next)
#define hlist_for_each_safe(pos, n, head) \
    for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
         pos = n)
/**
 * hlist_for_each_entry    - iterate over list of given type
 * @tpos:    the type * to use as a loop cursor.
 * @pos:    the &struct hlist_node to use as a loop cursor.
 * @head:    the head for your list.
 * @member:    the name of the hlist_node within the struct.
 */
#define hlist_for_each_entry(tpos, pos, head, member)             \
    for (pos = (head)->first;                     \
         pos && ({ prefetch(pos->next); 1;}) &&             \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = pos->next)
/**
 * hlist_for_each_entry_continue - iterate over a hlist continuing after current point
 * @tpos:    the type * to use as a loop cursor.
 * @pos:    the &struct hlist_node to use as a loop cursor.
 * @member:    the name of the hlist_node within the struct.
 */
#define hlist_for_each_entry_continue(tpos, pos, member)         \
    for (pos = (pos)->next;                         \
         pos && ({ prefetch(pos->next); 1;}) &&             \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = pos->next)
/**
 * hlist_for_each_entry_from - iterate over a hlist continuing from current point
 * @tpos:    the type * to use as a loop cursor.
 * @pos:    the &struct hlist_node to use as a loop cursor.
 * @member:    the name of the hlist_node within the struct.
 */
#define hlist_for_each_entry_from(tpos, pos, member)             \
    for (; pos && ({ prefetch(pos->next); 1;}) &&             \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = pos->next)
/**
 * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
 * @tpos:    the type * to use as a loop cursor.
 * @pos:    the &struct hlist_node to use as a loop cursor.
 * @n:        another &struct hlist_node to use as temporary storage
 * @head:    the head for your list.
 * @member:    the name of the hlist_node within the struct.
 */
#define hlist_for_each_entry_safe(tpos, pos, n, head, member)         \
    for (pos = (head)->first;                     \
         pos && ({ n = pos->next; 1; }) &&                 \
        ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \
         pos = n)
#endif
project/booster/logger.c
New file
@@ -0,0 +1,279 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  logger.c
 *    Description:  This file is common logger API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <pthread.h>
#include "logger.h"
typedef void (*log_LockFn)(void *udata, int lock);
static struct {
    char        file[32]; /* logger file name */
    FILE       *fp;       /* logger file pointer */
    long        size;     /* logger file max size */
    int         level;    /* logger level */
    log_LockFn  lockfn;   /* lock function */
    void       *udata;    /* lock data */
} L;
static const char *level_names[] = {
    "ERROR",
    "WARN",
    "INFO",
    "DEBUG",
    "TRACE"
};
static const char *level_colors[] = {
    "\x1b[31m",
    "\x1b[33m",
    "\x1b[32m",
    "\x1b[36m",
    "\x1b[94m"
};
static inline void time_to_str(char *buf)
{
    struct timeval   tv;
    struct tm       *tm;
    int              len;
    gettimeofday(&tv, NULL);
    tm = localtime(&tv.tv_sec);
    len = sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d.%06d ",
            tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
            tm->tm_hour, tm->tm_min, tm->tm_sec, (int)tv.tv_usec);
    buf[len] = '\0';
}
static void mutex_lock(void *udata, int lock)
{
    int              err;
    pthread_mutex_t *l = (pthread_mutex_t *) udata;
    if (lock)
    {
        if ( (err = pthread_mutex_lock(l)) != 0 )
            log_error("Unable to lock log lock: %s", strerror(err));
    }
    else
    {
        if ( (err = pthread_mutex_unlock(l) != 0) )
            log_error("Unable to unlock log lock: %s", strerror(err));
    }
}
int log_open(char *fname, int level, int size, int lock)
{
    FILE            *fp;
    L.level = level;
    L.size = size*1024;
    if( !fname || !strcmp(fname, "console") || !strcmp(fname, "stderr") )
    {
        strcpy(L.file, "console");
        L.fp = stderr;
        L.size = 0; /* console don't need rollback */
    }
    else
    {
        if ( !(fp = fopen(fname, "a+")) )
        {
            fprintf(stderr, "%s() failed: %s\n", __func__, strerror(errno));
            return -2;
        }
        L.fp = fp;
        strncpy(L.file, fname, sizeof(L.file));
    }
    if( lock )
    {
        static pthread_mutex_t     log_lock;
        pthread_mutex_init(&log_lock, NULL);
        L.udata = (void *)&log_lock;
        L.lockfn = mutex_lock;
    }
    fprintf(L.fp, "\n");
    log_info("logger system(%s) start: file:\"%s\", level:%s, maxsize:%luKiB\n\n",
            LOG_VERSION, L.file, level_names[level], size);
    return 0;
}
void log_close(void)
{
    if( L.fp && L.fp!=stderr )
        fclose(L.fp);
    if (L.udata )
        pthread_mutex_destroy( L.udata);
}
static void log_rollback(void)
{
    char       cmd[128]={0};
    long       fsize;
    /* don't need rollback */
    if(L.size <= 0 )
        return ;
    fsize = ftell(L.fp);
    if( fsize < L.size )
        return ;
    /* backup current log file  */
    snprintf(cmd, sizeof(cmd), "cp %s %s.bak", L.file, L.file);
    system(cmd);
    /* rollback file */
    fseek(L.fp, 0, SEEK_SET);
    truncate(L.file, 0);
    fprintf(L.fp, "\n");
    log_info("logger system(%s) rollback: file:\"%s\", level:%s, maxsize:%luKiB\n\n",
            LOG_VERSION, L.file, level_names[L.level], L.size/1024);
    return ;
}
void _log_write(int level, const char *file, int line, const char *fmt, ...)
{
    va_list    args;
    char       time_string[100];
    if ( !L.fp || level>L.level )
        return;
    /* Acquire lock */
    if ( L.lockfn )
        L.lockfn(L.udata, 1);
    log_rollback();
    /* check and rollback file */
    time_to_str(time_string);
    /* Log to stderr */
    if ( L.fp == stderr )
    {
        fprintf(L.fp, "%s %s %-5s\x1b[0m \x1b[90m%s:%03d:\x1b[0m ",
                time_string, level_colors[level], level_names[level], file, line);
    }
    else /* Log to file */
    {
        fprintf(L.fp, "%s %-5s %s:%03d: ", time_string, level_names[level], file, line);
    }
    va_start(args, fmt);
    vfprintf(L.fp, fmt, args);
    va_end(args);
    fflush(L.fp);
    /* Release lock */
    if ( L.lockfn )
        L.lockfn(L.udata, 0);
}
#define LINELEN 81
#define CHARS_PER_LINE 16
static char *print_char =
"                "
"                "
" !\"#$%&'()*+,-./"
"0123456789:;<=>?"
"@ABCDEFGHIJKLMNO"
"PQRSTUVWXYZ[\\]^_"
"`abcdefghijklmno"
"pqrstuvwxyz{|}~ "
"                "
"                "
" ???????????????"
"????????????????"
"????????????????"
"????????????????"
"????????????????"
"????????????????";
void log_dump(int level, const char *prompt, char *buf, size_t len)
{
    int rc;
    int idx;
    char prn[LINELEN];
    char lit[CHARS_PER_LINE + 2];
    char hc[4];
    short line_done = 1;
    if (!L.fp || level>L.level)
        return;
    if( prompt )
        _log_write(level, __FILE__, __LINE__, "%s", prompt);
    rc = len;
    idx = 0;
    lit[CHARS_PER_LINE] = '\0';
    while (rc > 0)
    {
        if (line_done)
            snprintf(prn, LINELEN, "%08X: ", idx);
        do
        {
            unsigned char c = buf[idx];
            snprintf(hc, 4, "%02X ", c);
            strncat(prn, hc, LINELEN);
            lit[idx % CHARS_PER_LINE] = print_char[c];
        }
        while (--rc > 0 && (++idx % CHARS_PER_LINE != 0));
        line_done = (idx % CHARS_PER_LINE) == 0;
        if (line_done)
        {
            if (L.fp)
                fprintf(L.fp, "%s  %s\n", prn, lit);
        }
    }
    if (!line_done)
    {
        int ldx = idx % CHARS_PER_LINE;
        lit[ldx++] = print_char[(int)buf[idx]];
        lit[ldx] = '\0';
        while ((++idx % CHARS_PER_LINE) != 0)
            strncat(prn, "   ", sizeof(prn)-strlen(prn));
        if (L.fp)
            fprintf(L.fp, "%s  %s\n", prn, lit);
    }
}
project/booster/logger.h
New file
@@ -0,0 +1,68 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  logger.h
 *    Description:  This file is common logger API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#ifndef  _LOGGER_H_
#define  _LOGGER_H_
#include <stdio.h>
#include <stdarg.h>
#define LOG_VERSION "v0.1"
/* log level */
enum {
    LOG_LEVEL_ERROR,
    LOG_LEVEL_WARN,
    LOG_LEVEL_INFO,
    LOG_LEVEL_DEBUG,
    LOG_LEVEL_TRACE,
    LOG_LEVEL_MAX
};
enum {
    LOG_LOCK_DISABLE, /* disable lock */
    LOG_LOCK_ENABLE,  /* enable lock */
};
#define ROLLBACK_NONE   0
/* description: Initial the logger system
 * arguments  :
 *             $fname: logger file name, NULL/"console"/"stderr" will log to console
 *             $level: logger level above;
 *             $size : logger file max size in KiB
 *             $lock : thread lock enable or not
 * return     : <0: Failed  ==0: Sucessfully
 */
int log_open(char *fname, int level, int size, int lock);
/* description: Terminate the logger system */
void log_close(void);
/* description: log message into log file. Don't call this function directly. */
void _log_write(int level, const char *file, int line, const char *fmt, ...);
/* description: dump a buffer in hex to logger file */
void log_dump(int level, const char *prompt, char *buf, size_t len);
/* function: log message into logger file with different log level */
#define log_trace(...) _log_write(LOG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define log_debug(...) _log_write(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define log_info(...)  _log_write(LOG_LEVEL_INFO,  __FILE__, __LINE__, __VA_ARGS__)
#define log_warn(...)  _log_write(LOG_LEVEL_WARN,  __FILE__, __LINE__, __VA_ARGS__)
#define log_error(...) _log_write(LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#endif
project/booster/makefile
New file
@@ -0,0 +1,35 @@
#********************************************************************************
#      Copyright:  (C) 2023 LingYun IoT System Studio
#                  All rights reserved.
#
#       Filename:  Makefile
#    Description:  This file used compile all the source code to static library
#
#        Version:  1.0.0(11/08/23)
#         Author:  Guo Wenxue <guowenxue@gmail.com>
#      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
#
#*******************************************************************************
PWD=$(shell pwd )
BUILD_ARCH=$(shell uname -m)
ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
    CROSS_COMPILE?=arm-linux-gnueabihf-
endif
LIBNAME=$(shell basename ${PWD} )
TOPDIR=$(shell dirname ${PWD} )
CFLAGS+=-D_GNU_SOURCE
all: clean
    @rm -f *.o
    @${CROSS_COMPILE}gcc ${CFLAGS} -I${TOPDIR} -c *.c
    ${CROSS_COMPILE}ar -rcs  lib${LIBNAME}.a *.o
clean:
    @rm -f *.o
    @rm -f *.a
distclean:
    @make clean
project/booster/ringbuf.c
New file
@@ -0,0 +1,107 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  ringbuf.c
 *    Description:  This file is common ring buffer API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#include <string.h>
#include <assert.h>
#include "ringbuf.h"
void rb_init (struct ring_buffer *ring, unsigned char* buff, int size)
{
    memset (ring, 0, sizeof (struct ring_buffer));
    ring->rd_pointer = 0;
    ring->wr_pointer = 0;
    ring->buffer= buff;
    ring->size = size;
}
int rb_write (struct ring_buffer *rb, unsigned char * buf, int len)
{
    int                  total;
    int                  i;
    /* total = len = min(space, len) */
    total = rb_free_size(rb);
    if(len > total)
        len = total;
    else
        total = len;
    i = rb->wr_pointer;
    if(i + len > rb->size)
    {
        memcpy(rb->buffer + i, buf, rb->size - i);
        buf += rb->size - i;
        len -= rb->size - i;
        i = 0;
    }
    memcpy(rb->buffer + i, buf, len);
    rb->wr_pointer = i + len;
    return total;
}
int rb_free_size (struct ring_buffer *rb)
{
    return (rb->size - 1 - rb_data_size(rb));
}
int rb_read (struct ring_buffer *rb, unsigned char * buf, int max)
{
    int                  total;
    int                  i;
    /* total = len = min(used, len) */
    total = rb_data_size(rb);
    if(max > total)
        max = total;
    else
        total = max;
    i = rb->rd_pointer;
    if(i + max > rb->size)
    {
        memcpy(buf, rb->buffer + i, rb->size - i);
        buf += rb->size - i;
        max -= rb->size - i;
        i = 0;
    }
    memcpy(buf, rb->buffer + i, max);
    rb->rd_pointer = i + max;
    return total;
}
int rb_data_size (struct ring_buffer *rb)
{
    return ((rb->wr_pointer - rb->rd_pointer) & (rb->size-1));
}
void rb_clear (struct ring_buffer *rb)
{
    memset(rb->buffer,0,rb->size);
    rb->rd_pointer=0;
    rb->wr_pointer=0;
}
unsigned char rb_peek(struct ring_buffer* rb, int index)
{
    assert(index < rb_data_size(rb));
    return rb->buffer[((rb->rd_pointer + index) % rb->size)];
}
project/booster/ringbuf.h
New file
@@ -0,0 +1,57 @@
/*********************************************************************************
 *      Copyright:  (C) 2023 LingYun IoT System Studio.
 *                  All rights reserved.
 *
 *       Filename:  ringbuf.h
 *    Description:  This file is common ring buffer API functions
 *
 *        Version:  1.0.0(11/08/23)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
 *
 ********************************************************************************/
#ifndef  _RINGBUF_H_
#define  _RINGBUF_H_
struct ring_buffer
{
    unsigned char      *buffer;
    int                 wr_pointer;
    int                 rd_pointer;
    int                 size;
};
/* Initial the ring buffer */
void rb_init (struct ring_buffer *ring, unsigned char *buff, int size) ;
/*  Description: Write $len bytes data in $buf into ring buffer $rb
 * Return Value: The actual written into ring buffer data size, if ring buffer
 * left space size small than $len, then only part of the data be written into.
 */
int rb_write (struct ring_buffer *rb, unsigned char *buf, int len) ;
/* Get ring buffer left free size  */
int rb_free_size (struct ring_buffer *rb);
/* Read $max bytes data from ring buffer $rb to $buf */
int rb_read (struct ring_buffer *rb, unsigned char *buf, int max);
/* Read a specify $index byte data in ring buffer $rb  */
unsigned char rb_peek(struct ring_buffer *rb, int index);
/* Get data size in the ring buffer  */
int rb_data_size (struct ring_buffer *rb);
/* Clear the ring buffer data  */
void rb_clear (struct ring_buffer *rb) ;
#endif   /* ----- #ifndef _RINGBUF_H_  ----- */
project/booster/util_proc.c
New file
@@ -0,0 +1,432 @@
/*********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  util_proc.c
 *    Description:  This file is the process API
 *
 *        Version:  1.0.0(7/06/2020)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "7/06/2020 09:19:02 PM"
 *
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "util_proc.h"
#include "logger.h"
proc_signal_t     g_signal={0};
void proc_default_sighandler(int sig)
{
    switch(sig)
    {
        case SIGINT:
            log_warn("SIGINT - stopping\n");
            g_signal.stop = 1;
            break;
        case SIGTERM:
            log_warn("SIGTERM - stopping\n");
            g_signal.stop = 1;
            break;
        case SIGSEGV:
            log_warn("SIGSEGV - stopping\n");
#if 0
            if(g_signal.stop)
                exit(0);
            g_signal.stop = 1;
#endif
            break;
        case SIGPIPE:
            log_warn("SIGPIPE - warnning\n");
            break;
        default:
            break;
    }
}
/* install default signal process functions  */
void install_default_signal(void)
{
    struct sigaction sigact, sigign;
    log_info("Install default signal handler.\n");
    /*  Initialize the catch signal structure. */
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;
    sigact.sa_handler = proc_default_sighandler;
    /*  Setup the ignore signal. */
    sigemptyset(&sigign.sa_mask);
    sigign.sa_flags = 0;
    sigign.sa_handler = SIG_IGN;
    sigaction(SIGTERM, &sigact, 0); /*  catch terminate signal "kill" command */
    sigaction(SIGINT,  &sigact, 0); /*  catch interrupt signal CTRL+C */
    //sigaction(SIGSEGV, &sigact, 0); /*  catch segmentation faults  */
    sigaction(SIGPIPE, &sigact, 0); /*  catch broken pipe */
#if 0
    sigaction(SIGCHLD, &sigact, 0); /*  catch child process return */
    sigaction(SIGUSR2, &sigact, 0); /*  catch USER signal */
#endif
}
/* ****************************************************************************
 * FunctionName: daemonize
 * Description : Set the programe runs as daemon in background
 * Inputs      : nodir: DON'T change the work directory to / :  1:NoChange 0:Change
 *               noclose: close the opened file descrtipion or not 1:Noclose 0:Close
 * Output      : NONE
 * Return      : NONE
 * *****************************************************************************/
void daemonize(int nochdir, int noclose)
{
    int rv, fd;
    int i;
    /*  already a daemon */
    if (1 == getppid())
        return;
    /*  fork error */
    rv = fork();
    if (rv < 0) exit(1);
    /*  parent process exit */
    if (rv > 0)
        exit(0);
    /*  obtain a new process session group */
    setsid();
    if (!noclose)
    {
        /*  close all descriptors */
        for (i = getdtablesize(); i >= 0; --i)
        {
            //if (i != g_logPtr->fd)
                close(i);
        }
        /*  Redirect Standard input [0] to /dev/null */
        fd = open("/dev/null", O_RDWR);
        /* Redirect Standard output [1] to /dev/null */
        dup(fd);
        /* Redirect Standard error [2] to /dev/null */
        dup(fd);
    }
    umask(0);
    if (!nochdir)
        chdir("/");
    return;
}
/* ****************************************************************************
 * FunctionName: check_set_program_running
 * Description : check program already running or not, if not then run it and
 *               record pid into $pidfile
 * Inputs      : daemon:  set program running in daemon or not
 *               pid_file:The record PID file path
 * Output      : NONE
 * Return      : 0: Record successfully  Else: Failure
 * *****************************************************************************/
int check_set_program_running(int daemon, char *pidfile)
{
    if( !pidfile )
        return 0;
    if( check_daemon_running(pidfile) )
    {
        log_error("Program already running, process exit now");
        return -1;
    }
    if( daemon )
    {
        if( set_daemon_running(pidfile) < 0 )
        {
            log_error("set program running as daemon failure\n");
            return -2;
        }
    }
    else
    {
        if( record_daemon_pid(pidfile) < 0 )
        {
            log_error("record program running PID failure\n");
            return -3;
        }
    }
    return 0;
}
/* ****************************************************************************
 * FunctionName: record_daemon_pid
 * Description : Record the running daemon program PID to the file "pid_file"
 * Inputs      : pid_file:The record PID file path
 * Output      : NONE
 * Return      : 0: Record successfully  Else: Failure
 * *****************************************************************************/
int record_daemon_pid(const char *pid_file)
{
    struct stat fStatBuf;
    int fd = -1;
    int mode = S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP | S_IRWXU;
    char ipc_dir[64] = { 0 };
    strncpy(ipc_dir, pid_file, 64);
    /* dirname() will modify ipc_dir and save the result */
    dirname(ipc_dir);
    /* If folder pid_file PATH doesnot exist, then we will create it" */
    if (stat(ipc_dir, &fStatBuf) < 0)
    {
        if (mkdir(ipc_dir, mode) < 0)
        {
            log_error("cannot create %s: %s\n", ipc_dir, strerror(errno));
            return -1;
        }
        (void)chmod(ipc_dir, mode);
    }
    /*  Create the process running PID file */
    mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    if ((fd = open(pid_file, O_RDWR | O_CREAT | O_TRUNC, mode)) >= 0)
    {
        char pid[PID_ASCII_SIZE];
        snprintf(pid, sizeof(pid), "%u\n", (unsigned)getpid());
        write(fd, pid, strlen(pid));
        close(fd);
        log_debug("Record PID<%u> to file %s.\n", getpid(), pid_file);
    }
    else
    {
        log_error("cannot create %s: %s\n", pid_file, strerror(errno));
        return -1;
    }
    return 0;
}
/* ****************************************************************************
 * FunctionName: get_daemon_pid
 * Description : Get the daemon process PID from the PID record file "pid_file"
 * Inputs      : pid_file: the PID record file
 * Output      : NONE
 * Return      : pid_t: The daemon process PID number
 * *****************************************************************************/
pid_t get_daemon_pid(const char *pid_file)
{
    FILE *f;
    pid_t pid;
    if ((f = fopen(pid_file, "rb")) != NULL)
    {
        char pid_ascii[PID_ASCII_SIZE];
        (void)fgets(pid_ascii, PID_ASCII_SIZE, f);
        (void)fclose(f);
        pid = atoi(pid_ascii);
    }
    else
    {
        log_error("Can't open PID record file %s: %s\n", pid_file, strerror(errno));
        return -1;
    }
    return pid;
}
/* ****************************************************************************
 * FunctionName: check_daemon_running
 * Description : Check the daemon program already running or not
 * Inputs      : pid_file: The record running daemon program PID
 * Output      : NONE
 * Return      : 1: The daemon program alread running   0: Not running
 * *****************************************************************************/
int check_daemon_running(const char *pid_file)
{
    int rv = -1;
    struct stat fStatBuf;
    rv = stat(pid_file, &fStatBuf);
    if (0 == rv)
    {
        pid_t pid = -1;
        printf("PID record file \"%s\" exist.\n", pid_file);
        pid = get_daemon_pid(pid_file);
        if (pid > 0)  /*  Process pid exist */
        {
            if ((rv = kill(pid, 0)) == 0)
            {
                printf("Program with PID[%d] seems running.\n", pid);
                return 1;
            }
            else   /* Send signal to the old process get no reply. */
            {
                printf("Program with PID[%d] seems exit.\n", pid);
                remove(pid_file);
                return 0;
            }
        }
        else if (0 == pid)
        {
            printf("Can not read program PID form record file.\n");
            remove(pid_file);
            return 0;
        }
        else  /* Read pid from file "pid_file" failure */
        {
            printf("Read record file \"%s\" failure, maybe program still running.\n", pid_file);
            return 1;
        }
    }
    return 0;
}
/* ****************************************************************************
 * FunctionName: stop_daemon_running
 * Description : Stop the daemon program running
 * Inputs      : pid_file: The record running daemon program PID
 * Output      : NONE
 * Return      : 1: The daemon program alread running   0: Not running
 * *****************************************************************************/
int stop_daemon_running(const char *pid_file)
{
    pid_t            pid = -1;
    struct stat      fStatBuf;
    if ( stat(pid_file, &fStatBuf) < 0)
        return 0;
    printf("PID record file \"%s\" exist.\n", pid_file);
    pid = get_daemon_pid(pid_file);
    if (pid > 0)  /*  Process pid exist */
    {
        while ( (kill(pid, 0) ) == 0)
        {
            kill(pid, SIGTERM);
            sleep(1);
        }
        remove(pid_file);
    }
    return 0;
}
/* ****************************************************************************
 * FunctionName: set_daemon_running
 * Description : Set the programe running as daemon if it's not running and record
 *               its PID to the pid_file.
 * Inputs      : pid_file: The record running daemon program PID
 * Output      : NONE
 * Return      : 0: Successfully. 1: Failure
 * *****************************************************************************/
int set_daemon_running(const char *pid_file)
{
    daemonize(0, 1);
    log_info("Program running as daemon [PID:%d].\n", getpid());
    if (record_daemon_pid(pid_file) < 0)
    {
        log_error("Record PID to file \"%s\" failure.\n", pid_file);
        return -2;
    }
    return 0;
}
/* start a new thread to run $thread_workbody point function  */
int thread_start(pthread_t *thread_id, thread_body_t thread_workbody, void *thread_arg)
{
    int                rv = 0;
    pthread_t          tid;
    pthread_attr_t     thread_attr;
    /* Initialize the thread  attribute */
    rv = pthread_attr_init(&thread_attr);
    if(rv)
        return -1;
    /* Set the stack size of the thread */
    rv = pthread_attr_setstacksize(&thread_attr, 120 * 1024);
    if(rv)
        goto CleanUp;
    /* Set thread to detached state:Don`t need pthread_join */
    rv = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
    if(rv)
        goto CleanUp;
    /* Create the thread */
    rv = pthread_create(&tid, &thread_attr, thread_workbody, thread_arg);
    if(rv)
        goto CleanUp;
CleanUp:
    if( thread_id )
    {
        if( rv )
            *thread_id = 0;
        else
            *thread_id = tid;
    }
    /* Destroy the  attributes  of  thread */
    pthread_attr_destroy(&thread_attr);
    return rv;
}
/* excute a linux command by system() */
void exec_system_cmd(const char *format, ...)
{
    char                cmd[256];
    va_list             args;
    memset(cmd, 0, sizeof(cmd));
    va_start(args, format);
    vsnprintf(cmd, sizeof(cmd), format, args);
    va_end(args);
    system(cmd);
}
project/booster/util_proc.h
New file
@@ -0,0 +1,89 @@
/********************************************************************************
 *      Copyright:  (C) 2020 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  util_proc.h
 *    Description:  This head file is for Linux process/thread API
 *
 *        Version:  1.0.0(7/06/2012~)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "7/06/2012 09:21:33 PM"
 *
 ********************************************************************************/
#ifndef __UTIL_PROC_H_
#define __UTIL_PROC_H_
#include <signal.h>
#include <time.h>
#define PID_ASCII_SIZE  11
typedef struct proc_signal_s
{
    int       signal;
    unsigned  stop;     /* 0: Not term  1: Stop  */
}  proc_signal_t;
typedef void *(* thread_body_t) (void *thread_arg);
extern proc_signal_t    g_signal;
/* install default signal process functions  */
extern void install_default_signal(void);
/* excute a linux command by system() */
extern void exec_system_cmd(const char *format, ...);
/* check program already running or not, if not then run it and record pid into $pidfile */
extern int check_set_program_running(int daemon, char *pidfile);
/* check program already running or not from $pid_file  */
extern int check_daemon_running(const char *pid_file);
/* set program daemon running and record pid in $pid_file  */
extern int set_daemon_running(const char *pid_file);
/* record proces ID into $pid_file  */
extern int record_daemon_pid(const char *pid_file);
/* stop program running from $pid_file  */
extern int stop_daemon_running(const char *pid_file);
/* my implementation for set program running in daemon   */
extern void daemonize(int nochdir, int noclose);
/* start a new thread to run $thread_workbody point function  */
extern int thread_start(pthread_t *thread_id, thread_body_t thread_workbody, void *thread_arg);
/* +---------------------+
 * |   Low level API     |
 * +---------------------+*/
/* get daemon process ID from $pid_file   */
extern pid_t get_daemon_pid(const char *pid_file);
/* +------------------------+
 * |  inline functions API  |
 * +------------------------+*/
static inline void msleep(unsigned long ms)
{
    struct timespec cSleep;
    unsigned long ulTmp;
    cSleep.tv_sec = ms / 1000;
    if (cSleep.tv_sec == 0)
    {
        ulTmp = ms * 10000;
        cSleep.tv_nsec = ulTmp * 100;
    }
    else
    {
        cSleep.tv_nsec = 0;
    }
    nanosleep(&cSleep, 0);
    return ;
}
#endif
project/coreMQTT/conf.c
New file
@@ -0,0 +1,200 @@
/*********************************************************************************
 *      Copyright:  (C) 2019 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  conf.c
 *    Description:  This file is mqttd configure file parser function
 *
 *        Version:  1.0.0(2019年06月25日)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2019年06月25日 22时23分55秒"
 *
 ********************************************************************************/
#include "conf.h"
#include "logger.h"
#include "iniparser.h"
int mqttd_parser_conf(const char *conf_file, mqtt_ctx_t *ctx, int debug)
{
    dictionary          *ini;
    const char          *str;
    int                  val;
    int                  rv = 0;
    if( !conf_file || !ctx )
    {
        fprintf(stderr, "%s() Invalid input arguments\n", __func__);
        return -1;
    }
    memset(ctx, 0, sizeof(*ctx));
    ini = iniparser_load(conf_file);
    if( !ini )
    {
        fprintf(stderr, "ERROR: Configure file '%s' load failed\n", conf_file);
        return -2;
    }
    /*+------------------------------------------------------+
     *|    parser logger settings and start logger system    |
     *+------------------------------------------------------+*/
    if( !debug )
    {
        str = iniparser_getstring(ini, "logger:file", "/tmp/mqttd.log");
        strncpy(ctx->logfile, str, sizeof(ctx->logfile));
        ctx->logsize = iniparser_getint(ini, "logger:size", 1024);
        ctx->loglevel = iniparser_getint(ini, "logger:level", LOG_LEVEL_INFO);
    }
    else
    {
        strncpy(ctx->logfile, "console", sizeof(ctx->logfile));
        ctx->loglevel = LOG_LEVEL_DEBUG;
        ctx->logsize = 0;
    }
    if( log_open(ctx->logfile, ctx->loglevel, ctx->logsize, LOG_LOCK_DISABLE) < 0 )
    {
        fprintf(stderr, "Logger system initialise failure\n");
        return -2;
    }
    log_info("Logger system initialise ok\n");
    /*+------------------------------------------------------+
     *|               parser production ID                   |
     *+------------------------------------------------------+*/
    if( !(str=iniparser_getstring(ini, "common:devid", NULL)) )
    {
        log_error("ERROR: Parser device ID failure\n");
        rv = -3;
        goto cleanup;
    }
    /* cJSON parser ID will get ""  */
    snprintf(ctx->devid, sizeof(ctx->devid), "\"%s\"", str);
    log_info("Parser device ID [%s]\n", ctx->devid);
    /*+------------------------------------------------------+
     *|       parser hardware module configuration           |
     *+------------------------------------------------------+*/
    /* relay */
    ctx->hwconf.relay=iniparser_getint(ini, "hardware:relay", 0);
    if( !ctx->hwconf.relay )
        log_warn("Parser relay module disabled\n");
    else
        log_info("Parser relay module enabled\n");
    /* RGB 3-colors LED */
    ctx->hwconf.led=iniparser_getint(ini, "hardware:rgbled", 0);
    if( !ctx->hwconf.led )
        log_warn("Parser RGB 3-colors Led module disabled\n");
    else
        log_info("Parser RGB 3-colors Led module enabled\n");
    /* beeper */
    ctx->hwconf.beeper=iniparser_getint(ini, "hardware:beep", 0);
    if( !ctx->hwconf.beeper )
        log_warn("Parser beeper module disabled\n");
    else
        log_info("Parser beeper module enabled\n");
    /* DS18B20 temperature module */
    ctx->hwconf.ds18b20=iniparser_getint(ini, "hardware:ds18b20", 0);
    if( !ctx->hwconf.ds18b20 )
        log_warn("Parser DS18B20 temperature module disabled\n");
    else
        log_info("Parser DS18B20 temperature module enabled\n");
    /* SHT20 temperature and hummidity module */
    ctx->hwconf.sht2x=iniparser_getint(ini, "hardware:sht2x", 0);
    if( !ctx->hwconf.sht2x )
        log_warn("Parser SHT2X temperature and hummidity module disabled\n");
    else
        log_info("Parser SHT2X temperature and hummidity module enabled\n");
    /* TSL2561 light intensity sensor module */
    ctx->hwconf.tsl2561=iniparser_getint(ini, "hardware:tsl2561", 0);
    if( !ctx->hwconf.tsl2561 )
        log_warn("Parser TSL2561 light intensity sensor module disabled\n");
    else
        log_info("Parser TSL2561 light intensity sensor module enabled\n");
    /*+------------------------------------------------------+
     *|              parser broker settings                  |
     *+------------------------------------------------------+*/
    if( !(str=iniparser_getstring(ini, "broker:hostname", NULL)) )
    {
        log_error("ERROR: Parser MQTT broker server hostname failure\n");
        rv = -4;
        goto cleanup;
    }
    strncpy(ctx->host, str, sizeof(ctx->host) );
    if( (val=iniparser_getint(ini, "broker:port", -1)) < 0 )
    {
        log_error("ERROR: Parser MQTT broker server port failure\n");
        rv = -5;
        goto cleanup;
    }
    ctx->port = val;
    log_info("Parser MQTT broker server [%s:%d]\n", ctx->host, ctx->port);
    str=iniparser_getstring(ini, "broker:username", NULL);
    strncpy(ctx->uid, str, sizeof(ctx->uid) );
    str=iniparser_getstring(ini, "broker:password", NULL);
    strncpy(ctx->pwd, str, sizeof(ctx->pwd) );
    if( ctx->uid && ctx->pwd )
        log_info("Parser broker author by [%s:%s]\n", ctx->uid, ctx->pwd);
    ctx->keepalive = iniparser_getint(ini, "broker:keepalive", DEF_KEEPALIVE);
    log_info("Parser broker keepalive timeout [%d] seconds\n", ctx->keepalive);
    /*+------------------------------------------------------+
     *|             parser publisher settings                |
     *+------------------------------------------------------+*/
    if( !(str=iniparser_getstring(ini, "publisher:pubTopic", NULL)) )
    {
        log_error("ERROR: Parser MQTT broker publisher topic failure\n");
        rv = -6;
        goto cleanup;
    }
    strncpy(ctx->pubTopic, str, sizeof(ctx->pubTopic) );
    ctx->pubQos = iniparser_getint(ini, "publisher:pubQos", DEF_QOS);
    ctx->interval = iniparser_getint(ini, "publisher:interval", DEF_PUBINTERVAL);
    log_info("Parser publisher topic \"%s\" with Qos[%d] interval[%d]\n", ctx->pubTopic, ctx->pubQos, ctx->interval);
    /*+------------------------------------------------------+
     *|             parser subscriber settings               |
     *+------------------------------------------------------+*/
    if( !(str=iniparser_getstring(ini, "subsciber:subTopic", NULL)) )
    {
        log_error("ERROR: Parser MQTT broker publisher topic failure\n");
        rv = -7;
        goto cleanup;
    }
    strncpy(ctx->subTopic, str, sizeof(ctx->subTopic) );
    ctx->subQos = iniparser_getint(ini, "subsciber:subQos", DEF_QOS);
    log_info("Parser subscriber topic \"%s\" with Qos[%d]\n", ctx->subTopic, ctx->subQos);
cleanup:
    if( ini )
        iniparser_freedict(ini);
    if( rv )
        log_close();
    return rv;
}
project/coreMQTT/conf.h
New file
@@ -0,0 +1,71 @@
/*********************************************************************************
 *      Copyright:  (C) 2019 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  conf.h
 *    Description:  This file is mqttd configure file parser function
 *
 *        Version:  1.0.0(2019年06月25日)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "2019年06月25日 22时23分55秒"
 *
 ********************************************************************************/
#ifndef  __CONF_H_
#define  __CONF_H_
enum
{
    Qos0, /* 发送者只发送一次消息,不进行重试,Broker不会返回确认消息。在Qos0情况下,Broker可能没有接受到消息 */
    Qos1, /* 发送者最少发送一次消息,确保消息到达Broker,Broker需要返回确认消息PUBACK。在Qos1情况下,Broker可能接受到重复消息 */
    Qos2, /* Qos2使用两阶段确认来保证消息的不丢失和不重复。在Qos2情况下,Broker肯定会收到消息,且只收到一次  */
};
#define DEF_KEEPALIVE       30
#define DEF_PUBINTERVAL     120
#define DEF_QOS             Qos2
typedef struct hwconf_s
{
    int            relay;   /* relay aviable or not.   0:Disable  1: Enable */
    int            led;     /* RGB led aviable or not. 0:Disable  1: Enable */
    int            beeper;  /* beeper aviable or not.  0:Disable  1: Enable */
    int            ds18b20; /* DS1B820 aviable or not. 0:Disable  1: Enable */
    int            sht2x;   /* SHT20  aviable or not.  0:Disable  1: Enable */
    int            tsl2561; /* TSL2561 aviable or not.  0:Disable  1: Enable */
} hwconf_t;
typedef struct mqtt_ctx_s
{
    char          devid[32];    /*  device ID */
    /* hardware configuration */
    hwconf_t      hwconf;
    /* logger settings */
    char          logfile[128]; /* logger record file */
    int           loglevel;     /* logger level  */
    int           logsize;      /* logger file maxsize, oversize will rollback */
    /* Broker settings  */
    char          host[128];  /* MQTT broker server name  */
    int           port;       /* MQTT broker listen port  */
    char          uid[64];    /* username */
    char          pwd[64];    /* password */
    int           keepalive;  /* MQTT broker send PING message to subsciber/publisher keepalive timeout<seconds> */
    /* Publisher settings */
    char          pubTopic[256]; /* Publisher topic  */
    int           pubQos;        /* Publisher Qos */
    int           interval ;     /* Publish sensor data interval time, unit seconds */
    /* Subscriber settings */
    char          subTopic[256]; /* Subscriber topic */
    int           subQos;        /* Subscriber Qos  */
} mqtt_ctx_t;
extern int mqttd_parser_conf(const char *conf_file, mqtt_ctx_t *ctx, int debug);
#endif   /* ----- #ifndef _CONF_H_  ----- */
project/coreMQTT/coreJSON/README.md
New file
@@ -0,0 +1,65 @@
## coreJSON Library
This repository contains the coreJSON library, a parser that strictly enforces the ECMA-404 JSON standard and is suitable for low memory footprint embedded devices. The coreJSON library is distributed under the [MIT Open Source License](LICENSE).
This library has gone through code quality checks including verification that no function has a [GNU Complexity](https://www.gnu.org/software/complexity/manual/complexity.html) score over 8, and checks against deviations from mandatory rules in the [MISRA coding standard](https://www.misra.org.uk). Deviations from the MISRA C:2012 guidelines are documented under [MISRA Deviations](MISRA.md). This library has also undergone both static code analysis from [Coverity static analysis](https://scan.coverity.com/), and validation of memory safety through the [CBMC automated reasoning tool](https://www.cprover.org/cbmc/).
See memory requirements for this library [here](./docs/doxygen/include/size_table.md).
**coreJSON v3.2.0 [source code](https://github.com/FreeRTOS/coreJSON/tree/v3.2.0/source) is part of the [FreeRTOS 202210.00 LTS](https://github.com/FreeRTOS/FreeRTOS-LTS/tree/202210.00-LTS) release.**
**coreJSON v3.0.0 [source code](https://github.com/FreeRTOS/coreJSON/tree/v3.0.0/source) is part of the [FreeRTOS 202012.00 LTS](https://github.com/FreeRTOS/FreeRTOS-LTS/tree/202012.00-LTS) release.**
## Reference example
```c
#include <stdio.h>
#include "core_json.h"
int main()
{
    // Variables used in this example.
    JSONStatus_t result;
    char buffer[] = "{\"foo\":\"abc\",\"bar\":{\"foo\":\"xyz\"}}";
    size_t bufferLength = sizeof( buffer ) - 1;
    char queryKey[] = "bar.foo";
    size_t queryKeyLength = sizeof( queryKey ) - 1;
    char * value;
    size_t valueLength;
    // Calling JSON_Validate() is not necessary if the document is guaranteed to be valid.
    result = JSON_Validate( buffer, bufferLength );
    if( result == JSONSuccess )
    {
        result = JSON_Search( buffer, bufferLength, queryKey, queryKeyLength,
                              &value, &valueLength );
    }
    if( result == JSONSuccess )
    {
        // The pointer "value" will point to a location in the "buffer".
        char save = value[ valueLength ];
        // After saving the character, set it to a null byte for printing.
        value[ valueLength ] = '\0';
        // "Found: bar.foo -> xyz" will be printed.
        printf( "Found: %s -> %s\n", queryKey, value );
        // Restore the original character.
        value[ valueLength ] = save;
    }
    return 0;
}
```
A search may descend through nested objects when the `queryKey` contains matching key strings joined by a separator, `.`. In the example above, `bar` has the value `{"foo":"xyz"}`. Therefore, a search for query key `bar.foo` would output `xyz`.
## Documentation
For pre-generated documentation, please see the documentation linked in the locations below:
| Location |
| :-: |
| [FreeRTOS.org](https://freertos.org/Documentation/api-ref/coreJSON/docs/doxygen/output/html/index.html) |
Note that the latest included version of the coreJSON library may differ across repositories.
project/coreMQTT/coreJSON/core_json.c
New file
@@ -0,0 +1,1818 @@
/*
 * coreJSON v3.2.0
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * @file core_json.c
 * @brief The source file that implements the user-facing functions in core_json.h.
 */
#include <assert.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include "core_json.h"
/** @cond DO_NOT_DOCUMENT */
/* A compromise to satisfy both MISRA and CBMC */
typedef union
{
    char c;
    uint8_t u;
} char_;
#if ( CHAR_MIN == 0 )
    #define isascii_( x )    ( ( x ) <= '\x7F' )
#else
    #define isascii_( x )    ( ( x ) >= '\0' )
#endif
#define iscntrl_( x )        ( isascii_( x ) && ( ( x ) < ' ' ) )
#define isdigit_( x )        ( ( ( x ) >= '0' ) && ( ( x ) <= '9' ) )
/* NB. This is whitespace as defined by the JSON standard (ECMA-404). */
#define isspace_( x )                          \
    ( ( ( x ) == ' ' ) || ( ( x ) == '\t' ) || \
      ( ( x ) == '\n' ) || ( ( x ) == '\r' ) )
#define isOpenBracket_( x )           ( ( ( x ) == '{' ) || ( ( x ) == '[' ) )
#define isCloseBracket_( x )          ( ( ( x ) == '}' ) || ( ( x ) == ']' ) )
#define isCurlyPair_( x, y )          ( ( ( x ) == '{' ) && ( ( y ) == '}' ) )
#define isSquarePair_( x, y )         ( ( ( x ) == '[' ) && ( ( y ) == ']' ) )
#define isMatchingBracket_( x, y )    ( isCurlyPair_( x, y ) || isSquarePair_( x, y ) )
#define isSquareOpen_( x )            ( ( x ) == '[' )
#define isSquareClose_( x )           ( ( x ) == ']' )
/**
 * @brief Advance buffer index beyond whitespace.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 */
static void skipSpace( const char * buf,
                       size_t * start,
                       size_t max )
{
    size_t i;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    for( i = *start; i < max; i++ )
    {
        if( !isspace_( buf[ i ] ) )
        {
            break;
        }
    }
    *start = i;
}
/**
 * @brief Count the leading 1s in a byte.
 *
 * The high-order 1 bits of the first byte in a UTF-8 encoding
 * indicate the number of additional bytes to follow.
 *
 * @return the count
 */
static size_t countHighBits( uint8_t c )
{
    uint8_t n = c;
    size_t i = 0;
    while( ( n & 0x80U ) != 0U )
    {
        i++;
        n = ( n & 0x7FU ) << 1U;
    }
    return i;
}
/**
 * @brief Is the value a legal Unicode code point and encoded with
 * the fewest bytes?
 *
 * The last Unicode code point is 0x10FFFF.
 *
 * Unicode 3.1 disallows UTF-8 interpretation of non-shortest form sequences.
 * 1 byte encodes 0 through 7 bits
 * 2 bytes encode 8 through 5+6 = 11 bits
 * 3 bytes encode 12 through 4+6+6 = 16 bits
 * 4 bytes encode 17 through 3+6+6+6 = 21 bits
 *
 * Unicode 3.2 disallows UTF-8 code point values in the surrogate range,
 * [U+D800 to U+DFFF].
 *
 * @note Disallow ASCII, as this is called only for multibyte sequences.
 */
static bool shortestUTF8( size_t length,
                          uint32_t value )
{
    bool ret = false;
    uint32_t min, max;
    assert( ( length >= 2U ) && ( length <= 4U ) );
    switch( length )
    {
        case 2:
            min = ( uint32_t ) 1 << 7U;
            max = ( ( uint32_t ) 1 << 11U ) - 1U;
            break;
        case 3:
            min = ( uint32_t ) 1 << 11U;
            max = ( ( uint32_t ) 1 << 16U ) - 1U;
            break;
        default:
            min = ( uint32_t ) 1 << 16U;
            max = 0x10FFFFU;
            break;
    }
    if( ( value >= min ) && ( value <= max ) &&
        ( ( value < 0xD800U ) || ( value > 0xDFFFU ) ) )
    {
        ret = true;
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond a UTF-8 code point.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @return true if a valid code point was present;
 * false otherwise.
 *
 * 00-7F    Single-byte character
 * 80-BF    Trailing byte
 * C0-DF    Leading byte of two-byte character
 * E0-EF    Leading byte of three-byte character
 * F0-F7    Leading byte of four-byte character
 * F8-FB    Illegal (formerly leading byte of five-byte character)
 * FC-FD    Illegal (formerly leading byte of six-byte character)
 * FE-FF    Illegal
 *
 * The octet values C0, C1, and F5 to FF are illegal, since C0 and C1
 * would introduce a non-shortest sequence, and F5 or above would
 * introduce a value greater than the last code point, 0x10FFFF.
 */
static bool skipUTF8MultiByte( const char * buf,
                               size_t * start,
                               size_t max )
{
    bool ret = false;
    size_t i, bitCount, j;
    uint32_t value = 0;
    char_ c;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    i = *start;
    assert( i < max );
    assert( !isascii_( buf[ i ] ) );
    c.c = buf[ i ];
    if( ( c.u > 0xC1U ) && ( c.u < 0xF5U ) )
    {
        bitCount = countHighBits( c.u );
        value = ( ( uint32_t ) c.u ) & ( ( ( uint32_t ) 1 << ( 7U - bitCount ) ) - 1U );
        /* The bit count is 1 greater than the number of bytes,
         * e.g., when j is 2, we skip one more byte. */
        for( j = bitCount - 1U; j > 0U; j-- )
        {
            i++;
            if( i >= max )
            {
                break;
            }
            c.c = buf[ i ];
            /* Additional bytes must match 10xxxxxx. */
            if( ( c.u & 0xC0U ) != 0x80U )
            {
                break;
            }
            value = ( value << 6U ) | ( c.u & 0x3FU );
        }
        if( ( j == 0U ) && ( shortestUTF8( bitCount, value ) == true ) )
        {
            *start = i + 1U;
            ret = true;
        }
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond an ASCII or UTF-8 code point.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @return true if a valid code point was present;
 * false otherwise.
 */
static bool skipUTF8( const char * buf,
                      size_t * start,
                      size_t max )
{
    bool ret = false;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    if( *start < max )
    {
        if( isascii_( buf[ *start ] ) )
        {
            *start += 1U;
            ret = true;
        }
        else
        {
            ret = skipUTF8MultiByte( buf, start, max );
        }
    }
    return ret;
}
/**
 * @brief Convert a hexadecimal character to an integer.
 *
 * @param[in] c  The character to convert.
 *
 * @return the integer value upon success or NOT_A_HEX_CHAR on failure.
 */
#define NOT_A_HEX_CHAR    ( 0x10U )
static uint8_t hexToInt( char c )
{
    char_ n;
    n.c = c;
    if( ( c >= 'a' ) && ( c <= 'f' ) )
    {
        n.c -= 'a';
        n.u += 10U;
    }
    else if( ( c >= 'A' ) && ( c <= 'F' ) )
    {
        n.c -= 'A';
        n.u += 10U;
    }
    else if( isdigit_( c ) )
    {
        n.c -= '0';
    }
    else
    {
        n.u = NOT_A_HEX_CHAR;
    }
    return n.u;
}
/**
 * @brief Advance buffer index beyond a single \u Unicode
 * escape sequence and output the value.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 * @param[out] outValue  The value of the hex digits.
 *
 * @return true if a valid escape sequence was present;
 * false otherwise.
 *
 * @note For the sake of security, \u0000 is disallowed.
 */
static bool skipOneHexEscape( const char * buf,
                              size_t * start,
                              size_t max,
                              uint16_t * outValue )
{
    bool ret = false;
    size_t i, end;
    uint16_t value = 0;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    assert( outValue != NULL );
    i = *start;
#define HEX_ESCAPE_LENGTH    ( 6U )   /* e.g., \u1234 */
    /* MISRA Ref 14.3.1 [Configuration dependent invariant] */
    /* More details at: https://github.com/FreeRTOS/coreJSON/blob/main/MISRA.md#rule-143 */
    /* coverity[misra_c_2012_rule_14_3_violation] */
    end = ( i <= ( SIZE_MAX - HEX_ESCAPE_LENGTH ) ) ? ( i + HEX_ESCAPE_LENGTH ) : SIZE_MAX;
    if( ( end < max ) && ( buf[ i ] == '\\' ) && ( buf[ i + 1U ] == 'u' ) )
    {
        for( i += 2U; i < end; i++ )
        {
            uint8_t n = hexToInt( buf[ i ] );
            if( n == NOT_A_HEX_CHAR )
            {
                break;
            }
            value = ( value << 4U ) | n;
        }
    }
    if( ( i == end ) && ( value > 0U ) )
    {
        ret = true;
        *outValue = value;
        *start = i;
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond one or a pair of \u Unicode escape sequences.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * Surrogate pairs are two escape sequences that together denote
 * a code point outside the Basic Multilingual Plane.  They must
 * occur as a pair with the first "high" value in [U+D800, U+DBFF],
 * and the second "low" value in [U+DC00, U+DFFF].
 *
 * @return true if a valid escape sequence was present;
 * false otherwise.
 *
 * @note For the sake of security, \u0000 is disallowed.
 */
#define isHighSurrogate( x )    ( ( ( x ) >= 0xD800U ) && ( ( x ) <= 0xDBFFU ) )
#define isLowSurrogate( x )     ( ( ( x ) >= 0xDC00U ) && ( ( x ) <= 0xDFFFU ) )
static bool skipHexEscape( const char * buf,
                           size_t * start,
                           size_t max )
{
    bool ret = false;
    size_t i;
    uint16_t value;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    i = *start;
    if( skipOneHexEscape( buf, &i, max, &value ) == true )
    {
        if( isHighSurrogate( value ) )
        {
            if( ( skipOneHexEscape( buf, &i, max, &value ) == true ) &&
                ( isLowSurrogate( value ) ) )
            {
                ret = true;
            }
        }
        else if( isLowSurrogate( value ) )
        {
            /* premature low surrogate */
        }
        else
        {
            ret = true;
        }
    }
    if( ret == true )
    {
        *start = i;
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond an escape sequence.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @return true if a valid escape sequence was present;
 * false otherwise.
 *
 * @note For the sake of security, \NUL is disallowed.
 */
static bool skipEscape( const char * buf,
                        size_t * start,
                        size_t max )
{
    bool ret = false;
    size_t i;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    i = *start;
    if( ( i < ( max - 1U ) ) && ( buf[ i ] == '\\' ) )
    {
        char c = buf[ i + 1U ];
        switch( c )
        {
            case '\0':
                break;
            case 'u':
                ret = skipHexEscape( buf, &i, max );
                break;
            case '"':
            case '\\':
            case '/':
            case 'b':
            case 'f':
            case 'n':
            case 'r':
            case 't':
                i += 2U;
                ret = true;
                break;
            default:
                /* a control character: (NUL,SPACE) */
                if( iscntrl_( c ) )
                {
                    i += 2U;
                    ret = true;
                }
                break;
        }
    }
    if( ret == true )
    {
        *start = i;
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond a double-quoted string.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @return true if a valid string was present;
 * false otherwise.
 */
static bool skipString( const char * buf,
                        size_t * start,
                        size_t max )
{
    bool ret = false;
    size_t i;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    i = *start;
    if( ( i < max ) && ( buf[ i ] == '"' ) )
    {
        i++;
        while( i < max )
        {
            if( buf[ i ] == '"' )
            {
                ret = true;
                i++;
                break;
            }
            if( buf[ i ] == '\\' )
            {
                if( skipEscape( buf, &i, max ) != true )
                {
                    break;
                }
            }
            /* An unescaped control character is not allowed. */
            else if( iscntrl_( buf[ i ] ) )
            {
                break;
            }
            else if( skipUTF8( buf, &i, max ) != true )
            {
                break;
            }
            else
            {
                /* MISRA 15.7 */
            }
        }
    }
    if( ret == true )
    {
        *start = i;
    }
    return ret;
}
/**
 * @brief Compare the leading n bytes of two character sequences.
 *
 * @param[in] a  first character sequence
 * @param[in] b  second character sequence
 * @param[in] n  number of bytes
 *
 * @return true if the sequences are the same;
 * false otherwise
 */
static bool strnEq( const char * a,
                    const char * b,
                    size_t n )
{
    size_t i;
    assert( ( a != NULL ) && ( b != NULL ) );
    for( i = 0; i < n; i++ )
    {
        if( a[ i ] != b[ i ] )
        {
            break;
        }
    }
    return ( i == n ) ? true : false;
}
/**
 * @brief Advance buffer index beyond a literal.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 * @param[in] literal  The type of literal.
 * @param[in] length  The length of the literal.
 *
 * @return true if the literal was present;
 * false otherwise.
 */
static bool skipLiteral( const char * buf,
                         size_t * start,
                         size_t max,
                         const char * literal,
                         size_t length )
{
    bool ret = false;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    assert( literal != NULL );
    if( ( *start < max ) && ( length <= ( max - *start ) ) )
    {
        ret = strnEq( &buf[ *start ], literal, length );
    }
    if( ret == true )
    {
        *start += length;
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond a JSON literal.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @return true if a valid literal was present;
 * false otherwise.
 */
static bool skipAnyLiteral( const char * buf,
                            size_t * start,
                            size_t max )
{
    bool ret = false;
#define skipLit_( x ) \
    ( skipLiteral( buf, start, max, ( x ), ( sizeof( x ) - 1UL ) ) == true )
    if( skipLit_( "true" ) || skipLit_( "false" ) || skipLit_( "null" ) )
    {
        ret = true;
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond one or more digits.
 * Optionally, output the integer value of the digits.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 * @param[out] outValue  The integer value of the digits.
 *
 * @note outValue may be NULL.  If not NULL, and the output
 * exceeds ~2 billion, then -1 is output.
 *
 * @return true if a digit was present;
 * false otherwise.
 */
#define MAX_FACTOR    ( MAX_INDEX_VALUE / 10 )
static bool skipDigits( const char * buf,
                        size_t * start,
                        size_t max,
                        int32_t * outValue )
{
    bool ret = false;
    size_t i, saveStart;
    int32_t value = 0;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    saveStart = *start;
    for( i = *start; i < max; i++ )
    {
        if( !isdigit_( buf[ i ] ) )
        {
            break;
        }
        if( ( outValue != NULL ) && ( value > -1 ) )
        {
            int8_t n = ( int8_t ) hexToInt( buf[ i ] );
            if( value <= MAX_FACTOR )
            {
                value = ( value * 10 ) + n;
            }
            else
            {
                value = -1;
            }
        }
    }
    if( i > saveStart )
    {
        ret = true;
        *start = i;
        if( outValue != NULL )
        {
            *outValue = value;
        }
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond the decimal portion of a number.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 */
static void skipDecimals( const char * buf,
                          size_t * start,
                          size_t max )
{
    size_t i;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    i = *start;
    if( ( i < max ) && ( buf[ i ] == '.' ) )
    {
        i++;
        if( skipDigits( buf, &i, max, NULL ) == true )
        {
            *start = i;
        }
    }
}
/**
 * @brief Advance buffer index beyond the exponent portion of a number.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 */
static void skipExponent( const char * buf,
                          size_t * start,
                          size_t max )
{
    size_t i;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    i = *start;
    if( ( i < max ) && ( ( buf[ i ] == 'e' ) || ( buf[ i ] == 'E' ) ) )
    {
        i++;
        if( ( i < max ) && ( ( buf[ i ] == '-' ) || ( buf[ i ] == '+' ) ) )
        {
            i++;
        }
        if( skipDigits( buf, &i, max, NULL ) == true )
        {
            *start = i;
        }
    }
}
/**
 * @brief Advance buffer index beyond a number.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @return true if a valid number was present;
 * false otherwise.
 */
static bool skipNumber( const char * buf,
                        size_t * start,
                        size_t max )
{
    bool ret = false;
    size_t i;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    i = *start;
    if( ( i < max ) && ( buf[ i ] == '-' ) )
    {
        i++;
    }
    if( i < max )
    {
        /* JSON disallows superfluous leading zeroes, so an
         * initial zero must either be alone, or followed by
         * a decimal or exponent.
         *
         * Should there be a digit after the zero, that digit
         * will not be skipped by this function, and later parsing
         * will judge this an illegal document. */
        if( buf[ i ] == '0' )
        {
            ret = true;
            i++;
        }
        else
        {
            ret = skipDigits( buf, &i, max, NULL );
        }
    }
    if( ret == true )
    {
        skipDecimals( buf, &i, max );
        skipExponent( buf, &i, max );
        *start = i;
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond a scalar value.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @return true if a scalar value was present;
 * false otherwise.
 */
static bool skipAnyScalar( const char * buf,
                           size_t * start,
                           size_t max )
{
    bool ret = false;
    if( ( skipString( buf, start, max ) == true ) ||
        ( skipAnyLiteral( buf, start, max ) == true ) ||
        ( skipNumber( buf, start, max ) == true ) )
    {
        ret = true;
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond a comma separator
 * and surrounding whitespace.
 *
 * JSON uses a comma to separate values in an array and key-value
 * pairs in an object.  JSON does not permit a trailing comma.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @return true if a non-terminal comma was present;
 * false otherwise.
 */
static bool skipSpaceAndComma( const char * buf,
                               size_t * start,
                               size_t max )
{
    bool ret = false;
    size_t i;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    skipSpace( buf, start, max );
    i = *start;
    if( ( i < max ) && ( buf[ i ] == ',' ) )
    {
        i++;
        skipSpace( buf, &i, max );
        if( ( i < max ) && !isCloseBracket_( buf[ i ] ) )
        {
            ret = true;
            *start = i;
        }
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond the scalar values of an array.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @note Stops advance if a value is an object or array.
 */
static void skipArrayScalars( const char * buf,
                              size_t * start,
                              size_t max )
{
    size_t i;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    i = *start;
    while( i < max )
    {
        if( skipAnyScalar( buf, &i, max ) != true )
        {
            break;
        }
        if( skipSpaceAndComma( buf, &i, max ) != true )
        {
            break;
        }
    }
    *start = i;
}
/**
 * @brief Advance buffer index beyond the scalar key-value pairs
 * of an object.
 *
 * In JSON, objects consist of comma-separated key-value pairs.
 * A key is always a string (a scalar) while a value may be a
 * scalar, an object, or an array.  A colon must appear between
 * each key and value.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @note Stops advance if a value is an object or array.
 */
static void skipObjectScalars( const char * buf,
                               size_t * start,
                               size_t max )
{
    size_t i;
    bool comma;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    i = *start;
    while( i < max )
    {
        if( skipString( buf, &i, max ) != true )
        {
            break;
        }
        skipSpace( buf, &i, max );
        if( ( i < max ) && ( buf[ i ] != ':' ) )
        {
            break;
        }
        i++;
        skipSpace( buf, &i, max );
        if( ( i < max ) && isOpenBracket_( buf[ i ] ) )
        {
            *start = i;
            break;
        }
        if( skipAnyScalar( buf, &i, max ) != true )
        {
            break;
        }
        comma = skipSpaceAndComma( buf, &i, max );
        *start = i;
        if( comma != true )
        {
            break;
        }
    }
}
/**
 * @brief Advance buffer index beyond one or more scalars.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 * @param[in] mode  The first character of an array '[' or object '{'.
 */
static void skipScalars( const char * buf,
                         size_t * start,
                         size_t max,
                         char mode )
{
    assert( isOpenBracket_( mode ) );
    skipSpace( buf, start, max );
    if( mode == '[' )
    {
        skipArrayScalars( buf, start, max );
    }
    else
    {
        skipObjectScalars( buf, start, max );
    }
}
/**
 * @brief Advance buffer index beyond a collection and handle nesting.
 *
 * A stack is used to continue parsing the prior collection type
 * when a nested collection is finished.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 *
 * @return #JSONSuccess if the buffer contents are a valid JSON collection;
 * #JSONIllegalDocument if the buffer contents are NOT valid JSON;
 * #JSONMaxDepthExceeded if object and array nesting exceeds a threshold;
 * #JSONPartial if the buffer contents are potentially valid but incomplete.
 */
#ifndef JSON_MAX_DEPTH
    #define JSON_MAX_DEPTH    32
#endif
static JSONStatus_t skipCollection( const char * buf,
                                    size_t * start,
                                    size_t max )
{
    JSONStatus_t ret = JSONPartial;
    char c, stack[ JSON_MAX_DEPTH ];
    int16_t depth = -1;
    size_t i;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    i = *start;
    while( i < max )
    {
        c = buf[ i ];
        i++;
        switch( c )
        {
            case '{':
            case '[':
                depth++;
                if( depth == JSON_MAX_DEPTH )
                {
                    ret = JSONMaxDepthExceeded;
                    break;
                }
                stack[ depth ] = c;
                skipScalars( buf, &i, max, stack[ depth ] );
                break;
            case '}':
            case ']':
                if( ( depth > 0 ) && isMatchingBracket_( stack[ depth ], c ) )
                {
                    depth--;
                    if( skipSpaceAndComma( buf, &i, max ) == true )
                    {
                        skipScalars( buf, &i, max, stack[ depth ] );
                    }
                    break;
                }
                ret = ( ( depth == 0 ) && isMatchingBracket_( stack[ depth ], c ) ) ?
                      JSONSuccess : JSONIllegalDocument;
                break;
            default:
                ret = JSONIllegalDocument;
                break;
        }
        if( ret != JSONPartial )
        {
            break;
        }
    }
    if( ret == JSONSuccess )
    {
        *start = i;
    }
    return ret;
}
/** @endcond */
/**
 * See core_json.h for docs.
 *
 * Verify that the entire buffer contains exactly one scalar
 * or collection within optional whitespace.
 */
JSONStatus_t JSON_Validate( const char * buf,
                            size_t max )
{
    JSONStatus_t ret;
    size_t i = 0;
    if( buf == NULL )
    {
        ret = JSONNullParameter;
    }
    else if( max == 0U )
    {
        ret = JSONBadParameter;
    }
    else
    {
        skipSpace( buf, &i, max );
        /** @cond DO_NOT_DOCUMENT */
        #ifndef JSON_VALIDATE_COLLECTIONS_ONLY
            if( skipAnyScalar( buf, &i, max ) == true )
            {
                ret = JSONSuccess;
            }
            else
        #endif
        /** @endcond */
        {
            ret = skipCollection( buf, &i, max );
        }
    }
    if( ( ret == JSONSuccess ) && ( i < max ) )
    {
        skipSpace( buf, &i, max );
        if( i != max )
        {
            ret = JSONIllegalDocument;
        }
    }
    return ret;
}
/** @cond DO_NOT_DOCUMENT */
/**
 * @brief Output index and length for the next value.
 *
 * Also advances the buffer index beyond the value.
 * The value may be a scalar or a collection.
 * The start index should point to the beginning of the value.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 * @param[out] value  A pointer to receive the index of the value.
 * @param[out] valueLength  A pointer to receive the length of the value.
 *
 * @return true if a value was present;
 * false otherwise.
 */
static bool nextValue( const char * buf,
                       size_t * start,
                       size_t max,
                       size_t * value,
                       size_t * valueLength )
{
    bool ret = true;
    size_t i, valueStart;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    assert( ( value != NULL ) && ( valueLength != NULL ) );
    i = *start;
    valueStart = i;
    if( ( skipAnyScalar( buf, &i, max ) == true ) ||
        ( skipCollection( buf, &i, max ) == JSONSuccess ) )
    {
        *value = valueStart;
        *valueLength = i - valueStart;
    }
    else
    {
        ret = false;
    }
    if( ret == true )
    {
        *start = i;
    }
    return ret;
}
/**
 * @brief Output indexes for the next key-value pair of an object.
 *
 * Also advances the buffer index beyond the key-value pair.
 * The value may be a scalar or a collection.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 * @param[out] key  A pointer to receive the index of the key.
 * @param[out] keyLength  A pointer to receive the length of the key.
 * @param[out] value  A pointer to receive the index of the value.
 * @param[out] valueLength  A pointer to receive the length of the value.
 *
 * @return true if a key-value pair was present;
 * false otherwise.
 */
static bool nextKeyValuePair( const char * buf,
                              size_t * start,
                              size_t max,
                              size_t * key,
                              size_t * keyLength,
                              size_t * value,
                              size_t * valueLength )
{
    bool ret = true;
    size_t i, keyStart;
    assert( ( buf != NULL ) && ( start != NULL ) && ( max > 0U ) );
    assert( ( key != NULL ) && ( keyLength != NULL ) );
    assert( ( value != NULL ) && ( valueLength != NULL ) );
    i = *start;
    keyStart = i;
    if( skipString( buf, &i, max ) == true )
    {
        *key = keyStart + 1U;
        *keyLength = i - keyStart - 2U;
    }
    else
    {
        ret = false;
    }
    if( ret == true )
    {
        skipSpace( buf, &i, max );
        if( ( i < max ) && ( buf[ i ] == ':' ) )
        {
            i++;
            skipSpace( buf, &i, max );
        }
        else
        {
            ret = false;
        }
    }
    if( ret == true )
    {
        ret = nextValue( buf, &i, max, value, valueLength );
    }
    if( ret == true )
    {
        *start = i;
    }
    return ret;
}
/**
 * @brief Find a key in a JSON object and output a pointer to its value.
 *
 * @param[in] buf  The buffer to search.
 * @param[in] max  size of the buffer.
 * @param[in] query  The object keys and array indexes to search for.
 * @param[in] queryLength  Length of the key.
 * @param[out] outValue  A pointer to receive the index of the value found.
 * @param[out] outValueLength  A pointer to receive the length of the value found.
 *
 * Iterate over the key-value pairs of an object, looking for a matching key.
 *
 * @return true if the query is matched and the value output;
 * false otherwise.
 *
 * @note Parsing stops upon finding a match.
 */
static bool objectSearch( const char * buf,
                          size_t max,
                          const char * query,
                          size_t queryLength,
                          size_t * outValue,
                          size_t * outValueLength )
{
    bool ret = false;
    size_t i = 0, key, keyLength, value = 0, valueLength = 0;
    assert( ( buf != NULL ) && ( query != NULL ) );
    assert( ( outValue != NULL ) && ( outValueLength != NULL ) );
    skipSpace( buf, &i, max );
    if( ( i < max ) && ( buf[ i ] == '{' ) )
    {
        i++;
        skipSpace( buf, &i, max );
        while( i < max )
        {
            if( nextKeyValuePair( buf, &i, max, &key, &keyLength,
                                  &value, &valueLength ) != true )
            {
                break;
            }
            if( ( queryLength == keyLength ) &&
                ( strnEq( query, &buf[ key ], keyLength ) == true ) )
            {
                ret = true;
                break;
            }
            if( skipSpaceAndComma( buf, &i, max ) != true )
            {
                break;
            }
        }
    }
    if( ret == true )
    {
        *outValue = value;
        *outValueLength = valueLength;
    }
    return ret;
}
/**
 * @brief Find an index in a JSON array and output a pointer to its value.
 *
 * @param[in] buf  The buffer to search.
 * @param[in] max  size of the buffer.
 * @param[in] queryIndex  The index to search for.
 * @param[out] outValue  A pointer to receive the index of the value found.
 * @param[out] outValueLength  A pointer to receive the length of the value found.
 *
 * Iterate over the values of an array, looking for a matching index.
 *
 * @return true if the queryIndex is found and the value output;
 * false otherwise.
 *
 * @note Parsing stops upon finding a match.
 */
static bool arraySearch( const char * buf,
                         size_t max,
                         uint32_t queryIndex,
                         size_t * outValue,
                         size_t * outValueLength )
{
    bool ret = false;
    size_t i = 0, value = 0, valueLength = 0;
    uint32_t currentIndex = 0;
    assert( buf != NULL );
    assert( ( outValue != NULL ) && ( outValueLength != NULL ) );
    skipSpace( buf, &i, max );
    if( ( i < max ) && ( buf[ i ] == '[' ) )
    {
        i++;
        skipSpace( buf, &i, max );
        while( i < max )
        {
            if( nextValue( buf, &i, max, &value, &valueLength ) != true )
            {
                break;
            }
            if( currentIndex == queryIndex )
            {
                ret = true;
                break;
            }
            if( skipSpaceAndComma( buf, &i, max ) != true )
            {
                break;
            }
            currentIndex++;
        }
    }
    if( ret == true )
    {
        *outValue = value;
        *outValueLength = valueLength;
    }
    return ret;
}
/**
 * @brief Advance buffer index beyond a query part.
 *
 * The part is the portion of the query which is not
 * a separator or array index.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in,out] start  The index at which to begin.
 * @param[in] max  The size of the buffer.
 * @param[out] outLength  The length of the query part.
 *
 * @return true if a valid string was present;
 * false otherwise.
 */
#ifndef JSON_QUERY_KEY_SEPARATOR
    #define JSON_QUERY_KEY_SEPARATOR    '.'
#endif
#define isSeparator_( x )    ( ( x ) == JSON_QUERY_KEY_SEPARATOR )
static bool skipQueryPart( const char * buf,
                           size_t * start,
                           size_t max,
                           size_t * outLength )
{
    bool ret = false;
    size_t i;
    assert( ( buf != NULL ) && ( start != NULL ) && ( outLength != NULL ) );
    assert( max > 0U );
    i = *start;
    while( ( i < max ) &&
           !isSeparator_( buf[ i ] ) &&
           !isSquareOpen_( buf[ i ] ) )
    {
        i++;
    }
    if( i > *start )
    {
        ret = true;
        *outLength = i - *start;
        *start = i;
    }
    return ret;
}
/**
 * @brief Handle a nested search by iterating over the parts of the query.
 *
 * @param[in] buf  The buffer to search.
 * @param[in] max  size of the buffer.
 * @param[in] query  The object keys and array indexes to search for.
 * @param[in] queryLength  Length of the key.
 * @param[out] outValue  A pointer to receive the index of the value found.
 * @param[out] outValueLength  A pointer to receive the length of the value found.
 *
 * @return #JSONSuccess if the query is matched and the value output;
 * #JSONBadParameter if the query is empty, or any part is empty,
 * or an index is too large to convert;
 * #JSONNotFound if the query is NOT found.
 *
 * @note Parsing stops upon finding a match.
 */
static JSONStatus_t multiSearch( const char * buf,
                                 size_t max,
                                 const char * query,
                                 size_t queryLength,
                                 size_t * outValue,
                                 size_t * outValueLength )
{
    JSONStatus_t ret = JSONSuccess;
    size_t i = 0, start = 0, queryStart = 0, value = 0, length = max;
    assert( ( buf != NULL ) && ( query != NULL ) );
    assert( ( outValue != NULL ) && ( outValueLength != NULL ) );
    assert( ( max > 0U ) && ( queryLength > 0U ) );
    while( i < queryLength )
    {
        bool found = false;
        if( isSquareOpen_( query[ i ] ) )
        {
            int32_t queryIndex = -1;
            i++;
            ( void ) skipDigits( query, &i, queryLength, &queryIndex );
            if( ( queryIndex < 0 ) ||
                ( i >= queryLength ) || !isSquareClose_( query[ i ] ) )
            {
                ret = JSONBadParameter;
                break;
            }
            i++;
            found = arraySearch( &buf[ start ], length, ( uint32_t ) queryIndex, &value, &length );
        }
        else
        {
            size_t keyLength = 0;
            queryStart = i;
            if( ( skipQueryPart( query, &i, queryLength, &keyLength ) != true ) ||
                /* catch an empty key part or a trailing separator */
                ( i == ( queryLength - 1U ) ) )
            {
                ret = JSONBadParameter;
                break;
            }
            found = objectSearch( &buf[ start ], length, &query[ queryStart ], keyLength, &value, &length );
        }
        if( found == false )
        {
            ret = JSONNotFound;
            break;
        }
        start += value;
        if( ( i < queryLength ) && isSeparator_( query[ i ] ) )
        {
            i++;
        }
    }
    if( ret == JSONSuccess )
    {
        *outValue = start;
        *outValueLength = length;
    }
    return ret;
}
/**
 * @brief Return a JSON type based on a separator character or
 * the first character of a value.
 *
 * @param[in] c  The character to classify.
 *
 * @return an enum of JSONTypes_t
 */
static JSONTypes_t getType( char c )
{
    JSONTypes_t t;
    switch( c )
    {
        case '"':
            t = JSONString;
            break;
        case '{':
            t = JSONObject;
            break;
        case '[':
            t = JSONArray;
            break;
        case 't':
            t = JSONTrue;
            break;
        case 'f':
            t = JSONFalse;
            break;
        case 'n':
            t = JSONNull;
            break;
        default:
            t = JSONNumber;
            break;
    }
    return t;
}
/** @endcond */
/**
 * See core_json.h for docs.
 */
JSONStatus_t JSON_SearchConst( const char * buf,
                               size_t max,
                               const char * query,
                               size_t queryLength,
                               const char ** outValue,
                               size_t * outValueLength,
                               JSONTypes_t * outType )
{
    JSONStatus_t ret;
    size_t value = 0U;
    if( ( buf == NULL ) || ( query == NULL ) ||
        ( outValue == NULL ) || ( outValueLength == NULL ) )
    {
        ret = JSONNullParameter;
    }
    else if( ( max == 0U ) || ( queryLength == 0U ) )
    {
        ret = JSONBadParameter;
    }
    else
    {
        ret = multiSearch( buf, max, query, queryLength, &value, outValueLength );
    }
    if( ret == JSONSuccess )
    {
        JSONTypes_t t = getType( buf[ value ] );
        if( t == JSONString )
        {
            /* strip the surrounding quotes */
            value++;
            *outValueLength -= 2U;
        }
        *outValue = &buf[ value ];
        if( outType != NULL )
        {
            *outType = t;
        }
    }
    return ret;
}
/**
 * See core_json.h for docs.
 */
JSONStatus_t JSON_SearchT( char * buf,
                           size_t max,
                           const char * query,
                           size_t queryLength,
                           char ** outValue,
                           size_t * outValueLength,
                           JSONTypes_t * outType )
{
    /* MISRA Ref 11.3.1 [Misaligned access] */
    /* More details at: https://github.com/FreeRTOS/coreJSON/blob/main/MISRA.md#rule-113 */
    /* coverity[misra_c_2012_rule_11_3_violation] */
    return JSON_SearchConst( ( const char * ) buf, max, query, queryLength,
                             ( const char ** ) outValue, outValueLength, outType );
}
/** @cond DO_NOT_DOCUMENT */
/**
 * @brief Output the next key-value pair or value from a collection.
 *
 * @param[in] buf  The buffer to search.
 * @param[in] max  size of the buffer.
 * @param[in] start  The index at which the collection begins.
 * @param[in,out] next  The index at which to seek the next value.
 * @param[out] outKey  A pointer to receive the index of the value found.
 * @param[out] outKeyLength  A pointer to receive the length of the value found.
 * @param[out] outValue  A pointer to receive the index of the value found.
 * @param[out] outValueLength  A pointer to receive the length of the value found.
 *
 * @return #JSONSuccess if a value is output;
 * #JSONIllegalDocument if the buffer does not begin with '[' or '{';
 * #JSONNotFound if there are no further values in the collection.
 */
static JSONStatus_t iterate( const char * buf,
                             size_t max,
                             size_t * start,
                             size_t * next,
                             size_t * outKey,
                             size_t * outKeyLength,
                             size_t * outValue,
                             size_t * outValueLength )
{
    JSONStatus_t ret = JSONNotFound;
    bool found = false;
    assert( ( buf != NULL ) && ( max > 0U ) );
    assert( ( start != NULL ) && ( next != NULL ) );
    assert( ( outKey != NULL ) && ( outKeyLength != NULL ) );
    assert( ( outValue != NULL ) && ( outValueLength != NULL ) );
    if( *start < max )
    {
        switch( buf[ *start ] )
        {
            case '[':
                found = nextValue( buf, next, max, outValue, outValueLength );
                if( found == true )
                {
                    *outKey = 0;
                    *outKeyLength = 0;
                }
                break;
            case '{':
                found = nextKeyValuePair( buf, next, max, outKey, outKeyLength,
                                          outValue, outValueLength );
                break;
            default:
                ret = JSONIllegalDocument;
                break;
        }
    }
    if( found == true )
    {
        ret = JSONSuccess;
        ( void ) skipSpaceAndComma( buf, next, max );
    }
    return ret;
}
/** @endcond */
/**
 * See core_json.h for docs.
 */
JSONStatus_t JSON_Iterate( const char * buf,
                           size_t max,
                           size_t * start,
                           size_t * next,
                           JSONPair_t * outPair )
{
    JSONStatus_t ret;
    size_t key, keyLength, value, valueLength;
    if( ( buf == NULL ) || ( start == NULL ) || ( next == NULL ) ||
        ( outPair == NULL ) )
    {
        ret = JSONNullParameter;
    }
    else if( ( max == 0U ) || ( *start >= max ) || ( *next > max ) )
    {
        ret = JSONBadParameter;
    }
    else
    {
        skipSpace( buf, start, max );
        if( *next <= *start )
        {
            *next = *start + 1U;
            skipSpace( buf, next, max );
        }
        ret = iterate( buf, max, start, next, &key, &keyLength,
                       &value, &valueLength );
    }
    if( ret == JSONSuccess )
    {
        JSONTypes_t t = getType( buf[ value ] );
        if( t == JSONString )
        {
            /* strip the surrounding quotes */
            value++;
            valueLength -= 2U;
        }
        outPair->key = ( key == 0U ) ? NULL : &buf[ key ];
        outPair->keyLength = keyLength;
        outPair->value = &buf[ value ];
        outPair->valueLength = valueLength;
        outPair->jsonType = t;
    }
    return ret;
}
project/coreMQTT/coreJSON/core_json.h
New file
@@ -0,0 +1,339 @@
/*
 * coreJSON v3.2.0
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * @file core_json.h
 * @brief Include this header file to use coreJSON in your application.
 */
#ifndef CORE_JSON_H_
#define CORE_JSON_H_
#include <stdbool.h>
#include <stddef.h>
/* *INDENT-OFF* */
#ifdef __cplusplus
    extern "C" {
#endif
/* *INDENT-ON* */
/**
 * @ingroup json_enum_types
 * @brief Return codes from coreJSON library functions.
 */
typedef enum
{
    JSONPartial = 0,      /**< @brief JSON document is valid so far but incomplete. */
    JSONSuccess,          /**< @brief JSON document is valid and complete. */
    JSONIllegalDocument,  /**< @brief JSON document is invalid or malformed. */
    JSONMaxDepthExceeded, /**< @brief JSON document has nesting that exceeds JSON_MAX_DEPTH. */
    JSONNotFound,         /**< @brief Query key could not be found in the JSON document. */
    JSONNullParameter,    /**< @brief Pointer parameter passed to a function is NULL. */
    JSONBadParameter      /**< @brief Query key is empty, or any subpart is empty, or max is 0. */
} JSONStatus_t;
/**
 * @brief Parse a buffer to determine if it contains a valid JSON document.
 *
 * @param[in] buf  The buffer to parse.
 * @param[in] max  The size of the buffer.
 *
 * @note The maximum nesting depth may be specified by defining the macro
 * JSON_MAX_DEPTH.  The default is 32 of sizeof(char).
 *
 * @note By default, a valid JSON document may contain a single element
 * (e.g., string, boolean, number).  To require that a valid document
 * contain an object or array, define JSON_VALIDATE_COLLECTIONS_ONLY.
 *
 * @return #JSONSuccess if the buffer contents are valid JSON;
 * #JSONNullParameter if buf is NULL;
 * #JSONBadParameter if max is 0;
 * #JSONIllegalDocument if the buffer contents are NOT valid JSON;
 * #JSONMaxDepthExceeded if object and array nesting exceeds a threshold;
 * #JSONPartial if the buffer contents are potentially valid but incomplete.
 *
 * <b>Example</b>
 * @code{c}
 *     // Variables used in this example.
 *     JSONStatus_t result;
 *     char buffer[] = "{\"foo\":\"abc\",\"bar\":{\"foo\":\"xyz\"}}";
 *     size_t bufferLength = sizeof( buffer ) - 1;
 *
 *     result = JSON_Validate( buffer, bufferLength );
 *
 *     // JSON document is valid.
 *     assert( result == JSONSuccess );
 * @endcode
 */
/* @[declare_json_validate] */
JSONStatus_t JSON_Validate( const char * buf,
                            size_t max );
/* @[declare_json_validate] */
/**
 * @brief Find a key or array index in a JSON document and output the
 * pointer @p outValue to its value.
 *
 * Any value may also be an object or an array to a maximum depth.  A search
 * may descend through nested objects or arrays when the query contains matching
 * key strings or array indexes joined by a separator.
 *
 * For example, if the provided buffer contains <code>{"foo":"abc","bar":{"foo":"xyz"}}</code>,
 * then a search for 'foo' would output <code>abc</code>, 'bar' would output
 * <code>{"foo":"xyz"}</code>, and a search for 'bar.foo' would output
 * <code>xyz</code>.
 *
 * If the provided buffer contains <code>[123,456,{"foo":"abc","bar":[88,99]}]</code>,
 * then a search for '[1]' would output <code>456</code>, '[2].foo' would output
 * <code>abc</code>, and '[2].bar[0]' would output <code>88</code>.
 *
 * On success, the pointer @p outValue points to a location in buf.  No null
 * termination is done for the value.  For valid JSON it is safe to place
 * a null character at the end of the value, so long as the character
 * replaced is put back before running another search.
 *
 * @param[in] buf  The buffer to search.
 * @param[in] max  size of the buffer.
 * @param[in] query  The object keys and array indexes to search for.
 * @param[in] queryLength  Length of the key.
 * @param[out] outValue  A pointer to receive the address of the value found.
 * @param[out] outValueLength  A pointer to receive the length of the value found.
 *
 * @note The maximum nesting depth may be specified by defining the macro
 * JSON_MAX_DEPTH.  The default is 32 of sizeof(char).
 *
 * @note JSON_Search() performs validation, but stops upon finding a matching
 * key and its value. To validate the entire JSON document, use JSON_Validate().
 *
 * @return #JSONSuccess if the query is matched and the value output;
 * #JSONNullParameter if any pointer parameters are NULL;
 * #JSONBadParameter if the query is empty, or the portion after a separator is empty,
 * or max is 0, or an index is too large to convert to a signed 32-bit integer;
 * #JSONNotFound if the query has no match.
 *
 * <b>Example</b>
 * @code{c}
 *     // Variables used in this example.
 *     JSONStatus_t result;
 *     char buffer[] = "{\"foo\":\"abc\",\"bar\":{\"foo\":\"xyz\"}}";
 *     size_t bufferLength = sizeof( buffer ) - 1;
 *     char query[] = "bar.foo";
 *     size_t queryLength = sizeof( query ) - 1;
 *     char * value;
 *     size_t valueLength;
 *
 *     // Calling JSON_Validate() is not necessary if the document is guaranteed to be valid.
 *     result = JSON_Validate( buffer, bufferLength );
 *
 *     if( result == JSONSuccess )
 *     {
 *         result = JSON_Search( buffer, bufferLength, query, queryLength,
 *                               &value, &valueLength );
 *     }
 *
 *     if( result == JSONSuccess )
 *     {
 *         // The pointer "value" will point to a location in the "buffer".
 *         char save = value[ valueLength ];
 *         // After saving the character, set it to a null byte for printing.
 *         value[ valueLength ] = '\0';
 *         // "Found: bar.foo -> xyz" will be printed.
 *         printf( "Found: %s -> %s\n", query, value );
 *         // Restore the original character.
 *         value[ valueLength ] = save;
 *     }
 * @endcode
 *
 * @note The maximum index value is ~2 billion ( 2^31 - 9 ).
 */
/* @[declare_json_search] */
#define JSON_Search( buf, max, query, queryLength, outValue, outValueLength ) \
    JSON_SearchT( buf, max, query, queryLength, outValue, outValueLength, NULL )
/* @[declare_json_search] */
/**
 * @brief The largest value usable as an array index in a query
 * for JSON_Search(), ~2 billion.
 */
#define MAX_INDEX_VALUE    ( 0x7FFFFFF7 )   /* 2^31 - 9 */
/**
 * @ingroup json_enum_types
 * @brief Value types from the JSON standard.
 */
typedef enum
{
    JSONInvalid = 0, /**< @brief Not a valid JSON type. */
    JSONString,      /**< @brief A quote delimited sequence of Unicode characters. */
    JSONNumber,      /**< @brief A rational number. */
    JSONTrue,        /**< @brief The literal value true. */
    JSONFalse,       /**< @brief The literal value false. */
    JSONNull,        /**< @brief The literal value null. */
    JSONObject,      /**< @brief A collection of zero or more key-value pairs. */
    JSONArray        /**< @brief A collection of zero or more values. */
} JSONTypes_t;
/**
 * @brief Same as JSON_Search(), but also outputs a type for the value found
 *
 * See @ref JSON_Search for documentation of common behavior.
 *
 * @param[in] buf  The buffer to search.
 * @param[in] max  size of the buffer.
 * @param[in] query  The object keys and array indexes to search for.
 * @param[in] queryLength  Length of the key.
 * @param[out] outValue  A pointer to receive the address of the value found.
 * @param[out] outValueLength  A pointer to receive the length of the value found.
 * @param[out] outType  An enum indicating the JSON-specific type of the value.
 */
/* @[declare_json_searcht] */
JSONStatus_t JSON_SearchT( char * buf,
                           size_t max,
                           const char * query,
                           size_t queryLength,
                           char ** outValue,
                           size_t * outValueLength,
                           JSONTypes_t * outType );
/* @[declare_json_searcht] */
/**
 * @brief Same as JSON_SearchT(), but with const qualified buf and outValue arguments.
 *
 * See @ref JSON_Search for documentation of common behavior.
 *
 * @param[in] buf  The buffer to search.
 * @param[in] max  size of the buffer.
 * @param[in] query  The object keys and array indexes to search for.
 * @param[in] queryLength  Length of the key.
 * @param[out] outValue  A pointer to receive the address of the value found.
 * @param[out] outValueLength  A pointer to receive the length of the value found.
 * @param[out] outType  An enum indicating the JSON-specific type of the value.
 */
/* @[declare_json_searchconst] */
JSONStatus_t JSON_SearchConst( const char * buf,
                               size_t max,
                               const char * query,
                               size_t queryLength,
                               const char ** outValue,
                               size_t * outValueLength,
                               JSONTypes_t * outType );
/* @[declare_json_searchconst] */
/**
 * @ingroup json_struct_types
 * @brief Structure to represent a key-value pair.
 */
typedef struct
{
    const char * key;     /**< @brief Pointer to the code point sequence for key. */
    size_t keyLength;     /**< @brief Length of the code point sequence for key. */
    const char * value;   /**< @brief Pointer to the code point sequence for value. */
    size_t valueLength;   /**< @brief Length of the code point sequence for value. */
    JSONTypes_t jsonType; /**< @brief JSON-specific type of the value. */
} JSONPair_t;
/**
 * @brief Output the next key-value pair or value from a collection.
 *
 * This function may be used in a loop to output each key-value pair from an object,
 * or each value from an array.  For the first invocation, the integers pointed to by
 * start and next should be initialized to 0.  These will be updated by the function.
 * If another key-value pair or value is present, the output structure is populated
 * and #JSONSuccess is returned; otherwise the structure is unchanged and #JSONNotFound
 * is returned.
 *
 * @param[in] buf  The buffer to search.
 * @param[in] max  size of the buffer.
 * @param[in,out] start  The index at which the collection begins.
 * @param[in,out] next  The index at which to seek the next value.
 * @param[out] outPair  A pointer to receive the next key-value pair.
 *
 * @note This function expects a valid JSON document; run JSON_Validate() first.
 *
 * @note For an object, the outPair structure will reference a key and its value.
 * For an array, only the value will be referenced (i.e., outPair.key will be NULL).
 *
 * @return #JSONSuccess if a value is output;
 * #JSONIllegalDocument if the buffer does not contain a collection;
 * #JSONNotFound if there are no further values in the collection.
 *
 * <b>Example</b>
 * @code{c}
 *     // Variables used in this example.
 *     static char * json_types[] =
 *     {
 *         "invalid",
 *         "string",
 *         "number",
 *         "true",
 *         "false",
 *         "null",
 *         "object",
 *         "array"
 *     };
 *
 *     void show( const char * json,
 *                size_t length )
 *     {
 *         size_t start = 0, next = 0;
 *         JSONPair_t pair = { 0 };
 *         JSONStatus_t result;
 *
 *         result = JSON_Validate( json, length );
 *         if( result == JSONSuccess )
 *         {
 *             result = JSON_Iterate( json, length, &start, &next, &pair );
 *         }
 *
 *         while( result == JSONSuccess )
 *         {
 *             if( pair.key != NULL )
 *             {
 *                 printf( "key: %.*s\t", ( int ) pair.keyLength, pair.key );
 *             }
 *
 *             printf( "value: (%s) %.*s\n", json_types[ pair.jsonType ],
 *                     ( int ) pair.valueLength, pair.value );
 *
 *             result = JSON_Iterate( json, length, &start, &next, &pair );
 *         }
 *     }
 * @endcode
 */
/* @[declare_json_iterate] */
JSONStatus_t JSON_Iterate( const char * buf,
                           size_t max,
                           size_t * start,
                           size_t * next,
                           JSONPair_t * outPair );
/* @[declare_json_iterate] */
/* *INDENT-OFF* */
#ifdef __cplusplus
    }
#endif
/* *INDENT-ON* */
#endif /* ifndef CORE_JSON_H_ */
project/coreMQTT/coreJSON/makefile
New file
@@ -0,0 +1,35 @@
#********************************************************************************
#      Copyright:  (C) 2023 LingYun IoT System Studio
#                  All rights reserved.
#
#       Filename:  Makefile
#    Description:  This file used compile all the source code to static library
#
#        Version:  1.0.0(11/08/23)
#         Author:  Guo Wenxue <guowenxue@gmail.com>
#      ChangeLog:  1, Release initial version on "11/08/23 16:18:43"
#
#*******************************************************************************
PWD=$(shell pwd )
BUILD_ARCH=$(shell uname -m)
ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),)
    CROSS_COMPILE?=arm-linux-gnueabihf-
endif
LIBNAME=$(shell basename ${PWD} )
TOPDIR=$(shell dirname ${PWD} )
CFLAGS+=-D_GNU_SOURCE
all: clean
    @rm -f *.o
    @${CROSS_COMPILE}gcc ${CFLAGS} -I${TOPDIR} -c *.c
    ${CROSS_COMPILE}ar -rcs  lib${LIBNAME}.a *.o
clean:
    @rm -f *.o
    @rm -f *.a
distclean:
    @make clean
project/coreMQTT/coreMQTT/README.md
New file
@@ -0,0 +1,35 @@
## coreMQTT Client Library
This repository contains the coreMQTT library that has been optimized for a low memory footprint.  The coreMQTT library is compliant with the [MQTT 3.1.1](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html) standard.  It has no dependencies on any additional libraries other than the standard C library, a customer-implemented network transport interface, and *optionally* a user-implemented platform time function.  This library is distributed under the [MIT Open Source License](LICENSE).
This library has gone through code quality checks including verification that no function has a [GNU Complexity](https://www.gnu.org/software/complexity/manual/complexity.html) score over 8, and checks against deviations from mandatory rules in the [MISRA coding standard](https://www.misra.org.uk).  Deviations from the MISRA C:2012 guidelines are documented under [MISRA Deviations](MISRA.md). This library has also undergone both static code analysis from [Coverity static analysis](https://scan.coverity.com/), and validation of memory safety through the [CBMC automated reasoning tool](https://www.cprover.org/cbmc/).
See memory requirements for this library [here](./docs/doxygen/include/size_table.md).
**coreMQTT v2.1.1 [source code](https://github.com/FreeRTOS/coreMQTT/tree/v2.1.1/source) is part of the [FreeRTOS 202210.01 LTS](https://github.com/FreeRTOS/FreeRTOS-LTS/tree/202210.01-LTS) release.**
## MQTT Config File
The MQTT client library exposes build configuration macros that are required for building the library.
A list of all the configurations and their default values are defined in [core_mqtt_config_defaults.h](source/include/core_mqtt_config_defaults.h).
To provide custom values for the configuration macros, a custom config file named `core_mqtt_config.h` can be
provided by the application to the library.
By default, a `core_mqtt_config.h` custom config is required to build the library. To disable this requirement
and build the library with default configuration values, provide `MQTT_DO_NOT_USE_CUSTOM_CONFIG` as a compile time preprocessor macro.
**Thus, the MQTT library can be built by either**:
* Defining a `core_mqtt_config.h` file in the application, and adding it to the include directories list of the library
**OR**
* Defining the `MQTT_DO_NOT_USE_CUSTOM_CONFIG` preprocessor macro for the library build.
## Documentation
For pre-generated documentation, please see the documentation linked in the locations below:
| Location |
| :-: |
| [FreeRTOS.org](https://freertos.org/Documentation/api-ref/coreMQTT/docs/doxygen/output/html/index.html) |
Note that the latest included version of coreMQTT may differ across repositories.
project/coreMQTT/coreMQTT/core_mqtt.c
New file
@@ -0,0 +1,3314 @@
/*
 * coreMQTT v2.1.1
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * @file core_mqtt.c
 * @brief Implements the user-facing functions in core_mqtt.h.
 */
#include <string.h>
#include <assert.h>
#include "core_mqtt.h"
#include "core_mqtt_state.h"
/* Include config defaults header to get default values of configs. */
#include "core_mqtt_config_defaults.h"
#include "core_mqtt_default_logging.h"
#ifndef MQTT_PRE_SEND_HOOK
/**
 * @brief Hook called before a 'send' operation is executed.
 */
    #define MQTT_PRE_SEND_HOOK( pContext )
#endif /* !MQTT_PRE_SEND_HOOK */
#ifndef MQTT_POST_SEND_HOOK
/**
 * @brief Hook called after the 'send' operation is complete.
 */
    #define MQTT_POST_SEND_HOOK( pContext )
#endif /* !MQTT_POST_SEND_HOOK */
#ifndef MQTT_PRE_STATE_UPDATE_HOOK
/**
 * @brief Hook called just before an update to the MQTT state is made.
 */
    #define MQTT_PRE_STATE_UPDATE_HOOK( pContext )
#endif /* !MQTT_PRE_STATE_UPDATE_HOOK */
#ifndef MQTT_POST_STATE_UPDATE_HOOK
/**
 * @brief Hook called just after an update to the MQTT state has
 * been made.
 */
    #define MQTT_POST_STATE_UPDATE_HOOK( pContext )
#endif /* !MQTT_POST_STATE_UPDATE_HOOK */
/*-----------------------------------------------------------*/
/**
 * @brief Sends provided buffer to network using transport send.
 *
 * @brief param[in] pContext Initialized MQTT context.
 * @brief param[in] pBufferToSend Buffer to be sent to network.
 * @brief param[in] bytesToSend Number of bytes to be sent.
 *
 * @note This operation may call the transport send function
 * repeatedly to send bytes over the network until either:
 * 1. The requested number of bytes @a bytesToSend have been sent.
 *                    OR
 * 2. MQTT_SEND_TIMEOUT_MS milliseconds have gone by since entering this
 * function.
 *                    OR
 * 3. There is an error in sending data over the network.
 *
 * @return Total number of bytes sent, or negative value on network error.
 */
static int32_t sendBuffer( MQTTContext_t * pContext,
                           const uint8_t * pBufferToSend,
                           size_t bytesToSend );
/**
 * @brief Sends MQTT connect without copying the users data into any buffer.
 *
 * @brief param[in] pContext Initialized MQTT context.
 * @brief param[in] pConnectInfo MQTT CONNECT packet information.
 * @brief param[in] pWillInfo Last Will and Testament. Pass NULL if Last Will and
 * Testament is not used.
 * @brief param[in] remainingLength the length of the connect packet.
 *
 * @note This operation may call the transport send function
 * repeatedly to send bytes over the network until either:
 * 1. The requested number of bytes @a remainingLength have been sent.
 *                    OR
 * 2. MQTT_SEND_TIMEOUT_MS milliseconds have gone by since entering this
 * function.
 *                    OR
 * 3. There is an error in sending data over the network.
 *
 * @return #MQTTSendFailed or #MQTTSuccess.
 */
static MQTTStatus_t sendConnectWithoutCopy( MQTTContext_t * pContext,
                                            const MQTTConnectInfo_t * pConnectInfo,
                                            const MQTTPublishInfo_t * pWillInfo,
                                            size_t remainingLength );
/**
 * @brief Sends the vector array passed through the parameters over the network.
 *
 * @note The preference is given to 'writev' function if it is present in the
 * transport interface. Otherwise, a send call is made repeatedly to achieve the
 * result.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] pIoVec The vector array to be sent.
 * @param[in] ioVecCount The number of elements in the array.
 *
 * @note This operation may call the transport send or writev functions
 * repeatedly to send bytes over the network until either:
 * 1. The requested number of bytes have been sent.
 *                    OR
 * 2. MQTT_SEND_TIMEOUT_MS milliseconds have gone by since entering this
 * function.
 *                    OR
 * 3. There is an error in sending data over the network.
 *
 * @return The total number of bytes sent or the error code as received from the
 * transport interface.
 */
static int32_t sendMessageVector( MQTTContext_t * pContext,
                                  TransportOutVector_t * pIoVec,
                                  size_t ioVecCount );
/**
 * @brief Add a string and its length after serializing it in a manner outlined by
 * the MQTT specification.
 *
 * @param[in] serailizedLength Array of two bytes to which the vector will point.
 * The array must remain in scope until the message has been sent.
 * @param[in] string The string to be serialized.
 * @param[in] length The length of the string to be serialized.
 * @param[in] iterator The iterator pointing to the first element in the
 * transport interface IO array.
 * @param[out] updatedLength This parameter will be added to with the number of
 * bytes added to the vector.
 *
 * @return The number of vectors added.
 */
static size_t addEncodedStringToVector( uint8_t serailizedLength[ 2 ],
                                        const char * const string,
                                        uint16_t length,
                                        TransportOutVector_t * iterator,
                                        size_t * updatedLength );
/**
 * @brief Send MQTT SUBSCRIBE message without copying the user data into a buffer and
 * directly sending it.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] pSubscriptionList List of MQTT subscription info.
 * @param[in] subscriptionCount The count of elements in the list.
 * @param[in] packetId The packet ID of the subscribe packet
 * @param[in] remainingLength The remaining length of the subscribe packet.
 *
 * @return #MQTTSuccess or #MQTTSendFailed.
 */
static MQTTStatus_t sendSubscribeWithoutCopy( MQTTContext_t * pContext,
                                              const MQTTSubscribeInfo_t * pSubscriptionList,
                                              size_t subscriptionCount,
                                              uint16_t packetId,
                                              size_t remainingLength );
/**
 * @brief Send MQTT UNSUBSCRIBE message without copying the user data into a buffer and
 * directly sending it.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] pSubscriptionList MQTT subscription info.
 * @param[in] subscriptionCount The count of elements in the list.
 * @param[in] packetId The packet ID of the unsubscribe packet.
 * @param[in] remainingLength The remaining length of the unsubscribe packet.
 *
 * @return #MQTTSuccess or #MQTTSendFailed.
 */
static MQTTStatus_t sendUnsubscribeWithoutCopy( MQTTContext_t * pContext,
                                                const MQTTSubscribeInfo_t * pSubscriptionList,
                                                size_t subscriptionCount,
                                                uint16_t packetId,
                                                size_t remainingLength );
/**
 * @brief Calculate the interval between two millisecond timestamps, including
 * when the later value has overflowed.
 *
 * @note In C, the operands are promoted to signed integers in subtraction.
 * Using this function avoids the need to cast the result of subtractions back
 * to uint32_t.
 *
 * @param[in] later The later time stamp, in milliseconds.
 * @param[in] start The earlier time stamp, in milliseconds.
 *
 * @return later - start.
 */
static uint32_t calculateElapsedTime( uint32_t later,
                                      uint32_t start );
/**
 * @brief Convert a byte indicating a publish ack type to an #MQTTPubAckType_t.
 *
 * @param[in] packetType First byte of fixed header.
 *
 * @return Type of ack.
 */
static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType );
/**
 * @brief Receive bytes into the network buffer.
 *
 * @param[in] pContext Initialized MQTT Context.
 * @param[in] bytesToRecv Number of bytes to receive.
 *
 * @note This operation calls the transport receive function
 * repeatedly to read bytes from the network until either:
 * 1. The requested number of bytes @a bytesToRecv are read.
 *                    OR
 * 2. No data is received from the network for MQTT_RECV_POLLING_TIMEOUT_MS duration.
 *
 *                    OR
 * 3. There is an error in reading from the network.
 *
 *
 * @return Number of bytes received, or negative number on network error.
 */
static int32_t recvExact( const MQTTContext_t * pContext,
                          size_t bytesToRecv );
/**
 * @brief Discard a packet from the transport interface.
 *
 * @param[in] pContext MQTT Connection context.
 * @param[in] remainingLength Remaining length of the packet to dump.
 * @param[in] timeoutMs Time remaining to discard the packet.
 *
 * @return #MQTTRecvFailed or #MQTTNoDataAvailable.
 */
static MQTTStatus_t discardPacket( const MQTTContext_t * pContext,
                                   size_t remainingLength,
                                   uint32_t timeoutMs );
/**
 * @brief Discard a packet from the MQTT buffer and the transport interface.
 *
 * @param[in] pContext MQTT Connection context.
 * @param[in] pPacketInfo Information struct of the packet to be discarded.
 *
 * @return #MQTTRecvFailed or #MQTTNoDataAvailable.
 */
static MQTTStatus_t discardStoredPacket( MQTTContext_t * pContext,
                                         const MQTTPacketInfo_t * pPacketInfo );
/**
 * @brief Receive a packet from the transport interface.
 *
 * @param[in] pContext MQTT Connection context.
 * @param[in] incomingPacket packet struct with remaining length.
 * @param[in] remainingTimeMs Time remaining to receive the packet.
 *
 * @return #MQTTSuccess or #MQTTRecvFailed.
 */
static MQTTStatus_t receivePacket( const MQTTContext_t * pContext,
                                   MQTTPacketInfo_t incomingPacket,
                                   uint32_t remainingTimeMs );
/**
 * @brief Get the correct ack type to send.
 *
 * @param[in] state Current state of publish.
 *
 * @return Packet Type byte of PUBACK, PUBREC, PUBREL, or PUBCOMP if one of
 * those should be sent, else 0.
 */
static uint8_t getAckTypeToSend( MQTTPublishState_t state );
/**
 * @brief Send acks for received QoS 1/2 publishes.
 *
 * @param[in] pContext MQTT Connection context.
 * @param[in] packetId packet ID of original PUBLISH.
 * @param[in] publishState Current publish state in record.
 *
 * @return #MQTTSuccess, #MQTTIllegalState or #MQTTSendFailed.
 */
static MQTTStatus_t sendPublishAcks( MQTTContext_t * pContext,
                                     uint16_t packetId,
                                     MQTTPublishState_t publishState );
/**
 * @brief Send a keep alive PINGREQ if the keep alive interval has elapsed.
 *
 * @param[in] pContext Initialized MQTT Context.
 *
 * @return #MQTTKeepAliveTimeout if a PINGRESP is not received in time,
 * #MQTTSendFailed if the PINGREQ cannot be sent, or #MQTTSuccess.
 */
static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext );
/**
 * @brief Handle received MQTT PUBLISH packet.
 *
 * @param[in] pContext MQTT Connection context.
 * @param[in] pIncomingPacket Incoming packet.
 *
 * @return MQTTSuccess, MQTTIllegalState or deserialization error.
 */
static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext,
                                           MQTTPacketInfo_t * pIncomingPacket );
/**
 * @brief Handle received MQTT publish acks.
 *
 * @param[in] pContext MQTT Connection context.
 * @param[in] pIncomingPacket Incoming packet.
 *
 * @return MQTTSuccess, MQTTIllegalState, or deserialization error.
 */
static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext,
                                       MQTTPacketInfo_t * pIncomingPacket );
/**
 * @brief Handle received MQTT ack.
 *
 * @param[in] pContext MQTT Connection context.
 * @param[in] pIncomingPacket Incoming packet.
 * @param[in] manageKeepAlive Flag indicating if PINGRESPs should not be given
 * to the application
 *
 * @return MQTTSuccess, MQTTIllegalState, or deserialization error.
 */
static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext,
                                       MQTTPacketInfo_t * pIncomingPacket,
                                       bool manageKeepAlive );
/**
 * @brief Run a single iteration of the receive loop.
 *
 * @param[in] pContext MQTT Connection context.
 * @param[in] manageKeepAlive Flag indicating if keep alive should be handled.
 *
 * @return #MQTTRecvFailed if a network error occurs during reception;
 * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ;
 * #MQTTBadResponse if an invalid packet is received;
 * #MQTTKeepAliveTimeout if the server has not sent a PINGRESP before
 * #MQTT_PINGRESP_TIMEOUT_MS milliseconds;
 * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an
 * invalid transition for the internal state machine;
 * #MQTTSuccess on success.
 */
static MQTTStatus_t receiveSingleIteration( MQTTContext_t * pContext,
                                            bool manageKeepAlive );
/**
 * @brief Validates parameters of #MQTT_Subscribe or #MQTT_Unsubscribe.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] pSubscriptionList List of MQTT subscription info.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[in] packetId Packet identifier.
 *
 * @return #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 */
static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * pContext,
                                                        const MQTTSubscribeInfo_t * pSubscriptionList,
                                                        size_t subscriptionCount,
                                                        uint16_t packetId );
/**
 * @brief Receives a CONNACK MQTT packet.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] timeoutMs Timeout for waiting for CONNACK packet.
 * @param[in] cleanSession Clean session flag set by application.
 * @param[out] pIncomingPacket List of MQTT subscription info.
 * @param[out] pSessionPresent Whether a previous session was present.
 * Only relevant if not establishing a clean session.
 *
 * @return #MQTTBadResponse if a bad response is received;
 * #MQTTNoDataAvailable if no data available for transport recv;
 * ##MQTTRecvFailed if transport recv failed;
 * #MQTTSuccess otherwise.
 */
static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext,
                                    uint32_t timeoutMs,
                                    bool cleanSession,
                                    MQTTPacketInfo_t * pIncomingPacket,
                                    bool * pSessionPresent );
/**
 * @brief Resends pending acks for a re-established MQTT session, or
 * clears existing state records for a clean session.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] sessionPresent Session present flag received from the MQTT broker.
 *
 * @return #MQTTSendFailed if transport send during resend failed;
 * #MQTTSuccess otherwise.
 */
static MQTTStatus_t handleSessionResumption( MQTTContext_t * pContext,
                                             bool sessionPresent );
/**
 * @brief Send the publish packet without copying the topic string and payload in
 * the buffer.
 *
 * @brief param[in] pContext Initialized MQTT context.
 * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters.
 * @brief param[in] pMqttHeader the serialized MQTT header with the header byte;
 * the encoded length of the packet; and the encoded length of the topic string.
 * @brief param[in] headerSize Size of the serialized PUBLISH header.
 * @brief param[in] packetId Packet Id of the publish packet.
 *
 * @return #MQTTSendFailed if transport send during resend failed;
 * #MQTTSuccess otherwise.
 */
static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext,
                                            const MQTTPublishInfo_t * pPublishInfo,
                                            const uint8_t * pMqttHeader,
                                            size_t headerSize,
                                            uint16_t packetId );
/**
 * @brief Function to validate #MQTT_Publish parameters.
 *
 * @brief param[in] pContext Initialized MQTT context.
 * @brief param[in] pPublishInfo MQTT PUBLISH packet parameters.
 * @brief param[in] packetId Packet Id for the MQTT PUBLISH packet.
 *
 * @return #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 */
static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext,
                                           const MQTTPublishInfo_t * pPublishInfo,
                                           uint16_t packetId );
/**
 * @brief Performs matching for special cases when a topic filter ends
 * with a wildcard character.
 *
 * When the topic name has been consumed but there are remaining characters to
 * to match in topic filter, this function handles the following 2 cases:
 * - When the topic filter ends with "/+" or "/#" characters, but the topic
 * name only ends with '/'.
 * - When the topic filter ends with "/#" characters, but the topic name
 * ends at the parent level.
 *
 * @note This function ASSUMES that the topic name been consumed in linear
 * matching with the topic filer, but the topic filter has remaining characters
 * to be matched.
 *
 * @param[in] pTopicFilter The topic filter containing the wildcard.
 * @param[in] topicFilterLength Length of the topic filter being examined.
 * @param[in] filterIndex Index of the topic filter being examined.
 *
 * @return Returns whether the topic filter and the topic name match.
 */
static bool matchEndWildcardsSpecialCases( const char * pTopicFilter,
                                           uint16_t topicFilterLength,
                                           uint16_t filterIndex );
/**
 * @brief Attempt to match topic name with a topic filter starting with a wildcard.
 *
 * If the topic filter starts with a '+' (single-level) wildcard, the function
 * advances the @a pNameIndex by a level in the topic name.
 * If the topic filter starts with a '#' (multi-level) wildcard, the function
 * concludes that both the topic name and topic filter match.
 *
 * @param[in] pTopicName The topic name to match.
 * @param[in] topicNameLength Length of the topic name.
 * @param[in] pTopicFilter The topic filter to match.
 * @param[in] topicFilterLength Length of the topic filter.
 * @param[in,out] pNameIndex Current index in the topic name being examined. It is
 * advanced by one level for `+` wildcards.
 * @param[in, out] pFilterIndex Current index in the topic filter being examined.
 * It is advanced to position of '/' level separator for '+' wildcard.
 * @param[out] pMatch Whether the topic filter and topic name match.
 *
 * @return `true` if the caller of this function should exit; `false` if the
 * caller should continue parsing the topics.
 */
static bool matchWildcards( const char * pTopicName,
                            uint16_t topicNameLength,
                            const char * pTopicFilter,
                            uint16_t topicFilterLength,
                            uint16_t * pNameIndex,
                            uint16_t * pFilterIndex,
                            bool * pMatch );
/**
 * @brief Match a topic name and topic filter allowing the use of wildcards.
 *
 * @param[in] pTopicName The topic name to check.
 * @param[in] topicNameLength Length of the topic name.
 * @param[in] pTopicFilter The topic filter to check.
 * @param[in] topicFilterLength Length of topic filter.
 *
 * @return `true` if the topic name and topic filter match; `false` otherwise.
 */
static bool matchTopicFilter( const char * pTopicName,
                              uint16_t topicNameLength,
                              const char * pTopicFilter,
                              uint16_t topicFilterLength );
/*-----------------------------------------------------------*/
static bool matchEndWildcardsSpecialCases( const char * pTopicFilter,
                                           uint16_t topicFilterLength,
                                           uint16_t filterIndex )
{
    bool matchFound = false;
    assert( pTopicFilter != NULL );
    assert( topicFilterLength != 0U );
    /* Check if the topic filter has 2 remaining characters and it ends in
     * "/#". This check handles the case to match filter "sport/#" with topic
     * "sport". The reason is that the '#' wildcard represents the parent and
     * any number of child levels in the topic name.*/
    if( ( topicFilterLength >= 3U ) &&
        ( filterIndex == ( topicFilterLength - 3U ) ) &&
        ( pTopicFilter[ filterIndex + 1U ] == '/' ) &&
        ( pTopicFilter[ filterIndex + 2U ] == '#' ) )
    {
        matchFound = true;
    }
    /* Check if the next character is "#" or "+" and the topic filter ends in
     * "/#" or "/+". This check handles the cases to match:
     *
     * - Topic filter "sport/+" with topic "sport/".
     * - Topic filter "sport/#" with topic "sport/".
     */
    if( ( filterIndex == ( topicFilterLength - 2U ) ) &&
        ( pTopicFilter[ filterIndex ] == '/' ) )
    {
        /* Check that the last character is a wildcard. */
        matchFound = ( pTopicFilter[ filterIndex + 1U ] == '+' ) ||
                     ( pTopicFilter[ filterIndex + 1U ] == '#' );
    }
    return matchFound;
}
/*-----------------------------------------------------------*/
static bool matchWildcards( const char * pTopicName,
                            uint16_t topicNameLength,
                            const char * pTopicFilter,
                            uint16_t topicFilterLength,
                            uint16_t * pNameIndex,
                            uint16_t * pFilterIndex,
                            bool * pMatch )
{
    bool shouldStopMatching = false;
    bool locationIsValidForWildcard;
    assert( pTopicName != NULL );
    assert( topicNameLength != 0U );
    assert( pTopicFilter != NULL );
    assert( topicFilterLength != 0U );
    assert( pNameIndex != NULL );
    assert( pFilterIndex != NULL );
    assert( pMatch != NULL );
    /* Wild card in a topic filter is only valid either at the starting position
     * or when it is preceded by a '/'.*/
    locationIsValidForWildcard = ( *pFilterIndex == 0u ) ||
                                 ( pTopicFilter[ *pFilterIndex - 1U ] == '/' );
    if( ( pTopicFilter[ *pFilterIndex ] == '+' ) && ( locationIsValidForWildcard == true ) )
    {
        bool nextLevelExistsInTopicName = false;
        bool nextLevelExistsinTopicFilter = false;
        /* Move topic name index to the end of the current level. The end of the
         * current level is identified by the last character before the next level
         * separator '/'. */
        while( *pNameIndex < topicNameLength )
        {
            /* Exit the loop if we hit the level separator. */
            if( pTopicName[ *pNameIndex ] == '/' )
            {
                nextLevelExistsInTopicName = true;
                break;
            }
            ( *pNameIndex )++;
        }
        /* Determine if the topic filter contains a child level after the current level
         * represented by the '+' wildcard. */
        if( ( *pFilterIndex < ( topicFilterLength - 1U ) ) &&
            ( pTopicFilter[ *pFilterIndex + 1U ] == '/' ) )
        {
            nextLevelExistsinTopicFilter = true;
        }
        /* If the topic name contains a child level but the topic filter ends at
         * the current level, then there does not exist a match. */
        if( ( nextLevelExistsInTopicName == true ) &&
            ( nextLevelExistsinTopicFilter == false ) )
        {
            *pMatch = false;
            shouldStopMatching = true;
        }
        /* If the topic name and topic filter have child levels, then advance the
         * filter index to the level separator in the topic filter, so that match
         * can be performed in the next level.
         * Note: The name index already points to the level separator in the topic
         * name. */
        else if( nextLevelExistsInTopicName == true )
        {
            ( *pFilterIndex )++;
        }
        else
        {
            /* If we have reached here, the the loop terminated on the
             * ( *pNameIndex < topicNameLength) condition, which means that have
             * reached past the end of the topic name, and thus, we decrement the
             * index to the last character in the topic name.*/
            ( *pNameIndex )--;
        }
    }
    /* '#' matches everything remaining in the topic name. It must be the
     * last character in a topic filter. */
    else if( ( pTopicFilter[ *pFilterIndex ] == '#' ) &&
             ( *pFilterIndex == ( topicFilterLength - 1U ) ) &&
             ( locationIsValidForWildcard == true ) )
    {
        /* Subsequent characters don't need to be checked for the
         * multi-level wildcard. */
        *pMatch = true;
        shouldStopMatching = true;
    }
    else
    {
        /* Any character mismatch other than '+' or '#' means the topic
         * name does not match the topic filter. */
        *pMatch = false;
        shouldStopMatching = true;
    }
    return shouldStopMatching;
}
/*-----------------------------------------------------------*/
static bool matchTopicFilter( const char * pTopicName,
                              uint16_t topicNameLength,
                              const char * pTopicFilter,
                              uint16_t topicFilterLength )
{
    bool matchFound = false, shouldStopMatching = false;
    uint16_t nameIndex = 0, filterIndex = 0;
    assert( pTopicName != NULL );
    assert( topicNameLength != 0 );
    assert( pTopicFilter != NULL );
    assert( topicFilterLength != 0 );
    while( ( nameIndex < topicNameLength ) && ( filterIndex < topicFilterLength ) )
    {
        /* Check if the character in the topic name matches the corresponding
         * character in the topic filter string. */
        if( pTopicName[ nameIndex ] == pTopicFilter[ filterIndex ] )
        {
            /* If the topic name has been consumed but the topic filter has not
             * been consumed, match for special cases when the topic filter ends
             * with wildcard character. */
            if( nameIndex == ( topicNameLength - 1U ) )
            {
                matchFound = matchEndWildcardsSpecialCases( pTopicFilter,
                                                            topicFilterLength,
                                                            filterIndex );
            }
        }
        else
        {
            /* Check for matching wildcards. */
            shouldStopMatching = matchWildcards( pTopicName,
                                                 topicNameLength,
                                                 pTopicFilter,
                                                 topicFilterLength,
                                                 &nameIndex,
                                                 &filterIndex,
                                                 &matchFound );
        }
        if( ( matchFound == true ) || ( shouldStopMatching == true ) )
        {
            break;
        }
        /* Increment indexes. */
        nameIndex++;
        filterIndex++;
    }
    if( matchFound == false )
    {
        /* If the end of both strings has been reached, they match. This represents the
         * case when the topic filter contains the '+' wildcard at a non-starting position.
         * For example, when matching either of "sport/+/player" OR "sport/hockey/+" topic
         * filters with "sport/hockey/player" topic name. */
        matchFound = ( nameIndex == topicNameLength ) &&
                     ( filterIndex == topicFilterLength );
    }
    return matchFound;
}
/*-----------------------------------------------------------*/
static int32_t sendMessageVector( MQTTContext_t * pContext,
                                  TransportOutVector_t * pIoVec,
                                  size_t ioVecCount )
{
    int32_t sendResult;
    uint32_t timeoutMs;
    TransportOutVector_t * pIoVectIterator;
    size_t vectorsToBeSent = ioVecCount;
    size_t bytesToSend = 0U;
    int32_t bytesSentOrError = 0;
    assert( pContext != NULL );
    assert( pIoVec != NULL );
    assert( pContext->getTime != NULL );
    /* Send must always be defined */
    assert( pContext->transportInterface.send != NULL );
    /* Count the total number of bytes to be sent as outlined in the vector. */
    for( pIoVectIterator = pIoVec; pIoVectIterator <= &( pIoVec[ ioVecCount - 1U ] ); pIoVectIterator++ )
    {
        bytesToSend += pIoVectIterator->iov_len;
    }
    /* Reset the iterator to point to the first entry in the array. */
    pIoVectIterator = pIoVec;
    /* Set the timeout. */
    timeoutMs = pContext->getTime() + MQTT_SEND_TIMEOUT_MS;
    while( ( bytesSentOrError < ( int32_t ) bytesToSend ) && ( bytesSentOrError >= 0 ) )
    {
        if( pContext->transportInterface.writev != NULL )
        {
            sendResult = pContext->transportInterface.writev( pContext->transportInterface.pNetworkContext,
                                                              pIoVectIterator,
                                                              vectorsToBeSent );
        }
        else
        {
            sendResult = pContext->transportInterface.send( pContext->transportInterface.pNetworkContext,
                                                            pIoVectIterator->iov_base,
                                                            pIoVectIterator->iov_len );
        }
        if( sendResult > 0 )
        {
            /* It is a bug in the application's transport send implementation if
             * more bytes than expected are sent. */
            assert( sendResult <= ( ( int32_t ) bytesToSend - bytesSentOrError ) );
            bytesSentOrError += sendResult;
            /* Set last transmission time. */
            pContext->lastPacketTxTime = pContext->getTime();
            LogDebug( ( "sendMessageVector: Bytes Sent=%ld, Bytes Remaining=%lu",
                        ( long int ) sendResult,
                        ( unsigned long ) ( bytesToSend - ( size_t ) bytesSentOrError ) ) );
        }
        else if( sendResult < 0 )
        {
            bytesSentOrError = sendResult;
            LogError( ( "sendMessageVector: Unable to send packet: Network Error." ) );
        }
        else
        {
            /* MISRA Empty body */
        }
        /* Check for timeout. */
        if( pContext->getTime() >= timeoutMs )
        {
            LogError( ( "sendMessageVector: Unable to send packet: Timed out." ) );
            break;
        }
        /* Update the send pointer to the correct vector and offset. */
        while( ( pIoVectIterator <= &( pIoVec[ ioVecCount - 1U ] ) ) &&
               ( sendResult >= ( int32_t ) pIoVectIterator->iov_len ) )
        {
            sendResult -= ( int32_t ) pIoVectIterator->iov_len;
            pIoVectIterator++;
            /* Update the number of vector which are yet to be sent. */
            vectorsToBeSent--;
        }
        /* Some of the bytes from this vector were sent as well, update the length
         * and the pointer to data in this vector. */
        if( ( sendResult > 0 ) &&
            ( pIoVectIterator <= &( pIoVec[ ioVecCount - 1U ] ) ) )
        {
            pIoVectIterator->iov_base = ( const void * ) &( ( ( const uint8_t * ) pIoVectIterator->iov_base )[ sendResult ] );
            pIoVectIterator->iov_len -= ( size_t ) sendResult;
        }
    }
    return bytesSentOrError;
}
static int32_t sendBuffer( MQTTContext_t * pContext,
                           const uint8_t * pBufferToSend,
                           size_t bytesToSend )
{
    int32_t sendResult;
    uint32_t timeoutMs;
    int32_t bytesSentOrError = 0;
    const uint8_t * pIndex = pBufferToSend;
    assert( pContext != NULL );
    assert( pContext->getTime != NULL );
    assert( pContext->transportInterface.send != NULL );
    assert( pIndex != NULL );
    /* Set the timeout. */
    timeoutMs = pContext->getTime() + MQTT_SEND_TIMEOUT_MS;
    while( ( bytesSentOrError < ( int32_t ) bytesToSend ) && ( bytesSentOrError >= 0 ) )
    {
        sendResult = pContext->transportInterface.send( pContext->transportInterface.pNetworkContext,
                                                        pIndex,
                                                        bytesToSend - ( size_t ) bytesSentOrError );
        if( sendResult > 0 )
        {
            /* It is a bug in the application's transport send implementation if
             * more bytes than expected are sent. */
            assert( sendResult <= ( ( int32_t ) bytesToSend - bytesSentOrError ) );
            bytesSentOrError += sendResult;
            pIndex = &pIndex[ sendResult ];
            /* Set last transmission time. */
            pContext->lastPacketTxTime = pContext->getTime();
            LogDebug( ( "sendBuffer: Bytes Sent=%ld, Bytes Remaining=%lu",
                        ( long int ) sendResult,
                        ( unsigned long ) ( bytesToSend - ( size_t ) bytesSentOrError ) ) );
        }
        else if( sendResult < 0 )
        {
            bytesSentOrError = sendResult;
            LogError( ( "sendBuffer: Unable to send packet: Network Error." ) );
        }
        else
        {
            /* MISRA Empty body */
        }
        /* Check for timeout. */
        if( pContext->getTime() >= timeoutMs )
        {
            LogError( ( "sendBuffer: Unable to send packet: Timed out." ) );
            break;
        }
    }
    return bytesSentOrError;
}
/*-----------------------------------------------------------*/
static uint32_t calculateElapsedTime( uint32_t later,
                                      uint32_t start )
{
    return later - start;
}
/*-----------------------------------------------------------*/
static MQTTPubAckType_t getAckFromPacketType( uint8_t packetType )
{
    MQTTPubAckType_t ackType = MQTTPuback;
    switch( packetType )
    {
        case MQTT_PACKET_TYPE_PUBACK:
            ackType = MQTTPuback;
            break;
        case MQTT_PACKET_TYPE_PUBREC:
            ackType = MQTTPubrec;
            break;
        case MQTT_PACKET_TYPE_PUBREL:
            ackType = MQTTPubrel;
            break;
        case MQTT_PACKET_TYPE_PUBCOMP:
        default:
            /* This function is only called after checking the type is one of
             * the above four values, so packet type must be PUBCOMP here. */
            assert( packetType == MQTT_PACKET_TYPE_PUBCOMP );
            ackType = MQTTPubcomp;
            break;
    }
    return ackType;
}
/*-----------------------------------------------------------*/
static int32_t recvExact( const MQTTContext_t * pContext,
                          size_t bytesToRecv )
{
    uint8_t * pIndex = NULL;
    size_t bytesRemaining = bytesToRecv;
    int32_t totalBytesRecvd = 0, bytesRecvd;
    uint32_t lastDataRecvTimeMs = 0U, timeSinceLastRecvMs = 0U;
    TransportRecv_t recvFunc = NULL;
    MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL;
    bool receiveError = false;
    assert( pContext != NULL );
    assert( bytesToRecv <= pContext->networkBuffer.size );
    assert( pContext->getTime != NULL );
    assert( pContext->transportInterface.recv != NULL );
    assert( pContext->networkBuffer.pBuffer != NULL );
    pIndex = pContext->networkBuffer.pBuffer;
    recvFunc = pContext->transportInterface.recv;
    getTimeStampMs = pContext->getTime;
    /* Part of the MQTT packet has been read before calling this function. */
    lastDataRecvTimeMs = getTimeStampMs();
    while( ( bytesRemaining > 0U ) && ( receiveError == false ) )
    {
        bytesRecvd = recvFunc( pContext->transportInterface.pNetworkContext,
                               pIndex,
                               bytesRemaining );
        if( bytesRecvd < 0 )
        {
            LogError( ( "Network error while receiving packet: ReturnCode=%ld.",
                        ( long int ) bytesRecvd ) );
            totalBytesRecvd = bytesRecvd;
            receiveError = true;
        }
        else if( bytesRecvd > 0 )
        {
            /* Reset the starting time as we have received some data from the network. */
            lastDataRecvTimeMs = getTimeStampMs();
            /* It is a bug in the application's transport receive implementation
             * if more bytes than expected are received. To avoid a possible
             * overflow in converting bytesRemaining from unsigned to signed,
             * this assert must exist after the check for bytesRecvd being
             * negative. */
            assert( ( size_t ) bytesRecvd <= bytesRemaining );
            bytesRemaining -= ( size_t ) bytesRecvd;
            totalBytesRecvd += ( int32_t ) bytesRecvd;
            /* Increment the index. */
            pIndex = &pIndex[ bytesRecvd ];
            LogDebug( ( "BytesReceived=%ld, BytesRemaining=%lu, TotalBytesReceived=%ld.",
                        ( long int ) bytesRecvd,
                        ( unsigned long ) bytesRemaining,
                        ( long int ) totalBytesRecvd ) );
        }
        else
        {
            /* No bytes were read from the network. */
            timeSinceLastRecvMs = calculateElapsedTime( getTimeStampMs(), lastDataRecvTimeMs );
            /* Check for timeout if we have been waiting to receive any byte on the network. */
            if( timeSinceLastRecvMs >= MQTT_RECV_POLLING_TIMEOUT_MS )
            {
                LogError( ( "Unable to receive packet: Timed out in transport recv." ) );
                receiveError = true;
            }
        }
    }
    return totalBytesRecvd;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t discardPacket( const MQTTContext_t * pContext,
                                   size_t remainingLength,
                                   uint32_t timeoutMs )
{
    MQTTStatus_t status = MQTTRecvFailed;
    int32_t bytesReceived = 0;
    size_t bytesToReceive = 0U;
    uint32_t totalBytesReceived = 0U;
    uint32_t entryTimeMs = 0U;
    uint32_t elapsedTimeMs = 0U;
    MQTTGetCurrentTimeFunc_t getTimeStampMs = NULL;
    bool receiveError = false;
    assert( pContext != NULL );
    assert( pContext->getTime != NULL );
    bytesToReceive = pContext->networkBuffer.size;
    getTimeStampMs = pContext->getTime;
    entryTimeMs = getTimeStampMs();
    while( ( totalBytesReceived < remainingLength ) && ( receiveError == false ) )
    {
        if( ( remainingLength - totalBytesReceived ) < bytesToReceive )
        {
            bytesToReceive = remainingLength - totalBytesReceived;
        }
        bytesReceived = recvExact( pContext, bytesToReceive );
        if( bytesReceived != ( int32_t ) bytesToReceive )
        {
            LogError( ( "Receive error while discarding packet."
                        "ReceivedBytes=%ld, ExpectedBytes=%lu.",
                        ( long int ) bytesReceived,
                        ( unsigned long ) bytesToReceive ) );
            receiveError = true;
        }
        else
        {
            totalBytesReceived += ( uint32_t ) bytesReceived;
            elapsedTimeMs = calculateElapsedTime( getTimeStampMs(), entryTimeMs );
            /* Check for timeout. */
            if( elapsedTimeMs >= timeoutMs )
            {
                LogError( ( "Time expired while discarding packet." ) );
                receiveError = true;
            }
        }
    }
    if( totalBytesReceived == remainingLength )
    {
        LogError( ( "Dumped packet. DumpedBytes=%lu.",
                    ( unsigned long ) totalBytesReceived ) );
        /* Packet dumped, so no data is available. */
        status = MQTTNoDataAvailable;
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t discardStoredPacket( MQTTContext_t * pContext,
                                         const MQTTPacketInfo_t * pPacketInfo )
{
    MQTTStatus_t status = MQTTRecvFailed;
    int32_t bytesReceived = 0;
    size_t bytesToReceive = 0U;
    uint32_t totalBytesReceived = 0U;
    bool receiveError = false;
    size_t mqttPacketSize = 0;
    size_t remainingLength;
    assert( pContext != NULL );
    assert( pPacketInfo != NULL );
    mqttPacketSize = pPacketInfo->remainingLength + pPacketInfo->headerLength;
    /* Assert that the packet being discarded is bigger than the
     * receive buffer. */
    assert( mqttPacketSize > pContext->networkBuffer.size );
    /* Discard these many bytes at a time. */
    bytesToReceive = pContext->networkBuffer.size;
    /* Number of bytes depicted by 'index' have already been received. */
    remainingLength = mqttPacketSize - pContext->index;
    while( ( totalBytesReceived < remainingLength ) && ( receiveError == false ) )
    {
        if( ( remainingLength - totalBytesReceived ) < bytesToReceive )
        {
            bytesToReceive = remainingLength - totalBytesReceived;
        }
        bytesReceived = recvExact( pContext, bytesToReceive );
        if( bytesReceived != ( int32_t ) bytesToReceive )
        {
            LogError( ( "Receive error while discarding packet."
                        "ReceivedBytes=%ld, ExpectedBytes=%lu.",
                        ( long int ) bytesReceived,
                        ( unsigned long ) bytesToReceive ) );
            receiveError = true;
        }
        else
        {
            totalBytesReceived += ( uint32_t ) bytesReceived;
        }
    }
    if( totalBytesReceived == remainingLength )
    {
        LogError( ( "Dumped packet. DumpedBytes=%lu.",
                    ( unsigned long ) totalBytesReceived ) );
        /* Packet dumped, so no data is available. */
        status = MQTTNoDataAvailable;
    }
    /* Clear the buffer */
    ( void ) memset( pContext->networkBuffer.pBuffer,
                     0,
                     pContext->networkBuffer.size );
    /* Reset the index. */
    pContext->index = 0;
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t receivePacket( const MQTTContext_t * pContext,
                                   MQTTPacketInfo_t incomingPacket,
                                   uint32_t remainingTimeMs )
{
    MQTTStatus_t status = MQTTSuccess;
    int32_t bytesReceived = 0;
    size_t bytesToReceive = 0U;
    assert( pContext != NULL );
    assert( pContext->networkBuffer.pBuffer != NULL );
    if( incomingPacket.remainingLength > pContext->networkBuffer.size )
    {
        LogError( ( "Incoming packet will be dumped: "
                    "Packet length exceeds network buffer size."
                    "PacketSize=%lu, NetworkBufferSize=%lu.",
                    ( unsigned long ) incomingPacket.remainingLength,
                    ( unsigned long ) pContext->networkBuffer.size ) );
        status = discardPacket( pContext,
                                incomingPacket.remainingLength,
                                remainingTimeMs );
    }
    else
    {
        bytesToReceive = incomingPacket.remainingLength;
        bytesReceived = recvExact( pContext, bytesToReceive );
        if( bytesReceived == ( int32_t ) bytesToReceive )
        {
            /* Receive successful, bytesReceived == bytesToReceive. */
            LogDebug( ( "Packet received. ReceivedBytes=%ld.",
                        ( long int ) bytesReceived ) );
        }
        else
        {
            LogError( ( "Packet reception failed. ReceivedBytes=%ld, "
                        "ExpectedBytes=%lu.",
                        ( long int ) bytesReceived,
                        ( unsigned long ) bytesToReceive ) );
            status = MQTTRecvFailed;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static uint8_t getAckTypeToSend( MQTTPublishState_t state )
{
    uint8_t packetTypeByte = 0U;
    switch( state )
    {
        case MQTTPubAckSend:
            packetTypeByte = MQTT_PACKET_TYPE_PUBACK;
            break;
        case MQTTPubRecSend:
            packetTypeByte = MQTT_PACKET_TYPE_PUBREC;
            break;
        case MQTTPubRelSend:
            packetTypeByte = MQTT_PACKET_TYPE_PUBREL;
            break;
        case MQTTPubCompSend:
            packetTypeByte = MQTT_PACKET_TYPE_PUBCOMP;
            break;
        case MQTTPubAckPending:
        case MQTTPubCompPending:
        case MQTTPubRecPending:
        case MQTTPubRelPending:
        case MQTTPublishDone:
        case MQTTPublishSend:
        case MQTTStateNull:
        default:
            /* Take no action for states that do not require sending an ack. */
            break;
    }
    return packetTypeByte;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t sendPublishAcks( MQTTContext_t * pContext,
                                     uint16_t packetId,
                                     MQTTPublishState_t publishState )
{
    MQTTStatus_t status = MQTTSuccess;
    MQTTPublishState_t newState = MQTTStateNull;
    int32_t sendResult = 0;
    uint8_t packetTypeByte = 0U;
    MQTTPubAckType_t packetType;
    MQTTFixedBuffer_t localBuffer;
    uint8_t pubAckPacket[ MQTT_PUBLISH_ACK_PACKET_SIZE ];
    localBuffer.pBuffer = pubAckPacket;
    localBuffer.size = MQTT_PUBLISH_ACK_PACKET_SIZE;
    assert( pContext != NULL );
    packetTypeByte = getAckTypeToSend( publishState );
    if( packetTypeByte != 0U )
    {
        packetType = getAckFromPacketType( packetTypeByte );
        status = MQTT_SerializeAck( &localBuffer,
                                    packetTypeByte,
                                    packetId );
        if( status == MQTTSuccess )
        {
            MQTT_PRE_SEND_HOOK( pContext );
            /* Here, we are not using the vector approach for efficiency. There is just one buffer
             * to be sent which can be achieved with a normal send call. */
            sendResult = sendBuffer( pContext,
                                     localBuffer.pBuffer,
                                     MQTT_PUBLISH_ACK_PACKET_SIZE );
            MQTT_POST_SEND_HOOK( pContext );
        }
        if( sendResult == ( int32_t ) MQTT_PUBLISH_ACK_PACKET_SIZE )
        {
            pContext->controlPacketSent = true;
            MQTT_PRE_STATE_UPDATE_HOOK( pContext );
            status = MQTT_UpdateStateAck( pContext,
                                          packetId,
                                          packetType,
                                          MQTT_SEND,
                                          &newState );
            MQTT_POST_STATE_UPDATE_HOOK( pContext );
            if( status != MQTTSuccess )
            {
                LogError( ( "Failed to update state of publish %hu.",
                            ( unsigned short ) packetId ) );
            }
        }
        else
        {
            LogError( ( "Failed to send ACK packet: PacketType=%02x, SentBytes=%ld, "
                        "PacketSize=%lu.",
                        ( unsigned int ) packetTypeByte, ( long int ) sendResult,
                        MQTT_PUBLISH_ACK_PACKET_SIZE ) );
            status = MQTTSendFailed;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t handleKeepAlive( MQTTContext_t * pContext )
{
    MQTTStatus_t status = MQTTSuccess;
    uint32_t now = 0U;
    uint32_t packetTxTimeoutMs = 0U;
    assert( pContext != NULL );
    assert( pContext->getTime != NULL );
    now = pContext->getTime();
    packetTxTimeoutMs = 1000U * ( uint32_t ) pContext->keepAliveIntervalSec;
    if( PACKET_TX_TIMEOUT_MS < packetTxTimeoutMs )
    {
        packetTxTimeoutMs = PACKET_TX_TIMEOUT_MS;
    }
    /* If keep alive interval is 0, it is disabled. */
    if( pContext->waitingForPingResp == true )
    {
        /* Has time expired? */
        if( calculateElapsedTime( now, pContext->pingReqSendTimeMs ) >
            MQTT_PINGRESP_TIMEOUT_MS )
        {
            status = MQTTKeepAliveTimeout;
        }
    }
    else
    {
        if( ( packetTxTimeoutMs != 0U ) && ( calculateElapsedTime( now, pContext->lastPacketTxTime ) >= packetTxTimeoutMs ) )
        {
            status = MQTT_Ping( pContext );
        }
        else
        {
            const uint32_t timeElapsed = calculateElapsedTime( now, pContext->lastPacketRxTime );
            if( ( timeElapsed != 0U ) && ( timeElapsed >= PACKET_RX_TIMEOUT_MS ) )
            {
                status = MQTT_Ping( pContext );
            }
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t handleIncomingPublish( MQTTContext_t * pContext,
                                           MQTTPacketInfo_t * pIncomingPacket )
{
    MQTTStatus_t status = MQTTBadParameter;
    MQTTPublishState_t publishRecordState = MQTTStateNull;
    uint16_t packetIdentifier = 0U;
    MQTTPublishInfo_t publishInfo;
    MQTTDeserializedInfo_t deserializedInfo;
    bool duplicatePublish = false;
    assert( pContext != NULL );
    assert( pIncomingPacket != NULL );
    assert( pContext->appCallback != NULL );
    status = MQTT_DeserializePublish( pIncomingPacket, &packetIdentifier, &publishInfo );
    LogInfo( ( "De-serialized incoming PUBLISH packet: DeserializerResult=%s.",
               MQTT_Status_strerror( status ) ) );
    if( ( status == MQTTSuccess ) &&
        ( pContext->incomingPublishRecords == NULL ) &&
        ( publishInfo.qos > MQTTQoS0 ) )
    {
        LogError( ( "Incoming publish has QoS > MQTTQoS0 but incoming "
                    "publish records have not been initialized. Dropping the "
                    "incoming publish. Please call MQTT_InitStatefulQoS to enable "
                    "use of QoS1 and QoS2 publishes." ) );
        status = MQTTRecvFailed;
    }
    if( status == MQTTSuccess )
    {
        MQTT_PRE_STATE_UPDATE_HOOK( pContext );
        status = MQTT_UpdateStatePublish( pContext,
                                          packetIdentifier,
                                          MQTT_RECEIVE,
                                          publishInfo.qos,
                                          &publishRecordState );
        MQTT_POST_STATE_UPDATE_HOOK( pContext );
        if( status == MQTTSuccess )
        {
            LogInfo( ( "State record updated. New state=%s.",
                       MQTT_State_strerror( publishRecordState ) ) );
        }
        /* Different cases in which an incoming publish with duplicate flag is
         * handled are as listed below.
         * 1. No collision - This is the first instance of the incoming publish
         *    packet received or an earlier received packet state is lost. This
         *    will be handled as a new incoming publish for both QoS1 and QoS2
         *    publishes.
         * 2. Collision - The incoming packet was received before and a state
         *    record is present in the state engine. For QoS1 and QoS2 publishes
         *    this case can happen at 2 different cases and handling is
         *    different.
         *    a. QoS1 - If a PUBACK is not successfully sent for the incoming
         *       publish due to a connection issue, it can result in broker
         *       sending out a duplicate publish with dup flag set, when a
         *       session is reestablished. It can result in a collision in
         *       state engine. This will be handled by processing the incoming
         *       publish as a new publish ignoring the
         *       #MQTTStateCollision status from the state engine. The publish
         *       data is not passed to the application.
         *    b. QoS2 - If a PUBREC is not successfully sent for the incoming
         *       publish or the PUBREC sent is not successfully received by the
         *       broker due to a connection issue, it can result in broker
         *       sending out a duplicate publish with dup flag set, when a
         *       session is reestablished. It can result in a collision in
         *       state engine. This will be handled by ignoring the
         *       #MQTTStateCollision status from the state engine. The publish
         *       data is not passed to the application. */
        else if( status == MQTTStateCollision )
        {
            status = MQTTSuccess;
            duplicatePublish = true;
            /* Calculate the state for the ack packet that needs to be sent out
             * for the duplicate incoming publish. */
            publishRecordState = MQTT_CalculateStatePublish( MQTT_RECEIVE,
                                                             publishInfo.qos );
            LogDebug( ( "Incoming publish packet with packet id %hu already exists.",
                        ( unsigned short ) packetIdentifier ) );
            if( publishInfo.dup == false )
            {
                LogError( ( "DUP flag is 0 for duplicate packet (MQTT-3.3.1.-1)." ) );
            }
        }
        else
        {
            LogError( ( "Error in updating publish state for incoming publish with packet id %hu."
                        " Error is %s",
                        ( unsigned short ) packetIdentifier,
                        MQTT_Status_strerror( status ) ) );
        }
    }
    if( status == MQTTSuccess )
    {
        /* Set fields of deserialized struct. */
        deserializedInfo.packetIdentifier = packetIdentifier;
        deserializedInfo.pPublishInfo = &publishInfo;
        deserializedInfo.deserializationResult = status;
        /* Invoke application callback to hand the buffer over to application
         * before sending acks.
         * Application callback will be invoked for all publishes, except for
         * duplicate incoming publishes. */
        if( duplicatePublish == false )
        {
            pContext->appCallback( pContext,
                                   pIncomingPacket,
                                   &deserializedInfo );
        }
        /* Send PUBACK or PUBREC if necessary. */
        status = sendPublishAcks( pContext,
                                  packetIdentifier,
                                  publishRecordState );
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t handlePublishAcks( MQTTContext_t * pContext,
                                       MQTTPacketInfo_t * pIncomingPacket )
{
    MQTTStatus_t status = MQTTBadResponse;
    MQTTPublishState_t publishRecordState = MQTTStateNull;
    uint16_t packetIdentifier;
    MQTTPubAckType_t ackType;
    MQTTEventCallback_t appCallback;
    MQTTDeserializedInfo_t deserializedInfo;
    assert( pContext != NULL );
    assert( pIncomingPacket != NULL );
    assert( pContext->appCallback != NULL );
    appCallback = pContext->appCallback;
    ackType = getAckFromPacketType( pIncomingPacket->type );
    status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL );
    LogInfo( ( "Ack packet deserialized with result: %s.",
               MQTT_Status_strerror( status ) ) );
    if( status == MQTTSuccess )
    {
        MQTT_PRE_STATE_UPDATE_HOOK( pContext );
        status = MQTT_UpdateStateAck( pContext,
                                      packetIdentifier,
                                      ackType,
                                      MQTT_RECEIVE,
                                      &publishRecordState );
        MQTT_POST_STATE_UPDATE_HOOK( pContext );
        if( status == MQTTSuccess )
        {
            LogInfo( ( "State record updated. New state=%s.",
                       MQTT_State_strerror( publishRecordState ) ) );
        }
        else
        {
            LogError( ( "Updating the state engine for packet id %hu"
                        " failed with error %s.",
                        ( unsigned short ) packetIdentifier,
                        MQTT_Status_strerror( status ) ) );
        }
    }
    if( status == MQTTSuccess )
    {
        /* Set fields of deserialized struct. */
        deserializedInfo.packetIdentifier = packetIdentifier;
        deserializedInfo.deserializationResult = status;
        deserializedInfo.pPublishInfo = NULL;
        /* Invoke application callback to hand the buffer over to application
         * before sending acks. */
        appCallback( pContext, pIncomingPacket, &deserializedInfo );
        /* Send PUBREL or PUBCOMP if necessary. */
        status = sendPublishAcks( pContext,
                                  packetIdentifier,
                                  publishRecordState );
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t handleIncomingAck( MQTTContext_t * pContext,
                                       MQTTPacketInfo_t * pIncomingPacket,
                                       bool manageKeepAlive )
{
    MQTTStatus_t status = MQTTBadResponse;
    uint16_t packetIdentifier = MQTT_PACKET_ID_INVALID;
    MQTTDeserializedInfo_t deserializedInfo;
    /* We should always invoke the app callback unless we receive a PINGRESP
     * and are managing keep alive, or if we receive an unknown packet. We
     * initialize this to false since the callback must be invoked before
     * sending any PUBREL or PUBCOMP. However, for other cases, we invoke it
     * at the end to reduce the complexity of this function. */
    bool invokeAppCallback = false;
    MQTTEventCallback_t appCallback = NULL;
    assert( pContext != NULL );
    assert( pIncomingPacket != NULL );
    assert( pContext->appCallback != NULL );
    appCallback = pContext->appCallback;
    LogDebug( ( "Received packet of type %02x.",
                ( unsigned int ) pIncomingPacket->type ) );
    switch( pIncomingPacket->type )
    {
        case MQTT_PACKET_TYPE_PUBACK:
        case MQTT_PACKET_TYPE_PUBREC:
        case MQTT_PACKET_TYPE_PUBREL:
        case MQTT_PACKET_TYPE_PUBCOMP:
            /* Handle all the publish acks. The app callback is invoked here. */
            status = handlePublishAcks( pContext, pIncomingPacket );
            break;
        case MQTT_PACKET_TYPE_PINGRESP:
            status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL );
            invokeAppCallback = ( status == MQTTSuccess ) && !manageKeepAlive;
            if( ( status == MQTTSuccess ) && ( manageKeepAlive == true ) )
            {
                pContext->waitingForPingResp = false;
            }
            break;
        case MQTT_PACKET_TYPE_SUBACK:
        case MQTT_PACKET_TYPE_UNSUBACK:
            /* Deserialize and give these to the app provided callback. */
            status = MQTT_DeserializeAck( pIncomingPacket, &packetIdentifier, NULL );
            invokeAppCallback = ( status == MQTTSuccess ) || ( status == MQTTServerRefused );
            break;
        default:
            /* Bad response from the server. */
            LogError( ( "Unexpected packet type from server: PacketType=%02x.",
                        ( unsigned int ) pIncomingPacket->type ) );
            status = MQTTBadResponse;
            break;
    }
    if( invokeAppCallback == true )
    {
        /* Set fields of deserialized struct. */
        deserializedInfo.packetIdentifier = packetIdentifier;
        deserializedInfo.deserializationResult = status;
        deserializedInfo.pPublishInfo = NULL;
        appCallback( pContext, pIncomingPacket, &deserializedInfo );
        /* In case a SUBACK indicated refusal, reset the status to continue the loop. */
        status = MQTTSuccess;
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t receiveSingleIteration( MQTTContext_t * pContext,
                                            bool manageKeepAlive )
{
    MQTTStatus_t status = MQTTSuccess;
    MQTTPacketInfo_t incomingPacket = { 0 };
    int32_t recvBytes;
    size_t totalMQTTPacketLength = 0;
    assert( pContext != NULL );
    assert( pContext->networkBuffer.pBuffer != NULL );
    /* Read as many bytes as possible into the network buffer. */
    recvBytes = pContext->transportInterface.recv( pContext->transportInterface.pNetworkContext,
                                                   &( pContext->networkBuffer.pBuffer[ pContext->index ] ),
                                                   pContext->networkBuffer.size - pContext->index );
    if( recvBytes < 0 )
    {
        /* The receive function has failed. Bubble up the error up to the user. */
        status = MQTTRecvFailed;
    }
    else if( ( recvBytes == 0 ) && ( pContext->index == 0U ) )
    {
        /* No more bytes available since the last read and neither is anything in
         * the buffer. */
        status = MQTTNoDataAvailable;
    }
    /* Either something was received, or there is still data to be processed in the
     * buffer, or both. */
    else
    {
        /* Update the number of bytes in the MQTT fixed buffer. */
        pContext->index += ( size_t ) recvBytes;
        status = MQTT_ProcessIncomingPacketTypeAndLength( pContext->networkBuffer.pBuffer,
                                                          &pContext->index,
                                                          &incomingPacket );
        totalMQTTPacketLength = incomingPacket.remainingLength + incomingPacket.headerLength;
    }
    /* No data was received, check for keep alive timeout. */
    if( recvBytes == 0 )
    {
        if( manageKeepAlive == true )
        {
            /* Keep the copy of the status to be reset later. */
            MQTTStatus_t statusCopy = status;
            /* Assign status so an error can be bubbled up to application,
             * but reset it on success. */
            status = handleKeepAlive( pContext );
            if( status == MQTTSuccess )
            {
                /* Reset the status. */
                status = statusCopy;
            }
            else
            {
                LogError( ( "Handling of keep alive failed. Status=%s",
                            MQTT_Status_strerror( status ) ) );
            }
        }
    }
    /* Check whether there is data available before processing the packet further. */
    if( ( status == MQTTNeedMoreBytes ) || ( status == MQTTNoDataAvailable ) )
    {
        /* Do nothing as there is nothing to be processed right now. The proper
         * error code will be bubbled up to the user. */
    }
    /* Any other error code. */
    else if( status != MQTTSuccess )
    {
        LogError( ( "Call to receiveSingleIteration failed. Status=%s",
                    MQTT_Status_strerror( status ) ) );
    }
    /* If the MQTT Packet size is bigger than the buffer itself. */
    else if( totalMQTTPacketLength > pContext->networkBuffer.size )
    {
        /* Discard the packet from the receive buffer and drain the pending
         * data from the socket buffer. */
        status = discardStoredPacket( pContext,
                                      &incomingPacket );
    }
    /* If the total packet is of more length than the bytes we have available. */
    else if( totalMQTTPacketLength > pContext->index )
    {
        status = MQTTNeedMoreBytes;
    }
    else
    {
        /* MISRA else. */
    }
    /* Handle received packet. If incomplete data was read then this will not execute. */
    if( status == MQTTSuccess )
    {
        incomingPacket.pRemainingData = &pContext->networkBuffer.pBuffer[ incomingPacket.headerLength ];
        /* PUBLISH packets allow flags in the lower four bits. For other
         * packet types, they are reserved. */
        if( ( incomingPacket.type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH )
        {
            status = handleIncomingPublish( pContext, &incomingPacket );
        }
        else
        {
            status = handleIncomingAck( pContext, &incomingPacket, manageKeepAlive );
        }
        /* Update the index to reflect the remaining bytes in the buffer.  */
        pContext->index -= totalMQTTPacketLength;
        /* Move the remaining bytes to the front of the buffer. */
        ( void ) memmove( pContext->networkBuffer.pBuffer,
                          &( pContext->networkBuffer.pBuffer[ totalMQTTPacketLength ] ),
                          pContext->index );
    }
    if( status == MQTTNoDataAvailable )
    {
        /* No data available is not an error. Reset to MQTTSuccess so the
         * return code will indicate success. */
        status = MQTTSuccess;
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t validateSubscribeUnsubscribeParams( const MQTTContext_t * pContext,
                                                        const MQTTSubscribeInfo_t * pSubscriptionList,
                                                        size_t subscriptionCount,
                                                        uint16_t packetId )
{
    MQTTStatus_t status = MQTTSuccess;
    size_t iterator;
    /* Validate all the parameters. */
    if( ( pContext == NULL ) || ( pSubscriptionList == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pContext=%p, "
                    "pSubscriptionList=%p.",
                    ( void * ) pContext,
                    ( void * ) pSubscriptionList ) );
        status = MQTTBadParameter;
    }
    else if( subscriptionCount == 0UL )
    {
        LogError( ( "Subscription count is 0." ) );
        status = MQTTBadParameter;
    }
    else if( packetId == 0U )
    {
        LogError( ( "Packet Id for subscription packet is 0." ) );
        status = MQTTBadParameter;
    }
    else
    {
        if( pContext->incomingPublishRecords == NULL )
        {
            for( iterator = 0; iterator < subscriptionCount; iterator++ )
            {
                if( pSubscriptionList->qos > MQTTQoS0 )
                {
                    LogError( ( "The incoming publish record list is not "
                                "initialised for QoS1/QoS2 records. Please call "
                                " MQTT_InitStatefulQoS to enable use of QoS1 and "
                                " QoS2 packets." ) );
                    status = MQTTBadParameter;
                    break;
                }
            }
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static size_t addEncodedStringToVector( uint8_t serailizedLength[ 2 ],
                                        const char * const string,
                                        uint16_t length,
                                        TransportOutVector_t * iterator,
                                        size_t * updatedLength )
{
    size_t packetLength = 0U;
    const size_t seralizedLengthFieldSize = 2U;
    TransportOutVector_t * pLocalIterator = iterator;
    /* This function always adds 2 vectors. */
    size_t vectorsAdded = 0U;
    /* When length is non-zero, the string must be non-NULL. */
    assert( ( length != 0U ) == ( string != NULL ) );
    serailizedLength[ 0 ] = ( ( uint8_t ) ( ( length ) >> 8 ) );
    serailizedLength[ 1 ] = ( ( uint8_t ) ( ( length ) & 0x00ffU ) );
    /* Add the serialized length of the string first. */
    pLocalIterator[ 0 ].iov_base = serailizedLength;
    pLocalIterator[ 0 ].iov_len = seralizedLengthFieldSize;
    vectorsAdded++;
    packetLength = seralizedLengthFieldSize;
    /* Sometimes the string can be NULL that is, of 0 length. In that case,
     * only the length field should be encoded in the vector. */
    if( ( string != NULL ) && ( length != 0U ) )
    {
        /* Then add the pointer to the string itself. */
        pLocalIterator[ 1 ].iov_base = string;
        pLocalIterator[ 1 ].iov_len = length;
        vectorsAdded++;
        packetLength += length;
    }
    ( *updatedLength ) = ( *updatedLength ) + packetLength;
    return vectorsAdded;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t sendSubscribeWithoutCopy( MQTTContext_t * pContext,
                                              const MQTTSubscribeInfo_t * pSubscriptionList,
                                              size_t subscriptionCount,
                                              uint16_t packetId,
                                              size_t remainingLength )
{
    MQTTStatus_t status = MQTTSuccess;
    uint8_t subscribeheader[ 7 ];
    uint8_t * pIndex;
    TransportOutVector_t pIoVector[ MQTT_SUB_UNSUB_MAX_VECTORS ];
    TransportOutVector_t * pIterator;
    uint8_t serializedTopicFieldLength[ MQTT_SUB_UNSUB_MAX_VECTORS ][ 2 ];
    size_t totalPacketLength = 0U;
    size_t ioVectorLength = 0U;
    size_t subscriptionsSent = 0U;
    /* For subscribe, only three vector slots are required per topic string. */
    const size_t subscriptionStringVectorSlots = 3U;
    size_t vectorsAdded;
    size_t topicFieldLengthIndex;
    /* The vector array should be at least three element long as the topic
     * string needs these many vector elements to be stored. */
    assert( MQTT_SUB_UNSUB_MAX_VECTORS >= subscriptionStringVectorSlots );
    pIndex = subscribeheader;
    pIterator = pIoVector;
    pIndex = MQTT_SerializeSubscribeHeader( remainingLength,
                                            pIndex,
                                            packetId );
    /* The header is to be sent first. */
    pIterator->iov_base = subscribeheader;
    /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-182 */
    /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-108 */
    /* coverity[misra_c_2012_rule_18_2_violation] */
    /* coverity[misra_c_2012_rule_10_8_violation] */
    pIterator->iov_len = ( size_t ) ( pIndex - subscribeheader );
    totalPacketLength += pIterator->iov_len;
    pIterator++;
    ioVectorLength++;
    while( ( status == MQTTSuccess ) && ( subscriptionsSent < subscriptionCount ) )
    {
        /* Reset the index for next iteration. */
        topicFieldLengthIndex = 0;
        /* Check whether the subscription topic (with QoS) will fit in the
         * given vector. */
        while( ( ioVectorLength <= ( MQTT_SUB_UNSUB_MAX_VECTORS - subscriptionStringVectorSlots ) ) &&
               ( subscriptionsSent < subscriptionCount ) )
        {
            /* The topic filter gets sent next. */
            vectorsAdded = addEncodedStringToVector( serializedTopicFieldLength[ topicFieldLengthIndex ],
                                                     pSubscriptionList[ subscriptionsSent ].pTopicFilter,
                                                     pSubscriptionList[ subscriptionsSent ].topicFilterLength,
                                                     pIterator,
                                                     &totalPacketLength );
            /* Update the pointer after the above operation. */
            pIterator = &pIterator[ vectorsAdded ];
            /* Lastly, the QoS gets sent. */
            pIterator->iov_base = &( pSubscriptionList[ subscriptionsSent ].qos );
            pIterator->iov_len = 1U;
            totalPacketLength += pIterator->iov_len;
            /* Increment the pointer. */
            pIterator++;
            /* Two slots get used by the topic string length and topic string.
             * One slot gets used by the quality of service. */
            ioVectorLength += vectorsAdded + 1U;
            subscriptionsSent++;
            /* The index needs to be updated for next iteration. */
            topicFieldLengthIndex++;
        }
        if( sendMessageVector( pContext,
                               pIoVector,
                               ioVectorLength ) != ( int32_t ) totalPacketLength )
        {
            status = MQTTSendFailed;
        }
        /* Update the iterator for the next potential loop iteration. */
        pIterator = pIoVector;
        /* Reset the vector length for the next potential loop iteration. */
        ioVectorLength = 0U;
        /* Reset the packet length for the next potential loop iteration. */
        totalPacketLength = 0U;
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t sendUnsubscribeWithoutCopy( MQTTContext_t * pContext,
                                                const MQTTSubscribeInfo_t * pSubscriptionList,
                                                size_t subscriptionCount,
                                                uint16_t packetId,
                                                size_t remainingLength )
{
    MQTTStatus_t status = MQTTSuccess;
    uint8_t unsubscribeheader[ 7 ];
    uint8_t * pIndex;
    TransportOutVector_t pIoVector[ MQTT_SUB_UNSUB_MAX_VECTORS ];
    TransportOutVector_t * pIterator;
    uint8_t serializedTopicFieldLength[ MQTT_SUB_UNSUB_MAX_VECTORS ][ 2 ];
    size_t totalPacketLength = 0U;
    size_t unsubscriptionsSent = 0U;
    size_t ioVectorLength = 0U;
    /* For unsubscribe, only two vector slots are required per topic string. */
    const size_t unsubscribeStringVectorSlots = 2U;
    size_t vectorsAdded;
    size_t topicFieldLengthIndex;
    /* The vector array should be at least three element long as the topic
     * string needs these many vector elements to be stored. */
    assert( MQTT_SUB_UNSUB_MAX_VECTORS >= unsubscribeStringVectorSlots );
    pIndex = unsubscribeheader;
    pIterator = pIoVector;
    pIndex = MQTT_SerializeUnsubscribeHeader( remainingLength,
                                              pIndex,
                                              packetId );
    /* The header is to be sent first. */
    pIterator->iov_base = unsubscribeheader;
    /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-182 */
    /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-108 */
    /* coverity[misra_c_2012_rule_18_2_violation] */
    /* coverity[misra_c_2012_rule_10_8_violation] */
    pIterator->iov_len = ( size_t ) ( pIndex - unsubscribeheader );
    totalPacketLength += pIterator->iov_len;
    pIterator++;
    ioVectorLength++;
    while( ( status == MQTTSuccess ) && ( unsubscriptionsSent < subscriptionCount ) )
    {
        /* Reset the index for next iteration. */
        topicFieldLengthIndex = 0;
        /* Check whether the subscription topic will fit in the given vector. */
        while( ( ioVectorLength <= ( MQTT_SUB_UNSUB_MAX_VECTORS - unsubscribeStringVectorSlots ) ) &&
               ( unsubscriptionsSent < subscriptionCount ) )
        {
            /* The topic filter gets sent next. */
            vectorsAdded = addEncodedStringToVector( serializedTopicFieldLength[ topicFieldLengthIndex ],
                                                     pSubscriptionList[ unsubscriptionsSent ].pTopicFilter,
                                                     pSubscriptionList[ unsubscriptionsSent ].topicFilterLength,
                                                     pIterator,
                                                     &totalPacketLength );
            /* Update the iterator to point to the next empty location. */
            pIterator = &pIterator[ vectorsAdded ];
            /* Update the total count based on how many vectors were added. */
            ioVectorLength += vectorsAdded;
            unsubscriptionsSent++;
            /* Update the index for next iteration. */
            topicFieldLengthIndex++;
        }
        if( sendMessageVector( pContext, pIoVector, ioVectorLength ) != ( int32_t ) totalPacketLength )
        {
            status = MQTTSendFailed;
        }
        /* Update the iterator for the next potential loop iteration. */
        pIterator = pIoVector;
        /* Reset the vector length for the next potential loop iteration. */
        ioVectorLength = 0U;
        /* Reset the packet length for the next potential loop iteration. */
        totalPacketLength = 0U;
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t sendPublishWithoutCopy( MQTTContext_t * pContext,
                                            const MQTTPublishInfo_t * pPublishInfo,
                                            const uint8_t * pMqttHeader,
                                            size_t headerSize,
                                            uint16_t packetId )
{
    MQTTStatus_t status = MQTTSuccess;
    uint8_t serializedPacketID[ 2 ];
    TransportOutVector_t pIoVector[ 4 ];
    size_t ioVectorLength;
    size_t totalMessageLength;
    const size_t packetIDLength = 2U;
    /* The header is sent first. */
    pIoVector[ 0U ].iov_base = pMqttHeader;
    pIoVector[ 0U ].iov_len = headerSize;
    totalMessageLength = headerSize;
    /* Then the topic name has to be sent. */
    pIoVector[ 1U ].iov_base = pPublishInfo->pTopicName;
    pIoVector[ 1U ].iov_len = pPublishInfo->topicNameLength;
    totalMessageLength += pPublishInfo->topicNameLength;
    /* The next field's index should be 2 as the first two fields
     * have been filled in. */
    ioVectorLength = 2U;
    if( pPublishInfo->qos > MQTTQoS0 )
    {
        /* Encode the packet ID. */
        serializedPacketID[ 0 ] = ( ( uint8_t ) ( ( packetId ) >> 8 ) );
        serializedPacketID[ 1 ] = ( ( uint8_t ) ( ( packetId ) & 0x00ffU ) );
        pIoVector[ ioVectorLength ].iov_base = serializedPacketID;
        pIoVector[ ioVectorLength ].iov_len = packetIDLength;
        ioVectorLength++;
        totalMessageLength += packetIDLength;
    }
    /* Publish packets are allowed to contain no payload. */
    if( pPublishInfo->payloadLength > 0U )
    {
        pIoVector[ ioVectorLength ].iov_base = pPublishInfo->pPayload;
        pIoVector[ ioVectorLength ].iov_len = pPublishInfo->payloadLength;
        ioVectorLength++;
        totalMessageLength += pPublishInfo->payloadLength;
    }
    if( sendMessageVector( pContext, pIoVector, ioVectorLength ) != ( int32_t ) totalMessageLength )
    {
        status = MQTTSendFailed;
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t sendConnectWithoutCopy( MQTTContext_t * pContext,
                                            const MQTTConnectInfo_t * pConnectInfo,
                                            const MQTTPublishInfo_t * pWillInfo,
                                            size_t remainingLength )
{
    MQTTStatus_t status = MQTTSuccess;
    TransportOutVector_t * iterator;
    size_t ioVectorLength = 0U;
    size_t totalMessageLength = 0U;
    int32_t bytesSentOrError;
    /* Connect packet header can be of maximum 15 bytes. */
    uint8_t connectPacketHeader[ 15 ];
    uint8_t * pIndex = connectPacketHeader;
    TransportOutVector_t pIoVector[ 11 ];
    uint8_t serializedClientIDLength[ 2 ];
    uint8_t serializedTopicLength[ 2 ];
    uint8_t serializedPayloadLength[ 2 ];
    uint8_t serializedUsernameLength[ 2 ];
    uint8_t serializedPasswordLength[ 2 ];
    size_t vectorsAdded;
    iterator = pIoVector;
    /* Validate arguments. */
    if( ( pWillInfo != NULL ) && ( pWillInfo->pTopicName == NULL ) )
    {
        LogError( ( "pWillInfo->pTopicName cannot be NULL if Will is present." ) );
        status = MQTTBadParameter;
    }
    else
    {
        pIndex = MQTT_SerializeConnectFixedHeader( pIndex,
                                                   pConnectInfo,
                                                   pWillInfo,
                                                   remainingLength );
        assert( ( pIndex - connectPacketHeader ) <= 15 );
        /* The header gets sent first. */
        iterator->iov_base = connectPacketHeader;
        /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-182 */
        /* More details at: https://github.com/FreeRTOS/coreMQTT/blob/main/MISRA.md#rule-108 */
        /* coverity[misra_c_2012_rule_18_2_violation] */
        /* coverity[misra_c_2012_rule_10_8_violation] */
        iterator->iov_len = ( size_t ) ( pIndex - connectPacketHeader );
        totalMessageLength += iterator->iov_len;
        iterator++;
        ioVectorLength++;
        /* Serialize the client ID. */
        vectorsAdded = addEncodedStringToVector( serializedClientIDLength,
                                                 pConnectInfo->pClientIdentifier,
                                                 pConnectInfo->clientIdentifierLength,
                                                 iterator,
                                                 &totalMessageLength );
        /* Update the iterator to point to the next empty slot. */
        iterator = &iterator[ vectorsAdded ];
        ioVectorLength += vectorsAdded;
        if( pWillInfo != NULL )
        {
            /* Serialize the topic. */
            vectorsAdded = addEncodedStringToVector( serializedTopicLength,
                                                     pWillInfo->pTopicName,
                                                     pWillInfo->topicNameLength,
                                                     iterator,
                                                     &totalMessageLength );
            /* Update the iterator to point to the next empty slot. */
            iterator = &iterator[ vectorsAdded ];
            ioVectorLength += vectorsAdded;
            /* Serialize the payload. Payload of last will and testament can be NULL. */
            vectorsAdded = addEncodedStringToVector( serializedPayloadLength,
                                                     pWillInfo->pPayload,
                                                     ( uint16_t ) pWillInfo->payloadLength,
                                                     iterator,
                                                     &totalMessageLength );
            /* Update the iterator to point to the next empty slot. */
            iterator = &iterator[ vectorsAdded ];
            ioVectorLength += vectorsAdded;
        }
        /* Encode the user name if provided. */
        if( pConnectInfo->pUserName != NULL )
        {
            /* Serialize the user name string. */
            vectorsAdded = addEncodedStringToVector( serializedUsernameLength,
                                                     pConnectInfo->pUserName,
                                                     pConnectInfo->userNameLength,
                                                     iterator,
                                                     &totalMessageLength );
            /* Update the iterator to point to the next empty slot. */
            iterator = &iterator[ vectorsAdded ];
            ioVectorLength += vectorsAdded;
        }
        /* Encode the password if provided. */
        if( pConnectInfo->pPassword != NULL )
        {
            /* Serialize the user name string. */
            vectorsAdded = addEncodedStringToVector( serializedPasswordLength,
                                                     pConnectInfo->pPassword,
                                                     pConnectInfo->passwordLength,
                                                     iterator,
                                                     &totalMessageLength );
            /* Update the iterator to point to the next empty slot. */
            iterator = &iterator[ vectorsAdded ];
            ioVectorLength += vectorsAdded;
        }
        bytesSentOrError = sendMessageVector( pContext, pIoVector, ioVectorLength );
        if( bytesSentOrError != ( int32_t ) totalMessageLength )
        {
            status = MQTTSendFailed;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t receiveConnack( const MQTTContext_t * pContext,
                                    uint32_t timeoutMs,
                                    bool cleanSession,
                                    MQTTPacketInfo_t * pIncomingPacket,
                                    bool * pSessionPresent )
{
    MQTTStatus_t status = MQTTSuccess;
    MQTTGetCurrentTimeFunc_t getTimeStamp = NULL;
    uint32_t entryTimeMs = 0U, remainingTimeMs = 0U, timeTakenMs = 0U;
    bool breakFromLoop = false;
    uint16_t loopCount = 0U;
    assert( pContext != NULL );
    assert( pIncomingPacket != NULL );
    assert( pContext->getTime != NULL );
    getTimeStamp = pContext->getTime;
    /* Get the entry time for the function. */
    entryTimeMs = getTimeStamp();
    do
    {
        /* Transport read for incoming CONNACK packet type and length.
         * MQTT_GetIncomingPacketTypeAndLength is a blocking call and it is
         * returned after a transport receive timeout, an error, or a successful
         * receive of packet type and length. */
        status = MQTT_GetIncomingPacketTypeAndLength( pContext->transportInterface.recv,
                                                      pContext->transportInterface.pNetworkContext,
                                                      pIncomingPacket );
        /* The loop times out based on 2 conditions.
         * 1. If timeoutMs is greater than 0:
         *    Loop times out based on the timeout calculated by getTime()
         *    function.
         * 2. If timeoutMs is 0:
         *    Loop times out based on the maximum number of retries config
         *    MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT. This config will control
         *    maximum the number of retry attempts to read the CONNACK packet.
         *    A value of 0 for the config will try once to read CONNACK. */
        if( timeoutMs > 0U )
        {
            breakFromLoop = calculateElapsedTime( getTimeStamp(), entryTimeMs ) >= timeoutMs;
        }
        else
        {
            breakFromLoop = loopCount >= MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT;
            loopCount++;
        }
        /* Loop until there is data to read or if we have exceeded the timeout/retries. */
    } while( ( status == MQTTNoDataAvailable ) && ( breakFromLoop == false ) );
    if( status == MQTTSuccess )
    {
        /* Time taken in this function so far. */
        timeTakenMs = calculateElapsedTime( getTimeStamp(), entryTimeMs );
        if( timeTakenMs < timeoutMs )
        {
            /* Calculate remaining time for receiving the remainder of
             * the packet. */
            remainingTimeMs = timeoutMs - timeTakenMs;
        }
        /* Reading the remainder of the packet by transport recv.
         * Attempt to read once even if the timeout has expired.
         * Invoking receivePacket with remainingTime as 0 would attempt to
         * recv from network once. If using retries, the remainder of the
         * CONNACK packet is tried to be read only once. Reading once would be
         * good as the packet type and remaining length was already read. Hence,
         * the probability of the remaining 2 bytes available to read is very high. */
        if( pIncomingPacket->type == MQTT_PACKET_TYPE_CONNACK )
        {
            status = receivePacket( pContext,
                                    *pIncomingPacket,
                                    remainingTimeMs );
        }
        else
        {
            LogError( ( "Incorrect packet type %X received while expecting"
                        " CONNACK(%X).",
                        ( unsigned int ) pIncomingPacket->type,
                        MQTT_PACKET_TYPE_CONNACK ) );
            status = MQTTBadResponse;
        }
    }
    if( status == MQTTSuccess )
    {
        /* Update the packet info pointer to the buffer read. */
        pIncomingPacket->pRemainingData = pContext->networkBuffer.pBuffer;
        /* Deserialize CONNACK. */
        status = MQTT_DeserializeAck( pIncomingPacket, NULL, pSessionPresent );
    }
    /* If a clean session is requested, a session present should not be set by
     * broker. */
    if( status == MQTTSuccess )
    {
        if( ( cleanSession == true ) && ( *pSessionPresent == true ) )
        {
            LogError( ( "Unexpected session present flag in CONNACK response from broker."
                        " CONNECT request with clean session was made with broker." ) );
            status = MQTTBadResponse;
        }
    }
    if( status == MQTTSuccess )
    {
        LogDebug( ( "Received MQTT CONNACK successfully from broker." ) );
    }
    else
    {
        LogError( ( "CONNACK recv failed with status = %s.",
                    MQTT_Status_strerror( status ) ) );
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t handleSessionResumption( MQTTContext_t * pContext,
                                             bool sessionPresent )
{
    MQTTStatus_t status = MQTTSuccess;
    MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER;
    uint16_t packetId = MQTT_PACKET_ID_INVALID;
    MQTTPublishState_t state = MQTTStateNull;
    assert( pContext != NULL );
    /* Reset the index and clear the buffer when a new session is established. */
    pContext->index = 0;
    ( void ) memset( pContext->networkBuffer.pBuffer, 0, pContext->networkBuffer.size );
    if( sessionPresent == true )
    {
        /* Get the next packet ID for which a PUBREL need to be resent. */
        packetId = MQTT_PubrelToResend( pContext, &cursor, &state );
        /* Resend all the PUBREL acks after session is reestablished. */
        while( ( packetId != MQTT_PACKET_ID_INVALID ) &&
               ( status == MQTTSuccess ) )
        {
            status = sendPublishAcks( pContext, packetId, state );
            packetId = MQTT_PubrelToResend( pContext, &cursor, &state );
        }
    }
    else
    {
        /* Clear any existing records if a new session is established. */
        if( pContext->outgoingPublishRecordMaxCount > 0U )
        {
            ( void ) memset( pContext->outgoingPublishRecords,
                             0x00,
                             pContext->outgoingPublishRecordMaxCount * sizeof( *pContext->outgoingPublishRecords ) );
        }
        if( pContext->incomingPublishRecordMaxCount > 0U )
        {
            ( void ) memset( pContext->incomingPublishRecords,
                             0x00,
                             pContext->incomingPublishRecordMaxCount * sizeof( *pContext->incomingPublishRecords ) );
        }
    }
    return status;
}
static MQTTStatus_t validatePublishParams( const MQTTContext_t * pContext,
                                           const MQTTPublishInfo_t * pPublishInfo,
                                           uint16_t packetId )
{
    MQTTStatus_t status = MQTTSuccess;
    /* Validate arguments. */
    if( ( pContext == NULL ) || ( pPublishInfo == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pContext=%p, "
                    "pPublishInfo=%p.",
                    ( void * ) pContext,
                    ( void * ) pPublishInfo ) );
        status = MQTTBadParameter;
    }
    else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) )
    {
        LogError( ( "Packet Id is 0 for PUBLISH with QoS=%u.",
                    ( unsigned int ) pPublishInfo->qos ) );
        status = MQTTBadParameter;
    }
    else if( ( pPublishInfo->payloadLength > 0U ) && ( pPublishInfo->pPayload == NULL ) )
    {
        LogError( ( "A nonzero payload length requires a non-NULL payload: "
                    "payloadLength=%lu, pPayload=%p.",
                    ( unsigned long ) pPublishInfo->payloadLength,
                    pPublishInfo->pPayload ) );
        status = MQTTBadParameter;
    }
    else if( ( pContext->outgoingPublishRecords == NULL ) && ( pPublishInfo->qos > MQTTQoS0 ) )
    {
        LogError( ( "Trying to publish a QoS > MQTTQoS0 packet when outgoing publishes "
                    "for QoS1/QoS2 have not been enabled. Please, call MQTT_InitStatefulQoS "
                    "to initialize and enable the use of QoS1/QoS2 publishes." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* MISRA else */
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_Init( MQTTContext_t * pContext,
                        const TransportInterface_t * pTransportInterface,
                        MQTTGetCurrentTimeFunc_t getTimeFunction,
                        MQTTEventCallback_t userCallback,
                        const MQTTFixedBuffer_t * pNetworkBuffer )
{
    MQTTStatus_t status = MQTTSuccess;
    /* Validate arguments. */
    if( ( pContext == NULL ) || ( pTransportInterface == NULL ) ||
        ( pNetworkBuffer == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pContext=%p, "
                    "pTransportInterface=%p, "
                    "pNetworkBuffer=%p",
                    ( void * ) pContext,
                    ( void * ) pTransportInterface,
                    ( void * ) pNetworkBuffer ) );
        status = MQTTBadParameter;
    }
    else if( getTimeFunction == NULL )
    {
        LogError( ( "Invalid parameter: getTimeFunction is NULL" ) );
        status = MQTTBadParameter;
    }
    else if( userCallback == NULL )
    {
        LogError( ( "Invalid parameter: userCallback is NULL" ) );
        status = MQTTBadParameter;
    }
    else if( pTransportInterface->recv == NULL )
    {
        LogError( ( "Invalid parameter: pTransportInterface->recv is NULL" ) );
        status = MQTTBadParameter;
    }
    else if( pTransportInterface->send == NULL )
    {
        LogError( ( "Invalid parameter: pTransportInterface->send is NULL" ) );
        status = MQTTBadParameter;
    }
    else
    {
        ( void ) memset( pContext, 0x00, sizeof( MQTTContext_t ) );
        pContext->connectStatus = MQTTNotConnected;
        pContext->transportInterface = *pTransportInterface;
        pContext->getTime = getTimeFunction;
        pContext->appCallback = userCallback;
        pContext->networkBuffer = *pNetworkBuffer;
        /* Zero is not a valid packet ID per MQTT spec. Start from 1. */
        pContext->nextPacketId = 1;
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_InitStatefulQoS( MQTTContext_t * pContext,
                                   MQTTPubAckInfo_t * pOutgoingPublishRecords,
                                   size_t outgoingPublishCount,
                                   MQTTPubAckInfo_t * pIncomingPublishRecords,
                                   size_t incomingPublishCount )
{
    MQTTStatus_t status = MQTTSuccess;
    if( pContext == NULL )
    {
        LogError( ( "Argument cannot be NULL: pContext=%p\n",
                    ( void * ) pContext ) );
        status = MQTTBadParameter;
    }
    /* Check whether the arguments make sense. Not equal here behaves
     * like an exclusive-or operator for boolean values. */
    else if( ( outgoingPublishCount == 0U ) !=
             ( pOutgoingPublishRecords == NULL ) )
    {
        LogError( ( "Arguments do not match: pOutgoingPublishRecords=%p, "
                    "outgoingPublishCount=%lu",
                    ( void * ) pOutgoingPublishRecords,
                    outgoingPublishCount ) );
        status = MQTTBadParameter;
    }
    /* Check whether the arguments make sense. Not equal here behaves
     * like an exclusive-or operator for boolean values. */
    else if( ( incomingPublishCount == 0U ) !=
             ( pIncomingPublishRecords == NULL ) )
    {
        LogError( ( "Arguments do not match: pIncomingPublishRecords=%p, "
                    "incomingPublishCount=%lu",
                    ( void * ) pIncomingPublishRecords,
                    incomingPublishCount ) );
        status = MQTTBadParameter;
    }
    else if( pContext->appCallback == NULL )
    {
        LogError( ( "MQTT_InitStatefulQoS must be called only after MQTT_Init has"
                    " been called succesfully.\n" ) );
        status = MQTTBadParameter;
    }
    else
    {
        pContext->incomingPublishRecordMaxCount = incomingPublishCount;
        pContext->incomingPublishRecords = pIncomingPublishRecords;
        pContext->outgoingPublishRecordMaxCount = outgoingPublishCount;
        pContext->outgoingPublishRecords = pOutgoingPublishRecords;
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_CancelCallback( const MQTTContext_t * pContext,
                                  uint16_t packetId )
{
    MQTTStatus_t status = MQTTSuccess;
    if( pContext == NULL )
    {
        LogWarn( ( "pContext is NULL\n" ) );
        status = MQTTBadParameter;
    }
    else if( pContext->outgoingPublishRecords == NULL )
    {
        LogError( ( "QoS1/QoS2 is not initialized for use. Please, "
                    "call MQTT_InitStatefulQoS to enable QoS1 and QoS2 "
                    "publishes.\n" ) );
        status = MQTTBadParameter;
    }
    else
    {
        MQTT_PRE_STATE_UPDATE_HOOK( pContext );
        status = MQTT_RemoveStateRecord( pContext,
                                         packetId );
        MQTT_POST_STATE_UPDATE_HOOK( pContext );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext,
                           const MQTTConnectInfo_t * pConnectInfo,
                           const MQTTPublishInfo_t * pWillInfo,
                           uint32_t timeoutMs,
                           bool * pSessionPresent )
{
    size_t remainingLength = 0UL, packetSize = 0UL;
    MQTTStatus_t status = MQTTSuccess;
    MQTTPacketInfo_t incomingPacket = { 0 };
    incomingPacket.type = ( uint8_t ) 0;
    if( ( pContext == NULL ) || ( pConnectInfo == NULL ) || ( pSessionPresent == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pContext=%p, "
                    "pConnectInfo=%p, pSessionPresent=%p.",
                    ( void * ) pContext,
                    ( void * ) pConnectInfo,
                    ( void * ) pSessionPresent ) );
        status = MQTTBadParameter;
    }
    if( status == MQTTSuccess )
    {
        /* Get MQTT connect packet size and remaining length. */
        status = MQTT_GetConnectPacketSize( pConnectInfo,
                                            pWillInfo,
                                            &remainingLength,
                                            &packetSize );
        LogDebug( ( "CONNECT packet size is %lu and remaining length is %lu.",
                    ( unsigned long ) packetSize,
                    ( unsigned long ) remainingLength ) );
    }
    if( status == MQTTSuccess )
    {
        MQTT_PRE_SEND_HOOK( pContext );
        status = sendConnectWithoutCopy( pContext,
                                         pConnectInfo,
                                         pWillInfo,
                                         remainingLength );
        MQTT_POST_SEND_HOOK( pContext );
    }
    /* Read CONNACK from transport layer. */
    if( status == MQTTSuccess )
    {
        status = receiveConnack( pContext,
                                 timeoutMs,
                                 pConnectInfo->cleanSession,
                                 &incomingPacket,
                                 pSessionPresent );
    }
    if( status == MQTTSuccess )
    {
        /* Resend PUBRELs when reestablishing a session, or clear records for new sessions. */
        status = handleSessionResumption( pContext, *pSessionPresent );
    }
    if( status == MQTTSuccess )
    {
        LogInfo( ( "MQTT connection established with the broker." ) );
        pContext->connectStatus = MQTTConnected;
        /* Initialize keep-alive fields after a successful connection. */
        pContext->keepAliveIntervalSec = pConnectInfo->keepAliveSeconds;
        pContext->waitingForPingResp = false;
        pContext->pingReqSendTimeMs = 0U;
    }
    else
    {
        LogError( ( "MQTT connection failed with status = %s.",
                    MQTT_Status_strerror( status ) ) );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext,
                             const MQTTSubscribeInfo_t * pSubscriptionList,
                             size_t subscriptionCount,
                             uint16_t packetId )
{
    size_t remainingLength = 0UL, packetSize = 0UL;
    /* Validate arguments. */
    MQTTStatus_t status = validateSubscribeUnsubscribeParams( pContext,
                                                              pSubscriptionList,
                                                              subscriptionCount,
                                                              packetId );
    if( status == MQTTSuccess )
    {
        /* Get the remaining length and packet size.*/
        status = MQTT_GetSubscribePacketSize( pSubscriptionList,
                                              subscriptionCount,
                                              &remainingLength,
                                              &packetSize );
        LogDebug( ( "SUBSCRIBE packet size is %lu and remaining length is %lu.",
                    ( unsigned long ) packetSize,
                    ( unsigned long ) remainingLength ) );
    }
    if( status == MQTTSuccess )
    {
        MQTT_PRE_SEND_HOOK( pContext );
        /* Send MQTT SUBSCRIBE packet. */
        status = sendSubscribeWithoutCopy( pContext,
                                           pSubscriptionList,
                                           subscriptionCount,
                                           packetId,
                                           remainingLength );
        MQTT_POST_SEND_HOOK( pContext );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext,
                           const MQTTPublishInfo_t * pPublishInfo,
                           uint16_t packetId )
{
    size_t headerSize = 0UL;
    size_t remainingLength = 0UL;
    size_t packetSize = 0UL;
    MQTTPublishState_t publishStatus = MQTTStateNull;
    bool stateUpdateHookExecuted = false;
    /* 1 header byte + 4 bytes (maximum) required for encoding the length +
     * 2 bytes for topic string. */
    uint8_t mqttHeader[ 7 ];
    /* Validate arguments. */
    MQTTStatus_t status = validatePublishParams( pContext, pPublishInfo, packetId );
    if( status == MQTTSuccess )
    {
        /* Get the remaining length and packet size.*/
        status = MQTT_GetPublishPacketSize( pPublishInfo,
                                            &remainingLength,
                                            &packetSize );
    }
    if( status == MQTTSuccess )
    {
        status = MQTT_SerializePublishHeaderWithoutTopic( pPublishInfo,
                                                          remainingLength,
                                                          mqttHeader,
                                                          &headerSize );
    }
    if( ( status == MQTTSuccess ) && ( pPublishInfo->qos > MQTTQoS0 ) )
    {
        MQTT_PRE_STATE_UPDATE_HOOK( pContext );
        /* Set the flag so that the corresponding hook can be called later. */
        stateUpdateHookExecuted = true;
        status = MQTT_ReserveState( pContext,
                                    packetId,
                                    pPublishInfo->qos );
        /* State already exists for a duplicate packet.
         * If a state doesn't exist, it will be handled as a new publish in
         * state engine. */
        if( ( status == MQTTStateCollision ) && ( pPublishInfo->dup == true ) )
        {
            status = MQTTSuccess;
        }
    }
    if( status == MQTTSuccess )
    {
        /* Take the mutex as multiple send calls are required for sending this
         * packet. */
        MQTT_PRE_SEND_HOOK( pContext );
        status = sendPublishWithoutCopy( pContext,
                                         pPublishInfo,
                                         mqttHeader,
                                         headerSize,
                                         packetId );
        /* Give the mutex away for the next taker. */
        MQTT_POST_SEND_HOOK( pContext );
    }
    if( ( status == MQTTSuccess ) &&
        ( pPublishInfo->qos > MQTTQoS0 ) )
    {
        /* Update state machine after PUBLISH is sent.
         * Only to be done for QoS1 or QoS2. */
        status = MQTT_UpdateStatePublish( pContext,
                                          packetId,
                                          MQTT_SEND,
                                          pPublishInfo->qos,
                                          &publishStatus );
        if( status != MQTTSuccess )
        {
            LogError( ( "Update state for publish failed with status %s."
                        " However PUBLISH packet was sent to the broker."
                        " Any further handling of ACKs for the packet Id"
                        " will fail.",
                        MQTT_Status_strerror( status ) ) );
        }
    }
    if( stateUpdateHookExecuted == true )
    {
        /* Regardless of the status, if the mutex was taken due to the
         * packet being of QoS > QoS0, then it should be relinquished. */
        MQTT_POST_STATE_UPDATE_HOOK( pContext );
    }
    if( status != MQTTSuccess )
    {
        LogError( ( "MQTT PUBLISH failed with status %s.",
                    MQTT_Status_strerror( status ) ) );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext )
{
    int32_t sendResult = 0;
    MQTTStatus_t status = MQTTSuccess;
    size_t packetSize = 0U;
    /* MQTT ping packets are of fixed length. */
    uint8_t pingreqPacket[ 2U ];
    MQTTFixedBuffer_t localBuffer;
    localBuffer.pBuffer = pingreqPacket;
    localBuffer.size = 2U;
    if( pContext == NULL )
    {
        LogError( ( "pContext is NULL." ) );
        status = MQTTBadParameter;
    }
    if( status == MQTTSuccess )
    {
        /* Get MQTT PINGREQ packet size. */
        status = MQTT_GetPingreqPacketSize( &packetSize );
        if( status == MQTTSuccess )
        {
            LogDebug( ( "MQTT PINGREQ packet size is %lu.",
                        ( unsigned long ) packetSize ) );
        }
        else
        {
            LogError( ( "Failed to get the PINGREQ packet size." ) );
        }
    }
    if( status == MQTTSuccess )
    {
        /* Serialize MQTT PINGREQ. */
        status = MQTT_SerializePingreq( &localBuffer );
    }
    if( status == MQTTSuccess )
    {
        /* Take the mutex as the send call should not be interrupted in
         * between. */
        MQTT_PRE_SEND_HOOK( pContext );
        /* Send the serialized PINGREQ packet to transport layer.
         * Here, we do not use the vectored IO approach for efficiency as the
         * Ping packet does not have numerous fields which need to be copied
         * from the user provided buffers. Thus it can be sent directly. */
        sendResult = sendBuffer( pContext,
                                 localBuffer.pBuffer,
                                 2U );
        /* Give the mutex away. */
        MQTT_POST_SEND_HOOK( pContext );
        /* It is an error to not send the entire PINGREQ packet. */
        if( sendResult < ( int32_t ) packetSize )
        {
            LogError( ( "Transport send failed for PINGREQ packet." ) );
            status = MQTTSendFailed;
        }
        else
        {
            pContext->pingReqSendTimeMs = pContext->lastPacketTxTime;
            pContext->waitingForPingResp = true;
            LogDebug( ( "Sent %ld bytes of PINGREQ packet.",
                        ( long int ) sendResult ) );
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext,
                               const MQTTSubscribeInfo_t * pSubscriptionList,
                               size_t subscriptionCount,
                               uint16_t packetId )
{
    size_t remainingLength = 0UL, packetSize = 0UL;
    /* Validate arguments. */
    MQTTStatus_t status = validateSubscribeUnsubscribeParams( pContext,
                                                              pSubscriptionList,
                                                              subscriptionCount,
                                                              packetId );
    if( status == MQTTSuccess )
    {
        /* Get the remaining length and packet size.*/
        status = MQTT_GetUnsubscribePacketSize( pSubscriptionList,
                                                subscriptionCount,
                                                &remainingLength,
                                                &packetSize );
        LogDebug( ( "UNSUBSCRIBE packet size is %lu and remaining length is %lu.",
                    ( unsigned long ) packetSize,
                    ( unsigned long ) remainingLength ) );
    }
    if( status == MQTTSuccess )
    {
        /* Take the mutex because the below call should not be interrupted. */
        MQTT_PRE_SEND_HOOK( pContext );
        status = sendUnsubscribeWithoutCopy( pContext,
                                             pSubscriptionList,
                                             subscriptionCount,
                                             packetId,
                                             remainingLength );
        /* Give the mutex away. */
        MQTT_POST_SEND_HOOK( pContext );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext )
{
    size_t packetSize = 0U;
    int32_t sendResult = 0;
    MQTTStatus_t status = MQTTSuccess;
    MQTTFixedBuffer_t localBuffer;
    uint8_t disconnectPacket[ 2U ];
    localBuffer.pBuffer = disconnectPacket;
    localBuffer.size = 2U;
    /* Validate arguments. */
    if( pContext == NULL )
    {
        LogError( ( "pContext cannot be NULL." ) );
        status = MQTTBadParameter;
    }
    if( status == MQTTSuccess )
    {
        /* Get MQTT DISCONNECT packet size. */
        status = MQTT_GetDisconnectPacketSize( &packetSize );
        LogDebug( ( "MQTT DISCONNECT packet size is %lu.",
                    ( unsigned long ) packetSize ) );
    }
    if( status == MQTTSuccess )
    {
        /* Serialize MQTT DISCONNECT packet. */
        status = MQTT_SerializeDisconnect( &localBuffer );
    }
    if( status == MQTTSuccess )
    {
        /* Take the mutex because the below call should not be interrupted. */
        MQTT_PRE_SEND_HOOK( pContext );
        /* Here we do not use vectors as the disconnect packet has fixed fields
         * which do not reside in user provided buffers. Thus, it can be sent
         * using a simple send call. */
        sendResult = sendBuffer( pContext,
                                 localBuffer.pBuffer,
                                 packetSize );
        /* Give the mutex away. */
        MQTT_POST_SEND_HOOK( pContext );
        if( sendResult < ( int32_t ) packetSize )
        {
            LogError( ( "Transport send failed for DISCONNECT packet." ) );
            status = MQTTSendFailed;
        }
        else
        {
            LogDebug( ( "Sent %ld bytes of DISCONNECT packet.",
                        ( long int ) sendResult ) );
        }
    }
    if( status == MQTTSuccess )
    {
        LogInfo( ( "Disconnected from the broker." ) );
        pContext->connectStatus = MQTTNotConnected;
        /* Reset the index and clean the buffer on a successful disconnect. */
        pContext->index = 0;
        ( void ) memset( pContext->networkBuffer.pBuffer, 0, pContext->networkBuffer.size );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext )
{
    MQTTStatus_t status = MQTTBadParameter;
    if( pContext == NULL )
    {
        LogError( ( "Invalid input parameter: MQTT Context cannot be NULL." ) );
    }
    else if( pContext->getTime == NULL )
    {
        LogError( ( "Invalid input parameter: MQTT Context must have valid getTime." ) );
    }
    else if( pContext->networkBuffer.pBuffer == NULL )
    {
        LogError( ( "Invalid input parameter: The MQTT context's networkBuffer must not be NULL." ) );
    }
    else
    {
        pContext->controlPacketSent = false;
        status = receiveSingleIteration( pContext, true );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext )
{
    MQTTStatus_t status = MQTTBadParameter;
    if( pContext == NULL )
    {
        LogError( ( "Invalid input parameter: MQTT Context cannot be NULL." ) );
    }
    else if( pContext->getTime == NULL )
    {
        LogError( ( "Invalid input parameter: MQTT Context must have a valid getTime function." ) );
    }
    else if( pContext->networkBuffer.pBuffer == NULL )
    {
        LogError( ( "Invalid input parameter: MQTT context's networkBuffer must not be NULL." ) );
    }
    else
    {
        status = receiveSingleIteration( pContext, false );
    }
    return status;
}
/*-----------------------------------------------------------*/
uint16_t MQTT_GetPacketId( MQTTContext_t * pContext )
{
    uint16_t packetId = 0U;
    if( pContext != NULL )
    {
        MQTT_PRE_STATE_UPDATE_HOOK( pContext );
        packetId = pContext->nextPacketId;
        /* A packet ID of zero is not a valid packet ID. When the max ID
         * is reached the next one should start at 1. */
        if( pContext->nextPacketId == ( uint16_t ) UINT16_MAX )
        {
            pContext->nextPacketId = 1;
        }
        else
        {
            pContext->nextPacketId++;
        }
        MQTT_POST_STATE_UPDATE_HOOK( pContext );
    }
    return packetId;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_MatchTopic( const char * pTopicName,
                              const uint16_t topicNameLength,
                              const char * pTopicFilter,
                              const uint16_t topicFilterLength,
                              bool * pIsMatch )
{
    MQTTStatus_t status = MQTTSuccess;
    bool topicFilterStartsWithWildcard = false;
    bool matchStatus = false;
    if( ( pTopicName == NULL ) || ( topicNameLength == 0u ) )
    {
        LogError( ( "Invalid paramater: Topic name should be non-NULL and its "
                    "length should be > 0: TopicName=%p, TopicNameLength=%hu",
                    ( void * ) pTopicName,
                    ( unsigned short ) topicNameLength ) );
        status = MQTTBadParameter;
    }
    else if( ( pTopicFilter == NULL ) || ( topicFilterLength == 0u ) )
    {
        LogError( ( "Invalid paramater: Topic filter should be non-NULL and "
                    "its length should be > 0: TopicName=%p, TopicFilterLength=%hu",
                    ( void * ) pTopicFilter,
                    ( unsigned short ) topicFilterLength ) );
        status = MQTTBadParameter;
    }
    else if( pIsMatch == NULL )
    {
        LogError( ( "Invalid paramater: Output parameter, pIsMatch, is NULL" ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Check for an exact match if the incoming topic name and the registered
         * topic filter length match. */
        if( topicNameLength == topicFilterLength )
        {
            matchStatus = strncmp( pTopicName, pTopicFilter, topicNameLength ) == 0;
        }
        if( matchStatus == false )
        {
            /* If an exact match was not found, match against wildcard characters in
             * topic filter.*/
            /* Determine if topic filter starts with a wildcard. */
            topicFilterStartsWithWildcard = ( pTopicFilter[ 0 ] == '+' ) ||
                                            ( pTopicFilter[ 0 ] == '#' );
            /* Note: According to the MQTT 3.1.1 specification, incoming PUBLISH topic names
             * starting with "$" character cannot be matched against topic filter starting with
             * a wildcard, i.e. for example, "$SYS/sport" cannot be matched with "#" or
             * "+/sport" topic filters. */
            if( !( ( pTopicName[ 0 ] == '$' ) && ( topicFilterStartsWithWildcard == true ) ) )
            {
                matchStatus = matchTopicFilter( pTopicName, topicNameLength, pTopicFilter, topicFilterLength );
            }
        }
        /* Update the output parameter with the match result. */
        *pIsMatch = matchStatus;
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket,
                                        uint8_t ** pPayloadStart,
                                        size_t * pPayloadSize )
{
    MQTTStatus_t status = MQTTSuccess;
    if( pSubackPacket == NULL )
    {
        LogError( ( "Invalid parameter: pSubackPacket is NULL." ) );
        status = MQTTBadParameter;
    }
    else if( pPayloadStart == NULL )
    {
        LogError( ( "Invalid parameter: pPayloadStart is NULL." ) );
        status = MQTTBadParameter;
    }
    else if( pPayloadSize == NULL )
    {
        LogError( ( "Invalid parameter: pPayloadSize is NULL." ) );
        status = MQTTBadParameter;
    }
    else if( pSubackPacket->type != MQTT_PACKET_TYPE_SUBACK )
    {
        LogError( ( "Invalid parameter: Input packet is not a SUBACK packet: "
                    "ExpectedType=%02x, InputType=%02x",
                    ( int ) MQTT_PACKET_TYPE_SUBACK,
                    ( int ) pSubackPacket->type ) );
        status = MQTTBadParameter;
    }
    else if( pSubackPacket->pRemainingData == NULL )
    {
        LogError( ( "Invalid parameter: pSubackPacket->pRemainingData is NULL" ) );
        status = MQTTBadParameter;
    }
    /* A SUBACK must have a remaining length of at least 3 to accommodate the
     * packet identifier and at least 1 return code. */
    else if( pSubackPacket->remainingLength < 3U )
    {
        LogError( ( "Invalid parameter: Packet remaining length is invalid: "
                    "Should be greater than 2 for SUBACK packet: InputRemainingLength=%lu",
                    ( unsigned long ) pSubackPacket->remainingLength ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* According to the MQTT 3.1.1 protocol specification, the "Remaining Length" field is a
         * length of the variable header (2 bytes) plus the length of the payload.
         * Therefore, we add 2 positions for the starting address of the payload, and
         * subtract 2 bytes from the remaining length for the length of the payload.*/
        *pPayloadStart = &pSubackPacket->pRemainingData[ sizeof( uint16_t ) ];
        *pPayloadSize = pSubackPacket->remainingLength - sizeof( uint16_t );
    }
    return status;
}
/*-----------------------------------------------------------*/
const char * MQTT_Status_strerror( MQTTStatus_t status )
{
    const char * str = NULL;
    switch( status )
    {
        case MQTTSuccess:
            str = "MQTTSuccess";
            break;
        case MQTTBadParameter:
            str = "MQTTBadParameter";
            break;
        case MQTTNoMemory:
            str = "MQTTNoMemory";
            break;
        case MQTTSendFailed:
            str = "MQTTSendFailed";
            break;
        case MQTTRecvFailed:
            str = "MQTTRecvFailed";
            break;
        case MQTTBadResponse:
            str = "MQTTBadResponse";
            break;
        case MQTTServerRefused:
            str = "MQTTServerRefused";
            break;
        case MQTTNoDataAvailable:
            str = "MQTTNoDataAvailable";
            break;
        case MQTTIllegalState:
            str = "MQTTIllegalState";
            break;
        case MQTTStateCollision:
            str = "MQTTStateCollision";
            break;
        case MQTTKeepAliveTimeout:
            str = "MQTTKeepAliveTimeout";
            break;
        default:
            str = "Invalid MQTT Status code";
            break;
    }
    return str;
}
/*-----------------------------------------------------------*/
project/coreMQTT/coreMQTT/core_mqtt.h
New file
@@ -0,0 +1,1015 @@
/*
 * coreMQTT v2.1.1
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * @file core_mqtt.h
 * @brief User-facing functions of the MQTT 3.1.1 library.
 */
#ifndef CORE_MQTT_H
#define CORE_MQTT_H
/* *INDENT-OFF* */
#ifdef __cplusplus
    extern "C" {
#endif
/* *INDENT-ON* */
/* Include MQTT serializer library. */
#include "core_mqtt_serializer.h"
/* Include transport interface. */
#include "transport_interface.h"
/**
 * @cond DOXYGEN_IGNORE
 * The current version of this library.
 */
#define MQTT_LIBRARY_VERSION    "v2.1.1"
/** @endcond */
/**
 * @ingroup mqtt_constants
 * @brief Invalid packet identifier.
 *
 * Zero is an invalid packet identifier as per MQTT v3.1.1 spec.
 */
#define MQTT_PACKET_ID_INVALID    ( ( uint16_t ) 0U )
/* Structures defined in this file. */
struct MQTTPubAckInfo;
struct MQTTContext;
struct MQTTDeserializedInfo;
/**
 * @ingroup mqtt_callback_types
 * @brief Application provided function to query the time elapsed since a given
 * epoch in milliseconds.
 *
 * @note The timer should be a monotonic timer. It just needs to provide an
 * incrementing count of milliseconds elapsed since a given epoch.
 *
 * @return The time elapsed in milliseconds.
 */
typedef uint32_t (* MQTTGetCurrentTimeFunc_t )( void );
/**
 * @ingroup mqtt_callback_types
 * @brief Application callback for receiving incoming publishes and incoming
 * acks.
 *
 * @note This callback will be called only if packets are deserialized with a
 * result of #MQTTSuccess or #MQTTServerRefused. The latter can be obtained
 * when deserializing a SUBACK, indicating a broker's rejection of a subscribe.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] pPacketInfo Information on the type of incoming MQTT packet.
 * @param[in] pDeserializedInfo Deserialized information from incoming packet.
 */
typedef void (* MQTTEventCallback_t )( struct MQTTContext * pContext,
                                       struct MQTTPacketInfo * pPacketInfo,
                                       struct MQTTDeserializedInfo * pDeserializedInfo );
/**
 * @ingroup mqtt_enum_types
 * @brief Values indicating if an MQTT connection exists.
 */
typedef enum MQTTConnectionStatus
{
    MQTTNotConnected, /**< @brief MQTT Connection is inactive. */
    MQTTConnected     /**< @brief MQTT Connection is active. */
} MQTTConnectionStatus_t;
/**
 * @ingroup mqtt_enum_types
 * @brief The state of QoS 1 or QoS 2 MQTT publishes, used in the state engine.
 */
typedef enum MQTTPublishState
{
    MQTTStateNull = 0,  /**< @brief An empty state with no corresponding PUBLISH. */
    MQTTPublishSend,    /**< @brief The library will send an outgoing PUBLISH packet. */
    MQTTPubAckSend,     /**< @brief The library will send a PUBACK for a received PUBLISH. */
    MQTTPubRecSend,     /**< @brief The library will send a PUBREC for a received PUBLISH. */
    MQTTPubRelSend,     /**< @brief The library will send a PUBREL for a received PUBREC. */
    MQTTPubCompSend,    /**< @brief The library will send a PUBCOMP for a received PUBREL. */
    MQTTPubAckPending,  /**< @brief The library is awaiting a PUBACK for an outgoing PUBLISH. */
    MQTTPubRecPending,  /**< @brief The library is awaiting a PUBREC for an outgoing PUBLISH. */
    MQTTPubRelPending,  /**< @brief The library is awaiting a PUBREL for an incoming PUBLISH. */
    MQTTPubCompPending, /**< @brief The library is awaiting a PUBCOMP for an outgoing PUBLISH. */
    MQTTPublishDone     /**< @brief The PUBLISH has been completed. */
} MQTTPublishState_t;
/**
 * @ingroup mqtt_enum_types
 * @brief Packet types used in acknowledging QoS 1 or QoS 2 publishes.
 */
typedef enum MQTTPubAckType
{
    MQTTPuback, /**< @brief PUBACKs are sent in response to a QoS 1 PUBLISH. */
    MQTTPubrec, /**< @brief PUBRECs are sent in response to a QoS 2 PUBLISH. */
    MQTTPubrel, /**< @brief PUBRELs are sent in response to a PUBREC. */
    MQTTPubcomp /**< @brief PUBCOMPs are sent in response to a PUBREL. */
} MQTTPubAckType_t;
/**
 * @ingroup mqtt_enum_types
 * @brief The status codes in the SUBACK response to a subscription request.
 */
typedef enum MQTTSubAckStatus
{
    MQTTSubAckSuccessQos0 = 0x00, /**< @brief Success with a maximum delivery at QoS 0. */
    MQTTSubAckSuccessQos1 = 0x01, /**< @brief Success with a maximum delivery at QoS 1. */
    MQTTSubAckSuccessQos2 = 0x02, /**< @brief Success with a maximum delivery at QoS 2. */
    MQTTSubAckFailure = 0x80      /**< @brief Failure. */
} MQTTSubAckStatus_t;
/**
 * @ingroup mqtt_struct_types
 * @brief An element of the state engine records for QoS 1 or Qos 2 publishes.
 */
typedef struct MQTTPubAckInfo
{
    uint16_t packetId;               /**< @brief The packet ID of the original PUBLISH. */
    MQTTQoS_t qos;                   /**< @brief The QoS of the original PUBLISH. */
    MQTTPublishState_t publishState; /**< @brief The current state of the publish process. */
} MQTTPubAckInfo_t;
/**
 * @ingroup mqtt_struct_types
 * @brief A struct representing an MQTT connection.
 */
typedef struct MQTTContext
{
    /**
     * @brief State engine records for outgoing publishes.
     */
    MQTTPubAckInfo_t * outgoingPublishRecords;
    /**
     * @brief State engine records for incoming publishes.
     */
    MQTTPubAckInfo_t * incomingPublishRecords;
    /**
     * @brief The maximum number of outgoing publish records.
     */
    size_t outgoingPublishRecordMaxCount;
    /**
     * @brief The maximum number of incoming publish records.
     */
    size_t incomingPublishRecordMaxCount;
    /**
     * @brief The transport interface used by the MQTT connection.
     */
    TransportInterface_t transportInterface;
    /**
     * @brief The buffer used in sending and receiving packets from the network.
     */
    MQTTFixedBuffer_t networkBuffer;
    /**
     * @brief The next available ID for outgoing MQTT packets.
     */
    uint16_t nextPacketId;
    /**
     * @brief Whether the context currently has a connection to the broker.
     */
    MQTTConnectionStatus_t connectStatus;
    /**
     * @brief Function used to get millisecond timestamps.
     */
    MQTTGetCurrentTimeFunc_t getTime;
    /**
     * @brief Callback function used to give deserialized MQTT packets to the application.
     */
    MQTTEventCallback_t appCallback;
    /**
     * @brief Timestamp of the last packet sent by the library.
     */
    uint32_t lastPacketTxTime;
    /**
     * @brief Timestamp of the last packet received by the library.
     */
    uint32_t lastPacketRxTime;
    /**
     * @brief Whether the library sent a packet during a call of #MQTT_ProcessLoop or
     * #MQTT_ReceiveLoop.
     */
    bool controlPacketSent;
    /**
     * @brief Index to keep track of the number of bytes received in network buffer.
     */
    size_t index;
    /* Keep alive members. */
    uint16_t keepAliveIntervalSec; /**< @brief Keep Alive interval. */
    uint32_t pingReqSendTimeMs;    /**< @brief Timestamp of the last sent PINGREQ. */
    bool waitingForPingResp;       /**< @brief If the library is currently awaiting a PINGRESP. */
} MQTTContext_t;
/**
 * @ingroup mqtt_struct_types
 * @brief Struct to hold deserialized packet information for an #MQTTEventCallback_t
 * callback.
 */
typedef struct MQTTDeserializedInfo
{
    uint16_t packetIdentifier;          /**< @brief Packet ID of deserialized packet. */
    MQTTPublishInfo_t * pPublishInfo;   /**< @brief Pointer to deserialized publish info. */
    MQTTStatus_t deserializationResult; /**< @brief Return code of deserialization. */
} MQTTDeserializedInfo_t;
/**
 * @brief Initialize an MQTT context.
 *
 * This function must be called on an #MQTTContext_t before any other function.
 *
 * @note The #MQTTGetCurrentTimeFunc_t function for querying time must be defined. If
 * there is no time implementation, it is the responsibility of the application
 * to provide a dummy function to always return 0, provide 0 timeouts for
 * all calls to #MQTT_Connect, #MQTT_ProcessLoop, and #MQTT_ReceiveLoop and configure
 * the #MQTT_RECV_POLLING_TIMEOUT_MS and #MQTT_SEND_TIMEOUT_MS configurations
 * to be 0. This will result in loop functions running for a single iteration, and
 * #MQTT_Connect relying on #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT to receive the CONNACK packet.
 *
 * @param[in] pContext The context to initialize.
 * @param[in] pTransportInterface The transport interface to use with the context.
 * @param[in] getTimeFunction The time utility function which can return the amount of time
 *    (in milliseconds) elapsed since a given epoch. This function will be used to ensure that
 *    timeouts in the API calls are met and keep-alive messages are sent on time.
 * @param[in] userCallback The user callback to use with the context to notify about incoming
 *     packet events.
 * @param[in] pNetworkBuffer Network buffer provided for the context. This buffer will be used
 *     to receive incoming messages from the broker. This buffer must remain valid and in scope
 *     for the entire lifetime of the @p pContext and must not be used by another context and/or
 *     application.
 *
 * @return #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Function for obtaining a timestamp.
 * uint32_t getTimeStampMs();
 * // Callback function for receiving packets.
 * void eventCallback(
 *      MQTTContext_t * pContext,
 *      MQTTPacketInfo_t * pPacketInfo,
 *      MQTTDeserializedInfo_t * pDeserializedInfo
 * );
 * // Network send.
 * int32_t networkSend( NetworkContext_t * pContext, const void * pBuffer, size_t bytes );
 * // Network receive.
 * int32_t networkRecv( NetworkContext_t * pContext, void * pBuffer, size_t bytes );
 *
 * MQTTContext_t mqttContext;
 * TransportInterface_t transport;
 * MQTTFixedBuffer_t fixedBuffer;
 * // Create a globally accessible buffer which remains in scope for the entire duration
 * // of the MQTT context.
 * uint8_t buffer[ 1024 ];
 *
 * // Clear context.
 * memset( ( void * ) &mqttContext, 0x00, sizeof( MQTTContext_t ) );
 *
 * // Set transport interface members.
 * transport.pNetworkContext = &someTransportContext;
 * transport.send = networkSend;
 * transport.recv = networkRecv;
 *
 * // Set buffer members.
 * fixedBuffer.pBuffer = buffer;
 * fixedBuffer.size = 1024;
 *
 * status = MQTT_Init( &mqttContext, &transport, getTimeStampMs, eventCallback, &fixedBuffer );
 *
 * if( status == MQTTSuccess )
 * {
 *      // Do something with mqttContext. The transport and fixedBuffer structs were
 *      // copied into the context, so the original structs do not need to stay in scope.
 *      // However, the memory pointed to by the fixedBuffer.pBuffer must remain in scope.
 * }
 * @endcode
 */
/* @[declare_mqtt_init] */
MQTTStatus_t MQTT_Init( MQTTContext_t * pContext,
                        const TransportInterface_t * pTransportInterface,
                        MQTTGetCurrentTimeFunc_t getTimeFunction,
                        MQTTEventCallback_t userCallback,
                        const MQTTFixedBuffer_t * pNetworkBuffer );
/* @[declare_mqtt_init] */
/**
 * @brief Initialize an MQTT context for QoS > 0.
 *
 * This function must be called on an #MQTTContext_t after MQTT_Init and before any other function.
 *
 * @param[in] pContext The context to initialize.
 * @param[in] pOutgoingPublishRecords Pointer to memory which will be used to store state of outgoing
 * publishes.
 * @param[in] outgoingPublishCount Maximum number of records which can be kept in the memory
 * pointed to by @p pOutgoingPublishRecords.
 * @param[in] pIncomingPublishRecords Pointer to memory which will be used to store state of incoming
 * publishes.
 * @param[in] incomingPublishCount Maximum number of records which can be kept in the memory
 * pointed to by @p pIncomingPublishRecords.
 *
 * @return #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Function for obtaining a timestamp.
 * uint32_t getTimeStampMs();
 * // Callback function for receiving packets.
 * void eventCallback(
 *      MQTTContext_t * pContext,
 *      MQTTPacketInfo_t * pPacketInfo,
 *      MQTTDeserializedInfo_t * pDeserializedInfo
 * );
 * // Network send.
 * int32_t networkSend( NetworkContext_t * pContext, const void * pBuffer, size_t bytes );
 * // Network receive.
 * int32_t networkRecv( NetworkContext_t * pContext, void * pBuffer, size_t bytes );
 *
 * MQTTContext_t mqttContext;
 * TransportInterface_t transport;
 * MQTTFixedBuffer_t fixedBuffer;
 * uint8_t buffer[ 1024 ];
 * const size_t outgoingPublishCount = 30;
 * MQTTPubAckInfo_t outgoingPublishes[ outgoingPublishCount ];
 *
 * // Clear context.
 * memset( ( void * ) &mqttContext, 0x00, sizeof( MQTTContext_t ) );
 *
 * // Set transport interface members.
 * transport.pNetworkContext = &someTransportContext;
 * transport.send = networkSend;
 * transport.recv = networkRecv;
 *
 * // Set buffer members.
 * fixedBuffer.pBuffer = buffer;
 * fixedBuffer.size = 1024;
 *
 * status = MQTT_Init( &mqttContext, &transport, getTimeStampMs, eventCallback, &fixedBuffer );
 *
 * if( status == MQTTSuccess )
 * {
 *      // We do not expect any incoming publishes in this example, therefore the incoming
 *      // publish pointer is NULL and the count is zero.
 *      status = MQTT_InitStatefulQoS( &mqttContext, outgoingPublishes, outgoingPublishCount, NULL, 0 );
 *
 *      // Now QoS1 and/or QoS2 publishes can be sent with this context.
 * }
 * @endcode
 */
/* @[declare_mqtt_initstatefulqos] */
MQTTStatus_t MQTT_InitStatefulQoS( MQTTContext_t * pContext,
                                   MQTTPubAckInfo_t * pOutgoingPublishRecords,
                                   size_t outgoingPublishCount,
                                   MQTTPubAckInfo_t * pIncomingPublishRecords,
                                   size_t incomingPublishCount );
/* @[declare_mqtt_initstatefulqos] */
/**
 * @brief Establish an MQTT session.
 *
 * This function will send MQTT CONNECT packet and receive a CONNACK packet. The
 * send and receive from the network is done through the transport interface.
 *
 * The maximum time this function waits for a CONNACK is decided in one of the
 * following ways:
 * 1. If @p timeoutMs is greater than 0:
 *    #MQTTContext_t.getTime is used to ensure that the function does not wait
 *    more than @p timeoutMs for CONNACK.
 * 2. If @p timeoutMs is 0:
 *    The network receive for CONNACK is retried up to the number of times
 *    configured by #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT.
 *
 * @note If a dummy #MQTTGetCurrentTimeFunc_t was passed to #MQTT_Init, then a
 * timeout value of 0 MUST be passed to the API, and the #MQTT_RECV_POLLING_TIMEOUT_MS
 * and #MQTT_SEND_TIMEOUT_MS timeout configurations MUST be set to 0.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] pConnectInfo MQTT CONNECT packet information.
 * @param[in] pWillInfo Last Will and Testament. Pass NULL if Last Will and
 * Testament is not used.
 * @param[in] timeoutMs Maximum time in milliseconds to wait for a CONNACK packet.
 * A zero timeout makes use of the retries for receiving CONNACK as configured with
 * #MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT.
 * @param[out] pSessionPresent This value will be set to true if a previous session
 * was present; otherwise it will be set to false. It is only relevant if not
 * establishing a clean session.
 *
 * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to
 * hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSendFailed if transport send failed;
 * #MQTTRecvFailed if transport receive failed for CONNACK;
 * #MQTTNoDataAvailable if no data available to receive in transport until
 * the @p timeoutMs for CONNACK;
 * #MQTTSuccess otherwise.
 *
 * @note This API may spend more time than provided in the timeoutMS parameters in
 * certain conditions as listed below:
 *
 * 1. Timeouts are incorrectly configured - If the timeoutMS is less than the
 *    transport receive timeout and if a CONNACK packet is not received within
 *    the transport receive timeout, the API will spend the transport receive
 *    timeout (which is more time than the timeoutMs). It is the case of incorrect
 *    timeout configuration as the timeoutMs parameter passed to this API must be
 *    greater than the transport receive timeout. Please refer to the transport
 *    interface documentation for more details about timeout configurations.
 *
 * 2. Partial CONNACK packet is received right before the expiry of the timeout - It
 *    is possible that first two bytes of CONNACK packet (packet type and remaining
 *    length) are received right before the expiry of the timeoutMS. In that case,
 *    the API makes one more network receive call in an attempt to receive the remaining
 *    2 bytes. In the worst case, it can happen that the remaining 2 bytes are never
 *    received and this API will end up spending timeoutMs + transport receive timeout.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTConnectInfo_t connectInfo = { 0 };
 * MQTTPublishInfo_t willInfo = { 0 };
 * bool sessionPresent;
 * // This is assumed to have been initialized before calling this function.
 * MQTTContext_t * pContext;
 *
 * // True for creating a new session with broker, false if we want to resume an old one.
 * connectInfo.cleanSession = true;
 * // Client ID must be unique to broker. This field is required.
 * connectInfo.pClientIdentifier = "someClientID";
 * connectInfo.clientIdentifierLength = strlen( connectInfo.pClientIdentifier );
 *
 * // The following fields are optional.
 * // Value for keep alive.
 * connectInfo.keepAliveSeconds = 60;
 * // Optional username and password.
 * connectInfo.pUserName = "someUserName";
 * connectInfo.userNameLength = strlen( connectInfo.pUserName );
 * connectInfo.pPassword = "somePassword";
 * connectInfo.passwordLength = strlen( connectInfo.pPassword );
 *
 * // The last will and testament is optional, it will be published by the broker
 * // should this client disconnect without sending a DISCONNECT packet.
 * willInfo.qos = MQTTQoS0;
 * willInfo.pTopicName = "/lwt/topic/name";
 * willInfo.topicNameLength = strlen( willInfo.pTopicName );
 * willInfo.pPayload = "LWT Message";
 * willInfo.payloadLength = strlen( "LWT Message" );
 *
 * // Send the connect packet. Use 100 ms as the timeout to wait for the CONNACK packet.
 * status = MQTT_Connect( pContext, &connectInfo, &willInfo, 100, &sessionPresent );
 *
 * if( status == MQTTSuccess )
 * {
 *      // Since we requested a clean session, this must be false
 *      assert( sessionPresent == false );
 *
 *      // Do something with the connection.
 * }
 * @endcode
 */
/* @[declare_mqtt_connect] */
MQTTStatus_t MQTT_Connect( MQTTContext_t * pContext,
                           const MQTTConnectInfo_t * pConnectInfo,
                           const MQTTPublishInfo_t * pWillInfo,
                           uint32_t timeoutMs,
                           bool * pSessionPresent );
/* @[declare_mqtt_connect] */
/**
 * @brief Sends MQTT SUBSCRIBE for the given list of topic filters to
 * the broker.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] pSubscriptionList Array of MQTT subscription info.
 * @param[in] subscriptionCount The number of elements in @ pSubscriptionList
 * array.
 * @param[in] packetId Packet ID generated by #MQTT_GetPacketId.
 *
 * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to
 * hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSendFailed if transport write failed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
 * uint16_t packetId;
 * // This context is assumed to be initialized and connected.
 * MQTTContext_t * pContext;
 * // This is assumed to be a list of filters we want to subscribe to.
 * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ];
 *
 * // Set each subscription.
 * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ )
 * {
 *      subscriptionList[ i ].qos = MQTTQoS0;
 *      // Each subscription needs a topic filter.
 *      subscriptionList[ i ].pTopicFilter = filters[ i ];
 *      subscriptionList[ i ].topicFilterLength = strlen( filters[ i ] );
 * }
 *
 * // Obtain a new packet id for the subscription.
 * packetId = MQTT_GetPacketId( pContext );
 *
 * status = MQTT_Subscribe( pContext, &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, packetId );
 *
 * if( status == MQTTSuccess )
 * {
 *      // We must now call MQTT_ReceiveLoop() or MQTT_ProcessLoop() to receive the SUBACK.
 *      // If the broker accepts the subscription we can now receive publishes
 *      // on the requested topics.
 * }
 * @endcode
 */
/* @[declare_mqtt_subscribe] */
MQTTStatus_t MQTT_Subscribe( MQTTContext_t * pContext,
                             const MQTTSubscribeInfo_t * pSubscriptionList,
                             size_t subscriptionCount,
                             uint16_t packetId );
/* @[declare_mqtt_subscribe] */
/**
 * @brief Publishes a message to the given topic name.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
 * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
 *
 * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSendFailed if transport write failed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTPublishInfo_t publishInfo;
 * uint16_t packetId;
 * // This context is assumed to be initialized and connected.
 * MQTTContext_t * pContext;
 *
 * // QoS of publish.
 * publishInfo.qos = MQTTQoS1;
 * publishInfo.pTopicName = "/some/topic/name";
 * publishInfo.topicNameLength = strlen( publishInfo.pTopicName );
 * publishInfo.pPayload = "Hello World!";
 * publishInfo.payloadLength = strlen( "Hello World!" );
 *
 * // Packet ID is needed for QoS > 0.
 * packetId = MQTT_GetPacketId( pContext );
 *
 * status = MQTT_Publish( pContext, &publishInfo, packetId );
 *
 * if( status == MQTTSuccess )
 * {
 *      // Since the QoS is > 0, we will need to call MQTT_ReceiveLoop()
 *      // or MQTT_ProcessLoop() to process the publish acknowledgments.
 * }
 * @endcode
 */
/* @[declare_mqtt_publish] */
MQTTStatus_t MQTT_Publish( MQTTContext_t * pContext,
                           const MQTTPublishInfo_t * pPublishInfo,
                           uint16_t packetId );
/* @[declare_mqtt_publish] */
/**
 * @brief Cancels an outgoing publish callback (only for QoS > QoS0) by
 * removing it from the pending ACK list.
 *
 * @note This cannot cancel the actual publish as that might have already
 * been sent to the broker. This only removes the details of the given packet
 * ID from the list of unACKed packet. That allows the caller to free any memory
 * associated with the publish payload, topic string etc. Also, after this API
 * call, the user provided callback will not be invoked when the ACK packet is
 * received.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] packetId packet ID corresponding to the outstanding publish.
 *
 * @return #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 */
/* @[declare_mqtt_cancelcallback] */
MQTTStatus_t MQTT_CancelCallback( const MQTTContext_t * pContext,
                                  uint16_t packetId );
/* @[declare_mqtt_cancelcallback] */
/**
 * @brief Sends an MQTT PINGREQ to broker.
 *
 * @param[in] pContext Initialized and connected MQTT context.
 *
 * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSendFailed if transport write failed;
 * #MQTTSuccess otherwise.
 */
/* @[declare_mqtt_ping] */
MQTTStatus_t MQTT_Ping( MQTTContext_t * pContext );
/* @[declare_mqtt_ping] */
/**
 * @brief Sends MQTT UNSUBSCRIBE for the given list of topic filters to
 * the broker.
 *
 * @param[in] pContext Initialized MQTT context.
 * @param[in] pSubscriptionList List of MQTT subscription info.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
 *
 * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to
 * hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSendFailed if transport write failed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTSubscribeInfo_t unsubscribeList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
 * uint16_t packetId;
 * // This context is assumed to be initialized and connected.
 * MQTTContext_t * pContext;
 * // This is assumed to be a list of filters we want to unsubscribe from.
 * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ];
 *
 * // Set information for each unsubscribe request.
 * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ )
 * {
 *      unsubscribeList[ i ].pTopicFilter = filters[ i ];
 *      unsubscribeList[ i ].topicFilterLength = strlen( filters[ i ] );
 *
 *      // The QoS field of MQTT_SubscribeInfo_t is unused for unsubscribing.
 * }
 *
 * // Obtain a new packet id for the unsubscribe request.
 * packetId = MQTT_GetPacketId( pContext );
 *
 * status = MQTT_Unsubscribe( pContext, &unsubscribeList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, packetId );
 *
 * if( status == MQTTSuccess )
 * {
 *      // We must now call MQTT_ReceiveLoop() or MQTT_ProcessLoop() to receive the UNSUBACK.
 *      // After this the broker should no longer send publishes for these topics.
 * }
 * @endcode
 */
/* @[declare_mqtt_unsubscribe] */
MQTTStatus_t MQTT_Unsubscribe( MQTTContext_t * pContext,
                               const MQTTSubscribeInfo_t * pSubscriptionList,
                               size_t subscriptionCount,
                               uint16_t packetId );
/* @[declare_mqtt_unsubscribe] */
/**
 * @brief Disconnect an MQTT session.
 *
 * @param[in] pContext Initialized and connected MQTT context.
 *
 * @return #MQTTNoMemory if the #MQTTContext_t.networkBuffer is too small to
 * hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSendFailed if transport send failed;
 * #MQTTSuccess otherwise.
 */
/* @[declare_mqtt_disconnect] */
MQTTStatus_t MQTT_Disconnect( MQTTContext_t * pContext );
/* @[declare_mqtt_disconnect] */
/**
 * @brief Loop to receive packets from the transport interface. Handles keep
 * alive.
 *
 * @note If a dummy timer function, #MQTTGetCurrentTimeFunc_t, is passed to the library,
 * then the keep-alive mechanism is not supported by the #MQTT_ProcessLoop API.
 * In that case, the #MQTT_ReceiveLoop API function should be used instead.
 *
 * @param[in] pContext Initialized and connected MQTT context.
 *
 * @note Calling this function blocks the calling context for a time period that
 * depends on the passed the configuration macros, #MQTT_RECV_POLLING_TIMEOUT_MS
 * and #MQTT_SEND_TIMEOUT_MS, and the underlying transport interface implementation
 * timeouts, unless an error occurs. The blocking period also depends on the execution time of the
 * #MQTTEventCallback_t callback supplied to the library. It is recommended that the supplied
 * #MQTTEventCallback_t callback does not contain blocking operations to prevent potential
 * non-deterministic blocking period of the #MQTT_ProcessLoop API call.
 *
 * @return #MQTTBadParameter if context is NULL;
 * #MQTTRecvFailed if a network error occurs during reception;
 * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ;
 * #MQTTBadResponse if an invalid packet is received;
 * #MQTTKeepAliveTimeout if the server has not sent a PINGRESP before
 * #MQTT_PINGRESP_TIMEOUT_MS milliseconds;
 * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an
 * invalid transition for the internal state machine;
 * #MQTTNeedMoreBytes if MQTT_ProcessLoop has received
 * incomplete data; it should be called again (probably after a delay);
 * #MQTTSuccess on success.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * // This context is assumed to be initialized and connected.
 * MQTTContext_t * pContext;
 *
 * while( true )
 * {
 *      status = MQTT_ProcessLoop( pContext );
 *
 *      if( status != MQTTSuccess && status != MQTTNeedMoreBytes )
 *      {
 *          // Determine the error. It's possible we might need to disconnect
 *          // the underlying transport connection.
 *      }
 *      else
 *      {
 *          // Other application functions.
 *      }
 * }
 * @endcode
 */
/* @[declare_mqtt_processloop] */
MQTTStatus_t MQTT_ProcessLoop( MQTTContext_t * pContext );
/* @[declare_mqtt_processloop] */
/**
 * @brief Loop to receive packets from the transport interface. Does not handle
 * keep alive.
 *
 * @note If a dummy #MQTTGetCurrentTimeFunc_t was passed to #MQTT_Init, then the
 * #MQTT_RECV_POLLING_TIMEOUT_MS and #MQTT_SEND_TIMEOUT_MS timeout configurations
 * MUST be set to 0.
 *
 * @param[in] pContext Initialized and connected MQTT context.
 *
 * @note Calling this function blocks the calling context for a time period that
 * depends on the the configuration macros, #MQTT_RECV_POLLING_TIMEOUT_MS and
 * #MQTT_SEND_TIMEOUT_MS, and the underlying transport interface implementation
 * timeouts, unless an error occurs. The blocking period also depends on the execution time of the
 * #MQTTEventCallback_t callback supplied to the library. It is recommended that the supplied
 * #MQTTEventCallback_t callback does not contain blocking operations to prevent potential
 * non-deterministic blocking period of the #MQTT_ReceiveLoop API call.
 *
 * @return #MQTTBadParameter if context is NULL;
 * #MQTTRecvFailed if a network error occurs during reception;
 * #MQTTSendFailed if a network error occurs while sending an ACK or PINGREQ;
 * #MQTTBadResponse if an invalid packet is received;
 * #MQTTIllegalState if an incoming QoS 1/2 publish or ack causes an
 * invalid transition for the internal state machine;
 * #MQTTNeedMoreBytes if MQTT_ReceiveLoop has received
 * incomplete data; it should be called again (probably after a delay);
 * #MQTTSuccess on success.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * uint32_t keepAliveMs = 60 * 1000;
 * // This context is assumed to be initialized and connected.
 * MQTTContext_t * pContext;
 *
 * while( true )
 * {
 *      status = MQTT_ReceiveLoop( pContext );
 *
 *      if( status != MQTTSuccess && status != MQTTNeedMoreBytes )
 *      {
 *          // Determine the error. It's possible we might need to disconnect
 *          // the underlying transport connection.
 *      }
 *      else
 *      {
 *          // Since this function does not send pings, the application may need
 *          // to in order to comply with keep alive.
 *          if( ( pContext->getTime() - pContext->lastPacketTxTime ) > keepAliveMs )
 *          {
 *              status = MQTT_Ping( pContext );
 *          }
 *
 *          // Other application functions.
 *      }
 * }
 * @endcode
 */
/* @[declare_mqtt_receiveloop] */
MQTTStatus_t MQTT_ReceiveLoop( MQTTContext_t * pContext );
/* @[declare_mqtt_receiveloop] */
/**
 * @brief Get a packet ID that is valid according to the MQTT 3.1.1 spec.
 *
 * @param[in] pContext Initialized MQTT context.
 *
 * @return A non-zero number.
 */
/* @[declare_mqtt_getpacketid] */
uint16_t MQTT_GetPacketId( MQTTContext_t * pContext );
/* @[declare_mqtt_getpacketid] */
/**
 * @brief A utility function that determines whether the passed topic filter and
 * topic name match according to the MQTT 3.1.1 protocol specification.
 *
 * @param[in] pTopicName The topic name to check.
 * @param[in] topicNameLength Length of the topic name.
 * @param[in] pTopicFilter The topic filter to check.
 * @param[in] topicFilterLength Length of topic filter.
 * @param[out] pIsMatch If the match is performed without any error, that is if the
 * return value is MQTTSuccess, then and only then the value in this parameter is valid
 * and updated. In such a case, if the topic filter and the topic name match, then this
 * value is set to true; otherwise if there is no match then it is set to false.
 *
 * @note The API assumes that the passed topic name is valid to meet the
 * requirements of the MQTT 3.1.1 specification. Invalid topic names (for example,
 * containing wildcard characters) should not be passed to the function.
 * Also, the API checks validity of topic filter for wildcard characters ONLY if
 * the passed topic name and topic filter do not have an exact string match.
 *
 * @return Returns one of the following:
 * - #MQTTBadParameter, if any of the input parameters is invalid.
 * - #MQTTSuccess, if the matching operation was performed.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * const char * pTopic = "topic/match/1";
 * const char * pFilter = "topic/#";
 * MQTTStatus_t status = MQTTSuccess;
 * bool match = false;
 *
 * status = MQTT_MatchTopic( pTopic, strlen( pTopic ), pFilter, strlen( pFilter ), &match );
 * // Our parameters were valid, so this will return success.
 * assert( status == MQTTSuccess );
 *
 * // For this specific example, we already know this value is true. This
 * // check is placed here as an example for use with variable topic names.
 * if( match )
 * {
 *      // Application can decide what to do with the matching topic name.
 * }
 * @endcode
 */
MQTTStatus_t MQTT_MatchTopic( const char * pTopicName,
                              const uint16_t topicNameLength,
                              const char * pTopicFilter,
                              const uint16_t topicFilterLength,
                              bool * pIsMatch );
/**
 * @brief Parses the payload of an MQTT SUBACK packet that contains status codes
 * corresponding to topic filter subscription requests from the original
 * subscribe packet.
 *
 * Each return code in the SUBACK packet corresponds to a topic filter in the
 * SUBSCRIBE Packet being acknowledged.
 * The status codes can be one of the following:
 *  - 0x00 - Success - Maximum QoS 0
 *  - 0x01 - Success - Maximum QoS 1
 *  - 0x02 - Success - Maximum QoS 2
 *  - 0x80 - Failure
 * Refer to #MQTTSubAckStatus_t for the status codes.
 *
 * @param[in] pSubackPacket The SUBACK packet whose payload is to be parsed.
 * @param[out] pPayloadStart This is populated with the starting address
 * of the payload (or return codes for topic filters) in the SUBACK packet.
 * @param[out] pPayloadSize This is populated with the size of the payload
 * in the SUBACK packet. It represents the number of topic filters whose
 * SUBACK status is present in the packet.
 *
 * @return Returns one of the following:
 * - #MQTTBadParameter if the input SUBACK packet is invalid.
 * - #MQTTSuccess if parsing the payload was successful.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Global variable used in this example.
 * // This is assumed to be the subscription list in the original SUBSCRIBE packet.
 * MQTTSubscribeInfo_t pSubscribes[ NUMBER_OF_SUBSCRIPTIONS ];
 *
 * // MQTT_GetSubAckStatusCodes is intended to be used from the application
 * // callback that is called by the library in MQTT_ProcessLoop or MQTT_ReceiveLoop.
 * void eventCallback(
 *      MQTTContext_t * pContext,
 *      MQTTPacketInfo_t * pPacketInfo,
 *      MQTTDeserializedInfo_t * pDeserializedInfo
 * )
 * {
 *      MQTTStatus_t status = MQTTSuccess;
 *      uint8_t * pCodes;
 *      size_t numCodes;
 *
 *      if( pPacketInfo->type == MQTT_PACKET_TYPE_SUBACK )
 *      {
 *          status = MQTT_GetSubAckStatusCodes( pPacketInfo, &pCodes, &numCodes );
 *
 *          // Since the pointers to the payload and payload size are not NULL, and
 *          // we use the packet info struct passed to the app callback (verified
 *          // to be valid by the library), this function must return success.
 *          assert( status == MQTTSuccess );
 *          // The server must send a response code for each topic filter in the
 *          // original SUBSCRIBE packet.
 *          assert( numCodes == NUMBER_OF_SUBSCRIPTIONS );
 *
 *          for( int i = 0; i < numCodes; i++ )
 *          {
 *              // The only failure code is 0x80 = MQTTSubAckFailure.
 *              if( pCodes[ i ] == MQTTSubAckFailure )
 *              {
 *                  // The subscription failed, we may want to retry the
 *                  // subscription in pSubscribes[ i ] outside of this callback.
 *              }
 *              else
 *              {
 *                  // The subscription was granted, but the maximum QoS may be
 *                  // lower than what was requested. We can verify the granted QoS.
 *                  if( pSubscribes[ i ].qos != pCodes[ i ] )
 *                  {
 *                      LogWarn( (
 *                          "Requested QoS %u, but granted QoS %u for %s",
 *                          pSubscribes[ i ].qos, pCodes[ i ], pSubscribes[ i ].pTopicFilter
 *                      ) );
 *                  }
 *              }
 *          }
 *      }
 *      // Handle other packet types.
 * }
 * @endcode
 */
/* @[declare_mqtt_getsubackstatuscodes] */
MQTTStatus_t MQTT_GetSubAckStatusCodes( const MQTTPacketInfo_t * pSubackPacket,
                                        uint8_t ** pPayloadStart,
                                        size_t * pPayloadSize );
/* @[declare_mqtt_getsubackstatuscodes] */
/**
 * @brief Error code to string conversion for MQTT statuses.
 *
 * @param[in] status The status to convert to a string.
 *
 * @return The string representation of the status.
 */
/* @[declare_mqtt_status_strerror] */
const char * MQTT_Status_strerror( MQTTStatus_t status );
/* @[declare_mqtt_status_strerror] */
/* *INDENT-OFF* */
#ifdef __cplusplus
    }
#endif
/* *INDENT-ON* */
#endif /* ifndef CORE_MQTT_H */
project/coreMQTT/coreMQTT/core_mqtt_config_defaults.h
New file
@@ -0,0 +1,204 @@
/*
 * coreMQTT v2.1.1
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * @file core_mqtt_config_defaults.h
 * @brief This represents the default values for the configuration macros
 * for the MQTT library.
 *
 * @note This file SHOULD NOT be modified. If custom values are needed for
 * any configuration macro, a core_mqtt_config.h file should be provided to
 * the MQTT library to override the default values defined in this file.
 * To use the custom config file, the MQTT_DO_NOT_USE_CUSTOM_CONFIG preprocessor
 * macro SHOULD NOT be set.
 */
#ifndef CORE_MQTT_CONFIG_DEFAULTS_H_
#define CORE_MQTT_CONFIG_DEFAULTS_H_
/* *INDENT-OFF* */
#ifdef __cplusplus
    extern "C" {
#endif
/* *INDENT-ON* */
/* MQTT_DO_NOT_USE_CUSTOM_CONFIG allows building the MQTT library
 * without a custom config. If a custom config is provided, the
 * MQTT_DO_NOT_USE_CUSTOM_CONFIG macro should not be defined. */
#ifndef MQTT_DO_NOT_USE_CUSTOM_CONFIG
/* Include custom config file before other headers. */
    #include "core_mqtt_config.h"
#endif
/* The macro definition for MQTT_DO_NOT_USE_CUSTOM_CONFIG is for Doxygen
 * documentation only. */
/**
 * @brief Define this macro to build the MQTT library without the custom config
 * file core_mqtt_config.h.
 *
 * Without the custom config, the MQTT library builds with
 * default values of config macros defined in core_mqtt_config_defaults.h file.
 *
 * If a custom config is provided, then MQTT_DO_NOT_USE_CUSTOM_CONFIG should not
 * be defined.
 */
#ifdef DOXYGEN
    #define MQTT_DO_NOT_USE_CUSTOM_CONFIG
#endif
/**
 * @ingroup mqtt_constants
 * @brief Maximum number of vectors in subscribe and unsubscribe packet.
 */
#ifndef MQTT_SUB_UNSUB_MAX_VECTORS
    #define MQTT_SUB_UNSUB_MAX_VECTORS    ( 4U )
#endif
/**
 * @brief The number of retries for receiving CONNACK.
 *
 * The MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT will be used only when the
 * timeoutMs parameter of #MQTT_Connect is passed as 0 . The transport
 * receive for CONNACK will be retried MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT
 * times before timing out. A value of 0 for this config will cause the
 * transport receive for CONNACK  to be invoked only once.
 *
 * <b>Possible values:</b> Any positive 16 bit integer. <br>
 * <b>Default value:</b> `5`
 */
#ifndef MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT
/* Default value for the CONNACK receive retries. */
    #define MQTT_MAX_CONNACK_RECEIVE_RETRY_COUNT    ( 5U )
#endif
/**
 * @brief Maximum number of milliseconds to wait for a ping response to a ping
 * request as part of the keep-alive mechanism.
 *
 * If a ping response is not received before this timeout, then
 * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout.
 *
 * @note If this value is more than half of the keep alive interval, and the
 * server does not receive the previous ping request, then it is likely that the
 * server will disconnect the client before #MQTTKeepAliveTimeout can be returned.
 *
 * @note If a dummy implementation of the #MQTTGetCurrentTimeFunc_t timer function,
 * is supplied to the library, then the keep-alive mechanism is not supported by the
 * #MQTT_ProcessLoop API function. In that case, the value of #MQTT_PINGRESP_TIMEOUT_MS
 * is irrelevant to the behavior of the library.
 *
 * <b>Possible values:</b> Any positive integer up to SIZE_MAX. <br>
 * <b>Default value:</b> `5000`
 */
#ifndef MQTT_PINGRESP_TIMEOUT_MS
/* Wait 5 seconds by default for a ping response. */
    #define MQTT_PINGRESP_TIMEOUT_MS    ( 5000U )
#endif
/**
 * @brief Maximum number of milliseconds of TX inactivity to wait
 * before initiating a PINGREQ
 *
 * @note If this value is less than the keep alive interval than
 * it will be used instead.
 *
 * <b>Possible values:</b> Any positive integer up to SIZE_MAX. <br>
 * <b>Default value:</b> '30000'
 */
#ifndef PACKET_TX_TIMEOUT_MS
    #define PACKET_TX_TIMEOUT_MS    ( 30000U )
#endif
/**
 * @brief Maximum number of milliseconds of RX inactivity to wait
 * before initiating a PINGREQ
 *
 * <b>Possible values:</b> Any positive integer up to SIZE_MAX. <br>
 * <b>Default value:</b> '30000'
 *
 */
#ifndef PACKET_RX_TIMEOUT_MS
    #define PACKET_RX_TIMEOUT_MS    ( 30000U )
#endif
/**
 * @brief The maximum duration between non-empty network reads while
 * receiving an MQTT packet via the #MQTT_ProcessLoop or #MQTT_ReceiveLoop
 * API functions.
 *
 * When an incoming MQTT packet is detected, the transport receive function
 * may be called multiple times until all of the expected number of bytes of the
 * packet are received. This timeout represents the maximum polling duration that
 * is allowed without any data reception from the network for the incoming packet.
 *
 * If the timeout expires, the #MQTT_ProcessLoop and #MQTT_ReceiveLoop functions
 * return #MQTTRecvFailed.
 *
 * @note If a dummy implementation of the #MQTTGetCurrentTimeFunc_t timer function,
 * is supplied to the library, then #MQTT_RECV_POLLING_TIMEOUT_MS MUST be set to 0.
 *
 * <b>Possible values:</b> Any positive 32 bit integer. Recommended to use a
 * small timeout value. <br>
 * <b>Default value:</b> `10`
 *
 */
#ifndef MQTT_RECV_POLLING_TIMEOUT_MS
    #define MQTT_RECV_POLLING_TIMEOUT_MS    ( 10U )
#endif
/**
 * @brief The maximum duration allowed to send an MQTT packet over the transport
 * interface.
 *
 * When sending an MQTT packet, the transport send or writev functions may be
 * called multiple times until all of the required number of bytes are sent.
 * This timeout represents the maximum duration that is allowed to send the MQTT
 * packet while calling the transport send or writev functions.
 *
 * If the timeout expires, #MQTTSendFailed will be returned by the public API
 * functions.
 *
 * @note If a dummy implementation of the #MQTTGetCurrentTimeFunc_t timer function,
 * is supplied to the library, then #MQTT_SEND_TIMEOUT_MS MUST be set to 0.
 *
 * <b>Possible values:</b> Any positive 32 bit integer. <br>
 * <b>Default value:</b> `20000`
 *
 */
#ifndef MQTT_SEND_TIMEOUT_MS
    #define MQTT_SEND_TIMEOUT_MS    ( 20000U )
#endif
#ifdef MQTT_SEND_RETRY_TIMEOUT_MS
    #error MQTT_SEND_RETRY_TIMEOUT_MS is deprecated. Instead use MQTT_SEND_TIMEOUT_MS.
#endif
/* *INDENT-OFF* */
#ifdef __cplusplus
    }
#endif
/* *INDENT-ON* */
#endif /* ifndef CORE_MQTT_CONFIG_DEFAULTS_H_ */
project/coreMQTT/coreMQTT/core_mqtt_default_logging.h
New file
@@ -0,0 +1,132 @@
/*
 * coreMQTT v2.1.1
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * @file core_mqtt_default_logging.h
 * @brief This represents the default values for the logging macros for the MQTT
 * library.
 *
 * @note This file SHOULD NOT be modified. If custom values are needed for
 * any configuration macro, a core_mqtt_config.h file should be provided to
 * the MQTT library to override the default values defined in this file.
 * To use the custom config file, the MQTT_DO_NOT_USE_CUSTOM_CONFIG preprocessor
 * macro SHOULD NOT be set.
 */
#ifndef CORE_MQTT_DEFAULT_LOGGING_H_
#define CORE_MQTT_DEFAULT_LOGGING_H_
/* *INDENT-OFF* */
#ifdef __cplusplus
    extern "C" {
#endif
/* *INDENT-ON* */
/**
 * @brief Macro that is called in the MQTT library for logging "Error" level
 * messages.
 *
 * To enable error level logging in the MQTT library, this macro should be mapped to the
 * application-specific logging implementation that supports error logging.
 *
 * @note This logging macro is called in the MQTT library with parameters wrapped in
 * double parentheses to be ISO C89/C90 standard compliant. For a reference
 * POSIX implementation of the logging macros, refer to core_mqtt_config.h files, and the
 * logging-stack in demos folder of the
 * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C).
 *
 * <b>Default value</b>: Error logging is turned off, and no code is generated for calls
 * to the macro in the MQTT library on compilation.
 */
#ifndef LogError
    #define LogError( message )
#endif
/**
 * @brief Macro that is called in the MQTT library for logging "Warning" level
 * messages.
 *
 * To enable warning level logging in the MQTT library, this macro should be mapped to the
 * application-specific logging implementation that supports warning logging.
 *
 * @note This logging macro is called in the MQTT library with parameters wrapped in
 * double parentheses to be ISO C89/C90 standard compliant. For a reference
 * POSIX implementation of the logging macros, refer to core_mqtt_config.h files, and the
 * logging-stack in demos folder of the
 * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
 *
 * <b>Default value</b>: Warning logs are turned off, and no code is generated for calls
 * to the macro in the MQTT library on compilation.
 */
#ifndef LogWarn
    #define LogWarn( message )
#endif
/**
 * @brief Macro that is called in the MQTT library for logging "Info" level
 * messages.
 *
 * To enable info level logging in the MQTT library, this macro should be mapped to the
 * application-specific logging implementation that supports info logging.
 *
 * @note This logging macro is called in the MQTT library with parameters wrapped in
 * double parentheses to be ISO C89/C90 standard compliant. For a reference
 * POSIX implementation of the logging macros, refer to core_mqtt_config.h files, and the
 * logging-stack in demos folder of the
 * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
 *
 * <b>Default value</b>: Info logging is turned off, and no code is generated for calls
 * to the macro in the MQTT library on compilation.
 */
#ifndef LogInfo
    #define LogInfo( message )
#endif
/**
 * @brief Macro that is called in the MQTT library for logging "Debug" level
 * messages.
 *
 * To enable debug level logging from MQTT library, this macro should be mapped to the
 * application-specific logging implementation that supports debug logging.
 *
 * @note This logging macro is called in the MQTT library with parameters wrapped in
 * double parentheses to be ISO C89/C90 standard compliant. For a reference
 * POSIX implementation of the logging macros, refer to core_mqtt_config.h files, and the
 * logging-stack in demos folder of the
 * [AWS IoT Embedded C SDK repository](https://github.com/aws/aws-iot-device-sdk-embedded-C/).
 *
 * <b>Default value</b>: Debug logging is turned off, and no code is generated for calls
 * to the macro in the MQTT library on compilation.
 */
#ifndef LogDebug
    #define LogDebug( message )
#endif
/* *INDENT-OFF* */
#ifdef __cplusplus
    }
#endif
/* *INDENT-ON* */
#endif /* ifndef CORE_MQTT_DEFAULT_LOGGING_H_ */
project/coreMQTT/coreMQTT/core_mqtt_serializer.c
New file
@@ -0,0 +1,2684 @@
/*
 * coreMQTT v2.1.1
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * @file core_mqtt_serializer.c
 * @brief Implements the user-facing functions in core_mqtt_serializer.h.
 */
#include <string.h>
#include <assert.h>
#include "core_mqtt_serializer.h"
/* Include config defaults header to get default values of configs. */
#include "core_mqtt_config_defaults.h"
#include "core_mqtt_default_logging.h"
/**
 * @brief MQTT protocol version 3.1.1.
 */
#define MQTT_VERSION_3_1_1                          ( ( uint8_t ) 4U )
/**
 * @brief Size of the fixed and variable header of a CONNECT packet.
 */
#define MQTT_PACKET_CONNECT_HEADER_SIZE             ( 10UL )
/* MQTT CONNECT flags. */
#define MQTT_CONNECT_FLAG_CLEAN                     ( 1 ) /**< @brief Clean session. */
#define MQTT_CONNECT_FLAG_WILL                      ( 2 ) /**< @brief Will present. */
#define MQTT_CONNECT_FLAG_WILL_QOS1                 ( 3 ) /**< @brief Will QoS 1. */
#define MQTT_CONNECT_FLAG_WILL_QOS2                 ( 4 ) /**< @brief Will QoS 2. */
#define MQTT_CONNECT_FLAG_WILL_RETAIN               ( 5 ) /**< @brief Will retain. */
#define MQTT_CONNECT_FLAG_PASSWORD                  ( 6 ) /**< @brief Password present. */
#define MQTT_CONNECT_FLAG_USERNAME                  ( 7 ) /**< @brief User name present. */
/*
 * Positions of each flag in the first byte of an MQTT PUBLISH packet's
 * fixed header.
 */
#define MQTT_PUBLISH_FLAG_RETAIN                    ( 0 ) /**< @brief MQTT PUBLISH retain flag. */
#define MQTT_PUBLISH_FLAG_QOS1                      ( 1 ) /**< @brief MQTT PUBLISH QoS1 flag. */
#define MQTT_PUBLISH_FLAG_QOS2                      ( 2 ) /**< @brief MQTT PUBLISH QoS2 flag. */
#define MQTT_PUBLISH_FLAG_DUP                       ( 3 ) /**< @brief MQTT PUBLISH duplicate flag. */
/**
 * @brief The size of MQTT DISCONNECT packets, per MQTT spec.
 */
#define MQTT_DISCONNECT_PACKET_SIZE                 ( 2UL )
/**
 * @brief A PINGREQ packet is always 2 bytes in size, defined by MQTT 3.1.1 spec.
 */
#define MQTT_PACKET_PINGREQ_SIZE                    ( 2UL )
/**
 * @brief The Remaining Length field of MQTT disconnect packets, per MQTT spec.
 */
#define MQTT_DISCONNECT_REMAINING_LENGTH            ( ( uint8_t ) 0 )
/*
 * Constants relating to CONNACK packets, defined by MQTT 3.1.1 spec.
 */
#define MQTT_PACKET_CONNACK_REMAINING_LENGTH        ( ( uint8_t ) 2U )    /**< @brief A CONNACK packet always has a "Remaining length" of 2. */
#define MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK    ( ( uint8_t ) 0x01U ) /**< @brief The "Session Present" bit is always the lowest bit. */
/*
 * UNSUBACK, PUBACK, PUBREC, PUBREL, and PUBCOMP always have a remaining length
 * of 2.
 */
#define MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH     ( ( uint8_t ) 2 ) /**< @brief PUBACK, PUBREC, PUBREl, PUBCOMP, UNSUBACK Remaining length. */
#define MQTT_PACKET_PINGRESP_REMAINING_LENGTH       ( 0U )            /**< @brief A PINGRESP packet always has a "Remaining length" of 0. */
/**
 * @brief Per the MQTT 3.1.1 spec, the largest "Remaining Length" of an MQTT
 * packet is this value, 256 MB.
 */
#define MQTT_MAX_REMAINING_LENGTH                   ( 268435455UL )
/**
 * @brief Set a bit in an 8-bit unsigned integer.
 */
#define UINT8_SET_BIT( x, position )      ( ( x ) = ( uint8_t ) ( ( x ) | ( 0x01U << ( position ) ) ) )
/**
 * @brief Macro for checking if a bit is set in a 1-byte unsigned int.
 *
 * @param[in] x The unsigned int to check.
 * @param[in] position Which bit to check.
 */
#define UINT8_CHECK_BIT( x, position )    ( ( ( x ) & ( 0x01U << ( position ) ) ) == ( 0x01U << ( position ) ) )
/**
 * @brief Get the high byte of a 16-bit unsigned integer.
 */
#define UINT16_HIGH_BYTE( x )             ( ( uint8_t ) ( ( x ) >> 8 ) )
/**
 * @brief Get the low byte of a 16-bit unsigned integer.
 */
#define UINT16_LOW_BYTE( x )              ( ( uint8_t ) ( ( x ) & 0x00ffU ) )
/**
 * @brief Macro for decoding a 2-byte unsigned int from a sequence of bytes.
 *
 * @param[in] ptr A uint8_t* that points to the high byte.
 */
#define UINT16_DECODE( ptr )                            \
    ( uint16_t ) ( ( ( ( uint16_t ) ptr[ 0 ] ) << 8 ) | \
                   ( ( uint16_t ) ptr[ 1 ] ) )
/**
 * @brief A value that represents an invalid remaining length.
 *
 * This value is greater than what is allowed by the MQTT specification.
 */
#define MQTT_REMAINING_LENGTH_INVALID             ( ( size_t ) 268435456 )
/**
 * @brief The minimum remaining length for a QoS 0 PUBLISH.
 *
 * Includes two bytes for topic name length and one byte for topic name.
 */
#define MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0    ( 3U )
/*-----------------------------------------------------------*/
/**
 * @brief MQTT Subscription packet types.
 */
typedef enum MQTTSubscriptionType
{
    MQTT_SUBSCRIBE,  /**< @brief The type is a SUBSCRIBE packet. */
    MQTT_UNSUBSCRIBE /**< @brief The type is a UNSUBSCRIBE packet. */
} MQTTSubscriptionType_t;
/*-----------------------------------------------------------*/
/**
 * @brief Serializes MQTT PUBLISH packet into the buffer provided.
 *
 * This function serializes MQTT PUBLISH packet into #MQTTFixedBuffer_t.pBuffer.
 * Copy of the payload into the buffer is done as part of the serialization
 * only if @p serializePayload is true.
 *
 * @brief param[in] pPublishInfo Publish information.
 * @brief param[in] remainingLength Remaining length of the PUBLISH packet.
 * @brief param[in] packetIdentifier Packet identifier of PUBLISH packet.
 * @brief param[in, out] pFixedBuffer Buffer to which PUBLISH packet will be
 * serialized.
 * @brief param[in] serializePayload Copy payload to the serialized buffer
 * only if true. Only PUBLISH header will be serialized if false.
 *
 * @return Total number of bytes sent; -1 if there is an error.
 */
static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo,
                                    size_t remainingLength,
                                    uint16_t packetIdentifier,
                                    const MQTTFixedBuffer_t * pFixedBuffer,
                                    bool serializePayload );
/**
 * @brief Calculates the packet size and remaining length of an MQTT
 * PUBLISH packet.
 *
 * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
 * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet.
 * @param[out] pPacketSize The total size of the MQTT PUBLISH packet.
 *
 * @return false if the packet would exceed the size allowed by the
 * MQTT spec; true otherwise.
 */
static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo,
                                        size_t * pRemainingLength,
                                        size_t * pPacketSize );
/**
 * @brief Calculates the packet size and remaining length of an MQTT
 * SUBSCRIBE or UNSUBSCRIBE packet.
 *
 * @param[in] pSubscriptionList List of MQTT subscription info.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[out] pRemainingLength The Remaining Length of the MQTT SUBSCRIBE or
 * UNSUBSCRIBE packet.
 * @param[out] pPacketSize The total size of the MQTT MQTT SUBSCRIBE or
 * UNSUBSCRIBE packet.
 * @param[in] subscriptionType #MQTT_SUBSCRIBE or #MQTT_UNSUBSCRIBE.
 *
 * #MQTTBadParameter if the packet would exceed the size allowed by the
 * MQTT spec or a subscription is empty; #MQTTSuccess otherwise.
 */
static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
                                                     size_t subscriptionCount,
                                                     size_t * pRemainingLength,
                                                     size_t * pPacketSize,
                                                     MQTTSubscriptionType_t subscriptionType );
/**
 * @brief Validates parameters of #MQTT_SerializeSubscribe or
 * #MQTT_SerializeUnsubscribe.
 *
 * @param[in] pSubscriptionList List of MQTT subscription info.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[in] packetId Packet identifier.
 * @param[in] remainingLength Remaining length of the packet.
 * @param[in] pFixedBuffer Buffer for packet serialization.
 *
 * @return #MQTTNoMemory if pBuffer is too small to hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 */
static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * pSubscriptionList,
                                                         size_t subscriptionCount,
                                                         uint16_t packetId,
                                                         size_t remainingLength,
                                                         const MQTTFixedBuffer_t * pFixedBuffer );
/**
 * @brief Serialize an MQTT CONNECT packet in the given buffer.
 *
 * @param[in] pConnectInfo MQTT CONNECT packet parameters.
 * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used.
 * @param[in] remainingLength Remaining Length of MQTT CONNECT packet.
 * @param[out] pFixedBuffer Buffer for packet serialization.
 */
static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo,
                                    const MQTTPublishInfo_t * pWillInfo,
                                    size_t remainingLength,
                                    const MQTTFixedBuffer_t * pFixedBuffer );
/**
 * @brief Prints the appropriate message for the CONNACK response code if logs
 * are enabled.
 *
 * @param[in] responseCode MQTT standard CONNACK response code.
 */
static void logConnackResponse( uint8_t responseCode );
/**
 * @brief Encodes the remaining length of the packet using the variable length
 * encoding scheme provided in the MQTT v3.1.1 specification.
 *
 * @param[out] pDestination The destination buffer to store the encoded remaining
 * length.
 * @param[in] length The remaining length to encode.
 *
 * @return The location of the byte following the encoded value.
 */
static uint8_t * encodeRemainingLength( uint8_t * pDestination,
                                        size_t length );
/**
 * @brief Retrieve the size of the remaining length if it were to be encoded.
 *
 * @param[in] length The remaining length to be encoded.
 *
 * @return The size of the remaining length if it were to be encoded.
 */
static size_t remainingLengthEncodedSize( size_t length );
/**
 * @brief Encode a string whose size is at maximum 16 bits in length.
 *
 * @param[out] pDestination Destination buffer for the encoding.
 * @param[in] pSource The source string to encode.
 * @param[in] sourceLength The length of the source string to encode.
 *
 * @return A pointer to the end of the encoded string.
 */
static uint8_t * encodeString( uint8_t * pDestination,
                               const char * pSource,
                               uint16_t sourceLength );
/**
 * @brief Retrieves and decodes the Remaining Length from the network interface
 * by reading a single byte at a time.
 *
 * @param[in] recvFunc Network interface receive function.
 * @param[in] pNetworkContext Network interface context to the receive function.
 *
 * @return The Remaining Length of the incoming packet.
 */
static size_t getRemainingLength( TransportRecv_t recvFunc,
                                  NetworkContext_t * pNetworkContext );
/**
 * @brief Retrieves, decodes and stores the Remaining Length from the network
 * interface by reading a single byte at a time.
 *
 * @param[in] pBuffer The buffer holding the raw data to be processed
 * @param[in] pIndex Pointer to the index within the buffer to marking the end of raw data
 *            available.
 * @param[in] pIncomingPacket Structure used to hold the fields of the
 *            incoming packet.
 *
 * @return MQTTNeedMoreBytes is returned to show that the incoming
 *         packet is not yet fully received and decoded. Otherwise, MQTTSuccess
 *         shows that processing of the packet was successful.
 */
static MQTTStatus_t processRemainingLength( const uint8_t * pBuffer,
                                            const size_t * pIndex,
                                            MQTTPacketInfo_t * pIncomingPacket );
/**
 * @brief Check if an incoming packet type is valid.
 *
 * @param[in] packetType The packet type to check.
 *
 * @return `true` if the packet type is valid; `false` otherwise.
 */
static bool incomingPacketValid( uint8_t packetType );
/**
 * @brief Check the remaining length of an incoming PUBLISH packet against some
 * value for QoS 0, or for QoS 1 and 2.
 *
 * The remaining length for a QoS 1 and 2 packet will always be two greater than
 * for a QoS 0.
 *
 * @param[in] remainingLength Remaining length of the PUBLISH packet.
 * @param[in] qos The QoS of the PUBLISH.
 * @param[in] qos0Minimum Minimum possible remaining length for a QoS 0 PUBLISH.
 *
 * @return #MQTTSuccess or #MQTTBadResponse.
 */
static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength,
                                                 MQTTQoS_t qos,
                                                 size_t qos0Minimum );
/**
 * @brief Process the flags of an incoming PUBLISH packet.
 *
 * @param[in] publishFlags Flags of an incoming PUBLISH.
 * @param[in, out] pPublishInfo Pointer to #MQTTPublishInfo_t struct where
 * output will be written.
 *
 * @return #MQTTSuccess or #MQTTBadResponse.
 */
static MQTTStatus_t processPublishFlags( uint8_t publishFlags,
                                         MQTTPublishInfo_t * pPublishInfo );
/**
 * @brief Deserialize a CONNACK packet.
 *
 * Converts the packet from a stream of bytes to an #MQTTStatus_t.
 *
 * @param[in] pConnack Pointer to an MQTT packet struct representing a
 * CONNACK.
 * @param[out] pSessionPresent Whether a previous session was present.
 *
 * @return #MQTTSuccess if CONNACK specifies that CONNECT was accepted;
 * #MQTTServerRefused if CONNACK specifies that CONNECT was rejected;
 * #MQTTBadResponse if the CONNACK packet doesn't follow MQTT spec.
 */
static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * pConnack,
                                        bool * pSessionPresent );
/**
 * @brief Decode the status bytes of a SUBACK packet to a #MQTTStatus_t.
 *
 * @param[in] statusCount Number of status bytes in the SUBACK.
 * @param[in] pStatusStart The first status byte in the SUBACK.
 *
 * @return #MQTTSuccess, #MQTTServerRefused, or #MQTTBadResponse.
 */
static MQTTStatus_t readSubackStatus( size_t statusCount,
                                      const uint8_t * pStatusStart );
/**
 * @brief Deserialize a SUBACK packet.
 *
 * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts
 * the packet identifier.
 *
 * @param[in] pSuback Pointer to an MQTT packet struct representing a SUBACK.
 * @param[out] pPacketIdentifier Packet ID of the SUBACK.
 *
 * @return #MQTTSuccess if SUBACK is valid; #MQTTBadResponse if SUBACK packet
 * doesn't follow the MQTT spec.
 */
static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * pSuback,
                                       uint16_t * pPacketIdentifier );
/**
 * @brief Deserialize a PUBLISH packet received from the server.
 *
 * Converts the packet from a stream of bytes to an #MQTTPublishInfo_t and
 * extracts the packet identifier. Also prints out debug log messages about the
 * packet.
 *
 * @param[in] pIncomingPacket Pointer to an MQTT packet struct representing a
 * PUBLISH.
 * @param[out] pPacketId Packet identifier of the PUBLISH.
 * @param[out] pPublishInfo Pointer to #MQTTPublishInfo_t where output is
 * written.
 *
 * @return #MQTTSuccess if PUBLISH is valid; #MQTTBadResponse
 * if the PUBLISH packet doesn't follow MQTT spec.
 */
static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket,
                                        uint16_t * pPacketId,
                                        MQTTPublishInfo_t * pPublishInfo );
/**
 * @brief Deserialize an UNSUBACK, PUBACK, PUBREC, PUBREL, or PUBCOMP packet.
 *
 * Converts the packet from a stream of bytes to an #MQTTStatus_t and extracts
 * the packet identifier.
 *
 * @param[in] pAck Pointer to the MQTT packet structure representing the packet.
 * @param[out] pPacketIdentifier Packet ID of the ack type packet.
 *
 * @return #MQTTSuccess if UNSUBACK, PUBACK, PUBREC, PUBREL, or PUBCOMP is valid;
 * #MQTTBadResponse if the packet doesn't follow the MQTT spec.
 */
static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * pAck,
                                          uint16_t * pPacketIdentifier );
/**
 * @brief Deserialize a PINGRESP packet.
 *
 * Converts the packet from a stream of bytes to an #MQTTStatus_t.
 *
 * @param[in] pPingresp Pointer to an MQTT packet struct representing a PINGRESP.
 *
 * @return #MQTTSuccess if PINGRESP is valid; #MQTTBadResponse if the PINGRESP
 * packet doesn't follow MQTT spec.
 */
static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * pPingresp );
/*-----------------------------------------------------------*/
static size_t remainingLengthEncodedSize( size_t length )
{
    size_t encodedSize;
    /* Determine how many bytes are needed to encode length.
     * The values below are taken from the MQTT 3.1.1 spec. */
    /* 1 byte is needed to encode lengths between 0 and 127. */
    if( length < 128U )
    {
        encodedSize = 1U;
    }
    /* 2 bytes are needed to encode lengths between 128 and 16,383. */
    else if( length < 16384U )
    {
        encodedSize = 2U;
    }
    /* 3 bytes are needed to encode lengths between 16,384 and 2,097,151. */
    else if( length < 2097152U )
    {
        encodedSize = 3U;
    }
    /* 4 bytes are needed to encode lengths between 2,097,152 and 268,435,455. */
    else
    {
        encodedSize = 4U;
    }
    LogDebug( ( "Encoded size for length %lu is %lu bytes.",
                ( unsigned long ) length,
                ( unsigned long ) encodedSize ) );
    return encodedSize;
}
/*-----------------------------------------------------------*/
static uint8_t * encodeRemainingLength( uint8_t * pDestination,
                                        size_t length )
{
    uint8_t lengthByte;
    uint8_t * pLengthEnd = NULL;
    size_t remainingLength = length;
    assert( pDestination != NULL );
    pLengthEnd = pDestination;
    /* This algorithm is copied from the MQTT v3.1.1 spec. */
    do
    {
        lengthByte = ( uint8_t ) ( remainingLength % 128U );
        remainingLength = remainingLength / 128U;
        /* Set the high bit of this byte, indicating that there's more data. */
        if( remainingLength > 0U )
        {
            UINT8_SET_BIT( lengthByte, 7 );
        }
        /* Output a single encoded byte. */
        *pLengthEnd = lengthByte;
        pLengthEnd++;
    } while( remainingLength > 0U );
    return pLengthEnd;
}
/*-----------------------------------------------------------*/
static uint8_t * encodeString( uint8_t * pDestination,
                               const char * pSource,
                               uint16_t sourceLength )
{
    uint8_t * pBuffer = NULL;
    /* Typecast const char * typed source buffer to const uint8_t *.
     * This is to use same type buffers in memcpy. */
    const uint8_t * pSourceBuffer = ( const uint8_t * ) pSource;
    assert( pDestination != NULL );
    pBuffer = pDestination;
    /* The first byte of a UTF-8 string is the high byte of the string length. */
    *pBuffer = UINT16_HIGH_BYTE( sourceLength );
    pBuffer++;
    /* The second byte of a UTF-8 string is the low byte of the string length. */
    *pBuffer = UINT16_LOW_BYTE( sourceLength );
    pBuffer++;
    /* Copy the string into pBuffer. */
    if( pSourceBuffer != NULL )
    {
        ( void ) memcpy( pBuffer, pSourceBuffer, sourceLength );
    }
    /* Return the pointer to the end of the encoded string. */
    pBuffer = &pBuffer[ sourceLength ];
    return pBuffer;
}
/*-----------------------------------------------------------*/
static bool calculatePublishPacketSize( const MQTTPublishInfo_t * pPublishInfo,
                                        size_t * pRemainingLength,
                                        size_t * pPacketSize )
{
    bool status = true;
    size_t packetSize = 0, payloadLimit = 0;
    assert( pPublishInfo != NULL );
    assert( pRemainingLength != NULL );
    assert( pPacketSize != NULL );
    /* The variable header of a PUBLISH packet always contains the topic name.
     * The first 2 bytes of UTF-8 string contains length of the string.
     */
    packetSize += pPublishInfo->topicNameLength + sizeof( uint16_t );
    /* The variable header of a QoS 1 or 2 PUBLISH packet contains a 2-byte
     * packet identifier. */
    if( pPublishInfo->qos > MQTTQoS0 )
    {
        packetSize += sizeof( uint16_t );
    }
    /* Calculate the maximum allowed size of the payload for the given parameters.
     * This calculation excludes the "Remaining length" encoding, whose size is not
     * yet known. */
    payloadLimit = MQTT_MAX_REMAINING_LENGTH - packetSize - 1U;
    /* Ensure that the given payload fits within the calculated limit. */
    if( pPublishInfo->payloadLength > payloadLimit )
    {
        LogError( ( "PUBLISH payload length of %lu cannot exceed "
                    "%lu so as not to exceed the maximum "
                    "remaining length of MQTT 3.1.1 packet( %lu ).",
                    ( unsigned long ) pPublishInfo->payloadLength,
                    ( unsigned long ) payloadLimit,
                    MQTT_MAX_REMAINING_LENGTH ) );
        status = false;
    }
    else
    {
        /* Add the length of the PUBLISH payload. At this point, the "Remaining length"
         * has been calculated. */
        packetSize += pPublishInfo->payloadLength;
        /* Now that the "Remaining length" is known, recalculate the payload limit
         * based on the size of its encoding. */
        payloadLimit -= remainingLengthEncodedSize( packetSize );
        /* Check that the given payload fits within the size allowed by MQTT spec. */
        if( pPublishInfo->payloadLength > payloadLimit )
        {
            LogError( ( "PUBLISH payload length of %lu cannot exceed "
                        "%lu so as not to exceed the maximum "
                        "remaining length of MQTT 3.1.1 packet( %lu ).",
                        ( unsigned long ) pPublishInfo->payloadLength,
                        ( unsigned long ) payloadLimit,
                        MQTT_MAX_REMAINING_LENGTH ) );
            status = false;
        }
        else
        {
            /* Set the "Remaining length" output parameter and calculate the full
             * size of the PUBLISH packet. */
            *pRemainingLength = packetSize;
            packetSize += 1U + remainingLengthEncodedSize( packetSize );
            *pPacketSize = packetSize;
        }
    }
    LogDebug( ( "PUBLISH packet remaining length=%lu and packet size=%lu.",
                ( unsigned long ) *pRemainingLength,
                ( unsigned long ) *pPacketSize ) );
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_SerializePublishHeaderWithoutTopic( const MQTTPublishInfo_t * pPublishInfo,
                                                      size_t remainingLength,
                                                      uint8_t * pBuffer,
                                                      size_t * headerSize )
{
    size_t headerLength;
    uint8_t * pIndex;
    MQTTStatus_t status = MQTTSuccess;
    /* The first byte of a PUBLISH packet contains the packet type and flags. */
    uint8_t publishFlags = MQTT_PACKET_TYPE_PUBLISH;
    /* Get the start address of the buffer. */
    pIndex = pBuffer;
    /* Length of serialized packet = First byte
     *                               + Length of encoded remaining length
     *                               + Encoded topic length. */
    headerLength = 1U + remainingLengthEncodedSize( remainingLength ) + 2U;
    if( pPublishInfo->qos == MQTTQoS1 )
    {
        LogDebug( ( "Adding QoS as QoS1 in PUBLISH flags." ) );
        UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 );
    }
    else if( pPublishInfo->qos == MQTTQoS2 )
    {
        LogDebug( ( "Adding QoS as QoS2 in PUBLISH flags." ) );
        UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 );
    }
    else
    {
        /* Empty else MISRA 15.7 */
    }
    if( pPublishInfo->retain == true )
    {
        LogDebug( ( "Adding retain bit in PUBLISH flags." ) );
        UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN );
    }
    if( pPublishInfo->dup == true )
    {
        LogDebug( ( "Adding dup bit in PUBLISH flags." ) );
        UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP );
    }
    *pIndex = publishFlags;
    pIndex++;
    /* The "Remaining length" is encoded from the second byte. */
    pIndex = encodeRemainingLength( pIndex, remainingLength );
    /* The first byte of a UTF-8 string is the high byte of the string length. */
    *pIndex = UINT16_HIGH_BYTE( pPublishInfo->topicNameLength );
    pIndex++;
    /* The second byte of a UTF-8 string is the low byte of the string length. */
    *pIndex = UINT16_LOW_BYTE( pPublishInfo->topicNameLength );
    pIndex++;
    *headerSize = headerLength;
    return status;
}
/*-----------------------------------------------------------*/
static void serializePublishCommon( const MQTTPublishInfo_t * pPublishInfo,
                                    size_t remainingLength,
                                    uint16_t packetIdentifier,
                                    const MQTTFixedBuffer_t * pFixedBuffer,
                                    bool serializePayload )
{
    uint8_t * pIndex = NULL;
    const uint8_t * pPayloadBuffer = NULL;
    /* The first byte of a PUBLISH packet contains the packet type and flags. */
    uint8_t publishFlags = MQTT_PACKET_TYPE_PUBLISH;
    assert( pPublishInfo != NULL );
    assert( pFixedBuffer != NULL );
    assert( pFixedBuffer->pBuffer != NULL );
    /* Packet Id should be non zero for Qos 1 and Qos 2. */
    assert( ( pPublishInfo->qos == MQTTQoS0 ) || ( packetIdentifier != 0U ) );
    /* Duplicate flag should be set only for Qos 1 or Qos 2. */
    assert( ( pPublishInfo->dup != true ) || ( pPublishInfo->qos != MQTTQoS0 ) );
    /* Get the start address of the buffer. */
    pIndex = pFixedBuffer->pBuffer;
    if( pPublishInfo->qos == MQTTQoS1 )
    {
        LogDebug( ( "Adding QoS as QoS1 in PUBLISH flags." ) );
        UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 );
    }
    else if( pPublishInfo->qos == MQTTQoS2 )
    {
        LogDebug( ( "Adding QoS as QoS2 in PUBLISH flags." ) );
        UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 );
    }
    else
    {
        /* Empty else MISRA 15.7 */
    }
    if( pPublishInfo->retain == true )
    {
        LogDebug( ( "Adding retain bit in PUBLISH flags." ) );
        UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN );
    }
    if( pPublishInfo->dup == true )
    {
        LogDebug( ( "Adding dup bit in PUBLISH flags." ) );
        UINT8_SET_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP );
    }
    *pIndex = publishFlags;
    pIndex++;
    /* The "Remaining length" is encoded from the second byte. */
    pIndex = encodeRemainingLength( pIndex, remainingLength );
    /* The topic name is placed after the "Remaining length". */
    pIndex = encodeString( pIndex,
                           pPublishInfo->pTopicName,
                           pPublishInfo->topicNameLength );
    /* A packet identifier is required for QoS 1 and 2 messages. */
    if( pPublishInfo->qos > MQTTQoS0 )
    {
        LogDebug( ( "Adding packet Id in PUBLISH packet." ) );
        /* Place the packet identifier into the PUBLISH packet. */
        *pIndex = UINT16_HIGH_BYTE( packetIdentifier );
        pIndex[ 1U ] = UINT16_LOW_BYTE( packetIdentifier );
        pIndex = &pIndex[ 2U ];
    }
    /* The payload is placed after the packet identifier.
     * Payload is copied over only if required by the flag serializePayload.
     * This will help reduce an unnecessary copy of the payload into the buffer.
     */
    if( ( pPublishInfo->payloadLength > 0U ) &&
        ( serializePayload == true ) )
    {
        LogDebug( ( "Copying PUBLISH payload of length =%lu to buffer",
                    ( unsigned long ) pPublishInfo->payloadLength ) );
        /* Typecast const void * typed payload buffer to const uint8_t *.
         * This is to use same type buffers in memcpy. */
        pPayloadBuffer = ( const uint8_t * ) pPublishInfo->pPayload;
        ( void ) memcpy( pIndex, pPayloadBuffer, pPublishInfo->payloadLength );
        /* Move the index to after the payload. */
        pIndex = &pIndex[ pPublishInfo->payloadLength ];
    }
    /* Ensure that the difference between the end and beginning of the buffer
     * is less than the buffer size. */
    assert( ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) <= pFixedBuffer->size );
}
static size_t getRemainingLength( TransportRecv_t recvFunc,
                                  NetworkContext_t * pNetworkContext )
{
    size_t remainingLength = 0, multiplier = 1, bytesDecoded = 0, expectedSize = 0;
    uint8_t encodedByte = 0;
    int32_t bytesReceived = 0;
    /* This algorithm is copied from the MQTT v3.1.1 spec. */
    do
    {
        if( multiplier > 2097152U ) /* 128 ^ 3 */
        {
            remainingLength = MQTT_REMAINING_LENGTH_INVALID;
        }
        else
        {
            bytesReceived = recvFunc( pNetworkContext, &encodedByte, 1U );
            if( bytesReceived == 1 )
            {
                remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier;
                multiplier *= 128U;
                bytesDecoded++;
            }
            else
            {
                remainingLength = MQTT_REMAINING_LENGTH_INVALID;
            }
        }
        if( remainingLength == MQTT_REMAINING_LENGTH_INVALID )
        {
            break;
        }
    } while( ( encodedByte & 0x80U ) != 0U );
    /* Check that the decoded remaining length conforms to the MQTT specification. */
    if( remainingLength != MQTT_REMAINING_LENGTH_INVALID )
    {
        expectedSize = remainingLengthEncodedSize( remainingLength );
        if( bytesDecoded != expectedSize )
        {
            remainingLength = MQTT_REMAINING_LENGTH_INVALID;
        }
    }
    return remainingLength;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t processRemainingLength( const uint8_t * pBuffer,
                                            const size_t * pIndex,
                                            MQTTPacketInfo_t * pIncomingPacket )
{
    size_t remainingLength = 0;
    size_t multiplier = 1;
    size_t bytesDecoded = 0;
    size_t expectedSize = 0;
    uint8_t encodedByte = 0;
    MQTTStatus_t status = MQTTSuccess;
    /* This algorithm is copied from the MQTT v3.1.1 spec. */
    do
    {
        if( multiplier > 2097152U ) /* 128 ^ 3 */
        {
            remainingLength = MQTT_REMAINING_LENGTH_INVALID;
            LogError( ( "Invalid remaining length in the packet.\n" ) );
            status = MQTTBadResponse;
        }
        else
        {
            if( *pIndex > ( bytesDecoded + 1U ) )
            {
                /* Get the next byte. It is at the next position after the bytes
                 * decoded till now since the header of one byte was read before. */
                encodedByte = pBuffer[ bytesDecoded + 1U ];
                remainingLength += ( ( size_t ) encodedByte & 0x7FU ) * multiplier;
                multiplier *= 128U;
                bytesDecoded++;
            }
            else
            {
                status = MQTTNeedMoreBytes;
            }
        }
        /* If the response is incorrect, or no more data is available, then
         * break out of the loop. */
        if( ( remainingLength == MQTT_REMAINING_LENGTH_INVALID ) ||
            ( status != MQTTSuccess ) )
        {
            break;
        }
    } while( ( encodedByte & 0x80U ) != 0U );
    if( status == MQTTSuccess )
    {
        /* Check that the decoded remaining length conforms to the MQTT specification. */
        expectedSize = remainingLengthEncodedSize( remainingLength );
        if( bytesDecoded != expectedSize )
        {
            LogError( ( "Expected and actual length of decoded bytes do not match.\n" ) );
            status = MQTTBadResponse;
        }
        else
        {
            pIncomingPacket->remainingLength = remainingLength;
            pIncomingPacket->headerLength = bytesDecoded + 1U;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static bool incomingPacketValid( uint8_t packetType )
{
    bool status = false;
    /* Check packet type. Mask out lower bits to ignore flags. */
    switch( packetType & 0xF0U )
    {
        /* Valid incoming packet types. */
        case MQTT_PACKET_TYPE_CONNACK:
        case MQTT_PACKET_TYPE_PUBLISH:
        case MQTT_PACKET_TYPE_PUBACK:
        case MQTT_PACKET_TYPE_PUBREC:
        case MQTT_PACKET_TYPE_PUBCOMP:
        case MQTT_PACKET_TYPE_SUBACK:
        case MQTT_PACKET_TYPE_UNSUBACK:
        case MQTT_PACKET_TYPE_PINGRESP:
            status = true;
            break;
        case ( MQTT_PACKET_TYPE_PUBREL & 0xF0U ):
            /* The second bit of a PUBREL must be set. */
            if( ( packetType & 0x02U ) > 0U )
            {
                status = true;
            }
            break;
        /* Any other packet type is invalid. */
        default:
            LogWarn( ( "Incoming packet invalid: Packet type=%u.",
                       ( unsigned int ) packetType ) );
            break;
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t checkPublishRemainingLength( size_t remainingLength,
                                                 MQTTQoS_t qos,
                                                 size_t qos0Minimum )
{
    MQTTStatus_t status = MQTTSuccess;
    /* Sanity checks for "Remaining length". */
    if( qos == MQTTQoS0 )
    {
        /* Check that the "Remaining length" is greater than the minimum. */
        if( remainingLength < qos0Minimum )
        {
            LogError( ( "QoS 0 PUBLISH cannot have a remaining length less than %lu.",
                        ( unsigned long ) qos0Minimum ) );
            status = MQTTBadResponse;
        }
    }
    else
    {
        /* Check that the "Remaining length" is greater than the minimum. For
         * QoS 1 or 2, this will be two bytes greater than for QoS 0 due to the
         * packet identifier. */
        if( remainingLength < ( qos0Minimum + 2U ) )
        {
            LogError( ( "QoS 1 or 2 PUBLISH cannot have a remaining length less than %lu.",
                        ( unsigned long ) ( qos0Minimum + 2U ) ) );
            status = MQTTBadResponse;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t processPublishFlags( uint8_t publishFlags,
                                         MQTTPublishInfo_t * pPublishInfo )
{
    MQTTStatus_t status = MQTTSuccess;
    assert( pPublishInfo != NULL );
    /* Check for QoS 2. */
    if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS2 ) )
    {
        /* PUBLISH packet is invalid if both QoS 1 and QoS 2 bits are set. */
        if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) )
        {
            LogError( ( "Bad QoS: 3." ) );
            status = MQTTBadResponse;
        }
        else
        {
            pPublishInfo->qos = MQTTQoS2;
        }
    }
    /* Check for QoS 1. */
    else if( UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_QOS1 ) )
    {
        pPublishInfo->qos = MQTTQoS1;
    }
    /* If the PUBLISH isn't QoS 1 or 2, then it's QoS 0. */
    else
    {
        pPublishInfo->qos = MQTTQoS0;
    }
    if( status == MQTTSuccess )
    {
        LogDebug( ( "QoS is %d.", ( int ) pPublishInfo->qos ) );
        /* Parse the Retain bit. */
        pPublishInfo->retain = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_RETAIN );
        LogDebug( ( "Retain bit is %d.", ( int ) pPublishInfo->retain ) );
        /* Parse the DUP bit. */
        pPublishInfo->dup = UINT8_CHECK_BIT( publishFlags, MQTT_PUBLISH_FLAG_DUP );
        LogDebug( ( "DUP bit is %d.", ( int ) pPublishInfo->dup ) );
    }
    return status;
}
/*-----------------------------------------------------------*/
static void logConnackResponse( uint8_t responseCode )
{
    const char * const pConnackResponses[ 6 ] =
    {
        "Connection accepted.",                               /* 0 */
        "Connection refused: unacceptable protocol version.", /* 1 */
        "Connection refused: identifier rejected.",           /* 2 */
        "Connection refused: server unavailable",             /* 3 */
        "Connection refused: bad user name or password.",     /* 4 */
        "Connection refused: not authorized."                 /* 5 */
    };
    /* Avoid unused parameter warning when assert and logs are disabled. */
    ( void ) responseCode;
    ( void ) pConnackResponses;
    assert( responseCode <= 5U );
    if( responseCode == 0u )
    {
        /* Log at Debug level for a success CONNACK response. */
        LogDebug( ( "%s", pConnackResponses[ 0 ] ) );
    }
    else
    {
        /* Log an error based on the CONNACK response code. */
        LogError( ( "%s", pConnackResponses[ responseCode ] ) );
    }
}
/*-----------------------------------------------------------*/
static MQTTStatus_t deserializeConnack( const MQTTPacketInfo_t * pConnack,
                                        bool * pSessionPresent )
{
    MQTTStatus_t status = MQTTSuccess;
    const uint8_t * pRemainingData = NULL;
    assert( pConnack != NULL );
    assert( pSessionPresent != NULL );
    pRemainingData = pConnack->pRemainingData;
    /* According to MQTT 3.1.1, the second byte of CONNACK must specify a
     * "Remaining length" of 2. */
    if( pConnack->remainingLength != MQTT_PACKET_CONNACK_REMAINING_LENGTH )
    {
        LogError( ( "CONNACK does not have remaining length of %u.",
                    ( unsigned int ) MQTT_PACKET_CONNACK_REMAINING_LENGTH ) );
        status = MQTTBadResponse;
    }
    /* Check the reserved bits in CONNACK. The high 7 bits of the third byte
     * in CONNACK must be 0. */
    else if( ( pRemainingData[ 0 ] | 0x01U ) != 0x01U )
    {
        LogError( ( "Reserved bits in CONNACK incorrect." ) );
        status = MQTTBadResponse;
    }
    else
    {
        /* Determine if the "Session Present" bit is set. This is the lowest bit of
         * the third byte in CONNACK. */
        if( ( pRemainingData[ 0 ] & MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK )
            == MQTT_PACKET_CONNACK_SESSION_PRESENT_MASK )
        {
            LogDebug( ( "CONNACK session present bit set." ) );
            *pSessionPresent = true;
            /* MQTT 3.1.1 specifies that the fourth byte in CONNACK must be 0 if the
             * "Session Present" bit is set. */
            if( pRemainingData[ 1 ] != 0U )
            {
                LogError( ( "Session Present bit is set, but connect return code in CONNACK is %u (nonzero).",
                            ( unsigned int ) pRemainingData[ 1 ] ) );
                status = MQTTBadResponse;
            }
        }
        else
        {
            LogDebug( ( "CONNACK session present bit not set." ) );
            *pSessionPresent = false;
        }
    }
    if( status == MQTTSuccess )
    {
        /* In MQTT 3.1.1, only values 0 through 5 are valid CONNACK response codes. */
        if( pRemainingData[ 1 ] > 5U )
        {
            LogError( ( "CONNACK response %u is invalid.",
                        ( unsigned int ) pRemainingData[ 1 ] ) );
            status = MQTTBadResponse;
        }
        else
        {
            /* Print the appropriate message for the CONNACK response code if logs are
             * enabled. */
            logConnackResponse( pRemainingData[ 1 ] );
            /* A nonzero CONNACK response code means the connection was refused. */
            if( pRemainingData[ 1 ] > 0U )
            {
                status = MQTTServerRefused;
            }
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t calculateSubscriptionPacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
                                                     size_t subscriptionCount,
                                                     size_t * pRemainingLength,
                                                     size_t * pPacketSize,
                                                     MQTTSubscriptionType_t subscriptionType )
{
    MQTTStatus_t status = MQTTSuccess;
    size_t i = 0, packetSize = 0;
    assert( pSubscriptionList != NULL );
    assert( subscriptionCount != 0U );
    assert( pRemainingLength != NULL );
    assert( pPacketSize != NULL );
    /* The variable header of a subscription packet consists of a 2-byte packet
     * identifier. */
    packetSize += sizeof( uint16_t );
    /* Sum the lengths of all subscription topic filters; add 1 byte for each
     * subscription's QoS if type is MQTT_SUBSCRIBE. */
    for( i = 0; i < subscriptionCount; i++ )
    {
        /* Add the length of the topic filter. MQTT strings are prepended
         * with 2 byte string length field. Hence 2 bytes are added to size. */
        packetSize += pSubscriptionList[ i ].topicFilterLength + sizeof( uint16_t );
        /* Only SUBSCRIBE packets include the QoS. */
        if( subscriptionType == MQTT_SUBSCRIBE )
        {
            packetSize += 1U;
        }
        /* Validate each topic filter. */
        if( ( pSubscriptionList[ i ].topicFilterLength == 0U ) ||
            ( pSubscriptionList[ i ].pTopicFilter == NULL ) )
        {
            status = MQTTBadParameter;
            LogError( ( "Subscription #%lu in %sSUBSCRIBE packet cannot be empty.",
                        ( unsigned long ) i,
                        ( subscriptionType == MQTT_SUBSCRIBE ) ? "" : "UN" ) );
            /* It is not necessary to break as an error status has already been set. */
        }
    }
    /* At this point, the "Remaining length" has been calculated. Return error
     * if the "Remaining length" exceeds what is allowed by MQTT 3.1.1. Otherwise,
     * set the output parameter.*/
    if( packetSize > MQTT_MAX_REMAINING_LENGTH )
    {
        LogError( ( "Subscription packet length of %lu exceeds"
                    "the MQTT 3.1.1 maximum packet length of %lu.",
                    ( unsigned long ) packetSize,
                    MQTT_MAX_REMAINING_LENGTH ) );
        status = MQTTBadParameter;
    }
    if( status == MQTTSuccess )
    {
        *pRemainingLength = packetSize;
        /* Calculate the full size of the subscription packet by adding
         * number of bytes required to encode the "Remaining length" field
         * plus 1 byte for the "Packet type" field. */
        packetSize += 1U + remainingLengthEncodedSize( packetSize );
        /*Set the pPacketSize output parameter. */
        *pPacketSize = packetSize;
    }
    LogDebug( ( "Subscription packet remaining length=%lu and packet size=%lu.",
                ( unsigned long ) *pRemainingLength,
                ( unsigned long ) *pPacketSize ) );
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t readSubackStatus( size_t statusCount,
                                      const uint8_t * pStatusStart )
{
    MQTTStatus_t status = MQTTSuccess;
    uint8_t subscriptionStatus = 0;
    size_t i = 0;
    assert( pStatusStart != NULL );
    /* Iterate through each status byte in the SUBACK packet. */
    for( i = 0; i < statusCount; i++ )
    {
        /* Read a single status byte in SUBACK. */
        subscriptionStatus = pStatusStart[ i ];
        /* MQTT 3.1.1 defines the following values as status codes. */
        switch( subscriptionStatus )
        {
            case 0x00:
            case 0x01:
            case 0x02:
                LogDebug( ( "Topic filter %lu accepted, max QoS %u.",
                            ( unsigned long ) i,
                            ( unsigned int ) subscriptionStatus ) );
                break;
            case 0x80:
                LogWarn( ( "Topic filter %lu refused.", ( unsigned long ) i ) );
                /* Application should remove subscription from the list */
                status = MQTTServerRefused;
                break;
            default:
                LogError( ( "Bad SUBSCRIBE status %u.",
                            ( unsigned int ) subscriptionStatus ) );
                status = MQTTBadResponse;
                break;
        }
        /* Stop parsing the subscription statuses if a bad response was received. */
        if( status == MQTTBadResponse )
        {
            break;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t deserializeSuback( const MQTTPacketInfo_t * pSuback,
                                       uint16_t * pPacketIdentifier )
{
    MQTTStatus_t status = MQTTSuccess;
    size_t remainingLength;
    const uint8_t * pVariableHeader = NULL;
    assert( pSuback != NULL );
    assert( pPacketIdentifier != NULL );
    remainingLength = pSuback->remainingLength;
    pVariableHeader = pSuback->pRemainingData;
    /* A SUBACK must have a remaining length of at least 3 to accommodate the
     * packet identifier and at least 1 return code. */
    if( remainingLength < 3U )
    {
        LogError( ( "SUBACK cannot have a remaining length less than 3." ) );
        status = MQTTBadResponse;
    }
    else
    {
        /* Extract the packet identifier (first 2 bytes of variable header) from SUBACK. */
        *pPacketIdentifier = UINT16_DECODE( pVariableHeader );
        LogDebug( ( "Packet identifier %hu.",
                    ( unsigned short ) *pPacketIdentifier ) );
        if( *pPacketIdentifier == 0U )
        {
            status = MQTTBadResponse;
        }
        else
        {
            status = readSubackStatus( remainingLength - sizeof( uint16_t ),
                                       &pVariableHeader[ sizeof( uint16_t ) ] );
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t validateSubscriptionSerializeParams( const MQTTSubscribeInfo_t * pSubscriptionList,
                                                         size_t subscriptionCount,
                                                         uint16_t packetId,
                                                         size_t remainingLength,
                                                         const MQTTFixedBuffer_t * pFixedBuffer )
{
    MQTTStatus_t status = MQTTSuccess;
    size_t packetSize = 0;
    /* Validate all the parameters. */
    if( ( pFixedBuffer == NULL ) || ( pSubscriptionList == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, "
                    "pSubscriptionList=%p.",
                    ( void * ) pFixedBuffer,
                    ( void * ) pSubscriptionList ) );
        status = MQTTBadParameter;
    }
    /* A buffer must be configured for serialization. */
    else if( pFixedBuffer->pBuffer == NULL )
    {
        LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) );
        status = MQTTBadParameter;
    }
    else if( subscriptionCount == 0U )
    {
        LogError( ( "Subscription count is 0." ) );
        status = MQTTBadParameter;
    }
    else if( packetId == 0U )
    {
        LogError( ( "Packet Id for subscription packet is 0." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* The serialized packet size = First byte
         * + length of encoded size of remaining length
         * + remaining length. */
        packetSize = 1U + remainingLengthEncodedSize( remainingLength )
                     + remainingLength;
        if( packetSize > pFixedBuffer->size )
        {
            LogError( ( "Buffer size of %lu is not sufficient to hold "
                        "serialized packet of size of %lu.",
                        ( unsigned long ) pFixedBuffer->size,
                        ( unsigned long ) packetSize ) );
            status = MQTTNoMemory;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t deserializePublish( const MQTTPacketInfo_t * pIncomingPacket,
                                        uint16_t * pPacketId,
                                        MQTTPublishInfo_t * pPublishInfo )
{
    MQTTStatus_t status = MQTTSuccess;
    const uint8_t * pVariableHeader, * pPacketIdentifierHigh = NULL;
    assert( pIncomingPacket != NULL );
    assert( pPacketId != NULL );
    assert( pPublishInfo != NULL );
    assert( pIncomingPacket->pRemainingData != NULL );
    pVariableHeader = pIncomingPacket->pRemainingData;
    /* The flags are the lower 4 bits of the first byte in PUBLISH. */
    status = processPublishFlags( ( pIncomingPacket->type & 0x0FU ), pPublishInfo );
    if( status == MQTTSuccess )
    {
        /* Sanity checks for "Remaining length". A QoS 0 PUBLISH  must have a remaining
         * length of at least 3 to accommodate topic name length (2 bytes) and topic
         * name (at least 1 byte). A QoS 1 or 2 PUBLISH must have a remaining length of
         * at least 5 for the packet identifier in addition to the topic name length and
         * topic name. */
        status = checkPublishRemainingLength( pIncomingPacket->remainingLength,
                                              pPublishInfo->qos,
                                              MQTT_MIN_PUBLISH_REMAINING_LENGTH_QOS0 );
    }
    if( status == MQTTSuccess )
    {
        /* Extract the topic name starting from the first byte of the variable header.
         * The topic name string starts at byte 3 in the variable header. */
        pPublishInfo->topicNameLength = UINT16_DECODE( pVariableHeader );
        /* Sanity checks for topic name length and "Remaining length". The remaining
         * length must be at least as large as the variable length header. */
        status = checkPublishRemainingLength( pIncomingPacket->remainingLength,
                                              pPublishInfo->qos,
                                              pPublishInfo->topicNameLength + sizeof( uint16_t ) );
    }
    if( status == MQTTSuccess )
    {
        /* Parse the topic. */
        pPublishInfo->pTopicName = ( const char * ) ( &pVariableHeader[ sizeof( uint16_t ) ] );
        LogDebug( ( "Topic name length: %hu.", ( unsigned short ) pPublishInfo->topicNameLength ) );
        /* Extract the packet identifier for QoS 1 or 2 PUBLISH packets. Packet
         * identifier starts immediately after the topic name. */
        pPacketIdentifierHigh = ( const uint8_t * ) ( &pPublishInfo->pTopicName[ pPublishInfo->topicNameLength ] );
        if( pPublishInfo->qos > MQTTQoS0 )
        {
            *pPacketId = UINT16_DECODE( pPacketIdentifierHigh );
            LogDebug( ( "Packet identifier %hu.",
                        ( unsigned short ) *pPacketId ) );
            /* Advance pointer two bytes to start of payload as in the QoS 0 case. */
            pPacketIdentifierHigh = &pPacketIdentifierHigh[ sizeof( uint16_t ) ];
            /* Packet identifier cannot be 0. */
            if( *pPacketId == 0U )
            {
                LogError( ( "Packet identifier cannot be 0." ) );
                status = MQTTBadResponse;
            }
        }
    }
    if( status == MQTTSuccess )
    {
        /* Calculate the length of the payload. QoS 1 or 2 PUBLISH packets contain
         * a packet identifier, but QoS 0 PUBLISH packets do not. */
        pPublishInfo->payloadLength = pIncomingPacket->remainingLength - pPublishInfo->topicNameLength - sizeof( uint16_t );
        if( pPublishInfo->qos != MQTTQoS0 )
        {
            /* Two more bytes for the packet identifier. */
            pPublishInfo->payloadLength -= sizeof( uint16_t );
        }
        /* Set payload if it exists. */
        pPublishInfo->pPayload = ( pPublishInfo->payloadLength != 0U ) ? pPacketIdentifierHigh : NULL;
        LogDebug( ( "Payload length %lu.",
                    ( unsigned long ) pPublishInfo->payloadLength ) );
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t deserializeSimpleAck( const MQTTPacketInfo_t * pAck,
                                          uint16_t * pPacketIdentifier )
{
    MQTTStatus_t status = MQTTSuccess;
    assert( pAck != NULL );
    assert( pPacketIdentifier != NULL );
    /* Check that the "Remaining length" of the received ACK is 2. */
    if( pAck->remainingLength != MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH )
    {
        LogError( ( "ACK does not have remaining length of %u.",
                    ( unsigned int ) MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH ) );
        status = MQTTBadResponse;
    }
    else
    {
        /* Extract the packet identifier (third and fourth bytes) from ACK. */
        *pPacketIdentifier = UINT16_DECODE( pAck->pRemainingData );
        LogDebug( ( "Packet identifier %hu.",
                    ( unsigned short ) *pPacketIdentifier ) );
        /* Packet identifier cannot be 0. */
        if( *pPacketIdentifier == 0U )
        {
            LogError( ( "Packet identifier cannot be 0." ) );
            status = MQTTBadResponse;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t deserializePingresp( const MQTTPacketInfo_t * pPingresp )
{
    MQTTStatus_t status = MQTTSuccess;
    assert( pPingresp != NULL );
    /* Check the "Remaining length" (second byte) of the received PINGRESP is 0. */
    if( pPingresp->remainingLength != MQTT_PACKET_PINGRESP_REMAINING_LENGTH )
    {
        LogError( ( "PINGRESP does not have remaining length of %u.",
                    MQTT_PACKET_PINGRESP_REMAINING_LENGTH ) );
        status = MQTTBadResponse;
    }
    return status;
}
uint8_t * MQTT_SerializeConnectFixedHeader( uint8_t * pIndex,
                                            const MQTTConnectInfo_t * pConnectInfo,
                                            const MQTTPublishInfo_t * pWillInfo,
                                            size_t remainingLength )
{
    uint8_t * pIndexLocal = pIndex;
    uint8_t connectFlags = 0U;
    /* The first byte in the CONNECT packet is the control packet type. */
    *pIndexLocal = MQTT_PACKET_TYPE_CONNECT;
    pIndexLocal++;
    /* The remaining length of the CONNECT packet is encoded starting from the
     * second byte. The remaining length does not include the length of the fixed
     * header or the encoding of the remaining length. */
    pIndexLocal = encodeRemainingLength( pIndexLocal, remainingLength );
    /* The string "MQTT" is placed at the beginning of the CONNECT packet's variable
     * header. This string is 4 bytes long. */
    pIndexLocal = encodeString( pIndexLocal, "MQTT", 4 );
    /* The MQTT protocol version is the second field of the variable header. */
    *pIndexLocal = MQTT_VERSION_3_1_1;
    pIndexLocal++;
    /* Set the clean session flag if needed. */
    if( pConnectInfo->cleanSession == true )
    {
        UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_CLEAN );
    }
    /* Set the flags for username and password if provided. */
    if( pConnectInfo->pUserName != NULL )
    {
        UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_USERNAME );
    }
    if( pConnectInfo->pPassword != NULL )
    {
        UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_PASSWORD );
    }
    /* Set will flag if a Last Will and Testament is provided. */
    if( pWillInfo != NULL )
    {
        UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL );
        /* Flags only need to be changed for Will QoS 1 or 2. */
        if( pWillInfo->qos == MQTTQoS1 )
        {
            UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS1 );
        }
        else if( pWillInfo->qos == MQTTQoS2 )
        {
            UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_QOS2 );
        }
        else
        {
            /* Empty else MISRA 15.7 */
        }
        if( pWillInfo->retain == true )
        {
            UINT8_SET_BIT( connectFlags, MQTT_CONNECT_FLAG_WILL_RETAIN );
        }
    }
    *pIndexLocal = connectFlags;
    pIndexLocal++;
    /* Write the 2 bytes of the keep alive interval into the CONNECT packet. */
    pIndexLocal[ 0 ] = UINT16_HIGH_BYTE( pConnectInfo->keepAliveSeconds );
    pIndexLocal[ 1 ] = UINT16_LOW_BYTE( pConnectInfo->keepAliveSeconds );
    pIndexLocal = &pIndexLocal[ 2 ];
    return pIndexLocal;
}
/*-----------------------------------------------------------*/
static void serializeConnectPacket( const MQTTConnectInfo_t * pConnectInfo,
                                    const MQTTPublishInfo_t * pWillInfo,
                                    size_t remainingLength,
                                    const MQTTFixedBuffer_t * pFixedBuffer )
{
    uint8_t * pIndex = NULL;
    assert( pConnectInfo != NULL );
    assert( pFixedBuffer != NULL );
    assert( pFixedBuffer->pBuffer != NULL );
    pIndex = pFixedBuffer->pBuffer;
    /* Serialize the header. */
    pIndex = MQTT_SerializeConnectFixedHeader( pIndex,
                                               pConnectInfo,
                                               pWillInfo,
                                               remainingLength );
    /* Write the client identifier into the CONNECT packet. */
    pIndex = encodeString( pIndex,
                           pConnectInfo->pClientIdentifier,
                           pConnectInfo->clientIdentifierLength );
    /* Write the will topic name and message into the CONNECT packet if provided. */
    if( pWillInfo != NULL )
    {
        pIndex = encodeString( pIndex,
                               pWillInfo->pTopicName,
                               pWillInfo->topicNameLength );
        pIndex = encodeString( pIndex,
                               pWillInfo->pPayload,
                               ( uint16_t ) pWillInfo->payloadLength );
    }
    /* Encode the user name if provided. */
    if( pConnectInfo->pUserName != NULL )
    {
        pIndex = encodeString( pIndex, pConnectInfo->pUserName, pConnectInfo->userNameLength );
    }
    /* Encode the password if provided. */
    if( pConnectInfo->pPassword != NULL )
    {
        pIndex = encodeString( pIndex, pConnectInfo->pPassword, pConnectInfo->passwordLength );
    }
    LogDebug( ( "Length of serialized CONNECT packet is %lu.",
                ( ( unsigned long ) ( pIndex - pFixedBuffer->pBuffer ) ) ) );
    /* Ensure that the difference between the end and beginning of the buffer
     * is less than the buffer size. */
    assert( ( ( size_t ) ( pIndex - pFixedBuffer->pBuffer ) ) <= pFixedBuffer->size );
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo,
                                        const MQTTPublishInfo_t * pWillInfo,
                                        size_t * pRemainingLength,
                                        size_t * pPacketSize )
{
    MQTTStatus_t status = MQTTSuccess;
    size_t remainingLength;
    /* The CONNECT packet will always include a 10-byte variable header. */
    size_t connectPacketSize = MQTT_PACKET_CONNECT_HEADER_SIZE;
    /* Validate arguments. */
    if( ( pConnectInfo == NULL ) || ( pRemainingLength == NULL ) ||
        ( pPacketSize == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pConnectInfo=%p, "
                    "pRemainingLength=%p, pPacketSize=%p.",
                    ( void * ) pConnectInfo,
                    ( void * ) pRemainingLength,
                    ( void * ) pPacketSize ) );
        status = MQTTBadParameter;
    }
    else if( ( pConnectInfo->clientIdentifierLength == 0U ) || ( pConnectInfo->pClientIdentifier == NULL ) )
    {
        LogError( ( "Mqtt_GetConnectPacketSize() client identifier must be set." ) );
        status = MQTTBadParameter;
    }
    else if( ( pWillInfo != NULL ) && ( pWillInfo->payloadLength > ( size_t ) UINT16_MAX ) )
    {
        /* The MQTTPublishInfo_t is reused for the will message. The payload
         * length for any other message could be larger than 65,535, but
         * the will message length is required to be represented in 2 bytes.
         * By bounding the payloadLength of the will message, the CONNECT
         * packet will never be larger than 327699 bytes. */
        LogError( ( "The Will Message length must not exceed %d. "
                    "pWillInfo->payloadLength=%lu.",
                    UINT16_MAX,
                    ( unsigned long ) pWillInfo->payloadLength ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Add the length of the client identifier. */
        connectPacketSize += pConnectInfo->clientIdentifierLength + sizeof( uint16_t );
        /* Add the lengths of the will message and topic name if provided. */
        if( pWillInfo != NULL )
        {
            connectPacketSize += pWillInfo->topicNameLength + sizeof( uint16_t ) +
                                 pWillInfo->payloadLength + sizeof( uint16_t );
        }
        /* Add the lengths of the user name and password if provided. */
        if( pConnectInfo->pUserName != NULL )
        {
            connectPacketSize += pConnectInfo->userNameLength + sizeof( uint16_t );
        }
        if( pConnectInfo->pPassword != NULL )
        {
            connectPacketSize += pConnectInfo->passwordLength + sizeof( uint16_t );
        }
        /* At this point, the "Remaining Length" field of the MQTT CONNECT packet has
         * been calculated. */
        remainingLength = connectPacketSize;
        /* Calculate the full size of the MQTT CONNECT packet by adding the size of
         * the "Remaining Length" field plus 1 byte for the "Packet Type" field. */
        connectPacketSize += 1U + remainingLengthEncodedSize( connectPacketSize );
        /* The connectPacketSize calculated from this function's parameters is
         * guaranteed to be less than the maximum MQTT CONNECT packet size, which
         * is 327700. If the maximum client identifier length, the maximum will
         * message topic length, the maximum will topic payload length, the
         * maximum username length, and the maximum password length are all present
         * in the MQTT CONNECT packet, the total size will be calculated to be
         * 327699:
         * (variable length header)10 +
         * (maximum client identifier length) 65535 + (encoded length) 2 +
         * (maximum will message topic name length) 65535 + (encoded length)2 +
         * (maximum will message payload length) 65535 + 2 +
         * (maximum username length) 65535 + (encoded length) 2 +
         * (maximum password length) 65535 + (encoded length) 2 +
         * (packet type field length) 1 +
         * (CONNECT packet encoded length) 3 = 327699 */
        *pRemainingLength = remainingLength;
        *pPacketSize = connectPacketSize;
        LogDebug( ( "CONNECT packet remaining length=%lu and packet size=%lu.",
                    ( unsigned long ) *pRemainingLength,
                    ( unsigned long ) *pPacketSize ) );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo,
                                    const MQTTPublishInfo_t * pWillInfo,
                                    size_t remainingLength,
                                    const MQTTFixedBuffer_t * pFixedBuffer )
{
    MQTTStatus_t status = MQTTSuccess;
    size_t connectPacketSize = 0;
    /* Validate arguments. */
    if( ( pConnectInfo == NULL ) || ( pFixedBuffer == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pConnectInfo=%p, "
                    "pFixedBuffer=%p.",
                    ( void * ) pConnectInfo,
                    ( void * ) pFixedBuffer ) );
        status = MQTTBadParameter;
    }
    /* A buffer must be configured for serialization. */
    else if( pFixedBuffer->pBuffer == NULL )
    {
        LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) );
        status = MQTTBadParameter;
    }
    else if( ( pWillInfo != NULL ) && ( pWillInfo->pTopicName == NULL ) )
    {
        LogError( ( "pWillInfo->pTopicName cannot be NULL if Will is present." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Calculate CONNECT packet size. Overflow in in this addition is not checked
         * because it is part of the API contract to call Mqtt_GetConnectPacketSize()
         * before this function. */
        connectPacketSize = remainingLength + remainingLengthEncodedSize( remainingLength ) + 1U;
        /* Check that the full packet size fits within the given buffer. */
        if( connectPacketSize > pFixedBuffer->size )
        {
            LogError( ( "Buffer size of %lu is not sufficient to hold "
                        "serialized CONNECT packet of size of %lu.",
                        ( unsigned long ) pFixedBuffer->size,
                        ( unsigned long ) connectPacketSize ) );
            status = MQTTNoMemory;
        }
        else
        {
            serializeConnectPacket( pConnectInfo,
                                    pWillInfo,
                                    remainingLength,
                                    pFixedBuffer );
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
                                          size_t subscriptionCount,
                                          size_t * pRemainingLength,
                                          size_t * pPacketSize )
{
    MQTTStatus_t status = MQTTSuccess;
    /* Validate parameters. */
    if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) ||
        ( pPacketSize == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pSubscriptionList=%p, "
                    "pRemainingLength=%p, pPacketSize=%p.",
                    ( void * ) pSubscriptionList,
                    ( void * ) pRemainingLength,
                    ( void * ) pPacketSize ) );
        status = MQTTBadParameter;
    }
    else if( subscriptionCount == 0U )
    {
        LogError( ( "subscriptionCount is 0." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Calculate the MQTT SUBSCRIBE packet size. */
        status = calculateSubscriptionPacketSize( pSubscriptionList,
                                                  subscriptionCount,
                                                  pRemainingLength,
                                                  pPacketSize,
                                                  MQTT_SUBSCRIBE );
    }
    return status;
}
/*-----------------------------------------------------------*/
uint8_t * MQTT_SerializeSubscribeHeader( size_t remainingLength,
                                         uint8_t * pIndex,
                                         uint16_t packetId )
{
    uint8_t * pIterator = pIndex;
    /* The first byte in SUBSCRIBE is the packet type. */
    *pIterator = MQTT_PACKET_TYPE_SUBSCRIBE;
    pIterator++;
    /* Encode the "Remaining length" starting from the second byte. */
    pIterator = encodeRemainingLength( pIterator, remainingLength );
    /* Place the packet identifier into the SUBSCRIBE packet. */
    pIterator[ 0 ] = UINT16_HIGH_BYTE( packetId );
    pIterator[ 1 ] = UINT16_LOW_BYTE( packetId );
    /* Advance the pointer. */
    pIterator = &pIterator[ 2 ];
    return pIterator;
}
/*-----------------------------------------------------------*/
uint8_t * MQTT_SerializeUnsubscribeHeader( size_t remainingLength,
                                           uint8_t * pIndex,
                                           uint16_t packetId )
{
    uint8_t * pIterator = pIndex;
    /* The first byte in UNSUBSCRIBE is the packet type. */
    *pIterator = MQTT_PACKET_TYPE_UNSUBSCRIBE;
    pIterator++;
    /* Encode the "Remaining length" starting from the second byte. */
    pIterator = encodeRemainingLength( pIterator, remainingLength );
    /* Place the packet identifier into the SUBSCRIBE packet. */
    pIterator[ 0 ] = UINT16_HIGH_BYTE( packetId );
    pIterator[ 1 ] = UINT16_LOW_BYTE( packetId );
    /* Increment the pointer. */
    pIterator = &pIterator[ 2 ];
    return pIterator;
}
MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionList,
                                      size_t subscriptionCount,
                                      uint16_t packetId,
                                      size_t remainingLength,
                                      const MQTTFixedBuffer_t * pFixedBuffer )
{
    size_t i = 0;
    uint8_t * pIndex = NULL;
    /* Validate all the parameters. */
    MQTTStatus_t status =
        validateSubscriptionSerializeParams( pSubscriptionList,
                                             subscriptionCount,
                                             packetId,
                                             remainingLength,
                                             pFixedBuffer );
    if( status == MQTTSuccess )
    {
        pIndex = pFixedBuffer->pBuffer;
        pIndex = MQTT_SerializeSubscribeHeader( remainingLength,
                                                pIndex,
                                                packetId );
        /* Serialize each subscription topic filter and QoS. */
        for( i = 0; i < subscriptionCount; i++ )
        {
            pIndex = encodeString( pIndex,
                                   pSubscriptionList[ i ].pTopicFilter,
                                   pSubscriptionList[ i ].topicFilterLength );
            /* Place the QoS in the SUBSCRIBE packet. */
            *pIndex = ( uint8_t ) ( pSubscriptionList[ i ].qos );
            pIndex++;
        }
        LogDebug( ( "Length of serialized SUBSCRIBE packet is %lu.",
                    ( ( unsigned long ) ( pIndex - pFixedBuffer->pBuffer ) ) ) );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
                                            size_t subscriptionCount,
                                            size_t * pRemainingLength,
                                            size_t * pPacketSize )
{
    MQTTStatus_t status = MQTTSuccess;
    /* Validate parameters. */
    if( ( pSubscriptionList == NULL ) || ( pRemainingLength == NULL ) ||
        ( pPacketSize == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pSubscriptionList=%p, "
                    "pRemainingLength=%p, pPacketSize=%p.",
                    ( void * ) pSubscriptionList,
                    ( void * ) pRemainingLength,
                    ( void * ) pPacketSize ) );
        status = MQTTBadParameter;
    }
    else if( subscriptionCount == 0U )
    {
        LogError( ( "Subscription count is 0." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Calculate the MQTT UNSUBSCRIBE packet size. */
        status = calculateSubscriptionPacketSize( pSubscriptionList,
                                                  subscriptionCount,
                                                  pRemainingLength,
                                                  pPacketSize,
                                                  MQTT_UNSUBSCRIBE );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptionList,
                                        size_t subscriptionCount,
                                        uint16_t packetId,
                                        size_t remainingLength,
                                        const MQTTFixedBuffer_t * pFixedBuffer )
{
    MQTTStatus_t status = MQTTSuccess;
    size_t i = 0;
    uint8_t * pIndex = NULL;
    /* Validate all the parameters. */
    status = validateSubscriptionSerializeParams( pSubscriptionList,
                                                  subscriptionCount,
                                                  packetId,
                                                  remainingLength,
                                                  pFixedBuffer );
    if( status == MQTTSuccess )
    {
        /* Get the start of the buffer to the iterator variable. */
        pIndex = pFixedBuffer->pBuffer;
        pIndex = MQTT_SerializeUnsubscribeHeader( remainingLength, pIndex, packetId );
        /* Serialize each subscription topic filter. */
        for( i = 0; i < subscriptionCount; i++ )
        {
            pIndex = encodeString( pIndex,
                                   pSubscriptionList[ i ].pTopicFilter,
                                   pSubscriptionList[ i ].topicFilterLength );
        }
        LogDebug( ( "Length of serialized UNSUBSCRIBE packet is %lu.",
                    ( ( unsigned long ) ( pIndex - pFixedBuffer->pBuffer ) ) ) );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo,
                                        size_t * pRemainingLength,
                                        size_t * pPacketSize )
{
    MQTTStatus_t status = MQTTSuccess;
    if( ( pPublishInfo == NULL ) || ( pRemainingLength == NULL ) || ( pPacketSize == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pPublishInfo=%p, "
                    "pRemainingLength=%p, pPacketSize=%p.",
                    ( void * ) pPublishInfo,
                    ( void * ) pRemainingLength,
                    ( void * ) pPacketSize ) );
        status = MQTTBadParameter;
    }
    else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) )
    {
        LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, "
                    "topicNameLength=%hu.",
                    ( void * ) pPublishInfo->pTopicName,
                    ( unsigned short ) pPublishInfo->topicNameLength ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Calculate the "Remaining length" field and total packet size. If it exceeds
         * what is allowed in the MQTT standard, return an error. */
        if( calculatePublishPacketSize( pPublishInfo, pRemainingLength, pPacketSize ) == false )
        {
            LogError( ( "PUBLISH packet remaining length exceeds %lu, which is the "
                        "maximum size allowed by MQTT 3.1.1.",
                        MQTT_MAX_REMAINING_LENGTH ) );
            status = MQTTBadParameter;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo,
                                    uint16_t packetId,
                                    size_t remainingLength,
                                    const MQTTFixedBuffer_t * pFixedBuffer )
{
    MQTTStatus_t status = MQTTSuccess;
    size_t packetSize = 0;
    if( ( pFixedBuffer == NULL ) || ( pPublishInfo == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, "
                    "pPublishInfo=%p.",
                    ( void * ) pFixedBuffer,
                    ( void * ) pPublishInfo ) );
        status = MQTTBadParameter;
    }
    /* A buffer must be configured for serialization. */
    else if( pFixedBuffer->pBuffer == NULL )
    {
        LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) );
        status = MQTTBadParameter;
    }
    /* For serializing a publish, if there exists a payload, then the buffer
     * cannot be NULL. */
    else if( ( pPublishInfo->payloadLength > 0U ) && ( pPublishInfo->pPayload == NULL ) )
    {
        LogError( ( "A nonzero payload length requires a non-NULL payload: "
                    "payloadLength=%lu, pPayload=%p.",
                    ( unsigned long ) pPublishInfo->payloadLength,
                    pPublishInfo->pPayload ) );
        status = MQTTBadParameter;
    }
    else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) )
    {
        LogError( ( "Invalid topic name for PUBLISH: pTopicName=%p, "
                    "topicNameLength=%hu.",
                    ( void * ) pPublishInfo->pTopicName,
                    ( unsigned short ) pPublishInfo->topicNameLength ) );
        status = MQTTBadParameter;
    }
    else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) )
    {
        LogError( ( "Packet ID is 0 for PUBLISH with QoS=%u.",
                    ( unsigned int ) pPublishInfo->qos ) );
        status = MQTTBadParameter;
    }
    else if( ( pPublishInfo->dup == true ) && ( pPublishInfo->qos == MQTTQoS0 ) )
    {
        LogError( ( "Duplicate flag is set for PUBLISH with Qos 0." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Length of serialized packet = First byte
         *                                + Length of encoded remaining length
         *                                + Remaining length. */
        packetSize = 1U + remainingLengthEncodedSize( remainingLength )
                     + remainingLength;
    }
    if( ( status == MQTTSuccess ) && ( packetSize > pFixedBuffer->size ) )
    {
        LogError( ( "Buffer size of %lu is not sufficient to hold "
                    "serialized PUBLISH packet of size of %lu.",
                    ( unsigned long ) pFixedBuffer->size,
                    ( unsigned long ) packetSize ) );
        status = MQTTNoMemory;
    }
    if( status == MQTTSuccess )
    {
        /* Serialize publish with header and payload. */
        serializePublishCommon( pPublishInfo,
                                remainingLength,
                                packetId,
                                pFixedBuffer,
                                true );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo,
                                          uint16_t packetId,
                                          size_t remainingLength,
                                          const MQTTFixedBuffer_t * pFixedBuffer,
                                          size_t * pHeaderSize )
{
    MQTTStatus_t status = MQTTSuccess;
    size_t packetSize = 0;
    if( ( pFixedBuffer == NULL ) || ( pPublishInfo == NULL ) ||
        ( pHeaderSize == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pFixedBuffer=%p, "
                    "pPublishInfo=%p, pHeaderSize=%p.",
                    ( void * ) pFixedBuffer,
                    ( void * ) pPublishInfo,
                    ( void * ) pHeaderSize ) );
        status = MQTTBadParameter;
    }
    /* A buffer must be configured for serialization. */
    else if( pFixedBuffer->pBuffer == NULL )
    {
        LogError( ( "Argument cannot be NULL: pFixedBuffer->pBuffer is NULL." ) );
        status = MQTTBadParameter;
    }
    else if( ( pPublishInfo->pTopicName == NULL ) || ( pPublishInfo->topicNameLength == 0U ) )
    {
        LogError( ( "Invalid topic name for publish: pTopicName=%p, "
                    "topicNameLength=%hu.",
                    ( void * ) pPublishInfo->pTopicName,
                    ( unsigned short ) pPublishInfo->topicNameLength ) );
        status = MQTTBadParameter;
    }
    else if( ( pPublishInfo->qos != MQTTQoS0 ) && ( packetId == 0U ) )
    {
        LogError( ( "Packet Id is 0 for publish with QoS=%hu.",
                    ( unsigned short ) pPublishInfo->qos ) );
        status = MQTTBadParameter;
    }
    else if( ( pPublishInfo->dup == true ) && ( pPublishInfo->qos == MQTTQoS0 ) )
    {
        LogError( ( "Duplicate flag is set for PUBLISH with Qos 0." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Length of serialized packet = First byte
         *                               + Length of encoded remaining length
         *                               + Remaining length
         *                               - Payload Length.
         */
        packetSize = 1U + remainingLengthEncodedSize( remainingLength )
                     + remainingLength
                     - pPublishInfo->payloadLength;
    }
    if( ( status == MQTTSuccess ) && ( packetSize > pFixedBuffer->size ) )
    {
        LogError( ( "Buffer size of %lu is not sufficient to hold "
                    "serialized PUBLISH header packet of size of %lu.",
                    ( unsigned long ) pFixedBuffer->size,
                    ( unsigned long ) ( packetSize - pPublishInfo->payloadLength ) ) );
        status = MQTTNoMemory;
    }
    if( status == MQTTSuccess )
    {
        /* Serialize publish without copying the payload. */
        serializePublishCommon( pPublishInfo,
                                remainingLength,
                                packetId,
                                pFixedBuffer,
                                false );
        /* Header size is the same as calculated packet size. */
        *pHeaderSize = packetSize;
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer,
                                uint8_t packetType,
                                uint16_t packetId )
{
    MQTTStatus_t status = MQTTSuccess;
    if( pFixedBuffer == NULL )
    {
        LogError( ( "Provided buffer is NULL." ) );
        status = MQTTBadParameter;
    }
    else if( pFixedBuffer->pBuffer == NULL )
    {
        LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) );
        status = MQTTBadParameter;
    }
    /* The buffer must be able to fit 4 bytes for the packet. */
    else if( pFixedBuffer->size < MQTT_PUBLISH_ACK_PACKET_SIZE )
    {
        LogError( ( "Insufficient memory for packet." ) );
        status = MQTTNoMemory;
    }
    else if( packetId == 0U )
    {
        LogError( ( "Packet ID cannot be 0." ) );
        status = MQTTBadParameter;
    }
    else
    {
        switch( packetType )
        {
            /* Only publish acks are serialized by the client. */
            case MQTT_PACKET_TYPE_PUBACK:
            case MQTT_PACKET_TYPE_PUBREC:
            case MQTT_PACKET_TYPE_PUBREL:
            case MQTT_PACKET_TYPE_PUBCOMP:
                pFixedBuffer->pBuffer[ 0 ] = packetType;
                pFixedBuffer->pBuffer[ 1 ] = MQTT_PACKET_SIMPLE_ACK_REMAINING_LENGTH;
                pFixedBuffer->pBuffer[ 2 ] = UINT16_HIGH_BYTE( packetId );
                pFixedBuffer->pBuffer[ 3 ] = UINT16_LOW_BYTE( packetId );
                break;
            default:
                LogError( ( "Packet type is not a publish ACK: Packet type=%02x",
                            ( unsigned int ) packetType ) );
                status = MQTTBadParameter;
                break;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize )
{
    MQTTStatus_t status = MQTTSuccess;
    if( pPacketSize == NULL )
    {
        LogError( ( "pPacketSize is NULL." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* MQTT DISCONNECT packets always have the same size. */
        *pPacketSize = MQTT_DISCONNECT_PACKET_SIZE;
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer )
{
    MQTTStatus_t status = MQTTSuccess;
    /* Validate arguments. */
    if( pFixedBuffer == NULL )
    {
        LogError( ( "pFixedBuffer cannot be NULL." ) );
        status = MQTTBadParameter;
    }
    else if( pFixedBuffer->pBuffer == NULL )
    {
        LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Empty else MISRA 15.7 */
    }
    if( status == MQTTSuccess )
    {
        if( pFixedBuffer->size < MQTT_DISCONNECT_PACKET_SIZE )
        {
            LogError( ( "Buffer size of %lu is not sufficient to hold "
                        "serialized DISCONNECT packet of size of %lu.",
                        ( unsigned long ) pFixedBuffer->size,
                        MQTT_DISCONNECT_PACKET_SIZE ) );
            status = MQTTNoMemory;
        }
    }
    if( status == MQTTSuccess )
    {
        pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_DISCONNECT;
        pFixedBuffer->pBuffer[ 1 ] = MQTT_DISCONNECT_REMAINING_LENGTH;
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize )
{
    MQTTStatus_t status = MQTTSuccess;
    if( pPacketSize == NULL )
    {
        LogError( ( "pPacketSize is NULL." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* MQTT PINGREQ packets always have the same size. */
        *pPacketSize = MQTT_PACKET_PINGREQ_SIZE;
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer )
{
    MQTTStatus_t status = MQTTSuccess;
    if( pFixedBuffer == NULL )
    {
        LogError( ( "pFixedBuffer is NULL." ) );
        status = MQTTBadParameter;
    }
    else if( pFixedBuffer->pBuffer == NULL )
    {
        LogError( ( "pFixedBuffer->pBuffer cannot be NULL." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Empty else MISRA 15.7 */
    }
    if( status == MQTTSuccess )
    {
        if( pFixedBuffer->size < MQTT_PACKET_PINGREQ_SIZE )
        {
            LogError( ( "Buffer size of %lu is not sufficient to hold "
                        "serialized PINGREQ packet of size of %lu.",
                        ( unsigned long ) pFixedBuffer->size,
                        MQTT_PACKET_PINGREQ_SIZE ) );
            status = MQTTNoMemory;
        }
    }
    if( status == MQTTSuccess )
    {
        /* Ping request packets are always the same. */
        pFixedBuffer->pBuffer[ 0 ] = MQTT_PACKET_TYPE_PINGREQ;
        pFixedBuffer->pBuffer[ 1 ] = 0x00;
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket,
                                      uint16_t * pPacketId,
                                      MQTTPublishInfo_t * pPublishInfo )
{
    MQTTStatus_t status = MQTTSuccess;
    if( ( pIncomingPacket == NULL ) || ( pPacketId == NULL ) || ( pPublishInfo == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pIncomingPacket=%p, "
                    "pPacketId=%p, pPublishInfo=%p",
                    ( void * ) pIncomingPacket,
                    ( void * ) pPacketId,
                    ( void * ) pPublishInfo ) );
        status = MQTTBadParameter;
    }
    else if( ( pIncomingPacket->type & 0xF0U ) != MQTT_PACKET_TYPE_PUBLISH )
    {
        LogError( ( "Packet is not publish. Packet type: %02x.",
                    ( unsigned int ) pIncomingPacket->type ) );
        status = MQTTBadParameter;
    }
    else if( pIncomingPacket->pRemainingData == NULL )
    {
        LogError( ( "Argument cannot be NULL: "
                    "pIncomingPacket->pRemainingData is NULL." ) );
        status = MQTTBadParameter;
    }
    else
    {
        status = deserializePublish( pIncomingPacket, pPacketId, pPublishInfo );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket,
                                  uint16_t * pPacketId,
                                  bool * pSessionPresent )
{
    MQTTStatus_t status = MQTTSuccess;
    if( pIncomingPacket == NULL )
    {
        LogError( ( "pIncomingPacket cannot be NULL." ) );
        status = MQTTBadParameter;
    }
    /* Pointer for packet identifier cannot be NULL for packets other than
     * CONNACK and PINGRESP. */
    else if( ( pPacketId == NULL ) &&
             ( ( pIncomingPacket->type != MQTT_PACKET_TYPE_CONNACK ) &&
               ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) ) )
    {
        LogError( ( "pPacketId cannot be NULL for packet type %02x.",
                    ( unsigned int ) pIncomingPacket->type ) );
        status = MQTTBadParameter;
    }
    /* Pointer for session present cannot be NULL for CONNACK. */
    else if( ( pSessionPresent == NULL ) &&
             ( pIncomingPacket->type == MQTT_PACKET_TYPE_CONNACK ) )
    {
        LogError( ( "pSessionPresent cannot be NULL for CONNACK packet." ) );
        status = MQTTBadParameter;
    }
    /* Pointer for remaining data cannot be NULL for packets other
     * than PINGRESP. */
    else if( ( pIncomingPacket->pRemainingData == NULL ) &&
             ( pIncomingPacket->type != MQTT_PACKET_TYPE_PINGRESP ) )
    {
        LogError( ( "Remaining data of incoming packet is NULL." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Make sure response packet is a valid ack. */
        switch( pIncomingPacket->type )
        {
            case MQTT_PACKET_TYPE_CONNACK:
                status = deserializeConnack( pIncomingPacket, pSessionPresent );
                break;
            case MQTT_PACKET_TYPE_SUBACK:
                status = deserializeSuback( pIncomingPacket, pPacketId );
                break;
            case MQTT_PACKET_TYPE_PINGRESP:
                status = deserializePingresp( pIncomingPacket );
                break;
            case MQTT_PACKET_TYPE_UNSUBACK:
            case MQTT_PACKET_TYPE_PUBACK:
            case MQTT_PACKET_TYPE_PUBREC:
            case MQTT_PACKET_TYPE_PUBREL:
            case MQTT_PACKET_TYPE_PUBCOMP:
                status = deserializeSimpleAck( pIncomingPacket, pPacketId );
                break;
            /* Any other packet type is invalid. */
            default:
                LogError( ( "IotMqtt_DeserializeResponse() called with unknown packet type:(%02x).",
                            ( unsigned int ) pIncomingPacket->type ) );
                status = MQTTBadResponse;
                break;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc,
                                                  NetworkContext_t * pNetworkContext,
                                                  MQTTPacketInfo_t * pIncomingPacket )
{
    MQTTStatus_t status = MQTTSuccess;
    int32_t bytesReceived = 0;
    if( pIncomingPacket == NULL )
    {
        LogError( ( "Invalid parameter: pIncomingPacket is NULL." ) );
        status = MQTTBadParameter;
    }
    else
    {
        /* Read a single byte. */
        bytesReceived = readFunc( pNetworkContext,
                                  &( pIncomingPacket->type ),
                                  1U );
    }
    if( bytesReceived == 1 )
    {
        /* Check validity. */
        if( incomingPacketValid( pIncomingPacket->type ) == true )
        {
            pIncomingPacket->remainingLength = getRemainingLength( readFunc,
                                                                   pNetworkContext );
            if( pIncomingPacket->remainingLength == MQTT_REMAINING_LENGTH_INVALID )
            {
                LogError( ( "Incoming packet remaining length invalid." ) );
                status = MQTTBadResponse;
            }
        }
        else
        {
            LogError( ( "Incoming packet invalid: Packet type=%u.",
                        ( unsigned int ) pIncomingPacket->type ) );
            status = MQTTBadResponse;
        }
    }
    else if( ( status != MQTTBadParameter ) && ( bytesReceived == 0 ) )
    {
        status = MQTTNoDataAvailable;
    }
    /* If the input packet was valid, then any other number of bytes received is
     * a failure. */
    else if( status != MQTTBadParameter )
    {
        LogError( ( "A single byte was not read from the transport: "
                    "transportStatus=%ld.",
                    ( long int ) bytesReceived ) );
        status = MQTTRecvFailed;
    }
    else
    {
        /* Empty else MISRA 15.7 */
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_ProcessIncomingPacketTypeAndLength( const uint8_t * pBuffer,
                                                      const size_t * pIndex,
                                                      MQTTPacketInfo_t * pIncomingPacket )
{
    MQTTStatus_t status = MQTTSuccess;
    if( pIncomingPacket == NULL )
    {
        LogError( ( "Invalid parameter: pIncomingPacket is NULL." ) );
        status = MQTTBadParameter;
    }
    else if( pIndex == NULL )
    {
        LogError( ( "Invalid parameter: pIndex is NULL." ) );
        status = MQTTBadParameter;
    }
    else if( pBuffer == NULL )
    {
        LogError( ( "Invalid parameter: pBuffer is NULL." ) );
        status = MQTTBadParameter;
    }
    /* There should be at least one byte in the buffer */
    else if( *pIndex < 1U )
    {
        /* No data is available. There are 0 bytes received from the network
         * receive function. */
        status = MQTTNoDataAvailable;
    }
    else
    {
        /* At least one byte is present which should be deciphered. */
        pIncomingPacket->type = pBuffer[ 0 ];
    }
    if( status == MQTTSuccess )
    {
        /* Check validity. */
        if( incomingPacketValid( pIncomingPacket->type ) == true )
        {
            status = processRemainingLength( pBuffer,
                                             pIndex,
                                             pIncomingPacket );
        }
        else
        {
            LogError( ( "Incoming packet invalid: Packet type=%u.",
                        ( unsigned int ) pIncomingPacket->type ) );
            status = MQTTBadResponse;
        }
    }
    return status;
}
/*-----------------------------------------------------------*/
project/coreMQTT/coreMQTT/core_mqtt_serializer.h
New file
@@ -0,0 +1,1306 @@
/*
 * coreMQTT v2.1.1
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * @file core_mqtt_serializer.h
 * @brief User-facing functions for serializing and deserializing MQTT 3.1.1
 * packets. This header should be included for building a lighter weight MQTT
 * client than the managed CSDK MQTT library API in core_mqtt.h, by using the
 * serializer and de-serializer functions exposed in this file's API.
 */
#ifndef CORE_MQTT_SERIALIZER_H
#define CORE_MQTT_SERIALIZER_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
/* *INDENT-OFF* */
#ifdef __cplusplus
    extern "C" {
#endif
/* *INDENT-ON */
/* MQTT_DO_NOT_USE_CUSTOM_CONFIG allows building the MQTT library
 * without a custom config. If a custom config is provided, the
 * MQTT_DO_NOT_USE_CUSTOM_CONFIG macro should not be defined. */
#ifndef MQTT_DO_NOT_USE_CUSTOM_CONFIG
    /* Include custom config file before other headers. */
    #include "core_mqtt_config.h"
#endif
/* Include config defaults header to get default values of configs not
 * defined in core_mqtt_config.h file. */
#include "core_mqtt_config_defaults.h"
#include "transport_interface.h"
/* MQTT packet types. */
/**
 * @addtogroup mqtt_constants
 * @{
 */
#define MQTT_PACKET_TYPE_CONNECT        ( ( uint8_t ) 0x10U )  /**< @brief CONNECT (client-to-server). */
#define MQTT_PACKET_TYPE_CONNACK        ( ( uint8_t ) 0x20U )  /**< @brief CONNACK (server-to-client). */
#define MQTT_PACKET_TYPE_PUBLISH        ( ( uint8_t ) 0x30U )  /**< @brief PUBLISH (bidirectional). */
#define MQTT_PACKET_TYPE_PUBACK         ( ( uint8_t ) 0x40U )  /**< @brief PUBACK (bidirectional). */
#define MQTT_PACKET_TYPE_PUBREC         ( ( uint8_t ) 0x50U )  /**< @brief PUBREC (bidirectional). */
#define MQTT_PACKET_TYPE_PUBREL         ( ( uint8_t ) 0x62U )  /**< @brief PUBREL (bidirectional). */
#define MQTT_PACKET_TYPE_PUBCOMP        ( ( uint8_t ) 0x70U )  /**< @brief PUBCOMP (bidirectional). */
#define MQTT_PACKET_TYPE_SUBSCRIBE      ( ( uint8_t ) 0x82U )  /**< @brief SUBSCRIBE (client-to-server). */
#define MQTT_PACKET_TYPE_SUBACK         ( ( uint8_t ) 0x90U )  /**< @brief SUBACK (server-to-client). */
#define MQTT_PACKET_TYPE_UNSUBSCRIBE    ( ( uint8_t ) 0xA2U )  /**< @brief UNSUBSCRIBE (client-to-server). */
#define MQTT_PACKET_TYPE_UNSUBACK       ( ( uint8_t ) 0xB0U )  /**< @brief UNSUBACK (server-to-client). */
#define MQTT_PACKET_TYPE_PINGREQ        ( ( uint8_t ) 0xC0U )  /**< @brief PINGREQ (client-to-server). */
#define MQTT_PACKET_TYPE_PINGRESP       ( ( uint8_t ) 0xD0U )  /**< @brief PINGRESP (server-to-client). */
#define MQTT_PACKET_TYPE_DISCONNECT     ( ( uint8_t ) 0xE0U )  /**< @brief DISCONNECT (client-to-server). */
/** @} */
/**
 * @ingroup mqtt_constants
 * @brief The size of MQTT PUBACK, PUBREC, PUBREL, and PUBCOMP packets, per MQTT spec.
 */
#define MQTT_PUBLISH_ACK_PACKET_SIZE    ( 4UL )
/* Structures defined in this file. */
struct MQTTFixedBuffer;
struct MQTTConnectInfo;
struct MQTTSubscribeInfo;
struct MQTTPublishInfo;
struct MQTTPacketInfo;
/**
 * @ingroup mqtt_enum_types
 * @brief Return codes from MQTT functions.
 */
typedef enum MQTTStatus
{
    MQTTSuccess = 0,      /**< Function completed successfully. */
    MQTTBadParameter,     /**< At least one parameter was invalid. */
    MQTTNoMemory,         /**< A provided buffer was too small. */
    MQTTSendFailed,       /**< The transport send function failed. */
    MQTTRecvFailed,       /**< The transport receive function failed. */
    MQTTBadResponse,      /**< An invalid packet was received from the server. */
    MQTTServerRefused,    /**< The server refused a CONNECT or SUBSCRIBE. */
    MQTTNoDataAvailable,  /**< No data available from the transport interface. */
    MQTTIllegalState,     /**< An illegal state in the state record. */
    MQTTStateCollision,   /**< A collision with an existing state record entry. */
    MQTTKeepAliveTimeout, /**< Timeout while waiting for PINGRESP. */
    MQTTNeedMoreBytes     /**< MQTT_ProcessLoop/MQTT_ReceiveLoop has received
                          incomplete data; it should be called again (probably after
                          a delay). */
} MQTTStatus_t;
/**
 * @ingroup mqtt_enum_types
 * @brief MQTT Quality of Service values.
 */
typedef enum MQTTQoS
{
    MQTTQoS0 = 0, /**< Delivery at most once. */
    MQTTQoS1 = 1, /**< Delivery at least once. */
    MQTTQoS2 = 2  /**< Delivery exactly once. */
} MQTTQoS_t;
/**
 * @ingroup mqtt_struct_types
 * @brief Buffer passed to MQTT library.
 *
 * These buffers are not copied and must remain in scope for the duration of the
 * MQTT operation.
 */
typedef struct MQTTFixedBuffer
{
    uint8_t * pBuffer; /**< @brief Pointer to buffer. */
    size_t size;       /**< @brief Size of buffer. */
} MQTTFixedBuffer_t;
/**
 * @ingroup mqtt_struct_types
 * @brief MQTT CONNECT packet parameters.
 */
typedef struct MQTTConnectInfo
{
    /**
     * @brief Whether to establish a new, clean session or resume a previous session.
     */
    bool cleanSession;
    /**
     * @brief MQTT keep alive period.
     */
    uint16_t keepAliveSeconds;
    /**
     * @brief MQTT client identifier. Must be unique per client.
     */
    const char * pClientIdentifier;
    /**
     * @brief Length of the client identifier.
     */
    uint16_t clientIdentifierLength;
    /**
     * @brief MQTT user name. Set to NULL if not used.
     */
    const char * pUserName;
    /**
     * @brief Length of MQTT user name. Set to 0 if not used.
     */
    uint16_t userNameLength;
    /**
     * @brief MQTT password. Set to NULL if not used.
     */
    const char * pPassword;
    /**
     * @brief Length of MQTT password. Set to 0 if not used.
     */
    uint16_t passwordLength;
} MQTTConnectInfo_t;
/**
 * @ingroup mqtt_struct_types
 * @brief MQTT SUBSCRIBE packet parameters.
 */
typedef struct MQTTSubscribeInfo
{
    /**
     * @brief Quality of Service for subscription.
     */
    MQTTQoS_t qos;
    /**
     * @brief Topic filter to subscribe to.
     */
    const char * pTopicFilter;
    /**
     * @brief Length of subscription topic filter.
     */
    uint16_t topicFilterLength;
} MQTTSubscribeInfo_t;
/**
 * @ingroup mqtt_struct_types
 * @brief MQTT PUBLISH packet parameters.
 */
typedef struct MQTTPublishInfo
{
    /**
     * @brief Quality of Service for message.
     */
    MQTTQoS_t qos;
    /**
     * @brief Whether this is a retained message.
     */
    bool retain;
    /**
     * @brief Whether this is a duplicate publish message.
     */
    bool dup;
    /**
     * @brief Topic name on which the message is published.
     */
    const char * pTopicName;
    /**
     * @brief Length of topic name.
     */
    uint16_t topicNameLength;
    /**
     * @brief Message payload.
     */
    const void * pPayload;
    /**
     * @brief Message payload length.
     */
    size_t payloadLength;
} MQTTPublishInfo_t;
/**
 * @ingroup mqtt_struct_types
 * @brief MQTT incoming packet parameters.
 */
typedef struct MQTTPacketInfo
{
    /**
     * @brief Type of incoming MQTT packet.
     */
    uint8_t type;
    /**
     * @brief Remaining serialized data in the MQTT packet.
     */
    uint8_t * pRemainingData;
    /**
     * @brief Length of remaining serialized data.
     */
    size_t remainingLength;
    /**
     * @brief The length of the MQTT header including the type and length.
     */
    size_t headerLength;
} MQTTPacketInfo_t;
/**
 * @brief Get the size and Remaining Length of an MQTT CONNECT packet.
 *
 * This function must be called before #MQTT_SerializeConnect in order to get
 * the size of the MQTT CONNECT packet that is generated from #MQTTConnectInfo_t
 * and optional #MQTTPublishInfo_t. The size of the #MQTTFixedBuffer_t supplied
 * to #MQTT_SerializeConnect must be at least @p pPacketSize. The provided
 * @p pConnectInfo and @p pWillInfo are valid for serialization with
 * #MQTT_SerializeConnect only if this function returns #MQTTSuccess. The
 * remaining length returned in @p pRemainingLength and the packet size returned
 * in @p pPacketSize are valid only if this function returns #MQTTSuccess.
 *
 * @param[in] pConnectInfo MQTT CONNECT packet parameters.
 * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used.
 * @param[out] pRemainingLength The Remaining Length of the MQTT CONNECT packet.
 * @param[out] pPacketSize The total size of the MQTT CONNECT packet.
 *
 * @return #MQTTBadParameter if the packet would exceed the size allowed by the
 * MQTT spec; #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTConnectInfo_t connectInfo = { 0 };
 * MQTTPublishInfo_t willInfo = { 0 };
 * size_t remainingLength = 0, packetSize = 0;
 *
 * // Initialize the connection info, the details are out of scope for this example.
 * initializeConnectInfo( &connectInfo );
 *
 * // Initialize the optional will info, the details are out of scope for this example.
 * initializeWillInfo( &willInfo );
 *
 * // Get the size requirement for the connect packet.
 * status = MQTT_GetConnectPacketSize(
 *      &connectInfo, &willInfo, &remainingLength, &packetSize
 * );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The application should allocate or use a static #MQTTFixedBuffer_t
 *      // of size >= packetSize to serialize the connect request.
 * }
 * @endcode
 */
/* @[declare_mqtt_getconnectpacketsize] */
MQTTStatus_t MQTT_GetConnectPacketSize( const MQTTConnectInfo_t * pConnectInfo,
                                        const MQTTPublishInfo_t * pWillInfo,
                                        size_t * pRemainingLength,
                                        size_t * pPacketSize );
/* @[declare_mqtt_getconnectpacketsize] */
/**
 * @brief Serialize an MQTT CONNECT packet in the given fixed buffer @p pFixedBuffer.
 *
 * #MQTT_GetConnectPacketSize should be called with @p pConnectInfo and
 * @p pWillInfo before invoking this function to get the size of the required
 * #MQTTFixedBuffer_t and @p remainingLength. The @p remainingLength must be
 * the same as returned by #MQTT_GetConnectPacketSize. The #MQTTFixedBuffer_t
 * must be at least as large as the size returned by #MQTT_GetConnectPacketSize.
 *
 * @param[in] pConnectInfo MQTT CONNECT packet parameters.
 * @param[in] pWillInfo Last Will and Testament. Pass NULL if not used.
 * @param[in] remainingLength Remaining Length provided by #MQTT_GetConnectPacketSize.
 * @param[out] pFixedBuffer Buffer for packet serialization.
 *
 * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTConnectInfo_t connectInfo = { 0 };
 * MQTTPublishInfo_t willInfo = { 0 };
 * MQTTFixedBuffer_t fixedBuffer;
 * uint8_t buffer[ BUFFER_SIZE ];
 * size_t remainingLength = 0, packetSize = 0;
 *
 * fixedBuffer.pBuffer = buffer;
 * fixedBuffer.size = BUFFER_SIZE;
 *
 * // Assume connectInfo and willInfo are initialized. Get the size requirement for
 * // the connect packet.
 * status = MQTT_GetConnectPacketSize(
 *      &connectInfo, &willInfo, &remainingLength, &packetSize
 * );
 * assert( status == MQTTSuccess );
 * assert( packetSize <= BUFFER_SIZE );
 *
 * // Serialize the connect packet into the fixed buffer.
 * status = MQTT_SerializeConnect( &connectInfo, &willInfo, remainingLength, &fixedBuffer );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The connect packet can now be sent to the broker.
 * }
 * @endcode
 */
/* @[declare_mqtt_serializeconnect] */
MQTTStatus_t MQTT_SerializeConnect( const MQTTConnectInfo_t * pConnectInfo,
                                    const MQTTPublishInfo_t * pWillInfo,
                                    size_t remainingLength,
                                    const MQTTFixedBuffer_t * pFixedBuffer );
/* @[declare_mqtt_serializeconnect] */
/**
 * @brief Get packet size and Remaining Length of an MQTT SUBSCRIBE packet.
 *
 * This function must be called before #MQTT_SerializeSubscribe in order to get
 * the size of the MQTT SUBSCRIBE packet that is generated from the list of
 * #MQTTSubscribeInfo_t. The size of the #MQTTFixedBuffer_t supplied
 * to #MQTT_SerializeSubscribe must be at least @p pPacketSize. The provided
 * @p pSubscriptionList is valid for serialization with #MQTT_SerializeSubscribe
 * only if this function returns #MQTTSuccess. The remaining length returned in
 * @p pRemainingLength and the packet size returned in @p pPacketSize are valid
 * only if this function returns #MQTTSuccess.
 *
 * @param[in] pSubscriptionList List of MQTT subscription info.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[out] pRemainingLength The Remaining Length of the MQTT SUBSCRIBE packet.
 * @param[out] pPacketSize The total size of the MQTT SUBSCRIBE packet.
 *
 * @return #MQTTBadParameter if the packet would exceed the size allowed by the
 * MQTT spec; #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
 * size_t remainingLength = 0, packetSize = 0;
 * // This is assumed to be a list of filters we want to subscribe to.
 * const char * filters[ NUMBER_OF_SUBSCRIPTIONS ];
 *
 * // Set each subscription.
 * for( int i = 0; i < NUMBER_OF_SUBSCRIPTIONS; i++ )
 * {
 *      subscriptionList[ i ].qos = MQTTQoS0;
 *      // Each subscription needs a topic filter.
 *      subscriptionList[ i ].pTopicFilter = filters[ i ];
 *      subscriptionList[ i ].topicFilterLength = strlen( filters[ i ] );
 * }
 *
 * // Get the size requirement for the subscribe packet.
 * status = MQTT_GetSubscribePacketSize(
 *      &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize
 * );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The application should allocate or use a static #MQTTFixedBuffer_t
 *      // of size >= packetSize to serialize the subscribe request.
 * }
 * @endcode
 */
/* @[declare_mqtt_getsubscribepacketsize] */
MQTTStatus_t MQTT_GetSubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
                                          size_t subscriptionCount,
                                          size_t * pRemainingLength,
                                          size_t * pPacketSize );
/* @[declare_mqtt_getsubscribepacketsize] */
/**
 * @brief Serialize an MQTT SUBSCRIBE packet in the given buffer.
 *
 * #MQTT_GetSubscribePacketSize should be called with @p pSubscriptionList
 * before invoking this function to get the size of the required
 * #MQTTFixedBuffer_t and @p remainingLength. The @p remainingLength must be
 * the same as returned by #MQTT_GetSubscribePacketSize. The #MQTTFixedBuffer_t
 * must be at least as large as the size returned by #MQTT_GetSubscribePacketSize.
 *
 * @param[in] pSubscriptionList List of MQTT subscription info.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
 * @param[in] remainingLength Remaining Length provided by #MQTT_GetSubscribePacketSize.
 * @param[out] pFixedBuffer Buffer for packet serialization.
 *
 * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
 * MQTTFixedBuffer_t fixedBuffer;
 * uint8_t buffer[ BUFFER_SIZE ];
 * size_t remainingLength = 0, packetSize = 0;
 * uint16_t packetId;
 *
 * fixedBuffer.pBuffer = buffer;
 * fixedBuffer.size = BUFFER_SIZE;
 *
 * // Function to return a valid, unused packet identifier. The details are out of
 * // scope for this example.
 * packetId = getNewPacketId();
 *
 * // Assume subscriptionList has been initialized. Get the subscribe packet size.
 * status = MQTT_GetSubscribePacketSize(
 *      &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize
 * );
 * assert( status == MQTTSuccess );
 * assert( packetSize <= BUFFER_SIZE );
 *
 * // Serialize the subscribe packet into the fixed buffer.
 * status = MQTT_SerializeSubscribe(
 *      &subscriptionList[ 0 ],
 *      NUMBER_OF_SUBSCRIPTIONS,
 *      packetId,
 *      remainingLength,
 *      &fixedBuffer
 * );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The subscribe packet can now be sent to the broker.
 * }
 * @endcode
 */
/* @[declare_mqtt_serializesubscribe] */
MQTTStatus_t MQTT_SerializeSubscribe( const MQTTSubscribeInfo_t * pSubscriptionList,
                                      size_t subscriptionCount,
                                      uint16_t packetId,
                                      size_t remainingLength,
                                      const MQTTFixedBuffer_t * pFixedBuffer );
/* @[declare_mqtt_serializesubscribe] */
/**
 * @brief Get packet size and Remaining Length of an MQTT UNSUBSCRIBE packet.
 *
 * This function must be called before #MQTT_SerializeUnsubscribe in order to
 * get the size of the MQTT UNSUBSCRIBE packet that is generated from the list
 * of #MQTTSubscribeInfo_t. The size of the #MQTTFixedBuffer_t supplied
 * to #MQTT_SerializeUnsubscribe must be at least @p pPacketSize. The provided
 * @p pSubscriptionList is valid for serialization with #MQTT_SerializeUnsubscribe
 * only if this function returns #MQTTSuccess. The remaining length returned in
 * @p pRemainingLength and the packet size returned in @p pPacketSize are valid
 * only if this function returns #MQTTSuccess.
 *
 * @param[in] pSubscriptionList List of MQTT subscription info.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[out] pRemainingLength The Remaining Length of the MQTT UNSUBSCRIBE packet.
 * @param[out] pPacketSize The total size of the MQTT UNSUBSCRIBE packet.
 *
 * @return #MQTTBadParameter if the packet would exceed the size allowed by the
 * MQTT spec; #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
 * size_t remainingLength = 0, packetSize = 0;
 *
 * // Initialize the subscribe info. The details are out of scope for this example.
 * initializeSubscribeInfo( &subscriptionList[ 0 ] );
 *
 * // Get the size requirement for the unsubscribe packet.
 * status = MQTT_GetUnsubscribePacketSize(
 *      &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize
 * );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The application should allocate or use a static #MQTTFixedBuffer_t
 *      // of size >= packetSize to serialize the unsubscribe request.
 * }
 * @endcode
 */
/* @[declare_mqtt_getunsubscribepacketsize] */
MQTTStatus_t MQTT_GetUnsubscribePacketSize( const MQTTSubscribeInfo_t * pSubscriptionList,
                                            size_t subscriptionCount,
                                            size_t * pRemainingLength,
                                            size_t * pPacketSize );
/* @[declare_mqtt_getunsubscribepacketsize] */
/**
 * @brief Serialize an MQTT UNSUBSCRIBE packet in the given buffer.
 *
 * #MQTT_GetUnsubscribePacketSize should be called with @p pSubscriptionList
 * before invoking this function to get the size of the required
 * #MQTTFixedBuffer_t and @p remainingLength. The @p remainingLength must be
 * the same as returned by #MQTT_GetUnsubscribePacketSize. The #MQTTFixedBuffer_t
 * must be at least as large as the size returned by #MQTT_GetUnsubscribePacketSize.
 *
 * @param[in] pSubscriptionList List of MQTT subscription info.
 * @param[in] subscriptionCount The number of elements in pSubscriptionList.
 * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
 * @param[in] remainingLength Remaining Length provided by #MQTT_GetUnsubscribePacketSize.
 * @param[out] pFixedBuffer Buffer for packet serialization.
 *
 * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTSubscribeInfo_t subscriptionList[ NUMBER_OF_SUBSCRIPTIONS ] = { 0 };
 * MQTTFixedBuffer_t fixedBuffer;
 * uint8_t buffer[ BUFFER_SIZE ];
 * size_t remainingLength = 0, packetSize = 0;
 * uint16_t packetId;
 *
 * fixedBuffer.pBuffer = buffer;
 * fixedBuffer.size = BUFFER_SIZE;
 *
 * // Function to return a valid, unused packet identifier. The details are out of
 * // scope for this example.
 * packetId = getNewPacketId();
 *
 * // Assume subscriptionList has been initialized. Get the unsubscribe packet size.
 * status = MQTT_GetUnsubscribePacketSize(
 *      &subscriptionList[ 0 ], NUMBER_OF_SUBSCRIPTIONS, &remainingLength, &packetSize
 * );
 * assert( status == MQTTSuccess );
 * assert( packetSize <= BUFFER_SIZE );
 *
 * // Serialize the unsubscribe packet into the fixed buffer.
 * status = MQTT_SerializeUnsubscribe(
 *      &subscriptionList[ 0 ],
 *      NUMBER_OF_SUBSCRIPTIONS,
 *      packetId,
 *      remainingLength,
 *      &fixedBuffer
 * );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The unsubscribe packet can now be sent to the broker.
 * }
 * @endcode
 */
/* @[declare_mqtt_serializeunsubscribe] */
MQTTStatus_t MQTT_SerializeUnsubscribe( const MQTTSubscribeInfo_t * pSubscriptionList,
                                        size_t subscriptionCount,
                                        uint16_t packetId,
                                        size_t remainingLength,
                                        const MQTTFixedBuffer_t * pFixedBuffer );
/* @[declare_mqtt_serializeunsubscribe] */
/**
 * @brief Get the packet size and remaining length of an MQTT PUBLISH packet.
 *
 * This function must be called before #MQTT_SerializePublish in order to get
 * the size of the MQTT PUBLISH packet that is generated from #MQTTPublishInfo_t.
 * The size of the #MQTTFixedBuffer_t supplied to #MQTT_SerializePublish must be
 * at least @p pPacketSize. The provided @p pPublishInfo is valid for
 * serialization with #MQTT_SerializePublish only if this function returns
 * #MQTTSuccess. The remaining length returned in @p pRemainingLength and the
 * packet size returned in @p pPacketSize are valid only if this function
 * returns #MQTTSuccess.
 *
 * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
 * @param[out] pRemainingLength The Remaining Length of the MQTT PUBLISH packet.
 * @param[out] pPacketSize The total size of the MQTT PUBLISH packet.
 *
 * @return #MQTTBadParameter if the packet would exceed the size allowed by the
 * MQTT spec or if invalid parameters are passed; #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTPublishInfo_t publishInfo = { 0 };
 * size_t remainingLength = 0, packetSize = 0;
 *
 * // Initialize the publish info.
 * publishInfo.qos = MQTTQoS0;
 * publishInfo.pTopicName = "/some/topic/name";
 * publishInfo.topicNameLength = strlen( publishInfo.pTopicName );
 * publishInfo.pPayload = "Hello World!";
 * publishInfo.payloadLength = strlen( "Hello World!" );
 *
 * // Get the size requirement for the publish packet.
 * status = MQTT_GetPublishPacketSize(
 *      &publishInfo, &remainingLength, &packetSize
 * );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The application should allocate or use a static #MQTTFixedBuffer_t
 *      // of size >= packetSize to serialize the publish.
 * }
 * @endcode
 */
/* @[declare_mqtt_getpublishpacketsize] */
MQTTStatus_t MQTT_GetPublishPacketSize( const MQTTPublishInfo_t * pPublishInfo,
                                        size_t * pRemainingLength,
                                        size_t * pPacketSize );
/* @[declare_mqtt_getpublishpacketsize] */
/**
 * @brief Serialize an MQTT PUBLISH packet in the given buffer.
 *
 * This function will serialize complete MQTT PUBLISH packet into
 * the given buffer. If the PUBLISH payload can be sent separately,
 * consider using #MQTT_SerializePublishHeader, which will serialize
 * only the PUBLISH header into the buffer.
 *
 * #MQTT_GetPublishPacketSize should be called with @p pPublishInfo before
 * invoking this function to get the size of the required #MQTTFixedBuffer_t and
 * @p remainingLength. The @p remainingLength must be the same as returned by
 * #MQTT_GetPublishPacketSize. The #MQTTFixedBuffer_t must be at least as large
 * as the size returned by #MQTT_GetPublishPacketSize.
 *
 * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
 * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
 * @param[in] remainingLength Remaining Length provided by #MQTT_GetPublishPacketSize.
 * @param[out] pFixedBuffer Buffer for packet serialization.
 *
 * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTPublishInfo_t publishInfo = { 0 };
 * MQTTFixedBuffer_t fixedBuffer;
 * uint8_t buffer[ BUFFER_SIZE ];
 * size_t remainingLength = 0, packetSize = 0;
 * uint16_t packetId;
 *
 * fixedBuffer.pBuffer = buffer;
 * fixedBuffer.size = BUFFER_SIZE;
 *
 * // A packet identifier is unused for QoS 0 publishes. Otherwise, a valid, unused packet
 * // identifier must be used.
 * packetId = 0;
 *
 * // Assume publishInfo has been initialized. Get publish packet size.
 * status = MQTT_GetPublishPacketSize(
 *      &publishInfo, &remainingLength, &packetSize
 * );
 * assert( status == MQTTSuccess );
 * assert( packetSize <= BUFFER_SIZE );
 *
 * // Serialize the publish packet into the fixed buffer.
 * status = MQTT_SerializePublish(
 *      &publishInfo,
 *      packetId,
 *      remainingLength,
 *      &fixedBuffer
 * );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The publish packet can now be sent to the broker.
 * }
 * @endcode
 */
/* @[declare_mqtt_serializepublish] */
MQTTStatus_t MQTT_SerializePublish( const MQTTPublishInfo_t * pPublishInfo,
                                    uint16_t packetId,
                                    size_t remainingLength,
                                    const MQTTFixedBuffer_t * pFixedBuffer );
/* @[declare_mqtt_serializepublish] */
/**
 * @brief Serialize an MQTT PUBLISH packet header without the topic string in the
 * given buffer. This function will add the topic string length to the provided
 * buffer. This helps reduce an unnecessary copy of the topic string into the
 * buffer.
 *
 * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
 * @param[in] remainingLength Remaining Length provided by #MQTT_GetPublishPacketSize.
 * @param[out] pBuffer Buffer for packet serialization.
 * @param[out] headerSize Size of the serialized MQTT PUBLISH header.
 *
 * @return #MQTTSuccess if the serialization is successful. Otherwise, #MQTTBadParameter.
 */
MQTTStatus_t MQTT_SerializePublishHeaderWithoutTopic( const MQTTPublishInfo_t * pPublishInfo,
                                                      size_t remainingLength,
                                                      uint8_t * pBuffer,
                                                      size_t * headerSize );
/**
 * @brief Serialize an MQTT PUBLISH packet header in the given buffer.
 *
 * This function serializes PUBLISH header in to the given buffer. The payload
 * for PUBLISH will not be copied over to the buffer. This will help reduce
 * the memory needed for the buffer and avoid an unwanted copy operation of the
 * PUBLISH payload into the buffer. If the payload also would need to be part of
 * the serialized buffer, consider using #MQTT_SerializePublish.
 *
 * #MQTT_GetPublishPacketSize should be called with @p pPublishInfo before
 * invoking this function to get the size of the required #MQTTFixedBuffer_t and
 * @p remainingLength. The @p remainingLength must be the same as returned by
 * #MQTT_GetPublishPacketSize. The #MQTTFixedBuffer_t must be at least as large
 * as the size returned by #MQTT_GetPublishPacketSize.
 *
 * @param[in] pPublishInfo MQTT PUBLISH packet parameters.
 * @param[in] packetId packet ID generated by #MQTT_GetPacketId.
 * @param[in] remainingLength Remaining Length provided by #MQTT_GetPublishPacketSize.
 * @param[out] pFixedBuffer Buffer for packet serialization.
 * @param[out] pHeaderSize Size of the serialized MQTT PUBLISH header.
 *
 * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTPublishInfo_t publishInfo = { 0 };
 * MQTTFixedBuffer_t fixedBuffer;
 * uint8_t buffer[ BUFFER_SIZE ];
 * size_t remainingLength = 0, packetSize = 0, headerSize = 0;
 * uint16_t packetId;
 * int32_t bytesSent;
 *
 * fixedBuffer.pBuffer = buffer;
 * fixedBuffer.size = BUFFER_SIZE;
 *
 * // A packet identifier is unused for QoS 0 publishes. Otherwise, a valid, unused packet
 * // identifier must be used.
 * packetId = 0;
 *
 * // Assume publishInfo has been initialized. Get the publish packet size.
 * status = MQTT_GetPublishPacketSize(
 *      &publishInfo, &remainingLength, &packetSize
 * );
 * assert( status == MQTTSuccess );
 * // The payload will not be serialized, so the the fixed buffer does not need to hold it.
 * assert( ( packetSize - publishInfo.payloadLength ) <= BUFFER_SIZE );
 *
 * // Serialize the publish packet header into the fixed buffer.
 * status = MQTT_SerializePublishHeader(
 *      &publishInfo,
 *      packetId,
 *      remainingLength,
 *      &fixedBuffer,
 *      &headerSize
 * );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The publish header and payload can now be sent to the broker.
 *      // mqttSocket here is a socket descriptor created and connected to the MQTT
 *      // broker outside of this function.
 *      bytesSent = send( mqttSocket, ( void * ) fixedBuffer.pBuffer, headerSize, 0 );
 *      assert( bytesSent == headerSize );
 *      bytesSent = send( mqttSocket, publishInfo.pPayload, publishInfo.payloadLength, 0 );
 *      assert( bytesSent == publishInfo.payloadLength );
 * }
 * @endcode
 */
/* @[declare_mqtt_serializepublishheader] */
MQTTStatus_t MQTT_SerializePublishHeader( const MQTTPublishInfo_t * pPublishInfo,
                                          uint16_t packetId,
                                          size_t remainingLength,
                                          const MQTTFixedBuffer_t * pFixedBuffer,
                                          size_t * pHeaderSize );
/* @[declare_mqtt_serializepublishheader] */
/**
 * @brief Serialize an MQTT PUBACK, PUBREC, PUBREL, or PUBCOMP into the given
 * buffer.
 *
 * @param[out] pFixedBuffer Buffer for packet serialization.
 * @param[in] packetType Byte of the corresponding packet fixed header per the
 * MQTT spec.
 * @param[in] packetId Packet ID of the publish.
 *
 * @return #MQTTBadParameter, #MQTTNoMemory, or #MQTTSuccess.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTFixedBuffer_t fixedBuffer;
 * uint8_t buffer[ BUFFER_SIZE ];
 * uint16_t packetId;
 * uint8_t packetType;
 *
 * fixedBuffer.pBuffer = buffer;
 * fixedBuffer.size = BUFFER_SIZE;
 * // The fixed buffer must be large enough to hold 4 bytes.
 * assert( BUFFER_SIZE >= MQTT_PUBLISH_ACK_PACKET_SIZE );
 *
 * // The packet ID must be the same as the original publish packet.
 * packetId = publishPacketId;
 *
 * // The byte representing a packet of type ACK. This function accepts PUBACK, PUBREC, PUBREL, or PUBCOMP.
 * packetType = MQTT_PACKET_TYPE_PUBACK;
 *
 * // Serialize the publish acknowledgment into the fixed buffer.
 * status = MQTT_SerializeAck( &fixedBuffer, packetType, packetId );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The publish acknowledgment can now be sent to the broker.
 * }
 * @endcode
 */
/* @[declare_mqtt_serializeack] */
MQTTStatus_t MQTT_SerializeAck( const MQTTFixedBuffer_t * pFixedBuffer,
                                uint8_t packetType,
                                uint16_t packetId );
/* @[declare_mqtt_serializeack] */
/**
 * @brief Get the size of an MQTT DISCONNECT packet.
 *
 * @param[out] pPacketSize The size of the MQTT DISCONNECT packet.
 *
 * @return #MQTTSuccess, or #MQTTBadParameter if @p pPacketSize is NULL.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * size_t packetSize = 0;
 *
 * // Get the size requirement for the disconnect packet.
 * status = MQTT_GetDisconnectPacketSize( &packetSize );
 * assert( status == MQTTSuccess );
 * assert( packetSize == 2 );
 *
 * // The application should allocate or use a static #MQTTFixedBuffer_t of
 * // size >= 2 to serialize the disconnect packet.
 *
 * @endcode
 */
/* @[declare_mqtt_getdisconnectpacketsize] */
MQTTStatus_t MQTT_GetDisconnectPacketSize( size_t * pPacketSize );
/* @[declare_mqtt_getdisconnectpacketsize] */
/**
 * @brief Serialize an MQTT DISCONNECT packet into the given buffer.
 *
 * The input #MQTTFixedBuffer_t.size must be at least as large as the size
 * returned by #MQTT_GetDisconnectPacketSize.
 *
 * @param[out] pFixedBuffer Buffer for packet serialization.
 *
 * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTFixedBuffer_t fixedBuffer;
 * uint8_t buffer[ BUFFER_SIZE ];
 *
 * fixedBuffer.pBuffer = buffer;
 * fixedBuffer.size = BUFFER_SIZE;
 *
 * // Get the disconnect packet size.
 * status = MQTT_GetDisconnectPacketSize( &packetSize );
 * assert( status == MQTTSuccess );
 * assert( packetSize <= BUFFER_SIZE );
 *
 * // Serialize the disconnect into the fixed buffer.
 * status = MQTT_SerializeDisconnect( &fixedBuffer );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The disconnect packet can now be sent to the broker.
 * }
 * @endcode
 */
/* @[declare_mqtt_serializedisconnect] */
MQTTStatus_t MQTT_SerializeDisconnect( const MQTTFixedBuffer_t * pFixedBuffer );
/* @[declare_mqtt_serializedisconnect] */
/**
 * @brief Get the size of an MQTT PINGREQ packet.
 *
 * @param[out] pPacketSize The size of the MQTT PINGREQ packet.
 *
 * @return  #MQTTSuccess or #MQTTBadParameter if pPacketSize is NULL.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * size_t packetSize = 0;
 *
 * // Get the size requirement for the ping request packet.
 * status = MQTT_GetPingreqPacketSize( &packetSize );
 * assert( status == MQTTSuccess );
 * assert( packetSize == 2 );
 *
 * // The application should allocate or use a static #MQTTFixedBuffer_t of
 * // size >= 2 to serialize the ping request.
 *
 * @endcode
 */
/* @[declare_mqtt_getpingreqpacketsize] */
MQTTStatus_t MQTT_GetPingreqPacketSize( size_t * pPacketSize );
/* @[declare_mqtt_getpingreqpacketsize] */
/**
 * @brief Serialize an MQTT PINGREQ packet into the given buffer.
 *
 * The input #MQTTFixedBuffer_t.size must be at least as large as the size
 * returned by #MQTT_GetPingreqPacketSize.
 *
 * @param[out] pFixedBuffer Buffer for packet serialization.
 *
 * @return #MQTTNoMemory if pFixedBuffer is too small to hold the MQTT packet;
 * #MQTTBadParameter if invalid parameters are passed;
 * #MQTTSuccess otherwise.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTFixedBuffer_t fixedBuffer;
 * uint8_t buffer[ BUFFER_SIZE ];
 *
 * fixedBuffer.pBuffer = buffer;
 * fixedBuffer.size = BUFFER_SIZE;
 *
 * // Get the ping request packet size.
 * status = MQTT_GetPingreqPacketSize( &packetSize );
 * assert( status == MQTTSuccess );
 * assert( packetSize <= BUFFER_SIZE );
 *
 * // Serialize the ping request into the fixed buffer.
 * status = MQTT_SerializePingreq( &fixedBuffer );
 *
 * if( status == MQTTSuccess )
 * {
 *      // The ping request can now be sent to the broker.
 * }
 * @endcode
 */
/* @[declare_mqtt_serializepingreq] */
MQTTStatus_t MQTT_SerializePingreq( const MQTTFixedBuffer_t * pFixedBuffer );
/* @[declare_mqtt_serializepingreq] */
/**
 * @brief Deserialize an MQTT PUBLISH packet.
 *
 * @param[in] pIncomingPacket #MQTTPacketInfo_t containing the buffer.
 * @param[out] pPacketId The packet ID obtained from the buffer.
 * @param[out] pPublishInfo Struct containing information about the publish.
 *
 * @return #MQTTBadParameter, #MQTTBadResponse, or #MQTTSuccess.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // TransportRecv_t function for reading from the network.
 * int32_t socket_recv(
 *      NetworkContext_t * pNetworkContext,
 *      void * pBuffer,
 *      size_t bytesToRecv
 * );
 * // Some context to be used with the above transport receive function.
 * NetworkContext_t networkContext;
 *
 * // Other variables used in this example.
 * MQTTStatus_t status;
 * MQTTPacketInfo_t incomingPacket;
 * MQTTPublishInfo_t publishInfo = { 0 };
 * uint16_t packetId;
 *
 * int32_t bytesRecvd;
 * // A buffer to hold remaining data of the incoming packet.
 * uint8_t buffer[ BUFFER_SIZE ];
 *
 * // Populate all fields of the incoming packet.
 * status = MQTT_GetIncomingPacketTypeAndLength(
 *      socket_recv,
 *      &networkContext,
 *      &incomingPacket
 * );
 * assert( status == MQTTSuccess );
 * assert( incomingPacket.remainingLength <= BUFFER_SIZE );
 * bytesRecvd = socket_recv(
 *      &networkContext,
 *      ( void * ) buffer,
 *      incomingPacket.remainingLength
 * );
 * incomingPacket.pRemainingData = buffer;
 *
 * // Deserialize the publish information if the incoming packet is a publish.
 * if( ( incomingPacket.type & 0xF0 ) == MQTT_PACKET_TYPE_PUBLISH )
 * {
 *      status = MQTT_DeserializePublish( &incomingPacket, &packetId, &publishInfo );
 *      if( status == MQTTSuccess )
 *      {
 *          // The deserialized publish information can now be used from `publishInfo`.
 *      }
 * }
 * @endcode
 */
/* @[declare_mqtt_deserializepublish] */
MQTTStatus_t MQTT_DeserializePublish( const MQTTPacketInfo_t * pIncomingPacket,
                                      uint16_t * pPacketId,
                                      MQTTPublishInfo_t * pPublishInfo );
/* @[declare_mqtt_deserializepublish] */
/**
 * @brief Deserialize an MQTT CONNACK, SUBACK, UNSUBACK, PUBACK, PUBREC, PUBREL,
 * PUBCOMP, or PINGRESP.
 *
 * @param[in] pIncomingPacket #MQTTPacketInfo_t containing the buffer.
 * @param[out] pPacketId The packet ID of obtained from the buffer. Not used
 * in CONNACK or PINGRESP.
 * @param[out] pSessionPresent Boolean flag from a CONNACK indicating present session.
 *
 * @return #MQTTBadParameter, #MQTTBadResponse, #MQTTServerRefused, or #MQTTSuccess.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTPacketInfo_t incomingPacket;
 * // Used for SUBACK, UNSUBACK, PUBACK, PUBREC, PUBREL, and PUBCOMP.
 * uint16_t packetId;
 * // Used for CONNACK.
 * bool sessionPresent;
 *
 * // Receive an incoming packet and populate all fields. The details are out of scope
 * // for this example.
 * receiveIncomingPacket( &incomingPacket );
 *
 * // Deserialize ack information if the incoming packet is not a publish.
 * if( ( incomingPacket.type & 0xF0 ) != MQTT_PACKET_TYPE_PUBLISH )
 * {
 *      status = MQTT_DeserializeAck( &incomingPacket, &packetId, &sessionPresent );
 *      if( status == MQTTSuccess )
 *      {
 *          // The packet ID or session present flag information is available. For
 *          // ping response packets, the only information is the status code.
 *      }
 * }
 * @endcode
 */
/* @[declare_mqtt_deserializeack] */
MQTTStatus_t MQTT_DeserializeAck( const MQTTPacketInfo_t * pIncomingPacket,
                                  uint16_t * pPacketId,
                                  bool * pSessionPresent );
/* @[declare_mqtt_deserializeack] */
/**
 * @brief Extract the MQTT packet type and length from incoming packet.
 *
 * This function must be called for every incoming packet to retrieve the
 * #MQTTPacketInfo_t.type and #MQTTPacketInfo_t.remainingLength. A
 * #MQTTPacketInfo_t is not valid until this routine has been invoked.
 *
 * @param[in] readFunc Transport layer read function pointer.
 * @param[in] pNetworkContext The network context pointer provided by the application.
 * @param[out] pIncomingPacket Pointer to MQTTPacketInfo_t structure. This is
 * where type, remaining length and packet identifier are stored.
 *
 * @return #MQTTSuccess on successful extraction of type and length,
 * #MQTTBadParameter if @p pIncomingPacket is invalid,
 * #MQTTRecvFailed on transport receive failure,
 * #MQTTBadResponse if an invalid packet is read, and
 * #MQTTNoDataAvailable if there is nothing to read.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // TransportRecv_t function for reading from the network.
 * int32_t socket_recv(
 *      NetworkContext_t * pNetworkContext,
 *      void * pBuffer,
 *      size_t bytesToRecv
 * );
 * // Some context to be used with above transport receive function.
 * NetworkContext_t networkContext;
 *
 * // Struct to hold the incoming packet information.
 * MQTTPacketInfo_t incomingPacket;
 * MQTTStatus_t status = MQTTSuccess;
 * int32_t bytesRecvd;
 * // Buffer to hold the remaining data of the incoming packet.
 * uint8_t buffer[ BUFFER_SIZE ];
 *
 * // Loop until data is available to be received.
 * do{
 *      status = MQTT_GetIncomingPacketTypeAndLength(
 *          socket_recv,
 *          &networkContext,
 *          &incomingPacket
 *      );
 * } while( status == MQTTNoDataAvailable );
 *
 * assert( status == MQTTSuccess );
 *
 * // Receive the rest of the incoming packet.
 * assert( incomingPacket.remainingLength <= BUFFER_SIZE );
 * bytesRecvd = socket_recv(
 *      &networkContext,
 *      ( void * ) buffer,
 *      incomingPacket.remainingLength
 * );
 *
 * // Set the remaining data field.
 * incomingPacket.pRemainingData = buffer;
 * @endcode
 */
/* @[declare_mqtt_getincomingpackettypeandlength] */
MQTTStatus_t MQTT_GetIncomingPacketTypeAndLength( TransportRecv_t readFunc,
                                                  NetworkContext_t * pNetworkContext,
                                                  MQTTPacketInfo_t * pIncomingPacket );
/* @[declare_mqtt_getincomingpackettypeandlength] */
/**
 * @brief Extract the MQTT packet type and length from incoming packet.
 *
 * This function must be called for every incoming packet to retrieve the
 * #MQTTPacketInfo_t.type and #MQTTPacketInfo_t.remainingLength. A
 * #MQTTPacketInfo_t is not valid until this routine has been invoked.
 *
 * @param[in] pBuffer The buffer holding the raw data to be processed
 * @param[in] pIndex Pointer to the index within the buffer to marking the end
 *            of raw data available.
 * @param[out] pIncomingPacket Structure used to hold the fields of the
 *            incoming packet.
 *
 * @return #MQTTSuccess on successful extraction of type and length,
 * #MQTTBadParameter if @p pIncomingPacket is invalid,
 * #MQTTBadResponse if an invalid packet is read, and
 * #MQTTNoDataAvailable if there is nothing to read.
 */
 /* @[declare_mqtt_processincomingpackettypeandlength] */
MQTTStatus_t MQTT_ProcessIncomingPacketTypeAndLength( const uint8_t * pBuffer,
                                                      const size_t * pIndex,
                                                      MQTTPacketInfo_t * pIncomingPacket );
/* @[declare_mqtt_processincomingpackettypeandlength] */
/**
 * @fn uint8_t * MQTT_SerializeConnectFixedHeader( uint8_t * pIndex, const MQTTConnectInfo_t * pConnectInfo, const MQTTPublishInfo_t * pWillInfo, size_t remainingLength );
 * @brief Serialize the fixed part of the connect packet header.
 *
 * @param[out] pIndex Pointer to the buffer where the header is to
 * be serialized.
 * @param[in] pConnectInfo The connect information.
 * @param[in] pWillInfo The last will and testament information.
 * @param[in] remainingLength The remaining length of the packet to be
 * serialized.
 *
 * @return A pointer to the end of the encoded string.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
uint8_t * MQTT_SerializeConnectFixedHeader( uint8_t * pIndex,
                                            const MQTTConnectInfo_t * pConnectInfo,
                                            const MQTTPublishInfo_t * pWillInfo,
                                            size_t remainingLength );
/** @endcond */
/**
 * @fn  uint8_t * MQTT_SerializeSubscribeHeader( size_t remainingLength, uint8_t * pIndex, uint16_t packetId );
 * @brief Serialize the fixed part of the subscribe packet header.
 *
 * @param[in] remainingLength The remaining length of the packet to be
 * serialized.
 * @param[in] pIndex Pointer to the buffer where the header is to
 * be serialized.
 * @param[in] packetId The packet ID to be serialized.
 *
 * @return A pointer to the end of the encoded string.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
uint8_t * MQTT_SerializeSubscribeHeader( size_t remainingLength,
                                         uint8_t * pIndex,
                                         uint16_t packetId );
/** @endcond */
/**
 * @fn uint8_t * MQTT_SerializeUnsubscribeHeader( size_t remainingLength, uint8_t * pIndex, uint16_t packetId );
 * @brief Serialize the fixed part of the unsubscribe packet header.
 *
 * @param[in] remainingLength The remaining length of the packet to be
 * serialized.
 * @param[in] pIndex Pointer to the buffer where the header is to
 * be serialized.
 * @param[in] packetId The packet ID to be serialized.
 *
 * @return A pointer to the end of the encoded string.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
uint8_t * MQTT_SerializeUnsubscribeHeader( size_t remainingLength,
                                           uint8_t * pIndex,
                                           uint16_t packetId );
/** @endcond */
/* *INDENT-OFF* */
#ifdef __cplusplus
    }
#endif
/* *INDENT-ON* */
#endif /* ifndef CORE_MQTT_SERIALIZER_H */
project/coreMQTT/coreMQTT/core_mqtt_state.c
New file
@@ -0,0 +1,1214 @@
/*
 * coreMQTT v2.1.1
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * @file core_mqtt_state.c
 * @brief Implements the functions in core_mqtt_state.h.
 */
#include <assert.h>
#include <string.h>
#include "core_mqtt_state.h"
/* Include config defaults header to get default values of configs. */
#include "core_mqtt_config_defaults.h"
#include "core_mqtt_default_logging.h"
/*-----------------------------------------------------------*/
/**
 * @brief A global static variable used to generate the macro
 * #MQTT_INVALID_STATE_COUNT of size_t length.
 */
static const size_t ZERO_SIZE_T = 0U;
/**
 * @brief This macro depicts the invalid value for the state publishes.
 */
#define MQTT_INVALID_STATE_COUNT    ( ~ZERO_SIZE_T )
/**
 * @brief Create a 16-bit bitmap with bit set at specified position.
 *
 * @param[in] position The position at which the bit need to be set.
 */
#define UINT16_BITMAP_BIT_SET_AT( position )    ( ( uint16_t ) 0x01U << ( ( uint16_t ) position ) )
/**
 * @brief Set a bit in an 16-bit unsigned integer.
 *
 * @param[in] x The 16-bit unsigned integer to set a bit.
 * @param[in] position The position at which the bit need to be set.
 */
#define UINT16_SET_BIT( x, position )           ( ( x ) = ( uint16_t ) ( ( x ) | ( UINT16_BITMAP_BIT_SET_AT( position ) ) ) )
/**
 * @brief Macro for checking if a bit is set in a 16-bit unsigned integer.
 *
 * @param[in] x The unsigned 16-bit integer to check.
 * @param[in] position Which bit to check.
 */
#define UINT16_CHECK_BIT( x, position )         ( ( ( x ) & ( UINT16_BITMAP_BIT_SET_AT( position ) ) ) == ( UINT16_BITMAP_BIT_SET_AT( position ) ) )
/*-----------------------------------------------------------*/
/**
 * @brief Test if a transition to new state is possible, when dealing with PUBLISHes.
 *
 * @param[in] currentState The current state.
 * @param[in] newState State to transition to.
 * @param[in] opType Reserve, Send, or Receive.
 * @param[in] qos 0, 1, or 2.
 *
 * @note This function does not validate the current state, or the new state
 * based on either the operation type or QoS. It assumes the new state is valid
 * given the opType and QoS, which will be the case if calculated by
 * MQTT_CalculateStatePublish().
 *
 * @return `true` if transition is possible, else `false`
 */
static bool validateTransitionPublish( MQTTPublishState_t currentState,
                                       MQTTPublishState_t newState,
                                       MQTTStateOperation_t opType,
                                       MQTTQoS_t qos );
/**
 * @brief Test if a transition to a new state is possible, when dealing with acks.
 *
 * @param[in] currentState The current state.
 * @param[in] newState State to transition to.
 *
 * @return `true` if transition is possible, else `false`.
 */
static bool validateTransitionAck( MQTTPublishState_t currentState,
                                   MQTTPublishState_t newState );
/**
 * @brief Test if the publish corresponding to an ack is outgoing or incoming.
 *
 * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP.
 * @param[in] opType Send, or Receive.
 *
 * @return `true` if corresponds to outgoing publish, else `false`.
 */
static bool isPublishOutgoing( MQTTPubAckType_t packetType,
                               MQTTStateOperation_t opType );
/**
 * @brief Find a packet ID in the state record.
 *
 * @param[in] records State record array.
 * @param[in] recordCount Length of record array.
 * @param[in] packetId packet ID to search for.
 * @param[out] pQos QoS retrieved from record.
 * @param[out] pCurrentState state retrieved from record.
 *
 * @return index of the packet id in the record if it exists, else the record length.
 */
static size_t findInRecord( const MQTTPubAckInfo_t * records,
                            size_t recordCount,
                            uint16_t packetId,
                            MQTTQoS_t * pQos,
                            MQTTPublishState_t * pCurrentState );
/**
 * @brief Compact records.
 *
 * Records are arranged in the relative order to maintain message ordering.
 * This will lead to fragmentation and this function will help in defragmenting
 * the records array.
 *
 * @param[in] records State record array.
 * @param[in] recordCount Length of record array.
 */
static void compactRecords( MQTTPubAckInfo_t * records,
                            size_t recordCount );
/**
 * @brief Store a new entry in the state record.
 *
 * @param[in] records State record array.
 * @param[in] recordCount Length of record array.
 * @param[in] packetId Packet ID of new entry.
 * @param[in] qos QoS of new entry.
 * @param[in] publishState State of new entry.
 *
 * @return #MQTTSuccess, #MQTTNoMemory, or #MQTTStateCollision.
 */
static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records,
                               size_t recordCount,
                               uint16_t packetId,
                               MQTTQoS_t qos,
                               MQTTPublishState_t publishState );
/**
 * @brief Update and possibly delete an entry in the state record.
 *
 * @param[in] records State record array.
 * @param[in] recordIndex index of record to update.
 * @param[in] newState New state to update.
 * @param[in] shouldDelete Whether an existing entry should be deleted.
 */
static void updateRecord( MQTTPubAckInfo_t * records,
                          size_t recordIndex,
                          MQTTPublishState_t newState,
                          bool shouldDelete );
/**
 * @brief Get the packet ID and index of an outgoing publish in specified
 * states.
 *
 * @param[in] pMqttContext Initialized MQTT context.
 * @param[in] searchStates The states to search for in 2-byte bit map.
 * @param[in,out] pCursor Index at which to start searching.
 *
 * @return Packet ID of the outgoing publish.
 */
static uint16_t stateSelect( const MQTTContext_t * pMqttContext,
                             uint16_t searchStates,
                             MQTTStateCursor_t * pCursor );
/**
 * @brief Update the state records for an ACK after state transition
 * validations.
 *
 * @param[in] records State records pointer.
 * @param[in] maxRecordCount The maximum number of records.
 * @param[in] recordIndex Index at which the record is stored.
 * @param[in] packetId Packet id of the packet.
 * @param[in] currentState Current state of the publish record.
 * @param[in] newState New state of the publish.
 *
 * @return #MQTTIllegalState, or #MQTTSuccess.
 */
static MQTTStatus_t updateStateAck( MQTTPubAckInfo_t * records,
                                    size_t maxRecordCount,
                                    size_t recordIndex,
                                    uint16_t packetId,
                                    MQTTPublishState_t currentState,
                                    MQTTPublishState_t newState );
/**
 * @brief Update the state record for a PUBLISH packet after validating
 * the state transitions.
 *
 * @param[in] pMqttContext Initialized MQTT context.
 * @param[in] recordIndex Index in state records at which publish record exists.
 * @param[in] packetId ID of the PUBLISH packet.
 * @param[in] opType Send or Receive.
 * @param[in] qos 0, 1, or 2.
 * @param[in] currentState Current state of the publish record.
 * @param[in] newState New state of the publish record.
 *
 * @return #MQTTIllegalState, #MQTTStateCollision or #MQTTSuccess.
 */
static MQTTStatus_t updateStatePublish( const MQTTContext_t * pMqttContext,
                                        size_t recordIndex,
                                        uint16_t packetId,
                                        MQTTStateOperation_t opType,
                                        MQTTQoS_t qos,
                                        MQTTPublishState_t currentState,
                                        MQTTPublishState_t newState );
/*-----------------------------------------------------------*/
static bool validateTransitionPublish( MQTTPublishState_t currentState,
                                       MQTTPublishState_t newState,
                                       MQTTStateOperation_t opType,
                                       MQTTQoS_t qos )
{
    bool isValid = false;
    switch( currentState )
    {
        case MQTTStateNull:
            /* Transitions from null occur when storing a new entry into the record. */
            if( opType == MQTT_RECEIVE )
            {
                isValid = ( newState == MQTTPubAckSend ) || ( newState == MQTTPubRecSend );
            }
            break;
        case MQTTPublishSend:
            /* Outgoing publish. All such publishes start in this state due to
             * the reserve operation. */
            switch( qos )
            {
                case MQTTQoS1:
                    isValid = newState == MQTTPubAckPending;
                    break;
                case MQTTQoS2:
                    isValid = newState == MQTTPubRecPending;
                    break;
                case MQTTQoS0:
                default:
                    /* QoS 0 is checked before calling this function. */
                    break;
            }
            break;
        /* Below cases are for validating the resends of publish when a session is
         * reestablished. */
        case MQTTPubAckPending:
            /* When a session is reestablished, outgoing QoS1 publishes in state
             * #MQTTPubAckPending can be resent. The state remains the same. */
            isValid = newState == MQTTPubAckPending;
            break;
        case MQTTPubRecPending:
            /* When a session is reestablished, outgoing QoS2 publishes in state
             * #MQTTPubRecPending can be resent. The state remains the same. */
            isValid = newState == MQTTPubRecPending;
            break;
        case MQTTPubAckSend:
        case MQTTPubCompPending:
        case MQTTPubCompSend:
        case MQTTPubRecSend:
        case MQTTPubRelPending:
        case MQTTPubRelSend:
        case MQTTPublishDone:
        default:
            /* For a PUBLISH, we should not start from any other state. */
            break;
    }
    return isValid;
}
/*-----------------------------------------------------------*/
static bool validateTransitionAck( MQTTPublishState_t currentState,
                                   MQTTPublishState_t newState )
{
    bool isValid = false;
    switch( currentState )
    {
        case MQTTPubAckSend:
        /* Incoming publish, QoS 1. */
        case MQTTPubAckPending:
            /* Outgoing publish, QoS 1. */
            isValid = newState == MQTTPublishDone;
            break;
        case MQTTPubRecSend:
            /* Incoming publish, QoS 2. */
            isValid = newState == MQTTPubRelPending;
            break;
        case MQTTPubRelPending:
            /* Incoming publish, QoS 2.
             * There are 2 valid transitions possible.
             * 1. MQTTPubRelPending -> MQTTPubCompSend : A PUBREL ack is received
             *    when publish record state is MQTTPubRelPending. This is the
             *    normal state transition without any connection interruptions.
             * 2. MQTTPubRelPending -> MQTTPubRelPending : Receiving a duplicate
             *    QoS2 publish can result in a transition to the same state.
             *    This can happen in the below state transition.
             *    1. Incoming publish received.
             *    2. PUBREC ack sent and state is now MQTTPubRelPending.
             *    3. TCP connection failure and broker didn't receive the PUBREC.
             *    4. Reestablished MQTT session.
             *    5. MQTT broker resent the un-acked publish.
             *    6. Publish is received when publish record state is in
             *       MQTTPubRelPending.
             *    7. Sending out a PUBREC will result in this transition
             *       to the same state. */
            isValid = ( newState == MQTTPubCompSend ) ||
                      ( newState == MQTTPubRelPending );
            break;
        case MQTTPubCompSend:
            /* Incoming publish, QoS 2.
             * There are 2 valid transitions possible.
             * 1. MQTTPubCompSend -> MQTTPublishDone : A PUBCOMP ack is sent
             *    after receiving a PUBREL from broker. This is the
             *    normal state transition without any connection interruptions.
             * 2. MQTTPubCompSend -> MQTTPubCompSend : Receiving a duplicate PUBREL
             *    can result in a transition to the same state.
             *    This can happen in the below state transition.
             *    1. A TCP connection failure happened before sending a PUBCOMP
             *       for an incoming PUBREL.
             *    2. Reestablished an MQTT session.
             *    3. MQTT broker resent the un-acked PUBREL.
             *    4. Receiving the PUBREL again will result in this transition
             *       to the same state. */
            isValid = ( newState == MQTTPublishDone ) ||
                      ( newState == MQTTPubCompSend );
            break;
        case MQTTPubRecPending:
            /* Outgoing publish, Qos 2. */
            isValid = newState == MQTTPubRelSend;
            break;
        case MQTTPubRelSend:
            /* Outgoing publish, Qos 2. */
            isValid = newState == MQTTPubCompPending;
            break;
        case MQTTPubCompPending:
            /* Outgoing publish, Qos 2.
             * There are 2 valid transitions possible.
             * 1. MQTTPubCompPending -> MQTTPublishDone : A PUBCOMP is received.
             *    This marks the complete state transition for the publish packet.
             *    This is the normal state transition without any connection
             *    interruptions.
             * 2. MQTTPubCompPending -> MQTTPubCompPending : Resending a PUBREL for
             *    packets in state #MQTTPubCompPending can result in this
             *    transition to the same state.
             *    This can happen in the below state transition.
             *    1. A TCP connection failure happened before receiving a PUBCOMP
             *       for an outgoing PUBREL.
             *    2. An MQTT session is reestablished.
             *    3. Resending the un-acked PUBREL results in this transition
             *       to the same state. */
            isValid = ( newState == MQTTPublishDone ) ||
                      ( newState == MQTTPubCompPending );
            break;
        case MQTTPublishDone:
        /* Done state should transition to invalid since it will be removed from the record. */
        case MQTTPublishSend:
        /* If an ack was sent/received we shouldn't have been in this state. */
        case MQTTStateNull:
        /* If an ack was sent/received the record should exist. */
        default:
            /* Invalid. */
            break;
    }
    return isValid;
}
/*-----------------------------------------------------------*/
static bool isPublishOutgoing( MQTTPubAckType_t packetType,
                               MQTTStateOperation_t opType )
{
    bool isOutgoing = false;
    switch( packetType )
    {
        case MQTTPuback:
        case MQTTPubrec:
        case MQTTPubcomp:
            isOutgoing = opType == MQTT_RECEIVE;
            break;
        case MQTTPubrel:
            isOutgoing = opType == MQTT_SEND;
            break;
        default:
            /* No other ack type. */
            break;
    }
    return isOutgoing;
}
/*-----------------------------------------------------------*/
static size_t findInRecord( const MQTTPubAckInfo_t * records,
                            size_t recordCount,
                            uint16_t packetId,
                            MQTTQoS_t * pQos,
                            MQTTPublishState_t * pCurrentState )
{
    size_t index = 0;
    assert( packetId != MQTT_PACKET_ID_INVALID );
    *pCurrentState = MQTTStateNull;
    for( index = 0; index < recordCount; index++ )
    {
        if( records[ index ].packetId == packetId )
        {
            *pQos = records[ index ].qos;
            *pCurrentState = records[ index ].publishState;
            break;
        }
    }
    if( index == recordCount )
    {
        index = MQTT_INVALID_STATE_COUNT;
    }
    return index;
}
/*-----------------------------------------------------------*/
static void compactRecords( MQTTPubAckInfo_t * records,
                            size_t recordCount )
{
    size_t index = 0;
    size_t emptyIndex = MQTT_INVALID_STATE_COUNT;
    assert( records != NULL );
    /* Find the empty spots and fill those with non empty values. */
    for( ; index < recordCount; index++ )
    {
        /* Find the first empty spot. */
        if( records[ index ].packetId == MQTT_PACKET_ID_INVALID )
        {
            if( emptyIndex == MQTT_INVALID_STATE_COUNT )
            {
                emptyIndex = index;
            }
        }
        else
        {
            if( emptyIndex != MQTT_INVALID_STATE_COUNT )
            {
                /* Copy over the contents at non empty index to empty index. */
                records[ emptyIndex ].packetId = records[ index ].packetId;
                records[ emptyIndex ].qos = records[ index ].qos;
                records[ emptyIndex ].publishState = records[ index ].publishState;
                /* Mark the record at current non empty index as invalid. */
                records[ index ].packetId = MQTT_PACKET_ID_INVALID;
                records[ index ].qos = MQTTQoS0;
                records[ index ].publishState = MQTTStateNull;
                /* Advance the emptyIndex. */
                emptyIndex++;
            }
        }
    }
}
/*-----------------------------------------------------------*/
static MQTTStatus_t addRecord( MQTTPubAckInfo_t * records,
                               size_t recordCount,
                               uint16_t packetId,
                               MQTTQoS_t qos,
                               MQTTPublishState_t publishState )
{
    MQTTStatus_t status = MQTTNoMemory;
    int32_t index = 0;
    size_t availableIndex = recordCount;
    bool validEntryFound = false;
    assert( packetId != MQTT_PACKET_ID_INVALID );
    assert( qos != MQTTQoS0 );
    /* Check if we have to compact the records. This is known by checking if
     * the last spot in the array is filled. */
    if( records[ recordCount - 1U ].packetId != MQTT_PACKET_ID_INVALID )
    {
        compactRecords( records, recordCount );
    }
    /* Start from end so first available index will be populated.
     * Available index is always found after the last element in the records.
     * This is to make sure the relative order of the records in order to meet
     * the message ordering requirement of MQTT spec 3.1.1. */
    for( index = ( ( int32_t ) recordCount - 1 ); index >= 0; index-- )
    {
        /* Available index is only found after packet at the highest index. */
        if( records[ index ].packetId == MQTT_PACKET_ID_INVALID )
        {
            if( validEntryFound == false )
            {
                availableIndex = ( size_t ) index;
            }
        }
        else
        {
            /* A non-empty spot found in the records. */
            validEntryFound = true;
            if( records[ index ].packetId == packetId )
            {
                /* Collision. */
                LogError( ( "Collision when adding PacketID=%u at index=%d.",
                            ( unsigned int ) packetId,
                            ( int ) index ) );
                status = MQTTStateCollision;
                availableIndex = recordCount;
                break;
            }
        }
    }
    if( availableIndex < recordCount )
    {
        records[ availableIndex ].packetId = packetId;
        records[ availableIndex ].qos = qos;
        records[ availableIndex ].publishState = publishState;
        status = MQTTSuccess;
    }
    return status;
}
/*-----------------------------------------------------------*/
static void updateRecord( MQTTPubAckInfo_t * records,
                          size_t recordIndex,
                          MQTTPublishState_t newState,
                          bool shouldDelete )
{
    assert( records != NULL );
    if( shouldDelete == true )
    {
        /* Mark the record as invalid. */
        records[ recordIndex ].packetId = MQTT_PACKET_ID_INVALID;
        records[ recordIndex ].qos = MQTTQoS0;
        records[ recordIndex ].publishState = MQTTStateNull;
    }
    else
    {
        records[ recordIndex ].publishState = newState;
    }
}
/*-----------------------------------------------------------*/
static uint16_t stateSelect( const MQTTContext_t * pMqttContext,
                             uint16_t searchStates,
                             MQTTStateCursor_t * pCursor )
{
    uint16_t packetId = MQTT_PACKET_ID_INVALID;
    uint16_t outgoingStates = 0U;
    const MQTTPubAckInfo_t * records = NULL;
    size_t maxCount;
    bool stateCheck = false;
    assert( pMqttContext != NULL );
    assert( searchStates != 0U );
    assert( pCursor != NULL );
    /* Create a bit map with all the outgoing publish states. */
    UINT16_SET_BIT( outgoingStates, MQTTPublishSend );
    UINT16_SET_BIT( outgoingStates, MQTTPubAckPending );
    UINT16_SET_BIT( outgoingStates, MQTTPubRecPending );
    UINT16_SET_BIT( outgoingStates, MQTTPubRelSend );
    UINT16_SET_BIT( outgoingStates, MQTTPubCompPending );
    /* Only outgoing publish records need to be searched. */
    assert( ( outgoingStates & searchStates ) > 0U );
    assert( ( ~outgoingStates & searchStates ) == 0U );
    records = pMqttContext->outgoingPublishRecords;
    maxCount = pMqttContext->outgoingPublishRecordMaxCount;
    while( *pCursor < maxCount )
    {
        /* Check if any of the search states are present. */
        stateCheck = UINT16_CHECK_BIT( searchStates, records[ *pCursor ].publishState );
        if( stateCheck == true )
        {
            packetId = records[ *pCursor ].packetId;
            ( *pCursor )++;
            break;
        }
        ( *pCursor )++;
    }
    return packetId;
}
/*-----------------------------------------------------------*/
MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType,
                                           MQTTStateOperation_t opType,
                                           MQTTQoS_t qos )
{
    MQTTPublishState_t calculatedState = MQTTStateNull;
    /* There are more QoS2 cases than QoS1, so initialize to that. */
    bool qosValid = qos == MQTTQoS2;
    switch( packetType )
    {
        case MQTTPuback:
            qosValid = qos == MQTTQoS1;
            calculatedState = MQTTPublishDone;
            break;
        case MQTTPubrec:
            /* Incoming publish: send PUBREC, PUBREL pending.
             * Outgoing publish: receive PUBREC, send PUBREL. */
            calculatedState = ( opType == MQTT_SEND ) ? MQTTPubRelPending : MQTTPubRelSend;
            break;
        case MQTTPubrel:
            /* Incoming publish: receive PUBREL, send PUBCOMP.
             * Outgoing publish: send PUBREL, PUBCOMP pending. */
            calculatedState = ( opType == MQTT_SEND ) ? MQTTPubCompPending : MQTTPubCompSend;
            break;
        case MQTTPubcomp:
            calculatedState = MQTTPublishDone;
            break;
        default:
            /* No other ack type. */
            break;
    }
    /* Sanity check, make sure ack and QoS agree. */
    if( qosValid == false )
    {
        calculatedState = MQTTStateNull;
    }
    return calculatedState;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t updateStateAck( MQTTPubAckInfo_t * records,
                                    size_t maxRecordCount,
                                    size_t recordIndex,
                                    uint16_t packetId,
                                    MQTTPublishState_t currentState,
                                    MQTTPublishState_t newState )
{
    MQTTStatus_t status = MQTTIllegalState;
    bool shouldDeleteRecord = false;
    bool isTransitionValid = false;
    assert( records != NULL );
    /* Record to be deleted if the state transition is completed or if a PUBREC
     * is received for an outgoing QoS2 publish. When a PUBREC is received,
     * record is deleted and added back to the end of the records to maintain
     * ordering for PUBRELs. */
    shouldDeleteRecord = ( newState == MQTTPublishDone ) || ( newState == MQTTPubRelSend );
    isTransitionValid = validateTransitionAck( currentState, newState );
    if( isTransitionValid == true )
    {
        status = MQTTSuccess;
        /* Update record for acks. When sending or receiving acks for packets that
         * are resent during a session reestablishment, the new state and
         * current state can be the same. No update of record required in that case. */
        if( currentState != newState )
        {
            updateRecord( records,
                          recordIndex,
                          newState,
                          shouldDeleteRecord );
            /* For QoS2 messages, in order to preserve the message ordering, when
             * a PUBREC is received for an outgoing publish, the record should be
             * moved to the last. This move will help preserve the order in which
             * a PUBREL needs to be resent in case of a session reestablishment. */
            if( newState == MQTTPubRelSend )
            {
                status = addRecord( records,
                                    maxRecordCount,
                                    packetId,
                                    MQTTQoS2,
                                    MQTTPubRelSend );
            }
        }
    }
    else
    {
        /* Invalid state transition. */
        LogError( ( "Invalid transition from state %s to state %s.",
                    MQTT_State_strerror( currentState ),
                    MQTT_State_strerror( newState ) ) );
    }
    return status;
}
/*-----------------------------------------------------------*/
static MQTTStatus_t updateStatePublish( const MQTTContext_t * pMqttContext,
                                        size_t recordIndex,
                                        uint16_t packetId,
                                        MQTTStateOperation_t opType,
                                        MQTTQoS_t qos,
                                        MQTTPublishState_t currentState,
                                        MQTTPublishState_t newState )
{
    MQTTStatus_t status = MQTTSuccess;
    bool isTransitionValid = false;
    assert( pMqttContext != NULL );
    assert( packetId != MQTT_PACKET_ID_INVALID );
    assert( qos != MQTTQoS0 );
    /* This will always succeed for an incoming publish. This is due to the fact
     * that the passed in currentState must be MQTTStateNull, since
     * #MQTT_UpdateStatePublish does not perform a lookup for receives. */
    isTransitionValid = validateTransitionPublish( currentState, newState, opType, qos );
    if( isTransitionValid == true )
    {
        /* addRecord will check for collisions. */
        if( opType == MQTT_RECEIVE )
        {
            status = addRecord( pMqttContext->incomingPublishRecords,
                                pMqttContext->incomingPublishRecordMaxCount,
                                packetId,
                                qos,
                                newState );
        }
        /* Send operation. */
        else
        {
            /* Skip updating record when publish is resend and no state
             * update is required. */
            if( currentState != newState )
            {
                updateRecord( pMqttContext->outgoingPublishRecords,
                              recordIndex,
                              newState,
                              false );
            }
        }
    }
    else
    {
        status = MQTTIllegalState;
        LogError( ( "Invalid transition from state %s to state %s.",
                    MQTT_State_strerror( currentState ),
                    MQTT_State_strerror( newState ) ) );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_ReserveState( const MQTTContext_t * pMqttContext,
                                uint16_t packetId,
                                MQTTQoS_t qos )
{
    MQTTStatus_t status = MQTTSuccess;
    if( qos == MQTTQoS0 )
    {
        status = MQTTSuccess;
    }
    else if( ( packetId == MQTT_PACKET_ID_INVALID ) || ( pMqttContext == NULL ) )
    {
        status = MQTTBadParameter;
    }
    else
    {
        /* Collisions are detected when adding the record. */
        status = addRecord( pMqttContext->outgoingPublishRecords,
                            pMqttContext->outgoingPublishRecordMaxCount,
                            packetId,
                            qos,
                            MQTTPublishSend );
    }
    return status;
}
/*-----------------------------------------------------------*/
MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType,
                                               MQTTQoS_t qos )
{
    MQTTPublishState_t calculatedState = MQTTStateNull;
    switch( qos )
    {
        case MQTTQoS0:
            calculatedState = MQTTPublishDone;
            break;
        case MQTTQoS1:
            calculatedState = ( opType == MQTT_SEND ) ? MQTTPubAckPending : MQTTPubAckSend;
            break;
        case MQTTQoS2:
            calculatedState = ( opType == MQTT_SEND ) ? MQTTPubRecPending : MQTTPubRecSend;
            break;
        default:
            /* No other QoS values. */
            break;
    }
    return calculatedState;
}
/*-----------------------------------------------------------*/
MQTTStatus_t MQTT_UpdateStatePublish( const MQTTContext_t * pMqttContext,
                                      uint16_t packetId,
                                      MQTTStateOperation_t opType,
                                      MQTTQoS_t qos,
                                      MQTTPublishState_t * pNewState )
{
    MQTTPublishState_t newState = MQTTStateNull;
    MQTTPublishState_t currentState = MQTTStateNull;
    MQTTStatus_t mqttStatus = MQTTSuccess;
    size_t recordIndex = MQTT_INVALID_STATE_COUNT;
    MQTTQoS_t foundQoS = MQTTQoS0;
    if( ( pMqttContext == NULL ) || ( pNewState == NULL ) )
    {
        LogError( ( "Argument cannot be NULL: pMqttContext=%p, pNewState=%p",
                    ( void * ) pMqttContext,
                    ( void * ) pNewState ) );
        mqttStatus = MQTTBadParameter;
    }
    else if( qos == MQTTQoS0 )
    {
        /* QoS 0 publish. Do nothing. */
        *pNewState = MQTTPublishDone;
    }
    else if( packetId == MQTT_PACKET_ID_INVALID )
    {
        /* Publishes > QoS 0 need a valid packet ID. */
        mqttStatus = MQTTBadParameter;
    }
    else if( opType == MQTT_SEND )
    {
        /* Search record for entry so we can check QoS. */
        recordIndex = findInRecord( pMqttContext->outgoingPublishRecords,
                                    pMqttContext->outgoingPublishRecordMaxCount,
                                    packetId,
                                    &foundQoS,
                                    &currentState );
        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,
                                    &currentState );
        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,
                                    &currentState );
    }
    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;
}
/*-----------------------------------------------------------*/
project/coreMQTT/coreMQTT/core_mqtt_state.h
New file
@@ -0,0 +1,310 @@
/*
 * coreMQTT v2.1.1
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/**
 * @file core_mqtt_state.h
 * @brief Function to keep state of MQTT PUBLISH packet deliveries.
 */
#ifndef CORE_MQTT_STATE_H
#define CORE_MQTT_STATE_H
/* *INDENT-OFF* */
#ifdef __cplusplus
    extern "C" {
#endif
/* *INDENT-ON* */
#include "core_mqtt.h"
/**
 * @ingroup mqtt_constants
 * @brief Initializer value for an #MQTTStateCursor_t, indicating a search
 * should start at the beginning of a state record array
 */
#define MQTT_STATE_CURSOR_INITIALIZER    ( ( size_t ) 0 )
/**
 * @ingroup mqtt_basic_types
 * @brief Cursor for iterating through state records.
 */
typedef size_t MQTTStateCursor_t;
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this section, this enum is private.
 *
 * @brief Value indicating either send or receive.
 */
typedef enum MQTTStateOperation
{
    MQTT_SEND,
    MQTT_RECEIVE
} MQTTStateOperation_t;
/** @endcond */
/**
 * @fn MQTTStatus_t MQTT_ReserveState( const MQTTContext_t * pMqttContext, uint16_t packetId, MQTTQoS_t qos );
 * @brief Reserve an entry for an outgoing QoS 1 or Qos 2 publish.
 *
 * @param[in] pMqttContext Initialized MQTT context.
 * @param[in] packetId The ID of the publish packet.
 * @param[in] qos 1 or 2.
 *
 * @return MQTTSuccess, MQTTNoMemory, or MQTTStateCollision.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
MQTTStatus_t MQTT_ReserveState( const MQTTContext_t * pMqttContext,
                                uint16_t packetId,
                                MQTTQoS_t qos );
/** @endcond */
/**
 * @fn MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType, MQTTQoS_t qos )
 * @brief Calculate the new state for a publish from its qos and operation type.
 *
 * @param[in] opType Send or Receive.
 * @param[in] qos 0, 1, or 2.
 *
 * @return The calculated state.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
MQTTPublishState_t MQTT_CalculateStatePublish( MQTTStateOperation_t opType,
                                               MQTTQoS_t qos );
/** @endcond */
/**
 * @fn MQTTStatus_t MQTT_UpdateStatePublish( const MQTTContext_t * pMqttContext, uint16_t packetId, MQTTStateOperation_t opType, MQTTQoS_t qos, MQTTPublishState_t * pNewState );
 * @brief Update the state record for a PUBLISH packet.
 *
 * @param[in] pMqttContext Initialized MQTT context.
 * @param[in] packetId ID of the PUBLISH packet.
 * @param[in] opType Send or Receive.
 * @param[in] qos 0, 1, or 2.
 * @param[out] pNewState Updated state of the publish.
 *
 * @return #MQTTBadParameter, #MQTTIllegalState, #MQTTStateCollision or
 * #MQTTSuccess.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
MQTTStatus_t MQTT_UpdateStatePublish( const MQTTContext_t * pMqttContext,
                                      uint16_t packetId,
                                      MQTTStateOperation_t opType,
                                      MQTTQoS_t qos,
                                      MQTTPublishState_t * pNewState );
/** @endcond */
/**
 * @fn MQTTStatus_t MQTT_RemoveStateRecord( const MQTTContext_t * pMqttContext, uint16_t packetId );
 * @brief Remove the state record for a PUBLISH packet.
 *
 * @param[in] pMqttContext Initialized MQTT context.
 * @param[in] packetId ID of the PUBLISH packet.
 *
 * @return #MQTTBadParameter or #MQTTSuccess.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
MQTTStatus_t MQTT_RemoveStateRecord( const MQTTContext_t * pMqttContext,
                                     uint16_t packetId );
/** @endcond */
/**
 * @fn MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTQoS_t qos );
 * @brief Calculate the state from a PUBACK, PUBREC, PUBREL, or PUBCOMP.
 *
 * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP.
 * @param[in] opType Send or Receive.
 * @param[in] qos 1 or 2.
 *
 * @return The calculated state.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
MQTTPublishState_t MQTT_CalculateStateAck( MQTTPubAckType_t packetType,
                                           MQTTStateOperation_t opType,
                                           MQTTQoS_t qos );
/** @endcond */
/**
 * @fn MQTTStatus_t MQTT_UpdateStateAck( const MQTTContext_t * pMqttContext, uint16_t packetId, MQTTPubAckType_t packetType, MQTTStateOperation_t opType, MQTTPublishState_t * pNewState );
 * @brief Update the state record for an ACKed publish.
 *
 * @param[in] pMqttContext Initialized MQTT context.
 * @param[in] packetId ID of the ack packet.
 * @param[in] packetType PUBACK, PUBREC, PUBREL, or PUBCOMP.
 * @param[in] opType Send or Receive.
 * @param[out] pNewState Updated state of the publish.
 *
 * @return #MQTTBadParameter if an invalid parameter is passed;
 * #MQTTBadResponse if the packet from the network is not found in the records;
 * #MQTTIllegalState if the requested update would result in an illegal transition;
 * #MQTTSuccess otherwise.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
MQTTStatus_t MQTT_UpdateStateAck( const MQTTContext_t * pMqttContext,
                                  uint16_t packetId,
                                  MQTTPubAckType_t packetType,
                                  MQTTStateOperation_t opType,
                                  MQTTPublishState_t * pNewState );
/** @endcond */
/**
 * @fn uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext, MQTTStateCursor_t * pCursor, MQTTPublishState_t * pState );
 * @brief Get the packet ID of next pending PUBREL ack to be resent.
 *
 * This function will need to be called to get the packet for which a PUBREL
 * need to be sent when a session is reestablished. Calling this function
 * repeatedly until packet id is 0 will give all the packets for which
 * a PUBREL need to be resent in the correct order.
 *
 * @param[in] pMqttContext Initialized MQTT context.
 * @param[in,out] pCursor Index at which to start searching.
 * @param[out] pState State indicating that PUBREL packet need to be sent.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
uint16_t MQTT_PubrelToResend( const MQTTContext_t * pMqttContext,
                              MQTTStateCursor_t * pCursor,
                              MQTTPublishState_t * pState );
/** @endcond */
/**
 * @brief Get the packet ID of next pending publish to be resent.
 *
 * This function will need to be called to get the packet for which a publish
 * need to be sent when a session is reestablished. Calling this function
 * repeatedly until packet id is 0 will give all the packets for which
 * a publish need to be resent in the correct order.
 *
 * @param[in] pMqttContext Initialized MQTT context.
 * @param[in,out] pCursor Index at which to start searching.
 *
 * <b>Example</b>
 * @code{c}
 *
 * // For this example assume this function returns an outgoing unacknowledged
 * // QoS 1 or 2 publish from its packet identifier.
 * MQTTPublishInfo_t * getPublish( uint16_t packetID );
 *
 * // Variables used in this example.
 * MQTTStatus_t status;
 * MQTTStateCursor_t cursor = MQTT_STATE_CURSOR_INITIALIZER;
 * bool sessionPresent;
 * uint16_t packetID;
 * MQTTPublishInfo_t * pResendPublish = NULL;
 * MQTTConnectInfo_t connectInfo = { 0 };
 *
 * // This is assumed to have been initialized before the call to MQTT_Connect().
 * MQTTContext_t * pContext;
 *
 * // Set clean session to false to attempt session resumption.
 * connectInfo.cleanSession = false;
 * connectInfo.pClientIdentifier = "someClientID";
 * connectInfo.clientIdentifierLength = strlen( connectInfo.pClientIdentifier );
 * connectInfo.keepAliveSeconds = 60;
 * // Optional connect parameters are not relevant to this example.
 *
 * // Create an MQTT connection. Use 100 milliseconds as a timeout.
 * status = MQTT_Connect( pContext, &connectInfo, NULL, 100, &sessionPresent );
 *
 * if( status == MQTTSuccess )
 * {
 *      if( sessionPresent )
 *      {
 *          // Loop while packet ID is nonzero.
 *          while( ( packetID = MQTT_PublishToResend( pContext, &cursor ) ) != 0 )
 *          {
 *              // Assume this function will succeed.
 *              pResendPublish = getPublish( packetID );
 *              // Set DUP flag.
 *              pResendPublish->dup = true;
 *              status = MQTT_Publish( pContext, pResendPublish, packetID );
 *
 *              if( status != MQTTSuccess )
 *              {
 *                  // Application can decide how to handle a failure.
 *              }
 *          }
 *      }
 *      else
 *      {
 *          // The broker did not resume a session, so we can clean up the
 *          // list of outgoing publishes.
 *      }
 * }
 * @endcode
 */
/* @[declare_mqtt_publishtoresend] */
uint16_t MQTT_PublishToResend( const MQTTContext_t * pMqttContext,
                               MQTTStateCursor_t * pCursor );
/* @[declare_mqtt_publishtoresend] */
/**
 * @fn const char * MQTT_State_strerror( MQTTPublishState_t state );
 * @brief State to string conversion for state engine.
 *
 * @param[in] state The state to convert to a string.
 *
 * @return The string representation of the state.
 */
/**
 * @cond DOXYGEN_IGNORE
 * Doxygen should ignore this definition, this function is private.
 */
const char * MQTT_State_strerror( MQTTPublishState_t state );
/** @endcond */
/* *INDENT-OFF* */
#ifdef __cplusplus
    }
#endif
/* *INDENT-ON* */
#endif /* ifndef CORE_MQTT_STATE_H */
Diff truncated after the above file
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