add paho.mqtt.c for mqtt client
This commit is contained in:
parent
aa9b3e3188
commit
f8ffd8cbfc
|
|
@ -7,3 +7,4 @@ ADD_SUBDIRECTORY(regex)
|
|||
ADD_SUBDIRECTORY(iconv)
|
||||
ADD_SUBDIRECTORY(lz4)
|
||||
ADD_SUBDIRECTORY(cJson)
|
||||
ADD_SUBDIRECTORY(paho.mqtt.c)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
|
||||
PROJECT(TDengine)
|
||||
INCLUDE_DIRECTORIES(inc)
|
||||
AUX_SOURCE_DIRECTORY(src SRC)
|
||||
ADD_LIBRARY(pahomqtt ${SRC})
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Wind River Systems, Inc. All Rights Reserved.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Keith Holman - initial implementation and documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(BASE64_H)
|
||||
#define BASE64_H
|
||||
|
||||
/** type for size of a buffer, it saves passing around @p size_t (unsigned long long or unsigned long int) */
|
||||
typedef unsigned int b64_size_t;
|
||||
/** type for raw base64 data */
|
||||
typedef unsigned char b64_data_t;
|
||||
|
||||
/**
|
||||
* Decodes base64 data
|
||||
*
|
||||
* @param[out] out decoded data
|
||||
* @param[in] out_len length of output buffer
|
||||
* @param[in] in base64 string to decode
|
||||
* @param[in] in_len length of input buffer
|
||||
*
|
||||
* @return the amount of data decoded
|
||||
*
|
||||
* @see Base64_decodeLength
|
||||
* @see Base64_encode
|
||||
*/
|
||||
b64_size_t Base64_decode( b64_data_t *out, b64_size_t out_len,
|
||||
const char *in, b64_size_t in_len );
|
||||
|
||||
/**
|
||||
* Size of buffer required to decode base64 data
|
||||
*
|
||||
* @param[in] in base64 string to decode
|
||||
* @param[in] in_len length of input buffer
|
||||
*
|
||||
* @return the size of buffer the decoded string would require
|
||||
*
|
||||
* @see Base64_decode
|
||||
* @see Base64_encodeLength
|
||||
*/
|
||||
b64_size_t Base64_decodeLength( const char *in, b64_size_t in_len );
|
||||
|
||||
/**
|
||||
* Encodes base64 data
|
||||
*
|
||||
* @param[out] out encode base64 string
|
||||
* @param[in] out_len length of output buffer
|
||||
* @param[in] in raw data to encode
|
||||
* @param[in] in_len length of input buffer
|
||||
*
|
||||
* @return the amount of data encoded
|
||||
*
|
||||
* @see Base64_decode
|
||||
* @see Base64_encodeLength
|
||||
*/
|
||||
b64_size_t Base64_encode( char *out, b64_size_t out_len,
|
||||
const b64_data_t *in, b64_size_t in_len );
|
||||
|
||||
/**
|
||||
* Size of buffer required to encode base64 data
|
||||
*
|
||||
* @param[in] in raw data to encode
|
||||
* @param[in] in_len length of input buffer
|
||||
*
|
||||
* @return the size of buffer the encoded string would require
|
||||
*
|
||||
* @see Base64_decodeLength
|
||||
* @see Base64_encode
|
||||
*/
|
||||
b64_size_t Base64_encodeLength( const b64_data_t *in, b64_size_t in_len );
|
||||
|
||||
#endif /* BASE64_H */
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - add SSL support
|
||||
* Ian Craggs - fix for bug 413429 - connectionLost not called
|
||||
* Ian Craggs - change will payload to binary
|
||||
* Ian Craggs - password to binary
|
||||
* Ian Craggs - MQTT 5 support
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(CLIENTS_H)
|
||||
#define CLIENTS_H
|
||||
|
||||
#include "MQTTTime.h"
|
||||
#if defined(OPENSSL)
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
#include <openssl/ssl.h>
|
||||
#endif
|
||||
#include "MQTTClient.h"
|
||||
#include "LinkedList.h"
|
||||
#include "MQTTClientPersistence.h"
|
||||
|
||||
/**
|
||||
* Stored publication data to minimize copying
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
char *topic;
|
||||
int topiclen;
|
||||
char* payload;
|
||||
int payloadlen;
|
||||
int refcount;
|
||||
} Publications;
|
||||
|
||||
/**
|
||||
* Client publication message data
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int qos;
|
||||
int retain;
|
||||
int msgid;
|
||||
int MQTTVersion;
|
||||
MQTTProperties properties;
|
||||
Publications *publish;
|
||||
START_TIME_TYPE lastTouch; /**> used for retry and expiry */
|
||||
char nextMessageType; /**> PUBREC, PUBREL, PUBCOMP */
|
||||
int len; /**> length of the whole structure+data */
|
||||
} Messages;
|
||||
|
||||
/**
|
||||
* Client will message data
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
char *topic;
|
||||
int payloadlen;
|
||||
void *payload;
|
||||
int retained;
|
||||
int qos;
|
||||
} willMessages;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int socket;
|
||||
START_TIME_TYPE lastSent;
|
||||
START_TIME_TYPE lastReceived;
|
||||
START_TIME_TYPE lastPing;
|
||||
#if defined(OPENSSL)
|
||||
SSL* ssl;
|
||||
SSL_CTX* ctx;
|
||||
char *https_proxy;
|
||||
char *https_proxy_auth;
|
||||
#endif
|
||||
char *http_proxy;
|
||||
char *http_proxy_auth;
|
||||
int websocket; /**< socket has been upgraded to use web sockets */
|
||||
char *websocket_key;
|
||||
const MQTTClient_nameValue* httpHeaders;
|
||||
} networkHandles;
|
||||
|
||||
|
||||
/* connection states */
|
||||
/** no connection in progress, see connected value */
|
||||
#define NOT_IN_PROGRESS 0x0
|
||||
/** TCP connection in progress */
|
||||
#define TCP_IN_PROGRESS 0x1
|
||||
/** SSL connection in progress */
|
||||
#define SSL_IN_PROGRESS 0x2
|
||||
/** Websocket connection in progress */
|
||||
#define WEBSOCKET_IN_PROGRESS 0x3
|
||||
/** TCP completed, waiting for MQTT ACK */
|
||||
#define WAIT_FOR_CONNACK 0x4
|
||||
/** Proxy connection in progress */
|
||||
#define PROXY_CONNECT_IN_PROGRESS 0x5
|
||||
/** Disconnecting */
|
||||
#define DISCONNECTING -2
|
||||
|
||||
/**
|
||||
* Data related to one client
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
char* clientID; /**< the string id of the client */
|
||||
const char* username; /**< MQTT v3.1 user name */
|
||||
int passwordlen; /**< MQTT password length */
|
||||
const void* password; /**< MQTT v3.1 binary password */
|
||||
unsigned int cleansession : 1; /**< MQTT V3 clean session flag */
|
||||
unsigned int cleanstart : 1; /**< MQTT V5 clean start flag */
|
||||
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;
|
||||
signed int connect_state : 4;
|
||||
networkHandles net; /**< network info for this client */
|
||||
int msgID; /**< the MQTT message id */
|
||||
int keepAliveInterval; /**< the MQTT keep alive interval */
|
||||
int retryInterval;
|
||||
int maxInflightMessages; /**< the max number of inflight outbound messages we allow */
|
||||
willMessages* will; /**< the MQTT will message, if any */
|
||||
List* inboundMsgs; /**< inbound in flight messages */
|
||||
List* outboundMsgs; /**< outbound in flight messages */
|
||||
List* messageQueue; /**< inbound complete but undelivered messages */
|
||||
unsigned int qentry_seqno;
|
||||
void* phandle; /**< the persistence handle */
|
||||
MQTTClient_persistence* persistence; /**< a persistence implementation */
|
||||
void* context; /* calling context - used when calling disconnect_internal */
|
||||
int MQTTVersion; /**< the version of MQTT being used, 3, 4 or 5 */
|
||||
int sessionExpiry; /**< MQTT 5 session expiry */
|
||||
#if defined(OPENSSL)
|
||||
MQTTClient_SSLOptions *sslopts; /**< the SSL/TLS connect options */
|
||||
SSL_SESSION* session; /**< SSL session pointer for fast handhake */
|
||||
#endif
|
||||
} Clients;
|
||||
|
||||
int clientIDCompare(void* a, void* b);
|
||||
int clientSocketCompare(void* a, void* b);
|
||||
|
||||
/**
|
||||
* Configuration data related to all clients
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
const char* version;
|
||||
List* clients;
|
||||
} ClientStates;
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp. and others
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - use tree data structure instead of list
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#if !defined(HEAP_H)
|
||||
#define HEAP_H
|
||||
|
||||
#if defined(HIGH_PERFORMANCE)
|
||||
#define NO_HEAP_TRACKING 1
|
||||
#endif
|
||||
|
||||
#define PAHO_MEMORY_ERROR -99
|
||||
|
||||
#include "MQTTExportDeclarations.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if !defined(NO_HEAP_TRACKING)
|
||||
|
||||
#if !defined(TREE_C)
|
||||
/**
|
||||
* redefines malloc to use "mymalloc" so that heap allocation can be tracked
|
||||
* @param x the size of the item to be allocated
|
||||
* @return the pointer to the item allocated, or NULL
|
||||
*/
|
||||
#define malloc(x) mymalloc(__FILE__, __LINE__, x)
|
||||
|
||||
/**
|
||||
* redefines realloc to use "myrealloc" so that heap allocation can be tracked
|
||||
* @param a the heap item to be reallocated
|
||||
* @param b the new size of the item
|
||||
* @return the new pointer to the heap item
|
||||
*/
|
||||
#define realloc(a, b) myrealloc(__FILE__, __LINE__, a, b)
|
||||
|
||||
/**
|
||||
* redefines free to use "myfree" so that heap allocation can be tracked
|
||||
* @param x the size of the item to be freed
|
||||
*/
|
||||
#define free(x) myfree(__FILE__, __LINE__, x)
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Information about the state of the heap.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
size_t current_size; /**< current size of the heap in bytes */
|
||||
size_t max_size; /**< max size the heap has reached in bytes */
|
||||
} heap_info;
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void* mymalloc(char*, int, size_t size);
|
||||
void* myrealloc(char*, int, void* p, size_t size);
|
||||
void myfree(char*, int, void* p);
|
||||
|
||||
void Heap_scan(FILE* file);
|
||||
int Heap_initialize(void);
|
||||
void Heap_terminate(void);
|
||||
LIBMQTT_API 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);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - updates for the async client
|
||||
* Ian Craggs - change size types from int to size_t
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(LINKEDLIST_H)
|
||||
#define LINKEDLIST_H
|
||||
|
||||
#include <stdlib.h> /* for size_t definition */
|
||||
|
||||
/*BE
|
||||
defm defList(T)
|
||||
|
||||
def T concat Item
|
||||
{
|
||||
at 4
|
||||
n32 ptr T concat Item suppress "next"
|
||||
at 0
|
||||
n32 ptr T concat Item suppress "prev"
|
||||
at 8
|
||||
n32 ptr T id2str(T)
|
||||
}
|
||||
|
||||
def T concat List
|
||||
{
|
||||
n32 ptr T concat Item suppress "first"
|
||||
n32 ptr T concat Item suppress "last"
|
||||
n32 ptr T concat Item suppress "current"
|
||||
n32 dec "count"
|
||||
n32 suppress "size"
|
||||
}
|
||||
endm
|
||||
|
||||
defList(INT)
|
||||
defList(STRING)
|
||||
defList(TMP)
|
||||
|
||||
BE*/
|
||||
|
||||
/**
|
||||
* Structure to hold all data for one list element
|
||||
*/
|
||||
typedef struct ListElementStruct
|
||||
{
|
||||
struct ListElementStruct *prev, /**< pointer to previous list element */
|
||||
*next; /**< pointer to next list element */
|
||||
void* content; /**< pointer to element content */
|
||||
} ListElement;
|
||||
|
||||
|
||||
/**
|
||||
* Structure to hold all data for one list
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
ListElement *first, /**< first element in the list */
|
||||
*last, /**< last element in the list */
|
||||
*current; /**< current element in the list, for iteration */
|
||||
int count; /**< no of items */
|
||||
size_t size; /**< heap storage used */
|
||||
} List;
|
||||
|
||||
void ListZero(List*);
|
||||
List* ListInitialize(void);
|
||||
|
||||
ListElement* ListAppend(List* aList, void* content, size_t size);
|
||||
void ListAppendNoMalloc(List* aList, void* content, ListElement* newel, size_t size);
|
||||
ListElement* ListInsert(List* aList, void* content, size_t 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);
|
||||
|
||||
int ListDetach(List* aList, void* content);
|
||||
int ListDetachItem(List* aList, void* content, int(*callback)(void*, void*));
|
||||
|
||||
void ListFree(List* aList);
|
||||
void ListEmpty(List* aList);
|
||||
void ListFreeNoContent(List* aList);
|
||||
|
||||
ListElement* ListNextElement(List* aList, ListElement** pos);
|
||||
ListElement* ListPrevElement(List* aList, ListElement** pos);
|
||||
|
||||
ListElement* ListFind(List* aList, void* content);
|
||||
ListElement* ListFindItem(List* aList, void* content, int(*callback)(void*, void*));
|
||||
|
||||
int intcompare(void* a, void* b);
|
||||
int stringcompare(void* a, void* b);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
/*******************************************************************************
|
||||
* 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 v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - updates for the async client
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(LOG_H)
|
||||
#define LOG_H
|
||||
|
||||
/*BE
|
||||
map LOG_LEVELS
|
||||
{
|
||||
"TRACE_MAXIMUM" 1
|
||||
"TRACE_MEDIUM" 2
|
||||
"TRACE_MINIMUM" 3
|
||||
"TRACE_PROTOCOL" 4
|
||||
|
||||
"ERROR" 5
|
||||
"SEVERE" 6
|
||||
"FATAL" 7
|
||||
}
|
||||
BE*/
|
||||
|
||||
enum LOG_LEVELS {
|
||||
INVALID_LEVEL = -1,
|
||||
TRACE_MAXIMUM = 1,
|
||||
TRACE_MEDIUM,
|
||||
TRACE_MINIMUM,
|
||||
TRACE_PROTOCOL,
|
||||
LOG_ERROR,
|
||||
LOG_SEVERE,
|
||||
LOG_FATAL,
|
||||
};
|
||||
|
||||
|
||||
/*BE
|
||||
def trace_settings_type
|
||||
{
|
||||
n32 map LOG_LEVELS "trace_level"
|
||||
n32 dec "max_trace_entries"
|
||||
n32 dec "trace_output_level"
|
||||
}
|
||||
BE*/
|
||||
typedef struct
|
||||
{
|
||||
enum LOG_LEVELS trace_level; /**< trace level */
|
||||
int max_trace_entries; /**< max no of entries in the trace buffer */
|
||||
enum LOG_LEVELS trace_output_level; /**< trace level to output to destination */
|
||||
} trace_settings_type;
|
||||
|
||||
extern trace_settings_type trace_settings;
|
||||
|
||||
#define LOG_PROTOCOL TRACE_PROTOCOL
|
||||
#define TRACE_MAX TRACE_MAXIMUM
|
||||
#define TRACE_MIN TRACE_MINIMUM
|
||||
#define TRACE_MED TRACE_MEDIUM
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char* name;
|
||||
const char* value;
|
||||
} Log_nameValue;
|
||||
|
||||
int Log_initialize(Log_nameValue*);
|
||||
void Log_terminate(void);
|
||||
|
||||
void Log(enum LOG_LEVELS, int, const char *, ...);
|
||||
void Log_stackTrace(enum LOG_LEVELS, int, int, int, const char*, int, int*);
|
||||
|
||||
typedef void Log_traceCallback(enum LOG_LEVELS level, const char *message);
|
||||
void Log_setTraceCallback(Log_traceCallback* callback);
|
||||
void Log_setTraceLevel(enum LOG_LEVELS level);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,254 @@
|
|||
/*******************************************************************************
|
||||
* 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 v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief This structure represents a persistent data store, used to store
|
||||
* outbound and inbound messages, in order to achieve reliable messaging.
|
||||
*
|
||||
* The MQTT Client persists QoS1 and QoS2 messages in order to meet the
|
||||
* assurances of delivery associated with these @ref qos levels. The messages
|
||||
* are saved in persistent storage
|
||||
* The type and context of the persistence implementation are specified when
|
||||
* the MQTT client is created (see MQTTClient_create()). The default
|
||||
* persistence type (::MQTTCLIENT_PERSISTENCE_DEFAULT) uses a file system-based
|
||||
* persistence mechanism. The <i>persistence_context</i> argument passed to
|
||||
* MQTTClient_create() when using the default peristence is a string
|
||||
* representing the location of the persistence directory. If the context
|
||||
* argument is NULL, the working directory will be used.
|
||||
*
|
||||
* To use memory-based persistence, an application passes
|
||||
* ::MQTTCLIENT_PERSISTENCE_NONE as the <i>persistence_type</i> to
|
||||
* MQTTClient_create(). This can lead to message loss in certain situations,
|
||||
* but can be appropriate in some cases (see @ref qos).
|
||||
*
|
||||
* Client applications can provide their own persistence mechanism by passing
|
||||
* ::MQTTCLIENT_PERSISTENCE_USER as the <i>persistence_type</i>. To implement a
|
||||
* custom persistence mechanism, the application must pass an initialized
|
||||
* ::MQTTClient_persistence structure as the <i>persistence_context</i>
|
||||
* argument to MQTTClient_create().
|
||||
*
|
||||
* If the functions defined return an ::MQTTCLIENT_PERSISTENCE_ERROR then the
|
||||
* state of the persisted data should remain as it was prior to the function
|
||||
* being called. For example, if Persistence_put() returns
|
||||
* ::MQTTCLIENT_PERSISTENCE_ERROR, then it is assumed tha tthe persistent store
|
||||
* does not contain the data that was passed to the function. Similarly, if
|
||||
* Persistence_remove() returns ::MQTTCLIENT_PERSISTENCE_ERROR then it is
|
||||
* assumed that the data to be removed is still held in the persistent store.
|
||||
*
|
||||
* It is up to the persistence implementation to log any error information that
|
||||
* may be required to diagnose a persistence mechanism failure.
|
||||
*/
|
||||
|
||||
/*
|
||||
/// @cond EXCLUDE
|
||||
*/
|
||||
#if !defined(MQTTCLIENTPERSISTENCE_H)
|
||||
#define MQTTCLIENTPERSISTENCE_H
|
||||
/*
|
||||
/// @endcond
|
||||
*/
|
||||
|
||||
/**
|
||||
* This <i>persistence_type</i> value specifies the default file system-based
|
||||
* persistence mechanism (see MQTTClient_create()).
|
||||
*/
|
||||
#define MQTTCLIENT_PERSISTENCE_DEFAULT 0
|
||||
/**
|
||||
* This <i>persistence_type</i> value specifies a memory-based
|
||||
* persistence mechanism (see MQTTClient_create()).
|
||||
*/
|
||||
#define MQTTCLIENT_PERSISTENCE_NONE 1
|
||||
/**
|
||||
* This <i>persistence_type</i> value specifies an application-specific
|
||||
* persistence mechanism (see MQTTClient_create()).
|
||||
*/
|
||||
#define MQTTCLIENT_PERSISTENCE_USER 2
|
||||
|
||||
/**
|
||||
* Application-specific persistence functions must return this error code if
|
||||
* there is a problem executing the function.
|
||||
*/
|
||||
#define MQTTCLIENT_PERSISTENCE_ERROR -2
|
||||
|
||||
/**
|
||||
* @brief Initialize the persistent store.
|
||||
*
|
||||
* Either open the existing persistent store for this client ID or create a new
|
||||
* one if one doesn't exist. If the persistent store is already open, return
|
||||
* without taking any action.
|
||||
*
|
||||
* An application can use the same client identifier to connect to many
|
||||
* different servers. The <i>clientid</i> in conjunction with the
|
||||
* <i>serverURI</i> uniquely identifies the persistence store required.
|
||||
*
|
||||
* @param handle The address of a pointer to a handle for this persistence
|
||||
* implementation. This function must set handle to a valid reference to the
|
||||
* persistence following a successful return.
|
||||
* The handle pointer is passed as an argument to all the other
|
||||
* persistence functions. It may include the context parameter and/or any other
|
||||
* data for use by the persistence functions.
|
||||
* @param clientID The client identifier for which the persistent store should
|
||||
* be opened.
|
||||
* @param serverURI The connection string specified when the MQTT client was
|
||||
* created (see MQTTClient_create()).
|
||||
* @param context A pointer to any data required to initialize the persistent
|
||||
* store (see ::MQTTClient_persistence).
|
||||
* @return Return 0 if the function completes successfully, otherwise return
|
||||
* ::MQTTCLIENT_PERSISTENCE_ERROR.
|
||||
*/
|
||||
typedef int (*Persistence_open)(void** handle, const char* clientID, const char* serverURI, void* context);
|
||||
|
||||
/**
|
||||
* @brief Close the persistent store referred to by the handle.
|
||||
*
|
||||
* @param handle The handle pointer from a successful call to
|
||||
* Persistence_open().
|
||||
* @return Return 0 if the function completes successfully, otherwise return
|
||||
* ::MQTTCLIENT_PERSISTENCE_ERROR.
|
||||
*/
|
||||
typedef int (*Persistence_close)(void* handle);
|
||||
|
||||
/**
|
||||
* @brief Put the specified data into the persistent store.
|
||||
*
|
||||
* @param handle The handle pointer from a successful call to
|
||||
* Persistence_open().
|
||||
* @param key A string used as the key for the data to be put in the store. The
|
||||
* key is later used to retrieve data from the store with Persistence_get().
|
||||
* @param bufcount The number of buffers to write to the persistence store.
|
||||
* @param buffers An array of pointers to the data buffers associated with
|
||||
* this <i>key</i>.
|
||||
* @param buflens An array of lengths of the data buffers. <i>buflen[n]</i>
|
||||
* gives the length of <i>buffer[n]</i>.
|
||||
* @return Return 0 if the function completes successfully, otherwise return
|
||||
* ::MQTTCLIENT_PERSISTENCE_ERROR.
|
||||
*/
|
||||
typedef int (*Persistence_put)(void* handle, char* key, int bufcount, char* buffers[], int buflens[]);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the specified data from the persistent store.
|
||||
*
|
||||
* @param handle The handle pointer from a successful call to
|
||||
* Persistence_open().
|
||||
* @param key A string that is the key for the data to be retrieved. This is
|
||||
* the same key used to save the data to the store with Persistence_put().
|
||||
* @param buffer The address of a pointer to a buffer. This function sets the
|
||||
* pointer to point at the retrieved data, if successful.
|
||||
* @param buflen The address of an int that is set to the length of
|
||||
* <i>buffer</i> by this function if successful.
|
||||
* @return Return 0 if the function completes successfully, otherwise return
|
||||
* ::MQTTCLIENT_PERSISTENCE_ERROR.
|
||||
*/
|
||||
typedef int (*Persistence_get)(void* handle, char* key, char** buffer, int* buflen);
|
||||
|
||||
/**
|
||||
* @brief Remove the data for the specified key from the store.
|
||||
*
|
||||
* @param handle The handle pointer from a successful call to
|
||||
* Persistence_open().
|
||||
* @param key A string that is the key for the data to be removed from the
|
||||
* store. This is the same key used to save the data to the store with
|
||||
* Persistence_put().
|
||||
* @return Return 0 if the function completes successfully, otherwise return
|
||||
* ::MQTTCLIENT_PERSISTENCE_ERROR.
|
||||
*/
|
||||
typedef int (*Persistence_remove)(void* handle, char* key);
|
||||
|
||||
/**
|
||||
* @brief Returns the keys in this persistent data store.
|
||||
*
|
||||
* @param handle The handle pointer from a successful call to
|
||||
* Persistence_open().
|
||||
* @param keys The address of a pointer to pointers to strings. Assuming
|
||||
* successful execution, this function allocates memory to hold the returned
|
||||
* keys (strings used to store the data with Persistence_put()). It also
|
||||
* allocates memory to hold an array of pointers to these strings. <i>keys</i>
|
||||
* is set to point to the array of pointers to strings.
|
||||
* @param nkeys A pointer to the number of keys in this persistent data store.
|
||||
* This function sets the number of keys, if successful.
|
||||
* @return Return 0 if the function completes successfully, otherwise return
|
||||
* ::MQTTCLIENT_PERSISTENCE_ERROR.
|
||||
*/
|
||||
typedef int (*Persistence_keys)(void* handle, char*** keys, int* nkeys);
|
||||
|
||||
/**
|
||||
* @brief Clears the persistence store, so that it no longer contains any
|
||||
* persisted data.
|
||||
*
|
||||
* @param handle The handle pointer from a successful call to
|
||||
* Persistence_open().
|
||||
* @return Return 0 if the function completes successfully, otherwise return
|
||||
* ::MQTTCLIENT_PERSISTENCE_ERROR.
|
||||
*/
|
||||
typedef int (*Persistence_clear)(void* handle);
|
||||
|
||||
/**
|
||||
* @brief Returns whether any data has been persisted using the specified key.
|
||||
*
|
||||
* @param handle The handle pointer from a successful call to
|
||||
* Persistence_open().
|
||||
* @param key The string to be tested for existence in the store.
|
||||
* @return Return 0 if the key was found in the store, otherwise return
|
||||
* ::MQTTCLIENT_PERSISTENCE_ERROR.
|
||||
*/
|
||||
typedef int (*Persistence_containskey)(void* handle, char* key);
|
||||
|
||||
/**
|
||||
* @brief A structure containing the function pointers to a persistence
|
||||
* implementation and the context or state that will be shared across all
|
||||
* the persistence functions.
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
* A pointer to any data required to initialize the persistent store.
|
||||
*/
|
||||
void* context;
|
||||
/**
|
||||
* A function pointer to an implementation of Persistence_open().
|
||||
*/
|
||||
Persistence_open popen;
|
||||
/**
|
||||
* A function pointer to an implementation of Persistence_close().
|
||||
*/
|
||||
Persistence_close pclose;
|
||||
/**
|
||||
* A function pointer to an implementation of Persistence_put().
|
||||
*/
|
||||
Persistence_put pput;
|
||||
/**
|
||||
* A function pointer to an implementation of Persistence_get().
|
||||
*/
|
||||
Persistence_get pget;
|
||||
/**
|
||||
* A function pointer to an implementation of Persistence_remove().
|
||||
*/
|
||||
Persistence_remove premove;
|
||||
/**
|
||||
* A function pointer to an implementation of Persistence_keys().
|
||||
*/
|
||||
Persistence_keys pkeys;
|
||||
/**
|
||||
* A function pointer to an implementation of Persistence_clear().
|
||||
*/
|
||||
Persistence_clear pclear;
|
||||
/**
|
||||
* A function pointer to an implementation of Persistence_containskey().
|
||||
*/
|
||||
Persistence_containskey pcontainskey;
|
||||
} MQTTClient_persistence;
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2020, 2020 Andreas Walter
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Andreas Walter - initially moved export declarations into separate fle
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(EXPORTDECLARATIONS_H)
|
||||
#define EXPORTDECLARATIONS_H
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
# if defined(PAHO_MQTT_EXPORTS)
|
||||
# define LIBMQTT_API __declspec(dllexport)
|
||||
# elif defined(PAHO_MQTT_IMPORTS)
|
||||
# define LIBMQTT_API __declspec(dllimport)
|
||||
# else
|
||||
# define LIBMQTT_API
|
||||
# endif
|
||||
#else
|
||||
# if defined(PAHO_MQTT_EXPORTS)
|
||||
# define LIBMQTT_API __attribute__ ((visibility ("default")))
|
||||
# else
|
||||
# define LIBMQTT_API extern
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2019 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs, Allan Stockdill-Mander - SSL updates
|
||||
* Ian Craggs - MQTT 3.1.1 support
|
||||
* Ian Craggs - big endian Linux reversed definition
|
||||
* Ian Craggs - MQTT 5.0 support
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MQTTPACKET_H)
|
||||
#define MQTTPACKET_H
|
||||
|
||||
#include "Socket.h"
|
||||
#if defined(OPENSSL)
|
||||
#include "SSLSocket.h"
|
||||
#endif
|
||||
#include "LinkedList.h"
|
||||
#include "Clients.h"
|
||||
|
||||
typedef unsigned int bool;
|
||||
typedef void* (*pf)(int, unsigned char, char*, size_t);
|
||||
|
||||
#include "MQTTProperties.h"
|
||||
#include "MQTTReasonCodes.h"
|
||||
|
||||
enum errors
|
||||
{
|
||||
MQTTPACKET_BAD = -4,
|
||||
MQTTPACKET_BUFFER_TOO_SHORT = -2,
|
||||
MQTTPACKET_READ_ERROR = -1,
|
||||
MQTTPACKET_READ_COMPLETE
|
||||
};
|
||||
|
||||
|
||||
enum msgTypes
|
||||
{
|
||||
CONNECT = 1, CONNACK, PUBLISH, PUBACK, PUBREC, PUBREL,
|
||||
PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK,
|
||||
PINGREQ, PINGRESP, DISCONNECT, AUTH
|
||||
};
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <endian.h>
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
#define REVERSED 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Bitfields for the MQTT header byte.
|
||||
*/
|
||||
typedef union
|
||||
{
|
||||
/*unsigned*/ char byte; /**< the whole byte */
|
||||
#if defined(REVERSED)
|
||||
struct
|
||||
{
|
||||
unsigned int type : 4; /**< message type nibble */
|
||||
bool dup : 1; /**< DUP flag bit */
|
||||
unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */
|
||||
bool retain : 1; /**< retained flag bit */
|
||||
} bits;
|
||||
#else
|
||||
struct
|
||||
{
|
||||
bool retain : 1; /**< retained flag bit */
|
||||
unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */
|
||||
bool dup : 1; /**< DUP flag bit */
|
||||
unsigned int type : 4; /**< message type nibble */
|
||||
} bits;
|
||||
#endif
|
||||
} Header;
|
||||
|
||||
|
||||
/**
|
||||
* Data for a connect packet.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
Header header; /**< MQTT header byte */
|
||||
union
|
||||
{
|
||||
unsigned char all; /**< all connect flags */
|
||||
#if defined(REVERSED)
|
||||
struct
|
||||
{
|
||||
bool username : 1; /**< 3.1 user name */
|
||||
bool password : 1; /**< 3.1 password */
|
||||
bool willRetain : 1; /**< will retain setting */
|
||||
unsigned int willQoS : 2; /**< will QoS value */
|
||||
bool will : 1; /**< will flag */
|
||||
bool cleanstart : 1; /**< cleansession flag */
|
||||
int : 1; /**< unused */
|
||||
} bits;
|
||||
#else
|
||||
struct
|
||||
{
|
||||
int : 1; /**< unused */
|
||||
bool cleanstart : 1; /**< cleansession flag */
|
||||
bool will : 1; /**< will flag */
|
||||
unsigned int willQoS : 2; /**< will QoS value */
|
||||
bool willRetain : 1; /**< will retain setting */
|
||||
bool password : 1; /**< 3.1 password */
|
||||
bool username : 1; /**< 3.1 user name */
|
||||
} bits;
|
||||
#endif
|
||||
} flags; /**< connect flags byte */
|
||||
|
||||
char *Protocol, /**< MQTT protocol name */
|
||||
*clientID, /**< string client id */
|
||||
*willTopic, /**< will topic */
|
||||
*willMsg; /**< will payload */
|
||||
|
||||
int keepAliveTimer; /**< keepalive timeout value in seconds */
|
||||
unsigned char version; /**< MQTT version number */
|
||||
} Connect;
|
||||
|
||||
|
||||
/**
|
||||
* Data for a connack packet.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
Header header; /**< MQTT header byte */
|
||||
union
|
||||
{
|
||||
unsigned char all; /**< all connack flags */
|
||||
#if defined(REVERSED)
|
||||
struct
|
||||
{
|
||||
unsigned int reserved : 7; /**< message type nibble */
|
||||
bool sessionPresent : 1; /**< was a session found on the server? */
|
||||
} bits;
|
||||
#else
|
||||
struct
|
||||
{
|
||||
bool sessionPresent : 1; /**< was a session found on the server? */
|
||||
unsigned int reserved : 7; /**< message type nibble */
|
||||
} bits;
|
||||
#endif
|
||||
} flags; /**< connack flags byte */
|
||||
unsigned char rc; /**< connack reason code */
|
||||
unsigned int MQTTVersion; /**< the version of MQTT */
|
||||
MQTTProperties properties; /**< MQTT 5.0 properties. Not used for MQTT < 5.0 */
|
||||
} Connack;
|
||||
|
||||
|
||||
/**
|
||||
* Data for a packet with header only.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
Header header; /**< MQTT header byte */
|
||||
} MQTTPacket;
|
||||
|
||||
|
||||
/**
|
||||
* Data for a suback packet.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
Header header; /**< MQTT header byte */
|
||||
int msgId; /**< MQTT message id */
|
||||
int MQTTVersion; /**< the version of MQTT */
|
||||
MQTTProperties properties; /**< MQTT 5.0 properties. Not used for MQTT < 5.0 */
|
||||
List* qoss; /**< list of granted QoSs (MQTT 3/4) / reason codes (MQTT 5) */
|
||||
} Suback;
|
||||
|
||||
|
||||
/**
|
||||
* Data for an MQTT V5 unsuback packet.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
Header header; /**< MQTT header byte */
|
||||
int msgId; /**< MQTT message id */
|
||||
int MQTTVersion; /**< the version of MQTT */
|
||||
MQTTProperties properties; /**< MQTT 5.0 properties. Not used for MQTT < 5.0 */
|
||||
List* reasonCodes; /**< list of reason codes */
|
||||
} Unsuback;
|
||||
|
||||
|
||||
/**
|
||||
* Data for a publish packet.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
Header header; /**< MQTT header byte */
|
||||
char* topic; /**< topic string */
|
||||
int topiclen;
|
||||
int msgId; /**< MQTT message id */
|
||||
char* payload; /**< binary payload, length delimited */
|
||||
int payloadlen; /**< payload length */
|
||||
int MQTTVersion; /**< the version of MQTT */
|
||||
MQTTProperties properties; /**< MQTT 5.0 properties. Not used for MQTT < 5.0 */
|
||||
} Publish;
|
||||
|
||||
|
||||
/**
|
||||
* Data for one of the ack packets.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
Header header; /**< MQTT header byte */
|
||||
int msgId; /**< MQTT message id */
|
||||
unsigned char rc; /**< MQTT 5 reason code */
|
||||
int MQTTVersion; /**< the version of MQTT */
|
||||
MQTTProperties properties; /**< MQTT 5.0 properties. Not used for MQTT < 5.0 */
|
||||
} Ack;
|
||||
|
||||
typedef Ack Puback;
|
||||
typedef Ack Pubrec;
|
||||
typedef Ack Pubrel;
|
||||
typedef Ack Pubcomp;
|
||||
|
||||
int MQTTPacket_encode(char* buf, size_t length);
|
||||
int MQTTPacket_decode(networkHandles* net, size_t* value);
|
||||
int readInt(char** pptr);
|
||||
char* readUTF(char** pptr, char* enddata);
|
||||
unsigned char readChar(char** pptr);
|
||||
void writeChar(char** pptr, char c);
|
||||
void writeInt(char** pptr, int anInt);
|
||||
void writeUTF(char** pptr, const char* string);
|
||||
void writeData(char** pptr, const void* data, int datalen);
|
||||
|
||||
const char* MQTTPacket_name(int ptype);
|
||||
|
||||
void* MQTTPacket_Factory(int MQTTVersion, networkHandles* net, int* error);
|
||||
int MQTTPacket_send(networkHandles* net, Header header, char* buffer, size_t buflen, int free, int MQTTVersion);
|
||||
int MQTTPacket_sends(networkHandles* net, Header header, int count, char** buffers, size_t* buflens, int* frees, int MQTTVersion);
|
||||
|
||||
void* MQTTPacket_header_only(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen);
|
||||
int MQTTPacket_send_disconnect(Clients* client, enum MQTTReasonCodes reason, MQTTProperties* props);
|
||||
|
||||
void* MQTTPacket_publish(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen);
|
||||
void MQTTPacket_freePublish(Publish* pack);
|
||||
int MQTTPacket_send_publish(Publish* pack, int dup, int qos, int retained, networkHandles* net, const char* clientID);
|
||||
int MQTTPacket_send_puback(int MQTTVersion, int msgid, networkHandles* net, const char* clientID);
|
||||
void* MQTTPacket_ack(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen);
|
||||
|
||||
void MQTTPacket_freeAck(Ack* pack);
|
||||
void MQTTPacket_freeSuback(Suback* pack);
|
||||
void MQTTPacket_freeUnsuback(Unsuback* pack);
|
||||
int MQTTPacket_send_pubrec(int MQTTVersion, int msgid, networkHandles* net, const char* clientID);
|
||||
int MQTTPacket_send_pubrel(int MQTTVersion, int msgid, int dup, networkHandles* net, const char* clientID);
|
||||
int MQTTPacket_send_pubcomp(int MQTTVersion, int msgid, networkHandles* net, const char* clientID);
|
||||
|
||||
void MQTTPacket_free_packet(MQTTPacket* pack);
|
||||
|
||||
void writeInt4(char** pptr, int anInt);
|
||||
int readInt4(char** pptr);
|
||||
void writeMQTTLenString(char** pptr, MQTTLenString lenstring);
|
||||
int MQTTLenStringRead(MQTTLenString* lenstring, char** pptr, char* enddata);
|
||||
int MQTTPacket_VBIlen(int rem_len);
|
||||
int MQTTPacket_decodeBuf(char* buf, unsigned int* value);
|
||||
|
||||
#include "MQTTPacketOut.h"
|
||||
|
||||
#endif /* MQTTPACKET_H */
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2018 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs, Allan Stockdill-Mander - SSL updates
|
||||
* Ian Craggs - MQTT 3.1.1 support
|
||||
* Ian Craggs - MQTT 5.0 support
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MQTTPACKETOUT_H)
|
||||
#define MQTTPACKETOUT_H
|
||||
|
||||
#include "MQTTPacket.h"
|
||||
|
||||
int MQTTPacket_send_connect(Clients* client, int MQTTVersion,
|
||||
MQTTProperties* connectProperties, MQTTProperties* willProperties);
|
||||
void* MQTTPacket_connack(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen);
|
||||
void MQTTPacket_freeConnack(Connack* pack);
|
||||
|
||||
int MQTTPacket_send_pingreq(networkHandles* net, const char* clientID);
|
||||
|
||||
int MQTTPacket_send_subscribe(List* topics, List* qoss, MQTTSubscribe_options* opts, MQTTProperties* props,
|
||||
int msgid, int dup, Clients* client);
|
||||
void* MQTTPacket_suback(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen);
|
||||
|
||||
int MQTTPacket_send_unsubscribe(List* topics, MQTTProperties* props, int msgid, int dup, Clients* client);
|
||||
void* MQTTPacket_unsuback(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2018 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - async client updates
|
||||
* Ian Craggs - fix for bug 432903 - queue persistence
|
||||
* Ian Craggs - MQTT V5 updates
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MQTTPERSISTENCE_H)
|
||||
#define MQTTPERSISTENCE_H
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "Clients.h"
|
||||
#include "MQTTProperties.h"
|
||||
|
||||
/** Stem of the key for a sent PUBLISH QoS1 or QoS2 */
|
||||
#define PERSISTENCE_PUBLISH_SENT "s-"
|
||||
/** Stem of the key for a sent PUBREL */
|
||||
#define PERSISTENCE_PUBREL "sc-"
|
||||
/** Stem of the key for a received PUBLISH QoS2 */
|
||||
#define PERSISTENCE_PUBLISH_RECEIVED "r-"
|
||||
|
||||
/** Stem of the key for a sent MQTT V5 PUBLISH QoS1 or QoS2 */
|
||||
#define PERSISTENCE_V5_PUBLISH_SENT "s5-"
|
||||
/** Stem of the key for a sent MQTT V5 PUBREL */
|
||||
#define PERSISTENCE_V5_PUBREL "sc5-"
|
||||
/** Stem of the key for a received MQTT V5 PUBLISH QoS2 */
|
||||
#define PERSISTENCE_V5_PUBLISH_RECEIVED "r5-"
|
||||
|
||||
/** Stem of the key for an async client command */
|
||||
#define PERSISTENCE_COMMAND_KEY "c-"
|
||||
/** Stem of the key for an MQTT V5 async client command */
|
||||
#define PERSISTENCE_V5_COMMAND_KEY "c5-"
|
||||
/** Stem of the key for an async client message queue */
|
||||
#define PERSISTENCE_QUEUE_KEY "q-"
|
||||
/** Stem of the key for an MQTT V5 message queue */
|
||||
#define PERSISTENCE_V5_QUEUE_KEY "q5-"
|
||||
#define PERSISTENCE_MAX_KEY_LENGTH 8
|
||||
|
||||
int MQTTPersistence_create(MQTTClient_persistence** per, int type, void* pcontext);
|
||||
int MQTTPersistence_initialize(Clients* c, const char* serverURI);
|
||||
int MQTTPersistence_close(Clients* c);
|
||||
int MQTTPersistence_clear(Clients* c);
|
||||
int MQTTPersistence_restore(Clients* c);
|
||||
void* MQTTPersistence_restorePacket(int MQTTVersion, char* buffer, size_t buflen);
|
||||
void MQTTPersistence_insertInOrder(List* list, void* content, size_t size);
|
||||
int MQTTPersistence_put(int socket, char* buf0, size_t buf0len, int count,
|
||||
char** buffers, size_t* buflens, int htype, int msgId, int scr, int MQTTVersion);
|
||||
int MQTTPersistence_remove(Clients* c, char* type, int qos, int msgId);
|
||||
void MQTTPersistence_wrapMsgID(Clients *c);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char struct_id[4];
|
||||
int struct_version;
|
||||
int payloadlen;
|
||||
void* payload;
|
||||
int qos;
|
||||
int retained;
|
||||
int dup;
|
||||
int msgid;
|
||||
MQTTProperties properties;
|
||||
} MQTTPersistence_message;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
MQTTPersistence_message* msg;
|
||||
char* topicName;
|
||||
int topicLen;
|
||||
unsigned int seqno; /* only used on restore */
|
||||
} MQTTPersistence_qEntry;
|
||||
|
||||
int MQTTPersistence_unpersistQueueEntry(Clients* client, MQTTPersistence_qEntry* qe);
|
||||
int MQTTPersistence_persistQueueEntry(Clients* aclient, MQTTPersistence_qEntry* qe);
|
||||
int MQTTPersistence_restoreMessageQueue(Clients* c);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2018 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MQTTPERSISTENCEDEFAULT_H)
|
||||
#define MQTTPERSISTENCEDEFAULT_H
|
||||
|
||||
/** 8.3 filesystem */
|
||||
#define MESSAGE_FILENAME_LENGTH 8
|
||||
/** Extension of the filename */
|
||||
#define MESSAGE_FILENAME_EXTENSION ".msg"
|
||||
|
||||
/* prototypes of the functions for the default file system persistence */
|
||||
int pstopen(void** handle, const char* clientID, const char* serverURI, void* context);
|
||||
int pstclose(void* handle);
|
||||
int pstput(void* handle, char* key, int bufcount, char* buffers[], int buflens[]);
|
||||
int pstget(void* handle, char* key, char** buffer, int* buflen);
|
||||
int pstremove(void* handle, char* key);
|
||||
int pstkeys(void* handle, char*** keys, int* nkeys);
|
||||
int pstclear(void* handle);
|
||||
int pstcontainskey(void* handle, char* key);
|
||||
|
||||
int pstmkdir(char *pPathname);
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2020 IBM Corp. and others
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MQTTPROPERTIES_H)
|
||||
#define MQTTPROPERTIES_H
|
||||
|
||||
#include "MQTTExportDeclarations.h"
|
||||
|
||||
#define MQTT_INVALID_PROPERTY_ID -2
|
||||
|
||||
/** The one byte MQTT V5 property indicator */
|
||||
enum MQTTPropertyCodes {
|
||||
MQTTPROPERTY_CODE_PAYLOAD_FORMAT_INDICATOR = 1, /**< The value is 1 */
|
||||
MQTTPROPERTY_CODE_MESSAGE_EXPIRY_INTERVAL = 2, /**< The value is 2 */
|
||||
MQTTPROPERTY_CODE_CONTENT_TYPE = 3, /**< The value is 3 */
|
||||
MQTTPROPERTY_CODE_RESPONSE_TOPIC = 8, /**< The value is 8 */
|
||||
MQTTPROPERTY_CODE_CORRELATION_DATA = 9, /**< The value is 9 */
|
||||
MQTTPROPERTY_CODE_SUBSCRIPTION_IDENTIFIER = 11, /**< The value is 11 */
|
||||
MQTTPROPERTY_CODE_SESSION_EXPIRY_INTERVAL = 17, /**< The value is 17 */
|
||||
MQTTPROPERTY_CODE_ASSIGNED_CLIENT_IDENTIFER = 18,/**< The value is 18 */
|
||||
MQTTPROPERTY_CODE_SERVER_KEEP_ALIVE = 19, /**< The value is 19 */
|
||||
MQTTPROPERTY_CODE_AUTHENTICATION_METHOD = 21, /**< The value is 21 */
|
||||
MQTTPROPERTY_CODE_AUTHENTICATION_DATA = 22, /**< The value is 22 */
|
||||
MQTTPROPERTY_CODE_REQUEST_PROBLEM_INFORMATION = 23,/**< The value is 23 */
|
||||
MQTTPROPERTY_CODE_WILL_DELAY_INTERVAL = 24, /**< The value is 24 */
|
||||
MQTTPROPERTY_CODE_REQUEST_RESPONSE_INFORMATION = 25,/**< The value is 25 */
|
||||
MQTTPROPERTY_CODE_RESPONSE_INFORMATION = 26, /**< The value is 26 */
|
||||
MQTTPROPERTY_CODE_SERVER_REFERENCE = 28, /**< The value is 28 */
|
||||
MQTTPROPERTY_CODE_REASON_STRING = 31, /**< The value is 31 */
|
||||
MQTTPROPERTY_CODE_RECEIVE_MAXIMUM = 33, /**< The value is 33*/
|
||||
MQTTPROPERTY_CODE_TOPIC_ALIAS_MAXIMUM = 34, /**< The value is 34 */
|
||||
MQTTPROPERTY_CODE_TOPIC_ALIAS = 35, /**< The value is 35 */
|
||||
MQTTPROPERTY_CODE_MAXIMUM_QOS = 36, /**< The value is 36 */
|
||||
MQTTPROPERTY_CODE_RETAIN_AVAILABLE = 37, /**< The value is 37 */
|
||||
MQTTPROPERTY_CODE_USER_PROPERTY = 38, /**< The value is 38 */
|
||||
MQTTPROPERTY_CODE_MAXIMUM_PACKET_SIZE = 39, /**< The value is 39 */
|
||||
MQTTPROPERTY_CODE_WILDCARD_SUBSCRIPTION_AVAILABLE = 40,/**< The value is 40 */
|
||||
MQTTPROPERTY_CODE_SUBSCRIPTION_IDENTIFIERS_AVAILABLE = 41,/**< The value is 41 */
|
||||
MQTTPROPERTY_CODE_SHARED_SUBSCRIPTION_AVAILABLE = 42/**< The value is 241 */
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a printable string description of an MQTT V5 property code.
|
||||
* @param value an MQTT V5 property code.
|
||||
* @return the printable string description of the input property code.
|
||||
* NULL if the code was not found.
|
||||
*/
|
||||
LIBMQTT_API const char* MQTTPropertyName(enum MQTTPropertyCodes value);
|
||||
|
||||
/** The one byte MQTT V5 property type */
|
||||
enum MQTTPropertyTypes {
|
||||
MQTTPROPERTY_TYPE_BYTE,
|
||||
MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER,
|
||||
MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER,
|
||||
MQTTPROPERTY_TYPE_VARIABLE_BYTE_INTEGER,
|
||||
MQTTPROPERTY_TYPE_BINARY_DATA,
|
||||
MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING,
|
||||
MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the MQTT V5 type code of an MQTT V5 property.
|
||||
* @param value an MQTT V5 property code.
|
||||
* @return the MQTT V5 type code of the input property. -1 if the code was not found.
|
||||
*/
|
||||
LIBMQTT_API int MQTTProperty_getType(enum MQTTPropertyCodes value);
|
||||
|
||||
/**
|
||||
* The data for a length delimited string
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int len; /**< the length of the string */
|
||||
char* data; /**< pointer to the string data */
|
||||
} MQTTLenString;
|
||||
|
||||
|
||||
/**
|
||||
* Structure to hold an MQTT version 5 property of any type
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
enum MQTTPropertyCodes identifier; /**< The MQTT V5 property id. A multi-byte integer. */
|
||||
/** The value of the property, as a union of the different possible types. */
|
||||
union {
|
||||
unsigned char byte; /**< holds the value of a byte property type */
|
||||
unsigned short integer2; /**< holds the value of a 2 byte integer property type */
|
||||
unsigned int integer4; /**< holds the value of a 4 byte integer property type */
|
||||
struct {
|
||||
MQTTLenString data; /**< The value of a string property, or the name of a user property. */
|
||||
MQTTLenString value; /**< The value of a user property. */
|
||||
};
|
||||
} value;
|
||||
} MQTTProperty;
|
||||
|
||||
/**
|
||||
* MQTT version 5 property list
|
||||
*/
|
||||
typedef struct MQTTProperties
|
||||
{
|
||||
int count; /**< number of property entries in the array */
|
||||
int max_count; /**< max number of properties that the currently allocated array can store */
|
||||
int length; /**< mbi: byte length of all properties */
|
||||
MQTTProperty *array; /**< array of properties */
|
||||
} MQTTProperties;
|
||||
|
||||
#define MQTTProperties_initializer {0, 0, 0, NULL}
|
||||
|
||||
/**
|
||||
* Returns the length of the properties structure when serialized ready for network transmission.
|
||||
* @param props an MQTT V5 property structure.
|
||||
* @return the length in bytes of the properties when serialized.
|
||||
*/
|
||||
int MQTTProperties_len(MQTTProperties* props);
|
||||
|
||||
/**
|
||||
* Add a property pointer to the property array. There is no memory allocation.
|
||||
* @param props The property list to add the property to.
|
||||
* @param prop The property to add to the list.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
LIBMQTT_API int MQTTProperties_add(MQTTProperties* props, const MQTTProperty* prop);
|
||||
|
||||
/**
|
||||
* Serialize the given property list to a character buffer, e.g. for writing to the network.
|
||||
* @param pptr pointer to the buffer - move the pointer as we add data
|
||||
* @param properties pointer to the property list, can be NULL
|
||||
* @return whether the write succeeded or not: number of bytes written, or < 0 on failure.
|
||||
*/
|
||||
int MQTTProperties_write(char** pptr, const MQTTProperties* properties);
|
||||
|
||||
/**
|
||||
* Reads a property list from a character buffer into an array.
|
||||
* @param properties pointer to the property list to be filled. Should be initalized but empty.
|
||||
* @param pptr pointer to the character buffer.
|
||||
* @param enddata pointer to the end of the character buffer so we don't read beyond.
|
||||
* @return 1 if the properties were read successfully.
|
||||
*/
|
||||
int MQTTProperties_read(MQTTProperties* properties, char** pptr, char* enddata);
|
||||
|
||||
/**
|
||||
* Free all memory allocated to the property list, including any to individual properties.
|
||||
* @param properties pointer to the property list.
|
||||
*/
|
||||
LIBMQTT_API void MQTTProperties_free(MQTTProperties* properties);
|
||||
|
||||
/**
|
||||
* Copy the contents of a property list, allocating additional memory if needed.
|
||||
* @param props pointer to the property list.
|
||||
* @return the duplicated property list.
|
||||
*/
|
||||
LIBMQTT_API MQTTProperties MQTTProperties_copy(const MQTTProperties* props);
|
||||
|
||||
/**
|
||||
* Checks if property list contains a specific property.
|
||||
* @param props pointer to the property list.
|
||||
* @param propid the property id to check for.
|
||||
* @return 1 if found, 0 if not.
|
||||
*/
|
||||
LIBMQTT_API int MQTTProperties_hasProperty(MQTTProperties *props, enum MQTTPropertyCodes propid);
|
||||
|
||||
/**
|
||||
* Returns the number of instances of a property id. Most properties can exist only once.
|
||||
* User properties and subscription ids can exist more than once.
|
||||
* @param props pointer to the property list.
|
||||
* @param propid the property id to check for.
|
||||
* @return the number of times found. Can be 0.
|
||||
*/
|
||||
LIBMQTT_API int MQTTProperties_propertyCount(MQTTProperties *props, enum MQTTPropertyCodes propid);
|
||||
|
||||
/**
|
||||
* Returns the integer value of a specific property. The property given must be a numeric type.
|
||||
* @param props pointer to the property list.
|
||||
* @param propid the property id to check for.
|
||||
* @return the integer value of the property. -9999999 on failure.
|
||||
*/
|
||||
LIBMQTT_API int MQTTProperties_getNumericValue(MQTTProperties *props, enum MQTTPropertyCodes propid);
|
||||
|
||||
/**
|
||||
* Returns the integer value of a specific property when it's not the only instance.
|
||||
* The property given must be a numeric type.
|
||||
* @param props pointer to the property list.
|
||||
* @param propid the property id to check for.
|
||||
* @param index the instance number, starting at 0.
|
||||
* @return the integer value of the property. -9999999 on failure.
|
||||
*/
|
||||
LIBMQTT_API int MQTTProperties_getNumericValueAt(MQTTProperties *props, enum MQTTPropertyCodes propid, int index);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the property structure for a specific property.
|
||||
* @param props pointer to the property list.
|
||||
* @param propid the property id to check for.
|
||||
* @return the pointer to the property structure if found. NULL if not found.
|
||||
*/
|
||||
LIBMQTT_API MQTTProperty* MQTTProperties_getProperty(MQTTProperties *props, enum MQTTPropertyCodes propid);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the property structure for a specific property when it's not the only instance.
|
||||
* @param props pointer to the property list.
|
||||
* @param propid the property id to check for.
|
||||
* @param index the instance number, starting at 0.
|
||||
* @return the pointer to the property structure if found. NULL if not found.
|
||||
*/
|
||||
LIBMQTT_API MQTTProperty* MQTTProperties_getPropertyAt(MQTTProperties *props, enum MQTTPropertyCodes propid, int index);
|
||||
|
||||
#endif /* MQTTPROPERTIES_H */
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2014 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - MQTT 3.1.1 updates
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MQTTPROTOCOL_H)
|
||||
#define MQTTPROTOCOL_H
|
||||
|
||||
#include "LinkedList.h"
|
||||
#include "MQTTPacket.h"
|
||||
#include "Clients.h"
|
||||
|
||||
#define MAX_MSG_ID 65535
|
||||
#define MAX_CLIENTID_LEN 65535
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int socket;
|
||||
Publications* p;
|
||||
} pending_write;
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
List publications;
|
||||
unsigned int msgs_received;
|
||||
unsigned int msgs_sent;
|
||||
List pending_writes; /* for qos 0 writes not complete */
|
||||
} MQTTProtocol;
|
||||
|
||||
|
||||
#include "MQTTProtocolOut.h"
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs, Allan Stockdill-Mander - SSL updates
|
||||
* Ian Craggs - MQTT 3.1.1 updates
|
||||
* Rong Xiang, Ian Craggs - C++ compatibility
|
||||
* Ian Craggs - add debug definition of MQTTStrdup for when needed
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MQTTPROTOCOLCLIENT_H)
|
||||
#define MQTTPROTOCOLCLIENT_H
|
||||
|
||||
#include "LinkedList.h"
|
||||
#include "MQTTPacket.h"
|
||||
#include "Log.h"
|
||||
#include "MQTTProtocol.h"
|
||||
#include "Messages.h"
|
||||
#include "MQTTProperties.h"
|
||||
|
||||
#define MAX_MSG_ID 65535
|
||||
#define MAX_CLIENTID_LEN 65535
|
||||
|
||||
int MQTTProtocol_startPublish(Clients* pubclient, Publish* publish, int qos, int retained, Messages** m);
|
||||
Messages* MQTTProtocol_createMessage(Publish* publish, Messages** mm, int qos, int retained, int allocatePayload);
|
||||
Publications* MQTTProtocol_storePublication(Publish* publish, int* len);
|
||||
int messageIDCompare(void* a, void* b);
|
||||
int MQTTProtocol_assignMsgId(Clients* client);
|
||||
void MQTTProtocol_removePublication(Publications* p);
|
||||
void Protocol_processPublication(Publish* publish, Clients* client, int allocatePayload);
|
||||
|
||||
int MQTTProtocol_handlePublishes(void* pack, int sock);
|
||||
int MQTTProtocol_handlePubacks(void* pack, int sock);
|
||||
int MQTTProtocol_handlePubrecs(void* pack, int sock);
|
||||
int MQTTProtocol_handlePubrels(void* pack, int sock);
|
||||
int MQTTProtocol_handlePubcomps(void* pack, int sock);
|
||||
|
||||
void MQTTProtocol_closeSession(Clients* c, int sendwill);
|
||||
void MQTTProtocol_keepalive(START_TIME_TYPE);
|
||||
void MQTTProtocol_retry(START_TIME_TYPE, int, int);
|
||||
void MQTTProtocol_freeClient(Clients* client);
|
||||
void MQTTProtocol_emptyMessageList(List* msgList);
|
||||
void MQTTProtocol_freeMessageList(List* msgList);
|
||||
|
||||
char* MQTTStrncpy(char *dest, const char* src, size_t num);
|
||||
char* MQTTStrdup(const char* src);
|
||||
|
||||
//#define MQTTStrdup(src) MQTTStrncpy(malloc(strlen(src)+1), src, strlen(src)+1)
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2019 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs, Allan Stockdill-Mander - SSL updates
|
||||
* Ian Craggs - MQTT 3.1.1 support
|
||||
* Ian Craggs - SNI support
|
||||
* Ian Craggs - MQTT 5.0 support
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MQTTPROTOCOLOUT_H)
|
||||
#define MQTTPROTOCOLOUT_H
|
||||
|
||||
#include "LinkedList.h"
|
||||
#include "MQTTPacket.h"
|
||||
#include "Clients.h"
|
||||
#include "Log.h"
|
||||
#include "Messages.h"
|
||||
#include "MQTTProtocol.h"
|
||||
#include "MQTTProtocolClient.h"
|
||||
|
||||
#define DEFAULT_PORT 1883
|
||||
|
||||
size_t MQTTProtocol_addressPort(const char* uri, int* port, const char **topic);
|
||||
void MQTTProtocol_reconnect(const char* ip_address, Clients* client);
|
||||
#if defined(OPENSSL)
|
||||
#if defined(__GNUC__) && defined(__linux__)
|
||||
int MQTTProtocol_connect(const char* ip_address, Clients* acClients, int ssl, int websocket, int MQTTVersion,
|
||||
MQTTProperties* connectProperties, MQTTProperties* willProperties, long timeout);
|
||||
#else
|
||||
int MQTTProtocol_connect(const char* ip_address, Clients* acClients, int ssl, int websocket, int MQTTVersion,
|
||||
MQTTProperties* connectProperties, MQTTProperties* willProperties);
|
||||
#endif
|
||||
#else
|
||||
#if defined(__GNUC__) && defined(__linux__)
|
||||
int MQTTProtocol_connect(const char* ip_address, Clients* acClients, int websocket, int MQTTVersion,
|
||||
MQTTProperties* connectProperties, MQTTProperties* willProperties, long timeout);
|
||||
#else
|
||||
int MQTTProtocol_connect(const char* ip_address, Clients* acClients, int websocket, int MQTTVersion,
|
||||
MQTTProperties* connectProperties, MQTTProperties* willProperties);
|
||||
#endif
|
||||
#endif
|
||||
int MQTTProtocol_handlePingresps(void* pack, int sock);
|
||||
int MQTTProtocol_subscribe(Clients* client, List* topics, List* qoss, int msgID,
|
||||
MQTTSubscribe_options* opts, MQTTProperties* props);
|
||||
int MQTTProtocol_handleSubacks(void* pack, int sock);
|
||||
int MQTTProtocol_unsubscribe(Clients* client, List* topics, int msgID, MQTTProperties* props);
|
||||
int MQTTProtocol_handleUnsubacks(void* pack, int sock);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2020 IBM Corp. and others
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MQTTREASONCODES_H)
|
||||
#define MQTTREASONCODES_H
|
||||
|
||||
#include "MQTTExportDeclarations.h"
|
||||
|
||||
/** The MQTT V5 one byte reason code */
|
||||
enum MQTTReasonCodes {
|
||||
MQTTREASONCODE_SUCCESS = 0,
|
||||
MQTTREASONCODE_NORMAL_DISCONNECTION = 0,
|
||||
MQTTREASONCODE_GRANTED_QOS_0 = 0,
|
||||
MQTTREASONCODE_GRANTED_QOS_1 = 1,
|
||||
MQTTREASONCODE_GRANTED_QOS_2 = 2,
|
||||
MQTTREASONCODE_DISCONNECT_WITH_WILL_MESSAGE = 4,
|
||||
MQTTREASONCODE_NO_MATCHING_SUBSCRIBERS = 16,
|
||||
MQTTREASONCODE_NO_SUBSCRIPTION_FOUND = 17,
|
||||
MQTTREASONCODE_CONTINUE_AUTHENTICATION = 24,
|
||||
MQTTREASONCODE_RE_AUTHENTICATE = 25,
|
||||
MQTTREASONCODE_UNSPECIFIED_ERROR = 128,
|
||||
MQTTREASONCODE_MALFORMED_PACKET = 129,
|
||||
MQTTREASONCODE_PROTOCOL_ERROR = 130,
|
||||
MQTTREASONCODE_IMPLEMENTATION_SPECIFIC_ERROR = 131,
|
||||
MQTTREASONCODE_UNSUPPORTED_PROTOCOL_VERSION = 132,
|
||||
MQTTREASONCODE_CLIENT_IDENTIFIER_NOT_VALID = 133,
|
||||
MQTTREASONCODE_BAD_USER_NAME_OR_PASSWORD = 134,
|
||||
MQTTREASONCODE_NOT_AUTHORIZED = 135,
|
||||
MQTTREASONCODE_SERVER_UNAVAILABLE = 136,
|
||||
MQTTREASONCODE_SERVER_BUSY = 137,
|
||||
MQTTREASONCODE_BANNED = 138,
|
||||
MQTTREASONCODE_SERVER_SHUTTING_DOWN = 139,
|
||||
MQTTREASONCODE_BAD_AUTHENTICATION_METHOD = 140,
|
||||
MQTTREASONCODE_KEEP_ALIVE_TIMEOUT = 141,
|
||||
MQTTREASONCODE_SESSION_TAKEN_OVER = 142,
|
||||
MQTTREASONCODE_TOPIC_FILTER_INVALID = 143,
|
||||
MQTTREASONCODE_TOPIC_NAME_INVALID = 144,
|
||||
MQTTREASONCODE_PACKET_IDENTIFIER_IN_USE = 145,
|
||||
MQTTREASONCODE_PACKET_IDENTIFIER_NOT_FOUND = 146,
|
||||
MQTTREASONCODE_RECEIVE_MAXIMUM_EXCEEDED = 147,
|
||||
MQTTREASONCODE_TOPIC_ALIAS_INVALID = 148,
|
||||
MQTTREASONCODE_PACKET_TOO_LARGE = 149,
|
||||
MQTTREASONCODE_MESSAGE_RATE_TOO_HIGH = 150,
|
||||
MQTTREASONCODE_QUOTA_EXCEEDED = 151,
|
||||
MQTTREASONCODE_ADMINISTRATIVE_ACTION = 152,
|
||||
MQTTREASONCODE_PAYLOAD_FORMAT_INVALID = 153,
|
||||
MQTTREASONCODE_RETAIN_NOT_SUPPORTED = 154,
|
||||
MQTTREASONCODE_QOS_NOT_SUPPORTED = 155,
|
||||
MQTTREASONCODE_USE_ANOTHER_SERVER = 156,
|
||||
MQTTREASONCODE_SERVER_MOVED = 157,
|
||||
MQTTREASONCODE_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED = 158,
|
||||
MQTTREASONCODE_CONNECTION_RATE_EXCEEDED = 159,
|
||||
MQTTREASONCODE_MAXIMUM_CONNECT_TIME = 160,
|
||||
MQTTREASONCODE_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED = 161,
|
||||
MQTTREASONCODE_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED = 162
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a printable string description of an MQTT V5 reason code.
|
||||
* @param value an MQTT V5 reason code.
|
||||
* @return the printable string description of the input reason code.
|
||||
* NULL if the code was not found.
|
||||
*/
|
||||
LIBMQTT_API const char* MQTTReasonCode_toString(enum MQTTReasonCodes value);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2018 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(SUBOPTS_H)
|
||||
#define SUBOPTS_H
|
||||
|
||||
/** The MQTT V5 subscribe options, apart from QoS which existed before V5. */
|
||||
typedef struct MQTTSubscribe_options
|
||||
{
|
||||
/** The eyecatcher for this structure. Must be MQSO. */
|
||||
char struct_id[4];
|
||||
/** The version number of this structure. Must be 0.
|
||||
*/
|
||||
int struct_version;
|
||||
/** To not receive our own publications, set to 1.
|
||||
* 0 is the original MQTT behaviour - all messages matching the subscription are received.
|
||||
*/
|
||||
unsigned char noLocal;
|
||||
/** To keep the retain flag as on the original publish message, set to 1.
|
||||
* If 0, defaults to the original MQTT behaviour where the retain flag is only set on
|
||||
* publications sent by a broker if in response to a subscribe request.
|
||||
*/
|
||||
unsigned char retainAsPublished;
|
||||
/** 0 - send retained messages at the time of the subscribe (original MQTT behaviour)
|
||||
* 1 - send retained messages on subscribe only if the subscription is new
|
||||
* 2 - do not send retained messages at all
|
||||
*/
|
||||
unsigned char retainHandling;
|
||||
} MQTTSubscribe_options;
|
||||
|
||||
#define MQTTSubscribe_options_initializer { {'M', 'Q', 'S', 'O'}, 0, 0, 0, 0 }
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial implementation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MQTTTIME_H)
|
||||
#define MQTTTIME_H
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <windows.h>
|
||||
#define START_TIME_TYPE DWORD
|
||||
#define START_TIME_ZERO 0
|
||||
#elif defined(AIX)
|
||||
#define START_TIME_TYPE struct timespec
|
||||
#define START_TIME_ZERO {0, 0}
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#define START_TIME_TYPE struct timeval
|
||||
#define START_TIME_ZERO {0, 0}
|
||||
#endif
|
||||
|
||||
void MQTTTime_sleep(long milliseconds);
|
||||
START_TIME_TYPE MQTTTime_start_clock(void);
|
||||
START_TIME_TYPE MQTTTime_now(void);
|
||||
long MQTTTime_elapsed(START_TIME_TYPE milliseconds);
|
||||
long MQTTTime_difftime(START_TIME_TYPE new, START_TIME_TYPE old);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*******************************************************************************
|
||||
* 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 v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(MESSAGES_H)
|
||||
#define MESSAGES_H
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
const char* Messages_get(int, enum LOG_LEVELS);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2017 logi.cals GmbH
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Gunter Raidl - timer support for VxWorks
|
||||
* Rainer Poisel - reusability
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(OSWRAPPER_H)
|
||||
#define OSWRAPPER_H
|
||||
|
||||
#if defined(_WRS_KERNEL)
|
||||
#include <time.h>
|
||||
|
||||
#define lstat stat
|
||||
|
||||
typedef unsigned long useconds_t;
|
||||
void usleep(useconds_t useconds);
|
||||
|
||||
#define timersub(a, b, result) \
|
||||
do \
|
||||
{ \
|
||||
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
|
||||
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
|
||||
if ((result)->tv_usec < 0) \
|
||||
{ \
|
||||
--(result)->tv_sec; \
|
||||
(result)->tv_usec += 1000000L; \
|
||||
} \
|
||||
} while (0)
|
||||
#endif /* defined(_WRS_KERNEL) */
|
||||
|
||||
#endif /* OSWRAPPER_H */
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2019 Wind River Systems, Inc. All Rights Reserved.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Keith Holman - initial implementation and documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(SHA1_H)
|
||||
#define SHA1_H
|
||||
|
||||
#if defined(OPENSSL)
|
||||
#include <openssl/sha.h>
|
||||
|
||||
/** SHA-1 Digest Length */
|
||||
#define SHA1_DIGEST_LENGTH SHA_DIGEST_LENGTH
|
||||
|
||||
#else /* if defined(OPENSSL) */
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
typedef struct SHA_CTX_S
|
||||
{
|
||||
HCRYPTPROV hProv;
|
||||
HCRYPTHASH hHash;
|
||||
} SHA_CTX;
|
||||
#else /* if defined(_WIN32) || defined(_WIN64) */
|
||||
|
||||
#include <stdint.h>
|
||||
typedef struct SHA_CTX_S {
|
||||
uint32_t h[5];
|
||||
union {
|
||||
uint32_t w[16];
|
||||
uint8_t buffer[64];
|
||||
};
|
||||
unsigned int size;
|
||||
unsigned int total;
|
||||
} SHA_CTX;
|
||||
#endif /* else if defined(_WIN32) || defined(_WIN64) */
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/** SHA-1 Digest Length (number of bytes in SHA1) */
|
||||
#define SHA1_DIGEST_LENGTH (160/8)
|
||||
|
||||
/**
|
||||
* Initializes the SHA1 hashing algorithm
|
||||
*
|
||||
* @param[in,out] ctx hashing context structure
|
||||
*
|
||||
* @see SHA1_Update
|
||||
* @see SHA1_Final
|
||||
*/
|
||||
int SHA1_Init(SHA_CTX *ctx);
|
||||
|
||||
/**
|
||||
* Updates a block to the SHA1 hash
|
||||
*
|
||||
* @param[in,out] ctx hashing context structure
|
||||
* @param[in] data block of data to hash
|
||||
* @param[in] len length of block to hash
|
||||
*
|
||||
* @see SHA1_Init
|
||||
* @see SHA1_Final
|
||||
*/
|
||||
int SHA1_Update(SHA_CTX *ctx, const void *data, size_t len);
|
||||
|
||||
/**
|
||||
* Produce final SHA1 hash
|
||||
*
|
||||
* @param[out] md SHA1 hash produced (must be atleast
|
||||
* @p SHA1_DIGEST_LENGTH in length)
|
||||
* @param[in,out] ctx hashing context structure
|
||||
*
|
||||
* @see SHA1_Init
|
||||
* @see SHA1_Final
|
||||
*/
|
||||
int SHA1_Final(unsigned char *md, SHA_CTX *ctx);
|
||||
|
||||
#endif /* if defined(OPENSSL) */
|
||||
#endif /* SHA1_H */
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2018 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs, Allan Stockdill-Mander - initial implementation
|
||||
* Ian Craggs - SNI support
|
||||
* Ian Craggs - post connect checks and CApath
|
||||
*******************************************************************************/
|
||||
#if !defined(SSLSOCKET_H)
|
||||
#define SSLSOCKET_H
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define ssl_mutex_type HANDLE
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#define ssl_mutex_type pthread_mutex_t
|
||||
#endif
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include "SocketBuffer.h"
|
||||
#include "Clients.h"
|
||||
|
||||
#define URI_SSL "ssl://"
|
||||
|
||||
/** if we should handle openssl initialization (bool_value == 1) or depend on it to be initalized externally (bool_value == 0) */
|
||||
void SSLSocket_handleOpensslInit(int bool_value);
|
||||
|
||||
int SSLSocket_initialize(void);
|
||||
void SSLSocket_terminate(void);
|
||||
int SSLSocket_setSocketForSSL(networkHandles* net, MQTTClient_SSLOptions* opts, const char* hostname, size_t hostname_len);
|
||||
|
||||
int SSLSocket_getch(SSL* ssl, int socket, char* c);
|
||||
char *SSLSocket_getdata(SSL* ssl, int socket, size_t bytes, size_t* actual_len);
|
||||
|
||||
int SSLSocket_close(networkHandles* net);
|
||||
int SSLSocket_putdatas(SSL* ssl, int socket, char* buf0, size_t buf0len, int count, char** buffers, size_t* buflens, int* frees);
|
||||
int SSLSocket_connect(SSL* ssl, int sock, const char* hostname, int verify, int (*cb)(const char *str, size_t len, void *u), void* u);
|
||||
|
||||
int SSLSocket_getPendingRead(void);
|
||||
int SSLSocket_continueWrite(pending_writes* pw);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2019 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial implementation and documentation
|
||||
* Ian Craggs - async client updates
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(SOCKET_H)
|
||||
#define SOCKET_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <errno.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#define MAXHOSTNAMELEN 256
|
||||
#if !defined(SSLSOCKET_H)
|
||||
#undef EAGAIN
|
||||
#define EAGAIN WSAEWOULDBLOCK
|
||||
#undef EINTR
|
||||
#define EINTR WSAEINTR
|
||||
#undef EINPROGRESS
|
||||
#define EINPROGRESS WSAEINPROGRESS
|
||||
#undef EWOULDBLOCK
|
||||
#define EWOULDBLOCK WSAEWOULDBLOCK
|
||||
#undef ENOTCONN
|
||||
#define ENOTCONN WSAENOTCONN
|
||||
#undef ECONNRESET
|
||||
#define ECONNRESET WSAECONNRESET
|
||||
#undef ETIMEDOUT
|
||||
#define ETIMEDOUT WAIT_TIMEOUT
|
||||
#endif
|
||||
#define ioctl ioctlsocket
|
||||
#define socklen_t int
|
||||
#else
|
||||
#define INVALID_SOCKET SOCKET_ERROR
|
||||
#include <sys/socket.h>
|
||||
#if !defined(_WRS_KERNEL)
|
||||
#include <sys/param.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/uio.h>
|
||||
#else
|
||||
#include <selectLib.h>
|
||||
#endif
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#define ULONG size_t
|
||||
#endif
|
||||
|
||||
#include "mutex_type.h" /* Needed for mutex_type */
|
||||
|
||||
/** socket operation completed successfully */
|
||||
#define TCPSOCKET_COMPLETE 0
|
||||
#if !defined(SOCKET_ERROR)
|
||||
/** error in socket operation */
|
||||
#define SOCKET_ERROR -1
|
||||
#endif
|
||||
/** must be the same as SOCKETBUFFER_INTERRUPTED */
|
||||
#define TCPSOCKET_INTERRUPTED -22
|
||||
#define SSL_FATAL -3
|
||||
|
||||
#if !defined(INET6_ADDRSTRLEN)
|
||||
#define INET6_ADDRSTRLEN 46 /** only needed for gcc/cygwin on windows */
|
||||
#endif
|
||||
|
||||
|
||||
#if !defined(max)
|
||||
#define max(A,B) ( (A) > (B) ? (A):(B))
|
||||
#endif
|
||||
|
||||
#include "LinkedList.h"
|
||||
|
||||
/*BE
|
||||
def FD_SET
|
||||
{
|
||||
128 n8 "data"
|
||||
}
|
||||
|
||||
def SOCKETS
|
||||
{
|
||||
FD_SET "rset"
|
||||
FD_SET "rset_saved"
|
||||
n32 dec "maxfdp1"
|
||||
n32 ptr INTList "clientsds"
|
||||
n32 ptr INTItem "cur_clientsds"
|
||||
n32 ptr INTList "connect_pending"
|
||||
n32 ptr INTList "write_pending"
|
||||
FD_SET "pending_wset"
|
||||
}
|
||||
BE*/
|
||||
|
||||
|
||||
/**
|
||||
* Structure to hold all socket data for the module
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
fd_set rset, /**< socket read set (see select doc) */
|
||||
rset_saved; /**< saved socket read set */
|
||||
int maxfdp1; /**< max descriptor used +1 (again see select doc) */
|
||||
List* clientsds; /**< list of client socket descriptors */
|
||||
ListElement* cur_clientsds; /**< current client socket descriptor (iterator) */
|
||||
List* connect_pending; /**< list of sockets for which a connect is pending */
|
||||
List* write_pending; /**< list of sockets for which a write is pending */
|
||||
fd_set pending_wset; /**< socket pending write set for select */
|
||||
} Sockets;
|
||||
|
||||
|
||||
void Socket_outInitialize(void);
|
||||
void Socket_outTerminate(void);
|
||||
int Socket_getReadySocket(int more_work, struct timeval *tp, mutex_type mutex);
|
||||
int Socket_getch(int socket, char* c);
|
||||
char *Socket_getdata(int socket, size_t bytes, size_t* actual_len);
|
||||
int Socket_putdatas(int socket, char* buf0, size_t buf0len, int count, char** buffers, size_t* buflens, int* frees);
|
||||
void Socket_close(int socket);
|
||||
#if defined(__GNUC__) && defined(__linux__)
|
||||
/* able to use GNU's getaddrinfo_a to make timeouts possible */
|
||||
int Socket_new(const char* addr, size_t addr_len, int port, int* socket, long timeout);
|
||||
#else
|
||||
int Socket_new(const char* addr, size_t addr_len, int port, int* socket);
|
||||
#endif
|
||||
|
||||
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, int rc);
|
||||
void Socket_setWriteCompleteCallback(Socket_writeComplete*);
|
||||
|
||||
#endif /* SOCKET_H */
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs, Allan Stockdill-Mander - SSL updates
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(SOCKETBUFFER_H)
|
||||
#define SOCKETBUFFER_H
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#if defined(OPENSSL)
|
||||
#include <openssl/ssl.h>
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
typedef WSABUF iobuf;
|
||||
#else
|
||||
typedef struct iovec iobuf;
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int socket;
|
||||
unsigned int index;
|
||||
size_t headerlen;
|
||||
char fixed_header[5]; /**< header plus up to 4 length bytes */
|
||||
size_t buflen, /**< total length of the buffer */
|
||||
datalen; /**< current length of data in buf */
|
||||
char* buf;
|
||||
} socket_queue;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int socket, count;
|
||||
size_t total;
|
||||
#if defined(OPENSSL)
|
||||
SSL* ssl;
|
||||
#endif
|
||||
size_t bytes;
|
||||
iobuf iovecs[5];
|
||||
int frees[5];
|
||||
} pending_writes;
|
||||
|
||||
#define SOCKETBUFFER_COMPLETE 0
|
||||
#if !defined(SOCKET_ERROR)
|
||||
#define SOCKET_ERROR -1
|
||||
#endif
|
||||
#define SOCKETBUFFER_INTERRUPTED -22 /* must be the same value as TCPSOCKET_INTERRUPTED */
|
||||
|
||||
int SocketBuffer_initialize(void);
|
||||
void SocketBuffer_terminate(void);
|
||||
void SocketBuffer_cleanup(int socket);
|
||||
char* SocketBuffer_getQueuedData(int socket, size_t bytes, size_t* actual_len);
|
||||
int SocketBuffer_getQueuedChar(int socket, char* c);
|
||||
void SocketBuffer_interrupted(int socket, size_t actual_len);
|
||||
char* SocketBuffer_complete(int socket);
|
||||
void SocketBuffer_queueChar(int socket, char c);
|
||||
|
||||
#if defined(OPENSSL)
|
||||
int SocketBuffer_pendingWrite(int socket, SSL* ssl, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes);
|
||||
#else
|
||||
int SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes);
|
||||
#endif
|
||||
pending_writes* SocketBuffer_getWrite(int socket);
|
||||
int SocketBuffer_writeComplete(int socket);
|
||||
pending_writes* SocketBuffer_updateWrite(int socket, char* topic, char* payload);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef STACKTRACE_H_
|
||||
#define STACKTRACE_H_
|
||||
|
||||
#if defined(HIGH_PERFORMANCE)
|
||||
#define NOSTACKTRACE 1
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include "Log.h"
|
||||
#include "Thread.h"
|
||||
|
||||
#if defined(NOSTACKTRACE)
|
||||
#define FUNC_ENTRY
|
||||
#define FUNC_ENTRY_NOLOG
|
||||
#define FUNC_ENTRY_MED
|
||||
#define FUNC_ENTRY_MAX
|
||||
#define FUNC_EXIT
|
||||
#define FUNC_EXIT_NOLOG
|
||||
#define FUNC_EXIT_MED
|
||||
#define FUNC_EXIT_MAX
|
||||
#define FUNC_EXIT_RC(x)
|
||||
#define FUNC_EXIT_MED_RC(x)
|
||||
#define FUNC_EXIT_MAX_RC(x)
|
||||
#else
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define inline __inline
|
||||
#define FUNC_ENTRY StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MINIMUM)
|
||||
#define FUNC_ENTRY_NOLOG StackTrace_entry(__FUNCTION__, __LINE__, -1)
|
||||
#define FUNC_ENTRY_MED StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MEDIUM)
|
||||
#define FUNC_ENTRY_MAX StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MAXIMUM)
|
||||
#define FUNC_EXIT StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MINIMUM)
|
||||
#define FUNC_EXIT_NOLOG StackTrace_exit(__FUNCTION__, __LINE__, NULL, -1)
|
||||
#define FUNC_EXIT_MED StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MEDIUM)
|
||||
#define FUNC_EXIT_MAX StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MAXIMUM)
|
||||
#define FUNC_EXIT_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MINIMUM)
|
||||
#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MEDIUM)
|
||||
#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MAXIMUM)
|
||||
#else
|
||||
#define FUNC_ENTRY StackTrace_entry(__func__, __LINE__, TRACE_MINIMUM)
|
||||
#define FUNC_ENTRY_NOLOG StackTrace_entry(__func__, __LINE__, -1)
|
||||
#define FUNC_ENTRY_MED StackTrace_entry(__func__, __LINE__, TRACE_MEDIUM)
|
||||
#define FUNC_ENTRY_MAX StackTrace_entry(__func__, __LINE__, TRACE_MAXIMUM)
|
||||
#define FUNC_EXIT StackTrace_exit(__func__, __LINE__, NULL, TRACE_MINIMUM)
|
||||
#define FUNC_EXIT_NOLOG StackTrace_exit(__func__, __LINE__, NULL, -1)
|
||||
#define FUNC_EXIT_MED StackTrace_exit(__func__, __LINE__, NULL, TRACE_MEDIUM)
|
||||
#define FUNC_EXIT_MAX StackTrace_exit(__func__, __LINE__, NULL, TRACE_MAXIMUM)
|
||||
#define FUNC_EXIT_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MINIMUM)
|
||||
#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MEDIUM)
|
||||
#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MAXIMUM)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void StackTrace_entry(const char* name, int line, enum LOG_LEVELS trace);
|
||||
void StackTrace_exit(const char* name, int line, void* return_value, enum LOG_LEVELS trace);
|
||||
|
||||
void StackTrace_printStack(FILE* dest);
|
||||
char* StackTrace_get(thread_id_type, char* buf, int bufsize);
|
||||
|
||||
#endif /* STACKTRACE_H_ */
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial implementation
|
||||
* Ian Craggs, Allan Stockdill-Mander - async client updates
|
||||
* Ian Craggs - fix for bug #420851
|
||||
* Ian Craggs - change MacOS semaphore implementation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(THREAD_H)
|
||||
#define THREAD_H
|
||||
|
||||
#include "MQTTExportDeclarations.h"
|
||||
|
||||
#include "MQTTClient.h"
|
||||
|
||||
#include "mutex_type.h" /* Needed for mutex_type */
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <windows.h>
|
||||
#define thread_type HANDLE
|
||||
#define thread_id_type DWORD
|
||||
#define thread_return_type DWORD
|
||||
#define thread_fn LPTHREAD_START_ROUTINE
|
||||
#define cond_type HANDLE
|
||||
#define sem_type HANDLE
|
||||
#undef ETIMEDOUT
|
||||
#define ETIMEDOUT WSAETIMEDOUT
|
||||
#else
|
||||
#include <pthread.h>
|
||||
|
||||
#define thread_type pthread_t
|
||||
#define thread_id_type pthread_t
|
||||
#define thread_return_type void*
|
||||
typedef thread_return_type (*thread_fn)(void*);
|
||||
typedef struct { pthread_cond_t cond; pthread_mutex_t mutex; } cond_type_struct;
|
||||
typedef cond_type_struct *cond_type;
|
||||
#if defined(OSX)
|
||||
#include <dispatch/dispatch.h>
|
||||
typedef dispatch_semaphore_t sem_type;
|
||||
#else
|
||||
#include <semaphore.h>
|
||||
typedef sem_t *sem_type;
|
||||
#endif
|
||||
|
||||
cond_type Thread_create_cond(int*);
|
||||
int Thread_signal_cond(cond_type);
|
||||
int Thread_wait_cond(cond_type condvar, int timeout);
|
||||
int Thread_destroy_cond(cond_type);
|
||||
#endif
|
||||
|
||||
LIBMQTT_API thread_type Thread_start(thread_fn, void*);
|
||||
|
||||
LIBMQTT_API mutex_type Thread_create_mutex(int*);
|
||||
LIBMQTT_API int Thread_lock_mutex(mutex_type);
|
||||
LIBMQTT_API int Thread_unlock_mutex(mutex_type);
|
||||
int Thread_destroy_mutex(mutex_type);
|
||||
|
||||
LIBMQTT_API thread_id_type Thread_getid();
|
||||
|
||||
sem_type Thread_create_sem(int*);
|
||||
int Thread_wait_sem(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);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/*******************************************************************************
|
||||
* 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 v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial implementation and documentation
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
#if !defined(TREE_H)
|
||||
#define TREE_H
|
||||
|
||||
#include <stdlib.h> /* for size_t definition */
|
||||
|
||||
/*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 */
|
||||
size_t 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_t 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, size_t 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
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef VERSIONINFO_H
|
||||
#define VERSIONINFO_H
|
||||
|
||||
#define BUILD_TIMESTAMP "2020-05-23T04:16:51Z"
|
||||
#define CLIENT_VERSION "1.3.2"
|
||||
|
||||
#endif /* VERSIONINFO_H */
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2020 Wind River Systems, Inc. and others. All Rights Reserved.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Keith Holman - initial implementation and documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(WEBSOCKET_H)
|
||||
#define WEBSOCKET_H
|
||||
|
||||
#include "Clients.h"
|
||||
|
||||
/**
|
||||
* WebSocket op codes
|
||||
* @{
|
||||
*/
|
||||
#define WebSocket_OP_CONTINUE 0x0 /* 0000 - continue frame */
|
||||
#define WebSocket_OP_TEXT 0x1 /* 0001 - text frame */
|
||||
#define WebSocket_OP_BINARY 0x2 /* 0010 - binary frame */
|
||||
#define WebSocket_OP_CLOSE 0x8 /* 1000 - close frame */
|
||||
#define WebSocket_OP_PING 0x9 /* 1001 - ping frame */
|
||||
#define WebSocket_OP_PONG 0xA /* 1010 - pong frame */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Various close status codes
|
||||
* @{
|
||||
*/
|
||||
#define WebSocket_CLOSE_NORMAL 1000
|
||||
#define WebSocket_CLOSE_GOING_AWAY 1001
|
||||
#define WebSocket_CLOSE_PROTOCOL_ERROR 1002
|
||||
#define WebSocket_CLOSE_UNKNOWN_DATA 1003
|
||||
#define WebSocket_CLOSE_RESERVED 1004
|
||||
#define WebSocket_CLOSE_NO_STATUS_CODE 1005 /* reserved: not to be used */
|
||||
#define WebSocket_CLOSE_ABNORMAL 1006 /* reserved: not to be used */
|
||||
#define WebSocket_CLOSE_BAD_DATA 1007
|
||||
#define WebSocket_CLOSE_POLICY 1008
|
||||
#define WebSocket_CLOSE_MSG_TOO_BIG 1009
|
||||
#define WebSocket_CLOSE_NO_EXTENSION 1010
|
||||
#define WebScoket_CLOSE_UNEXPECTED 1011
|
||||
#define WebSocket_CLOSE_TLS_FAIL 1015 /* reserved: not be used */
|
||||
/** @} */
|
||||
|
||||
/* closes a websocket connection */
|
||||
void WebSocket_close(networkHandles *net, int status_code, const char *reason);
|
||||
|
||||
/* sends upgrade request */
|
||||
int WebSocket_connect(networkHandles *net, const char *uri);
|
||||
|
||||
/* obtain data from network socket */
|
||||
int WebSocket_getch(networkHandles *net, char* c);
|
||||
char *WebSocket_getdata(networkHandles *net, size_t bytes, size_t* actual_len);
|
||||
|
||||
/* send data out, in websocket format only if required */
|
||||
int WebSocket_putdatas(networkHandles* net, char** buf0, size_t* buf0len,
|
||||
int count, char** buffers, size_t* buflens, int* freeData);
|
||||
|
||||
/* releases any resources used by the websocket system */
|
||||
void WebSocket_terminate(void);
|
||||
|
||||
/* handles websocket upgrade request */
|
||||
int WebSocket_upgrade(networkHandles *net);
|
||||
|
||||
/* Notify the IP address and port of the endpoint to proxy, and wait connection to endpoint */
|
||||
int WebSocket_proxy_connect( networkHandles *net, int ssl, const char *hostname);
|
||||
|
||||
#endif /* WEBSOCKET_H */
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2018 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
*******************************************************************************/
|
||||
#if !defined(_MUTEX_TYPE_H_)
|
||||
#define _MUTEX_TYPE_H_
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <windows.h>
|
||||
#define mutex_type HANDLE
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#define mutex_type pthread_mutex_t*
|
||||
#endif
|
||||
|
||||
#endif /* _MUTEX_TYPE_H_ */
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*******************************************************************************
|
||||
* 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 v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#if !defined(UTF8_H)
|
||||
#define UTF8_H
|
||||
|
||||
int UTF8_validate(int len, const char *data);
|
||||
int UTF8_validateString(const char* string);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,352 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2019 Wind River Systems, Inc. All Rights Reserved.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Keith Holman - initial implementation and documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#include "Base64.h"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
b64_size_t Base64_decode( b64_data_t *out, b64_size_t out_len, const char *in, b64_size_t in_len )
|
||||
{
|
||||
b64_size_t ret = 0u;
|
||||
DWORD dw_out_len = (DWORD)out_len;
|
||||
if ( CryptStringToBinaryA( in, in_len, CRYPT_STRING_BASE64, out, &dw_out_len, NULL, NULL ) )
|
||||
ret = (b64_size_t)dw_out_len;
|
||||
return ret;
|
||||
}
|
||||
|
||||
b64_size_t Base64_encode( char *out, b64_size_t out_len, const b64_data_t *in, b64_size_t in_len )
|
||||
{
|
||||
b64_size_t ret = 0u;
|
||||
DWORD dw_out_len = (DWORD)out_len;
|
||||
if ( CryptBinaryToStringA( in, in_len, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, out, &dw_out_len ) )
|
||||
ret = (b64_size_t)dw_out_len;
|
||||
return ret;
|
||||
}
|
||||
#else /* if defined(_WIN32) || defined(_WIN64) */
|
||||
|
||||
#if defined(OPENSSL)
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/evp.h>
|
||||
static b64_size_t Base64_encodeDecode(
|
||||
char *out, b64_size_t out_len, const char *in, b64_size_t in_len, int encode )
|
||||
{
|
||||
b64_size_t ret = 0u;
|
||||
if ( in_len > 0u )
|
||||
{
|
||||
int rv;
|
||||
BIO *bio, *b64, *b_in, *b_out;
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
b64 = BIO_push(b64, bio);
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); /* ignore new-lines */
|
||||
|
||||
if ( encode )
|
||||
{
|
||||
b_in = bio;
|
||||
b_out = b64;
|
||||
}
|
||||
else
|
||||
{
|
||||
b_in = b64;
|
||||
b_out = bio;
|
||||
}
|
||||
|
||||
rv = BIO_write(b_out, in, (int)in_len);
|
||||
BIO_flush(b_out); /* indicate end of encoding */
|
||||
|
||||
if ( rv > 0 )
|
||||
{
|
||||
rv = BIO_read(b_in, out, (int)out_len);
|
||||
if ( rv > 0 )
|
||||
{
|
||||
ret = (b64_size_t)rv;
|
||||
if ( out_len > ret )
|
||||
out[ret] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
BIO_free_all(b64); /* free all used memory */
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
b64_size_t Base64_decode( b64_data_t *out, b64_size_t out_len, const char *in, b64_size_t in_len )
|
||||
{
|
||||
return Base64_encodeDecode( (char*)out, out_len, in, in_len, 0 );
|
||||
}
|
||||
|
||||
b64_size_t Base64_encode( char *out, b64_size_t out_len, const b64_data_t *in, b64_size_t in_len )
|
||||
{
|
||||
return Base64_encodeDecode( out, out_len, (const char*)in, in_len, 1 );
|
||||
}
|
||||
|
||||
#else /* if defined(OPENSSL) */
|
||||
b64_size_t Base64_decode( b64_data_t *out, b64_size_t out_len, const char *in, b64_size_t in_len )
|
||||
{
|
||||
#define NV 64
|
||||
static const unsigned char BASE64_DECODE_TABLE[] =
|
||||
{
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, /* 0-15 */
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, /* 16-31 */
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, 62, NV, NV, NV, 63, /* 32-47 */
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NV, NV, NV, NV, NV, NV, /* 48-63 */
|
||||
NV, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 64-79 */
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NV, NV, NV, NV, NV, /* 80-95 */
|
||||
NV, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 96-111 */
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NV, NV, NV, NV, NV, /* 112-127 */
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, /* 128-143 */
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, /* 144-159 */
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, /* 160-175 */
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, /* 176-191 */
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, /* 192-207 */
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, /* 208-223 */
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, /* 224-239 */
|
||||
NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV /* 240-255 */
|
||||
};
|
||||
|
||||
b64_size_t ret = 0u;
|
||||
b64_size_t out_count = 0u;
|
||||
|
||||
/* in valid base64, length must be multiple of 4's: 0, 4, 8, 12, etc */
|
||||
while ( in_len > 3u && out_count < out_len )
|
||||
{
|
||||
int i;
|
||||
unsigned char c[4];
|
||||
for ( i = 0; i < 4; ++i, ++in )
|
||||
c[i] = BASE64_DECODE_TABLE[(int)(*in)];
|
||||
in_len -= 4u;
|
||||
|
||||
/* first byte */
|
||||
*out = c[0] << 2;
|
||||
*out |= (c[1] & ~0xF) >> 4;
|
||||
++out;
|
||||
++out_count;
|
||||
|
||||
if ( out_count < out_len )
|
||||
{
|
||||
/* second byte */
|
||||
*out = (c[1] & 0xF) << 4;
|
||||
if ( c[2] < NV )
|
||||
{
|
||||
*out |= (c[2] & ~0x3) >> 2;
|
||||
++out;
|
||||
++out_count;
|
||||
|
||||
if ( out_count < out_len )
|
||||
{
|
||||
/* third byte */
|
||||
*out = (c[2] & 0x3) << 6;
|
||||
if ( c[3] < NV )
|
||||
{
|
||||
*out |= c[3];
|
||||
++out;
|
||||
++out_count;
|
||||
}
|
||||
else
|
||||
in_len = 0u;
|
||||
}
|
||||
}
|
||||
else
|
||||
in_len = 0u;
|
||||
}
|
||||
}
|
||||
|
||||
if ( out_count <= out_len )
|
||||
{
|
||||
ret = out_count;
|
||||
if ( out_count < out_len )
|
||||
*out = '\0';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
b64_size_t Base64_encode( char *out, b64_size_t out_len, const b64_data_t *in, b64_size_t in_len )
|
||||
{
|
||||
static const char BASE64_ENCODE_TABLE[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/=";
|
||||
b64_size_t ret = 0u;
|
||||
b64_size_t out_count = 0u;
|
||||
|
||||
while ( in_len > 0u && out_count < out_len )
|
||||
{
|
||||
int i;
|
||||
unsigned char c[] = { 0, 0, 64, 64 }; /* index of '=' char */
|
||||
|
||||
/* first character */
|
||||
i = *in;
|
||||
c[0] = (i & ~0x3) >> 2;
|
||||
|
||||
/* second character */
|
||||
c[1] = (i & 0x3) << 4;
|
||||
--in_len;
|
||||
if ( in_len > 0u )
|
||||
{
|
||||
++in;
|
||||
i = *in;
|
||||
c[1] |= (i & ~0xF) >> 4;
|
||||
|
||||
/* third character */
|
||||
c[2] = (i & 0xF) << 2;
|
||||
--in_len;
|
||||
if ( in_len > 0u )
|
||||
{
|
||||
++in;
|
||||
i = *in;
|
||||
c[2] |= (i & ~0x3F) >> 6;
|
||||
|
||||
/* fourth character */
|
||||
c[3] = (i & 0x3F);
|
||||
--in_len;
|
||||
++in;
|
||||
}
|
||||
}
|
||||
|
||||
/* encode the characters */
|
||||
out_count += 4u;
|
||||
for ( i = 0; i < 4 && out_count <= out_len; ++i, ++out )
|
||||
*out = BASE64_ENCODE_TABLE[c[i]];
|
||||
}
|
||||
|
||||
if ( out_count <= out_len )
|
||||
{
|
||||
if ( out_count < out_len )
|
||||
*out = '\0';
|
||||
ret = out_count;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif /* else if defined(OPENSSL) */
|
||||
#endif /* if else defined(_WIN32) || defined(_WIN64) */
|
||||
|
||||
b64_size_t Base64_decodeLength( const char *in, b64_size_t in_len )
|
||||
{
|
||||
b64_size_t pad = 0u;
|
||||
|
||||
if ( in && in_len > 1u )
|
||||
pad += ( in[in_len - 2u] == '=' ? 1u : 0u );
|
||||
if ( in && in_len > 0u )
|
||||
pad += ( in[in_len - 1u] == '=' ? 1u : 0u );
|
||||
return (in_len / 4u * 3u) - pad;
|
||||
}
|
||||
|
||||
b64_size_t Base64_encodeLength( const b64_data_t *in, b64_size_t in_len )
|
||||
{
|
||||
return ((4u * in_len / 3u) + 3u) & ~0x3;
|
||||
}
|
||||
|
||||
#if defined(BASE64_TEST)
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TEST_EXPECT(i,x) if (!(x)) {fprintf( stderr, "failed test: %s (for i == %d)\n", #x, i ); ++fails;}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct _td
|
||||
{
|
||||
const char *in;
|
||||
const char *out;
|
||||
};
|
||||
|
||||
int i;
|
||||
unsigned int fails = 0u;
|
||||
struct _td test_data[] = {
|
||||
{ "", "" },
|
||||
{ "p", "cA==" },
|
||||
{ "pa", "cGE=" },
|
||||
{ "pah", "cGFo" },
|
||||
{ "paho", "cGFobw==" },
|
||||
{ "paho ", "cGFobyA=" },
|
||||
{ "paho w", "cGFobyB3" },
|
||||
{ "paho wi", "cGFobyB3aQ==" },
|
||||
{ "paho wit", "cGFobyB3aXQ=" },
|
||||
{ "paho with", "cGFobyB3aXRo" },
|
||||
{ "paho with ", "cGFobyB3aXRoIA==" },
|
||||
{ "paho with w", "cGFobyB3aXRoIHc=" },
|
||||
{ "paho with we", "cGFobyB3aXRoIHdl" },
|
||||
{ "paho with web", "cGFobyB3aXRoIHdlYg==" },
|
||||
{ "paho with webs", "cGFobyB3aXRoIHdlYnM=" },
|
||||
{ "paho with webso", "cGFobyB3aXRoIHdlYnNv" },
|
||||
{ "paho with websoc", "cGFobyB3aXRoIHdlYnNvYw==" },
|
||||
{ "paho with websock", "cGFobyB3aXRoIHdlYnNvY2s=" },
|
||||
{ "paho with websocke", "cGFobyB3aXRoIHdlYnNvY2tl" },
|
||||
{ "paho with websocket", "cGFobyB3aXRoIHdlYnNvY2tldA==" },
|
||||
{ "paho with websockets", "cGFobyB3aXRoIHdlYnNvY2tldHM=" },
|
||||
{ "paho with websockets.", "cGFobyB3aXRoIHdlYnNvY2tldHMu" },
|
||||
{ "The quick brown fox jumps over the lazy dog",
|
||||
"VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==" },
|
||||
{ "Man is distinguished, not only by his reason, but by this singular passion from\n"
|
||||
"other animals, which is a lust of the mind, that by a perseverance of delight\n"
|
||||
"in the continued and indefatigable generation of knowledge, exceeds the short\n"
|
||||
"vehemence of any carnal pleasure.",
|
||||
"TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz"
|
||||
"IHNpbmd1bGFyIHBhc3Npb24gZnJvbQpvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg"
|
||||
"dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodAppbiB0aGUgY29udGlu"
|
||||
"dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo"
|
||||
"ZSBzaG9ydAp2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* decode tests */
|
||||
i = 0;
|
||||
while ( test_data[i].in != NULL )
|
||||
{
|
||||
int r;
|
||||
char out[512u];
|
||||
r = Base64_decode( out, sizeof(out), test_data[i].out, strlen(test_data[i].out) );
|
||||
TEST_EXPECT( i, r == strlen(test_data[i].in) && strncmp(out, test_data[i].in, strlen(test_data[i].in)) == 0 );
|
||||
++i;
|
||||
}
|
||||
|
||||
/* decode length tests */
|
||||
i = 0;
|
||||
while ( test_data[i].in != NULL )
|
||||
{
|
||||
TEST_EXPECT( i, Base64_decodeLength(test_data[i].out, strlen(test_data[i].out)) == strlen(test_data[i].in));
|
||||
++i;
|
||||
}
|
||||
|
||||
/* encode tests */
|
||||
i = 0;
|
||||
while ( test_data[i].in != NULL )
|
||||
{
|
||||
int r;
|
||||
char out[512u];
|
||||
r = Base64_encode( out, sizeof(out), test_data[i].in, strlen(test_data[i].in) );
|
||||
TEST_EXPECT( i, r == strlen(test_data[i].out) && strncmp(out, test_data[i].out, strlen(test_data[i].out)) == 0 );
|
||||
++i;
|
||||
}
|
||||
|
||||
/* encode length tests */
|
||||
i = 0;
|
||||
while ( test_data[i].in != NULL )
|
||||
{
|
||||
TEST_EXPECT( i, Base64_encodeLength(test_data[i].in, strlen(test_data[i].in)) == strlen(test_data[i].out) );
|
||||
++i;
|
||||
}
|
||||
|
||||
if ( fails )
|
||||
printf( "%u test failed!\n", fails );
|
||||
else
|
||||
printf( "all tests passed\n" );
|
||||
return fails;
|
||||
}
|
||||
#endif /* if defined(BASE64_TEST) */
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*******************************************************************************
|
||||
* 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 v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - add SSL support
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief functions which apply to client structures
|
||||
* */
|
||||
|
||||
|
||||
#include "Clients.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
/**
|
||||
* List callback function for comparing clients by clientid
|
||||
* @param a first integer value
|
||||
* @param b second integer value
|
||||
* @return boolean indicating whether a and b are equal
|
||||
*/
|
||||
int clientIDCompare(void* a, void* b)
|
||||
{
|
||||
Clients* client = (Clients*)a;
|
||||
/*printf("comparing clientdIDs %s with %s\n", client->clientID, (char*)b);*/
|
||||
return strcmp(client->clientID, (char*)b) == 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 clientSocketCompare(void* a, void* b)
|
||||
{
|
||||
Clients* client = (Clients*)a;
|
||||
/*printf("comparing %d with %d\n", (char*)a, (char*)b); */
|
||||
return client->net.socket == *(int*)b;
|
||||
}
|
||||
|
|
@ -0,0 +1,528 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - use tree data structure instead of list
|
||||
* Ian Craggs - change roundup to Heap_roundup to avoid macro name clash on MacOSX
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief functions to manage the heap with the goal of eliminating memory leaks
|
||||
*
|
||||
* For any module to use these functions transparently, simply include the Heap.h
|
||||
* header file. Malloc and free will be redefined, but will behave in exactly the same
|
||||
* way as normal, so no recoding is necessary.
|
||||
*
|
||||
* */
|
||||
|
||||
#include "Tree.h"
|
||||
#include "Log.h"
|
||||
#include "StackTrace.h"
|
||||
#include "Thread.h"
|
||||
|
||||
#if defined(HEAP_UNIT_TESTS)
|
||||
char* Broker_recordFFDC(char* symptoms);
|
||||
#endif /* HEAP_UNIT_TESTS */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "Heap.h"
|
||||
|
||||
#if !defined(NO_HEAP_TRACKING)
|
||||
|
||||
#undef malloc
|
||||
#undef realloc
|
||||
#undef free
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
mutex_type heap_mutex;
|
||||
#else
|
||||
static pthread_mutex_t heap_mutex_store = PTHREAD_MUTEX_INITIALIZER;
|
||||
static mutex_type heap_mutex = &heap_mutex_store;
|
||||
#endif
|
||||
|
||||
static heap_info state = {0, 0}; /**< global heap state information */
|
||||
|
||||
typedef double eyecatcherType;
|
||||
static eyecatcherType eyecatcher = (eyecatcherType)0x8888888888888888;
|
||||
|
||||
/*#define HEAP_STACK 1 */
|
||||
|
||||
/**
|
||||
* Each item on the heap is recorded with this structure.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
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 */
|
||||
size_t size; /**< size of the allocated storage */
|
||||
#if defined(HEAP_STACK)
|
||||
char* stack;
|
||||
#endif
|
||||
} storageElement;
|
||||
|
||||
static Tree heap; /**< Tree that holds the allocation records */
|
||||
static const char *errmsg = "Memory allocation error";
|
||||
|
||||
|
||||
static size_t Heap_roundup(size_t size);
|
||||
static int ptrCompare(void* a, void* b, int value);
|
||||
/*static void Heap_check(char* string, void* ptr);*/
|
||||
static void checkEyecatchers(char* file, int line, void* p, size_t size);
|
||||
static int Internal_heap_unlink(char* file, int line, void* p);
|
||||
static void HeapScan(enum LOG_LEVELS log_level);
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
static size_t Heap_roundup(size_t 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
|
||||
*/
|
||||
static 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;
|
||||
}
|
||||
|
||||
/*
|
||||
static void Heap_check(char* string, void* ptr)
|
||||
{
|
||||
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
|
||||
* 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.
|
||||
* @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 size the size of the item to be allocated
|
||||
* @return pointer to the allocated item, or NULL if there was an error
|
||||
*/
|
||||
void* mymalloc(char* file, int line, size_t size)
|
||||
{
|
||||
storageElement* s = NULL;
|
||||
size_t space = sizeof(storageElement);
|
||||
size_t filenamelen = strlen(file)+1;
|
||||
void* rc = NULL;
|
||||
|
||||
Thread_lock_mutex(heap_mutex);
|
||||
size = Heap_roundup(size);
|
||||
if ((s = malloc(sizeof(storageElement))) == NULL)
|
||||
{
|
||||
Log(LOG_ERROR, 13, errmsg);
|
||||
goto exit;
|
||||
}
|
||||
memset(s, 0, sizeof(storageElement));
|
||||
|
||||
s->size = size; /* size without eyecatchers */
|
||||
if ((s->file = malloc(filenamelen)) == NULL)
|
||||
{
|
||||
Log(LOG_ERROR, 13, errmsg);
|
||||
free(s);
|
||||
goto exit;
|
||||
}
|
||||
memset(s->file, 0, sizeof(filenamelen));
|
||||
|
||||
space += filenamelen;
|
||||
strcpy(s->file, file);
|
||||
#if defined(HEAP_STACK)
|
||||
#define STACK_LEN 300
|
||||
if ((s->stack = malloc(STACK_LEN)) == NULL)
|
||||
{
|
||||
Log(LOG_ERROR, 13, errmsg);
|
||||
free(s->file);
|
||||
free(s);
|
||||
goto exit;
|
||||
}
|
||||
memset(s->stack, 0, sizeof(filenamelen));
|
||||
StackTrace_get(Thread_getid(), s->stack, STACK_LEN);
|
||||
#endif
|
||||
s->line = line;
|
||||
/* Add space for eyecatcher at each end */
|
||||
if ((s->ptr = malloc(size + 2*sizeof(eyecatcherType))) == NULL)
|
||||
{
|
||||
Log(LOG_ERROR, 13, errmsg);
|
||||
free(s->file);
|
||||
free(s);
|
||||
goto exit;
|
||||
}
|
||||
memset(s->ptr, 0, size + 2*sizeof(eyecatcherType));
|
||||
space += size + 2*sizeof(eyecatcherType);
|
||||
*(eyecatcherType*)(s->ptr) = eyecatcher; /* start eyecatcher */
|
||||
*(eyecatcherType*)(((char*)(s->ptr)) + (sizeof(eyecatcherType) + size)) = eyecatcher; /* end eyecatcher */
|
||||
Log(TRACE_MAX, -1, "Allocating %d bytes in heap at file %s line %d ptr %p\n", (int)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;
|
||||
rc = ((eyecatcherType*)(s->ptr)) + 1; /* skip start eyecatcher */
|
||||
exit:
|
||||
Thread_unlock_mutex(heap_mutex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static void checkEyecatchers(char* file, int line, void* p, size_t size)
|
||||
{
|
||||
eyecatcherType *sp = (eyecatcherType*)p;
|
||||
char *cp = (char*)p;
|
||||
eyecatcherType us;
|
||||
static const 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);
|
||||
|
||||
cp += size;
|
||||
if ((us = *(eyecatcherType*)cp) != eyecatcher)
|
||||
Log(LOG_ERROR, 13, msg, "end", us, file, line);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
static int Internal_heap_unlink(char* file, int line, void* p)
|
||||
{
|
||||
Node* e = NULL;
|
||||
int rc = 0;
|
||||
|
||||
e = TreeFind(&heap, ((eyecatcherType*)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*)(e->content);
|
||||
Log(TRACE_MAX, -1, "Freeing %d bytes in heap at file %s line %d, heap use now %d bytes\n",
|
||||
(int)s->size, file, line, (int)state.current_size);
|
||||
checkEyecatchers(file, line, p, s->size);
|
||||
/* free(s->ptr); */
|
||||
free(s->file);
|
||||
state.current_size -= s->size;
|
||||
TreeRemoveNodeIndex(&heap, e, 0);
|
||||
free(s);
|
||||
rc = 1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Frees a block of memory. A direct replacement for free, but checks that a item is in
|
||||
* the allocates list first.
|
||||
* @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 freed
|
||||
*/
|
||||
void myfree(char* file, int line, void* p)
|
||||
{
|
||||
if (p) /* it is legal und usual to call free(NULL) */
|
||||
{
|
||||
Thread_lock_mutex(heap_mutex);
|
||||
if (Internal_heap_unlink(file, line, p))
|
||||
free(((eyecatcherType*)p)-1);
|
||||
Thread_unlock_mutex(heap_mutex);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(LOG_ERROR, -1, "Call of free(NULL) in %s,%d",file,line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
Thread_unlock_mutex(heap_mutex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param size the new size of the item
|
||||
* @return pointer to the allocated item, or NULL if there was an error
|
||||
*/
|
||||
void *myrealloc(char* file, int line, void* p, size_t size)
|
||||
{
|
||||
void* rc = NULL;
|
||||
storageElement* s = NULL;
|
||||
|
||||
Thread_lock_mutex(heap_mutex);
|
||||
s = TreeRemoveKey(&heap, ((eyecatcherType*)p)-1);
|
||||
if (s == NULL)
|
||||
Log(LOG_ERROR, 13, "Failed to reallocate heap item at file %s line %d", file, line);
|
||||
else
|
||||
{
|
||||
size_t space = sizeof(storageElement);
|
||||
size_t filenamelen = strlen(file)+1;
|
||||
|
||||
checkEyecatchers(file, line, p, s->size);
|
||||
size = Heap_roundup(size);
|
||||
state.current_size += size - s->size;
|
||||
if (state.current_size > state.max_size)
|
||||
state.max_size = state.current_size;
|
||||
if ((s->ptr = realloc(s->ptr, size + 2*sizeof(eyecatcherType))) == NULL)
|
||||
{
|
||||
Log(LOG_ERROR, 13, errmsg);
|
||||
goto exit;
|
||||
}
|
||||
space += size + 2*sizeof(eyecatcherType) - s->size;
|
||||
*(eyecatcherType*)(s->ptr) = eyecatcher; /* start eyecatcher */
|
||||
*(eyecatcherType*)(((char*)(s->ptr)) + (sizeof(eyecatcherType) + size)) = eyecatcher; /* end eyecatcher */
|
||||
s->size = size;
|
||||
space -= strlen(s->file);
|
||||
s->file = realloc(s->file, filenamelen);
|
||||
space += filenamelen;
|
||||
strcpy(s->file, file);
|
||||
s->line = line;
|
||||
rc = s->ptr;
|
||||
TreeAdd(&heap, s, space);
|
||||
}
|
||||
exit:
|
||||
Thread_unlock_mutex(heap_mutex);
|
||||
return (rc == NULL) ? NULL : ((eyecatcherType*)(rc)) + 1; /* skip start eyecatcher */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
Node* e = NULL;
|
||||
|
||||
Thread_lock_mutex(heap_mutex);
|
||||
e = TreeFind(&heap, ((eyecatcherType*)p)-1);
|
||||
Thread_unlock_mutex(heap_mutex);
|
||||
return (e == NULL) ? NULL : e->content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Scans the heap and reports any items currently allocated.
|
||||
* To be used at shutdown if any heap items have not been freed.
|
||||
*/
|
||||
static void HeapScan(enum LOG_LEVELS log_level)
|
||||
{
|
||||
Node* current = NULL;
|
||||
|
||||
Thread_lock_mutex(heap_mutex);
|
||||
Log(log_level, -1, "Heap scan start, total %d bytes", (int)state.current_size);
|
||||
while ((current = TreeNextElement(&heap, current)) != NULL)
|
||||
{
|
||||
storageElement* s = (storageElement*)(current->content);
|
||||
Log(log_level, -1, "Heap element size %d, line %d, file %s, ptr %p", (int)s->size, s->line, s->file, s->ptr);
|
||||
Log(log_level, -1, " Content %.*s", (10 > current->size) ? (int)s->size : 10, (char*)(((eyecatcherType*)s->ptr) + 1));
|
||||
#if defined(HEAP_STACK)
|
||||
Log(log_level, -1, " Stack:\n%s", s->stack);
|
||||
#endif
|
||||
}
|
||||
Log(log_level, -1, "Heap scan end");
|
||||
Thread_unlock_mutex(heap_mutex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Heap initialization.
|
||||
*/
|
||||
int Heap_initialize(void)
|
||||
{
|
||||
TreeInitializeNoMalloc(&heap, ptrCompare);
|
||||
heap.heap_tracking = 0; /* no recursive heap tracking! */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Heap termination.
|
||||
*/
|
||||
void Heap_terminate(void)
|
||||
{
|
||||
Log(TRACE_MIN, -1, "Maximum heap use was %d bytes", (int)state.max_size);
|
||||
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(LOG_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Access to heap state
|
||||
* @return pointer to the heap state structure
|
||||
*/
|
||||
heap_info* Heap_get_info(void)
|
||||
{
|
||||
return &state;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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, could be NULL
|
||||
*/
|
||||
int HeapDumpString(FILE* file, char* str)
|
||||
{
|
||||
int rc = 0;
|
||||
size_t len = str ? strlen(str) + 1 : 0; /* include the trailing null */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dump the state of the heap
|
||||
* @param file file handle to dump the heap contents to
|
||||
*/
|
||||
int HeapDump(FILE* file)
|
||||
{
|
||||
int rc = 0;
|
||||
Node* current = NULL;
|
||||
|
||||
while (rc == 0 && (current = TreeNextElement(&heap, current)))
|
||||
{
|
||||
storageElement* s = (storageElement*)(current->content);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(HEAP_UNIT_TESTS)
|
||||
|
||||
void Log(enum LOG_LEVELS log_level, int msgno, char* format, ...)
|
||||
{
|
||||
printf("Log %s", format);
|
||||
}
|
||||
|
||||
char* Broker_recordFFDC(char* symptoms)
|
||||
{
|
||||
printf("recordFFDC");
|
||||
return "";
|
||||
}
|
||||
|
||||
#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 /* HEAP_UNIT_TESTS */
|
||||
|
||||
/* Local Variables: */
|
||||
/* indent-tabs-mode: t */
|
||||
/* c-basic-offset: 8 */
|
||||
/* End: */
|
||||
|
|
@ -0,0 +1,508 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - updates for the async client
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief functions which apply to linked list structures.
|
||||
*
|
||||
* These linked lists can hold data of any sort, pointed to by the content pointer of the
|
||||
* ListElement structure. ListElements hold the points to the next and previous items in the
|
||||
* list.
|
||||
* */
|
||||
|
||||
#include "LinkedList.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Heap.h"
|
||||
|
||||
|
||||
static int ListUnlink(List* aList, void* content, int(*callback)(void*, void*), int freeContent);
|
||||
|
||||
|
||||
/**
|
||||
* Sets a list structure to empty - all null values. Does not remove any items from the list.
|
||||
* @param newl a pointer to the list structure to be initialized
|
||||
*/
|
||||
void ListZero(List* newl)
|
||||
{
|
||||
memset(newl, '\0', sizeof(List));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allocates and initializes a new list structure.
|
||||
* @return a pointer to the new list structure
|
||||
*/
|
||||
List* ListInitialize(void)
|
||||
{
|
||||
List* newl = malloc(sizeof(List));
|
||||
if (newl)
|
||||
ListZero(newl);
|
||||
return newl;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append an already allocated ListElement and content to a list. Can be used to move
|
||||
* an item from one list to another.
|
||||
* @param aList the list to which the item is to be added
|
||||
* @param content the list item content itself
|
||||
* @param newel the ListElement to be used in adding the new item
|
||||
* @param size the size of the element
|
||||
*/
|
||||
void ListAppendNoMalloc(List* aList, void* content, ListElement* newel, size_t size)
|
||||
{ /* for heap use */
|
||||
newel->content = content;
|
||||
newel->next = NULL;
|
||||
newel->prev = aList->last;
|
||||
if (aList->first == NULL)
|
||||
aList->first = newel;
|
||||
else
|
||||
aList->last->next = newel;
|
||||
aList->last = newel;
|
||||
++(aList->count);
|
||||
aList->size += size;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append an item to a list.
|
||||
* @param aList the list to which the item is to be added
|
||||
* @param content the list item content itself
|
||||
* @param size the size of the element
|
||||
*/
|
||||
ListElement* ListAppend(List* aList, void* content, size_t size)
|
||||
{
|
||||
ListElement* newel = malloc(sizeof(ListElement));
|
||||
if (newel)
|
||||
ListAppendNoMalloc(aList, content, newel, size);
|
||||
return newel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Insert an item to a list at a specific position.
|
||||
* @param aList the list to which the item is to be added
|
||||
* @param content the list item content itself
|
||||
* @param size the size of the element
|
||||
* @param index the position in the list. If NULL, this function is equivalent
|
||||
* to ListAppend.
|
||||
*/
|
||||
ListElement* ListInsert(List* aList, void* content, size_t size, ListElement* index)
|
||||
{
|
||||
ListElement* newel = malloc(sizeof(ListElement));
|
||||
|
||||
if (newel == NULL)
|
||||
return newel;
|
||||
if ( index == NULL )
|
||||
ListAppendNoMalloc(aList, content, newel, size);
|
||||
else
|
||||
{
|
||||
newel->content = content;
|
||||
newel->next = index;
|
||||
newel->prev = index->prev;
|
||||
|
||||
index->prev = newel;
|
||||
if ( newel->prev != NULL )
|
||||
newel->prev->next = newel;
|
||||
else
|
||||
aList->first = newel;
|
||||
|
||||
++(aList->count);
|
||||
aList->size += size;
|
||||
}
|
||||
return newel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds an element in a list by comparing the content pointers, rather than the contents
|
||||
* @param aList the list in which the search is to be conducted
|
||||
* @param content pointer to the list item content itself
|
||||
* @return the list item found, or NULL
|
||||
*/
|
||||
ListElement* ListFind(List* aList, void* content)
|
||||
{
|
||||
return ListFindItem(aList, content, NULL);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds an element in a list by comparing the content or pointer to the content. A callback
|
||||
* function is used to define the method of comparison for each element.
|
||||
* @param aList the list in which the search is to be conducted
|
||||
* @param content pointer to the content to look for
|
||||
* @param callback pointer to a function which compares each element (NULL means compare by content pointer)
|
||||
* @return the list element found, or NULL
|
||||
*/
|
||||
ListElement* ListFindItem(List* aList, void* content, int(*callback)(void*, void*))
|
||||
{
|
||||
ListElement* rc = NULL;
|
||||
|
||||
if (aList->current != NULL && ((callback == NULL && aList->current->content == content) ||
|
||||
(callback != NULL && callback(aList->current->content, content))))
|
||||
rc = aList->current;
|
||||
else
|
||||
{
|
||||
ListElement* current = NULL;
|
||||
|
||||
/* find the content */
|
||||
while (ListNextElement(aList, ¤t) != NULL)
|
||||
{
|
||||
if (callback == NULL)
|
||||
{
|
||||
if (current->content == content)
|
||||
{
|
||||
rc = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (callback(current->content, content))
|
||||
{
|
||||
rc = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rc != NULL)
|
||||
aList->current = rc;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes and optionally frees an element in a list by comparing the content.
|
||||
* A callback function is used to define the method of comparison for each element.
|
||||
* @param aList the list in which the search is to be conducted
|
||||
* @param content pointer to the content to look for
|
||||
* @param callback pointer to a function which compares each element
|
||||
* @param freeContent boolean value to indicate whether the item found is to be freed
|
||||
* @return 1=item removed, 0=item not removed
|
||||
*/
|
||||
static int ListUnlink(List* aList, void* content, int(*callback)(void*, void*), int freeContent)
|
||||
{
|
||||
ListElement* next = NULL;
|
||||
ListElement* saved = aList->current;
|
||||
int saveddeleted = 0;
|
||||
|
||||
if (!ListFindItem(aList, content, callback))
|
||||
return 0; /* false, did not remove item */
|
||||
|
||||
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;
|
||||
if (freeContent)
|
||||
{
|
||||
free(aList->current->content);
|
||||
aList->current->content = NULL;
|
||||
}
|
||||
if (saved == aList->current)
|
||||
saveddeleted = 1;
|
||||
free(aList->current);
|
||||
if (saveddeleted)
|
||||
aList->current = next;
|
||||
else
|
||||
aList->current = saved;
|
||||
--(aList->count);
|
||||
return 1; /* successfully removed item */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes but does not free an item in a list by comparing the pointer to the content.
|
||||
* @param aList the list in which the search is to be conducted
|
||||
* @param content pointer to the content to look for
|
||||
* @return 1=item removed, 0=item not removed
|
||||
*/
|
||||
int ListDetach(List* aList, void* content)
|
||||
{
|
||||
return ListUnlink(aList, content, NULL, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes and frees an item in a list by comparing the pointer to the content.
|
||||
* @param aList the list from which the item is to be removed
|
||||
* @param content pointer to the content to look for
|
||||
* @return 1=item removed, 0=item not removed
|
||||
*/
|
||||
int ListRemove(List* aList, void* content)
|
||||
{
|
||||
return ListUnlink(aList, content, NULL, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
void* ListDetachHead(List* aList)
|
||||
{
|
||||
void *content = NULL;
|
||||
if (aList->count > 0)
|
||||
{
|
||||
ListElement* first = aList->first;
|
||||
if (aList->current == first)
|
||||
aList->current = first->next;
|
||||
if (aList->last == first) /* i.e. no of items in list == 1 */
|
||||
aList->last = NULL;
|
||||
content = first->content;
|
||||
aList->first = aList->first->next;
|
||||
if (aList->first)
|
||||
aList->first->prev = NULL;
|
||||
free(first);
|
||||
--(aList->count);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes but does not free the last item in a list.
|
||||
* @param aList the list from which the item is to be removed
|
||||
* @return the last item removed (or NULL if none was)
|
||||
*/
|
||||
void* ListPopTail(List* aList)
|
||||
{
|
||||
void* content = NULL;
|
||||
if (aList->count > 0)
|
||||
{
|
||||
ListElement* last = aList->last;
|
||||
if (aList->current == last)
|
||||
aList->current = last->prev;
|
||||
if (aList->first == last) /* i.e. no of items in list == 1 */
|
||||
aList->first = NULL;
|
||||
content = last->content;
|
||||
aList->last = aList->last->prev;
|
||||
if (aList->last)
|
||||
aList->last->next = NULL;
|
||||
free(last);
|
||||
--(aList->count);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes but does not free an element in a list by comparing the content.
|
||||
* A callback function is used to define the method of comparison for each element.
|
||||
* @param aList the list in which the search is to be conducted
|
||||
* @param content pointer to the content to look for
|
||||
* @param callback pointer to a function which compares each element
|
||||
* @return 1=item removed, 0=item not removed
|
||||
*/
|
||||
int ListDetachItem(List* aList, void* content, int(*callback)(void*, void*))
|
||||
{ /* do not free the content */
|
||||
return ListUnlink(aList, content, callback, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes and frees an element in a list by comparing the content.
|
||||
* A callback function is used to define the method of comparison for each element
|
||||
* @param aList the list in which the search is to be conducted
|
||||
* @param content pointer to the content to look for
|
||||
* @param callback pointer to a function which compares each element
|
||||
* @return 1=item removed, 0=item not removed
|
||||
*/
|
||||
int ListRemoveItem(List* aList, void* content, int(*callback)(void*, void*))
|
||||
{ /* remove from list and free the content */
|
||||
return ListUnlink(aList, content, callback, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes and frees all items in a list, leaving the list ready for new items.
|
||||
* @param aList the list to which the operation is to be applied
|
||||
*/
|
||||
void ListEmpty(List* aList)
|
||||
{
|
||||
while (aList->first != NULL)
|
||||
{
|
||||
ListElement* first = aList->first;
|
||||
if (first->content != NULL)
|
||||
{
|
||||
free(first->content);
|
||||
first->content = NULL;
|
||||
}
|
||||
aList->first = first->next;
|
||||
free(first);
|
||||
}
|
||||
aList->count = 0;
|
||||
aList->size = 0;
|
||||
aList->current = aList->first = aList->last = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes and frees all items in a list, and frees the list itself
|
||||
* @param aList the list to which the operation is to be applied
|
||||
*/
|
||||
void ListFree(List* aList)
|
||||
{
|
||||
ListEmpty(aList);
|
||||
free(aList);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes and but does not free all items in a list, and frees the list itself
|
||||
* @param aList the list to which the operation is to be applied
|
||||
*/
|
||||
void ListFreeNoContent(List* aList)
|
||||
{
|
||||
while (aList->first != NULL)
|
||||
{
|
||||
ListElement* first = aList->first;
|
||||
aList->first = first->next;
|
||||
free(first);
|
||||
}
|
||||
free(aList);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Forward iteration through a list
|
||||
* @param aList the list to which the operation is to be applied
|
||||
* @param pos pointer to the current position in the list. NULL means start from the beginning of the list
|
||||
* This is updated on return to the same value as that returned from this function
|
||||
* @return pointer to the current list element
|
||||
*/
|
||||
ListElement* ListNextElement(List* aList, ListElement** pos)
|
||||
{
|
||||
return *pos = (*pos == NULL) ? aList->first : (*pos)->next;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Backward iteration through a list
|
||||
* @param aList the list to which the operation is to be applied
|
||||
* @param pos pointer to the current position in the list. NULL means start from the end of the list
|
||||
* This is updated on return to the same value as that returned from this function
|
||||
* @return pointer to the current list element
|
||||
*/
|
||||
ListElement* ListPrevElement(List* aList, ListElement** pos)
|
||||
{
|
||||
return *pos = (*pos == NULL) ? aList->last : (*pos)->prev;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List callback function for comparing integers
|
||||
* @param a first integer value
|
||||
* @param b second integer value
|
||||
* @return boolean indicating whether a and b are equal
|
||||
*/
|
||||
int intcompare(void* a, void* b)
|
||||
{
|
||||
return *((int*)a) == *((int*)b);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List callback function for comparing C strings
|
||||
* @param a first integer value
|
||||
* @param b second integer value
|
||||
* @return boolean indicating whether a and b are equal
|
||||
*/
|
||||
int stringcompare(void* a, void* b)
|
||||
{
|
||||
return strcmp((char*)a, (char*)b) == 0;
|
||||
}
|
||||
|
||||
|
||||
#if defined(UNIT_TESTS)
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, *ip, *todelete;
|
||||
ListElement* current = NULL;
|
||||
List* l = ListInitialize();
|
||||
printf("List initialized\n");
|
||||
|
||||
for (i = 0; i < 10; i++)
|
||||
{
|
||||
ip = malloc(sizeof(int));
|
||||
*ip = i;
|
||||
ListAppend(l, (void*)ip, sizeof(int));
|
||||
if (i==5)
|
||||
todelete = ip;
|
||||
printf("List element appended %d\n", *((int*)(l->last->content)));
|
||||
}
|
||||
|
||||
printf("List contents:\n");
|
||||
current = NULL;
|
||||
while (ListNextElement(l, ¤t) != NULL)
|
||||
printf("List element: %d\n", *((int*)(current->content)));
|
||||
|
||||
printf("List contents in reverse order:\n");
|
||||
current = NULL;
|
||||
while (ListPrevElement(l, ¤t) != NULL)
|
||||
printf("List element: %d\n", *((int*)(current->content)));
|
||||
|
||||
/* if ListFindItem(l, *ip, intcompare)->content */
|
||||
|
||||
printf("List contents having deleted element %d:\n", *todelete);
|
||||
ListRemove(l, todelete);
|
||||
current = NULL;
|
||||
while (ListNextElement(l, ¤t) != NULL)
|
||||
printf("List element: %d\n", *((int*)(current->content)));
|
||||
|
||||
i = 9;
|
||||
ListRemoveItem(l, &i, intcompare);
|
||||
printf("List contents having deleted another element, %d, size now %d:\n", i, l->size);
|
||||
current = NULL;
|
||||
while (ListNextElement(l, ¤t) != NULL)
|
||||
printf("List element: %d\n", *((int*)(current->content)));
|
||||
|
||||
ListFree(l);
|
||||
printf("List freed\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,577 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2018 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - updates for the async client
|
||||
* Ian Craggs - fix for bug #427028
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief Logging and tracing module
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Log.h"
|
||||
#include "MQTTPacket.h"
|
||||
#include "MQTTProtocol.h"
|
||||
#include "MQTTProtocolClient.h"
|
||||
#include "Messages.h"
|
||||
#include "LinkedList.h"
|
||||
#include "StackTrace.h"
|
||||
#include "Thread.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
#include <syslog.h>
|
||||
#include <sys/stat.h>
|
||||
#define GETTIMEOFDAY 1
|
||||
#else
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
#if defined(GETTIMEOFDAY)
|
||||
#include <sys/time.h>
|
||||
#else
|
||||
#include <sys/timeb.h>
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
/**
|
||||
* _unlink mapping for linux
|
||||
*/
|
||||
#define _unlink unlink
|
||||
#endif
|
||||
|
||||
|
||||
#if !defined(min)
|
||||
#define min(A,B) ( (A) < (B) ? (A):(B))
|
||||
#endif
|
||||
|
||||
trace_settings_type trace_settings =
|
||||
{
|
||||
TRACE_MINIMUM,
|
||||
400,
|
||||
INVALID_LEVEL
|
||||
};
|
||||
|
||||
#define MAX_FUNCTION_NAME_LENGTH 256
|
||||
|
||||
typedef struct
|
||||
{
|
||||
#if defined(GETTIMEOFDAY)
|
||||
struct timeval ts;
|
||||
#else
|
||||
struct timeb ts;
|
||||
#endif
|
||||
int sametime_count;
|
||||
int number;
|
||||
int thread_id;
|
||||
int depth;
|
||||
char name[MAX_FUNCTION_NAME_LENGTH + 1];
|
||||
int line;
|
||||
int has_rc;
|
||||
int rc;
|
||||
enum LOG_LEVELS level;
|
||||
} traceEntry;
|
||||
|
||||
static int start_index = -1,
|
||||
next_index = 0;
|
||||
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 enum LOG_LEVELS trace_output_level = INVALID_LEVEL;
|
||||
static Log_traceCallback* trace_callback = NULL;
|
||||
static traceEntry* Log_pretrace(void);
|
||||
static char* Log_formatTraceEntry(traceEntry* cur_entry);
|
||||
static void Log_output(enum LOG_LEVELS log_level, const char *msg);
|
||||
static void Log_posttrace(enum LOG_LEVELS log_level, traceEntry* cur_entry);
|
||||
static void Log_trace(enum LOG_LEVELS log_level, const char *buf);
|
||||
#if 0
|
||||
static FILE* Log_destToFile(const char *dest);
|
||||
static int Log_compareEntries(const char *entry1, const char *entry2);
|
||||
#endif
|
||||
|
||||
static int sametime_count = 0;
|
||||
#if defined(GETTIMEOFDAY)
|
||||
struct timeval ts, last_ts;
|
||||
#else
|
||||
struct timeb ts, last_ts;
|
||||
#endif
|
||||
static char msg_buf[512];
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
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(Log_nameValue* info)
|
||||
{
|
||||
int rc = SOCKET_ERROR;
|
||||
char* envval = NULL;
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
struct stat buf;
|
||||
#endif
|
||||
|
||||
if ((trace_queue = malloc(sizeof(traceEntry) * trace_settings.max_trace_entries)) == NULL)
|
||||
goto exit;
|
||||
trace_queue_size = trace_settings.max_trace_entries;
|
||||
|
||||
if ((envval = getenv("MQTT_C_CLIENT_TRACE")) != NULL && strlen(envval) > 0)
|
||||
{
|
||||
if (strcmp(envval, "ON") == 0 || (trace_destination = fopen(envval, "w")) == NULL)
|
||||
trace_destination = stdout;
|
||||
else
|
||||
{
|
||||
if ((trace_destination_name = malloc(strlen(envval) + 1)) == NULL)
|
||||
{
|
||||
free(trace_queue);
|
||||
goto exit;
|
||||
}
|
||||
strcpy(trace_destination_name, envval);
|
||||
if ((trace_destination_backup_name = malloc(strlen(envval) + 3)) == NULL)
|
||||
{
|
||||
free(trace_queue);
|
||||
free(trace_destination_name);
|
||||
goto exit;
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (strcmp(envval, "MAXIMUM") == 0 || strcmp(envval, "TRACE_MAXIMUM") == 0)
|
||||
trace_settings.trace_level = TRACE_MAXIMUM;
|
||||
else if (strcmp(envval, "MEDIUM") == 0 || strcmp(envval, "TRACE_MEDIUM") == 0)
|
||||
trace_settings.trace_level = TRACE_MEDIUM;
|
||||
else if (strcmp(envval, "MINIMUM") == 0 || strcmp(envval, "TRACE_MINIMUM") == 0)
|
||||
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)
|
||||
{
|
||||
snprintf(msg_buf, sizeof(msg_buf), "%s: %s", info->name, info->value);
|
||||
Log_output(TRACE_MINIMUM, msg_buf);
|
||||
info++;
|
||||
}
|
||||
}
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
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, "=========================================================");
|
||||
exit:
|
||||
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(void)
|
||||
{
|
||||
free(trace_queue);
|
||||
trace_queue = NULL;
|
||||
trace_queue_size = 0;
|
||||
if (trace_destination)
|
||||
{
|
||||
if (trace_destination != stdout)
|
||||
fclose(trace_destination);
|
||||
trace_destination = NULL;
|
||||
}
|
||||
if (trace_destination_name) {
|
||||
free(trace_destination_name);
|
||||
trace_destination_name = NULL;
|
||||
}
|
||||
if (trace_destination_backup_name) {
|
||||
free(trace_destination_backup_name);
|
||||
trace_destination_backup_name = NULL;
|
||||
}
|
||||
start_index = -1;
|
||||
next_index = 0;
|
||||
trace_output_level = INVALID_LEVEL;
|
||||
sametime_count = 0;
|
||||
}
|
||||
|
||||
|
||||
static traceEntry* Log_pretrace(void)
|
||||
{
|
||||
traceEntry *cur_entry = NULL;
|
||||
|
||||
/* calling ftime/gettimeofday seems to be comparatively expensive, so we need to limit its use */
|
||||
if (++sametime_count % 20 == 0)
|
||||
{
|
||||
#if defined(GETTIMEOFDAY)
|
||||
gettimeofday(&ts, NULL);
|
||||
if (ts.tv_sec != last_ts.tv_sec || ts.tv_usec != last_ts.tv_usec)
|
||||
#else
|
||||
ftime(&ts);
|
||||
if (ts.time != last_ts.time || ts.millitm != last_ts.millitm)
|
||||
#endif
|
||||
{
|
||||
sametime_count = 0;
|
||||
last_ts = ts;
|
||||
}
|
||||
}
|
||||
|
||||
if (trace_queue_size != trace_settings.max_trace_entries)
|
||||
{
|
||||
traceEntry* new_trace_queue = malloc(sizeof(traceEntry) * trace_settings.max_trace_entries);
|
||||
|
||||
if (new_trace_queue == NULL)
|
||||
goto exit;
|
||||
memcpy(new_trace_queue, trace_queue, min(trace_queue_size, trace_settings.max_trace_entries) * sizeof(traceEntry));
|
||||
free(trace_queue);
|
||||
trace_queue = new_trace_queue;
|
||||
trace_queue_size = trace_settings.max_trace_entries;
|
||||
|
||||
if (start_index > trace_settings.max_trace_entries + 1 ||
|
||||
next_index > trace_settings.max_trace_entries + 1)
|
||||
{
|
||||
start_index = -1;
|
||||
next_index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* add to trace buffer */
|
||||
cur_entry = &trace_queue[next_index];
|
||||
if (next_index == start_index) /* means the buffer is full */
|
||||
{
|
||||
if (++start_index == trace_settings.max_trace_entries)
|
||||
start_index = 0;
|
||||
} else if (start_index == -1)
|
||||
start_index = 0;
|
||||
if (++next_index == trace_settings.max_trace_entries)
|
||||
next_index = 0;
|
||||
exit:
|
||||
return cur_entry;
|
||||
}
|
||||
|
||||
static char* Log_formatTraceEntry(traceEntry* cur_entry)
|
||||
{
|
||||
struct tm *timeinfo;
|
||||
int buf_pos = 31;
|
||||
|
||||
#if defined(GETTIMEOFDAY)
|
||||
timeinfo = localtime((time_t *)&cur_entry->ts.tv_sec);
|
||||
#else
|
||||
timeinfo = localtime(&cur_entry->ts.time);
|
||||
#endif
|
||||
strftime(&msg_buf[7], 80, "%Y%m%d %H%M%S ", timeinfo);
|
||||
#if defined(GETTIMEOFDAY)
|
||||
sprintf(&msg_buf[22], ".%.3lu ", cur_entry->ts.tv_usec / 1000L);
|
||||
#else
|
||||
sprintf(&msg_buf[22], ".%.3hu ", cur_entry->ts.millitm);
|
||||
#endif
|
||||
buf_pos = 27;
|
||||
|
||||
sprintf(msg_buf, "(%.4d)", cur_entry->sametime_count);
|
||||
msg_buf[6] = ' ';
|
||||
|
||||
if (cur_entry->has_rc == 2)
|
||||
strncpy(&msg_buf[buf_pos], cur_entry->name, sizeof(msg_buf)-buf_pos);
|
||||
else
|
||||
{
|
||||
const char *format = Messages_get(cur_entry->number, cur_entry->level);
|
||||
if (cur_entry->has_rc == 1)
|
||||
snprintf(&msg_buf[buf_pos], sizeof(msg_buf)-buf_pos, format, cur_entry->thread_id,
|
||||
cur_entry->depth, "", cur_entry->depth, cur_entry->name, cur_entry->line, cur_entry->rc);
|
||||
else
|
||||
snprintf(&msg_buf[buf_pos], sizeof(msg_buf)-buf_pos, format, cur_entry->thread_id,
|
||||
cur_entry->depth, "", cur_entry->depth, cur_entry->name, cur_entry->line);
|
||||
}
|
||||
return msg_buf;
|
||||
}
|
||||
|
||||
|
||||
static void Log_output(enum LOG_LEVELS log_level, const char *msg)
|
||||
{
|
||||
if (trace_destination)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
if (trace_callback)
|
||||
(*trace_callback)(log_level, msg);
|
||||
}
|
||||
|
||||
|
||||
static void Log_posttrace(enum LOG_LEVELS log_level, traceEntry* cur_entry)
|
||||
{
|
||||
if (((trace_output_level == -1) ? log_level >= trace_settings.trace_level : log_level >= trace_output_level))
|
||||
{
|
||||
char* msg = NULL;
|
||||
|
||||
if (trace_destination || trace_callback)
|
||||
msg = &Log_formatTraceEntry(cur_entry)[7];
|
||||
|
||||
Log_output(log_level, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void Log_trace(enum LOG_LEVELS log_level, const char *buf)
|
||||
{
|
||||
traceEntry *cur_entry = NULL;
|
||||
|
||||
if (trace_queue == NULL)
|
||||
return;
|
||||
|
||||
cur_entry = Log_pretrace();
|
||||
|
||||
memcpy(&(cur_entry->ts), &ts, sizeof(ts));
|
||||
cur_entry->sametime_count = sametime_count;
|
||||
|
||||
cur_entry->has_rc = 2;
|
||||
strncpy(cur_entry->name, buf, sizeof(cur_entry->name));
|
||||
cur_entry->name[MAX_FUNCTION_NAME_LENGTH] = '\0';
|
||||
|
||||
Log_posttrace(log_level, cur_entry);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log a message. If possible, all messages should be indexed by message number, and
|
||||
* the use of the format string should be minimized or negated altogether. If format is
|
||||
* provided, the message number is only used as a message label.
|
||||
* @param log_level the log level of the message
|
||||
* @param msgno the id of the message to use if the format string is NULL
|
||||
* @param aFormat the printf format string to be used if the message id does not exist
|
||||
* @param ... the printf inserts
|
||||
*/
|
||||
void Log(enum LOG_LEVELS log_level, int msgno, const char *format, ...)
|
||||
{
|
||||
if (log_level >= trace_settings.trace_level)
|
||||
{
|
||||
const char *temp = NULL;
|
||||
va_list args;
|
||||
|
||||
/* we're using a static character buffer, so we need to make sure only one thread uses it at a time */
|
||||
Thread_lock_mutex(log_mutex);
|
||||
if (format == NULL && (temp = Messages_get(msgno, log_level)) != NULL)
|
||||
format = temp;
|
||||
|
||||
va_start(args, format);
|
||||
vsnprintf(msg_buf, sizeof(msg_buf), format, args);
|
||||
|
||||
Log_trace(log_level, msg_buf);
|
||||
va_end(args);
|
||||
Thread_unlock_mutex(log_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The reason for this function is to make trace logging as fast as possible so that the
|
||||
* function exit/entry history can be captured by default without unduly impacting
|
||||
* performance. Therefore it must do as little as possible.
|
||||
* @param log_level the log level of the message
|
||||
* @param msgno the id of the message to use if the format string is NULL
|
||||
* @param aFormat the printf format string to be used if the message id does not exist
|
||||
* @param ... the printf inserts
|
||||
*/
|
||||
void Log_stackTrace(enum LOG_LEVELS log_level, int msgno, int thread_id, int current_depth, const char* name, int line, int* rc)
|
||||
{
|
||||
traceEntry *cur_entry = NULL;
|
||||
|
||||
if (trace_queue == NULL)
|
||||
return;
|
||||
|
||||
if (log_level < trace_settings.trace_level)
|
||||
return;
|
||||
|
||||
Thread_lock_mutex(log_mutex);
|
||||
cur_entry = Log_pretrace();
|
||||
|
||||
memcpy(&(cur_entry->ts), &ts, sizeof(ts));
|
||||
cur_entry->sametime_count = sametime_count;
|
||||
cur_entry->number = msgno;
|
||||
cur_entry->thread_id = thread_id;
|
||||
cur_entry->depth = current_depth;
|
||||
strcpy(cur_entry->name, name);
|
||||
cur_entry->level = log_level;
|
||||
cur_entry->line = line;
|
||||
if (rc == NULL)
|
||||
cur_entry->has_rc = 0;
|
||||
else
|
||||
{
|
||||
cur_entry->has_rc = 1;
|
||||
cur_entry->rc = *rc;
|
||||
}
|
||||
|
||||
Log_posttrace(log_level, cur_entry);
|
||||
Thread_unlock_mutex(log_mutex);
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
static FILE* Log_destToFile(const char *dest)
|
||||
{
|
||||
FILE* file = NULL;
|
||||
|
||||
if (strcmp(dest, "stdout") == 0)
|
||||
file = stdout;
|
||||
else if (strcmp(dest, "stderr") == 0)
|
||||
file = stderr;
|
||||
else
|
||||
{
|
||||
if (strstr(dest, "FFDC"))
|
||||
file = fopen(dest, "ab");
|
||||
else
|
||||
file = fopen(dest, "wb");
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
static int Log_compareEntries(const char *entry1, const char *entry2)
|
||||
{
|
||||
int comp = strncmp(&entry1[7], &entry2[7], 19);
|
||||
|
||||
/* if timestamps are equal, use the sequence numbers */
|
||||
if (comp == 0)
|
||||
comp = strncmp(&entry1[1], &entry2[1], 4);
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write the contents of the stored trace to a stream
|
||||
* @param dest string which contains a file name or the special strings stdout or stderr
|
||||
*/
|
||||
int Log_dumpTrace(char* dest)
|
||||
{
|
||||
FILE* file = NULL;
|
||||
ListElement* cur_trace_entry = NULL;
|
||||
const int msgstart = 7;
|
||||
int rc = -1;
|
||||
int trace_queue_index = 0;
|
||||
|
||||
if ((file = Log_destToFile(dest)) == NULL)
|
||||
{
|
||||
Log(LOG_ERROR, 9, NULL, "trace", dest, "trace entries");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
fprintf(file, "=========== Start of trace dump ==========\n");
|
||||
/* Interleave the log and trace entries together appropriately */
|
||||
ListNextElement(trace_buffer, &cur_trace_entry);
|
||||
trace_queue_index = start_index;
|
||||
if (trace_queue_index == -1)
|
||||
trace_queue_index = next_index;
|
||||
else
|
||||
{
|
||||
Log_formatTraceEntry(&trace_queue[trace_queue_index++]);
|
||||
if (trace_queue_index == trace_settings.max_trace_entries)
|
||||
trace_queue_index = 0;
|
||||
}
|
||||
while (cur_trace_entry || trace_queue_index != next_index)
|
||||
{
|
||||
if (cur_trace_entry && trace_queue_index != -1)
|
||||
{ /* compare these timestamps */
|
||||
if (Log_compareEntries((char*)cur_trace_entry->content, msg_buf) > 0)
|
||||
cur_trace_entry = NULL;
|
||||
}
|
||||
|
||||
if (cur_trace_entry)
|
||||
{
|
||||
fprintf(file, "%s\n", &((char*)(cur_trace_entry->content))[msgstart]);
|
||||
ListNextElement(trace_buffer, &cur_trace_entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(file, "%s\n", &msg_buf[7]);
|
||||
if (trace_queue_index != next_index)
|
||||
{
|
||||
Log_formatTraceEntry(&trace_queue[trace_queue_index++]);
|
||||
if (trace_queue_index == trace_settings.max_trace_entries)
|
||||
trace_queue_index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(file, "========== End of trace dump ==========\n\n");
|
||||
if (file != stdout && file != stderr && file != NULL)
|
||||
fclose(file);
|
||||
rc = 0;
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,464 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs, Allan Stockdill-Mander - SSL updates
|
||||
* Ian Craggs - MQTT 3.1.1 support
|
||||
* Rong Xiang, Ian Craggs - C++ compatibility
|
||||
* Ian Craggs - binary password and will payload
|
||||
* Ian Craggs - MQTT 5.0 support
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief functions to deal with reading and writing of MQTT packets from and to sockets
|
||||
*
|
||||
* Some other related functions are in the MQTTPacket module
|
||||
*/
|
||||
|
||||
|
||||
#include "MQTTPacketOut.h"
|
||||
#include "Log.h"
|
||||
#include "StackTrace.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "Heap.h"
|
||||
|
||||
|
||||
/**
|
||||
* Send an MQTT CONNECT packet down a socket for V5 or later
|
||||
* @param client a structure from which to get all the required values
|
||||
* @param MQTTVersion the MQTT version to connect with
|
||||
* @param connectProperties MQTT V5 properties for the connect packet
|
||||
* @param willProperties MQTT V5 properties for the will message, if any
|
||||
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
|
||||
*/
|
||||
int MQTTPacket_send_connect(Clients* client, int MQTTVersion,
|
||||
MQTTProperties* connectProperties, MQTTProperties* willProperties)
|
||||
{
|
||||
char *buf, *ptr;
|
||||
Connect packet;
|
||||
int rc = SOCKET_ERROR, len;
|
||||
|
||||
FUNC_ENTRY;
|
||||
packet.header.byte = 0;
|
||||
packet.header.bits.type = CONNECT;
|
||||
|
||||
len = ((MQTTVersion == MQTTVERSION_3_1) ? 12 : 10) + (int)strlen(client->clientID)+2;
|
||||
if (client->will)
|
||||
len += (int)strlen(client->will->topic)+2 + client->will->payloadlen+2;
|
||||
if (client->username)
|
||||
len += (int)strlen(client->username)+2;
|
||||
if (client->password)
|
||||
len += client->passwordlen+2;
|
||||
if (MQTTVersion >= MQTTVERSION_5)
|
||||
{
|
||||
len += MQTTProperties_len(connectProperties);
|
||||
if (client->will)
|
||||
len += MQTTProperties_len(willProperties);
|
||||
}
|
||||
|
||||
ptr = buf = malloc(len);
|
||||
if (ptr == NULL)
|
||||
goto exit_nofree;
|
||||
if (MQTTVersion == MQTTVERSION_3_1)
|
||||
{
|
||||
writeUTF(&ptr, "MQIsdp");
|
||||
writeChar(&ptr, (char)MQTTVERSION_3_1);
|
||||
}
|
||||
else if (MQTTVersion == MQTTVERSION_3_1_1 || MQTTVersion == MQTTVERSION_5)
|
||||
{
|
||||
writeUTF(&ptr, "MQTT");
|
||||
writeChar(&ptr, (char)MQTTVersion);
|
||||
}
|
||||
else
|
||||
goto exit;
|
||||
|
||||
packet.flags.all = 0;
|
||||
if (MQTTVersion >= MQTTVERSION_5)
|
||||
packet.flags.bits.cleanstart = client->cleanstart;
|
||||
else
|
||||
packet.flags.bits.cleanstart = client->cleansession;
|
||||
packet.flags.bits.will = (client->will) ? 1 : 0;
|
||||
if (packet.flags.bits.will)
|
||||
{
|
||||
packet.flags.bits.willQoS = client->will->qos;
|
||||
packet.flags.bits.willRetain = client->will->retained;
|
||||
}
|
||||
if (client->username)
|
||||
packet.flags.bits.username = 1;
|
||||
if (client->password)
|
||||
packet.flags.bits.password = 1;
|
||||
|
||||
writeChar(&ptr, packet.flags.all);
|
||||
writeInt(&ptr, client->keepAliveInterval);
|
||||
if (MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_write(&ptr, connectProperties);
|
||||
writeUTF(&ptr, client->clientID);
|
||||
if (client->will)
|
||||
{
|
||||
if (MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_write(&ptr, willProperties);
|
||||
writeUTF(&ptr, client->will->topic);
|
||||
writeData(&ptr, client->will->payload, client->will->payloadlen);
|
||||
}
|
||||
if (client->username)
|
||||
writeUTF(&ptr, client->username);
|
||||
if (client->password)
|
||||
writeData(&ptr, client->password, client->passwordlen);
|
||||
|
||||
rc = MQTTPacket_send(&client->net, packet.header, buf, len, 1, MQTTVersion);
|
||||
Log(LOG_PROTOCOL, 0, NULL, client->net.socket, client->clientID,
|
||||
MQTTVersion, client->cleansession, rc);
|
||||
exit:
|
||||
if (rc != TCPSOCKET_INTERRUPTED)
|
||||
free(buf);
|
||||
exit_nofree:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function used in the new packets table to create connack packets.
|
||||
* @param MQTTVersion MQTT 5 or less?
|
||||
* @param aHeader the MQTT header byte
|
||||
* @param data the rest of the packet
|
||||
* @param datalen the length of the rest of the packet
|
||||
* @return pointer to the packet structure
|
||||
*/
|
||||
void* MQTTPacket_connack(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen)
|
||||
{
|
||||
Connack* pack = NULL;
|
||||
char* curdata = data;
|
||||
char* enddata = &data[datalen];
|
||||
|
||||
FUNC_ENTRY;
|
||||
if ((pack = malloc(sizeof(Connack))) == NULL)
|
||||
goto exit;
|
||||
pack->MQTTVersion = MQTTVersion;
|
||||
pack->header.byte = aHeader;
|
||||
pack->flags.all = readChar(&curdata); /* connect flags */
|
||||
pack->rc = readChar(&curdata); /* reason code */
|
||||
if (MQTTVersion < MQTTVERSION_5)
|
||||
{
|
||||
if (datalen != 2)
|
||||
{
|
||||
free(pack);
|
||||
pack = NULL;
|
||||
}
|
||||
}
|
||||
else if (datalen > 2)
|
||||
{
|
||||
MQTTProperties props = MQTTProperties_initializer;
|
||||
pack->properties = props;
|
||||
if (MQTTProperties_read(&pack->properties, &curdata, enddata) != 1)
|
||||
{
|
||||
if (pack->properties.array)
|
||||
free(pack->properties.array);
|
||||
if (pack)
|
||||
free(pack);
|
||||
pack = NULL; /* signal protocol error */
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
exit:
|
||||
FUNC_EXIT;
|
||||
return pack;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Free allocated storage for a connack packet.
|
||||
* @param pack pointer to the connack packet structure
|
||||
*/
|
||||
void MQTTPacket_freeConnack(Connack* pack)
|
||||
{
|
||||
FUNC_ENTRY;
|
||||
if (pack->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&pack->properties);
|
||||
free(pack);
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an MQTT PINGREQ packet down a socket.
|
||||
* @param socket the open socket to send the data to
|
||||
* @param clientID the string client identifier, only used for tracing
|
||||
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
|
||||
*/
|
||||
int MQTTPacket_send_pingreq(networkHandles* net, const char* clientID)
|
||||
{
|
||||
Header header;
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
header.byte = 0;
|
||||
header.bits.type = PINGREQ;
|
||||
rc = MQTTPacket_send(net, header, NULL, 0, 0, MQTTVERSION_3_1_1);
|
||||
Log(LOG_PROTOCOL, 20, NULL, net->socket, clientID, rc);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an MQTT subscribe packet down a socket.
|
||||
* @param topics list of topics
|
||||
* @param qoss list of corresponding QoSs
|
||||
* @param msgid the MQTT message id to use
|
||||
* @param dup boolean - whether to set the MQTT DUP flag
|
||||
* @param socket the open socket to send the data to
|
||||
* @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, MQTTSubscribe_options* opts, MQTTProperties* props,
|
||||
int msgid, int dup, Clients* client)
|
||||
{
|
||||
Header header;
|
||||
char *data, *ptr;
|
||||
int rc = -1;
|
||||
ListElement *elem = NULL, *qosElem = NULL;
|
||||
int datalen, i = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
header.bits.type = SUBSCRIBE;
|
||||
header.bits.dup = dup;
|
||||
header.bits.qos = 1;
|
||||
header.bits.retain = 0;
|
||||
|
||||
datalen = 2 + topics->count * 3; /* utf length + char qos == 3 */
|
||||
while (ListNextElement(topics, &elem))
|
||||
datalen += (int)strlen((char*)(elem->content));
|
||||
if (client->MQTTVersion >= MQTTVERSION_5)
|
||||
datalen += MQTTProperties_len(props);
|
||||
|
||||
ptr = data = malloc(datalen);
|
||||
if (ptr == NULL)
|
||||
goto exit;
|
||||
writeInt(&ptr, msgid);
|
||||
|
||||
if (client->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_write(&ptr, props);
|
||||
|
||||
elem = NULL;
|
||||
while (ListNextElement(topics, &elem))
|
||||
{
|
||||
char subopts = 0;
|
||||
|
||||
ListNextElement(qoss, &qosElem);
|
||||
writeUTF(&ptr, (char*)(elem->content));
|
||||
subopts = *(int*)(qosElem->content);
|
||||
if (client->MQTTVersion >= MQTTVERSION_5 && opts != NULL)
|
||||
{
|
||||
subopts |= (opts[i].noLocal << 2); /* 1 bit */
|
||||
subopts |= (opts[i].retainAsPublished << 3); /* 1 bit */
|
||||
subopts |= (opts[i].retainHandling << 4); /* 2 bits */
|
||||
}
|
||||
writeChar(&ptr, subopts);
|
||||
++i;
|
||||
}
|
||||
rc = MQTTPacket_send(&client->net, header, data, datalen, 1, client->MQTTVersion);
|
||||
Log(LOG_PROTOCOL, 22, NULL, client->net.socket, client->clientID, msgid, rc);
|
||||
if (rc != TCPSOCKET_INTERRUPTED)
|
||||
free(data);
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function used in the new packets table to create suback packets.
|
||||
* @param MQTTVersion the version of MQTT
|
||||
* @param aHeader the MQTT header byte
|
||||
* @param data the rest of the packet
|
||||
* @param datalen the length of the rest of the packet
|
||||
* @return pointer to the packet structure
|
||||
*/
|
||||
void* MQTTPacket_suback(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen)
|
||||
{
|
||||
Suback* pack = NULL;
|
||||
char* curdata = data;
|
||||
char* enddata = &data[datalen];
|
||||
|
||||
FUNC_ENTRY;
|
||||
if ((pack = malloc(sizeof(Suback))) == NULL)
|
||||
goto exit;
|
||||
pack->MQTTVersion = MQTTVersion;
|
||||
pack->header.byte = aHeader;
|
||||
pack->msgId = readInt(&curdata);
|
||||
if (MQTTVersion >= MQTTVERSION_5)
|
||||
{
|
||||
MQTTProperties props = MQTTProperties_initializer;
|
||||
pack->properties = props;
|
||||
if (MQTTProperties_read(&pack->properties, &curdata, enddata) != 1)
|
||||
{
|
||||
if (pack->properties.array)
|
||||
free(pack->properties.array);
|
||||
if (pack)
|
||||
free(pack);
|
||||
pack = NULL; /* signal protocol error */
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
pack->qoss = ListInitialize();
|
||||
while ((size_t)(curdata - data) < datalen)
|
||||
{
|
||||
unsigned int* newint;
|
||||
newint = malloc(sizeof(unsigned int));
|
||||
if (newint == NULL)
|
||||
{
|
||||
if (pack->properties.array)
|
||||
free(pack->properties.array);
|
||||
if (pack)
|
||||
free(pack);
|
||||
pack = NULL; /* signal protocol error */
|
||||
goto exit;
|
||||
}
|
||||
*newint = (unsigned int)readChar(&curdata);
|
||||
ListAppend(pack->qoss, newint, sizeof(unsigned int));
|
||||
}
|
||||
if (pack->qoss->count == 0)
|
||||
{
|
||||
if (pack->properties.array)
|
||||
free(pack->properties.array);
|
||||
if (pack)
|
||||
free(pack);
|
||||
ListFree(pack->qoss);
|
||||
pack = NULL;
|
||||
}
|
||||
exit:
|
||||
FUNC_EXIT;
|
||||
return pack;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an MQTT unsubscribe packet down a socket.
|
||||
* @param topics list of topics
|
||||
* @param msgid the MQTT message id to use
|
||||
* @param dup boolean - whether to set the MQTT DUP flag
|
||||
* @param socket the open socket to send the data to
|
||||
* @param clientID the string client identifier, only used for tracing
|
||||
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
|
||||
*/
|
||||
int MQTTPacket_send_unsubscribe(List* topics, MQTTProperties* props, int msgid, int dup, Clients* client)
|
||||
{
|
||||
Header header;
|
||||
char *data, *ptr;
|
||||
int rc = SOCKET_ERROR;
|
||||
ListElement *elem = NULL;
|
||||
int datalen;
|
||||
|
||||
FUNC_ENTRY;
|
||||
header.bits.type = UNSUBSCRIBE;
|
||||
header.bits.dup = dup;
|
||||
header.bits.qos = 1;
|
||||
header.bits.retain = 0;
|
||||
|
||||
datalen = 2 + topics->count * 2; /* utf length == 2 */
|
||||
while (ListNextElement(topics, &elem))
|
||||
datalen += (int)strlen((char*)(elem->content));
|
||||
if (client->MQTTVersion >= MQTTVERSION_5)
|
||||
datalen += MQTTProperties_len(props);
|
||||
ptr = data = malloc(datalen);
|
||||
if (ptr == NULL)
|
||||
goto exit;
|
||||
|
||||
writeInt(&ptr, msgid);
|
||||
|
||||
if (client->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_write(&ptr, props);
|
||||
|
||||
elem = NULL;
|
||||
while (ListNextElement(topics, &elem))
|
||||
writeUTF(&ptr, (char*)(elem->content));
|
||||
rc = MQTTPacket_send(&client->net, header, data, datalen, 1, client->MQTTVersion);
|
||||
Log(LOG_PROTOCOL, 25, NULL, client->net.socket, client->clientID, msgid, rc);
|
||||
if (rc != TCPSOCKET_INTERRUPTED)
|
||||
free(data);
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function used in the new packets table to create unsuback packets.
|
||||
* @param MQTTVersion the version of MQTT
|
||||
* @param aHeader the MQTT header byte
|
||||
* @param data the rest of the packet
|
||||
* @param datalen the length of the rest of the packet
|
||||
* @return pointer to the packet structure
|
||||
*/
|
||||
void* MQTTPacket_unsuback(int MQTTVersion, unsigned char aHeader, char* data, size_t datalen)
|
||||
{
|
||||
Unsuback* pack = NULL;
|
||||
char* curdata = data;
|
||||
char* enddata = &data[datalen];
|
||||
|
||||
FUNC_ENTRY;
|
||||
if ((pack = malloc(sizeof(Unsuback))) == NULL)
|
||||
goto exit;
|
||||
pack->MQTTVersion = MQTTVersion;
|
||||
pack->header.byte = aHeader;
|
||||
pack->msgId = readInt(&curdata);
|
||||
pack->reasonCodes = NULL;
|
||||
if (MQTTVersion >= MQTTVERSION_5)
|
||||
{
|
||||
MQTTProperties props = MQTTProperties_initializer;
|
||||
pack->properties = props;
|
||||
if (MQTTProperties_read(&pack->properties, &curdata, enddata) != 1)
|
||||
{
|
||||
if (pack->properties.array)
|
||||
free(pack->properties.array);
|
||||
if (pack)
|
||||
free(pack);
|
||||
pack = NULL; /* signal protocol error */
|
||||
goto exit;
|
||||
}
|
||||
pack->reasonCodes = ListInitialize();
|
||||
while ((size_t)(curdata - data) < datalen)
|
||||
{
|
||||
enum MQTTReasonCodes* newrc;
|
||||
newrc = malloc(sizeof(enum MQTTReasonCodes));
|
||||
if (newrc == NULL)
|
||||
{
|
||||
if (pack->properties.array)
|
||||
free(pack->properties.array);
|
||||
if (pack)
|
||||
free(pack);
|
||||
pack = NULL; /* signal protocol error */
|
||||
goto exit;
|
||||
}
|
||||
*newrc = (enum MQTTReasonCodes)readChar(&curdata);
|
||||
ListAppend(pack->reasonCodes, newrc, sizeof(enum MQTTReasonCodes));
|
||||
}
|
||||
if (pack->reasonCodes->count == 0)
|
||||
{
|
||||
ListFree(pack->reasonCodes);
|
||||
if (pack->properties.array)
|
||||
free(pack->properties.array);
|
||||
if (pack)
|
||||
free(pack);
|
||||
pack = NULL;
|
||||
}
|
||||
}
|
||||
exit:
|
||||
FUNC_EXIT;
|
||||
return pack;
|
||||
}
|
||||
|
|
@ -0,0 +1,827 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - async client updates
|
||||
* Ian Craggs - fix for bug 432903 - queue persistence
|
||||
* Ian Craggs - MQTT V5 updates
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief Functions that apply to persistence operations.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "MQTTPersistence.h"
|
||||
#include "MQTTPersistenceDefault.h"
|
||||
#include "MQTTProtocolClient.h"
|
||||
#include "Heap.h"
|
||||
|
||||
|
||||
static MQTTPersistence_qEntry* MQTTPersistence_restoreQueueEntry(char* buffer, size_t buflen, int MQTTVersion);
|
||||
static void MQTTPersistence_insertInSeqOrder(List* list, MQTTPersistence_qEntry* qEntry, size_t size);
|
||||
|
||||
/**
|
||||
* Creates a ::MQTTClient_persistence structure representing a persistence implementation.
|
||||
* @param persistence the ::MQTTClient_persistence structure.
|
||||
* @param type the type of the persistence implementation. See ::MQTTClient_create.
|
||||
* @param pcontext the context for this persistence implementation. See ::MQTTClient_create.
|
||||
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
|
||||
*/
|
||||
#include "StackTrace.h"
|
||||
|
||||
int MQTTPersistence_create(MQTTClient_persistence** persistence, int type, void* pcontext)
|
||||
{
|
||||
int rc = 0;
|
||||
MQTTClient_persistence* per = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
#if !defined(NO_PERSISTENCE)
|
||||
switch (type)
|
||||
{
|
||||
case MQTTCLIENT_PERSISTENCE_NONE :
|
||||
per = NULL;
|
||||
break;
|
||||
case MQTTCLIENT_PERSISTENCE_DEFAULT :
|
||||
per = malloc(sizeof(MQTTClient_persistence));
|
||||
if ( per != NULL )
|
||||
{
|
||||
if ( pcontext != NULL )
|
||||
{
|
||||
if ((per->context = malloc(strlen(pcontext) + 1)) == NULL)
|
||||
{
|
||||
free(per);
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
strcpy(per->context, pcontext);
|
||||
}
|
||||
else
|
||||
per->context = "."; /* working directory */
|
||||
/* file system functions */
|
||||
per->popen = pstopen;
|
||||
per->pclose = pstclose;
|
||||
per->pput = pstput;
|
||||
per->pget = pstget;
|
||||
per->premove = pstremove;
|
||||
per->pkeys = pstkeys;
|
||||
per->pclear = pstclear;
|
||||
per->pcontainskey = pstcontainskey;
|
||||
}
|
||||
else
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
break;
|
||||
case MQTTCLIENT_PERSISTENCE_USER :
|
||||
per = (MQTTClient_persistence *)pcontext;
|
||||
if ( per == NULL || (per != NULL && (per->context == NULL || per->pclear == NULL ||
|
||||
per->pclose == NULL || per->pcontainskey == NULL || per->pget == NULL || per->pkeys == NULL ||
|
||||
per->popen == NULL || per->pput == NULL || per->premove == NULL)) )
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
break;
|
||||
default:
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
*persistence = per;
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Open persistent store and restore any persisted messages.
|
||||
* @param client the client as ::Clients.
|
||||
* @param serverURI the URI of the remote end.
|
||||
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
|
||||
*/
|
||||
int MQTTPersistence_initialize(Clients *c, const char *serverURI)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if ( c->persistence != NULL )
|
||||
{
|
||||
rc = c->persistence->popen(&(c->phandle), c->clientID, serverURI, c->persistence->context);
|
||||
if ( rc == 0 )
|
||||
rc = MQTTPersistence_restore(c);
|
||||
}
|
||||
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close persistent store.
|
||||
* @param client the client as ::Clients.
|
||||
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
|
||||
*/
|
||||
int MQTTPersistence_close(Clients *c)
|
||||
{
|
||||
int rc =0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (c->persistence != NULL)
|
||||
{
|
||||
rc = c->persistence->pclose(c->phandle);
|
||||
c->phandle = NULL;
|
||||
#if !defined(NO_PERSISTENCE)
|
||||
if ( c->persistence->popen == pstopen )
|
||||
free(c->persistence);
|
||||
#endif
|
||||
c->persistence = NULL;
|
||||
}
|
||||
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the persistent store.
|
||||
* @param client the client as ::Clients.
|
||||
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
|
||||
*/
|
||||
int MQTTPersistence_clear(Clients *c)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (c->persistence != NULL)
|
||||
rc = c->persistence->pclear(c->phandle);
|
||||
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restores the persisted records to the outbound and inbound message queues of the
|
||||
* client.
|
||||
* @param client the client as ::Clients.
|
||||
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
|
||||
*/
|
||||
int MQTTPersistence_restore(Clients *c)
|
||||
{
|
||||
int rc = 0;
|
||||
char **msgkeys = NULL,
|
||||
*buffer = NULL;
|
||||
int nkeys, buflen;
|
||||
int i = 0;
|
||||
int msgs_sent = 0;
|
||||
int msgs_rcvd = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0)
|
||||
{
|
||||
while (rc == 0 && i < nkeys)
|
||||
{
|
||||
if (strncmp(msgkeys[i], PERSISTENCE_COMMAND_KEY, strlen(PERSISTENCE_COMMAND_KEY)) == 0 ||
|
||||
strncmp(msgkeys[i], PERSISTENCE_V5_COMMAND_KEY, strlen(PERSISTENCE_V5_COMMAND_KEY)) == 0)
|
||||
{
|
||||
;
|
||||
}
|
||||
else if (strncmp(msgkeys[i], PERSISTENCE_QUEUE_KEY, strlen(PERSISTENCE_QUEUE_KEY)) == 0 ||
|
||||
strncmp(msgkeys[i], PERSISTENCE_V5_QUEUE_KEY, strlen(PERSISTENCE_V5_QUEUE_KEY)) == 0)
|
||||
{
|
||||
;
|
||||
}
|
||||
else if ((rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0)
|
||||
{
|
||||
int data_MQTTVersion = MQTTVERSION_3_1_1;
|
||||
char* cur_key = msgkeys[i];
|
||||
MQTTPacket* pack = NULL;
|
||||
|
||||
if ( strncmp(cur_key, PERSISTENCE_V5_PUBLISH_RECEIVED,
|
||||
strlen(PERSISTENCE_V5_PUBLISH_RECEIVED)) == 0)
|
||||
{
|
||||
data_MQTTVersion = MQTTVERSION_5;
|
||||
cur_key = PERSISTENCE_PUBLISH_RECEIVED;
|
||||
}
|
||||
else if (strncmp(cur_key, PERSISTENCE_V5_PUBLISH_SENT,
|
||||
strlen(PERSISTENCE_V5_PUBLISH_SENT)) == 0)
|
||||
{
|
||||
data_MQTTVersion = MQTTVERSION_5;
|
||||
cur_key = PERSISTENCE_PUBLISH_SENT;
|
||||
}
|
||||
else if (strncmp(cur_key, PERSISTENCE_V5_PUBREL,
|
||||
strlen(PERSISTENCE_V5_PUBREL)) == 0)
|
||||
{
|
||||
data_MQTTVersion = MQTTVERSION_5;
|
||||
cur_key = PERSISTENCE_PUBREL;
|
||||
}
|
||||
|
||||
if (data_MQTTVersion == MQTTVERSION_5 && c->MQTTVersion < MQTTVERSION_5)
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR; /* can't restore version 5 data with a version 3 client */
|
||||
goto exit;
|
||||
}
|
||||
|
||||
pack = MQTTPersistence_restorePacket(data_MQTTVersion, buffer, buflen);
|
||||
if ( pack != NULL )
|
||||
{
|
||||
if (strncmp(cur_key, PERSISTENCE_PUBLISH_RECEIVED,
|
||||
strlen(PERSISTENCE_PUBLISH_RECEIVED)) == 0)
|
||||
{
|
||||
Publish* publish = (Publish*)pack;
|
||||
Messages* msg = NULL;
|
||||
publish->MQTTVersion = c->MQTTVersion;
|
||||
msg = MQTTProtocol_createMessage(publish, &msg, publish->header.bits.qos, publish->header.bits.retain, 1);
|
||||
msg->nextMessageType = PUBREL;
|
||||
/* order does not matter for persisted received messages */
|
||||
ListAppend(c->inboundMsgs, msg, msg->len);
|
||||
if (c->MQTTVersion >= MQTTVERSION_5)
|
||||
{
|
||||
free(msg->publish->payload);
|
||||
free(msg->publish->topic);
|
||||
msg->publish->payload = msg->publish->topic = NULL;
|
||||
}
|
||||
publish->topic = NULL;
|
||||
MQTTPacket_freePublish(publish);
|
||||
msgs_rcvd++;
|
||||
}
|
||||
else if (strncmp(cur_key, PERSISTENCE_PUBLISH_SENT,
|
||||
strlen(PERSISTENCE_PUBLISH_SENT)) == 0)
|
||||
{
|
||||
Publish* publish = (Publish*)pack;
|
||||
Messages* msg = NULL;
|
||||
char *key = malloc(MESSAGE_FILENAME_LENGTH + 1);
|
||||
|
||||
if (!key)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
publish->MQTTVersion = c->MQTTVersion;
|
||||
if (publish->MQTTVersion >= MQTTVERSION_5)
|
||||
sprintf(key, "%s%d", PERSISTENCE_V5_PUBREL, publish->msgId);
|
||||
else
|
||||
sprintf(key, "%s%d", PERSISTENCE_PUBREL, publish->msgId);
|
||||
msg = MQTTProtocol_createMessage(publish, &msg, publish->header.bits.qos, publish->header.bits.retain, 1);
|
||||
if (c->persistence->pcontainskey(c->phandle, key) == 0)
|
||||
/* PUBLISH Qo2 and PUBREL sent */
|
||||
msg->nextMessageType = PUBCOMP;
|
||||
/* else: PUBLISH QoS1, or PUBLISH QoS2 and PUBREL not sent */
|
||||
/* retry at the first opportunity */
|
||||
memset(&msg->lastTouch, '\0', sizeof(msg->lastTouch));
|
||||
MQTTPersistence_insertInOrder(c->outboundMsgs, msg, msg->len);
|
||||
publish->topic = NULL;
|
||||
MQTTPacket_freePublish(publish);
|
||||
free(key);
|
||||
msgs_sent++;
|
||||
}
|
||||
else if (strncmp(cur_key, PERSISTENCE_PUBREL, strlen(PERSISTENCE_PUBREL)) == 0)
|
||||
{
|
||||
/* orphaned PUBRELs ? */
|
||||
Pubrel* pubrel = (Pubrel*)pack;
|
||||
char *key = malloc(MESSAGE_FILENAME_LENGTH + 1);
|
||||
|
||||
if (!key)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
pubrel->MQTTVersion = c->MQTTVersion;
|
||||
if (pubrel->MQTTVersion >= MQTTVERSION_5)
|
||||
sprintf(key, "%s%d", PERSISTENCE_V5_PUBLISH_SENT, pubrel->msgId);
|
||||
else
|
||||
sprintf(key, "%s%d", PERSISTENCE_PUBLISH_SENT, pubrel->msgId);
|
||||
if (c->persistence->pcontainskey(c->phandle, key) != 0)
|
||||
rc = c->persistence->premove(c->phandle, msgkeys[i]);
|
||||
free(pubrel);
|
||||
free(key);
|
||||
}
|
||||
}
|
||||
else /* pack == NULL -> bad persisted record */
|
||||
rc = c->persistence->premove(c->phandle, msgkeys[i]);
|
||||
}
|
||||
if (buffer)
|
||||
{
|
||||
free(buffer);
|
||||
buffer = NULL;
|
||||
}
|
||||
if (msgkeys[i])
|
||||
free(msgkeys[i]);
|
||||
i++;
|
||||
}
|
||||
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);
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a MQTT packet restored from persisted data.
|
||||
* @param buffer the persisted data.
|
||||
* @param buflen the number of bytes of the data buffer.
|
||||
*/
|
||||
void* MQTTPersistence_restorePacket(int MQTTVersion, char* buffer, size_t buflen)
|
||||
{
|
||||
void* pack = NULL;
|
||||
Header header;
|
||||
int fixed_header_length = 1, ptype, remaining_length = 0;
|
||||
char c;
|
||||
int multiplier = 1;
|
||||
extern pf new_packets[];
|
||||
|
||||
FUNC_ENTRY;
|
||||
header.byte = buffer[0];
|
||||
/* decode the message length according to the MQTT algorithm */
|
||||
do
|
||||
{
|
||||
c = *(++buffer);
|
||||
remaining_length += (c & 127) * multiplier;
|
||||
multiplier *= 128;
|
||||
fixed_header_length++;
|
||||
} while ((c & 128) != 0);
|
||||
|
||||
if ( (fixed_header_length + remaining_length) == buflen )
|
||||
{
|
||||
ptype = header.bits.type;
|
||||
if (ptype >= CONNECT && ptype <= DISCONNECT && new_packets[ptype] != NULL)
|
||||
pack = (*new_packets[ptype])(MQTTVersion, header.byte, ++buffer, remaining_length);
|
||||
}
|
||||
|
||||
FUNC_EXIT;
|
||||
return pack;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inserts the specified message into the list, maintaining message ID order.
|
||||
* @param list the list to insert the message into.
|
||||
* @param content the message to add.
|
||||
* @param size size of the message.
|
||||
*/
|
||||
void MQTTPersistence_insertInOrder(List* list, void* content, size_t size)
|
||||
{
|
||||
ListElement* index = NULL;
|
||||
ListElement* current = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
while(ListNextElement(list, ¤t) != NULL && index == NULL)
|
||||
{
|
||||
if ( ((Messages*)content)->msgid < ((Messages*)current->content)->msgid )
|
||||
index = current;
|
||||
}
|
||||
|
||||
ListInsert(list, content, size, index);
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a record to the persistent store. This function must not be called for QoS0
|
||||
* messages.
|
||||
* @param socket the socket of the client.
|
||||
* @param buf0 fixed header.
|
||||
* @param buf0len length of the fixed header.
|
||||
* @param count number of buffers representing the variable header and/or the payload.
|
||||
* @param buffers the buffers representing the variable header and/or the payload.
|
||||
* @param buflens length of the buffers representing the variable header and/or the payload.
|
||||
* @param htype MQTT packet type - PUBLISH or PUBREL
|
||||
* @param msgId the message ID.
|
||||
* @param scr 0 indicates message in the sending direction; 1 indicates message in the
|
||||
* receiving direction.
|
||||
* @param the MQTT version being used (>= MQTTVERSION_5 means properties included)
|
||||
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
|
||||
*/
|
||||
int MQTTPersistence_put(int socket, char* buf0, size_t buf0len, int count,
|
||||
char** buffers, size_t* buflens, int htype, int msgId, int scr, int MQTTVersion)
|
||||
{
|
||||
int rc = 0;
|
||||
extern ClientStates* bstate;
|
||||
int nbufs, i;
|
||||
int* lens = NULL;
|
||||
char** bufs = NULL;
|
||||
char *key;
|
||||
Clients* client = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
client = (Clients*)(ListFindItem(bstate->clients, &socket, clientSocketCompare)->content);
|
||||
if (client->persistence != NULL)
|
||||
{
|
||||
if ((key = malloc(MESSAGE_FILENAME_LENGTH + 1)) == NULL)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
nbufs = 1 + count;
|
||||
if ((lens = (int *)malloc(nbufs * sizeof(int))) == NULL)
|
||||
{
|
||||
free(key);
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
if ((bufs = (char **)malloc(nbufs * sizeof(char *))) == NULL)
|
||||
{
|
||||
free(key);
|
||||
free(lens);
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
lens[0] = (int)buf0len;
|
||||
bufs[0] = buf0;
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
lens[i+1] = (int)buflens[i];
|
||||
bufs[i+1] = buffers[i];
|
||||
}
|
||||
|
||||
/* key */
|
||||
if (scr == 0)
|
||||
{ /* sending */
|
||||
char* key_id = PERSISTENCE_PUBLISH_SENT;
|
||||
|
||||
if (htype == PUBLISH) /* PUBLISH QoS1 and QoS2*/
|
||||
{
|
||||
if (MQTTVersion >= MQTTVERSION_5)
|
||||
key_id = PERSISTENCE_V5_PUBLISH_SENT;
|
||||
}
|
||||
else if (htype == PUBREL) /* PUBREL */
|
||||
{
|
||||
if (MQTTVersion >= MQTTVERSION_5)
|
||||
key_id = PERSISTENCE_V5_PUBREL;
|
||||
else
|
||||
key_id = PERSISTENCE_PUBREL;
|
||||
}
|
||||
sprintf(key, "%s%d", key_id, msgId);
|
||||
}
|
||||
else if (scr == 1) /* receiving PUBLISH QoS2 */
|
||||
{
|
||||
char* key_id = PERSISTENCE_PUBLISH_RECEIVED;
|
||||
|
||||
if (MQTTVersion >= MQTTVERSION_5)
|
||||
key_id = PERSISTENCE_V5_PUBLISH_RECEIVED;
|
||||
sprintf(key, "%s%d", key_id, msgId);
|
||||
}
|
||||
|
||||
rc = client->persistence->pput(client->phandle, key, nbufs, bufs, lens);
|
||||
|
||||
free(key);
|
||||
free(lens);
|
||||
free(bufs);
|
||||
}
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes a record from the persistent store.
|
||||
* @param client the client as ::Clients.
|
||||
* @param type the type of the persisted record: #PERSISTENCE_PUBLISH_SENT, #PERSISTENCE_PUBREL
|
||||
* or #PERSISTENCE_PUBLISH_RECEIVED.
|
||||
* @param qos the qos field of the message.
|
||||
* @param msgId the message ID.
|
||||
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
|
||||
*/
|
||||
int MQTTPersistence_remove(Clients* c, char *type, int qos, int msgId)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (c->persistence != NULL)
|
||||
{
|
||||
char *key = malloc(MESSAGE_FILENAME_LENGTH + 1);
|
||||
|
||||
if (!key)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
if (strcmp(type, PERSISTENCE_PUBLISH_SENT) == 0 ||
|
||||
strcmp(type, PERSISTENCE_V5_PUBLISH_SENT) == 0)
|
||||
{
|
||||
sprintf(key, "%s%d", PERSISTENCE_V5_PUBLISH_SENT, msgId) ;
|
||||
rc = c->persistence->premove(c->phandle, key);
|
||||
sprintf(key, "%s%d", PERSISTENCE_V5_PUBREL, msgId) ;
|
||||
rc += c->persistence->premove(c->phandle, key);
|
||||
sprintf(key, "%s%d", PERSISTENCE_PUBLISH_SENT, msgId) ;
|
||||
rc += c->persistence->premove(c->phandle, key);
|
||||
sprintf(key, "%s%d", PERSISTENCE_PUBREL, msgId) ;
|
||||
rc += c->persistence->premove(c->phandle, key);
|
||||
}
|
||||
else /* PERSISTENCE_PUBLISH_SENT && qos == 1 */
|
||||
{ /* or PERSISTENCE_PUBLISH_RECEIVED */
|
||||
|
||||
sprintf(key, "%s%d", PERSISTENCE_V5_PUBLISH_RECEIVED, msgId);
|
||||
rc = c->persistence->premove(c->phandle, key);
|
||||
sprintf(key, "%s%d", PERSISTENCE_PUBLISH_RECEIVED, msgId);
|
||||
rc += c->persistence->premove(c->phandle, key);
|
||||
}
|
||||
free(key);
|
||||
}
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the message IDs wrapped by looking for the largest gap between two consecutive
|
||||
* message IDs in the outboundMsgs queue.
|
||||
* @param client the client as ::Clients.
|
||||
*/
|
||||
void MQTTPersistence_wrapMsgID(Clients *client)
|
||||
{
|
||||
ListElement* wrapel = NULL;
|
||||
ListElement* current = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if ( client->outboundMsgs->count > 0 )
|
||||
{
|
||||
int firstMsgID = ((Messages*)client->outboundMsgs->first->content)->msgid;
|
||||
int lastMsgID = ((Messages*)client->outboundMsgs->last->content)->msgid;
|
||||
int gap = MAX_MSG_ID - lastMsgID + firstMsgID;
|
||||
current = ListNextElement(client->outboundMsgs, ¤t);
|
||||
|
||||
while(ListNextElement(client->outboundMsgs, ¤t) != NULL)
|
||||
{
|
||||
int curMsgID = ((Messages*)current->content)->msgid;
|
||||
int curPrevMsgID = ((Messages*)current->prev->content)->msgid;
|
||||
int curgap = curMsgID - curPrevMsgID;
|
||||
if ( curgap > gap )
|
||||
{
|
||||
gap = curgap;
|
||||
wrapel = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( wrapel != NULL )
|
||||
{
|
||||
/* put wrapel at the beginning of the queue */
|
||||
client->outboundMsgs->first->prev = client->outboundMsgs->last;
|
||||
client->outboundMsgs->last->next = client->outboundMsgs->first;
|
||||
client->outboundMsgs->first = wrapel;
|
||||
client->outboundMsgs->last = wrapel->prev;
|
||||
client->outboundMsgs->first->prev = NULL;
|
||||
client->outboundMsgs->last->next = NULL;
|
||||
}
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
#if !defined(NO_PERSISTENCE)
|
||||
int MQTTPersistence_unpersistQueueEntry(Clients* client, MQTTPersistence_qEntry* qe)
|
||||
{
|
||||
int rc = 0;
|
||||
char key[PERSISTENCE_MAX_KEY_LENGTH + 1];
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (client->MQTTVersion >= MQTTVERSION_5)
|
||||
sprintf(key, "%s%u", PERSISTENCE_V5_QUEUE_KEY, qe->seqno);
|
||||
else
|
||||
sprintf(key, "%s%u", 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;
|
||||
}
|
||||
|
||||
|
||||
#define MAX_NO_OF_BUFFERS 9
|
||||
int MQTTPersistence_persistQueueEntry(Clients* aclient, MQTTPersistence_qEntry* qe)
|
||||
{
|
||||
int rc = 0;
|
||||
int bufindex = 0;
|
||||
char key[PERSISTENCE_MAX_KEY_LENGTH + 1];
|
||||
int lens[MAX_NO_OF_BUFFERS];
|
||||
void* bufs[MAX_NO_OF_BUFFERS];
|
||||
int props_allocated = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
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++] = (int)strlen(qe->topicName) + 1;
|
||||
|
||||
bufs[bufindex] = &qe->topicLen;
|
||||
lens[bufindex++] = sizeof(qe->topicLen);
|
||||
|
||||
if (aclient->MQTTVersion >= MQTTVERSION_5) /* persist properties */
|
||||
{
|
||||
MQTTProperties no_props = MQTTProperties_initializer;
|
||||
MQTTProperties* props = &no_props;
|
||||
int temp_len = 0;
|
||||
char* ptr = NULL;
|
||||
|
||||
if (qe->msg->struct_version >= 1)
|
||||
props = &qe->msg->properties;
|
||||
|
||||
temp_len = MQTTProperties_len(props);
|
||||
ptr = bufs[bufindex] = malloc(temp_len);
|
||||
if (!ptr)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
props_allocated = bufindex;
|
||||
rc = MQTTProperties_write(&ptr, props);
|
||||
lens[bufindex++] = temp_len;
|
||||
|
||||
sprintf(key, "%s%u", PERSISTENCE_V5_QUEUE_KEY, ++aclient->qentry_seqno);
|
||||
}
|
||||
else
|
||||
sprintf(key, "%s%u", PERSISTENCE_QUEUE_KEY, ++aclient->qentry_seqno);
|
||||
|
||||
qe->seqno = aclient->qentry_seqno;
|
||||
|
||||
if ((rc = aclient->persistence->pput(aclient->phandle, key, bufindex, (char**)bufs, lens)) != 0)
|
||||
Log(LOG_ERROR, 0, "Error persisting queue entry, rc %d", rc);
|
||||
|
||||
if (props_allocated != 0)
|
||||
free(bufs[props_allocated]);
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static MQTTPersistence_qEntry* MQTTPersistence_restoreQueueEntry(char* buffer, size_t buflen, int MQTTVersion)
|
||||
{
|
||||
MQTTPersistence_qEntry* qe = NULL;
|
||||
char* ptr = buffer;
|
||||
int data_size;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if ((qe = malloc(sizeof(MQTTPersistence_qEntry))) == NULL)
|
||||
goto exit;
|
||||
memset(qe, '\0', sizeof(MQTTPersistence_qEntry));
|
||||
|
||||
if ((qe->msg = malloc(sizeof(MQTTPersistence_message))) == NULL)
|
||||
{
|
||||
free(qe);
|
||||
qe = NULL;
|
||||
goto exit;
|
||||
}
|
||||
memset(qe->msg, '\0', sizeof(MQTTPersistence_message));
|
||||
|
||||
qe->msg->struct_version = 1;
|
||||
|
||||
qe->msg->payloadlen = *(int*)ptr;
|
||||
ptr += sizeof(int);
|
||||
|
||||
data_size = qe->msg->payloadlen;
|
||||
if ((qe->msg->payload = malloc(data_size)) == NULL)
|
||||
{
|
||||
free(qe->msg);
|
||||
free(qe);
|
||||
qe = NULL;
|
||||
goto exit;
|
||||
}
|
||||
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 = (int)strlen(ptr) + 1;
|
||||
if ((qe->topicName = malloc(data_size)) == NULL)
|
||||
{
|
||||
free(qe->msg->payload);
|
||||
free(qe->msg);
|
||||
free(qe);
|
||||
qe = NULL;
|
||||
goto exit;
|
||||
}
|
||||
strcpy(qe->topicName, ptr);
|
||||
ptr += data_size;
|
||||
|
||||
qe->topicLen = *(int*)ptr;
|
||||
ptr += sizeof(int);
|
||||
|
||||
if (MQTTVersion >= MQTTVERSION_5 &&
|
||||
MQTTProperties_read(&qe->msg->properties, &ptr, buffer + buflen) != 1)
|
||||
Log(LOG_ERROR, -1, "Error restoring properties from persistence");
|
||||
|
||||
exit:
|
||||
FUNC_EXIT;
|
||||
return qe;
|
||||
}
|
||||
|
||||
|
||||
static void MQTTPersistence_insertInSeqOrder(List* list, MQTTPersistence_qEntry* qEntry, size_t size)
|
||||
{
|
||||
ListElement* index = NULL;
|
||||
ListElement* current = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
while (ListNextElement(list, ¤t) != NULL && index == NULL)
|
||||
{
|
||||
if (qEntry->seqno < ((MQTTPersistence_qEntry*)current->content)->seqno)
|
||||
index = current;
|
||||
}
|
||||
ListInsert(list, qEntry, size, index);
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restores a queue of messages from persistence to memory
|
||||
* @param c the client as ::Clients - the client object to restore the messages to
|
||||
* @return return code, 0 if successful
|
||||
*/
|
||||
int MQTTPersistence_restoreMessageQueue(Clients* c)
|
||||
{
|
||||
int rc = 0;
|
||||
char **msgkeys;
|
||||
int nkeys;
|
||||
int i = 0;
|
||||
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 &&
|
||||
strncmp(msgkeys[i], PERSISTENCE_V5_QUEUE_KEY, strlen(PERSISTENCE_V5_QUEUE_KEY)) != 0)
|
||||
{
|
||||
; /* ignore if not a queue entry key */
|
||||
}
|
||||
else if ((rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0)
|
||||
{
|
||||
int MQTTVersion =
|
||||
(strncmp(msgkeys[i], PERSISTENCE_V5_QUEUE_KEY, strlen(PERSISTENCE_V5_QUEUE_KEY)) == 0)
|
||||
? MQTTVERSION_5 : MQTTVERSION_3_1_1;
|
||||
MQTTPersistence_qEntry* qe = MQTTPersistence_restoreQueueEntry(buffer, buflen, MQTTVersion);
|
||||
|
||||
if (qe)
|
||||
{
|
||||
qe->seqno = atoi(strchr(msgkeys[i], '-')+1); /* key format is tag'-'seqno */
|
||||
MQTTPersistence_insertInSeqOrder(c->messageQueue, qe, sizeof(MQTTPersistence_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
|
||||
|
|
@ -0,0 +1,950 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - async client updates
|
||||
* Ian Craggs - fix for bug 484496
|
||||
* Ian Craggs - fix for issue 285
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief A file system based persistence implementation.
|
||||
*
|
||||
* A directory is specified when the MQTT client is created. When the persistence is then
|
||||
* opened (see ::Persistence_open), a sub-directory is made beneath the base for this
|
||||
* particular client ID and connection key. This allows one persistence base directory to
|
||||
* be shared by multiple clients.
|
||||
*
|
||||
*/
|
||||
|
||||
#if !defined(NO_PERSISTENCE)
|
||||
|
||||
#include "OsWrapper.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <direct.h>
|
||||
/* Windows doesn't have strtok_r, so remap it to strtok */
|
||||
#define strtok_r( A, B, C ) strtok( A, B )
|
||||
int keysWin32(char *, char ***, int *);
|
||||
int clearWin32(char *);
|
||||
int containskeyWin32(char *, char *);
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
int keysUnix(char *, char ***, int *);
|
||||
int clearUnix(char *);
|
||||
int containskeyUnix(char *, char *);
|
||||
#endif
|
||||
|
||||
#include "MQTTClientPersistence.h"
|
||||
#include "MQTTPersistenceDefault.h"
|
||||
#include "StackTrace.h"
|
||||
#include "Heap.h"
|
||||
|
||||
/** Create persistence directory for the client: context/clientID-serverURI.
|
||||
* See ::Persistence_open
|
||||
*/
|
||||
|
||||
int pstopen(void **handle, const char* clientID, const char* serverURI, void* context)
|
||||
{
|
||||
int rc = 0;
|
||||
char *dataDir = context;
|
||||
char *clientDir;
|
||||
char *pToken = NULL;
|
||||
char *save_ptr = NULL;
|
||||
char *pCrtDirName = NULL;
|
||||
char *pTokDirName = NULL;
|
||||
char *perserverURI = NULL, *ptraux;
|
||||
|
||||
FUNC_ENTRY;
|
||||
/* Note that serverURI=address:port, but ":" not allowed in Windows directories */
|
||||
if ((perserverURI = malloc(strlen(serverURI) + 1)) == NULL)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
strcpy(perserverURI, serverURI);
|
||||
while ((ptraux = strstr(perserverURI, ":")) != NULL)
|
||||
*ptraux = '-' ;
|
||||
|
||||
/* consider '/' + '-' + '\0' */
|
||||
clientDir = malloc(strlen(dataDir) + strlen(clientID) + strlen(perserverURI) + 3);
|
||||
if (!clientDir)
|
||||
{
|
||||
free(perserverURI);
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
sprintf(clientDir, "%s/%s-%s", dataDir, clientID, perserverURI);
|
||||
|
||||
|
||||
/* create clientDir directory */
|
||||
|
||||
/* pCrtDirName - holds the directory name we are currently trying to create. */
|
||||
/* This gets built up level by level untipwdl the full path name is created.*/
|
||||
/* pTokDirName - holds the directory name that gets used by strtok. */
|
||||
if ((pCrtDirName = (char*)malloc(strlen(clientDir) + 1)) == NULL)
|
||||
{
|
||||
free(clientDir);
|
||||
free(perserverURI);
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
if ((pTokDirName = (char*)malloc( strlen(clientDir) + 1 )) == NULL)
|
||||
{
|
||||
free(pCrtDirName);
|
||||
free(clientDir);
|
||||
free(perserverURI);
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
strcpy( pTokDirName, clientDir );
|
||||
|
||||
/* If first character is directory separator, make sure it's in the created directory name #285 */
|
||||
if (*pTokDirName == '/' || *pTokDirName == '\\')
|
||||
{
|
||||
*pCrtDirName = *pTokDirName;
|
||||
pToken = strtok_r( pTokDirName + 1, "\\/", &save_ptr );
|
||||
strcpy( pCrtDirName + 1, pToken );
|
||||
}
|
||||
else
|
||||
{
|
||||
pToken = strtok_r( pTokDirName, "\\/", &save_ptr );
|
||||
strcpy( pCrtDirName, pToken );
|
||||
}
|
||||
|
||||
rc = pstmkdir( pCrtDirName );
|
||||
pToken = strtok_r( NULL, "\\/", &save_ptr );
|
||||
while ( (pToken != NULL) && (rc == 0) )
|
||||
{
|
||||
/* Append the next directory level and try to create it */
|
||||
strcat( pCrtDirName, "/" );
|
||||
strcat( pCrtDirName, pToken );
|
||||
rc = pstmkdir( pCrtDirName );
|
||||
pToken = strtok_r( NULL, "\\/", &save_ptr );
|
||||
}
|
||||
|
||||
*handle = clientDir;
|
||||
|
||||
free(pTokDirName);
|
||||
free(pCrtDirName);
|
||||
free(perserverURI);
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/** Function to create a directory.
|
||||
* Returns 0 on success or if the directory already exists.
|
||||
*/
|
||||
int pstmkdir( char *pPathname )
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
if ( _mkdir( pPathname ) != 0 )
|
||||
{
|
||||
#else
|
||||
/* Create a directory with read, write and execute access for the owner and read access for the group */
|
||||
#if !defined(_WRS_KERNEL)
|
||||
if ( mkdir( pPathname, S_IRWXU | S_IRGRP ) != 0 )
|
||||
#else
|
||||
if ( mkdir( pPathname ) != 0 )
|
||||
#endif /* !defined(_WRS_KERNEL) */
|
||||
{
|
||||
#endif
|
||||
if ( errno != EEXIST )
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
}
|
||||
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Write wire message to the client persistence directory.
|
||||
* See ::Persistence_put
|
||||
*/
|
||||
int pstput(void* handle, char* key, int bufcount, char* buffers[], int buflens[])
|
||||
{
|
||||
int rc = 0;
|
||||
char *clientDir = handle;
|
||||
char *file;
|
||||
FILE *fp;
|
||||
size_t bytesWritten = 0,
|
||||
bytesTotal = 0;
|
||||
int i;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (clientDir == NULL)
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* consider '/' + '\0' */
|
||||
file = malloc(strlen(clientDir) + strlen(key) + strlen(MESSAGE_FILENAME_EXTENSION) + 2 );
|
||||
if (!file)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
sprintf(file, "%s/%s%s", clientDir, key, MESSAGE_FILENAME_EXTENSION);
|
||||
|
||||
fp = fopen(file, "wb");
|
||||
if ( fp != NULL )
|
||||
{
|
||||
for(i=0; i<bufcount; i++)
|
||||
{
|
||||
bytesTotal += buflens[i];
|
||||
bytesWritten += fwrite(buffers[i], sizeof(char), buflens[i], fp );
|
||||
}
|
||||
fclose(fp);
|
||||
fp = NULL;
|
||||
} else
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
|
||||
if (bytesWritten != bytesTotal)
|
||||
{
|
||||
pstremove(handle, key);
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
}
|
||||
|
||||
free(file);
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
};
|
||||
|
||||
|
||||
/** Retrieve a wire message from the client persistence directory.
|
||||
* See ::Persistence_get
|
||||
*/
|
||||
int pstget(void* handle, char* key, char** buffer, int* buflen)
|
||||
{
|
||||
int rc = 0;
|
||||
FILE *fp = NULL;
|
||||
char *clientDir = handle;
|
||||
char *file = NULL;
|
||||
char *buf = NULL;
|
||||
unsigned long fileLen = 0;
|
||||
unsigned long bytesRead = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (clientDir == NULL)
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* consider '/' + '\0' */
|
||||
file = malloc(strlen(clientDir) + strlen(key) + strlen(MESSAGE_FILENAME_EXTENSION) + 2);
|
||||
if (!file)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
sprintf(file, "%s/%s%s", clientDir, key, MESSAGE_FILENAME_EXTENSION);
|
||||
|
||||
fp = fopen(file, "rb");
|
||||
if ( fp != NULL )
|
||||
{
|
||||
fseek(fp, 0, SEEK_END);
|
||||
fileLen = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
if ((buf = (char *)malloc(fileLen)) == NULL)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
bytesRead = (int)fread(buf, sizeof(char), fileLen, fp);
|
||||
*buffer = buf;
|
||||
*buflen = bytesRead;
|
||||
if ( bytesRead != fileLen )
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
} else
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
|
||||
/* the caller must free buf */
|
||||
exit:
|
||||
if (file)
|
||||
free(file);
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Delete a persisted message from the client persistence directory.
|
||||
* See ::Persistence_remove
|
||||
*/
|
||||
int pstremove(void* handle, char* key)
|
||||
{
|
||||
int rc = 0;
|
||||
char *clientDir = handle;
|
||||
char *file;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (clientDir == NULL)
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* consider '/' + '\0' */
|
||||
file = malloc(strlen(clientDir) + strlen(key) + strlen(MESSAGE_FILENAME_EXTENSION) + 2);
|
||||
if (!file)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
sprintf(file, "%s/%s%s", clientDir, key, MESSAGE_FILENAME_EXTENSION);
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
if ( _unlink(file) != 0 )
|
||||
{
|
||||
#else
|
||||
if ( unlink(file) != 0 )
|
||||
{
|
||||
#endif
|
||||
if ( errno != ENOENT )
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
}
|
||||
|
||||
free(file);
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/** Delete client persistence directory (if empty).
|
||||
* See ::Persistence_close
|
||||
*/
|
||||
int pstclose(void* handle)
|
||||
{
|
||||
int rc = 0;
|
||||
char *clientDir = handle;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (clientDir == NULL)
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
if ( _rmdir(clientDir) != 0 )
|
||||
{
|
||||
#else
|
||||
if ( rmdir(clientDir) != 0 )
|
||||
{
|
||||
#endif
|
||||
if ( errno != ENOENT && errno != ENOTEMPTY )
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
}
|
||||
|
||||
free(clientDir);
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/** Returns whether if a wire message is persisted in the client persistence directory.
|
||||
* See ::Persistence_containskey
|
||||
*/
|
||||
int pstcontainskey(void *handle, char *key)
|
||||
{
|
||||
int rc = 0;
|
||||
char *clientDir = handle;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (clientDir == NULL)
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
rc = containskeyWin32(clientDir, key);
|
||||
#else
|
||||
rc = containskeyUnix(clientDir, key);
|
||||
#endif
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
int containskeyWin32(char *dirname, char *key)
|
||||
{
|
||||
int notFound = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
int fFinished = 0;
|
||||
char *filekey, *ptraux;
|
||||
char dir[MAX_PATH+1];
|
||||
WIN32_FIND_DATAA FileData;
|
||||
HANDLE hDir;
|
||||
|
||||
FUNC_ENTRY;
|
||||
sprintf(dir, "%s/*", dirname);
|
||||
|
||||
hDir = FindFirstFileA(dir, &FileData);
|
||||
if (hDir != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
while (!fFinished)
|
||||
{
|
||||
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
|
||||
{
|
||||
if ((filekey = malloc(strlen(FileData.cFileName) + 1)) == NULL)
|
||||
{
|
||||
notFound = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
strcpy(filekey, FileData.cFileName);
|
||||
ptraux = strstr(filekey, MESSAGE_FILENAME_EXTENSION);
|
||||
if ( ptraux != NULL )
|
||||
*ptraux = '\0' ;
|
||||
if(strcmp(filekey, key) == 0)
|
||||
{
|
||||
notFound = 0;
|
||||
fFinished = 1;
|
||||
}
|
||||
free(filekey);
|
||||
}
|
||||
if (!FindNextFileA(hDir, &FileData))
|
||||
{
|
||||
if (GetLastError() == ERROR_NO_MORE_FILES)
|
||||
fFinished = 1;
|
||||
}
|
||||
}
|
||||
FindClose(hDir);
|
||||
}
|
||||
exit:
|
||||
FUNC_EXIT_RC(notFound);
|
||||
return notFound;
|
||||
}
|
||||
#else
|
||||
int containskeyUnix(char *dirname, char *key)
|
||||
{
|
||||
int notFound = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
char *filekey, *ptraux;
|
||||
DIR *dp = NULL;
|
||||
struct dirent *dir_entry;
|
||||
struct stat stat_info;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if((dp = opendir(dirname)) != NULL)
|
||||
{
|
||||
while((dir_entry = readdir(dp)) != NULL && notFound)
|
||||
{
|
||||
char* filename = malloc(strlen(dirname) + strlen(dir_entry->d_name) + 2);
|
||||
|
||||
if (!filename)
|
||||
{
|
||||
notFound = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
sprintf(filename, "%s/%s", dirname, dir_entry->d_name);
|
||||
lstat(filename, &stat_info);
|
||||
free(filename);
|
||||
if(S_ISREG(stat_info.st_mode))
|
||||
{
|
||||
if ((filekey = malloc(strlen(dir_entry->d_name) + 1)) == NULL)
|
||||
{
|
||||
notFound = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
strcpy(filekey, dir_entry->d_name);
|
||||
ptraux = strstr(filekey, MESSAGE_FILENAME_EXTENSION);
|
||||
if ( ptraux != NULL )
|
||||
*ptraux = '\0' ;
|
||||
if(strcmp(filekey, key) == 0)
|
||||
notFound = 0;
|
||||
free(filekey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
if (dp)
|
||||
closedir(dp);
|
||||
FUNC_EXIT_RC(notFound);
|
||||
return notFound;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/** Delete all the persisted message in the client persistence directory.
|
||||
* See ::Persistence_clear
|
||||
*/
|
||||
int pstclear(void *handle)
|
||||
{
|
||||
int rc = 0;
|
||||
char *clientDir = handle;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (clientDir == NULL)
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
rc = clearWin32(clientDir);
|
||||
#else
|
||||
rc = clearUnix(clientDir);
|
||||
#endif
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
int clearWin32(char *dirname)
|
||||
{
|
||||
int rc = 0;
|
||||
int fFinished = 0;
|
||||
char *file;
|
||||
char dir[MAX_PATH+1];
|
||||
WIN32_FIND_DATAA FileData;
|
||||
HANDLE hDir;
|
||||
|
||||
FUNC_ENTRY;
|
||||
sprintf(dir, "%s/*", dirname);
|
||||
|
||||
hDir = FindFirstFileA(dir, &FileData);
|
||||
if (hDir != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
while (!fFinished)
|
||||
{
|
||||
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
|
||||
{
|
||||
file = malloc(strlen(dirname) + strlen(FileData.cFileName) + 2);
|
||||
if (!file)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
sprintf(file, "%s/%s", dirname, FileData.cFileName);
|
||||
rc = remove(file);
|
||||
free(file);
|
||||
if ( rc != 0 )
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!FindNextFileA(hDir, &FileData))
|
||||
{
|
||||
if (GetLastError() == ERROR_NO_MORE_FILES)
|
||||
fFinished = 1;
|
||||
}
|
||||
}
|
||||
FindClose(hDir);
|
||||
} else
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
int clearUnix(char *dirname)
|
||||
{
|
||||
int rc = 0;
|
||||
DIR *dp;
|
||||
struct dirent *dir_entry;
|
||||
struct stat stat_info;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if((dp = opendir(dirname)) != NULL)
|
||||
{
|
||||
while((dir_entry = readdir(dp)) != NULL && rc == 0)
|
||||
{
|
||||
lstat(dir_entry->d_name, &stat_info);
|
||||
if(S_ISREG(stat_info.st_mode))
|
||||
{
|
||||
if (remove(dir_entry->d_name) != 0 && errno != ENOENT)
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
}
|
||||
}
|
||||
closedir(dp);
|
||||
} else
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/** Returns the keys (file names w/o the extension) in the client persistence directory.
|
||||
* See ::Persistence_keys
|
||||
*/
|
||||
int pstkeys(void *handle, char ***keys, int *nkeys)
|
||||
{
|
||||
int rc = 0;
|
||||
char *clientDir = handle;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (clientDir == NULL)
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
rc = keysWin32(clientDir, keys, nkeys);
|
||||
#else
|
||||
rc = keysUnix(clientDir, keys, nkeys);
|
||||
#endif
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
int keysWin32(char *dirname, char ***keys, int *nkeys)
|
||||
{
|
||||
int rc = 0;
|
||||
char **fkeys = NULL;
|
||||
int nfkeys = 0;
|
||||
char dir[MAX_PATH+1];
|
||||
WIN32_FIND_DATAA FileData;
|
||||
HANDLE hDir;
|
||||
int fFinished = 0;
|
||||
char *ptraux;
|
||||
int i;
|
||||
|
||||
FUNC_ENTRY;
|
||||
sprintf(dir, "%s/*", dirname);
|
||||
|
||||
/* get number of keys */
|
||||
hDir = FindFirstFileA(dir, &FileData);
|
||||
if (hDir != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
while (!fFinished)
|
||||
{
|
||||
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
|
||||
nfkeys++;
|
||||
if (!FindNextFileA(hDir, &FileData))
|
||||
{
|
||||
if (GetLastError() == ERROR_NO_MORE_FILES)
|
||||
fFinished = 1;
|
||||
}
|
||||
}
|
||||
FindClose(hDir);
|
||||
} else
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (nfkeys != 0)
|
||||
{
|
||||
if ((fkeys = (char **)malloc(nfkeys * sizeof(char *))) == NULL)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
/* copy the keys */
|
||||
hDir = FindFirstFileA(dir, &FileData);
|
||||
if (hDir != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
fFinished = 0;
|
||||
i = 0;
|
||||
while (!fFinished)
|
||||
{
|
||||
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
|
||||
{
|
||||
if ((fkeys[i] = malloc(strlen(FileData.cFileName) + 1)) == NULL)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
strcpy(fkeys[i], FileData.cFileName);
|
||||
ptraux = strstr(fkeys[i], MESSAGE_FILENAME_EXTENSION);
|
||||
if ( ptraux != NULL )
|
||||
*ptraux = '\0' ;
|
||||
i++;
|
||||
}
|
||||
if (!FindNextFileA(hDir, &FileData))
|
||||
{
|
||||
if (GetLastError() == ERROR_NO_MORE_FILES)
|
||||
fFinished = 1;
|
||||
}
|
||||
}
|
||||
FindClose(hDir);
|
||||
} else
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
*nkeys = nfkeys;
|
||||
*keys = fkeys;
|
||||
/* the caller must free keys */
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
#else
|
||||
int keysUnix(char *dirname, char ***keys, int *nkeys)
|
||||
{
|
||||
int rc = 0;
|
||||
char **fkeys = NULL;
|
||||
int nfkeys = 0;
|
||||
char *ptraux;
|
||||
int i;
|
||||
DIR *dp = NULL;
|
||||
struct dirent *dir_entry;
|
||||
struct stat stat_info;
|
||||
|
||||
FUNC_ENTRY;
|
||||
/* get number of keys */
|
||||
if((dp = opendir(dirname)) != NULL)
|
||||
{
|
||||
while((dir_entry = readdir(dp)) != NULL)
|
||||
{
|
||||
char* temp = malloc(strlen(dirname)+strlen(dir_entry->d_name)+2);
|
||||
|
||||
if (!temp)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
sprintf(temp, "%s/%s", dirname, dir_entry->d_name);
|
||||
if (lstat(temp, &stat_info) == 0 && S_ISREG(stat_info.st_mode))
|
||||
nfkeys++;
|
||||
free(temp);
|
||||
}
|
||||
closedir(dp);
|
||||
dp = NULL;
|
||||
} else
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (nfkeys != 0)
|
||||
{
|
||||
if ((fkeys = (char **)malloc(nfkeys * sizeof(char *))) == NULL)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* copy the keys */
|
||||
if((dp = opendir(dirname)) != NULL)
|
||||
{
|
||||
i = 0;
|
||||
while((dir_entry = readdir(dp)) != NULL)
|
||||
{
|
||||
char* temp = malloc(strlen(dirname)+strlen(dir_entry->d_name)+2);
|
||||
|
||||
if (!temp)
|
||||
{
|
||||
free(fkeys);
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
sprintf(temp, "%s/%s", dirname, dir_entry->d_name);
|
||||
if (lstat(temp, &stat_info) == 0 && S_ISREG(stat_info.st_mode))
|
||||
{
|
||||
if ((fkeys[i] = malloc(strlen(dir_entry->d_name) + 1)) == NULL)
|
||||
{
|
||||
free(temp);
|
||||
free(fkeys);
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
strcpy(fkeys[i], dir_entry->d_name);
|
||||
ptraux = strstr(fkeys[i], MESSAGE_FILENAME_EXTENSION);
|
||||
if ( ptraux != NULL )
|
||||
*ptraux = '\0' ;
|
||||
i++;
|
||||
}
|
||||
free(temp);
|
||||
}
|
||||
} else
|
||||
{
|
||||
rc = MQTTCLIENT_PERSISTENCE_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
*nkeys = nfkeys;
|
||||
*keys = fkeys;
|
||||
/* the caller must free keys */
|
||||
|
||||
exit:
|
||||
if (dp)
|
||||
closedir(dp);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if defined(UNIT_TESTS)
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
#define MSTEM "m-"
|
||||
#define NMSGS 10
|
||||
#define NBUFS 4
|
||||
#define NDEL 2
|
||||
#define RC !rc ? "(Success)" : "(Failed) "
|
||||
|
||||
int rc;
|
||||
char *handle;
|
||||
char *perdir = ".";
|
||||
const char *clientID = "TheUTClient";
|
||||
const char *serverURI = "127.0.0.1:1883";
|
||||
|
||||
char *stem = MSTEM;
|
||||
int msgId, i;
|
||||
int nm[NDEL] = {5 , 8}; /* msgIds to get and remove */
|
||||
|
||||
char *key;
|
||||
char **keys;
|
||||
int nkeys;
|
||||
char *buffer, *buff;
|
||||
int buflen;
|
||||
|
||||
int nbufs = NBUFS;
|
||||
char *bufs[NBUFS] = {"m0", "mm1", "mmm2" , "mmmm3"}; /* message content */
|
||||
int buflens[NBUFS];
|
||||
for(i=0;i<nbufs;i++)
|
||||
buflens[i]=strlen(bufs[i]);
|
||||
|
||||
/* open */
|
||||
/* printf("Persistence directory : %s\n", perdir); */
|
||||
rc = pstopen((void**)&handle, clientID, serverURI, perdir);
|
||||
printf("%s Persistence directory for client %s : %s\n", RC, clientID, handle);
|
||||
|
||||
/* put */
|
||||
for(msgId=0;msgId<NMSGS;msgId++)
|
||||
{
|
||||
key = malloc(MESSAGE_FILENAME_LENGTH + 1);
|
||||
sprintf(key, "%s%d", stem, msgId);
|
||||
rc = pstput(handle, key, nbufs, bufs, buflens);
|
||||
printf("%s Adding message %s\n", RC, key);
|
||||
free(key);
|
||||
}
|
||||
|
||||
/* keys ,ie, list keys added */
|
||||
rc = pstkeys(handle, &keys, &nkeys);
|
||||
printf("%s Found %d messages persisted in %s\n", RC, nkeys, handle);
|
||||
for(i=0;i<nkeys;i++)
|
||||
printf("%13s\n", keys[i]);
|
||||
|
||||
if (keys !=NULL)
|
||||
free(keys);
|
||||
|
||||
/* containskey */
|
||||
for(i=0;i<NDEL;i++)
|
||||
{
|
||||
key = malloc(MESSAGE_FILENAME_LENGTH + 1);
|
||||
sprintf(key, "%s%d", stem, nm[i]);
|
||||
rc = pstcontainskey(handle, key);
|
||||
printf("%s Message %s is persisted ?\n", RC, key);
|
||||
free(key);
|
||||
}
|
||||
|
||||
/* get && remove*/
|
||||
for(i=0;i<NDEL;i++)
|
||||
{
|
||||
key = malloc(MESSAGE_FILENAME_LENGTH + 1);
|
||||
sprintf(key, "%s%d", stem, nm[i]);
|
||||
rc = pstget(handle, key, &buffer, &buflen);
|
||||
buff = malloc(buflen+1);
|
||||
memcpy(buff, buffer, buflen);
|
||||
buff[buflen] = '\0';
|
||||
printf("%s Retrieving message %s : %s\n", RC, key, buff);
|
||||
rc = pstremove(handle, key);
|
||||
printf("%s Removing message %s\n", RC, key);
|
||||
free(key);
|
||||
free(buff);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
/* containskey */
|
||||
for(i=0;i<NDEL;i++)
|
||||
{
|
||||
key = malloc(MESSAGE_FILENAME_LENGTH + 1);
|
||||
sprintf(key, "%s%d", stem, nm[i]);
|
||||
rc = pstcontainskey(handle, key);
|
||||
printf("%s Message %s is persisted ?\n", RC, key);
|
||||
free(key);
|
||||
}
|
||||
|
||||
/* keys ,ie, list keys added */
|
||||
rc = pstkeys(handle, &keys, &nkeys);
|
||||
printf("%s Found %d messages persisted in %s\n", RC, nkeys, handle);
|
||||
for(i=0;i<nkeys;i++)
|
||||
printf("%13s\n", keys[i]);
|
||||
|
||||
if (keys != NULL)
|
||||
free(keys);
|
||||
|
||||
|
||||
/* close -> it will fail, since client persistence directory is not empty */
|
||||
rc = pstclose(&handle);
|
||||
printf("%s Closing client persistence directory for client %s\n", RC, clientID);
|
||||
|
||||
/* clear */
|
||||
rc = pstclear(handle);
|
||||
printf("%s Deleting all persisted messages in %s\n", RC, handle);
|
||||
|
||||
/* keys ,ie, list keys added */
|
||||
rc = pstkeys(handle, &keys, &nkeys);
|
||||
printf("%s Found %d messages persisted in %s\n", RC, nkeys, handle);
|
||||
for(i=0;i<nkeys;i++)
|
||||
printf("%13s\n", keys[i]);
|
||||
|
||||
if ( keys != NULL )
|
||||
free(keys);
|
||||
|
||||
/* close */
|
||||
rc = pstclose(&handle);
|
||||
printf("%s Closing client persistence directory for client %s\n", RC, clientID);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* NO_PERSISTENCE */
|
||||
|
|
@ -0,0 +1,533 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2020 IBM Corp. and others
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#include "MQTTProperties.h"
|
||||
|
||||
#include "MQTTPacket.h"
|
||||
#include "MQTTProtocolClient.h"
|
||||
#include "Heap.h"
|
||||
#include "StackTrace.h"
|
||||
|
||||
#include <memory.h>
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
static struct nameToType
|
||||
{
|
||||
enum MQTTPropertyCodes name;
|
||||
enum MQTTPropertyTypes type;
|
||||
} namesToTypes[] =
|
||||
{
|
||||
{MQTTPROPERTY_CODE_PAYLOAD_FORMAT_INDICATOR, MQTTPROPERTY_TYPE_BYTE},
|
||||
{MQTTPROPERTY_CODE_MESSAGE_EXPIRY_INTERVAL, MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER},
|
||||
{MQTTPROPERTY_CODE_CONTENT_TYPE, MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING},
|
||||
{MQTTPROPERTY_CODE_RESPONSE_TOPIC, MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING},
|
||||
{MQTTPROPERTY_CODE_CORRELATION_DATA, MQTTPROPERTY_TYPE_BINARY_DATA},
|
||||
{MQTTPROPERTY_CODE_SUBSCRIPTION_IDENTIFIER, MQTTPROPERTY_TYPE_VARIABLE_BYTE_INTEGER},
|
||||
{MQTTPROPERTY_CODE_SESSION_EXPIRY_INTERVAL, MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER},
|
||||
{MQTTPROPERTY_CODE_ASSIGNED_CLIENT_IDENTIFER, MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING},
|
||||
{MQTTPROPERTY_CODE_SERVER_KEEP_ALIVE, MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER},
|
||||
{MQTTPROPERTY_CODE_AUTHENTICATION_METHOD, MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING},
|
||||
{MQTTPROPERTY_CODE_AUTHENTICATION_DATA, MQTTPROPERTY_TYPE_BINARY_DATA},
|
||||
{MQTTPROPERTY_CODE_REQUEST_PROBLEM_INFORMATION, MQTTPROPERTY_TYPE_BYTE},
|
||||
{MQTTPROPERTY_CODE_WILL_DELAY_INTERVAL, MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER},
|
||||
{MQTTPROPERTY_CODE_REQUEST_RESPONSE_INFORMATION, MQTTPROPERTY_TYPE_BYTE},
|
||||
{MQTTPROPERTY_CODE_RESPONSE_INFORMATION, MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING},
|
||||
{MQTTPROPERTY_CODE_SERVER_REFERENCE, MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING},
|
||||
{MQTTPROPERTY_CODE_REASON_STRING, MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING},
|
||||
{MQTTPROPERTY_CODE_RECEIVE_MAXIMUM, MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER},
|
||||
{MQTTPROPERTY_CODE_TOPIC_ALIAS_MAXIMUM, MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER},
|
||||
{MQTTPROPERTY_CODE_TOPIC_ALIAS, MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER},
|
||||
{MQTTPROPERTY_CODE_MAXIMUM_QOS, MQTTPROPERTY_TYPE_BYTE},
|
||||
{MQTTPROPERTY_CODE_RETAIN_AVAILABLE, MQTTPROPERTY_TYPE_BYTE},
|
||||
{MQTTPROPERTY_CODE_USER_PROPERTY, MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR},
|
||||
{MQTTPROPERTY_CODE_MAXIMUM_PACKET_SIZE, MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER},
|
||||
{MQTTPROPERTY_CODE_WILDCARD_SUBSCRIPTION_AVAILABLE, MQTTPROPERTY_TYPE_BYTE},
|
||||
{MQTTPROPERTY_CODE_SUBSCRIPTION_IDENTIFIERS_AVAILABLE, MQTTPROPERTY_TYPE_BYTE},
|
||||
{MQTTPROPERTY_CODE_SHARED_SUBSCRIPTION_AVAILABLE, MQTTPROPERTY_TYPE_BYTE}
|
||||
};
|
||||
|
||||
|
||||
static char* datadup(const MQTTLenString* str)
|
||||
{
|
||||
char* temp = malloc(str->len);
|
||||
if (temp)
|
||||
memcpy(temp, str->data, str->len);
|
||||
return temp;
|
||||
}
|
||||
|
||||
|
||||
int MQTTProperty_getType(enum MQTTPropertyCodes value)
|
||||
{
|
||||
int i, rc = -1;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(namesToTypes); ++i)
|
||||
{
|
||||
if (namesToTypes[i].name == value)
|
||||
{
|
||||
rc = namesToTypes[i].type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int MQTTProperties_len(MQTTProperties* props)
|
||||
{
|
||||
/* properties length is an mbi */
|
||||
return (props == NULL) ? 1 : props->length + MQTTPacket_VBIlen(props->length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a new property to a property list
|
||||
* @param props the property list
|
||||
* @param prop the new property
|
||||
* @return code 0 is success
|
||||
*/
|
||||
int MQTTProperties_add(MQTTProperties* props, const MQTTProperty* prop)
|
||||
{
|
||||
int rc = 0, type;
|
||||
|
||||
if ((type = MQTTProperty_getType(prop->identifier)) < 0)
|
||||
{
|
||||
/*StackTrace_printStack(stdout);*/
|
||||
rc = MQTT_INVALID_PROPERTY_ID;
|
||||
goto exit;
|
||||
}
|
||||
else if (props->array == NULL)
|
||||
{
|
||||
props->max_count = 10;
|
||||
props->array = malloc(sizeof(MQTTProperty) * props->max_count);
|
||||
}
|
||||
else if (props->count == props->max_count)
|
||||
{
|
||||
props->max_count += 10;
|
||||
props->array = realloc(props->array, sizeof(MQTTProperty) * props->max_count);
|
||||
}
|
||||
|
||||
if (props->array)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
props->array[props->count++] = *prop;
|
||||
/* calculate length */
|
||||
switch (type)
|
||||
{
|
||||
case MQTTPROPERTY_TYPE_BYTE:
|
||||
len = 1;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER:
|
||||
len = 2;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER:
|
||||
len = 4;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_VARIABLE_BYTE_INTEGER:
|
||||
len = MQTTPacket_VBIlen(prop->value.integer4);
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_BINARY_DATA:
|
||||
case MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING:
|
||||
case MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR:
|
||||
len = 2 + prop->value.data.len;
|
||||
props->array[props->count-1].value.data.data = datadup(&prop->value.data);
|
||||
if (type == MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR)
|
||||
{
|
||||
len += 2 + prop->value.value.len;
|
||||
props->array[props->count-1].value.value.data = datadup(&prop->value.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
props->length += len + 1; /* add identifier byte */
|
||||
}
|
||||
else
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int MQTTProperty_write(char** pptr, MQTTProperty* prop)
|
||||
{
|
||||
int rc = -1,
|
||||
type = -1;
|
||||
|
||||
type = MQTTProperty_getType(prop->identifier);
|
||||
if (type >= MQTTPROPERTY_TYPE_BYTE && type <= MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR)
|
||||
{
|
||||
writeChar(pptr, prop->identifier);
|
||||
switch (type)
|
||||
{
|
||||
case MQTTPROPERTY_TYPE_BYTE:
|
||||
writeChar(pptr, prop->value.byte);
|
||||
rc = 1;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER:
|
||||
writeInt(pptr, prop->value.integer2);
|
||||
rc = 2;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER:
|
||||
writeInt4(pptr, prop->value.integer4);
|
||||
rc = 4;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_VARIABLE_BYTE_INTEGER:
|
||||
rc = MQTTPacket_encode(*pptr, prop->value.integer4);
|
||||
*pptr += rc;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_BINARY_DATA:
|
||||
case MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING:
|
||||
writeMQTTLenString(pptr, prop->value.data);
|
||||
rc = prop->value.data.len + 2; /* include length field */
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR:
|
||||
writeMQTTLenString(pptr, prop->value.data);
|
||||
writeMQTTLenString(pptr, prop->value.value);
|
||||
rc = prop->value.data.len + prop->value.value.len + 4; /* include length fields */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc + 1; /* include identifier byte */
|
||||
}
|
||||
|
||||
|
||||
int MQTTProperties_write(char** pptr, const MQTTProperties* properties)
|
||||
{
|
||||
int rc = -1;
|
||||
int i = 0, len = 0;
|
||||
|
||||
/* write the entire property list length first */
|
||||
if (properties == NULL)
|
||||
{
|
||||
*pptr += MQTTPacket_encode(*pptr, 0);
|
||||
rc = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
*pptr += MQTTPacket_encode(*pptr, properties->length);
|
||||
len = rc = 1;
|
||||
for (i = 0; i < properties->count; ++i)
|
||||
{
|
||||
rc = MQTTProperty_write(pptr, &properties->array[i]);
|
||||
if (rc < 0)
|
||||
break;
|
||||
else
|
||||
len += rc;
|
||||
}
|
||||
if (rc >= 0)
|
||||
rc = len;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int MQTTProperty_read(MQTTProperty* prop, char** pptr, char* enddata)
|
||||
{
|
||||
int type = -1,
|
||||
len = 0;
|
||||
|
||||
prop->identifier = readChar(pptr);
|
||||
type = MQTTProperty_getType(prop->identifier);
|
||||
if (type >= MQTTPROPERTY_TYPE_BYTE && type <= MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MQTTPROPERTY_TYPE_BYTE:
|
||||
prop->value.byte = readChar(pptr);
|
||||
len = 1;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER:
|
||||
prop->value.integer2 = readInt(pptr);
|
||||
len = 2;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER:
|
||||
prop->value.integer4 = readInt4(pptr);
|
||||
len = 4;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_VARIABLE_BYTE_INTEGER:
|
||||
len = MQTTPacket_decodeBuf(*pptr, &prop->value.integer4);
|
||||
*pptr += len;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_BINARY_DATA:
|
||||
case MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING:
|
||||
case MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR:
|
||||
len = MQTTLenStringRead(&prop->value.data, pptr, enddata);
|
||||
prop->value.data.data = datadup(&prop->value.data);
|
||||
if (type == MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR)
|
||||
{
|
||||
len += MQTTLenStringRead(&prop->value.value, pptr, enddata);
|
||||
prop->value.value.data = datadup(&prop->value.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return len + 1; /* 1 byte for identifier */
|
||||
}
|
||||
|
||||
|
||||
int MQTTProperties_read(MQTTProperties* properties, char** pptr, char* enddata)
|
||||
{
|
||||
int rc = 0;
|
||||
unsigned int remlength = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
/* we assume an initialized properties structure */
|
||||
if (enddata - (*pptr) > 0) /* enough length to read the VBI? */
|
||||
{
|
||||
*pptr += MQTTPacket_decodeBuf(*pptr, &remlength);
|
||||
properties->length = remlength;
|
||||
while (remlength > 0)
|
||||
{
|
||||
if (properties->count == properties->max_count)
|
||||
{
|
||||
properties->max_count += 10;
|
||||
if (properties->max_count == 10)
|
||||
properties->array = malloc(sizeof(MQTTProperty) * properties->max_count);
|
||||
else
|
||||
properties->array = realloc(properties->array, sizeof(MQTTProperty) * properties->max_count);
|
||||
}
|
||||
if (properties->array == NULL)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
remlength -= MQTTProperty_read(&properties->array[properties->count], pptr, enddata);
|
||||
properties->count++;
|
||||
}
|
||||
if (remlength == 0)
|
||||
rc = 1; /* data read successfully */
|
||||
}
|
||||
|
||||
if (rc != 1 && properties->array != NULL)
|
||||
{
|
||||
free(properties->array);
|
||||
properties->array = NULL;
|
||||
properties->max_count = properties->count = 0;
|
||||
}
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct {
|
||||
enum MQTTPropertyCodes value;
|
||||
const char* name;
|
||||
} nameToString[] =
|
||||
{
|
||||
{MQTTPROPERTY_CODE_PAYLOAD_FORMAT_INDICATOR, "PAYLOAD_FORMAT_INDICATOR"},
|
||||
{MQTTPROPERTY_CODE_MESSAGE_EXPIRY_INTERVAL, "MESSAGE_EXPIRY_INTERVAL"},
|
||||
{MQTTPROPERTY_CODE_CONTENT_TYPE, "CONTENT_TYPE"},
|
||||
{MQTTPROPERTY_CODE_RESPONSE_TOPIC, "RESPONSE_TOPIC"},
|
||||
{MQTTPROPERTY_CODE_CORRELATION_DATA, "CORRELATION_DATA"},
|
||||
{MQTTPROPERTY_CODE_SUBSCRIPTION_IDENTIFIER, "SUBSCRIPTION_IDENTIFIER"},
|
||||
{MQTTPROPERTY_CODE_SESSION_EXPIRY_INTERVAL, "SESSION_EXPIRY_INTERVAL"},
|
||||
{MQTTPROPERTY_CODE_ASSIGNED_CLIENT_IDENTIFER, "ASSIGNED_CLIENT_IDENTIFER"},
|
||||
{MQTTPROPERTY_CODE_SERVER_KEEP_ALIVE, "SERVER_KEEP_ALIVE"},
|
||||
{MQTTPROPERTY_CODE_AUTHENTICATION_METHOD, "AUTHENTICATION_METHOD"},
|
||||
{MQTTPROPERTY_CODE_AUTHENTICATION_DATA, "AUTHENTICATION_DATA"},
|
||||
{MQTTPROPERTY_CODE_REQUEST_PROBLEM_INFORMATION, "REQUEST_PROBLEM_INFORMATION"},
|
||||
{MQTTPROPERTY_CODE_WILL_DELAY_INTERVAL, "WILL_DELAY_INTERVAL"},
|
||||
{MQTTPROPERTY_CODE_REQUEST_RESPONSE_INFORMATION, "REQUEST_RESPONSE_INFORMATION"},
|
||||
{MQTTPROPERTY_CODE_RESPONSE_INFORMATION, "RESPONSE_INFORMATION"},
|
||||
{MQTTPROPERTY_CODE_SERVER_REFERENCE, "SERVER_REFERENCE"},
|
||||
{MQTTPROPERTY_CODE_REASON_STRING, "REASON_STRING"},
|
||||
{MQTTPROPERTY_CODE_RECEIVE_MAXIMUM, "RECEIVE_MAXIMUM"},
|
||||
{MQTTPROPERTY_CODE_TOPIC_ALIAS_MAXIMUM, "TOPIC_ALIAS_MAXIMUM"},
|
||||
{MQTTPROPERTY_CODE_TOPIC_ALIAS, "TOPIC_ALIAS"},
|
||||
{MQTTPROPERTY_CODE_MAXIMUM_QOS, "MAXIMUM_QOS"},
|
||||
{MQTTPROPERTY_CODE_RETAIN_AVAILABLE, "RETAIN_AVAILABLE"},
|
||||
{MQTTPROPERTY_CODE_USER_PROPERTY, "USER_PROPERTY"},
|
||||
{MQTTPROPERTY_CODE_MAXIMUM_PACKET_SIZE, "MAXIMUM_PACKET_SIZE"},
|
||||
{MQTTPROPERTY_CODE_WILDCARD_SUBSCRIPTION_AVAILABLE, "WILDCARD_SUBSCRIPTION_AVAILABLE"},
|
||||
{MQTTPROPERTY_CODE_SUBSCRIPTION_IDENTIFIERS_AVAILABLE, "SUBSCRIPTION_IDENTIFIERS_AVAILABLE"},
|
||||
{MQTTPROPERTY_CODE_SHARED_SUBSCRIPTION_AVAILABLE, "SHARED_SUBSCRIPTION_AVAILABLE"}
|
||||
};
|
||||
|
||||
const char* MQTTPropertyName(enum MQTTPropertyCodes value)
|
||||
{
|
||||
int i = 0;
|
||||
const char* result = NULL;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(nameToString); ++i)
|
||||
{
|
||||
if (nameToString[i].value == value)
|
||||
{
|
||||
result = nameToString[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void MQTTProperties_free(MQTTProperties* props)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (props == NULL)
|
||||
goto exit;
|
||||
for (i = 0; i < props->count; ++i)
|
||||
{
|
||||
int id = props->array[i].identifier;
|
||||
int type = MQTTProperty_getType(id);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MQTTPROPERTY_TYPE_BINARY_DATA:
|
||||
case MQTTPROPERTY_TYPE_UTF_8_ENCODED_STRING:
|
||||
case MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR:
|
||||
free(props->array[i].value.data.data);
|
||||
if (type == MQTTPROPERTY_TYPE_UTF_8_STRING_PAIR)
|
||||
free(props->array[i].value.value.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (props->array)
|
||||
free(props->array);
|
||||
memset(props, '\0', sizeof(MQTTProperties)); /* zero all fields */
|
||||
exit:
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
MQTTProperties MQTTProperties_copy(const MQTTProperties* props)
|
||||
{
|
||||
int i = 0;
|
||||
MQTTProperties result = MQTTProperties_initializer;
|
||||
|
||||
FUNC_ENTRY;
|
||||
for (i = 0; i < props->count; ++i)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if ((rc = MQTTProperties_add(&result, &props->array[i])) != 0)
|
||||
Log(LOG_ERROR, -1, "Error from MQTTProperties add %d", rc);
|
||||
}
|
||||
|
||||
FUNC_EXIT;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int MQTTProperties_hasProperty(MQTTProperties *props, enum MQTTPropertyCodes propid)
|
||||
{
|
||||
int i = 0;
|
||||
int found = 0;
|
||||
|
||||
for (i = 0; i < props->count; ++i)
|
||||
{
|
||||
if (propid == props->array[i].identifier)
|
||||
{
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
int MQTTProperties_propertyCount(MQTTProperties *props, enum MQTTPropertyCodes propid)
|
||||
{
|
||||
int i = 0;
|
||||
int count = 0;
|
||||
|
||||
for (i = 0; i < props->count; ++i)
|
||||
{
|
||||
if (propid == props->array[i].identifier)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
int MQTTProperties_getNumericValueAt(MQTTProperties *props, enum MQTTPropertyCodes propid, int index)
|
||||
{
|
||||
int i = 0;
|
||||
int rc = -9999999;
|
||||
int cur_index = 0;
|
||||
|
||||
for (i = 0; i < props->count; ++i)
|
||||
{
|
||||
int id = props->array[i].identifier;
|
||||
|
||||
if (id == propid)
|
||||
{
|
||||
if (cur_index < index)
|
||||
{
|
||||
cur_index++;
|
||||
continue;
|
||||
}
|
||||
switch (MQTTProperty_getType(id))
|
||||
{
|
||||
case MQTTPROPERTY_TYPE_BYTE:
|
||||
rc = props->array[i].value.byte;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_TWO_BYTE_INTEGER:
|
||||
rc = props->array[i].value.integer2;
|
||||
break;
|
||||
case MQTTPROPERTY_TYPE_FOUR_BYTE_INTEGER:
|
||||
case MQTTPROPERTY_TYPE_VARIABLE_BYTE_INTEGER:
|
||||
rc = props->array[i].value.integer4;
|
||||
break;
|
||||
default:
|
||||
rc = -999999;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int MQTTProperties_getNumericValue(MQTTProperties *props, enum MQTTPropertyCodes propid)
|
||||
{
|
||||
return MQTTProperties_getNumericValueAt(props, propid, 0);
|
||||
}
|
||||
|
||||
|
||||
MQTTProperty* MQTTProperties_getPropertyAt(MQTTProperties *props, enum MQTTPropertyCodes propid, int index)
|
||||
{
|
||||
int i = 0;
|
||||
MQTTProperty* result = NULL;
|
||||
int cur_index = 0;
|
||||
|
||||
for (i = 0; i < props->count; ++i)
|
||||
{
|
||||
int id = props->array[i].identifier;
|
||||
|
||||
if (id == propid)
|
||||
{
|
||||
if (cur_index == index)
|
||||
{
|
||||
result = &props->array[i];
|
||||
break;
|
||||
}
|
||||
else
|
||||
cur_index++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
MQTTProperty* MQTTProperties_getProperty(MQTTProperties *props, enum MQTTPropertyCodes propid)
|
||||
{
|
||||
return MQTTProperties_getPropertyAt(props, propid, 0);
|
||||
}
|
||||
|
|
@ -0,0 +1,926 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs - fix for bug 413429 - connectionLost not called
|
||||
* Ian Craggs - fix for bug 421103 - trying to write to same socket, in retry
|
||||
* Rong Xiang, Ian Craggs - C++ compatibility
|
||||
* Ian Craggs - turn off DUP flag for PUBREL - MQTT 3.1.1
|
||||
* Ian Craggs - ensure that acks are not sent if write is outstanding on socket
|
||||
* Ian Craggs - MQTT 5.0 support
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief Functions dealing with the MQTT protocol exchanges
|
||||
*
|
||||
* Some other related functions are in the MQTTProtocolOut module
|
||||
* */
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "MQTTProtocolClient.h"
|
||||
#if !defined(NO_PERSISTENCE)
|
||||
#include "MQTTPersistence.h"
|
||||
#endif
|
||||
#include "SocketBuffer.h"
|
||||
#include "StackTrace.h"
|
||||
#include "Heap.h"
|
||||
|
||||
#if !defined(min)
|
||||
#define min(A,B) ( (A) < (B) ? (A):(B))
|
||||
#endif
|
||||
|
||||
extern MQTTProtocol state;
|
||||
extern ClientStates* bstate;
|
||||
|
||||
static void MQTTProtocol_storeQoS0(Clients* pubclient, Publish* publish);
|
||||
static int MQTTProtocol_startPublishCommon(
|
||||
Clients* pubclient,
|
||||
Publish* publish,
|
||||
int qos,
|
||||
int retained);
|
||||
static void MQTTProtocol_retries(START_TIME_TYPE now, Clients* client, int regardless);
|
||||
|
||||
|
||||
/**
|
||||
* List callback function for comparing Message structures by message id
|
||||
* @param a first integer value
|
||||
* @param b second integer value
|
||||
* @return boolean indicating whether a and b are equal
|
||||
*/
|
||||
int messageIDCompare(void* a, void* b)
|
||||
{
|
||||
Messages* msg = (Messages*)a;
|
||||
return msg->msgid == *(int*)b;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assign a new message id for a client. Make sure it isn't already being used and does
|
||||
* not exceed the maximum.
|
||||
* @param client a client structure
|
||||
* @return the next message id to use, or 0 if none available
|
||||
*/
|
||||
int MQTTProtocol_assignMsgId(Clients* client)
|
||||
{
|
||||
int start_msgid = client->msgID;
|
||||
int msgid = start_msgid;
|
||||
|
||||
FUNC_ENTRY;
|
||||
msgid = (msgid == MAX_MSG_ID) ? 1 : msgid + 1;
|
||||
while (ListFindItem(client->outboundMsgs, &msgid, messageIDCompare) != NULL)
|
||||
{
|
||||
msgid = (msgid == MAX_MSG_ID) ? 1 : msgid + 1;
|
||||
if (msgid == start_msgid)
|
||||
{ /* we've tried them all - none free */
|
||||
msgid = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (msgid != 0)
|
||||
client->msgID = msgid;
|
||||
FUNC_EXIT_RC(msgid);
|
||||
return msgid;
|
||||
}
|
||||
|
||||
|
||||
static void MQTTProtocol_storeQoS0(Clients* pubclient, Publish* publish)
|
||||
{
|
||||
int len;
|
||||
pending_write* pw = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
/* store the publication until the write is finished */
|
||||
if ((pw = malloc(sizeof(pending_write))) == NULL)
|
||||
goto exit;
|
||||
Log(TRACE_MIN, 12, NULL);
|
||||
if ((pw->p = MQTTProtocol_storePublication(publish, &len)) == NULL)
|
||||
{
|
||||
free(pw);
|
||||
goto exit;
|
||||
}
|
||||
pw->socket = pubclient->net.socket;
|
||||
if (!ListAppend(&(state.pending_writes), pw, sizeof(pending_write)+len))
|
||||
{
|
||||
free(pw->p);
|
||||
free(pw);
|
||||
goto exit;
|
||||
}
|
||||
/* 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 */
|
||||
if (SocketBuffer_updateWrite(pw->socket, pw->p->topic, pw->p->payload) == NULL)
|
||||
Log(LOG_SEVERE, 0, "Error updating write");
|
||||
exit:
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Utility function to start a new publish exchange.
|
||||
* @param pubclient the client to send the publication to
|
||||
* @param publish the publication data
|
||||
* @param qos the MQTT QoS to use
|
||||
* @param retained boolean - whether to set the MQTT retained flag
|
||||
* @return the completion code
|
||||
*/
|
||||
static int MQTTProtocol_startPublishCommon(Clients* pubclient, Publish* publish, int qos, int retained)
|
||||
{
|
||||
int rc = TCPSOCKET_COMPLETE;
|
||||
|
||||
FUNC_ENTRY;
|
||||
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);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start a new publish exchange. Store any state necessary and try to send the packet
|
||||
* @param pubclient the client to send the publication to
|
||||
* @param publish the publication data
|
||||
* @param qos the MQTT QoS to use
|
||||
* @param retained boolean - whether to set the MQTT retained flag
|
||||
* @param mm - pointer to the message to send
|
||||
* @return the completion code
|
||||
*/
|
||||
int MQTTProtocol_startPublish(Clients* pubclient, Publish* publish, int qos, int retained, Messages** mm)
|
||||
{
|
||||
Publish p = *publish;
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (qos > 0)
|
||||
{
|
||||
*mm = MQTTProtocol_createMessage(publish, mm, qos, retained, 0);
|
||||
ListAppend(pubclient->outboundMsgs, *mm, (*mm)->len);
|
||||
/* we change these pointers to the saved message location just in case the packet could not be written
|
||||
entirely; the socket buffer will use these locations to finish writing the packet */
|
||||
p.payload = (*mm)->publish->payload;
|
||||
p.topic = (*mm)->publish->topic;
|
||||
p.properties = (*mm)->properties;
|
||||
p.MQTTVersion = (*mm)->MQTTVersion;
|
||||
}
|
||||
rc = MQTTProtocol_startPublishCommon(pubclient, &p, qos, retained);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy and store message data for retries
|
||||
* @param publish the publication data
|
||||
* @param mm - pointer to the message data to store
|
||||
* @param qos the MQTT QoS to use
|
||||
* @param retained boolean - whether to set the MQTT retained flag
|
||||
* @param allocatePayload boolean - whether or not to malloc payload
|
||||
* @return pointer to the message data stored
|
||||
*/
|
||||
Messages* MQTTProtocol_createMessage(Publish* publish, Messages **mm, int qos, int retained, int allocatePayload)
|
||||
{
|
||||
Messages* m = malloc(sizeof(Messages));
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (!m)
|
||||
goto exit;
|
||||
m->len = sizeof(Messages);
|
||||
if (*mm == NULL || (*mm)->publish == NULL)
|
||||
{
|
||||
int len1;
|
||||
*mm = m;
|
||||
if ((m->publish = MQTTProtocol_storePublication(publish, &len1)) == NULL)
|
||||
{
|
||||
free(m);
|
||||
goto exit;
|
||||
}
|
||||
m->len += len1;
|
||||
if (allocatePayload)
|
||||
{
|
||||
char *temp = m->publish->payload;
|
||||
|
||||
if ((m->publish->payload = malloc(m->publish->payloadlen)) == NULL)
|
||||
{
|
||||
free(m);
|
||||
goto exit;
|
||||
}
|
||||
memcpy(m->publish->payload, temp, m->publish->payloadlen);
|
||||
}
|
||||
}
|
||||
else /* this is now never used, I think */
|
||||
{
|
||||
++(((*mm)->publish)->refcount);
|
||||
m->publish = (*mm)->publish;
|
||||
}
|
||||
m->msgid = publish->msgId;
|
||||
m->qos = qos;
|
||||
m->retain = retained;
|
||||
m->MQTTVersion = publish->MQTTVersion;
|
||||
if (m->MQTTVersion >= 5)
|
||||
m->properties = MQTTProperties_copy(&publish->properties);
|
||||
m->lastTouch = MQTTTime_now();
|
||||
if (qos == 2)
|
||||
m->nextMessageType = PUBREC;
|
||||
exit:
|
||||
FUNC_EXIT;
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store message data for possible retry
|
||||
* @param publish the publication data
|
||||
* @param len returned length of the data stored
|
||||
* @return the publication stored
|
||||
*/
|
||||
Publications* MQTTProtocol_storePublication(Publish* publish, int* len)
|
||||
{
|
||||
Publications* p = malloc(sizeof(Publications));
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (!p)
|
||||
goto exit;
|
||||
p->refcount = 1;
|
||||
*len = (int)strlen(publish->topic)+1;
|
||||
p->topic = publish->topic;
|
||||
publish->topic = NULL;
|
||||
*len += sizeof(Publications);
|
||||
p->topiclen = publish->topiclen;
|
||||
p->payloadlen = publish->payloadlen;
|
||||
p->payload = publish->payload;
|
||||
publish->payload = NULL;
|
||||
*len += publish->payloadlen;
|
||||
|
||||
if ((ListAppend(&(state.publications), p, *len)) == NULL)
|
||||
{
|
||||
free(p);
|
||||
p = NULL;
|
||||
}
|
||||
exit:
|
||||
FUNC_EXIT;
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove stored message data. Opposite of storePublication
|
||||
* @param p stored publication to remove
|
||||
*/
|
||||
void MQTTProtocol_removePublication(Publications* p)
|
||||
{
|
||||
FUNC_ENTRY;
|
||||
if (p && --(p->refcount) == 0)
|
||||
{
|
||||
free(p->payload);
|
||||
free(p->topic);
|
||||
ListRemove(&(state.publications), p);
|
||||
}
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming publish packet for a socket
|
||||
* The payload field of the packet has not been transferred to another buffer at this point.
|
||||
* If it's needed beyond the scope of this function, it has to be copied.
|
||||
* @param pack pointer to the publish packet
|
||||
* @param sock the socket on which the packet was received
|
||||
* @return completion code
|
||||
*/
|
||||
int MQTTProtocol_handlePublishes(void* pack, int sock)
|
||||
{
|
||||
Publish* publish = (Publish*)pack;
|
||||
Clients* client = NULL;
|
||||
char* clientid = NULL;
|
||||
int rc = TCPSOCKET_COMPLETE;
|
||||
|
||||
FUNC_ENTRY;
|
||||
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
|
||||
clientid = client->clientID;
|
||||
Log(LOG_PROTOCOL, 11, NULL, sock, clientid, publish->msgId, publish->header.bits.qos,
|
||||
publish->header.bits.retain, publish->payloadlen, min(20, publish->payloadlen), publish->payload);
|
||||
|
||||
if (publish->header.bits.qos == 0)
|
||||
Protocol_processPublication(publish, client, 1);
|
||||
else if (!Socket_noPendingWrites(sock))
|
||||
rc = SOCKET_ERROR; /* queue acks? */
|
||||
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->MQTTVersion, 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, 1);
|
||||
}
|
||||
else if (publish->header.bits.qos == 2)
|
||||
{
|
||||
/* store publication in inbound list */
|
||||
int len;
|
||||
int already_received = 0;
|
||||
ListElement* listElem = NULL;
|
||||
Messages* m = malloc(sizeof(Messages));
|
||||
Publications* p = NULL;
|
||||
if (!m)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
p = MQTTProtocol_storePublication(publish, &len);
|
||||
|
||||
m->publish = p;
|
||||
m->msgid = publish->msgId;
|
||||
m->qos = publish->header.bits.qos;
|
||||
m->retain = publish->header.bits.retain;
|
||||
m->MQTTVersion = publish->MQTTVersion;
|
||||
if (m->MQTTVersion >= MQTTVERSION_5)
|
||||
m->properties = MQTTProperties_copy(&publish->properties);
|
||||
m->nextMessageType = PUBREL;
|
||||
if ((listElem = ListFindItem(client->inboundMsgs, &(m->msgid), messageIDCompare)) != NULL)
|
||||
{ /* discard queued publication with same msgID that the current incoming message */
|
||||
Messages* msg = (Messages*)(listElem->content);
|
||||
MQTTProtocol_removePublication(msg->publish);
|
||||
if (msg->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&msg->properties);
|
||||
ListInsert(client->inboundMsgs, m, sizeof(Messages) + len, listElem);
|
||||
ListRemove(client->inboundMsgs, msg);
|
||||
already_received = 1;
|
||||
} else
|
||||
ListAppend(client->inboundMsgs, m, sizeof(Messages) + len);
|
||||
rc = MQTTPacket_send_pubrec(publish->MQTTVersion, publish->msgId, &client->net, client->clientID);
|
||||
if (m->MQTTVersion >= MQTTVERSION_5 && already_received == 0)
|
||||
{
|
||||
Publish publish1;
|
||||
|
||||
publish1.header.bits.qos = m->qos;
|
||||
publish1.header.bits.retain = m->retain;
|
||||
publish1.msgId = m->msgid;
|
||||
publish1.topic = m->publish->topic;
|
||||
publish1.topiclen = m->publish->topiclen;
|
||||
publish1.payload = m->publish->payload;
|
||||
publish1.payloadlen = m->publish->payloadlen;
|
||||
publish1.MQTTVersion = m->MQTTVersion;
|
||||
publish1.properties = m->properties;
|
||||
|
||||
Protocol_processPublication(&publish1, client, 1);
|
||||
ListRemove(&(state.publications), m->publish);
|
||||
m->publish = NULL;
|
||||
} else
|
||||
{ /* allocate and copy payload data as it's needed for pubrel.
|
||||
For other cases, it's done in Protocol_processPublication */
|
||||
char *temp = m->publish->payload;
|
||||
|
||||
if ((m->publish->payload = malloc(m->publish->payloadlen)) == NULL)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
memcpy(m->publish->payload, temp, m->publish->payloadlen);
|
||||
}
|
||||
publish->topic = NULL;
|
||||
}
|
||||
exit:
|
||||
MQTTPacket_freePublish(publish);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming puback packet for a socket
|
||||
* @param pack pointer to the publish packet
|
||||
* @param sock the socket on which the packet was received
|
||||
* @return completion code
|
||||
*/
|
||||
int MQTTProtocol_handlePubacks(void* pack, int sock)
|
||||
{
|
||||
Puback* puback = (Puback*)pack;
|
||||
Clients* client = NULL;
|
||||
int rc = TCPSOCKET_COMPLETE;
|
||||
|
||||
FUNC_ENTRY;
|
||||
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
|
||||
Log(LOG_PROTOCOL, 14, NULL, sock, client->clientID, puback->msgId);
|
||||
|
||||
/* look for the message by message id in the records of outbound messages for this client */
|
||||
if (ListFindItem(client->outboundMsgs, &(puback->msgId), messageIDCompare) == NULL)
|
||||
Log(TRACE_MIN, 3, NULL, "PUBACK", client->clientID, puback->msgId);
|
||||
else
|
||||
{
|
||||
Messages* m = (Messages*)(client->outboundMsgs->current->content);
|
||||
if (m->qos != 1)
|
||||
Log(TRACE_MIN, 4, NULL, "PUBACK", client->clientID, puback->msgId, m->qos);
|
||||
else
|
||||
{
|
||||
Log(TRACE_MIN, 6, NULL, "PUBACK", client->clientID, puback->msgId);
|
||||
#if !defined(NO_PERSISTENCE)
|
||||
rc = MQTTPersistence_remove(client,
|
||||
(m->MQTTVersion >= MQTTVERSION_5) ? PERSISTENCE_V5_PUBLISH_SENT : PERSISTENCE_PUBLISH_SENT,
|
||||
m->qos, puback->msgId);
|
||||
#endif
|
||||
MQTTProtocol_removePublication(m->publish);
|
||||
if (m->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&m->properties);
|
||||
ListRemove(client->outboundMsgs, m);
|
||||
}
|
||||
}
|
||||
if (puback->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&puback->properties);
|
||||
free(pack);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process an incoming pubrec packet for a socket
|
||||
* @param pack pointer to the publish packet
|
||||
* @param sock the socket on which the packet was received
|
||||
* @return completion code
|
||||
*/
|
||||
int MQTTProtocol_handlePubrecs(void* pack, int sock)
|
||||
{
|
||||
Pubrec* pubrec = (Pubrec*)pack;
|
||||
Clients* client = NULL;
|
||||
int rc = TCPSOCKET_COMPLETE;
|
||||
|
||||
FUNC_ENTRY;
|
||||
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
|
||||
Log(LOG_PROTOCOL, 15, NULL, sock, client->clientID, pubrec->msgId);
|
||||
|
||||
/* look for the message by message id in the records of outbound messages for this client */
|
||||
client->outboundMsgs->current = NULL;
|
||||
if (ListFindItem(client->outboundMsgs, &(pubrec->msgId), messageIDCompare) == NULL)
|
||||
{
|
||||
if (pubrec->header.bits.dup == 0)
|
||||
Log(TRACE_MIN, 3, NULL, "PUBREC", client->clientID, pubrec->msgId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages* m = (Messages*)(client->outboundMsgs->current->content);
|
||||
if (m->qos != 2)
|
||||
{
|
||||
if (pubrec->header.bits.dup == 0)
|
||||
Log(TRACE_MIN, 4, NULL, "PUBREC", client->clientID, pubrec->msgId, m->qos);
|
||||
}
|
||||
else if (m->nextMessageType != PUBREC)
|
||||
{
|
||||
if (pubrec->header.bits.dup == 0)
|
||||
Log(TRACE_MIN, 5, NULL, "PUBREC", client->clientID, pubrec->msgId);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pubrec->MQTTVersion >= MQTTVERSION_5 && pubrec->rc >= MQTTREASONCODE_UNSPECIFIED_ERROR)
|
||||
{
|
||||
Log(TRACE_MIN, -1, "Pubrec error %d received for client %s msgid %d, not sending PUBREL",
|
||||
pubrec->rc, client->clientID, pubrec->msgId);
|
||||
#if !defined(NO_PERSISTENCE)
|
||||
rc = MQTTPersistence_remove(client,
|
||||
(pubrec->MQTTVersion >= MQTTVERSION_5) ? PERSISTENCE_V5_PUBLISH_SENT : PERSISTENCE_PUBLISH_SENT,
|
||||
m->qos, pubrec->msgId);
|
||||
#endif
|
||||
MQTTProtocol_removePublication(m->publish);
|
||||
if (m->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&m->properties);
|
||||
ListRemove(client->outboundMsgs, m);
|
||||
(++state.msgs_sent);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = MQTTPacket_send_pubrel(pubrec->MQTTVersion, pubrec->msgId, 0, &client->net, client->clientID);
|
||||
m->nextMessageType = PUBCOMP;
|
||||
m->lastTouch = MQTTTime_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pubrec->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&pubrec->properties);
|
||||
free(pack);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process an incoming pubrel packet for a socket
|
||||
* @param pack pointer to the publish packet
|
||||
* @param sock the socket on which the packet was received
|
||||
* @return completion code
|
||||
*/
|
||||
int MQTTProtocol_handlePubrels(void* pack, int sock)
|
||||
{
|
||||
Pubrel* pubrel = (Pubrel*)pack;
|
||||
Clients* client = NULL;
|
||||
int rc = TCPSOCKET_COMPLETE;
|
||||
|
||||
FUNC_ENTRY;
|
||||
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
|
||||
Log(LOG_PROTOCOL, 17, NULL, sock, client->clientID, pubrel->msgId);
|
||||
|
||||
/* look for the message by message id in the records of inbound messages for this client */
|
||||
if (ListFindItem(client->inboundMsgs, &(pubrel->msgId), messageIDCompare) == NULL)
|
||||
{
|
||||
if (pubrel->header.bits.dup == 0)
|
||||
Log(TRACE_MIN, 3, NULL, "PUBREL", client->clientID, pubrel->msgId);
|
||||
else if (!Socket_noPendingWrites(sock))
|
||||
rc = SOCKET_ERROR; /* queue acks? */
|
||||
else
|
||||
/* Apparently this is "normal" behaviour, so we don't need to issue a warning */
|
||||
rc = MQTTPacket_send_pubcomp(pubrel->MQTTVersion, pubrel->msgId, &client->net, client->clientID);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages* m = (Messages*)(client->inboundMsgs->current->content);
|
||||
if (m->qos != 2)
|
||||
Log(TRACE_MIN, 4, NULL, "PUBREL", client->clientID, pubrel->msgId, m->qos);
|
||||
else if (m->nextMessageType != PUBREL)
|
||||
Log(TRACE_MIN, 5, NULL, "PUBREL", client->clientID, pubrel->msgId);
|
||||
else if (!Socket_noPendingWrites(sock))
|
||||
rc = SOCKET_ERROR; /* queue acks? */
|
||||
else
|
||||
{
|
||||
Publish publish;
|
||||
|
||||
memset(&publish, '\0', sizeof(publish));
|
||||
/* send pubcomp before processing the publications because a lot of return publications could fill up the socket buffer */
|
||||
rc = MQTTPacket_send_pubcomp(pubrel->MQTTVersion, pubrel->msgId, &client->net, client->clientID);
|
||||
publish.header.bits.qos = m->qos;
|
||||
publish.header.bits.retain = m->retain;
|
||||
publish.msgId = m->msgid;
|
||||
if (m->publish)
|
||||
{
|
||||
publish.topic = m->publish->topic;
|
||||
publish.topiclen = m->publish->topiclen;
|
||||
publish.payload = m->publish->payload;
|
||||
publish.payloadlen = m->publish->payloadlen;
|
||||
}
|
||||
publish.MQTTVersion = m->MQTTVersion;
|
||||
if (publish.MQTTVersion >= MQTTVERSION_5)
|
||||
publish.properties = m->properties;
|
||||
else
|
||||
Protocol_processPublication(&publish, client, 0); /* only for 3.1.1 and lower */
|
||||
#if !defined(NO_PERSISTENCE)
|
||||
rc += MQTTPersistence_remove(client,
|
||||
(m->MQTTVersion >= MQTTVERSION_5) ? PERSISTENCE_V5_PUBLISH_RECEIVED : PERSISTENCE_PUBLISH_RECEIVED,
|
||||
m->qos, pubrel->msgId);
|
||||
#endif
|
||||
if (m->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&m->properties);
|
||||
if (m->publish)
|
||||
ListRemove(&(state.publications), m->publish);
|
||||
ListRemove(client->inboundMsgs, m);
|
||||
++(state.msgs_received);
|
||||
}
|
||||
}
|
||||
if (pubrel->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&pubrel->properties);
|
||||
free(pack);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process an incoming pubcomp packet for a socket
|
||||
* @param pack pointer to the publish packet
|
||||
* @param sock the socket on which the packet was received
|
||||
* @return completion code
|
||||
*/
|
||||
int MQTTProtocol_handlePubcomps(void* pack, int sock)
|
||||
{
|
||||
Pubcomp* pubcomp = (Pubcomp*)pack;
|
||||
Clients* client = NULL;
|
||||
int rc = TCPSOCKET_COMPLETE;
|
||||
|
||||
FUNC_ENTRY;
|
||||
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
|
||||
Log(LOG_PROTOCOL, 19, NULL, sock, client->clientID, pubcomp->msgId);
|
||||
|
||||
/* look for the message by message id in the records of outbound messages for this client */
|
||||
if (ListFindItem(client->outboundMsgs, &(pubcomp->msgId), messageIDCompare) == NULL)
|
||||
{
|
||||
if (pubcomp->header.bits.dup == 0)
|
||||
Log(TRACE_MIN, 3, NULL, "PUBCOMP", client->clientID, pubcomp->msgId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages* m = (Messages*)(client->outboundMsgs->current->content);
|
||||
if (m->qos != 2)
|
||||
Log(TRACE_MIN, 4, NULL, "PUBCOMP", client->clientID, pubcomp->msgId, m->qos);
|
||||
else
|
||||
{
|
||||
if (m->nextMessageType != PUBCOMP)
|
||||
Log(TRACE_MIN, 5, NULL, "PUBCOMP", client->clientID, pubcomp->msgId);
|
||||
else
|
||||
{
|
||||
Log(TRACE_MIN, 6, NULL, "PUBCOMP", client->clientID, pubcomp->msgId);
|
||||
#if !defined(NO_PERSISTENCE)
|
||||
rc = MQTTPersistence_remove(client,
|
||||
(m->MQTTVersion >= MQTTVERSION_5) ? PERSISTENCE_V5_PUBLISH_SENT : PERSISTENCE_PUBLISH_SENT,
|
||||
m->qos, pubcomp->msgId);
|
||||
if (rc != 0)
|
||||
Log(LOG_ERROR, -1, "Error removing PUBCOMP for client id %s msgid %d from persistence", client->clientID, pubcomp->msgId);
|
||||
#endif
|
||||
MQTTProtocol_removePublication(m->publish);
|
||||
if (m->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&m->properties);
|
||||
ListRemove(client->outboundMsgs, m);
|
||||
(++state.msgs_sent);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pubcomp->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&pubcomp->properties);
|
||||
free(pack);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MQTT protocol keepAlive processing. Sends PINGREQ packets as required.
|
||||
* @param now current time
|
||||
*/
|
||||
void MQTTProtocol_keepalive(START_TIME_TYPE now)
|
||||
{
|
||||
ListElement* current = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
ListNextElement(bstate->clients, ¤t);
|
||||
while (current)
|
||||
{
|
||||
Clients* client = (Clients*)(current->content);
|
||||
ListNextElement(bstate->clients, ¤t);
|
||||
|
||||
if (client->connected == 0 || client->keepAliveInterval == 0)
|
||||
continue;
|
||||
|
||||
if (client->ping_outstanding == 1)
|
||||
{
|
||||
if (MQTTTime_difftime(now, client->net.lastPing) >= (long)(client->keepAliveInterval * 1000))
|
||||
{
|
||||
Log(TRACE_PROTOCOL, -1, "PINGRESP not received in keepalive interval for client %s on socket %d, disconnecting", client->clientID, client->net.socket);
|
||||
MQTTProtocol_closeSession(client, 1);
|
||||
}
|
||||
}
|
||||
else if (MQTTTime_difftime(now, client->net.lastSent) >= (long)(client->keepAliveInterval * 1000) ||
|
||||
MQTTTime_difftime(now, client->net.lastReceived) >= (long)(client->keepAliveInterval * 1000))
|
||||
{
|
||||
if (Socket_noPendingWrites(client->net.socket))
|
||||
{
|
||||
if (MQTTPacket_send_pingreq(&client->net, client->clientID) != TCPSOCKET_COMPLETE)
|
||||
{
|
||||
Log(TRACE_PROTOCOL, -1, "Error sending PINGREQ for client %s on socket %d, disconnecting", client->clientID, client->net.socket);
|
||||
MQTTProtocol_closeSession(client, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
client->net.lastPing = now;
|
||||
client->ping_outstanding = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MQTT retry processing per client
|
||||
* @param now current time
|
||||
* @param client - the client to which to apply the retry processing
|
||||
* @param regardless boolean - retry packets regardless of retry interval (used on reconnect)
|
||||
*/
|
||||
static void MQTTProtocol_retries(START_TIME_TYPE now, Clients* client, int regardless)
|
||||
{
|
||||
ListElement* outcurrent = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
|
||||
if (!regardless && client->retryInterval <= 0) /* 0 or -ive retryInterval turns off retry except on reconnect */
|
||||
goto exit;
|
||||
|
||||
while (client && ListNextElement(client->outboundMsgs, &outcurrent) &&
|
||||
client->connected && client->good && /* client is connected and has no errors */
|
||||
Socket_noPendingWrites(client->net.socket)) /* there aren't any previous packets still stacked up on the socket */
|
||||
{
|
||||
Messages* m = (Messages*)(outcurrent->content);
|
||||
if (regardless || MQTTTime_difftime(now, m->lastTouch) > (long)(max(client->retryInterval, 10) * 1000))
|
||||
{
|
||||
if (m->qos == 1 || (m->qos == 2 && m->nextMessageType == PUBREC))
|
||||
{
|
||||
Publish publish;
|
||||
int rc;
|
||||
|
||||
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;
|
||||
publish.properties = m->properties;
|
||||
publish.MQTTVersion = m->MQTTVersion;
|
||||
rc = MQTTPacket_send_publish(&publish, 1, m->qos, m->retain, &client->net, client->clientID);
|
||||
if (rc == SOCKET_ERROR)
|
||||
{
|
||||
client->good = 0;
|
||||
Log(TRACE_PROTOCOL, 29, NULL, client->clientID, client->net.socket,
|
||||
Socket_getpeer(client->net.socket));
|
||||
MQTTProtocol_closeSession(client, 1);
|
||||
client = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m->qos == 0 && rc == TCPSOCKET_INTERRUPTED)
|
||||
MQTTProtocol_storeQoS0(client, &publish);
|
||||
m->lastTouch = MQTTTime_now();
|
||||
}
|
||||
}
|
||||
else if (m->qos && m->nextMessageType == PUBCOMP)
|
||||
{
|
||||
Log(TRACE_MIN, 7, NULL, "PUBREL", client->clientID, client->net.socket, m->msgid);
|
||||
if (MQTTPacket_send_pubrel(m->MQTTVersion, m->msgid, 0, &client->net, client->clientID) != TCPSOCKET_COMPLETE)
|
||||
{
|
||||
client->good = 0;
|
||||
Log(TRACE_PROTOCOL, 29, NULL, client->clientID, client->net.socket,
|
||||
Socket_getpeer(client->net.socket));
|
||||
MQTTProtocol_closeSession(client, 1);
|
||||
client = NULL;
|
||||
}
|
||||
else
|
||||
m->lastTouch = MQTTTime_now();
|
||||
}
|
||||
/* break; why not do all retries at once? */
|
||||
}
|
||||
}
|
||||
exit:
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MQTT retry protocol and socket pending writes processing.
|
||||
* @param now current time
|
||||
* @param doRetry boolean - retries as well as pending writes?
|
||||
* @param regardless boolean - retry packets regardless of retry interval (used on reconnect)
|
||||
*/
|
||||
void MQTTProtocol_retry(START_TIME_TYPE now, int doRetry, int regardless)
|
||||
{
|
||||
ListElement* current = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
ListNextElement(bstate->clients, ¤t);
|
||||
/* look through the outbound message list of each client, checking to see if a retry is necessary */
|
||||
while (current)
|
||||
{
|
||||
Clients* client = (Clients*)(current->content);
|
||||
ListNextElement(bstate->clients, ¤t);
|
||||
if (client->connected == 0)
|
||||
continue;
|
||||
if (client->good == 0)
|
||||
{
|
||||
MQTTProtocol_closeSession(client, 1);
|
||||
continue;
|
||||
}
|
||||
if (Socket_noPendingWrites(client->net.socket) == 0)
|
||||
continue;
|
||||
if (doRetry)
|
||||
MQTTProtocol_retries(now, client, regardless);
|
||||
}
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Free a client structure
|
||||
* @param client the client data to free
|
||||
*/
|
||||
void MQTTProtocol_freeClient(Clients* client)
|
||||
{
|
||||
FUNC_ENTRY;
|
||||
/* free up pending message lists here, and any other allocated data */
|
||||
MQTTProtocol_freeMessageList(client->outboundMsgs);
|
||||
MQTTProtocol_freeMessageList(client->inboundMsgs);
|
||||
ListFree(client->messageQueue);
|
||||
free(client->clientID);
|
||||
client->clientID = NULL;
|
||||
if (client->will)
|
||||
{
|
||||
free(client->will->payload);
|
||||
free(client->will->topic);
|
||||
free(client->will);
|
||||
client->will = NULL;
|
||||
}
|
||||
if (client->username)
|
||||
free((void*)client->username);
|
||||
if (client->password)
|
||||
free((void*)client->password);
|
||||
#if defined(OPENSSL)
|
||||
if (client->sslopts)
|
||||
{
|
||||
if (client->sslopts->trustStore)
|
||||
free((void*)client->sslopts->trustStore);
|
||||
if (client->sslopts->keyStore)
|
||||
free((void*)client->sslopts->keyStore);
|
||||
if (client->sslopts->privateKey)
|
||||
free((void*)client->sslopts->privateKey);
|
||||
if (client->sslopts->privateKeyPassword)
|
||||
free((void*)client->sslopts->privateKeyPassword);
|
||||
if (client->sslopts->enabledCipherSuites)
|
||||
free((void*)client->sslopts->enabledCipherSuites);
|
||||
if (client->sslopts->struct_version >= 2)
|
||||
{
|
||||
if (client->sslopts->CApath)
|
||||
free((void*)client->sslopts->CApath);
|
||||
}
|
||||
free(client->sslopts);
|
||||
client->sslopts = NULL;
|
||||
}
|
||||
#endif
|
||||
/* don't free the client structure itself... this is done elsewhere */
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Empty a message list, leaving it able to accept new messages
|
||||
* @param msgList the message list to empty
|
||||
*/
|
||||
void MQTTProtocol_emptyMessageList(List* msgList)
|
||||
{
|
||||
ListElement* current = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
while (ListNextElement(msgList, ¤t))
|
||||
{
|
||||
Messages* m = (Messages*)(current->content);
|
||||
MQTTProtocol_removePublication(m->publish);
|
||||
if (m->MQTTVersion >= MQTTVERSION_5)
|
||||
MQTTProperties_free(&m->properties);
|
||||
}
|
||||
ListEmpty(msgList);
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Empty and free up all storage used by a message list
|
||||
* @param msgList the message list to empty and free
|
||||
*/
|
||||
void MQTTProtocol_freeMessageList(List* msgList)
|
||||
{
|
||||
FUNC_ENTRY;
|
||||
MQTTProtocol_emptyMessageList(msgList);
|
||||
ListFree(msgList);
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copy no more than dest_size -1 characters from the string pointed to by src to the array pointed to by dest.
|
||||
* The destination string will always be null-terminated.
|
||||
* @param dest the array which characters copy to
|
||||
* @param src the source string which characters copy from
|
||||
* @param dest_size the size of the memory pointed to by dest: copy no more than this -1 (allow for null). Must be >= 1
|
||||
* @return the destination string pointer
|
||||
*/
|
||||
char* MQTTStrncpy(char *dest, const char *src, size_t dest_size)
|
||||
{
|
||||
size_t count = dest_size;
|
||||
char *temp = dest;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (dest_size < strlen(src))
|
||||
Log(TRACE_MIN, -1, "the src string is truncated");
|
||||
|
||||
/* We must copy only the first (dest_size - 1) bytes */
|
||||
while (count > 1 && (*temp++ = *src++))
|
||||
count--;
|
||||
|
||||
*temp = '\0';
|
||||
|
||||
FUNC_EXIT;
|
||||
return dest;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Duplicate a string, safely, allocating space on the heap
|
||||
* @param src the source string which characters copy from
|
||||
* @return the duplicated, allocated string
|
||||
*/
|
||||
char* MQTTStrdup(const char* src)
|
||||
{
|
||||
size_t mlen = strlen(src) + 1;
|
||||
char* temp = malloc(mlen);
|
||||
if (temp)
|
||||
MQTTStrncpy(temp, src, mlen);
|
||||
return temp;
|
||||
}
|
||||
|
|
@ -0,0 +1,418 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs, Allan Stockdill-Mander - SSL updates
|
||||
* Ian Craggs - fix for buffer overflow in addressPort bug #433290
|
||||
* Ian Craggs - MQTT 3.1.1 support
|
||||
* Rong Xiang, Ian Craggs - C++ compatibility
|
||||
* Ian Craggs - fix for bug 479376
|
||||
* Ian Craggs - SNI support
|
||||
* Ian Craggs - fix for issue #164
|
||||
* Ian Craggs - fix for issue #179
|
||||
* Ian Craggs - MQTT 5.0 support
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief Functions dealing with the MQTT protocol exchanges
|
||||
*
|
||||
* Some other related functions are in the MQTTProtocolClient module
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "MQTTProtocolOut.h"
|
||||
#include "StackTrace.h"
|
||||
#include "Heap.h"
|
||||
#include "WebSocket.h"
|
||||
#include "Base64.h"
|
||||
|
||||
extern ClientStates* bstate;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Separates an address:port into two separate values
|
||||
* @param[in] uri the input string - hostname:port
|
||||
* @param[out] port the returned port integer
|
||||
* @param[out] topic optional topic portion of the address starting with '/'
|
||||
* @return the address string
|
||||
*/
|
||||
size_t MQTTProtocol_addressPort(const char* uri, int* port, const char **topic)
|
||||
{
|
||||
char* colon_pos = strrchr(uri, ':'); /* reverse find to allow for ':' in IPv6 addresses */
|
||||
char* buf = (char*)uri;
|
||||
size_t len;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (uri[0] == '[')
|
||||
{ /* ip v6 */
|
||||
if (colon_pos < strrchr(uri, ']'))
|
||||
colon_pos = NULL; /* means it was an IPv6 separator, not for host:port */
|
||||
}
|
||||
|
||||
if (colon_pos) /* have to strip off the port */
|
||||
{
|
||||
len = colon_pos - uri;
|
||||
*port = atoi(colon_pos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
len = strlen(buf);
|
||||
*port = DEFAULT_PORT;
|
||||
}
|
||||
|
||||
/* try and find topic portion */
|
||||
if ( topic )
|
||||
{
|
||||
const char* addr_start = uri;
|
||||
if ( colon_pos )
|
||||
addr_start = colon_pos;
|
||||
*topic = strchr( addr_start, '/' );
|
||||
}
|
||||
|
||||
if (buf[len - 1] == ']')
|
||||
{
|
||||
/* we are stripping off the final ], so length is 1 shorter */
|
||||
--len;
|
||||
}
|
||||
FUNC_EXIT;
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allow user or password characters to be expressed in the form of %XX, XX being the
|
||||
* hexadecimal value of the caracter. This will avoid problems when a user code or a password
|
||||
* contains a '@' or another special character ('%' included)
|
||||
* @param p0
|
||||
* @param p1
|
||||
* @param basic_auth_in_len
|
||||
*/
|
||||
void MQTTProtocol_specialChars(char* p0, char* p1, b64_size_t *basic_auth_in_len)
|
||||
{
|
||||
while(*p1 != '@') {
|
||||
if (*p1 != '%') {
|
||||
*p0++ = *p1++;
|
||||
}
|
||||
else if (isxdigit(*(p1 + 1)) && isxdigit(*(p1 + 2))) {
|
||||
/* next 2 characters are hexa digits */
|
||||
char hex[3];
|
||||
p1++;
|
||||
hex[0] = *p1++;
|
||||
hex[1] = *p1++;
|
||||
hex[2] = '\0';
|
||||
*p0++ = (char)strtol(hex, 0, 16);
|
||||
/* 3 input char => 1 output char */
|
||||
*basic_auth_in_len -= 2;
|
||||
}
|
||||
}
|
||||
*p0 = 0x0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MQTT outgoing connect processing for a client
|
||||
* @param ip_address the TCP address:port to connect to
|
||||
* @param aClient a structure with all MQTT data needed
|
||||
* @param int ssl
|
||||
* @param int MQTTVersion the MQTT version to connect with (3 or 4)
|
||||
* @param long timeout how long to wait for a new socket to be created
|
||||
* @return return code
|
||||
*/
|
||||
#if defined(OPENSSL)
|
||||
#if defined(__GNUC__) && defined(__linux__)
|
||||
int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int ssl, int websocket, int MQTTVersion,
|
||||
MQTTProperties* connectProperties, MQTTProperties* willProperties, long timeout)
|
||||
#else
|
||||
int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int ssl, int websocket, int MQTTVersion,
|
||||
MQTTProperties* connectProperties, MQTTProperties* willProperties)
|
||||
#endif
|
||||
#else
|
||||
#if defined(__GNUC__) && defined(__linux__)
|
||||
int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int websocket, int MQTTVersion,
|
||||
MQTTProperties* connectProperties, MQTTProperties* willProperties, long timeout)
|
||||
#else
|
||||
int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int websocket, int MQTTVersion,
|
||||
MQTTProperties* connectProperties, MQTTProperties* willProperties)
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
int rc, port;
|
||||
size_t addr_len;
|
||||
b64_size_t basic_auth_in_len, basic_auth_out_len;
|
||||
char *p0, *p1;
|
||||
b64_data_t *basic_auth;
|
||||
|
||||
FUNC_ENTRY;
|
||||
aClient->good = 1;
|
||||
|
||||
aClient->net.http_proxy = NULL;
|
||||
aClient->net.http_proxy_auth = NULL;
|
||||
if ((p0 = getenv("http_proxy")))
|
||||
{
|
||||
p1 = strchr(p0, '@');
|
||||
if(p1)
|
||||
{
|
||||
aClient->net.http_proxy = p1 + 1;
|
||||
p1 = strchr(p0, ':') + 3;
|
||||
basic_auth_in_len = (b64_size_t)(aClient->net.http_proxy - p1);
|
||||
basic_auth = (b64_data_t *)malloc(sizeof(char)*basic_auth_in_len);
|
||||
if (!basic_auth)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
basic_auth_in_len--;
|
||||
p0 = (char *)basic_auth;
|
||||
MQTTProtocol_specialChars(p0, p1, &basic_auth_in_len);
|
||||
basic_auth_out_len = Base64_encodeLength(basic_auth, basic_auth_in_len);
|
||||
if ((aClient->net.http_proxy_auth = (char *)malloc(sizeof(char) * basic_auth_out_len)) == NULL)
|
||||
{
|
||||
free(basic_auth);
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
Base64_encode(aClient->net.http_proxy_auth, basic_auth_out_len, basic_auth, basic_auth_in_len);
|
||||
free(basic_auth);
|
||||
}
|
||||
else {
|
||||
aClient->net.http_proxy = strchr(p0, ':') + 3;
|
||||
}
|
||||
}
|
||||
#if defined(OPENSSL)
|
||||
aClient->net.https_proxy = NULL;
|
||||
aClient->net.https_proxy_auth = NULL;
|
||||
if ((p0 = getenv("https_proxy"))) {
|
||||
p1 = strchr(p0, '@');
|
||||
if(p1) {
|
||||
aClient->net.https_proxy = p1 + 1;
|
||||
p1 = strchr(p0, ':') + 3;
|
||||
basic_auth_in_len = (b64_size_t)(aClient->net.https_proxy - p1);
|
||||
basic_auth = (b64_data_t *)malloc(sizeof(char)*basic_auth_in_len);
|
||||
if (!basic_auth)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
basic_auth_in_len--;
|
||||
p0 = (char *)basic_auth;
|
||||
MQTTProtocol_specialChars(p0, p1, &basic_auth_in_len);
|
||||
basic_auth_out_len = Base64_encodeLength(basic_auth, basic_auth_in_len);
|
||||
if ((aClient->net.https_proxy_auth = (char *)malloc(sizeof(char) * basic_auth_out_len)) == NULL)
|
||||
{
|
||||
free(basic_auth);
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
Base64_encode(aClient->net.https_proxy_auth, basic_auth_out_len, basic_auth, basic_auth_in_len);
|
||||
free(basic_auth);
|
||||
}
|
||||
else {
|
||||
aClient->net.https_proxy = strchr(p0, ':') + 3;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ssl && websocket && aClient->net.http_proxy) {
|
||||
#else
|
||||
if (websocket && aClient->net.http_proxy) {
|
||||
#endif
|
||||
addr_len = MQTTProtocol_addressPort(aClient->net.http_proxy, &port, NULL);
|
||||
#if defined(__GNUC__) && defined(__linux__)
|
||||
if (timeout < 0)
|
||||
rc = -1;
|
||||
else
|
||||
rc = Socket_new(aClient->net.http_proxy, addr_len, port, &(aClient->net.socket), timeout);
|
||||
#else
|
||||
rc = Socket_new(aClient->net.http_proxy, addr_len, port, &(aClient->net.socket));
|
||||
#endif
|
||||
}
|
||||
#if defined(OPENSSL)
|
||||
else if (ssl && websocket && aClient->net.https_proxy) {
|
||||
addr_len = MQTTProtocol_addressPort(aClient->net.https_proxy, &port, NULL);
|
||||
#if defined(__GNUC__) && defined(__linux__)
|
||||
if (timeout < 0)
|
||||
rc = -1;
|
||||
else
|
||||
rc = Socket_new(aClient->net.https_proxy, addr_len, port, &(aClient->net.socket), timeout);
|
||||
#else
|
||||
rc = Socket_new(aClient->net.https_proxy, addr_len, port, &(aClient->net.socket));
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
addr_len = MQTTProtocol_addressPort(ip_address, &port, NULL);
|
||||
#if defined(__GNUC__) && defined(__linux__)
|
||||
if (timeout < 0)
|
||||
rc = -1;
|
||||
else
|
||||
rc = Socket_new(ip_address, addr_len, port, &(aClient->net.socket), timeout);
|
||||
#else
|
||||
rc = Socket_new(ip_address, addr_len, port, &(aClient->net.socket));
|
||||
#endif
|
||||
}
|
||||
if (rc == EINPROGRESS || rc == EWOULDBLOCK)
|
||||
aClient->connect_state = TCP_IN_PROGRESS; /* TCP connect called - wait for connect completion */
|
||||
else if (rc == 0)
|
||||
{ /* TCP connect completed. If SSL, send SSL connect */
|
||||
#if defined(OPENSSL)
|
||||
if (ssl)
|
||||
{
|
||||
if (websocket && aClient->net.https_proxy) {
|
||||
aClient->connect_state = PROXY_CONNECT_IN_PROGRESS;
|
||||
rc = WebSocket_proxy_connect( &aClient->net, 1, ip_address);
|
||||
}
|
||||
if (rc == 0 && SSLSocket_setSocketForSSL(&aClient->net, aClient->sslopts, ip_address, addr_len) == 1)
|
||||
{
|
||||
rc = aClient->sslopts->struct_version >= 3 ?
|
||||
SSLSocket_connect(aClient->net.ssl, aClient->net.socket, ip_address,
|
||||
aClient->sslopts->verify, aClient->sslopts->ssl_error_cb, aClient->sslopts->ssl_error_context) :
|
||||
SSLSocket_connect(aClient->net.ssl, aClient->net.socket, ip_address,
|
||||
aClient->sslopts->verify, NULL, NULL);
|
||||
if (rc == TCPSOCKET_INTERRUPTED)
|
||||
aClient->connect_state = SSL_IN_PROGRESS; /* SSL connect called - wait for completion */
|
||||
}
|
||||
else
|
||||
rc = SOCKET_ERROR;
|
||||
}
|
||||
else if (websocket && aClient->net.http_proxy) {
|
||||
#else
|
||||
if (websocket && aClient->net.http_proxy) {
|
||||
#endif
|
||||
aClient->connect_state = PROXY_CONNECT_IN_PROGRESS;
|
||||
rc = WebSocket_proxy_connect( &aClient->net, 0, ip_address);
|
||||
}
|
||||
if ( websocket )
|
||||
{
|
||||
rc = WebSocket_connect( &aClient->net, ip_address );
|
||||
if ( rc == TCPSOCKET_INTERRUPTED )
|
||||
aClient->connect_state = WEBSOCKET_IN_PROGRESS; /* Websocket connect called - wait for completion */
|
||||
}
|
||||
if (rc == 0)
|
||||
{
|
||||
/* Now send the MQTT connect packet */
|
||||
if ((rc = MQTTPacket_send_connect(aClient, MQTTVersion, connectProperties, willProperties)) == 0)
|
||||
aClient->connect_state = WAIT_FOR_CONNACK; /* MQTT Connect sent - wait for CONNACK */
|
||||
else
|
||||
aClient->connect_state = NOT_IN_PROGRESS;
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process an incoming pingresp packet for a socket
|
||||
* @param pack pointer to the publish packet
|
||||
* @param sock the socket on which the packet was received
|
||||
* @return completion code
|
||||
*/
|
||||
int MQTTProtocol_handlePingresps(void* pack, int sock)
|
||||
{
|
||||
Clients* client = NULL;
|
||||
int rc = TCPSOCKET_COMPLETE;
|
||||
|
||||
FUNC_ENTRY;
|
||||
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
|
||||
Log(LOG_PROTOCOL, 21, NULL, sock, client->clientID);
|
||||
client->ping_outstanding = 0;
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MQTT outgoing subscribe processing for a client
|
||||
* @param client the client structure
|
||||
* @param topics list of topics
|
||||
* @param qoss corresponding list of QoSs
|
||||
* @param opts MQTT 5.0 subscribe options
|
||||
* @param props MQTT 5.0 subscribe properties
|
||||
* @return completion code
|
||||
*/
|
||||
int MQTTProtocol_subscribe(Clients* client, List* topics, List* qoss, int msgID,
|
||||
MQTTSubscribe_options* opts, MQTTProperties* props)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
rc = MQTTPacket_send_subscribe(topics, qoss, opts, props, msgID, 0, client);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process an incoming suback packet for a socket
|
||||
* @param pack pointer to the publish packet
|
||||
* @param sock the socket on which the packet was received
|
||||
* @return completion code
|
||||
*/
|
||||
int MQTTProtocol_handleSubacks(void* pack, int sock)
|
||||
{
|
||||
Suback* suback = (Suback*)pack;
|
||||
Clients* client = NULL;
|
||||
int rc = TCPSOCKET_COMPLETE;
|
||||
|
||||
FUNC_ENTRY;
|
||||
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
|
||||
Log(LOG_PROTOCOL, 23, NULL, sock, client->clientID, suback->msgId);
|
||||
MQTTPacket_freeSuback(suback);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MQTT outgoing unsubscribe processing for a client
|
||||
* @param client the client structure
|
||||
* @param topics list of topics
|
||||
* @return completion code
|
||||
*/
|
||||
int MQTTProtocol_unsubscribe(Clients* client, List* topics, int msgID, MQTTProperties* props)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
rc = MQTTPacket_send_unsubscribe(topics, props, msgID, 0, client);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process an incoming unsuback packet for a socket
|
||||
* @param pack pointer to the publish packet
|
||||
* @param sock the socket on which the packet was received
|
||||
* @return completion code
|
||||
*/
|
||||
int MQTTProtocol_handleUnsubacks(void* pack, int sock)
|
||||
{
|
||||
Unsuback* unsuback = (Unsuback*)pack;
|
||||
Clients* client = NULL;
|
||||
int rc = TCPSOCKET_COMPLETE;
|
||||
|
||||
FUNC_ENTRY;
|
||||
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
|
||||
Log(LOG_PROTOCOL, 24, NULL, sock, client->clientID, unsuback->msgId);
|
||||
MQTTPacket_freeUnsuback(unsuback);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#include "MQTTReasonCodes.h"
|
||||
|
||||
#include "Heap.h"
|
||||
#include "StackTrace.h"
|
||||
|
||||
#include <memory.h>
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
static struct {
|
||||
enum MQTTReasonCodes value;
|
||||
const char* name;
|
||||
} nameToString[] =
|
||||
{
|
||||
{MQTTREASONCODE_SUCCESS, "SUCCESS"},
|
||||
{MQTTREASONCODE_NORMAL_DISCONNECTION, "Normal disconnection"},
|
||||
{MQTTREASONCODE_GRANTED_QOS_0, "Granted QoS 0"},
|
||||
{MQTTREASONCODE_GRANTED_QOS_1, "Granted QoS 1"},
|
||||
{MQTTREASONCODE_GRANTED_QOS_2, "Granted QoS 2"},
|
||||
{MQTTREASONCODE_DISCONNECT_WITH_WILL_MESSAGE, "Disconnect with Will Message"},
|
||||
{MQTTREASONCODE_NO_MATCHING_SUBSCRIBERS, "No matching subscribers"},
|
||||
{MQTTREASONCODE_NO_SUBSCRIPTION_FOUND, "No subscription found"},
|
||||
{MQTTREASONCODE_CONTINUE_AUTHENTICATION, "Continue authentication"},
|
||||
{MQTTREASONCODE_RE_AUTHENTICATE, "Re-authenticate"},
|
||||
{MQTTREASONCODE_UNSPECIFIED_ERROR, "Unspecified error"},
|
||||
{MQTTREASONCODE_MALFORMED_PACKET, "Malformed Packet"},
|
||||
{MQTTREASONCODE_PROTOCOL_ERROR, "Protocol error"},
|
||||
{MQTTREASONCODE_IMPLEMENTATION_SPECIFIC_ERROR, "Implementation specific error"},
|
||||
{MQTTREASONCODE_UNSUPPORTED_PROTOCOL_VERSION, "Unsupported Protocol Version"},
|
||||
{MQTTREASONCODE_CLIENT_IDENTIFIER_NOT_VALID, "Client Identifier not valid"},
|
||||
{MQTTREASONCODE_BAD_USER_NAME_OR_PASSWORD, "Bad User Name or Password"},
|
||||
{MQTTREASONCODE_NOT_AUTHORIZED, "Not authorized"},
|
||||
{MQTTREASONCODE_SERVER_UNAVAILABLE, "Server unavailable"},
|
||||
{MQTTREASONCODE_SERVER_BUSY, "Server busy"},
|
||||
{MQTTREASONCODE_BANNED, "Banned"},
|
||||
{MQTTREASONCODE_SERVER_SHUTTING_DOWN, "Server shutting down"},
|
||||
{MQTTREASONCODE_BAD_AUTHENTICATION_METHOD, "Bad authentication method"},
|
||||
{MQTTREASONCODE_KEEP_ALIVE_TIMEOUT, "Keep Alive timeout"},
|
||||
{MQTTREASONCODE_SESSION_TAKEN_OVER, "Session taken over"},
|
||||
{MQTTREASONCODE_TOPIC_FILTER_INVALID, "Topic filter invalid"},
|
||||
{MQTTREASONCODE_TOPIC_NAME_INVALID, "Topic name invalid"},
|
||||
{MQTTREASONCODE_PACKET_IDENTIFIER_IN_USE, "Packet Identifier in use"},
|
||||
{MQTTREASONCODE_PACKET_IDENTIFIER_NOT_FOUND, "Packet Identifier not found"},
|
||||
{MQTTREASONCODE_RECEIVE_MAXIMUM_EXCEEDED, "Receive Maximum exceeded"},
|
||||
{MQTTREASONCODE_TOPIC_ALIAS_INVALID, "Topic Alias invalid"},
|
||||
{MQTTREASONCODE_PACKET_TOO_LARGE, "Packet too large"},
|
||||
{MQTTREASONCODE_MESSAGE_RATE_TOO_HIGH, "Message rate too high"},
|
||||
{MQTTREASONCODE_QUOTA_EXCEEDED, "Quota exceeded"},
|
||||
{MQTTREASONCODE_ADMINISTRATIVE_ACTION, "Administrative action"},
|
||||
{MQTTREASONCODE_PAYLOAD_FORMAT_INVALID, "Payload format invalid"},
|
||||
{MQTTREASONCODE_RETAIN_NOT_SUPPORTED, "Retain not supported"},
|
||||
{MQTTREASONCODE_QOS_NOT_SUPPORTED, "QoS not supported"},
|
||||
{MQTTREASONCODE_USE_ANOTHER_SERVER, "Use another server"},
|
||||
{MQTTREASONCODE_SERVER_MOVED, "Server moved"},
|
||||
{MQTTREASONCODE_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED, "Shared subscriptions not supported"},
|
||||
{MQTTREASONCODE_CONNECTION_RATE_EXCEEDED, "Connection rate exceeded"},
|
||||
{MQTTREASONCODE_MAXIMUM_CONNECT_TIME, "Maximum connect time"},
|
||||
{MQTTREASONCODE_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED, "Subscription Identifiers not supported"},
|
||||
{MQTTREASONCODE_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED, "Wildcard Subscriptions not supported"}
|
||||
};
|
||||
|
||||
const char* MQTTReasonCode_toString(enum MQTTReasonCodes value)
|
||||
{
|
||||
int i = 0;
|
||||
const char* result = NULL;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(nameToString); ++i)
|
||||
{
|
||||
if (nameToString[i].value == value)
|
||||
{
|
||||
result = nameToString[i].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial implementation
|
||||
*******************************************************************************/
|
||||
|
||||
#include "MQTTTime.h"
|
||||
#include "StackTrace.h"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
void MQTTTime_sleep(long milliseconds)
|
||||
{
|
||||
FUNC_ENTRY;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
Sleep(milliseconds);
|
||||
#else
|
||||
usleep(milliseconds*1000);
|
||||
#endif
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
START_TIME_TYPE MQTTTime_start_clock(void)
|
||||
{
|
||||
return GetTickCount();
|
||||
}
|
||||
#elif defined(AIX)
|
||||
START_TIME_TYPE MQTTTime_start_clock(void)
|
||||
{
|
||||
static struct timespec start;
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
return start;
|
||||
}
|
||||
#else
|
||||
START_TIME_TYPE MQTTTime_start_clock(void)
|
||||
{
|
||||
static struct timeval start;
|
||||
static struct timespec start_ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &start_ts);
|
||||
start.tv_sec = start_ts.tv_sec;
|
||||
start.tv_usec = start_ts.tv_nsec / 1000;
|
||||
return start;
|
||||
}
|
||||
#endif
|
||||
START_TIME_TYPE MQTTTime_now(void)
|
||||
{
|
||||
return MQTTTime_start_clock();
|
||||
}
|
||||
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
long MQTTTime_elapsed(DWORD milliseconds)
|
||||
{
|
||||
return GetTickCount() - milliseconds;
|
||||
}
|
||||
#elif defined(AIX)
|
||||
#define assert(a)
|
||||
long MQTTTime_elapsed(struct timespec start)
|
||||
{
|
||||
struct timespec now, res;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
ntimersub(now, start, res);
|
||||
return (res.tv_sec)*1000L + (res.tv_nsec)/1000000L;
|
||||
}
|
||||
#else
|
||||
long MQTTTime_elapsed(struct timeval start)
|
||||
{
|
||||
struct timeval now, res;
|
||||
static struct timespec now_ts;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &now_ts);
|
||||
now.tv_sec = now_ts.tv_sec;
|
||||
now.tv_usec = now_ts.tv_nsec / 1000;
|
||||
timersub(&now, &start, &res);
|
||||
return (res.tv_sec)*1000 + (res.tv_usec)/1000;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
/*
|
||||
* @param new most recent time in milliseconds from GetTickCount()
|
||||
* @param old older time in milliseconds from GetTickCount()
|
||||
* @return difference in milliseconds
|
||||
*/
|
||||
long MQTTTime_difftime(DWORD new, DWORD old)
|
||||
{
|
||||
return new - old;
|
||||
}
|
||||
#elif defined(AIX)
|
||||
#define assert(a)
|
||||
long MQTTTime_difftime(struct timespec new, struct timespec old)
|
||||
{
|
||||
struct timespec result;
|
||||
|
||||
ntimersub(new, old, result);
|
||||
return (result.tv_sec)*1000L + (result.tv_nsec)/1000000L; /* convert to milliseconds */
|
||||
}
|
||||
#else
|
||||
long MQTTTime_difftime(struct timeval new, struct timeval old)
|
||||
{
|
||||
struct timeval result;
|
||||
|
||||
timersub(&new, &old, &result);
|
||||
return (result.tv_sec)*1000 + (result.tv_usec)/1000; /* convert to milliseconds */
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2012, 2018 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#if !defined(_WRS_KERNEL)
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <ctype.h>
|
||||
#include "MQTTAsync.h"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <windows.h>
|
||||
#include <tchar.h>
|
||||
#include <io.h>
|
||||
#include <sys/stat.h>
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#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.
|
||||
*
|
||||
* */
|
||||
|
||||
|
||||
static const char* libraries[] = {"paho-mqtt3c", "paho-mqtt3cs", "paho-mqtt3a", "paho-mqtt3as"};
|
||||
static const char* eyecatchers[] = {"MQTTAsyncV3_Version", "MQTTAsyncV3_Timestamp",
|
||||
"MQTTClientV3_Version", "MQTTClientV3_Timestamp"};
|
||||
|
||||
|
||||
char* FindString(char* filename, const char* eyecatcher_input);
|
||||
int printVersionInfo(MQTTAsync_nameValue* info);
|
||||
int loadandcall(const char* libname);
|
||||
void printEyecatchers(char* filename);
|
||||
|
||||
|
||||
/**
|
||||
* 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, const char* eyecatcher_input)
|
||||
{
|
||||
FILE* infile = NULL;
|
||||
static char value[100];
|
||||
const char* eyecatcher = eyecatcher_input;
|
||||
|
||||
memset(value, 0, 100);
|
||||
if ((infile = fopen(filename, "rb")) != NULL)
|
||||
{
|
||||
size_t buflen = strlen(eyecatcher);
|
||||
char* buffer = (char*) malloc(buflen + 1); /* added space for unused null terminator to stop LGTM complaint */
|
||||
|
||||
if (buffer != NULL)
|
||||
{
|
||||
int c = fgetc(infile);
|
||||
|
||||
while (feof(infile) == 0)
|
||||
{
|
||||
int count = 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);
|
||||
}
|
||||
|
||||
fclose(infile);
|
||||
}
|
||||
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(const char* libname)
|
||||
{
|
||||
int rc = 0;
|
||||
MQTTAsync_nameValue* (*func_address)(void) = NULL;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
HMODULE APILibrary = LoadLibraryA(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 */
|
||||
if (APILibrary == NULL)
|
||||
printf("Error loading library %s, error %s\n", libname, dlerror());
|
||||
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, 2018 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(__CYGWIN__)
|
||||
sprintf(namebuf, "cyg%s-1.dll", libraries[i]);
|
||||
#elif defined(_WIN32) || defined(_WIN64)
|
||||
sprintf(namebuf, "%s.dll", libraries[i]);
|
||||
#elif defined(OSX)
|
||||
sprintf(namebuf, "lib%s.1.dylib", libraries[i]);
|
||||
#else
|
||||
sprintf(namebuf, "lib%s.so.1", 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;
|
||||
}
|
||||
#else
|
||||
int main(void)
|
||||
{
|
||||
fprintf(stderr, "This tool is not supported on this platform yet.\n");
|
||||
return 1;
|
||||
}
|
||||
#endif /* !defined(_WRS_KERNEL) */
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief Trace messages
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "Messages.h"
|
||||
#include "Log.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Heap.h"
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
#define max_msg_len 120
|
||||
|
||||
static const char *protocol_message_list[] =
|
||||
{
|
||||
"%d %s -> CONNECT version %d clean: %d (%d)", /* 0, was 131, 68 and 69 */
|
||||
"%d %s <- CONNACK rc: %d", /* 1, was 132 */
|
||||
"%d %s -> CONNACK rc: %d (%d)", /* 2, was 138 */
|
||||
"%d %s <- PINGREQ", /* 3, was 35 */
|
||||
"%d %s -> PINGRESP (%d)", /* 4 */
|
||||
"%d %s <- DISCONNECT", /* 5 */
|
||||
"%d %s <- SUBSCRIBE msgid: %d", /* 6, was 39 */
|
||||
"%d %s -> SUBACK msgid: %d (%d)", /* 7, was 40 */
|
||||
"%d %s <- UNSUBSCRIBE msgid: %d", /* 8, was 41 */
|
||||
"%d %s -> UNSUBACK msgid: %d (%d)", /* 9 */
|
||||
"%d %s -> PUBLISH msgid: %d qos: %d retained: %d rc %d payload len(%d): %.*s", /* 10, was 42 */
|
||||
"%d %s <- PUBLISH msgid: %d qos: %d retained: %d payload len(%d): %.*s", /* 11, was 46 */
|
||||
"%d %s -> PUBACK msgid: %d (%d)", /* 12, was 47 */
|
||||
"%d %s -> PUBREC msgid: %d (%d)", /* 13, was 48 */
|
||||
"%d %s <- PUBACK msgid: %d", /* 14, was 49 */
|
||||
"%d %s <- PUBREC msgid: %d", /* 15, was 53 */
|
||||
"%d %s -> PUBREL msgid: %d (%d)", /* 16, was 57 */
|
||||
"%d %s <- PUBREL msgid %d", /* 17, was 58 */
|
||||
"%d %s -> PUBCOMP msgid %d (%d)", /* 18, was 62 */
|
||||
"%d %s <- PUBCOMP msgid:%d", /* 19, was 63 */
|
||||
"%d %s -> PINGREQ (%d)", /* 20, was 137 */
|
||||
"%d %s <- PINGRESP", /* 21, was 70 */
|
||||
"%d %s -> SUBSCRIBE msgid: %d (%d)", /* 22, was 72 */
|
||||
"%d %s <- SUBACK msgid: %d", /* 23, was 73 */
|
||||
"%d %s <- UNSUBACK msgid: %d", /* 24, was 74 */
|
||||
"%d %s -> UNSUBSCRIBE msgid: %d (%d)", /* 25, was 106 */
|
||||
"%d %s <- CONNECT", /* 26 */
|
||||
"%d %s -> PUBLISH qos: 0 retained: %d rc: %d payload len(%d): %.*s", /* 27 */
|
||||
"%d %s -> DISCONNECT (%d)", /* 28 */
|
||||
"Socket error for client identifier %s, socket %d, peer address %s; ending connection", /* 29 */
|
||||
};
|
||||
|
||||
static const char *trace_message_list[] =
|
||||
{
|
||||
"Failed to remove client from bstate->clients", /* 0 */
|
||||
"Removed client %s from bstate->clients, socket %d", /* 1 */
|
||||
"Packet_Factory: unhandled packet type %d", /* 2 */
|
||||
"Packet %s received from client %s for message identifier %d, but no record of that message identifier found", /* 3 */
|
||||
"Packet %s received from client %s for message identifier %d, but message is wrong QoS, %d", /* 4 */
|
||||
"Packet %s received from client %s for message identifier %d, but message is in wrong state", /* 5 */
|
||||
"%s received from client %s for message id %d - removing publication", /* 6 */
|
||||
"Trying %s again for client %s, socket %d, message identifier %d", /* 7 */
|
||||
"", /* 8 */
|
||||
"(%lu) %*s(%d)> %s:%d", /* 9 */
|
||||
"(%lu) %*s(%d)< %s:%d", /* 10 */
|
||||
"(%lu) %*s(%d)< %s:%d (%d)", /* 11 */
|
||||
"Storing unsent QoS 0 message", /* 12 */
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a log message by its index
|
||||
* @param index the integer index
|
||||
* @param log_level the log level, used to determine which message list to use
|
||||
* @return the message format string
|
||||
*/
|
||||
const char* Messages_get(int index, enum LOG_LEVELS log_level)
|
||||
{
|
||||
const char *msg = NULL;
|
||||
|
||||
if (log_level == TRACE_PROTOCOL)
|
||||
msg = (index >= 0 && index < ARRAY_SIZE(protocol_message_list)) ? protocol_message_list[index] : NULL;
|
||||
else
|
||||
msg = (index >= 0 && index < ARRAY_SIZE(trace_message_list)) ? trace_message_list[index] : NULL;
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2017 logi.cals GmbH
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Gunter Raidl - timer support for VxWorks
|
||||
* Rainer Poisel - reusability
|
||||
*******************************************************************************/
|
||||
|
||||
#include "OsWrapper.h"
|
||||
|
||||
#if defined(_WRS_KERNEL)
|
||||
void usleep(useconds_t useconds)
|
||||
{
|
||||
struct timespec tv;
|
||||
tv.tv_sec = useconds / 1000000;
|
||||
tv.tv_nsec = (useconds % 1000000) * 1000;
|
||||
nanosleep(&tv, NULL);
|
||||
}
|
||||
#endif /* defined(_WRS_KERNEL) */
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Wind River Systems, Inc. All Rights Reserved.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Keith Holman - initial implementation and documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#include "SHA1.h"
|
||||
|
||||
#if !defined(OPENSSL)
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
|
||||
int SHA1_Init(SHA_CTX *c)
|
||||
{
|
||||
if (!CryptAcquireContext(&c->hProv, NULL, NULL,
|
||||
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
|
||||
return 0;
|
||||
if (!CryptCreateHash(c->hProv, CALG_SHA1, 0, 0, &c->hHash))
|
||||
{
|
||||
CryptReleaseContext(c->hProv, 0);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int SHA1_Update(SHA_CTX *c, const void *data, size_t len)
|
||||
{
|
||||
int rv = 1;
|
||||
if (!CryptHashData(c->hHash, data, (DWORD)len, 0))
|
||||
rv = 0;
|
||||
return rv;
|
||||
}
|
||||
|
||||
int SHA1_Final(unsigned char *md, SHA_CTX *c)
|
||||
{
|
||||
int rv = 0;
|
||||
DWORD md_len = SHA1_DIGEST_LENGTH;
|
||||
if (CryptGetHashParam(c->hHash, HP_HASHVAL, md, &md_len, 0))
|
||||
rv = 1;
|
||||
CryptDestroyHash(c->hHash);
|
||||
CryptReleaseContext(c->hProv, 0);
|
||||
return rv;
|
||||
}
|
||||
|
||||
#else /* if defined(_WIN32) || defined(_WIN64) */
|
||||
#if defined(__linux__) || defined(__CYGWIN__)
|
||||
# include <endian.h>
|
||||
#elif defined(__APPLE__)
|
||||
# include <libkern/OSByteOrder.h>
|
||||
# define htobe32(x) OSSwapHostToBigInt32(x)
|
||||
# define be32toh(x) OSSwapBigToHostInt32(x)
|
||||
#elif defined(__FreeBSD__) || defined(__NetBSD__)
|
||||
# include <sys/endian.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
static unsigned char pad[64] = {
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
int SHA1_Init(SHA_CTX *ctx)
|
||||
{
|
||||
int ret = 0;
|
||||
if ( ctx )
|
||||
{
|
||||
ctx->h[0] = 0x67452301;
|
||||
ctx->h[1] = 0xEFCDAB89;
|
||||
ctx->h[2] = 0x98BADCFE;
|
||||
ctx->h[3] = 0x10325476;
|
||||
ctx->h[4] = 0xC3D2E1F0;
|
||||
ctx->size = 0u;
|
||||
ctx->total = 0u;
|
||||
ret = 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define ROTATE_LEFT32(a, n) (((a) << (n)) | ((a) >> (32 - (n))))
|
||||
static void SHA1_ProcessBlock(SHA_CTX *ctx)
|
||||
{
|
||||
uint32_t blks[5];
|
||||
uint32_t *w;
|
||||
int i;
|
||||
|
||||
/* initialize */
|
||||
for ( i = 0; i < 5; ++i )
|
||||
blks[i] = ctx->h[i];
|
||||
|
||||
w = ctx->w;
|
||||
|
||||
/* perform SHA-1 hash */
|
||||
for ( i = 0; i < 16; ++i )
|
||||
w[i] = be32toh(w[i]);
|
||||
|
||||
for( i = 0; i < 80; ++i )
|
||||
{
|
||||
int tmp;
|
||||
if ( i >= 16 )
|
||||
w[i & 0x0F] = ROTATE_LEFT32( w[(i+13) & 0x0F] ^ w[(i+8) & 0x0F] ^ w[(i+2) & 0x0F] ^ w[i & 0x0F], 1 );
|
||||
|
||||
if ( i < 20 )
|
||||
tmp = ROTATE_LEFT32(blks[0], 5) + ((blks[1] & blks[2]) | (~(blks[1]) & blks[3])) + blks[4] + w[i & 0x0F] + 0x5A827999;
|
||||
else if ( i < 40 )
|
||||
tmp = ROTATE_LEFT32(blks[0], 5) + (blks[1]^blks[2]^blks[3]) + blks[4] + w[i & 0x0F] + 0x6ED9EBA1;
|
||||
else if ( i < 60 )
|
||||
tmp = ROTATE_LEFT32(blks[0], 5) + ((blks[1] & blks[2]) | (blks[1] & blks[3]) | (blks[2] & blks[3])) + blks[4] + w[i & 0x0F] + 0x8F1BBCDC;
|
||||
else
|
||||
tmp = ROTATE_LEFT32(blks[0], 5) + (blks[1]^blks[2]^blks[3]) + blks[4] + w[i & 0x0F] + 0xCA62C1D6;
|
||||
|
||||
/* update registers */
|
||||
blks[4] = blks[3];
|
||||
blks[3] = blks[2];
|
||||
blks[2] = ROTATE_LEFT32(blks[1], 30);
|
||||
blks[1] = blks[0];
|
||||
blks[0] = tmp;
|
||||
}
|
||||
|
||||
/* update of hash */
|
||||
for ( i = 0; i < 5; ++i )
|
||||
ctx->h[i] += blks[i];
|
||||
}
|
||||
|
||||
int SHA1_Final(unsigned char *md, SHA_CTX *ctx)
|
||||
{
|
||||
int i;
|
||||
int ret = 0;
|
||||
size_t pad_amount;
|
||||
uint64_t total;
|
||||
|
||||
/* length before pad */
|
||||
total = ctx->total * 8;
|
||||
|
||||
if ( ctx->size < 56 )
|
||||
pad_amount = 56 - ctx->size;
|
||||
else
|
||||
pad_amount = 64 + 56 - ctx->size;
|
||||
|
||||
SHA1_Update(ctx, pad, pad_amount);
|
||||
|
||||
ctx->w[14] = htobe32((uint32_t)(total >> 32));
|
||||
ctx->w[15] = htobe32((uint32_t)total);
|
||||
|
||||
SHA1_ProcessBlock(ctx);
|
||||
|
||||
for ( i = 0; i < 5; ++i )
|
||||
ctx->h[i] = htobe32(ctx->h[i]);
|
||||
|
||||
if ( md )
|
||||
{
|
||||
memcpy( md, &ctx->h[0], SHA1_DIGEST_LENGTH );
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SHA1_Update(SHA_CTX *ctx, const void *data, size_t len)
|
||||
{
|
||||
while ( len > 0 )
|
||||
{
|
||||
unsigned int n = 64 - ctx->size;
|
||||
if ( len < n )
|
||||
n = len;
|
||||
|
||||
memcpy(ctx->buffer + ctx->size, data, n);
|
||||
|
||||
ctx->size += n;
|
||||
ctx->total += n;
|
||||
|
||||
data = (uint8_t *)data + n;
|
||||
len -= n;
|
||||
|
||||
if ( ctx->size == 64 )
|
||||
{
|
||||
SHA1_ProcessBlock(ctx);
|
||||
ctx->size = 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* else if defined(_WIN32) || defined(_WIN64) */
|
||||
#endif /* elif !defined(OPENSSL) */
|
||||
|
||||
#if defined(SHA1_TEST)
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TEST_EXPECT(i,x) if (!(x)) {fprintf( stderr, "failed test: %s (for i == %d)\n", #x, i ); ++fails;}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct _td
|
||||
{
|
||||
const char *in;
|
||||
const char *out;
|
||||
};
|
||||
|
||||
int i;
|
||||
unsigned int fails = 0u;
|
||||
struct _td test_data[] = {
|
||||
{ "", "da39a3ee5e6b4b0d3255bfef95601890afd80709" },
|
||||
{ "this string", "fda4e74bc7489a18b146abdf23346d166663dab8" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* only 1 update */
|
||||
i = 0;
|
||||
while ( test_data[i].in != NULL )
|
||||
{
|
||||
int r[3] = { 1, 1, 1 };
|
||||
unsigned char sha_out[SHA1_DIGEST_LENGTH];
|
||||
char out[SHA1_DIGEST_LENGTH * 2 + 1];
|
||||
SHA_CTX c;
|
||||
int j;
|
||||
r[0] = SHA1_Init( &c );
|
||||
r[1] = SHA1_Update( &c, test_data[i].in, strlen(test_data[i].in));
|
||||
r[2] = SHA1_Final( sha_out, &c );
|
||||
for ( j = 0u; j < SHA1_DIGEST_LENGTH; ++j )
|
||||
snprintf( &out[j*2], 3u, "%02x", sha_out[j] );
|
||||
out[SHA1_DIGEST_LENGTH * 2] = '\0';
|
||||
TEST_EXPECT( i, r[0] == 1 && r[1] == 1 && r[2] == 1 && strncmp(out, test_data[i].out, strlen(test_data[i].out)) == 0 );
|
||||
++i;
|
||||
}
|
||||
|
||||
if ( fails )
|
||||
printf( "%u test failed!\n", fails );
|
||||
else
|
||||
printf( "all tests passed\n" );
|
||||
return fails;
|
||||
}
|
||||
#endif /* if defined(SHA1_TEST) */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,442 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
* Ian Craggs, Allan Stockdill-Mander - SSL updates
|
||||
* Ian Craggs - fix for issue #244, issue #20
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief Socket buffering related functions
|
||||
*
|
||||
* Some other related functions are in the Socket module
|
||||
*/
|
||||
#include "SocketBuffer.h"
|
||||
#include "LinkedList.h"
|
||||
#include "Log.h"
|
||||
#include "Messages.h"
|
||||
#include "StackTrace.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Heap.h"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define iov_len len
|
||||
#define iov_base buf
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Default input queue buffer
|
||||
*/
|
||||
static socket_queue* def_queue;
|
||||
|
||||
/**
|
||||
* List of queued input buffers
|
||||
*/
|
||||
static List* queues;
|
||||
|
||||
/**
|
||||
* List of queued write buffers
|
||||
*/
|
||||
static List writes;
|
||||
|
||||
|
||||
int socketcompare(void* a, void* b);
|
||||
int SocketBuffer_newDefQ(void);
|
||||
void SocketBuffer_freeDefQ(void);
|
||||
int pending_socketcompare(void* a, void* b);
|
||||
|
||||
|
||||
/**
|
||||
* List callback function for comparing socket_queues by socket
|
||||
* @param a first integer value
|
||||
* @param b second integer value
|
||||
* @return boolean indicating whether a and b are equal
|
||||
*/
|
||||
int socketcompare(void* a, void* b)
|
||||
{
|
||||
return ((socket_queue*)a)->socket == *(int*)b;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new default queue when one has just been used.
|
||||
*/
|
||||
int SocketBuffer_newDefQ(void)
|
||||
{
|
||||
int rc = PAHO_MEMORY_ERROR;
|
||||
|
||||
def_queue = malloc(sizeof(socket_queue));
|
||||
if (def_queue)
|
||||
{
|
||||
def_queue->buflen = 1000;
|
||||
def_queue->buf = malloc(def_queue->buflen);
|
||||
if (def_queue->buf)
|
||||
{
|
||||
def_queue->socket = def_queue->index = 0;
|
||||
def_queue->buflen = def_queue->datalen = def_queue->headerlen = 0;
|
||||
rc = 0;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the socketBuffer module
|
||||
*/
|
||||
int SocketBuffer_initialize(void)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
rc = SocketBuffer_newDefQ();
|
||||
if (rc == 0)
|
||||
{
|
||||
if ((queues = ListInitialize()) == NULL)
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
}
|
||||
ListZero(&writes);
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Free the default queue memory
|
||||
*/
|
||||
void SocketBuffer_freeDefQ(void)
|
||||
{
|
||||
free(def_queue->buf);
|
||||
free(def_queue);
|
||||
def_queue = NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Terminate the socketBuffer module
|
||||
*/
|
||||
void SocketBuffer_terminate(void)
|
||||
{
|
||||
ListElement* cur = NULL;
|
||||
ListEmpty(&writes);
|
||||
|
||||
FUNC_ENTRY;
|
||||
while (ListNextElement(queues, &cur))
|
||||
free(((socket_queue*)(cur->content))->buf);
|
||||
ListFree(queues);
|
||||
SocketBuffer_freeDefQ();
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cleanup any buffers for a specific socket
|
||||
* @param socket the socket to clean up
|
||||
*/
|
||||
void SocketBuffer_cleanup(int socket)
|
||||
{
|
||||
FUNC_ENTRY;
|
||||
SocketBuffer_writeComplete(socket); /* clean up write buffers */
|
||||
if (ListFindItem(queues, &socket, socketcompare))
|
||||
{
|
||||
free(((socket_queue*)(queues->current->content))->buf);
|
||||
ListRemove(queues, queues->current->content);
|
||||
}
|
||||
if (def_queue->socket == socket)
|
||||
{
|
||||
def_queue->socket = def_queue->index = 0;
|
||||
def_queue->headerlen = def_queue->datalen = 0;
|
||||
}
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get any queued data for a specific socket
|
||||
* @param socket the socket to get queued data for
|
||||
* @param bytes the number of bytes of data to retrieve
|
||||
* @param actual_len the actual length returned
|
||||
* @return the actual data
|
||||
*/
|
||||
char* SocketBuffer_getQueuedData(int socket, size_t bytes, size_t* actual_len)
|
||||
{
|
||||
socket_queue* queue = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (ListFindItem(queues, &socket, socketcompare))
|
||||
{ /* if there is queued data for this socket, add any data read to it */
|
||||
queue = (socket_queue*)(queues->current->content);
|
||||
*actual_len = queue->datalen;
|
||||
}
|
||||
else
|
||||
{
|
||||
*actual_len = 0;
|
||||
queue = def_queue;
|
||||
}
|
||||
if (bytes > queue->buflen)
|
||||
{
|
||||
if (queue->datalen > 0)
|
||||
{
|
||||
void* newmem = malloc(bytes);
|
||||
|
||||
free(queue->buf);
|
||||
queue->buf = newmem;
|
||||
if (!newmem)
|
||||
goto exit;
|
||||
memcpy(newmem, queue->buf, queue->datalen);
|
||||
}
|
||||
else
|
||||
queue->buf = realloc(queue->buf, bytes);
|
||||
queue->buflen = bytes;
|
||||
}
|
||||
exit:
|
||||
FUNC_EXIT;
|
||||
return queue->buf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get any queued character for a specific socket
|
||||
* @param socket the socket to get queued data for
|
||||
* @param c the character returned if any
|
||||
* @return completion code
|
||||
*/
|
||||
int SocketBuffer_getQueuedChar(int socket, char* c)
|
||||
{
|
||||
int rc = SOCKETBUFFER_INTERRUPTED;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (ListFindItem(queues, &socket, socketcompare))
|
||||
{ /* if there is queued data for this socket, read that first */
|
||||
socket_queue* queue = (socket_queue*)(queues->current->content);
|
||||
if (queue->index < queue->headerlen)
|
||||
{
|
||||
*c = queue->fixed_header[(queue->index)++];
|
||||
Log(TRACE_MAX, -1, "index is now %d, headerlen %d", queue->index, (int)queue->headerlen);
|
||||
rc = SOCKETBUFFER_COMPLETE;
|
||||
goto exit;
|
||||
}
|
||||
else if (queue->index > 4)
|
||||
{
|
||||
Log(LOG_FATAL, -1, "header is already at full length");
|
||||
rc = SOCKET_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc; /* there was no queued char if rc is SOCKETBUFFER_INTERRUPTED*/
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A socket read was interrupted so we need to queue data
|
||||
* @param socket the socket to get queued data for
|
||||
* @param actual_len the actual length of data that was read
|
||||
*/
|
||||
void SocketBuffer_interrupted(int socket, size_t actual_len)
|
||||
{
|
||||
socket_queue* queue = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (ListFindItem(queues, &socket, socketcompare))
|
||||
queue = (socket_queue*)(queues->current->content);
|
||||
else /* new saved queue */
|
||||
{
|
||||
queue = def_queue;
|
||||
/* if SocketBuffer_queueChar() has not yet been called, then the socket number
|
||||
in def_queue will not have been set. Issue #244.
|
||||
If actual_len == 0 then we may not need to do anything - I'll leave that
|
||||
optimization for another time. */
|
||||
queue->socket = socket;
|
||||
ListAppend(queues, def_queue, sizeof(socket_queue)+def_queue->buflen);
|
||||
SocketBuffer_newDefQ();
|
||||
}
|
||||
queue->index = 0;
|
||||
queue->datalen = actual_len;
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A socket read has now completed so we can get rid of the queue
|
||||
* @param socket the socket for which the operation is now complete
|
||||
* @return pointer to the default queue data
|
||||
*/
|
||||
char* SocketBuffer_complete(int socket)
|
||||
{
|
||||
FUNC_ENTRY;
|
||||
if (ListFindItem(queues, &socket, socketcompare))
|
||||
{
|
||||
socket_queue* queue = (socket_queue*)(queues->current->content);
|
||||
SocketBuffer_freeDefQ();
|
||||
def_queue = queue;
|
||||
ListDetach(queues, queue);
|
||||
}
|
||||
def_queue->socket = def_queue->index = 0;
|
||||
def_queue->headerlen = def_queue->datalen = 0;
|
||||
FUNC_EXIT;
|
||||
return def_queue->buf;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Queued a Charactor to a specific socket
|
||||
* @param socket the socket for which to queue char for
|
||||
* @param c the character to queue
|
||||
*/
|
||||
void SocketBuffer_queueChar(int socket, char c)
|
||||
{
|
||||
int error = 0;
|
||||
socket_queue* curq = def_queue;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (ListFindItem(queues, &socket, socketcompare))
|
||||
curq = (socket_queue*)(queues->current->content);
|
||||
else if (def_queue->socket == 0)
|
||||
{
|
||||
def_queue->socket = socket;
|
||||
def_queue->index = 0;
|
||||
def_queue->datalen = 0;
|
||||
}
|
||||
else if (def_queue->socket != socket)
|
||||
{
|
||||
Log(LOG_FATAL, -1, "attempt to reuse socket queue");
|
||||
error = 1;
|
||||
}
|
||||
if (curq->index > 4)
|
||||
{
|
||||
Log(LOG_FATAL, -1, "socket queue fixed_header field full");
|
||||
error = 1;
|
||||
}
|
||||
if (!error)
|
||||
{
|
||||
curq->fixed_header[(curq->index)++] = c;
|
||||
curq->headerlen = curq->index;
|
||||
}
|
||||
Log(TRACE_MAX, -1, "queueChar: index is now %d, headerlen %d", curq->index, (int)curq->headerlen);
|
||||
FUNC_EXIT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A socket write was interrupted so store the remaining data
|
||||
* @param socket the socket for which the write was interrupted
|
||||
* @param count the number of iovec buffers
|
||||
* @param iovecs buffer array
|
||||
* @param frees a set of flags indicating which of the iovecs array should be freed
|
||||
* @param total total data length to be written
|
||||
* @param bytes actual data length that was written
|
||||
*/
|
||||
#if defined(OPENSSL)
|
||||
int SocketBuffer_pendingWrite(int socket, SSL* ssl, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes)
|
||||
#else
|
||||
int SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int* frees, size_t total, size_t bytes)
|
||||
#endif
|
||||
{
|
||||
int i = 0;
|
||||
pending_writes* pw = NULL;
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
/* store the buffers until the whole packet is written */
|
||||
if ((pw = malloc(sizeof(pending_writes))) == NULL)
|
||||
{
|
||||
rc = PAHO_MEMORY_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
pw->socket = socket;
|
||||
#if defined(OPENSSL)
|
||||
pw->ssl = ssl;
|
||||
#endif
|
||||
pw->bytes = bytes;
|
||||
pw->total = total;
|
||||
pw->count = count;
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
pw->iovecs[i] = iovecs[i];
|
||||
pw->frees[i] = frees[i];
|
||||
}
|
||||
ListAppend(&writes, pw, sizeof(pw) + total);
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List callback function for comparing pending_writes by socket
|
||||
* @param a first integer value
|
||||
* @param b second integer value
|
||||
* @return boolean indicating whether a and b are equal
|
||||
*/
|
||||
int pending_socketcompare(void* a, void* b)
|
||||
{
|
||||
return ((pending_writes*)a)->socket == *(int*)b;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get any queued write data for a specific socket
|
||||
* @param socket the socket to get queued data for
|
||||
* @return pointer to the queued data or NULL
|
||||
*/
|
||||
pending_writes* SocketBuffer_getWrite(int socket)
|
||||
{
|
||||
ListElement* le = ListFindItem(&writes, &socket, pending_socketcompare);
|
||||
return (le) ? (pending_writes*)(le->content) : NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A socket write has now completed so we can get rid of the queue
|
||||
* @param socket the socket for which the operation is now complete
|
||||
* @return completion code, boolean - was the queue removed?
|
||||
*/
|
||||
int SocketBuffer_writeComplete(int socket)
|
||||
{
|
||||
return ListRemoveItem(&writes, &socket, pending_socketcompare);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the queued write data for a socket in the case of QoS 0 messages.
|
||||
* @param socket the socket for which the operation is now complete
|
||||
* @param topic the topic of the QoS 0 write
|
||||
* @param payload the payload of the QoS 0 write
|
||||
* @return pointer to the updated queued data structure, or NULL
|
||||
*/
|
||||
pending_writes* SocketBuffer_updateWrite(int socket, char* topic, char* payload)
|
||||
{
|
||||
pending_writes* pw = NULL;
|
||||
ListElement* le = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if ((le = ListFindItem(&writes, &socket, pending_socketcompare)) != NULL)
|
||||
{
|
||||
pw = (pending_writes*)(le->content);
|
||||
if (pw->count == 4)
|
||||
{
|
||||
pw->iovecs[2].iov_base = topic;
|
||||
pw->iovecs[3].iov_base = payload;
|
||||
}
|
||||
}
|
||||
|
||||
FUNC_EXIT;
|
||||
return pw;
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
#include "StackTrace.h"
|
||||
#include "Log.h"
|
||||
#include "LinkedList.h"
|
||||
|
||||
#include "Clients.h"
|
||||
#include "Thread.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
/*BE
|
||||
def STACKENTRY
|
||||
{
|
||||
n32 ptr STRING open "name"
|
||||
n32 dec "line"
|
||||
}
|
||||
|
||||
defList(STACKENTRY)
|
||||
BE*/
|
||||
|
||||
#define MAX_STACK_DEPTH 50
|
||||
#define MAX_FUNCTION_NAME_LENGTH 30
|
||||
#define MAX_THREADS 255
|
||||
|
||||
typedef struct
|
||||
{
|
||||
thread_id_type threadid;
|
||||
char name[MAX_FUNCTION_NAME_LENGTH];
|
||||
int line;
|
||||
} stackEntry;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
thread_id_type id;
|
||||
int maxdepth;
|
||||
int current_depth;
|
||||
stackEntry callstack[MAX_STACK_DEPTH];
|
||||
} threadEntry;
|
||||
|
||||
#include "StackTrace.h"
|
||||
|
||||
#if !defined(NOSTACKTRACE)
|
||||
|
||||
static int thread_count = 0;
|
||||
static threadEntry threads[MAX_THREADS];
|
||||
static threadEntry *my_thread = NULL;
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
mutex_type stack_mutex;
|
||||
#else
|
||||
static pthread_mutex_t stack_mutex_store = PTHREAD_MUTEX_INITIALIZER;
|
||||
static mutex_type stack_mutex = &stack_mutex_store;
|
||||
#endif
|
||||
|
||||
|
||||
int setStack(int create);
|
||||
|
||||
|
||||
int setStack(int create)
|
||||
{
|
||||
int i = -1;
|
||||
thread_id_type curid = Thread_getid();
|
||||
|
||||
my_thread = NULL;
|
||||
for (i = 0; i < MAX_THREADS && i < thread_count; ++i)
|
||||
{
|
||||
if (threads[i].id == curid)
|
||||
{
|
||||
my_thread = &threads[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (my_thread == NULL && create && thread_count < MAX_THREADS)
|
||||
{
|
||||
my_thread = &threads[thread_count];
|
||||
my_thread->id = curid;
|
||||
my_thread->maxdepth = 0;
|
||||
my_thread->current_depth = 0;
|
||||
++thread_count;
|
||||
}
|
||||
return my_thread != NULL; /* good == 1 */
|
||||
}
|
||||
|
||||
void StackTrace_entry(const char* name, int line, enum LOG_LEVELS trace_level)
|
||||
{
|
||||
Thread_lock_mutex(stack_mutex);
|
||||
if (!setStack(1))
|
||||
goto exit;
|
||||
if (trace_level != -1)
|
||||
Log_stackTrace(trace_level, 9, (int)my_thread->id, my_thread->current_depth, name, line, NULL);
|
||||
strncpy(my_thread->callstack[my_thread->current_depth].name, name, sizeof(my_thread->callstack[0].name)-1);
|
||||
my_thread->callstack[(my_thread->current_depth)++].line = line;
|
||||
if (my_thread->current_depth > my_thread->maxdepth)
|
||||
my_thread->maxdepth = my_thread->current_depth;
|
||||
if (my_thread->current_depth >= MAX_STACK_DEPTH)
|
||||
Log(LOG_FATAL, -1, "Max stack depth exceeded");
|
||||
exit:
|
||||
Thread_unlock_mutex(stack_mutex);
|
||||
}
|
||||
|
||||
|
||||
void StackTrace_exit(const char* name, int line, void* rc, enum LOG_LEVELS trace_level)
|
||||
{
|
||||
Thread_lock_mutex(stack_mutex);
|
||||
if (!setStack(0))
|
||||
goto exit;
|
||||
if (--(my_thread->current_depth) < 0)
|
||||
Log(LOG_FATAL, -1, "Minimum stack depth exceeded for thread %lu", my_thread->id);
|
||||
if (strncmp(my_thread->callstack[my_thread->current_depth].name, name, sizeof(my_thread->callstack[0].name)-1) != 0)
|
||||
Log(LOG_FATAL, -1, "Stack mismatch. Entry:%s Exit:%s\n", my_thread->callstack[my_thread->current_depth].name, name);
|
||||
if (trace_level != -1)
|
||||
{
|
||||
if (rc == NULL)
|
||||
Log_stackTrace(trace_level, 10, (int)my_thread->id, my_thread->current_depth, name, line, NULL);
|
||||
else
|
||||
Log_stackTrace(trace_level, 11, (int)my_thread->id, my_thread->current_depth, name, line, (int*)rc);
|
||||
}
|
||||
exit:
|
||||
Thread_unlock_mutex(stack_mutex);
|
||||
}
|
||||
|
||||
|
||||
void StackTrace_printStack(FILE* dest)
|
||||
{
|
||||
FILE* file = stdout;
|
||||
int t = 0;
|
||||
|
||||
if (dest)
|
||||
file = dest;
|
||||
for (t = 0; t < thread_count; ++t)
|
||||
{
|
||||
threadEntry *cur_thread = &threads[t];
|
||||
|
||||
if (cur_thread->id > 0)
|
||||
{
|
||||
int i = cur_thread->current_depth - 1;
|
||||
|
||||
fprintf(file, "=========== Start of stack trace for thread %lu ==========\n", (unsigned long)cur_thread->id);
|
||||
if (i >= 0)
|
||||
{
|
||||
fprintf(file, "%s (%d)\n", cur_thread->callstack[i].name, cur_thread->callstack[i].line);
|
||||
while (--i >= 0)
|
||||
fprintf(file, " at %s (%d)\n", cur_thread->callstack[i].name, cur_thread->callstack[i].line);
|
||||
}
|
||||
fprintf(file, "=========== End of stack trace for thread %lu ==========\n\n", (unsigned long)cur_thread->id);
|
||||
}
|
||||
}
|
||||
if (file != stdout && file != stderr && file != NULL)
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
|
||||
char* StackTrace_get(thread_id_type threadid, char* buf, int bufsize)
|
||||
{
|
||||
int t = 0;
|
||||
|
||||
if (bufsize < 100)
|
||||
goto exit;
|
||||
buf[0] = '\0';
|
||||
for (t = 0; t < thread_count; ++t)
|
||||
{
|
||||
threadEntry *cur_thread = &threads[t];
|
||||
|
||||
if (cur_thread->id == threadid)
|
||||
{
|
||||
int i = cur_thread->current_depth - 1;
|
||||
int curpos = 0;
|
||||
|
||||
if (i >= 0)
|
||||
{
|
||||
curpos += snprintf(&buf[curpos], bufsize - curpos -1,
|
||||
"%s (%d)\n", cur_thread->callstack[i].name, cur_thread->callstack[i].line);
|
||||
while (--i >= 0)
|
||||
curpos += snprintf(&buf[curpos], bufsize - curpos -1, /* lgtm [cpp/overflowing-snprintf] */
|
||||
" at %s (%d)\n", cur_thread->callstack[i].name, cur_thread->callstack[i].line);
|
||||
if (buf[--curpos] == '\n')
|
||||
buf[curpos] = '\0';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
exit:
|
||||
return buf;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,649 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial implementation
|
||||
* Ian Craggs, Allan Stockdill-Mander - async client updates
|
||||
* Ian Craggs - bug #415042 - start Linux thread as disconnected
|
||||
* Ian Craggs - fix for bug #420851
|
||||
* Ian Craggs - change MacOS semaphore implementation
|
||||
* Ian Craggs - fix for clock #284
|
||||
*******************************************************************************/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief Threading related functions
|
||||
*
|
||||
* Used to create platform independent threading functions
|
||||
*/
|
||||
|
||||
|
||||
#include "Thread.h"
|
||||
#if defined(THREAD_UNIT_TESTS)
|
||||
#define NOSTACKTRACE
|
||||
#endif
|
||||
#include "Log.h"
|
||||
#include "StackTrace.h"
|
||||
|
||||
#undef malloc
|
||||
#undef realloc
|
||||
#undef free
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <limits.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "OsWrapper.h"
|
||||
|
||||
/**
|
||||
* Start a new thread
|
||||
* @param fn the function to run, must be of the correct signature
|
||||
* @param parameter pointer to the function parameter, can be NULL
|
||||
* @return the new thread
|
||||
*/
|
||||
thread_type Thread_start(thread_fn fn, void* parameter)
|
||||
{
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
thread_type thread = NULL;
|
||||
#else
|
||||
thread_type thread = 0;
|
||||
pthread_attr_t attr;
|
||||
#endif
|
||||
|
||||
FUNC_ENTRY;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
thread = CreateThread(NULL, 0, fn, parameter, 0, NULL);
|
||||
#else
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
if (pthread_create(&thread, &attr, fn, parameter) != 0)
|
||||
thread = 0;
|
||||
pthread_attr_destroy(&attr);
|
||||
#endif
|
||||
FUNC_EXIT;
|
||||
return thread;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new mutex
|
||||
* @return the new mutex
|
||||
*/
|
||||
mutex_type Thread_create_mutex(int* rc)
|
||||
{
|
||||
mutex_type mutex = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
*rc = -1;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
mutex = CreateMutex(NULL, 0, NULL);
|
||||
if (mutex == NULL)
|
||||
*rc = GetLastError();
|
||||
#else
|
||||
mutex = malloc(sizeof(pthread_mutex_t));
|
||||
if (mutex)
|
||||
*rc = pthread_mutex_init(mutex, NULL);
|
||||
#endif
|
||||
FUNC_EXIT_RC(*rc);
|
||||
return mutex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lock a mutex which has alrea
|
||||
* @return completion code, 0 is success
|
||||
*/
|
||||
int Thread_lock_mutex(mutex_type mutex)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
/* don't add entry/exit trace points as the stack log uses mutexes - recursion beckons */
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
/* WaitForSingleObject returns WAIT_OBJECT_0 (0), on success */
|
||||
rc = WaitForSingleObject(mutex, INFINITE);
|
||||
#else
|
||||
rc = pthread_mutex_lock(mutex);
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unlock a mutex which has already been locked
|
||||
* @param mutex the mutex
|
||||
* @return completion code, 0 is success
|
||||
*/
|
||||
int Thread_unlock_mutex(mutex_type mutex)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
/* don't add entry/exit trace points as the stack log uses mutexes - recursion beckons */
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
/* if ReleaseMutex fails, the return value is 0 */
|
||||
if (ReleaseMutex(mutex) == 0)
|
||||
rc = GetLastError();
|
||||
else
|
||||
rc = 0;
|
||||
#else
|
||||
rc = pthread_mutex_unlock(mutex);
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destroy a mutex which has already been created
|
||||
* @param mutex the mutex
|
||||
*/
|
||||
int Thread_destroy_mutex(mutex_type mutex)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
rc = CloseHandle(mutex);
|
||||
#else
|
||||
rc = pthread_mutex_destroy(mutex);
|
||||
free(mutex);
|
||||
#endif
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the thread id of the thread from which this function is called
|
||||
* @return thread id, type varying according to OS
|
||||
*/
|
||||
thread_id_type Thread_getid(void)
|
||||
{
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
return GetCurrentThreadId();
|
||||
#else
|
||||
return pthread_self();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new semaphore
|
||||
* @return the new condition variable
|
||||
*/
|
||||
sem_type Thread_create_sem(int *rc)
|
||||
{
|
||||
sem_type sem = NULL;
|
||||
|
||||
FUNC_ENTRY;
|
||||
*rc = -1;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
sem = CreateEvent(
|
||||
NULL, /* default security attributes */
|
||||
FALSE, /* manual-reset event? */
|
||||
FALSE, /* initial state is nonsignaled */
|
||||
NULL /* object name */
|
||||
);
|
||||
#if 0
|
||||
sem = CreateSemaphore(
|
||||
NULL, /* default security attributes */
|
||||
0, /* initial count - non signaled */
|
||||
1, /* maximum count */
|
||||
NULL /* unnamed semaphore */
|
||||
);
|
||||
#endif
|
||||
#elif defined(OSX)
|
||||
sem = dispatch_semaphore_create(0L);
|
||||
*rc = (sem == NULL) ? -1 : 0;
|
||||
#else
|
||||
sem = malloc(sizeof(sem_t));
|
||||
if (sem)
|
||||
*rc = sem_init(sem, 0, 0);
|
||||
#endif
|
||||
FUNC_EXIT_RC(*rc);
|
||||
return sem;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wait for a semaphore to be posted, or timeout.
|
||||
* @param sem the semaphore
|
||||
* @param timeout the maximum time to wait, in milliseconds
|
||||
* @return completion code
|
||||
*/
|
||||
int Thread_wait_sem(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;
|
||||
#if !defined(_WIN32) && !defined(_WIN64) && !defined(OSX)
|
||||
#define USE_TRYWAIT
|
||||
#if defined(USE_TRYWAIT)
|
||||
int i = 0;
|
||||
useconds_t interval = 10000; /* 10000 microseconds: 10 milliseconds */
|
||||
int count = (1000 * timeout) / interval; /* how many intervals in timeout period */
|
||||
#else
|
||||
struct timespec ts;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
FUNC_ENTRY;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
/* returns 0 (WAIT_OBJECT_0) on success, non-zero (WAIT_TIMEOUT) if timeout occurred */
|
||||
rc = WaitForSingleObject(sem, timeout < 0 ? 0 : timeout);
|
||||
if (rc == WAIT_TIMEOUT)
|
||||
rc = ETIMEDOUT;
|
||||
#elif defined(OSX)
|
||||
/* returns 0 on success, non-zero if timeout occurred */
|
||||
rc = (int)dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, (int64_t)timeout*1000000L));
|
||||
if (rc != 0)
|
||||
rc = ETIMEDOUT;
|
||||
#elif defined(USE_TRYWAIT)
|
||||
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
|
||||
/* We have to use CLOCK_REALTIME rather than MONOTONIC for sem_timedwait interval.
|
||||
* Does this make it susceptible to system clock changes?
|
||||
* The intervals are small enough, and repeated, that I think it's not an issue.
|
||||
*/
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) != -1)
|
||||
{
|
||||
ts.tv_sec += timeout;
|
||||
rc = sem_timedwait(sem, &ts);
|
||||
}
|
||||
#endif
|
||||
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check to see if a semaphore has been posted, without waiting
|
||||
* The semaphore will be unchanged, if the return value is false.
|
||||
* The semaphore will have been decremented, if the return value is true.
|
||||
* @param sem the semaphore
|
||||
* @return 0 (false) or 1 (true)
|
||||
*/
|
||||
int Thread_check_sem(sem_type sem)
|
||||
{
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
/* if the return value is not 0, the semaphore will not have been decremented */
|
||||
return WaitForSingleObject(sem, 0) == WAIT_OBJECT_0;
|
||||
#elif defined(OSX)
|
||||
/* if the return value is not 0, the semaphore will not have been decremented */
|
||||
return dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW) == 0;
|
||||
#else
|
||||
/* If the call was unsuccessful, the state of the semaphore shall be unchanged,
|
||||
* and the function shall return a value of -1 */
|
||||
return sem_trywait(sem) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Post a semaphore
|
||||
* @param sem the semaphore
|
||||
* @return 0 on success
|
||||
*/
|
||||
int Thread_post_sem(sem_type sem)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
if (SetEvent(sem) == 0)
|
||||
rc = GetLastError();
|
||||
#elif defined(OSX)
|
||||
rc = (int)dispatch_semaphore_signal(sem);
|
||||
#else
|
||||
int val;
|
||||
int rc1 = sem_getvalue(sem, &val);
|
||||
if (rc1 != 0)
|
||||
rc = errno;
|
||||
else if (val == 0 && sem_post(sem) == -1)
|
||||
rc = errno;
|
||||
#endif
|
||||
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destroy a semaphore which has already been created
|
||||
* @param sem the semaphore
|
||||
*/
|
||||
int Thread_destroy_sem(sem_type sem)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
rc = CloseHandle(sem);
|
||||
#elif defined(OSX)
|
||||
dispatch_release(sem);
|
||||
#else
|
||||
rc = sem_destroy(sem);
|
||||
free(sem);
|
||||
#endif
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
|
||||
/**
|
||||
* Create a new condition variable
|
||||
* @return the condition variable struct
|
||||
*/
|
||||
cond_type Thread_create_cond(int *rc)
|
||||
{
|
||||
cond_type condvar = NULL;
|
||||
pthread_condattr_t attr;
|
||||
|
||||
FUNC_ENTRY;
|
||||
*rc = -1;
|
||||
pthread_condattr_init(&attr);
|
||||
|
||||
#if 0
|
||||
/* in theory, a monotonic clock should be able to be used. However on at least
|
||||
* one system reported, even though setclock() reported success, it didn't work.
|
||||
*/
|
||||
if ((rc = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) == 0)
|
||||
use_clock_monotonic = 1;
|
||||
else
|
||||
Log(LOG_ERROR, -1, "Error %d calling pthread_condattr_setclock(CLOCK_MONOTONIC)", rc);
|
||||
#endif
|
||||
|
||||
condvar = malloc(sizeof(cond_type_struct));
|
||||
if (condvar)
|
||||
{
|
||||
*rc = pthread_cond_init(&condvar->cond, &attr);
|
||||
*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;
|
||||
|
||||
FUNC_ENTRY;
|
||||
pthread_mutex_lock(&condvar->mutex);
|
||||
rc = pthread_cond_signal(&condvar->cond);
|
||||
pthread_mutex_unlock(&condvar->mutex);
|
||||
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait with a timeout (seconds) for condition variable
|
||||
* @return 0 for success, ETIMEDOUT otherwise
|
||||
*/
|
||||
int Thread_wait_cond(cond_type condvar, int timeout)
|
||||
{
|
||||
int rc = 0;
|
||||
struct timespec cond_timeout;
|
||||
|
||||
FUNC_ENTRY;
|
||||
clock_gettime(CLOCK_REALTIME, &cond_timeout);
|
||||
|
||||
cond_timeout.tv_sec += timeout;
|
||||
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)
|
||||
|
||||
#if defined(_WIN32) || defined(_WINDOWS)
|
||||
#define mqsleep(A) Sleep(1000*A)
|
||||
#define START_TIME_TYPE DWORD
|
||||
static DWORD start_time = 0;
|
||||
START_TIME_TYPE start_clock(void)
|
||||
{
|
||||
return GetTickCount();
|
||||
}
|
||||
#elif defined(AIX)
|
||||
#define mqsleep sleep
|
||||
#define START_TIME_TYPE struct timespec
|
||||
START_TIME_TYPE start_clock(void)
|
||||
{
|
||||
static struct timespec start;
|
||||
clock_gettime(CLOCK_REALTIME, &start);
|
||||
return start;
|
||||
}
|
||||
#else
|
||||
#define mqsleep sleep
|
||||
#define START_TIME_TYPE struct timeval
|
||||
/* TODO - unused - remove? static struct timeval start_time; */
|
||||
START_TIME_TYPE start_clock(void)
|
||||
{
|
||||
struct timeval start_time;
|
||||
gettimeofday(&start_time, NULL);
|
||||
return start_time;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
long elapsed(START_TIME_TYPE start_time)
|
||||
{
|
||||
return GetTickCount() - start_time;
|
||||
}
|
||||
#elif defined(AIX)
|
||||
#define assert(a)
|
||||
long 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 elapsed(START_TIME_TYPE start_time)
|
||||
{
|
||||
struct timeval now, res;
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
timersub(&now, &start_time, &res);
|
||||
return (res.tv_sec)*1000 + (res.tv_usec)/1000;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
int tests = 0, failures = 0;
|
||||
|
||||
void myassert(char* filename, int lineno, char* description, int value, char* format, ...)
|
||||
{
|
||||
++tests;
|
||||
if (!value)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
++failures;
|
||||
printf("Assertion failed, file %s, line %d, description: %s\n", filename, lineno, description);
|
||||
|
||||
va_start(args, format);
|
||||
vprintf(format, args);
|
||||
va_end(args);
|
||||
|
||||
//cur_output += sprintf(cur_output, "<failure type=\"%s\">file %s, line %d </failure>\n",
|
||||
// description, filename, lineno);
|
||||
}
|
||||
else
|
||||
printf("Assertion succeeded, file %s, line %d, description: %s\n", filename, lineno, description);
|
||||
}
|
||||
|
||||
#define assert(a, b, c, d) myassert(__FILE__, __LINE__, a, b, c, d)
|
||||
#define assert1(a, b, c, d, e) myassert(__FILE__, __LINE__, a, b, c, d, e)
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
thread_return_type cond_secondary(void* n)
|
||||
{
|
||||
int rc = 0;
|
||||
cond_type cond = n;
|
||||
|
||||
printf("This should return immediately as it was posted already\n");
|
||||
rc = Thread_wait_cond(cond, 99999);
|
||||
assert("rc 1 from wait_cond", rc == 1, "rc was %d", rc);
|
||||
|
||||
printf("This should hang around a few seconds\n");
|
||||
rc = Thread_wait_cond(cond, 99999);
|
||||
assert("rc 1 from wait_cond", rc == 1, "rc was %d", rc);
|
||||
|
||||
printf("Secondary cond thread ending\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int cond_test()
|
||||
{
|
||||
int rc = 0;
|
||||
cond_type cond = Thread_create_cond();
|
||||
thread_type thread;
|
||||
|
||||
printf("Post secondary so it should return immediately\n");
|
||||
rc = Thread_signal_cond(cond);
|
||||
assert("rc 0 from signal cond", rc == 0, "rc was %d", rc);
|
||||
|
||||
printf("Starting secondary thread\n");
|
||||
thread = Thread_start(cond_secondary, (void*)cond);
|
||||
|
||||
sleep(3);
|
||||
|
||||
printf("post secondary\n");
|
||||
rc = Thread_signal_cond(cond);
|
||||
assert("rc 1 from signal cond", rc == 1, "rc was %d", rc);
|
||||
|
||||
sleep(3);
|
||||
|
||||
printf("Main thread ending\n");
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
|
||||
thread_return_type sem_secondary(void* n)
|
||||
{
|
||||
int rc = 0;
|
||||
sem_type sem = n;
|
||||
|
||||
printf("Secondary semaphore pointer %p\n", sem);
|
||||
|
||||
rc = Thread_check_sem(sem);
|
||||
assert("rc 1 from check_sem", rc == 1, "rc was %d", rc);
|
||||
|
||||
printf("Secondary thread about to wait\n");
|
||||
rc = Thread_wait_sem(sem, 99999);
|
||||
printf("Secondary thread returned from wait %d\n", rc);
|
||||
|
||||
printf("Secondary thread about to wait\n");
|
||||
rc = Thread_wait_sem(sem, 99999);
|
||||
printf("Secondary thread returned from wait %d\n", rc);
|
||||
printf("Secondary check sem %d\n", Thread_check_sem(sem));
|
||||
|
||||
printf("Secondary thread ending\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int sem_test()
|
||||
{
|
||||
int rc = 0;
|
||||
sem_type sem = Thread_create_sem();
|
||||
thread_type thread;
|
||||
|
||||
printf("Primary semaphore pointer %p\n", sem);
|
||||
|
||||
rc = Thread_check_sem(sem);
|
||||
assert("rc 0 from check_sem", rc == 0, "rc was %d\n", rc);
|
||||
|
||||
printf("post secondary so then check should be 1\n");
|
||||
rc = Thread_post_sem(sem);
|
||||
assert("rc 0 from post_sem", rc == 0, "rc was %d\n", rc);
|
||||
|
||||
rc = Thread_check_sem(sem);
|
||||
assert("rc 1 from check_sem", rc == 1, "rc was %d", rc);
|
||||
|
||||
printf("Starting secondary thread\n");
|
||||
thread = Thread_start(sem_secondary, (void*)sem);
|
||||
|
||||
sleep(3);
|
||||
rc = Thread_check_sem(sem);
|
||||
assert("rc 1 from check_sem", rc == 1, "rc was %d", rc);
|
||||
|
||||
printf("post secondary\n");
|
||||
rc = Thread_post_sem(sem);
|
||||
assert("rc 1 from post_sem", rc == 1, "rc was %d", rc);
|
||||
|
||||
sleep(3);
|
||||
|
||||
printf("Main thread ending\n");
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
sem_test();
|
||||
//cond_test();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,729 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2020 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* 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 TREE_C /* so that malloc/free/realloc aren't redefined by Heap.h */
|
||||
|
||||
#include "Tree.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Heap.h"
|
||||
|
||||
|
||||
int isRed(Node* aNode);
|
||||
int isBlack(Node* aNode);
|
||||
/*int TreeWalk(Node* curnode, int depth);*/
|
||||
/*int TreeMaxDepth(Tree *aTree);*/
|
||||
void TreeRotate(Tree* aTree, Node* curnode, int direction, int index);
|
||||
Node* TreeBAASub(Tree* aTree, Node* curnode, int which, int index);
|
||||
void TreeBalanceAfterAdd(Tree* aTree, Node* curnode, int index);
|
||||
void* TreeAddByIndex(Tree* aTree, void* content, size_t size, int index);
|
||||
Node* TreeFindIndex1(Tree* aTree, void* key, int index, int value);
|
||||
Node* TreeFindContentIndex(Tree* aTree, void* key, int index);
|
||||
Node* TreeMinimum(Node* curnode);
|
||||
Node* TreeSuccessor(Node* curnode);
|
||||
Node* TreeNextElementIndex(Tree* aTree, Node* curnode, int index);
|
||||
Node* TreeBARSub(Tree* aTree, Node* curnode, int which, int index);
|
||||
void TreeBalanceAfterRemove(Tree* aTree, Node* curnode, int index);
|
||||
void* TreeRemoveIndex(Tree* aTree, void* content, int index);
|
||||
|
||||
|
||||
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) || defined(NO_HEAP_TRACKING)
|
||||
Tree* newt = malloc(sizeof(Tree));
|
||||
#else
|
||||
Tree* newt = mymalloc(__FILE__, __LINE__, sizeof(Tree));
|
||||
#endif
|
||||
if (newt)
|
||||
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) || defined(NO_HEAP_TRACKING)
|
||||
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);
|
||||
}
|
||||
|
||||
#if 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
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, size_t 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)
|
||||
goto exit; /* exit(-99); */
|
||||
else
|
||||
{
|
||||
newel = curnode;
|
||||
if (index == 0)
|
||||
aTree->size += (size - curnode->size);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#if defined(UNIT_TESTS) || defined(NO_HEAP_TRACKING)
|
||||
newel = malloc(sizeof(Node));
|
||||
#else
|
||||
newel = (aTree->heap_tracking) ? mymalloc(__FILE__, __LINE__, sizeof(Node)) : malloc(sizeof(Node));
|
||||
#endif
|
||||
if (newel == NULL)
|
||||
goto exit;
|
||||
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;
|
||||
rc = newel->content;
|
||||
TreeBalanceAfterAdd(aTree, newel, index);
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
void* TreeAdd(Tree* aTree, void* content, size_t 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;
|
||||
size_t 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->parent;
|
||||
temp.red = 0;
|
||||
TreeBalanceAfterRemove(aTree, &temp, index);
|
||||
}
|
||||
}
|
||||
else
|
||||
TreeBalanceAfterRemove(aTree, curchild, index);
|
||||
}
|
||||
|
||||
#if defined(UNIT_TESTS) || defined(NO_HEAP_TRACKING)
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,239 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2009, 2018 IBM Corp.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v2.0
|
||||
* and Eclipse Distribution License v1.0 which accompany this distribution.
|
||||
*
|
||||
* The Eclipse Public License is available at
|
||||
* https://www.eclipse.org/legal/epl-2.0/
|
||||
* and the Eclipse Distribution License is available at
|
||||
* http://www.eclipse.org/org/documents/edl-v10.php.
|
||||
*
|
||||
* Contributors:
|
||||
* Ian Craggs - initial API and implementation and/or initial documentation
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @file
|
||||
* \brief Functions for checking that strings contain UTF-8 characters only
|
||||
*
|
||||
* See page 104 of the Unicode Standard 5.0 for the list of well formed
|
||||
* UTF-8 byte sequences.
|
||||
*
|
||||
*/
|
||||
#include "utf-8.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "StackTrace.h"
|
||||
|
||||
/**
|
||||
* Macro to determine the number of elements in a single-dimension array
|
||||
*/
|
||||
#if !defined(ARRAY_SIZE)
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* Structure to hold the valid ranges of UTF-8 characters, for each byte up to 4
|
||||
*/
|
||||
struct
|
||||
{
|
||||
int len; /**< number of elements in the following array (1 to 4) */
|
||||
struct
|
||||
{
|
||||
char lower; /**< lower limit of valid range */
|
||||
char upper; /**< upper limit of valid range */
|
||||
} bytes[4]; /**< up to 4 bytes can be used per character */
|
||||
}
|
||||
valid_ranges[] =
|
||||
{
|
||||
{1, { {00, 0x7F} } },
|
||||
{2, { {0xC2, 0xDF}, {0x80, 0xBF} } },
|
||||
{3, { {0xE0, 0xE0}, {0xA0, 0xBF}, {0x80, 0xBF} } },
|
||||
{3, { {0xE1, 0xEC}, {0x80, 0xBF}, {0x80, 0xBF} } },
|
||||
{3, { {0xED, 0xED}, {0x80, 0x9F}, {0x80, 0xBF} } },
|
||||
{3, { {0xEE, 0xEF}, {0x80, 0xBF}, {0x80, 0xBF} } },
|
||||
{4, { {0xF0, 0xF0}, {0x90, 0xBF}, {0x80, 0xBF}, {0x80, 0xBF} } },
|
||||
{4, { {0xF1, 0xF3}, {0x80, 0xBF}, {0x80, 0xBF}, {0x80, 0xBF} } },
|
||||
{4, { {0xF4, 0xF4}, {0x80, 0x8F}, {0x80, 0xBF}, {0x80, 0xBF} } },
|
||||
};
|
||||
|
||||
|
||||
static const char* UTF8_char_validate(int len, const char* data);
|
||||
|
||||
|
||||
/**
|
||||
* Validate a single UTF-8 character
|
||||
* @param len the length of the string in "data"
|
||||
* @param data the bytes to check for a valid UTF-8 char
|
||||
* @return pointer to the start of the next UTF-8 character in "data"
|
||||
*/
|
||||
static const char* UTF8_char_validate(int len, const char* data)
|
||||
{
|
||||
int good = 0;
|
||||
int charlen = 2;
|
||||
int i, j;
|
||||
const char *rc = NULL;
|
||||
|
||||
if (data == NULL)
|
||||
goto exit; /* don't have data, can't continue */
|
||||
|
||||
/* first work out how many bytes this char is encoded in */
|
||||
if ((data[0] & 128) == 0)
|
||||
charlen = 1;
|
||||
else if ((data[0] & 0xF0) == 0xF0)
|
||||
charlen = 4;
|
||||
else if ((data[0] & 0xE0) == 0xE0)
|
||||
charlen = 3;
|
||||
|
||||
if (charlen > len)
|
||||
goto exit; /* not enough characters in the string we were given */
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(valid_ranges); ++i)
|
||||
{ /* just has to match one of these rows */
|
||||
if (valid_ranges[i].len == charlen)
|
||||
{
|
||||
good = 1;
|
||||
for (j = 0; j < charlen; ++j)
|
||||
{
|
||||
if (data[j] < valid_ranges[i].bytes[j].lower ||
|
||||
data[j] > valid_ranges[i].bytes[j].upper)
|
||||
{
|
||||
good = 0; /* failed the check */
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (good)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (good)
|
||||
rc = data + charlen;
|
||||
exit:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate a length-delimited string has only UTF-8 characters
|
||||
* @param len the length of the string in "data"
|
||||
* @param data the bytes to check for valid UTF-8 characters
|
||||
* @return 1 (true) if the string has only UTF-8 characters, 0 (false) otherwise
|
||||
*/
|
||||
int UTF8_validate(int len, const char* data)
|
||||
{
|
||||
const char* curdata = NULL;
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (len == 0 || data == NULL)
|
||||
{
|
||||
rc = 1;
|
||||
goto exit;
|
||||
}
|
||||
curdata = UTF8_char_validate(len, data);
|
||||
while (curdata && (curdata < data + len))
|
||||
curdata = UTF8_char_validate((int)(data + len - curdata), curdata);
|
||||
|
||||
rc = curdata != NULL;
|
||||
exit:
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate a null-terminated string has only UTF-8 characters
|
||||
* @param string the string to check for valid UTF-8 characters
|
||||
* @return 1 (true) if the string has only UTF-8 characters, 0 (false) otherwise
|
||||
*/
|
||||
int UTF8_validateString(const char* string)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
FUNC_ENTRY;
|
||||
if (string != NULL)
|
||||
{
|
||||
rc = UTF8_validate((int)strlen(string), string);
|
||||
}
|
||||
FUNC_EXIT_RC(rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if defined(UNIT_TESTS)
|
||||
#include <stdio.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int len;
|
||||
char data[20];
|
||||
} tests;
|
||||
|
||||
tests valid_strings[] =
|
||||
{
|
||||
{3, "hjk" },
|
||||
{7, {0x41, 0xE2, 0x89, 0xA2, 0xCE, 0x91, 0x2E} },
|
||||
{3, {'f', 0xC9, 0xB1 } },
|
||||
{9, {0xED, 0x95, 0x9C, 0xEA, 0xB5, 0xAD, 0xEC, 0x96, 0xB4} },
|
||||
{9, {0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC, 0xE8, 0xAA, 0x9E} },
|
||||
{4, {0x2F, 0x2E, 0x2E, 0x2F} },
|
||||
{7, {0xEF, 0xBB, 0xBF, 0xF0, 0xA3, 0x8E, 0xB4} },
|
||||
};
|
||||
|
||||
tests invalid_strings[] =
|
||||
{
|
||||
{2, {0xC0, 0x80} },
|
||||
{5, {0x2F, 0xC0, 0xAE, 0x2E, 0x2F} },
|
||||
{6, {0xED, 0xA1, 0x8C, 0xED, 0xBE, 0xB4} },
|
||||
{1, {0xF4} },
|
||||
};
|
||||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
int i, failed = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(valid_strings); ++i)
|
||||
{
|
||||
if (!UTF8_validate(valid_strings[i].len, valid_strings[i].data))
|
||||
{
|
||||
printf("valid test %d failed\n", i);
|
||||
failed = 1;
|
||||
}
|
||||
else
|
||||
printf("valid test %d passed\n", i);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(invalid_strings); ++i)
|
||||
{
|
||||
if (UTF8_validate(invalid_strings[i].len, invalid_strings[i].data))
|
||||
{
|
||||
printf("invalid test %d failed\n", i);
|
||||
failed = 1;
|
||||
}
|
||||
else
|
||||
printf("invalid test %d passed\n", i);
|
||||
}
|
||||
|
||||
if (failed)
|
||||
printf("Failed\n");
|
||||
else
|
||||
printf("Passed\n");
|
||||
|
||||
//Don't crash on null data
|
||||
UTF8_validateString(NULL);
|
||||
UTF8_validate(1, NULL);
|
||||
UTF8_char_validate(1, NULL);
|
||||
|
||||
return 0;
|
||||
} /* End of main function*/
|
||||
|
||||
#endif
|
||||
|
||||
Loading…
Reference in New Issue