diff --git a/build/Makefile b/build/Makefile index 61791a81..46a6b88a 100644 --- a/build/Makefile +++ b/build/Makefile @@ -6,38 +6,134 @@ ifndef MQTTCLIENT_DIR MQTTCLIENT_DIR = $(CURDIR) endif -MQTTLIB = mqttv3c -VPATH = ${MQTTCLIENT_DIR} -SOURCE_FILES = $(MQTTCLIENT_DIR)/*.c -HEADERS = $(MQTTCLIENT_DIR)/*.h - -ifeq ($(OS),Windows_NT) - OSTYPE = $(OS) -else - OSTYPE = $(shell uname -s) - MACHINETYPE = $(shell uname -m) +ifndef SSL_DIR + SSL_DIR = ${MQTTCLIENT_DIR}/../../../../source/openssl-1.0.1c/ endif +ifndef SAMPLES_DIR +SAMPLES_DIR = ${SSL_DIR}/../com.ibm.mq.mqxr.listener/SDK/clients/c/samples/ +endif + +MQTTLIB = mqttv3c +VPATH = ${MQTTCLIENT_DIR} +ALL_SOURCE_FILES = $(wildcard $(MQTTCLIENT_DIR)/*.c) +SOURCE_FILES = $(filter-out $(MQTTCLIENT_DIR)/MQTTAsync.c $(MQTTCLIENT_DIR)/MQTTVersion.c $(MQTTCLIENT_DIR)/SSLSocket.c, $(ALL_SOURCE_FILES)) +ALL_HEADERS = $(MQTTCLIENT_DIR)/*.h +HEADERS = $(filter-out $(MQTTCLIENT_DIR)/MQTTAsync.h, $(ALL_HEADERS)) + +MQTTLIB_S = mqttv3cs +SOURCE_FILES_S = $(filter-out $(MQTTCLIENT_DIR)/MQTTAsync.c $(MQTTCLIENT_DIR)/MQTTVersion.c, $(ALL_SOURCE_FILES)) + +MQTTLIB_A = mqttv3a +SOURCE_FILES_A = $(filter-out $(MQTTCLIENT_DIR)/MQTTClient.c $(MQTTCLIENT_DIR)/MQTTVersion.c $(MQTTCLIENT_DIR)/SSLSocket.c, $(ALL_SOURCE_FILES)) +HEADERS_A = $(MQTTCLIENT_DIR)/*.h + +MQTTLIB_AS = mqttv3as +SOURCE_FILES_AS = $(filter-out $(MQTTCLIENT_DIR)/MQTTClient.c $(MQTTCLIENT_DIR)/MQTTVersion.c, $(ALL_SOURCE_FILES)) + +# In most cases the targets will be built for the native platform. However, to allow for cross compiling, +# a target platform variable can be set to override the platform choice. If this variable is set to +# a recognised string (currently just 'Arm') the appropriate values of OSTYPE and MACHINETYPE are set. +ifeq ($(TARGET_PLATFORM),Arm) + OSTYPE = Linux + MACHINETYPE = Arm +else + ifeq ($(OS),Windows_NT) + OSTYPE = $(OS) + else + OSTYPE = $(shell uname -s) + MACHINETYPE = $(shell uname -m) + endif +endif ifeq ($(OSTYPE),Linux) ifeq ($(MACHINETYPE),x86_64) - CC = gcc + CC = gcc - CCFLAGS_SO = -fPIC -Os -Wall - CCFLAGS_EXE = -I ${MQTTCLIENT_DIR} - LDFLAGS = -shared -Wl,-soname,lib${MQTTLIB}.so - LDFLAGS_EXE = -lpthread + CCFLAGS_SO = -fPIC -Os -Wall -I ${SSL_DIR}/include + CCFLAGS_EXE = -I ${MQTTCLIENT_DIR} + LDFLAGS = -shared -Wl,-soname,lib${MQTTLIB}.so + LDFLAGS_S = -shared -Wl,-soname,lib${MQTTLIB_S}.so -ldl -Wl,-whole-archive -L${SSL_DIR}/output/lib -lcrypto -lssl -Wl,-no-whole-archive + LDFLAGS_A = -shared -Wl,-soname,lib${MQTTLIB_A}.so + LDFLAGS_AS = -shared -Wl,-soname,lib${MQTTLIB_AS}.so -ldl -Wl,-whole-archive -L${SSL_DIR}/output/lib -lcrypto -lssl -Wl,-no-whole-archive + LDFLAGS_EXE = -lpthread - MQTTLIB_IA64 = linux_ia64/lib${MQTTLIB}.so - - all: ${MQTTLIB_IA64} + MQTTLIB_IA64 = linux_ia64/lib${MQTTLIB}.so + MQTTLIB_IA64_S = linux_ia64/lib${MQTTLIB_S}.so + MQTTLIB_IA64_A = linux_ia64/lib${MQTTLIB_A}.so + MQTTLIB_IA64_AS = linux_ia64/lib${MQTTLIB_AS}.so + +ifdef BUILD_SAMPLES +all: linux_ia64 ${MQTTLIB_IA64} ${MQTTLIB_IA64_A} ${MQTTLIB_IA64_S} ${MQTTLIB_IA64_AS} samples +else +all: linux_ia64 ${MQTTLIB_IA64} ${MQTTLIB_IA64_A} ${MQTTLIB_IA64_S} ${MQTTLIB_IA64_AS} +endif + +linux_ia64: + -mkdir linux_ia64 + +samples: + -mkdir ${SAMPLES_DIR}/linux_ia64 + ${CC} -m64 -I ${MQTTCLIENT_DIR} -L ${MQTTCLIENT_DIR}/linux_ia64 -o ${SAMPLES_DIR}/linux_ia64/MQTTV3Sample ${SAMPLES_DIR}/MQTTV3Sample.c -lmqttv3c -lpthread + ${CC} -m64 -I ${MQTTCLIENT_DIR} -L ${MQTTCLIENT_DIR}/linux_ia64 -o ${SAMPLES_DIR}/linux_ia64/MQTTV3SSample ${SAMPLES_DIR}/MQTTV3SSample.c -lmqttv3cs -lpthread + ${CC} -m64 -I ${MQTTCLIENT_DIR} -L ${MQTTCLIENT_DIR}/linux_ia64 -o ${SAMPLES_DIR}/linux_ia64/MQTTV3ASample ${SAMPLES_DIR}/MQTTV3ASample.c -lmqttv3a -lpthread + ${CC} -m64 -I ${MQTTCLIENT_DIR} -L ${MQTTCLIENT_DIR}/linux_ia64 -o ${SAMPLES_DIR}/linux_ia64/MQTTV3ASSample ${SAMPLES_DIR}/MQTTV3ASSample.c -lmqttv3as -lpthread ${MQTTLIB_IA64}: ${SOURCE_FILES} ${HEADERS} - -mkdir linux_ia64 ${CC} -m64 ${CCFLAGS_SO} ${LDFLAGS} -o $@ ${SOURCE_FILES} strip $@ +${MQTTLIB_IA64_S}: ${SOURCE_FILES_S} ${HEADERS} + ${CC} -m64 ${CCFLAGS_SO} ${LDFLAGS_S} -o $@ ${SOURCE_FILES_S} -DOPENSSL + strip $@ + +${MQTTLIB_IA64_A}: ${SOURCE_FILES_A} ${HEADERS_A} + ${CC} -m64 ${CCFLAGS_SO} ${LDFLAGS_A} -o $@ ${SOURCE_FILES_A} + strip $@ + +${MQTTLIB_IA64_AS}: ${SOURCE_FILES_AS} ${HEADERS_A} + ${CC} -m64 ${CCFLAGS_SO} ${LDFLAGS_AS} -o $@ ${SOURCE_FILES_AS} -DOPENSSL + strip $@ + +else +ifeq ($(MACHINETYPE),ppc64) + CC = gcc + + CCFLAGS_SO = -fPIC -Os -Wall -I ${SSL_DIR}/include + CCFLAGS_EXE = -I ${MQTTCLIENT_DIR} + LDFLAGS = -shared -Wl,-soname,lib${MQTTLIB}.so + LDFLAGS_S = -shared -Wl,-soname,lib${MQTTLIB_S}.so -ldl -Wl,-whole-archive -L${SSL_DIR}/output/lib -lcrypto -lssl -Wl,-no-whole-archive + LDFLAGS_A = -shared -Wl,-soname,lib${MQTTLIB_A}.so + LDFLAGS_AS = -shared -Wl,-soname,lib${MQTTLIB_AS}.so -ldl -Wl,-whole-archive -L${SSL_DIR}/output/lib -lcrypto -lssl -Wl,-no-whole-archive + LDFLAGS_EXE = -lpthread + + MQTTLIB_PPC64 = linux_ppc64/lib${MQTTLIB}.so + MQTTLIB_PPC64_S = linux_ppc64/lib${MQTTLIB_S}.so + MQTTLIB_PPC64_A = linux_ppc64/lib${MQTTLIB_A}.so + MQTTLIB_PPC64_AS = linux_ppc64/lib${MQTTLIB_AS}.so + +all: linux_ppc64 ${MQTTLIB_PPC64} ${MQTTLIB_PPC64_A} #${MQTTLIB_PPC64_S} ${MQTTLIB_PPC64_AS} + +linux_ppc64: + -mkdir linux_ppc64 + +${MQTTLIB_PPC64}: ${SOURCE_FILES} ${HEADERS} + ${CC} -m64 ${CCFLAGS_SO} ${LDFLAGS} -o $@ ${SOURCE_FILES} + strip $@ + +${MQTTLIB_PPC64_S}: ${SOURCE_FILES_S} ${HEADERS} + ${CC} -m64 ${CCFLAGS_SO} ${LDFLAGS_S} -o $@ ${SOURCE_FILES_S} -DOPENSSL + strip $@ + +${MQTTLIB_PPC64_A}: ${SOURCE_FILES_A} ${HEADERS_A} + ${CC} -m64 ${CCFLAGS_SO} ${LDFLAGS_A} -o $@ ${SOURCE_FILES_A} + strip $@ + +${MQTTLIB_PPC64_AS}: ${SOURCE_FILES_AS} ${HEADERS_A} + ${CC} -m64 ${CCFLAGS_SO} ${LDFLAGS_AS} -o $@ ${SOURCE_FILES_AS} -DOPENSSL + strip $@ + else ifeq ($(MACHINETYPE),s390x) CC = gcc @@ -57,35 +153,101 @@ ${MQTTLIB_390}: ${SOURCE_FILES} ${HEADERS} strip $@ else - CC = gcc - ARM_GLIBC_PREFIX = arm-linux- - ARM_UCLIBC_PREFIX = ~/x-tools/arm-unknown-linux-uclibcgnueabi/bin/arm-unknown-linux-uclibcgnueabi- +ifeq ($(MACHINETYPE),Arm) - CCFLAGS_SO = -fPIC -Os -Wall - CCFLAGS_EXE = -I ${MQTTCLIENT_DIR} - LDFLAGS = -shared -Wl,-soname,lib${MQTTLIB}.so - LDFLAGS_EXE = -lpthread + CC = gcc + # ARM_GLIBC_CC = arm-linux-gcc + # ARM_UCLIBC_CC = ~/x-tools/arm-unknown-linux-uclibcgnueabi/bin/arm-unknown-linux-uclibcgnueabi-gcc - MQTTLIB_IA32 = linux_ia32/lib${MQTTLIB}.so - MQTTLIB_ARM_GLIBC = linux_ARM_glibc/lib${MQTTLIB}.so - MQTTLIB_ARM_UCLIBC = linux_ARM_uclibc/lib${MQTTLIB}.so + ifndef ARM_GLIBC_CC + ARM_GLIBC_CC = arm-linux-gcc + endif + ifndef ARM_UCLIBC_CC + ARM_UCLIBC_CC = arm-unknown-linux-uclibcgnueabi-gcc + endif -all: ${MQTTLIB_IA32} ${MQTTLIB_ARM_GLIBC} ${MQTTLIB_ARM_UCLIBC} + ifndef ARM_GLIBC_STRIP + ARM_GLIBC_STRIP = $(subst -gcc,-strip,${ARM_GLIBC_CC}) + endif + ifndef ARM_UCLIBC_STRIP + ARM_UCLIBC_STRIP = $(subst -gcc,-strip,${ARM_UCLIBC_CC}) + endif + + CCFLAGS_SO = -fPIC -Os -Wall + CCFLAGS_EXE = -I ${MQTTCLIENT_DIR} + LDFLAGS = -shared -Wl,-soname,lib${MQTTLIB}.so + LDFLAGS_EXE = -lpthread + + MQTTLIB_ARM_GLIBC = linux_ARM_glibc/lib${MQTTLIB}.so + MQTTLIB_ARM_UCLIBC = linux_ARM_uclibc/lib${MQTTLIB}.so + + MQTTLIB_ARM_GLIBC_A = linux_ARM_glibc/lib${MQTTLIB_A}.so + MQTTLIB_ARM_UCLIBC_A = linux_ARM_uclibc/lib${MQTTLIB_A}.so + +all: ${MQTTLIB_ARM_GLIBC} ${MQTTLIB_ARM_UCLIBC} -${MQTTLIB_IA32}: ${SOURCE_FILES} ${HEADERS} - -mkdir linux_ia32 - ${CC} -m32 ${CCFLAGS_SO} ${LDFLAGS} -o $@ ${SOURCE_FILES} - strip $@ - ${MQTTLIB_ARM_GLIBC}: ${SOURCE_FILES} ${HEADERS} -mkdir linux_ARM_glibc - ${ARM_GLIBC_PREFIX}gcc ${CCFLAGS_SO} ${LDFLAGS} -o $@ ${SOURCE_FILES} - ${ARM_GLIBC_PREFIX}strip $@ + ${ARM_GLIBC_CC} ${CCFLAGS_SO} ${LDFLAGS} -o $@ ${SOURCE_FILES} + ${ARM_GLIBC_STRIP} $@ ${MQTTLIB_ARM_UCLIBC}: ${SOURCE_FILES} ${HEADERS} -mkdir linux_ARM_uclibc - ${ARM_UCLIBC_PREFIX}gcc ${CCFLAGS_SO} ${LDFLAGS} -o $@ ${SOURCE_FILES} - ${ARM_UCLIBC_PREFIX}strip $@ + ${ARM_UCLIBC_CC} ${CCFLAGS_SO} ${LDFLAGS} -o $@ ${SOURCE_FILES} + ${ARM_UCLIBC_STRIP} $@ + +else + + CC = gcc + + CCFLAGS_SO = -fPIC -Os -Wall -I ${SSL_DIR}/include + CCFLAGS_EXE = -I ${MQTTCLIENT_DIR} + LDFLAGS = -shared -Wl,-soname,lib${MQTTLIB}.so + LDFLAGS_S = -shared -Wl,-soname,lib${MQTTLIB_S}.so -ldl -Wl,-whole-archive -L${SSL_DIR}/output/lib -lcrypto -lssl -Wl,-no-whole-archive + LDFLAGS_A = -shared -Wl,-soname,lib${MQTTLIB_A}.so + LDFLAGS_AS = -shared -Wl,-soname,lib${MQTTLIB_AS}.so -ldl -Wl,-whole-archive -L/${SSL_DIR}/output/lib -lcrypto -lssl -Wl,-no-whole-archive + LDFLAGS_EXE = -lpthread + + MQTTLIB_IA32 = linux_ia32/lib${MQTTLIB}.so + MQTTLIB_IA32_S = linux_ia32/lib${MQTTLIB_S}.so + MQTTLIB_IA32_A = linux_ia32/lib${MQTTLIB_A}.so + MQTTLIB_IA32_AS = linux_ia32/lib${MQTTLIB_AS}.so + +ifdef BUILD_SAMPLES +all: linux_ia32 ${MQTTLIB_IA32_A} ${MQTTLIB_IA32} ${MQTTLIB_IA32_S} ${MQTTLIB_IA32_AS} samples +else +all: linux_ia32 ${MQTTLIB_IA32_A} ${MQTTLIB_IA32} ${MQTTLIB_IA32_S} ${MQTTLIB_IA32_AS} +endif + +samples: + -mkdir ${SAMPLES_DIR}/linux_ia32 + ${CC} -m32 -I ${MQTTCLIENT_DIR} -L ${MQTTCLIENT_DIR}/linux_ia32 -o ${SAMPLES_DIR}/linux_ia32/MQTTV3Sample ${SAMPLES_DIR}/MQTTV3Sample.c -lmqttv3c -lpthread + ${CC} -m32 -I ${MQTTCLIENT_DIR} -L ${MQTTCLIENT_DIR}/linux_ia32 -o ${SAMPLES_DIR}/linux_ia32/MQTTV3SSample ${SAMPLES_DIR}/MQTTV3SSample.c -lmqttv3cs -lpthread + ${CC} -m32 -I ${MQTTCLIENT_DIR} -L ${MQTTCLIENT_DIR}/linux_ia32 -o ${SAMPLES_DIR}/linux_ia32/MQTTV3ASample ${SAMPLES_DIR}/MQTTV3ASample.c -lmqttv3a -lpthread + ${CC} -m32 -I ${MQTTCLIENT_DIR} -L ${MQTTCLIENT_DIR}/linux_ia32 -o ${SAMPLES_DIR}/linux_ia32/MQTTV3ASSample ${SAMPLES_DIR}/MQTTV3ASSample.c -lmqttv3as -lpthread + +linux_ia32: + -mkdir linux_ia32 + +${MQTTLIB_IA32}: ${SOURCE_FILES} ${HEADERS} + ${CC} -m32 ${CCFLAGS_SO} ${LDFLAGS} -o $@ ${SOURCE_FILES} + strip $@ + +${MQTTLIB_IA32_S}: ${SOURCE_FILES_S} ${HEADERS} + ${CC} -m32 ${CCFLAGS_SO} ${LDFLAGS_S} -o $@ ${SOURCE_FILES_S} -DOPENSSL + strip $@ + +${MQTTLIB_IA32_A}: ${SOURCE_FILES_A} ${HEADERS_A} + ${CC} -m32 ${CCFLAGS_SO} ${LDFLAGS_A} -o $@ ${SOURCE_FILES_A} + strip $@ + +${MQTTLIB_IA32_AS}: ${SOURCE_FILES_AS} ${HEADERS_A} + ${CC} -m32 ${CCFLAGS_SO} ${LDFLAGS_AS} -o $@ ${SOURCE_FILES_AS} -DOPENSSL + strip $@ +endif + +endif + endif endif @@ -95,9 +257,9 @@ endif ifeq ($(OSTYPE),AIX) # applications must be linked with -I. -L. -lmqttv3c -Wl,-brtl -pthread - CC = gcc + CC = gxlc - CCFLAGS_SO = -fPIC -Os -Wall -shared -DREVERSED + CCFLAGS_SO = -fPIC -Os -shared -DREVERSED -DAIX LDFLAGS = -Wl,-G MQTTLIB_AIX = aix/lib${MQTTLIB}.so @@ -118,16 +280,20 @@ ifeq ($(OSTYPE),Windows_NT) # compiler flags CPPFLAGS = /D "WIN32" /D "_UNICODE" /D "UNICODE" /D "_CRT_SECURE_NO_WARNINGS" CFLAGS = /nologo /c /O2 /W3 /Fd /MD /TC + CPPFLAGS_S = /D "OPENSSL" /I ${SSL_DIR}/include # linker flags LINKFLAGS = /nologo /machine:x86 /manifest WINLIBS = kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib ws2_32.lib + WINLIBS_S = crypt32.lib ssleay32.lib libeay32.lib + SSLLIBPATH = /LIBPATH:${SSL_DIR}/output/lib # Flags to link C libraries LIBLINKFLAGS = /dll IMP = /implib:$(@:.dll=.lib) LIBPDB = /pdb:$(@:.dll=.pdb) LIBMAP = /map:$(@:.dll=.map) LIBLDFLAGS = $(LINKFLAGS) $(WINLIBS) $(LIBLINKFLAGS) $(IMP) $(LIBPDB) $(LIBMAP) + LIBLDFLAGS_S = $(LINKFLAGS) $(SSLLIBPATH) $(WINLIBS) $(WINLIBS_S) $(LIBLINKFLAGS) $(IMP) $(LIBPDB) $(LIBMAP) # Flags to link C programs MAINLINKFLAGS = /subsystem:console EXEPDB = /pdb:$(@:.exe=.pdb) @@ -136,14 +302,64 @@ ifeq ($(OSTYPE),Windows_NT) MQTTLDLIB = windows_ia32/$(MQTTLIB).lib MQTTDLL = windows_ia32/$(MQTTLIB).dll + MQTTLDLIB_A = windows_ia32/$(MQTTLIB_A).lib + MQTTDLL_A = windows_ia32/$(MQTTLIB_A).dll + MQTTLDLIB_AS = windows_ia32/$(MQTTLIB_AS).lib + MQTTDLL_AS = windows_ia32/$(MQTTLIB_AS).dll + MQTTLDLIB_S = windows_ia32/$(MQTTLIB_S).lib + MQTTDLL_S = windows_ia32/$(MQTTLIB_S).dll -all: ${MQTTDLL} +ifdef BUILD_SAMPLES +all: windows_ia32 ${MQTTDLL} ${MQTTDLL_A} ${MQTTDLL_AS} ${MQTTDLL_S} samples +else +all: windows_ia32 ${MQTTDLL} ${MQTTDLL_A} ${MQTTDLL_AS} ${MQTTDLL_S} +endif -$(MQTTDLL): $(shell cygpath -au ${SOURCE_FILES}) $(shell cygpath -au ${HEADERS}) +samples: + -mkdir ${SAMPLES_DIR}/windows_ia32 + ${CC} /nologo /D "WIN32" /I ${MQTTCLIENT_DIR} ${SAMPLES_DIR}/MQTTV3Sample.c /Fe${SAMPLES_DIR}/windows_ia32/ /link /LIBPATH:${MQTTCLIENT_DIR}/windows_ia32 /nologo /subsystem:console /manifest /machine:x86 mqttv3c.lib + ${CC} /nologo /D "WIN32" /I ${MQTTCLIENT_DIR} ${SAMPLES_DIR}/MQTTV3CSSample.c /Fe${SAMPLES_DIR}/windows_ia32/ /link /LIBPATH:${MQTTCLIENT_DIR}/windows_ia32 /nologo /subsystem:console /manifest /machine:x86 mqttv3cs.lib + ${CC} /nologo /D "WIN32" /I ${MQTTCLIENT_DIR} ${SAMPLES_DIR}/MQTTV3ASample.c /Fe${SAMPLES_DIR}/windows_ia32/ /link /LIBPATH:${MQTTCLIENT_DIR}/windows_ia32 /nologo /subsystem:console /manifest /machine:x86 mqttv3a.lib + ${CC} /nologo /D "WIN32" /I ${MQTTCLIENT_DIR} ${SAMPLES_DIR}/MQTTV3ASSample.c /Fe${SAMPLES_DIR}/windows_ia32/ /link /LIBPATH:${MQTTCLIENT_DIR}/windows_ia32 /nologo /subsystem:console /manifest /machine:x86 mqttv3as.lib + +windows_ia32: -mkdir windows_ia32 - ${CC} ${CPPFLAGS} ${CFLAGS} /I $(shell cygpath -am ${MQTTCLIENT_DIR}) /I $(shell cygpath -am ${MQTTCLIENT_DIR})/.. /Fo $(shell cygpath -am ${SOURCE_FILES}) + +$(MQTTDLL): ${SOURCE_FILES} ${HEADERS} + -rm -f *.obj + ${CC} ${CPPFLAGS} ${CFLAGS} /I ${MQTTCLIENT_DIR} /I ${MQTTCLIENT_DIR}/.. /Fo ${SOURCE_FILES} $(LD) $(LIBLDFLAGS) *.obj /out:$(MQTTDLL) mt -manifest windows_ia32/mqttv3c.dll.manifest -outputresource:$(MQTTDLL)\;2 +$(MQTTDLL_A): ${SOURCE_FILES_A} ${HEADERS_A} + -rm -f *.obj + ${CC} ${CPPFLAGS} ${CFLAGS} /I ${MQTTCLIENT_DIR} /I ${MQTTCLIENT_DIR}/.. /Fo ${SOURCE_FILES_A} + $(LD) $(LIBLDFLAGS) *.obj /out:$(MQTTDLL_A) + mt -manifest windows_ia32/mqttv3a.dll.manifest -outputresource:$(MQTTDLL_A)\;2 + +$(MQTTDLL_S): ${SOURCE_FILES_S} ${HEADERS} + -rm -f *.obj + ${CC} ${CPPFLAGS} ${CFLAGS} ${CPPFLAGS_S} /I ${MQTTCLIENT_DIR} /I ${MQTTCLIENT_DIR}/.. /Fo ${SOURCE_FILES_S} + $(LD) $(LIBLDFLAGS_S) *.obj /out:$(MQTTDLL_S) + mt -manifest windows_ia32/mqttv3cs.dll.manifest -outputresource:$(MQTTDLL_S)\;2 + +$(MQTTDLL_AS): ${SOURCE_FILES} ${HEADERS} + -rm -f *.obj + ${CC} ${CPPFLAGS} ${CFLAGS} ${CPPFLAGS_S} /I ${MQTTCLIENT_DIR} /I ${MQTTCLIENT_DIR}/.. /Fo ${SOURCE_FILES_AS} + $(LD) $(LIBLDFLAGS_S) *.obj /out:$(MQTTDLL_AS) + mt -manifest windows_ia32/mqttv3as.dll.manifest -outputresource:$(MQTTDLL_AS)\;2 + endif +######################################################################################################### +# The following was how the C Clients were built using cygwin GNU make and has been left here as a reference +# This has been converted to use ming GNU make (which uses the native format of file names) +# +#$(MQTTDLL): $(shell cygpath -au ${SOURCE_FILES}) $(shell cygpath -au ${HEADERS}) +# -mkdir windows_ia32 +# ${CC} ${CPPFLAGS} ${CFLAGS} /I $(shell cygpath -am ${MQTTCLIENT_DIR}) /I $(shell cygpath -am ${MQTTCLIENT_DIR})/.. /Fo $(shell cygpath -am ${SOURCE_FILES}) +# $(LD) $(LIBLDFLAGS) *.obj /out:$(MQTTDLL) +# mt -manifest windows_ia32/mqttv3c.dll.manifest -outputresource:$(MQTTDLL)\;2 +# +#endif +######################################################################################################### diff --git a/src/Clients.c b/src/Clients.c index 9f6dbc02..01280a89 100644 --- a/src/Clients.c +++ b/src/Clients.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - add SSL support *******************************************************************************/ /** @@ -46,5 +47,5 @@ int clientSocketCompare(void* a, void* b) { Clients* client = (Clients*)a; /*printf("comparing %d with %d\n", (char*)a, (char*)b); */ - return client->socket == *(int*)b; + return client->net.socket == *(int*)b; } diff --git a/src/Clients.h b/src/Clients.h index 6a7ea6fe..bf9ced26 100644 --- a/src/Clients.h +++ b/src/Clients.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,12 +8,20 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - add SSL support *******************************************************************************/ #if !defined(CLIENTS_H) #define CLIENTS_H #include +#if defined(OPENSSL) +#if defined(WIN32) +#include "winsock2.h" +#endif +#include +#endif +#include "MQTTClient.h" #include "LinkedList.h" #include "MQTTClientPersistence.h" /*BE @@ -116,6 +124,7 @@ def CLIENTS at 4 n8 bits 7:6 dec "connect_state" at 8 n32 dec "socket" + n32 ptr "SSL" n32 dec "msgID" n32 dec "keepAliveInterval" n32 dec "maxInflightMessages" @@ -132,6 +141,15 @@ defList(CLIENTS) BE*/ +typedef struct +{ + int socket; +#if defined(OPENSSL) + SSL* ssl; + SSL_CTX* ctx; +#endif +} networkHandles; + /** * Data related to one client */ @@ -144,8 +162,8 @@ typedef struct unsigned int connected : 1; /**< whether it is currently connected */ unsigned int good : 1; /**< if we have an error on the socket we turn this off */ unsigned int ping_outstanding : 1; - unsigned int connect_state : 2; - int socket; + int connect_state : 4; + networkHandles net; int msgID; int keepAliveInterval; int retryInterval; @@ -155,9 +173,13 @@ typedef struct List* inboundMsgs; List* outboundMsgs; /**< in flight */ List* messageQueue; + unsigned int qentry_seqno; void* phandle; /* the persistence handle */ MQTTClient_persistence* persistence; /* a persistence implementation */ - int connectOptionsVersion; +#if defined(OPENSSL) + MQTTClient_SSLOptions *sslopts; + SSL_SESSION* session; /***< SSL session pointer for fast handhake */ +#endif } Clients; int clientIDCompare(void* a, void* b); diff --git a/src/Heap.c b/src/Heap.c index a8dd8403..302056fb 100644 --- a/src/Heap.c +++ b/src/Heap.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,9 +8,9 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - use tree data structure instead of list *******************************************************************************/ - /** * @file * \brief functions to manage the heap with the goal of eliminating memory leaks @@ -21,7 +21,7 @@ * * */ -#include "LinkedList.h" +#include "Tree.h" #include "Log.h" #include "StackTrace.h" #include "Thread.h" @@ -30,6 +30,7 @@ #include #include #include +#include #include "Heap.h" @@ -37,20 +38,88 @@ #undef realloc #undef free -static heap_info state = {0, 0}; +#if defined(WIN32) +mutex_type heap_mutex; +#else +static pthread_mutex_t heap_mutex_store = PTHREAD_MUTEX_INITIALIZER; +static mutex_type heap_mutex = &heap_mutex_store; +#endif -int ListRemoveCurrentItem(List* aList); +static heap_info state = {0, 0}; /**< global heap state information */ +static int eyecatcher = 0x88888888; +/** + * Each item on the heap is recorded with this structure. + */ typedef struct { - char* file; - int line; - void* ptr; - size_t size; - char* stack; + char* file; /**< the name of the source file where the storage was allocated */ + int line; /**< the line no in the source file where it was allocated */ + void* ptr; /**< pointer to the allocated storage */ + int size; /**< size of the allocated storage */ } storageElement; -static List heap = {NULL, NULL, NULL}; +static Tree heap; /**< Tree that holds the allocation records */ +static char* errmsg = "Memory allocation error"; + +/** + * Round allocation size up to a multiple of the size of an int. Apart from possibly reducing fragmentation, + * on the old v3 gcc compilers I was hitting some weird behaviour, which might have been errors in + * sizeof() used on structures and related to packing. In any case, this fixes that too. + * @param size the size actually needed + * @return the rounded up size + */ +int roundup(int size) +{ + static int multsize = 4*sizeof(int); + + if (size % multsize != 0) + size += multsize - (size % multsize); + return size; +} + + +/** + * List callback function for comparing storage elements + * @param a pointer to the current content in the tree (storageElement*) + * @param b pointer to the memory to free + * @return boolean indicating whether a and b are equal + */ +int ptrCompare(void* a, void* b, int value) +{ + a = ((storageElement*)a)->ptr; + if (value) + b = ((storageElement*)b)->ptr; + + return (a > b) ? -1 : (a == b) ? 0 : 1; +} + + +void Heap_check(char* string, void* ptr) +{ + return; + /*Node* curnode = NULL; + storageElement* prev, *s = NULL; + + printf("Heap_check start %p\n", ptr); + while ((curnode = TreeNextElement(&heap, curnode)) != NULL) + { + prev = s; + s = (storageElement*)(curnode->content); + + if (prev) + { + if (ptrCompare(s, prev, 1) != -1) + { + printf("%s: heap order error %d %p %p\n", string, ptrCompare(s, prev, 1), prev->ptr, s->ptr); + exit(99); + } + else + printf("%s: heap order good %d %p %p\n", string, ptrCompare(s, prev, 1), prev->ptr, s->ptr); + } + }*/ +} + /** * Allocates a block of memory. A direct replacement for malloc, but keeps track of items @@ -63,90 +132,61 @@ static List heap = {NULL, NULL, NULL}; */ void* mymalloc(char* file, int line, size_t size) { - ListElement* e = NULL; storageElement* s = NULL; - static char* errmsg = "Memory allocation error"; + int space = sizeof(storageElement); + int filenamelen = strlen(file)+1; + Thread_lock_mutex(heap_mutex); + size = roundup(size); if ((s = malloc(sizeof(storageElement))) == NULL) { - Log(LOG_ERROR, -1, errmsg); + Log(LOG_ERROR, 13, errmsg); return NULL; } - if ((s->file = malloc(strlen(file)+1)) == NULL) + s->size = size; /* size without eyecatchers */ + if ((s->file = malloc(filenamelen)) == NULL) { - Log(LOG_ERROR, -1, errmsg); + Log(LOG_ERROR, 13, errmsg); free(s); return NULL; } + space += filenamelen; strcpy(s->file, file); s->line = line; - if ((s->ptr = malloc(size)) == NULL) + /* Add space for eyecatcher at each end */ + if ((s->ptr = malloc(size + 2*sizeof(int))) == NULL) { - Log(LOG_ERROR, -1, errmsg); + Log(LOG_ERROR, 13, errmsg); free(s->file); free(s); return NULL; } - s->size = size; - s->stack = NULL; - if (trace_settings.trace_level == TRACE_MAXIMUM) - { - if ((s->stack = StackTrace_get(Thread_getid())) == NULL) - { - Log(LOG_ERROR, -1, errmsg); - free(s->ptr); - free(s->file); - free(s); - return NULL; - } - else - { - Log(TRACE_MAX, -1, "Allocating %d bytes in heap at line %d of file %s, ptr %p, heap use now %d bytes", - s->size, line, file, s->ptr, state.current_size); - Log(TRACE_MAX, -1, "Stack trace is %s", s->stack); - } - } - if ((e = malloc(sizeof(ListElement))) == NULL) - { - Log(LOG_ERROR, -1, errmsg); - if (s->stack) - free(s->stack); - free(s->ptr); - free(s->file); - free(s); - return NULL; - } - ListAppendNoMalloc(&heap, s, e, sizeof(ListElement)); + space += size + 2*sizeof(int); + *(int*)(s->ptr) = eyecatcher; /* start eyecatcher */ + *(int*)(((char*)(s->ptr)) + (sizeof(int) + size)) = eyecatcher; /* end eyecatcher */ + Log(TRACE_MAX, -1, "Allocating %d bytes in heap at file %s line %d ptr %p\n", size, file, line, s->ptr); + TreeAdd(&heap, s, space); state.current_size += size; if (state.current_size > state.max_size) state.max_size = state.current_size; - return s->ptr; + Thread_unlock_mutex(heap_mutex); + return ((int*)(s->ptr)) + 1; /* skip start eyecatcher */ } -/** - * List callback function for comparing storage elements - * @param a first integer value - * @param b second integer value - * @return boolean indicating whether a and b are equal - */ -int ptrCompare(void* a, void* b) +void checkEyecatchers(char* file, int line, void* p, int size) { - storageElement* s = (storageElement*)a; - return s->ptr == b; -} + int *sp = (int*)p; + char *cp = (char*)p; + int us; + static char* msg = "Invalid %s eyecatcher %d in heap item at file %s line %d"; + if ((us = *--sp) != eyecatcher) + Log(LOG_ERROR, 13, msg, "start", us, file, line); -/** - * Utility to find an item in the heap. Lets you know if the heap already contains - * the memory location in question. - * @param p pointer to a memory location - * @return pointer to the storage element if found, or NULL - */ -void* Heap_findItem(void* p) -{ - ListElement* e = ListFindItem(&heap, p, ptrCompare); - return (e == NULL) ? NULL : e->content; + cp += size; + if ((us = *(int*)cp) != eyecatcher) + Log(LOG_ERROR, 13, msg, "end", us, file, line); } @@ -157,36 +197,28 @@ void* Heap_findItem(void* p) * @param line use the __LINE__ macro to indicate which line this item was allocated at * @param p pointer to the item to be removed */ -void Internal_heap_unlink(char* file, int line, void* p) +int Internal_heap_unlink(char* file, int line, void* p) { - ListElement* e = NULL; + Node* e = NULL; + int rc = 0; - if ((e = ListFindItem(&heap, p, ptrCompare)) == NULL) - Log(LOG_ERROR, -1, "Failed to remove heap item at file %s line %d", file, line); + e = TreeFind(&heap, ((int*)p)-1); + if (e == NULL) + Log(LOG_ERROR, 13, "Failed to remove heap item at file %s line %d", file, line); else { - storageElement* s = (storageElement*)(heap.current->content); - Log(TRACE_MAX, -1, "Freeing %d bytes in heap at file %s line %d, heap use now %d bytes", + storageElement* s = (storageElement*)(e->content); + Log(TRACE_MAX, -1, "Freeing %d bytes in heap at file %s line %d, heap use now %d bytes\n", s->size, file, line, state.current_size); + checkEyecatchers(file, line, p, s->size); + //free(s->ptr); free(s->file); - if (s->stack) - free(s->stack); state.current_size -= s->size; - ListRemoveCurrentItem(&heap); + TreeRemoveNodeIndex(&heap, e, 0); + free(s); + rc = 1; } -} - - -/** - * Remove an item from the recorded heap without actually freeing it. - * Use sparingly! - * @param file use the __FILE__ macro to indicate which file this item was allocated in - * @param line use the __LINE__ macro to indicate which line this item was allocated at - * @param p pointer to the item to be removed - */ -void Heap_unlink(char* file, int line, void* p) -{ - Internal_heap_unlink(file, line, p); + return rc; } @@ -199,8 +231,25 @@ void Heap_unlink(char* file, int line, void* p) */ void myfree(char* file, int line, void* p) { + Thread_lock_mutex(heap_mutex); + if (Internal_heap_unlink(file, line, p)) + free(((int*)p)-1); + Thread_unlock_mutex(heap_mutex); +} + + +/** + * Remove an item from the recorded heap without actually freeing it. + * Use sparingly! + * @param file use the __FILE__ macro to indicate which file this item was allocated in + * @param line use the __LINE__ macro to indicate which line this item was allocated at + * @param p pointer to the item to be removed + */ +void Heap_unlink(char* file, int line, void* p) +{ + Thread_lock_mutex(heap_mutex); Internal_heap_unlink(file, line, p); - free(p); + Thread_unlock_mutex(heap_mutex); } @@ -208,6 +257,8 @@ void myfree(char* file, int line, void* p) * Reallocates a block of memory. A direct replacement for realloc, but keeps track of items * allocated in a list, so that free can check that a item is being freed correctly and that * we can check that all memory is freed at shutdown. + * We have to remove the item from the tree, as the memory is in order and so it needs to + * be reinserted in the correct place. * @param file use the __FILE__ macro to indicate which file this item was reallocated in * @param line use the __LINE__ macro to indicate which line this item was reallocated at * @param p pointer to the item to be reallocated @@ -217,56 +268,58 @@ void myfree(char* file, int line, void* p) void *myrealloc(char* file, int line, void* p, size_t size) { void* rc = NULL; - - ListElement* e = ListFindItem(&heap, p, ptrCompare); - if (e == NULL) - Log(LOG_ERROR, -1, "Failed to reallocate heap item at file %s line %d", file, line); + storageElement* s = NULL; + + Thread_lock_mutex(heap_mutex); + s = TreeRemoveKey(&heap, ((int*)p)-1); + if (s == NULL) + Log(LOG_ERROR, 13, "Failed to reallocate heap item at file %s line %d", file, line); else { - storageElement* s = (storageElement*)(heap.current->content); + int space = sizeof(storageElement); + int filenamelen = strlen(file)+1; + + checkEyecatchers(file, line, p, s->size); + size = roundup(size); state.current_size += size - s->size; if (state.current_size > state.max_size) state.max_size = state.current_size; - rc = s->ptr = realloc(s->ptr, size); + if ((s->ptr = realloc(s->ptr, size + 2*sizeof(int))) == NULL) + { + Log(LOG_ERROR, 13, errmsg); + return NULL; + } + space += size + 2*sizeof(int) - s->size; + *(int*)(s->ptr) = eyecatcher; /* start eyecatcher */ + *(int*)(((char*)(s->ptr)) + (sizeof(int) + size)) = eyecatcher; /* end eyecatcher */ s->size = size; - s->file = realloc(s->file, strlen(file)+1); + space -= strlen(s->file); + s->file = realloc(s->file, filenamelen); + space += filenamelen; strcpy(s->file, file); s->line = line; - if (s->stack) - { - free(s->stack); - s->stack = StackTrace_get(Thread_getid()); - } + rc = s->ptr; + TreeAdd(&heap, s, space); } - return rc; + Thread_unlock_mutex(heap_mutex); + return (rc == NULL) ? NULL : ((int*)(rc)) + 1; /* skip start eyecatcher */ } /** - * Removes and frees the current item in a list. A list function only used by myfree. - * @param aList the list from which the item is to be removed - * @return 1=item removed, 0=item not removed + * Utility to find an item in the heap. Lets you know if the heap already contains + * the memory location in question. + * @param p pointer to a memory location + * @return pointer to the storage element if found, or NULL */ -int ListRemoveCurrentItem(List* aList) +void* Heap_findItem(void* p) { - ListElement* next = NULL; + Node* e = NULL; - if (aList->current->prev == NULL) - /* so this is the first element, and we have to update the "first" pointer */ - aList->first = aList->current->next; - else - aList->current->prev->next = aList->current->next; - - if (aList->current->next == NULL) - aList->last = aList->current->prev; - else - aList->current->next->prev = aList->current->prev; - - next = aList->current->next; - free(aList->current->content); - free(aList->current); - aList->current = next; - return 1; /* successfully removed item */ + Thread_lock_mutex(heap_mutex); + e = TreeFind(&heap, ((int*)p)-1); + Thread_unlock_mutex(heap_mutex); + return (e == NULL) ? NULL : e->content; } @@ -274,19 +327,20 @@ int ListRemoveCurrentItem(List* aList) * Scans the heap and reports any items currently allocated. * To be used at shutdown if any heap items have not been freed. */ -void HeapScan() +void HeapScan(int log_level) { - ListElement* current = NULL; - Log(TRACE_MIN, -1, "Heap scan start, total %d bytes", state.current_size); - while (ListNextElement(&heap, ¤t)) + Node* current = NULL; + + Thread_lock_mutex(heap_mutex); + Log(log_level, -1, "Heap scan start, total %d bytes", state.current_size); + while ((current = TreeNextElement(&heap, current)) != NULL) { storageElement* s = (storageElement*)(current->content); - Log(TRACE_MIN, -1, "Heap element size %d, line %d, file %s, ptr %p", s->size, s->line, s->file, s->ptr); - Log(TRACE_MIN, -1, " Content %*.s", (10 > s->size) ? s->size : 10, (char*)s->ptr); - if (s->stack) - Log(TRACE_MIN, -1, " Stack trace: %s", s->stack); + Log(log_level, -1, "Heap element size %d, line %d, file %s, ptr %p", s->size, s->line, s->file, s->ptr); + Log(log_level, -1, " Content %*.s", (10 > current->size) ? s->size : 10, (char*)(((int*)s->ptr) + 1)); } - Log(TRACE_MIN, -1, "Heap scan end"); + Log(log_level, -1, "Heap scan end"); + Thread_unlock_mutex(heap_mutex); } @@ -295,6 +349,8 @@ void HeapScan() */ int Heap_initialize() { + TreeInitializeNoMalloc(&heap, ptrCompare); + heap.heap_tracking = 0; /* no recursive heap tracking! */ return 0; } @@ -308,7 +364,7 @@ void Heap_terminate() if (state.current_size > 20) /* One log list is freed after this function is called */ { Log(LOG_ERROR, -1, "Some memory not freed at shutdown, possible memory leak"); - HeapScan(); + HeapScan(LOG_ERROR); } } @@ -326,29 +382,19 @@ heap_info* Heap_get_info() /** * Dump a string from the heap so that it can be displayed conveniently * @param file file handle to dump the heap contents to - * @param str the string to dump + * @param str the string to dump, could be NULL */ int HeapDumpString(FILE* file, char* str) { int rc = 0; + int len = str ? strlen(str) + 1 : 0; /* include the trailing null */ - if (str) - { - int* i = (int*)str; - int j; - int len = strlen(str); - - if (fwrite(&(str), sizeof(int), 1, file) != 1) - rc = -1; - if (fwrite(&(len), sizeof(int), 1 ,file) != 1) - rc = -1; - for (j = 0; j < len; j++) - { - if (fwrite(i, sizeof(int), 1, file) != 1) - rc = -1; - i++; - } - } + if (fwrite(&(str), sizeof(char*), 1, file) != 1) + rc = -1; + else if (fwrite(&(len), sizeof(int), 1 ,file) != 1) + rc = -1; + else if (len > 0 && fwrite(str, len, 1, file) != 1) + rc = -1; return rc; } @@ -356,32 +402,58 @@ int HeapDumpString(FILE* file, char* str) /** * Dump the state of the heap * @param file file handle to dump the heap contents to - * @return completion code, success == 0 */ int HeapDump(FILE* file) { int rc = 0; + Node* current = NULL; - if (file != NULL) + while (rc == 0 && (current = TreeNextElement(&heap, current))) { - ListElement* current = NULL; - while (ListNextElement(&heap, ¤t)) - { - storageElement* s = (storageElement*)(current->content); - int* i = s->ptr; - unsigned int j; + storageElement* s = (storageElement*)(current->content); - if (fwrite(&(s->ptr), sizeof(int), 1, file) != 1) - rc = -1; - if (fwrite(&(s->size), sizeof(int), 1, file) != 1) - rc = -1; - for (j = 0; j < s->size; j++) - { - if (fwrite(i, sizeof(int), 1, file) != -1) - rc = -1; - i++; - } - } + if (fwrite(&(s->ptr), sizeof(s->ptr), 1, file) != 1) + rc = -1; + else if (fwrite(&(current->size), sizeof(current->size), 1, file) != 1) + rc = -1; + else if (fwrite(s->ptr, current->size, 1, file) != 1) + rc = -1; } return rc; } + + +#if defined(HEAP_UNIT_TESTS) + +void Log(int log_level, int msgno, char* format, ...) +{ + printf("Log %s", format); +} + +#define malloc(x) mymalloc(__FILE__, __LINE__, x) +#define realloc(a, b) myrealloc(__FILE__, __LINE__, a, b) +#define free(x) myfree(__FILE__, __LINE__, x) + +int main(int argc, char *argv[]) +{ + char* h = NULL; + Heap_initialize(); + + h = malloc(12); + free(h); + printf("freed h\n"); + + h = malloc(12); + h = realloc(h, 14); + h = realloc(h, 25); + h = realloc(h, 255); + h = realloc(h, 2225); + h = realloc(h, 22225); + printf("freeing h\n"); + free(h); + Heap_terminate(); + printf("Finishing\n"); + return 0; +} + +#endif diff --git a/src/Heap.h b/src/Heap.h index df47834c..903a3e8e 100644 --- a/src/Heap.h +++ b/src/Heap.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,19 +8,22 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - use tree data structure instead of list *******************************************************************************/ + #if !defined(HEAP_H) #define HEAP_H -#include +#if defined(HIGH_PERFORMANCE) +#define NO_HEAP_TRACKING 1 +#endif +#include #include #include -#define HEAP_TRACKING 1 -#if HEAP_TRACKING - +#if !defined(NO_HEAP_TRACKING) /** * redefines malloc to use "mymalloc" so that heap allocation can be tracked * @param x the size of the item to be allocated @@ -44,23 +47,27 @@ #endif +/** + * Information about the state of the heap. + */ +typedef struct +{ + int current_size; /**< current size of the heap in bytes */ + int max_size; /**< max size the heap has reached in bytes */ +} heap_info; + + void* mymalloc(char*, int, size_t size); void* myrealloc(char*, int, void* p, size_t size); void myfree(char*, int, void* p); -void* Heap_findItem(void* p); -void Heap_unlink(char*, int, void* p); -typedef struct -{ - int current_size; - int max_size; -} heap_info; - -void HeapScan(); +void Heap_scan(FILE* file); int Heap_initialize(void); void Heap_terminate(void); heap_info* Heap_get_info(void); int HeapDump(FILE* file); int HeapDumpString(FILE* file, char* str); +void* Heap_findItem(void* p); +void Heap_unlink(char* file, int line, void* p); #endif diff --git a/src/LinkedList.c b/src/LinkedList.c index 9eda3556..ae3a67fd 100644 --- a/src/LinkedList.c +++ b/src/LinkedList.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - updates for the async client *******************************************************************************/ /** @@ -253,9 +254,9 @@ int ListRemove(List* aList, void* content) * @param aList the list from which the item is to be removed * @return 1=item removed, 0=item not removed */ -int ListRemoveHead(List* aList) +void* ListDetachHead(List* aList) { - int rc = 0; + void *content = NULL; if (aList->count > 0) { ListElement* first = aList->first; @@ -263,14 +264,26 @@ int ListRemoveHead(List* aList) aList->current = first->next; if (aList->last == first) /* i.e. no of items in list == 1 */ aList->last = NULL; - free(first->content); + content = first->content; aList->first = aList->first->next; if (aList->first) aList->first->prev = NULL; free(first); --(aList->count); } - return rc; + return content; +} + + +/** + * Removes and frees an the first item in a list. + * @param aList the list from which the item is to be removed + * @return 1=item removed, 0=item not removed + */ +int ListRemoveHead(List* aList) +{ + free(ListDetachHead(aList)); + return 0; } diff --git a/src/LinkedList.h b/src/LinkedList.h index 473d8ed4..e11257a5 100644 --- a/src/LinkedList.h +++ b/src/LinkedList.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - updates for the async client *******************************************************************************/ #if !defined(LINKEDLIST_H) @@ -74,6 +75,7 @@ void ListInsert(List* aList, void* content, int size, ListElement* index); int ListRemove(List* aList, void* content); int ListRemoveItem(List* aList, void* content, int(*callback)(void*, void*)); +void* ListDetachHead(List* aList); int ListRemoveHead(List* aList); void* ListPopTail(List* aList); diff --git a/src/Log.c b/src/Log.c index a27e6301..01e35d27 100644 --- a/src/Log.c +++ b/src/Log.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - updates for the async client *******************************************************************************/ #include "Log.h" @@ -17,6 +18,7 @@ #include "Messages.h" #include "LinkedList.h" #include "StackTrace.h" +#include "Thread.h" #include #include @@ -26,6 +28,7 @@ #if !defined(WIN32) #include +#include #define GETTIMEOFDAY 1 #else #define snprintf _snprintf @@ -37,6 +40,13 @@ #include #endif +#if !defined(WIN32) +/** + * _unlink mapping for linux + */ +#define _unlink unlink +#endif + #if !defined(min) #define min(A,B) ( (A) < (B) ? (A):(B)) @@ -75,7 +85,13 @@ static traceEntry* trace_queue = NULL; static int trace_queue_size = 0; static FILE* trace_destination = NULL; /**< flag to indicate if trace is to be sent to a stream */ +static char* trace_destination_name = NULL; /**< the name of the trace file */ +static char* trace_destination_backup_name = NULL; /**< the name of the backup trace file */ +static int lines_written = 0; /**< number of lines written to the current output file */ +static int max_lines_per_file = 1000; /**< maximum number of lines to write to one trace file */ static int trace_output_level = -1; +static Log_traceCallback* trace_callback = NULL; +static void Log_output(int log_level, char* msg); static int sametime_count = 0; #if defined(GETTIMEOFDAY) @@ -85,8 +101,15 @@ struct timeb ts, last_ts; #endif static char msg_buf[512]; +#if defined(WIN32) +mutex_type log_mutex; +#else +static pthread_mutex_t log_mutex_store = PTHREAD_MUTEX_INITIALIZER; +static mutex_type log_mutex = &log_mutex_store; +#endif -int Log_initialize() + +int Log_initialize(Log_nameValue* info) { int rc = -1; char* envval = NULL; @@ -97,8 +120,21 @@ int Log_initialize() if ((envval = getenv("MQTT_C_CLIENT_TRACE")) != NULL && strlen(envval) > 0) { - if (strcmp(envval, "ON") == 0 || (trace_destination = fopen(envval, "wt")) == NULL) + if (strcmp(envval, "ON") == 0 || (trace_destination = fopen(envval, "w")) == NULL) trace_destination = stdout; + else + { + trace_destination_name = malloc(strlen(envval) + 1); + strcpy(trace_destination_name, envval); + trace_destination_backup_name = malloc(strlen(envval) + 3); + sprintf(trace_destination_backup_name, "%s.0", trace_destination_name); + } + } + if ((envval = getenv("MQTT_C_CLIENT_TRACE_MAX_LINES")) != NULL && strlen(envval) > 0) + { + max_lines_per_file = atoi(envval); + if (max_lines_per_file <= 0) + max_lines_per_file = 1000; } if ((envval = getenv("MQTT_C_CLIENT_TRACE_LEVEL")) != NULL && strlen(envval) > 0) { @@ -110,11 +146,58 @@ int Log_initialize() trace_settings.trace_level = TRACE_MINIMUM; else if (strcmp(envval, "PROTOCOL") == 0 || strcmp(envval, "TRACE_PROTOCOL") == 0) trace_output_level = TRACE_PROTOCOL; + else if (strcmp(envval, "ERROR") == 0 || strcmp(envval, "TRACE_ERROR") == 0) + trace_output_level = LOG_ERROR; } + Log_output(TRACE_MINIMUM, "========================================================="); + Log_output(TRACE_MINIMUM, " Trace Output"); + if (info) + { + while (info->name) + { + sprintf(msg_buf, "%s: %s", info->name, info->value); + Log_output(TRACE_MINIMUM, msg_buf); + info++; + } + } +#if !defined(WIN32) + struct stat buf; + if (stat("/proc/version", &buf) != -1) + { + FILE* vfile; + + if ((vfile = fopen("/proc/version", "r")) != NULL) + { + int len; + + strcpy(msg_buf, "/proc/version: "); + len = strlen(msg_buf); + if (fgets(&msg_buf[len], sizeof(msg_buf) - len, vfile)) + Log_output(TRACE_MINIMUM, msg_buf); + fclose(vfile); + } + } +#endif + Log_output(TRACE_MINIMUM, "========================================================="); + return rc; } +void Log_setTraceCallback(Log_traceCallback* callback) +{ + trace_callback = callback; +} + + +void Log_setTraceLevel(enum LOG_LEVELS level) +{ + if (level < TRACE_MINIMUM) /* the lowest we can go is TRACE_MINIMUM*/ + trace_settings.trace_level = level; + trace_output_level = level; +} + + void Log_terminate() { free(trace_queue); @@ -126,6 +209,10 @@ void Log_terminate() fclose(trace_destination); trace_destination = NULL; } + if (trace_destination_name) + free(trace_destination_name); + if (trace_destination_backup_name) + free(trace_destination_backup_name); start_index = -1; next_index = 0; trace_output_level = -1; @@ -222,13 +309,44 @@ static char* Log_formatTraceEntry(traceEntry* cur_entry) } +static void Log_output(int log_level, char* msg) +{ + if (trace_destination) + { + Thread_lock_mutex(log_mutex); /* need to lock around the fiddling with the log files */ + fprintf(trace_destination, "%s\n", msg); + + if (trace_destination != stdout && ++lines_written >= max_lines_per_file) + { + + fclose(trace_destination); + _unlink(trace_destination_backup_name); /* remove any old backup trace file */ + rename(trace_destination_name, trace_destination_backup_name); /* rename recently closed to backup */ + trace_destination = fopen(trace_destination_name, "w"); /* open new trace file */ + if (trace_destination == NULL) + trace_destination = stdout; + lines_written = 0; + } + else + fflush(trace_destination); + Thread_unlock_mutex(log_mutex); + } + + if (trace_callback) + (*trace_callback)(log_level, msg); +} + + static void Log_posttrace(int log_level, traceEntry* cur_entry) { - if (trace_destination && - ((trace_output_level == -1) ? log_level >= trace_settings.trace_level : log_level >= trace_output_level)) + if (((trace_output_level == -1) ? log_level >= trace_settings.trace_level : log_level >= trace_output_level)) { - fprintf(trace_destination, "%s\n", &Log_formatTraceEntry(cur_entry)[7]); - fflush(trace_destination); + char* msg = NULL; + + if (trace_destination || trace_callback) + msg = &Log_formatTraceEntry(cur_entry)[7]; + + Log_output(log_level, msg); } } diff --git a/src/Log.h b/src/Log.h index a4b9ac37..43c1bd94 100644 --- a/src/Log.h +++ b/src/Log.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - updates for the async client *******************************************************************************/ #if !defined(LOG_H) @@ -27,7 +28,7 @@ map LOG_LEVELS } BE*/ -enum { +enum LOG_LEVELS { TRACE_MAXIMUM = 1, TRACE_MEDIUM, TRACE_MINIMUM, @@ -60,10 +61,20 @@ extern trace_settings_type trace_settings; #define TRACE_MIN TRACE_MINIMUM #define TRACE_MED TRACE_MEDIUM -int Log_initialize(); +typedef struct +{ + const char* name; + const char* value; +} Log_nameValue; + +int Log_initialize(Log_nameValue*); void Log_terminate(); void Log(int, int, char *, ...); void Log_stackTrace(int, int, int, int, const char*, int, int*); +typedef void Log_traceCallback(enum LOG_LEVELS level, char* message); +void Log_setTraceCallback(Log_traceCallback* callback); +void Log_setTraceLevel(enum LOG_LEVELS level); + #endif diff --git a/src/MQTTAsync.c b/src/MQTTAsync.c new file mode 100644 index 00000000..b5e6826d --- /dev/null +++ b/src/MQTTAsync.c @@ -0,0 +1,2542 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Ian Craggs - initial implementation and documentation + * Ian Craggs, Allan Stockdill-Mander - SSL support + *******************************************************************************/ + +#include +#if !defined(WIN32) + #include +#endif + +#if !defined(NO_PERSISTENCE) +#include "MQTTPersistence.h" +#endif +#include "MQTTAsync.h" +#include "utf-8.h" +#include "MQTTProtocol.h" +#include "MQTTProtocolOut.h" +#include "Thread.h" +#include "SocketBuffer.h" +#include "StackTrace.h" +#include "Heap.h" + +#define URI_TCP "tcp://" + +#define BUILD_TIMESTAMP "##MQTTCLIENT_BUILD_TAG##" +#define CLIENT_VERSION "0.9.0.0" + +char* client_timestamp_eye = "MQTTAsyncV3_Timestamp " BUILD_TIMESTAMP; +char* client_version_eye = "MQTTAsyncV3_Version " CLIENT_VERSION; + +extern Sockets s; + +static ClientStates ClientState = +{ + CLIENT_VERSION, /* version */ + NULL /* client list */ +}; + +ClientStates* bstate = &ClientState; + +MQTTProtocol state; + +enum MQTTAsync_threadStates +{ + STOPPED, STARTING, RUNNING, STOPPING +}; + +enum MQTTAsync_threadStates sendThread_state = STOPPED; +enum MQTTAsync_threadStates receiveThread_state = STOPPED; + +#if !defined(WIN32) + static cond_type send_cond; +#else + static sem_type send_sem; +#endif + +#if defined(WIN32) +static mutex_type mqttasync_mutex = NULL; +static mutex_type mqttcommand_mutex = NULL; +extern mutex_type stack_mutex; +extern mutex_type heap_mutex; +extern mutex_type log_mutex; +BOOL APIENTRY DllMain(HANDLE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + Log(TRACE_MAX, -1, "DLL process attach"); + if (mqttasync_mutex == NULL) + { + mqttasync_mutex = CreateMutex(NULL, 0, NULL); + mqttcommand_mutex = CreateMutex(NULL, 0, NULL); + stack_mutex = CreateMutex(NULL, 0, NULL); + heap_mutex = CreateMutex(NULL, 0, NULL); + log_mutex = CreateMutex(NULL, 0, NULL); + } + case DLL_THREAD_ATTACH: + Log(TRACE_MAX, -1, "DLL thread attach"); + case DLL_THREAD_DETACH: + Log(TRACE_MAX, -1, "DLL thread detach"); + case DLL_PROCESS_DETACH: + Log(TRACE_MAX, -1, "DLL process detach"); + } + return TRUE; +} +#else +static pthread_mutex_t mqttasync_mutex_store = PTHREAD_MUTEX_INITIALIZER; +static mutex_type mqttasync_mutex = &mqttasync_mutex_store; +static pthread_mutex_t mqttcommand_mutex_store = PTHREAD_MUTEX_INITIALIZER; +static mutex_type mqttcommand_mutex = &mqttcommand_mutex_store; +#define WINAPI +#endif + +static volatile int initialized = 0; +static List* handles = NULL; +static int tostop = 0; +static List* commands = NULL; + +MQTTPacket* MQTTAsync_cycle(int* sock, unsigned long timeout, int* rc); +int MQTTAsync_cleanSession(Clients* client); +void MQTTAsync_stop(); +int MQTTAsync_disconnect_internal(MQTTAsync handle, int timeout); +void MQTTProtocol_closeSession(Clients* client, int sendwill); +void MQTTAsync_writeComplete(int socket); + +#if defined(WIN32) +#define START_TIME_TYPE DWORD +START_TIME_TYPE MQTTAsync_start_clock(void) +{ + return GetTickCount(); +} +#elif defined(AIX) +#define START_TIME_TYPE struct timespec +START_TIME_TYPE MQTTAsync_start_clock(void) +{ + static struct timespec start; + clock_gettime(CLOCK_REALTIME, &start); + return start; +} +#else +#define START_TIME_TYPE struct timeval +START_TIME_TYPE MQTTAsync_start_clock(void) +{ + static struct timeval start; + gettimeofday(&start, NULL); + return start; +} +#endif + + +#if defined(WIN32) +long MQTTAsync_elapsed(DWORD milliseconds) +{ + return GetTickCount() - milliseconds; +} +#elif defined(AIX) +#define assert(a) +long MQTTAsync_elapsed(struct timespec start) +{ + struct timespec now, res; + + clock_gettime(CLOCK_REALTIME, &now); + ntimersub(now, start, res); + return (res.tv_sec)*1000L + (res.tv_nsec)/1000000L; +} +#else +long MQTTAsync_elapsed(struct timeval start) +{ + struct timeval now, res; + + gettimeofday(&now, NULL); + timersub(&now, &start, &res); + return (res.tv_sec)*1000 + (res.tv_usec)/1000; +} +#endif + + +typedef struct +{ + MQTTAsync_message* msg; + char* topicName; + int topicLen; + unsigned int seqno; /* only used on restore */ +} qEntry; + +typedef struct +{ + int type; + MQTTAsync_onSuccess* onSuccess; + MQTTAsync_onFailure* onFailure; + MQTTAsync_token token; + void* context; + START_TIME_TYPE start_time; + union + { + struct + { + int count; + char** topics; + int* qoss; + } sub; + struct + { + int count; + char** topics; + } unsub; + struct + { + char* destinationName; + int payloadlen; + void* payload; + int qos; + int retained; + } pub; + struct + { + int internal; + int timeout; + } dis; + struct + { + int timeout; + } conn; + } details; +} MQTTAsync_command; + + +typedef struct MQTTAsync_struct +{ + char* serverURI; + int ssl; + Clients* c; + + /* "Global", to the client, callback definitions */ + MQTTAsync_connectionLost* cl; + MQTTAsync_messageArrived* ma; + MQTTAsync_deliveryComplete* dc; + void* context; /* the context to be associated with the main callbacks*/ + + MQTTAsync_command connect; /* Connect operation properties */ + MQTTAsync_command disconnect; /* Disconnect operation properties */ + MQTTAsync_command* pending_write; /* Is there a socket write pending? */ + + List* responses; + unsigned int command_seqno; + + MQTTPacket* pack; + +} MQTTAsyncs; + + +typedef struct +{ + MQTTAsync_command command; + MQTTAsyncs* client; + unsigned int seqno; /* only used on restore */ +} MQTTAsync_queuedCommand; + +void MQTTAsync_freeCommand(MQTTAsync_queuedCommand *command); +void MQTTAsync_freeCommand1(MQTTAsync_queuedCommand *command); +#if !defined(NO_PERSISTENCE) +int MQTTAsync_restoreCommands(MQTTAsyncs* client); +int MQTTAsync_unpersistQueueEntry(Clients*, qEntry*); +int MQTTAsync_restoreMessageQueue(MQTTAsyncs* client); +#endif + +void MQTTAsync_sleep(long milliseconds) +{ + FUNC_ENTRY; +#if defined(WIN32) + Sleep(milliseconds); +#else + usleep(milliseconds*1000); +#endif + FUNC_EXIT; +} + + +/** + * List callback function for comparing clients by socket + * @param a first integer value + * @param b second integer value + * @return boolean indicating whether a and b are equal + */ +int clientSockCompare(void* a, void* b) +{ + MQTTAsyncs* m = (MQTTAsyncs*)a; + return m->c->net.socket == *(int*)b; +} + + +int MQTTAsync_create(MQTTAsync* handle, char* serverURI, char* clientId, + int persistence_type, void* persistence_context) +{ + int rc = 0; + MQTTAsyncs *m = NULL; + + FUNC_ENTRY; + rc = Thread_lock_mutex(mqttasync_mutex); + + if (serverURI == NULL || clientId == NULL) + { + rc = MQTTASYNC_NULL_PARAMETER; + goto exit; + } + + if (!UTF8_validateString(clientId)) + { + rc = MQTTASYNC_BAD_UTF8_STRING; + goto exit; + } + + if (!initialized) + { + #if defined(HEAP_H) + Heap_initialize(); + #endif + Log_initialize((Log_nameValue*)MQTTAsync_getVersionInfo()); + bstate->clients = ListInitialize(); + Socket_outInitialize(); + Socket_setWriteCompleteCallback(MQTTAsync_writeComplete); + handles = ListInitialize(); + commands = ListInitialize(); +#if defined(OPENSSL) + SSLSocket_initialize(); +#endif +#if !defined(WIN32) + send_cond = Thread_create_cond(); +#else + send_sem = Thread_create_sem(); +#endif + initialized = 1; + } + m = malloc(sizeof(MQTTAsyncs)); + *handle = m; + memset(m, '\0', sizeof(MQTTAsyncs)); + if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0) + serverURI += strlen(URI_TCP); +#if defined(OPENSSL) + else if (strncmp(URI_SSL, serverURI, strlen(URI_SSL)) == 0) + { + serverURI += strlen(URI_SSL); + m->ssl = 1; + } +#endif + m->serverURI = malloc(strlen(serverURI)+1); + strcpy(m->serverURI, serverURI); + m->responses = ListInitialize(); + ListAppend(handles, m, sizeof(MQTTAsyncs)); + + m->c = malloc(sizeof(Clients)); + memset(m->c, '\0', sizeof(Clients)); + m->c->outboundMsgs = ListInitialize(); + m->c->inboundMsgs = ListInitialize(); + m->c->messageQueue = ListInitialize(); + m->c->clientID = malloc(strlen(clientId)+1); + strcpy(m->c->clientID, clientId); + +#if !defined(NO_PERSISTENCE) + rc = MQTTPersistence_create(&(m->c->persistence), persistence_type, persistence_context); + if (rc == 0) + { + rc = MQTTPersistence_initialize(m->c, m->serverURI); + if (rc == 0) + { + MQTTAsync_restoreCommands(m); + MQTTAsync_restoreMessageQueue(m); + } + } +#endif + ListAppend(bstate->clients, m->c, sizeof(Clients) + 3*sizeof(List)); + +exit: + if (Thread_unlock_mutex(mqttasync_mutex) != 0) + /* FFDC? */; + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTAsync_terminate(void) +{ + FUNC_ENTRY; + MQTTAsync_stop(); + if (initialized) + { + ListElement* elem = NULL; +#if !defined(WIN32) + Thread_destroy_cond(send_cond); +#else + Thread_destroy_sem(send_sem); +#endif + ListFree(bstate->clients); + ListFree(handles); + while (ListNextElement(commands, &elem)) + MQTTAsync_freeCommand1((MQTTAsync_queuedCommand*)(elem->content)); + ListFree(commands); + handles = NULL; + Socket_outTerminate(); + #if defined(HEAP_H) + Heap_terminate(); + #endif + Log_terminate(); + initialized = 0; + } + FUNC_EXIT; +} + + +#if !defined(NO_PERSISTENCE) +int MQTTAsync_unpersistCommand(MQTTAsync_queuedCommand* qcmd) +{ + int rc = 0; + char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; + + FUNC_ENTRY; + sprintf(key, "%s%d", PERSISTENCE_COMMAND_KEY, qcmd->seqno); + if ((rc = qcmd->client->c->persistence->premove(qcmd->client->c->phandle, key)) != 0) + Log(LOG_ERROR, 0, "Error %d removing command from persistence", rc); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_persistCommand(MQTTAsync_queuedCommand* qcmd) +{ + int rc = 0; + MQTTAsyncs* aclient = qcmd->client; + MQTTAsync_command* command = &qcmd->command; + int* lens = NULL; + void** bufs = NULL; + int bufindex = 0, i, nbufs = 0; + char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; + + FUNC_ENTRY; + switch (command->type) + { + case SUBSCRIBE: + nbufs = 2 + (command->details.sub.count * 2); + + lens = (int*)malloc(nbufs * sizeof(int)); + bufs = malloc(nbufs * sizeof(char *)); + + bufs[bufindex] = &command->type; + lens[bufindex++] = sizeof(command->type); + + bufs[bufindex] = &command->details.sub.count; + lens[bufindex++] = sizeof(command->details.sub.count); + + for (i = 0; i < command->details.sub.count; ++i) + { + bufs[bufindex] = command->details.sub.topics[i]; + lens[bufindex++] = strlen(command->details.sub.topics[i]) + 1; + bufs[bufindex] = &command->details.sub.qoss[i]; + lens[bufindex++] = sizeof(command->details.sub.qoss[i]); + } + sprintf(key, "%s%d", PERSISTENCE_COMMAND_KEY, ++aclient->command_seqno); + break; + + case UNSUBSCRIBE: + nbufs = 2 + command->details.unsub.count; + + lens = (int*)malloc(nbufs * sizeof(int)); + bufs = malloc(nbufs * sizeof(char *)); + + bufs[bufindex] = &command->type; + lens[bufindex++] = sizeof(command->type); + + bufs[bufindex] = &command->details.unsub.count; + lens[bufindex++] = sizeof(command->details.unsub.count); + + for (i = 0; i < command->details.unsub.count; ++i) + { + bufs[bufindex] = command->details.unsub.topics[i]; + lens[bufindex++] = strlen(command->details.unsub.topics[i]) + 1; + } + sprintf(key, "%s%d", PERSISTENCE_COMMAND_KEY, ++aclient->command_seqno); + break; + + case PUBLISH: + nbufs = 6; + + lens = (int*)malloc(nbufs * sizeof(int)); + bufs = malloc(nbufs * sizeof(char *)); + + bufs[bufindex] = &command->type; + lens[bufindex++] = sizeof(command->type); + + bufs[bufindex] = command->details.pub.destinationName; + lens[bufindex++] = strlen(command->details.pub.destinationName) + 1; + + bufs[bufindex] = &command->details.pub.payloadlen; + lens[bufindex++] = sizeof(command->details.pub.payloadlen); + + bufs[bufindex] = command->details.pub.payload; + lens[bufindex++] = command->details.pub.payloadlen; + + bufs[bufindex] = &command->details.pub.qos; + lens[bufindex++] = sizeof(command->details.pub.qos); + + bufs[bufindex] = &command->details.pub.retained; + lens[bufindex++] = sizeof(command->details.pub.retained); + + sprintf(key, "%s%d", PERSISTENCE_COMMAND_KEY, ++aclient->command_seqno); + break; + } + if (nbufs > 0) + { + if ((rc = aclient->c->persistence->pput(aclient->c->phandle, key, nbufs, (char**)bufs, lens)) != 0) + Log(LOG_ERROR, 0, "Error persisting command, rc %d", rc); + qcmd->seqno = aclient->command_seqno; + } + if (lens) + free(lens); + if (bufs) + free(bufs); + FUNC_EXIT_RC(rc); + return rc; +} + + +MQTTAsync_queuedCommand* MQTTAsync_restoreCommand(char* buffer, int buflen) +{ + MQTTAsync_command* command = NULL; + MQTTAsync_queuedCommand* qcommand = NULL; + char* ptr = buffer; + int i, data_size; + + FUNC_ENTRY; + qcommand = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(qcommand, '\0', sizeof(MQTTAsync_queuedCommand)); + command = &qcommand->command; + + command->type = *(int*)ptr; + ptr += sizeof(int); + + switch (command->type) + { + case SUBSCRIBE: + command->details.sub.count = *(int*)ptr; + ptr += sizeof(int); + + for (i = 0; i < command->details.sub.count; ++i) + { + data_size = strlen(ptr) + 1; + + command->details.sub.topics[i] = malloc(data_size); + strcpy(command->details.sub.topics[i], ptr); + ptr += data_size; + + command->details.sub.qoss[i] = *(int*)ptr; + ptr += sizeof(int); + } + break; + + case UNSUBSCRIBE: + command->details.sub.count = *(int*)ptr; + ptr += sizeof(int); + + for (i = 0; i < command->details.unsub.count; ++i) + { + int data_size = strlen(ptr) + 1; + + command->details.unsub.topics[i] = malloc(data_size); + strcpy(command->details.unsub.topics[i], ptr); + ptr += data_size; + } + break; + + case PUBLISH: + data_size = strlen(ptr) + 1; + command->details.pub.destinationName = malloc(data_size); + strcpy(command->details.pub.destinationName, ptr); + ptr += data_size; + + command->details.pub.payloadlen = *(int*)ptr; + ptr += sizeof(int); + + data_size = command->details.pub.payloadlen; + command->details.pub.payload = malloc(data_size); + memcpy(command->details.pub.payload, ptr, data_size); + ptr += data_size; + + command->details.pub.qos = *(int*)ptr; + ptr += sizeof(int); + + command->details.pub.retained = *(int*)ptr; + ptr += sizeof(int); + break; + + default: + free(qcommand); + qcommand = NULL; + + } + + FUNC_EXIT; + return qcommand; +} + + +void MQTTAsync_insertInOrder(List* list, void* content, int size) +{ + ListElement* index = NULL; + ListElement* current = NULL; + + FUNC_ENTRY; + while (ListNextElement(list, ¤t) != NULL && index == NULL) + { + if (((MQTTAsync_queuedCommand*)content)->seqno < ((MQTTAsync_queuedCommand*)current->content)->seqno) + index = current; + } + + ListInsert(list, content, size, index); + FUNC_EXIT; +} + + +int MQTTAsync_restoreCommands(MQTTAsyncs* client) +{ + int rc = 0; + char **msgkeys; + int nkeys; + int i = 0; + Clients* c = client->c; + int commands_restored = 0; + + FUNC_ENTRY; + if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0) + { + while (rc == 0 && i < nkeys) + { + char *buffer = NULL; + int buflen; + + if (strncmp(msgkeys[i], PERSISTENCE_COMMAND_KEY, strlen(PERSISTENCE_COMMAND_KEY)) != 0) + ; + else if ((rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0) + { + MQTTAsync_queuedCommand* cmd = MQTTAsync_restoreCommand(buffer, buflen); + + if (cmd) + { + cmd->client = client; + cmd->seqno = atoi(msgkeys[i]+2); + MQTTPersistence_insertInOrder(commands, cmd, sizeof(MQTTAsync_queuedCommand)); + free(buffer); + client->command_seqno = max(client->command_seqno, cmd->seqno); + commands_restored++; + } + } + if (msgkeys[i]) + free(msgkeys[i]); + i++; + } + if (msgkeys != NULL) + free(msgkeys); + } + Log(TRACE_MINIMUM, -1, "%d commands restored for client %s", commands_restored, c->clientID); + FUNC_EXIT_RC(rc); + return rc; +} +#endif + + +int MQTTAsync_addCommand(MQTTAsync_queuedCommand* command, int command_size) +{ + int rc; + + FUNC_ENTRY; + rc = Thread_lock_mutex(mqttcommand_mutex); + command->command.start_time = MQTTAsync_start_clock(); + if (command->command.type == CONNECT || + (command->command.type == DISCONNECT && command->command.details.dis.internal)) + { + MQTTAsync_queuedCommand* head = NULL; + + if (commands->first) + head = (MQTTAsync_queuedCommand*)(commands->first->content); + + if (head != NULL && head->client == command->client && head->command.type == command->command.type) + MQTTAsync_freeCommand(command); /* ignore duplicate connect or disconnect command */ + else + ListInsert(commands, command, command_size, commands->first); /* add to the head of the list */ + } + else + { + ListAppend(commands, command, command_size); +#if !defined(NO_PERSISTENCE) + if (command->client->c->persistence) + MQTTAsync_persistCommand(command); +#endif + } + rc = Thread_unlock_mutex(mqttcommand_mutex); +#if !defined(WIN32) + Thread_signal_cond(send_cond); +#else + if (!Thread_check_sem(send_sem)) + Thread_post_sem(send_sem); +#endif + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTAsync_checkDisconnect(MQTTAsync handle, MQTTAsync_command* command) +{ + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + /* wait for all inflight message flows to finish, up to timeout */; + if (m->c->outboundMsgs->count == 0 || MQTTAsync_elapsed(command->start_time) >= command->details.dis.timeout) + { + int was_connected = m->c->connected; + MQTTProtocol_closeSession(m->c, 0); + if (command->details.dis.internal && m->cl && was_connected) + { + Log(TRACE_MIN, -1, "Calling connectionLost for client %s", m->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(m->cl))(m->context, NULL); + Thread_lock_mutex(mqttasync_mutex); + } + else if (!command->details.dis.internal && command->onSuccess) + { + Log(TRACE_MIN, -1, "Calling disconnect complete for client %s", m->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(command->onSuccess))(command->context, NULL); + Thread_lock_mutex(mqttasync_mutex); + } + } + FUNC_EXIT; +} + + +/** + * See if any pending writes have been completed, and cleanup if so. + * Cleaning up means removing any publication data that was stored because the write did + * not originally complete. + */ +void MQTTProtocol_checkPendingWrites() +{ + FUNC_ENTRY; + if (state.pending_writes.count > 0) + { + ListElement* le = state.pending_writes.first; + while (le) + { + if (Socket_noPendingWrites(((pending_write*)(le->content))->socket)) + { + MQTTProtocol_removePublication(((pending_write*)(le->content))->p); + state.pending_writes.current = le; + ListRemove(&(state.pending_writes), le->content); /* does NextElement itself */ + le = state.pending_writes.current; + } + else + ListNextElement(&(state.pending_writes), &le); + } + } + FUNC_EXIT; +} + + +void MQTTAsync_freeCommand1(MQTTAsync_queuedCommand *command) +{ + if (command->command.type == SUBSCRIBE) + { + int i; + + for (i = 0; i < command->command.details.sub.count; i++) + { + free(command->command.details.sub.topics[i]); + free(command->command.details.sub.topics); + free(command->command.details.sub.qoss); + } + } + else if (command->command.type == UNSUBSCRIBE) + { + int i; + + for (i = 0; i < command->command.details.unsub.count; i++) + { + free(command->command.details.unsub.topics[i]); + free(command->command.details.unsub.topics); + } + } + else if (command->command.type == PUBLISH) + { + /* qos 1 and 2 topics are freed in the protocol code when the flows are completed */ + if (command->command.details.pub.destinationName) + free(command->command.details.pub.destinationName); + free(command->command.details.pub.payload); + } +} + +void MQTTAsync_freeCommand(MQTTAsync_queuedCommand *command) +{ + MQTTAsync_freeCommand1(command); + free(command); +} + + +void MQTTAsync_writeComplete(int socket) +{ + ListElement* found = NULL; + + FUNC_ENTRY; + /* a partial write is now complete for a socket - this will be on a publish*/ + + MQTTProtocol_checkPendingWrites(); + + /* find the client using this socket */ + if ((found = ListFindItem(handles, &socket, clientSockCompare)) != NULL) + { + MQTTAsyncs* m = (MQTTAsyncs*)(found->content); + + /* see if there is a pending write flagged */ + if (m->pending_write) + { + ListElement* cur_response = NULL; + MQTTAsync_command* command = m->pending_write; + MQTTAsync_queuedCommand* com = NULL; + + while (ListNextElement(m->responses, &cur_response)) + { + com = (MQTTAsync_queuedCommand*)(cur_response->content); + if (com->client->pending_write == m->pending_write) + break; + } + + if (cur_response && command->onSuccess) + { + MQTTAsync_successData data; + + data.token = command->token; + data.alt.pub.destinationName = command->details.pub.destinationName; + data.alt.pub.message.payload = command->details.pub.payload; + data.alt.pub.message.payloadlen = command->details.pub.payloadlen; + data.alt.pub.message.qos = command->details.pub.qos; + data.alt.pub.message.retained = command->details.pub.retained; + Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); + (*(command->onSuccess))(command->context, &data); + } + m->pending_write = NULL; + + ListDetach(m->responses, com); + MQTTAsync_freeCommand(com); + } + } + FUNC_EXIT; +} + + +void MQTTAsync_processCommand() +{ + int rc = 0; + MQTTAsync_queuedCommand* command = NULL; + ListElement* cur_command = NULL; + + FUNC_ENTRY; + Thread_lock_mutex(mqttcommand_mutex); + + /* don't try a command until there isn't a pending write for that client, and we are not connecting */ + while (ListNextElement(commands, &cur_command)) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(cur_command->content); + + if (cmd->command.type == CONNECT || (cmd->client->c->connected && + cmd->client->c->connect_state == 0 && Socket_noPendingWrites(cmd->client->c->net.socket))) + { + command = cmd; + break; + } + } + if (command) + { + ListDetach(commands, command); +#if !defined(NO_PERSISTENCE) + if (command->client->c->persistence) + MQTTAsync_unpersistCommand(command); +#endif + } + Thread_unlock_mutex(mqttcommand_mutex); + + if (!command) + goto exit; /* nothing to do */ + + Thread_lock_mutex(mqttasync_mutex); + if (command->command.type == CONNECT) + { + if (command->client->c->connect_state != 0 || command->client->c->connected) + rc = 0; + else + { + Log(TRACE_MIN, -1, "Connecting to serverURI %s", command->client->serverURI); +#if defined(OPENSSL) + rc = MQTTProtocol_connect(command->client->serverURI, command->client->c, command->client->ssl); +#else + rc = MQTTProtocol_connect(command->client->serverURI, command->client->c); +#endif + if (command->client->c->connect_state == 0) + rc = SOCKET_ERROR; + + /* if the TCP connect is pending, then we must call select to determine when the connect has completed, + which is indicated by the socket being ready *either* for reading *or* writing. The next couple of lines + make sure we check for writeability as well as readability, otherwise we wait around longer than we need to + in Socket_getReadySocket() */ + if (rc == EINPROGRESS) + Socket_addPendingWrite(command->client->c->net.socket); + } + } + else if (command->command.type == SUBSCRIBE) + { + List* topics = ListInitialize(); + List* qoss = ListInitialize(); + int i; + + for (i = 0; i < command->command.details.sub.count; i++) + { + ListAppend(topics, command->command.details.sub.topics[i], strlen(command->command.details.sub.topics[i])); + ListAppend(qoss, &command->command.details.sub.qoss[i], sizeof(int)); + } + rc = MQTTProtocol_subscribe(command->client->c, topics, qoss); + ListFreeNoContent(topics); + ListFreeNoContent(qoss); + } + else if (command->command.type == UNSUBSCRIBE) + { + List* topics = ListInitialize(); + int i; + + for (i = 0; i < command->command.details.unsub.count; i++) + ListAppend(topics, command->command.details.unsub.topics[i], strlen(command->command.details.unsub.topics[i])); + + rc = MQTTProtocol_unsubscribe(command->client->c, topics); + ListFreeNoContent(topics); + } + else if (command->command.type == PUBLISH) + { + Messages* msg = NULL; + Publish* p = NULL; + + p = malloc(sizeof(Publish)); + + p->payload = command->command.details.pub.payload; + p->payloadlen = command->command.details.pub.payloadlen; + p->topic = command->command.details.pub.destinationName; + p->msgId = -1; + + rc = MQTTProtocol_startPublish(command->client->c, p, command->command.details.pub.qos, command->command.details.pub.retained, &msg); + + if (command->command.details.pub.qos == 0) + { + if (rc == TCPSOCKET_COMPLETE) + { + if (command->command.onSuccess) + { + MQTTAsync_successData data; + + data.token = command->command.token; + data.alt.pub.destinationName = command->command.details.pub.destinationName; + data.alt.pub.message.payload = command->command.details.pub.payload; + data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; + data.alt.pub.message.qos = command->command.details.pub.qos; + data.alt.pub.message.retained = command->command.details.pub.retained; + Log(TRACE_MIN, -1, "Calling publish success for client %s", command->client->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(command->command.onSuccess))(command->command.context, &data); + Thread_lock_mutex(mqttasync_mutex); + } + } + else + { + command->command.details.pub.destinationName = NULL; /* this will be freed by the protocol code */ + command->client->pending_write = &command->command; + } + } + else + command->command.details.pub.destinationName = NULL; /* this will be freed by the protocol code */ + free(p); /* should this be done if the write isn't complete? */ + } + else if (command->command.type == DISCONNECT) + { + if (command->client->c->connect_state != 0 || command->client->c->connected != 0) + { + command->client->c->connect_state = -2; + MQTTAsync_checkDisconnect(command->client, &command->command); + } + } + + if (command->command.type == CONNECT && rc != SOCKET_ERROR && rc != MQTTASYNC_PERSISTENCE_ERROR) + { + command->client->connect = command->command; + MQTTAsync_freeCommand(command); + } + else if (command->command.type == DISCONNECT) + { + command->client->disconnect = command->command; + MQTTAsync_freeCommand(command); + } + else if (command->command.type == PUBLISH && command->command.details.pub.qos == 0) + { + if (rc == TCPSOCKET_INTERRUPTED) + ListAppend(command->client->responses, command, sizeof(command)); + else + MQTTAsync_freeCommand(command); + } + else if (rc == SOCKET_ERROR || rc == MQTTASYNC_PERSISTENCE_ERROR) + { + if (command->command.type == CONNECT) + { + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + MQTTAsync_disconnect(command->client, &opts); /* not "internal" because we don't want to call connection lost */ + } + else + MQTTAsync_disconnect_internal(command->client->c, 0); + if (command->command.onFailure) + { + Log(TRACE_MIN, -1, "Calling command failure for client %s", command->client->c->clientID); + + Thread_unlock_mutex(mqttasync_mutex); + (*(command->command.onFailure))(command->command.context, NULL); + Thread_lock_mutex(mqttasync_mutex); + } + MQTTAsync_freeCommand(command); /* free up the command if necessary */ + } + else + { + /* put the command into a waiting for response queue for each client, indexed by msgid */ + command->command.token = command->client->c->msgID; + ListAppend(command->client->responses, command, sizeof(command)); + } + + Thread_unlock_mutex(mqttasync_mutex); +exit: + FUNC_EXIT; +} + + +void MQTTAsync_checkTimeouts() +{ + ListElement* current = NULL; + static time_t last = 0L; + time_t now; + + FUNC_ENTRY; + time(&(now)); + if (difftime(now, last) < 3) + goto exit; + + Thread_lock_mutex(mqttasync_mutex); + last = now; + while (ListNextElement(handles, ¤t)) /* for each client */ + { + ListElement* cur_response = NULL; + int i = 0, + timed_out_count = 0; + + MQTTAsyncs* m = (MQTTAsyncs*)(current->content); + + /* check connect timeout */ + if (m->c->connect_state != 0 && MQTTAsync_elapsed(m->connect.start_time) > (m->connect.details.conn.timeout * 1000)) + { + if (m->connect.onFailure) + { + Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(m->connect.onFailure))(m->connect.context, NULL); + Thread_lock_mutex(mqttasync_mutex); + } + MQTTProtocol_closeSession(m->c, 0); + continue; + } + + /* check disconnect timeout */ + if (m->c->connect_state == -2) + MQTTAsync_checkDisconnect(m, &m->disconnect); + + timed_out_count = 0; + /* check response timeouts */ + while (ListNextElement(m->responses, &cur_response)) + { + MQTTAsync_queuedCommand* com = (MQTTAsync_queuedCommand*)(cur_response->content); + + if (MQTTAsync_elapsed(com->command.start_time) < 30000) + break; /* command has not timed out */ + else + { + if (com->command.onFailure) + { + Log(TRACE_MIN, -1, "Calling %s failure for client %s", + MQTTPacket_name(com->command.type), m->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(com->command.onFailure))(com->command.context, NULL); + Thread_lock_mutex(mqttasync_mutex); + } + timed_out_count++; + } + } + for (i = 0; i < timed_out_count; ++i) + ListRemoveHead(m->responses); /* remove the first response in the list */ + } +exit: + Thread_unlock_mutex(mqttasync_mutex); + FUNC_EXIT; +} + + +thread_return_type WINAPI MQTTAsync_sendThread(void* n) +{ + FUNC_ENTRY; + Thread_lock_mutex(mqttasync_mutex); + sendThread_state = RUNNING; + Thread_unlock_mutex(mqttasync_mutex); + while (!tostop) + { + /* int rc; */ + + while (commands->count > 0) + MQTTAsync_processCommand(); +#if !defined(WIN32) + /*rc = */Thread_wait_cond_timeout(send_cond, 1); +#else + /*rc = */Thread_wait_sem_timeout(send_sem, 1); +#endif + + MQTTAsync_checkTimeouts(); + } + sendThread_state = STOPPING; + Thread_lock_mutex(mqttasync_mutex); + sendThread_state = STOPPED; + Thread_unlock_mutex(mqttasync_mutex); + FUNC_EXIT; + return 0; +} + + +void MQTTAsync_emptyMessageQueue(Clients* client) +{ + FUNC_ENTRY; + /* empty message queue */ + if (client->messageQueue->count > 0) + { + ListElement* current = NULL; + while (ListNextElement(client->messageQueue, ¤t)) + { + qEntry* qe = (qEntry*)(current->content); + free(qe->topicName); + free(qe->msg->payload); + free(qe->msg); + } + ListEmpty(client->messageQueue); + } + FUNC_EXIT; +} + + +void MQTTAsync_removeResponsesAndCommands(MQTTAsyncs* m) +{ + int count = 0; + ListElement* current = NULL; + ListElement *next = NULL; + + FUNC_ENTRY; + if (m->responses) + { + ListElement* elem = NULL; + + while (ListNextElement(m->responses, &elem)) + { + MQTTAsync_freeCommand1((MQTTAsync_queuedCommand*) (elem->content)); + count++; + } + } + Log(TRACE_MINIMUM, -1, "%d responses removed for client %s", count, m->c->clientID); + + /* remove commands in the command queue relating to this client */ + count = 0; + current = ListNextElement(commands, &next); + ListNextElement(commands, &next); + while (current) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); + + if (cmd->client == m) + { + ListDetach(commands, cmd); + MQTTAsync_freeCommand(cmd); + count++; + } + current = next; + ListNextElement(commands, &next); + } + Log(TRACE_MINIMUM, -1, "%d commands removed for client %s", count, m->c->clientID); + FUNC_EXIT; +} + + +void MQTTAsync_destroy(MQTTAsync* handle) +{ + MQTTAsyncs* m = *handle; + + FUNC_ENTRY; + Thread_lock_mutex(mqttasync_mutex); + + if (m == NULL) + goto exit; + + MQTTAsync_removeResponsesAndCommands(m); + ListFree(m->responses); + + if (m->c) + { + int saved_socket = m->c->net.socket; + char* saved_clientid = malloc(strlen(m->c->clientID)+1); + strcpy(saved_clientid, m->c->clientID); +#if !defined(NO_PERSISTENCE) + MQTTPersistence_close(m->c); +#endif + MQTTAsync_emptyMessageQueue(m->c); + MQTTProtocol_freeClient(m->c); + if (!ListRemove(bstate->clients, m->c)) + Log(LOG_ERROR, 0, NULL); + else + Log(TRACE_MIN, 1, NULL, saved_clientid, saved_socket); + free(saved_clientid); + } + + if (m->serverURI) + free(m->serverURI); + if (!ListRemove(handles, m)) + Log(LOG_ERROR, -1, "free error"); + *handle = NULL; + if (bstate->clients->count == 0) + MQTTAsync_terminate(); + +exit: + Thread_unlock_mutex(mqttasync_mutex); + FUNC_EXIT; +} + + +void MQTTAsync_freeMessage(MQTTAsync_message** message) +{ + FUNC_ENTRY; + free((*message)->payload); + free(*message); + *message = NULL; + FUNC_EXIT; +} + + +void MQTTAsync_free(void* memory) +{ + FUNC_ENTRY; + free(memory); + FUNC_EXIT; +} + + +int MQTTAsync_completeConnection(MQTTAsyncs* m, MQTTPacket* pack) +{ + int rc = MQTTCLIENT_FAILURE; + + FUNC_ENTRY; + if (m->c->connect_state == 3) /* MQTT connect sent - wait for CONNACK */ + { + Connack* connack = (Connack*)pack; + if ((rc = connack->rc) == MQTTCLIENT_SUCCESS) + { + m->c->connected = 1; + m->c->good = 1; + m->c->connect_state = 0; + time(&(m->c->lastContact)); + if (m->c->cleansession) + rc = MQTTAsync_cleanSession(m->c); + if (m->c->outboundMsgs->count > 0) + { + ListElement* outcurrent = NULL; + while (ListNextElement(m->c->outboundMsgs, &outcurrent)) + { + Messages* m = (Messages*)(outcurrent->content); + m->lastTouch = 0; + } + MQTTProtocol_retry(m->c->lastContact, 1); + if (m->c->connected != 1) + rc = MQTTCLIENT_DISCONNECTED; + } + } + else + MQTTAsync_disconnect_internal(m, 0); + free(connack); + m->pack = NULL; + } + FUNC_EXIT_RC(rc); + return rc; +} + +/* This is the thread function that handles the calling of callback functions if set */ +thread_return_type WINAPI MQTTAsync_receiveThread(void* n) +{ + long timeout = 10L; /* first time in we have a small timeout. Gets things started more quickly */ + + FUNC_ENTRY; + Thread_lock_mutex(mqttasync_mutex); + receiveThread_state = RUNNING; + while (!tostop) + { + int rc = SOCKET_ERROR; + int sock = -1; + MQTTAsyncs* m = NULL; + MQTTPacket* pack = NULL; + + Thread_unlock_mutex(mqttasync_mutex); + pack = MQTTAsync_cycle(&sock, timeout, &rc); + Thread_lock_mutex(mqttasync_mutex); + if (tostop) + break; + timeout = 1000L; + + /* find client corresponding to socket */ + if (ListFindItem(handles, &sock, clientSockCompare) == NULL) + { + /* assert: should not happen */ + continue; + } + m = (MQTTAsyncs*)(handles->current->content); + if (m == NULL) + { + /* assert: should not happen */ + continue; + } + if (rc == SOCKET_ERROR) + { + Thread_unlock_mutex(mqttasync_mutex); + MQTTAsync_disconnect_internal(m, 0); + Thread_lock_mutex(mqttasync_mutex); + } + else + { + if (m->c->messageQueue->count > 0) + { + qEntry* qe = (qEntry*)(m->c->messageQueue->first->content); + int topicLen = qe->topicLen; + + if (strlen(qe->topicName) == topicLen) + topicLen = 0; + + if (m->ma) + { + Log(TRACE_MIN, -1, "Calling messageArrived for client %s, queue depth %d", + m->c->clientID, m->c->messageQueue->count); + Thread_unlock_mutex(mqttasync_mutex); + rc = (*(m->ma))(m->context, qe->topicName, topicLen, qe->msg); + Thread_lock_mutex(mqttasync_mutex); + /* if 0 (false) is returned by the callback then it failed, so we don't remove the message from + * the queue, and it will be retried later. If 1 is returned then the message data may have been freed, + * so we must be careful how we use it. + */ + } + else + rc = 1; + if (rc) + { + ListRemove(m->c->messageQueue, qe); +#if !defined(NO_PERSISTENCE) + if (m->c->persistence) + MQTTAsync_unpersistQueueEntry(m->c, qe); +#endif + } + else + Log(TRACE_MIN, -1, "False returned from messageArrived for client %s, message remains on queue", + m->c->clientID); + } + if (pack) + { + if (pack->header.bits.type == CONNACK) + { + int rc = MQTTAsync_completeConnection(m, pack); + + if (rc == MQTTCLIENT_SUCCESS && m->connect.onSuccess) + { + Log(TRACE_MIN, -1, "Calling connect success for client %s", m->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(m->connect.onSuccess))(m->connect.context, NULL); + Thread_lock_mutex(mqttasync_mutex); + } + else if (rc != MQTTCLIENT_SUCCESS && m->connect.onFailure) + { + MQTTAsync_failureData data; + + data.token = 0; + data.code = rc; + data.message = "CONNACK return code"; + Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(m->connect.onFailure))(m->connect.context, &data); + Thread_lock_mutex(mqttasync_mutex); + } + } + else if (pack->header.bits.type == SUBACK) + { + ListElement* current = NULL; + int handleCalled = 0; + + /* use the msgid to find the callback to be called */ + while (ListNextElement(m->responses, ¤t)) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); + if (command->command.token == ((Suback*)pack)->msgId) + { + if (!ListDetach(m->responses, command)) /* remove the response from the list */ + Log(LOG_ERROR, -1, "Subscribe command not removed from command list"); + if (command->command.onSuccess) + { + MQTTAsync_successData data; + Suback* sub = (Suback*)pack; + int* array = NULL; + + if (sub->qoss->count == 1) + data.alt.qos = *(int*)(sub->qoss->first->content); + else if (sub->qoss->count > 1) + { + ListElement* cur_qos = NULL; + int* element = array; + + array = data.alt.qosList = malloc(sub->qoss->count * sizeof(int)); + while (ListNextElement(sub->qoss, &cur_qos)) + *element++ = *(int*)(cur_qos->content); + } + data.token = command->command.token; + + rc = MQTTProtocol_handleSubacks(pack, m->c->net.socket); + handleCalled = 1; + Log(TRACE_MIN, -1, "Calling subscribe success for client %s", m->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(command->command.onSuccess))(command->command.context, &data); + Thread_lock_mutex(mqttasync_mutex); + if (array) + free(array); + } + MQTTAsync_freeCommand(command); + break; + } + } + if (!handleCalled) + rc = MQTTProtocol_handleSubacks(pack, m->c->net.socket); + } + else if (pack->header.bits.type == UNSUBACK) + { + ListElement* current = NULL; + int handleCalled = 0; + + /* use the msgid to find the callback to be called */ + while (ListNextElement(m->responses, ¤t)) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); + if (command->command.token == ((Unsuback*)pack)->msgId) + { + if (!ListDetach(m->responses, command)) /* remove the response from the list */ + Log(LOG_ERROR, -1, "Unsubscribe command not removed from command list"); + if (command->command.onSuccess) + { + rc = MQTTProtocol_handleUnsubacks(pack, m->c->net.socket); + handleCalled = 1; + Log(TRACE_MIN, -1, "Calling unsubscribe success for client %s", m->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(command->command.onSuccess))(command->command.context, NULL); + Thread_lock_mutex(mqttasync_mutex); + } + MQTTAsync_freeCommand(command); + break; + } + } + if (!handleCalled) + rc = MQTTProtocol_handleUnsubacks(pack, m->c->net.socket); + } + } + } + } + receiveThread_state = STOPPED; + Thread_unlock_mutex(mqttasync_mutex); +#if !defined(WIN32) + if (sendThread_state != STOPPED) + Thread_signal_cond(send_cond); +#else + if (sendThread_state != STOPPED && !Thread_check_sem(send_sem)) + Thread_post_sem(send_sem); +#endif + FUNC_EXIT; + return 0; +} + + +void MQTTAsync_stop() +{ + int rc = 0; + + FUNC_ENTRY; + if (sendThread_state != STOPPED || receiveThread_state != STOPPED) + { + int conn_count = 0; + ListElement* current = NULL; + + if (handles != NULL) + { + /* find out how many handles are still connected */ + while (ListNextElement(handles, ¤t)) + { + if (((MQTTAsyncs*)(current->content))->c->connect_state > 0 || + ((MQTTAsyncs*)(current->content))->c->connected) + ++conn_count; + } + } + Log(TRACE_MIN, -1, "Conn_count is %d", conn_count); + /* stop the background thread, if we are the last one to be using it */ + if (conn_count == 0) + { + int count = 0; + tostop = 1; + while ((sendThread_state != STOPPED || receiveThread_state != STOPPED) && ++count < 100) + { + Thread_unlock_mutex(mqttasync_mutex); + Log(TRACE_MIN, -1, "sleeping"); + MQTTAsync_sleep(100L); + Thread_lock_mutex(mqttasync_mutex); + } + rc = 1; + tostop = 0; + } + } + FUNC_EXIT_RC(rc); +} + + +int MQTTAsync_setCallbacks(MQTTAsync handle, void* context, + MQTTAsync_connectionLost* cl, + MQTTAsync_messageArrived* ma, + MQTTAsync_deliveryComplete* dc) +{ + int rc = MQTTCLIENT_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + Thread_lock_mutex(mqttasync_mutex); + + if (m == NULL || ma == NULL || m->c->connect_state != 0) + rc = MQTTCLIENT_FAILURE; + else + { + m->context = context; + m->cl = cl; + m->ma = ma; + m->dc = dc; + } + + Thread_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTProtocol_closeSession(Clients* client, int sendwill) +{ + FUNC_ENTRY; + client->good = 0; + if (client->net.socket > 0) + { + if (client->connected || client->connect_state) + MQTTPacket_send_disconnect(&client->net, client->clientID); +#if defined(OPENSSL) + if (client->net.ssl) + SSLSocket_close(client->net.ssl); +#endif + Socket_close(client->net.socket); + client->net.socket = 0; +#if defined(OPENSSL) + client->net.ssl = NULL; +#endif + } + client->connected = 0; + client->connect_state = 0; + + if (client->cleansession) + MQTTAsync_cleanSession(client); + + FUNC_EXIT; +} + + +/** + * List callback function for comparing clients by client structure + * @param a Async structure + * @param b Client structure + * @return boolean indicating whether a and b are equal + */ +int clientStructCompare(void* a, void* b) +{ + MQTTAsyncs* m = (MQTTAsyncs*)a; + return m->c == (Clients*)b; +} + + +int MQTTAsync_cleanSession(Clients* client) +{ + int rc = 0; + ListElement* found = NULL; + + FUNC_ENTRY; +#if !defined(NO_PERSISTENCE) + rc = MQTTPersistence_clear(client); +#endif + MQTTProtocol_emptyMessageList(client->inboundMsgs); + MQTTProtocol_emptyMessageList(client->outboundMsgs); + MQTTAsync_emptyMessageQueue(client); + client->msgID = 0; + + if ((found = ListFindItem(handles, client, clientStructCompare)) != NULL) + { + MQTTAsyncs* m = (MQTTAsyncs*)(found->content); + MQTTAsync_removeResponsesAndCommands(m); + } + else + Log(LOG_ERROR, -1, "cleanSession: did not find client structure in handles list"); + FUNC_EXIT_RC(rc); + return rc; +} + + +#if !defined(NO_PERSISTENCE) +int MQTTAsync_unpersistQueueEntry(Clients* client, qEntry* qe) +{ + int rc = 0; + char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; + + FUNC_ENTRY; + sprintf(key, "%s%d", PERSISTENCE_QUEUE_KEY, qe->seqno); + if ((rc = client->persistence->premove(client->phandle, key)) != 0) + Log(LOG_ERROR, 0, "Error %d removing qEntry from persistence", rc); + FUNC_EXIT_RC(rc); + return rc; +} + +int MQTTAsync_persistQueueEntry(Clients* aclient, qEntry* qe) +{ + int rc = 0; + int nbufs = 8; + int bufindex = 0; + char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; + int* lens = NULL; + void** bufs = NULL; + + FUNC_ENTRY; + lens = (int*)malloc(nbufs * sizeof(int)); + bufs = malloc(nbufs * sizeof(char *)); + + bufs[bufindex] = &qe->msg->payloadlen; + lens[bufindex++] = sizeof(qe->msg->payloadlen); + + bufs[bufindex] = qe->msg->payload; + lens[bufindex++] = qe->msg->payloadlen; + + bufs[bufindex] = &qe->msg->qos; + lens[bufindex++] = sizeof(qe->msg->qos); + + bufs[bufindex] = &qe->msg->retained; + lens[bufindex++] = sizeof(qe->msg->retained); + + bufs[bufindex] = &qe->msg->dup; + lens[bufindex++] = sizeof(qe->msg->dup); + + bufs[bufindex] = &qe->msg->msgid; + lens[bufindex++] = sizeof(qe->msg->msgid); + + bufs[bufindex] = qe->topicName; + lens[bufindex++] = strlen(qe->topicName) + 1; + + bufs[bufindex] = &qe->topicLen; + lens[bufindex++] = sizeof(qe->topicLen); + + sprintf(key, "%s%d", PERSISTENCE_QUEUE_KEY, ++aclient->qentry_seqno); + qe->seqno = aclient->qentry_seqno; + + if ((rc = aclient->persistence->pput(aclient->phandle, key, nbufs, (char**)bufs, lens)) != 0) + Log(LOG_ERROR, 0, "Error persisting queue entry, rc %d", rc); + + free(lens); + free(bufs); + + FUNC_EXIT_RC(rc); + return rc; +} + + +qEntry* MQTTAsync_restoreQueueEntry(char* buffer, int buflen) +{ + qEntry* qe = NULL; + char* ptr = buffer; + int data_size; + + FUNC_ENTRY; + qe = malloc(sizeof(qEntry)); + memset(qe, '\0', sizeof(qEntry)); + + qe->msg = malloc(sizeof(MQTTAsync_message)); + memset(qe->msg, '\0', sizeof(MQTTAsync_message)); + + qe->msg->payloadlen = *(int*)ptr; + ptr += sizeof(int); + + data_size = qe->msg->payloadlen; + qe->msg->payload = malloc(data_size); + memcpy(qe->msg->payload, ptr, data_size); + ptr += data_size; + + qe->msg->qos = *(int*)ptr; + ptr += sizeof(int); + + qe->msg->retained = *(int*)ptr; + ptr += sizeof(int); + + qe->msg->dup = *(int*)ptr; + ptr += sizeof(int); + + qe->msg->msgid = *(int*)ptr; + ptr += sizeof(int); + + data_size = strlen(ptr) + 1; + qe->topicName = malloc(data_size); + strcpy(qe->topicName, ptr); + ptr += data_size; + + qe->topicLen = *(int*)ptr; + ptr += sizeof(int); + + FUNC_EXIT; + return qe; +} + + +int MQTTAsync_restoreMessageQueue(MQTTAsyncs* client) +{ + int rc = 0; + char **msgkeys; + int nkeys; + int i = 0; + Clients* c = client->c; + int entries_restored = 0; + + FUNC_ENTRY; + if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0) + { + while (rc == 0 && i < nkeys) + { + char *buffer = NULL; + int buflen; + + if (strncmp(msgkeys[i], PERSISTENCE_QUEUE_KEY, strlen(PERSISTENCE_QUEUE_KEY)) != 0) + ; + else if ((rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0) + { + qEntry* qe = MQTTAsync_restoreQueueEntry(buffer, buflen); + + if (qe) + { + qe->seqno = atoi(msgkeys[i]+2); + MQTTPersistence_insertInOrder(c->messageQueue, qe, sizeof(qEntry)); + free(buffer); + c->qentry_seqno = max(c->qentry_seqno, qe->seqno); + entries_restored++; + } + } + if (msgkeys[i]) + free(msgkeys[i]); + i++; + } + if (msgkeys != NULL) + free(msgkeys); + } + Log(TRACE_MINIMUM, -1, "%d queued messages restored for client %s", entries_restored, c->clientID); + FUNC_EXIT_RC(rc); + return rc; +} +#endif + + +int MQTTAsync_deliverMessage(MQTTAsyncs* m, char* topicName, int topicLen, MQTTAsync_message* mm) +{ + int rc; + + Log(TRACE_MIN, -1, "Calling messageArrived for client %s, queue depth %d", + m->c->clientID, m->c->messageQueue->count); + Thread_unlock_mutex(mqttasync_mutex); + rc = (*(m->ma))(m->context, topicName, topicLen, mm); + Thread_lock_mutex(mqttasync_mutex); + /* if 0 (false) is returned by the callback then it failed, so we don't remove the message from + * the queue, and it will be retried later. If 1 is returned then the message data may have been freed, + * so we must be careful how we use it. + */ + return rc; +} + + +void Protocol_processPublication(Publish* publish, Clients* client) +{ + MQTTAsync_message* mm = NULL; + int rc = 0; + + FUNC_ENTRY; + mm = malloc(sizeof(MQTTAsync_message)); + + /* If the message is QoS 2, then we have already stored the incoming payload + * in an allocated buffer, so we don't need to copy again. + */ + if (publish->header.bits.qos == 2) + mm->payload = publish->payload; + else + { + mm->payload = malloc(publish->payloadlen); + memcpy(mm->payload, publish->payload, publish->payloadlen); + } + + mm->payloadlen = publish->payloadlen; + mm->qos = publish->header.bits.qos; + mm->retained = publish->header.bits.retain; + if (publish->header.bits.qos == 2) + mm->dup = 0; /* ensure that a QoS2 message is not passed to the application with dup = 1 */ + else + mm->dup = publish->header.bits.dup; + mm->msgid = publish->msgId; + + if (client->messageQueue->count == 0 && client->connected) + { + ListElement* found = NULL; + + if ((found = ListFindItem(handles, client, clientStructCompare)) == NULL) + Log(LOG_ERROR, -1, "processPublication: did not find client structure in handles list"); + else + { + MQTTAsyncs* m = (MQTTAsyncs*)(found->content); + + if (m->ma) + rc = MQTTAsync_deliverMessage(m, publish->topic, publish->topiclen, mm); + } + } + + if (rc == 0) /* if message was not delivered, queue it up */ + { + qEntry* qe = malloc(sizeof(qEntry)); + qe->msg = mm; + qe->topicName = publish->topic; + qe->topicLen = publish->topiclen; + ListAppend(client->messageQueue, qe, sizeof(qe) + sizeof(mm) + mm->payloadlen + strlen(qe->topicName)+1); +#if !defined(NO_PERSISTENCE) + if (client->persistence) + MQTTAsync_persistQueueEntry(client, qe); +#endif + } + publish->topic = NULL; + FUNC_EXIT; +} + + +int MQTTAsync_connect(MQTTAsync handle, MQTTAsync_connectOptions* options) +{ + MQTTAsyncs* m = handle; + int rc = MQTTCLIENT_SUCCESS; + MQTTAsync_queuedCommand* conn; + + FUNC_ENTRY; + if (options == NULL) + { + rc = MQTTCLIENT_NULL_PARAMETER; + goto exit; + } + if (strncmp(options->struct_id, "MQTC", 4) != 0 || (options->struct_version != 0 && options->struct_version != 1)) + { + rc = MQTTCLIENT_BAD_STRUCTURE; + goto exit; + } + if (options->will) /* check validity of will options structure */ + { + if (strncmp(options->will->struct_id, "MQTW", 4) != 0 || options->will->struct_version != 0) + { + rc = MQTTCLIENT_BAD_STRUCTURE; + goto exit; + } + if (options->will->qos < 0 || options->will->qos > 2) + { + rc = MQTTCLIENT_BAD_QOS; + goto exit; + } + } + if (options->struct_version != 0 && options->ssl) /* check validity of SSL options structure */ + { + if (strncmp(options->ssl->struct_id, "MQTS", 4) != 0 || options->ssl->struct_version != 0) + { + rc = MQTTCLIENT_BAD_STRUCTURE; + goto exit; + } + } + if ((options->username && !UTF8_validateString(options->username)) || + (options->password && !UTF8_validateString(options->password))) + { + rc = MQTTCLIENT_BAD_UTF8_STRING; + goto exit; + } + + m->connect.onSuccess = options->onSuccess; + m->connect.onFailure = options->onFailure; + m->connect.context = options->context; + + Thread_lock_mutex(mqttasync_mutex); + tostop = 0; + if (sendThread_state != STARTING && sendThread_state != RUNNING) + { + sendThread_state = STARTING; + Thread_start(MQTTAsync_sendThread, NULL); + } + if (receiveThread_state != STARTING && receiveThread_state != RUNNING) + { + receiveThread_state = STARTING; + Thread_start(MQTTAsync_receiveThread, handle); + } + Thread_unlock_mutex(mqttasync_mutex); + + m->c->keepAliveInterval = options->keepAliveInterval; + m->c->cleansession = options->cleansession; + m->c->maxInflightMessages = options->maxInflight; + + if (m->c->will) + { + free(m->c->will); + m->c->will = NULL; + } + + if (options->will && options->will->struct_version == 0) + { + m->c->will = malloc(sizeof(willMessages)); + m->c->will->msg = malloc(strlen(options->will->message) + 1); + strcpy(m->c->will->msg, options->will->message); + m->c->will->qos = options->will->qos; + m->c->will->retained = options->will->retained; + m->c->will->topic = malloc(strlen(options->will->topicName) + 1); + strcpy(m->c->will->topic, options->will->topicName); + } + +#if defined(OPENSSL) + if (options->struct_version != 0 && options->ssl) + { + m->c->sslopts = malloc(sizeof(MQTTClient_SSLOptions)); + m->c->sslopts->trustStore = options->ssl->trustStore; + m->c->sslopts->keyStore = options->ssl->keyStore; + m->c->sslopts->privateKey = options->ssl->privateKey; + m->c->sslopts->privateKeyPassword = options->ssl->privateKeyPassword; + m->c->sslopts->enabledCipherSuites = options->ssl->enabledCipherSuites; + m->c->sslopts->enableServerCertAuth = options->ssl->enableServerCertAuth; + } +#endif + + m->c->username = options->username; + m->c->password = options->password; + m->c->retryInterval = options->retryInterval; + + /* Add connect request to operation queue */ + conn = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); + conn->client = m; + if (options) + { + conn->command.onSuccess = options->onSuccess; + conn->command.onFailure = options->onFailure; + conn->command.context = options->context; + conn->command.details.conn.timeout = options->connectTimeout; + } + conn->command.type = CONNECT; + rc = MQTTAsync_addCommand(conn, sizeof(conn)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_disconnect1(MQTTAsync handle, MQTTAsync_disconnectOptions* options, int internal) +{ + MQTTAsyncs* m = handle; + int rc = MQTTCLIENT_SUCCESS; + MQTTAsync_queuedCommand* dis; + + FUNC_ENTRY; + if (m == NULL || m->c == NULL) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTCLIENT_DISCONNECTED; + goto exit; + } + + /* Add disconnect request to operation queue */ + dis = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(dis, '\0', sizeof(MQTTAsync_queuedCommand)); + dis->client = m; + if (options) + { + dis->command.onSuccess = options->onSuccess; + dis->command.onFailure = options->onFailure; + dis->command.context = options->context; + dis->command.details.dis.timeout = options->timeout; + } + dis->command.type = DISCONNECT; + dis->command.details.dis.internal = internal; + rc = MQTTAsync_addCommand(dis, sizeof(dis)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_disconnect_internal(MQTTAsync handle, int timeout) +{ + MQTTAsync_disconnectOptions options = MQTTAsync_disconnectOptions_initializer; + + options.timeout = timeout; + return MQTTAsync_disconnect1(handle, &options, 1); +} + + +int MQTTAsync_disconnect(MQTTAsync handle, MQTTAsync_disconnectOptions* options) +{ + return MQTTAsync_disconnect1(handle, options, 0); +} + + +int MQTTAsync_isConnected(MQTTAsync handle) +{ + MQTTAsyncs* m = handle; + int rc = 0; + + FUNC_ENTRY; + Thread_lock_mutex(mqttasync_mutex); + if (m && m->c) + rc = m->c->connected; + Thread_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_subscribeMany(MQTTAsync handle, int count, char** topic, int* qos, MQTTAsync_responseOptions* response) +{ + MQTTAsyncs* m = handle; + int i = 0; + int rc = MQTTCLIENT_FAILURE; + MQTTAsync_queuedCommand* sub; + + FUNC_ENTRY; + if (m == NULL || m->c == NULL) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTCLIENT_DISCONNECTED; + goto exit; + } + for (i = 0; i < count; i++) + { + if (!UTF8_validateString(topic[i])) + { + rc = MQTTCLIENT_BAD_UTF8_STRING; + goto exit; + } + if (qos[i] < 0 || qos[i] > 2) + { + rc = MQTTCLIENT_BAD_QOS; + goto exit; + } + } + + /* Add subscribe request to operation queue */ + sub = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(sub, '\0', sizeof(MQTTAsync_queuedCommand)); + sub->client = m; + if (response) + { + sub->command.onSuccess = response->onSuccess; + sub->command.onFailure = response->onFailure; + sub->command.context = response->context; + } + sub->command.type = SUBSCRIBE; + sub->command.details.sub.count = count; + sub->command.details.sub.topics = malloc(sizeof(char*) * count); + sub->command.details.sub.qoss = malloc(sizeof(int) * count); + for (i = 0; i < count; ++i) + { + sub->command.details.sub.topics[i] = malloc(strlen(topic[i]) + 1); + strcpy(sub->command.details.sub.topics[i], topic[i]); + sub->command.details.sub.qoss[i] = qos[i]; + } + rc = MQTTAsync_addCommand(sub, sizeof(sub)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_subscribe(MQTTAsync handle, char* topic, int qos, MQTTAsync_responseOptions* response) +{ + int rc = 0; + + FUNC_ENTRY; + rc = MQTTAsync_subscribeMany(handle, 1, &topic, &qos, response); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_unsubscribeMany(MQTTAsync handle, int count, char** topic, MQTTAsync_responseOptions* response) +{ + MQTTAsyncs* m = handle; + int i = 0; + int rc = SOCKET_ERROR; + MQTTAsync_queuedCommand* unsub; + + FUNC_ENTRY; + if (m == NULL || m->c == NULL) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTCLIENT_DISCONNECTED; + goto exit; + } + + for (i = 0; i < count; i++) + { + if (!UTF8_validateString(topic[i])) + { + rc = MQTTCLIENT_BAD_UTF8_STRING; + goto exit; + } + } + + /* Add unsubscribe request to operation queue */ + unsub = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(unsub, '\0', sizeof(MQTTAsync_queuedCommand)); + unsub->client = m; + unsub->command.type = UNSUBSCRIBE; + if (response) + { + unsub->command.onSuccess = response->onSuccess; + unsub->command.onFailure = response->onFailure; + unsub->command.context = response->context; + } + unsub->command.details.unsub.count = count; + unsub->command.details.unsub.topics = malloc(sizeof(char*) * count); + for (i = 0; i < count; ++i) + { + unsub->command.details.unsub.topics[i] = malloc(strlen(topic[i]) + 1); + strcpy(unsub->command.details.unsub.topics[i], topic[i]); + } + rc = MQTTAsync_addCommand(unsub, sizeof(unsub)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_unsubscribe(MQTTAsync handle, char* topic, MQTTAsync_responseOptions* response) +{ + int rc = 0; + + FUNC_ENTRY; + rc = MQTTAsync_unsubscribeMany(handle, 1, &topic, response); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_send(MQTTAsync handle, char* destinationName, int payloadlen, void* payload, + int qos, int retained, MQTTAsync_responseOptions* response) +{ + int rc = MQTTCLIENT_SUCCESS; + MQTTAsyncs* m = handle; + MQTTAsync_queuedCommand* pub; + + FUNC_ENTRY; + if (m == NULL || m->c == NULL) + rc = MQTTCLIENT_FAILURE; + else if (m->c->connected == 0) + rc = MQTTCLIENT_DISCONNECTED; + else if (!UTF8_validateString(destinationName)) + rc = MQTTCLIENT_BAD_UTF8_STRING; + else if (qos < 0 || qos > 2) + rc = MQTTCLIENT_BAD_QOS; + if (rc != MQTTCLIENT_SUCCESS) + goto exit; + + /* Add publish request to operation queue */ + pub = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(pub, '\0', sizeof(MQTTAsync_queuedCommand)); + pub->client = m; + pub->command.type = PUBLISH; + if (response) + { + pub->command.onSuccess = response->onSuccess; + pub->command.onFailure = response->onFailure; + pub->command.context = response->context; + } + pub->command.details.pub.destinationName = malloc(strlen(destinationName) + 1); + strcpy(pub->command.details.pub.destinationName, destinationName); + pub->command.details.pub.payloadlen = payloadlen; + pub->command.details.pub.payload = malloc(payloadlen); + memcpy(pub->command.details.pub.payload, payload, payloadlen); + pub->command.details.pub.qos = qos; + pub->command.details.pub.retained = retained; + rc = MQTTAsync_addCommand(pub, sizeof(pub)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +int MQTTAsync_sendMessage(MQTTAsync handle, char* destinationName, MQTTAsync_message* message, + MQTTAsync_responseOptions* response) +{ + int rc = MQTTCLIENT_SUCCESS; + + FUNC_ENTRY; + if (message == NULL) + { + rc = MQTTCLIENT_NULL_PARAMETER; + goto exit; + } + if (strncmp(message->struct_id, "MQTM", 4) != 0 || message->struct_version != 0) + { + rc = MQTTCLIENT_BAD_STRUCTURE; + goto exit; + } + + rc = MQTTAsync_send(handle, destinationName, message->payloadlen, message->payload, + message->qos, message->retained, response); +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTAsync_retry(void) +{ + static time_t last = 0L; + time_t now; + + FUNC_ENTRY; + time(&(now)); + if (difftime(now, last) > 5) + { + time(&(last)); + MQTTProtocol_keepalive(now); + MQTTProtocol_retry(now, 1); + } + else + MQTTProtocol_retry(now, 0); + FUNC_EXIT; +} + + +int MQTTAsync_connecting(MQTTAsyncs* m) +{ + int rc = -1; + + FUNC_ENTRY; + if (m->c->connect_state == 1) /* TCP connect started - check for completion */ + { + int error; + socklen_t len = sizeof(error); + + if ((rc = getsockopt(m->c->net.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0) + rc = error; + + if (rc != 0) + goto exit; + + Socket_clearPendingWrite(m->c->net.socket); + +#if defined(OPENSSL) + if (m->ssl) + { + m->c->net.ssl = SSLSocket_setSocketForSSL(m->c->net.socket, m->c->sslopts); + rc = SSLSocket_connect(m->c->net.ssl, m->c->net.socket); + if (rc == -1) + m->c->connect_state = 2; + } + else + { +#endif + m->c->connect_state = 3; /* TCP/SSL connect completed, in which case send the MQTT connect packet */ + if ((rc = MQTTPacket_send_connect(m->c)) == SOCKET_ERROR) + goto exit; +#if defined(OPENSSL) + } +#endif + } +#if defined(OPENSSL) + else if (m->c->connect_state == 2) /* SSL connect sent - wait for completion */ + { + if ((rc = SSLSocket_connect(m->c->net.ssl, m->c->net.socket)) != 1) + goto exit; + + m->c->connect_state = 3; /* SSL connect completed, in which case send the MQTT connect packet */ + if ((rc = MQTTPacket_send_connect(m->c)) == SOCKET_ERROR) + goto exit; + } +#endif + +exit: + if ((rc != 0 && m->c->connect_state != 2) || (rc == SSL_FATAL)) + { + if (m->connect.onFailure) + { + Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(m->connect.onFailure))(m->connect.context, NULL); + Thread_lock_mutex(mqttasync_mutex); + } + MQTTProtocol_closeSession(m->c, 0); + } + FUNC_EXIT_RC(rc); + return rc; +} + + +MQTTPacket* MQTTAsync_cycle(int* sock, unsigned long timeout, int* rc) +{ + struct timeval tp = {0L, 0L}; + static Ack ack; + MQTTPacket* pack = NULL; + static int nosockets_count = 0; + + FUNC_ENTRY; + if (timeout > 0L) + { + tp.tv_sec = timeout / 1000; + tp.tv_usec = (timeout % 1000) * 1000; /* this field is microseconds! */ + } + +#if defined(OPENSSL) + if ((*sock = SSLSocket_getPendingRead()) == -1) + { +#endif + /* 0 from getReadySocket indicates no work to do, -1 == error, but can happen normally */ + *sock = Socket_getReadySocket(0, &tp); + if (!tostop && *sock == 0 && (tp.tv_sec > 0L || tp.tv_usec > 0L)) + { + MQTTAsync_sleep(100L); + if (s.clientsds->count == 0) + { + if (++nosockets_count == 50) /* 5 seconds with no sockets */ + tostop = 1; + } + } + else + nosockets_count = 0; +#if defined(OPENSSL) + } +#endif + Thread_lock_mutex(mqttasync_mutex); + if (*sock > 0) + { + MQTTAsyncs* m = NULL; + if (ListFindItem(handles, sock, clientSockCompare) != NULL) + m = (MQTTAsync)(handles->current->content); + if (m != NULL) + { + if (m->c->connect_state == 1 || m->c->connect_state == 2) + *rc = MQTTAsync_connecting(m); + else + pack = MQTTPacket_Factory(&m->c->net, rc); + } + if (pack) + { + int freed = 1; + + /* Note that these handle... functions free the packet structure that they are dealing with */ + if (pack->header.bits.type == PUBLISH) + *rc = MQTTProtocol_handlePublishes(pack, *sock); + else if (pack->header.bits.type == PUBACK || pack->header.bits.type == PUBCOMP) + { + int msgid; + + ack = (pack->header.bits.type == PUBCOMP) ? *(Pubcomp*)pack : *(Puback*)pack; + msgid = ack.msgId; + *rc = (pack->header.bits.type == PUBCOMP) ? + MQTTProtocol_handlePubcomps(pack, *sock) : MQTTProtocol_handlePubacks(pack, *sock); + if (m) + { + ListElement* current = NULL; + + if (m->dc) + { + Log(TRACE_MIN, -1, "Calling deliveryComplete for client %s, msgid %d", m->c->clientID, msgid); + (*(m->dc))(m->context, msgid); + } + /* use the msgid to find the callback to be called */ + while (ListNextElement(m->responses, ¤t)) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); + if (command->command.token == ((Puback*)pack)->msgId) + { + if (!ListDetach(m->responses, command)) /* then remove the response from the list */ + Log(LOG_ERROR, -1, "Publish command not removed from command list"); + if (command->command.onSuccess) + { + MQTTAsync_successData data; + + data.token = command->command.token; + data.alt.pub.destinationName = command->command.details.pub.destinationName; + data.alt.pub.message.payload = command->command.details.pub.payload; + data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; + data.alt.pub.message.qos = command->command.details.pub.qos; + data.alt.pub.message.retained = command->command.details.pub.retained; + Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); + Thread_unlock_mutex(mqttasync_mutex); + (*(command->command.onSuccess))(command->command.context, &data); + Thread_lock_mutex(mqttasync_mutex); + } + MQTTAsync_freeCommand(command); + break; + } + } + } + } + else if (pack->header.bits.type == PUBREC) + *rc = MQTTProtocol_handlePubrecs(pack, *sock); + else if (pack->header.bits.type == PUBREL) + *rc = MQTTProtocol_handlePubrels(pack, *sock); + else if (pack->header.bits.type == PINGRESP) + *rc = MQTTProtocol_handlePingresps(pack, *sock); + else + freed = 0; + if (freed) + pack = NULL; + } + } + MQTTAsync_retry(); + Thread_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(*rc); + return pack; +} + + +int pubCompare(void* a, void* b) +{ + Messages* msg = (Messages*)a; + return msg->publish == (Publications*)b; +} + + +int MQTTAsync_getPendingTokens(MQTTAsync handle, MQTTAsync_token **tokens) +{ + int rc = MQTTCLIENT_SUCCESS; + MQTTAsyncs* m = handle; + *tokens = NULL; + + FUNC_ENTRY; + Thread_lock_mutex(mqttasync_mutex); + + if (m == NULL) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + + if (m->c && m->c->outboundMsgs->count > 0) + { + ListElement* current = NULL; + int count = 0; + + *tokens = malloc(sizeof(MQTTAsync_token) * (m->c->outboundMsgs->count + 1)); + while (ListNextElement(m->c->outboundMsgs, ¤t)) + { + Messages* m = (Messages*)(current->content); + (*tokens)[count++] = m->msgid; + } + (*tokens)[count] = -1; + } + +exit: + Thread_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + + +void MQTTAsync_setTraceLevel(enum MQTTASYNC_TRACE_LEVELS level) +{ + Log_setTraceLevel(level); +} + + +void MQTTAsync_setTraceCallback(MQTTAsync_traceCallback* callback) +{ + Log_setTraceCallback((Log_traceCallback*)callback); +} + + +MQTTAsync_nameValue* MQTTAsync_getVersionInfo() +{ + #define MAX_INFO_STRINGS 8 + static MQTTAsync_nameValue libinfo[MAX_INFO_STRINGS + 1]; + int i = 0; + + libinfo[i].name = "Product name"; + libinfo[i++].value = "Asynchronous MQTT C Client Library"; + + libinfo[i].name = "Version"; + libinfo[i++].value = CLIENT_VERSION; + + libinfo[i].name = "Build level"; + libinfo[i++].value = BUILD_TIMESTAMP; +#if defined(OPENSSL) + libinfo[i].name = "OpenSSL version"; + libinfo[i++].value = SSLeay_version(SSLEAY_VERSION); + + libinfo[i].name = "OpenSSL flags"; + libinfo[i++].value = SSLeay_version(SSLEAY_CFLAGS); + + libinfo[i].name = "OpenSSL build timestamp"; + libinfo[i++].value = SSLeay_version(SSLEAY_BUILT_ON); + + libinfo[i].name = "OpenSSL platform"; + libinfo[i++].value = SSLeay_version(SSLEAY_PLATFORM); + + libinfo[i].name = "OpenSSL directory"; + libinfo[i++].value = SSLeay_version(SSLEAY_DIR); +#endif + libinfo[i].name = NULL; + libinfo[i].value = NULL; + return libinfo; +} diff --git a/src/MQTTAsync.h b/src/MQTTAsync.h new file mode 100644 index 00000000..8a5959c6 --- /dev/null +++ b/src/MQTTAsync.h @@ -0,0 +1,1334 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Ian Craggs - initial API and implementation + * Ian Craggs, Allan Stockdill-Mander - SSL connections + *******************************************************************************/ + +/********************************************************************/ + +/** + * @mainpage Asynchronous MQTT client library for C + * © Copyright IBM Corp. 2009, 2013 + * + * @brief An Asynchronous MQTT client library for C. + * + * An MQTT client application connects to MQTT-capable servers. + * A typical client is responsible for collecting information from a telemetry + * device and publishing the information to the server. It can also subscribe + * to topics, receive messages, and use this information to control the + * telemetry device. + * + * MQTT clients implement the published MQTT v3 protocol. You can write your own + * API to the MQTT protocol using the programming language and platform of your + * choice. This can be time-consuming and error-prone. + * + * To simplify writing MQTT client applications, WebSphere MQ Telemetry provides + * C and Java client libraries that encapsulate the MQTT v3 protocol for a + * number of platforms. If you incorporate these libraries in your MQTT + * applications, a fully functional MQTT client can be written in a few lines of + * code. The information presented here documents the API provided by the IBM + * Asynchronous MQTT Client library for C. + * + * Using the client
+ * Applications that use the client library typically use a similar structure: + *
    + *
  • Create a clent
  • + *
  • Set the options to connect to an MQTT server
  • + *
  • Set up callback functions
  • + *
  • Connect the client to an MQTT server
  • + *
  • Subscribe to any topics the client needs to receive
  • + *
  • Repeat until finished:
  • + *
      + *
    • Publish any messages the client needs to
    • + *
    • Handle any incoming messages
    • + *
    + *
  • Disconnect the client
  • + *
  • Free any memory being used by the client
  • + *
