From ad5c2e11d93f7b0f7161c1f4c34aa9e58b8f9e65 Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Tue, 15 Oct 2019 11:59:22 +0100 Subject: [PATCH] Use cJSON for producing JSON output in clients. Closes #1222. Thanks to Ben Barbour. --- CMakeLists.txt | 2 + ChangeLog.txt | 1 + client/CMakeLists.txt | 27 ++++++++-- client/sub_client_output.c | 106 +++++++++++++++++++++++++++++++++++-- cmake/FindcJSON.cmake | 39 ++++++++++++++ compiling.txt | 3 +- config.mk | 8 +++ man/mosquitto_rr.1.xml | 4 +- man/mosquitto_sub.1.xml | 5 +- readme.md | 1 + 10 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 cmake/FindcJSON.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c37be2e..f9414dd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,8 @@ cmake_minimum_required(VERSION 2.8) set (VERSION 1.6.7) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") + add_definitions (-DCMAKE -DVERSION=\"${VERSION}\") if (WIN32) diff --git a/ChangeLog.txt b/ChangeLog.txt index 3819a644..bdc30c96 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -15,6 +15,7 @@ Clients: `mosquitto_rr -W `. Closes #275. - Add support for connecting to brokers through Unix domain sockets with the `--unix` argument. +- Use cJSON library for producing JSON output, where available. Closes #1222. 1.6.7 - 20190925 diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 6aa24b2b..adb9c897 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,7 +1,4 @@ -include_directories(${mosquitto_SOURCE_DIR} ${mosquitto_SOURCE_DIR}/lib - ${STDBOOL_H_PATH} ${STDINT_H_PATH} ${PTHREAD_INCLUDE_DIR} - ${OPENSSL_INCLUDE_DIR}) -link_directories(${mosquitto_BINARY_DIR}/lib) +FIND_PACKAGE(cJSON) set(shared_src client_shared.c client_shared.h client_props.c) @@ -9,10 +6,32 @@ if (WITH_SRV) add_definitions("-DWITH_SRV") endif (WITH_SRV) +set( CLIENT_INC ${mosquitto_SOURCE_DIR} ${mosquitto_SOURCE_DIR}/lib + ${STDBOOL_H_PATH} ${STDINT_H_PATH} ${PTHREAD_INCLUDE_DIR} + ${OPENSSL_INCLUDE_DIR}) + +set( CLIENT_DIR ${mosquitto_BINARY_DIR}/lib) + +if (CJSON_FOUND) + message(STATUS ${CJSON_FOUND}) + set( CLIENT_DIR "${CLIENT_DIR} ${CJSON_DIR}" ) + set( CLIENT_INC "${CLIENT_INC};${CJSON_INCLUDE_DIRS}" ) + add_definitions("-DWITH_CJSON") +endif() + +include_directories(${CLIENT_INC}) +link_directories(${CLIENT_DIR}) + add_executable(mosquitto_pub pub_client.c pub_shared.c ${shared_src}) add_executable(mosquitto_sub sub_client.c sub_client_output.c ${shared_src}) add_executable(mosquitto_rr rr_client.c pub_shared.c sub_client_output.c ${shared_src}) +if (CJSON_FOUND) + target_link_libraries(mosquitto_pub ${CJSON_LIBRARIES}) + target_link_libraries(mosquitto_sub ${CJSON_LIBRARIES}) + target_link_libraries(mosquitto_rr ${CJSON_LIBRARIES}) +endif() + if (WITH_STATIC_LIBRARIES) target_link_libraries(mosquitto_pub libmosquitto_static) target_link_libraries(mosquitto_sub libmosquitto_static) diff --git a/client/sub_client_output.c b/client/sub_client_output.c index a7cdefb2..9eb1c6de 100644 --- a/client/sub_client_output.c +++ b/client/sub_client_output.c @@ -30,6 +30,10 @@ Contributors: #define snprintf sprintf_s #endif +#ifdef WITH_CJSON +# include +#endif + #ifdef __APPLE__ # include #endif @@ -96,6 +100,7 @@ static void write_payload(const unsigned char *payload, int payloadlen, int hex) } +#ifndef WITH_CJSON static void write_json_payload(const char *payload, int payloadlen) { int i; @@ -108,10 +113,91 @@ static void write_json_payload(const char *payload, int payloadlen) } } } +#endif -static void json_print(const struct mosquitto_message *message, const struct tm *ti, bool escaped) +static int json_print(const struct mosquitto_message *message, const struct tm *ti, bool escaped) { +#ifdef WITH_CJSON + cJSON *root; + cJSON *tmp; + char *json_str; + const char *return_parse_end; + + root = cJSON_CreateObject(); + if(root == NULL){ + return MOSQ_ERR_NOMEM; + } + + tmp = cJSON_CreateNumber(time(NULL)); + if(tmp == NULL){ + cJSON_Delete(root); + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToObject(root, "tst", tmp); + + tmp = cJSON_CreateStringReference(message->topic); + if(tmp == NULL){ + cJSON_Delete(root); + return MOSQ_ERR_NOMEM; + } + + cJSON_AddItemToObject(root, "topic", tmp); + + tmp = cJSON_CreateNumber(message->qos); + if(tmp == NULL){ + cJSON_Delete(root); + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToObject(root, "qos", tmp); + + tmp = cJSON_CreateNumber(message->retain); + if(tmp == NULL){ + cJSON_Delete(root); + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToObject(root, "retain", tmp); + + tmp = cJSON_CreateNumber(message->payloadlen); + if(tmp == NULL){ + cJSON_Delete(root); + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToObject(root, "payloadlen", tmp); + + if(message->qos > 0){ + tmp = cJSON_CreateNumber(message->mid); + if(tmp == NULL){ + cJSON_Delete(root); + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToObject(root, "mid", tmp); + } + if(escaped){ + tmp = cJSON_CreateStringReference(message->payload); + if(tmp == NULL){ + cJSON_Delete(root); + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToObject(root, "payload", tmp); + }else{ + return_parse_end = NULL; + tmp = cJSON_ParseWithOpts(message->payload, &return_parse_end, true); + if(tmp == NULL || return_parse_end != message->payload + message->payloadlen){ + cJSON_Delete(root); + return MOSQ_ERR_INVAL; + } + cJSON_AddItemToObject(root, "payload", tmp); + } + + json_str = cJSON_PrintUnformatted(root); + cJSON_Delete(root); + + fputs(json_str, stdout); + free(json_str); + + return MOSQ_ERR_SUCCESS; +#else char buf[100]; strftime(buf, 100, "%s", ti); @@ -128,6 +214,9 @@ static void json_print(const struct mosquitto_message *message, const struct tm write_payload(message->payload, message->payloadlen, 0); fputs("}", stdout); } + + return MOSQ_ERR_SUCCESS; +#endif } @@ -139,6 +228,7 @@ static void formatted_print(const struct mosq_config *lcfg, const struct mosquit long ns; char strf[3]; char buf[100]; + int rc; len = strlen(lcfg->format); @@ -170,7 +260,10 @@ static void formatted_print(const struct mosq_config *lcfg, const struct mosquit return; } } - json_print(message, ti, true); + if(json_print(message, ti, true) != MOSQ_ERR_SUCCESS){ + err_printf(lcfg, "Error: Out of memory.\n"); + return; + } break; case 'J': @@ -180,7 +273,14 @@ static void formatted_print(const struct mosq_config *lcfg, const struct mosquit return; } } - json_print(message, ti, false); + rc = json_print(message, ti, false); + if(rc == MOSQ_ERR_NOMEM){ + err_printf(lcfg, "Error: Out of memory.\n"); + return; + }else if(rc == MOSQ_ERR_INVAL){ + err_printf(lcfg, "Error: Message payload is not valid JSON on topic %s.\n", message->topic); + return; + } break; case 'l': diff --git a/cmake/FindcJSON.cmake b/cmake/FindcJSON.cmake new file mode 100644 index 00000000..e4d2b732 --- /dev/null +++ b/cmake/FindcJSON.cmake @@ -0,0 +1,39 @@ +INCLUDE( FindPackageHandleStandardArgs ) + +# Checks an environment variable; note that the first check +# does not require the usual CMake $-sign. +IF( DEFINED ENV{CJSON_DIR} ) + SET( CJSON_DIR "$ENV{CJSON_DIR}" ) +ENDIF() + +FIND_PATH( + CJSON_INCLUDE_DIR + cJSON.h + HINTS + CJSON_DIR + /usr/include/cjson +) + +FIND_LIBRARY( CJSON_LIBRARY + NAMES cjson + HINTS ${CJSON_DIR} +) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS( CJSON DEFAULT_MSG + CJSON_INCLUDE_DIR CJSON_LIBRARY +) + +IF( CJSON_FOUND ) + SET( CJSON_INCLUDE_DIRS ${CJSON_INCLUDE_DIR} ) + SET( CJSON_LIBRARIES ${CJSON_LIBRARY} ) + + MARK_AS_ADVANCED( + CJSON_LIBRARY + CJSON_INCLUDE_DIR + CJSON_DIR + ) +ELSE() + SET( CJSON_DIR "" CACHE STRING + "An optional hint to a directory for finding `cJSON`" + ) +ENDIF() diff --git a/compiling.txt b/compiling.txt index 8d44b449..55c9b242 100644 --- a/compiling.txt +++ b/compiling.txt @@ -4,7 +4,8 @@ are optional. * openssl * c-ares (for DNS-SRV support, disabled by default) * tcp-wrappers (optional, package name libwrap0-dev) -* libwebsockets (optional, disabled by default, version 1.3 and above) +* libwebsockets (optional, disabled by default, version 2.4 and above) +* cJSON (optional, for JSON output from mosquitto_sub/mosquitto_rr) * On Windows, a pthreads library is required if threading support is to be included. diff --git a/config.mk b/config.mk index 11b29bae..62b7b56b 100644 --- a/config.mk +++ b/config.mk @@ -100,6 +100,9 @@ WITH_COVERAGE:=no # Build with unix domain socket support WITH_UNIX_SOCKETS:=yes +# Build mosquitto_sub with cJSON support +WITH_CJSON:=yes + # ============================================================================= # End of user configuration # ============================================================================= @@ -321,3 +324,8 @@ ifeq ($(WITH_COVERAGE),yes) CLIENT_CFLAGS:=$(CLIENT_CFLAGS) -coverage CLIENT_LDFLAGS:=$(CLIENT_LDFLAGS) -coverage endif + +ifeq ($(WITH_CJSON),yes) + CLIENT_CFLAGS:=$(CLIENT_CFLAGS) -DWITH_CJSON -I/usr/include/cjson + CLIENT_LDADD:=$(CLIENT_LDADD) -lcjson +endif diff --git a/man/mosquitto_rr.1.xml b/man/mosquitto_rr.1.xml index 3a783486..e073fa4b 100644 --- a/man/mosquitto_rr.1.xml +++ b/man/mosquitto_rr.1.xml @@ -680,7 +680,9 @@ parameters and timestamp, with a non-quoted and non-escaped payload - this means the payload must itself be valid JSON. For example: - {"tst":1470825369,"topic":"foo","qos":0,"retain":0,"payload":{"temperature":27.0,"humidity":57}}. + {"tst":1470825369,"topic":"foo","qos":0,"retain":0,"payload":{"temperature":27.0,"humidity":57}}. + If the payload is not valid JSON, then the error message "Error: Message payload is not valid JSON on topic + <topic>" will be printed to stderr. ISO-8601 format date and time, e.g. 2016-08-10T09:47:38+0100 Unix timestamp with nanoseconds, e.g. 1470818943.786368637 diff --git a/man/mosquitto_sub.1.xml b/man/mosquitto_sub.1.xml index d80afed1..3ee457c9 100644 --- a/man/mosquitto_sub.1.xml +++ b/man/mosquitto_sub.1.xml @@ -797,7 +797,10 @@ mosquitto_sub -t 'bbc/#' -T bbc/bbc1 --remove-retained parameters and timestamp, with a non-quoted and non-escaped payload - this means the payload must itself be valid JSON. For example: - {"tst":1470825369,"topic":"foo","qos":0,"retain":0,"payload":{"temperature":27.0,"humidity":57}}. + {"tst":1470825369,"topic":"foo","qos":0,"retain":0,"payload":{"temperature":27.0,"humidity":57}}. + If the payload is not valid JSON, then the error message "Error: Message payload is not valid JSON on topic + <topic>" will be printed to stderr. + ISO-8601 format date and time, e.g. 2016-08-10T09:47:38+0100 Unix timestamp with nanoseconds, e.g. 1470818943.786368637 diff --git a/readme.md b/readme.md index 95cc4dbf..eb2c2720 100644 --- a/readme.md +++ b/readme.md @@ -71,6 +71,7 @@ already be built. Use `make binary` to skip building the man pages, or install * openssl (libssl-dev on Debian based systems) - disable with `make WITH_TLS=no` * xsltproc (xsltproc and docbook-xsl on Debian based systems) - only needed when building from git sources - disable with `make WITH_DOCS=no` * uthash / utlist - bundled versions of these headers are provided, disable their use with `make WITH_BUNDLED_DEPS=no` +* cJSON - for client JSON output support. Disable with `make WITH_CJSON=no` Auto detected with CMake. Equivalent options for enabling/disabling features are available when using the CMake build.