From 6e29e6ad3c5abb935189c6e8d762a84d5a2d9b18 Mon Sep 17 00:00:00 2001 From: Federico Parente Date: Wed, 4 Sep 2024 18:13:15 +0200 Subject: [PATCH] First import --- .gitmodules | 7 + .vscode/c_cpp_properties.json | 21 + .vscode/launch.json | 37 ++ .vscode/settings.json | 24 + .vscode/tasks.json | 16 + Makefile | 39 ++ cert/ca.crt | 22 + cert/client.crt | 21 + cert/client.key | 28 + config.h | 17 + main.c | 115 ++++ modbus_task.c | 133 +++++ modbus_task.h | 19 + mqtt/MQTTClient.c | 699 +++++++++++++++++++++++ mqtt/MQTTClient.h | 239 ++++++++ mqtt/MQTTInterface.c | 340 +++++++++++ mqtt/MQTTInterface.h | 48 ++ mqtt/MQTTPacket/MQTTConnect.h | 148 +++++ mqtt/MQTTPacket/MQTTConnectClient.c | 213 +++++++ mqtt/MQTTPacket/MQTTConnectServer.c | 148 +++++ mqtt/MQTTPacket/MQTTDeserializePublish.c | 107 ++++ mqtt/MQTTPacket/MQTTFormat.c | 261 +++++++++ mqtt/MQTTPacket/MQTTFormat.h | 37 ++ mqtt/MQTTPacket/MQTTPacket.c | 411 +++++++++++++ mqtt/MQTTPacket/MQTTPacket.h | 133 +++++ mqtt/MQTTPacket/MQTTPublish.h | 38 ++ mqtt/MQTTPacket/MQTTSerializePublish.c | 168 ++++++ mqtt/MQTTPacket/MQTTSubscribe.h | 39 ++ mqtt/MQTTPacket/MQTTSubscribeClient.c | 136 +++++ mqtt/MQTTPacket/MQTTSubscribeServer.c | 111 ++++ mqtt/MQTTPacket/MQTTUnsubscribe.h | 38 ++ mqtt/MQTTPacket/MQTTUnsubscribeClient.c | 105 ++++ mqtt/MQTTPacket/MQTTUnsubscribeServer.c | 101 ++++ mqtt/MQTTPacket/StackTrace.h | 78 +++ mqtt_task.c | 205 +++++++ mqtt_task.h | 13 + nanoMODBUS | 1 + openssl_3.2 | 1 + platform.h | 272 +++++++++ utilis.c | 25 + utilis.h | 10 + 41 files changed, 4624 insertions(+) create mode 100644 .gitmodules create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 Makefile create mode 100644 cert/ca.crt create mode 100644 cert/client.crt create mode 100644 cert/client.key create mode 100644 config.h create mode 100644 main.c create mode 100644 modbus_task.c create mode 100644 modbus_task.h create mode 100644 mqtt/MQTTClient.c create mode 100644 mqtt/MQTTClient.h create mode 100644 mqtt/MQTTInterface.c create mode 100644 mqtt/MQTTInterface.h create mode 100644 mqtt/MQTTPacket/MQTTConnect.h create mode 100644 mqtt/MQTTPacket/MQTTConnectClient.c create mode 100644 mqtt/MQTTPacket/MQTTConnectServer.c create mode 100644 mqtt/MQTTPacket/MQTTDeserializePublish.c create mode 100644 mqtt/MQTTPacket/MQTTFormat.c create mode 100644 mqtt/MQTTPacket/MQTTFormat.h create mode 100644 mqtt/MQTTPacket/MQTTPacket.c create mode 100644 mqtt/MQTTPacket/MQTTPacket.h create mode 100644 mqtt/MQTTPacket/MQTTPublish.h create mode 100644 mqtt/MQTTPacket/MQTTSerializePublish.c create mode 100644 mqtt/MQTTPacket/MQTTSubscribe.h create mode 100644 mqtt/MQTTPacket/MQTTSubscribeClient.c create mode 100644 mqtt/MQTTPacket/MQTTSubscribeServer.c create mode 100644 mqtt/MQTTPacket/MQTTUnsubscribe.h create mode 100644 mqtt/MQTTPacket/MQTTUnsubscribeClient.c create mode 100644 mqtt/MQTTPacket/MQTTUnsubscribeServer.c create mode 100644 mqtt/MQTTPacket/StackTrace.h create mode 100644 mqtt_task.c create mode 100644 mqtt_task.h create mode 160000 nanoMODBUS create mode 160000 openssl_3.2 create mode 100644 platform.h create mode 100644 utilis.c create mode 100644 utilis.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..73d24ad --- /dev/null +++ b/.gitmodules @@ -0,0 +1,7 @@ +[submodule "nanoMODBUS"] + path = nanoMODBUS + url = https://github.com/debevv/nanoMODBUS +[submodule "openssl_3.2"] + path = openssl_3.2 + url = https://github.com/openssl/openssl + branch = openssl-3.2 diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..580edfe --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/mqtt/**", + "${workspaceFolder}/nanoMODBUS/**", + "${workspaceFolder}/openssl-openssl-3.3.1/**", + "${workspaceFolder}/mqtt/MQTTPacket" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "c17", + "cppStandard": "gnu++17", + "intelliSenseMode": "linux-gcc-x64", + "configurationProvider": "ms-vscode.makefile-tools" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9c1331b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,37 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) Launch", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/main", + "args": [], + "stopAtEntry": false, + "cwd": "${fileDirname}", + "environment": [], + "externalConsole": false, + }, + { + "name": "Debug C Program", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/main", // Path to the compiled executable + "args": [], // Arguments to pass to the program + "stopAtEntry": false, // Set to true to stop at main() + "cwd": "${workspaceFolder}", // Current working directory + "environment": [], + "externalConsole": false, // If true, opens an external console + "MIMode": "gdb", // Debugger type, could be "gdb" or "lldb" + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "build", // Task to run before launching the debugger + "miDebuggerPath": "/usr/bin/gdb", // Path to the debugger + }, + ], +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c179dd4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,24 @@ +{ + "files.associations": { + "ssl.h": "c", + "bio.h": "c", + "stdio.h": "c", + "e_os2.h": "c", + "err.h": "c", + "modbus_task.h": "c", + "stdlib.h": "c", + "mqttclient.h": "c", + "mqttinterface.h": "c", + "mqtt_task.h": "c", + "*.inc": "c", + "array": "c", + "string": "c", + "string_view": "c", + "string.h": "c", + "pthread.h": "c", + "nanomodbus.h": "c", + "initializer_list": "c", + "utility": "c" + }, + "cmake.sourceDirectory": "/home/federico/git/qubip-mqtt-linux-client-openssl/nanoMODBUS" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..0be03a8 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,16 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "shell", + "command": "make", + "args": [], + "group": "build", + "problemMatcher": [ + "$gcc" + ], + "detail": "Generated task for building the C program using make" + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b424990 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +# Target executable +TARGET := main + +# Compiler and flags +CC := gcc +CFLAGS := -Wall -Wextra -pedantic -g -pthread + +# Path to OpenSSL header files +CFLAGS += -Iopenssl-3.2.2/ -Imqtt/ -Imqtt/MQTTPacket/ + +# Percorso agli header files di nanoModbus +CFLAGS += -InanoMODBUS/ + +# Libraries +LIBS := -lstdc++ -Lopenssl-3.2.2 -lssl -lcrypto + +# Source files +#SRCS := main.c modbus_task.c mqtt_task.c nanoMODBUS/nanomodbus.c utilis.c mqtt/MQTTInterface.c mqtt/MQTTClient.c +SRCS := $(wildcard *.c nanoMODBUS/*.c mqtt/*.c mqtt/MQTTPacket/*.c) + +# Object files +OBJS := $(SRCS:.c=.o) + +# Default target +all: $(TARGET) + +# Build the target +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) $^ -o $@ $(LIBS) + +# Compile source files into object files +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +# Clean up +clean: + rm -f $(TARGET) $(OBJS) + +.PHONY: all clean diff --git a/cert/ca.crt b/cert/ca.crt new file mode 100644 index 0000000..fa55732 --- /dev/null +++ b/cert/ca.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlTCCAn2gAwIBAgIUKCfpw6t5lK6GDuZDtgD9w7FHqEMwDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCaXQxCzAJBgNVBAgMAmFsMQswCQYDVQQHDAJ0cDELMAkG +A1UECgwCc2YxCzAJBgNVBAsMAmViMRcwFQYDVQQDDA4xOTIuMTY4LjEwMS42MzAe +Fw0yNDA3MTAxMzE4MjFaFw0yNTA3MTAxMzE4MjFaMFoxCzAJBgNVBAYTAml0MQsw +CQYDVQQIDAJhbDELMAkGA1UEBwwCdHAxCzAJBgNVBAoMAnNmMQswCQYDVQQLDAJl +YjEXMBUGA1UEAwwOMTkyLjE2OC4xMDEuNjMwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCXDHuChb178kyELFJ8Pxw1lNVt5mPlu8yndBZqU3dONo+NF6bw +o5cfQgZhNveSRNeuGVPOuDe/KkjaTsYpwqIFpNE6fYN9mpJmPImDox9z6qEiyOS9 +/b4DlKLYcQ3ypWf4DPvi9Zpm2md7FTXfYUEI/HxJlSJUhF6VDsGxwS2UcTOalTZx +tcfIt+v0UChiYhybdD6u7+dBEMPkt200xKE48OOfDSeUGqM5LAYsQGNZstcxpwrS +r4gdjS9pmCxUqT99LoWidwrmxiZLuYwfH0ap2KQEZdQZIb25j+nX2LbS3D1sL5YW +kXNrmpXEmmKFIAIM/U/tddbTt6OGKv2V/zppAgMBAAGjUzBRMB0GA1UdDgQWBBSH +Hm+ISZlaDqa8FSoix0SLxdePVzAfBgNVHSMEGDAWgBSHHm+ISZlaDqa8FSoix0SL +xdePVzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAe3mV1yCN4 +kq92BGbeY0W+KkQ8HoJYsulINaLxpLvxM/JnxXvZBknmWRPRQ5G4bKCPhLDgoj2U +w+PGqV71FPGUhFlF+ofKiARh4FsAlpxiTuC2RGYr/9TYRHJn1a3eCWc1+SNQBK8e +F2z6NbPZX1Ow32TU0KOjXaZ3im0lQLTBUNlymrXup/7P4eIuexQY8+BzQXLIw+DY +KAwcNnBcDtEc/I3uLzrF1LsYGZbJ8lElxjY13l2TzkAvonO90ZXMZSmWpssLW7k4 +z81M5G1nHW10HNzukG1nrahcZMHsq9bxM86yA4k/0eIINxbxhByTNMYhq6744BDh +bBeMGN3kxMWs +-----END CERTIFICATE----- \ No newline at end of file diff --git a/cert/client.crt b/cert/client.crt new file mode 100644 index 0000000..5631a4e --- /dev/null +++ b/cert/client.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhDCCAmygAwIBAgIUc+vC6JB/wiRlgXzEyCM31gLDFOowDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCaXQxCzAJBgNVBAgMAmFsMQswCQYDVQQHDAJ0cDELMAkG +A1UECgwCc2YxCzAJBgNVBAsMAmViMRcwFQYDVQQDDA4xOTIuMTY4LjEwMS42MzAe +Fw0yNDA3MTAxMzE5NTVaFw0yNTA3MTAxMzE5NTVaMFoxCzAJBgNVBAYTAml0MQsw +CQYDVQQIDAJhbDELMAkGA1UEBwwCdG8xCzAJBgNVBAoMAnNmMQswCQYDVQQLDAJl +YjEXMBUGA1UEAwwOMTkyLjE2OC4xMDEuNjMwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQC+edJqS8QecC0Q/+dR4h/89oSzhT9U7Es1DPYLFXekKz70T30n +qWZTBvIhGtmS6ZeDkjDIpQDf50KgqsPlHI84KNEPrcB3ZGlmvmUIvDILTIuSDWAF +WpO1ovRbwmU5GDqWWw4qMCT0QdvYbtbHkmMDyJ3x1gFtDejsXggFrlfvTvAJx/AH +lXytCsXpsCn+3+N2kXhABR3K7k/jMo60D924hi/980drYrwLxpKuV6+QFgpTAoZ4 +aabwu4os6BdrS0O9IEzi7GADs0AlA8ewNTVjNqqUDgTzC7XdaMg4fwJsB9IKiLGZ +qzAWxyT6pxiGj8Ji8Y0zJHAKZW0sdzcIiwBdAgMBAAGjQjBAMB0GA1UdDgQWBBQ+ +tgdM1sGHb+WtGBAxo+Suc8sXqjAfBgNVHSMEGDAWgBSHHm+ISZlaDqa8FSoix0SL +xdePVzANBgkqhkiG9w0BAQsFAAOCAQEAJ95diL7bnVOs2KCQbzv5SYyAUxuLLdBm +JUAbi4RNrFeHmgdiHIeAGXamlxZglAn8ovdp06DRNdMZRkG9kN3L+mnh4TIH7cUu +NbzBFzwkNcEdavyRn3lzjWXLQYtS+ixry1kUJ5rVPKFsuR9pRf19Wjd+ZWX0GO9Z +HdZ0QkSPosleYtHn0TKexs7+P+1x8vtI86cmiZPwn02tGh1OpTkvKmenzRxWw7m+ +4YHwSump5LqkFyk+N+DnITXwJBn+l9RdZ2b1hVww1L/jlLAT1fxi+7FxFp2nZbwF +jasYJBVqhNm7As4Z85JGBjZI7HPwMXM2n1Aw5vV8s8W7DK+qJ+17og== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/cert/client.key b/cert/client.key new file mode 100644 index 0000000..3c2fd2f --- /dev/null +++ b/cert/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+edJqS8QecC0Q +/+dR4h/89oSzhT9U7Es1DPYLFXekKz70T30nqWZTBvIhGtmS6ZeDkjDIpQDf50Kg +qsPlHI84KNEPrcB3ZGlmvmUIvDILTIuSDWAFWpO1ovRbwmU5GDqWWw4qMCT0QdvY +btbHkmMDyJ3x1gFtDejsXggFrlfvTvAJx/AHlXytCsXpsCn+3+N2kXhABR3K7k/j +Mo60D924hi/980drYrwLxpKuV6+QFgpTAoZ4aabwu4os6BdrS0O9IEzi7GADs0Al +A8ewNTVjNqqUDgTzC7XdaMg4fwJsB9IKiLGZqzAWxyT6pxiGj8Ji8Y0zJHAKZW0s +dzcIiwBdAgMBAAECggEAQFEfYWAzGXoUZZybjfU2ivLs7Tdtoq3lWUUGoch+bTNj +HxjmMGnNkPST9uS7mhWBYV6QVXgN+wz6XQk8e5UwsSxrJ4mqp0YDJzvcHt55YYJX +0JnulfA3V0pui7tw9Z3+Tn3xowI9wDKq2wLWSG5gO8tWte3m7l2XjJTVzaFItRfT +0DCWUfb6jel+P6b0jffunMs11KI4tMGOIlb3VWt7ljTaXvebPpoZ9Kt5E0W0vqHs +J7JD1iNvw2bL2IQ6itF+g+Cf2YrBJuyaGpsrYPHRJpxUC1Z/Bd5tz3e6vPVeCOSi +goy3l1VdMjnUk4oYigNaH1JkAMyxShGoKw5vi77cCwKBgQDlEy6zxZXDidfZL4tT +wZkJ+iBkmSra5feIDknyNkVhilpVjJm1zdunrL727k43ClmJ1aaDdKOZOKHJdx9N +NuTL4uM8EnZx/ePc41jDmb2lxo9q/MFFRpNavAmhZonuoEIN4SaiUMbqIURIr+86 +zUqAtWuRz/4gxHi58nAasCdaOwKBgQDU3TObp6nGyhCbB8O98+HMvy/XhBPoaFvw +ylQLNmwypPvg+Ce5dzKuvyJJq6ISyvzjN7sest+jUOqvNZ/ZXUa9A864GGcLghXH +wlyipZxLf7FAzz5UmuvwMclefYH8FXRj8MD1V6FTziHqOLjHcp/XHtiR42YkcEhO +dXPouzlORwKBgQCFwbSkXbu8CHHTrDJDfqiYrcdaViEy3dKyS/2bg1rxwHJMv6NF +B+W5O2HqF23uL4nmtKzc1y9rmSjG1VqeoG3qKxoaCoHEv8XcRZef5tZYxN8bTmif +xbzm3yMUbiYeAs9vAUeowVfUgAY6FxiuEg7tpoEgC/3MLkx77vbMbo0b3wKBgGEU +VRIbQDnSNAqQWvxJuuRHGYmfyfiHh87kZ7oJYwUh62HpqyxRqYK61udkaHFLtFPo +OeXBTG9OWwn3WeSnPri7gM7DClPcSxSkltzyzLo+DVfybInncc1E14LJmLugCUn/ +JfF+uqve6ebJYbRMmYthnQHEBPR/ZOqrdGZi5LrHAoGAOxjXBJr9m7OWtWc6QCv2 +ZJ+Fjiuxr60KGBwjK3Fgeo2AsDpP72wtEBFKjCQijbJLHm1bhUvw8ray8qO/81ox +6H3gcyUul76LlfHPpKOa/IV7F5R6GvvkQyxerJkOBsJ7UNg3lJ66JJEbQMoA7we5 +jPOMOOrhlRIhcb7des6DPgU= +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/config.h b/config.h new file mode 100644 index 0000000..fe37b2a --- /dev/null +++ b/config.h @@ -0,0 +1,17 @@ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +// #define MOBBUS_PLC_IP "192.168.101.212" +#define MOBBUS_PLC_IP "192.168.168.133" +//#define MODBUS_PLC_PORT "502" +#define MODBUS_PLC_PORT "5002" +//#define MODBUS_PLC_REGISTER (32770) +#define MODBUS_PLC_REGISTER (10) + +#define MQTT_BROKER_IP "192.168.101.63" +#define MQTT_BROKER_PORT "1883" + +#define MQTT_TOPIC "2023/test" + + +#endif \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..1057416 --- /dev/null +++ b/main.c @@ -0,0 +1,115 @@ +/* + Include mqtt lib + include openssl lib + + fd -> openssl -> mqtt + mqtt -> openssl -> fd + + init + deinit + connenct + disconnect + write + read + +*/ + +#include +#include +#include +#include +#include +#include "config.h" +#include "modbus_task.h" +#include "mqtt_task.h" + +void init_signals(void); +static void signal_handler(int); +static void cleanup(void); +void panic(const char *, ...); +struct sigaction sigact; + +int main() +{ + pthread_t modbus_thread = 0; + pthread_t mqtt_sub_thread = 0; + pthread_t mqtt_pub_thread = 0; + int ret = 0; + + atexit(cleanup); + init_signals(); + + // Create threads + + // MODBUS TCP thread + modbus_task_config_t modbus_config = {MOBBUS_PLC_IP, MODBUS_PLC_PORT, MODBUS_PLC_REGISTER}; + + ret = pthread_create(&modbus_thread, NULL, modbus_task, (void *)&modbus_config); + if (ret != 0) + { + fprintf(stderr, "Error in creating modbus thread\n"); + return -1; + } + + // MQTT thread + mqtt_config_t mqtt_config = {MQTT_BROKER_IP, MQTT_BROKER_PORT}; + + ret = pthread_create(&mqtt_sub_thread, NULL, mqtt_sub_task, (void *)&mqtt_config); + if (ret != 0) + { + fprintf(stderr, "Error in creating mqtt sub thread\n"); + return -1; + } + + ret = pthread_create(&mqtt_pub_thread, NULL, mqtt_pub_task, (void *)&mqtt_config); + if (ret != 0) + { + fprintf(stderr, "Error in creating mqtt pub thread\n"); + return -1; + } + + // Wait till threads are complete before main continues. + pthread_join(modbus_thread, NULL); + pthread_join(mqtt_sub_thread, NULL); + pthread_join(mqtt_pub_thread, NULL); + + return 0; +} + +void init_signals(void) +{ + sigact.sa_handler = signal_handler; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction(SIGINT, &sigact, (struct sigaction *)NULL); +} + +static void signal_handler(int sig) +{ + switch (sig) + { + case SIGINT: + panic("\nCaught signal for Ctrl+C\n"); + break; + + default: + break; + } +} + +void panic(const char *fmt, ...) +{ + char buf[150]; + va_list argptr; + va_start(argptr, fmt); + vsprintf(buf, fmt, argptr); + va_end(argptr); + fprintf(stderr, "%s", buf); + exit(-1); +} + +void cleanup(void) +{ + sigemptyset(&sigact.sa_mask); + /* Do any cleaning up chores here */ +} diff --git a/modbus_task.c b/modbus_task.c new file mode 100644 index 0000000..ec3b28e --- /dev/null +++ b/modbus_task.c @@ -0,0 +1,133 @@ +#include +#include +#include "modbus_task.h" +#include "nanomodbus.h" +#include "platform.h" +#include "config.h" +#include "utilis.h" + +#define MAX_CONN_ATTEMPS 10 + +unsigned long shared_value = 0; + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + +// This task read a holding register, send it to mqtt task, increment and send it back to the plc. + +// Connect +// connected ? no -> connect +// connected +// read register +// send task notification to mqtt task +// increment plc data +// send new data to plc +// check if i need to disconnect and exit + +void *modbus_task(void *arg) +{ + modbus_task_config_t *config = (modbus_task_config_t *)arg; + + if(config == NULL) + { + // No config + fprintf(stderr, "modbus_task: Error no config!\n"); + pthread_exit(NULL); + return NULL; + } + + int nAttemps = 0; + void *conn = NULL; + do + { + // Set up the TCP connection + conn = connect_tcp(config->plc_ip, config->plc_port); + if (!conn) + { + fprintf(stderr, "modbus_task: [%d] Error connecting to server retry...\n", nAttemps++); + } + else + { + break; + } + } while (nAttemps < MAX_CONN_ATTEMPS); + + if (!conn) + { + fprintf(stderr, "modbus_task: Error connecting to server\n"); + return NULL; + } + + nmbs_platform_conf platform_conf; + platform_conf.transport = NMBS_TRANSPORT_TCP; + platform_conf.read = read_fd_linux; + platform_conf.write = write_fd_linux; + platform_conf.arg = conn; // Passing our TCP connection handle to the read/write functions + + // Create the modbus client + nmbs_t nmbs; + nmbs_error err = nmbs_client_create(&nmbs, &platform_conf); + if (err != NMBS_ERROR_NONE) + { + fprintf(stderr, "modbus_task: Error creating modbus client\n"); + if (!nmbs_error_is_exception(err)) + { + return NULL; + } + } + + // Set only the response timeout. Byte timeout will be handled by the TCP connection + nmbs_set_read_timeout(&nmbs, 1000); + + do + { + // Read holding register + uint16_t r_regs[1]; + err = nmbs_read_holding_registers(&nmbs, MODBUS_PLC_REGISTER, 1, r_regs); + if (err != NMBS_ERROR_NONE) + { + fprintf(stderr, "modbus_task: Error reading 2 holding registers at address %d - %s\n", MODBUS_PLC_REGISTER, nmbs_strerror(err)); + if (!nmbs_error_is_exception(err)) + { + disconnect(conn); + continue; // check while condition + } + } + + printf("modbus_task: Register at address %d: %d\n", MODBUS_PLC_REGISTER, r_regs[0]); + + // Send register to mqtt task + pthread_mutex_lock(&mutex); + shared_value = r_regs[0]; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mutex); + //printf("Send data [%d] to mqtt task", r_regs[0]); + + // Increment data + ++r_regs[0]; + + // Write data in holding register + uint16_t w_regs[1] = {r_regs[0]}; + err = nmbs_write_multiple_registers(&nmbs, MODBUS_PLC_REGISTER, 1, w_regs); + if (err != NMBS_ERROR_NONE) + { + fprintf(stderr, "modbus_task: Error writing register at address %d - %s\n", MODBUS_PLC_REGISTER, nmbs_strerror(err)); + if (!nmbs_error_is_exception(err)) + { + disconnect(conn); + continue; // check while condition + } + } + + // wait some time + sleep_ms(1000); + + // check if an error occurred or i need disconnect + } while (err == NMBS_ERROR_NONE && 1); + + // Close the TCP connection + disconnect(conn); + + // No need to destroy the nmbs instance, bye bye + return NULL; +} diff --git a/modbus_task.h b/modbus_task.h new file mode 100644 index 0000000..5a977c6 --- /dev/null +++ b/modbus_task.h @@ -0,0 +1,19 @@ +#ifndef __MODBUS_TASK_H_ +#define __MODBUS_TASK_H_ + +#include + +extern unsigned long shared_value; +extern pthread_mutex_t mutex; +extern pthread_cond_t cond; + +typedef struct +{ + char *plc_ip; + char *plc_port; + unsigned int plc_register; +} modbus_task_config_t; + +void *modbus_task(void *arg); + +#endif \ No newline at end of file diff --git a/mqtt/MQTTClient.c b/mqtt/MQTTClient.c new file mode 100644 index 0000000..812d11c --- /dev/null +++ b/mqtt/MQTTClient.c @@ -0,0 +1,699 @@ +/******************************************************************************* + * Copyright (c) 2014, 2017 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - fix for #96 - check rem_len in readPacket + * Ian Craggs - add ability to set message handler separately #6 + *******************************************************************************/ +#include +#include +#include + +static void NewMessageData(MessageData* md, MQTTString* aTopicName, MQTTMessage* aMessage) { + md->topicName = aTopicName; + md->message = aMessage; +} + + +static int getNextPacketId(MQTTClient *c) { + return c->next_packetid = (c->next_packetid == MAX_PACKET_ID) ? 1 : c->next_packetid + 1; +} + + +static int sendPacket(MQTTClient* c, int length, Timer* timer) +{ + int rc = FAILURE, + sent = 0; + + while (sent < length && !TimerIsExpired(timer)) + { + rc = c->ipstack->mqttwrite(c->ipstack, &c->buf[sent], length, TimerLeftMS(timer)); + if (rc < 0) // there was an error writing the data + break; + sent += rc; + } + if (sent == length) + { + TimerCountdown(&c->last_sent, c->keepAliveInterval); // record the fact that we have MQTT_SUCCESSfully sent the packet + rc = MQTT_SUCCESS; + } + else + rc = FAILURE; + return rc; +} + + +void MQTTClientInit(MQTTClient* c, Network* network, unsigned int command_timeout_ms, + unsigned char* sendbuf, size_t sendbuf_size, unsigned char* readbuf, size_t readbuf_size) +{ + int i; + c->ipstack = network; + + for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) + c->messageHandlers[i].topicFilter = 0; + c->command_timeout_ms = command_timeout_ms; + c->buf = sendbuf; + c->buf_size = sendbuf_size; + c->readbuf = readbuf; + c->readbuf_size = readbuf_size; + c->isconnected = 0; + c->cleansession = 0; + c->ping_outstanding = 0; + c->defaultMessageHandler = NULL; + c->next_packetid = 1; + TimerInit(&c->last_sent); + TimerInit(&c->last_received); +#if defined(MQTT_TASK) + MutexInit(&c->mutex); +#endif +} + + +static int decodePacket(MQTTClient* c, int* value, int timeout) +{ + unsigned char i; + int multiplier = 1; + int len = 0; + const int MAX_NO_OF_REMAINING_LENGTH_BYTES = 4; + + *value = 0; + do + { + int rc = MQTTPACKET_READ_ERROR; + + if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES) + { + rc = MQTTPACKET_READ_ERROR; /* bad data */ + goto exit; + } + rc = c->ipstack->mqttread(c->ipstack, &i, 1, timeout); + if (rc != 1) + goto exit; + *value += (i & 127) * multiplier; + multiplier *= 128; + } while ((i & 128) != 0); +exit: + return len; +} + + +static int readPacket(MQTTClient* c, Timer* timer) +{ + MQTTHeader header = {0}; + int len = 0; + int rem_len = 0; + + /* 1. read the header byte. This has the packet type in it */ + int rc = c->ipstack->mqttread(c->ipstack, c->readbuf, 1, TimerLeftMS(timer)); + if (rc != 1) + goto exit; + + len = 1; + /* 2. read the remaining length. This is variable in itself */ + decodePacket(c, &rem_len, TimerLeftMS(timer)); + len += MQTTPacket_encode(c->readbuf + 1, rem_len); /* put the original remaining length back into the buffer */ + + if (rem_len > (c->readbuf_size - len)) + { + rc = BUFFER_OVERFLOW; + goto exit; + } + + /* 3. read the rest of the buffer using a callback to supply the rest of the data */ + if (rem_len > 0 && (rc = c->ipstack->mqttread(c->ipstack, c->readbuf + len, rem_len, TimerLeftMS(timer)) != rem_len)) { + rc = 0; + goto exit; + } + + header.byte = c->readbuf[0]; + rc = header.bits.type; + if (c->keepAliveInterval > 0) + TimerCountdown(&c->last_received, c->keepAliveInterval); // record the fact that we have MQTT_SUCCESSfully received a packet +exit: + return rc; +} + + +// assume topic filter and name is in correct format +// # can only be at end +// + and # can only be next to separator +static char isTopicMatched(char* topicFilter, MQTTString* topicName) +{ + char* curf = topicFilter; + char* curn = topicName->lenstring.data; + char* curn_end = curn + topicName->lenstring.len; + + while (*curf && curn < curn_end) + { + if (*curn == '/' && *curf != '/') + break; + if (*curf != '+' && *curf != '#' && *curf != *curn) + break; + if (*curf == '+') + { // skip until we meet the next separator, or end of string + char* nextpos = curn + 1; + while (nextpos < curn_end && *nextpos != '/') + nextpos = ++curn + 1; + } + else if (*curf == '#') + curn = curn_end - 1; // skip until end of string + curf++; + curn++; + }; + + return (curn == curn_end) && (*curf == '\0'); +} + + +int deliverMessage(MQTTClient* c, MQTTString* topicName, MQTTMessage* message) +{ + int i; + int rc = FAILURE; + + // we have to find the right message handler - indexed by topic + for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) + { + if (c->messageHandlers[i].topicFilter != 0 && (MQTTPacket_equals(topicName, (char*)c->messageHandlers[i].topicFilter) || + isTopicMatched((char*)c->messageHandlers[i].topicFilter, topicName))) + { + if (c->messageHandlers[i].fp != NULL) + { + MessageData md; + NewMessageData(&md, topicName, message); + c->messageHandlers[i].fp(&md); + rc = MQTT_SUCCESS; + } + } + } + + if (rc == FAILURE && c->defaultMessageHandler != NULL) + { + MessageData md; + NewMessageData(&md, topicName, message); + c->defaultMessageHandler(&md); + rc = MQTT_SUCCESS; + } + + return rc; +} + + +int keepalive(MQTTClient* c) +{ + int rc = MQTT_SUCCESS; + + if (c->keepAliveInterval == 0) + goto exit; + + if (TimerIsExpired(&c->last_sent) || TimerIsExpired(&c->last_received)) + { + if (c->ping_outstanding) + rc = FAILURE; /* PINGRESP not received in keepalive interval */ + else + { + Timer timer; + TimerInit(&timer); + TimerCountdownMS(&timer, 1000); + int len = MQTTSerialize_pingreq(c->buf, c->buf_size); + if (len > 0 && (rc = sendPacket(c, len, &timer)) == MQTT_SUCCESS) // send the ping packet + c->ping_outstanding = 1; + } + } + +exit: + return rc; +} + + +void MQTTCleanSession(MQTTClient* c) +{ + int i = 0; + + for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) + c->messageHandlers[i].topicFilter = NULL; +} + + +void MQTTCloseSession(MQTTClient* c) +{ + c->ping_outstanding = 0; + c->isconnected = 0; + if (c->cleansession) + MQTTCleanSession(c); +} + + +int cycle(MQTTClient* c, Timer* timer) +{ + int len = 0, + rc = MQTT_SUCCESS; + + int packet_type = readPacket(c, timer); /* read the socket, see what work is due */ + + switch (packet_type) + { + default: + /* no more data to read, unrecoverable. Or read packet fails due to unexpected network error */ + rc = packet_type; + goto exit; + case 0: /* timed out reading packet */ + break; + case CONNACK: + case PUBACK: + case SUBACK: + case UNSUBACK: + break; + case PUBLISH: + { + MQTTString topicName; + MQTTMessage msg; + int intQoS; + msg.payloadlen = 0; /* this is a size_t, but deserialize publish sets this as int */ + if (MQTTDeserialize_publish(&msg.dup, &intQoS, &msg.retained, &msg.id, &topicName, + (unsigned char**)&msg.payload, (int*)&msg.payloadlen, c->readbuf, c->readbuf_size) != 1) + goto exit; + msg.qos = (enum QoS)intQoS; + deliverMessage(c, &topicName, &msg); + if (msg.qos != QOS0) + { + if (msg.qos == QOS1) + len = MQTTSerialize_ack(c->buf, c->buf_size, PUBACK, 0, msg.id); + else if (msg.qos == QOS2) + len = MQTTSerialize_ack(c->buf, c->buf_size, PUBREC, 0, msg.id); + if (len <= 0) + rc = FAILURE; + else + rc = sendPacket(c, len, timer); + if (rc == FAILURE) + goto exit; // there was a problem + } + break; + } + case PUBREC: + case PUBREL: + { + unsigned short mypacketid; + unsigned char dup, type; + if (MQTTDeserialize_ack(&type, &dup, &mypacketid, c->readbuf, c->readbuf_size) != 1) + rc = FAILURE; + else if ((len = MQTTSerialize_ack(c->buf, c->buf_size, + (packet_type == PUBREC) ? PUBREL : PUBCOMP, 0, mypacketid)) <= 0) + rc = FAILURE; + else if ((rc = sendPacket(c, len, timer)) != MQTT_SUCCESS) // send the PUBREL packet + rc = FAILURE; // there was a problem + if (rc == FAILURE) + goto exit; // there was a problem + break; + } + + case PUBCOMP: + break; + case PINGRESP: + c->ping_outstanding = 0; + break; + } + + if (keepalive(c) != MQTT_SUCCESS) { + //check only keepalive FAILURE status so that previous FAILURE status can be considered as FAULT + rc = FAILURE; + } + +exit: + if (rc == MQTT_SUCCESS) + rc = packet_type; + else if (c->isconnected) + MQTTCloseSession(c); + return rc; +} + + +int MQTTYield(MQTTClient* c, int timeout_ms) +{ + int rc = MQTT_SUCCESS; + Timer timer; + + TimerInit(&timer); + TimerCountdownMS(&timer, timeout_ms); + + do + { + if (cycle(c, &timer) < 0) + { + rc = FAILURE; + break; + } + } while (!TimerIsExpired(&timer)); + + return rc; +} + +int MQTTIsConnected(MQTTClient* client) +{ + return client->isconnected; +} + +void MQTTRun(void* parm) +{ + Timer timer; + MQTTClient* c = (MQTTClient*)parm; + + TimerInit(&timer); + + while (1) + { +#if defined(MQTT_TASK) + MutexLock(&c->mutex); +#endif + TimerCountdownMS(&timer, 500); /* Don't wait too long if no traffic is incoming */ + cycle(c, &timer); +#if defined(MQTT_TASK) + MutexUnlock(&c->mutex); +#endif + } +} + + +#if defined(MQTT_TASK) +int MQTTStartTask(MQTTClient* client) +{ + return ThreadStart(&client->thread, &MQTTRun, client); +} +#endif + + +int waitfor(MQTTClient* c, int packet_type, Timer* timer) +{ + int rc = FAILURE; + + do + { + if (TimerIsExpired(timer)) + break; // we timed out + rc = cycle(c, timer); + } + while (rc != packet_type && rc >= 0); + + return rc; +} + + + + +int MQTTConnectWithResults(MQTTClient* c, MQTTPacket_connectData* options, MQTTConnackData* data) +{ + Timer connect_timer; + int rc = FAILURE; + MQTTPacket_connectData default_options = MQTTPacket_connectData_initializer; + int len = 0; + +#if defined(MQTT_TASK) + MutexLock(&c->mutex); +#endif + if (c->isconnected) /* don't send connect packet again if we are already connected */ + goto exit; + + TimerInit(&connect_timer); + TimerCountdownMS(&connect_timer, c->command_timeout_ms); + + if (options == 0) + options = &default_options; /* set default options if none were supplied */ + + c->keepAliveInterval = options->keepAliveInterval; + c->cleansession = options->cleansession; + TimerCountdown(&c->last_received, c->keepAliveInterval); + if ((len = MQTTSerialize_connect(c->buf, c->buf_size, options)) <= 0) + goto exit; + if ((rc = sendPacket(c, len, &connect_timer)) != MQTT_SUCCESS) // send the connect packet + goto exit; // there was a problem + + // this will be a blocking call, wait for the connack + if (waitfor(c, CONNACK, &connect_timer) == CONNACK) + { + data->rc = 0; + data->sessionPresent = 0; + if (MQTTDeserialize_connack(&data->sessionPresent, &data->rc, c->readbuf, c->readbuf_size) == 1) + rc = data->rc; + else + rc = FAILURE; + } + else + rc = FAILURE; + +exit: + if (rc == MQTT_SUCCESS) + { + c->isconnected = 1; + c->ping_outstanding = 0; + } + +#if defined(MQTT_TASK) + MutexUnlock(&c->mutex); +#endif + + return rc; +} + + +int MQTTConnect(MQTTClient* c, MQTTPacket_connectData* options) +{ + MQTTConnackData data; + return MQTTConnectWithResults(c, options, &data); +} + + +int MQTTSetMessageHandler(MQTTClient* c, const char* topicFilter, messageHandler messageHandler) +{ + int rc = FAILURE; + int i = -1; + + /* first check for an existing matching slot */ + for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) + { + if (c->messageHandlers[i].topicFilter != NULL && strcmp(c->messageHandlers[i].topicFilter, topicFilter) == 0) + { + if (messageHandler == NULL) /* remove existing */ + { + c->messageHandlers[i].topicFilter = NULL; + c->messageHandlers[i].fp = NULL; + } + rc = MQTT_SUCCESS; /* return i when adding new subscription */ + break; + } + } + /* if no existing, look for empty slot (unless we are removing) */ + if (messageHandler != NULL) { + if (rc == FAILURE) + { + for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) + { + if (c->messageHandlers[i].topicFilter == NULL) + { + rc = MQTT_SUCCESS; + break; + } + } + } + if (i < MAX_MESSAGE_HANDLERS) + { + c->messageHandlers[i].topicFilter = topicFilter; + c->messageHandlers[i].fp = messageHandler; + } + } + return rc; +} + + +int MQTTSubscribeWithResults(MQTTClient* c, const char* topicFilter, enum QoS qos, + messageHandler messageHandler, MQTTSubackData* data) +{ + int rc = FAILURE; + Timer timer; + int len = 0; + MQTTString topic = MQTTString_initializer; + topic.cstring = (char *)topicFilter; + +#if defined(MQTT_TASK) + MutexLock(&c->mutex); +#endif + if (!c->isconnected) + goto exit; + + TimerInit(&timer); + TimerCountdownMS(&timer, c->command_timeout_ms); + + len = MQTTSerialize_subscribe(c->buf, c->buf_size, 0, getNextPacketId(c), 1, &topic, (int*)&qos); + if (len <= 0) + goto exit; + if ((rc = sendPacket(c, len, &timer)) != MQTT_SUCCESS) // send the subscribe packet + goto exit; // there was a problem + + if (waitfor(c, SUBACK, &timer) == SUBACK) // wait for suback + { + int count = 0; + unsigned short mypacketid; + data->grantedQoS = QOS0; + if (MQTTDeserialize_suback(&mypacketid, 1, &count, (int*)&data->grantedQoS, c->readbuf, c->readbuf_size) == 1) + { + if (data->grantedQoS != 0x80) + rc = MQTTSetMessageHandler(c, topicFilter, messageHandler); + } + } + else + rc = FAILURE; + +exit: + if (rc == FAILURE) + MQTTCloseSession(c); +#if defined(MQTT_TASK) + MutexUnlock(&c->mutex); +#endif + return rc; +} + + +int MQTTSubscribe(MQTTClient* c, const char* topicFilter, enum QoS qos, + messageHandler messageHandler) +{ + MQTTSubackData data; + return MQTTSubscribeWithResults(c, topicFilter, qos, messageHandler, &data); +} + + +int MQTTUnsubscribe(MQTTClient* c, const char* topicFilter) +{ + int rc = FAILURE; + Timer timer; + MQTTString topic = MQTTString_initializer; + topic.cstring = (char *)topicFilter; + int len = 0; + +#if defined(MQTT_TASK) + MutexLock(&c->mutex); +#endif + if (!c->isconnected) + goto exit; + + TimerInit(&timer); + TimerCountdownMS(&timer, c->command_timeout_ms); + + if ((len = MQTTSerialize_unsubscribe(c->buf, c->buf_size, 0, getNextPacketId(c), 1, &topic)) <= 0) + goto exit; + if ((rc = sendPacket(c, len, &timer)) != MQTT_SUCCESS) // send the subscribe packet + goto exit; // there was a problem + + if (waitfor(c, UNSUBACK, &timer) == UNSUBACK) + { + unsigned short mypacketid; // should be the same as the packetid above + if (MQTTDeserialize_unsuback(&mypacketid, c->readbuf, c->readbuf_size) == 1) + { + /* remove the subscription message handler associated with this topic, if there is one */ + MQTTSetMessageHandler(c, topicFilter, NULL); + } + } + else + rc = FAILURE; + +exit: + if (rc == FAILURE) + MQTTCloseSession(c); +#if defined(MQTT_TASK) + MutexUnlock(&c->mutex); +#endif + return rc; +} + + +int MQTTPublish(MQTTClient* c, const char* topicName, MQTTMessage* message) +{ + int rc = FAILURE; + Timer timer; + MQTTString topic = MQTTString_initializer; + topic.cstring = (char *)topicName; + int len = 0; + +#if defined(MQTT_TASK) + MutexLock(&c->mutex); +#endif + if (!c->isconnected) + goto exit; + + TimerInit(&timer); + TimerCountdownMS(&timer, c->command_timeout_ms); + + if (message->qos == QOS1 || message->qos == QOS2) + message->id = getNextPacketId(c); + + len = MQTTSerialize_publish(c->buf, c->buf_size, 0, message->qos, message->retained, message->id, + topic, (unsigned char*)message->payload, message->payloadlen); + if (len <= 0) + goto exit; + if ((rc = sendPacket(c, len, &timer)) != MQTT_SUCCESS) // send the subscribe packet + goto exit; // there was a problem + + if (message->qos == QOS1) + { + if (waitfor(c, PUBACK, &timer) == PUBACK) + { + unsigned short mypacketid; + unsigned char dup, type; + if (MQTTDeserialize_ack(&type, &dup, &mypacketid, c->readbuf, c->readbuf_size) != 1) + rc = FAILURE; + } + else + rc = FAILURE; + } + else if (message->qos == QOS2) + { + if (waitfor(c, PUBCOMP, &timer) == PUBCOMP) + { + unsigned short mypacketid; + unsigned char dup, type; + if (MQTTDeserialize_ack(&type, &dup, &mypacketid, c->readbuf, c->readbuf_size) != 1) + rc = FAILURE; + } + else + rc = FAILURE; + } + +exit: + if (rc == FAILURE) + MQTTCloseSession(c); +#if defined(MQTT_TASK) + MutexUnlock(&c->mutex); +#endif + return rc; +} + + +int MQTTDisconnect(MQTTClient* c) +{ + int rc = FAILURE; + Timer timer; // we might wait for incomplete incoming publishes to complete + int len = 0; + +#if defined(MQTT_TASK) + MutexLock(&c->mutex); +#endif + TimerInit(&timer); + TimerCountdownMS(&timer, c->command_timeout_ms); + + len = MQTTSerialize_disconnect(c->buf, c->buf_size); + if (len > 0) + rc = sendPacket(c, len, &timer); // send the disconnect packet + MQTTCloseSession(c); + +#if defined(MQTT_TASK) + MutexUnlock(&c->mutex); +#endif + return rc; +} diff --git a/mqtt/MQTTClient.h b/mqtt/MQTTClient.h new file mode 100644 index 0000000..8a42ef1 --- /dev/null +++ b/mqtt/MQTTClient.h @@ -0,0 +1,239 @@ +/******************************************************************************* + * Copyright (c) 2014, 2017 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - documentation and platform specific header + * Ian Craggs - add setMessageHandler function + *******************************************************************************/ + +#if !defined(MQTT_CLIENT_H) +#define MQTT_CLIENT_H + +#if defined(__cplusplus) + extern "C" { +#endif + +#if defined(WIN32_DLL) || defined(WIN64_DLL) + #define DLLImport __declspec(dllimport) + #define DLLExport __declspec(dllexport) +#elif defined(LINUX_SO) + #define DLLImport extern + #define DLLExport __attribute__ ((visibility ("default"))) +#else + #define DLLImport + #define DLLExport +#endif + + +//#define MQTT_TASK 1 + + +#include +#include + +#if defined(MQTTCLIENT_PLATFORM_HEADER) +/* The following sequence of macros converts the MQTTCLIENT_PLATFORM_HEADER value + * into a string constant suitable for use with include. + */ +#define xstr(s) str(s) +#define str(s) #s +#include xstr(MQTTCLIENT_PLATFORM_HEADER) +#endif + +#define MAX_PACKET_ID 65535 /* according to the MQTT specification - do not change! */ + +#if !defined(MAX_MESSAGE_HANDLERS) +#define MAX_MESSAGE_HANDLERS 5 /* redefinable - how many subscriptions do you want? */ +#endif + +enum QoS { QOS0, QOS1, QOS2, SUBFAIL=0x80 }; + +/* all failure return codes must be negative */ +enum returnCode { BUFFER_OVERFLOW = -2, FAILURE = -1, MQTT_SUCCESS = 0 }; + +/* The Platform specific header must define the Network and Timer structures and functions + * which operate on them. + * +typedef struct Network +{ + int (*mqttread)(Network*, unsigned char* read_buffer, int, int); + int (*mqttwrite)(Network*, unsigned char* send_buffer, int, int); +} Network;*/ + +/* The Timer structure must be defined in the platform specific header, + * and have the following functions to operate on it. */ +extern void TimerInit(Timer*); +extern char TimerIsExpired(Timer*); +extern void TimerCountdownMS(Timer*, unsigned int); +extern void TimerCountdown(Timer*, unsigned int); +extern int TimerLeftMS(Timer*); + +typedef struct MQTTMessage +{ + enum QoS qos; + unsigned char retained; + unsigned char dup; + unsigned short id; + void *payload; + size_t payloadlen; +} MQTTMessage; + +typedef struct MessageData +{ + MQTTMessage* message; + MQTTString* topicName; +} MessageData; + +typedef struct MQTTConnackData +{ + unsigned char rc; + unsigned char sessionPresent; +} MQTTConnackData; + +typedef struct MQTTSubackData +{ + enum QoS grantedQoS; +} MQTTSubackData; + +typedef void (*messageHandler)(MessageData*); + +typedef struct MQTTClient +{ + unsigned int next_packetid, + command_timeout_ms; + size_t buf_size, + readbuf_size; + unsigned char *buf, + *readbuf; + unsigned int keepAliveInterval; + char ping_outstanding; + int isconnected; + int cleansession; + + struct MessageHandlers + { + const char* topicFilter; + void (*fp) (MessageData*); + } messageHandlers[MAX_MESSAGE_HANDLERS]; /* Message handlers are indexed by subscription topic */ + + void (*defaultMessageHandler) (MessageData*); + + Network* ipstack; + Timer last_sent, last_received; +#if defined(MQTT_TASK) + Mutex mutex; + Thread thread; +#endif +} MQTTClient; + +#define DefaultClient {0, 0, 0, 0, NULL, NULL, 0, 0, 0} + +void MQTTCloseSession(MQTTClient* c); + +/** + * Create an MQTT client object + * @param client + * @param network + * @param command_timeout_ms + * @param + */ +DLLExport void MQTTClientInit(MQTTClient* client, Network* network, unsigned int command_timeout_ms, + unsigned char* sendbuf, size_t sendbuf_size, unsigned char* readbuf, size_t readbuf_size); + +/** MQTT Connect - send an MQTT connect packet down the network and wait for a Connack + * The nework object must be connected to the network endpoint before calling this + * @param options - connect options + * @return success code + */ +DLLExport int MQTTConnectWithResults(MQTTClient* client, MQTTPacket_connectData* options, + MQTTConnackData* data); + +/** MQTT Connect - send an MQTT connect packet down the network and wait for a Connack + * The nework object must be connected to the network endpoint before calling this + * @param options - connect options + * @return success code + */ +DLLExport int MQTTConnect(MQTTClient* client, MQTTPacket_connectData* options); + +/** MQTT Publish - send an MQTT publish packet and wait for all acks to complete for all QoSs + * @param client - the client object to use + * @param topic - the topic to publish to + * @param message - the message to send + * @return success code + */ +DLLExport int MQTTPublish(MQTTClient* client, const char*, MQTTMessage*); + +/** MQTT SetMessageHandler - set or remove a per topic message handler + * @param client - the client object to use + * @param topicFilter - the topic filter set the message handler for + * @param messageHandler - pointer to the message handler function or NULL to remove + * @return success code + */ +DLLExport int MQTTSetMessageHandler(MQTTClient* c, const char* topicFilter, messageHandler messageHandler); + +/** MQTT Subscribe - send an MQTT subscribe packet and wait for suback before returning. + * @param client - the client object to use + * @param topicFilter - the topic filter to subscribe to + * @param message - the message to send + * @return success code + */ +DLLExport int MQTTSubscribe(MQTTClient* client, const char* topicFilter, enum QoS, messageHandler); + +/** MQTT Subscribe - send an MQTT subscribe packet and wait for suback before returning. + * @param client - the client object to use + * @param topicFilter - the topic filter to subscribe to + * @param message - the message to send + * @param data - suback granted QoS returned + * @return success code + */ +DLLExport int MQTTSubscribeWithResults(MQTTClient* client, const char* topicFilter, enum QoS, messageHandler, MQTTSubackData* data); + +/** MQTT Subscribe - send an MQTT unsubscribe packet and wait for unsuback before returning. + * @param client - the client object to use + * @param topicFilter - the topic filter to unsubscribe from + * @return success code + */ +DLLExport int MQTTUnsubscribe(MQTTClient* client, const char* topicFilter); + +/** MQTT Disconnect - send an MQTT disconnect packet and close the connection + * @param client - the client object to use + * @return success code + */ +DLLExport int MQTTDisconnect(MQTTClient* client); + +/** MQTT Yield - MQTT background + * @param client - the client object to use + * @param time - the time, in milliseconds, to yield for + * @return success code + */ +DLLExport int MQTTYield(MQTTClient* client, int time); + +/** MQTT isConnected + * @param client - the client object to use + * @return truth value indicating whether the client is connected to the server + */ +DLLExport int MQTTIsConnected(MQTTClient* client); + +#if defined(MQTT_TASK) +/** MQTT start background thread for a client. After this, MQTTYield should not be called. +* @param client - the client object to use +* @return success code +*/ +DLLExport int MQTTStartTask(MQTTClient* client); +#endif + +#if defined(__cplusplus) + } +#endif + +#endif diff --git a/mqtt/MQTTInterface.c b/mqtt/MQTTInterface.c new file mode 100644 index 0000000..93dfbe0 --- /dev/null +++ b/mqtt/MQTTInterface.c @@ -0,0 +1,340 @@ +#include "MQTTInterface.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Specifica il percorso al certificato CA (adatta in base alla tua situazione) +// #define CA_CERT_FILE "./ca.crt" // Percorso relativo +// #define CA_CERT_FILE "/etc/ssl/certs/ca-certificates.crt" // Percorso assoluto +// #define CA_CERT_FILE getenv("CA_CERT_PATH") // Variabile d'ambiente + +#define CA_CERT_FILE "cert/ca.crt" // Percorso al certificato CA del server +#define CLIENT_CERT_FILE "cert/client.crt" // Percorso al certificato del client +#define CLIENT_KEY_FILE "cert/client.key" // Percorso alla chiave privata del client + +int sockfd, ret; +struct sockaddr_in server_addr; // Nome variabile più descrittivo +struct hostent *server; +SSL_CTX *ctx; +SSL *ssl; +BIO *bio_err; + +void mqtt_network_init(Network *n) +{ + n->socket = 0; // clear + n->mqttread = mqtt_network_read; // receive function + n->mqttwrite = mqtt_network_write; // send function + n->disconnect = mqtt_network_disconnect; // disconnection function +} + +int mqtt_network_connect(Network *n, char *ip, char *port) +{ + mqtt_network_init(n); + // mqtt_network_clear(); + + // Inizializza OpenSSL + SSL_library_init(); + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + + // Inizializza bio_err + bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); + + // Crea il contesto SSL + ctx = SSL_CTX_new(TLS_client_method()); + if (!ctx) + { + fprintf(stderr, "Errore nella creazione del contesto SSL\n"); + ERR_print_errors_fp(stderr); + return 1; + } + + // Carica il certificato CA del server + if (SSL_CTX_load_verify_locations(ctx, CA_CERT_FILE, NULL) != 1) + { + fprintf(stderr, "Errore nel caricamento del certificato CA\n"); + ERR_print_errors_fp(stderr); + return 1; + } + + // Carica il certificato e la chiave privata del client + if (SSL_CTX_use_certificate_file(ctx, CLIENT_CERT_FILE, SSL_FILETYPE_PEM) != 1) + { + fprintf(stderr, "Errore nel caricamento del certificato client\n"); + ERR_print_errors_fp(stderr); + return 1; + } + + if (SSL_CTX_use_PrivateKey_file(ctx, CLIENT_KEY_FILE, SSL_FILETYPE_PEM) != 1) + { + fprintf(stderr, "Errore nel caricamento della chiave privata client\n"); + ERR_print_errors_fp(stderr); + return 1; + } + + // Verifica che la chiave privata corrisponda al certificato + if (SSL_CTX_check_private_key(ctx) != 1) + { + fprintf(stderr, "Errore: la chiave privata non corrisponde al certificato\n"); + ERR_print_errors_fp(stderr); + return 1; + } + + // Carica i certificati di root CA di sistema (opzionale ma consigliato) + if (SSL_CTX_set_default_verify_paths(ctx) != 1) + { + fprintf(stderr, "Avviso: errore nel caricamento dei certificati CA di sistema (potrebbe non essere un problema se stai utilizzando un certificato CA personalizzato)\n"); + ERR_print_errors_fp(stderr); + // Non terminare il programma, poiché potresti comunque essere in grado di verificare il certificato utilizzando il certificato CA specifico + } + + // Crea il socket TCP + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + { + perror("Errore nella creazione del socket"); + return 1; + } + + // Risolve l'hostname + server = gethostbyname(ip); + if (server == NULL) + { + fprintf(stderr, "Errore nella risoluzione dell'host %s\n", ip); + return 1; + } + + // Imposta l'indirizzo del server + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + memcpy(&server_addr.sin_addr.s_addr, server->h_addr, server->h_length); + int int_port = atoi(port); + server_addr.sin_port = htons(int_port); + + // Connetti al server + if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) + { + perror("Errore nella connessione"); + return 1; + } + + // Crea la struttura SSL e associa il socket + ssl = SSL_new(ctx); + if (!ssl) + { + fprintf(stderr, "Errore nella creazione della struttura SSL\n"); + ERR_print_errors_fp(stderr); + return 1; + } + SSL_set_fd(ssl, sockfd); + + // Abilita i messaggi di debug + // SSL_set_info_callback(ssl, apps_ssl_info_callback); + + // Effettua l'handshake SSL + ret = SSL_connect(ssl); + if (ret != 1) + { + fprintf(stderr, "Errore nell'handshake SSL\n"); + ERR_print_errors_fp(stderr); + return 1; + } + + // Verifica il certificato del server + if (SSL_get_verify_result(ssl) != X509_V_OK) + { + fprintf(stderr, "Errore nella verifica del certificato del server\n"); + ERR_print_errors_fp(stderr); // Stampa gli errori di OpenSSL per ottenere maggiori dettagli + return 1; + } + + return 0; +} + +int mqtt_network_read(Network *n, unsigned char *buffer, int len, int timeout_ms) +{ + int read = 0; + int total_read = 0; + + while (total_read < len) + { + read = SSL_read(ssl, buffer + total_read, len - total_read); + if (read <= 0) + { + int err = SSL_get_error(ssl, read); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + { + // La lettura è stata bloccata, riprova più tardi + continue; + } + else + { + fprintf(stderr, "Errore nella lettura dei dati dal server: %s\n", ERR_error_string(ERR_get_error(), NULL)); + return -1; // Indica un errore + } + } + total_read += read; + } + + return total_read; // Restituisce il numero totale di byte letti +} + +int mqtt_network_read_(Network *n, unsigned char *buffer, int len, int timeout_ms) +{ + int read = 0; + int total_read = 0; + struct timeval tv; + fd_set readfds; + + (void)n; + + while (total_read < len) + { + // Imposta il timeout + tv.tv_sec = timeout_ms / 1000; // Secondi + tv.tv_usec = (timeout_ms % 1000) * 1000; // Microsecondi + + // Inizializza il file descriptor set + FD_ZERO(&readfds); + FD_SET(n->socket, &readfds); + + // Attendi che il socket sia pronto per la lettura o che scada il timeout + int rc = select(n->socket + 1, &readfds, NULL, NULL, &tv); + + if (rc < 0) + { + // Errore in select + perror("Error in select"); + return -1; + } + else if (rc == 0) + { + // Timeout scaduto + return 0; // Indica un timeout + } + else + { + // Socket pronto per la lettura + read = SSL_read(ssl, buffer + total_read, len - total_read); // Assumiamo che n->ssl sia il puntatore all'oggetto SSL + if (read <= 0) + { + int err = SSL_get_error(ssl, read); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + { + // La lettura è stata bloccata, riprova più tardi + continue; + } + else + { + fprintf(stderr, "Errore nella lettura dei dati dal server: %s\n", ERR_error_string(ERR_get_error(), NULL)); + return -1; // Indica un errore + } + } + total_read += read; + } + } + + return total_read; // Restituisce il numero totale di byte letti +} + +int mqtt_network_write(Network *n, unsigned char *buffer, int len, int timeout_ms) +{ + int written = 0; + int total_written = 0; + + (void)n; + (void)timeout_ms; + + while (total_written < len) + { + written = SSL_write(ssl, buffer + total_written, len - total_written); + if (written <= 0) + { + int err = SSL_get_error(ssl, written); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + { + // La scrittura è stata bloccata, riprova più tardi + continue; + } + else + { + fprintf(stderr, "Errore nella scrittura dei dati al server: %s\n", ERR_error_string(ERR_get_error(), NULL)); + return -1; // Indica un errore + } + } + total_written += written; + } + + return total_written; // Restituisce il numero totale di byte scritti +} + +void mqtt_network_disconnect(Network *n) +{ + (void)n; + + // Chiudi la connessione SSL e il socket + SSL_shutdown(ssl); + SSL_free(ssl); + close(sockfd); + // Libera bio_err alla fine del programma + BIO_free(bio_err); + SSL_CTX_free(ctx); +} + +void mqtt_network_clear() +{ + // Chiudi la connessione SSL e il socket + // SSL_shutdown(ssl); + SSL_free(ssl); + close(sockfd); + // Libera bio_err alla fine del programma + BIO_free(bio_err); + SSL_CTX_free(ctx); +} + +// Timer functions +void TimerInit(Timer *timer) +{ + timer->end_time = (struct timeval){0, 0}; +} + +char TimerIsExpired(Timer *timer) +{ + struct timeval now, res; + gettimeofday(&now, NULL); + timersub(&timer->end_time, &now, &res); + return res.tv_sec < 0 || (res.tv_sec == 0 && res.tv_usec <= 0); +} + +void TimerCountdownMS(Timer *timer, unsigned int timeout) +{ + struct timeval now; + gettimeofday(&now, NULL); + struct timeval interval = {timeout / 1000, (timeout % 1000) * 1000}; + timeradd(&now, &interval, &timer->end_time); +} + +void TimerCountdown(Timer *timer, unsigned int timeout) +{ + struct timeval now; + gettimeofday(&now, NULL); + struct timeval interval = {timeout, 0}; + timeradd(&now, &interval, &timer->end_time); +} + +int TimerLeftMS(Timer *timer) +{ + struct timeval now, res; + gettimeofday(&now, NULL); + timersub(&timer->end_time, &now, &res); + // printf("left %d ms\n", (res.tv_sec < 0) ? 0 : res.tv_sec * 1000 + res.tv_usec / 1000); + return (res.tv_sec < 0) ? 0 : res.tv_sec * 1000 + res.tv_usec / 1000; +} diff --git a/mqtt/MQTTInterface.h b/mqtt/MQTTInterface.h new file mode 100644 index 0000000..8d3ab25 --- /dev/null +++ b/mqtt/MQTTInterface.h @@ -0,0 +1,48 @@ +#ifndef __MQTT_INTERFACE_H__ +#define __MQTT_INTERFACE_H__ +/* Private includes ----------------------------------------------------------*/ +#include + +/* Private define ------------------------------------------------------------*/ +/* CONFIG */ + +#if defined MQTT_INTERFACE_DEBUG +#define MQTT_INTERFACE_DEBUG_LOG(message, ...) DEBUG_LOG(message, ##__VA_ARGS__) +#else +#define MQTT_INTERFACE_DEBUG_LOG(message, ...) +#endif + +/* Typedef -----------------------------------------------------------*/ +typedef struct +{ + struct timeval end_time; +} Timer; + +typedef struct Network Network; + +struct Network +{ + int socket; + int (*mqttread)(Network *, unsigned char *, int, int); + int (*mqttwrite)(Network *, unsigned char *, int, int); + void (*disconnect)(Network *); +}; + +/* Extern variables ----------------------------------------------------------*/ + +/* Private function prototypes -----------------------------------------------*/ + +void TimerInit(Timer *); +char TimerIsExpired(Timer *); +void TimerCountdownMS(Timer *, unsigned int); +void TimerCountdown(Timer *, unsigned int); +int TimerLeftMS(Timer *); + +int mqtt_network_read(Network *, unsigned char *, int, int); +int mqtt_network_write(Network *, unsigned char *, int, int); +void mqtt_network_disconnect(Network *); +void mqtt_network_init(Network *); +void mqtt_network_clear(void); +int mqtt_network_connect(Network *, char *, char *); + +#endif diff --git a/mqtt/MQTTPacket/MQTTConnect.h b/mqtt/MQTTPacket/MQTTConnect.h new file mode 100644 index 0000000..98c2c16 --- /dev/null +++ b/mqtt/MQTTPacket/MQTTConnect.h @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2014, 2017 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - add connack return code definitions + * Xiang Rong - 442039 Add makefile to Embedded C client + * Ian Craggs - fix for issue #64, bit order in connack response + *******************************************************************************/ + +#ifndef MQTTCONNECT_H_ +#define MQTTCONNECT_H_ + +enum connack_return_codes +{ + MQTT_CONNECTION_ACCEPTED = 0, + MQTT_UNNACCEPTABLE_PROTOCOL = 1, + MQTT_CLIENTID_REJECTED = 2, + MQTT_SERVER_UNAVAILABLE = 3, + MQTT_BAD_USERNAME_OR_PASSWORD = 4, + MQTT_NOT_AUTHORIZED = 5, +}; + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + + +typedef union +{ + unsigned char all; /**< all connect flags */ +#if defined(REVERSED) + struct + { + unsigned int username : 1; /**< 3.1 user name */ + unsigned int password : 1; /**< 3.1 password */ + unsigned int willRetain : 1; /**< will retain setting */ + unsigned int willQoS : 2; /**< will QoS value */ + unsigned int will : 1; /**< will flag */ + unsigned int cleansession : 1; /**< clean session flag */ + unsigned int : 1; /**< unused */ + } bits; +#else + struct + { + unsigned int : 1; /**< unused */ + unsigned int cleansession : 1; /**< cleansession flag */ + unsigned int will : 1; /**< will flag */ + unsigned int willQoS : 2; /**< will QoS value */ + unsigned int willRetain : 1; /**< will retain setting */ + unsigned int password : 1; /**< 3.1 password */ + unsigned int username : 1; /**< 3.1 user name */ + } bits; +#endif +} MQTTConnectFlags; /**< connect flags byte */ + + + +/** + * Defines the MQTT "Last Will and Testament" (LWT) settings for + * the connect packet. + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTW. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** The LWT topic to which the LWT message will be published. */ + MQTTString topicName; + /** The LWT payload. */ + MQTTString message; + /** + * The retained flag for the LWT message (see MQTTAsync_message.retained). + */ + unsigned char retained; + /** + * The quality of service setting for the LWT message (see + * MQTTAsync_message.qos and @ref qos). + */ + char qos; +} MQTTPacket_willOptions; + + +#define MQTTPacket_willOptions_initializer { {'M', 'Q', 'T', 'W'}, 0, {NULL, {0, NULL}}, {NULL, {0, NULL}}, 0, 0 } + + +typedef struct +{ + /** The eyecatcher for this structure. must be MQTC. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** Version of MQTT to be used. 3 = 3.1 4 = 3.1.1 + */ + unsigned char MQTTVersion; + MQTTString clientID; + unsigned short keepAliveInterval; + unsigned char cleansession; + unsigned char willFlag; + MQTTPacket_willOptions will; + MQTTString username; + MQTTString password; +} MQTTPacket_connectData; + +typedef union +{ + unsigned char all; /**< all connack flags */ +#if defined(REVERSED) + struct + { + unsigned int reserved : 7; /**< unused */ + unsigned int sessionpresent : 1; /**< session present flag */ + } bits; +#else + struct + { + unsigned int sessionpresent : 1; /**< session present flag */ + unsigned int reserved: 7; /**< unused */ + } bits; +#endif +} MQTTConnackFlags; /**< connack flags byte */ + +#define MQTTPacket_connectData_initializer { {'M', 'Q', 'T', 'C'}, 0, 4, {NULL, {0, NULL}}, 60, 1, 0, \ + MQTTPacket_willOptions_initializer, {NULL, {0, NULL}}, {NULL, {0, NULL}} } + +DLLExport int MQTTSerialize_connect(unsigned char* buf, int buflen, MQTTPacket_connectData* options); +DLLExport int MQTTDeserialize_connect(MQTTPacket_connectData* data, unsigned char* buf, int len); + +DLLExport int MQTTSerialize_connack(unsigned char* buf, int buflen, unsigned char connack_rc, unsigned char sessionPresent); +DLLExport int MQTTDeserialize_connack(unsigned char* sessionPresent, unsigned char* connack_rc, unsigned char* buf, int buflen); + +DLLExport int MQTTSerialize_disconnect(unsigned char* buf, int buflen); +DLLExport int MQTTSerialize_pingreq(unsigned char* buf, int buflen); + +#endif /* MQTTCONNECT_H_ */ diff --git a/mqtt/MQTTPacket/MQTTConnectClient.c b/mqtt/MQTTPacket/MQTTConnectClient.c new file mode 100644 index 0000000..9284410 --- /dev/null +++ b/mqtt/MQTTPacket/MQTTConnectClient.c @@ -0,0 +1,213 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include +#include +#include + +/** + * Determines the length of the MQTT connect packet that would be produced using the supplied connect options. + * @param options the options to be used to build the connect packet + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_connectLength(MQTTPacket_connectData* options) +{ + int len = 0; + + FUNC_ENTRY; + + if (options->MQTTVersion == 3) + len = 12; /* variable depending on MQTT or MQIsdp */ + else if (options->MQTTVersion == 4) + len = 10; + + len += MQTTstrlen(options->clientID)+2; + if (options->willFlag) + len += MQTTstrlen(options->will.topicName)+2 + MQTTstrlen(options->will.message)+2; + if (options->username.cstring || options->username.lenstring.data) + len += MQTTstrlen(options->username)+2; + if (options->password.cstring || options->password.lenstring.data) + len += MQTTstrlen(options->password)+2; + + FUNC_EXIT_RC(len); + return len; +} + + +/** + * Serializes the connect options into the buffer. + * @param buf the buffer into which the packet will be serialized + * @param len the length in bytes of the supplied buffer + * @param options the options to be used to build the connect packet + * @return serialized length, or error if 0 + */ +int MQTTSerialize_connect(unsigned char* buf, int buflen, MQTTPacket_connectData* options) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + MQTTConnectFlags flags = {0}; + int len = 0; + int rc = -1; + + FUNC_ENTRY; + if (MQTTPacket_len(len = MQTTSerialize_connectLength(options)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.byte = 0; + header.bits.type = CONNECT; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, len); /* write remaining length */ + + if (options->MQTTVersion == 4) + { + writeCString(&ptr, "MQTT"); + writeChar(&ptr, (char) 4); + } + else + { + writeCString(&ptr, "MQIsdp"); + writeChar(&ptr, (char) 3); + } + + flags.all = 0; + flags.bits.cleansession = options->cleansession; + flags.bits.will = (options->willFlag) ? 1 : 0; + if (flags.bits.will) + { + flags.bits.willQoS = options->will.qos; + flags.bits.willRetain = options->will.retained; + } + + if (options->username.cstring || options->username.lenstring.data) + flags.bits.username = 1; + if (options->password.cstring || options->password.lenstring.data) + flags.bits.password = 1; + + writeChar(&ptr, flags.all); + writeInt(&ptr, options->keepAliveInterval); + writeMQTTString(&ptr, options->clientID); + if (options->willFlag) + { + writeMQTTString(&ptr, options->will.topicName); + writeMQTTString(&ptr, options->will.message); + } + if (flags.bits.username) + writeMQTTString(&ptr, options->username); + if (flags.bits.password) + writeMQTTString(&ptr, options->password); + + rc = ptr - buf; + + exit: FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Deserializes the supplied (wire) buffer into connack data - return code + * @param sessionPresent the session present flag returned (only for MQTT 3.1.1) + * @param connack_rc returned integer value of the connack return code + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param len the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_connack(unsigned char* sessionPresent, unsigned char* connack_rc, unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen; + MQTTConnackFlags flags = {0}; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != CONNACK) + goto exit; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + if (enddata - curdata < 2) + goto exit; + + flags.all = readChar(&curdata); + *sessionPresent = flags.bits.sessionpresent; + *connack_rc = readChar(&curdata); + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes a 0-length packet into the supplied buffer, ready for writing to a socket + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer, to avoid overruns + * @param packettype the message type + * @return serialized length, or error if 0 + */ +int MQTTSerialize_zero(unsigned char* buf, int buflen, unsigned char packettype) +{ + MQTTHeader header = {0}; + int rc = -1; + unsigned char *ptr = buf; + + FUNC_ENTRY; + if (buflen < 2) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.byte = 0; + header.bits.type = packettype; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 0); /* write remaining length */ + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes a disconnect packet into the supplied buffer, ready for writing to a socket + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer, to avoid overruns + * @return serialized length, or error if 0 + */ +int MQTTSerialize_disconnect(unsigned char* buf, int buflen) +{ + return MQTTSerialize_zero(buf, buflen, DISCONNECT); +} + + +/** + * Serializes a disconnect packet into the supplied buffer, ready for writing to a socket + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer, to avoid overruns + * @return serialized length, or error if 0 + */ +int MQTTSerialize_pingreq(unsigned char* buf, int buflen) +{ + return MQTTSerialize_zero(buf, buflen, PINGREQ); +} diff --git a/mqtt/MQTTPacket/MQTTConnectServer.c b/mqtt/MQTTPacket/MQTTConnectServer.c new file mode 100644 index 0000000..661f29d --- /dev/null +++ b/mqtt/MQTTPacket/MQTTConnectServer.c @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include +#include +#include + +#define min(a, b) ((a < b) ? a : b) + + +/** + * Validates MQTT protocol name and version combinations + * @param protocol the MQTT protocol name as an MQTTString + * @param version the MQTT protocol version number, as in the connect packet + * @return correct MQTT combination? 1 is true, 0 is false + */ +int MQTTPacket_checkVersion(MQTTString* protocol, int version) +{ + int rc = 0; + + if (version == 3 && memcmp(protocol->lenstring.data, "MQIsdp", + min(6, protocol->lenstring.len)) == 0) + rc = 1; + else if (version == 4 && memcmp(protocol->lenstring.data, "MQTT", + min(4, protocol->lenstring.len)) == 0) + rc = 1; + return rc; +} + + +/** + * Deserializes the supplied (wire) buffer into connect data structure + * @param data the connect data structure to be filled out + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param len the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_connect(MQTTPacket_connectData* data, unsigned char* buf, int len) +{ + MQTTHeader header = {0}; + MQTTConnectFlags flags = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = &buf[len]; + int rc = 0; + MQTTString Protocol; + int version; + int mylen = 0; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != CONNECT) + goto exit; + + curdata += MQTTPacket_decodeBuf(curdata, &mylen); /* read remaining length */ + + if (!readMQTTLenString(&Protocol, &curdata, enddata) || + enddata - curdata < 0) /* do we have enough data to read the protocol version byte? */ + goto exit; + + version = (int)readChar(&curdata); /* Protocol version */ + /* If we don't recognize the protocol version, we don't parse the connect packet on the + * basis that we don't know what the format will be. + */ + if (MQTTPacket_checkVersion(&Protocol, version)) + { + flags.all = readChar(&curdata); + data->cleansession = flags.bits.cleansession; + data->keepAliveInterval = readInt(&curdata); + if (!readMQTTLenString(&data->clientID, &curdata, enddata)) + goto exit; + data->willFlag = flags.bits.will; + if (flags.bits.will) + { + data->will.qos = flags.bits.willQoS; + data->will.retained = flags.bits.willRetain; + if (!readMQTTLenString(&data->will.topicName, &curdata, enddata) || + !readMQTTLenString(&data->will.message, &curdata, enddata)) + goto exit; + } + if (flags.bits.username) + { + if (enddata - curdata < 3 || !readMQTTLenString(&data->username, &curdata, enddata)) + goto exit; /* username flag set, but no username supplied - invalid */ + if (flags.bits.password && + (enddata - curdata < 3 || !readMQTTLenString(&data->password, &curdata, enddata))) + goto exit; /* password flag set, but no password supplied - invalid */ + } + else if (flags.bits.password) + goto exit; /* password flag set without username - invalid */ + rc = 1; + } +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes the connack packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param connack_rc the integer connack return code to be used + * @param sessionPresent the MQTT 3.1.1 sessionPresent flag + * @return serialized length, or error if 0 + */ +int MQTTSerialize_connack(unsigned char* buf, int buflen, unsigned char connack_rc, unsigned char sessionPresent) +{ + MQTTHeader header = {0}; + int rc = 0; + unsigned char *ptr = buf; + MQTTConnackFlags flags = {0}; + + FUNC_ENTRY; + if (buflen < 2) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.byte = 0; + header.bits.type = CONNACK; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */ + + flags.all = 0; + flags.bits.sessionpresent = sessionPresent; + writeChar(&ptr, flags.all); + writeChar(&ptr, connack_rc); + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + diff --git a/mqtt/MQTTPacket/MQTTDeserializePublish.c b/mqtt/MQTTPacket/MQTTDeserializePublish.c new file mode 100644 index 0000000..bf6f1cc --- /dev/null +++ b/mqtt/MQTTPacket/MQTTDeserializePublish.c @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include +#include +#include + +#define min(a, b) ((a < b) ? 1 : 0) + +/** + * Deserializes the supplied (wire) buffer into publish data + * @param dup returned integer - the MQTT dup flag + * @param qos returned integer - the MQTT QoS value + * @param retained returned integer - the MQTT retained flag + * @param packetid returned integer - the MQTT packet identifier + * @param topicName returned MQTTString - the MQTT topic in the publish + * @param payload returned byte buffer - the MQTT publish payload + * @param payloadlen returned integer - the length of the MQTT payload + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success + */ +int MQTTDeserialize_publish(unsigned char* dup, int* qos, unsigned char* retained, unsigned short* packetid, MQTTString* topicName, + unsigned char** payload, int* payloadlen, unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen = 0; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != PUBLISH) + goto exit; + *dup = header.bits.dup; + *qos = header.bits.qos; + *retained = header.bits.retain; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + + if (!readMQTTLenString(topicName, &curdata, enddata) || + enddata - curdata < 0) /* do we have enough data to read the protocol version byte? */ + goto exit; + + if (*qos > 0) + *packetid = readInt(&curdata); + + *payloadlen = enddata - curdata; + *payload = curdata; + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Deserializes the supplied (wire) buffer into an ack + * @param packettype returned integer - the MQTT packet type + * @param dup returned integer - the MQTT dup flag + * @param packetid returned integer - the MQTT packet identifier + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_ack(unsigned char* packettype, unsigned char* dup, unsigned short* packetid, unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + *dup = header.bits.dup; + *packettype = header.bits.type; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + + if (enddata - curdata < 2) + goto exit; + *packetid = readInt(&curdata); + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + diff --git a/mqtt/MQTTPacket/MQTTFormat.c b/mqtt/MQTTPacket/MQTTFormat.c new file mode 100644 index 0000000..4fbfdd1 --- /dev/null +++ b/mqtt/MQTTPacket/MQTTFormat.c @@ -0,0 +1,261 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include +#include +#include + + +const char* MQTTPacket_names[] = +{ + "RESERVED", "CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC", "PUBREL", + "PUBCOMP", "SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK", + "PINGREQ", "PINGRESP", "DISCONNECT" +}; + + +const char* MQTTPacket_getName(unsigned short packetid) +{ + return MQTTPacket_names[packetid]; +} + + +int MQTTStringFormat_connect(char* strbuf, int strbuflen, MQTTPacket_connectData* data) +{ + int strindex = 0; + + strindex = snprintf(strbuf, strbuflen, + "CONNECT MQTT version %d, client id %.*s, clean session %d, keep alive %d", + (int)data->MQTTVersion, data->clientID.lenstring.len, data->clientID.lenstring.data, + (int)data->cleansession, data->keepAliveInterval); + if (data->willFlag) + strindex += snprintf(&strbuf[strindex], strbuflen - strindex, + ", will QoS %d, will retain %d, will topic %.*s, will message %.*s", + data->will.qos, data->will.retained, + data->will.topicName.lenstring.len, data->will.topicName.lenstring.data, + data->will.message.lenstring.len, data->will.message.lenstring.data); + if (data->username.lenstring.data && data->username.lenstring.len > 0) + strindex += snprintf(&strbuf[strindex], strbuflen - strindex, + ", user name %.*s", data->username.lenstring.len, data->username.lenstring.data); + if (data->password.lenstring.data && data->password.lenstring.len > 0) + strindex += snprintf(&strbuf[strindex], strbuflen - strindex, + ", password %.*s", data->password.lenstring.len, data->password.lenstring.data); + return strindex; +} + + +int MQTTStringFormat_connack(char* strbuf, int strbuflen, unsigned char connack_rc, unsigned char sessionPresent) +{ + int strindex = snprintf(strbuf, strbuflen, "CONNACK session present %d, rc %d", sessionPresent, connack_rc); + return strindex; +} + + +int MQTTStringFormat_publish(char* strbuf, int strbuflen, unsigned char dup, int qos, unsigned char retained, + unsigned short packetid, MQTTString topicName, unsigned char* payload, int payloadlen) +{ + int strindex = snprintf(strbuf, strbuflen, + "PUBLISH dup %d, QoS %d, retained %d, packet id %d, topic %.*s, payload length %d, payload %.*s", + dup, qos, retained, packetid, + (topicName.lenstring.len < 20) ? topicName.lenstring.len : 20, topicName.lenstring.data, + payloadlen, (payloadlen < 20) ? payloadlen : 20, payload); + return strindex; +} + + +int MQTTStringFormat_ack(char* strbuf, int strbuflen, unsigned char packettype, unsigned char dup, unsigned short packetid) +{ + int strindex = snprintf(strbuf, strbuflen, "%s, packet id %d", MQTTPacket_names[packettype], packetid); + if (dup) + strindex += snprintf(strbuf + strindex, strbuflen - strindex, ", dup %d", dup); + return strindex; +} + + +int MQTTStringFormat_subscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, int count, + MQTTString topicFilters[], int requestedQoSs[]) +{ + return snprintf(strbuf, strbuflen, + "SUBSCRIBE dup %d, packet id %d count %d topic %.*s qos %d", + dup, packetid, count, + topicFilters[0].lenstring.len, topicFilters[0].lenstring.data, + requestedQoSs[0]); +} + + +int MQTTStringFormat_suback(char* strbuf, int strbuflen, unsigned short packetid, int count, int* grantedQoSs) +{ + return snprintf(strbuf, strbuflen, + "SUBACK packet id %d count %d granted qos %d", packetid, count, grantedQoSs[0]); +} + + +int MQTTStringFormat_unsubscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]) +{ + return snprintf(strbuf, strbuflen, + "UNSUBSCRIBE dup %d, packet id %d count %d topic %.*s", + dup, packetid, count, + topicFilters[0].lenstring.len, topicFilters[0].lenstring.data); +} + + +#if defined(MQTT_CLIENT) +char* MQTTFormat_toClientString(char* strbuf, int strbuflen, unsigned char* buf, int buflen) +{ + int index = 0; + int rem_length = 0; + MQTTHeader header = {0}; + int strindex = 0; + + header.byte = buf[index++]; + index += MQTTPacket_decodeBuf(&buf[index], &rem_length); + + switch (header.bits.type) + { + + case CONNACK: + { + unsigned char sessionPresent, connack_rc; + if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, buflen) == 1) + strindex = MQTTStringFormat_connack(strbuf, strbuflen, connack_rc, sessionPresent); + } + break; + case PUBLISH: + { + unsigned char dup, retained, *payload; + unsigned short packetid; + int qos, payloadlen; + MQTTString topicName = MQTTString_initializer; + if (MQTTDeserialize_publish(&dup, &qos, &retained, &packetid, &topicName, + &payload, &payloadlen, buf, buflen) == 1) + strindex = MQTTStringFormat_publish(strbuf, strbuflen, dup, qos, retained, packetid, + topicName, payload, payloadlen); + } + break; + case PUBACK: + case PUBREC: + case PUBREL: + case PUBCOMP: + { + unsigned char packettype, dup; + unsigned short packetid; + if (MQTTDeserialize_ack(&packettype, &dup, &packetid, buf, buflen) == 1) + strindex = MQTTStringFormat_ack(strbuf, strbuflen, packettype, dup, packetid); + } + break; + case SUBACK: + { + unsigned short packetid; + int maxcount = 1, count = 0; + int grantedQoSs[1]; + if (MQTTDeserialize_suback(&packetid, maxcount, &count, grantedQoSs, buf, buflen) == 1) + strindex = MQTTStringFormat_suback(strbuf, strbuflen, packetid, count, grantedQoSs); + } + break; + case UNSUBACK: + { + unsigned short packetid; + if (MQTTDeserialize_unsuback(&packetid, buf, buflen) == 1) + strindex = MQTTStringFormat_ack(strbuf, strbuflen, UNSUBACK, 0, packetid); + } + break; + case PINGREQ: + case PINGRESP: + case DISCONNECT: + strindex = snprintf(strbuf, strbuflen, "%s", MQTTPacket_names[header.bits.type]); + break; + } + return strbuf; +} +#endif + +#if defined(MQTT_SERVER) +char* MQTTFormat_toServerString(char* strbuf, int strbuflen, unsigned char* buf, int buflen) +{ + int index = 0; + int rem_length = 0; + MQTTHeader header = {0}; + int strindex = 0; + + header.byte = buf[index++]; + index += MQTTPacket_decodeBuf(&buf[index], &rem_length); + + switch (header.bits.type) + { + case CONNECT: + { + MQTTPacket_connectData data; + int rc; + if ((rc = MQTTDeserialize_connect(&data, buf, buflen)) == 1) + strindex = MQTTStringFormat_connect(strbuf, strbuflen, &data); + } + break; + case PUBLISH: + { + unsigned char dup, retained, *payload; + unsigned short packetid; + int qos, payloadlen; + MQTTString topicName = MQTTString_initializer; + if (MQTTDeserialize_publish(&dup, &qos, &retained, &packetid, &topicName, + &payload, &payloadlen, buf, buflen) == 1) + strindex = MQTTStringFormat_publish(strbuf, strbuflen, dup, qos, retained, packetid, + topicName, payload, payloadlen); + } + break; + case PUBACK: + case PUBREC: + case PUBREL: + case PUBCOMP: + { + unsigned char packettype, dup; + unsigned short packetid; + if (MQTTDeserialize_ack(&packettype, &dup, &packetid, buf, buflen) == 1) + strindex = MQTTStringFormat_ack(strbuf, strbuflen, packettype, dup, packetid); + } + break; + case SUBSCRIBE: + { + unsigned char dup; + unsigned short packetid; + int maxcount = 1, count = 0; + MQTTString topicFilters[1]; + int requestedQoSs[1]; + if (MQTTDeserialize_subscribe(&dup, &packetid, maxcount, &count, + topicFilters, requestedQoSs, buf, buflen) == 1) + strindex = MQTTStringFormat_subscribe(strbuf, strbuflen, dup, packetid, count, topicFilters, requestedQoSs);; + } + break; + case UNSUBSCRIBE: + { + unsigned char dup; + unsigned short packetid; + int maxcount = 1, count = 0; + MQTTString topicFilters[1]; + if (MQTTDeserialize_unsubscribe(&dup, &packetid, maxcount, &count, topicFilters, buf, buflen) == 1) + strindex = MQTTStringFormat_unsubscribe(strbuf, strbuflen, dup, packetid, count, topicFilters); + } + break; + case PINGREQ: + case PINGRESP: + case DISCONNECT: + strindex = snprintf(strbuf, strbuflen, "%s", MQTTPacket_names[header.bits.type]); + break; + } + strbuf[strbuflen] = '\0'; + return strbuf; +} +#endif diff --git a/mqtt/MQTTPacket/MQTTFormat.h b/mqtt/MQTTPacket/MQTTFormat.h new file mode 100644 index 0000000..4b1e74a --- /dev/null +++ b/mqtt/MQTTPacket/MQTTFormat.h @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#if !defined(MQTTFORMAT_H) +#define MQTTFORMAT_H + +#include +#include + +const char* MQTTPacket_getName(unsigned short packetid); +int MQTTStringFormat_connect(char* strbuf, int strbuflen, MQTTPacket_connectData* data); +int MQTTStringFormat_connack(char* strbuf, int strbuflen, unsigned char connack_rc, unsigned char sessionPresent); +int MQTTStringFormat_publish(char* strbuf, int strbuflen, unsigned char dup, int qos, unsigned char retained, + unsigned short packetid, MQTTString topicName, unsigned char* payload, int payloadlen); +int MQTTStringFormat_ack(char* strbuf, int strbuflen, unsigned char packettype, unsigned char dup, unsigned short packetid); +int MQTTStringFormat_subscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, int count, + MQTTString topicFilters[], int requestedQoSs[]); +int MQTTStringFormat_suback(char* strbuf, int strbuflen, unsigned short packetid, int count, int* grantedQoSs); +int MQTTStringFormat_unsubscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]); +char* MQTTFormat_toClientString(char* strbuf, int strbuflen, unsigned char* buf, int buflen); +char* MQTTFormat_toServerString(char* strbuf, int strbuflen, unsigned char* buf, int buflen); + +#endif diff --git a/mqtt/MQTTPacket/MQTTPacket.c b/mqtt/MQTTPacket/MQTTPacket.c new file mode 100644 index 0000000..2da3e00 --- /dev/null +++ b/mqtt/MQTTPacket/MQTTPacket.c @@ -0,0 +1,411 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Sergio R. Caprile - non-blocking packet read functions for stream transport + *******************************************************************************/ + +#include +#include +#include + +/** + * Encodes the message length according to the MQTT algorithm + * @param buf the buffer into which the encoded data is written + * @param length the length to be encoded + * @return the number of bytes written to buffer + */ +int MQTTPacket_encode(unsigned char* buf, int length) +{ + int rc = 0; + + FUNC_ENTRY; + do + { + char d = length % 128; + length /= 128; + /* if there are more digits to encode, set the top bit of this digit */ + if (length > 0) + d |= 0x80; + buf[rc++] = d; + } while (length > 0); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Decodes the message length according to the MQTT algorithm + * @param getcharfn pointer to function to read the next character from the data source + * @param value the decoded length returned + * @return the number of bytes read from the socket + */ +int MQTTPacket_decode(int (*getcharfn)(unsigned char*, int), int* value) +{ + unsigned char c; + int multiplier = 1; + int len = 0; +#define MAX_NO_OF_REMAINING_LENGTH_BYTES 4 + + FUNC_ENTRY; + *value = 0; + do + { + int rc = MQTTPACKET_READ_ERROR; + + if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES) + { + rc = MQTTPACKET_READ_ERROR; /* bad data */ + goto exit; + } + rc = (*getcharfn)(&c, 1); + if (rc != 1) + goto exit; + *value += (c & 127) * multiplier; + multiplier *= 128; + } while ((c & 128) != 0); +exit: + FUNC_EXIT_RC(len); + return len; +} + + +int MQTTPacket_len(int rem_len) +{ + rem_len += 1; /* header byte */ + + /* now remaining_length field */ + if (rem_len < 128) + rem_len += 1; + else if (rem_len < 16384) + rem_len += 2; + else if (rem_len < 2097151) + rem_len += 3; + else + rem_len += 4; + return rem_len; +} + + +static unsigned char* bufptr; + +int bufchar(unsigned char* c, int count) +{ + int i; + + for (i = 0; i < count; ++i) + *c = *bufptr++; + return count; +} + + +int MQTTPacket_decodeBuf(unsigned char* buf, int* value) +{ + bufptr = buf; + return MQTTPacket_decode(bufchar, value); +} + + +/** + * Calculates an integer from two bytes read from the input buffer + * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned + * @return the integer value calculated + */ +int readInt(unsigned char** pptr) +{ + unsigned char* ptr = *pptr; + int len = 256*(*ptr) + (*(ptr+1)); + *pptr += 2; + return len; +} + + +/** + * Reads one character from the input buffer. + * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned + * @return the character read + */ +char readChar(unsigned char** pptr) +{ + char c = **pptr; + (*pptr)++; + return c; +} + + +/** + * Writes one character to an output buffer. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param c the character to write + */ +void writeChar(unsigned char** pptr, char c) +{ + **pptr = c; + (*pptr)++; +} + + +/** + * Writes an integer as 2 bytes to an output buffer. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param anInt the integer to write + */ +void writeInt(unsigned char** pptr, int anInt) +{ + **pptr = (unsigned char)(anInt / 256); + (*pptr)++; + **pptr = (unsigned char)(anInt % 256); + (*pptr)++; +} + + +/** + * Writes a "UTF" string to an output buffer. Converts C string to length-delimited. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param string the C string to write + */ +void writeCString(unsigned char** pptr, const char* string) +{ + int len = strlen(string); + writeInt(pptr, len); + memcpy(*pptr, string, len); + *pptr += len; +} + + +int getLenStringLen(char* ptr) +{ + int len = 256*((unsigned char)(*ptr)) + (unsigned char)(*(ptr+1)); + return len; +} + + +void writeMQTTString(unsigned char** pptr, MQTTString mqttstring) +{ + if (mqttstring.lenstring.len > 0) + { + writeInt(pptr, mqttstring.lenstring.len); + memcpy(*pptr, mqttstring.lenstring.data, mqttstring.lenstring.len); + *pptr += mqttstring.lenstring.len; + } + else if (mqttstring.cstring) + writeCString(pptr, mqttstring.cstring); + else + writeInt(pptr, 0); +} + + +/** + * @param mqttstring the MQTTString structure into which the data is to be read + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param enddata pointer to the end of the data: do not read beyond + * @return 1 if successful, 0 if not + */ +int readMQTTLenString(MQTTString* mqttstring, unsigned char** pptr, unsigned char* enddata) +{ + int rc = 0; + + FUNC_ENTRY; + /* the first two bytes are the length of the string */ + if (enddata - (*pptr) > 1) /* enough length to read the integer? */ + { + mqttstring->lenstring.len = readInt(pptr); /* increments pptr to point past length */ + if (&(*pptr)[mqttstring->lenstring.len] <= enddata) + { + mqttstring->lenstring.data = (char*)*pptr; + *pptr += mqttstring->lenstring.len; + rc = 1; + } + } + mqttstring->cstring = NULL; + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Return the length of the MQTTstring - C string if there is one, otherwise the length delimited string + * @param mqttstring the string to return the length of + * @return the length of the string + */ +int MQTTstrlen(MQTTString mqttstring) +{ + int rc = 0; + + if (mqttstring.cstring) + rc = strlen(mqttstring.cstring); + else + rc = mqttstring.lenstring.len; + return rc; +} + + +/** + * Compares an MQTTString to a C string + * @param a the MQTTString to compare + * @param bptr the C string to compare + * @return boolean - equal or not + */ +int MQTTPacket_equals(MQTTString* a, char* bptr) +{ + int alen = 0, + blen = 0; + char *aptr; + + if (a->cstring) + { + aptr = a->cstring; + alen = strlen(a->cstring); + } + else + { + aptr = a->lenstring.data; + alen = a->lenstring.len; + } + blen = strlen(bptr); + + return (alen == blen) && (strncmp(aptr, bptr, alen) == 0); +} + + +/** + * Helper function to read packet data from some source into a buffer + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param getfn pointer to a function which will read any number of bytes from the needed source + * @return integer MQTT packet type, or -1 on error + * @note the whole message must fit into the caller's buffer + */ +int MQTTPacket_read(unsigned char* buf, int buflen, int (*getfn)(unsigned char*, int)) +{ + int rc = -1; + MQTTHeader header = {0}; + int len = 0; + int rem_len = 0; + + /* 1. read the header byte. This has the packet type in it */ + if ((*getfn)(buf, 1) != 1) + goto exit; + + len = 1; + /* 2. read the remaining length. This is variable in itself */ + MQTTPacket_decode(getfn, &rem_len); + len += MQTTPacket_encode(buf + 1, rem_len); /* put the original remaining length back into the buffer */ + + /* 3. read the rest of the buffer using a callback to supply the rest of the data */ + if((rem_len + len) > buflen) + goto exit; + if (rem_len && ((*getfn)(buf + len, rem_len) != rem_len)) + goto exit; + + header.byte = buf[0]; + rc = header.bits.type; +exit: + return rc; +} + +/** + * Decodes the message length according to the MQTT algorithm, non-blocking + * @param trp pointer to a transport structure holding what is needed to solve getting data from it + * @param value the decoded length returned + * @return integer the number of bytes read from the socket, 0 for call again, or -1 on error + */ +static int MQTTPacket_decodenb(MQTTTransport *trp) +{ + unsigned char c; + int rc = MQTTPACKET_READ_ERROR; + + FUNC_ENTRY; + if(trp->len == 0){ /* initialize on first call */ + trp->multiplier = 1; + trp->rem_len = 0; + } + do { + int frc; + if (trp->len >= MAX_NO_OF_REMAINING_LENGTH_BYTES) + goto exit; + if ((frc=(*trp->getfn)(trp->sck, &c, 1)) == -1) + goto exit; + if (frc == 0){ + rc = 0; + goto exit; + } + ++(trp->len); + trp->rem_len += (c & 127) * trp->multiplier; + trp->multiplier *= 128; + } while ((c & 128) != 0); + rc = trp->len; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + +/** + * Helper function to read packet data from some source into a buffer, non-blocking + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param trp pointer to a transport structure holding what is needed to solve getting data from it + * @return integer MQTT packet type, 0 for call again, or -1 on error + * @note the whole message must fit into the caller's buffer + */ +int MQTTPacket_readnb(unsigned char* buf, int buflen, MQTTTransport *trp) +{ + int rc = -1, frc; + MQTTHeader header = {0}; + + switch(trp->state){ + default: + trp->state = 0; + /*FALLTHROUGH*/ + case 0: + /* read the header byte. This has the packet type in it */ + if ((frc=(*trp->getfn)(trp->sck, buf, 1)) == -1) + goto exit; + if (frc == 0) + return 0; + trp->len = 0; + ++trp->state; + /*FALLTHROUGH*/ + /* read the remaining length. This is variable in itself */ + case 1: + if((frc=MQTTPacket_decodenb(trp)) == MQTTPACKET_READ_ERROR) + goto exit; + if(frc == 0) + return 0; + trp->len = 1 + MQTTPacket_encode(buf + 1, trp->rem_len); /* put the original remaining length back into the buffer */ + if((trp->rem_len + trp->len) > buflen) + goto exit; + ++trp->state; + /*FALLTHROUGH*/ + case 2: + if(trp->rem_len){ + /* read the rest of the buffer using a callback to supply the rest of the data */ + if ((frc=(*trp->getfn)(trp->sck, buf + trp->len, trp->rem_len)) == -1) + goto exit; + if (frc == 0) + return 0; + trp->rem_len -= frc; + trp->len += frc; + if(trp->rem_len) + return 0; + } + header.byte = buf[0]; + rc = header.bits.type; + break; + } + +exit: + trp->state = 0; + return rc; +} + diff --git a/mqtt/MQTTPacket/MQTTPacket.h b/mqtt/MQTTPacket/MQTTPacket.h new file mode 100644 index 0000000..4f5741a --- /dev/null +++ b/mqtt/MQTTPacket/MQTTPacket.h @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTPACKET_H_ +#define MQTTPACKET_H_ + +#if defined(__cplusplus) /* If this is a C++ compiler, use C linkage */ +extern "C" { +#endif + +#if defined(WIN32_DLL) || defined(WIN64_DLL) + #define DLLImport __declspec(dllimport) + #define DLLExport __declspec(dllexport) +#elif defined(LINUX_SO) + #define DLLImport extern + #define DLLExport __attribute__ ((visibility ("default"))) +#else + #define DLLImport + #define DLLExport +#endif + +enum errors +{ + MQTTPACKET_BUFFER_TOO_SHORT = -2, + MQTTPACKET_READ_ERROR = -1, + MQTTPACKET_READ_COMPLETE +}; + +enum msgTypes +{ + CONNECT = 1, CONNACK, PUBLISH, PUBACK, PUBREC, PUBREL, + PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK, + PINGREQ, PINGRESP, DISCONNECT +}; + +/** + * Bitfields for the MQTT header byte. + */ +typedef union +{ + unsigned char byte; /**< the whole byte */ +#if defined(REVERSED) + struct + { + unsigned int type : 4; /**< message type nibble */ + unsigned int dup : 1; /**< DUP flag bit */ + unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */ + unsigned int retain : 1; /**< retained flag bit */ + } bits; +#else + struct + { + unsigned int retain : 1; /**< retained flag bit */ + unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */ + unsigned int dup : 1; /**< DUP flag bit */ + unsigned int type : 4; /**< message type nibble */ + } bits; +#endif +} MQTTHeader; + +typedef struct +{ + int len; + char* data; +} MQTTLenString; + +typedef struct +{ + char* cstring; + MQTTLenString lenstring; +} MQTTString; + +#define MQTTString_initializer {NULL, {0, NULL}} + +int MQTTstrlen(MQTTString mqttstring); + +#include +#include +#include +#include +#include + +DLLExport int MQTTSerialize_ack(unsigned char* buf, int buflen, unsigned char type, unsigned char dup, unsigned short packetid); +DLLExport int MQTTDeserialize_ack(unsigned char* packettype, unsigned char* dup, unsigned short* packetid, unsigned char* buf, int buflen); + +int MQTTPacket_len(int rem_len); +DLLExport int MQTTPacket_equals(MQTTString* a, char* b); + +DLLExport int MQTTPacket_encode(unsigned char* buf, int length); +int MQTTPacket_decode(int (*getcharfn)(unsigned char*, int), int* value); +int MQTTPacket_decodeBuf(unsigned char* buf, int* value); + +int readInt(unsigned char** pptr); +char readChar(unsigned char** pptr); +void writeChar(unsigned char** pptr, char c); +void writeInt(unsigned char** pptr, int anInt); +int readMQTTLenString(MQTTString* mqttstring, unsigned char** pptr, unsigned char* enddata); +void writeCString(unsigned char** pptr, const char* string); +void writeMQTTString(unsigned char** pptr, MQTTString mqttstring); + +DLLExport int MQTTPacket_read(unsigned char* buf, int buflen, int (*getfn)(unsigned char*, int)); + +typedef struct { + int (*getfn)(void *, unsigned char*, int); /* must return -1 for error, 0 for call again, or the number of bytes read */ + void *sck; /* pointer to whatever the system may use to identify the transport */ + int multiplier; + int rem_len; + int len; + char state; +}MQTTTransport; + +int MQTTPacket_readnb(unsigned char* buf, int buflen, MQTTTransport *trp); + +#ifdef __cplusplus /* If this is a C++ compiler, use C linkage */ +} +#endif + + +#endif /* MQTTPACKET_H_ */ diff --git a/mqtt/MQTTPacket/MQTTPublish.h b/mqtt/MQTTPacket/MQTTPublish.h new file mode 100644 index 0000000..ebe479d --- /dev/null +++ b/mqtt/MQTTPacket/MQTTPublish.h @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTPUBLISH_H_ +#define MQTTPUBLISH_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + +DLLExport int MQTTSerialize_publish(unsigned char* buf, int buflen, unsigned char dup, int qos, unsigned char retained, unsigned short packetid, + MQTTString topicName, unsigned char* payload, int payloadlen); + +DLLExport int MQTTDeserialize_publish(unsigned char* dup, int* qos, unsigned char* retained, unsigned short* packetid, MQTTString* topicName, + unsigned char** payload, int* payloadlen, unsigned char* buf, int len); + +DLLExport int MQTTSerialize_puback(unsigned char* buf, int buflen, unsigned short packetid); +DLLExport int MQTTSerialize_pubrel(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid); +DLLExport int MQTTSerialize_pubcomp(unsigned char* buf, int buflen, unsigned short packetid); + +#endif /* MQTTPUBLISH_H_ */ diff --git a/mqtt/MQTTPacket/MQTTSerializePublish.c b/mqtt/MQTTPacket/MQTTSerializePublish.c new file mode 100644 index 0000000..3f5c80a --- /dev/null +++ b/mqtt/MQTTPacket/MQTTSerializePublish.c @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=453144 + *******************************************************************************/ + +#include +#include +#include + + +/** + * Determines the length of the MQTT publish packet that would be produced using the supplied parameters + * @param qos the MQTT QoS of the publish (packetid is omitted for QoS 0) + * @param topicName the topic name to be used in the publish + * @param payloadlen the length of the payload to be sent + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_publishLength(int qos, MQTTString topicName, int payloadlen) +{ + int len = 0; + + len += 2 + MQTTstrlen(topicName) + payloadlen; + if (qos > 0) + len += 2; /* packetid */ + return len; +} + + +/** + * Serializes the supplied publish data into the supplied buffer, ready for sending + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param dup integer - the MQTT dup flag + * @param qos integer - the MQTT QoS value + * @param retained integer - the MQTT retained flag + * @param packetid integer - the MQTT packet identifier + * @param topicName MQTTString - the MQTT topic in the publish + * @param payload byte buffer - the MQTT publish payload + * @param payloadlen integer - the length of the MQTT payload + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_publish(unsigned char* buf, int buflen, unsigned char dup, int qos, unsigned char retained, unsigned short packetid, + MQTTString topicName, unsigned char* payload, int payloadlen) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + int rem_len = 0; + int rc = 0; + + FUNC_ENTRY; + if (MQTTPacket_len(rem_len = MQTTSerialize_publishLength(qos, topicName, payloadlen)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.bits.type = PUBLISH; + header.bits.dup = dup; + header.bits.qos = qos; + header.bits.retain = retained; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */; + + writeMQTTString(&ptr, topicName); + + if (qos > 0) + writeInt(&ptr, packetid); + + memcpy(ptr, payload, payloadlen); + ptr += payloadlen; + + rc = ptr - buf; + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Serializes the ack packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param type the MQTT packet type + * @param dup the MQTT dup flag + * @param packetid the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_ack(unsigned char* buf, int buflen, unsigned char packettype, unsigned char dup, unsigned short packetid) +{ + MQTTHeader header = {0}; + int rc = 0; + unsigned char *ptr = buf; + + FUNC_ENTRY; + if (buflen < 4) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.bits.type = packettype; + header.bits.dup = dup; + header.bits.qos = (packettype == PUBREL) ? 1 : 0; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */ + writeInt(&ptr, packetid); + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes a puback packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param packetid integer - the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_puback(unsigned char* buf, int buflen, unsigned short packetid) +{ + return MQTTSerialize_ack(buf, buflen, PUBACK, 0, packetid); +} + + +/** + * Serializes a pubrel packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param dup integer - the MQTT dup flag + * @param packetid integer - the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_pubrel(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid) +{ + return MQTTSerialize_ack(buf, buflen, PUBREL, dup, packetid); +} + + +/** + * Serializes a pubrel packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param packetid integer - the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_pubcomp(unsigned char* buf, int buflen, unsigned short packetid) +{ + return MQTTSerialize_ack(buf, buflen, PUBCOMP, 0, packetid); +} + + diff --git a/mqtt/MQTTPacket/MQTTSubscribe.h b/mqtt/MQTTPacket/MQTTSubscribe.h new file mode 100644 index 0000000..aa91826 --- /dev/null +++ b/mqtt/MQTTPacket/MQTTSubscribe.h @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTSUBSCRIBE_H_ +#define MQTTSUBSCRIBE_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + +DLLExport int MQTTSerialize_subscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[], int requestedQoSs[]); + +DLLExport int MQTTDeserialize_subscribe(unsigned char* dup, unsigned short* packetid, + int maxcount, int* count, MQTTString topicFilters[], int requestedQoSs[], unsigned char* buf, int len); + +DLLExport int MQTTSerialize_suback(unsigned char* buf, int buflen, unsigned short packetid, int count, int* grantedQoSs); + +DLLExport int MQTTDeserialize_suback(unsigned short* packetid, int maxcount, int* count, int grantedQoSs[], unsigned char* buf, int len); + + +#endif /* MQTTSUBSCRIBE_H_ */ diff --git a/mqtt/MQTTPacket/MQTTSubscribeClient.c b/mqtt/MQTTPacket/MQTTSubscribeClient.c new file mode 100644 index 0000000..2544f03 --- /dev/null +++ b/mqtt/MQTTPacket/MQTTSubscribeClient.c @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include +#include +#include + +/** + * Determines the length of the MQTT subscribe packet that would be produced using the supplied parameters + * @param count the number of topic filter strings in topicFilters + * @param topicFilters the array of topic filter strings to be used in the publish + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_subscribeLength(int count, MQTTString topicFilters[]) +{ + int i; + int len = 2; /* packetid */ + + for (i = 0; i < count; ++i) + len += 2 + MQTTstrlen(topicFilters[i]) + 1; /* length + topic + req_qos */ + return len; +} + + +/** + * Serializes the supplied subscribe data into the supplied buffer, ready for sending + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied bufferr + * @param dup integer - the MQTT dup flag + * @param packetid integer - the MQTT packet identifier + * @param count - number of members in the topicFilters and reqQos arrays + * @param topicFilters - array of topic filter names + * @param requestedQoSs - array of requested QoS + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_subscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, int count, + MQTTString topicFilters[], int requestedQoSs[]) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + int rem_len = 0; + int rc = 0; + int i = 0; + + FUNC_ENTRY; + if (MQTTPacket_len(rem_len = MQTTSerialize_subscribeLength(count, topicFilters)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.byte = 0; + header.bits.type = SUBSCRIBE; + header.bits.dup = dup; + header.bits.qos = 1; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */; + + writeInt(&ptr, packetid); + + for (i = 0; i < count; ++i) + { + writeMQTTString(&ptr, topicFilters[i]); + writeChar(&ptr, requestedQoSs[i]); + } + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Deserializes the supplied (wire) buffer into suback data + * @param packetid returned integer - the MQTT packet identifier + * @param maxcount - the maximum number of members allowed in the grantedQoSs array + * @param count returned integer - number of members in the grantedQoSs array + * @param grantedQoSs returned array of integers - the granted qualities of service + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_suback(unsigned short* packetid, int maxcount, int* count, int grantedQoSs[], unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != SUBACK) + goto exit; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + if (enddata - curdata < 2) + goto exit; + + *packetid = readInt(&curdata); + + *count = 0; + while (curdata < enddata) + { + if (*count > maxcount) + { + rc = -1; + goto exit; + } + grantedQoSs[(*count)++] = readChar(&curdata); + } + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + diff --git a/mqtt/MQTTPacket/MQTTSubscribeServer.c b/mqtt/MQTTPacket/MQTTSubscribeServer.c new file mode 100644 index 0000000..8d87997 --- /dev/null +++ b/mqtt/MQTTPacket/MQTTSubscribeServer.c @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include +#include +#include + + +/** + * Deserializes the supplied (wire) buffer into subscribe data + * @param dup integer returned - the MQTT dup flag + * @param packetid integer returned - the MQTT packet identifier + * @param maxcount - the maximum number of members allowed in the topicFilters and requestedQoSs arrays + * @param count - number of members in the topicFilters and requestedQoSs arrays + * @param topicFilters - array of topic filter names + * @param requestedQoSs - array of requested QoS + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTDeserialize_subscribe(unsigned char* dup, unsigned short* packetid, int maxcount, int* count, MQTTString topicFilters[], + int requestedQoSs[], unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = -1; + int mylen = 0; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != SUBSCRIBE) + goto exit; + *dup = header.bits.dup; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + + *packetid = readInt(&curdata); + + *count = 0; + while (curdata < enddata) + { + if (!readMQTTLenString(&topicFilters[*count], &curdata, enddata)) + goto exit; + if (curdata >= enddata) /* do we have enough data to read the req_qos version byte? */ + goto exit; + requestedQoSs[*count] = readChar(&curdata); + (*count)++; + } + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes the supplied suback data into the supplied buffer, ready for sending + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param packetid integer - the MQTT packet identifier + * @param count - number of members in the grantedQoSs array + * @param grantedQoSs - array of granted QoS + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_suback(unsigned char* buf, int buflen, unsigned short packetid, int count, int* grantedQoSs) +{ + MQTTHeader header = {0}; + int rc = -1; + unsigned char *ptr = buf; + int i; + + FUNC_ENTRY; + if (buflen < 2 + count) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.byte = 0; + header.bits.type = SUBACK; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 2 + count); /* write remaining length */ + + writeInt(&ptr, packetid); + + for (i = 0; i < count; ++i) + writeChar(&ptr, grantedQoSs[i]); + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + diff --git a/mqtt/MQTTPacket/MQTTUnsubscribe.h b/mqtt/MQTTPacket/MQTTUnsubscribe.h new file mode 100644 index 0000000..355ca9a --- /dev/null +++ b/mqtt/MQTTPacket/MQTTUnsubscribe.h @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTUNSUBSCRIBE_H_ +#define MQTTUNSUBSCRIBE_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + +DLLExport int MQTTSerialize_unsubscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]); + +DLLExport int MQTTDeserialize_unsubscribe(unsigned char* dup, unsigned short* packetid, int max_count, int* count, MQTTString topicFilters[], + unsigned char* buf, int len); + +DLLExport int MQTTSerialize_unsuback(unsigned char* buf, int buflen, unsigned short packetid); + +DLLExport int MQTTDeserialize_unsuback(unsigned short* packetid, unsigned char* buf, int len); + +#endif /* MQTTUNSUBSCRIBE_H_ */ diff --git a/mqtt/MQTTPacket/MQTTUnsubscribeClient.c b/mqtt/MQTTPacket/MQTTUnsubscribeClient.c new file mode 100644 index 0000000..999dffa --- /dev/null +++ b/mqtt/MQTTPacket/MQTTUnsubscribeClient.c @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include +#include +#include + +/** + * Determines the length of the MQTT unsubscribe packet that would be produced using the supplied parameters + * @param count the number of topic filter strings in topicFilters + * @param topicFilters the array of topic filter strings to be used in the publish + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_unsubscribeLength(int count, MQTTString topicFilters[]) +{ + int i; + int len = 2; /* packetid */ + + for (i = 0; i < count; ++i) + len += 2 + MQTTstrlen(topicFilters[i]); /* length + topic*/ + return len; +} + + +/** + * Serializes the supplied unsubscribe data into the supplied buffer, ready for sending + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @param dup integer - the MQTT dup flag + * @param packetid integer - the MQTT packet identifier + * @param count - number of members in the topicFilters array + * @param topicFilters - array of topic filter names + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_unsubscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + int rem_len = 0; + int rc = -1; + int i = 0; + + FUNC_ENTRY; + if (MQTTPacket_len(rem_len = MQTTSerialize_unsubscribeLength(count, topicFilters)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.byte = 0; + header.bits.type = UNSUBSCRIBE; + header.bits.dup = dup; + header.bits.qos = 1; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */; + + writeInt(&ptr, packetid); + + for (i = 0; i < count; ++i) + writeMQTTString(&ptr, topicFilters[i]); + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Deserializes the supplied (wire) buffer into unsuback data + * @param packetid returned integer - the MQTT packet identifier + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_unsuback(unsigned short* packetid, unsigned char* buf, int buflen) +{ + unsigned char type = 0; + unsigned char dup = 0; + int rc = 0; + + FUNC_ENTRY; + rc = MQTTDeserialize_ack(&type, &dup, packetid, buf, buflen); + if (type == UNSUBACK) + rc = 1; + FUNC_EXIT_RC(rc); + return rc; +} + + diff --git a/mqtt/MQTTPacket/MQTTUnsubscribeServer.c b/mqtt/MQTTPacket/MQTTUnsubscribeServer.c new file mode 100644 index 0000000..958e5bd --- /dev/null +++ b/mqtt/MQTTPacket/MQTTUnsubscribeServer.c @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include +#include +#include + + +/** + * Deserializes the supplied (wire) buffer into unsubscribe data + * @param dup integer returned - the MQTT dup flag + * @param packetid integer returned - the MQTT packet identifier + * @param maxcount - the maximum number of members allowed in the topicFilters and requestedQoSs arrays + * @param count - number of members in the topicFilters and requestedQoSs arrays + * @param topicFilters - array of topic filter names + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTDeserialize_unsubscribe(unsigned char* dup, unsigned short* packetid, int maxcount, int* count, MQTTString topicFilters[], + unsigned char* buf, int len) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen = 0; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != UNSUBSCRIBE) + goto exit; + *dup = header.bits.dup; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + + *packetid = readInt(&curdata); + + *count = 0; + while (curdata < enddata) + { + if (!readMQTTLenString(&topicFilters[*count], &curdata, enddata)) + goto exit; + (*count)++; + } + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes the supplied unsuback data into the supplied buffer, ready for sending + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param packetid integer - the MQTT packet identifier + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_unsuback(unsigned char* buf, int buflen, unsigned short packetid) +{ + MQTTHeader header = {0}; + int rc = 0; + unsigned char *ptr = buf; + + FUNC_ENTRY; + if (buflen < 2) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.byte = 0; + header.bits.type = UNSUBACK; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */ + + writeInt(&ptr, packetid); + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + diff --git a/mqtt/MQTTPacket/StackTrace.h b/mqtt/MQTTPacket/StackTrace.h new file mode 100644 index 0000000..2808a0d --- /dev/null +++ b/mqtt/MQTTPacket/StackTrace.h @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - fix for bug #434081 + *******************************************************************************/ + +#ifndef STACKTRACE_H_ +#define STACKTRACE_H_ + +#include +#define NOSTACKTRACE 1 + +#if defined(NOSTACKTRACE) +#define FUNC_ENTRY +#define FUNC_ENTRY_NOLOG +#define FUNC_ENTRY_MED +#define FUNC_ENTRY_MAX +#define FUNC_EXIT +#define FUNC_EXIT_NOLOG +#define FUNC_EXIT_MED +#define FUNC_EXIT_MAX +#define FUNC_EXIT_RC(x) +#define FUNC_EXIT_MED_RC(x) +#define FUNC_EXIT_MAX_RC(x) + +#else + +#if defined(WIN32) +#define inline __inline +#define FUNC_ENTRY StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MINIMUM) +#define FUNC_ENTRY_NOLOG StackTrace_entry(__FUNCTION__, __LINE__, -1) +#define FUNC_ENTRY_MED StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MEDIUM) +#define FUNC_ENTRY_MAX StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MAXIMUM) +#define FUNC_EXIT StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MINIMUM) +#define FUNC_EXIT_NOLOG StackTrace_exit(__FUNCTION__, __LINE__, -1) +#define FUNC_EXIT_MED StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MEDIUM) +#define FUNC_EXIT_MAX StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MAXIMUM) +#define FUNC_EXIT_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MINIMUM) +#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MEDIUM) +#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MAXIMUM) +#else +#define FUNC_ENTRY StackTrace_entry(__func__, __LINE__, TRACE_MINIMUM) +#define FUNC_ENTRY_NOLOG StackTrace_entry(__func__, __LINE__, -1) +#define FUNC_ENTRY_MED StackTrace_entry(__func__, __LINE__, TRACE_MEDIUM) +#define FUNC_ENTRY_MAX StackTrace_entry(__func__, __LINE__, TRACE_MAXIMUM) +#define FUNC_EXIT StackTrace_exit(__func__, __LINE__, NULL, TRACE_MINIMUM) +#define FUNC_EXIT_NOLOG StackTrace_exit(__func__, __LINE__, NULL, -1) +#define FUNC_EXIT_MED StackTrace_exit(__func__, __LINE__, NULL, TRACE_MEDIUM) +#define FUNC_EXIT_MAX StackTrace_exit(__func__, __LINE__, NULL, TRACE_MAXIMUM) +#define FUNC_EXIT_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MINIMUM) +#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MEDIUM) +#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MAXIMUM) + +void StackTrace_entry(const char* name, int line, int trace); +void StackTrace_exit(const char* name, int line, void* return_value, int trace); + +void StackTrace_printStack(FILE* dest); +char* StackTrace_get(unsigned long); + +#endif + +#endif + + + + +#endif /* STACKTRACE_H_ */ diff --git a/mqtt_task.c b/mqtt_task.c new file mode 100644 index 0000000..003dc50 --- /dev/null +++ b/mqtt_task.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include "mqtt_task.h" +#include "modbus_task.h" +#include "MQTTInterface.h" +#include "MQTTClient.h" +#include "utilis.h" +#include "config.h" + +#define MAX_CONN_ATTEMPS (10) +#define MQTT_BUFSIZE (1 * 1024) + +Network mqttNet; +MQTTClient mqttClient; + +static unsigned char sndBuffer[MQTT_BUFSIZE]; // mqtt send buffer +static unsigned char rcvBuffer[MQTT_BUFSIZE]; // mqtt receive buffer +static unsigned char msgBuffer[MQTT_BUFSIZE]; // mqtt message buffer + +/** + * @brief Callback function invoked when an MQTT message arrives. + * @param argument: None + * @param msg Pointer to the received message data. + */ +void MqttMessageArrived(MessageData *msg) +{ + MQTTMessage *message = msg->message; + + // Clear the message buffer and copy the received payload into it. + memset(msgBuffer, 0, sizeof(msgBuffer)); + memcpy(msgBuffer, message->payload, message->payloadlen); + + // Log the received message payload and its length. + fprintf(stdout, "[MQTT_SUB_TASK] INFO: MQTT MSG[%d]:%s\n", (int)message->payloadlen, msgBuffer); +} + +/** + * @brief Connects to an MQTT broker and subscribes to a topic. + * @param argument: None + * @retval MQTT_SUCCESS on success, or an MQTT error code on failure. + */ +int MqttConnectBroker(void) +{ + // Connect to MQTT broker + int ret = mqtt_network_connect(&mqttNet, MQTT_BROKER_IP, MQTT_BROKER_PORT); + + if (ret != MQTT_SUCCESS) + { + // Handle network connection failure. + fprintf(stderr, "\n[MQTT_PUB_TASK] ERROR: ConnectNetwork failed.\n"); + mqtt_network_disconnect(&mqttNet); + return -1; + } + + // Initialize the MQTT client + MQTTClientInit(&mqttClient, &mqttNet, 1000, sndBuffer, sizeof(sndBuffer), rcvBuffer, sizeof(rcvBuffer)); + + // Set up MQTT connection parameters + MQTTPacket_connectData data = MQTTPacket_connectData_initializer; + data.willFlag = 0; + data.MQTTVersion = 3; + data.clientID.cstring = "Linux client"; + // data.username.cstring = "roger"; + // data.password.cstring = "password"; + data.keepAliveInterval = 60; + data.cleansession = 1; + + ret = MQTTConnect(&mqttClient, &data); + if (ret != MQTT_SUCCESS) + { + // Handle MQTT connection failure + fprintf(stderr, "[MQTT_PUB_TASK] ERROR: MQTTConnect failed.\n"); + MQTTCloseSession(&mqttClient); + mqtt_network_disconnect(&mqttNet); + return ret; + } + + // Subscribe to the desired topic + ret = MQTTSubscribe(&mqttClient, MQTT_TOPIC, QOS0, MqttMessageArrived); + if (ret != MQTT_SUCCESS) + { + // Handle subscription failure + fprintf(stderr, "[MQTT_PUB_TASK] ERROR: MQTTSubscribe failed.\n"); + MQTTCloseSession(&mqttClient); + mqtt_network_disconnect(&mqttNet); + return ret; + } + + fprintf(stdout, "[MQTT_PUB_TASK] INFO: MQTT_ConnectBroker O.K.\n"); + return MQTT_SUCCESS; +} + +// Function implementing the MqttClientSubTask thread. +void *mqtt_sub_task(void *arg) +{ + mqtt_config_t *config = (mqtt_config_t *)arg; + + if (config == NULL) + { + // No config + fprintf(stderr, "mqtt_sub_task: Error no config!\n"); + pthread_exit(NULL); + return NULL; + } + + for (;;) + { + // If disconnected from MQTT broker fast blink the green led + if (!mqttClient.isconnected) + { + sleep_ms(50); + continue; + } + + // Handle timer and incoming messages + MQTTYield(&mqttClient, 500); /* Don't wait too long if no traffic is incoming */ + sleep_ms(100); + } + + return NULL; +} + +// Function implementing the MqttClientPubTask thread +void *mqtt_pub_task(void *arg) +{ + mqtt_config_t *config = (mqtt_config_t *)arg; + + if (config == NULL) + { + // No config + fprintf(stderr, "mqtt_pub_task: Error no config!\n"); + pthread_exit(NULL); + return NULL; + } + + for (;;) + { + + // 1) Attempt to establish the connection with the MQTT broker: + // - If connection fails, frees resources and retry. + // - If the connection is successful, sends MQTT messages. + + int ret = FAILURE; + int nAttemps = 0; + do + { + ret = MqttConnectBroker(); + + if (ret != MQTT_SUCCESS) + { + fprintf(stderr, "mqtt_pub_task: [%d] Error connecting to broker retry...\n", nAttemps++); + } + } while (ret != MQTT_SUCCESS && nAttemps < 10); + + if (ret != MQTT_SUCCESS) + { + fprintf(stderr, "mqtt_pub_task: Error connecting to broker\n"); + return NULL; + } + + int error = 0; + char str[256] = {0}; + MQTTMessage message; + unsigned long ulNotifiedValue = 0; + + do + { + // Wait for plc data + pthread_mutex_lock(&mutex); + // Attendi un nuovo valore + while (pthread_cond_wait(&cond, &mutex) != 0) + ; + ulNotifiedValue = shared_value; + pthread_mutex_unlock(&mutex); + + // Composing the message to be sent + snprintf(str, sizeof(str), + "{\n" + " \"device\": \"LINUX\",\n" + " \"data\": %lu\n" + "}", + ulNotifiedValue); + // snprintf(str, sizeof(str), "MQTT message from STM32: %lu", ulNotifiedValue); + message.payload = (void *)str; + message.payloadlen = strlen(str); + + // Send the message at topic + if (MQTTPublish(&mqttClient, MQTT_TOPIC, &message) != MQTT_SUCCESS) + { + MQTTCloseSession(&mqttClient); + mqtt_network_disconnect(&mqttNet); + error = 1; + continue; + } + + // Wait + sleep_ms(1000); + + } while (mqttClient.isconnected && !error); + } + + return NULL; +} diff --git a/mqtt_task.h b/mqtt_task.h new file mode 100644 index 0000000..63880a5 --- /dev/null +++ b/mqtt_task.h @@ -0,0 +1,13 @@ +#ifndef __MQTT_TASK_H__ +#define __MQTT_TASK_H__ + +typedef struct +{ + char *broker_ip; + char *broker_port; +} mqtt_config_t; + +void *mqtt_sub_task(void *arg); +void *mqtt_pub_task(void *arg); + +#endif \ No newline at end of file diff --git a/nanoMODBUS b/nanoMODBUS new file mode 160000 index 0000000..67bd7b2 --- /dev/null +++ b/nanoMODBUS @@ -0,0 +1 @@ +Subproject commit 67bd7b2075732c22fce5c89b5df3a21a4f5c8e12 diff --git a/openssl_3.2 b/openssl_3.2 new file mode 160000 index 0000000..7c01bb2 --- /dev/null +++ b/openssl_3.2 @@ -0,0 +1 @@ +Subproject commit 7c01bb25bcfecc15778114b47540035b06b46f0f diff --git a/platform.h b/platform.h new file mode 100644 index 0000000..9ed9f81 --- /dev/null +++ b/platform.h @@ -0,0 +1,272 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "nanomodbus.h" + +#define UNUSED_PARAM(x) ((x) = (x)) + +// Connection management + +int client_connection = -1; + +void *connect_tcp(const char *address, const char *port) +{ + struct addrinfo ainfo = {0}; + struct addrinfo *results; + struct addrinfo *rp; + int fd; + + ainfo.ai_family = AF_INET; + ainfo.ai_socktype = SOCK_STREAM; + int ret = getaddrinfo(address, port, &ainfo, &results); + if (ret != 0) + return NULL; + + for (rp = results; rp != NULL; rp = rp->ai_next) + { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) + continue; + + ret = connect(fd, rp->ai_addr, rp->ai_addrlen); + if (ret == 0) + break; + + close(fd); + } + + freeaddrinfo(results); + + if (rp == NULL) + return NULL; + + client_connection = fd; + return &client_connection; +} + +int server_fd = -1; +int client_read_fd = -1; +fd_set client_connections; + +void close_tcp_server() +{ + if (server_fd != -1) + { + close(server_fd); + printf("Server closed\n"); + } +} + +int create_tcp_server(const char *address, const char *port) +{ + struct addrinfo ainfo = {0}; + struct addrinfo *results; + struct addrinfo *rp; + int fd = -1; + + ainfo.ai_family = AF_INET; + ainfo.ai_socktype = SOCK_STREAM; + int ret = getaddrinfo(address, port, &ainfo, &results); + if (ret != 0) + return -1; + + for (rp = results; rp != NULL; rp = rp->ai_next) + { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) + continue; + + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); + if (ret != 0) + return -1; + + ret = bind(fd, rp->ai_addr, rp->ai_addrlen); + if (ret != 0) + { + close(fd); + fd = -1; + continue; + } + + ret = listen(fd, 1); + if (ret != 0) + { + close(fd); + fd = -1; + continue; + } + + break; + } + + freeaddrinfo(results); + + if (fd < 0) + { + return errno; + } + + server_fd = fd; + FD_ZERO(&client_connections); + + return 0; +} + +void *server_poll(void) +{ + fd_set read_fd_set; + FD_ZERO(&read_fd_set); + + while (true) + { + read_fd_set = client_connections; + FD_SET(server_fd, &read_fd_set); + + int ret = select(FD_SETSIZE, &read_fd_set, NULL, NULL, NULL); + if (ret < 0) + return NULL; + + for (int i = 0; i < FD_SETSIZE; ++i) + { + if (FD_ISSET(i, &read_fd_set)) + { + if (i == server_fd) + { + struct sockaddr_in client_addr; + socklen_t client_addr_size = sizeof(client_addr); + + int client = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_size); + if (client < 0) + { + fprintf(stderr, "Error accepting client connection from %s - %s\n", + inet_ntoa(client_addr.sin_addr), strerror(errno)); + continue; + } + + FD_SET(client, &client_connections); + printf("Accepted connection %d from %s\n", client, inet_ntoa(client_addr.sin_addr)); + } + else + { + client_read_fd = i; + return &client_read_fd; + } + } + } + } +} + +void disconnect(void *conn) +{ + int fd = *(int *)conn; + FD_CLR(fd, &client_connections); + close(fd); + printf("Closed connection %d\n", fd); +} + +// Read/write/sleep platform functions + +int32_t read_fd_linux(uint8_t *buf, uint16_t count, int32_t timeout_ms, void *arg) +{ + int fd = *(int *)arg; + + uint16_t total = 0; + while (total != count) + { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + struct timeval *tv_p = NULL; + struct timeval tv; + if (timeout_ms >= 0) + { + tv_p = &tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (int64_t)(timeout_ms % 1000) * 1000; + } + + int ret = select(fd + 1, &rfds, NULL, NULL, tv_p); + if (ret == 0) + { + return total; + } + + if (ret == 1) + { + ssize_t r = read(fd, buf + total, 1); + if (r == 0) + { + disconnect(arg); + return -1; + } + + if (r < 0) + return -1; + + total += r; + } + else + return -1; + } + + return total; +} + +int32_t write_fd_linux(const uint8_t *buf, uint16_t count, int32_t timeout_ms, void *arg) +{ + int fd = *(int *)arg; + + uint16_t total = 0; + while (total != count) + { + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(fd, &wfds); + + struct timeval *tv_p = NULL; + struct timeval tv; + if (timeout_ms >= 0) + { + tv_p = &tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (int64_t)(timeout_ms % 1000) * 1000; + } + + int ret = select(fd + 1, NULL, &wfds, NULL, tv_p); + if (ret == 0) + { + return 0; + } + + if (ret == 1) + { + ssize_t w = write(fd, buf + total, count); + if (w == 0) + { + disconnect(arg); + return -1; + } + + if (w <= 0) + return -1; + + total += (int32_t)w; + } + else + return -1; + } + + return total; +} diff --git a/utilis.c b/utilis.c new file mode 100644 index 0000000..0597ce8 --- /dev/null +++ b/utilis.c @@ -0,0 +1,25 @@ +#include "utilis.h" +#include +#include +#ifdef WIN32 +#include +#endif + +/** + * Cross-platform sleep function for C + * @param int milliseconds + */ +void sleep_ms(int milliseconds) +{ + #ifdef WIN32 + Sleep(milliseconds); + #elif _POSIX_C_SOURCE >= 199309L + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1000000; + nanosleep(&ts, NULL); + #else + usleep(milliseconds * 1000); + #endif +} + diff --git a/utilis.h b/utilis.h new file mode 100644 index 0000000..f2b420f --- /dev/null +++ b/utilis.h @@ -0,0 +1,10 @@ +#ifndef __UTILIS_H__ +#define __UTILIS_H__ + +/** + * Cross-platform sleep function for C + * @param int milliseconds + */ +void sleep_ms(int milliseconds); + +#endif \ No newline at end of file