+ * Some simple examples are shown here: + *
    + *
  • @ref publish
  • + *
  • @ref subscribe
  • + *
+ * Additional information about important concepts is provided here: + *
    + *
  • @ref async
  • + *
  • @ref wildcard
  • + *
  • @ref qos
  • + *
+ */ + + +/// @cond EXCLUDE +#if !defined(MQTTASYNC_H) +#define MQTTASYNC_H + +#if defined(WIN32) + #define DLLImport __declspec(dllimport) + #define DLLExport __declspec(dllexport) +#else + #define DLLImport extern + #define DLLExport +#endif + +#include +/// @endcond + +#if !defined(NO_PERSISTENCE) +#include "MQTTClientPersistence.h" +#endif + +/** + * Return code: No error. Indicates successful completion of an MQTT client + * operation. + */ +#define MQTTASYNC_SUCCESS 0 +/** + * Return code: A generic error code indicating the failure of an MQTT client + * operation. + */ +#define MQTTASYNC_FAILURE -1 + +/* error code -2 is MQTTAsync_PERSISTENCE_ERROR */ + +#define MQTTASYNC_PERSISTENCE_ERROR -2 + +/** + * Return code: The client is disconnected. + */ +#define MQTTASYNC_DISCONNECTED -3 +/** + * Return code: The maximum number of messages allowed to be simultaneously + * in-flight has been reached. + */ +#define MQTTASYNC_MAX_MESSAGES_INFLIGHT -4 +/** + * Return code: An invalid UTF-8 string has been detected. + */ +#define MQTTASYNC_BAD_UTF8_STRING -5 +/** + * Return code: A NULL parameter has been supplied when this is invalid. + */ +#define MQTTASYNC_NULL_PARAMETER -6 +/** + * Return code: The topic has been truncated (the topic string includes + * embedded NULL characters). String functions will not access the full topic. + * Use the topic length value to access the full topic. + */ +#define MQTTASYNC_TOPICNAME_TRUNCATED -7 +/** + * Return code: A structure parameter does not have the correct eyecatcher + * and version number. + */ +#define MQTTASYNC_BAD_STRUCTURE -8 +/** + * Return code: A qos parameter is not 0, 1 or 2 + */ +#define MQTTCLIENT_BAD_QOS -9 + +/** + * A handle representing an MQTT client. A valid client handle is available + * following a successful call to MQTTAsync_create(). + */ +typedef void* MQTTAsync; +/** + * A value representing an MQTT message. A token is returned to the + * client application when a message is published. The token can then be used to + * check that the message was successfully delivered to its destination (see + * MQTTAsync_publish(), + * MQTTAsync_publishMessage(), + * MQTTAsync_deliveryComplete(), and + * MQTTAsync_getPendingTokens()). + */ +typedef int MQTTAsync_token; + +/** + * A structure representing the payload and attributes of an MQTT message. The + * message topic is not part of this structure (see MQTTAsync_publishMessage(), + * MQTTAsync_publish(), MQTTAsync_receive(), MQTTAsync_freeMessage() + * and MQTTAsync_messageArrived()). + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTM. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** The length of the MQTT message payload in bytes. */ + int payloadlen; + /** A pointer to the payload of the MQTT message. */ + void* payload; + /** + * The quality of service (QoS) assigned to the message. + * There are three levels of QoS: + *
+ *
QoS0
+ *
Fire and forget - the message may not be delivered
+ *
QoS1
+ *
At least once - the message will be delivered, but may be + * delivered more than once in some circumstances.
+ *
QoS2
+ *
Once and one only - the message will be delivered exactly once.
+ *
+ */ + int qos; + /** + * The retained flag serves two purposes depending on whether the message + * it is associated with is being published or received. + * + * retained = true
+ * For messages being published, a true setting indicates that the MQTT + * server should retain a copy of the message. The message will then be + * transmitted to new subscribers to a topic that matches the message topic. + * For subscribers registering a new subscription, the flag being true + * indicates that the received message is not a new one, but one that has + * been retained by the MQTT server. + * + * retained = false
+ * For publishers, this ndicates that this message should not be retained + * by the MQTT server. For subscribers, a false setting indicates this is + * a normal message, received as a result of it being published to the + * server. + */ + int retained; + /** + * The dup flag indicates whether or not this message is a duplicate. + * It is only meaningful when receiving QoS1 messages. When true, the + * client application should take appropriate action to deal with the + * duplicate message. + */ + int dup; + /** The message identifier is normally reserved for internal use by the + * MQTT client and server. + */ + int msgid; +} MQTTAsync_message; + +#define MQTTAsync_message_initializer { "MQTM", 0, 0, NULL, 0, 0, 0, 0 } + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * receipt of messages. The function is registered with the client library by + * passing it as an argument to MQTTAsync_setCallbacks(). It is + * called by the client library when a new message that matches a client + * subscription has been received from the server. This function is executed on + * a separate thread to the one on which the client application is running. + * @param context A pointer to the context value originally passed to + * MQTTAsync_setCallbacks(), which contains any application-specific context. + * @param topicName The topic associated with the received message. + * @param topicLen The length of the topic if there are one + * more NULL characters embedded in topicName, otherwise topicLen + * is 0. If topicLen is 0, the value returned by strlen(topicName) + * can be trusted. If topicLen is greater than 0, the full topic name + * can be retrieved by accessing topicName as a byte array of length + * topicLen. + * @param message The MQTTAsync_message structure for the received message. + * This structure contains the message payload and attributes. + * @return This function must return a boolean value indicating whether or not + * the message has been safely received by the client application. Returning + * true indicates that the message has been successfully handled. + * Returning false indicates that there was a problem. In this + * case, the client library will reinvoke MQTTAsync_messageArrived() to + * attempt to deliver the message to the application again. + */ +typedef int MQTTAsync_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message); + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * notification of delivery of messages to the server. The function is + * registered with the client library by passing it as an argument to MQTTAsync_setCallbacks(). + * It is called by the client library after the client application has + * published a message to the server. It indicates that the necessary + * handshaking and acknowledgements for the requested quality of service (see + * MQTTAsync_message.qos) have been completed. This function is executed on a + * separate thread to the one on which the client application is running. + * @param context A pointer to the context value originally passed to + * MQTTAsync_setCallbacks(), which contains any application-specific context. + * @param token The ::MQTTAsync_token associated with + * the published message. Applications can check that all messages have been + * correctly published by matching the tokens returned from calls to + * MQTTAsync_send() and MQTTAsync_sendMessage() with the tokens passed + * to this callback. + */ +typedef void MQTTAsync_deliveryComplete(void* context, MQTTAsync_token token); + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * notification of the loss of connection to the server. The function is + * registered with the client library by passing it as an argument to + * MQTTAsync_setCallbacks(). It is called by the client library if the client + * loses its connection to the server. The client application must take + * appropriate action, such as trying to reconnect or reporting the problem. + * This function is executed on a separate thread to the one on which the + * client application is running. + * @param context A pointer to the context value originally passed to + * MQTTAsync_setCallbacks(), which contains any application-specific context. + * @param cause The reason for the disconnection. + * Currently, cause is always set to NULL. + */ +typedef void MQTTAsync_connectionLost(void* context, char* cause); + +/** The data returned on completion of an unsuccessful API call in the response callback onFailure. */ +typedef struct +{ + /** A token identifying the failed request. */ + MQTTAsync_token token; + /** A numeric code identifying the error. */ + int code; + /** Optional text explaining the error. Can be NULL. */ + char* message; +} MQTTAsync_failureData; + +/** The data returned on completion of a successful API call in the response callback onSuccess. */ +typedef struct +{ + /** A token identifying the successful request. Can be used to refer to the request later. */ + MQTTAsync_token token; + /** A union of the different values that can be returned for subscribe, unsubscribe and publish. */ + union + { + /** For subscribe, the granted QoS of the subscription returned by the server. */ + int qos; + /** For subscribeMany, the list of granted QoSs of the subscriptions returned by the server. */ + int* qosList; + /** For publish, the message being sent to the server. */ + struct + { + MQTTAsync_message message; + char* destinationName; + } pub; + } alt; +} MQTTAsync_successData; + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * notification of the successful completion of an API call. The function is + * registered with the client library by passing it as an argument in + * ::MQTTAsync_responseOptions. + * @param context A pointer to the context value originally passed to + * ::MQTTAsync_responseOptions, which contains any application-specific context. + * @param response Any success data associated with the API completion. + */ +typedef void MQTTAsync_onSuccess(void* context, MQTTAsync_successData* response); + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * notification of the unsuccessful completion of an API call. The function is + * registered with the client library by passing it as an argument in + * ::MQTTAsync_responseOptions. + * @param context A pointer to the context value originally passed to + * ::MQTTAsync_responseOptions, which contains any application-specific context. + * @param response Any failure data associated with the API completion. + */ +typedef void MQTTAsync_onFailure(void* context, MQTTAsync_failureData* response); + +typedef struct +{ + /** The eyecatcher for this structure. Must be MQTR */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** + * A pointer to a callback function to be called if the API call successfully + * completes. Can be set to NULL, in which case no indication of successful + * completion will be received. + */ + MQTTAsync_onSuccess* onSuccess; + /** + * A pointer to a callback function to be called if the API call fails. + * Can be set to NULL, in which case no indication of unsuccessful + * completion will be received. + */ + MQTTAsync_onFailure* onFailure; + /** + * A pointer to any application-specific context. The + * the context pointer is passed to success or failure callback functions to + * provide access to the context information in the callback. + */ + void* context; + MQTTAsync_token token; /* output */ +} MQTTAsync_responseOptions; + +#define MQTTAsync_responseOptions_initializer { "MQTR", 0, NULL, NULL, 0, 0 } + + +/** + * This function sets the global callback functions for a specific client. + * If your client application doesn't use a particular callback, set the + * relevant parameter to NULL. Any necessary message acknowledgements and + * status communications are handled in the background without any intervention + * from the client application. If you do not set a messageArrived callback + * function, you will not be notified of the receipt of any messages as a + * result of a subscription. + * + * Note: The MQTT client must be disconnected when this function is + * called. + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param context A pointer to any application-specific context. The + * the context pointer is passed to each of the callback functions to + * provide access to the context information in the callback. + * @param cl A pointer to an MQTTAsync_connectionLost() callback + * function. You can set this to NULL if your application doesn't handle + * disconnections. + * @param ma A pointer to an MQTTAsync_messageArrived() callback + * function. You can set this to NULL if your application doesn't handle + * receipt of messages. + * @param dc A pointer to an MQTTAsync_deliveryComplete() callback + * function. You can set this to NULL if you do not want to check + * for successful delivery. + * @return ::MQTTASYNC_SUCCESS if the callbacks were correctly set, + * ::MQTTASYNC_FAILURE if an error occurred. + */ +DLLExport int MQTTAsync_setCallbacks(MQTTAsync handle, void* context, MQTTAsync_connectionLost* cl, + MQTTAsync_messageArrived* ma, MQTTAsync_deliveryComplete* dc); + + +/** + * This function creates an MQTT client ready for connection to the + * specified server and using the specified persistent storage (see + * MQTTAsync_persistence). See also MQTTAsync_destroy(). + * @param handle A pointer to an ::MQTTClient handle. The handle is + * populated with a valid client reference following a successful return from + * this function. + * @param serverURI A null-terminated string specifying the server to + * which the client will connect. It takes the form protocol://host:port. + * protocol must be tcp or ssl. For host, you can + * specify either an IP address or a domain name. For instance, to connect to + * a server running on the local machines with the default MQTT port, specify + * tcp://localhost:1883. + * @param clientId The client identifier passed to the server when the + * client connects to it. It is a null-terminated UTF-8 encoded string. + * ClientIDs must be no longer than 23 characters according to the MQTT + * specification. + * @param persistence_type The type of persistence to be used by the client: + *
+ * ::MQTTCLIENT_PERSISTENCE_NONE: Use in-memory persistence. If the device or + * system on which the client is running fails or is switched off, the current + * state of any in-flight messages is lost and some messages may not be + * delivered even at QoS1 and QoS2. + *
+ * ::MQTTCLIENT_PERSISTENCE_DEFAULT: Use the default (file system-based) + * persistence mechanism. Status about in-flight messages is held in persistent + * storage and provides some protection against message loss in the case of + * unexpected failure. + *
+ * ::MQTTCLIENT_PERSISTENCE_USER: Use an application-specific persistence + * implementation. Using this type of persistence gives control of the + * persistence mechanism to the application. The application has to implement + * the MQTTAsync_persistence interface. + * @param persistence_context If the application uses + * ::MQTTASYNC_PERSISTENCE_NONE persistence, this argument is unused and should + * be set to NULL. For ::MQTTASYNC_PERSISTENCE_DEFAULT persistence, it + * should be set to the location of the persistence directory (if set + * to NULL, the persistence directory used is the working directory). + * Applications that use ::MQTTASYNC_PERSISTENCE_USER persistence set this + * argument to point to a valid MQTTAsync_persistence structure. + * @return ::MQTTASYNC_SUCCESS if the client is successfully created, otherwise + * an error code is returned. + */ +DLLExport int MQTTAsync_create(MQTTAsync* handle, char* serverURI, char* clientId, + int persistence_type, void* persistence_context); + +/** + * MQTTAsync_willOptions defines the MQTT "Last Will and Testament" (LWT) settings for + * the client. In the event that a client unexpectedly loses its connection to + * the server, the server publishes the LWT message to the LWT topic on + * behalf of the client. This allows other clients (subscribed to the LWT topic) + * to be made aware that the client has disconnected. To enable the LWT + * function for a specific client, a valid pointer to an MQTTAsync_willOptions + * structure is passed in the MQTTAsync_connectOptions structure used in the + * MQTTAsync_connect() call that connects the client to the server. The pointer + * to MQTTAsync_willOptions can be set to NULL if the LWT function is not + * required. + */ +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. */ + char* topicName; + /** The LWT payload. */ + char* message; + /** + * The retained flag for the LWT message (see MQTTAsync_message.retained). + */ + int retained; + /** + * The quality of service setting for the LWT message (see + * MQTTAsync_message.qos and @ref qos). + */ + int qos; +} MQTTAsync_willOptions; + +#define MQTTAsync_willOptions_initializer { "MQTW", 0, NULL, NULL, 0, 0 } + +/** +* MQTTAsync_sslProperties defines the settings to establish an SSL/TLS connection using the +* OpenSSL library. It covers the following scenarios: +* - Server authentication: The client needs the digital certificate of the server. It is included +* in a store containting trusted material (also known as "trust store"). +* - Mutual authentication: Both client and server are authenticated during the SSL handshake. In +* addition to the digital certificate of the server in a trust store, the client will need its own +* digital certificate and the private key used to sign its digital certificate stored in a "key store". +* - Anonymous connection: Both client and server do not get authenticated and no credentials are needed +* to establish an SSL connection. Note that this scenario is not fully secure since it is subject to +* man-in-the-middle attacks. +*/ +typedef struct +{ + /** The eyecatcher for this structure. Must be MQTS */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + + /** The file in PEM format containing the public digital certificates trusted by the client. */ + char* trustStore; + + /** The file in PEM format containing the public certificate chain of the client. It may also include + * the client's private key. + */ + char* keyStore; + + /** If not included in the sslKeyStore, this setting points to the file in PEM format containing + * the client's private key. + */ + char* privateKey; + /** The password to load the client's privateKey if encrypted. */ + char* privateKeyPassword; + + /** + * The list of cipher suites that the client will present to the server during the SSL handshake. For a + * full explanation of the cipher list format, please see the OpenSSL on-line documentation: + * http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT + * If this setting is ommitted, its default value will be "ALL", that is, all the cipher suites -excluding + * those offering no encryption- will be considered. + * This setting can be used to set an SSL anonymous connection ("aNULL" string value, for instance). + */ + char* enabledCipherSuites; + + /** True/False option to enable verification of the server certificate **/ + int enableServerCertAuth; + +} MQTTAsync_SSLOptions; + +#define MQTTAsync_SSLOptions_initializer { "MQTS", 0, NULL, NULL, NULL, NULL, NULL, 1 } + +/** + * MQTTAsync_connectOptions defines several settings that control the way the + * client connects to an MQTT server. Default values are set in + * MQTTAsync_connectOptions_initializer. + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTC. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 or 1. 0 signifies no SSL options */ + int struct_version; + /** The "keep alive" interval, measured in seconds, defines the maximum time + * that should pass without communication between the client and the server + * The client will ensure that at least one message travels across the + * network within each keep alive period. In the absence of a data-related + * message during the time period, the client sends a very small MQTT + * "ping" message, which the server will acknowledge. The keep alive + * interval enables the client to detect when the server is no longer + * available without having to wait for the long TCP/IP timeout. + * Set to 0 if you do not want any keep alive processing. + */ + int keepAliveInterval; + /** + * This is a boolean value. The cleansession setting controls the behaviour + * of both the client and the server at connection and disconnection time. + * The client and server both maintain session state information. This + * information is used to ensure "at least once" and "exactly once" + * delivery, and "exactly once" receipt of messages. Session state also + * includes subscriptions created by an MQTT client. You can choose to + * maintain or discard state information between sessions. + * + * When cleansession is true, the state information is discarded at + * connect and disconnect. Setting cleansession to false keeps the state + * information. When you connect an MQTT client application with + * MQTTAsync_connect(), the client identifies the connection using the + * client identifier and the address of the server. The server checks + * whether session information for this client + * has been saved from a previous connection to the server. If a previous + * session still exists, and cleansession=true, then the previous session + * information at the client and server is cleared. If cleansession=false, + * the previous session is resumed. If no previous session exists, a new + * session is started. + */ + int cleansession; + /** + * This controls how many messages can be in-flight simultaneously. + */ + int maxInflight; + /** + * This is a pointer to an MQTTAsync_willOptions structure. If your + * application does not make use of the Last Will and Testament feature, + * set this pointer to NULL. + */ + MQTTAsync_willOptions* will; + /** + * MQTT servers that support the MQTT v3.1 protocol provide authentication + * and authorisation by user name and password. This is the user name + * parameter. + */ + char* username; + /** + * MQTT servers that support the MQTT v3.1 protocol provide authentication + * and authorisation by user name and password. This is the password + * parameter. + */ + char* password; + /** + * The time interval in seconds to allow a connect to complete. + */ + int connectTimeout; + /** + * The time interval in seconds + */ + int retryInterval; + /** + * This is a pointer to an MQTTAsync_SSLOptions structure. If your + * application does not make use of SSL, set this pointer to NULL. + */ + MQTTAsync_SSLOptions* ssl; + /** + * A pointer to a callback function to be called if the connect successfully + * completes. Can be set to NULL, in which case no indication of successful + * completion will be received. + */ + MQTTAsync_onSuccess* onSuccess; + /** + * A pointer to a callback function to be called if the connect fails. + * Can be set to NULL, in which case no indication of unsuccessful + * completion will be received. + */ + MQTTAsync_onFailure* onFailure; + /** + * A pointer to any application-specific context. The + * the context pointer is passed to success or failure callback functions to + * provide access to the context information in the callback. + */ + void* context; +} MQTTAsync_connectOptions; + + +#define MQTTAsync_connectOptions_initializer { "MQTC", 1, 60, 1, 10, NULL, NULL, NULL, 30, 20, NULL, NULL } + +/** + * This function attempts to connect a previously-created client (see + * MQTTAsync_create()) to an MQTT server using the specified options. If you + * want to enable asynchronous message and status notifications, you must call + * MQTTAsync_setCallbacks() prior to MQTTAsync_connect(). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param options A pointer to a valid MQTTAsync_connectOptions + * structure. + * @return ::MQTTASYNC_SUCCESS if the client connect request was accepted. + * If the client was unable to connect to the server, an error code is + * returned via the onFailure callback, if set. + * Error codes greater than 0 are returned by the MQTT protocol:

+ * 1: Connection refused: Unacceptable protocol version
+ * 2: Connection refused: Identifier rejected
+ * 3: Connection refused: Server unavailable
+ * 4: Connection refused: Bad user name or password
+ * 5: Connection refused: Not authorized
+ * 6-255: Reserved for future use
+ */ +DLLExport int MQTTAsync_connect(MQTTAsync handle, MQTTAsync_connectOptions* options); + + +typedef struct +{ + /** The eyecatcher for this structure. Must be MQTD. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 or 1. 0 signifies no SSL options */ + int struct_version; + /** + * The client delays disconnection for up to this time (in + * milliseconds) in order to allow in-flight message transfers to complete. + */ + int timeout; + /** + * A pointer to a callback function to be called if the disconnect successfully + * completes. Can be set to NULL, in which case no indication of successful + * completion will be received. + */ + MQTTAsync_onSuccess* onSuccess; + /** + * A pointer to a callback function to be called if the disconnect fails. + * Can be set to NULL, in which case no indication of unsuccessful + * completion will be received. + */ + MQTTAsync_onFailure* onFailure; + /** + * A pointer to any application-specific context. The + * the context pointer is passed to success or failure callback functions to + * provide access to the context information in the callback. + */ + void* context; +} MQTTAsync_disconnectOptions; + +#define MQTTAsync_disconnectOptions_initializer { "MQTD", 0, 0, NULL, NULL, NULL } + + +/** + * This function attempts to disconnect the client from the MQTT + * server. In order to allow the client time to complete handling of messages + * that are in-flight when this function is called, a timeout period is + * specified. When the timeout period has expired, the client disconnects even + * if there are still outstanding message acknowledgements. + * The next time the client connects to the same server, any QoS 1 or 2 + * messages which have not completed will be retried depending on the + * cleansession settings for both the previous and the new connection (see + * MQTTAsync_connectOptions.cleansession and MQTTAsync_connect()). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param options The client delays disconnection for up to this time (in + * milliseconds) in order to allow in-flight message transfers to complete. + * @return ::MQTTASYNC_SUCCESS if the client successfully disconnects from + * the server. An error code is returned if the client was unable to disconnect + * from the server + */ +DLLExport int MQTTAsync_disconnect(MQTTAsync handle, MQTTAsync_disconnectOptions* options); + + +/** + * This function allows the client application to test whether or not a + * client is currently connected to the MQTT server. + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @return Boolean true if the client is connected, otherwise false. + */ +DLLExport int MQTTAsync_isConnected(MQTTAsync handle); + + +/** + * This function attempts to subscribe a client to a single topic, which may + * contain wildcards (see @ref wildcard). This call also specifies the + * @ref qos requested for the subscription + * (see also MQTTAsync_subscribeMany()). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param topic The subscription topic, which may include wildcards. + * @param qos The requested quality of service for the subscription. + * @param response A pointer to a response options structure. Used to set callback functions. + * @return ::MQTTASYNC_SUCCESS if the subscription request is successful. + * An error code is returned if there was a problem registering the + * subscription. + */ +DLLExport int MQTTAsync_subscribe(MQTTAsync handle, char* topic, int qos, MQTTAsync_responseOptions* response); + + +/** + * This function attempts to subscribe a client to a list of topics, which may + * contain wildcards (see @ref wildcard). This call also specifies the + * @ref qos requested for each topic (see also MQTTAsync_subscribe()). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param count The number of topics for which the client is requesting + * subscriptions. + * @param topic An array (of length count) of pointers to + * topics, each of which may include wildcards. + * @param qos An array (of length count) of @ref qos + * values. qos[n] is the requested QoS for topic[n]. + * @param response A pointer to a response options structure. Used to set callback functions. + * @return ::MQTTASYNC_SUCCESS if the subscription request is successful. + * An error code is returned if there was a problem registering the + * subscriptions. + */ +DLLExport int MQTTAsync_subscribeMany(MQTTAsync handle, int count, char** topic, int* qos, MQTTAsync_responseOptions* response); + +/** + * This function attempts to remove an existing subscription made by the + * specified client. + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param topic The topic for the subscription to be removed, which may + * include wildcards (see @ref wildcard). + * @param response A pointer to a response options structure. Used to set callback functions. + * @return ::MQTTASYNC_SUCCESS if the subscription is removed. + * An error code is returned if there was a problem removing the + * subscription. + */ +DLLExport int MQTTAsync_unsubscribe(MQTTAsync handle, char* topic, MQTTAsync_responseOptions* response); + +/** + * This function attempts to remove existing subscriptions to a list of topics + * made by the specified client. + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param count The number subscriptions to be removed. + * @param topic An array (of length count) of pointers to the topics of + * the subscriptions to be removed, each of which may include wildcards. + * @param response A pointer to a response options structure. Used to set callback functions. + * @return ::MQTTASYNC_SUCCESS if the subscriptions are removed. + * An error code is returned if there was a problem removing the subscriptions. + */ +DLLExport int MQTTAsync_unsubscribeMany(MQTTAsync handle, int count, char** topic, MQTTAsync_responseOptions* response); + + +/** + * This function attempts to publish a message to a given topic (see also + * ::MQTTAsync_sendMessage()). An ::MQTTAsync_token is issued when + * this function returns successfully. If the client application needs to + * test for successful delivery of messages, a callback should be set + * (see ::MQTTAsync_onSuccess() and ::MQTTAsync_deliveryComplete()). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param destinationName The topic associated with this message. + * @param payloadlen The length of the payload in bytes. + * @param payload A pointer to the byte array payload of the message. + * @param qos The @ref qos of the message. + * @param retained The retained flag for the message. + * @param response A pointer to an ::MQTTAsync_responseOptions structure. Used to set callback functions. + * This is optional and can be set to NULL. + * @return ::MQTTASYNC_SUCCESS if the message is accepted for publication. + * An error code is returned if there was a problem accepting the message. + */ +DLLExport int MQTTAsync_send(MQTTAsync handle, char* destinationName, int payloadlen, void* payload, int qos, int retained, + MQTTAsync_responseOptions* response); + + +/** + * This function attempts to publish a message to a given topic (see also + * MQTTAsync_publish()). An ::MQTTAsync_token is issued when + * this function returns successfully. If the client application needs to + * test for successful delivery of messages, a callback should be set + * (see ::MQTTAsync_onSuccess() and ::MQTTAsync_deliveryComplete()). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param destinationName The topic associated with this message. + * @param msg A pointer to a valid MQTTAsync_message structure containing + * the payload and attributes of the message to be published. + * @param response A pointer to an ::MQTTAsync_responseOptions structure. Used to set callback functions. + * @return ::MQTTASYNC_SUCCESS if the message is accepted for publication. + * An error code is returned if there was a problem accepting the message. + */ +DLLExport int MQTTAsync_sendMessage(MQTTAsync handle, char* destinationName, MQTTAsync_message* msg, MQTTAsync_responseOptions* response); + + +/** + * This function sets a pointer to an array of tokens for + * messages that are currently in-flight (pending completion). + * + * Important note: The memory used to hold the array of tokens is + * malloc()'d in this function. The client application is responsible for + * freeing this memory when it is no longer required. + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param tokens The address of a pointer to an ::MQTTAsync_token. + * When the function returns successfully, the pointer is set to point to an + * array of tokens representing messages pending completion. The last member of + * the array is set to -1 to indicate there are no more tokens. If no tokens + * are pending, the pointer is set to NULL. + * @return ::MQTTASYNC_SUCCESS if the function returns successfully. + * An error code is returned if there was a problem obtaining the list of + * pending tokens. + */ +DLLExport int MQTTAsync_getPendingTokens(MQTTAsync handle, MQTTAsync_token **tokens); + + +/** + * This function frees memory allocated to an MQTT message, including the + * additional memory allocated to the message payload. The client application + * calls this function when the message has been fully processed. Important + * note: This function does not free the memory allocated to a message + * topic string. It is the responsibility of the client application to free + * this memory using the MQTTAsync_free() library function. + * @param msg The address of a pointer to the ::MQTTAsync_message structure + * to be freed. + */ +DLLExport void MQTTAsync_freeMessage(MQTTAsync_message** msg); + +/** + * This function frees memory allocated by the MQTT C client library, especially the + * topic name. This is needed on Windows when the client libary and application + * program have been compiled with different versions of the C compiler. It is + * thus good policy to always use this function when freeing any MQTT C client- + * allocated memory. + * @param ptr The pointer to the client library storage to be freed. + */ +DLLExport void MQTTAsync_free(void* ptr); + +/** + * This function frees the memory allocated to an MQTT client (see + * MQTTAsync_create()). It should be called when the client is no longer + * required. + * @param handle A pointer to the handle referring to the ::MQTTAsync + * structure to be freed. + */ +DLLExport void MQTTAsync_destroy(MQTTAsync* handle); + + + +enum MQTTASYNC_TRACE_LEVELS +{ + MQTTASYNC_TRACE_MAXIMUM = 1, + MQTTASYNC_TRACE_MEDIUM, + MQTTASYNC_TRACE_MINIMUM, + MQTTASYNC_TRACE_PROTOCOL, + MQTTASYNC_TRACE_ERROR, + MQTTASYNC_TRACE_SEVERE, + MQTTASYNC_TRACE_FATAL, +}; + + +/** + * This function sets the level of trace information which will be + * returned in the trace callback. + * @param level the trace level required + */ +DLLExport void MQTTAsync_setTraceLevel(enum MQTTASYNC_TRACE_LEVELS level); + + +/** + * This is a callback function prototype which must be implemented if you want + * to receive trace information. + * @param level the trace level of the message returned + * @param meesage the trace message. This is a pointer to a static buffer which + * will be overwritten on each call. You must copy the data if you want to keep + * it for later. + */ +typedef void MQTTAsync_traceCallback(enum MQTTASYNC_TRACE_LEVELS level, char* message); + +/** + * This function sets the trace callback if needed. If set to NULL, + * no trace information will be returned. The default trace level is + * MQTTASYNC_TRACE_MINIMUM. + * @param callback a pointer to the function which will handle the trace information + */ +DLLExport void MQTTAsync_setTraceCallback(MQTTAsync_traceCallback* callback); + + +typedef struct +{ + const char* name; + const char* value; +} MQTTAsync_nameValue; + +/** + * This function returns version information about the library. + * no trace information will be returned. The default trace level is + * MQTTASYNC_TRACE_MINIMUM + * @return an array of strings describing the library. The last entry is a NULL pointer. + */ +DLLExport MQTTAsync_nameValue* MQTTAsync_getVersionInfo(); + + +/** + * @page async Threading + * The client application runs on several threads. + * Processing of handshaking and maintaining + * the network connection is performed in the background. + * Notifications of status and message reception are provided to the client + * application using callbacks registered with the library by the call to + * MQTTAsync_setCallbacks() (see MQTTAsync_messageArrived(), + * MQTTAsync_connectionLost() and MQTTAsync_deliveryComplete()). + * + * @page wildcard Subscription wildcards + * Every MQTT message includes a topic that classifies it. MQTT servers use + * topics to determine which subscribers should receive messages published to + * the server. + * + * Consider the server receiving messages from several environmental sensors. + * Each sensor publishes its measurement data as a message with an associated + * topic. Subscribing applications need to know which sensor originally + * published each received message. A unique topic is thus used to identify + * each sensor and measurement type. Topics such as SENSOR1TEMP, + * SENSOR1HUMIDITY, SENSOR2TEMP and so on achieve this but are not very + * flexible. If additional sensors are added to the system at a later date, + * subscribing applications must be modified to receive them. + * + * To provide more flexibility, MQTT supports a hierarchical topic namespace. + * This allows application designers to organize topics to simplify their + * management. Levels in the hierarchy are delimited by the '/' character, + * such as SENSOR/1/HUMIDITY. Publishers and subscribers use these + * hierarchical topics as already described. + * + * For subscriptions, two wildcard characters are supported: + *
    + *
  • A '#' character represents a complete sub-tree of the hierarchy and + * thus must be the last character in a subscription topic string, such as + * SENSOR/#. This will match any topic starting with SENSOR/, such as + * SENSOR/1/TEMP and SENSOR/2/HUMIDITY.
  • + *
  • A '+' character represents a single level of the hierarchy and is + * used between delimiters. For example, SENSOR/+/TEMP will match + * SENSOR/1/TEMP and SENSOR/2/TEMP.
  • + *
+ * Publishers are not allowed to use the wildcard characters in their topic + * names. + * + * Deciding on your topic hierarchy is an important step in your system design. + * + * @page qos Quality of service + * The MQTT protocol provides three qualities of service for delivering + * messages between clients and servers: "at most once", "at least once" and + * "exactly once". + * + * Quality of service (QoS) is an attribute of an individual message being + * published. An application sets the QoS for a specific message by setting the + * MQTTAsync_message.qos field to the required value. + * + * A subscribing client can set the maximum quality of service a server uses + * to send messages that match the client subscriptions. The + * MQTTAsync_subscribe() and MQTTAsync_subscribeMany() functions set this + * maximum. The QoS of a message forwarded to a subscriber thus might be + * different to the QoS given to the message by the original publisher. + * The lower of the two values is used to forward a message. + * + * The three levels are: + * + * QoS0, At most once: The message is delivered at most once, or it + * may not be delivered at all. Its delivery across the network is not + * acknowledged. The message is not stored. The message could be lost if the + * client is disconnected, or if the server fails. QoS0 is the fastest mode of + * transfer. It is sometimes called "fire and forget". + * + * The MQTT protocol does not require servers to forward publications at QoS0 + * to a client. If the client is disconnected at the time the server receives + * the publication, the publication might be discarded, depending on the + * server implementation. + * + * QoS1, At least once: The message is always delivered at least once. + * It might be delivered multiple times if there is a failure before an + * acknowledgment is received by the sender. The message must be stored + * locally at the sender, until the sender receives confirmation that the + * message has been published by the receiver. The message is stored in case + * the message must be sent again. + * + * QoS2, Exactly once: The message is always delivered exactly once. + * The message must be stored locally at the sender, until the sender receives + * confirmation that the message has been published by the receiver. The + * message is stored in case the message must be sent again. QoS2 is the + * safest, but slowest mode of transfer. A more sophisticated handshaking + * and acknowledgement sequence is used than for QoS1 to ensure no duplication + * of messages occurs. + + + * @page publish Publication example + * @code +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTAsync.h" + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientPub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +volatile MQTTAsync_token deliveredtoken; + +int finished = 0; + +void connlost(void *context, char *cause) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + int rc; + + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); + + printf("Reconnecting\n"); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + finished = 1; + } +} + + +void onDisconnect(void* context, MQTTAsync_successData* response) +{ + printf("Successful disconnection\n"); + finished = 1; +} + + +void onSend(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + printf("Message with token value %d delivery confirmed\n", response->token); + + opts.onSuccess = onDisconnect; + opts.context = client; + + if ((rc = MQTTAsync_disconnect(client, &opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start sendMessage, return code %d\n", rc); + exit(-1); + } +} + + +void onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Connect failed, rc %d\n", response->code); + finished = 1; +} + + +void onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + printf("Successful connection\n"); + + opts.onSuccess = onSend; + opts.context = client; + + pubmsg.payload = PAYLOAD; + pubmsg.payloadlen = strlen(PAYLOAD); + pubmsg.qos = QOS; + pubmsg.retained = 0; + deliveredtoken = 0; + + if ((rc = MQTTAsync_sendMessage(client, TOPIC, &pubmsg, &opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start sendMessage, return code %d\n", rc); + exit(-1); + } +} + + +int main(int argc, char* argv[]) +{ + MQTTAsync client; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_token token; + int rc; + + MQTTAsync_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + MQTTAsync_setCallbacks(client, NULL, connlost, NULL, NULL); + + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + exit(-1); + } + + printf("Waiting for publication of %s\n" + "on topic %s for client with ClientID: %s\n", + PAYLOAD, TOPIC, CLIENTID); + while (!finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + MQTTAsync_destroy(&client); + return rc; +} + + * @endcode + * @page subscribe Subscription example + * @code +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTAsync.h" + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientSub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +volatile MQTTAsync_token deliveredtoken; + +int disc_finished = 0; +int subscribed = 0; +int finished = 0; + +void connlost(void *context, char *cause) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + int rc; + + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); + + printf("Reconnecting\n"); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + finished = 1; + } +} + + +int msgarrvd(void *context, char *topicName, int topicLen, MQTTAsync_message *message) +{ + int i; + char* payloadptr; + + printf("Message arrived\n"); + printf(" topic: %s\n", topicName); + printf(" message: "); + + payloadptr = message->payload; + for(i=0; ipayloadlen; i++) + { + putchar(*payloadptr++); + } + putchar('\n'); + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + return 1; +} + + +void onDisconnect(void* context, MQTTAsync_successData* response) +{ + printf("Successful disconnection\n"); + disc_finished = 1; +} + + +void onSubscribe(void* context, MQTTAsync_successData* response) +{ + printf("Subscribe succeeded\n"); + subscribed = 1; +} + +void onSubscribeFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Subscribe failed, rc %d\n", response->code); + finished = 1; +} + + +void onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Connect failed, rc %d\n", response->code); + finished = 1; +} + + +void onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + printf("Successful connection\n"); + + printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n" + "Press Q to quit\n\n", TOPIC, CLIENTID, QOS); + opts.onSuccess = onSubscribe; + opts.onFailure = onSubscribeFailure; + opts.context = client; + + deliveredtoken = 0; + + if ((rc = MQTTAsync_subscribe(client, TOPIC, QOS, &opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start subscribe, return code %d\n", rc); + exit(-1); + } +} + + +int main(int argc, char* argv[]) +{ + MQTTAsync client; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_disconnectOptions disc_opts = MQTTAsync_disconnectOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_token token; + int rc; + int ch; + + MQTTAsync_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + MQTTAsync_setCallbacks(client, NULL, connlost, msgarrvd, NULL); + + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + exit(-1); + } + + while (!subscribed) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + if (finished) + goto exit; + + do + { + ch = getchar(); + } while (ch!='Q' && ch != 'q'); + + disc_opts.onSuccess = onDisconnect; + if ((rc = MQTTAsync_disconnect(client, &disc_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start disconnect, return code %d\n", rc); + exit(-1); + } + while (!disc_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + +exit: + MQTTAsync_destroy(&client); + return rc; +} + + * @endcode + */ + + +#endif diff --git a/src/MQTTClient.c b/src/MQTTClient.c index 51f147f8..390427e7 100644 --- a/src/MQTTClient.c +++ b/src/MQTTClient.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -10,6 +10,7 @@ * Ian Craggs - initial API and implementation and/or initial documentation * Ian Craggs - bug 384016 - segv setting will message * Ian Craggs - bug 384053 - v1.0.0.7 - stop MQTTClient_receive on socket error + * Ian Craggs, Allan Stockdill-Mander - add ability to connect with SSL *******************************************************************************/ #include @@ -29,10 +30,14 @@ #include "StackTrace.h" #include "Heap.h" +#if defined(OPENSSL) +#include +#endif + #define URI_TCP "tcp://" #define BUILD_TIMESTAMP __DATE__ " " __TIME__ /* __TIMESTAMP__ */ -#define CLIENT_VERSION "1.0.0.7" /* __VERSION__ */ +#define CLIENT_VERSION "1.0.0.8" char* client_timestamp_eye = "MQTTClientV3_Timestamp " BUILD_TIMESTAMP; char* client_version_eye = "MQTTClientV3_Version " CLIENT_VERSION; @@ -50,6 +55,8 @@ MQTTProtocol state; #if defined(WIN32) static mutex_type mqttclient_mutex = NULL; extern mutex_type stack_mutex; +extern mutex_type heap_mutex; +extern mutex_type log_mutex; BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) @@ -62,6 +69,8 @@ BOOL APIENTRY DllMain(HANDLE hModule, { mqttclient_mutex = CreateMutex(NULL, 0, NULL); stack_mutex = CreateMutex(NULL, 0, NULL); + heap_mutex = CreateMutex(NULL, 0, NULL); + log_mutex = CreateMutex(NULL, 0, NULL); } case DLL_THREAD_ATTACH: Log(TRACE_MAX, -1, "DLL thread attach"); @@ -104,6 +113,9 @@ typedef struct typedef struct { char* serverURI; +#if defined(OPENSSL) + int ssl; +#endif Clients* c; MQTTClient_connectionLost* cl; MQTTClient_messageArrived* ma; @@ -119,7 +131,6 @@ typedef struct } MQTTClients; - void MQTTClient_sleep(long milliseconds) { FUNC_ENTRY; @@ -210,10 +221,13 @@ int MQTTClient_create(MQTTClient* handle, char* serverURI, char* clientId, #if defined(HEAP_H) Heap_initialize(); #endif - Log_initialize(); + Log_initialize(NULL); bstate->clients = ListInitialize(); Socket_outInitialize(); handles = ListInitialize(); +#if defined(OPENSSL) + SSLSocket_initialize(); +#endif initialized = 1; } m = malloc(sizeof(MQTTClients)); @@ -221,6 +235,13 @@ int MQTTClient_create(MQTTClient* handle, char* serverURI, char* clientId, memset(m, '\0', sizeof(MQTTClients)); if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0) serverURI += strlen(URI_TCP); +#if defined(OPENSSL) + else if (strncmp(URI_SSL, serverURI, strlen(URI_SSL)) == 0) + { + serverURI += strlen(URI_SSL); + m->ssl = 1; + } +#endif m->serverURI = malloc(strlen(serverURI)+1); strcpy(m->serverURI, serverURI); ListAppend(handles, m, sizeof(MQTTClients)); @@ -304,7 +325,7 @@ void MQTTClient_destroy(MQTTClient* handle) if (m->c) { - int saved_socket = m->c->socket; + int saved_socket = m->c->net.socket; char* saved_clientid = malloc(strlen(m->c->clientID)+1); strcpy(saved_clientid, m->c->clientID); #if !defined(NO_PERSISTENCE) @@ -332,11 +353,6 @@ exit: } -#if defined(HEAP_H) -#undef malloc -#undef realloc -#undef free -#endif void MQTTClient_freeMessage(MQTTClient_message** message) { FUNC_ENTRY; @@ -354,13 +370,6 @@ void MQTTClient_free(void* memory) FUNC_EXIT; } -#if defined(HEAP_H) -#define malloc(x) mymalloc(__FILE__, __LINE__, x) -#define realloc(a, b) myrealloc(__FILE__, __LINE__, a, b) -#define free(x) myfree(__FILE__, __LINE__, x) -#endif - - int MQTTClient_deliverMessage(int rc, MQTTClients* m, char** topicName, int* topicLen, MQTTClient_message** message) { @@ -373,9 +382,6 @@ int MQTTClient_deliverMessage(int rc, MQTTClients* m, char** topicName, int* top if (strlen(*topicName) != *topicLen) rc = MQTTCLIENT_TOPICNAME_TRUNCATED; ListRemove(m->c->messageQueue, m->c->messageQueue->first->content); - Heap_unlink(__FILE__, __LINE__, (*message)->payload); - Heap_unlink(__FILE__, __LINE__, *message); - Heap_unlink(__FILE__, __LINE__, *topicName); FUNC_EXIT_RC(rc); return rc; } @@ -390,7 +396,7 @@ int MQTTClient_deliverMessage(int rc, MQTTClients* m, char** topicName, int* top int clientSockCompare(void* a, void* b) { MQTTClients* m = (MQTTClients*)a; - return m->c->socket == *(int*)b; + return m->c->net.socket == *(int*)b; } @@ -457,8 +463,6 @@ thread_return_type WINAPI MQTTClient_run(void* n) { qEntry* qe = (qEntry*)(m->c->messageQueue->first->content); int topicLen = qe->topicLen; - void* payload_ptr = qe->msg->payload; /* saved so we can unlink it after a successful messageArrived call, - because it is held in a structure which might be freed */ if (strlen(qe->topicName) == topicLen) topicLen = 0; @@ -473,12 +477,7 @@ thread_return_type WINAPI MQTTClient_run(void* n) * so we must be careful how we use it. */ if (rc) - { - Heap_unlink(__FILE__, __LINE__, qe->topicName); - Heap_unlink(__FILE__, __LINE__, payload_ptr); - Heap_unlink(__FILE__, __LINE__, qe->msg); ListRemove(m->c->messageQueue, qe); - } else Log(TRACE_MIN, -1, "False returned from messageArrived for client %s, message remains on queue", m->c->clientID); @@ -509,11 +508,31 @@ thread_return_type WINAPI MQTTClient_run(void* n) int error; socklen_t len = sizeof(error); - if ((m->rc = getsockopt(m->c->socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0) + if ((m->rc = getsockopt(m->c->net.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0) m->rc = error; Log(TRACE_MIN, -1, "Posting connect semaphore for client %s rc %d", m->c->clientID, m->rc); Thread_post_sem(m->connect_sem); } +#if defined(OPENSSL) + else if (m->c->connect_state == 2 && !Thread_check_sem(m->connect_sem)) + { + rc = SSLSocket_connect(m->c->net.ssl, m->c->net.socket); + if (rc == 1) + { + if (!m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + m->rc = rc; + Log(TRACE_MIN, -1, "Posting connect semaphore for SSL client %s rc %d", m->c->clientID, m->rc); + Thread_post_sem(m->connect_sem); + } + else if (rc == SSL_FATAL) + { + m->rc = rc; + //tostop = 1; + break; + } + } +#endif } } run_id = 0; @@ -594,16 +613,23 @@ void MQTTProtocol_closeSession(Clients* client, int sendwill) { FUNC_ENTRY; client->good = 0; - if (client->socket > 0) + if (client->net.socket > 0) { if (client->connected || client->connect_state) { - MQTTPacket_send_disconnect(client->socket, client->clientID); + MQTTPacket_send_disconnect(&client->net, client->clientID); client->connected = 0; client->connect_state = 0; } - Socket_close(client->socket); - client->socket = 0; +#if defined(OPENSSL) + if (client->net.ssl) + SSLSocket_close(client->net.ssl); +#endif + Socket_close(client->net.socket); + client->net.socket = 0; +#if defined(OPENSSL) + client->net.ssl = NULL; +#endif } if (client->cleansession) @@ -685,7 +711,7 @@ int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options) goto exit; } - if (strncmp(options->struct_id, "MQTC", 4) != 0 || options->struct_version != 0) + if (strncmp(options->struct_id, "MQTC", 4) != 0 || (options->struct_version != 0 && options->struct_version != 1)) { rc = MQTTCLIENT_BAD_STRUCTURE; goto exit; @@ -699,6 +725,17 @@ int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options) goto exit; } } + +#if defined(OPENSSL) + if (options->struct_version != 0 && options->ssl) /* check validity of SSL options structure */ + { + if (strncmp(options->ssl->struct_id, "MQTS", 4) != 0 || options->ssl->struct_version != 0) + { + rc = MQTTCLIENT_BAD_STRUCTURE; + goto exit; + } + } +#endif if ((options->username && !UTF8_validateString(options->username)) || (options->password && !UTF8_validateString(options->password))) @@ -726,20 +763,30 @@ int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options) if (options->will && options->will->struct_version == 0) { - m->c->will = malloc(sizeof(willMessages)); + m->c->will = malloc(sizeof(willMessages)); m->c->will->msg = options->will->message; m->c->will->qos = options->will->qos; m->c->will->retained = options->will->retained; m->c->will->topic = options->will->topicName; } + +#if defined(OPENSSL) + if (options->struct_version != 0 && options->ssl) + m->c->sslopts = options->ssl; +#endif m->c->username = options->username; m->c->password = options->password; m->c->retryInterval = options->retryInterval; - m->c->connectOptionsVersion = options->struct_version; Log(TRACE_MIN, -1, "Connecting to serverURI %s", m->serverURI); +#if defined(OPENSSL) + rc = MQTTProtocol_connect(m->serverURI, m->c, m->ssl); +#else rc = MQTTProtocol_connect(m->serverURI, m->c); +#endif + if (rc == SOCKET_ERROR) + goto exit; if (m->c->connect_state == 0) { @@ -747,7 +794,7 @@ int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options) goto exit; } - if (m->c->connect_state == 1) + if (m->c->connect_state == 1) /* TCP connect started - wait for completion */ { Thread_unlock_mutex(mqttclient_mutex); MQTTClient_waitfor(handle, CONNECT, &rc, millisecsTimeout - MQTTClient_elapsed(start)); @@ -757,16 +804,67 @@ int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options) rc = SOCKET_ERROR; goto exit; } - - m->c->connect_state = 2; /* TCP connect completed, in which case send the MQTT connect packet */ + +#if defined(OPENSSL) + if (m->ssl) + { + if ((m->c->net.ssl = SSLSocket_setSocketForSSL(m->c->net.socket, m->c->sslopts)) != NULL) + { + if (m->c->session != NULL) + if ((rc = SSL_set_session(m->c->net.ssl, m->c->session)) != 1) + Log(TRACE_MIN, -1, "Failed to set SSL session with stored data, non critical"); + rc = SSLSocket_connect(m->c->net.ssl, m->c->net.socket); + if (rc == -1) + m->c->connect_state = 2; + else if (rc == SSL_FATAL) + { + rc = SOCKET_ERROR; + goto exit; + } + else if (rc == 1 && !m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + } + else + { + rc = SOCKET_ERROR; + goto exit; + } + } + else + { +#endif + m->c->connect_state = 3; /* TCP connect completed, in which case send the MQTT connect packet */ + if (MQTTPacket_send_connect(m->c) == SOCKET_ERROR) + { + rc = SOCKET_ERROR; + goto exit; + } +#if defined(OPENSSL) + } +#endif + } + +#if defined(OPENSSL) + if (m->c->connect_state == 2) /* SSL connect sent - wait for completion */ + { + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_waitfor(handle, CONNECT, &rc, millisecsTimeout - MQTTClient_elapsed(start)); + Thread_lock_mutex(mqttclient_mutex); + if (rc != 1) + { + rc = SOCKET_ERROR; + goto exit; + } + m->c->connect_state = 3; /* TCP connect completed, in which case send the MQTT connect packet */ if (MQTTPacket_send_connect(m->c) == SOCKET_ERROR) { rc = SOCKET_ERROR; goto exit; } } +#endif - if (m->c->connect_state == 2) + if (m->c->connect_state == 3) /* MQTT connect sent - wait for CONNACK */ { MQTTPacket* pack = NULL; @@ -778,11 +876,12 @@ int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options) else { Connack* connack = (Connack*)pack; + Log(LOG_PROTOCOL, 1, NULL, m->c->net.socket, m->c->clientID, connack->rc); if ((rc = connack->rc) == MQTTCLIENT_SUCCESS) { m->c->connected = 1; m->c->good = 1; - m->c->connect_state = 3; + m->c->connect_state = 0; //3; time(&(m->c->lastContact)); if (m->c->cleansession) rc = MQTTClient_cleanSession(m->c); @@ -813,11 +912,11 @@ int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options) } exit: - if (m->c->will) - { - free(m->c->will); - m->c->will = NULL; - } + if (m->c->will) + { + free(m->c->will); + m->c->will = NULL; + } Thread_unlock_mutex(mqttclient_mutex); FUNC_EXIT_RC(rc); return rc; @@ -935,6 +1034,12 @@ int MQTTClient_subscribeMany(MQTTClient handle, int count, char** topic, int* qo rc = MQTTCLIENT_BAD_UTF8_STRING; goto exit; } + + if(qos[i] < 0 || qos[i] > 2) + { + rc = MQTTCLIENT_BAD_QOS; + goto exit; + } } for (i = 0; i < count; i++) @@ -955,7 +1060,7 @@ int MQTTClient_subscribeMany(MQTTClient handle, int count, char** topic, int* qo Thread_lock_mutex(mqttclient_mutex); if (pack != NULL) { - rc = MQTTProtocol_handleSubacks(pack, m->c->socket); + rc = MQTTProtocol_handleSubacks(pack, m->c->net.socket); m->pack = NULL; } else @@ -1033,7 +1138,7 @@ int MQTTClient_unsubscribeMany(MQTTClient handle, int count, char** topic) Thread_lock_mutex(mqttclient_mutex); if (pack != NULL) { - rc = MQTTProtocol_handleUnsubacks(pack, m->c->socket); + rc = MQTTProtocol_handleUnsubacks(pack, m->c->net.socket); m->pack = NULL; } else @@ -1122,7 +1227,7 @@ int MQTTClient_publish(MQTTClient handle, char* topicName, int payloadlen, void* */ if (rc == TCPSOCKET_INTERRUPTED) { - while (m->c->connected == 1 && SocketBuffer_getWrite(m->c->socket)) + while (m->c->connected == 1 && SocketBuffer_getWrite(m->c->net.socket)) { Thread_unlock_mutex(mqttclient_mutex); MQTTClient_yield(); @@ -1210,18 +1315,28 @@ MQTTPacket* MQTTClient_cycle(int* sock, unsigned long timeout, int* rc) tp.tv_usec = (timeout % 1000) * 1000; /* this field is microseconds! */ } - /* 0 from getReadySocket indicates no work to do, -1 == error, but can happen normally */ - *sock = Socket_getReadySocket(0, &tp); +#if defined(OPENSSL) + if ((*sock = SSLSocket_getPendingRead()) == -1) + { + /* 0 from getReadySocket indicates no work to do, -1 == error, but can happen normally */ +#endif + *sock = Socket_getReadySocket(0, &tp); +#if defined(OPENSSL) + } +#endif Thread_lock_mutex(mqttclient_mutex); if (*sock > 0) { MQTTClients* m = NULL; if (ListFindItem(handles, sock, clientSockCompare) != NULL) m = (MQTTClient)(handles->current->content); - if (m != NULL && m->c->connect_state == 1) - *rc = 0; /* waiting for connect state to clear */ - else - pack = MQTTPacket_Factory(*sock, rc); + if (m != NULL) + { + if (m->c->connect_state == 1 || m->c->connect_state == 2) + *rc = 0; /* waiting for connect state to clear */ + else + pack = MQTTPacket_Factory(&m->c->net, rc); + } if (pack) { int freed = 1; @@ -1257,7 +1372,7 @@ MQTTPacket* MQTTClient_cycle(int* sock, unsigned long timeout, int* rc) } MQTTClient_retry(); Thread_unlock_mutex(mqttclient_mutex); - FUNC_EXIT; + FUNC_EXIT_RC(*rc); return pack; } @@ -1299,7 +1414,7 @@ MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* rc, long { int sock = -1; pack = MQTTClient_cycle(&sock, 100L, rc); - if (sock == m->c->socket) + if (sock == m->c->net.socket) { if (pack && (pack->header.bits.type == packet_type)) break; @@ -1308,10 +1423,22 @@ MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* rc, long int error; socklen_t len = sizeof(error); - if ((*rc = getsockopt(m->c->socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0) + if ((*rc = getsockopt(m->c->net.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0) *rc = error; break; } +#if defined(OPENSSL) + else if (m->c->connect_state == 2) + { + *rc = SSLSocket_connect(m->c->net.ssl, sock); + if (*rc == 1 || *rc == SSL_FATAL) + { + if (*rc == 1 && !m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + break; + } + } +#endif } else if (MQTTClient_elapsed(start) > timeout) { @@ -1359,15 +1486,13 @@ int MQTTClient_receive(MQTTClient handle, char** topicName, int* topicLen, MQTTC { int sock = 0; MQTTClient_cycle(&sock, (timeout > elapsed) ? timeout - elapsed : 0L, &rc); - - + if (rc == SOCKET_ERROR) { if (ListFindItem(handles, &sock, clientSockCompare) && /* find client corresponding to socket */ (MQTTClient)(handles->current->content) == handle) break; /* there was an error on the socket we are interested in */ } - elapsed = MQTTClient_elapsed(start); } while (elapsed < timeout && m->c->messageQueue->count == 0); @@ -1493,7 +1618,7 @@ int MQTTClient_getPendingDeliveryTokens(MQTTClient handle, MQTTClient_deliveryTo int count = 0; *tokens = malloc(sizeof(MQTTClient_deliveryToken) * (m->c->outboundMsgs->count + 1)); - Heap_unlink(__FILE__, __LINE__, *tokens); + /*Heap_unlink(__FILE__, __LINE__, *tokens);*/ while (ListNextElement(m->c->outboundMsgs, ¤t)) { Messages* m = (Messages*)(current->content); @@ -1507,3 +1632,35 @@ exit: FUNC_EXIT_RC(rc); return rc; } + +MQTTClient_nameValue* MQTTClient_getVersionInfo() +{ + #define MAX_INFO_STRINGS 7 + static MQTTClient_nameValue libinfo[MAX_INFO_STRINGS + 1]; + int i = 0; + + libinfo[i].name = "version"; + libinfo[i++].value = CLIENT_VERSION; + + libinfo[i].name = "build level"; + libinfo[i++].value = BUILD_TIMESTAMP; +#if defined(OPENSSL) + libinfo[i].name = "OpenSSL version"; + libinfo[i++].value = SSLeay_version(SSLEAY_VERSION); + + libinfo[i].name = "OpenSSL flags"; + libinfo[i++].value = SSLeay_version(SSLEAY_CFLAGS); + + libinfo[i].name = "OpenSSL build timestamp"; + libinfo[i++].value = SSLeay_version(SSLEAY_BUILT_ON); + + libinfo[i].name = "OpenSSL platform"; + libinfo[i++].value = SSLeay_version(SSLEAY_PLATFORM); + + libinfo[i].name = "OpenSSL directory"; + libinfo[i++].value = SSLeay_version(SSLEAY_DIR); +#endif + libinfo[i].name = NULL; + libinfo[i].value = NULL; + return libinfo; +} diff --git a/src/MQTTClient.h b/src/MQTTClient.h index 11e86f2b..5569e52a 100644 --- a/src/MQTTClient.h +++ b/src/MQTTClient.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,19 +8,31 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ /** * @mainpage MQTT Client library for C - * © Copyright IBM Corp. 2009, 2011 + * © Copyright IBM Corp. 2009, 2012 * * @brief An MQTT client library in C. * - * An MQTT client application connects to MQTT-capable servers. + * An MQTT client application connects to MQTT-capable servers. * A typical client is responsible for collecting information from a telemetry * device and publishing the information to the server. It can also subscribe * to topics, receive messages, and use this information to control the - * telemetry device. MQTT clients implement the published MQTT v3 protocol. + * telemetry device. + * + * MQTT clients implement the published MQTT v3 protocol. You can write your own + * API to the MQTT protocol using the programming language and platform of your + * choice. This can be time-consuming and error-prone. + * + * To simplify writing MQTT client applications, WebSphere MQ Telemetry provides + * C and Java client libraries that encapsulate the MQTT v3 protocol for a + * number of platforms. If you incorporate these libraries in your MQTT + * applications, a fully functional MQTT client can be written in a few lines of + * code. The information presented here documents the API provided by the IBM + * MQTT Client library for C. * * Using the client
* Applications that use the client library typically use a similar structure: @@ -112,6 +124,10 @@ * and version number. */ #define MQTTCLIENT_BAD_STRUCTURE -8 +/** + * Return code: A QoS value that falls outside of the acceptable range (0,1,2) + */ +#define MQTTCLIENT_BAD_QOS -9 /** * A handle representing an MQTT client. A valid client handle is available @@ -129,6 +145,7 @@ typedef void* MQTTClient; * MQTTClient_getPendingDeliveryTokens()). */ typedef int MQTTClient_deliveryToken; +typedef int MQTTClient_token; /** * A structure representing the payload and attributes of an MQTT message. The @@ -261,7 +278,6 @@ typedef void MQTTClient_deliveryComplete(void* context, MQTTClient_deliveryToken */ typedef void MQTTClient_connectionLost(void* context, char* cause); - /** * This function sets the callback functions for a specific client. * If your client application doesn't use a particular callback, set the @@ -290,7 +306,8 @@ typedef void MQTTClient_connectionLost(void* context, char* cause); * ::MQTTCLIENT_FAILURE if an error occurred. */ DLLExport int MQTTClient_setCallbacks(MQTTClient handle, void* context, MQTTClient_connectionLost* cl, - MQTTClient_messageArrived* ma, MQTTClient_deliveryComplete* dc); + MQTTClient_messageArrived* ma, MQTTClient_deliveryComplete* dc); + /** * This function creates an MQTT client ready for connection to the @@ -373,6 +390,57 @@ typedef struct #define MQTTClient_willOptions_initializer { "MQTW", 0, NULL, NULL, 0, 0 } +/** +* MQTTClient_sslProperties defines the settings to establish an SSL/TLS connection using the +* OpenSSL library. It covers the following scenarios: +* - Server authentication: The client needs the digital certificate of the server. It is included +* in a store containting trusted material (also known as "trust store"). +* - Mutual authentication: Both client and server are authenticated during the SSL handshake. In +* addition to the digital certificate of the server in a trust store, the client will need its own +* digital certificate and the private key used to sign its digital certificate stored in a "key store". +* - Anonymous connection: Both client and server do not get authenticated and no credentials are needed +* to establish an SSL connection. Note that this scenario is not fully secure since it is subject to +* man-in-the-middle attacks. +*/ +typedef struct +{ + /** The eyecatcher for this structure. Must be MQTS */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + + /** The file in PEM format containing the public digital certificates trusted by the client. */ + char* trustStore; + + /** The file in PEM format containing the public certificate chain of the client. It may also include + * the client's private key. + */ + char* keyStore; + + /** If not included in the sslKeyStore, this setting points to the file in PEM format containing + * the client's private key. + */ + char* privateKey; + /** The password to load the client's privateKey if encrypted. */ + char* privateKeyPassword; + + /** + * The list of cipher suites that the client will present to the server during the SSL handshake. For a + * full explanation of the cipher list format, please see the OpenSSL on-line documentation: + * http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT + * If this setting is ommitted, its default value will be "ALL", that is, all the cipher suites -excluding + * those offering no encryption- will be considered. + * This setting can be used to set an SSL anonymous connection ("aNULL" string value, for instance). + */ + char* enabledCipherSuites; + + /** True/False option to enable verification of the server certificate **/ + int enableServerCertAuth; + +} MQTTClient_SSLOptions; + +#define MQTTClient_SSLOptions_initializer { "MQTS", 0, NULL, NULL, NULL, NULL, NULL, 1 } + /** * MQTTClient_connectOptions defines several settings that control the way the * client connects to an MQTT server. @@ -391,7 +459,7 @@ typedef struct { /** The eyecatcher for this structure. must be MQTC. */ char struct_id[4]; - /** The version number of this structure. Must be 0 */ + /** The version number of this structure. Must be 0 or 1. 0 signifies no SSL options */ int struct_version; /** The "keep alive" interval, measured in seconds, defines the maximum time * that should pass without communication between the client and the server @@ -461,9 +529,34 @@ typedef struct * The time interval in seconds */ int retryInterval; + /** + * This is a pointer to an MQTTClient_SSLOptions structure. If your + * application does not make use of SSL, set this pointer to NULL. + */ + MQTTClient_SSLOptions* ssl; } MQTTClient_connectOptions; -#define MQTTClient_connectOptions_initializer { "MQTC", 0, 60, 1, 1, NULL, NULL, NULL, 30, 20 } +#define MQTTClient_connectOptions_initializer { "MQTC", 1, 60, 1, 1, NULL, NULL, NULL, 30, 20, NULL } + +/** + * MQTTClient_libraryInfo is used to store details relating to the currently used + * library such as the version in use, the time it was built and relevant openSSL + * options. + * There is one static instance of this struct in MQTTClient.c + */ + +typedef struct +{ + const char* name; + const char* value; +} MQTTClient_nameValue; + +/** + * This function returns version information about the library. + * no trace information will be returned. + * @return an array of strings describing the library. The last entry is a NULL pointer. + */ +DLLExport MQTTClient_nameValue* MQTTClient_getVersionInfo(); /** * This function attempts to connect a previously-created client (see @@ -602,7 +695,6 @@ DLLExport int MQTTClient_unsubscribeMany(MQTTClient handle, int count, char** to */ DLLExport int MQTTClient_publish(MQTTClient handle, char* topicName, int payloadlen, void* payload, int qos, int retained, MQTTClient_deliveryToken* dt); - /** * This function attempts to publish a message to a given topic (see also * MQTTClient_publish()). An ::MQTTClient_deliveryToken is issued when diff --git a/src/MQTTPacket.c b/src/MQTTPacket.c index 91b17454..ed3ef621 100644 --- a/src/MQTTPacket.c +++ b/src/MQTTPacket.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ /** @@ -44,6 +45,8 @@ static char* packet_names[] = "PINGREQ", "PINGRESP", "DISCONNECT" }; +char** MQTTClient_packet_names = packet_names; + /** * Converts an MQTT packet code into its name @@ -84,7 +87,7 @@ pf new_packets[] = * @param error pointer to the error code which is completed if no packet is returned * @return the packet structure or NULL if there was an error */ -void* MQTTPacket_Factory(int socket, int* error) +void* MQTTPacket_Factory(networkHandles* net, int* error) { char* data = NULL; static Header header; @@ -96,15 +99,26 @@ void* MQTTPacket_Factory(int socket, int* error) *error = SOCKET_ERROR; /* indicate whether an error occurred, or not */ /* read the packet data from the socket */ - if ((*error = Socket_getch(socket, &(header.byte))) != TCPSOCKET_COMPLETE) /* first byte is the header byte */ +#if defined(OPENSSL) + *error = (net->ssl) ? SSLSocket_getch(net->ssl, net->socket, &header.byte) : Socket_getch(net->socket, &header.byte); +#else + *error = Socket_getch(net->socket, &header.byte); +#endif + if (*error != TCPSOCKET_COMPLETE) /* first byte is the header byte */ goto exit; /* packet not read, *error indicates whether SOCKET_ERROR occurred */ /* now read the remaining length, so we know how much more to read */ - if ((*error = MQTTPacket_decode(socket, &remaining_length)) != TCPSOCKET_COMPLETE) + if ((*error = MQTTPacket_decode(net, &remaining_length)) != TCPSOCKET_COMPLETE) goto exit; /* packet not read, *error indicates whether SOCKET_ERROR occurred */ /* now read the rest, the variable header and payload */ - if ((data = Socket_getdata(socket, remaining_length, &actual_len)) == NULL) +#if defined(OPENSSL) + data = (net->ssl) ? SSLSocket_getdata(net->ssl, net->socket, remaining_length, &actual_len) : + Socket_getdata(net->socket, remaining_length, &actual_len); +#else + data = Socket_getdata(net->socket, remaining_length, &actual_len); +#endif + if (data == NULL) { *error = SOCKET_ERROR; goto exit; /* socket error */ @@ -128,7 +142,7 @@ void* MQTTPacket_Factory(int socket, int* error) char *buf = malloc(10); buf[0] = header.byte; buf0len = 1 + MQTTPacket_encode(&buf[1], remaining_length); - *error = MQTTPersistence_put(socket, buf, buf0len, 1, + *error = MQTTPersistence_put(net->socket, buf, buf0len, 1, &data, &remaining_length, header.bits.type, ((Publish *)pack)->msgId, 1); free(buf); } @@ -149,7 +163,7 @@ exit: * @param buflen the length of the data in buffer to be written * @return the completion code (TCPSOCKET_COMPLETE etc) */ -int MQTTPacket_send(int socket, Header header, char* buffer, int buflen) +int MQTTPacket_send(networkHandles* net, Header header, char* buffer, int buflen) { int rc, buf0len; char *buf; @@ -163,11 +177,18 @@ int MQTTPacket_send(int socket, Header header, char* buffer, int buflen) { char* ptraux = buffer; int msgId = readInt(&ptraux); - rc = MQTTPersistence_put(socket, buf, buf0len, 1, &buffer, &buflen, + rc = MQTTPersistence_put(net->socket, buf, buf0len, 1, &buffer, &buflen, header.bits.type, msgId, 0); } #endif - rc = Socket_putdatas(socket, buf, buf0len, 1, &buffer, &buflen); + +#if defined(OPENSSL) + if (net->ssl) + rc = SSLSocket_putdatas(net->ssl, net->socket, buf, buf0len, 1, &buffer, &buflen); + else +#endif + rc = Socket_putdatas(net->socket, buf, buf0len, 1, &buffer, &buflen); + if (rc != TCPSOCKET_INTERRUPTED) free(buf); @@ -185,7 +206,7 @@ int MQTTPacket_send(int socket, Header header, char* buffer, int buflen) * @param buflens the lengths of the data in the array of buffers to be written * @return the completion code (TCPSOCKET_COMPLETE etc) */ -int MQTTPacket_sends(int socket, Header header, int count, char** buffers, int* buflens) +int MQTTPacket_sends(networkHandles* net, Header header, int count, char** buffers, int* buflens) { int i, rc, buf0len, total = 0; char *buf; @@ -201,11 +222,16 @@ int MQTTPacket_sends(int socket, Header header, int count, char** buffers, int* { /* persist PUBLISH QoS1 and Qo2 */ char *ptraux = buffers[2]; int msgId = readInt(&ptraux); - rc = MQTTPersistence_put(socket, buf, buf0len, count, buffers, buflens, + rc = MQTTPersistence_put(net->socket, buf, buf0len, count, buffers, buflens, header.bits.type, msgId, 0); } #endif - rc = Socket_putdatas(socket, buf, buf0len, count, buffers, buflens); +#if defined(OPENSSL) + if (net->ssl) + rc = SSLSocket_putdatas(net->ssl, net->socket, buf, buf0len, count, buffers, buflens); + else +#endif + rc = Socket_putdatas(net->socket, buf, buf0len, count, buffers, buflens); if (rc != TCPSOCKET_INTERRUPTED) free(buf); FUNC_EXIT_RC(rc); @@ -244,7 +270,7 @@ int MQTTPacket_encode(char* buf, int length) * @param value the decoded length returned * @return the number of bytes read from the socket */ -int MQTTPacket_decode(int socket, int* value) +int MQTTPacket_decode(networkHandles* net, int* value) { int rc = SOCKET_ERROR; char c; @@ -261,8 +287,13 @@ int MQTTPacket_decode(int socket, int* value) rc = SOCKET_ERROR; /* bad data */ goto exit; } - if ((rc = Socket_getch(socket, &c)) != TCPSOCKET_COMPLETE) - goto exit; +#if defined(OPENSSL) + rc = (net->ssl) ? SSLSocket_getch(net->ssl, net->socket, &c) : Socket_getch(net->socket, &c); +#else + rc = Socket_getch(net->socket, &c); +#endif + if (rc != TCPSOCKET_COMPLETE) + goto exit; *value += (c & 127) * multiplier; multiplier *= 128; } while ((c & 128) != 0); @@ -409,7 +440,7 @@ void* MQTTPacket_header_only(unsigned char aHeader, char* data, int datalen) * @param socket the open socket to send the data to * @return the completion code (e.g. TCPSOCKET_COMPLETE) */ -int MQTTPacket_send_disconnect(int socket, char* clientID) +int MQTTPacket_send_disconnect(networkHandles *net, char* clientID) { Header header; int rc = 0; @@ -417,8 +448,8 @@ int MQTTPacket_send_disconnect(int socket, char* clientID) FUNC_ENTRY; header.byte = 0; header.bits.type = DISCONNECT; - rc = MQTTPacket_send(socket, header, NULL, 0); - Log(LOG_PROTOCOL, 28, NULL, socket, clientID, rc); + rc = MQTTPacket_send(net, header, NULL, 0); + Log(LOG_PROTOCOL, 28, NULL, net->socket, clientID, rc); FUNC_EXIT_RC(rc); return rc; } @@ -479,7 +510,7 @@ void MQTTPacket_freePublish(Publish* pack) * @param socket the open socket to send the data to * @return the completion code (e.g. TCPSOCKET_COMPLETE) */ -int MQTTPacket_send_ack(int type, int msgid, int dup, int socket) +int MQTTPacket_send_ack(int type, int msgid, int dup, networkHandles *net) { Header header; int rc; @@ -491,7 +522,7 @@ int MQTTPacket_send_ack(int type, int msgid, int dup, int socket) header.bits.type = type; header.bits.dup = dup; writeInt(&ptr, msgid); - if ((rc = MQTTPacket_send(socket, header, buf, 2)) != TCPSOCKET_INTERRUPTED) + if ((rc = MQTTPacket_send(net, header, buf, 2)) != TCPSOCKET_INTERRUPTED) free(buf); FUNC_EXIT_RC(rc); return rc; @@ -504,13 +535,13 @@ int MQTTPacket_send_ack(int type, int msgid, int dup, int socket) * @param socket the open socket to send the data to * @return the completion code (e.g. TCPSOCKET_COMPLETE) */ -int MQTTPacket_send_puback(int msgid, int socket, char* clientID) +int MQTTPacket_send_puback(int msgid, networkHandles* net, char* clientID) { int rc = 0; FUNC_ENTRY; - rc = MQTTPacket_send_ack(PUBACK, msgid, 0, socket); - Log(LOG_PROTOCOL, 12, NULL, socket, clientID, msgid, rc); + rc = MQTTPacket_send_ack(PUBACK, msgid, 0, net); + Log(LOG_PROTOCOL, 12, NULL, net->socket, clientID, msgid, rc); FUNC_EXIT_RC(rc); return rc; } @@ -536,13 +567,13 @@ void MQTTPacket_freeSuback(Suback* pack) * @param socket the open socket to send the data to * @return the completion code (e.g. TCPSOCKET_COMPLETE) */ -int MQTTPacket_send_pubrec(int msgid, int socket, char* clientID) +int MQTTPacket_send_pubrec(int msgid, networkHandles* net, char* clientID) { int rc = 0; FUNC_ENTRY; - rc = MQTTPacket_send_ack(PUBREC, msgid, 0, socket); - Log(LOG_PROTOCOL, 13, NULL, socket, clientID, msgid, rc); + rc = MQTTPacket_send_ack(PUBREC, msgid, 0, net); + Log(LOG_PROTOCOL, 13, NULL, net->socket, clientID, msgid, rc); FUNC_EXIT_RC(rc); return rc; } @@ -555,13 +586,13 @@ int MQTTPacket_send_pubrec(int msgid, int socket, char* clientID) * @param socket the open socket to send the data to * @return the completion code (e.g. TCPSOCKET_COMPLETE) */ -int MQTTPacket_send_pubrel(int msgid, int dup, int socket, char* clientID) +int MQTTPacket_send_pubrel(int msgid, int dup, networkHandles* net, char* clientID) { int rc = 0; FUNC_ENTRY; - rc = MQTTPacket_send_ack(PUBREL, msgid, dup, socket); - Log(LOG_PROTOCOL, 16, NULL, socket, clientID, msgid, rc); + rc = MQTTPacket_send_ack(PUBREL, msgid, dup, net); + Log(LOG_PROTOCOL, 16, NULL, net->socket, clientID, msgid, rc); FUNC_EXIT_RC(rc); return rc; } @@ -573,13 +604,13 @@ int MQTTPacket_send_pubrel(int msgid, int dup, int socket, char* clientID) * @param socket the open socket to send the data to * @return the completion code (e.g. TCPSOCKET_COMPLETE) */ -int MQTTPacket_send_pubcomp(int msgid, int socket, char* clientID) +int MQTTPacket_send_pubcomp(int msgid, networkHandles* net, char* clientID) { int rc = 0; FUNC_ENTRY; - rc = MQTTPacket_send_ack(PUBCOMP, msgid, 0, socket); - Log(LOG_PROTOCOL, 18, NULL, socket, clientID, msgid, rc); + rc = MQTTPacket_send_ack(PUBCOMP, msgid, 0, net); + Log(LOG_PROTOCOL, 18, NULL, net->socket, clientID, msgid, rc); FUNC_EXIT_RC(rc); return rc; } @@ -614,7 +645,7 @@ void* MQTTPacket_ack(unsigned char aHeader, char* data, int datalen) * @param socket the open socket to send the data to * @return the completion code (e.g. TCPSOCKET_COMPLETE) */ -int MQTTPacket_send_publish(Publish* pack, int dup, int qos, int retained, int socket, char* clientID) +int MQTTPacket_send_publish(Publish* pack, int dup, int qos, int retained, networkHandles* net, char* clientID) { Header header; char *topiclen; @@ -636,7 +667,7 @@ int MQTTPacket_send_publish(Publish* pack, int dup, int qos, int retained, int s writeInt(&ptr, pack->msgId); ptr = topiclen; writeInt(&ptr, lens[1]); - rc = MQTTPacket_sends(socket, header, 4, bufs, lens); + rc = MQTTPacket_sends(net, header, 4, bufs, lens); if (rc != TCPSOCKET_INTERRUPTED) free(buf); } @@ -646,14 +677,14 @@ int MQTTPacket_send_publish(Publish* pack, int dup, int qos, int retained, int s char* bufs[3] = {topiclen, pack->topic, pack->payload}; int lens[3] = {2, strlen(pack->topic), pack->payloadlen}; writeInt(&ptr, lens[1]); - rc = MQTTPacket_sends(socket, header, 3, bufs, lens); + rc = MQTTPacket_sends(net, header, 3, bufs, lens); } if (rc != TCPSOCKET_INTERRUPTED) free(topiclen); if (qos == 0) - Log(LOG_PROTOCOL, 27, NULL, socket, clientID, retained, rc); + Log(LOG_PROTOCOL, 27, NULL, net->socket, clientID, retained, rc); else - Log(LOG_PROTOCOL, 10, NULL, socket, clientID, pack->msgId, qos, retained, rc, + Log(LOG_PROTOCOL, 10, NULL, net->socket, clientID, pack->msgId, qos, retained, rc, min(20, pack->payloadlen), pack->payload); FUNC_EXIT_RC(rc); return rc; diff --git a/src/MQTTPacket.h b/src/MQTTPacket.h index 36116365..324e43d7 100644 --- a/src/MQTTPacket.h +++ b/src/MQTTPacket.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,12 +8,16 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ #if !defined(MQTTPACKET_H) #define MQTTPACKET_H #include "Socket.h" +#if defined(OPENSSL) +#include "SSLSocket.h" +#endif #include "LinkedList.h" #include "Clients.h" @@ -191,7 +195,7 @@ typedef Ack Pubcomp; typedef Ack Unsuback; int MQTTPacket_encode(char* buf, int length); -int MQTTPacket_decode(int socket, int* value); +int MQTTPacket_decode(networkHandles* net, int* value); int readInt(char** pptr); char* readUTF(char** pptr, char* enddata); char readChar(char** pptr); @@ -201,23 +205,23 @@ void writeUTF(char** pptr, char* string); char* MQTTPacket_name(int ptype); -void* MQTTPacket_Factory(int socket, int* error); -int MQTTPacket_send(int socket, Header header, char* buffer, int buflen); -int MQTTPacket_sends(int socket, Header header, int count, char** buffers, int* buflens); +void* MQTTPacket_Factory(networkHandles* net, int* error); +int MQTTPacket_send(networkHandles* net, Header header, char* buffer, int buflen); +int MQTTPacket_sends(networkHandles* net, Header header, int count, char** buffers, int* buflens); void* MQTTPacket_header_only(unsigned char aHeader, char* data, int datalen); -int MQTTPacket_send_disconnect(int socket, char* clientID); +int MQTTPacket_send_disconnect(networkHandles* net, char* clientID); void* MQTTPacket_publish(unsigned char aHeader, char* data, int datalen); void MQTTPacket_freePublish(Publish* pack); -int MQTTPacket_send_publish(Publish* pack, int dup, int qos, int retained, int socket, char* clientID); -int MQTTPacket_send_puback(int msgid, int socket, char* clientID); +int MQTTPacket_send_publish(Publish* pack, int dup, int qos, int retained, networkHandles* net, char* clientID); +int MQTTPacket_send_puback(int msgid, networkHandles* net, char* clientID); void* MQTTPacket_ack(unsigned char aHeader, char* data, int datalen); void MQTTPacket_freeSuback(Suback* pack); -int MQTTPacket_send_pubrec(int msgid, int socket, char* clientID); -int MQTTPacket_send_pubrel(int msgid, int dup, int socket, char* clientID); -int MQTTPacket_send_pubcomp(int msgid, int socket, char* clientID); +int MQTTPacket_send_pubrec(int msgid, networkHandles* net, char* clientID); +int MQTTPacket_send_pubrel(int msgid, int dup, networkHandles* net, char* clientID); +int MQTTPacket_send_pubcomp(int msgid, networkHandles* net, char* clientID); void MQTTPacket_free_packet(MQTTPacket* pack); diff --git a/src/MQTTPacketOut.c b/src/MQTTPacketOut.c index 842ddddb..0cd2ccba 100644 --- a/src/MQTTPacketOut.c +++ b/src/MQTTPacketOut.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ /** @@ -83,8 +84,8 @@ int MQTTPacket_send_connect(Clients* client) if (client->password) writeUTF(&ptr, client->password); - rc = MQTTPacket_send(client->socket, packet.header, buf, len); - Log(LOG_PROTOCOL, 0, NULL, client->socket, client->clientID, client->cleansession, rc); + rc = MQTTPacket_send(&client->net, packet.header, buf, len); + Log(LOG_PROTOCOL, 0, NULL, client->net.socket, client->clientID, client->cleansession, rc); free(buf); FUNC_EXIT_RC(rc); return rc; @@ -118,7 +119,7 @@ void* MQTTPacket_connack(unsigned char aHeader, char* data, int datalen) * @param clientID the string client identifier, only used for tracing * @return the completion code (e.g. TCPSOCKET_COMPLETE) */ -int MQTTPacket_send_pingreq(int socket, char* clientID) +int MQTTPacket_send_pingreq(networkHandles* net, char* clientID) { Header header; int rc = 0; @@ -126,7 +127,7 @@ int MQTTPacket_send_pingreq(int socket, char* clientID) FUNC_ENTRY; header.byte = 0; header.bits.type = PINGREQ; - rc = MQTTPacket_send(socket, header, NULL, 0); + rc = MQTTPacket_send(net, header, NULL, 0); Log(LOG_PROTOCOL, 20, NULL, socket, clientID, rc); FUNC_EXIT_RC(rc); return rc; @@ -143,7 +144,7 @@ int MQTTPacket_send_pingreq(int socket, char* clientID) * @param clientID the string client identifier, only used for tracing * @return the completion code (e.g. TCPSOCKET_COMPLETE) */ -int MQTTPacket_send_subscribe(List* topics, List* qoss, int msgid, int dup, int socket, char* clientID) +int MQTTPacket_send_subscribe(List* topics, List* qoss, int msgid, int dup, networkHandles* net, char* clientID) { Header header; char *data, *ptr; @@ -170,8 +171,8 @@ int MQTTPacket_send_subscribe(List* topics, List* qoss, int msgid, int dup, int writeUTF(&ptr, (char*)(elem->content)); writeChar(&ptr, *(int*)(qosElem->content)); } - rc = MQTTPacket_send(socket, header, data, datalen); - Log(LOG_PROTOCOL, 22, NULL, socket, clientID, msgid, rc); + rc = MQTTPacket_send(net, header, data, datalen); + Log(LOG_PROTOCOL, 22, NULL, net->socket, clientID, msgid, rc); free(data); FUNC_EXIT_RC(rc); return rc; @@ -215,7 +216,7 @@ void* MQTTPacket_suback(unsigned char aHeader, char* data, int datalen) * @param clientID the string client identifier, only used for tracing * @return the completion code (e.g. TCPSOCKET_COMPLETE) */ -int MQTTPacket_send_unsubscribe(List* topics, int msgid, int dup, int socket, char* clientID) +int MQTTPacket_send_unsubscribe(List* topics, int msgid, int dup, networkHandles* net, char* clientID) { Header header; char *data, *ptr; @@ -238,8 +239,8 @@ int MQTTPacket_send_unsubscribe(List* topics, int msgid, int dup, int socket, ch elem = NULL; while (ListNextElement(topics, &elem)) writeUTF(&ptr, (char*)(elem->content)); - rc = MQTTPacket_send(socket, header, data, datalen); - Log(LOG_PROTOCOL, 25, NULL, socket, clientID, msgid, rc); + rc = MQTTPacket_send(net, header, data, datalen); + Log(LOG_PROTOCOL, 25, NULL, net->socket, clientID, msgid, rc); free(data); FUNC_EXIT_RC(rc); return rc; diff --git a/src/MQTTPacketOut.h b/src/MQTTPacketOut.h index c22769e9..a716a4d6 100644 --- a/src/MQTTPacketOut.h +++ b/src/MQTTPacketOut.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ #if !defined(MQTTPACKETOUT_H) @@ -18,11 +19,11 @@ int MQTTPacket_send_connect(Clients* client); void* MQTTPacket_connack(unsigned char aHeader, char* data, int datalen); -int MQTTPacket_send_pingreq(int socket, char* clientID); +int MQTTPacket_send_pingreq(networkHandles* net, char* clientID); -int MQTTPacket_send_subscribe(List* topics, List* qoss, int msgid, int dup, int socket, char* clientID); +int MQTTPacket_send_subscribe(List* topics, List* qoss, int msgid, int dup, networkHandles* net, char* clientID); void* MQTTPacket_suback(unsigned char aHeader, char* data, int datalen); -int MQTTPacket_send_unsubscribe(List* topics, int msgid, int dup, int socket, char* clientID); +int MQTTPacket_send_unsubscribe(List* topics, int msgid, int dup, networkHandles* net, char* clientID); #endif diff --git a/src/MQTTPersistence.c b/src/MQTTPersistence.c index 5b521f9c..67443688 100644 --- a/src/MQTTPersistence.c +++ b/src/MQTTPersistence.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - async client updates *******************************************************************************/ /** @@ -165,17 +166,23 @@ int MQTTPersistence_clear(Clients *c) int MQTTPersistence_restore(Clients *c) { int rc = 0; - char **msgkeys, *buffer; + char **msgkeys = NULL, + *buffer = NULL; int nkeys, buflen; int i = 0; + int msgs_sent = 0; + int msgs_rcvd = 0; FUNC_ENTRY; - if ( c->persistence != NULL && - (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0 ) + if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0) { - while( rc == 0 && i < nkeys) + while (rc == 0 && i < nkeys) { - if ( (rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0 ) + if (strncmp(msgkeys[i], PERSISTENCE_COMMAND_KEY, strlen(PERSISTENCE_COMMAND_KEY)) == 0) + ; + else if (strncmp(msgkeys[i], PERSISTENCE_QUEUE_KEY, strlen(PERSISTENCE_QUEUE_KEY)) == 0) + ; + else if ((rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0) { MQTTPacket* pack = MQTTPersistence_restorePacket(buffer, buflen); if ( pack != NULL ) @@ -190,6 +197,7 @@ int MQTTPersistence_restore(Clients *c) ListAppend(c->inboundMsgs, msg, msg->len); publish->topic = NULL; MQTTPacket_freePublish(publish); + msgs_rcvd++; } else if ( strstr(msgkeys[i],PERSISTENCE_PUBLISH_SENT) != NULL ) { @@ -208,6 +216,7 @@ int MQTTPersistence_restore(Clients *c) publish->topic = NULL; MQTTPacket_freePublish(publish); free(key); + msgs_sent++; } else if ( strstr(msgkeys[i],PERSISTENCE_PUBREL) != NULL ) { @@ -224,16 +233,20 @@ int MQTTPersistence_restore(Clients *c) else /* pack == NULL -> bad persisted record */ rc = c->persistence->premove(c->phandle, msgkeys[i]); } - if ( buffer != NULL ) + if (buffer) + { free(buffer); - if ( msgkeys[i] != NULL ) + buffer = NULL; + } + if (msgkeys[i]) free(msgkeys[i]); i++; } - if ( msgkeys != NULL ) + if (msgkeys) free(msgkeys); } - + Log(TRACE_MINIMUM, -1, "%d sent messages and %d received messages restored for client %s\n", + msgs_sent, msgs_rcvd, c->clientID); MQTTPersistence_wrapMsgID(c); FUNC_EXIT_RC(rc); diff --git a/src/MQTTPersistence.h b/src/MQTTPersistence.h index 6eb7140f..a595eeb2 100644 --- a/src/MQTTPersistence.h +++ b/src/MQTTPersistence.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - async client updates *******************************************************************************/ #include "Clients.h" @@ -18,7 +19,11 @@ #define PERSISTENCE_PUBREL "sc-" /** Stem of the key for a received PUBLISH QoS2 */ #define PERSISTENCE_PUBLISH_RECEIVED "r-" - +/** Stem of the key for an async client command */ +#define PERSISTENCE_COMMAND_KEY "c-" +/** Stem of the key for an async client message queue */ +#define PERSISTENCE_QUEUE_KEY "q-" +#define PERSISTENCE_MAX_KEY_LENGTH 8 int MQTTPersistence_create(MQTTClient_persistence** per, int type, void* pcontext); int MQTTPersistence_initialize(Clients* c, char* serverURI); diff --git a/src/MQTTPersistenceDefault.c b/src/MQTTPersistenceDefault.c index 80fdccf0..de434903 100644 --- a/src/MQTTPersistenceDefault.c +++ b/src/MQTTPersistenceDefault.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - async client updates *******************************************************************************/ /** @@ -395,7 +396,10 @@ int containskeyUnix(char *dirname, char *key) { while((dir_entry = readdir(dp)) != NULL && notFound) { - lstat(dir_entry->d_name, &stat_info); + char* filename = malloc(strlen(dirname) + strlen(dir_entry->d_name) + 2); + sprintf(filename, "%s/%s", dirname, dir_entry->d_name); + lstat(filename, &stat_info); + free(filename); if(S_ISREG(stat_info.st_mode)) { filekey = malloc(strlen(dir_entry->d_name) + 1); diff --git a/src/MQTTProtocol.h b/src/MQTTProtocol.h index 982d8492..d411cb82 100644 --- a/src/MQTTProtocol.h +++ b/src/MQTTProtocol.h @@ -9,6 +9,7 @@ * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation *******************************************************************************/ + #if !defined(MQTTPROTOCOL_H) #define MQTTPROTOCOL_H diff --git a/src/MQTTProtocolClient.c b/src/MQTTProtocolClient.c index 3125a81a..f3d6becc 100644 --- a/src/MQTTProtocolClient.c +++ b/src/MQTTProtocolClient.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ /** @@ -80,7 +81,7 @@ void MQTTProtocol_storeQoS0(Clients* pubclient, Publish* publish) pw = malloc(sizeof(pending_write)); Log(TRACE_MIN, 12, NULL); pw->p = MQTTProtocol_storePublication(publish, &len); - pw->socket = pubclient->socket; + pw->socket = pubclient->net.socket; ListAppend(&(state.pending_writes), pw, sizeof(pending_write)+len); /* we don't copy QoS 0 messages unless we have to, so now we have to tell the socket buffer where the saved copy is */ @@ -103,7 +104,7 @@ int MQTTProtocol_startPublishCommon(Clients* pubclient, Publish* publish, int qo int rc = TCPSOCKET_COMPLETE; FUNC_ENTRY; - rc = MQTTPacket_send_publish(publish, 0, qos, retained, pubclient->socket, pubclient->clientID); + rc = MQTTPacket_send_publish(publish, 0, qos, retained, &pubclient->net, pubclient->clientID); if (qos == 0 && rc == TCPSOCKET_INTERRUPTED) MQTTProtocol_storeQoS0(pubclient, publish); FUNC_EXIT_RC(rc); @@ -253,7 +254,7 @@ int MQTTProtocol_handlePublishes(void* pack, int sock) else if (publish->header.bits.qos == 1) { /* send puback before processing the publications because a lot of return publications could fill up the socket buffer */ - rc = MQTTPacket_send_puback(publish->msgId, sock, client->clientID); + rc = MQTTPacket_send_puback(publish->msgId, &client->net, client->clientID); /* if we get a socket error from sending the puback, should we ignore the publication? */ Protocol_processPublication(publish, client); } @@ -277,7 +278,7 @@ int MQTTProtocol_handlePublishes(void* pack, int sock) ListRemove(client->inboundMsgs, msg); } else ListAppend(client->inboundMsgs, m, sizeof(Messages) + len); - rc = MQTTPacket_send_pubrec(publish->msgId, sock, client->clientID); + rc = MQTTPacket_send_pubrec(publish->msgId, &client->net, client->clientID); publish->topic = NULL; } MQTTPacket_freePublish(publish); @@ -363,7 +364,7 @@ int MQTTProtocol_handlePubrecs(void* pack, int sock) } else { - rc = MQTTPacket_send_pubrel(pubrec->msgId, 0, sock, client->clientID); + rc = MQTTPacket_send_pubrel(pubrec->msgId, 0, &client->net, client->clientID); m->nextMessageType = PUBCOMP; time(&(m->lastTouch)); } @@ -397,7 +398,7 @@ int MQTTProtocol_handlePubrels(void* pack, int sock) Log(TRACE_MIN, 3, NULL, "PUBREL", client->clientID, pubrel->msgId); else /* Apparently this is "normal" behaviour, so we don't need to issue a warning */ - rc = MQTTPacket_send_pubcomp(pubrel->msgId, sock, client->clientID); + rc = MQTTPacket_send_pubcomp(pubrel->msgId, &client->net, client->clientID); } else { @@ -411,7 +412,7 @@ int MQTTProtocol_handlePubrels(void* pack, int sock) Publish publish; /* send pubcomp before processing the publications because a lot of return publications could fill up the socket buffer */ - rc = MQTTPacket_send_pubcomp(pubrel->msgId, sock, client->clientID); + rc = MQTTPacket_send_pubcomp(pubrel->msgId, &client->net, client->clientID); publish.header.bits.qos = m->qos; publish.header.bits.retain = m->retain; publish.msgId = m->msgid; @@ -497,10 +498,10 @@ void MQTTProtocol_keepalive(time_t now) { Clients* client = (Clients*)(current->content); ListNextElement(bstate->clients, ¤t); - if (client->connected && client->keepAliveInterval > 0 + if (client->connected && client->keepAliveInterval > 0 && Socket_noPendingWrites(client->net.socket) && (difftime(now, client->lastContact) >= client->keepAliveInterval)) { - MQTTPacket_send_pingreq(client->socket, client->clientID); + MQTTPacket_send_pingreq(&client->net, client->clientID); client->lastContact = now; client->ping_outstanding = 1; } @@ -533,17 +534,17 @@ void MQTTProtocol_retries(time_t now, Clients* client) Publish publish; int rc; - Log(TRACE_MIN, 7, NULL, "PUBLISH", client->clientID, client->socket, m->msgid); + Log(TRACE_MIN, 7, NULL, "PUBLISH", client->clientID, client->net.socket, m->msgid); publish.msgId = m->msgid; publish.topic = m->publish->topic; publish.payload = m->publish->payload; publish.payloadlen = m->publish->payloadlen; - rc = MQTTPacket_send_publish(&publish, 1, m->qos, m->retain, client->socket, client->clientID); + rc = MQTTPacket_send_publish(&publish, 1, m->qos, m->retain, &client->net, client->clientID); if (rc == SOCKET_ERROR) { client->good = 0; - Log(TRACE_MIN, 8, NULL, client->clientID, client->socket, - Socket_getpeer(client->socket)); + Log(TRACE_MIN, 8, NULL, client->clientID, client->net.socket, + Socket_getpeer(client->net.socket)); MQTTProtocol_closeSession(client, 1); client = NULL; } @@ -556,12 +557,12 @@ void MQTTProtocol_retries(time_t now, Clients* client) } else if (m->qos && m->nextMessageType == PUBCOMP) { - Log(TRACE_MIN, 7, NULL, "PUBREL", client->clientID, client->socket, m->msgid); - if (MQTTPacket_send_pubrel(m->msgid, 1, client->socket, client->clientID) != TCPSOCKET_COMPLETE) + Log(TRACE_MIN, 7, NULL, "PUBREL", client->clientID, client->net.socket, m->msgid); + if (MQTTPacket_send_pubrel(m->msgid, 1, &client->net, client->clientID) != TCPSOCKET_COMPLETE) { client->good = 0; - Log(TRACE_MIN, 8, NULL, client->clientID, client->socket, - Socket_getpeer(client->socket)); + Log(TRACE_MIN, 8, NULL, client->clientID, client->net.socket, + Socket_getpeer(client->net.socket)); MQTTProtocol_closeSession(client, 1); client = NULL; } @@ -599,7 +600,7 @@ void MQTTProtocol_retry(time_t now, int doRetry) MQTTProtocol_closeSession(client, 1); continue; } - if (Socket_noPendingWrites(client->socket) == 0) + if (Socket_noPendingWrites(client->net.socket) == 0) continue; if (doRetry) MQTTProtocol_retries(now, client); @@ -620,13 +621,16 @@ void MQTTProtocol_freeClient(Clients* client) MQTTProtocol_freeMessageList(client->inboundMsgs); ListFree(client->messageQueue); free(client->clientID); - /*if (client->will != NULL) + if (client->will) { - willMessages* w = client->will; - free(w->msg); - free(w->topic); + free(client->will->msg); + free(client->will->topic); free(client->will); - }*/ + } +#if defined(OPENSSL) + if (client->sslopts) + free(client->sslopts); +#endif /* don't free the client structure itself... this is done elsewhere */ FUNC_EXIT; } diff --git a/src/MQTTProtocolClient.h b/src/MQTTProtocolClient.h index bd8096dc..3ade0206 100644 --- a/src/MQTTProtocolClient.h +++ b/src/MQTTProtocolClient.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ #if !defined(MQTTPROTOCOLCLIENT_H) @@ -28,6 +29,7 @@ Messages* MQTTProtocol_createMessage(Publish* publish, Messages** mm, int qos, i Publications* MQTTProtocol_storePublication(Publish* publish, int* len); int messageIDCompare(void* a, void* b); int MQTTProtocol_assignMsgId(Clients* client); +void MQTTProtocol_removePublication(Publications* p); int MQTTProtocol_handlePublishes(void* pack, int sock); int MQTTProtocol_handlePubacks(void* pack, int sock); diff --git a/src/MQTTProtocolOut.c b/src/MQTTProtocolOut.c index b0a67816..6fd9f5ae 100644 --- a/src/MQTTProtocolOut.c +++ b/src/MQTTProtocolOut.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 201 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ /** @@ -80,7 +81,11 @@ char* MQTTProtocol_addressPort(char* ip_address, int* port) * @param password MQTT 3.1 password, or NULL * @return the new client structure */ +#if defined(OPENSSL) +int MQTTProtocol_connect(char* ip_address, Clients* aClient, int ssl) +#else int MQTTProtocol_connect(char* ip_address, Clients* aClient) +#endif { int rc, port; char* addr; @@ -90,15 +95,33 @@ int MQTTProtocol_connect(char* ip_address, Clients* aClient) time(&(aClient->lastContact)); addr = MQTTProtocol_addressPort(ip_address, &port); - rc = Socket_new(addr, port, &(aClient->socket)); + rc = Socket_new(addr, port, &(aClient->net.socket)); if (rc == EINPROGRESS || rc == EWOULDBLOCK) - aClient->connect_state = 1; /* TCP connect called */ + aClient->connect_state = 1; /* TCP connect called - wait for connect completion */ else if (rc == 0) - { - if ((rc = MQTTPacket_send_connect(aClient)) == 0) - aClient->connect_state = 2; /* TCP connect completed, in which case send the MQTT connect packet */ - else - aClient->connect_state = 0; + { /* TCP connect completed. If SSL, send SSL connect */ +#if defined(OPENSSL) + if (ssl) + { + if((aClient->net.ssl = SSLSocket_setSocketForSSL(aClient->net.socket, aClient->sslopts)) != NULL) + { + rc = SSLSocket_connect(aClient->net.ssl, aClient->net.socket); + if (rc == -1) + aClient->connect_state = 2; /* SSL connect called - wait for completion */ + } + else + rc = SOCKET_ERROR; + } +#endif + + if (rc == 0) + { + /* Now send the MQTT connect packet */ + if ((rc = MQTTPacket_send_connect(aClient)) == 0) + aClient->connect_state = 3; /* MQTT Connect sent - wait for CONNACK */ + else + aClient->connect_state = 0; + } } FUNC_EXIT_RC(rc); @@ -139,7 +162,7 @@ int MQTTProtocol_subscribe(Clients* client, List* topics, List* qoss) FUNC_ENTRY; /* we should stack this up for retry processing too */ - rc = MQTTPacket_send_subscribe(topics, qoss, MQTTProtocol_assignMsgId(client), 0, client->socket, client->clientID); + rc = MQTTPacket_send_subscribe(topics, qoss, MQTTProtocol_assignMsgId(client), 0, &client->net, client->clientID); FUNC_EXIT_RC(rc); return rc; } @@ -178,7 +201,7 @@ int MQTTProtocol_unsubscribe(Clients* client, List* topics) FUNC_ENTRY; /* we should stack this up for retry processing too? */ - rc = MQTTPacket_send_unsubscribe(topics, MQTTProtocol_assignMsgId(client), 0, client->socket, client->clientID); + rc = MQTTPacket_send_unsubscribe(topics, MQTTProtocol_assignMsgId(client), 0, &client->net, client->clientID); FUNC_EXIT_RC(rc); return rc; } diff --git a/src/MQTTProtocolOut.h b/src/MQTTProtocolOut.h index 7dd8c4f3..9be3e90c 100644 --- a/src/MQTTProtocolOut.h +++ b/src/MQTTProtocolOut.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ #if !defined(MQTTPROTOCOLOUT_H) @@ -24,7 +25,11 @@ #define DEFAULT_PORT 1883 void MQTTProtocol_reconnect(char* ip_address, Clients* client); +#if defined(OPENSSL) +int MQTTProtocol_connect(char* ip_address, Clients* acClients, int ssl); +#else int MQTTProtocol_connect(char* ip_address, Clients* acClients); +#endif int MQTTProtocol_handlePingresps(void* pack, int sock); int MQTTProtocol_subscribe(Clients* client, List* topics, List* qoss); int MQTTProtocol_handleSubacks(void* pack, int sock); diff --git a/src/MQTTVersion.c b/src/MQTTVersion.c new file mode 100644 index 00000000..283620a1 --- /dev/null +++ b/src/MQTTVersion.c @@ -0,0 +1,203 @@ +/******************************************************************************* + * Copyright (c) 2012 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "MQTTAsync.h" + +#if defined(WIN32) +#include +#include +#include +#include +#else +#include +#include +#include +#endif + + +/** + * + * @file + * \brief MQTTVersion - display the version and build information strings for a library. + * + * With no arguments, we try to load and call the version string for the libraries we + * know about: mqttv3c, mqttv3cs, mqttv3a, mqttv3as. + * With an argument: + * 1) we try to load the named library, call getVersionInfo and display those values. + * 2) If that doesn't work, we look through the binary for eyecatchers, and display those. + * This will work if the library is not executable in the current environment. + * + * */ + + + char* libraries[] = {"mqttv3c", "mqttv3cs", "mqttv3a", "mqttv3as"}; + char* eyecatchers[] = {"MQTTAsyncV3_Version", "MQTTAsyncV3_Timestamp", + "MQTTClientV3_Version", "MQTTClientV3_Timestamp"}; + + +/** + * Finds an eyecatcher in a binary file and returns the following value. + * @param filename the name of the file + * @param eyecatcher_input the eyecatcher string to look for + * @return the value found - "" if not found + */ +char* FindString(char* filename, char* eyecatcher_input) +{ + FILE* infile = NULL; + static char value[100]; + char* eyecatcher = eyecatcher_input; + + memset(value, 0, 100); + if ((infile = fopen(filename, "rb")) != NULL) + { + int buflen = strlen(eyecatcher); + char* buffer = (char*) malloc(buflen); + int count = 0; + int c = fgetc(infile); + + while (feof(infile) == 0) + { + buffer[count++] = c; + if (memcmp(eyecatcher, buffer, buflen) == 0) + { + char* ptr = value; + c = fgetc(infile); /* skip space */ + c = fgetc(infile); + while (isprint(c)) + { + *ptr++ = c; + c = fgetc(infile); + } + break; + } + if (count == buflen) + { + memmove(buffer, &buffer[1], buflen - 1); + count--; + } + c = fgetc(infile); + } + free(buffer); + } + return value; +} + + +int printVersionInfo(MQTTAsync_nameValue* info) +{ + int rc = 0; + + while (info->name) + { + printf("%s: %s\n", info->name, info->value); + info++; + rc = 1; /* at least one value printed */ + } + return rc; +} + +typedef MQTTAsync_nameValue* (*func_type)(void); + +int loadandcall(char* libname) +{ + int rc = 0; + MQTTAsync_nameValue* (*func_address)(void) = NULL; +#if defined(WIN32) + HMODULE APILibrary = LoadLibrary(libname); + + if (APILibrary == NULL) + printf("Error loading library %s, error code %d\n", libname, GetLastError()); + else + { + func_address = (func_type)GetProcAddress(APILibrary, "MQTTAsync_getVersionInfo"); + if (func_address == NULL) + func_address = (func_type)GetProcAddress(APILibrary, "MQTTClient_getVersionInfo"); + if (func_address) + rc = printVersionInfo((*func_address)()); + FreeLibrary(APILibrary); + } +#else + void* APILibrary = dlopen(libname, RTLD_LAZY); /* Open the Library in question */ + char* ErrorOutput = dlerror(); /* Check it opened properly */ + if (ErrorOutput != NULL) + printf("Error loading library %s, error %s\n", libname, ErrorOutput); + else + { + *(void **) (&func_address) = dlsym(APILibrary, "MQTTAsync_getVersionInfo"); + if (func_address == NULL) + func_address = dlsym(APILibrary, "MQTTClient_getVersionInfo"); + if (func_address) + rc = printVersionInfo((*func_address)()); + dlclose(APILibrary); + } +#endif + return rc; +} + + +#if !defined(ARRAY_SIZE) +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#endif + +void printEyecatchers(char* filename) +{ + int i = 0; + + for (i = 0; i < ARRAY_SIZE(eyecatchers); ++i) + { + char* value = FindString(filename, eyecatchers[i]); + if (value[0]) + printf("%s: %s\n", eyecatchers[i], value); + } +} + + +int main(int argc, char** argv) +{ + printf("MQTTVersion: print the version strings of an MQTT client library\n"); + printf("Copyright (c) 2012 IBM Corp.\n"); + + if (argc == 1) + { + int i = 0; + char namebuf[60]; + + printf("Specify a particular library name if it is not in the current directory, or not executable on this platform\n"); + + for (i = 0; i < ARRAY_SIZE(libraries); ++i) + { +#if defined(WIN32) + sprintf(namebuf, "%s.dll", libraries[i]); +#else + sprintf(namebuf, "lib%s.so", libraries[i]); +#endif + printf("--- Trying library %s ---\n", libraries[i]); + if (!loadandcall(namebuf)) + printEyecatchers(namebuf); + } + } + else + { + if (!loadandcall(argv[1])) + printEyecatchers(argv[1]); + } + + return 0; +} diff --git a/src/SSLSocket.c b/src/SSLSocket.c new file mode 100644 index 00000000..cec94ebf --- /dev/null +++ b/src/SSLSocket.c @@ -0,0 +1,682 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Ian Craggs, Allan Stockdill-Mander - initial implementation + *******************************************************************************/ + +/** + * @file + * \brief SSL related functions + * + */ + +#if defined(OPENSSL) + +#include "SocketBuffer.h" +#include "MQTTClient.h" +#include "SSLSocket.h" +#include "Log.h" +#include "StackTrace.h" +#include "Socket.h" + +#include "Heap.h" + +#include +#include +#include + +extern Sockets s; + +void SSLSocket_addPendingRead(int sock); + +static ssl_mutex_type* sslLocks = NULL; +static ssl_mutex_type sslCoreMutex; + +#if defined(WIN32) +#define iov_len len +#define iov_base buf +#endif + +/** + * Gets the specific error corresponding to SOCKET_ERROR + * @param aString the function that was being used when the error occurred + * @param sock the socket on which the error occurred + * @return the specific TCP error code + */ +int SSLSocket_error(char* aString, SSL* ssl, int sock, int rc) +{ + int error; + + FUNC_ENTRY; + if (ssl) + error = SSL_get_error(ssl, rc); + else + error = ERR_get_error(); + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) + { + Log(TRACE_MIN, -1, "SSLSocket error WANT_READ/WANT_WRITE"); + } + else + { + static char buf[120]; + + if (strcmp(aString, "shutdown") != 0) + Log(TRACE_MIN, -1, "SSLSocket error %s(%d) in %s for socket %d rc %d errno %d %s\n", buf, error, aString, sock, rc, errno, strerror(errno)); + //ERR_print_errors_fp(stderr); + if (error == SSL_ERROR_SSL || error == SSL_ERROR_SYSCALL) + error = SSL_FATAL; + } + FUNC_EXIT_RC(error); + return error; +} + +static struct +{ + int code; + char* string; +} +X509_message_table[] = +{ + { X509_V_OK, "X509_V_OK" }, + { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT" }, + { X509_V_ERR_UNABLE_TO_GET_CRL, "X509_V_ERR_UNABLE_TO_GET_CRL" }, + { X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE, "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE" }, + { X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE, "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE" }, + { X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY" }, + { X509_V_ERR_CERT_SIGNATURE_FAILURE, "X509_V_ERR_CERT_SIGNATURE_FAILURE" }, + { X509_V_ERR_CRL_SIGNATURE_FAILURE, "X509_V_ERR_CRL_SIGNATURE_FAILURE" }, + { X509_V_ERR_CERT_NOT_YET_VALID, "X509_V_ERR_CERT_NOT_YET_VALID" }, + { X509_V_ERR_CERT_HAS_EXPIRED, "X509_V_ERR_CERT_HAS_EXPIRED" }, + { X509_V_ERR_CRL_NOT_YET_VALID, "X509_V_ERR_CRL_NOT_YET_VALID" }, + { X509_V_ERR_CRL_HAS_EXPIRED, "X509_V_ERR_CRL_HAS_EXPIRED" }, + { X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD, "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD" }, + { X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD, "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD" }, + { X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD, "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD" }, + { X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD, "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD" }, + { X509_V_ERR_OUT_OF_MEM, "X509_V_ERR_OUT_OF_MEM" }, + { X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT" }, + { X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN, "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN" }, + { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY" }, + { X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE" }, + { X509_V_ERR_CERT_CHAIN_TOO_LONG, "X509_V_ERR_CERT_CHAIN_TOO_LONG" }, + { X509_V_ERR_CERT_REVOKED, "X509_V_ERR_CERT_REVOKED" }, + { X509_V_ERR_INVALID_CA, "X509_V_ERR_INVALID_CA" }, + { X509_V_ERR_PATH_LENGTH_EXCEEDED, "X509_V_ERR_PATH_LENGTH_EXCEEDED" }, + { X509_V_ERR_INVALID_PURPOSE, "X509_V_ERR_INVALID_PURPOSE" }, + { X509_V_ERR_CERT_UNTRUSTED, "X509_V_ERR_CERT_UNTRUSTED" }, + { X509_V_ERR_CERT_REJECTED, "X509_V_ERR_CERT_REJECTED" }, + { X509_V_ERR_SUBJECT_ISSUER_MISMATCH, "X509_V_ERR_SUBJECT_ISSUER_MISMATCH" }, + { X509_V_ERR_AKID_SKID_MISMATCH, "X509_V_ERR_AKID_SKID_MISMATCH" }, + { X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH, "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH" }, + { X509_V_ERR_KEYUSAGE_NO_CERTSIGN, "X509_V_ERR_KEYUSAGE_NO_CERTSIGN" }, + { X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER, "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER" }, + { X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION" }, + { X509_V_ERR_KEYUSAGE_NO_CRL_SIGN, "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN" }, + { X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION, "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION" }, + { X509_V_ERR_INVALID_NON_CA, "X509_V_ERR_INVALID_NON_CA" }, + { X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED, "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED" }, + { X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE, "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE" }, + { X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED, "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED" }, + { X509_V_ERR_INVALID_EXTENSION, "X509_V_ERR_INVALID_EXTENSION" }, + { X509_V_ERR_INVALID_POLICY_EXTENSION, "X509_V_ERR_INVALID_POLICY_EXTENSION" }, + { X509_V_ERR_NO_EXPLICIT_POLICY, "X509_V_ERR_NO_EXPLICIT_POLICY" }, + { X509_V_ERR_DIFFERENT_CRL_SCOPE, "X509_V_ERR_DIFFERENT_CRL_SCOPE" }, + { X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE, "X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE" }, + { X509_V_ERR_UNNESTED_RESOURCE, "X509_V_ERR_UNNESTED_RESOURCE" }, + { X509_V_ERR_PERMITTED_VIOLATION, "X509_V_ERR_PERMITTED_VIOLATION" }, + { X509_V_ERR_EXCLUDED_VIOLATION, "X509_V_ERR_EXCLUDED_VIOLATION" }, + { X509_V_ERR_SUBTREE_MINMAX, "X509_V_ERR_SUBTREE_MINMAX" }, + { X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE, "X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE" }, + { X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX, "X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX" }, + { X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, "X509_V_ERR_UNSUPPORTED_NAME_SYNTAX" }, +}; + +#if !defined(ARRAY_SIZE) +/** + * Macro to calculate the number of entries in an array + */ +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#endif + +char* SSL_get_verify_result_string(int rc) +{ + int i; + char* retstring = "undef"; + + for (i = 0; i < ARRAY_SIZE(X509_message_table); ++i) + { + if (X509_message_table[i].code == rc) + { + retstring = X509_message_table[i].string; + break; + } + } + return retstring; +} + +void SSL_CTX_info_callback(const SSL* ssl, int where, int ret) +{ + FUNC_ENTRY; + + if (where & SSL_CB_LOOP) + { + Log(TRACE_PROTOCOL, 1, "SSL state %s:%s:%s", + (where & SSL_ST_CONNECT) ? "connect" : (where & SSL_ST_ACCEPT) ? "accept" : "undef", + SSL_state_string_long(ssl), SSL_get_cipher_name(ssl)); + } + else if (where & SSL_CB_EXIT) + { + Log(TRACE_PROTOCOL, 1, "SSL %s:%s", + (where & SSL_ST_CONNECT) ? "connect" : (where & SSL_ST_ACCEPT) ? "accept" : "undef", + SSL_state_string_long(ssl)); + } + else if (where & SSL_CB_ALERT) + { + Log(TRACE_PROTOCOL, 1, "SSL alert %s:%s:%s", + (where & SSL_CB_READ) ? "read" : "write", + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + } + else if (where & SSL_CB_HANDSHAKE_START) + { + Log(TRACE_PROTOCOL, 1, "SSL handshake started %s:%s:%s", + (where & SSL_CB_READ) ? "read" : "write", + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + } + else if (where & SSL_CB_HANDSHAKE_DONE) + { + Log(TRACE_PROTOCOL, 1, "SSL handshake done %s:%s:%s", + (where & SSL_CB_READ) ? "read" : "write", + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + Log(TRACE_PROTOCOL, 1, "SSL certificate verification: %s", + SSL_get_verify_result_string(SSL_get_verify_result(ssl))); + } + else + { + Log(TRACE_PROTOCOL, 1, "SSL state %s:%s:%s", SSL_state_string_long(ssl), + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + } + FUNC_EXIT; +} + +int pem_passwd_cb(char* buf, int size, int rwflag, void* userdata) +{ + FUNC_ENTRY; + + if (!rwflag) + { + strncpy(buf, (char*)(userdata), size); + buf[size-1] = '\0'; + } + FUNC_EXIT; + if (!rwflag) return strlen(buf); + else return 0; +} + +int SSL_create_mutex(ssl_mutex_type* mutex) +{ + int rc = 0; + + FUNC_ENTRY; +#if defined(WIN32) + mutex = CreateMutex(NULL, 0, NULL); +#else + rc = pthread_mutex_init(mutex, NULL); +#endif + FUNC_EXIT_RC(rc); + return rc; +} + +int SSL_lock_mutex(ssl_mutex_type* mutex) +{ + int rc = -1; + + /* don't add entry/exit trace points as the stack log uses mutexes - recursion beckons */ +#if defined(WIN32) + if (WaitForSingleObject(mutex, INFINITE) != WAIT_FAILED) +#else + if ((rc = pthread_mutex_lock(mutex)) == 0) +#endif + rc = 0; + + return rc; +} + +int SSL_unlock_mutex(ssl_mutex_type* mutex) +{ + int rc = -1; + + /* don't add entry/exit trace points as the stack log uses mutexes - recursion beckons */ +#if defined(WIN32) + if (ReleaseMutex(mutex) != 0) +#else + if ((rc = pthread_mutex_unlock(mutex)) == 0) +#endif + rc = 0; + + return rc; +} + +void SSL_destroy_mutex(ssl_mutex_type* mutex) +{ + int rc = 0; + + FUNC_ENTRY; +#if defined(WIN32) + rc = CloseHandle(mutex); +#else + rc = pthread_mutex_destroy(mutex); + free(mutex); +#endif + FUNC_EXIT_RC(rc); +} + +extern void SSLThread_id(CRYPTO_THREADID *id) +{ +#if defined(WIN32) + CRYPTO_THREADID_set_numeric(id, (unsigned long)GetCurrentThreadId()); +#else + CRYPTO_THREADID_set_numeric(id, (unsigned long)pthread_self()); +#endif +} + +extern void SSLLocks_callback(int mode, int n, const char *file, int line) +{ + if (mode & CRYPTO_LOCK) + SSL_lock_mutex(&sslLocks[n]); + else + SSL_unlock_mutex(&sslLocks[n]); +} + +int SSLSocket_initialize() +{ + int rc = 0; + /*int prc;*/ + int i; + + FUNC_ENTRY; + + if ((rc = SSL_library_init()) != 1) + rc = -1; + + ERR_load_crypto_strings(); + SSL_load_error_strings(); + + /* OpenSSL 0.9.8o and 1.0.0a and later added SHA2 algorithms to SSL_library_init(). + Applications which need to use SHA2 in earlier versions of OpenSSL should call + OpenSSL_add_all_algorithms() as well. */ + + OpenSSL_add_all_algorithms(); + + sslLocks = calloc(CRYPTO_num_locks(), sizeof(ssl_mutex_type)); + if (!sslLocks) + { + rc = -1; + goto exit; + } + for (i = 0; i < CRYPTO_num_locks(); i++) + { + /* prc = */SSL_create_mutex(&sslLocks[i]); + } + + CRYPTO_THREADID_set_callback(SSLThread_id); + CRYPTO_set_locking_callback(SSLLocks_callback); + + SSL_create_mutex(&sslCoreMutex); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + +SSL_CTX* SSLSocket_createContext(int socket, MQTTClient_SSLOptions* opts) +{ + int rc = 1; + SSL_CTX* ctx = NULL; + char* ciphers = NULL; + + FUNC_ENTRY; + if ((ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) /* SSLv23 for compatibility with SSLv2, SSLv3 and TLSv1 */ + { + SSLSocket_error("SSL_CTX_new", NULL, socket, rc); + goto exit; + } + + if (opts->keyStore) + { + if ((rc = SSL_CTX_use_certificate_chain_file(ctx, opts->keyStore)) != 1) + { + SSLSocket_error("SSL_CTX_use_certificate_chain_file", NULL, socket, rc); + goto free_ctx; /*If we can't load the certificate (chain) file then loading the privatekey won't work either as it needs a matching cert already loaded */ + } + + if (opts->privateKey == NULL) + opts->privateKey = opts->keyStore; /* the privateKey can be included in the keyStore */ + + if (opts->privateKeyPassword != NULL) + { + SSL_CTX_set_default_passwd_cb(ctx, pem_passwd_cb); + SSL_CTX_set_default_passwd_cb_userdata(ctx, (void*)opts->privateKeyPassword); + } + + /* support for ASN.1 == DER format? DER can contain only one certificate? */ + if ((rc = SSL_CTX_use_PrivateKey_file(ctx, opts->privateKey, SSL_FILETYPE_PEM)) != 1) + { + SSLSocket_error("SSL_CTX_use_PrivateKey_file", NULL, socket, rc); + goto free_ctx; + } + } + + if (opts->trustStore) + { + if ((rc = SSL_CTX_load_verify_locations(ctx, opts->trustStore, NULL)) != 1) + { + SSLSocket_error("SSL_CTX_load_verify_locations", NULL, socket, rc); + goto free_ctx; + } + } + else if ((rc = SSL_CTX_set_default_verify_paths(ctx)) != 1) + { + SSLSocket_error("SSL_CTX_set_default_verify_paths", NULL, socket, rc); + goto free_ctx; + } + + if (opts->enabledCipherSuites == NULL) + ciphers = "DEFAULT"; + else + ciphers = opts->enabledCipherSuites; + + if ((rc = SSL_CTX_set_cipher_list(ctx, ciphers)) != 1) + { + SSLSocket_error("SSL_CTX_set_cipher_list", NULL, socket, rc); + goto free_ctx; + } + + SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + goto exit; +free_ctx: + SSL_CTX_free(ctx); + ctx = NULL; + +exit: + FUNC_EXIT; + return ctx; +} + + +SSL* SSLSocket_setSocketForSSL(int socket, MQTTClient_SSLOptions* opts) +{ + int rc = 1; + SSL_CTX* ctx = NULL; + SSL* ssl = NULL; + + FUNC_ENTRY; + + if ((ctx = SSLSocket_createContext(socket, opts)) != NULL) + { + int i; + SSL_CTX_set_info_callback(ctx, SSL_CTX_info_callback); + if (opts->enableServerCertAuth) + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + + ssl = SSL_new(ctx); + + /* Log all ciphers available to the SSL sessions (loaded in ctx) */ + for (i = 0; ;i++) + { + const char* cipher = SSL_get_cipher_list(ssl, i); + if (cipher == NULL) break; + Log(TRACE_MIN, 1, "SSL cipher available: %d:%s", i, cipher); + } + + if ((rc = SSL_set_fd(ssl, socket)) != 1) + SSLSocket_error("SSL_set_fd", ssl, socket, rc); + } + + FUNC_EXIT_RC(rc); + return ssl; +} + + +int SSLSocket_connect(SSL* ssl, int sock) +{ + int rc = 0; + + FUNC_ENTRY; + + rc = SSL_connect(ssl); + if (rc != 1) + { + int error; + error = SSLSocket_error("SSL_connect", ssl, sock, rc); + if (error == SSL_FATAL) + rc = error; + } + + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Reads one byte from a socket + * @param socket the socket to read from + * @param c the character read, returned + * @return completion code + */ +int SSLSocket_getch(SSL* ssl, int socket, char* c) +{ + int rc = SOCKET_ERROR; + + FUNC_ENTRY; + if ((rc = SocketBuffer_getQueuedChar(socket, c)) != SOCKETBUFFER_INTERRUPTED) + goto exit; + + if ((rc = SSL_read(ssl, c, (size_t)1)) < 0) + { + int err = SSLSocket_error("SSL_read - getch", ssl, socket, rc); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + { + rc = TCPSOCKET_INTERRUPTED; + SocketBuffer_interrupted(socket, 0); + } + } + else if (rc == 0) + rc = SOCKET_ERROR; /* The return value from recv is 0 when the peer has performed an orderly shutdown. */ + else if (rc == 1) + { + SocketBuffer_queueChar(socket, *c); + rc = TCPSOCKET_COMPLETE; + } +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Attempts to read a number of bytes from a socket, non-blocking. If a previous read did not + * finish, then retrieve that data. + * @param socket the socket to read from + * @param bytes the number of bytes to read + * @param actual_len the actual number of bytes read + * @return completion code + */ +char *SSLSocket_getdata(SSL* ssl, int socket, int bytes, int* actual_len) +{ + int rc; + char* buf; + + FUNC_ENTRY; + if (bytes == 0) + { + buf = SocketBuffer_complete(socket); + goto exit; + } + + buf = SocketBuffer_getQueuedData(socket, bytes, actual_len); + + if ((rc = SSL_read(ssl, buf + (*actual_len), (size_t)(bytes - (*actual_len)))) < 0) + { + rc = SSLSocket_error("SSL_read - getdata", ssl, socket, rc); + if (rc != SSL_ERROR_WANT_READ && rc != SSL_ERROR_WANT_WRITE) + { + buf = NULL; + goto exit; + } + } + else if (rc == 0) /* rc 0 means the other end closed the socket */ + { + buf = NULL; + goto exit; + } + else + *actual_len += rc; + + if (*actual_len == bytes) + { + SocketBuffer_complete(socket); + /* if we read the whole packet, there might still be data waiting in the SSL buffer, which + isn't picked up by select. So here we should check for any data remaining in the SSL buffer, and + if so, add this socket to a new "pending SSL reads" list. + */ + if (SSL_pending(ssl) > 0) /* return no of bytes pending */ + SSLSocket_addPendingRead(socket); + } + else /* we didn't read the whole packet */ + { + SocketBuffer_interrupted(socket, *actual_len); + Log(TRACE_MAX, -1, "SSL_read: %d bytes expected but %d bytes now received", bytes, *actual_len); + } +exit: + FUNC_EXIT; + return buf; +} + + +int SSLSocket_close(SSL* ssl) +{ + return SSL_shutdown(ssl); +} + + +/* No SSL_writev() provided by OpenSSL. Boo. */ +int SSLSocket_putdatas(SSL* ssl, int socket, char* buf0, int buf0len, int count, char** buffers, int* buflens) +{ + int rc = 0; + int i; + char *ptr; + iobuf iovec; + int sslerror; + + FUNC_ENTRY; + iovec.iov_len = buf0len; + for (i = 0; i < count; i++) + iovec.iov_len += buflens[i]; + + ptr = iovec.iov_base = (char *)malloc(iovec.iov_len); + memcpy(ptr, buf0, buf0len); + ptr += buf0len; + for (i = 0; i < count; i++) + { + memcpy(ptr, buffers[i], buflens[i]); + ptr += buflens[i]; + } + + SSL_lock_mutex(&sslCoreMutex); + if ((rc = SSL_write(ssl, iovec.iov_base, iovec.iov_len)) == iovec.iov_len) + rc = TCPSOCKET_COMPLETE; + else + { + sslerror = SSLSocket_error("SSL_write", ssl, socket, rc); + + if (sslerror == SSL_ERROR_WANT_WRITE) + { + int* sockmem = (int*)malloc(sizeof(int)); + Log(TRACE_MIN, -1, "Partial write: incomplete write of %d bytes on SSL socket %d", + iovec.iov_len, socket); + SocketBuffer_pendingWrite(socket, ssl, 1, &iovec, iovec.iov_len, 0); + *sockmem = socket; + ListAppend(s.write_pending, sockmem, sizeof(int)); + FD_SET(socket, &(s.pending_wset)); + rc = TCPSOCKET_INTERRUPTED; + iovec.iov_base = NULL; /* don't free it because it hasn't been completely written yet */ + } + else + rc = SOCKET_ERROR; + } + SSL_unlock_mutex(&sslCoreMutex); + + if (iovec.iov_base) + free(iovec.iov_base); + FUNC_EXIT_RC(rc); + return rc; +} + +static List pending_reads = {NULL, NULL, NULL, 0, 0}; + +void SSLSocket_addPendingRead(int sock) +{ + FUNC_ENTRY; + if (ListFindItem(&pending_reads, &sock, intcompare) == NULL) /* make sure we don't add the same socket twice */ + { + int* psock = (int*)malloc(sizeof(sock)); + *psock = sock; + ListAppend(&pending_reads, psock, sizeof(sock)); + } + else + Log(TRACE_MIN, -1, "SSLSocket_addPendingRead: socket %d already in the list", sock); + + FUNC_EXIT; +} + + +int SSLSocket_getPendingRead() +{ + int sock = -1; + + if (pending_reads.count > 0) + { + sock = *(int*)(pending_reads.first->content); + ListRemoveHead(&pending_reads); + } + return sock; +} + + +int SSLSocket_continueWrite(pending_writes* pw) +{ + int rc = 0; + + FUNC_ENTRY; + if ((rc = SSL_write(pw->ssl, pw->iovecs[0].iov_base, pw->iovecs[0].iov_len)) == pw->iovecs[0].iov_len) + { + /* topic and payload buffers are freed elsewhere, when all references to them have been removed */ + free(pw->iovecs[0].iov_base); + if (pw->count > 1) + { + free(pw->iovecs[1].iov_base); + if (pw->count == 5) + free(pw->iovecs[3].iov_base); + } + Log(TRACE_MIN, -1, "SSL continueWrite: partial write now complete for socket %d", pw->socket); + rc = 1; + } + else + { + int sslerror = SSLSocket_error("SSL_write", pw->ssl, pw->socket, rc); + if (sslerror == SSL_ERROR_WANT_WRITE) + rc = 0; /* indicate we haven't finished writing the payload yet */ + } + FUNC_EXIT_RC(rc); + return rc; +} +#endif diff --git a/src/SSLSocket.h b/src/SSLSocket.h new file mode 100644 index 00000000..2e7e8392 --- /dev/null +++ b/src/SSLSocket.h @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Ian Craggs, Allan Stockdill-Mander - initial implementation + *******************************************************************************/ + +#include "MQTTClient.h" + +#if !defined(SSLSOCKET_H) +#define SSLSOCKET_H + +#if defined(WIN32) + #define ssl_mutex_type HANDLE +#else + #include + #include + #define ssl_mutex_type pthread_mutex_t +#endif + +#include +#include "SocketBuffer.h" + +#define URI_SSL "ssl://" + +int SSLSocket_initialize(); +SSL* SSLSocket_setSocketForSSL(int socket, MQTTClient_SSLOptions* opts); +int SSLSocket_getch(SSL* ssl, int socket, char* c); +char *SSLSocket_getdata(SSL* ssl, int socket, int bytes, int* actual_len); + +int SSLSocket_close(SSL* ssl); +int SSLSocket_putdatas(SSL* ssl, int socket, char* buf0, int buf0len, int count, char** buffers, int* buflens); +int SSLSocket_connect(SSL* ssl, int socket); + +int SSLSocket_getPendingRead(); +int SSLSocket_continueWrite(pending_writes* pw); + +#endif diff --git a/src/Socket.c b/src/Socket.c index 9c2a5836..9caeed84 100644 --- a/src/Socket.c +++ b/src/Socket.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -7,7 +7,8 @@ * http://www.eclipse.org/legal/epl-v10.html * * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - initial implementation and documentation + * Ian Craggs - async client updates *******************************************************************************/ /** @@ -23,6 +24,9 @@ #include "SocketBuffer.h" #include "Messages.h" #include "StackTrace.h" +#if defined(OPENSSL) +#include "SSLSocket.h" +#endif #include #include @@ -37,14 +41,12 @@ int Socket_continueWrites(fd_set* pwset); #if defined(WIN32) #define iov_len len #define iov_base buf -#else -#include #endif /** * Structure to hold all socket data for the module */ -static Sockets s; +Sockets s; static fd_set wset; /** @@ -92,7 +94,7 @@ int Socket_error(char* aString, int sock) if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && errno != EWOULDBLOCK) { if (strcmp(aString, "shutdown") != 0 || (errno != ENOTCONN && errno != ECONNRESET)) - Log(TRACE_MIN, -1, "Socket error %d in %s for socket %d", errno, aString, sock); + Log(TRACE_MIN, -1, "Socket error %s in %s for socket %d", strerror(errno), aString, sock); } FUNC_EXIT_RC(errno); return errno; @@ -461,7 +463,11 @@ int Socket_putdatas(int socket, char* buf0, int buf0len, int count, char** buffe int* sockmem = (int*)malloc(sizeof(int)); Log(TRACE_MIN, -1, "Partial write: %ld bytes of %d actually written on socket %d", bytes, total, socket); +#if defined(OPENSSL) + SocketBuffer_pendingWrite(socket, NULL, count+1, iovecs, total, bytes); +#else SocketBuffer_pendingWrite(socket, count+1, iovecs, total, bytes); +#endif *sockmem = socket; ListAppend(s.write_pending, sockmem, sizeof(int)); FD_SET(socket, &(s.pending_wset)); @@ -474,6 +480,29 @@ exit: } +/** + * Add a socket to the pending write list, so that it is checked for writing in select. This is used + * in connect processing when the TCP connect is incomplete, as we need to check the socket for both + * ready to read and write states. + * @param socket the socket to add + */ +void Socket_addPendingWrite(int socket) +{ + FD_SET(socket, &(s.pending_wset)); +} + + +/** + * Clear a socket from the pending write list - if one was added with Socket_addPendingWrite + * @param socket the socket to remove + */ +void Socket_clearPendingWrite(int socket) +{ + if (FD_ISSET(socket, &(s.pending_wset))) + FD_CLR(socket, &(s.pending_wset)); +} + + /** * Close a socket without removing it from the select list. * @param socket the socket to close @@ -490,7 +519,9 @@ int Socket_close_only(int socket) if ((rc = closesocket(socket)) == SOCKET_ERROR) Socket_error("close", socket); #else - if (shutdown(socket, SHUT_RDWR) == SOCKET_ERROR) + if (shutdown(socket, SHUT_WR) == SOCKET_ERROR) + Socket_error("shutdown", socket); + if ((rc = recv(socket, NULL, (size_t)0, 0)) == SOCKET_ERROR) Socket_error("shutdown", socket); if ((rc = close(socket)) == SOCKET_ERROR) Socket_error("close", socket); @@ -613,6 +644,13 @@ int Socket_new(char* addr, int port, int* sock) rc = Socket_error("socket", *sock); else { +#if defined(NOSIGPIPE) + int opt = 1; + + if (setsockopt(*sock, SOL_SOCKET, SO_NOSIGPIPE, (void*)&opt, sizeof(opt)) != 0) + Log(TRACE_MIN, -1, "Could not set SO_NOSIGPIPE for socket %d", *sock); +#endif + Log(TRACE_MIN, -1, "New socket %d for %s, port %d", *sock, addr, port); if (Socket_addSocket(*sock) == SOCKET_ERROR) rc = Socket_error("setnonblocking", *sock); @@ -642,6 +680,13 @@ int Socket_new(char* addr, int port, int* sock) } +static Socket_writeComplete* writecomplete = NULL; + +void Socket_setWriteCompleteCallback(Socket_writeComplete* mywritecomplete) +{ + writecomplete = mywritecomplete; +} + /** * Continue an outstanding write for a particular socket * @param socket that socket @@ -658,6 +703,14 @@ int Socket_continueWrite(int socket) FUNC_ENTRY; pw = SocketBuffer_getWrite(socket); + +#if defined(OPENSSL) + if (pw->ssl) + { + rc = SSLSocket_continueWrite(pw); + goto exit; + } +#endif for (i = 0; i < pw->count; ++i) { @@ -687,11 +740,14 @@ int Socket_continueWrite(int socket) free(pw->iovecs[1].iov_base); if (pw->count == 5) free(pw->iovecs[3].iov_base); - Log(TRACE_MIN, -1, "ContinueWrite: partial write now complete for socket %d", socket); + Log(TRACE_MIN, -1, "ContinueWrite: partial write now complete for socket %d", socket); } else Log(TRACE_MIN, -1, "ContinueWrite wrote +%lu bytes on socket %d", bytes, socket); } +#if defined(OPENSSL) +exit: +#endif FUNC_EXIT_RC(rc); return rc; } @@ -722,6 +778,9 @@ int Socket_continueWrites(fd_set* pwset) ListNextElement(s.write_pending, &curpending); } curpending = s.write_pending->current; + + if (writecomplete) + (*writecomplete)(socket); } else ListNextElement(s.write_pending, &curpending); diff --git a/src/Socket.h b/src/Socket.h index 273a73f1..94ac4cf0 100644 --- a/src/Socket.h +++ b/src/Socket.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -7,7 +7,8 @@ * http://www.eclipse.org/legal/epl-v10.html * * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - initial implementation and documentation + * Ian Craggs - async client updates *******************************************************************************/ #if !defined(SOCKET_H) @@ -50,7 +51,8 @@ #define SOCKET_ERROR -1 #endif /** must be the same as SOCKETBUFFER_INTERRUPTED */ -#define TCPSOCKET_INTERRUPTED -2 +#define TCPSOCKET_INTERRUPTED -22 +#define SSL_FATAL -3 #if !defined(INET6_ADDRSTRLEN) #define INET6_ADDRSTRLEN 46 /** only needed for gcc/cygwin on windows */ @@ -111,4 +113,10 @@ int Socket_new(char* addr, int port, int* socket); int Socket_noPendingWrites(int socket); char* Socket_getpeer(int sock); +void Socket_addPendingWrite(int socket); +void Socket_clearPendingWrite(int socket); + +typedef void Socket_writeComplete(int socket); +void Socket_setWriteCompleteCallback(Socket_writeComplete*); + #endif /* SOCKET_H */ diff --git a/src/SocketBuffer.c b/src/SocketBuffer.c index 302fc4f7..2a50ecd8 100644 --- a/src/SocketBuffer.c +++ b/src/SocketBuffer.c @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ /** @@ -296,7 +297,11 @@ void SocketBuffer_queueChar(int socket, char c) * @param total total data length to be written * @param bytes actual data length that was written */ +#if defined(OPENSSL) +void SocketBuffer_pendingWrite(int socket, SSL* ssl, int count, iobuf* iovecs, int total, int bytes) +#else void SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int total, int bytes) +#endif { int i = 0; pending_writes* pw = NULL; @@ -305,6 +310,9 @@ void SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int total, /* store the buffers until the whole packet is written */ pw = malloc(sizeof(pending_writes)); pw->socket = socket; +#if defined(OPENSSL) + pw->ssl = ssl; +#endif pw->bytes = bytes; pw->total = total; pw->count = count; diff --git a/src/SocketBuffer.h b/src/SocketBuffer.h index dc41c9a3..66d687f2 100644 --- a/src/SocketBuffer.h +++ b/src/SocketBuffer.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates *******************************************************************************/ #if !defined(SOCKETBUFFER_H) @@ -19,6 +20,10 @@ #include #endif +#if defined(OPENSSL) +#include +#endif + #if defined(WIN32) typedef WSABUF iobuf; #else @@ -38,6 +43,9 @@ typedef struct typedef struct { int socket, total, count; +#if defined(OPENSSL) + SSL* ssl; +#endif unsigned long bytes; iobuf iovecs[5]; } pending_writes; @@ -46,7 +54,7 @@ typedef struct #if !defined(SOCKET_ERROR) #define SOCKET_ERROR -1 #endif -#define SOCKETBUFFER_INTERRUPTED -2 /* must be the same value as TCPSOCKET_INTERRUPTED */ +#define SOCKETBUFFER_INTERRUPTED -22 /* must be the same value as TCPSOCKET_INTERRUPTED */ void SocketBuffer_initialize(void); void SocketBuffer_terminate(void); @@ -57,7 +65,11 @@ void SocketBuffer_interrupted(int socket, int actual_len); char* SocketBuffer_complete(int socket); void SocketBuffer_queueChar(int socket, char c); +#if defined(OPENSSL) +void SocketBuffer_pendingWrite(int socket, SSL* ssl, int count, iobuf* iovecs, int total, int bytes); +#else void SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int total, int bytes); +#endif pending_writes* SocketBuffer_getWrite(int socket); int SocketBuffer_writeComplete(int socket); pending_writes* SocketBuffer_updateWrite(int socket, char* topic, char* payload); diff --git a/src/Thread.c b/src/Thread.c index 17c20349..58b3b2c7 100644 --- a/src/Thread.c +++ b/src/Thread.c @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -7,7 +7,8 @@ * http://www.eclipse.org/legal/epl-v10.html * * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - initial implementation + * Ian Craggs, Allan Stockdill-Mander - async client updates *******************************************************************************/ /** @@ -32,6 +33,10 @@ #include #include #include +#include +#include +#include +#include #endif #include #include @@ -158,6 +163,20 @@ thread_id_type Thread_getid() } +#if defined(USE_NAMED_SEMAPHORES) +#define MAX_NAMED_SEMAPHORES 10 + +static int named_semaphore_count = 0; + +static struct +{ + sem_type sem; + char name[NAME_MAX-4]; +} named_semaphores[MAX_NAMED_SEMAPHORES]; + +#endif + + /** * Create a new semaphore * @return the new condition variable @@ -175,9 +194,30 @@ sem_type Thread_create_sem() FALSE, // initial state is nonsignaled NULL // object name ); + #elif defined(USE_NAMED_SEMAPHORES) + if (named_semaphore_count == 0) + memset(named_semaphores, '\0', sizeof(named_semaphores)); + char* name = &(strrchr(tempnam("/", "MQTT"), '/'))[1]; /* skip first slash of name */ + if ((sem = sem_open(name, O_CREAT, S_IRWXU, 0)) == SEM_FAILED) + rc = -1; + else + { + int i; + + named_semaphore_count++; + for (i = 0; i < MAX_NAMED_SEMAPHORES; ++i) + { + if (named_semaphores[i].name[0] == '\0') + { + named_semaphores[i].sem = sem; + strcpy(named_semaphores[i].name, name); + break; + } + } + } #else - sem = malloc(sizeof(sem_t)); - rc = sem_init(sem, 0, 0); + sem = malloc(sizeof(sem_t)); + rc = sem_init(sem, 0, 0); #endif FUNC_EXIT_RC(rc); return sem; @@ -185,24 +225,26 @@ sem_type Thread_create_sem() /** - * Lock a mutex which has already been created, block until ready - * @param mutex the mutex + * Wait for a semaphore to be posted, or timeout. + * @param sem the semaphore + * @param timeout the maximum time to wait, in seconds * @return completion code */ -int Thread_wait_sem(sem_type sem) +int Thread_wait_sem_timeout(sem_type sem, int timeout) { /* sem_timedwait is the obvious call to use, but seemed not to work on the Viper, * so I've used trywait in a loop instead. Ian Craggs 23/7/2010 */ int rc = -1; - int timeout = 10; /* seconds */ +#if !defined(WIN32) #define USE_TRYWAIT #if defined(USE_TRYWAIT) int i = 0; int interval = 10000; int count = (1000000 / interval) * timeout; -#elif !defined(WIN32) +#else struct timespec ts; +#endif #endif FUNC_ENTRY; @@ -212,14 +254,17 @@ int Thread_wait_sem(sem_type sem) while (++i < count && (rc = sem_trywait(sem)) != 0) { if (rc == -1 && ((rc = errno) != EAGAIN)) + { + rc = 0; break; + } usleep(interval); /* microseconds - .1 of a second */ } #else if (clock_gettime(CLOCK_REALTIME, &ts) != -1) { ts.tv_sec += timeout; - rc = sem_timedwait(sem, &ts); + rc = sem_timedwait(sem, &ts); } #endif @@ -228,6 +273,22 @@ int Thread_wait_sem(sem_type sem) } +/** + * Wait for a semaphore to be posted, or timeout after 10 seconds. + * @param sem the semaphore + * @return completion code + */ +int Thread_wait_sem(sem_type sem) +{ + return Thread_wait_sem_timeout(sem, 10); +} + + +/** + * Check to see if a semaphore has been posted, without waiting. + * @param sem the semaphore + * @return 0 (false) or 1 (true) + */ int Thread_check_sem(sem_type sem) { #if defined(WIN32) @@ -241,8 +302,8 @@ int Thread_check_sem(sem_type sem) /** - * Lock a mutex which has already been created, block until ready - * @param mutex the mutex + * Post a semaphore + * @param sem the semaphore * @return completion code */ int Thread_post_sem(sem_type sem) @@ -274,6 +335,19 @@ int Thread_destroy_sem(sem_type sem) FUNC_ENTRY; #if defined(WIN32) rc = CloseHandle(sem); + #elif defined(USE_NAMED_SEMAPHORES) + int i; + rc = sem_close(sem); + for (i = 0; i < MAX_NAMED_SEMAPHORES; ++i) + { + if (named_semaphores[i].sem == sem) + { + rc = sem_unlink(named_semaphores[i].name); + named_semaphores[i].name[0] = '\0'; + break; + } + } + named_semaphore_count--; #else rc = sem_destroy(sem); free(sem); @@ -283,6 +357,80 @@ int Thread_destroy_sem(sem_type sem) } +#if !defined(WIN32) +/** + * Create a new condition variable + * @return the condition variable struct + */ +cond_type Thread_create_cond() +{ + cond_type condvar = NULL; + int rc = 0; + + FUNC_ENTRY; + condvar = malloc(sizeof(cond_type_struct)); + rc = pthread_cond_init(&condvar->cond, NULL); + rc = pthread_mutex_init(&condvar->mutex, NULL); + + FUNC_EXIT_RC(rc); + return condvar; +} + +/** + * Signal a condition variable + * @return completion code + */ +int Thread_signal_cond(cond_type condvar) +{ + int rc = 0; + + pthread_mutex_lock(&condvar->mutex); + rc = pthread_cond_signal(&condvar->cond); + pthread_mutex_unlock(&condvar->mutex); + + return rc; +} + +/** + * Wait with a timeout (seconds) for condition variable + * @return completion code + */ +int Thread_wait_cond_timeout(cond_type condvar, int timeout) +{ + FUNC_ENTRY; + int rc = 0; + struct timespec cond_timeout; + struct timeval cur_time; + + gettimeofday(&cur_time, NULL); + + cond_timeout.tv_sec = cur_time.tv_sec + timeout; + cond_timeout.tv_nsec = cur_time.tv_usec * 1000; + + pthread_mutex_lock(&condvar->mutex); + rc = pthread_cond_timedwait(&condvar->cond, &condvar->mutex, &cond_timeout); + pthread_mutex_unlock(&condvar->mutex); + + FUNC_EXIT_RC(rc); + return rc; +} + +/** + * Destroy a condition variable + * @return completion code + */ +int Thread_destroy_cond(cond_type condvar) +{ + int rc = 0; + + rc = pthread_mutex_destroy(&condvar->mutex); + rc = pthread_cond_destroy(&condvar->cond); + free(condvar); + + return rc; +} +#endif + #if defined(THREAD_UNIT_TESTS) diff --git a/src/Thread.h b/src/Thread.h index 8bdfb820..ed6e651c 100644 --- a/src/Thread.h +++ b/src/Thread.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2012 IBM Corp. + * Copyright (c) 2009, 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -7,7 +7,8 @@ * http://www.eclipse.org/legal/epl-v10.html * * Contributors: - * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - initial implementation + * Ian Craggs, Allan Stockdill-Mander - async client updates *******************************************************************************/ #if !defined(THREAD_H) @@ -33,6 +34,11 @@ typedef struct { pthread_cond_t cond; pthread_mutex_t mutex; } cond_type_struct; typedef cond_type_struct *cond_type; typedef sem_t *sem_type; + + cond_type Thread_create_cond(); + int Thread_signal_cond(cond_type); + int Thread_wait_cond_timeout(cond_type condvar, int timeout); + int Thread_destroy_cond(cond_type); #endif thread_type Thread_start(thread_fn, void*); @@ -46,6 +52,7 @@ thread_id_type Thread_getid(); sem_type Thread_create_sem(); int Thread_wait_sem(sem_type sem); +int Thread_wait_sem_timeout(sem_type sem, int timeout); int Thread_check_sem(sem_type sem); int Thread_post_sem(sem_type sem); int Thread_destroy_sem(sem_type sem); diff --git a/src/Tree.c b/src/Tree.c new file mode 100644 index 00000000..d060ccc3 --- /dev/null +++ b/src/Tree.c @@ -0,0 +1,703 @@ +/******************************************************************************* + * Copyright (c) 2009, 2012 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Ian Craggs - initial implementation and documentation + *******************************************************************************/ + +/** @file + * \brief functions which apply to tree structures. + * + * These trees can hold data of any sort, pointed to by the content pointer of the + * Node structure. + * */ + +#define NO_HEAP_TRACKING 1 + +#include "Tree.h" + +#include +#include +#include +#include + +#include "Heap.h" + + +void TreeInitializeNoMalloc(Tree* aTree, int(*compare)(void*, void*, int)) +{ + memset(aTree, '\0', sizeof(Tree)); + aTree->heap_tracking = 1; + aTree->index[0].compare = compare; + aTree->indexes = 1; +} + +/** + * Allocates and initializes a new tree structure. + * @return a pointer to the new tree structure + */ +Tree* TreeInitialize(int(*compare)(void*, void*, int)) +{ +#if defined(UNIT_TESTS) + Tree* newt = malloc(sizeof(Tree)); +#else + Tree* newt = mymalloc(__FILE__, __LINE__, sizeof(Tree)); +#endif + TreeInitializeNoMalloc(newt, compare); + return newt; +} + + +void TreeAddIndex(Tree* aTree, int(*compare)(void*, void*, int)) +{ + aTree->index[aTree->indexes].compare = compare; + ++(aTree->indexes); +} + + +void TreeFree(Tree* aTree) +{ +#if defined(UNIT_TESTS) + free(aTree); +#else + (aTree->heap_tracking) ? myfree(__FILE__, __LINE__, aTree) : free(aTree); +#endif +} + + +#define LEFT 0 +#define RIGHT 1 +#if !defined(max) +#define max(a, b) (a > b) ? a : b; +#endif + + + +int isRed(Node* aNode) +{ + return (aNode != NULL) && (aNode->red); +} + + +int isBlack(Node* aNode) +{ + return (aNode == NULL) || (aNode->red == 0); +} + + +int TreeWalk(Node* curnode, int depth) +{ + if (curnode) + { + int left = TreeWalk(curnode->child[LEFT], depth+1); + int right = TreeWalk(curnode->child[RIGHT], depth+1); + depth = max(left, right); + if (curnode->red) + { + /*if (isRed(curnode->child[LEFT]) || isRed(curnode->child[RIGHT])) + { + printf("red/black tree violation %p\n", curnode->content); + exit(-99); + }*/; + } + } + return depth; +} + + +int TreeMaxDepth(Tree *aTree) +{ + int rc = TreeWalk(aTree->index[0].root, 0); + /*if (aTree->root->red) + { + printf("root node should not be red %p\n", aTree->root->content); + exit(-99); + }*/ + return rc; +} + + +void TreeRotate(Tree* aTree, Node* curnode, int direction, int index) +{ + Node* other = curnode->child[!direction]; + + curnode->child[!direction] = other->child[direction]; + if (other->child[direction] != NULL) + other->child[direction]->parent = curnode; + other->parent = curnode->parent; + if (curnode->parent == NULL) + aTree->index[index].root = other; + else if (curnode == curnode->parent->child[direction]) + curnode->parent->child[direction] = other; + else + curnode->parent->child[!direction] = other; + other->child[direction] = curnode; + curnode->parent = other; +} + + +Node* TreeBAASub(Tree* aTree, Node* curnode, int which, int index) +{ + Node* uncle = curnode->parent->parent->child[which]; + + if (isRed(uncle)) + { + curnode->parent->red = uncle->red = 0; + curnode = curnode->parent->parent; + curnode->red = 1; + } + else + { + if (curnode == curnode->parent->child[which]) + { + curnode = curnode->parent; + TreeRotate(aTree, curnode, !which, index); + } + curnode->parent->red = 0; + curnode->parent->parent->red = 1; + TreeRotate(aTree, curnode->parent->parent, which, index); + } + return curnode; +} + + +void TreeBalanceAfterAdd(Tree* aTree, Node* curnode, int index) +{ + while (curnode && isRed(curnode->parent) && curnode->parent->parent) + { + if (curnode->parent == curnode->parent->parent->child[LEFT]) + curnode = TreeBAASub(aTree, curnode, RIGHT, index); + else + curnode = TreeBAASub(aTree, curnode, LEFT, index); + } + aTree->index[index].root->red = 0; +} + + +/** + * Add an item to a tree + * @param aTree the list to which the item is to be added + * @param content the list item content itself + * @param size the size of the element + */ +void* TreeAddByIndex(Tree* aTree, void* content, int size, int index) +{ + Node* curparent = NULL; + Node* curnode = aTree->index[index].root; + Node* newel = NULL; + int left = 0; + int result = 1; + void* rc = NULL; + + while (curnode) + { + result = aTree->index[index].compare(curnode->content, content, 1); + left = (result > 0); + if (result == 0) + break; + else + { + curparent = curnode; + curnode = curnode->child[left]; + } + } + + if (result == 0) + { + if (aTree->allow_duplicates) + exit(-99); + { + newel = curnode; + rc = newel->content; + if (index == 0) + aTree->size += (size - curnode->size); + } + } + else + { + #if defined(UNIT_TESTS) + newel = malloc(sizeof(Node)); + #else + newel = (aTree->heap_tracking) ? mymalloc(__FILE__, __LINE__, sizeof(Node)) : malloc(sizeof(Node)); + #endif + memset(newel, '\0', sizeof(Node)); + if (curparent) + curparent->child[left] = newel; + else + aTree->index[index].root = newel; + newel->parent = curparent; + newel->red = 1; + if (index == 0) + { + ++(aTree->count); + aTree->size += size; + } + } + newel->content = content; + newel->size = size; + TreeBalanceAfterAdd(aTree, newel, index); + return rc; +} + + +void* TreeAdd(Tree* aTree, void* content, int size) +{ + void* rc = NULL; + int i; + + for (i = 0; i < aTree->indexes; ++i) + rc = TreeAddByIndex(aTree, content, size, i); + + return rc; +} + + +Node* TreeFindIndex1(Tree* aTree, void* key, int index, int value) +{ + int result = 0; + Node* curnode = aTree->index[index].root; + + while (curnode) + { + result = aTree->index[index].compare(curnode->content, key, value); + if (result == 0) + break; + else + curnode = curnode->child[result > 0]; + } + return curnode; +} + + +Node* TreeFindIndex(Tree* aTree, void* key, int index) +{ + return TreeFindIndex1(aTree, key, index, 0); +} + + +Node* TreeFindContentIndex(Tree* aTree, void* key, int index) +{ + return TreeFindIndex1(aTree, key, index, 1); +} + + +Node* TreeFind(Tree* aTree, void* key) +{ + return TreeFindIndex(aTree, key, 0); +} + + +Node* TreeMinimum(Node* curnode) +{ + if (curnode) + while (curnode->child[LEFT]) + curnode = curnode->child[LEFT]; + return curnode; +} + + +Node* TreeSuccessor(Node* curnode) +{ + if (curnode->child[RIGHT]) + curnode = TreeMinimum(curnode->child[RIGHT]); + else + { + Node* curparent = curnode->parent; + while (curparent && curnode == curparent->child[RIGHT]) + { + curnode = curparent; + curparent = curparent->parent; + } + curnode = curparent; + } + return curnode; +} + + +Node* TreeNextElementIndex(Tree* aTree, Node* curnode, int index) +{ + if (curnode == NULL) + curnode = TreeMinimum(aTree->index[index].root); + else + curnode = TreeSuccessor(curnode); + return curnode; +} + + +Node* TreeNextElement(Tree* aTree, Node* curnode) +{ + return TreeNextElementIndex(aTree, curnode, 0); +} + + +Node* TreeBARSub(Tree* aTree, Node* curnode, int which, int index) +{ + Node* sibling = curnode->parent->child[which]; + + if (isRed(sibling)) + { + sibling->red = 0; + curnode->parent->red = 1; + TreeRotate(aTree, curnode->parent, !which, index); + sibling = curnode->parent->child[which]; + } + if (!sibling) + curnode = curnode->parent; + else if (isBlack(sibling->child[!which]) && isBlack(sibling->child[which])) + { + sibling->red = 1; + curnode = curnode->parent; + } + else + { + if (isBlack(sibling->child[which])) + { + sibling->child[!which]->red = 0; + sibling->red = 1; + TreeRotate(aTree, sibling, which, index); + sibling = curnode->parent->child[which]; + } + sibling->red = curnode->parent->red; + curnode->parent->red = 0; + sibling->child[which]->red = 0; + TreeRotate(aTree, curnode->parent, !which, index); + curnode = aTree->index[index].root; + } + return curnode; +} + + +void TreeBalanceAfterRemove(Tree* aTree, Node* curnode, int index) +{ + while (curnode != aTree->index[index].root && isBlack(curnode)) + { + /* curnode->content == NULL must equal curnode == NULL */ + if (((curnode->content) ? curnode : NULL) == curnode->parent->child[LEFT]) + curnode = TreeBARSub(aTree, curnode, RIGHT, index); + else + curnode = TreeBARSub(aTree, curnode, LEFT, index); + } + curnode->red = 0; +} + + +/** + * Remove an item from a tree + * @param aTree the list to which the item is to be added + * @param curnode the list item content itself + */ +void* TreeRemoveNodeIndex(Tree* aTree, Node* curnode, int index) +{ + Node* redundant = curnode; + Node* curchild = NULL; + int size = curnode->size; + void* content = curnode->content; + + /* if the node to remove has 0 or 1 children, it can be removed without involving another node */ + if (curnode->child[LEFT] && curnode->child[RIGHT]) /* 2 children */ + redundant = TreeSuccessor(curnode); /* now redundant must have at most one child */ + + curchild = redundant->child[(redundant->child[LEFT] != NULL) ? LEFT : RIGHT]; + if (curchild) /* we could have no children at all */ + curchild->parent = redundant->parent; + + if (redundant->parent == NULL) + aTree->index[index].root = curchild; + else + { + if (redundant == redundant->parent->child[LEFT]) + redundant->parent->child[LEFT] = curchild; + else + redundant->parent->child[RIGHT] = curchild; + } + + if (redundant != curnode) + { + curnode->content = redundant->content; + curnode->size = redundant->size; + } + + if (isBlack(redundant)) + { + if (curchild == NULL) + { + if (redundant->parent) + { + Node temp; + memset(&temp, '\0', sizeof(Node)); + temp.parent = (redundant) ? redundant->parent : NULL; + temp.red = 0; + TreeBalanceAfterRemove(aTree, &temp, index); + } + } + else + TreeBalanceAfterRemove(aTree, curchild, index); + } + +#if defined(UNIT_TESTS) + free(redundant); +#else + (aTree->heap_tracking) ? myfree(__FILE__, __LINE__, redundant) : free(redundant); +#endif + if (index == 0) + { + aTree->size -= size; + --(aTree->count); + } + return content; +} + + +/** + * Remove an item from a tree + * @param aTree the list to which the item is to be added + * @param curnode the list item content itself + */ +void* TreeRemoveIndex(Tree* aTree, void* content, int index) +{ + Node* curnode = TreeFindContentIndex(aTree, content, index); + + if (curnode == NULL) + return NULL; + + return TreeRemoveNodeIndex(aTree, curnode, index); +} + + +void* TreeRemove(Tree* aTree, void* content) +{ + int i; + void* rc = NULL; + + for (i = 0; i < aTree->indexes; ++i) + rc = TreeRemoveIndex(aTree, content, i); + + return rc; +} + + +void* TreeRemoveKeyIndex(Tree* aTree, void* key, int index) +{ + Node* curnode = TreeFindIndex(aTree, key, index); + void* content = NULL; + int i; + + if (curnode == NULL) + return NULL; + + content = TreeRemoveNodeIndex(aTree, curnode, index); + for (i = 0; i < aTree->indexes; ++i) + { + if (i != index) + content = TreeRemoveIndex(aTree, content, i); + } + return content; +} + + +void* TreeRemoveKey(Tree* aTree, void* key) +{ + return TreeRemoveKeyIndex(aTree, key, 0); +} + + +int TreeIntCompare(void* a, void* b, int content) +{ + int i = *((int*)a); + int j = *((int*)b); + + //printf("comparing %d %d\n", *((int*)a), *((int*)b)); + return (i > j) ? -1 : (i == j) ? 0 : 1; +} + + +int TreePtrCompare(void* a, void* b, int content) +{ + return (a > b) ? -1 : (a == b) ? 0 : 1; +} + + +int TreeStringCompare(void* a, void* b, int content) +{ + return strcmp((char*)a, (char*)b); +} + + +#if defined(UNIT_TESTS) + +int check(Tree *t) +{ + Node* curnode = NULL; + int rc = 0; + + curnode = TreeNextElement(t, curnode); + while (curnode) + { + Node* prevnode = curnode; + + curnode = TreeNextElement(t, curnode); + + if (prevnode && curnode && (*(int*)(curnode->content) < *(int*)(prevnode->content))) + { + printf("out of order %d < %d\n", *(int*)(curnode->content), *(int*)(prevnode->content)); + rc = 99; + } + } + return rc; +} + + +int traverse(Tree *t, int lookfor) +{ + Node* curnode = NULL; + int rc = 0; + + printf("Traversing\n"); + curnode = TreeNextElement(t, curnode); + //printf("content int %d\n", *(int*)(curnode->content)); + while (curnode) + { + Node* prevnode = curnode; + + curnode = TreeNextElement(t, curnode); + //if (curnode) + // printf("content int %d\n", *(int*)(curnode->content)); + if (prevnode && curnode && (*(int*)(curnode->content) < *(int*)(prevnode->content))) + { + printf("out of order %d < %d\n", *(int*)(curnode->content), *(int*)(prevnode->content)); + } + if (curnode && (lookfor == *(int*)(curnode->content))) + printf("missing item %d actually found\n", lookfor); + } + printf("End traverse %d\n", rc); + return rc; +} + + +int test(int limit) +{ + int i, *ip, *todelete; + Node* current = NULL; + Tree* t = TreeInitialize(TreeIntCompare); + int rc = 0; + + printf("Tree initialized\n"); + + srand(time(NULL)); + + ip = malloc(sizeof(int)); + *ip = 2; + TreeAdd(t, (void*)ip, sizeof(int)); + + check(t); + + i = 2; + void* result = TreeRemove(t, (void*)&i); + if (result) + free(result); + + int actual[limit]; + for (i = 0; i < limit; i++) + { + void* replaced = NULL; + + ip = malloc(sizeof(int)); + *ip = rand(); + replaced = TreeAdd(t, (void*)ip, sizeof(int)); + if (replaced) /* duplicate */ + { + free(replaced); + actual[i] = -1; + } + else + actual[i] = *ip; + if (i==5) + todelete = ip; + printf("Tree element added %d\n", *ip); + if (1 % 1000 == 0) + { + rc = check(t); + printf("%d elements, check result %d\n", i+1, rc); + if (rc != 0) + return 88; + } + } + + check(t); + + for (i = 0; i < limit; i++) + { + int parm = actual[i]; + + if (parm == -1) + continue; + + Node* found = TreeFind(t, (void*)&parm); + if (found) + printf("Tree find %d %d\n", parm, *(int*)(found->content)); + else + { + printf("%d not found\n", parm); + traverse(t, parm); + return -2; + } + } + + check(t); + + for (i = limit -1; i >= 0; i--) + { + int parm = actual[i]; + void *found; + + if (parm == -1) /* skip duplicate */ + continue; + + found = TreeRemove(t, (void*)&parm); + if (found) + { + printf("%d Tree remove %d %d\n", i, parm, *(int*)(found)); + free(found); + } + else + { + int count = 0; + printf("%d %d not found\n", i, parm); + traverse(t, parm); + for (i = 0; i < limit; i++) + if (actual[i] == parm) + ++count; + printf("%d occurs %d times\n", parm, count); + return -2; + } + if (i % 1000 == 0) + { + rc = check(t); + printf("%d elements, check result %d\n", i+1, rc); + if (rc != 0) + return 88; + } + } + printf("finished\n"); + return 0; +} + +int main(int argc, char *argv[]) +{ + int rc = 0; + + while (rc == 0) + rc = test(999999); +} + +#endif + + + + + diff --git a/src/Tree.h b/src/Tree.h new file mode 100644 index 00000000..cb08e9bf --- /dev/null +++ b/src/Tree.h @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2009, 2012 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Ian Craggs - initial implementation and documentation + *******************************************************************************/ + + +#if !defined(TREE_H) +#define TREE_H + +/*BE +defm defTree(T) // macro to define a tree + +def T concat Node +{ + n32 ptr T concat Node "parent" + n32 ptr T concat Node "left" + n32 ptr T concat Node "right" + n32 ptr T id2str(T) + n32 suppress "size" +} + + +def T concat Tree +{ + struct + { + n32 ptr T concat Node suppress "root" + n32 ptr DATA suppress "compare" + } + struct + { + n32 ptr T concat Node suppress "root" + n32 ptr DATA suppress "compare" + } + n32 dec "count" + n32 dec suppress "size" +} + +endm + +defTree(INT) +defTree(STRING) +defTree(TMP) + +BE*/ + +/** + * Structure to hold all data for one list element + */ +typedef struct NodeStruct +{ + struct NodeStruct *parent, /**< pointer to parent tree node, in case we need it */ + *child[2]; /**< pointers to child tree nodes 0 = left, 1 = right */ + void* content; /**< pointer to element content */ + int size; /**< size of content */ + unsigned int red : 1; +} Node; + + +/** + * Structure to hold all data for one tree + */ +typedef struct +{ + struct + { + Node *root; /**< root node pointer */ + int (*compare)(void*, void*, int); /**< comparison function */ + } index[2]; + int indexes, /**< no of indexes into tree */ + count, /**< no of items */ + size; /**< heap storage used */ + unsigned int heap_tracking : 1; /**< switch on heap tracking for this tree? */ + unsigned int allow_duplicates : 1; /**< switch to allow duplicate entries */ +} Tree; + + +Tree* TreeInitialize(int(*compare)(void*, void*, int)); +void TreeInitializeNoMalloc(Tree* aTree, int(*compare)(void*, void*, int)); +void TreeAddIndex(Tree* aTree, int(*compare)(void*, void*, int)); + +void* TreeAdd(Tree* aTree, void* content, int size); + +void* TreeRemove(Tree* aTree, void* content); + +void* TreeRemoveKey(Tree* aTree, void* key); +void* TreeRemoveKeyIndex(Tree* aTree, void* key, int index); + +void* TreeRemoveNodeIndex(Tree* aTree, Node* aNode, int index); + +void TreeFree(Tree* aTree); + +Node* TreeFind(Tree* aTree, void* key); +Node* TreeFindIndex(Tree* aTree, void* key, int index); + +Node* TreeNextElement(Tree* aTree, Node* curnode); + +int TreeIntCompare(void* a, void* b, int); +int TreePtrCompare(void* a, void* b, int); +int TreeStringCompare(void* a, void* b, int); + +#endif