diff --git a/Package.swift b/Package.swift index 6443e5a..deee9ed 100644 --- a/Package.swift +++ b/Package.swift @@ -16,19 +16,50 @@ import PackageDescription +#if os(Linux) + let excludePortDir = "Sources/agentcore/ibmras/common/port/osx" +#else + let excludePortDir = "Sources/agentcore/ibmras/common/port/linux" +#endif + let package = Package( name: "SwiftMetrics", targets: [ + Target(name: "SwiftMetrics", dependencies: [.Target(name: "agentcore"), + .Target(name: "cpuplugin"), + .Target(name: "envplugin"), + .Target(name: "memplugin"), + .Target(name: "hcapiplugin")]), Target(name: "SwiftMetricsKitura", dependencies: ["SwiftMetrics"]), Target(name: "SwiftMetricsBluemix", dependencies: ["SwiftMetricsKitura"]), - Target(name: "SwiftMetricsDash", dependencies: ["SwiftMetricsBluemix"]) - ], + Target(name: "SwiftMetricsDash", dependencies: ["SwiftMetricsBluemix"]), + Target(name: "mqttplugin", dependencies: [.Target(name: "paho"), + .Target(name: "agentcore")]), + Target(name: "cpuplugin", dependencies: [.Target(name: "agentcore")]), + Target(name: "envplugin", dependencies: [.Target(name: "agentcore")]), + Target(name: "memplugin", dependencies: [.Target(name: "agentcore")]), + Target(name: "hcapiplugin", dependencies: [.Target(name: "agentcore")]) + ], dependencies: [ - .Package(url: "https://github.com/RuntimeTools/omr-agentcore.git", majorVersion: 3), .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 6), .Package(url: "https://github.com/IBM-Swift/Kitura-WebSocket.git", majorVersion: 0, minor: 7), .Package(url: "https://github.com/IBM-Swift/Kitura-Request.git", majorVersion: 0, minor: 7), .Package(url: "https://github.com/IBM-Swift/CloudConfiguration.git", majorVersion: 1) - ] + ], + exclude: [ "Sources/agentcore/ibmras/common/port/aix", + "Sources/agentcore/ibmras/common/port/windows", + "Sources/agentcore/ibmras/common/data", + "Sources/agentcore/ibmras/common/util/memUtils.cpp", + "Sources/ostreamplugin", + "Sources/paho/Windows Build", + "Sources/paho/build", + "Sources/paho/doc", + "Sources/paho/test", + "Sources/paho/src/MQTTClient.c", + "Sources/paho/src/MQTTVersion.c", + "Sources/paho/src/SSLSocket.c", + "Sources/paho/src/samples", + excludePortDir + ] ) diff --git a/Package@swift-3.1.swift b/Package@swift-3.1.swift new file mode 100644 index 0000000..3d64fbd --- /dev/null +++ b/Package@swift-3.1.swift @@ -0,0 +1,65 @@ +/** +* Copyright IBM Corporation 2017 +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +import PackageDescription + +#if os(Linux) + let excludePortDir = "Sources/agentcore/ibmras/common/port/osx" +#else + let excludePortDir = "Sources/agentcore/ibmras/common/port/linux" +#endif + +let package = Package( + name: "SwiftMetrics", + targets: [ + Target(name: "SwiftMetrics", dependencies: [.Target(name: "agentcore"), + .Target(name: "cpuplugin"), + .Target(name: "envplugin"), + .Target(name: "memplugin"), + .Target(name: "hcapiplugin")]), + Target(name: "SwiftMetricsKitura", dependencies: ["SwiftMetrics"]), + Target(name: "SwiftMetricsBluemix", dependencies: ["SwiftMetricsKitura"]), + Target(name: "SwiftMetricsDash", dependencies: ["SwiftMetricsBluemix"]), + Target(name: "mqttplugin", dependencies: [.Target(name: "paho"), + .Target(name: "agentcore")]), + Target(name: "cpuplugin", dependencies: [.Target(name: "agentcore")]), + Target(name: "envplugin", dependencies: [.Target(name: "agentcore")]), + Target(name: "memplugin", dependencies: [.Target(name: "agentcore")]), + Target(name: "hcapiplugin", dependencies: [.Target(name: "agentcore")]) + ], + dependencies: [ + .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 7), + .Package(url: "https://github.com/IBM-Swift/Kitura-WebSocket.git", majorVersion: 0, minor: 8), + .Package(url: "https://github.com/IBM-Swift/Kitura-Request.git", majorVersion: 0, minor: 8), + .Package(url: "https://github.com/IBM-Swift/CloudConfiguration.git", majorVersion: 2) + ], + exclude: [ "Sources/agentcore/ibmras/common/port/aix", + "Sources/agentcore/ibmras/common/port/windows", + "Sources/agentcore/ibmras/common/data", + "Sources/agentcore/ibmras/common/util/memUtils.cpp", + "Sources/ostreamplugin", + "Sources/paho/Windows Build", + "Sources/paho/build", + "Sources/paho/doc", + "Sources/paho/test", + "Sources/paho/src/MQTTClient.c", + "Sources/paho/src/MQTTVersion.c", + "Sources/paho/src/SSLSocket.c", + "Sources/paho/src/samples", + excludePortDir + ] +) + diff --git a/Sources/SwiftMetrics/SwiftMetrics.swift b/Sources/SwiftMetrics/SwiftMetrics.swift index 320ff4b..9c9fb94 100644 --- a/Sources/SwiftMetrics/SwiftMetrics.swift +++ b/Sources/SwiftMetrics/SwiftMetrics.swift @@ -88,8 +88,7 @@ open class SwiftMetrics { var latencyEnabled: Bool = true let jobsQueue = DispatchQueue(label: "Swift Metrics Jobs Queue") - public init() throws{ - + public init() throws { self.loaderApi = loader_entrypoint().pointee try self.loadProperties() loaderApi.setLogLevels() @@ -144,7 +143,7 @@ open class SwiftMetrics { let fm = FileManager.default var propertiesPath = "" let currentDir = fm.currentDirectoryPath - var dirContents = try fm.contentsOfDirectory(atPath: currentDir) + let dirContents = try fm.contentsOfDirectory(atPath: currentDir) for dir in dirContents { if dir.contains("swiftmetrics.properties") { @@ -170,16 +169,33 @@ open class SwiftMetrics { } else { packagesPath = workingPath.substring(to: i!.lowerBound) } - packagesPath.append("Packages") - _ = fm.changeCurrentDirectoryPath(packagesPath) - ///omr-agentcore has a version number in it, so search for it - dirContents = try fm.contentsOfDirectory(atPath: fm.currentDirectoryPath) - for dir in dirContents { - if dir.contains("SwiftMetrics") { + // Swift 3.1 + let checkoutsPath = packagesPath + ".build/checkouts/" + if fm.fileExists(atPath: checkoutsPath) { + _ = fm.changeCurrentDirectoryPath(checkoutsPath) + + } else { // Swift 3.0 + packagesPath.append("Packages/"); + if fm.fileExists(atPath: packagesPath) { + _ = fm.changeCurrentDirectoryPath(packagesPath) + } else { + print("SwiftMetrics: Error finding install directory") + } + } + + do { + let dirContents = try fm.contentsOfDirectory(atPath: fm.currentDirectoryPath) + for dir in dirContents { + if dir.contains("SwiftMetrics") { ///that's where we want to be! _ = fm.changeCurrentDirectoryPath(dir) + } } + } catch { + print("SwiftMetrics: Error searching directory: \(packagesPath), \(error).") + throw error } + propertiesPath = "\(fm.currentDirectoryPath)/swiftmetrics.properties" _ = fm.changeCurrentDirectoryPath(currentDir) diff --git a/Sources/SwiftMetricsDash/SwiftMetricsDash.swift b/Sources/SwiftMetricsDash/SwiftMetricsDash.swift index 5eec38c..2ba755b 100644 --- a/Sources/SwiftMetricsDash/SwiftMetricsDash.swift +++ b/Sources/SwiftMetricsDash/SwiftMetricsDash.swift @@ -81,13 +81,29 @@ public class SwiftMetricsDash { } else { packagesPath = workingPath.substring(to: i!.lowerBound) } - packagesPath.append("Packages/") - let dirContents = try fm.contentsOfDirectory(atPath: packagesPath) - for dir in dirContents { + + // Swift 3.1 + let checkoutsPath = packagesPath + ".build/checkouts/" + if fm.fileExists(atPath: checkoutsPath) { + packagesPath = checkoutsPath; + } else if fm.fileExists(atPath: packagesPath + "Packages/") { // Swift 3.0 + packagesPath.append("Packages/"); + } else { + print("SwiftMetricsDash: error finding install directory") + } + + do { + let dirContents = try fm.contentsOfDirectory(atPath: packagesPath) + for dir in dirContents { if dir.contains("SwiftMetrics") { packagesPath.append("\(dir)/public") } + } + } catch { + print("SwiftMetricsDash: Error opening directory: \(packagesPath), \(error).") + throw error } + router.all("/swiftmetrics-dash", middleware: StaticFileServer(path: packagesPath)) if createServer { diff --git a/Sources/agentcore/ibmras/common/LogManager.cpp b/Sources/agentcore/ibmras/common/LogManager.cpp new file mode 100644 index 0000000..9c01b31 --- /dev/null +++ b/Sources/agentcore/ibmras/common/LogManager.cpp @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#include +#include +#include +#include + +#include "LogManager.h" + + +#if defined(_WINDOWS) +#define VPRINT vsprintf_s +#else +#define VPRINT vsprintf +#endif + +extern "C" { + +DECL void* ibmras_common_LogManager_getLogger(const char* name) { + return (void*) ibmras::common::LogManager::getLogger(name); +} +} + +namespace ibmras { +namespace common { + +LogManager* LogManager::instance = NULL; + +LOCAL_LOGGER_CALLBACK LogManager::localLogFunc = NULL; + +LogManager::LogManager() : + level(info), localLog(true), lock(NULL) { + /* do not create a lock in the constructor as it will create a loop with the logging in the port library */ +} + + +void LogManager::processMsg(const std::string &msg) { + + if (localLog) { + /* local logging is overriding */ + if (localLogFunc) { + localLogFunc(msg); + } else { + std::cerr << msg << '\n'; + std::cerr.flush(); + } + return; + } + +} + +void LogManager::msgHandler(const std::string &message, loggingLevel level, + Logger* logger) { + + /* logger level has priority over log manager level (which should be considered a default level */ + if ((logger->level >= level) || (instance->level >= level)) { + instance->processMsg(message); + } +} + +void LogManager::setLevel(loggingLevel newlevel) { + LogManager::level = newlevel; + for (std::vector::iterator i = loggers.begin(); i != loggers.end(); + ++i) { + if ((*i)->level <= level) { + (*i)->level = level; + } + } +} + +void LogManager::setLevel(const std::string &name, loggingLevel newlevel) { + if (name.compare("level") == 0) { + setLevel(newlevel); + } else { + Logger* logger = getLogger(name); + if (level > newlevel) { + logger->level = level; + } else { + logger->level = newlevel; + } + logger->debugLevel = newlevel; + } +} + +LogManager* LogManager::getInstance() { + if (!instance) { + instance = new LogManager; + instance->lock = new ibmras::common::port::Lock; + } + return instance; +} + +Logger* LogManager::getLogger(const std::string &name) { + LogManager* instance = getInstance(); + Logger* logger = instance->findLogger(name); + if (!logger) { /* logger not found so need to create a new instance and return that */ + logger = new Logger(name, LogManager::msgHandler); + instance->loggers.push_back(logger); + } + + return logger; +} + +void LogManager::setLevel(const std::string& name, const std::string& value) { + loggingLevel lev = none; + if (value.compare("warning") == 0) { + lev = warning; + } else if (value.compare("info") == 0) { + lev = info; + } else if (value.compare("fine") == 0) { + lev = fine; + } else if (value.compare("finest") == 0) { + lev = finest; + } else if (value.compare("debug") == 0) { + lev = debug; + } else { + lev = none; + } + setLevel(name, lev); +} + +Logger* LogManager::findLogger(const std::string &name) { + + for (std::vector::iterator i = loggers.begin(); i != loggers.end(); + ++i) { + if ((*i)->component == name) { + return (*i); + } + } + return NULL; /* no match found */ +} + + +} +} diff --git a/Sources/agentcore/ibmras/common/LogManager.h b/Sources/agentcore/ibmras/common/LogManager.h new file mode 100644 index 0000000..05b49fd --- /dev/null +++ b/Sources/agentcore/ibmras/common/LogManager.h @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_logmanager_h +#define ibmras_common_logmanager_h + +#include +#include +#include "AgentExtensions.h" +#include "../monitoring/Typesdef.h" +#include "Logger.h" +#include "port/Lock.h" + +namespace ibmras { + namespace common { + + typedef void* (*LOCAL_LOGGER_CALLBACK)(const std::string &msg); /* shortcut definition for the local log callback */ + + /* + * Common logging functions + */ + class DECL LogManager { + public: + static LogManager* getInstance(); + + loggingLevel level; /* will default to 0 which is 'none' in the log level enum */ + bool localLog; /* setting this to true will push all output to local stderr */ + static LOCAL_LOGGER_CALLBACK localLogFunc; /* optional function to invoke for local callbacks */ + static void msgHandler(const std::string &msg, loggingLevel level, Logger* logger); /* common message processing */ + + static Logger* getLogger(const std::string &name); /* return instance of the logger */ + + void setLevel(loggingLevel level); /* set the log level for all components */ + void setLevel(const std::string &name, const std::string &level); + void setLevel(const std::string &name, loggingLevel level); /* set the log level for a named component logger */ + protected: + LogManager(); + + private: + static LogManager* instance; + std::vector loggers; + ibmras::common::port::Lock* lock; /* lock to prevent spills whilst publishing/sending */ + + void processMsg(const std::string& msg); /* common message processing */ + + Logger* findLogger(const std::string &name); /* find a named logger */ + }; + } +} +#endif /* ibmras_common_logmanager_h */ diff --git a/Sources/agentcore/ibmras/common/Logger.cpp b/Sources/agentcore/ibmras/common/Logger.cpp new file mode 100644 index 0000000..269ea8c --- /dev/null +++ b/Sources/agentcore/ibmras/common/Logger.cpp @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#if defined (_ZOS) +#define _ISOC99_SOURCE +#endif +#include +#include +#include +#include +#include + +#include + +#include "Logger.h" + +#if defined(_WINDOWS) +#define VPRINT vsnprintf_s +#else +#define VPRINT vsnprintf +#endif +#if defined(_ZOS) +#include +#endif + +namespace ibmras { +namespace common { + +Logger::Logger(const std::string &name, MSG_HANDLER h) : + level(none), debugLevel(none), component(name), handler(h) { +} + +Logger::~Logger() { +} + +void Logger::header(std::stringstream &str, loggingLevel lev, bool dbg) { + std::time_t time = std::time(NULL); + char buffer[100]; + + if (std::strftime(buffer, sizeof(buffer), "%c", std::localtime(&time))) { + str << '[' << buffer << ']'; + } + str << " com.ibm.diagnostics.healthcenter." << component; + + if (dbg) { + str << ".debug"; + } + + switch (lev) { + case info: + str << " INFO: "; + break; + case warning: + str << " WARNING: "; + break; + case fine: + str << " FINE: "; + break; + case finest: + str << " FINEST: "; + break; + case debug: + str << " DEBUG: "; + break; + default: + str << " "; + break; + } +} + +void Logger::log(loggingLevel lev, const char* format, ...) { + std::stringstream str; + header(str, lev); + va_list messages; + va_start(messages, format); + char buffer[1024]; + int result = VPRINT(buffer, 1024, format, messages); + va_end(messages); + if (result >= 0) { + str << buffer; + } else { + str << "(warning) failed to write replacements for :" << format; + } + std::string msg = str.str(); + handler(msg.c_str(), lev, this); + +} + +void Logger::logDebug(loggingLevel lev, const char* format, ...) { + std::stringstream str; + header(str, lev, true); + va_list messages; + va_start(messages, format); + char buffer[1024]; + int result = VPRINT(buffer, 1024, format, messages); + va_end(messages); + if (result >= 0) { + str << buffer; + } else { + str << "(warning) failed to write replacements for :" << format; + } + std::string msg = str.str(); + handler(msg.c_str(), lev, this); +} + +} + +} diff --git a/Sources/agentcore/ibmras/common/Logger.h b/Sources/agentcore/ibmras/common/Logger.h new file mode 100644 index 0000000..ffb0bb5 --- /dev/null +++ b/Sources/agentcore/ibmras/common/Logger.h @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_logger_h +#define ibmras_common_logger_h + +#include "AgentExtensions.h" +#include "../monitoring/Typesdef.h" + +#include + +namespace ibmras { +namespace common { + +class DECL Logger; + +typedef void (*MSG_HANDLER)(const std::string &msg, loggingLevel Level, Logger* logger); /* common message processing */ + +class DECL Logger { +public: + Logger(const std::string &name, MSG_HANDLER h); + virtual ~Logger(); + + void log(loggingLevel lev, const char* format, ...); /* variable number of parameters should be string messages */ + void logDebug(loggingLevel lev, const char* format, ...); /* variable number of parameters should be string messages */ + + loggingLevel level; /* level that the logger is operating at */ + loggingLevel debugLevel; /* level that the logger is operating at */ + std::string component; + +private: + MSG_HANDLER handler; + + void header(std::stringstream &str, loggingLevel lev, bool debug=false); +}; + +} /* namespace common */ +} /* namespace ibmras */ +#endif /* ibmras_common_logger_h */ diff --git a/Sources/agentcore/ibmras/common/MemoryManager.cpp b/Sources/agentcore/ibmras/common/MemoryManager.cpp new file mode 100644 index 0000000..38e559d --- /dev/null +++ b/Sources/agentcore/ibmras/common/MemoryManager.cpp @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#include "MemoryManager.h" +#include "logging.h" +#include + +namespace ibmras { +namespace common { + +IBMRAS_DEFINE_LOGGER("memory"); + +MemoryManager::MemoryManager() { +} + + +MemoryManager::~MemoryManager() { +} + +unsigned char* MemoryManager::allocate(uint32 size) { + unsigned char* memory = new (std::nothrow) unsigned char[size]; + if (memory == 0) { + IBMRAS_LOG_1(warning, "Failed to allocate memory of size %d", size); + return NULL; + } + + memset(memory, 0, size); + + IBMRAS_DEBUG_2(debug, "Allocated %d at %p", size, (void*)memory); + + return memory; +} + +void MemoryManager::deallocate(unsigned char** memoryPtr) { + + IBMRAS_DEBUG_1(debug, "Deallocate called for %p", (void*)memoryPtr); + if (memoryPtr != NULL && *memoryPtr != NULL) { + IBMRAS_DEBUG_1(debug, "Deallocating memory at %p", (void*)*memoryPtr); + delete[] *memoryPtr; + *memoryPtr = NULL; + } +} + +namespace memory { + +static MemoryManager* defaultMemoryManager = NULL; + +MemoryManager* getDefaultMemoryManager() { + if (defaultMemoryManager == NULL) { + defaultMemoryManager = new MemoryManager(); + } + return defaultMemoryManager; +} + +bool setDefaultMemoryManager(MemoryManager* manager) { + if (defaultMemoryManager == NULL && manager != NULL) { + defaultMemoryManager = manager; + return true; + } + return false; +} + +unsigned char* allocate(uint32 size) { + return getDefaultMemoryManager()->allocate(size); +} + +void deallocate(unsigned char** memoryPtr) { + getDefaultMemoryManager()->deallocate(memoryPtr); +} + +} /* namespace memory */ + + +} /* namespace common */ +} /* namespace ibmras */ diff --git a/Sources/agentcore/ibmras/common/MemoryManager.h b/Sources/agentcore/ibmras/common/MemoryManager.h new file mode 100644 index 0000000..c633cbf --- /dev/null +++ b/Sources/agentcore/ibmras/common/MemoryManager.h @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#ifndef ibmras_common_memorymanager_h +#define ibmras_common_memorymanager_h + +/* + * Memory management functionality + */ + +#include "../monitoring/Typesdef.h" +#include "AgentExtensions.h" + +namespace ibmras { +namespace common { + +class MemoryManager { + +public: + + MemoryManager(); + virtual ~MemoryManager(); + + virtual unsigned char* allocate(uint32 size); + virtual void deallocate(unsigned char**); + +protected: +private: + +}; + +namespace memory { + +MemoryManager* getDefaultMemoryManager(); +bool setDefaultMemoryManager(MemoryManager* manager); + +DECL unsigned char* allocate(uint32 size); +DECL void deallocate(unsigned char**); + +} + +} +} + +#endif /* ibmras_common_memorymanager_h */ diff --git a/Sources/agentcore/ibmras/common/Properties.cpp b/Sources/agentcore/ibmras/common/Properties.cpp new file mode 100644 index 0000000..009a8e3 --- /dev/null +++ b/Sources/agentcore/ibmras/common/Properties.cpp @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#include "Properties.h" +#include +#include +#include "util/strUtils.h" + + +namespace ibmras { +namespace common { + +std::string Properties::get(const std::string &key, + const std::string &defaultValue) { + std::map::iterator propsiter; + + propsiter = props.find(key); + if (propsiter == props.end()) { + return defaultValue; + } else { + return propsiter->second; + } +} + +void Properties::put(const std::string &key, const std::string &value) { + props[key] = value; +} + +void Properties::add(const Properties &p) { + for (std::map::const_iterator propsiter = p.props.begin(); + propsiter != p.props.end(); ++propsiter) { + put(propsiter->first, propsiter->second); + } +} + +bool Properties::exists(const std::string& key) { + if (props.find(key) == props.end()) { + return false; + } + return true; +} + +std::list Properties::getKeys(const std::string& prefix) { + + std::list keys; + + for (std::map::iterator propsiter = props.begin(); + propsiter != props.end(); ++propsiter) { + if (propsiter->first.compare(0, prefix.length(), prefix) == 0) { + keys.push_back(propsiter->first); + } + } + + return keys; +} + +void Properties::add(const std::string& propString) { + std::vector stringProps = ibmras::common::util::split(propString, '\n'); + for (std::vector::iterator it = stringProps.begin(); it != stringProps.end(); ++it ) { + std::vector propPair = ibmras::common::util::split((*it), '='); + if (propPair.size() == 2) { + put(propPair[0], propPair[1]); + } + } + +} + +std::string Properties::toString() { + std::stringstream ss; + for (std::map::iterator propsiter = props.begin(); + propsiter != props.end(); ++propsiter) { + ss << propsiter->first << "=" << propsiter->second << '\n'; + } + return ss.str(); +} + +} +} + diff --git a/Sources/agentcore/ibmras/common/Properties.h b/Sources/agentcore/ibmras/common/Properties.h new file mode 100644 index 0000000..129cd22 --- /dev/null +++ b/Sources/agentcore/ibmras/common/Properties.h @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_properties_h +#define ibmras_common_properties_h +#include "AgentExtensions.h" +#include +#include +#include +#include +#include + +namespace ibmras { +namespace common { + +class DECL Properties { +public: + virtual std::string get(const std::string &key, const std::string &defaultValue = ""); + virtual bool exists(const std::string &key); + virtual void put(const std::string &key, const std::string &value); + virtual void add(const Properties &p); + virtual void add(const std::string &propString); + virtual std::list getKeys(const std::string& prefix = ""); + virtual std::string toString(); + + virtual ~Properties() {} +protected: + std::map props; + +}; +/* end class Properties */ +} +} /* end namespace RASCommon */ + +#endif /* ibmras_common_properties_h */ diff --git a/Sources/agentcore/ibmras/common/PropertiesFile.cpp b/Sources/agentcore/ibmras/common/PropertiesFile.cpp new file mode 100644 index 0000000..eddd47a --- /dev/null +++ b/Sources/agentcore/ibmras/common/PropertiesFile.cpp @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#include "PropertiesFile.h" + +#include +#include +#include +#include + +namespace ibmras { +namespace common { + +bool IsWhitespace(char x) { return std::isspace(x); } + +int PropertiesFile::load(const std::string &inputFile) { + std::ifstream in_file(inputFile.c_str()); + if (in_file.fail()) { + return -1; + } + std::string line; + while(std::getline(in_file, line)) { + if (line.find('#') == 0) { + continue; + } + // trim line ending + if (line.length() > 0 && line.at(line.length() - 1) == '\r') { + line.erase(line.length() - 1); + } + // erase whitespace + line.erase(std::remove_if(line.begin(), + line.end(), + IsWhitespace), + line.end()); + size_t pos = line.find('='); + if ((pos != std::string::npos) && (pos < line.size())) { + put(line.substr(0, pos), line.substr(pos + 1)); + } + } + + return 0; +} + + +} +} /* end namespace monitoring */ + + diff --git a/Sources/agentcore/ibmras/common/PropertiesFile.h b/Sources/agentcore/ibmras/common/PropertiesFile.h new file mode 100644 index 0000000..d13d431 --- /dev/null +++ b/Sources/agentcore/ibmras/common/PropertiesFile.h @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_propertiesfile_h +#define ibmras_common_propertiesfile_h + +#include +#include +#include "Properties.h" + +namespace ibmras { +namespace common { + +class DECL PropertiesFile: public Properties { +public: + int load(const std::string &inputFile); + +protected: + +}; +/* end class PropertiesFile */ +} +} /* end namespace RASCommon */ + +#endif /* ibmras_common_propertiesfile_h */ diff --git a/Sources/agentcore/ibmras/common/common.h b/Sources/agentcore/ibmras/common/common.h new file mode 100644 index 0000000..1042a1f --- /dev/null +++ b/Sources/agentcore/ibmras/common/common.h @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_common_h +#define ibmras_common_common_h + +#include +namespace ibmras { +namespace common { + +template +std::string itoa(T t) { + std::stringstream s; + s << t; + return s.str(); +} + +} +} + + +#endif /* ibmras_common_common_h */ diff --git a/Sources/agentcore/ibmras/common/data/json/JSON.cpp b/Sources/agentcore/ibmras/common/data/json/JSON.cpp new file mode 100644 index 0000000..b9ecc8a --- /dev/null +++ b/Sources/agentcore/ibmras/common/data/json/JSON.cpp @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "JSON.h" + + +JSONStats::JSONStats(uint32 max) { + stats = new JSONStat*[max]; + data = NULL; + json = NULL; + count = 0; + this->max = max; +} + +const char* JSONStats::JSON() { + JSONStat* stat = stats[0]; + json = new std::string; + json->append("{ \n\"count\" : "); + json->append(ibmras::common::itoa(count++)); + json->append(",\n"); + for(uint32 i = 0, j = max - 1; i < max; stat++, i++, j--) { + json->append("\""); + json->append(stat->getName()); + json->append("\" : "); + if(!i) json->append("\""); + char* value = stat->getValue(); + if(value) { + json->append(value); + } else { + json->append("0"); /* no data available for that stat */ + } + if(!i) json->append("\""); + if(j) { + json->append(",\n"); + } else { + json->append("\n"); + } + } + json->append(" }\n"); + char* result = new char[json->length() + 1]; + *(result + json->length()) = 0; /* null terminate the string */ + json->copy(result, json->length()); + delete json; + json = NULL; + return result; +} + +JSONStats::~JSONStats() { + if(data) { + delete[] data; + } + if(json) { + delete json; + } +} + +const char* JSONStat::getName() { + return name; +} + +char* JSONStat::getValue() { + return value; +} diff --git a/Sources/agentcore/ibmras/common/data/json/JSON.h b/Sources/agentcore/ibmras/common/data/json/JSON.h new file mode 100644 index 0000000..76a7583 --- /dev/null +++ b/Sources/agentcore/ibmras/common/data/json/JSON.h @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#ifndef ibmras_common_data_json_json_h +#define ibmras_common_data_json_json_h + +#include "../../port/ThreadData.h" +#include "../../common.h" + +/* + * Header file for working with JSON data builder + */ + +/* + * A stat used for JSON formatting of the data. It consists of a string representation + * of a name value pair. + * + * This code has not yet been tested + */ + +class JSONStat { +public: + JSONStat(const char* name) { this->name = name; value = NULL; } + void setValue(char* value) { this->value = value;} + void setValue(double value) { this->value = ibmras::common::itoa(value); }; + const char* getName(); + char* getValue(); +private: + const char* name; + char* value; +}; + +/* + * The container for one or more data statistics + */ + +class JSONStats { +public: + JSONStats(uint32 max); + const char* JSON(); + ~JSONStats(); +protected: + JSONStat** stats; + std::string* data; + std::string* json; + uint32 count; + uint32 max; +}; + + +#endif /* ibmras_common_data_json_json_h */ diff --git a/Sources/agentcore/ibmras/common/logging.h b/Sources/agentcore/ibmras/common/logging.h new file mode 100644 index 0000000..5dab0ee --- /dev/null +++ b/Sources/agentcore/ibmras/common/logging.h @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_logging_h +#define ibmras_common_logging_h + +#include "LogManager.h" +extern "C" { + void* ibmras_common_LogManager_getLogger(const char* name); +} + +#define IBMRAS_DECLARE_LOGGER ibmras::common::Logger* logger; +#define IBMRAS_ASSIGN_LOGGER(name) {logger = (ibmras::common::Logger*)ibmras_common_LogManager_getLogger( name );} +#define IBMRAS_DEFINE_LOGGER(name) ibmras::common::Logger* logger = (ibmras::common::Logger*)ibmras_common_LogManager_getLogger( name ); + +/* Define logging macros */ +#define IBMRAS_LOG(LOGLEVEL, MSG) {if (logger->level >= LOGLEVEL) { logger->log(LOGLEVEL, MSG);}} +#define IBMRAS_LOG_1(LOGLEVEL, MSG, INSERT1) {if (logger->level >= LOGLEVEL) { logger->log(LOGLEVEL, MSG, INSERT1);}} +#define IBMRAS_LOG_2(LOGLEVEL, MSG, INSERT1, INSERT2) {if (logger->level >= LOGLEVEL) { logger->log(LOGLEVEL, MSG, INSERT1, INSERT2);}} +#define IBMRAS_LOG_3(LOGLEVEL, MSG, INSERT1, INSERT2, INSERT3) {if (logger->level >= LOGLEVEL) { logger->log(LOGLEVEL, MSG, INSERT1, INSERT2, INSERT3);}} +#define IBMRAS_LOG_4(LOGLEVEL, MSG, INSERT1, INSERT2, INSERT3, INSERT4) {if (logger->level >= LOGLEVEL) { logger->log(LOGLEVEL, MSG, INSERT1, INSERT2, INSERT3, INSERT4);}} + +/* Define debug logging macros */ +#if defined(IBMRAS_DEBUG_LOGGING) +#define IBMRAS_DEBUG(LOGLEVEL, MSG) {if (logger->debugLevel >= LOGLEVEL) { logger->logDebug(LOGLEVEL, MSG);}} +#define IBMRAS_DEBUG_1(LOGLEVEL, MSG, INSERT1) {if (logger->debugLevel >= LOGLEVEL) { logger->logDebug(LOGLEVEL, MSG, INSERT1);}} +#define IBMRAS_DEBUG_2(LOGLEVEL, MSG, INSERT1, INSERT2) {if (logger->debugLevel >= LOGLEVEL) { logger->logDebug(LOGLEVEL, MSG, INSERT1, INSERT2);}} +#define IBMRAS_DEBUG_3(LOGLEVEL, MSG, INSERT1, INSERT2, INSERT3) {if (logger->debugLevel >= LOGLEVEL) { logger->logDebug(LOGLEVEL, MSG, INSERT1, INSERT2, INSERT3);}} +#define IBMRAS_DEBUG_4(LOGLEVEL, MSG, INSERT1, INSERT2, INSERT3, INSERT4) {if (logger->debugLevel >= LOGLEVEL) { logger->logDebug(LOGLEVEL, MSG, INSERT1, INSERT2, INSERT3, INSERT4);}} +#else +#define IBMRAS_DEBUG(LOGLEVEL, MSG) +#define IBMRAS_DEBUG_1(LOGLEVEL, MSG, INSERT1) +#define IBMRAS_DEBUG_2(LOGLEVEL, MSG, INSERT1, INSERT2) +#define IBMRAS_DEBUG_3(LOGLEVEL, MSG, INSERT1, INSERT2, INSERT3) +#define IBMRAS_DEBUG_4(LOGLEVEL, MSG, INSERT1, INSERT2, INSERT3, INSERT4) + +#endif /* IBMRAS_DEBUG_LOGGING */ + +#endif /* ibmras_common_logging_h */ diff --git a/Sources/agentcore/ibmras/common/port/Lock.cpp b/Sources/agentcore/ibmras/common/port/Lock.cpp new file mode 100644 index 0000000..2100b64 --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/Lock.cpp @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#if defined(_ZOS) +#define _OPEN_THREADS +#endif + +#include "Lock.h" +#include "../logging.h" + +#if defined(WINDOWS) +#include +#include +#else +#include "pthread.h" +#endif + +namespace ibmras{ +namespace common{ +namespace port { + +namespace locking { +IBMRAS_DEFINE_LOGGER("locking") +} +using namespace locking; + +Lock::Lock() { +#if defined(WINDOWS) + lock = new CRITICAL_SECTION; /* create a new lock fpr this class */ + CRITICAL_SECTION* c = reinterpret_cast(reinterpret_cast(lock)); + InitializeCriticalSection(c); +#else + lock = new pthread_mutex_t; /* create a new lock fpr this class */ + pthread_mutex_t* mutex = reinterpret_cast(lock); + pthread_mutex_init(mutex, NULL); +#endif +} + +/* acquire a pthread mutex */ +int Lock::acquire() { + if(lock) { +#if defined(WINDOWS) + CRITICAL_SECTION* c = reinterpret_cast(reinterpret_cast(lock)); + EnterCriticalSection(c); + return 0; +#else + return pthread_mutex_lock(reinterpret_cast(lock)); +#endif + + } else { + IBMRAS_DEBUG(warning, "Attempted to acquire a previously failed lock"); + return LOCK_FAIL; + } +} + +/* release the mutex */ +int Lock::release() { + if(lock) { +#if defined(WINDOWS) + CRITICAL_SECTION* c = reinterpret_cast(reinterpret_cast(lock)); + LeaveCriticalSection(c); + return 0; +#else + return pthread_mutex_unlock(reinterpret_cast(lock)); +#endif + } else { + IBMRAS_DEBUG(warning, "Attempted to release a previously failed lock"); + return LOCK_FAIL; + } +} + +void Lock::destroy() { + if(lock) { +#if defined(WINDOWS) + CRITICAL_SECTION* c = reinterpret_cast(reinterpret_cast(lock)); + DeleteCriticalSection(c); +#else + pthread_mutex_destroy(reinterpret_cast(lock)); +#endif + lock = NULL; + } +} + +bool Lock::isDestroyed() { + return lock == NULL; +} + +Lock::~Lock() { + destroy(); +} + +} +} +} + diff --git a/Sources/agentcore/ibmras/common/port/Lock.h b/Sources/agentcore/ibmras/common/port/Lock.h new file mode 100644 index 0000000..f90f31f --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/Lock.h @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_port_lock_h +#define ibmras_common_port_lock_h + +#include +#include +#include "AgentExtensions.h" + +#ifndef NULL +#define NULL 0 +#endif + +#if defined(_WINDOWS) +#define LOCK_FAIL -1 + +#elif defined(__linux__) +#define LOCK_FAIL -1 + +#elif defined(_AIX) + +#elif defined(_ZOS) +#define LOCK_FAIL -1 +#elif defined(__MACH__) || defined(__APPLE__) +#define LOCK_FAIL -1 +#endif + +namespace ibmras { +namespace common { +namespace port { + +/* different type of lock functionality required by threads */ +class DECL Lock { +public: + Lock(); /* default constructor */ + int acquire(); /* acquire the lock associated with this class */ + int release(); /* release the lock */ + void destroy(); /* Detroy / release the platform lock */ + bool isDestroyed(); /* true if the underlygin platform lock has been destroyed */ + ~Lock(); /* destructor to allow lock release */ +private: + void* lock; /* platform lock structure */ +}; + +} +} +} /*end of namespace port */ + +#endif /* ibmras_common_port_lock_h */ diff --git a/Sources/agentcore/ibmras/common/port/Process.h b/Sources/agentcore/ibmras/common/port/Process.h new file mode 100644 index 0000000..4b03b5d --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/Process.h @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_port_process_h +#define ibmras_common_port_process_h +#include "AgentExtensions.h" + +namespace ibmras { +namespace common { +namespace port { + +DECL int getProcessId(); /* get hold of the process id */ +DECL std::string getHostName(); /* Return the host name*/ + +} +} +} + + +#endif /* ibmras_common_port_process_h */ diff --git a/Sources/agentcore/ibmras/common/port/Semaphore.h b/Sources/agentcore/ibmras/common/port/Semaphore.h new file mode 100644 index 0000000..a22f792 --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/Semaphore.h @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_port_semaphore_h +#define ibmras_common_port_semaphore_h + +#include +#include + +#ifndef NULL +#define NULL 0 +#endif + +#include "../Logger.h" + +namespace ibmras { +namespace common { +namespace port { + +/* class to provide semaphore semantics */ +class Semaphore { +public: + Semaphore(uint32 initial, uint32 max); /* semaphore initial and max count */ + void inc(); /* increase the semaphore count */ + bool wait(uint32 timeout); /* decrement the semaphore count */ + ~Semaphore(); /* OS cleanup of semaphore */ +private: + void* handle; /* opaque handle to platform data structure */ +#if defined __MACH__ + std::string name; +#endif +}; + +} +} +} /*end of namespace port */ + +#endif /* ibmras_common_port_semaphore_h */ diff --git a/Sources/agentcore/ibmras/common/port/ThreadData.cpp b/Sources/agentcore/ibmras/common/port/ThreadData.cpp new file mode 100644 index 0000000..9808813 --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/ThreadData.cpp @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +/* + * Implementation of common port library classes + */ + +#include "ThreadData.h" + + +namespace ibmras { + +namespace common { + +namespace port { + + +ThreadData::ThreadData(THREAD_CALLBACK callback) { + this->callback = callback; + handle = (static_cast(NULL)); + args = NULL; + hasstopmethod = false; +} + + +ThreadData::ThreadData(THREAD_CALLBACK callback, THREAD_CALLBACK stopMethod) { + this->callback = callback; + this->stopmethod = stopMethod; + handle = (static_cast(NULL)); + args = NULL; + hasstopmethod = true; +} + +void ThreadData::setArgs(void* args) { + this->args = args; +} + +void* ThreadData::getArgs() { + return args; +} + +THREAD_CALLBACK ThreadData::getCallback() { + return callback; +} + +THREAD_CALLBACK ThreadData::getStopMethod() { + return stopmethod; +} + +bool ThreadData::hasStopMethod() { + return hasstopmethod; +} + +} +} +} /* end of namespace port */ diff --git a/Sources/agentcore/ibmras/common/port/ThreadData.h b/Sources/agentcore/ibmras/common/port/ThreadData.h new file mode 100644 index 0000000..62b165c --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/ThreadData.h @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_port_threaddata_h +#define ibmras_common_port_threaddata_h + +#include +#include +#include "../../monitoring/Typesdef.h" +#include "AgentExtensions.h" + + +namespace ibmras { +namespace common { +namespace port { + + +class ThreadData; /* forward declaration of ThreadData class */ +typedef void* (*THREAD_CALLBACK)(ThreadData*); /* shortcut definition for the thread callback */ + +/* provides the encapsulation of different thread semantics for each platform */ +class DECL ThreadData { +public: + ThreadData(THREAD_CALLBACK callback); + ThreadData(THREAD_CALLBACK callback, THREAD_CALLBACK stopMethod); + ~ThreadData(){} + void setArgs(void* args); + void* getArgs(); + THREAD_CALLBACK getCallback(); + THREAD_CALLBACK getStopMethod(); + bool hasStopMethod(); +private: + uintptr_t handle; /* handle to underlying OS thread */ + THREAD_CALLBACK callback; /* callback to make */ + THREAD_CALLBACK stopmethod; /* method to call when stopping */ + bool hasstopmethod; /* flag for indicating prescence of stop method */ + void* args; +}; + + +DECL uintptr_t createThread(ThreadData *data); /* create a thread and start it with specified callback and args */ +void exitThread(void *val); /* exit current thread with an optional return value */ +DECL void sleep(uint32 seconds); /* sleep the current thread */ +void stopAllThreads(); /* Stops all threads */ + +} +} +} /*end of namespace port */ + +#endif /* ibmras_common_port_threaddata_h */ diff --git a/Sources/agentcore/ibmras/common/port/aix/Process.cpp b/Sources/agentcore/ibmras/common/port/aix/Process.cpp new file mode 100644 index 0000000..6ba243a --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/aix/Process.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include +#include + +namespace ibmras { +namespace common { +namespace port { + +int getProcessId() { + return getpid(); +} + +std::string getHostName() { + char buffer[256]; + if (gethostname(buffer, 255) == 0) { + return buffer; + } else { + return "unknown"; + } +} + + +} +} +} /* end namespace port */ diff --git a/Sources/agentcore/ibmras/common/port/aix/Thread.cpp b/Sources/agentcore/ibmras/common/port/aix/Thread.cpp new file mode 100644 index 0000000..53fdcb8 --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/aix/Thread.cpp @@ -0,0 +1,192 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +/* + * Functions that control thread behaviour + */ + +#include +#include +#include "../ThreadData.h" +#include "../Semaphore.h" +#include "../../logging.h" +#include +#include +#include +#include +#include +#include +#include + +namespace ibmras { +namespace common { +namespace port { + +IBMRAS_DEFINE_LOGGER("Port"); + +std::list condMap; +std::stack threadMap; +pthread_mutex_t condMapMux = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t threadMapMux = PTHREAD_MUTEX_INITIALIZER; +bool stopping = false; + +void* wrapper(void *params) { + IBMRAS_DEBUG(fine,"in thread.cpp->wrapper"); + ThreadData* data = reinterpret_cast(params); + void* result; + if (data->hasStopMethod()) { + IBMRAS_DEBUG(debug,"stopMethod present"); + pthread_cleanup_push(reinterpret_cast(data->getStopMethod()), data); + IBMRAS_DEBUG(debug,"executing callback"); + result = data->getCallback()(data); + pthread_cleanup_pop(1); + } else { + IBMRAS_DEBUG(debug,"stopMethod not present, executing callback"); + result = data->getCallback()(data); + } + return result; +} + +uintptr_t createThread(ThreadData* data) { + IBMRAS_DEBUG(fine,"in thread.cpp->createThread"); + uintptr_t retval; + // lock the threadMap as we might be making updates to it + pthread_mutex_lock(&threadMapMux); + if (!stopping) { + pthread_t thread; + retval = pthread_create(&thread, NULL, wrapper, data); + if (retval == 0) { + IBMRAS_DEBUG(debug,"Thread created successfully"); + // only store valid threads + threadMap.push(thread); + } + } else { + IBMRAS_DEBUG(debug,"Trying to stop - thread not created"); + retval = ECANCELED; + } + pthread_mutex_unlock(&threadMapMux); + return retval; +} + +void exitThread(void *val) { + IBMRAS_DEBUG(fine,"in thread.cpp->exitThread"); + pthread_exit(NULL); +} + +void sleep(uint32 seconds) { + IBMRAS_DEBUG(fine,"in thread.cpp->sleep"); + /* each sleep has its own mutex and condvar - the condvar will either + be triggered by condBroadcast or it will timeout.*/ + pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; + pthread_cond_t c = PTHREAD_COND_INITIALIZER; + + IBMRAS_DEBUG(debug,"Updating condvar map"); + // lock the condvar map for update + pthread_mutex_lock(&condMapMux); + std::list::iterator it = condMap.insert(condMap.end(),c); + pthread_mutex_unlock(&condMapMux); + pthread_mutex_lock(&m); + + struct timespec t; + clock_gettime(CLOCK_REALTIME, &t); + t.tv_sec += seconds; /* configure the sleep interval */ + IBMRAS_DEBUG_1(finest,"Sleeping for %d seconds", seconds); + pthread_cond_timedwait(&c, &m, &t); + IBMRAS_DEBUG(finest,"Woke up"); + pthread_mutex_unlock(&m); + + pthread_mutex_lock(&condMapMux); + condMap.erase(it); + pthread_mutex_unlock(&condMapMux); +} + +void condBroadcast() { + IBMRAS_DEBUG(fine,"in thread.cpp->condBroadcast"); + //prevent other threads adding to the condMap + pthread_mutex_lock(&condMapMux); + for (std::list::iterator it=condMap.begin(); it!=condMap.end(); ++it) { + pthread_cond_broadcast(&(*it)); + } + pthread_mutex_unlock(&condMapMux); +} + +void stopAllThreads() { + IBMRAS_DEBUG(fine,"in thread.cpp->stopAllThreads"); + //prevent new thread creation + pthread_mutex_lock(&threadMapMux); + stopping = true; + // wake currently sleeping threads + condBroadcast(); + while (!threadMap.empty()) { + pthread_cancel(threadMap.top()); + //wait for the thread to stop + pthread_join(threadMap.top(), NULL); + threadMap.pop(); + } + pthread_mutex_unlock(&threadMapMux); +} + +Semaphore::Semaphore(uint32 initial, uint32 max) { + if (!stopping) { + handle = new sem_t; + IBMRAS_DEBUG(fine,"in thread.cpp creating CreateSemaphoreA"); + int result; + result = sem_init(reinterpret_cast(handle), 0, initial); + if (result) { + IBMRAS_DEBUG_1(warning, "Failed to create semaphore : error code %d", result); + handle = NULL; + } + } else { + IBMRAS_DEBUG(debug,"Trying to stop - semaphore not created"); + handle = NULL; + } +} + +void Semaphore::inc() { + IBMRAS_DEBUG(finest, "Incrementing semaphore ticket count"); + if (handle) { + sem_post(reinterpret_cast(handle)); + } +} + +bool Semaphore::wait(uint32 timeout) { + int result; + struct timespec t; + while (!handle) { + sleep(timeout); /* wait for the semaphore to be established */ + } + clock_gettime(CLOCK_REALTIME, &t); + t.tv_sec++; /* configure the sleep interval */ + IBMRAS_DEBUG(finest, "semaphore wait"); + result = sem_timedwait(reinterpret_cast(handle), &t); + if (!result) { + IBMRAS_DEBUG(finest, "semaphore posted"); + return true; + } + + IBMRAS_DEBUG(finest, "semaphore timeout"); + return (errno != ETIMEDOUT); +} + +Semaphore::~Semaphore() { + sem_destroy(reinterpret_cast(handle)); + delete (sem_t*)handle; +} + +} +} +} /* end namespace port */ diff --git a/Sources/agentcore/ibmras/common/port/linux/Process.cpp b/Sources/agentcore/ibmras/common/port/linux/Process.cpp new file mode 100644 index 0000000..6ba243a --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/linux/Process.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include +#include + +namespace ibmras { +namespace common { +namespace port { + +int getProcessId() { + return getpid(); +} + +std::string getHostName() { + char buffer[256]; + if (gethostname(buffer, 255) == 0) { + return buffer; + } else { + return "unknown"; + } +} + + +} +} +} /* end namespace port */ diff --git a/Sources/agentcore/ibmras/common/port/linux/Thread.cpp b/Sources/agentcore/ibmras/common/port/linux/Thread.cpp new file mode 100644 index 0000000..d1f5111 --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/linux/Thread.cpp @@ -0,0 +1,190 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +/* + * Functions that control thread behaviour + */ + +#include "pthread.h" +#include "time.h" +#include +#include + +#include "../ThreadData.h" +#include "../Semaphore.h" +#include "../../logging.h" +#include +#include +#include + +namespace ibmras { +namespace common { +namespace port { + +IBMRAS_DEFINE_LOGGER("Port"); + +std::list condMap; +std::stack threadMap; +pthread_mutex_t condMapMux = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t threadMapMux = PTHREAD_MUTEX_INITIALIZER; +bool stopping = false; + +void* wrapper(void *params) { + IBMRAS_DEBUG(fine,"in thread.cpp->wrapper"); + ThreadData* data = reinterpret_cast(params); + void* result; + if (data->hasStopMethod()) { + IBMRAS_DEBUG(debug,"stopMethod present"); + pthread_cleanup_push(reinterpret_cast(data->getStopMethod()), data); + IBMRAS_DEBUG(debug,"executing callback"); + result = data->getCallback()(data); + pthread_cleanup_pop(1); + } else { + IBMRAS_DEBUG(debug,"stopMethod not present, executing callback"); + result = data->getCallback()(data); + } + return result; +} + +uintptr_t createThread(ThreadData* data) { + IBMRAS_DEBUG(fine,"in thread.cpp->createThread"); + uintptr_t retval; + // lock the threadMap as we might be making updates to it + pthread_mutex_lock(&threadMapMux); + if (!stopping) { + pthread_t thread; + retval = pthread_create(&thread, NULL, wrapper, data); + if (retval == 0) { + IBMRAS_DEBUG(debug,"Thread created successfully"); + // only store valid threads + threadMap.push(thread); + } + } else { + IBMRAS_DEBUG(debug,"Trying to stop - thread not created"); + retval = ECANCELED; + } + pthread_mutex_unlock(&threadMapMux); + return retval; +} + +void exitThread(void *val) { + IBMRAS_DEBUG(fine,"in thread.cpp->exitThread"); + pthread_exit(NULL); +} + +void sleep(uint32 seconds) { + IBMRAS_DEBUG(fine,"in thread.cpp->sleep"); + /* each sleep has its own mutex and condvar - the condvar will either + be triggered by condBroadcast or it will timeout.*/ + pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; + pthread_cond_t c = PTHREAD_COND_INITIALIZER; + + IBMRAS_DEBUG(debug,"Updating condvar map"); + // lock the condvar map for update + pthread_mutex_lock(&condMapMux); + std::list::iterator it = condMap.insert(condMap.end(),c); + pthread_mutex_unlock(&condMapMux); + pthread_mutex_lock(&m); + + struct timespec t; + clock_gettime(CLOCK_REALTIME, &t); + t.tv_sec += seconds; /* configure the sleep interval */ + IBMRAS_DEBUG_1(finest,"Sleeping for %d seconds", seconds); + pthread_cond_timedwait(&c, &m, &t); + IBMRAS_DEBUG(finest,"Woke up"); + pthread_mutex_unlock(&m); + + pthread_mutex_lock(&condMapMux); + condMap.erase(it); + pthread_mutex_unlock(&condMapMux); +} + +void condBroadcast() { + IBMRAS_DEBUG(fine,"in thread.cpp->condBroadcast"); + //prevent other threads adding to the condMap + pthread_mutex_lock(&condMapMux); + for (std::list::iterator it=condMap.begin(); it!=condMap.end(); ++it) { + pthread_cond_broadcast(&(*it)); + } + pthread_mutex_unlock(&condMapMux); +} + +void stopAllThreads() { + IBMRAS_DEBUG(fine,"in thread.cpp->stopAllThreads"); + //prevent new thread creation + pthread_mutex_lock(&threadMapMux); + stopping = true; + // wake currently sleeping threads + condBroadcast(); + while (!threadMap.empty()) { + pthread_cancel(threadMap.top()); + //wait for the thread to stop + pthread_join(threadMap.top(), NULL); + threadMap.pop(); + } + pthread_mutex_unlock(&threadMapMux); +} + +Semaphore::Semaphore(uint32 initial, uint32 max) { + if (!stopping) { + handle = new sem_t; + IBMRAS_DEBUG(fine,"in thread.cpp creating CreateSemaphoreA"); + int result; + result = sem_init(reinterpret_cast(handle), 0, initial); + if (result) { + IBMRAS_DEBUG_1(warning, "Failed to create semaphore : error code %d", result); + handle = NULL; + } + } else { + IBMRAS_DEBUG(debug,"Trying to stop - semaphore not created"); + handle = NULL; + } +} + +void Semaphore::inc() { + IBMRAS_DEBUG(finest, "Incrementing semaphore ticket count"); + if (handle) { + sem_post(reinterpret_cast(handle)); + } +} + +bool Semaphore::wait(uint32 timeout) { + int result; + struct timespec t; + while (!handle) { + sleep(timeout); /* wait for the semaphore to be established */ + } + clock_gettime(CLOCK_REALTIME, &t); + t.tv_sec++; /* configure the sleep interval */ + IBMRAS_DEBUG(finest, "semaphore wait"); + result = sem_timedwait(reinterpret_cast(handle), &t); + if(!result) { + IBMRAS_DEBUG(finest, "semaphore posted"); + return true; + } + + IBMRAS_DEBUG(finest, "semaphore timeout"); + return (errno != ETIMEDOUT); +} + +Semaphore::~Semaphore() { + sem_destroy(reinterpret_cast(handle)); + delete (sem_t*)handle; +} + +} +} +} /* end namespace port */ diff --git a/Sources/agentcore/ibmras/common/port/osx/Process.cpp b/Sources/agentcore/ibmras/common/port/osx/Process.cpp new file mode 100644 index 0000000..6ba243a --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/osx/Process.cpp @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include +#include + +namespace ibmras { +namespace common { +namespace port { + +int getProcessId() { + return getpid(); +} + +std::string getHostName() { + char buffer[256]; + if (gethostname(buffer, 255) == 0) { + return buffer; + } else { + return "unknown"; + } +} + + +} +} +} /* end namespace port */ diff --git a/Sources/agentcore/ibmras/common/port/osx/Thread.cpp b/Sources/agentcore/ibmras/common/port/osx/Thread.cpp new file mode 100644 index 0000000..cc262b7 --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/osx/Thread.cpp @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +/* + * Functions that control thread behaviour + */ +#define _XOPEN_SOURCE_EXTENDED 1 +#include "pthread.h" +#include +#include +#include +#include +#include +#include +#include /* For O_* constants */ +#include /* For mode constants */ +#include +#include +#include + + +#include "../ThreadData.h" +#include "../Semaphore.h" +#include "../../logging.h" +#include "../../common.h" +#include "../../util/sysUtils.h" +#include +#include +#include + +namespace ibmras { +namespace common { +namespace port { + +IBMRAS_DEFINE_LOGGER("Port"); + +std::list condMap; +std::stack threadMap; +pthread_mutex_t condMapMux = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t threadMapMux = PTHREAD_MUTEX_INITIALIZER; +bool stopping = false; + +void* wrapper(void *params) { + IBMRAS_DEBUG(fine,"in thread.cpp->wrapper"); + ThreadData* data = reinterpret_cast(params); + void* result; + if (data->hasStopMethod()) { + IBMRAS_DEBUG(debug,"stopMethod present"); + pthread_cleanup_push(reinterpret_cast(data->getStopMethod()), data); + IBMRAS_DEBUG(debug,"executing callback"); + result = data->getCallback()(data); + pthread_cleanup_pop(1); + } else { + IBMRAS_DEBUG(debug,"stopMethod not present, executing callback"); + result = data->getCallback()(data); + } + return result; +} + +uintptr_t createThread(ThreadData* data) { + IBMRAS_DEBUG(fine,"in thread.cpp->createThread"); + uintptr_t retval; + // lock the threadMap as we might be making updates to it + pthread_mutex_lock(&threadMapMux); + if (!stopping) { + pthread_t thread; + retval = pthread_create(&thread, NULL, wrapper, data); + if (retval == 0) { + IBMRAS_DEBUG(debug,"Thread created successfully"); + // only store valid threads + threadMap.push(thread); + } + } else { + IBMRAS_DEBUG(debug,"Trying to stop - thread not created"); + retval = ECANCELED; + } + pthread_mutex_unlock(&threadMapMux); + return retval; +} + +void exitThread(void *val) { + IBMRAS_DEBUG(fine,"in thread.cpp->exitThread"); + pthread_exit(NULL); +} + +void sleep(uint32 seconds) { + IBMRAS_DEBUG(fine,"in thread.cpp->sleep"); + /* each sleep has its own mutex and condvar - the condvar will either + be triggered by condBroadcast or it will timeout.*/ + pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; + pthread_cond_t c = PTHREAD_COND_INITIALIZER; + + IBMRAS_DEBUG(debug,"Updating condvar map"); + // lock the condvar map for update + pthread_mutex_lock(&condMapMux); + std::list::iterator it = condMap.insert(condMap.end(),c); + pthread_mutex_unlock(&condMapMux); + pthread_mutex_lock(&m); + + struct timeval tv; + struct timespec ts; + gettimeofday(&tv, NULL); + ts.tv_sec = tv.tv_sec + seconds; + ts.tv_nsec = 0; + IBMRAS_DEBUG_1(finest,"Sleeping for %d seconds", seconds); + pthread_cond_timedwait(&c, &m, &ts); + IBMRAS_DEBUG(finest,"Woke up"); + pthread_mutex_unlock(&m); + pthread_mutex_lock(&condMapMux); + condMap.erase(it); + pthread_mutex_unlock(&condMapMux); +} + +void condBroadcast() { + IBMRAS_DEBUG(fine,"in thread.cpp->condBroadcast"); + //prevent other threads adding to the condMap + pthread_mutex_lock(&condMapMux); + for (std::list::iterator it=condMap.begin(); it!=condMap.end(); ++it) { + pthread_cond_broadcast(&(*it)); + } + pthread_mutex_unlock(&condMapMux); +} + +void stopAllThreads() { + IBMRAS_DEBUG(fine,"in thread.cpp->stopAllThreads"); + //prevent new thread creation + pthread_mutex_lock(&threadMapMux); + stopping = true; + // wake currently sleeping threads + condBroadcast(); + /* + while (!threadMap.empty()) { + pthread_cancel(threadMap.top()); + //wait for the thread to stop + pthread_join(threadMap.top(), NULL); + threadMap.pop(); + } + */ + pthread_mutex_unlock(&threadMapMux); +} + +Semaphore::Semaphore(uint32 initial, uint32 max) { + if (!stopping) { + name = "/hc/"; + name.append(ibmras::common::itoa(getpid())); + name.append("/"); + name.append(ibmras::common::itoa(pthread_self())); + handle = new sem_t; + IBMRAS_DEBUG_1(fine, "in thread.cpp creating semaphore %s", name.c_str()); + + handle = sem_open(name.c_str(), O_CREAT | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO, initial); + int i=0; + if (handle == SEM_FAILED) { + while (i<=20) { + std::string i_string = ibmras::common::itoa(i); + name.replace(name.length()-i_string.length(), i_string.length(), i_string); + IBMRAS_DEBUG_1(fine, "Failed; creating semaphore %s", name.c_str()); + handle = sem_open(name.c_str(), O_CREAT | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO, initial); + if (handle == SEM_FAILED) { + i++; + } else { + return; + } + } + IBMRAS_DEBUG(warning, "Failed to create semaphore : error code SEM_FAILED\n"); + handle = NULL; + } + } else { + IBMRAS_DEBUG(debug,"Trying to stop - semaphore not created"); + handle = NULL; + } +} + +void Semaphore::inc() { + IBMRAS_DEBUG_1(finest, "Incrementing semaphore %s ticket count\n", name.c_str()); + if (handle) { + sem_post(reinterpret_cast(handle)); + } +} + +bool Semaphore::wait(uint32 timeout) { + int result; + while (!handle) { + sleep(timeout); /* wait for the semaphore to be established */ + } + IBMRAS_DEBUG_1(finest, "semaphore %s wait\n", name.c_str()); + + //best can do here as OSX doesn't do sem_timedwait; trywait returns immediately + //and we can check the result to see if we need to sleep and try again. + + result = sem_trywait(reinterpret_cast(handle)); + if (result == -1 && errno == EAGAIN) { + ibmras::common::port::sleep(timeout); + result = sem_trywait(reinterpret_cast(handle)); + } + + if(!result) { + IBMRAS_DEBUG_1(finest, "semaphore %s posted\n", name.c_str()); + return true; + } + + IBMRAS_DEBUG_1(finest, "possible semaphore %s timeout\n", name.c_str()); + return (errno != EAGAIN); +} + +Semaphore::~Semaphore() { + sem_close(reinterpret_cast(handle)); + sem_unlink(name.c_str()); +} + +} +} +} /* end namespace port */ diff --git a/Sources/agentcore/ibmras/common/port/windows/Process.cpp b/Sources/agentcore/ibmras/common/port/windows/Process.cpp new file mode 100644 index 0000000..51ec5ce --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/windows/Process.cpp @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "../ThreadData.h" +#include "AgentExtensions.h" + +#include +#include + +namespace ibmras { +namespace common { +namespace port { + +DECL int getProcessId() { + return _getpid(); +} + +DECL std::string getHostName() { + char buffer[256]; + if (gethostname(buffer, 255) == 0) { + return buffer; + } else { + return "unknown"; + } +} + +} +} +} /* end namespace port */ diff --git a/Sources/agentcore/ibmras/common/port/windows/Thread.cpp b/Sources/agentcore/ibmras/common/port/windows/Thread.cpp new file mode 100644 index 0000000..0cccd1d --- /dev/null +++ b/Sources/agentcore/ibmras/common/port/windows/Thread.cpp @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +/* + * Functions that control thread behaviour + */ + +#include "process.h" +#include "windows.h" +#include "stdio.h" +#include "../ThreadData.h" +#include "../Semaphore.h" +#include "../../logging.h" + +namespace ibmras{ +namespace common{ +namespace port { + +IBMRAS_DEFINE_LOGGER("Port"); + +typedef void *(*callback) (void *); + +/* wrapper function to convert from void* to void return types */ +void wrapper(void *params) { + IBMRAS_DEBUG(fine, "in thread.cpp->wrapper"); + ThreadData* data = reinterpret_cast(params); + data->getCallback()(data); +} + + +uintptr_t createThread(ThreadData* data) { + uintptr_t result; + IBMRAS_DEBUG(fine, "in thread.cpp->createThread"); + result = _beginthread(wrapper, 0, data); + if(result) { + return 0; /* works = handle to thread, so convert to NULL for consistent semantics */ + } + return 1; +} + + +void exitThread(void *val) { + _endthread(); +} + +void sleep(uint32 seconds) { + Sleep(1000 * seconds); +} + +void stopAllThreads() { + IBMRAS_DEBUG(fine,"in thread.cpp->stopAllThreads"); +} + +Semaphore::Semaphore(uint32 initial, uint32 max) { + handle = new HANDLE; + IBMRAS_DEBUG(fine, "in thread.cpp creating CreateSemaphoreA"); + handle = CreateSemaphoreA(NULL, initial, max, NULL); + if(handle == NULL) { + IBMRAS_DEBUG_1(warning, "Failed to create semaphore : error code %d", GetLastError()); + handle = NULL; + } +} + +void Semaphore::inc() { + IBMRAS_DEBUG(finest, "Incrementing semaphore ticket count"); + if(handle) { + ReleaseSemaphore(handle,1,NULL); + } +} + +bool Semaphore::wait(uint32 timeout) { + + IBMRAS_DEBUG(finest, "Semaphore::wait"); + DWORD retVal = WaitForSingleObject(handle, timeout * 1000); + if ( !GetLastError()) { + return (retVal == WAIT_OBJECT_0); + } + return false; + +} + +Semaphore::~Semaphore() { + IBMRAS_DEBUG(finest, "Semaphore::~Semaphore()"); + ReleaseSemaphore(handle,1,NULL); + CloseHandle(handle); +} + +} +} +} /* end of namespace port */ diff --git a/Sources/agentcore/ibmras/common/types.h b/Sources/agentcore/ibmras/common/types.h new file mode 100644 index 0000000..9ef9c9b --- /dev/null +++ b/Sources/agentcore/ibmras/common/types.h @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_types_h +#define ibmras_common_types_h + +#ifndef NULL +#define NULL 0 +#endif + +#if defined(_WINDOWS) + #include + #include +#else + #include + #include + #include +#endif + +typedef signed int INT; +typedef unsigned int UINT; +typedef signed int INT32; +#ifndef UINT32 + typedef unsigned int UINT32; +#endif +typedef unsigned int uint; +typedef signed int int32; +typedef unsigned int uint32; +typedef signed int int_t; +typedef unsigned int uint_t; + +#if defined(_WINDOWS) + typedef signed int int32_t; + typedef unsigned int uint32_t; + + typedef signed __int64 INT64; + typedef unsigned __int64 UINT64; + + typedef signed __int64 int64; + typedef unsigned __int64 uint64; + + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; + + #define _P64 "I64" +#else + #if (__WORDSIZE == 64) + #define _P64 "l" + #else + #define _P64 "ll" + #endif + + typedef int64_t INT64; + #ifndef UINT64 + typedef uint64_t UINT64; + #endif + + typedef int64_t int64; + typedef uint64_t uint64; + +#endif + +#endif /* ibmras_common_types_h */ diff --git a/Sources/agentcore/ibmras/common/util/FileUtils.cpp b/Sources/agentcore/ibmras/common/util/FileUtils.cpp new file mode 100644 index 0000000..e92b824 --- /dev/null +++ b/Sources/agentcore/ibmras/common/util/FileUtils.cpp @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#if defined(_ZOS) +#define _UNIX03_SOURCE +#endif + +#include "FileUtils.h" +#include "../logging.h" + +#if defined(WINDOWS) +#include +#include +#else +#include +#include +#include +#include +#endif + +namespace ibmras { +namespace common { +namespace util { + + + +IBMRAS_DEFINE_LOGGER("FileUtils"); + +bool ibmras::common::util::FileUtils::createDirectory(std::string& path) { + IBMRAS_DEBUG(debug, ">>>HLConnector::createDirectory"); + bool created = false; + + const char* pathName = path.c_str(); + +#if defined(WINDOWS) + DWORD dirAttr; + IBMRAS_DEBUG_1(debug, "Creating directory: %s", pathName); + dirAttr = GetFileAttributes(reinterpret_cast(pathName)); + + if(INVALID_FILE_ATTRIBUTES == dirAttr) { + switch (GetLastError()) { + case ERROR_PATH_NOT_FOUND: + IBMRAS_DEBUG(warning, "The directory was not found"); + IBMRAS_DEBUG_1(debug, "Creating directory: %s", pathName); + if(!CreateDirectory(reinterpret_cast(pathName), NULL)) { + switch (GetLastError()) { + //if the directory already exists we will use it instead of the current one. + case ERROR_ALREADY_EXISTS: + IBMRAS_DEBUG(warning, "The specified directory already exists."); + created = true; + break; + case ERROR_PATH_NOT_FOUND: + IBMRAS_DEBUG(warning, "The system cannot find the path specified."); + break; + } + } else { + created = true; + } + break; + case ERROR_INVALID_NAME: + IBMRAS_DEBUG(warning, "The filename, directory name, or volume label syntax is incorrect"); + break; + case ERROR_BAD_NETPATH: + IBMRAS_DEBUG(warning, "The network path was not found."); + break; + default: + IBMRAS_DEBUG(warning, "The directory could not be found, permissions?."); + IBMRAS_DEBUG_1(debug, "Creating directory: %s", pathName); + if(!CreateDirectory(reinterpret_cast(pathName), NULL)) { + switch (GetLastError()) { + case ERROR_ALREADY_EXISTS: + IBMRAS_DEBUG(warning, "The specified directory already exists."); + created = true; + break; + case ERROR_PATH_NOT_FOUND: + IBMRAS_DEBUG(warning, "The system cannot find the path specified."); + break; + } + } else { + created = true; + } + } + } + +#else + struct stat dir; + IBMRAS_DEBUG_1(debug, "Pathname...%s\n", pathName); + if (stat(pathName, &dir)) { + IBMRAS_DEBUG_1(debug, "Directory does not exist, creating...%s\n", pathName); + if (mkdir(pathName, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + IBMRAS_DEBUG_1(debug, "Directory could not be created: ", strerror(errno)); + if(EEXIST == errno) { + IBMRAS_DEBUG_1(debug, "Directory % already existed", pathName); + created = true; + } + } else { + IBMRAS_DEBUG_1(debug, "Directory %s was created: ", pathName); + created = true; + } + } else { + IBMRAS_DEBUG(debug, "stat() returned 0, we'll check whether it was an existing directory"); + if(S_ISDIR(dir.st_mode)) { + created = true; + } + } +#endif + IBMRAS_DEBUG(debug, "<< + +namespace ibmras { +namespace common { +namespace util { + +class FileUtils { + +public: + +bool createDirectory(std::string& path); + +}; + +} +} +} + +#endif /* ibhmras_common_util_fileutils_h */ diff --git a/Sources/agentcore/ibmras/common/util/LibraryUtils.cpp b/Sources/agentcore/ibmras/common/util/LibraryUtils.cpp new file mode 100644 index 0000000..cdf751c --- /dev/null +++ b/Sources/agentcore/ibmras/common/util/LibraryUtils.cpp @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#if defined(_ZOS) +#define _UNIX03_SOURCE +#endif + +#include "LibraryUtils.h" + +#if defined(WINDOWS) +#include +#include +#include +const char PATHSEPARATOR = '\\'; +#else +#include +#include +const char PATHSEPARATOR = '/'; +#endif + + +namespace ibmras { +namespace common { +namespace util { + +void* LibraryUtils::getSymbol(Handle libHandle, const std::string& symbolName) { + + void *symbol = NULL; + +#if defined(WINDOWS) + symbol = (void*) GetProcAddress(libHandle.handle, symbolName.c_str()); +#else + /* Unix platforms */ + symbol = dlsym(libHandle.handle, symbolName.c_str()); +#endif + + return symbol; + +} + +ibmras::common::util::LibraryUtils::Handle LibraryUtils::openLibrary(const std::string& lib) { + + Handle handle; +#if defined(WINDOWS) + handle.handle = LoadLibrary(lib.c_str()); +#else +#if defined(__MACH__) || defined(__APPLE__) + std::size_t found = lib.rfind(".dylib", lib.size() - 6); + if (found == std::string::npos) { + return Handle(); + } +#endif + handle.handle = dlopen(lib.c_str(), RTLD_LAZY); +#endif + return handle; +} + +void LibraryUtils::closeLibrary(Handle libHandle) { + +#if defined(WINDOWS) + FreeLibrary(libHandle.handle); +#else + dlclose(libHandle.handle); +#endif + libHandle.handle = NULL; +} + +std::string LibraryUtils::getLibraryLocation(const void* func) { + std::string path; + +#if defined (WINDOWS) +#elif defined(AIX) +#elif defined(_ZOS) +#else + Dl_info dlInfo; + int rc = dladdr(func, &dlInfo); + if (rc != 0) { + path = dlInfo.dli_fname; + } + +#endif + return path; +} + +std::string LibraryUtils::getLibraryDir(const std::string &library, const void* func) { + std::string path; +#if defined (WINDOWS) + path = getLibraryLocation(library); +#else + path = getLibraryLocation(func); +#endif + size_t pos = path.find_last_of(PATHSEPARATOR); + if (pos != std::string::npos) { + path = path.substr(0, pos); + } + return path; +} + +std::string LibraryUtils::getLibraryLocation(const std::string &library) { + std::string path; + +#if defined (WINDOWS) + HMODULE hModule = GetModuleHandle(library.c_str()); + TCHAR dllPath[_MAX_PATH]; + GetModuleFileName(hModule, dllPath, _MAX_PATH); + path = dllPath; +#else +#endif + return path; +} + +} +} +} diff --git a/Sources/agentcore/ibmras/common/util/LibraryUtils.h b/Sources/agentcore/ibmras/common/util/LibraryUtils.h new file mode 100644 index 0000000..868d353 --- /dev/null +++ b/Sources/agentcore/ibmras/common/util/LibraryUtils.h @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibhmras_common_util_libraryutils_h +#define ibhmras_common_util_libraryutils_h + +#if defined(WINDOWS) +#include +#else +#endif + +#include + +namespace ibmras { +namespace common { +namespace util { + +class LibraryUtils { + +public: + class Handle { + public: +#if defined(WINDOWS) + typedef HINSTANCE handle_type; +#else + typedef void* handle_type; +#endif + + Handle() : + handle(NULL) { + } + + bool isValid() {return (handle != NULL); } + handle_type handle; + }; + + static void* getSymbol(Handle libHandle, const std::string& symbol); + static Handle openLibrary(const std::string &lib); + static void closeLibrary(Handle libHandle); + + static std::string getLibraryDir(const std::string &library, const void* func); + static std::string getLibraryLocation(const void* func); + static std::string getLibraryLocation(const std::string &library); + +}; + +} +} +} + +#endif /* ibhmras_common_util_libraryutils_h */ diff --git a/Sources/agentcore/ibmras/common/util/memUtils.cpp b/Sources/agentcore/ibmras/common/util/memUtils.cpp new file mode 100644 index 0000000..b51b7ea --- /dev/null +++ b/Sources/agentcore/ibmras/common/util/memUtils.cpp @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#include "memUtils.h" + +namespace ibmras { +namespace common { +namespace utility { + + +}/*end of namespace utility*/ +}/*end of namespace common*/ +} /*end of namespace ibmras*/ + + diff --git a/Sources/agentcore/ibmras/common/util/memUtils.h b/Sources/agentcore/ibmras/common/util/memUtils.h new file mode 100644 index 0000000..dd910bf --- /dev/null +++ b/Sources/agentcore/ibmras/common/util/memUtils.h @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_utility_memUtils_h_ +#define ibmras_common_utility_memUtils_h_ + +namespace ibmras { +namespace common { +namespace utility { + +//unsigned char* hc_alloc(int size); +//void hc_dealloc(unsigned char** buffer); +//void dealloc_report_lines(char *lines[], int count); +//void* hc_realloc_ptr_array(char** source[], int currentSize, int newSize); + +}/*end of namespace utility*/ +}/*end of namespace common*/ +} /*end of namespace ibmras*/ + + + + +#endif /* ibmras_common_utility_memUtils_h_ */ diff --git a/Sources/agentcore/ibmras/common/util/strUtils.cpp b/Sources/agentcore/ibmras/common/util/strUtils.cpp new file mode 100644 index 0000000..7820520 --- /dev/null +++ b/Sources/agentcore/ibmras/common/util/strUtils.cpp @@ -0,0 +1,167 @@ + /** + * IBM Confidential + * OCO Source Materials + * IBM Monitoring and Diagnostic Tools - Health Center + * (C) Copyright IBM Corp. 2007, 2015 All Rights Reserved. + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has + * been deposited with the U.S. Copyright Office. + */ + + +#include "strUtils.h" +#include "../MemoryManager.h" +#include +#include "../Logger.h" +#include +#include +#include +#include + + +#if defined(WINDOWS) +#include +#include +#include +#endif + +#if defined(_ZOS) +#include +#endif + +namespace ibmras { +namespace common { +namespace util { + +std::vector &split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; +} + +std::vector split(const std::string &s, char delim) { + std::vector elems; + split(s, delim, elems); + return elems; +} + +bool endsWith(const std::string& str, const std::string& suffix) { + return (str.length() >= suffix.length() && (0 == str.compare(str.length() - suffix.length(), suffix.length(), suffix))); +} + +bool startsWith(const std::string& str, const std::string& prefix) { + return (str.length() >= prefix.length() && (0 == str.compare(0, prefix.length(), prefix))); +} + + +bool equalsIgnoreCase(const std::string& s1, const std::string& s2) { + + + if (s1.length() != s2.length()) { + return false; + } + + for(std::string::size_type i = 0; i < s1.size(); ++i) { + if (toupper(s1[i]) != toupper(s2[i]) ) { + return false; + } + } + + return true; +} + + +void native2Ascii(char * str) { +#if defined(_ZOS) + if ( NULL != str ) + { + __etoa(str); + } +#endif +} + + +/******************************/ +void +ascii2Native(char * str) +{ +#if defined(_ZOS) + if ( NULL != str ) + { + __atoe(str); + } +#endif + +} + + +/******************************/ +void +force2Native(char * str) +{ +#ifdef _ZOS + char *p = str; + + if ( NULL != str ) + { + while ( 0 != *p ) + { + if ( 0 != ( 0x80 & *p ) ) + { + p = NULL; + break; + } + p++; + } + + if ( NULL != p ) + { + __atoe(str); + } + } +#endif +} + +char* createAsciiString(const char* nativeString) { + char* cp = NULL; + if ( NULL != nativeString ) + { + cp = (char*)ibmras::common::memory::allocate(strlen(nativeString) + 1); + if ( NULL == cp ) + { + return NULL; + } else + { + /* jnm is valid, so is cp */ + strcpy(cp,nativeString); + native2Ascii(cp); + } + } + return cp; +} + +char* createNativeString(const char* asciiString) { + char* cp = NULL; + if ( NULL != asciiString ) + { + cp = (char*)ibmras::common::memory::allocate(strlen(asciiString) + 1); + if ( NULL == cp ) + { + return NULL; + } else + { + /* jnm is valid, so is cp */ + strcpy(cp,asciiString); + ascii2Native(cp); + } + } + return cp; +} + + +}/*end of namespace util*/ +}/*end of namespace common*/ +} /*end of namespace ibmras*/ diff --git a/Sources/agentcore/ibmras/common/util/strUtils.h b/Sources/agentcore/ibmras/common/util/strUtils.h new file mode 100644 index 0000000..801f3d6 --- /dev/null +++ b/Sources/agentcore/ibmras/common/util/strUtils.h @@ -0,0 +1,41 @@ + /** + * IBM Confidential + * OCO Source Materials + * IBM Monitoring and Diagnostic Tools - Health Center + * (C) Copyright IBM Corp. 2007, 2015 All Rights Reserved. + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has + * been deposited with the U.S. Copyright Office. + */ + + +#include +#include +#include "AgentExtensions.h" + +#ifndef STRUTILS_H_ +#define STRUTILS_H_ + +namespace ibmras { +namespace common { +namespace util { + +std::vector &split(const std::string &s, char delim, std::vector &elems); +std::vector split(const std::string &s, char delim); +bool endsWith(const std::string& str, const std::string& suffix); +bool startsWith(const std::string& str, const std::string& prefix); +DECL bool equalsIgnoreCase(const std::string& s1, const std::string& s2); +DECL void native2Ascii(char * str); +DECL void ascii2Native(char * str); +DECL void force2Native(char * str); +DECL char* createAsciiString(const char* nativeString); +DECL char* createNativeString(const char* asciiString); + +}/*end of namespace util*/ +}/*end of namespace common*/ +} /*end of namespace ibmras*/ + + + + +#endif /* STRUTILS_H_ */ diff --git a/Sources/agentcore/ibmras/common/util/sysUtils.cpp b/Sources/agentcore/ibmras/common/util/sysUtils.cpp new file mode 100644 index 0000000..5b50e64 --- /dev/null +++ b/Sources/agentcore/ibmras/common/util/sysUtils.cpp @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +#if defined(_ZOS) +#define _XOPEN_SOURCE_EXTENDED 1 +#undef _ALL_SOURCE +#endif + +#include "sysUtils.h" + +#if defined(WINDOWS) + #include +// #include + #include +#elif defined(__linux__) || defined(__MACH__) || defined(__APPLE__) +#include +#elif defined(AIX) +#include +#elif defined(_ZOS) +#include +#endif + +#include + + +namespace ibmras { +namespace common { +namespace util { + +unsigned long long getMilliseconds() { + unsigned long long millisecondsSinceEpoch; +#if defined(WINDOWS) + + SYSTEMTIME st; + GetSystemTime(&st); + + millisecondsSinceEpoch = time(NULL)*1000+st.wMilliseconds; + +#else + struct timeval tv; + gettimeofday(&tv, NULL); + + millisecondsSinceEpoch = + (unsigned long long)(tv.tv_sec) * 1000 + + (unsigned long long)(tv.tv_usec) / 1000; +#endif + return millisecondsSinceEpoch; +} + +}/*end of namespace util*/ +}/*end of namespace common*/ +} /*end of namespace ibmras*/ + + diff --git a/Sources/agentcore/ibmras/common/util/sysUtils.h b/Sources/agentcore/ibmras/common/util/sysUtils.h new file mode 100644 index 0000000..d4ffb16 --- /dev/null +++ b/Sources/agentcore/ibmras/common/util/sysUtils.h @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_common_util_sysutils_h +#define ibmras_common_util_sysutils_h + +#include "AgentExtensions.h" + +namespace ibmras { +namespace common { +namespace util { + +DECL unsigned long long getMilliseconds(); + +}/*end of namespace util */ +}/*end of namespace common*/ +} /*end of namespace ibmras*/ + + + +#endif /* ibmras_common_util_ysutils_h */ diff --git a/Sources/agentcore/ibmras/monitoring/.jazzignore b/Sources/agentcore/ibmras/monitoring/.jazzignore new file mode 100644 index 0000000..4e49100 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/.jazzignore @@ -0,0 +1,20 @@ +### Jazz Ignore 0 +# The property core.ignore specifies a list of file patterns that will be +# ignored in this directory. +# +# The value of core.ignore.recursive will be ignored. +# +# Ignore properties should contain a space separated list of filename patterns. +# Each pattern is case sensitive and surrounded by braces ('{' and '}'). +# "*" matches zero or more characters, and "?" matches single characters. +# +# e.g: {*.sh} {\.*} ignores shell scripts and hidden files + +# NOTE: modifying ignore files will not change the ignore status of derived +# resources. + +core.ignore.recursive= + + {dlfcn.h} \ + {loader} \ + {loader.cpp} \ No newline at end of file diff --git a/Sources/agentcore/ibmras/monitoring/AgentExtensionReceiver.h b/Sources/agentcore/ibmras/monitoring/AgentExtensionReceiver.h new file mode 100644 index 0000000..314ca2a --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/AgentExtensionReceiver.h @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef AGENTEXTENSIONRECEIVER_H_ +#define AGENTEXTENSIONRECEIVER_H_ + +#include "AgentExtensions.h" +#include "connector/Receiver.h" +#include "AgentExtensions.h" + +namespace ibmras { +namespace monitoring { + +class DECL AgentExtensionReceiver : public ibmras::monitoring::connector::Receiver { +public: + AgentExtensionReceiver(RECEIVE_MESSAGE cb) : + receiveMessageCallback(cb) { + } + virtual ~AgentExtensionReceiver() {} + void receiveMessage(const std::string &id, uint32 size, void *data) { + if (receiveMessageCallback) { + receiveMessageCallback(id.c_str(), size, data); + } + } + +private: + RECEIVE_MESSAGE receiveMessageCallback; +}; + +} +} + +#endif /* AGENTEXTENSIONRECEIVER_H_ */ \ No newline at end of file diff --git a/Sources/agentcore/ibmras/monitoring/Plugin.cpp b/Sources/agentcore/ibmras/monitoring/Plugin.cpp new file mode 100644 index 0000000..edda542 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/Plugin.cpp @@ -0,0 +1,247 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#if defined(_ZOS) +#define _UNIX03_SOURCE +#endif + +#include "Plugin.h" +#include "../common/logging.h" + +#include + +#if defined(WINDOWS) +#include +#include +#include +#else +#include +#include +#include +#endif + +namespace ibmras { +namespace monitoring { + +IBMRAS_DEFINE_LOGGER("Plugin") +; + +/* these names are produced by gcc and may not be the same for all compilers */ +const char* SYM_INIT = "ibmras_monitoring_plugin_init"; +const char* SYM_REGISTER_PUSH_SOURCE = "ibmras_monitoring_registerPushSource"; +const char* SYM_REGISTER_PULL_SOURCE = "ibmras_monitoring_registerPullSource"; +const char* SYM_STOP = "ibmras_monitoring_plugin_stop"; +const char* SYM_START = "ibmras_monitoring_plugin_start"; +const char* SYM_CONNECTOR_FACTORY = "ibmras_monitoring_getConnector"; +const char* SYM_RECEIVER_FACTORY = "ibmras_monitoring_getReceiver"; +const char* SYM_RECEIVE_MESSAGE = "ibmras_monitoring_receiveMessage"; +const char* SYM_VERSION = "ibmras_monitoring_getVersion"; + +Plugin::Plugin() : + name(""), init(NULL), push(NULL), pull(NULL), start(NULL), stop(NULL), confactory( + NULL), recvfactory(NULL), receiveMessage(NULL), type(0), version(0), getVersion(NULL) { +} + +std::vector Plugin::scan(const std::string& dir) { + + std::vector plugins; + + IBMRAS_DEBUG_1(fine, "Processing plugin path: %s", dir.c_str()); + +#if defined(WINDOWS) + WIN32_FIND_DATA ffd; + LARGE_INTEGER filesize; + TCHAR szDir[MAX_PATH]; + HANDLE hFind = INVALID_HANDLE_VALUE; + DWORD dwError = 0; + HINSTANCE handle; + + TCHAR* path = (TCHAR*) dir.c_str(); /* cast to the unicode or ascii version */ + + size_t length_of_arg; + StringCchLength(path, MAX_PATH, &length_of_arg); + if (length_of_arg > (MAX_PATH - 3)) { + IBMRAS_DEBUG(fine, "The path is too long"); + return plugins; + } + + StringCchCopy(szDir, MAX_PATH, path); + StringCchCat(szDir, MAX_PATH, TEXT("\\*.dll")); + + IBMRAS_DEBUG_1(finest, "Scanning %s", szDir); + + hFind = FindFirstFile(szDir, &ffd); + + if (INVALID_HANDLE_VALUE == hFind) { + IBMRAS_DEBUG(warning, "Unable to access the contents"); + return plugins; + } + + do { + if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + StringCchCopy(szDir, MAX_PATH, path); + StringCchCat(szDir, MAX_PATH, TEXT("\\")); + StringCchCat(szDir, MAX_PATH, ffd.cFileName); + + Plugin *plugin = processLibrary(szDir); + if (plugin != NULL) { + IBMRAS_LOG_2(fine, "%s, version %s", (plugin->name).c_str(), (plugin->getVersion())); + plugins.push_back(plugin); + } + } + }while (FindNextFile(hFind, &ffd) != 0); + + dwError = GetLastError(); + + if (dwError != ERROR_NO_MORE_FILES) { + IBMRAS_DEBUG(fine, "Error while traversing directory"); + } + + FindClose(hFind); + +#else + + struct dirent *entry; + + DIR *dp = opendir(dir.c_str()); + + if (dp == NULL) { + IBMRAS_DEBUG_1(fine, "Warning, unable to open directory %s", dir.c_str()); + return plugins; + } + + while ((entry = readdir(dp)) != NULL) { + if (entry->d_name[0] != '.') { + + std::string filePath = dir; + filePath += "/"; + filePath += entry->d_name; + + Plugin *plugin = processLibrary(filePath); + if (plugin != NULL) { + IBMRAS_LOG_2(fine, "%s, version %s", (plugin->name).c_str(), (plugin->getVersion())); + plugins.push_back(plugin); + } + + } + } + closedir(dp); + +#endif + + return plugins; + +} + +Plugin* Plugin::processLibrary(const std::string &filePath) { + + Plugin* plugin = NULL; + IBMRAS_DEBUG_1(fine, "Processing plugin library: %s", filePath.c_str()); + + ibmras::common::util::LibraryUtils::Handle handle = + ibmras::common::util::LibraryUtils::openLibrary(filePath.c_str()); + if (handle.isValid()) { + + void* init = ibmras::common::util::LibraryUtils::getSymbol(handle, + SYM_INIT); + void* push = ibmras::common::util::LibraryUtils::getSymbol(handle, + SYM_REGISTER_PUSH_SOURCE); + void* pull = ibmras::common::util::LibraryUtils::getSymbol(handle, + SYM_REGISTER_PULL_SOURCE); + void* start = ibmras::common::util::LibraryUtils::getSymbol(handle, + SYM_START); + void* stop = ibmras::common::util::LibraryUtils::getSymbol(handle, + SYM_STOP); + void* getVersion = ibmras::common::util::LibraryUtils::getSymbol(handle, + SYM_VERSION); + void* connectorFactory = ibmras::common::util::LibraryUtils::getSymbol( + handle, SYM_CONNECTOR_FACTORY); + void* receiverFactory = ibmras::common::util::LibraryUtils::getSymbol( + handle, SYM_RECEIVER_FACTORY); + void* receiveMessage = ibmras::common::util::LibraryUtils::getSymbol( + handle, SYM_RECEIVE_MESSAGE); + + IBMRAS_DEBUG_4(fine, "Library %s: start=%p stop=%p getVersion=%p", filePath.c_str(), start, stop, getVersion); + + /* External plugins MUST implement both start, stop and getVersion */ + if (start && stop && getVersion) { + IBMRAS_DEBUG_1(fine, "Library %s was successfully recognised", filePath.c_str()); + plugin = new Plugin; + + plugin->name = filePath; + plugin->handle = handle; + + plugin->init =reinterpret_cast(init); + + plugin->pull = reinterpret_cast(pull); + + plugin->push = reinterpret_cast(push); + + plugin->stop = reinterpret_cast(stop); + + plugin->start = reinterpret_cast(start); + + plugin->getVersion = reinterpret_cast(getVersion); + + plugin->confactory = reinterpret_cast(connectorFactory); + + plugin->recvfactory = reinterpret_cast(receiverFactory); + + plugin->receiveMessage = reinterpret_cast(receiveMessage); + + if (plugin->recvfactory && plugin->receiveMessage) { + IBMRAS_DEBUG_4(warning, "Library %s: Both %s and %s are defined. Ignoring %s.", filePath.c_str(), SYM_RECEIVER_FACTORY, SYM_RECEIVE_MESSAGE, SYM_RECEIVER_FACTORY); + plugin->receiveMessage = NULL; + } + + plugin->setType(); + } else { + /* not a plugin so close the handle */ + ibmras::common::util::LibraryUtils::closeLibrary(handle); + } + } else { +#if defined(WINDOWS) + +#else + IBMRAS_DEBUG_2(fine, "Not valid handler for library candidate %s. \ndlerror output: \"%s\"", filePath.c_str(), dlerror()); +#endif + } + return plugin; +} + +void Plugin::unload() { + if (handle.isValid()) { + ibmras::common::util::LibraryUtils::closeLibrary(handle); + } +} + +void Plugin::setType() { + type = plugin::none; + if (pull || push) { + type = plugin::data; + } + if (confactory) { + type = type | plugin::connector; + } + if (recvfactory || receiveMessage) { + type = type | plugin::receiver; + } +} + +} +} + diff --git a/Sources/agentcore/ibmras/monitoring/Plugin.h b/Sources/agentcore/ibmras/monitoring/Plugin.h new file mode 100644 index 0000000..8f4acc7 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/Plugin.h @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_plugin_h +#define ibmras_monitoring_plugin_h + +#include "AgentExtensions.h" +#include "Typesdef.h" +#include +#include + +#include "../common/util/LibraryUtils.h" + +typedef void* (*RECEIVER_FACTORY)(); /* short cut for the function pointer for the factory used to create a Receiver object for the plugin */ + +namespace ibmras { +namespace monitoring { + +namespace plugin { + +enum PluginType {none = 0, data = 1, connector = 2, receiver = 4}; + +} /* end namespace Plugin */ + + +/* + * Defines a plugin that implements either push and/or pull source functions + */ +class Plugin { +public: + Plugin(); + virtual ~Plugin() {} + + void unload(); + + static std::vector scan(const std::string &dir); /* scan a directory and return a list of candidate plugins */ + static Plugin* processLibrary(const std::string &filePath); + + std::string name; /* name of the library - typically this is the full path */ + const char* version; + + int (*init)(const char *properties); /* Plugin inialization method */ + pushsource* (*push)(agentCoreFunctions, uint32); /* push source function pointer or NULL */ + pullsource* (*pull)(agentCoreFunctions, uint32); /* pull source function pointer or NULL */ + int (*start)(void); /* start function to begin data production */ + int (*stop)(void); /* stop function to end data production */ + const char* (*getVersion)(void); /* returns plugin version, used to enforce versioning */ + CONNECTOR_FACTORY confactory; /* Connector factory */ + RECEIVER_FACTORY recvfactory; /* Receiver factory */ + RECEIVE_MESSAGE receiveMessage; /* receiveMessage function to be wrapped by an AgentExtensionReceiver */ + ibmras::common::util::LibraryUtils::Handle handle; /* handle to be used when closing the dynamically loaded plugin */ + int type; + +private: + void setType(); +}; + + + +} +} + +#endif /* ibmras_monitoring_plugin_h */ diff --git a/Sources/agentcore/ibmras/monitoring/Typesdef.h b/Sources/agentcore/ibmras/monitoring/Typesdef.h new file mode 100644 index 0000000..8174fb8 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/Typesdef.h @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#if defined(_WINDOWS) + #include + #include +#else + #include + #include + #include +#endif + +#ifndef NULL +#define NULL 0 +#endif + +typedef signed int INT; +typedef unsigned int UINT; +typedef signed int INT32; +#ifndef UINT32 +typedef unsigned int UINT32; +#endif +typedef unsigned int uint; +typedef signed int int32; +typedef unsigned int uint32; +typedef signed int int_t; +typedef unsigned int uint_t; + +#if defined(_WINDOWS) +typedef signed int int32_t; +typedef unsigned int uint32_t; + +typedef signed __int64 INT64; +typedef unsigned __int64 UINT64; + +typedef signed __int64 int64; +typedef unsigned __int64 uint64; + +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#define _P64 "I64" +#else +#if (__WORDSIZE == 64) +#define _P64 "l" +#else +#define _P64 "ll" +#endif + +typedef int64_t INT64; +#ifndef UINT64 +typedef uint64_t UINT64; +#endif + +typedef int64_t int64; +typedef uint64_t uint64; + +#endif diff --git a/Sources/agentcore/ibmras/monitoring/agent/Agent.cpp b/Sources/agentcore/ibmras/monitoring/agent/Agent.cpp new file mode 100644 index 0000000..285fc61 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/Agent.cpp @@ -0,0 +1,785 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +/* + * Main agent functionality + */ + +#include +#include +#include +#include + +#include "Agent.h" +#include "../../common/logging.h" +#include "threads/ThreadPool.h" +#include "../../common/PropertiesFile.h" +#include "../../common/LogManager.h" +#include "SystemReceiver.h" +#include "../AgentExtensionReceiver.h" +#include "../../common/util/strUtils.h" + + +#if defined(WINDOWS) +const char PATHSEPARATOR = '\\'; +const char* LIBPREFIX = ""; +const char* LIBSUFFIX = ".dll"; +#define AGENT_DECL __declspec(dllexport) +#else +#define AGENT_DECL +const char PATHSEPARATOR = '/'; +const char* LIBPREFIX = "lib"; +#if defined(AIX) +const char* LIBSUFFIX = ".a"; +#else +const char* LIBSUFFIX = ".so"; +#endif +#endif + + +namespace ibmras { +namespace monitoring { +namespace agent { +ibmras::common::Logger* pluginlogger = (ibmras::common::Logger*)ibmras_common_LogManager_getLogger( "plugins" ); +ibmras::common::Logger* corelogger = (ibmras::common::Logger*)ibmras_common_LogManager_getLogger( "loader" ); +} +} +} + +//THIS IS THE EXTERNAL FUNCTION THAT WILL BE LOOKED FOR BY THE LOADER +//-------------------------------------------------------------------- +extern "C" { +AGENT_DECL loaderCoreFunctions* loader_entrypoint() { + + loaderCoreFunctions* lCF = new loaderCoreFunctions; + + lCF->init = initWrapper; + lCF->initialize = initWrapper; + lCF->start = startWrapper; + lCF->stop = stopWrapper; + lCF->shutdown = shutdownWrapper; + lCF->getProperty = getPropertyWrapper; + lCF->setProperty = setPropertyWrapper; + lCF->logMessage = logCoreMessageWrapper; + lCF->loadPropertiesFile = loadPropertiesFileWrapper; + lCF->getAgentVersion = getVersionWrapper; + lCF->setLogLevels = setLogLevelsWrapper; + lCF->registerZipFunction = registerZipFunctionWrapper; + lCF->addPlugin = addPluginWrapper; + + return lCF; +} + +const char* getPropertyWrapper(const char * key) { + return ibmras::monitoring::agent::getPropertyImpl(key); +} + +void setPropertyWrapper(const char* key, const char* value) { + ibmras::monitoring::agent::setPropertyImpl(key, value); +} + +//THESE ARE THE IMPLEMENTATIONS FOR THE FUNCTIONS THAT GET EXPOSED THRU THE API (PLUGINS) +//--------------------------------------------------------------------------------------- + +/* This is the function callback a plugin gets to send data + * to its bucket + */ +void pushDataWrapper(monitordata* data) { + ibmras::monitoring::agent::Agent::getInstance()->addData(data); +} + +int sendMessageWrapper(const char *sourceId, uint32 size, void *data) { + return ibmras::monitoring::agent::Agent::getInstance()->getConnectionManager()->sendMessage(std::string(sourceId), size, data); +} + +/* This is the function callback that a plugin will get if + * they want to log a message. + */ +void logMessageWrapper(loggingLevel lev, const char * message){ + ibmras::monitoring::agent::pluginlogger->log(lev, message); +} + +//THESE ARE THE IMPLEMENTATIONS FOR THE FUNCTIONS THAT GET EXPOSED THRU THE API (CORE) +//--------------------------------------------------------------------------------------- + +void initWrapper() { + ibmras::monitoring::agent::Agent::getInstance()->init(); +} + +void startWrapper() { + ibmras::monitoring::agent::Agent::getInstance()->start(); +} + +void stopWrapper() { + ibmras::monitoring::agent::Agent::getInstance()->stop(); +} + +void shutdownWrapper() { + ibmras::monitoring::agent::Agent::getInstance()->shutdown(); +} + +void setLogLevelsWrapper() { + ibmras::monitoring::agent::Agent::getInstance()->setLogLevels(); +} + +const char* getVersionWrapper() { + const char * retString = ibmras::common::util::createAsciiString(ibmras::monitoring::agent::Agent::getInstance()->getVersion().c_str()); + return retString; +} + +void logCoreMessageWrapper(loggingLevel lev, const char * message){ + ibmras::monitoring::agent::corelogger->log(lev, message); +} + +bool loadPropertiesFileWrapper(const char* fileName) { + return ibmras::monitoring::agent::Agent::getInstance()->loadPropertiesFile(fileName); +} + +void registerZipFunctionWrapper(void(*zipFunc)(const char*)) { + return ibmras::monitoring::agent::Agent::getInstance()->registerZipFunction(zipFunc); +} + +void addPluginWrapper(const char* completeLibraryPath) { + return ibmras::monitoring::agent::Agent::getInstance()->addPlugin(completeLibraryPath); +} + +} // extern "C" + +//-------------------------------------------------------------------- + +namespace ibmras { +namespace monitoring { +namespace agent { + +static const char* PROPERTIES_PREFIX = "com.ibm.diagnostics.healthcenter."; +static const char* HEARTBEAT_TOPIC = "heartbeat"; + +bool running = false; +bool updateNow = false; +bool headlessRunning = false; + +Agent* instance = new Agent; +agentCoreFunctions aCF; + +//Agent* agentInstance = ibmras::monitoring::agent::Agent::getInstance(); + +IBMRAS_DEFINE_LOGGER("Agent"); + +Agent::Agent() { + activeThreadCount = 0; +} + +void Agent::setLogOutput(ibmras::common::LOCAL_LOGGER_CALLBACK func) { + ibmras::common::LogManager::localLogFunc = func; +} + +std::string Agent::getBuildDate() { + return __DATE__ " " __TIME__; +} + +std::string Agent::getVersion() { + return "3.2.0"; +} + +void Agent::setLogLevels() { + + std::string loggingPropertyPrefix = PROPERTIES_PREFIX; + loggingPropertyPrefix +="logging."; + + ibmras::common::LogManager* logMan = ibmras::common::LogManager::getInstance(); + std::list keys = properties.getKeys(loggingPropertyPrefix); + for (std::list::iterator i = keys.begin(); i != keys.end(); + ++i) { + std::string component = i->substr(loggingPropertyPrefix.length()); + if (component.length() > 0) { + std::string value = properties.get(*i); + loggingLevel lev = none; + if (value.compare("warning") == 0) { + lev = warning; + } else if (value.compare("info") == 0) { + lev = info; + } else if (value.compare("fine") == 0) { + lev = fine; + } else if (value.compare("finest") == 0) { + lev = finest; + } else if (value.compare("debug") == 0) { + lev = debug; + } else { + lev = none; + } + logMan->setLevel(component, lev); + } + } + +} + +void Agent::setLocalLog(bool local) { + ibmras::common::LogManager::getInstance()->localLog = local; +} + +DataSourceList Agent::getPullSources() { + return pullSourceList; +} + +DataSourceList Agent::getPushSources() { + return pushSourceList; +} + +DataSource* Agent::getPullSource(std::string uniqueID) { + Agent* agent = Agent::getInstance(); + uint32 pullcount = agent->getPullSources().getSize(); + for (uint32 i = 0; i < pullcount; i++) { + DataSource *dsrc = agent->getPullSources().getItem(i); + if (dsrc->getUniqueID().compare(uniqueID) == 0) { + return dsrc; + } + } + return NULL; +} + +DataSource* Agent::getPushSource(std::string uniqueID) { + Agent* agent = Agent::getInstance(); + uint32 pushcount = agent->getPushSources().getSize(); + for (uint32 i = 0; i < pushcount; i++) { + DataSource *dsrc = agent->getPushSources().getItem(i); + if (dsrc->getUniqueID().compare(uniqueID) == 0) { + return dsrc; + } + } + return NULL; +} + +//THESE ARE THE IMPLEMENTATIONS FOR THE FUNCTIONS THAT GET EXPOSED THRU THE API (PLUGINS) +//--------------------------------------------------------------------------------------- + +/* This is the function callback a plugin gets to send data + * to its bucket + */ +//void pushDataImpl(monitordata* data) { +// Agent* agent = Agent::getInstance(); +// agent->addData(data); +//} +// +//int sendMessageWrapper(const char *sourceId, uint32 size, void *data) { +// return instance->getConnectionManager()->sendMessage(std::string(sourceId), size, data); +//} +// +///* This is the function callback that a plugin will get if +// * they want to log a message. +// */ +//void logMessageImpl(loggingLevel lev, const char * message){ +// pluginlogger->log(lev, message); +//} + +//THIS IMPLEMENTATION IS SHARED BETWEEN THE API EXPOSED TO PLUGINS AND THE ONE EXPOSED TO LOADERS +//----------------------------------------------------------------------------------------------- +const char* getPropertyImpl(const char * key){ + std::string property = Agent::getInstance()->getProperty(std::string(key)); + const char * retString = ibmras::common::util::createAsciiString(property.c_str()); + return retString; +} + +//WE CAN EXPOSE THIS IN THE FUTURE IF WE WANT TO, SO THE PLUGINS CAN ALSO SET PROPERTIES +//-------------------------------------------------------------------------------------- +void setPropertyImpl(const char* key, const char* value) { + + Agent::getInstance()->setProperty(key, value); + +} + + +/* thread entry point for publishing data from buckets to the registered connector */ +void* processPublishLoop(ibmras::common::port::ThreadData* param) { + IBMRAS_DEBUG(info, "Starting agent publishing loop"); + Agent* agent = Agent::getInstance(); + std::string headless = agent->getAgentProperty("headless"); + + int count = 0; + while (running) { + ibmras::common::port::sleep(2); + agent->publish(); + + if (!headless.compare("on") && !agent->isHeadlessRunning()) { + running = false; + agent->stop(); + } + + // Send heartbeat ping every 20 seconds + if (++count > 10) { + count = 0; + agent->getConnectionManager()->sendMessage(HEARTBEAT_TOPIC, 0, NULL); + } + + } + IBMRAS_DEBUG(info, "Exiting agent publishing loop"); + return NULL; +} + +void* endPullSourceLoop(ibmras::common::port::ThreadData* data) { + Agent* agent = Agent::getInstance(); + agent->threadStop(); + return NULL; +} + +void* processPullSourceLoop(ibmras::common::port::ThreadData* data) { + Agent* agent = Agent::getInstance(); + uint32 pullcount = agent->getPullSources().getSize(); + + ibmras::monitoring::agent::threads::ThreadPool pool; + + for (uint32 i = 0; i < pullcount; i++) { + DataSource *dsrc = agent->getPullSources().getItem(i); + if (!(dsrc->getSource()->callback && dsrc->getSource()->complete)) { + IBMRAS_DEBUG_1(warning, "Pull source %s disabled due to missing callback or complete function", + dsrc->getUniqueID().c_str()) + } else { + pool.addPullSource(dsrc->getSource()); + } + } + + IBMRAS_DEBUG(info, "Starting agent process pull source loop"); + + pool.startAll(); + while (running) { + ibmras::common::port::sleep(1); /* polling interval for thread */ + if (running) { + pool.process(updateNow); /* process the pull sources */ + updateNow = false; + } + } + +#if defined(_WINDOWS) || defined(_ZOS) + pool.stopAll(); + agent->threadStop(); +#endif + + IBMRAS_DEBUG(info, "Exiting agent process pull source loop"); + ibmras::common::port::exitThread(NULL); + return NULL; +} + +void Agent::immediateUpdate() { + updateNow = true; +} + +void Agent::publish() { + bucketList.publish(connectionManager); +} + +void Agent::republish(const std::string &topicPrefix) { + bucketList.republish(topicPrefix, connectionManager); +} + + +void Agent::addPushSource(std::vector::iterator i, + uint32 provID) { + if ((*i)->push) { + pushsource *push = (*i)->push(aCF, provID); + if (push) { + IBMRAS_DEBUG(debug, "Push sources were defined"); + pushSourceList.add(provID, push, (*i)->name); + IBMRAS_DEBUG_1(debug, "Push source list size now : %d", + pushSourceList.getSize()); + IBMRAS_DEBUG(debug, pushSourceList.toString().c_str()); + } else { + IBMRAS_DEBUG(info, "No sources were defined !"); + } + } +} + +void Agent::addPullSource(std::vector::iterator i, + uint32 provID) { + if ((*i)->pull) { + pullsource *pull = (*i)->pull(aCF,provID); + if (pull) { + IBMRAS_DEBUG(debug, "Pull sources were defined"); + pullSourceList.add(provID, pull, (*i)->name); + IBMRAS_DEBUG_1(info, "Pull source list size now : %d", + pullSourceList.getSize()); + IBMRAS_DEBUG(debug, pullSourceList.toString().c_str()); + } else { + IBMRAS_DEBUG(info, "No pull sources were defined !"); + } + } +} + +/* + * Add a bucket to the overall list of data buckets + */ +void Agent::createBuckets() { + IBMRAS_DEBUG(fine, "Creating buckets"); + bucketList.add(pushSourceList.getBuckets()); + bucketList.add(pullSourceList.getBuckets()); +} + +void Agent::addPlugin(ibmras::monitoring::Plugin* plugin) { + if (plugin) { + IBMRAS_DEBUG_1(info, "Adding plugin %s", plugin->name.c_str());IBMRAS_DEBUG_4(info, "Push source %p, Pull source %p, start %p, stop %p", + plugin->push, plugin->pull, plugin->start, plugin->stop); + IBMRAS_LOG_2(fine, "%s, version %s", (plugin->name).c_str(), (plugin->getVersion())); + plugins.push_back(plugin); + IBMRAS_DEBUG(info, "Plugin added"); + } else { + IBMRAS_DEBUG(warning, "Attempt to add null plugin"); + } +} + +void Agent::addPlugin(const std::string &dir, const std::string library) { + ibmras::monitoring::Plugin *plugin = ibmras::monitoring::Plugin::processLibrary(dir + PATHSEPARATOR + LIBPREFIX + library + LIBSUFFIX); + if (plugin) { + IBMRAS_LOG_2(fine, "%s, version %s", (plugin->name).c_str(), (plugin->getVersion())); + plugins.push_back(plugin); + } +} + +void Agent::addPlugin(const char* completeLibraryPath) { + ibmras::monitoring::Plugin *plugin = ibmras::monitoring::Plugin::processLibrary(std::string(completeLibraryPath)); + if (plugin) { + IBMRAS_LOG_2(fine, "%s, version %s", (plugin->name).c_str(), (plugin->getVersion())); + plugins.push_back(plugin); + } +} + + +void Agent::addSystemPlugins() { +// addPlugin(ibmras::common::LogManager::getPlugin()); + addPlugin( + (ibmras::monitoring::Plugin*) new ibmras::monitoring::agent::SystemReceiver()); +} + +void Agent::addConnector(ibmras::monitoring::connector::Connector* con) { + connectionManager.addConnector(con); +} +void Agent::removeConnector(ibmras::monitoring::connector::Connector* con) { + connectionManager.removeConnector(con); +} + +void Agent::registerZipFunction(void(*zipFunc)(const char*)) { + zipFunction = zipFunc; +} + +void Agent::zipHeadlessFiles(const char* dir) { + if (zipFunction == NULL) { + IBMRAS_LOG(warning, "Zip called for by headless plugin, but no zip function set on Agent."); + } else { + zipFunction(dir); + } +} + +void Agent::init() { + IBMRAS_DEBUG(info, "Agent initialisation : start"); + aCF.agentPushData = pushDataWrapper; + aCF.agentSendMessage = sendMessageWrapper; + aCF.logMessage = logMessageWrapper; + aCF.getProperty = getPropertyWrapper; + + std::string searchPath = getAgentProperty("plugin.path"); + IBMRAS_DEBUG_1(debug, "Plugin search path : %s", searchPath.c_str()); + if (searchPath.size() > 0) { + std::vector found = + ibmras::monitoring::Plugin::scan(searchPath); + plugins.insert(plugins.begin(), found.begin(), found.end()); + } + + addSystemPlugins(); + setProperty("agent.native.build.date", getBuildDate()); + + std::string pluginProperties = properties.toString(); + + IBMRAS_DEBUG_1(info, "%d plugins found", plugins.size()); + uint32 provID = 0; + for (std::vector::iterator i = + plugins.begin(); i != plugins.end(); ++i, provID++) { + IBMRAS_DEBUG_1(fine, "Library : %s", (*i)->name.c_str()); + if ((*i)->init) { + (*i)->init(pluginProperties.c_str()); + } + if ((*i)->type & ibmras::monitoring::plugin::data) { + addPushSource(i, provID); + addPullSource(i, provID); + } + } + createBuckets(); + addConnector(&configConn); + IBMRAS_DEBUG(finest, bucketList.toString().c_str()); +} + +std::string Agent::getConfig(const std::string& name) { + return configConn.getConfig(name); +} + +bool Agent::readOnly() { + std::string readOnlyMode = getAgentProperty("readonly"); + if (!readOnlyMode.compare("on")) { + return true; + } + return false; +} + + +void Agent::start() { + int result = 0; + IBMRAS_DEBUG(info, "Agent start : begin"); + + /* Receivers first as they are added to connection manager */ + IBMRAS_DEBUG(info, "Agent start : receivers"); + startReceivers(); + + /* Connectors must be started before the plugins start pushing data */ + IBMRAS_DEBUG(info, "Agent start : connectors"); + startConnectors(); + + IBMRAS_DEBUG(info, "Agent start : data providers"); + startPlugins(); + + running = true; /* if any of the thread creation below fails then running will be set to false and started threads will exit */ + + ibmras::common::port::ThreadData* data = + new ibmras::common::port::ThreadData(processPullSourceLoop, endPullSourceLoop); + result = ibmras::common::port::createThread(data); + if (result) { + running = false; + } else { + activeThreadCount++; + data = new ibmras::common::port::ThreadData(processPublishLoop); + result = ibmras::common::port::createThread(data); + if (result) { + running = false; + } + } + IBMRAS_DEBUG(info, "Agent start : finish"); +} + +void Agent::startPlugins() { + for (std::vector::iterator i = + plugins.begin(); i != plugins.end(); ++i) { + if ((*i)->start) { + IBMRAS_DEBUG_1(info, "Invoking plugin start method %s", + (*i)->name.c_str()); + (*i)->start(); + } else { + IBMRAS_DEBUG_1(info, "Warning : no start method defined on %s", + (*i)->name.c_str()); + } + } +} +void Agent::stopPlugins() { + for (std::vector::iterator i = + plugins.begin(); i != plugins.end(); ++i) { + if ((*i)->stop) { + IBMRAS_DEBUG_1(info, "Invoking plugin stop method %s", + (*i)->name.c_str()); + (*i)->stop(); + } else { + IBMRAS_DEBUG_1(info, "Warning : no stop method defined on %s", + (*i)->name.c_str()); + } + } +} + +BucketList* Agent::getBucketList() { + return &bucketList; +} + + +void Agent::startReceivers() { + for (std::vector::iterator i = + plugins.begin(); i != plugins.end(); ++i) { + if ((*i)->type & ibmras::monitoring::plugin::receiver) { + if ((*i)->recvfactory) { + void* instance = (*i)->recvfactory(); + ibmras::monitoring::connector::Receiver* receiver = + reinterpret_cast(instance); + if (receiver) { + IBMRAS_DEBUG_1(info, "Add receiver %s to connector manager", + (*i)->name.c_str()); + connectionManager.addReceiver(receiver); + } + } else if ((*i)->receiveMessage) { + ibmras::monitoring::connector::Receiver* receiver = + new ibmras::monitoring::AgentExtensionReceiver((*i)->receiveMessage); + if (receiver) { + IBMRAS_DEBUG_1(info, "Add extension receiver %s to connector manager", + (*i)->name.c_str()); + connectionManager.addReceiver(receiver); + } + } + } + } +} + +void Agent::startConnectors() { + std::string connectorProperties = properties.toString(); + for (std::vector::iterator i = + plugins.begin(); i != plugins.end(); ++i) { + IBMRAS_DEBUG_2(info, "Agent::startConnectors %s type is %d", (*i)->name.c_str(), + (*i)->type); + if ((*i)->type & ibmras::monitoring::plugin::connector) { + IBMRAS_DEBUG(info, "it is a connector"); + if ((*i)->confactory) { + IBMRAS_DEBUG_1(info, "Invoking factory method for %s", + (*i)->name.c_str()); + void* instance = (*i)->confactory(connectorProperties.c_str()); + ibmras::monitoring::connector::Connector* con = + reinterpret_cast(instance); + if (con) { + IBMRAS_DEBUG(info, "Add connector to connector manager"); + connectionManager.addConnector(con); + // Register the receiver with each connector + con->registerReceiver(&connectionManager); + } + } else { + IBMRAS_DEBUG_1(info, "Warning : no factory method defined on %s", + (*i)->name.c_str()); + }; + } + } + connectionManager.start(); +} + +void Agent::stop() { + if(running) { + IBMRAS_DEBUG(info, "Agent stop : begin"); + running = false; + IBMRAS_DEBUG(fine, "Waiting for active threads to stop"); +#if defined(_WINDOWS) || defined(_ZOS) + while (activeThreadCount) { + ibmras::common::port::sleep(1); + IBMRAS_DEBUG_1(debug, "Checking thread count - current [%d]", + activeThreadCount); + } +#else + ibmras::common::port::stopAllThreads(); +#endif + + IBMRAS_DEBUG(fine, "All active threads now quit"); + + + stopPlugins(); + connectionManager.stop(); + connectionManager.removeAllReceivers(); + + IBMRAS_DEBUG(info, "Agent stop : finish"); + } +} + +void Agent::shutdown() { + + IBMRAS_DEBUG(info, "Agent shutdown : begin"); + std::string str = bucketList.toString(); + IBMRAS_DEBUG(info, str.c_str()); + IBMRAS_DEBUG(info, "Agent shutdown : finish"); +} + +ibmras::monitoring::connector::Connector* Agent::getConnector( + const std::string &id) { + return connectionManager.getConnector(id); +} + +ibmras::monitoring::connector::ConnectorManager* Agent::getConnectionManager() { + return &connectionManager; +} + + +Agent* Agent::getInstance() { + return instance; +} + +bool Agent::addData(monitordata* data) { + return bucketList.addData(data); +} + +void Agent::threadStop() { + activeThreadCount--; + IBMRAS_DEBUG_1(debug, "Number of active threads %d", activeThreadCount); +} + + +void Agent::setProperties(const ibmras::common::Properties& props) { + properties.add(props); +} + +ibmras::common::Properties Agent::getProperties() { + return properties; +} + +void Agent::setProperty(const std::string& prop, const std::string& value) { + properties.put(prop, value); +} + + + +std::string Agent::getProperty(const std::string& prop) { + return properties.get(prop); +} + + +bool Agent::propertyExists(const std::string& prop) { + return properties.exists(prop); +} + +std::string Agent::getAgentPropertyPrefix() { + return PROPERTIES_PREFIX; +} + +std::string Agent::getAgentProperty(const std::string& agentProp) { + return getProperty(getAgentPropertyPrefix() + agentProp); +} + +void Agent::setAgentProperty(const std::string& agentProp, const std::string& value) { + setProperty(getAgentPropertyPrefix() + agentProp, value); +} + +bool Agent::agentPropertyExists(const std::string& agentProp) { + return propertyExists(getAgentPropertyPrefix() + agentProp); +} + +bool Agent::isHeadlessRunning(){ + return headlessRunning; +} + +void Agent::setHeadlessRunning(bool isRunning){ + headlessRunning = isRunning; + + // if we are in proper headless mode, then we need to toggle on/off + // whether the agent is running to allow late attach to query this property + std::string dataCollectionLevel = getAgentProperty("data.collection.level"); + if (ibmras::common::util::equalsIgnoreCase(dataCollectionLevel,"headless")) { + if(headlessRunning) { + setProperty("com.ibm.java.diagnostics.healthcenter.running", "true"); + } else { + setProperty("com.ibm.java.diagnostics.healthcenter.running", "false"); + } + } +} + +bool Agent::loadPropertiesFile(const char* filestr){ + std::string filename(filestr); + ibmras::common::PropertiesFile props; + if (!props.load(filename)) { + setProperties(props); + return true; + } else { + return false; + } +} + + +} +} +} /* end namespace monitoring */ + diff --git a/Sources/agentcore/ibmras/monitoring/agent/Agent.h b/Sources/agentcore/ibmras/monitoring/agent/Agent.h new file mode 100644 index 0000000..43fdd35 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/Agent.h @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_agent_agent_h +#define ibmras_monitoring_agent_agent_h + +#include "AgentExtensions.h" +#include "../Typesdef.h" +#include "DataSource.h" +#include "DataSourceList.h" +#include "../connector/Connector.h" +#include "BucketList.h" +#include "../connector/ConnectorManager.h" +#include "../Plugin.h" +#include "../../common/Logger.h" +#include "../../common/LogManager.h" +#include "../../common/Properties.h" +#include "../connector/configuration/ConfigurationConnector.h" + +#include +/* + * Definitions for the internal workings of the agent + * + */ + +extern "C" { + +void initWrapper(); +void startWrapper(); +void stopWrapper(); +void shutdownWrapper(); +void setLogLevelsWrapper(); +const char* getVersionWrapper(); +void logCoreMessageWrapper(loggingLevel lev, const char * message); +void setPropertyWrapper(const char* key, const char* value); +const char* getPropertyWrapper(const char* key); +bool loadPropertiesFileWrapper(const char* fileName); +void registerZipFunctionWrapper(void(*zF)(const char*)); +void addPluginWrapper(const char*); + +} + +namespace ibmras { +namespace monitoring { +namespace agent { + +void setPropertyImpl(const char* key, const char* value); +const char* getPropertyImpl(const char* key); + +class DECL Agent{ +public: + static Agent* getInstance(); /* return the singleton instance of the agent */ + void init(); /* invoke to start the agent initialisation lifecycle event */ + void start(); /* invoke to start the agent start lifecycle event */ + void stop(); /* invoke to start the agent stop lifecycle event */ + void shutdown(); /* invoke to shutdown the agent, it cannot be restarted after this */ + bool loadPropertiesFile(const char* filename); + /* the location of the healthcenter.properties file to load */ + + static std::string getBuildDate(); + static std::string getVersion(); + + void addConnector(ibmras::monitoring::connector::Connector* con); + void removeConnector(ibmras::monitoring::connector::Connector* con); + DataSourceList getPullSources(); + DataSourceList getPushSources(); + DataSource* getPullSource(std::string uniqueID); + DataSource* getPushSource(std::string uniqueID); + BucketList* getBucketList(); + + ibmras::monitoring::connector::Connector* getConnector(const std::string &id); + ibmras::monitoring::connector::ConnectorManager* getConnectionManager(); + bool addData(monitordata* data); + + void publish(); /* publish messages to connectors */ + void republish(const std::string &prefix); /* republish history */ + void immediateUpdate(); /* Signal immediate update from pullsources */ + + void threadStop(); /* fired when an agent processing thread stops */ + void setLogOutput(ibmras::common::LOCAL_LOGGER_CALLBACK func); + void setLogLevels(); + void setLocalLog(bool local); + void addPlugin(ibmras::monitoring::Plugin* plugin); /* manually add a plugin to the agent */ + void addPlugin(const std::string &dir, const std::string library); /* manually add a plugin to the agent */ + void addPlugin(const char* completeLibraryPath); /* manually add a plugin to the agent */ + + ibmras::common::Properties getProperties(); + void setProperties(const ibmras::common::Properties &props); + void setProperty(const std::string &prop, const std::string &value); + std::string getProperty(const std::string &prop); + bool propertyExists(const std::string &prop); + std::string getAgentPropertyPrefix(); + std::string getAgentProperty(const std::string &agentProp); + void setAgentProperty(const std::string &agentProp, const std::string &value); + bool agentPropertyExists(const std::string &agentProp); + + bool isHeadlessRunning(); + void setHeadlessRunning(bool); + + std::string getConfig(const std::string& name); + bool readOnly(); + void registerZipFunction(void(*zipFn)(const char*)); + void zipHeadlessFiles(const char* dir); + + Agent(); /* public constructor */ + +private: + void addPushSource(std::vector::iterator i, uint32 provID); + void addPullSource(std::vector::iterator i, uint32 provID); + void addSystemPlugins(); /* adds agent internal / system push or pull sources */ + void createBuckets(); + void startPlugins(); /* call the start method on each plugin */ + void stopPlugins(); /* call the stop method on each plugin */ + void startConnectors(); /* initialise the connectors */ + void startReceivers(); /* initialise any receivers */ + BucketList bucketList; + + ibmras::monitoring::connector::ConnectorManager connectionManager; + DataSourceList pushSourceList; + DataSourceList pullSourceList; + std::vector plugins; + uint32 activeThreadCount; /* number of active threads */ + //static Agent* instance; /* singleton instance */ + ibmras::common::Properties properties; + ibmras::monitoring::connector::ConfigurationConnector configConn; + void(*zipFunction)(const char*); + +}; +} +} +} /* end namespace agent */ + + +#endif /* ibmras_monitoring_agent_agent_h */ diff --git a/Sources/agentcore/ibmras/monitoring/agent/Bucket.cpp b/Sources/agentcore/ibmras/monitoring/agent/Bucket.cpp new file mode 100644 index 0000000..26ee026 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/Bucket.cpp @@ -0,0 +1,398 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "Bucket.h" +#include "../../common/common.h" +#include "../../common/logging.h" +#include "../../common/MemoryManager.h" +#include +#include + +namespace ibmras { +namespace monitoring { +namespace agent { +namespace bucket { +IBMRAS_DEFINE_LOGGER("Bucket") +; +} +using namespace bucket; + +Bucket::Bucket(uint32 provID, uint32 sourceID, uint32 capacity, + const std::string &uniqueID) { + this->provID = provID; + this->sourceID = sourceID; + this->capacity = capacity; + publishSize = 1024 * 1024; + this->uniqueID = uniqueID; + count = 0; + size = 0; + head = NULL; + tail = NULL; + masterID = 0; + lock = new ibmras::common::port::Lock; + lastPublish = 0; + IBMRAS_DEBUG_4(fine, "Bucket created for: %s, provider id: %d, source id: %d, capacity: %d", uniqueID.c_str(), provID, sourceID, capacity); +} + +Bucket::BucketData::BucketData(monitordata* mdata) : + id(0), persistentData(mdata->persistent), size(0), data(NULL), next( + NULL) { + if (mdata->size > 0 && mdata->data != NULL) { + this->size = mdata->size; + data = ibmras::common::memory::allocate(size); + if (data != NULL) { + memcpy(this->data, mdata->data, size); + } + } +} + +Bucket::BucketData::~BucketData() { + if (data) { + ibmras::common::memory::deallocate(&data); + } +} + +void Bucket::publish(ibmras::monitoring::connector::Connector &con) { + if (!lock->acquire()) { + if (!lock->isDestroyed()) { + + uint32 maxSendSize = size; + if (maxSendSize > publishSize) { + maxSendSize = publishSize; + } + + unsigned char* dataToSend = ibmras::common::memory::allocate( + maxSendSize); + uint32 sizeToSend = 0; + + uint32 lastidsent = lastPublish; + BucketData* current = head; + while (current) { + if ((current->id > lastPublish) || !lastPublish) { + if ((sizeToSend > 0) + && ((sizeToSend + current->size) > maxSendSize)) { + // We have a batch and the next will not fit in the buffer so send it + IBMRAS_DEBUG_2(fine, "publishing batched message to %s of %d bytes", + uniqueID.c_str(), sizeToSend); + con.sendMessage(uniqueID, sizeToSend, dataToSend); + sizeToSend = 0; + } + + if (dataToSend + && ((sizeToSend + current->size) <= maxSendSize)) { + // We are batching the messages and this will fit in the buffer + // Batch the message in the buffer + memcpy(dataToSend + sizeToSend, current->data, + current->size); + sizeToSend += current->size; + + } else { + // Publish from bucket + IBMRAS_DEBUG_2(fine, "publishing message to %s of %d bytes", + uniqueID.c_str(), current->size); + con.sendMessage(uniqueID, current->size, current->data); + } + lastidsent = current->id; + } + current = current->next; + } + // Publish any remaining batched data + if (dataToSend && (sizeToSend > 0)) { + IBMRAS_DEBUG_2(fine, "publishing batched message to %s of %d bytes", + uniqueID.c_str(), sizeToSend); + con.sendMessage(uniqueID, sizeToSend, dataToSend); + } + lastPublish = lastidsent; + ibmras::common::memory::deallocate(&dataToSend); + + lock->release(); + } + } +} + +bool Bucket::spill(uint32 entrysize) { + + BucketData* bdata; /* used to manage the bucket data */ + uint32 i = 0; + + BucketData *cursor = head; + BucketData *prev = NULL; + while (((size + entrysize) > capacity) && (cursor != NULL) + && (cursor->id <= lastPublish)) { + if (!cursor->persistentData) { + bdata = cursor; + size -= bdata->size; + count--; + i++; + if (prev == NULL) { + head = bdata->next; + } else { + prev->next = bdata->next; + } + cursor = cursor->next; + delete bdata; + } else { + prev = cursor; + cursor = prev->next; + } + } + if (!head) { + tail = NULL; /* emptied the queue so there is no tail now either */ + } + + if (head && ((size + entrysize) > capacity)) { + // No room within capacity + return false; + } + + IBMRAS_DEBUG_1(debug, "Removed %d entries from the bucket", i); + + IBMRAS_DEBUG_4(debug, "Bucket stats [%d:%d] : count = %d, size = %d", provID, + sourceID, count, size); + + return true; + +} + +bool Bucket::add(monitordata* data) { + + if ((data->provID != provID) || (data->sourceID != sourceID)) { + IBMRAS_DEBUG_4(info, + "Wrong data sent to bucket : received %d:%d, expected %d, %d", + data->provID, data->sourceID, provID, sourceID); + return false; /* data not added as provider and source IDs do not match */ + } + bool added = false; + + if (!lock->acquire()) { + if (!lock->isDestroyed()) { + if (spill(data->size)) { + BucketData* bdata = new BucketData(data); + if (bdata->data == NULL) { + IBMRAS_DEBUG_2(warning, "Unable to allocate memory for %s data of size %d", uniqueID.c_str(), bdata->size); + delete bdata; + } else { + bdata->id = ++masterID; + + if (tail) { + tail->next = bdata; /* add new entry to tail */ + tail = bdata; /* make a new tail */ + } else { + head = bdata; + tail = bdata; + } + count++; + size += data->size; + added = true; + } + } else { + IBMRAS_DEBUG_2(warning, "No room in bucket %s for data of size %d", uniqueID.c_str(), data->size); + } + lock->release(); + } + } + + IBMRAS_DEBUG_4(debug, + "Bucket data [%s] : data size = %d, bucket size = %d, count = %d", + uniqueID.c_str(), data->size, size, count); + return added; /* data added to bucket */ +} + +uint32 Bucket::getNextData(uint32 id, int32 &dataSize, void* *data, + uint32 &droppedCount) { + uint32 returnId = id; + droppedCount = 0; + *data = NULL; + if (!lock->acquire()) { + if (!lock->isDestroyed()) { + uint32 requestedSize = dataSize; + dataSize = 0; + + BucketData* current = head; + while (current) { + if (current->id > id) { + droppedCount = current->id - (id + 1); + + // Calculate size to return + BucketData* dataToSend = current; + uint32 bufferSize = 0; + if (requestedSize == 0) { + bufferSize = current->size; + } else { + + while (dataToSend) { + bufferSize += dataToSend->size; + if (requestedSize > 0 + && bufferSize > requestedSize) { + break; + } + if (dataToSend->next) { + droppedCount += (dataToSend->next->id + - (dataToSend->id + 1)); + } + dataToSend = dataToSend->next; + } + + } + // Allocate buffer + unsigned char* buffer = ibmras::common::memory::allocate( + bufferSize); + + if (buffer == NULL) { + IBMRAS_DEBUG_1(warning, "Unable to allocate buffer of %d", bufferSize); + break; + } + dataToSend = current; + while (dataToSend) { + if ((dataToSend->size + dataSize) > bufferSize) { + break; + + } + // copy data to buffer + unsigned char* dataPtr = dataToSend->data; + memcpy(buffer + dataSize, dataPtr, dataToSend->size); + dataSize += dataToSend->size; + returnId = dataToSend->id; + dataToSend = dataToSend->next; + } + *data = buffer; + + break; + } + current = current->next; + } + lock->release(); + } + } + + return returnId; +} + +/* + * NOTE This method has NO locking as it is intended to be called by the thread that + * already owns the bucket lock, ie from connectors called by the publish method + * + * NOTE as the caller has the lock we trust them with the data pointer rather than a copy + */ +uint32 Bucket::getNextPersistentData(uint32 id, uint32& dataSize, void** data) { + uint32 returnId = id; + + IBMRAS_DEBUG(debug, "in Bucket::getNextPersistentData()"); + + IBMRAS_DEBUG(debug, "in Bucket::getNextPersistentData() lock acquired"); + dataSize = 0; + *data = NULL; + + BucketData* current = head; + while (current && current->id <= lastPublish) { + if (current->id > id && current->persistentData) { + IBMRAS_DEBUG_1(debug, "in Bucket::getNextPersistentData() persistent entry found id", current->id); + // Allocate buffer + dataSize = current->size; + *data = current->data; + returnId = current->id; + break; + } + current = current->next; + } + + return returnId; +} + +void Bucket::republish(const std::string &topicPrefix, + ibmras::monitoring::connector::Connector &con) { + IBMRAS_DEBUG_1(debug, "in Bucket::republish for %s", uniqueID.c_str()); + if (!lock->acquire()) { + if (!lock->isDestroyed()) { + + uint32 maxSendSize = size; + if (maxSendSize > publishSize) { + maxSendSize = publishSize; + } + + unsigned char* dataToSend = ibmras::common::memory::allocate( + maxSendSize); + uint32 sizeToSend = 0; + + std::string messageTopic = topicPrefix + uniqueID; + + BucketData* current = head; + while (current && (current->id <= lastPublish)) { + + if ((sizeToSend > 0) + && ((sizeToSend + current->size) > maxSendSize)) { + // We have a batch and the next will not fit in the buffer so send it + IBMRAS_DEBUG_2(fine, "publishing batched message to %s of %d bytes", + messageTopic.c_str(), sizeToSend); + con.sendMessage(messageTopic, sizeToSend, dataToSend); + sizeToSend = 0; + } + + if (dataToSend + && ((sizeToSend + current->size) <= maxSendSize)) { + // We are batching the messages and this will fit in the buffer + // Batch the message in the buffer + memcpy(dataToSend + sizeToSend, current->data, + current->size); + sizeToSend += current->size; + + } else { + // Publish from bucket + IBMRAS_DEBUG_2(fine, "publishing message to %s of %d bytes", + messageTopic.c_str(), current->size); + con.sendMessage(messageTopic, current->size, current->data); + } + current = current->next; + } + + // Publish any remaining batched data + if (dataToSend && (sizeToSend > 0)) { + IBMRAS_DEBUG_2(fine, "publishing batched message to %s of %d bytes", + messageTopic.c_str(), sizeToSend); + con.sendMessage(messageTopic, sizeToSend, dataToSend); + } + ibmras::common::memory::deallocate(&dataToSend); + + con.sendMessage(messageTopic, 0, NULL); + lock->release(); + } + } +} + +std::string Bucket::toString() { + std::stringstream str; + str << "Bucket [" << common::itoa(provID) << ":" << common::itoa(sourceID) + << "], capacity = " << common::itoa(capacity) << ", count = " + << common::itoa(count) << ", used = " << common::itoa(size) + << '\n'; + return str.str(); +} + +uint32 Bucket::getProvID() { + return provID; +} + +uint32 Bucket::getSourceID() { + return sourceID; +} + +std::string Bucket::getUniqueID() { + return uniqueID; +} + +} +} +} /* end namespace agent */ diff --git a/Sources/agentcore/ibmras/monitoring/agent/Bucket.h b/Sources/agentcore/ibmras/monitoring/agent/Bucket.h new file mode 100644 index 0000000..fafc414 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/Bucket.h @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#ifndef ibmras_monitoring_bucket_h +#define ibmras_monitoring_bucket_h + +#include "../../common/port/ThreadData.h" +#include "../../common/port/Lock.h" +#include "../connector/Connector.h" +#include "AgentExtensions.h" +#include "../Typesdef.h" + +namespace ibmras { +namespace monitoring { +namespace agent { + +/* a bucket holds a set of monitor data */ +class DECL Bucket { + +public: + Bucket(uint32 provID, uint32 sourceID, uint32 capacity, const std::string& uniqueID); + bool add(monitordata* entry); /* adds monitor data to the bucket */ + std::string toString(); /* debug / log string version */ + uint32 getProvID(); + uint32 getSourceID(); + std::string getUniqueID(); + void publish(ibmras::monitoring::connector::Connector &con); /* publish bucket contents to the connector manager */ + uint32 getNextData(uint32 id, int32 &size, void* *data, uint32 &droppedCount); + uint32 getNextPersistentData(uint32 id, uint32 &size, void* *data); + void republish(const std::string &topicPrefix, ibmras::monitoring::connector::Connector &con); +private: + bool spill(uint32 size); /* spill bucket contents until there is the requested space */ + + /* bucket data builds on the monitor data to add control meta-data. It also removes any + * unnecessary or repeated data elements + */ + class BucketData { + public: + BucketData(monitordata* mdata); + virtual ~BucketData(); + uint32 id; /* used by clients to request ranges of data */ + bool persistentData; + uint32 size; /* amount of data being provided */ + unsigned char *data; /* char array of the data to store */ + BucketData* next; /* next item in the bucket or null if this is the last item */ + }; + + uint32 provID; + uint32 sourceID; + std::string uniqueID; /*Name of the uniqueID plugin providing the name */ + BucketData* head; /* when a bucket over flows then items are removed from the head */ + BucketData* tail; /* when items are added to the bucket they are added to the tail */ + uint32 lastPublish;/* the last entry published */ + uint32 capacity; /* maximum capacity for the bucket in bytes */ + uint32 publishSize; /* max data to send */ + uint32 size; /* current size of the bucket */ + uint32 count; /* number of items in the bucket */ + uint32 masterID; /* master ID for items placed in the bucket */ + ibmras::common::port::Lock* lock; /* lock to prevent spills whilst publishing/sending */ + +}; + + +} +} +} /* end namespace agent */ + +#endif /* ibmras_monitoring_bucket_h */ diff --git a/Sources/agentcore/ibmras/monitoring/agent/BucketList.cpp b/Sources/agentcore/ibmras/monitoring/agent/BucketList.cpp new file mode 100644 index 0000000..9d9d598 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/BucketList.cpp @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "BucketList.h" +#include "../../common/logging.h" +#include + +namespace ibmras { +namespace monitoring { +namespace agent { + +namespace bucket { +extern IBMRAS_DECLARE_LOGGER; +} +using namespace bucket; + + + + + +bool BucketList::add(Bucket* bucket) { + IBMRAS_DEBUG(fine, "BucketList::add(Bucket* bucket) adding a bucket"); + buckets.push_back(bucket); + return true; +} + +std::string BucketList::toString() { + std::stringstream str; + str << "Bucket list : start\n"; + for (std::vector::iterator i = buckets.begin(); i != buckets.end(); + ++i) { + str << (*i)->toString(); + } + return str.str(); +} + +bool BucketList::add(std::vector buckets) { + IBMRAS_DEBUG(fine, + "BucketList::add(std::vector buckets) adding a bucket"); + bool result = true; + for (uint32 i = 0; i < buckets.size(); i++) { + add(buckets[i]); + } + return result; /* cumulative result of all additions */ +} + +Bucket* BucketList::findBucket(uint32 provID, uint32 sourceID) { + for (uint32 i = 0; i < buckets.size(); i++) { + Bucket* b = buckets[i]; + if ((b->getProvID() == provID) && (b->getSourceID() == sourceID)) { + return b; /* found a matching bucket */ + } + } + return NULL; /* did not find a matching bucket */ +} + +Bucket* BucketList::findBucket(const std::string &uniqueID) { + for (uint32 i = 0; i < buckets.size(); i++) { + Bucket* b = buckets[i]; + + if (uniqueID.compare((b->getUniqueID())) == 0) { + return b; /* found a matching bucket */ + } + } + return NULL; /* did not find a matching bucket */ +} + +void BucketList::publish(ibmras::monitoring::connector::Connector &con) { + for (uint32 i = 0; i < buckets.size(); i++) { + Bucket* b = buckets[i]; + b->publish(con); + } +} + +void BucketList::republish(const std::string &prefix, ibmras::monitoring::connector::Connector &con) { + for (uint32 i = 0; i < buckets.size(); i++) { + Bucket* b = buckets[i]; + b->republish(prefix, con); + } +} + +bool BucketList::addData(monitordata* data) { + if (data != NULL && (data->size > 0 && data->data != NULL)) { + Bucket* b = findBucket(data->provID, data->sourceID); + if (b) { + return b->add(data); /* found a matching bucket so add the data*/ + } + + IBMRAS_DEBUG_2(warning, "Attempted to add data to missing bucket [%d:%d]", + data->provID, data->sourceID); + } + return false; +} + +std::vector BucketList::getIDs() { + std::vector ids; + + for (std::vector::iterator i = buckets.begin(); i != buckets.end(); + ++i) { + ids.push_back(((*i)->getUniqueID())); + } + + return ids; +} + +} +} +} /* end namespace agent */ diff --git a/Sources/agentcore/ibmras/monitoring/agent/BucketList.h b/Sources/agentcore/ibmras/monitoring/agent/BucketList.h new file mode 100644 index 0000000..bc507c8 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/BucketList.h @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#ifndef ibmras_monitoring_bucketlist_h +#define ibmras_monitoring_bucketlist_h + +#include "../../common/port/ThreadData.h" +#include "../../common/port/Lock.h" +#include "../connector/Connector.h" +#include "AgentExtensions.h" +#include "../Typesdef.h" +#include "Bucket.h" + +namespace ibmras { +namespace monitoring { +namespace agent { + + +/* the list of all available buckets */ +class DECL BucketList { +public: + bool add(Bucket* bucket); /* add a bucket to the list */ + bool add(std::vector buckets); /* add multiple buckets in one go */ + Bucket* findBucket(uint32 provID, uint32 sourceID); /* find a bucket for a given provider */ + Bucket* findBucket(const std::string &uniqueID); + void publish(ibmras::monitoring::connector::Connector &con); /* publish all bucket contents */ + void republish(const std::string &prefix, ibmras::monitoring::connector::Connector &con); + bool addData(monitordata* data); + std::vector getIDs(); + std::string toString(); /* debug / log string version */ +private: + std::vector buckets; /* start of the list of buckets */ +}; +} +} +} /* end namespace agent */ + +#endif /* ibmras_monitoring_bucketlist_h */ diff --git a/Sources/agentcore/ibmras/monitoring/agent/DataSource.h b/Sources/agentcore/ibmras/monitoring/agent/DataSource.h new file mode 100644 index 0000000..1626277 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/DataSource.h @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_agent_datasource_h +#define ibmras_monitoring_agent_datasource_h + +#include +#include "../../common/port/ThreadData.h" +#include "Bucket.h" +#include "../../common/common.h" + +/* + * Internal representation of data sources e.g. push or pull sources. + * Because this is a template class, all of the definition needs to go in the + * header file. + */ + +namespace ibmras { +namespace monitoring { +namespace agent { + +/* defines a data source */ +template +class DataSource { +public: + DataSource(uint32 provID, T *src, const std::string &providerName) { + this->provID = provID; + this->src = src; + next = NULL; + this->providerName = providerName; + uniqueID = src->header.name; + } + DataSource *next; + std::string toString(); + uint32 getProvID(); + uint32 getSourceID(); + uint32 getCapacity(); + std::string getProviderName(); + std::string getUniqueID(); + const char* getDescription(); + T* getSource(); +private: + uint32 provID; + std::string providerName; + std::string uniqueID; + T *src; /* source for this provider */ +}; + +template +std::string DataSource::toString() { + std::stringstream str; + str << src->header.name << " (id = " << common::itoa(provID) << ":" << common::itoa(src->header.sourceID) << ")\n"; + return str.str(); +} + +template +uint32 DataSource::getProvID() { + return provID; +} + +template +std::string DataSource::getProviderName() { + return providerName; +} + +template +std::string DataSource::getUniqueID() { + return uniqueID; +} + +template +uint32 DataSource::getSourceID() { + return src->header.sourceID; +} + +template +uint32 DataSource::getCapacity() { + return src->header.capacity; +} + +template +const char* DataSource::getDescription() { + return src->header.description; +} + +template +T* DataSource::getSource() { + return src; +} + +} +} +} /* end namespace agent */ + +#endif /* ibmras_monitoring_agent_datasource_h */ diff --git a/Sources/agentcore/ibmras/monitoring/agent/DataSourceList.h b/Sources/agentcore/ibmras/monitoring/agent/DataSourceList.h new file mode 100644 index 0000000..4c9726d --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/DataSourceList.h @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_agent_datasourcelist_h +#define ibmras_monitoring_agent_datasourcelist_h + +#include "../../common/port/ThreadData.h" +#include "Bucket.h" +#include "../../common/common.h" +#include + +/* + * Internal representation of data sources e.g. push or pull sources. + * Because this is a template class, all of the definition needs to go in the + * header file. + */ + +namespace ibmras { +namespace monitoring { +namespace agent { + +/* + * Q : will data sources reference buckets, or will buckets reference data sources ? + */ +template +class DataSourceList { +public: + void add(uint32 provID, T *src, std::string providerName); + DataSourceList() {head = NULL; size = 0;} + uint32 getSize(); + std::string toString(); + std::vector getBuckets(); + DataSource* getItem(uint32 index); +private: + DataSource *head; + uint32 size; +}; + + +/* + * Add all push/pull sources from a particular provider to the master data source list + */ +template +void DataSourceList::add(uint32 provID, T *src, std::string providerName) { + DataSource *dsrc = NULL; + DataSource *insertAt = NULL; + while(src) { + size++; + dsrc = new DataSource(provID, src, providerName); + if(!insertAt) { + if(head) { /* items are already in the list */ + insertAt = head; + while(insertAt->next) { + insertAt = insertAt->next; + } + } else { + head = dsrc; + insertAt = head; /* nothing in list at the moment so this is the new head */ + src = src->next; + continue; + } + } + insertAt->next = dsrc; + insertAt = dsrc; + src = src->next; + } +} + +template +uint32 DataSourceList::getSize() { + return size; +} + +template +std::string DataSourceList::toString() { + DataSource *src = head; + std::stringstream str; + str << "Data source list : size = " << common::itoa(getSize()) << '\n'; + while(src) { + str << src->toString(); + src = src->next; + } + return str.str(); +} + +template +std::vector DataSourceList::getBuckets() { + std::vector buckets; + DataSource *src = head; + while(src) { + Bucket* bucket = new Bucket(src->getProvID(), src->getSourceID(), src->getCapacity(),src->getUniqueID()); + buckets.push_back(bucket); + src = src->next; + } + return buckets; +} + + +/* could improve this by remembering the last index/value as this is likely used from an iterator */ +template +DataSource* DataSourceList::getItem(uint32 index) { + uint32 count = 0; + DataSource *src = head; + while(src && (count++ < index)) { + src = src->next; + } + return src; +} + + +} +} +} /* end namespace agent */ + +#endif /* ibmras_monitoring_agent_datasourcelist_h */ diff --git a/Sources/agentcore/ibmras/monitoring/agent/SystemReceiver.cpp b/Sources/agentcore/ibmras/monitoring/agent/SystemReceiver.cpp new file mode 100644 index 0000000..32145bc --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/SystemReceiver.cpp @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#include "SystemReceiver.h" +#include "../connector/Receiver.h" +#include "../Plugin.h" +#include "Agent.h" +#include "../connector/configuration/ConfigurationConnector.h" +#include + +namespace ibmras { +namespace monitoring { +namespace agent { + +const char* sysRecVersion = "1.0"; + +int startReceiver() { + return 0; +} + +int stopReceiver() { + return 0; +} + +const char* getVersionSys() { + return sysRecVersion; +} + +SystemReceiver::SystemReceiver() { + name = "System receiver"; + pull = NULL; + push = NULL; + start = ibmras::monitoring::agent::startReceiver; + stop = ibmras::monitoring::agent::stopReceiver; + getVersion = getVersionSys; + type = ibmras::monitoring::plugin::receiver; + recvfactory = (RECEIVER_FACTORY) ibmras_getSystemReceiver; + confactory = NULL; +} + +SystemReceiver::~SystemReceiver() { +} + +void SystemReceiver::receiveMessage(const std::string &id, uint32 size, + void *data) { + + ibmras::monitoring::agent::Agent* agent = + ibmras::monitoring::agent::Agent::getInstance(); + + // If the topic is "datasources" it means we have had a request + // to send back the source names and config (one for each bucket) to the client + if (id == "datasources") { + if(size <= 0 || data == NULL) { + return; + } + std::string topic((char*)data, size); + topic += "/datasource"; + + ibmras::monitoring::connector::ConnectorManager *conMan = + agent->getConnectionManager(); + + ibmras::monitoring::agent::BucketList* buckets = agent->getBucketList(); + + std::vector < std::string > ids = buckets->getIDs(); + + for (uint32 i = 0; i < ids.size(); i++) { + + std::string config = agent->getConfig(ids[i]); + + std::stringstream str; + str << ids[i]; + str << ','; + str << config; + str << '\n'; + std::string msg = str.str(); + + conMan->sendMessage(topic, msg.length(), (void*) msg.c_str()); + } + } else if (id == "history") { + std::string topic((char*) data, size); + topic += "/history/"; + agent->republish(topic); + } else if (id == "headless") { + if(size == 0 || data == NULL) { + // force immediate update for pull sources + agent->immediateUpdate(); + } else { + agent->zipHeadlessFiles((const char*)data); + } + } +} + +} +} +} /* end namespace agent */ + +void* ibmras_getSystemReceiver() { + return new ibmras::monitoring::agent::SystemReceiver(); +} + diff --git a/Sources/agentcore/ibmras/monitoring/agent/SystemReceiver.h b/Sources/agentcore/ibmras/monitoring/agent/SystemReceiver.h new file mode 100644 index 0000000..28dfd96 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/SystemReceiver.h @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef SYSTEMRECEIVER_H_ +#define SYSTEMRECEIVER_H_ + +#include "../connector/Receiver.h" +#include "../Plugin.h" +#include "AgentExtensions.h" + +extern "C" DECL void* ibmras_getSystemReceiver(); + + +namespace ibmras{ +namespace monitoring { +namespace agent { + +extern "C" DECL const char* getVersionSys(); + +class SystemReceiver: public ibmras::monitoring::connector::Receiver, public ibmras::monitoring::Plugin { +public: + SystemReceiver(); + virtual ~SystemReceiver(); + int startReceiver(); + int stopReceiver(); + void receiveMessage(const std::string &id, uint32 size, void *data); +}; +} +} +} /* namespace monitoring */ +#endif /* SYSTEMRECEIVER_H_ */ diff --git a/Sources/agentcore/ibmras/monitoring/agent/threads/ThreadPool.cpp b/Sources/agentcore/ibmras/monitoring/agent/threads/ThreadPool.cpp new file mode 100644 index 0000000..ce2d46d --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/threads/ThreadPool.cpp @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +/* + * Thread pool and associated worker threads + */ + +#include "ThreadPool.h" +#include "../Agent.h" +#include "../../../common/logging.h" + +namespace ibmras { +namespace monitoring { +namespace agent { +namespace threads { + +IBMRAS_DEFINE_LOGGER("Threads") +; + +ThreadPool::ThreadPool() { + stopping = false; +} + +void ThreadPool::addPullSource(pullsource* src) { + if (!stopping) { + threads.push_back(new WorkerThread(src)); + } +} + +void ThreadPool::startAll() { + IBMRAS_DEBUG(info, "Starting thread pool"); + stopping = false; + for (uint32 i = 0; i < threads.size(); i++) { + threads[i]->start(); + } +} + +void ThreadPool::stopAll() { + IBMRAS_DEBUG(info, "Stopping thread pool"); + //prevent any new pull sources being added + stopping = true; + for (uint32 i = 0; i < threads.size(); i++) { + threads[i]->stop(); + } + uint32 stoppedCount = 0; + uint32 maxWait = 5; + while ((stoppedCount < threads.size()) && (maxWait > 0)) { + stoppedCount = 0; + for (uint32 i = 0; i < threads.size(); i++) { + if (threads[i]->isStopped()) { + stoppedCount++; + } + } + + if (stoppedCount == threads.size()) { + break; + } + + IBMRAS_DEBUG_1(debug, "Waiting for %d worker threads to stop", threads.size() - stoppedCount); + ibmras::common::port::sleep(1); + maxWait--; + } +} + +ThreadPool::~ThreadPool() { + stopping = true; + for (uint32 i = 0; i < threads.size(); i++) { + // Only delete threads that are stopped + // unlikely leak but prevents an abort + if (threads[i]->isStopped()) { + delete threads[i]; + } + } +} + +void ThreadPool::process(bool immediate) { + IBMRAS_DEBUG(finest, "Processing pull sources"); + for (uint32 i = 0; i < threads.size(); i++) { + threads[i]->process(immediate); + } +} + +} +} +} +} /* end of namespace threads */ + diff --git a/Sources/agentcore/ibmras/monitoring/agent/threads/ThreadPool.h b/Sources/agentcore/ibmras/monitoring/agent/threads/ThreadPool.h new file mode 100644 index 0000000..d90f585 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/threads/ThreadPool.h @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_agent__threads_threadpool_h +#define ibmras_monitoring_agent__threads_threadpool_h + +#include "../../../common/port/ThreadData.h" +#include "../../../common/port/Semaphore.h" +#include "AgentExtensions.h" +#include "../../Typesdef.h" +#include "WorkerThread.h" + +namespace ibmras { +namespace monitoring { +namespace agent { +namespace threads { + +/* a pool of worker threads */ +class ThreadPool { +public: + ThreadPool(); + void addPullSource(pullsource* src); + + void startAll(); /* start all threads in this pool */ + void stopAll(); /* stop all threads in this pool */ + + void process(bool immediate); /* process queue entries */ + ~ThreadPool(); +private: + std::vector threads; /* worker threads */ + bool stopping; /* flag to prevent adding pull sources during stop */ +}; + +} +} +} +} /* end namespace threads */ + +#endif /* ibmras_monitoring_agent__threads_threadpool_h */ diff --git a/Sources/agentcore/ibmras/monitoring/agent/threads/WorkerThread.cpp b/Sources/agentcore/ibmras/monitoring/agent/threads/WorkerThread.cpp new file mode 100644 index 0000000..40ae0b5 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/threads/WorkerThread.cpp @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#include "WorkerThread.h" +#include "../Agent.h" +#include "../../../common/logging.h" + +namespace ibmras { +namespace monitoring { +namespace agent { +namespace threads { + +extern IBMRAS_DECLARE_LOGGER; + + +WorkerThread::WorkerThread(pullsource* pullSource) : semaphore(0, 1), data(threadEntry, cleanUp), countdown(0) { + source = pullSource; + running = false; + stopped = true; + data.setArgs(this); +} + + +void WorkerThread::start() { + IBMRAS_DEBUG_1(fine, "Starting worker thread for %s", source->header.name); + running = true; + stopped = false; + ibmras::common::port::createThread(&data); +} + +void WorkerThread::stop() { + running = false; + semaphore.inc(); + IBMRAS_DEBUG_1(debug, "Worker thread for %s stopped", source->header.name); +} + +void* WorkerThread::cleanUp(ibmras::common::port::ThreadData* data) { + ((WorkerThread*) data->getArgs())->stop(); + return NULL; +} + +void* WorkerThread::threadEntry(ibmras::common::port::ThreadData* data) { + ((WorkerThread*) data->getArgs())->processLoop(); + ibmras::common::port::exitThread(NULL); + return NULL; +} + +void WorkerThread::process(bool immediate) { + IBMRAS_DEBUG_2(finest, "Worker thread process for %s, countdown is %d", source->header.name, countdown); + if ((immediate && countdown > 120) || (countdown == 0)) { + semaphore.inc(); + countdown = source->pullInterval; + } else { + countdown--; + } +} + +bool WorkerThread::isStopped() { + return stopped; +} + +void* WorkerThread::processLoop() { + IBMRAS_DEBUG_1(finest, "Worker thread started for %s", source->header.name); + Agent* agent = Agent::getInstance(); + while (running) { + if (semaphore.wait(1) && running) { + IBMRAS_DEBUG_1(fine, "Pulling data from source %s", source->header.name); + monitordata* data = source->callback(); + if (data != NULL) { + if (data->size > 0) { + IBMRAS_DEBUG_2(finest, "%d bytes of data pulled from source %s", data->size, source->header.name); + agent->addData(data); /* put pulled data on queue for processing */ + } + source->complete(data); + } + } + } + + source->complete(NULL); + stopped = true; + IBMRAS_DEBUG_1(finest, "Worker thread for %s exiting process loop", source->header.name); + return NULL; +} + + +} +} +} +} /* end of namespace threads */ + diff --git a/Sources/agentcore/ibmras/monitoring/agent/threads/WorkerThread.h b/Sources/agentcore/ibmras/monitoring/agent/threads/WorkerThread.h new file mode 100644 index 0000000..056123a --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/agent/threads/WorkerThread.h @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_agent_threads_workerthread_h +#define ibmras_monitoring_agent_threads_workerthread_h + +#include "../../../common/port/ThreadData.h" +#include "../../../common/port/Semaphore.h" +#include "AgentExtensions.h" +#include "../../Typesdef.h" + +namespace ibmras { +namespace monitoring { +namespace agent { +namespace threads { + +class WorkerThread { +public: + WorkerThread(pullsource* source); + void start(); /* start this worker thread taking from the queue */ + void stop(); /* stop this thread from taking any more entries */ + + void process(bool immediate); + bool isStopped(); + + static void* threadEntry(ibmras::common::port::ThreadData* data); + static void* cleanUp(ibmras::common::port::ThreadData* data); +private: + void* processLoop(); + bool running; + bool stopped; + ibmras::common::port::Semaphore semaphore; /* sempahore to control data processing */ + pullsource* source; /* source to pull data from */ + ibmras::common::port::ThreadData data; + int countdown; +}; + +} +} +} +} /* end namespace threads */ + +#endif /* ibmras_monitoring_agent_threads_workerthread_h */ diff --git a/Sources/agentcore/ibmras/monitoring/connector/Connector.h b/Sources/agentcore/ibmras/monitoring/connector/Connector.h new file mode 100644 index 0000000..9338140 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/connector/Connector.h @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_connector_connector_h +#define ibmras_monitoring_connector_connector_h + +#include "Receiver.h" + + +namespace ibmras{ +namespace monitoring { +namespace connector { + +class Connector { +public: + virtual std::string getID() { return "Connector"; }; /* unique ID for the connector */ + virtual int sendMessage(const std::string &sourceId, uint32 size, + void *data) {return 0;}; + + virtual void registerReceiver(Receiver *receiver) {}; + + virtual int start() {return 0;}; + virtual int stop() {return 0;}; + + virtual ~Connector() {}; + +protected: + Connector() { + } + ; + +}; +/* end class Connector */ + +} +} +} /* end namespace connector */ + +#endif /* ibmras_monitoring_connector_connector_h */ diff --git a/Sources/agentcore/ibmras/monitoring/connector/ConnectorManager.cpp b/Sources/agentcore/ibmras/monitoring/connector/ConnectorManager.cpp new file mode 100644 index 0000000..8a82f91 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/connector/ConnectorManager.cpp @@ -0,0 +1,181 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "ConnectorManager.h" +#include "../../common/logging.h" + +namespace ibmras { +namespace monitoring { +namespace connector { + +ConnectorManager::ConnectorManager() : + running(false), threadData(processThread) { + threadData.setArgs(this); +} + +ConnectorManager::~ConnectorManager() { + removeAllConnectors(); +} + +void ConnectorManager::addConnector(Connector *connector) { + connectors.insert(connector); +} + +void ConnectorManager::removeConnector(Connector *connector) { + connectors.erase(connector); +} + +void ConnectorManager::removeAllConnectors() { + connectors.clear(); +} + +void ConnectorManager::addReceiver(Receiver *receiver) { + receivers.insert(receiver); +} + +void ConnectorManager::removeReceiver(Receiver *receiver) { + receivers.erase(receiver); +} + +void ConnectorManager::removeAllReceivers() { + receivers.clear(); +} + +void ConnectorManager::receiveMessage(const std::string &id, uint32 size, + void *data) { + if (running && !receiveLock.acquire() && !receiveLock.isDestroyed()) { + ReceivedMessage msg(id, size, data); + receiveQueue.push(msg); + receiveLock.release(); + } +} + +void ConnectorManager::processMessage(const std::string &id, uint32 size, + void *data) { + ReceivedMessage msg(id, size, data); + if (!receiveLock.acquire() && !receiveLock.isDestroyed()) { + processReceivedMessage(msg); + receiveLock.release(); + } +} + +void ConnectorManager::processReceivedMessage(const ReceivedMessage& msg) { + for (std::set::iterator it = receivers.begin(); + it != receivers.end(); ++it) { + if (*it) { + (*it)->receiveMessage(msg.getId(), + msg.getMessage().size(), + (void*) msg.getMessage().c_str()); + } + } +} + +void* ConnectorManager::processThread(ibmras::common::port::ThreadData *td) { + ConnectorManager* conMan = (ConnectorManager*) td->getArgs(); + if (conMan) { + conMan->processReceivedMessages(); + } + + return NULL; +} + +void ConnectorManager::processReceivedMessages() { + while (running) { + + if (!receiveLock.acquire() && !receiveLock.isDestroyed()) { + while (!receiveQueue.empty()) { + ReceivedMessage msg = receiveQueue.front(); + receiveQueue.pop(); + processReceivedMessage(msg); + } + receiveLock.release(); + } + ibmras::common::port::sleep(1); + } +} + +int ConnectorManager::sendMessage(const std::string &sourceId, uint32 size, + void *data) { + int count = 0; + if (running && !sendLock.acquire()) { + try { + + for (std::set::iterator it = connectors.begin(); + it != connectors.end(); ++it) { + if ((*it)->sendMessage(sourceId, size, data) > 0) { + count++; + } + } + + } catch (...) { + } + sendLock.release(); + } + + return count; +} + +Connector* ConnectorManager::getConnector(const std::string &id) { + + for (std::set::iterator it = connectors.begin(); + it != connectors.end(); ++it) { + if (!(*it)->getID().compare(id)) { + return *it; + } + } + return NULL; +} + +int ConnectorManager::start() { + if (running) { + return 0; + } + + running = true; + ibmras::common::port::createThread(&threadData); + + int rc = 0; + for (std::set::iterator it = connectors.begin(); + it != connectors.end(); ++it) { + rc += (*it)->start(); + } + return rc; +} + +int ConnectorManager::stop() { + int rc = 0; + for (std::set::iterator it = connectors.begin(); + it != connectors.end(); ++it) { + rc += (*it)->stop(); + } + running = false; + return rc; +} + +ConnectorManager::ReceivedMessage::ReceivedMessage(const std::string& id, + uint32 size, void* data) { + this->id = id; + if (size > 0 && data != NULL) { + message = std::string((const char*) data, size); + } else { + data = NULL; + } +} + +} +} +} /* namespace monitoring */ + diff --git a/Sources/agentcore/ibmras/monitoring/connector/ConnectorManager.h b/Sources/agentcore/ibmras/monitoring/connector/ConnectorManager.h new file mode 100644 index 0000000..c9b2674 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/connector/ConnectorManager.h @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_connector_connectormanager_h +#define ibmras_monitoring_connector_connectormanager_h + +#include "Connector.h" +#include "Receiver.h" +#include "../../common/port/ThreadData.h" +#include "../../common/port/Lock.h" +#include +#include + +namespace ibmras{ +namespace monitoring { +namespace connector { + +class DECL ConnectorManager: public Connector, public Receiver { +public: + ConnectorManager(); + virtual ~ConnectorManager(); + + void addConnector(Connector *connector); + void removeConnector(Connector *connector); + void removeAllConnectors(); + Connector* getConnector(const std::string &id); + + void addReceiver(Receiver *receiver); + void removeReceiver(Receiver *receiver); + void removeAllReceivers(); + + int sendMessage(const std::string &sourceId, uint32 size, void *data); + void receiveMessage(const std::string &id, uint32 size, void *data); + + void processMessage(const std::string &id, uint32 size, void *data); + + int start(); + int stop(); + +private: + bool running; + ibmras::common::port::ThreadData threadData; + + class ReceivedMessage { + public: + ReceivedMessage(const std::string &id, uint32 size, void *data); + virtual ~ReceivedMessage() {} + + const std::string& getId() const { + return id; + } + + const std::string& getMessage() const { + return message; + } + + private: + std::string id; + std::string message; + }; + + std::queue receiveQueue; + ibmras::common::port::Lock receiveLock; + ibmras::common::port::Lock sendLock; + + std::set connectors; + std::set receivers; + + void processReceivedMessages(); + void processReceivedMessage(const ReceivedMessage &msg); + static void* processThread(ibmras::common::port::ThreadData *td); +}; +} +} +} /* namespace connector */ +#endif /* ibmras_monitoring_connector_connectormanager_h */ diff --git a/Sources/agentcore/ibmras/monitoring/connector/Receiver.h b/Sources/agentcore/ibmras/monitoring/connector/Receiver.h new file mode 100644 index 0000000..26534aa --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/connector/Receiver.h @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_connector_receiver_h +#define ibmras_monitoring_connector_receiver_h + +#include +#include "../../common/types.h" + +namespace ibmras{ +namespace monitoring { +namespace connector { + +class Receiver { +public: + virtual void receiveMessage(const std::string &id, uint32 size, + void *data) = 0; + + virtual ~Receiver() { + } + +protected: + Receiver() { + } +}; + +} +} +} /* namespace connector */ +#endif /* ibmras_monitoring_connector_receiver_h */ diff --git a/Sources/agentcore/ibmras/monitoring/connector/configuration/ConfigurationConnector.cpp b/Sources/agentcore/ibmras/monitoring/connector/configuration/ConfigurationConnector.cpp new file mode 100644 index 0000000..ff76c93 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/connector/configuration/ConfigurationConnector.cpp @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#include "ConfigurationConnector.h" +#include "../../../common/util/strUtils.h" +#include + +namespace ibmras { +namespace monitoring { +namespace connector { + +static const std::string CONFIGURATION_TOPIC("configuration/"); + +ConfigurationConnector::ConfigurationConnector() { +} + +ConfigurationConnector::~ConfigurationConnector() { +} + +std::string ConfigurationConnector::getConfig(const std::string& name) { + std::string configString(""); + if (!configLock.acquire()) { + configString = config.get(name); + configLock.release(); + } + return configString; +} + +int ConfigurationConnector::sendMessage(const std::string& sourceId, + uint32 size, void* data) { + + if (ibmras::common::util::startsWith(sourceId, CONFIGURATION_TOPIC)) { + std::string configName = sourceId.substr(CONFIGURATION_TOPIC.length()); + + std::string configString((char*)data, size); + if (!configLock.acquire()) { + config.put(configName, configString); + configLock.release(); + } + } + return 0; +} + +} /* namespace connector */ +} /* namespace monitoring */ +} /* namespace ibmras */ diff --git a/Sources/agentcore/ibmras/monitoring/connector/configuration/ConfigurationConnector.h b/Sources/agentcore/ibmras/monitoring/connector/configuration/ConfigurationConnector.h new file mode 100644 index 0000000..a52d94a --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/connector/configuration/ConfigurationConnector.h @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_connector_configurationconnector_h +#define ibmras_monitoring_connector_configurationconnector_h + + +#include "../Connector.h" +#include "../../../common/Properties.h" +#include "../../../common/port/Lock.h" + +namespace ibmras { +namespace monitoring { +namespace connector { + +class DECL ConfigurationConnector : public ibmras::monitoring::connector::Connector { +public: + ConfigurationConnector(); + virtual ~ConfigurationConnector(); + + std::string getConfig(const std::string &name); + + int sendMessage(const std::string &sourceId, uint32 size, void *data); + + std::string getID() { + return "ConfigurationConnector"; + } +private: + ibmras::common::Properties config; + ibmras::common::port::Lock configLock; + +}; + +} /* namespace connector */ +} /* namespace monitoring */ +} /* namespace ibmras */ +#endif /* ibmras_monitoring_connector_configurationconnector_h */ diff --git a/Sources/agentcore/ibmras/monitoring/connector/headless/HLConnector.cpp b/Sources/agentcore/ibmras/monitoring/connector/headless/HLConnector.cpp new file mode 100644 index 0000000..cf093a3 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/connector/headless/HLConnector.cpp @@ -0,0 +1,651 @@ +/******************************************************************************* + * Copyright 2007-2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined(WINDOWS) +#include +#include "windows.h" +#include "WinBase.h" +#include "direct.h" +#include "io.h" +#define PATHSEPARATOR "\\" +#else /* Unix platforms */ +#include +#include +#include +#define PATHSEPARATOR "/" +#endif + +#include "HLConnector.h" +#include "../../agent/Agent.h" +#include "../../../common/logging.h" +#include "../../../common/MemoryManager.h" +#include "../../../common/util/strUtils.h" +#include "../../../common/util/sysUtils.h" +#include "../../../common/port/Process.h" + +#if defined(_WINDOWS) +#define HEADLESS_DECL __declspec(dllexport) /* required for DLLs to export the plugin functions */ +#else +#define HEADLESS_DECL +#endif + +namespace ibmras { +namespace monitoring { +namespace connector { +namespace headless { + +IBMRAS_DEFINE_LOGGER("headless"); + +const char* headlessConnVersion = "1.0"; + +static HLConnector* instance = NULL; +static bool collect = true; + +static const char* HEADLESS_TOPIC = "headless"; + +HLConnector* HLConnector::getInstance() { + if (!instance) { + instance = new HLConnector(); + } + return instance; +} + +HLConnector::HLConnector() : + enabled(false), running(false), filesInitialized(false), seqNumber( + 1), lastPacked(0), times_run(0), startDelay(0) { + + number_runs = 0; + run_duration = 0; + startTime = 0; + files_to_keep = 0; + run_pause = 0; + upper_limit = INT_MAX; + lock = new ibmras::common::port::Lock; +} + +HLConnector::~HLConnector() { + +} + +int HLConnector::start() { + + IBMRAS_DEBUG(debug, ">>>HLConnector::start()"); + ibmras::monitoring::agent::Agent* agent = + ibmras::monitoring::agent::Agent::getInstance(); + + std::string enabledProp = agent->getAgentProperty("headless"); + if (ibmras::common::util::equalsIgnoreCase(enabledProp, "on")) { + enabled = true; + collect = true; + IBMRAS_LOG_1(info, "%s", agent->getVersion().c_str()); + } else { + enabled = false; + collect = false; + return 0; + } + + // initialise run values (in case of late attach causing multiple runs) + times_run = 0; + number_runs = 0; + createdFiles.clear(); + + agent->setHeadlessRunning(true); + + std::string delay = agent->getAgentProperty("headless.delay.start"); + if (delay.length()) { + collect = false; + startDelay = atoi(delay.c_str()); + } + + std::string ulString = agent->getAgentProperty("headless.files.max.size"); + if (ulString.length()) { + upper_limit = atoi(ulString.c_str()); + } + + IBMRAS_DEBUG_1(debug, "upper_limit = %d", upper_limit); + + + std::string fkString = agent->getAgentProperty("headless.files.to.keep"); + if (fkString.length()) { + files_to_keep = atoi(fkString.c_str()); + } + + IBMRAS_DEBUG_1(debug, "files_to_keep = %d", files_to_keep); + + + std::string rdString = agent->getAgentProperty("headless.run.duration"); + if (rdString.length()) { + run_duration = atoi(rdString.c_str()); + } + + IBMRAS_DEBUG_1(debug, "run_duration = %d", run_duration); + + + std::string rpString = agent->getAgentProperty( + "headless.run.pause.duration"); + if (rpString.length()) { + run_pause = atoi(rpString.c_str()); + } + + IBMRAS_DEBUG_1(debug, "run_pause = %d", run_pause); + + + std::string nrString = agent->getAgentProperty( + "headless.run.number.of.runs"); + if (nrString.length()) { + number_runs = atoi(nrString.c_str()); + } + + IBMRAS_DEBUG_1(debug, "number_runs = %d", number_runs); + + + + //The temporary files will be written at a temporary directory under the user defined path + //(or the current directory if the one requested by user could not be created.) + startNewTempDir(); + + // Check the correct number of files have been created + std::vector sourceIDs = agent->getBucketList()->getIDs(); + if (createdFiles.size() != sourceIDs.size()) { + return -1; + } + + running = true; + + ibmras::common::port::ThreadData* data = + new ibmras::common::port::ThreadData(thread); + data->setArgs(this); + ibmras::common::port::createThread(data); + + IBMRAS_DEBUG(debug, "<<getAgentProperty( + "headless.output.directory"); + if (!outputDir.length()) { + userDefinedPath = defaultPath; + } else { + userDefinedPath = outputDir; + if (!createDirectory(userDefinedPath)) { + IBMRAS_DEBUG_1(warning, "The directory %s could not be created, using default path", outputDir.c_str()); + userDefinedPath = defaultPath; + } + } + + IBMRAS_DEBUG_1(debug, "Path = %s", userDefinedPath.c_str()); + + //The temporary files will be written at a temporary directory under the user defined path + tmpPath = userDefinedPath; + tmpPath.append(PATHSEPARATOR); + tmpPath.append("tmp_"); + tmpPath.append(startDate); + createDirectory(tmpPath); + + std::string filePrefix = agent->getAgentProperty("headless.filename"); + if (!filePrefix.length()) { + userDefinedPrefix = ""; + } else { + IBMRAS_DEBUG_1(debug, "Prefix = %s", filePrefix.c_str()); + userDefinedPrefix = filePrefix; + } + + IBMRAS_DEBUG_1(debug, "Prefix = %s", userDefinedPrefix.c_str()); + + /*** + * First we create a vector which will contain the IDs of the datasources, + * these names will match the names of the files created by createFile + */ + std::vector sourceIDs = agent->getBucketList()->getIDs(); + + for (std::vector::iterator it = sourceIDs.begin(); + it != sourceIDs.end(); ++it) { + createFile(*it); + } + filesInitialized = false; +} + +void HLConnector::createFile(const std::string &fileName) { + IBMRAS_DEBUG(debug, ">>>HLConnector::createFile()"); + std::fstream* file = new std::fstream; + + std::string escapedFile = fileName; + replace(escapedFile.begin(), escapedFile.end(), '/', '_'); + std::string fullPath = tmpPath; + fullPath.append(PATHSEPARATOR); + fullPath.append(escapedFile); + + createdFiles[fullPath] = file; + expandedIDs[fileName] = fullPath; + IBMRAS_DEBUG(debug, "<<>>HLConnector::createDirectory"); + bool created = false; + + const char* pathName = path.c_str(); + +#if defined(WINDOWS) + DWORD dirAttr; + IBMRAS_DEBUG_1(debug, "Creating directory: %s", pathName); + dirAttr = GetFileAttributes(reinterpret_cast(pathName)); + + if(INVALID_FILE_ATTRIBUTES == dirAttr) { + switch (GetLastError()) { + case ERROR_PATH_NOT_FOUND: + IBMRAS_DEBUG(warning, "The directory was not found"); + IBMRAS_DEBUG_1(debug, "Creating directory: %s", pathName); + if(!CreateDirectory(reinterpret_cast(pathName), NULL)) { + switch (GetLastError()) { + //if the directory already exists we will use it instead of the current one. + case ERROR_ALREADY_EXISTS: + IBMRAS_DEBUG(warning, "The specified directory already exists."); + created = true; + break; + case ERROR_PATH_NOT_FOUND: + IBMRAS_DEBUG(warning, "The system cannot find the path specified."); + break; + } + } else { + created = true; + } + break; + case ERROR_INVALID_NAME: + IBMRAS_DEBUG(warning, "The filename, directory name, or volume label syntax is incorrect"); + break; + case ERROR_BAD_NETPATH: + IBMRAS_DEBUG(warning, "The network path was not found."); + break; + default: + IBMRAS_DEBUG(warning, "The directory could not be found, permissions?."); + IBMRAS_DEBUG_1(debug, "Creating directory: %s", pathName); + if(!CreateDirectory(reinterpret_cast(pathName), NULL)) { + switch (GetLastError()) { + case ERROR_ALREADY_EXISTS: + IBMRAS_DEBUG(warning, "The specified directory already exists."); + created = true; + break; + case ERROR_PATH_NOT_FOUND: + IBMRAS_DEBUG(warning, "The system cannot find the path specified."); + break; + } + } else { + created = true; + } + } + + }else if(FILE_ATTRIBUTE_DIRECTORY == dirAttr) { + created = true; + } + +#else + struct stat dir; + IBMRAS_DEBUG_1(debug, "Pathname...%s\n", pathName); + if (stat(pathName, &dir)) { + IBMRAS_DEBUG_1(debug, "Directory does not exist, creating...%s\n", pathName); + if (mkdir(pathName, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { + IBMRAS_DEBUG_1(debug, "Directory could not be created: ", strerror(errno)); + if (EEXIST == errno) { + IBMRAS_DEBUG_1(debug, "Directory % already existed", pathName); + created = true; + } + } else { + IBMRAS_DEBUG_1(debug, "Directory %s was created: ", pathName); + created = true; + } + } else { + IBMRAS_DEBUG(debug, "stat() returned 0, we'll check whether it was an existing directory"); + if (S_ISDIR(dir.st_mode)) { + created = true; + } + } +#endif + IBMRAS_DEBUG(debug, "<<>>HLConnector::stop()"); + + running = false; + + if (enabled == false) { + return 0; + } + + ibmras::monitoring::agent::Agent* agent = + ibmras::monitoring::agent::Agent::getInstance(); + + // Take lock before packing then clearing the files + if (!lock->acquire()) { + if (!lock->isDestroyed()) { + + if (collect) { + IBMRAS_DEBUG(debug, "Closing files at stop"); + closeFilesAndNotify(); + } else { + IBMRAS_DEBUG(debug, "collect is false"); + } + + } + lock->release(); + } + + return 0; +} + +int HLConnector::sendMessage(const std::string &sourceId, uint32 size, + void* data) { + + if (!running || !collect || !enabled) { + IBMRAS_DEBUG(debug, "<<>>HLConnector::sendMessage() %s", sourceId.c_str()); + + std::map::iterator it; + it = expandedIDs.find(sourceId); + + if (it == expandedIDs.end()) { + return -1; + } + + if (!lock->acquire()) { + if (!lock->isDestroyed()) { + + // Ensure we are still running after acquiring the lock + if (!running || !collect || !enabled ) { + lock->release(); + return 0; + } + + std::string currentKey = it->second; + std::fstream* currentSource = createdFiles[currentKey]; + + const char* cdata = reinterpret_cast(data); + IBMRAS_DEBUG_1(debug, "currentKey %s", currentKey.c_str()); + + if (!filesInitialized) { + // Send initialize notification to providers + ibmras::monitoring::agent::Agent* agent = + ibmras::monitoring::agent::Agent::getInstance(); + agent->getConnectionManager()->receiveMessage("headless", 0, + NULL); + filesInitialized = true; + } + if (currentSource->is_open()) { + IBMRAS_DEBUG(debug, "open"); + std::time_t currentTime; + time(¤tTime); + uint32 length = currentSource->tellg(); + if ((length + size > upper_limit)) { + IBMRAS_DEBUG_1(debug, "SendMessage from = %s", sourceId.c_str()); + IBMRAS_DEBUG_1(debug, "MAX_FILE_SIZE = %d", upper_limit); + IBMRAS_DEBUG_1(debug, "Current time = %d", currentTime); + IBMRAS_DEBUG(debug, "Closing files due to max file size reached"); + closeFilesAndNotify(); + startNewTempDir(); + } + } + + if (!currentSource->is_open()) { + IBMRAS_DEBUG(debug, "not open"); + currentSource->open(currentKey.c_str(), + std::ios::out | std::ios::app | std::ios::binary); + + // Get persistent Data eg trace header and write to start of file + ibmras::monitoring::agent::Agent* agent = + ibmras::monitoring::agent::Agent::getInstance(); + ibmras::monitoring::agent::Bucket *bucket = + agent->getBucketList()->findBucket(sourceId); + if (bucket) { + + uint32 id = 0; + while (true) { + + const char* persistentData = NULL; + uint32 persistentDataSize = 0; + + IBMRAS_DEBUG_2(debug, "getting persistent data for %s id %d", sourceId.c_str(), id); + id = bucket->getNextPersistentData(id, + persistentDataSize, (void**) &persistentData); + if (persistentData != NULL && size > 0) { + currentSource->write(persistentData, + persistentDataSize); + } else { + IBMRAS_DEBUG(debug, "persistent data was Null or 0"); + break; + } + } + } + + } + + if (currentSource->is_open()) { + IBMRAS_DEBUG_1(debug, "Write: %s", cdata); + currentSource->write(cdata, size); + } + + lock->release(); + } + } + IBMRAS_DEBUG(debug, "<<::iterator it = + createdFiles.begin(); it != createdFiles.end(); it++) { + + if ((it->second)->is_open()) { + (it->second)->close(); + } + } + // Send message that files have been created in 'tmpPath' temporary directory + ibmras::monitoring::agent::Agent* agent = ibmras::monitoring::agent::Agent::getInstance(); + IBMRAS_DEBUG_1(info, "tmpPath: %s", tmpPath.c_str()); + std::string outputDir (tmpPath); + agent->getConnectionManager()->receiveMessage(ibmras::monitoring::connector::headless::HEADLESS_TOPIC, outputDir.length(), (void*) outputDir.c_str()); +} + +void HLConnector::lockCloseFilesAndNotify(bool createNewTempDir) { + if (!lock->acquire()) { + if (!lock->isDestroyed()) { + closeFilesAndNotify(); + if(createNewTempDir) { + startNewTempDir(); + } + } + lock->release(); + } +} + +void HLConnector::processLoop() { + IBMRAS_DEBUG(debug, ">> processLoop"); + ibmras::monitoring::agent::Agent* agent = + ibmras::monitoring::agent::Agent::getInstance(); + + if (startDelay) { + IBMRAS_LOG_1(info, + "Headless data collection starting with delay of %d minutes", + startDelay); + sleep(startDelay * 60); + } + IBMRAS_LOG(info, "Headless data collection has started"); + IBMRAS_DEBUG_1(debug, "run_duration = %d", run_duration); + IBMRAS_DEBUG_1(debug, "number_runs = %d", number_runs); + if (run_duration) { + IBMRAS_LOG_1(info, "Each data collection run will last for %d minutes", + run_duration); + } + if (run_pause) { + IBMRAS_LOG_1(info, + "Agent will pause for %d minutes between each data collection run", + run_pause); + } + if (number_runs) { + IBMRAS_LOG_1(info, "Agent will run for %d collections", number_runs); + } + if (files_to_keep) { + IBMRAS_LOG_1(info, "Agent will keep last %d hcd files", files_to_keep); + } + IBMRAS_LOG_1(info, "Headless collection output directory is %s", + userDefinedPath.c_str()); + + if (number_runs) { + IBMRAS_DEBUG_1(debug, "Produce HCDs for %d minutes", run_duration); + while (running && (times_run < number_runs)) { + collect = true; + IBMRAS_DEBUG_2(debug, "We've run %d times and have to run %d in total", times_run, number_runs); + sleep(run_duration * 60); + times_run++; + if (running) { + lockCloseFilesAndNotify(times_run < number_runs); + } + + if (run_pause > 0) { + collect = false; + IBMRAS_DEBUG_1(warning, "Not producing HCDs for %d minutes", run_pause); + sleep(run_pause * 60); + } + } + running = false; + agent->setHeadlessRunning(false); + + } else if (run_duration || run_pause) { + while (running) { + collect = true; + IBMRAS_DEBUG_1(debug, "Produce HCDs for %d minutes", run_duration); + sleep(run_duration * 60); + if (running) { + lockCloseFilesAndNotify(false); + } + + if (run_pause > 0) { + collect = false; + IBMRAS_DEBUG_1(warning, "Rest for %d minutes", run_pause); + sleep(run_pause * 60); + } + } + agent->setHeadlessRunning(false); + } + + IBMRAS_DEBUG(debug, "<< processLoop"); +} + + + + + +void* HLConnector::thread(ibmras::common::port::ThreadData* tData) { + HLConnector* hlc = HLConnector::getInstance(); + hlc->processLoop(); + return NULL; +} + +} /*end namespace headless*/ +} /*end namespace connector*/ +} /*end namespace monitoring*/ +} /*end namespace ibmras*/ + +extern "C" { + +HEADLESS_DECL int ibmras_monitoring_plugin_start() { + return 0; +} + +HEADLESS_DECL int ibmras_monitoring_plugin_stop() { + return 0; +} + +HEADLESS_DECL const char* ibmras_monitoring_getVersion() { + return ibmras::monitoring::connector::headless::headlessConnVersion; +} + +bool headlessInitialized = false; + +HEADLESS_DECL int ibmras_monitoring_plugin_init(const char* properties) { + if (!headlessInitialized) { + headlessInitialized = true; + } + return 0; +} + +HEADLESS_DECL void* ibmras_monitoring_getConnector(const char* properties) { + + ibmras::common::Properties props; + props.add(properties); + + std::string enabledProp = props.get("com.ibm.diagnostics.healthcenter.headless"); + if (!ibmras::common::util::equalsIgnoreCase(enabledProp, "on")) { + return NULL; + } + + std::string loggingProp = props.get("com.ibm.diagnostics.healthcenter.logging.level"); + ibmras::common::LogManager::getInstance()->setLevel("level", loggingProp); + loggingProp = props.get("com.ibm.diagnostics.healthcenter.logging.headless"); + ibmras::common::LogManager::getInstance()->setLevel("headless", loggingProp); + + return ibmras::monitoring::connector::headless::HLConnector::getInstance(); +} +} diff --git a/Sources/agentcore/ibmras/monitoring/connector/headless/HLConnector.h b/Sources/agentcore/ibmras/monitoring/connector/headless/HLConnector.h new file mode 100644 index 0000000..4f47921 --- /dev/null +++ b/Sources/agentcore/ibmras/monitoring/connector/headless/HLConnector.h @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright 2007-2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#ifndef ibmras_monitoring_connector_headless_hlconnector_h +#define ibmras_monitoring_connector_headless_hlconnector_h + +#include +#include +#include +#include + +#include "../Connector.h" +#include "../../agent/BucketList.h" +#include "../Receiver.h" +#include "../../../common/port/ThreadData.h" + +namespace ibmras { +namespace monitoring { +namespace connector { +namespace headless { + +class HLConnector: public ibmras::monitoring::connector::Connector { +public: + + static HLConnector* getInstance(); + virtual ~HLConnector(); + HLConnector(); + + virtual std::string getID() {return "HLConnector"; } + int sendMessage(const std::string &sourceID, uint32 size, void* data); + + int start(); + int stop(); + +private: + static void* thread(ibmras::common::port::ThreadData* tData); + void processLoop(); + void sleep(uint32 seconds); + void closeFilesAndNotify(); + void lockCloseFilesAndNotify(bool createNewTempDir); + void startNewTempDir(); + + bool enabled; + bool running; + bool filesInitialized; + + int32 seqNumber; + time_t lastPacked; + uint32 upper_limit; + int32 files_to_keep; + std::map createdFiles; + std::map expandedIDs; + ibmras::common::port::Lock* lock; + int32 run_duration; + int32 run_pause; + int32 number_runs; + std::string userDefinedPath; + std::string tmpPath; + std::string userDefinedPrefix; + int32 times_run; + std::time_t startTime; + char startDate[100]; + int startDelay; + + void createFile(const std::string &fileName); + bool createDirectory(std::string& path); +}; + +void* runCounterThread(ibmras::common::port::ThreadData* tData); + + +} /*end namespace headless*/ +} /*end namespace connector*/ +} /*end namespace monitoring*/ +} /*end namespace ibmras*/ + +#endif /*ibmras_monitoring_connector_headless_hlconnector_h*/ diff --git a/Sources/agentcore/include/AgentExtensions.h b/Sources/agentcore/include/AgentExtensions.h new file mode 100644 index 0000000..583338d --- /dev/null +++ b/Sources/agentcore/include/AgentExtensions.h @@ -0,0 +1,167 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_monitoring_h +#define ibmras_monitoring_monitoring_h + +#ifdef __cplusplus +extern "C"{ +#endif + +#ifndef PLUGIN_API_VERSION +#define PLUGIN_API_VERSION "1.0" +#endif + +/* provide a default definition of DECL of the platform does not define one */ +#ifndef DECL +#define DECL +#endif + +#if defined(_WINDOWS) + #if defined(EXPORT) + #define DECL __declspec(dllexport) /* required for DLLs to export the plugin functions */ + #else + #define DECL __declspec(dllimport) + #endif + #define PLUGIN_API_DECL __declspec(dllexport) /* required for DLLs to export the plugin functions */ +#else + #define PLUGIN_API_DECL +#endif + +/* + * API definitions for data sources to connect to the monitoring + * agent. + */ + +/* data from a source */ +typedef struct monitordata { + unsigned int provID; /* provider ID, previously allocated during the source registration */ + unsigned int sourceID; /* source ID, previously supplied by the source during registration */ + unsigned int size; /* amount of data being provided */ + const char *data; /* char array of the data to store */ +#ifdef __cplusplus + bool persistent; /* persistent data will not be removed from the bucket */ +#else + int persistent; /* persistent data will not be removed from the bucket */ +#endif +} monitordata; + +typedef monitordata* (*PULL_CALLBACK)(void); /* shortcut definition for the pull source callback */ +typedef void (*PULL_CALLBACK_COMPLETE)(monitordata*); /* callback to indicate when the data source can free / re-use the memory */ +typedef char* (*GET_CONFIG)(void); + +/* common header for data sources */ +typedef struct srcheader { + unsigned int sourceID; /* ID assigned by the provider - unique by provider */ + unsigned int capacity; /* the amount of space in bytes that should be allocated for this source */ + const char *name; /* null terminated C string */ + const char *description; /* null terminated C string */ +} srcheader; + +typedef struct pushsource { + srcheader header; /* common source header */ +#ifdef __cplusplus + pushsource *next; /* next source or null if this is the last one in the list */ +#else + struct pushsource *next; /* next source or null if this is the last one in the list */ +#endif +} pushsource; + +typedef struct pullsource{ + srcheader header; /* common source header */ +#ifdef __cplusplus + pullsource *next; /* the next source or null if this is the last one in the list */ +#else + struct pullsource *next; /* the next source or null if this is the last one in the list */ +#endif + unsigned int pullInterval; /* time in seconds at which data should be pulled from this source */ + PULL_CALLBACK callback; + PULL_CALLBACK_COMPLETE complete; +} pullsource; + +/* definition for connectors */ +typedef void* (*CONNECTOR_FACTORY)(const char* properties); /* short cut for the function pointer to invoke in the connector library */ + +/* definition for receivers */ +typedef void (*RECEIVE_MESSAGE)(const char* id, unsigned int size, void *data); /* short cut for the function pointer to invoke in the receiver library */ + +/* + * Enumeration levels to set for the logger + */ +enum loggingLevel { + /* log levels are ranked with debug being the most verbose */ + none=0, warning, info, fine, finest, debug +}; + +typedef void (*pushData)(monitordata *data); +typedef int (*sendMessage)(const char * sourceId, unsigned int size,void *data); +#ifdef __cplusplus +typedef void (*exposedLogger)(loggingLevel lev, const char * message); +#else +typedef void (*exposedLogger)(enum loggingLevel lev, const char * message); +#endif +typedef const char * (*agentProperty)(const char * key); +typedef void (*setAgentProp)(const char* key, const char* value); +typedef void (*lifeCycle)(); +#ifdef __cplusplus +typedef bool (*loadPropFunc)(const char* filename); +#else +typedef int (*loadPropFunc)(const char* filename); +#endif +typedef const char* (*getVer)(); +typedef void (*setLogLvls)(); +typedef void (*registerZipFn)(void(*)(const char*)); +typedef void (*addPlgn)(const char*); + +typedef struct agentCoreFunctions { + pushData agentPushData; + sendMessage agentSendMessage; + exposedLogger logMessage; + agentProperty getProperty; +} agentCoreFunctions; + +typedef struct loaderCoreFunctions { + lifeCycle init; + lifeCycle initialize; + lifeCycle start; + lifeCycle stop; + lifeCycle shutdown; + exposedLogger logMessage; + agentProperty getProperty; + setAgentProp setProperty; + loadPropFunc loadPropertiesFile; + getVer getAgentVersion; + setLogLvls setLogLevels; + registerZipFn registerZipFunction; + addPlgn addPlugin; + +} loaderCoreFunctions; + +DECL loaderCoreFunctions* loader_entrypoint(); + +typedef int (*PLUGIN_INITIALIZE)(const char* properties); +typedef pushsource* (*PUSH_SOURCE_REGISTER)(agentCoreFunctions aCF, unsigned int provID); +typedef void (*PUSH_CALLBACK)(monitordata* data); + +#ifdef __cplusplus +} +#endif + +#endif /* ibmras_monitoring_monitoring_h */ + + + diff --git a/Sources/cpuplugin/cpuplugin.cpp b/Sources/cpuplugin/cpuplugin.cpp new file mode 100644 index 0000000..b201488 --- /dev/null +++ b/Sources/cpuplugin/cpuplugin.cpp @@ -0,0 +1,613 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "AgentExtensions.h" +#include "../agentcore/ibmras/monitoring/Typesdef.h" +#include "cpuplugin.h" +#include +#include +#include +#include +#include +#include +#include +#if defined(_AIX) +#include +#include +#endif +#if defined(__MACH__) || defined(__APPLE__) +#include +#include +#include +#include +#include +#include +#include +#include +#endif +#if defined(__linux__) +#include +#include +#include +#endif + +#if defined(_WINDOWS) +#include +#include +#include +#endif + +#define CPUSOURCE_PULL_INTERVAL 2 +#define DEFAULT_CAPACITY 1024*10 + +namespace ibmras { +namespace monitoring { +namespace plugins { +namespace common { +namespace cpuplugin { + +CpuPlugin* CpuPlugin::instance = 0; +agentCoreFunctions CpuPlugin::aCF; + + CpuPlugin::CpuPlugin(uint32 provID): + provID(provID), noFailures(false), current(NULL), last(NULL){ + } + + CpuPlugin::~CpuPlugin(){} + + int CpuPlugin::start() { + aCF.logMessage(debug, ">>>CpuPlugin::start()"); + noFailures = true; + aCF.logMessage(debug, "<<>>CpuPlugin::stop()"); + aCF.logMessage(debug, "<<createPullSource(0, "common_cpu"); + } + + CpuPlugin* CpuPlugin::getInstance() { + return instance; + } + + char* CpuPlugin::NewCString(const std::string& s) { + char *result = new char[s.length() + 1]; + std::strcpy(result, s.c_str()); + return result; + } + + double CpuPlugin::clamp(double value, double min, double max) { + if (value > max) return max; + if (value < min) return min; + return value; + } + + double CpuPlugin::CalculateTotalCPU(struct CPUTime* start, struct CPUTime* finish) { + double cpu = (double)(finish->total - start->total) / (double)(finish->time - start->time); + if (cpu < 0.0 || cpu > 1.0) { + std::stringstream cpuss; + cpuss << "[cpu_os] Total CPU reported is out of range 0.0 to 1.0 ("<process - start->process) / (double)(finish->time - start->time); + if (cpu < 0.0 || cpu > 1.0) { + std::stringstream cpuss; + cpuss << "[cpu_os] Process CPU reported is out of range 0.0 to 1.0 ("<current->time / 1000000); // time in ms + contentss << "@#" << CalculateProcessCPU(instance->last, instance->current); + contentss << "@#" << CalculateTotalCPU(instance->last, instance->current); + contentss << '\n'; + } + + bool CpuPlugin::IsValidData(struct CPUTime* cputime) { + aCF.logMessage(debug, ">>>CpuPlugin::IsValidData"); + aCF.logMessage(debug, "<<time != start->time; + } + + monitordata* CpuPlugin::OnRequestData() { + aCF.logMessage(debug, ">>>CpuPlugin::OnRequestData"); + monitordata *data = new monitordata; + data->provID = provID; + data->sourceID = 0; + data->size = 0; + data->data = NULL; + data->persistent = false; + + if (last != NULL) { + delete last; + } + last = current; + current = getCPUTime(); + + if (IsValidData(instance->last) && IsValidData(instance->current) + && TimesAreDifferent(instance->last, instance->current)) { + + std::stringstream contentss; + contentss << "#CPUSource\n"; + AppendCPUTime(contentss); + + std::string content = contentss.str(); + data->size = static_cast(content.length()); // should data->size be a size_t? + data->data = NewCString(content); + } else { + if (!IsValidData(instance->current)) { + aCF.logMessage(debug, "[cpu_os] Skipped sending data (reason: invalid data)"); + if (noFailures) { + aCF.logMessage(warning, "[cpu_os] At least one data gathering failure occurred"); + noFailures = false; + } + } else if (IsValidData(last) && !TimesAreDifferent(last, current)) { + aCF.logMessage(debug, "[cpu_os] Skipped sending data (reason: time did not advance)"); + if (noFailures) { + aCF.logMessage(warning, "[cpu_os] At least one data gathering failure occurred"); + noFailures = false; + } + } + } + + aCF.logMessage(debug, "<<data != NULL) { + delete[] data->data; + } + delete data; + } + } + + pullsource* CpuPlugin::createPullSource(uint32 srcid, const char* name) { + aCF.logMessage(fine, "CpuPlugin::createPullSource"); + pullsource *src = new pullsource(); + src->header.name = name; + std::string desc("Description for "); + desc.append(name); + src->header.description = NewCString(desc); + src->header.sourceID = srcid; + src->next = NULL; + src->header.capacity = DEFAULT_CAPACITY; + src->callback = pullWrapper; + src->complete = pullCompleteWrapper; + src->pullInterval = CPUSOURCE_PULL_INTERVAL; // seconds + return src; + } + /***************************************************************************** + * CALLBACK WRAPPERS + *****************************************************************************/ + + extern "C" monitordata* pullWrapper() { + return CpuPlugin::getInstance()->OnRequestData(); + } + + extern "C" void pullCompleteWrapper(monitordata* data) { + CpuPlugin::getInstance()->OnComplete(data); + } + + /***************************************************************************** + * FUNCTIONS EXPORTED BY THE LIBRARY + *****************************************************************************/ + + extern "C" { + pullsource* ibmras_monitoring_registerPullSource(agentCoreFunctions aCF, uint32 provID) { + aCF.logMessage(fine, ">>>ibmras_monitoring_registerPullSource"); + pullsource *source = CpuPlugin::createSource(aCF, provID); + aCF.logMessage(fine, "<<start(); + } + + int ibmras_monitoring_plugin_stop() { + + return CpuPlugin::getInstance()->stop(); + } + + const char* ibmras_monitoring_getVersion() { + return PLUGIN_API_VERSION; + } + } + + + /***************************************************************************** + * PLATFORM DEPENDENT FUNCTIONS + *****************************************************************************/ +#if defined(_AIX) || defined(__linux__) || defined(__MACH__) || defined(__APPLE__) +#define USECS_PER_SEC (1000000) +uint64 CpuPlugin::time_microseconds() { + struct timeval tv; + gettimeofday(&tv, NULL); + + time_t seconds = tv.tv_sec; + suseconds_t microseconds = tv.tv_usec; + + return (static_cast(seconds) * USECS_PER_SEC) + microseconds; +} +#endif + +#if defined(_WINDOWS) +static inline uint64 FILETIME_to_ns(FILETIME wintime) { + DWORD high = wintime.dwHighDateTime; + DWORD low = wintime.dwLowDateTime; + return ((static_cast(high) << 32) + low) * 100; +} + +#define NSEC_TO_UNIX_EPOCH 11644473600000000000ULL +static inline bool FILETIME_to_unixtimestamp(FILETIME wintime, uint64* unixtimestamp) { + // ns since Windows epoch 1601-01-01T00:00:00Z + uint64 ns = FILETIME_to_ns(wintime); + if (ns < NSEC_TO_UNIX_EPOCH) { + // error, time is before unix epoch + CpuPlugin::aCF.logMessage(debug, "[cpu_os] Failed to convert Windows time to UNIX timestamp (before UNIX epoch)"); + return false; + } + // convert to ns since UNIX epoch 1970-01-01T00:00:00Z + (*unixtimestamp) = ns - NSEC_TO_UNIX_EPOCH; + return true; +} +#endif + +struct CPUTime* CpuPlugin::getCPUTime() { + + aCF.logMessage(debug, ">>>CpuPlugin::getCPUTime"); + +#if defined(_AIX) + static const uint32 NS_PER_CPU_TICK = 10000000; + static const uint32 NS_PER_MS = 1000000; + struct CPUTime* cputime = new CPUTime; + uint64 nsStart, nsEnd; + perfstat_cpu_total_t stats; + perfstat_process_t pstats; + perfstat_id_t psid; + + nsStart = time_microseconds() * 1000; + + if (perfstat_cpu_total(NULL, &stats, sizeof(perfstat_cpu_total_t), 1) == -1) { + std::stringstream ss; + ss << "[cpu_os] Failed to read total CPU (errno=" << errno << ")"; + aCF.logMessage(debug, ss.str().c_str()); + + delete cputime; + return NULL; + } + + // psid.name is char[IDENTIIFER_LENGTH] (64); see libperfstat.h + sprintf(psid.name, "%d", getpid()); + if (perfstat_process(&psid, &pstats, sizeof(perfstat_process_t), 1) == -1) { + std::stringstream ss; + ss << "[cpu_os] Failed to read process CPU (errno=" << errno << ")"; + aCF.logMessage(debug, ss.str().c_str()); + + delete cputime; + return NULL; + } + + nsEnd = time_microseconds() * 1000; + + cputime->nprocs = stats.ncpus; + cputime->total = (stats.user + stats.sys) * NS_PER_CPU_TICK / cputime->nprocs; + cputime->process = (pstats.ucpu_time + pstats.scpu_time) * NS_PER_MS / cputime->nprocs; + cputime->time = nsStart + ((nsEnd - nsStart) / 2); + + return cputime; +#elif defined(__linux__) + static const uint32 userHz = sysconf(_SC_CLK_TCK); + static const uint32 NS_PER_HZ = 1000000000 / userHz; + struct CPUTime* cputime = new CPUTime; + uint64 nsStart, nsEnd; + + nsStart = time_microseconds() * 1000; + + if (!read_total_cpu_time(&cputime->total, NS_PER_HZ)) { + delete cputime; + return NULL; + } + if (!read_process_cpu_time(&cputime->process, NS_PER_HZ)) { + delete cputime; + return NULL; + } + + nsEnd = time_microseconds() * 1000; + + cputime->nprocs = get_nprocs(); + cputime->total /= cputime->nprocs; + cputime->process /= cputime->nprocs; + cputime->time = nsStart + ((nsEnd - nsStart) / 2); + + return cputime; +#elif defined(_WINDOWS) + struct CPUTime* cputime = new CPUTime; + SYSTEM_INFO sysinfo; + + GetSystemInfo(&sysinfo); + cputime->nprocs = sysinfo.dwNumberOfProcessors; + + if (!read_process_cpu_time(&cputime->process, 0)) { + aCF.logMessage(debug, "[cpu_os] Failed to read process CPU"); + delete cputime; + return NULL; + } + if (!read_total_cpu_time(&cputime->time, &cputime->total)) { + aCF.logMessage(debug, "[cpu_os] Failed to read total CPU"); + delete cputime; + return NULL; + } + + cputime->process /= cputime->nprocs; // process cpu is %age of 1 core (system cpu is %age of all cores) + return cputime; + +#elif defined(__MACH__) || defined(__APPLE__) + + //We first retrieve the number of processors, since the total load will have to be divided by this value + int phys = 0; + uint64 nsStart, nsEnd; + size_t len = sizeof(phys); + struct CPUTime* cputime = new CPUTime; + int err = sysctlbyname("hw.physicalcpu", &phys, &len, NULL, 0); + if(!err){ + cputime->nprocs = phys; + } else { + std::stringstream errorss; + errorss << "[env_os] Number of CPUs not set, error: "; + errorss << strerror(errno); + aCF.logMessage(warning, errorss.str().c_str()); + } +//We do now get the PROCESS usage. + struct rusage usage; + nsStart = time_microseconds() * 1000; + err = getrusage(RUSAGE_SELF, &usage); + nsEnd = time_microseconds() * 1000; + if(!err) { + cputime->process = (usage.ru_utime.tv_sec*1000000 + usage.ru_utime.tv_usec + usage.ru_stime.tv_sec*1000000 + usage.ru_stime.tv_usec)*1000/cputime->nprocs; + cputime->total = 0;//TODO implement total cpu time (the API used by the top command is subject to change and would require testing on more than one platform, eg, not only 10.9.5) + cputime->time = nsStart + ((nsEnd - nsStart) / 2); + } + + return cputime; +#else + return NULL; +#endif +} + +#if defined(_WINDOWS) +bool CpuPlugin::read_total_cpu_time(uint64* unixtimestamp, uint64* totaltime) { +#else +bool CpuPlugin::read_total_cpu_time(uint64* totaltime, const uint32 NS_PER_HZ) { +#endif + aCF.logMessage(debug, ">>>read_total_cpu_time"); +#if defined(__linux__) + uint64 user = 0, nice = 0, system = 0; + + std::ifstream filestream("/proc/stat"); + + if (!filestream.is_open()) { + aCF.logMessage(debug, "[cpu_os] Failed to open /proc/stat"); + return false; + } + + std::string prompt; // "cpu" + filestream >> prompt >> user >> nice >> system; + bool parsedSuccessfully = filestream.good(); + filestream.close(); + + if (!parsedSuccessfully) { + aCF.logMessage(debug, "[cpu_os] Failed to parse /proc/stat"); + return false; + } + + (*totaltime) = (user + nice + system) * NS_PER_HZ; + + return true; +#elif defined(_WINDOWS) + LONGLONG user, kernel; + FILETIME utcTimeStamp; + HQUERY Query = NULL; + HCOUNTER userCounter = NULL; + HCOUNTER privilegedCounter = NULL; + PDH_RAW_COUNTER counterValue; + PDH_STATUS Status = ERROR_SUCCESS; + + Status = PdhOpenQuery(NULL, (DWORD_PTR) NULL, &Query); + if (ERROR_SUCCESS != Status) { + std::stringstream ss; + ss << "[cpu_os] Failed to open pdh query for total cpu (status=" << Status << ")"; + aCF.logMessage(debug, ss.str().c_str()); + return false; + } + + Status = PdhAddCounter(Query, (LPCTSTR) (WCHAR *) + "\\Processor(_Total)\\% User Time", 0, &userCounter); + + if (ERROR_SUCCESS != Status) { + PdhCloseQuery(Query); + std::stringstream ss; + ss << "[cpu_os] Failed to add user time pdh counter for total cpu (status=" << Status << ")"; + aCF.logMessage(debug, ss.str().c_str()); + return false; + } + + Status = PdhAddCounter(Query, (LPCTSTR) (WCHAR *) + "\\Processor(_Total)\\% Privileged Time", 0, &privilegedCounter); + if (ERROR_SUCCESS != Status) { + PdhCloseQuery(Query); + std::stringstream ss; + ss << "[cpu_os] Failed to add kernel time pdh counter for total cpu (status=" << Status << ")"; + aCF.logMessage(debug, ss.str().c_str()); + return false; + } + + Status = PdhCollectQueryData(Query); + if (ERROR_SUCCESS != Status) { + PdhCloseQuery(Query); + std::stringstream ss; + ss << "[cpu_os] Failed to collect pdh query data for total cpu (status=" << Status << ")"; + aCF.logMessage(debug, ss.str().c_str()); + return false; + } + + Status = PdhGetRawCounterValue(privilegedCounter, NULL, &counterValue); + if (ERROR_SUCCESS != Status) { + PdhCloseQuery(Query); + std::stringstream ss; + ss << "[cpu_os] Failed to get kernel time counter value for total cpu (status=" << Status << ")"; + aCF.logMessage(debug, ss.str().c_str()); + return false; + } + user = counterValue.FirstValue; + + Status = PdhGetRawCounterValue(userCounter, NULL, &counterValue); + if (ERROR_SUCCESS != Status) { + PdhCloseQuery(Query); + std::stringstream ss; + ss << "[cpu_os] Failed to get user time counter value for total cpu (status=" << Status << ")"; + aCF.logMessage(debug, ss.str().c_str()); + return false; + } + kernel = counterValue.FirstValue; + + PdhCloseQuery(Query); + + (*totaltime) = (static_cast(user) + static_cast(kernel)) * 100; // to ns + if (!LocalFileTimeToFileTime(&counterValue.TimeStamp, &utcTimeStamp)) { + aCF.logMessage(debug, "[cpu_os] Failed to convert local time to UTC"); + return false; + } + if (!FILETIME_to_unixtimestamp(utcTimeStamp, unixtimestamp)) { + // message already logged + return false; + } + + return true; +#else + return false; +#endif +} + +bool CpuPlugin::read_process_cpu_time(uint64* proctime, const uint32 NS_PER_HZ) { + aCF.logMessage(debug, ">>>read_process_cpu_time"); + +#if defined(__linux__) + uint64 user = 0, kernel = 0; + + std::stringstream filenamess; + filenamess << "/proc/" << getpid() << "/stat"; + std::string filename = filenamess.str(); + + std::ifstream filestream(filename.c_str()); + + if (!filestream.is_open()) { + std::stringstream ss; + ss << "[cpu_os] Failed to open " << filename; + aCF.logMessage(debug, ss.str().c_str()); + return false; + } + + int32 dummyInt; + uint32 dummyUInt; + std::string dummyStr; + filestream >> dummyInt >> dummyStr; + + // the second parameter in a /proc//stat is a possible filename + // of the running process. This can pose a problem if the filename + // has a space (i.e. (Passenger NodeA)). This checks to read the stream + // until the end parenthese is found. + while(dummyStr[dummyStr.length()-1] != ')') { + filestream >> dummyStr; + } + + filestream >> dummyStr >> dummyInt >> dummyInt; + filestream >> dummyInt >> dummyInt >> dummyInt >> dummyUInt >> dummyUInt; + filestream >> dummyUInt >> dummyUInt >> dummyUInt; + filestream >> user >> kernel; + bool parsedSuccessfully = filestream.good(); + filestream.close(); + + if (!parsedSuccessfully) { + std::stringstream ss; + ss << "[cpu_os] Failed to parse " << filename; + aCF.logMessage(debug, ss.str().c_str()); + return false; + } + + (*proctime) = (user + kernel) * NS_PER_HZ; + + return true; +#elif defined(_WINDOWS) + FILETIME create; + FILETIME exit; + FILETIME kernel; + FILETIME user; + HANDLE process = GetCurrentProcess(); + BOOL rc = GetProcessTimes(process, &create, &exit, &kernel, &user); + + if (!rc) { + std::stringstream ss; + ss << "[cpu_os] Failed to get process cpu time (error=" << GetLastError() << ")"; + aCF.logMessage(debug, ss.str().c_str()); + return false; + } + (*proctime) = FILETIME_to_ns(kernel) + FILETIME_to_ns(user); + return true; +#else + return false; +#endif +} + +} +} +} +} +} diff --git a/Sources/cpuplugin/include/cpuplugin.h b/Sources/cpuplugin/include/cpuplugin.h new file mode 100644 index 0000000..4f8befa --- /dev/null +++ b/Sources/cpuplugin/include/cpuplugin.h @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +/* + * CpuPlugin.h + * + * Created on: 30 Apr 2015 + * Author: Admin + */ + +#ifndef CPUPLUGIN_H_ +#define CPUPLUGIN_H_ + +#include "AgentExtensions.h" +#include "../../agentcore/ibmras/monitoring/Typesdef.h" +#include + +namespace ibmras { +namespace monitoring { +namespace plugins { +namespace common { +namespace cpuplugin { + +struct CPUTime { + uint64 time; /* ns since fixed point */ + uint64 total; /* cumulative total cpu time in ns */ + uint64 process; /* cumulative process cpu time in ns */ + uint32 nprocs; +}; + +class CpuPlugin { +public: + static agentCoreFunctions aCF; + + monitordata* OnRequestData(); + void OnComplete(monitordata* data); + static pullsource* createSource(agentCoreFunctions aCF, uint32 provID); + static CpuPlugin* getInstance(); + virtual ~CpuPlugin(); + int start(); + int stop(); + +private: + uint32 provID; + static CpuPlugin* instance; + bool noFailures; + struct CPUTime* last; + struct CPUTime* current; + + CpuPlugin(uint32 provID); + static char* NewCString(const std::string& s); + static double clamp(double value, double min, double max); + static double CalculateTotalCPU(struct CPUTime* start, struct CPUTime* finish); + static double CalculateProcessCPU(struct CPUTime* start, struct CPUTime* finish); + static void AppendCPUTime(std::stringstream& contentss); + static bool IsValidData(struct CPUTime* cputime); + static bool TimesAreDifferent(struct CPUTime* start, struct CPUTime* finish); + pullsource* createPullSource(uint32 srcid, const char* name); + CPUTime* getCPUTime(); +#if defined(_WINDOWS) + static bool read_total_cpu_time(uint64* unixtimestamp, uint64* totaltime); +#else + static bool read_total_cpu_time(uint64* totaltime, const uint32 NS_PER_HZ); +#endif + static bool read_process_cpu_time(uint64* proctime, const uint32 NS_PER_HZ); +#if defined(_AIX) || defined(__linux__) || defined(__MACH__) || defined(__APPLE__) + static uint64 time_microseconds(); +#endif +}; +/*------------------------------------------------------------------------------------- + * These are the namespace functions that are used to avoid the restrictions imposed + * by the defined typedefs for callback functions. Non-static member function pointers + * would have a different prototype than the one generically typedef'd in the headers, + * which is: + * typedef monitordata* (*PULL_CALLBACK)(void); + * typedef void (*PULL_CALLBACK_COMPLETE)(monitordata*); + * These functions will be passed to the agent using the pullsource* structure that is + * returned by the registerPullSource method. + *-----------------------------------------------------------------------------------*/ +extern "C" monitordata* pullWrapper(); +extern "C" void pullCompleteWrapper(monitordata* data); + +/* + * These 4 functions are the symbols that the Plugin.scan method will look for when scanning the + * plugins directory, therefore they must be declared as extern "C" so their names are not mangled + * by the compiler. Also, PLUGIN_API_DECL must be in front of the declarations so the f's are exported + * when a Windows dll is generated. + */ +extern "C" { +PLUGIN_API_DECL pullsource* ibmras_monitoring_registerPullSource(agentCoreFunctions aCF, uint32 provID); +PLUGIN_API_DECL int ibmras_monitoring_plugin_init(const char* properties); +PLUGIN_API_DECL int ibmras_monitoring_plugin_start(); +PLUGIN_API_DECL int ibmras_monitoring_plugin_stop(); +PLUGIN_API_DECL const char* ibmras_monitoring_getVersion(); +} + +} +} +} +} +} + + + + + + +#endif /* CPUPLUGIN_H_ */ diff --git a/Sources/envplugin/envplugin.cpp b/Sources/envplugin/envplugin.cpp new file mode 100644 index 0000000..73d60ef --- /dev/null +++ b/Sources/envplugin/envplugin.cpp @@ -0,0 +1,605 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + + +#if defined(_ZOS) +#define _XOPEN_SOURCE_EXTENDED 1 //This macro makes zOS' unistd.h expose gethostname(). +#endif + + +#include "envplugin.h" +#include "AgentExtensions.h" +#include "../agentcore/ibmras/monitoring/Typesdef.h" +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include // uname() +#include // get_nprocs() +#include // gethostname() +#endif + +#if defined(__MACH__) || defined(__APPLE__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // gethostname() +#define HOST_NAME_MAX 256 +#endif + +#if defined(_AIX) +#include_next +#include // uname() +#include // get_nprocs() +#include // gethostname() +#include +#include +#endif + +#ifdef _WINDOWS +#include "windows.h" +#define HOST_NAME_MAX 256 +#endif +#ifdef _ZOS +#define HOST_NAME_MAX 256 +#endif + +#define ENVIRONMENT_PULL_INTERVAL 1200 + +namespace ibmras { +namespace monitoring { +namespace plugins { +namespace common { +namespace environment { + + +#if defined(__linux__) || defined(_AIX) || defined(_ZOS) || defined(__MACH__) || defined(__APPLE__) +extern "C" char **environ; // use GetEnvironmentStrings() on Windows (maybe getenv() on POSIX?) +#endif + + +template +std::string itoa(T t); + +#define DEFAULT_BUCKET_CAPACITY 1024*10 + +EnvPlugin* EnvPlugin::instance = NULL; +agentCoreFunctions EnvPlugin::aCF; + + EnvPlugin::EnvPlugin(uint32 provID): + provID(provID) { + } + + EnvPlugin::~EnvPlugin(){} + + + int EnvPlugin::start() { + aCF.logMessage(debug, ">>>EnvPlugin::start()"); + EnvPlugin::initStaticInfo(); // See below for platform-specific implementation, protected by ifdefs + aCF.logMessage(debug, "<<>>EnvPlugin::stop()"); + aCF.logMessage(debug, "<<createPullSource(0, "common_env"); + } + + EnvPlugin* EnvPlugin::getInstance() { + return instance; + } + + char* EnvPlugin::NewCString(const std::string& s) { + char *result = new char[s.length() + 1]; + std::strcpy(result, s.c_str()); + return result; + } + +void EnvPlugin::AppendEnvVars(std::stringstream &ss) { + bool hostnameDefined = false; + int i = 0; + while (environ[i] != NULL) { + ss << "environment." << environ[i] << '\n'; + if (std::strncmp("HOSTNAME=", environ[i], std::strlen("HOSTNAME=")) == 0) { + hostnameDefined = true; + } + i++; + } + if (!hostnameDefined) { + char hostname[HOST_NAME_MAX + 1]; + if (gethostname(hostname, HOST_NAME_MAX) == 0) { + ss << "environment.HOSTNAME=" << hostname << '\n'; + } + } +} + +void EnvPlugin::AppendSystemInfo(std::stringstream &ss) { + ss << "os.arch=" << EnvPlugin::getInstance()->arch << '\n'; // eg "amd64" + ss << "os.name=" << EnvPlugin::getInstance()->osName << '\n'; // eg "Windows 7" + ss << "os.version=" << EnvPlugin::getInstance()->osVersion << '\n'; // eg "6.1 build 7601 Service Pack 1" + ss << "pid=" << EnvPlugin::getInstance()->pid << '\n'; // eg "12345" + ss << "native.library.date=" << EnvPlugin::getInstance()->agentNativeBuildDate << '\n'; // eg "Oct 10 2014 11:44:56" + ss << "jar.version=" << EnvPlugin::getInstance()->agentVersion << '\n'; // eg "3.0.0.20141030" + ss << "number.of.processors=" << EnvPlugin::getInstance()->nprocs << '\n'; // eg 8 + ss << "command.line=" << EnvPlugin::getInstance()->commandLine << '\n'; +} + +monitordata* EnvPlugin::OnRequestData() { + aCF.logMessage(debug, ">>>EnvPlugin::OnRequestData"); + monitordata *data = new monitordata; + data->provID = provID; + data->sourceID = 0; + + std::stringstream contentss; + contentss << "#EnvironmentSource\n"; + AppendEnvVars(contentss); + AppendSystemInfo(contentss); + + std::string content = contentss.str(); + data->size = static_cast(content.length()); // should data->size be a size_t? + data->data = NewCString(content); + data->persistent = false; + aCF.logMessage(debug, "<<data != NULL) { + delete[] data->data; + } + delete data; + } +} + +pullsource* EnvPlugin::createPullSource(uint32 srcid, const char* name) { + pullsource *src = new pullsource(); + src->header.name = name; + std::string desc("Description for "); + desc.append(name); + src->header.description = NewCString(desc); + src->header.sourceID = srcid; + src->next = NULL; + src->header.capacity = DEFAULT_BUCKET_CAPACITY; + src->callback = pullWrapper; + src->complete = pullCompleteWrapper; + src->pullInterval = ENVIRONMENT_PULL_INTERVAL; + return src; +} + + /***************************************************************************** + * CALLBACK WRAPPERS + *****************************************************************************/ + + extern "C" monitordata* pullWrapper() { + return EnvPlugin::getInstance()->OnRequestData(); + } + + extern "C" void pullCompleteWrapper(monitordata* data) { + EnvPlugin::getInstance()->OnComplete(data); + } + + /***************************************************************************** + * FUNCTIONS EXPORTED BY THE LIBRARY + *****************************************************************************/ + +extern "C" { + pullsource* ibmras_monitoring_registerPullSource(agentCoreFunctions aCF, uint32 provID) { + aCF.logMessage(fine, ">>>ibmras_monitoring_registerPullSource"); + pullsource *source = EnvPlugin::createSource(aCF, provID); + EnvPlugin::getInstance()->agentVersion = std::string(aCF.getProperty("agent.version")); + EnvPlugin::getInstance()->agentNativeBuildDate = std::string(aCF.getProperty("agent.native.build.date")); + aCF.logMessage(fine, "<<start(); + } + + int ibmras_monitoring_plugin_stop() { + EnvPlugin::aCF.logMessage(fine, "[environment_os] Stopping"); + return EnvPlugin::getInstance()->stop(); + } + + const char* ibmras_monitoring_getVersion() { + return PLUGIN_API_VERSION; + } +} + +/* + * Linux + */ +#if defined(__linux__) +std::string EnvPlugin::GetCommandLine() { + std::stringstream filenamess; + filenamess << "/proc/" << getpid() << "/cmdline"; + std::string filename = filenamess.str(); + + std::ifstream filestream(filename.c_str()); + + if (!filestream.is_open()) { + + std::stringstream envss; + envss << "Failed to open " << filename.c_str(); + + return ""; + } + + std::istreambuf_iterator begin(filestream), end; + std::string cmdline(begin, end); + filestream.close(); + + for (unsigned i=0; i < cmdline.length(); i++) { + if (cmdline[i] == '\0') { + cmdline[i] = ' '; + } + } + return cmdline; +} + +void EnvPlugin::initStaticInfo() { + struct utsname sysinfo; + int rc = uname(&sysinfo); + if (rc >= 0) { + EnvPlugin::getInstance()->arch = std::string(sysinfo.machine); + EnvPlugin::getInstance()->osName = std::string(sysinfo.sysname); + EnvPlugin::getInstance()->osVersion = std::string(sysinfo.release) + std::string(sysinfo.version); + } else { + EnvPlugin::getInstance()->arch = "unknown"; // could fallback to compile-time information + EnvPlugin::getInstance()->osName = "Linux"; + EnvPlugin::getInstance()->osVersion = ""; + } + + EnvPlugin::getInstance()->nprocs = itoa(get_nprocs()); + EnvPlugin::getInstance()->pid = itoa(getpid()); + EnvPlugin::getInstance()->commandLine = GetCommandLine(); +} + +#endif + +/* + * AIX + */ +#if defined (_AIX) + +std::string EnvPlugin::GetCommandLine() { + struct procsinfo proc; + char procargs[512]; // Is this a decent length? Should we heap allocate and expand? + + proc.pi_pid = getpid(); + int rc = getargs(&proc, sizeof(proc), procargs, sizeof(procargs)); + if (rc < 0) { + + + + std::stringstream envss; + envss << "Failed to get command line " << errno; + aCF.logMessage(debug, envss.str().c_str()); + + return std::string(); + } + std::stringstream cmdliness; + char *current = procargs; + int written = 0; + while (std::strlen(current) > 0) { + if (written++ > 0) cmdliness << ' '; + cmdliness << current; + current = current + std::strlen(current) + 1; + } + return cmdliness.str(); +} + +void EnvPlugin::initStaticInfo() { + struct utsname sysinfo; + int rc = uname(&sysinfo); + if (rc >= 0) { + uint64_t architecture = getsystemcfg(SC_ARCH); + uint64_t width = getsystemcfg(SC_WIDTH); + + std::string bits = (width == 32) ? "32" : + (width == 64) ? "64" : + ""; + EnvPlugin::getInstance()->arch = (architecture == POWER_PC) ? "ppc" : ""; + if (EnvPlugin::getInstance()->arch != "") { + EnvPlugin::getInstance()->arch += bits; + } else { + EnvPlugin::getInstance()->arch = std::string(sysinfo.machine); + } + EnvPlugin::getInstance()->osName = std::string(sysinfo.sysname); + EnvPlugin::getInstance()->osVersion = std::string(sysinfo.release) + std::string(sysinfo.version); + } else { + EnvPlugin::getInstance()->arch = "unknown"; // could fallback to compile-time information + EnvPlugin::getInstance()->osName = "AIX"; + EnvPlugin::getInstance()->osVersion = ""; + } + // might be _SC_NPROCESSORS_ONLN -https://www.ibm.com/developerworks/community/forums/html/topic?id=77777777-0000-0000-0000-000014250083 + EnvPlugin::getInstance()->nprocs = itoa(sysconf(_SC_NPROCESSORS_CONF)); + EnvPlugin::getInstance()->pid = itoa(getpid()); + EnvPlugin::getInstance()->commandLine = GetCommandLine(); +} + +#endif + + +#if defined(__APPLE__) || defined(__MACH__) + +std::string EnvPlugin::GetCommandLine() { + + int pid = getpid(); + + char buf[128]; + proc_name(pid, buf, 128); + + std::string app = std::string(buf); + + /* + * Second part of this workaround, is to execute ps -f and then parse it to only get + * the command and arguments passed to it, (with the help of the fact that we know + * the name of the executable from the above search) + */ + + std::stringstream command; + command <<"ps -f"<arch = std::string(architecture); + } else { + EnvPlugin::getInstance()->arch = ""; + + std::stringstream errorss; + errorss << "[env_os] CPU arch not set, error: "; + errorss << strerror(errno); + aCF.logMessage(warning, errorss.str().c_str()); + } + + //Amount of physical CPUs + int phys = 0; + len = sizeof(phys); + res = sysctlbyname("hw.physicalcpu", &phys, &len, NULL, 0); + if(!res){ + EnvPlugin::getInstance()->nprocs = itoa(phys); + } else { + EnvPlugin::getInstance()->nprocs = ""; + + std::stringstream errorss; + errorss << "[env_os] Number of CPUs not set, error: "; + errorss << strerror(errno); + aCF.logMessage(warning, errorss.str().c_str()); + } + + //Process ID + int pid = getpid(); + EnvPlugin::getInstance()->pid = itoa(pid); + + //Command line + EnvPlugin::getInstance()->commandLine = GetCommandLine(); + + //OS version and name + std::string osVersion; + osVersion = getCommandOutput("sw_vers -productVersion"); + EnvPlugin::getInstance()->osVersion = osVersion.substr(0, osVersion.find("\n")); + + std::string osType; + osType = getCommandOutput("sw_vers -productName"); + EnvPlugin::getInstance()->osName = osType.substr(0, osType.find("\n")); + +} + +std::string getCommandOutput(std::string command) { + FILE *fp; + char str[40]; + std::stringstream output; + fp = popen(command.c_str(), "r"); + while (fgets(str, sizeof(str), fp)) + { + output << str; + } + pclose(fp); + return output.str().c_str(); +} + +#endif + +/* + * Windows + */ +#ifdef _WINDOWS +const std::string EnvPlugin::GetWindowsMajorVersion() { + OSVERSIONINFOEX versionInfo; + versionInfo.dwOSVersionInfoSize = sizeof(versionInfo); + + static const std::string defaultVersion = "Windows"; + + if (!GetVersionEx((OSVERSIONINFO *) &versionInfo)) { + return defaultVersion; + } + + switch (versionInfo.dwPlatformId) { + case VER_PLATFORM_WIN32s: return "Windows 3.1"; + case VER_PLATFORM_WIN32_WINDOWS: + switch (versionInfo.dwMinorVersion) { + case 0: return "Windows 95"; + case 90: return "Windows Me"; + default: return "Windows 98"; + } + break; /* VER_PLATFORM_WIN32_WINDOWS */ + + case VER_PLATFORM_WIN32_NT: + if (versionInfo.dwMajorVersion < 5) { + return "Windows NT"; + + } else if (versionInfo.dwMajorVersion == 5) { + switch (versionInfo.dwMinorVersion) { + case 0: return "Windows 2000"; + /* case 1: WinNT 5.1 => Windows XP. Handled by the default. */ + case 2: + /* WinNT 5.2 can be either Win2003 Server or Workstation (e.g. XP64). + * Report workstation products as "Windows XP". + * See CMVC 89090 and CMVC 89127 */ + switch (versionInfo.wProductType) { + case VER_NT_WORKSTATION: return "Windows XP"; + case VER_NT_DOMAIN_CONTROLLER: + case VER_NT_SERVER: + default: return "Windows Server 2003"; + } + default: return "Windows XP"; + } + + } else if (versionInfo.dwMajorVersion == 6) { + switch (versionInfo.wProductType) { + case VER_NT_WORKSTATION: + switch (versionInfo.dwMinorVersion) { + case 0: return "Windows Vista"; + case 1: return "Windows 7"; + case 2: return "Windows 8"; + default: return defaultVersion; + } /* VER_NT_WORKSTATION */ + default: + switch (versionInfo.dwMinorVersion) { + case 0: return "Windows Server 2008"; + case 1: return "Windows Server 2008 R2"; + case 2: return "Windows Server 2012"; + default: return defaultVersion; + } + } + } else { + return defaultVersion; + } + break; /* VER_PLATFORM_WIN32_NT */ + + default: return defaultVersion; + } +} + +const std::string EnvPlugin::GetWindowsBuild() { + OSVERSIONINFOW versionInfo; + int len = sizeof("0123456789.0123456789 build 0123456789 ") + 1; + char *buffer; + int position; + + static const std::string defaultBuild = ""; + + versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); + + if (!GetVersionExW(&versionInfo)) { + return defaultBuild; + } + + if (NULL != versionInfo.szCSDVersion) { + len += WideCharToMultiByte(CP_UTF8, 0, versionInfo.szCSDVersion, -1, NULL, 0, NULL, NULL); + } + buffer = new char[len]; + if (NULL == buffer) { + return defaultBuild; + } + position = sprintf(buffer,"%d.%d build %d", + versionInfo.dwMajorVersion, + versionInfo.dwMinorVersion, + versionInfo.dwBuildNumber & 0x0000FFFF); + + if ((NULL != versionInfo.szCSDVersion) && ('\0' != versionInfo.szCSDVersion[0])) { + buffer[position++] = ' '; + WideCharToMultiByte(CP_UTF8, 0, versionInfo.szCSDVersion, -1, &buffer[position], len - position - 1, NULL, NULL); + } + + std::string version(buffer); + delete[] buffer; + return version; +} + +void EnvPlugin::initStaticInfo() { + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + switch (sysinfo.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: EnvPlugin::getInstance()->arch = "x86_64"; break; + case PROCESSOR_ARCHITECTURE_ARM: EnvPlugin::getInstance()->arch = "arm"; break; + case PROCESSOR_ARCHITECTURE_IA64: EnvPlugin::getInstance()->arch = "itanium"; break; + case PROCESSOR_ARCHITECTURE_INTEL: EnvPlugin::getInstance()->arch = "x86"; break; + default: + EnvPlugin::getInstance()->arch = "unknown"; // could fallback to compile-time information + break; + } + EnvPlugin::getInstance()->osName = GetWindowsMajorVersion(); + EnvPlugin::getInstance()->osVersion = GetWindowsBuild(); + EnvPlugin::getInstance()->nprocs = itoa(sysinfo.dwNumberOfProcessors); + EnvPlugin::getInstance()->pid = itoa(GetCurrentProcessId()); + EnvPlugin::getInstance()->commandLine = std::string(GetCommandLine()); +} +#endif + +template +std::string itoa(T t) { + std::stringstream s; + s << t; + return s.str(); +} + +}//environment +}//common +}//plugins +}//monitoring +}//ibmras + + diff --git a/Sources/envplugin/include/envplugin.h b/Sources/envplugin/include/envplugin.h new file mode 100644 index 0000000..81548fa --- /dev/null +++ b/Sources/envplugin/include/envplugin.h @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +/* + * envplugin.h + * + * Created on: 21 Apr 2015 + * Author: MC + */ + +#ifndef ibmras_monitoring_plugins_common_environment_envplugin_h +#define ibmras_monitoring_plugins_common_environment_envplugin_h + +#include "AgentExtensions.h" +#include "../../agentcore/ibmras/monitoring/Typesdef.h" +#include + +namespace ibmras { +namespace monitoring { +namespace plugins { +namespace common { +namespace environment { + +class EnvPlugin { +public: + static agentCoreFunctions aCF; + + std::string arch; + std::string osName; + std::string osVersion; + std::string nprocs; + std::string pid; + std::string commandLine; + std::string agentVersion; + std::string agentNativeBuildDate; + + monitordata* OnRequestData(); + void OnComplete(monitordata* data); + static pullsource* createSource(agentCoreFunctions aCF, uint32 provID); + static EnvPlugin* getInstance(); + virtual ~EnvPlugin(); + int start(); + int stop(); + +private: + uint32 provID; + static EnvPlugin* instance; + + EnvPlugin(uint32 provID); + static char* NewCString(const std::string& s); + void AppendEnvVars(std::stringstream &ss); + void AppendSystemInfo(std::stringstream &ss); + pullsource* createPullSource(uint32 srcid, const char* name); + static void initStaticInfo(); +#if defined(_WINDOWS) + static const std::string GetWindowsMajorVersion(); + static const std::string GetWindowsBuild(); +#else + static std::string GetCommandLine(); +#endif + +}; +/*------------------------------------------------------------------------------------- + * These are the namespace functions that are used to avoid the restrictions imposed + * by the defined typedefs for callback functions. Non-static member function pointers + * would have a different prototype than the one generically typedef'd in the headers, + * which is: + * typedef monitordata* (*PULL_CALLBACK)(void); + * typedef void (*PULL_CALLBACK_COMPLETE)(monitordata*); + * These functions will be passed to the agent using the pullsource* structure that is + * returned by the registerPullSource method. + *-----------------------------------------------------------------------------------*/ +extern "C" monitordata* pullWrapper(); +extern "C" void pullCompleteWrapper(monitordata* data); +#if defined(__MACH__) || defined(__APPLE__) +std::string getCommandOutput(std::string command); +#endif +/* + * These 4 functions are the symbols that the Plugin.scan method will look for when scanning the + * plugins directory, therefore they must be declared as extern "C" so their names are not mangled + * by the compiler. Also, PLUGIN_API_DECL must be in front of the declarations so the f's are exported + * when a Windows dll is generated. + */ +extern "C" { +PLUGIN_API_DECL pullsource* ibmras_monitoring_registerPullSource(agentCoreFunctions aCF, uint32 provID); +PLUGIN_API_DECL int ibmras_monitoring_plugin_init(const char* properties); +PLUGIN_API_DECL int ibmras_monitoring_plugin_start(); +PLUGIN_API_DECL int ibmras_monitoring_plugin_stop(); +PLUGIN_API_DECL const char* ibmras_monitoring_getVersion(); +} + +} +} +} +} +} + +//#endif +#endif /* ENVIRONMENTPLUGIN_H_ */ diff --git a/Sources/hcapiplugin/APIConnector.cpp b/Sources/hcapiplugin/APIConnector.cpp new file mode 100644 index 0000000..e050a35 --- /dev/null +++ b/Sources/hcapiplugin/APIConnector.cpp @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "APIConnector.h" +#include "../agentcore/ibmras/common/util/strUtils.h" +#include "../agentcore/ibmras/common/MemoryManager.h" + +#define DEFAULT_CAPACITY 1024000 /* default bucket capacity = 1MB */ + +#if defined(_WINDOWS) +#define APICONNECTORPLUGIN_DECL __declspec(dllexport) /* required for DLLs to export the plugin functions */ +#else +#define APICONNECTORPLUGIN_DECL +#endif + +namespace APIConnector { + +const char* apiConnVersion = "1.0"; + +APIConnector::APIConnector() { +} + +void (*listener)(const char*, unsigned int, void*); + +int APIConnector::sendMessage(const std::string &sourceId, uint32 size, void *data) { + if (listener != NULL) { + char* asciiString = ibmras::common::util::createAsciiString(sourceId.c_str()); + listener(asciiString, size, data); + ibmras::common::memory::deallocate((unsigned char**)&asciiString); + } + return size; +} + +extern "C" { +APICONNECTORPLUGIN_DECL void registerListener(void(*func)(const char *, unsigned int, void*)){ + listener = func; +} + +APICONNECTORPLUGIN_DECL void deregisterListener(){ + listener = NULL; +} + +APICONNECTORPLUGIN_DECL void pushData(const char *sendData) { + monitordata data; + data.persistent = false; + data.provID = plugin::provid; + data.sourceID = 0; + data.size = strlen(sendData); // should data->size be a size_t? + data.data = sendData; + plugin::api.agentPushData(&data); +} + +APICONNECTORPLUGIN_DECL void sendControl(const char* topic, unsigned int length, void* message) { + char* nativeString = ibmras::common::util::createNativeString(topic); + plugin::receiver->receiveMessage(std::string(nativeString), length, message); + ibmras::common::memory::deallocate((unsigned char**)&nativeString); +} + +} // end extern C + +void APIConnector::registerReceiver(ibmras::monitoring::connector::Receiver *receiver) { + plugin::receiver = receiver; +} + +int APIConnector::start() { + return 0; +} + +int APIConnector::stop() { + return 0; +} + +APIConnector::~APIConnector() { +} + +static char* NewCString(const std::string& s) { + char *result = new char[s.length() + 1]; + std::strcpy(result, s.c_str()); + return result; +} + +pushsource* createPushSource(uint32 srcid, const char* name) { + pushsource *src = new pushsource(); + src->header.name = name; + std::string desc("Description for "); + desc.append(name); + src->header.description = NewCString(desc); + src->header.sourceID = srcid; + src->next = NULL; + src->header.capacity = DEFAULT_CAPACITY; + return src; +} + + +extern "C" { +APICONNECTORPLUGIN_DECL pushsource* ibmras_monitoring_registerPushSource(agentCoreFunctions api, uint32 provID) { + plugin::api = api; + plugin::api.logMessage(debug, "[api_push] Registering push sources"); + pushsource *head = createPushSource(0, "api"); + plugin::provid = provID; + return head; +} + +APICONNECTORPLUGIN_DECL void* ibmras_monitoring_getConnector(const char* properties) { + listener = NULL; + return new APIConnector(); +} + +APICONNECTORPLUGIN_DECL int ibmras_monitoring_plugin_start() { + return 0; +} + +APICONNECTORPLUGIN_DECL int ibmras_monitoring_plugin_stop() { + return 0; +} + +APICONNECTORPLUGIN_DECL const char* ibmras_monitoring_getVersion() { + return apiConnVersion; +} + +} // extern "C" + +} // Listener namespace diff --git a/Sources/hcapiplugin/include/APIConnector.h b/Sources/hcapiplugin/include/APIConnector.h new file mode 100644 index 0000000..6069c45 --- /dev/null +++ b/Sources/hcapiplugin/include/APIConnector.h @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#ifndef ibmras_monitoring_connector_apiconnector_h +#define ibmras_monitoring_connector_apiconnector_h + +#include "../../agentcore/ibmras/monitoring/connector/Connector.h" +#include "AgentExtensions.h" +#include "../../agentcore/ibmras/monitoring/Typesdef.h" + +#include + +#if defined(_WINDOWS) +#define APICONNECTORPLUGIN_DECL __declspec(dllexport) /* required for DLLs to export the plugin functions */ +#else +#define APICONNECTORPLUGIN_DECL +#endif + +namespace APIConnector { + +extern "C" { +APICONNECTORPLUGIN_DECL void registerListener(void(*)(const char*, unsigned int, void*)); +APICONNECTORPLUGIN_DECL void deregisterListener(); +APICONNECTORPLUGIN_DECL void sendControl(const char*, unsigned int length, void* message); +} + +class APIConnector: public ibmras::monitoring::connector::Connector { +public: + + APIConnector(); + + std::string getID() { return "APIConnector"; } + + int sendMessage(const std::string &sourceId, uint32 size, void *data); + + void registerReceiver(ibmras::monitoring::connector::Receiver *receiver); + void deregisterReceiver(); + + int start(); + int stop(); + + ~APIConnector(); + +private: + +}; + +/* end class Connector */ + +namespace plugin { + agentCoreFunctions api; + uint32 provid; + ibmras::monitoring::connector::Receiver *receiver; +} + + +} /* end APIConnector monitoring */ + +#endif /* ibmras_monitoring_connector_apiconnector_h */ + diff --git a/Sources/memplugin/MemoryPlugin.cpp b/Sources/memplugin/MemoryPlugin.cpp new file mode 100644 index 0000000..c361b2f --- /dev/null +++ b/Sources/memplugin/MemoryPlugin.cpp @@ -0,0 +1,767 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +/* + * MemoryPlugin.cpp + * + * Created on: 5 May 2015 + * Author: Admin + */ + +#include "AgentExtensions.h" +#include "../agentcore/ibmras/monitoring/Typesdef.h" +#include "MemoryPlugin.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include +#include +#include +#include +#include +#endif + +#if defined(__MACH__) || defined(__APPLE__) +#include +#include +#include +#include +#include +#include +#include +#endif + +#if defined(_WINDOWS) +#include +#include +#include +#include +#include +#pragma comment(lib, "psapi.lib") +#endif + +#if defined(AIXPPC) +#include +#include +#include +#include +#include +#include +#include + +#if !defined(VMINFO_GETPSIZES) + +#define VMINFO_GETPSIZES 102 /* report a system's supported page sizes */ +#define VMINFO_PSIZE 103 /* report statistics for a page size */ + +struct vminfo_psize +{ + psize_t psize; /* IN: page size */ + + /* The rest of this struct is output from vmgetinfo() */ + + uint64_t attr; /* bitmap of page size's attributes */ + + /* Page size attributes reported in the vminfo_psize.attr field: */ +#define VM_PSIZE_ATTR_PAGEABLE 0x1 /* page size supports paging */ + + uint64_t pgexct; /* count of page faults */ + uint64_t pgrclm; /* count of page reclaims */ + uint64_t lockexct; /* count of lockmisses */ + uint64_t backtrks; /* count of backtracks */ + uint64_t pageins; /* count of pages paged in */ + uint64_t pageouts; /* count of pages paged out */ + uint64_t pgspgins; /* count of page ins from paging space */ + uint64_t pgspgouts; /* count of page outs from paging space */ + uint64_t numsios; /* count of start I/Os */ + uint64_t numiodone; /* count of iodones */ + uint64_t zerofills; /* count of zero filled pages */ + uint64_t exfills; /* count of exec filled pages */ + uint64_t scans; /* count of page scans by clock */ + uint64_t cycles; /* count of clock hand cycles */ + uint64_t pgsteals; /* count of page steals */ + uint64_t freewts; /* count of free frame waits */ + uint64_t extendwts; /* count of extend XPT waits */ + uint64_t pendiowts; /* count of pending I/O waits */ + + /* + * the next fields need to be computed by vmgetinfo + * system call, else their value will be inaccurate. + */ + rpn64_t numframes; /* # of real memory frames of this psize */ + rpn64_t numfrb; /* number of pages on free list */ + rpn64_t numclient; /* number of client frames */ + rpn64_t numcompress; /* no of frames in compressed segments */ + rpn64_t numperm; /* number frames non-working segments */ + rpn64_t numvpages; /* accessed virtual pages */ + rpn64_t minfree; /* minimun pages free list (fblru) */ + rpn64_t maxfree; /* maxfree pages free list (fblru) */ +#ifndef RPTYPES +#define RPTYPES 2 +#endif + rpn64_t rpgcnt[RPTYPES];/* repaging cnt */ + rpn64_t numpout; /* number of fblru page-outs */ + + rpn64_t numremote; /* number of fblru remote page-outs */ + rpn64_t numwseguse; /* count of pages in use for working seg */ + rpn64_t numpseguse; /* count of pages in use for persistent seg */ + rpn64_t numclseguse; /* count of pages in use for client seg */ + rpn64_t numwsegpin; /* count of pages pinned for working seg */ + rpn64_t numpsegpin; /* count of pages pinned for persistent seg */ + rpn64_t numclsegpin; /* count of pages pinned for client seg */ + rpn64_t numpgsp_pgs; /* # of wseg pages with allocated paging space */ + + rpn64_t numralloc; /* number of remote allocations */ + rpn64_t pfrsvdblks; /* number of system reserved blocks */ + rpn64_t pfavail; /* number of pages available for pinning */ + rpn64_t pfpinavail; /* app-level num pages avail for pinning */ + rpn64_t numpermio; /* number of fblru non-w.s. pageouts */ + + rpn64_t system_pgs; /* pages on SCBs marked V_SYSTEM */ + rpn64_t nonsys_pgs; /* pages on SCBs not marked V_SYSTEM */ +}; + +#endif /* !defined(VMINFO_GETPSIZES) */ +#endif + +#define MEMSOURCE_PULL_INTERVAL 2 +#define DEFAULT_CAPACITY 1024*10 + +namespace ibmras { +namespace monitoring { +namespace plugins { +namespace common { +namespace memoryplugin { + +int counter = 0; + +MemoryPlugin* MemoryPlugin::instance = 0; +agentCoreFunctions MemoryPlugin::aCF; + +MemoryPlugin::MemoryPlugin(uint32 provID): + provID(provID), noFailures(false){ +} + +MemoryPlugin* MemoryPlugin::getInstance() { + return instance; +} + +MemoryPlugin::~MemoryPlugin(){} + +int MemoryPlugin::start() { + aCF.logMessage(debug, ">>>MemoryPlugin::start()"); + noFailures = true; + aCF.logMessage(debug, "<<>>MemoryPlugin::stop()"); + aCF.logMessage(debug, "<<createPullSource(0, "common_memory"); +} + +pullsource* MemoryPlugin::createPullSource(uint32 srcid, const char* name) { + pullsource *src = new pullsource(); + src->header.name = name; + std::string desc("Description for "); + desc.append(name); + src->header.description = NewCString(desc); + src->header.sourceID = srcid; + src->next = NULL; + src->header.capacity = DEFAULT_CAPACITY; + src->callback = pullWrapper; + src->complete = pullCompleteWrapper; + src->pullInterval = MEMSOURCE_PULL_INTERVAL; // seconds + return src; +} + +monitordata* MemoryPlugin::OnRequestData() { + monitordata *data = new monitordata; + data->provID = provID; + data->size = 0; + data->data = NULL; + + data->persistent = false; + data->sourceID = 0; + + std::stringstream ss; + + ss << MEMORY_SOURCE << COMMA; + ss << getTime() << COMMA; + ss << TOTAL_MEMORY << EQUALS << getTotalPhysicalMemorySize() << COMMA; + ss << PHYSICAL_MEMORY << EQUALS << getProcessPhysicalMemorySize() << COMMA; + ss << PRIVATE_MEMORY << EQUALS << getProcessPrivateMemorySize() << COMMA; + ss << VIRTUAL_MEMORY << EQUALS << getProcessVirtualMemorySize() << COMMA; + ss << FREE_PHYSICAL_MEMORY << EQUALS << getFreePhysicalMemorySize() << std::endl; + + std::string memorydata = ss.str(); + + int len = memorydata.length(); + char* sval = new char[len + 1]; + if (sval) { + strcpy(sval, memorydata.c_str()); + + data->size = len; + data->data = sval; + + } + + return data; +} + +void MemoryPlugin::OnComplete(monitordata* data) { + if (data != NULL) { + if (data->data != NULL) { + delete[] data->data; + } + delete data; + } +} + +/***************************************************************************** + * CALLBACK WRAPPERS + *****************************************************************************/ + +extern "C" monitordata* pullWrapper() { + return MemoryPlugin::getInstance()->OnRequestData(); +} + +extern "C" void pullCompleteWrapper(monitordata* data) { + MemoryPlugin::getInstance()->OnComplete(data); +} + +/***************************************************************************** + * FUNCTIONS EXPORTED BY THE LIBRARY + *****************************************************************************/ + +extern "C" { +pullsource* ibmras_monitoring_registerPullSource(agentCoreFunctions aCF, uint32 provID) { + aCF.logMessage(debug, "[memory_os] Registering pull source"); + pullsource *src = MemoryPlugin::createSource(aCF, provID); + return src; +} + +int ibmras_monitoring_plugin_init(const char* properties) { + return 0; +} + +int ibmras_monitoring_plugin_start() { + MemoryPlugin::aCF.logMessage(fine, "[memory_os] Starting"); + return 0; +} + +int ibmras_monitoring_plugin_stop() { + MemoryPlugin::aCF.logMessage(fine, "[memory_os] Stopping"); + return 0; +} + +const char* ibmras_monitoring_getVersion() { + return PLUGIN_API_VERSION; +} +} + +/***************************************************************************** + * PLATFORM DEPENDENT CODE + *****************************************************************************/ + + +int64 MemoryPlugin::getProcessPhysicalMemorySize() { +#if defined(__linux__) + /* Read rss field from /proc//stat as per 'man proc'. */ +#define RSS_FIELD_INDEX 23 + long rss; + + if (1 == readProcStatField(RSS_FIELD_INDEX, "%ld", &rss)) + { + /* NOTE: This is accurate even in the context of huge pages. */ + return(int64)rss * sysconf(_SC_PAGESIZE); + } +#undef RSS_FIELD_INDEX + +#elif defined(__MACH__) || defined(__APPLE__) + struct task_basic_info t_info; + mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; + task_info(current_task(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); + size_t size = t_info.resident_size; + return size; + +#elif defined(AIXPPC) + /* + * There is no API on AIX to get the rss of the shared memory used by this process. + * If such an API was available, this function should return the following: + * + * sharedRss + (pe.pi_trss + pe.pi_drss)*4096 + * + * NOTE: pi_trss and pi_drss are always in 4K units regardless of pi_text_l2psize. + */ +#elif defined(WINDOWS) + PROCESS_MEMORY_COUNTERS info; + + info.cb = sizeof(info); + if (0 != GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info))) + { + return info.WorkingSetSize; + } + +#endif + return -1; + +} + +int64 MemoryPlugin::getProcessPrivateMemorySize() { + +#if defined(__linux__) + /* + * Read shared field from /proc//statm as per 'man proc'. + * Return difference between virtual memory size and shared. + */ +#define SHARED_FIELD_INDEX 2 + char buf[512]; + + if (-1 != readProcFile("statm", buf, sizeof(buf))) + { + const char *str = skipFields(buf, SHARED_FIELD_INDEX); + + if (NULL != str) + { + long shared; + if (1 == sscanf(str, "%ld", &shared)) + { + int64 vsize = getProcessVirtualMemorySize(); + if (-1 != vsize) + { + int64 priv = vsize - ((int64)shared * sysconf(_SC_PAGESIZE)); + return(priv > 0 ? priv : -1); + } + } + } + } +#undef SHARED_FIELD_INDEX +#elif defined(AIXPPC) + struct procentry64 pe; + pid_t pid = getpid(); + + if (1 == getprocs64((struct procentry64*)&pe, sizeof(pe), NULL, 0, &pid, 1)) + { + /* NOTE: pi_dvm is always in 4K units regardless of pi_data_l2psize. */ + int64 size = (int64)pe.pi_tsize + (int64)pe.pi_dvm * 4096; + + return(size > 0 ? size : -1); + } + +#elif defined(__MACH__) || defined(__APPLE__) + +#elif defined(WINDOWS) + + //IBMRAS_DEBUG(debug, ">>MEMPullSource::getProcessPrivateMemorySizeImpl()"); + + PROCESS_MEMORY_COUNTERS_EX procMemCount; + + bool result = GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&procMemCount), sizeof(PROCESS_MEMORY_COUNTERS_EX)); + + if(result) { + return procMemCount.PrivateUsage > 0 ? procMemCount.PrivateUsage : -1; + } + return -1; + +#endif + // IBMRAS_DEBUG(debug, "</stat as per 'man proc'. */ +#define VSIZE_FIELD_INDEX 22 + unsigned long vsize; + + if (1 == readProcStatField(VSIZE_FIELD_INDEX, "%lu", &vsize)) + { + return(int64)(vsize > 0 ? vsize : -1); + } +#undef VSIZE_FIELD_INDEX +#elif defined(__MACH__) || defined(__APPLE__) + struct task_basic_info t_info; + mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; + task_info(current_task(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); + size_t size = t_info.virtual_size; + return size; +#elif defined(AIXPPC) + /* There is no API on AIX to get shared memory usage for the process. If such an + * API existed, we could return getProcessPrivateMemorySize() + sharedSize here. + * + * Note: Iterating through /proc//map and looking at the pages that are + * not marked MA_SHARED does not account for shared code pages when in fact + * command-line AIX utilities (such as svmon) do show that pages are shared. + */ +#elif defined(WINDOWS) + PROCESS_MEMORY_COUNTERS info; + + info.cb = sizeof(info); + if (0 != GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info))) + { + return(int64)info.PagefileUsage; + } +#endif + return -1; +} + +int64 MemoryPlugin::getFreePhysicalMemorySize() { +#if defined(__linux__) + /* NOTE: This is accurate even in the context of huge pages. */ + return(int64)sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE); + +#elif defined(__MACH__) || defined(__APPLE__) + + vm_size_t pageSize = 4096; + mach_port_t myHost = mach_host_self(); + + if(host_page_size(myHost, &pageSize) != KERN_SUCCESS) { + aCF.logMessage(warning, "Failed to get pagesize, default set to 4K"); + } + vm_statistics64_data_t vm_stat; + unsigned int count = HOST_VM_INFO64_COUNT; + kern_return_t ret; + ret = host_statistics64(myHost, HOST_VM_INFO64, reinterpret_cast(&vm_stat), &count); + if (( ret != KERN_SUCCESS)) { + aCF.logMessage(warning, "Failed to get host statistics"); + return -1; + } + return vm_stat.free_count*pageSize; + +#elif defined(AIXPPC) + /* NOTE: This works on AIX 5.3 and later. */ + IDATA numPageSizes = vmgetinfo(NULL, VMINFO_GETPSIZES, 0); + + if (numPageSizes > 0) + { + psize_t *pageSizes = (psize_t*)alloca(numPageSizes*sizeof(psize_t)); + IDATA numPageSizesRetrieved = vmgetinfo(pageSizes, VMINFO_GETPSIZES, numPageSizes); + + if (numPageSizes == numPageSizesRetrieved) + { + int64 size = 0; + IDATA i; + + for (i = 0; i < numPageSizes; i++) + { + struct vminfo_psize pageSize; + + pageSize.psize = pageSizes[i]; + if (0 == vmgetinfo(&pageSize, VMINFO_PSIZE, sizeof(pageSize))) + { + size += (int64)pageSize.psize * pageSize.numfrb; + } + } + return(size > 0 ? size : -1); + } + } + return -1; +#elif defined(WINDOWS) + MEMORYSTATUSEX statex; + + statex.dwLength = sizeof(statex); + if (0 != GlobalMemoryStatusEx(&statex)) + { + return statex.ullAvailPhys; + } + return -1; +#else + return -1; +#endif +} + +int64 MemoryPlugin::getTotalPhysicalMemorySize() { +#if defined (_AIX) + return (int64)(sysconf(_SC_AIX_REALMEM) * 1024); + +#elif defined(__linux__) ||defined(__MACH__)&&defined(_SC_PAGESIZE)&&defined(_SC_PHYS_PAGES) ||defined(__APPLE__)&&defined(_SC_PAGESIZE)&&defined(_SC_PHYS_PAGES) + IDATA pagesize, num_pages; + + pagesize = sysconf(_SC_PAGESIZE); + num_pages = sysconf(_SC_PHYS_PAGES); + + if (pagesize == -1 || num_pages == -1) { + return 0; + } else { + return (int64) pagesize *num_pages; + } + /* + * There is a bug in OSX Mavericks which may cause the compilation to fail + * due to _SC_PHYS_PAGES not being defined in so we have to resource + * to sysctl if that's the case + */ +#elif defined(__MACH__)|| defined(__APPLE__)//OSX + + +#if defined(CTL_HW) && defined(HW_MEMSIZE) //64Bit + int mib[2] = {CTL_HW, HW_MEMSIZE}; + unsigned long physicalMemSize; + size_t len = sizeof(physicalMemSize); + if(!sysctl(mib, 2, &physicalMemSize, &len, NULL, 0)) { + return physicalMemSize; + }else { + aCF.logMessage(debug, strerror(errno)); + return -1; + } +#elif defined(CTL_HW) && defined(HW_PHYSMEM) //32Bit + int mib[2] = {CTL_HW, HW_PHYSMEM}; + unsigned long physicalMemSize; + size_t len = sizeof(physicalMemSize); + if(!sysctl(mib, 2, &physicalMemSize, &len, NULL, 0)) { + return physicalMemSize; + }else { + aCF.logMessage(debug, strerror(errno)); + return -1; + } +#endif + +#elif defined (_WINDOWS) + MEMORYSTATUSEX statex; + + statex.dwLength = sizeof(statex); + if (0 != GlobalMemoryStatusEx(&statex)) + { + return statex.ullTotalPhys; + } + return -1; +#elif defined(_S390) + /* Get_Physical_Memory returns "SIZE OF ACTUAL REAL STORAGE ONLINE IN 'K'" */ + return Get_Physical_Memory() * 1024; +#else + return -1; +#endif +} + + +#if defined(__linux__) +/** + * Opens file at /proc// for reading. + * + * @param[in] fname Name of file to open. + * + * @return File descriptor of the opened file or -1 on failure. + */ +static IDATA openProcFile(const char *fname) +{ + char proc[MAXPATHLEN]; + + snprintf(proc, sizeof(proc), "/proc/%d/%s", getpid(), fname); + + return open(proc,O_RDONLY); +} + +/** + * Read proc file at /proc// into buf of size nbytes. + * Null-terminates the buffer so it can be treated as a string. + * + * @param[in] fname Name of file to open. + * @param[out] buf Buffer to read file into. + * @param[in] nbytes Size of buffer. + * + * @return Returns number of bytes read excluding null-terminator + * or -1 on failure. + */ +static IDATA readProcFile(const char *fname, char *buf, UDATA nbytes) +{ + IDATA ret = -1; + IDATA fd = openProcFile(fname); + + if (-1 != fd) + { + ret = 0; + /* Read up to (nbytes - 1) bytes to save space for the null terminator. */ + while (nbytes - ret > 1) + { + IDATA nread = read(fd, buf + ret, nbytes - ret - 1); + + if (nread <= 0) + break; + ret += (UDATA)nread; + } + buf[ret] = '\0'; + close(fd); + } + return ret; +} + +/** + * Gets the running process name as null-terminated string. + * + * @param[out] name Buffer to store name of the process. + * @param[in] nameLength Length of the name buffer. + * + * @return Pointer to name on success or NULL on error. + */ +static char* getProcessName(char *name, UDATA nameLength) +{ + /* + * Read the first line from /proc//status and parse + * the process name from it. + * + * It would be nice to use prctl() with PR_GET_NAME - but + * it is only supported on kernel version 2.6.11 and later. + */ +#define PROC_NAME_PREFIX "Name:\t" + char *ret = NULL; + char buf[128]; + + if (-1 != readProcFile("status", buf, sizeof(buf))) + { + if (0 == strncmp(buf, PROC_NAME_PREFIX, sizeof(PROC_NAME_PREFIX) - 1)) + { + UDATA i; + char *from = buf + sizeof(PROC_NAME_PREFIX) - 1; + + for (i = 0; (i < nameLength - 1) && ('\0' != from[i]) && ('\n' != from[i]); i++) + { + name[i] = from[i]; + } + name[i] = '\0'; + ret = name; + } + } + return ret; +#undef PROC_NAME_PREFIX +} + +/** + * Skips n number of space-separator fields in str. The string + * must not begin with whitespace. + * + * @param[in] str Null-terminated string that will be scanned. + * @param[in] n Number of fields to skip, must be positive. + * + * @return Pointer to the location in the string after the skipped + * fields, or NULL if end of string was encountered. + */ +static const char* skipFields(const char *str, UDATA n) +{ + str++; + while (('\0' != *str) && (n > 0)) + { + if (isspace(*str)) + { + n--; + } + str++; + } + return(n != 0 ? NULL : str); +} + + +/** + * Opens /proc//stat file and reads the field at position index + * from the file as sscanf would. Field index must be >= 2. Returns + * result of sscanf (i.e. number of fields read), or -1 on failure. + * + * @param[in] index Index of the field to be read. + * @param[in] format Format string for field to be read. + * @param[out] ... Field(s) to be read as per format. + * + * @return Number of fields read, or -1 on failure. + */ +//static IDATA readProcStatField(UDATA index, const char *format, ...) +//__attribute__((format(scanf,3,4))); +static IDATA readProcStatField(UDATA index, const char *format, ...) +{ + IDATA ret = -1; + char buf[512]; + + if (-1 != readProcFile("stat", buf, sizeof(buf))) + { + /* + * The second field in /proc//stat is the process name + * surrounded by parentheses. Unfortunately, the process + * name can have both spaces and parentheses in it, neither + * of which are escaped. Thus, to parse the file correctly, + * we must get the process name in order to be able to skip + * it in the /proc//stat file. + */ + char processName[64]; + + if (NULL != getProcessName(processName, sizeof(processName))) + { + char expected[128]; + size_t length = (size_t)snprintf(expected, sizeof(expected), + "%d (%s) ", getpid(), processName); + + /* Verify that the start of the file matches what we expected. */ + if (0 == memcmp(buf, expected, length)) + { + const char *str = skipFields(buf + length, index - 2); + + if (NULL != str) + { + va_list ap; + va_start(ap, format); + ret = vsscanf(str, format, ap); + va_end(ap); + } + } + } + } + + return ret; +} +#endif + +int64 MemoryPlugin::getTime() { +#if defined(__linux__) || defined(_AIX) || defined(__MACH__) || defined(__APPLE__) + struct timeval tv; + gettimeofday(&tv, NULL); + return ((int64) tv.tv_sec)*1000 + tv.tv_usec/1000; +#elif defined(_WINDOWS) + LONGLONG time; + GetSystemTimeAsFileTime( (FILETIME*)&time ); + return (int64) ((time - 116444736000000000) /10000); +#elif defined(_S390) + int64 millisec = MAXPREC() / 8000; + return millisec; +#else + return -1; +#endif +} + +char* MemoryPlugin::NewCString(const std::string& s) { + char *result = new char[s.length() + 1]; + std::strcpy(result, s.c_str()); + return result; +} + + +} //memoryplugin +} //common +} //plugins +} //monitoring +} //ibmras + diff --git a/Sources/memplugin/include/MemoryPlugin.h b/Sources/memplugin/include/MemoryPlugin.h new file mode 100644 index 0000000..09eadd2 --- /dev/null +++ b/Sources/memplugin/include/MemoryPlugin.h @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +/* + * MemoryPlugin.h + * + * Created on: 5 May 2015 + * Author: Admin + */ + +#include "AgentExtensions.h" +#include "../../agentcore/ibmras/monitoring/Typesdef.h" +#include + +namespace ibmras { +namespace monitoring { +namespace plugins { +namespace common { +namespace memoryplugin { + +class MemoryPlugin { +public: + static agentCoreFunctions aCF; + + monitordata* OnRequestData(); + void OnComplete(monitordata* data); + static pullsource* createSource(agentCoreFunctions aCF, uint32 provID); + static MemoryPlugin* getInstance(); + virtual ~MemoryPlugin(); + int start(); + int stop(); +private: + uint32 provID; + static MemoryPlugin* instance; + bool noFailures; + + MemoryPlugin(uint32 provID); + static int64 getProcessPhysicalMemorySize(); + static int64 getProcessPrivateMemorySize(); + static int64 getProcessVirtualMemorySize(); + static int64 getFreePhysicalMemorySize(); + static int64 getTotalPhysicalMemorySize(); + pullsource* createPullSource(uint32 srcid, const char* name); + + static char* NewCString(const std::string& s); + int64 getTime(); + +}; + +typedef long IDATA; +typedef size_t UDATA; +#if defined(__linux__) +static IDATA openProcFile(const char *fname); +static IDATA readProcFile(const char *fname, char *buf, UDATA nbytes); +static char* getProcessName(char *name, UDATA nameLength); +static const char* skipFields(const char *str, UDATA n); +static IDATA readProcStatField(UDATA index, const char *format, ...); +#endif + +#if defined(__MACH__) || defined(__APPLE__) + +#endif + +extern "C" monitordata* pullWrapper(); +extern "C" void pullCompleteWrapper(monitordata* data); + +extern "C" { +PLUGIN_API_DECL pullsource* ibmras_monitoring_registerPullSource(agentCoreFunctions aCF, uint32 provID); +PLUGIN_API_DECL int ibmras_monitoring_plugin_init(const char* properties); +PLUGIN_API_DECL int ibmras_monitoring_plugin_start(); +PLUGIN_API_DECL int ibmras_monitoring_plugin_stop(); +PLUGIN_API_DECL const char* ibmras_monitoring_getVersion(); +} + +const std::string COMMA = ","; +const std::string EQUALS = "="; + +const std::string MEMORY_SOURCE = "MemorySource"; +const std::string TOTAL_MEMORY = "totalphysicalmemory"; +const std::string PHYSICAL_MEMORY = "physicalmemory"; +const std::string PRIVATE_MEMORY = "privatememory"; +const std::string VIRTUAL_MEMORY = "virtualmemory"; +const std::string FREE_PHYSICAL_MEMORY = "freephysicalmemory"; +const std::string TOTAL_PHYSICAL_MEMORY = "totalphysicalmemory"; + +} //memoryplugin +} //common +} //plugins +} //monitoring +} //ibmras diff --git a/Sources/mqttplugin/MQTTConnector.cpp b/Sources/mqttplugin/MQTTConnector.cpp new file mode 100644 index 0000000..8f96d11 --- /dev/null +++ b/Sources/mqttplugin/MQTTConnector.cpp @@ -0,0 +1,414 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#if defined(_ZOS) +#define _XOPEN_SOURCE_EXTENDED 1 +#endif + +#include "MQTTConnector.h" +#include "../agentcore/ibmras/common/logging.h" +#include +#include +#include "stdlib.h" +#include "time.h" +#include "../agentcore/ibmras/common/Properties.h" +#include "../agentcore/ibmras/common/util/sysUtils.h" +#include "../agentcore/ibmras/common/util/strUtils.h" +#include "../agentcore/ibmras/common/common.h" +#include "../agentcore/ibmras/common/port/Process.h" +#include "../agentcore/ibmras/common/MemoryManager.h" + +extern "C" { +#define NO_HEAP_TRACKING +#include "../paho/include/Heap.h" +#undef NO_HEAP_TRACKING +} + +#define AGENT_TOPIC_PREFIX "ibm/healthcenter" +#define CLIENT_IDENTIFY_TOPIC AGENT_TOPIC_PREFIX "/identify" +#define CLIENT_IDENTITY_TOPIC AGENT_TOPIC_PREFIX "/id" +#define DEFAULT_HOST "localhost" +#define DEFAULT_PORT "1883" + +#if defined(_WINDOWS) +#define MQTT_DECL __declspec(dllexport) /* required for DLLs to export the plugin functions */ +#else +#define MQTT_DECL +#endif + +namespace ibmras { +namespace monitoring { +namespace connector { +namespace mqttcon { + +IBMRAS_DEFINE_LOGGER("mqtt") +; + +const char* mqttConnVersion = "1.0"; + + + +MQTTConnector::MQTTConnector(const std::string &host, const std::string &port, + const std::string &user, const std::string &pass, + const std::string &topicNamespace, const std::string &applicationId) : + brokerHost(host), brokerPort(port), brokerUser(user), brokerPass(pass), mqttClient( + NULL) { + + enabled = false; + + int processId = ibmras::common::port::getProcessId(); + unsigned long long time = ibmras::common::util::getMilliseconds(); + srand((unsigned int) time); + + std::stringstream clientIdStream; + clientIdStream << "agent_" << rand(); + std::string clientId = clientIdStream.str(); + + std::string namespacePrefix = topicNamespace; + if (topicNamespace.length() > 0 + && topicNamespace[topicNamespace.length() - 1] != '/') { + namespacePrefix += '/'; + } + + std::stringstream rootTopicStream; + rootTopicStream << namespacePrefix << AGENT_TOPIC_PREFIX << "/" << clientId; + rootTopic = rootTopicStream.str(); + + std::stringstream agentTopicStream; + agentTopicStream << namespacePrefix << AGENT_TOPIC_PREFIX << "/agent/" + << clientId << "/"; + agentTopic = agentTopicStream.str(); + + std::stringstream agentIdMessageStream; + std::string applicationIdentifier; + applicationIdentifier = ibmras::common::port::getHostName() + ":"; + applicationIdentifier += ibmras::common::itoa(processId); + if (applicationId.length() > 0) { + applicationIdentifier += ":" + applicationId; + } + agentIdMessageStream << rootTopic << "\n" << applicationIdentifier; + agentIdMessage = agentIdMessageStream.str(); + + willTopic = rootTopic + "/will"; + willMessage = agentIdMessage; + createClient(clientId); + + IBMRAS_DEBUG_1(fine, "MQTTConnector: creating client: %s", clientId.c_str()); +} + +MQTTConnector::~MQTTConnector() { + if (mqttClient != NULL) { + MQTTAsync_disconnect(mqttClient, NULL); + MQTTAsync_destroy(&mqttClient); + } +} + +void MQTTConnector::createClient(const std::string &clientId) { + if (mqttClient == NULL) { + std::string address("tcp://"); + address += brokerHost; + address += ":"; + address += brokerPort; + + // Pass Paho our allocation functions so we can track its memory usage within the rest + // of the agents memory usage. + Heap_set_allocator(&ibmras::common::memory::allocate, &ibmras::common::memory::deallocate); + + int rc = MQTTAsync_create(&mqttClient, address.c_str(), + clientId.c_str(), MQTTCLIENT_PERSISTENCE_NONE, NULL); + + if (rc != MQTTASYNC_SUCCESS) { + IBMRAS_DEBUG_1(fine, "MQTTConnector: client create failed: %d", rc); + } else { + rc = MQTTAsync_setCallbacks(mqttClient, this, connectionLost, + messageReceived, NULL); + if (rc != MQTTASYNC_SUCCESS) { + IBMRAS_DEBUG_1(fine, "MQTTConnector: setCallbacks failed: %d", rc); + } + } + + } +} + +int MQTTConnector::connect() { + IBMRAS_DEBUG(fine, "MQTTConnector: connecting"); + int rc = MQTTASYNC_FAILURE; + if (mqttClient != NULL) { + if (MQTTAsync_isConnected(mqttClient)) { + return MQTTASYNC_SUCCESS; + } + + MQTTAsync_connectOptions connOpts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions willOpts = MQTTAsync_willOptions_initializer; + willOpts.message = willMessage.c_str(); + willOpts.topicName = willTopic.c_str(); + + connOpts.cleansession = 1; + connOpts.keepAliveInterval = 20; + connOpts.onSuccess = onConnect; + connOpts.onFailure = onFailure; + connOpts.context = this; + connOpts.will = &willOpts; + + if (brokerUser != "") { + connOpts.username = strdup(brokerUser.c_str()); + } + if (brokerPass != "") { + connOpts.password = strdup(brokerPass.c_str()); + } + + rc = MQTTAsync_connect(mqttClient, &connOpts); + if (rc != MQTTASYNC_SUCCESS) { + IBMRAS_DEBUG_1(fine, "MQTTAsync_connect failed. rc=%d", rc); + } + } + return rc; +} + +void MQTTConnector::onConnect(void* context, MQTTAsync_successData* response) { + ((MQTTConnector*) context)->handleOnConnect(response); +} + +void MQTTConnector::onFailure(void* context, MQTTAsync_failureData* response) { + if (response == NULL) { + IBMRAS_DEBUG(fine, "MQTTAsync_connect failed"); + } else { + IBMRAS_DEBUG_1(fine, "MQTTAsync_connect failed. rc: %d", response->code); + if (response->message != NULL) { + IBMRAS_DEBUG_1(fine, "MQTTAsync_connect failure reason: %s", response->message); + } + } +} + +void MQTTConnector::handleOnConnect(MQTTAsync_successData* response) { + IBMRAS_LOG_2(info, "Connected to broker %s:%s", brokerHost.c_str(), brokerPort.c_str()); + + char *topic = new char[agentTopic.length() + 2]; +#if defined(_ZOS) +#pragma convert("ISO8859-1") +#endif + sprintf(topic, "%s#", agentTopic.c_str()); +#if defined(_ZOS) +#pragma convert(pop) +#endif + IBMRAS_DEBUG_1(debug, "MQTTAsync_subscribe to %s", topic); + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + opts.context = this; + int rc = MQTTAsync_subscribe(mqttClient, topic, 1, &opts); + if (rc != MQTTASYNC_SUCCESS) { + IBMRAS_DEBUG_2(fine, "MQTTAsync_subscribe to %s failed. rc=%d", topic, rc); + } + delete[] topic; + + char identifyTopic[] = CLIENT_IDENTIFY_TOPIC; + IBMRAS_DEBUG_1(debug, "MQTTAsync_subscribe to %s", identifyTopic); + rc = MQTTAsync_subscribe(mqttClient, identifyTopic, 1, &opts); + if (rc != MQTTASYNC_SUCCESS) { + IBMRAS_DEBUG_2(fine, "MQTTAsync_subscribe to %s failed. rc=%d", CLIENT_IDENTIFY_TOPIC, rc); + } else { + sendIdentityMessage(); + } +} + +void MQTTConnector::connectionLost(void *context, char *cause) { + IBMRAS_LOG_2(warning, "Connection to broker %s:%s has been lost", ((MQTTConnector*) context)->brokerHost.c_str(), ((MQTTConnector*) context)->brokerPort.c_str()); +} + +int MQTTConnector::sendMessage(const std::string &sourceId, uint32 size, + void *data) { + + if (!enabled) { + return 0; + } + + if (mqttClient == NULL) { + return -1; + } + + if (!MQTTAsync_isConnected(mqttClient)) { + if (sourceId == "heartbeat") { + connect(); + return 0; + } else { + return -1; + } + } + + IBMRAS_DEBUG_3(fine, "Sending message : topic %s : data %p : length %d", sourceId.c_str(), data, size); + + /* topic = /sourceId */ + char *topic = new char[rootTopic.length() + 1 + sourceId.length() + 1]; +#if defined(_ZOS) +#pragma convert("ISO8859-1") +#endif + sprintf(topic, "%s/%s", rootTopic.c_str(), sourceId.c_str()); +#if defined(_ZOS) +#pragma convert(pop) +#endif + + // MQTTAsync_deliveryToken token; + MQTTAsync_send(mqttClient, topic, size, data, 1, 0, NULL); + + delete[] topic; + + return size; +} + +int MQTTConnector::messageReceived(void *context, char *topicName, int topicLen, + MQTTAsync_message *message) { + return ((MQTTConnector*) context)->handleReceivedmessage(topicName, + topicLen, message); +} + +int MQTTConnector::handleReceivedmessage(char *topicName, int topicLen, + MQTTAsync_message *message) { + + IBMRAS_DEBUG_1(debug, "MQTT message received for %s", topicName); + std::string topic(topicName); + + if (topic == CLIENT_IDENTIFY_TOPIC) { + sendIdentityMessage(); + } + if (receiver != NULL) { + if (topic.find(agentTopic) == 0) { + topic = topic.substr(agentTopic.length()); + IBMRAS_DEBUG_1(debug, "forwarding message %s", topic.c_str()); + } + receiver->receiveMessage(topic, message->payloadlen, message->payload); + } + + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + return true; +} + +void MQTTConnector::registerReceiver( + ibmras::monitoring::connector::Receiver *receiver) { + IBMRAS_DEBUG(debug, "registerReceiver"); + this->receiver = receiver; +} + +ibmras::monitoring::connector::Receiver* MQTTConnector::returnReceiver() { + return receiver; +} + +int MQTTConnector::start() { + IBMRAS_DEBUG(debug, "start"); + + IBMRAS_LOG_2(info, "Connecting to broker %s:%s", brokerHost.c_str(), brokerPort.c_str()); + enabled = true; + return connect(); +} + +int MQTTConnector::stop() { + IBMRAS_DEBUG(debug, "stop"); + + if (mqttClient != NULL) { + if (MQTTAsync_isConnected(mqttClient)) { + // Send will message before our clean termination + char* message = new char[willMessage.length() + 1]; + strcpy(message, willMessage.c_str()); + MQTTAsync_send(mqttClient, willTopic.c_str(), strlen(message), + message, 1, 0, NULL); + delete[] message; + + return MQTTAsync_disconnect(mqttClient, NULL); + } + } + return -1; +} + +void MQTTConnector::sendIdentityMessage() { + IBMRAS_DEBUG_1(debug, "sending identity message: %s", agentIdMessage.c_str()); + char topic[] = CLIENT_IDENTITY_TOPIC; + char* idMessage = new char[agentIdMessage.length() + 1]; + strcpy(idMessage, agentIdMessage.c_str()); + MQTTAsync_send(mqttClient, topic, strlen(idMessage), idMessage, 1, 0, NULL); + delete[] idMessage; +} + +} +} +} +} /* end mqttcon monitoring */ + +extern "C" { + +#if defined(WINDOWS) +#else +void MQTTAsync_init(); +#endif + +MQTT_DECL int ibmras_monitoring_plugin_start() { + return 0; +} + +MQTT_DECL int ibmras_monitoring_plugin_stop() { + return 0; +} + +MQTT_DECL const char* ibmras_monitoring_getVersion() { + return ibmras::monitoring::connector::mqttcon::mqttConnVersion; +} + +bool mqttInitialized = false; + +MQTT_DECL int ibmras_monitoring_plugin_init(const char* properties) { + if (!mqttInitialized) { +#if defined(WINDOWS) +#else + MQTTAsync_init(); +#endif + mqttInitialized = true; + } + return 0; +} + +MQTT_DECL void* ibmras_monitoring_getConnector(const char* properties) { + + ibmras::common::Properties props; + props.add(properties); + + std::string enabledProp = props.get("com.ibm.diagnostics.healthcenter.mqtt"); + if (!ibmras::common::util::equalsIgnoreCase(enabledProp, "on")) { + return NULL; + } + + std::string loggingProp = props.get("com.ibm.diagnostics.healthcenter.logging.level"); + ibmras::common::LogManager::getInstance()->setLevel("level", loggingProp); + loggingProp = props.get("com.ibm.diagnostics.healthcenter.logging.mqtt"); + ibmras::common::LogManager::getInstance()->setLevel("mqtt", loggingProp); + + + std::string brokerHost = props.get("com.ibm.diagnostics.healthcenter.mqtt.broker.host"); + if (!brokerHost.compare("")) { + brokerHost = DEFAULT_HOST; + } + + std::string brokerPort = props.get("com.ibm.diagnostics.healthcenter.mqtt.broker.port"); + if (!brokerPort.compare("")) { + brokerPort = DEFAULT_PORT; + } + std::string brokerUser = props.get("com.ibm.diagnostics.healthcenter.mqtt.broker.user"); + std::string brokerPass = props.get("com.ibm.diagnostics.healthcenter.mqtt.broker.pass"); + std::string topcNamespace = props.get("com.ibm.diagnostics.healthcenter.mqtt.topic.namespace"); + std::string applicationId = props.get("com.ibm.diagnostics.healthcenter.mqtt.application.id"); + + return new ibmras::monitoring::connector::mqttcon::MQTTConnector(brokerHost, + brokerPort, brokerUser, brokerPass, topcNamespace, applicationId); +} +} diff --git a/Sources/mqttplugin/include/MQTTConnector.h b/Sources/mqttplugin/include/MQTTConnector.h new file mode 100644 index 0000000..17df11a --- /dev/null +++ b/Sources/mqttplugin/include/MQTTConnector.h @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_connector_mqtt_mqttconnector_h +#define ibmras_monitoring_connector_mqtt_mqttconnector_h + +#include "../../agentcore/ibmras/monitoring/connector/Connector.h" + +extern "C" { +#include "../../paho/include/MQTTAsync.h" +} + +namespace ibmras { +namespace monitoring { +namespace connector { +namespace mqttcon { + +class MQTTConnector: public ibmras::monitoring::connector::Connector { +public: + + MQTTConnector(const std::string &host, const std::string &port, + const std::string &user, const std::string &pass, + const std::string &topicNamespace, const std::string &applicationId); + + int sendMessage(const std::string &sourceId, uint32 size, void *data); + + void registerReceiver(ibmras::monitoring::connector::Receiver *receiver); + ibmras::monitoring::connector::Receiver* returnReceiver(); + + int start(); + int stop(); + + virtual ~MQTTConnector(); + std::string getID() { + return "MQTTConnector"; + } +private: + bool enabled; + void createClient(const std::string &id); + int connect(); + + int handleReceivedmessage(char *topicName, int topicLen, + MQTTAsync_message *message); + static int messageReceived(void *context, char *topicName, int topicLen, + MQTTAsync_message *message); + static void connectionLost(void* context, char* cause); + + void handleOnConnect(MQTTAsync_successData* response); + static void onConnect(void* context, MQTTAsync_successData* response); + static void onFailure(void* context, MQTTAsync_failureData* response); + + void sendIdentityMessage(); + + std::string brokerHost; + std::string brokerPort; + std::string brokerUser; + std::string brokerPass; + + MQTTAsync mqttClient; + ibmras::monitoring::connector::Receiver *receiver; + + std::string rootTopic; + std::string agentTopic; + std::string agentIdMessage; + + std::string willTopic; + std::string willMessage; +}; + +} /* end namespace mqttcon */ +} +} +} +#endif /* ibmras_monitoring_connector_mqtt_mqttconnector_h */ diff --git a/Sources/ostreamplugin/OStreamConnector.cpp b/Sources/ostreamplugin/OStreamConnector.cpp new file mode 100644 index 0000000..4610800 --- /dev/null +++ b/Sources/ostreamplugin/OStreamConnector.cpp @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +#include "OStreamConnector.h" +#include "../agentcore/ibmras/common/logging.h" + +#if defined(_WINDOWS) +#define OSTREAMCONNECTOR_DECL __declspec(dllexport) /* required for DLLs to export the plugin functions */ +#else +#define OSTREAMCONNECTOR_DECL +#endif + +namespace OStream { + +IBMRAS_DEFINE_LOGGER("ostream"); + +OStreamConnector::OStreamConnector(std::ostream &outputStream) : + output(outputStream) { +} + +int OStreamConnector::sendMessage(const std::string &sourceId, uint32 size, + void *data) { + + char* charData = reinterpret_cast(data); + uint32 i; + output << "----------------------------------------------------------------------------------------------------------\n"; + output << sourceId << "\n"; + output << "----------------------------------------------------------------------------------------------------------\n"; + for (i = 0; i < size; i++) { + output.put(charData[i]); + } + output << "\n----------------------------------------------------------------------------------------------------------\n"; + return i; +} + +void OStreamConnector::registerReceiver(ibmras::monitoring::connector::Receiver *receiver) { + +} + +int OStreamConnector::start() { + IBMRAS_DEBUG(info, "Starting ostream connector"); + return 0; +} + +int OStreamConnector::stop() { + IBMRAS_DEBUG(info, "Stopping ostream connector"); + return 0; +} + +OStreamConnector::~OStreamConnector() { +} + +} /* end namespace monitoring */ + +extern "C" { +OSTREAMCONNECTOR_DECL int ibmras_monitoring_plugin_start() { + return 0; +} + +OSTREAMCONNECTOR_DECL int ibmras_monitoring_plugin_stop() { + return 0; +} + +OSTREAMCONNECTOR_DECL void* ibmras_monitoring_getConnector(const char* properties) { + return new OStream::OStreamConnector(std::cout); +} + +OSTREAMCONNECTOR_DECL char* ibmras_monitoring_getVersion() { + return "1.0"; +} +} diff --git a/Sources/ostreamplugin/include/OStreamConnector.h b/Sources/ostreamplugin/include/OStreamConnector.h new file mode 100644 index 0000000..595f7ad --- /dev/null +++ b/Sources/ostreamplugin/include/OStreamConnector.h @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + + +#ifndef ibmras_monitoring_connector_ostreamconnector_h +#define ibmras_monitoring_connector_ostreamconnector_h + +#include "../../agentcore/ibmras/monitoring/connector/Connector.h" +#include + +namespace OStream { + +class OStreamConnector: public ibmras::monitoring::connector::Connector { +public: + + OStreamConnector(); + OStreamConnector(std::ostream &outputStream); + + std::string getID() { return "OStreamConnector"; } + + int sendMessage(const std::string &sourceId, uint32 size, void *data); + + void registerReceiver(ibmras::monitoring::connector::Receiver *receiver); + + int start(); + int stop(); + + ~OStreamConnector(); +private: + std::ostream & output; + +}; + +/* end class Connector */ + +} /* end OStream monitoring */ + +#endif /* ibmras_monitoring_connector_ostreamconnector_h */ diff --git a/Sources/paho/.cproject b/Sources/paho/.cproject new file mode 100644 index 0000000..be79d8e --- /dev/null +++ b/Sources/paho/.cproject @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/paho/.project b/Sources/paho/.project new file mode 100644 index 0000000..8c28da6 --- /dev/null +++ b/Sources/paho/.project @@ -0,0 +1,27 @@ + + + org.eclipse.paho.mqtt.c + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/Sources/paho/CONTRIBUTING.md b/Sources/paho/CONTRIBUTING.md new file mode 100644 index 0000000..7e6bc93 --- /dev/null +++ b/Sources/paho/CONTRIBUTING.md @@ -0,0 +1,87 @@ +Contributing to Paho +==================== + +Thanks for your interest in this project. + +Project description: +-------------------- + +The Paho project has been created to provide scalable open-source implementations of open and standard messaging protocols aimed at new, existing, and emerging applications for Machine-to-Machine (M2M) and Internet of Things (IoT). +Paho reflects the inherent physical and cost constraints of device connectivity. Its objectives include effective levels of decoupling between devices and applications, designed to keep markets open and encourage the rapid growth of scalable Web and Enterprise middleware and applications. Paho is being kicked off with MQTT publish/subscribe client implementations for use on embedded platforms, along with corresponding server support as determined by the community. + +- [Project web site](https://www.eclipse.org/paho) +- [Project information](https://projects.eclipse.org/projects/iot.paho) + +Source +------ + +The Paho C client is stored in a git repository. The URLs to access it are: + +ssh://@git.eclipse.org:29418/paho/org.eclipse.paho.mqtt.c +https://@git.eclipse.org/r/paho/org.eclipse.paho.mqtt.c + +A [web browsable repository](http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.c.git) is available. + +Contributing a patch +-------------------- + +The Paho repositories are accessed through Gerrit, the code review +project, which makes it possible for anybody to clone the repository, make +changes and push them back for review and eventual acceptance into the project. + +To do this, you must follow a few steps. The first of these are described at + +- [Contributing via git](https://wiki.eclipse.org/Development_Resources/Contributing_via_Git) + +* Sign the Eclipse CLA +* Use a valid commit record, including a signed-off-by entry. + +There are further details at + +- [Handling Git Contributions](https://wiki.eclipse.org/Development_Resources/Handling_Git_Contributions) + +Once the patch is pushed back to Gerrit, the project committers will be +informed and they will undertake a review of the code. The patch may need +modifying for some reason. In order to make amending commits more +straightforward, the steps at +https://git.eclipse.org/r/Documentation/cmd-hook-commit-msg.html should be +followed. This automatically inserts a "Change-Id" entry to your commit message +which allows you to amend commits and have Gerrit track them as the same +change. + +What happens next depends on the content of the patch. If it is 100% authored +by the contributor and is less than 1000 lines (and meets the needs of the +project), then it can be committed to the main repository. If not, more steps +are required. These are detailed in the +[legal process poster](http://www.eclipse.org/legal/EclipseLegalProcessPoster.pdf). + + +Developer resources: +-------------------- + +Information regarding source code management, builds, coding standards, and more. + +- [https://projects.eclipse.org/projects/iot.paho/developer](https://projects.eclipse.org/projects/iot.paho/developer) + +Contributor License Agreement: +------------------------------ + +Before your contribution can be accepted by the project, you need to create and electronically sign the Eclipse Foundation [Contributor License Agreement (CLA)](http://www.eclipse.org/legal/CLA.php). + +Contact: +-------- + +Contact the project developers via the project's development +[mailing list](https://dev.eclipse.org/mailman/listinfo/paho-dev). + +Search for bugs: +---------------- + +This project uses [Bugzilla](https://bugs.eclipse.org/bugs/buglist.cgi?product=Paho) to track ongoing development and issues. + +Create a new bug: +----------------- + +Be sure to search for existing bugs before you create another one. Remember that contributions are always welcome! + +- [Create new Paho bug](https://bugs.eclipse.org/bugs/enter_bug.cgi?product=Paho) diff --git a/Sources/paho/Makefile b/Sources/paho/Makefile new file mode 100644 index 0000000..6f7fef4 --- /dev/null +++ b/Sources/paho/Makefile @@ -0,0 +1,336 @@ +#******************************************************************************* +# 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 v1.0 +# and Eclipse Distribution License v1.0 which accompany this distribution. +# +# The Eclipse Public License is available at +# http://www.eclipse.org/legal/epl-v10.html +# and the Eclipse Distribution License is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# Contributors: +# Ian Craggs - initial API and implementation and/or initial documentation +# Allan Stockdill-Mander - SSL updates +# Andy Piper - various fixes +# Ian Craggs - OSX build +#*******************************************************************************/ + +# Note: on OS X you should install XCode and the associated command-line tools + +SHELL = /bin/sh +.PHONY: clean, mkdir, install, uninstall, html + +# assume this is normally run in the main Paho directory +ifndef srcdir + srcdir = src +endif + +ifndef blddir + blddir = build/output +endif + +ifndef prefix + prefix = /usr/local +endif + +ifndef exec_prefix + exec_prefix = ${prefix} +endif + +bindir = $(exec_prefix)/bin +includedir = $(prefix)/include +libdir = $(exec_prefix)/lib + +SOURCE_FILES = $(wildcard $(srcdir)/*.c) +SOURCE_FILES_C = $(filter-out $(srcdir)/MQTTAsync.c $(srcdir)/MQTTVersion.c $(srcdir)/SSLSocket.c, $(SOURCE_FILES)) +SOURCE_FILES_CS = $(filter-out $(srcdir)/MQTTAsync.c $(srcdir)/MQTTVersion.c, $(SOURCE_FILES)) +SOURCE_FILES_A = $(filter-out $(srcdir)/MQTTClient.c $(srcdir)/MQTTVersion.c $(srcdir)/SSLSocket.c, $(SOURCE_FILES)) +SOURCE_FILES_AS = $(filter-out $(srcdir)/MQTTClient.c $(srcdir)/MQTTVersion.c, $(SOURCE_FILES)) + +HEADERS = $(srcdir)/*.h +HEADERS_C = $(filter-out $(srcdir)/MQTTAsync.h, $(HEADERS)) +HEADERS_A = $(HEADERS) + +SAMPLE_FILES_C = stdinpub stdoutsub pubsync pubasync subasync +SYNC_SAMPLES = ${addprefix ${blddir}/samples/,${SAMPLE_FILES_C}} + +SAMPLE_FILES_A = stdoutsuba MQTTAsync_subscribe MQTTAsync_publish +ASYNC_SAMPLES = ${addprefix ${blddir}/samples/,${SAMPLE_FILES_A}} + +TEST_FILES_C = test1 sync_client_test test_mqtt4sync +SYNC_TESTS = ${addprefix ${blddir}/test/,${TEST_FILES_C}} + +TEST_FILES_CS = test3 +SYNC_SSL_TESTS = ${addprefix ${blddir}/test/,${TEST_FILES_CS}} + +TEST_FILES_A = test4 test_mqtt4async +ASYNC_TESTS = ${addprefix ${blddir}/test/,${TEST_FILES_A}} + +TEST_FILES_AS = test5 +ASYNC_SSL_TESTS = ${addprefix ${blddir}/test/,${TEST_FILES_AS}} + +# The names of the four different libraries to be built +MQTTLIB_C = paho-mqtt3c +MQTTLIB_CS = paho-mqtt3cs +MQTTLIB_A = paho-mqtt3a +MQTTLIB_AS = paho-mqtt3as + +# determine current platform +ifeq ($(OS),Windows_NT) + OSTYPE = $(OS) +else + OSTYPE = $(shell uname -s) + MACHINETYPE = $(shell uname -m) +endif + +ifeq ($(OSTYPE),Linux) + +CC ?= gcc + +ifndef INSTALL +INSTALL = install +endif +INSTALL_PROGRAM = $(INSTALL) +INSTALL_DATA = $(INSTALL) -m 644 +DOXYGEN_COMMAND = doxygen + +MAJOR_VERSION = 1 +MINOR_VERSION = 0 +VERSION = ${MAJOR_VERSION}.${MINOR_VERSION} + +MQTTLIB_C_TARGET = ${blddir}/lib${MQTTLIB_C}.so.${VERSION} +MQTTLIB_CS_TARGET = ${blddir}/lib${MQTTLIB_CS}.so.${VERSION} +MQTTLIB_A_TARGET = ${blddir}/lib${MQTTLIB_A}.so.${VERSION} +MQTTLIB_AS_TARGET = ${blddir}/lib${MQTTLIB_AS}.so.${VERSION} +MQTTVERSION_TARGET = ${blddir}/MQTTVersion + +CCFLAGS_SO = -g -fPIC -Os -Wall -fvisibility=hidden +FLAGS_EXE = -I ${srcdir} -lpthread -L ${blddir} + +LDFLAGS_C = -shared -Wl,-soname,lib$(MQTTLIB_C).so.${MAJOR_VERSION} -Wl,-init,MQTTClient_init -lpthread +LDFLAGS_CS = -shared -Wl,-soname,lib$(MQTTLIB_CS).so.${MAJOR_VERSION} -lpthread -ldl -lssl -lcrypto -Wl,-no-whole-archive -Wl,-init,MQTTClient_init +LDFLAGS_A = -shared -Wl,-soname,lib${MQTTLIB_A}.so.${MAJOR_VERSION} -Wl,-init,MQTTAsync_init -lpthread +LDFLAGS_AS = -shared -Wl,-soname,lib${MQTTLIB_AS}.so.${MAJOR_VERSION} -lpthread -ldl -lssl -lcrypto -Wl,-no-whole-archive -Wl,-init,MQTTAsync_init + +all: build + +build: | mkdir ${MQTTLIB_C_TARGET} ${MQTTLIB_CS_TARGET} ${MQTTLIB_A_TARGET} ${MQTTLIB_AS_TARGET} ${MQTTVERSION_TARGET} ${SYNC_SAMPLES} ${ASYNC_SAMPLES} ${SYNC_TESTS} ${SYNC_SSL_TESTS} ${ASYNC_TESTS} ${ASYNC_SSL_TESTS} + +clean: + rm -rf ${blddir}/* + +mkdir: + -mkdir -p ${blddir}/samples + -mkdir -p ${blddir}/test + +${SYNC_TESTS}: ${blddir}/test/%: ${srcdir}/../test/%.c + ${CC} -g -o ${blddir}/test/${basename ${+F}} $< -l${MQTTLIB_C} ${FLAGS_EXE} + +${SYNC_SSL_TESTS}: ${blddir}/test/%: ${srcdir}/../test/%.c + ${CC} -g -o ${blddir}/test/${basename ${+F}} $< -l${MQTTLIB_CS} ${FLAGS_EXE} -lssl + +${ASYNC_TESTS}: ${blddir}/test/%: ${srcdir}/../test/%.c + ${CC} -g -o ${blddir}/test/${basename ${+F}} $< -l${MQTTLIB_A} ${FLAGS_EXE} + +${ASYNC_SSL_TESTS}: ${blddir}/test/%: ${srcdir}/../test/%.c + ${CC} -g -o ${blddir}/test/${basename ${+F}} $< -l${MQTTLIB_AS} ${FLAGS_EXE} -lssl + +${SYNC_SAMPLES}: ${blddir}/samples/%: ${srcdir}/samples/%.c + ${CC} -o ${blddir}/samples/${basename ${+F}} $< -l${MQTTLIB_C} ${FLAGS_EXE} + +${ASYNC_SAMPLES}: ${blddir}/samples/%: ${srcdir}/samples/%.c + ${CC} -o ${blddir}/samples/${basename ${+F}} $< -l${MQTTLIB_A} ${FLAGS_EXE} + +${MQTTLIB_C_TARGET}: ${SOURCE_FILES_C} ${HEADERS_C} + ${CC} ${CCFLAGS_SO} -o $@ ${SOURCE_FILES_C} ${LDFLAGS_C} + -ln -s lib$(MQTTLIB_C).so.${VERSION} ${blddir}/lib$(MQTTLIB_C).so.${MAJOR_VERSION} + -ln -s lib$(MQTTLIB_C).so.${MAJOR_VERSION} ${blddir}/lib$(MQTTLIB_C).so + +${MQTTLIB_CS_TARGET}: ${SOURCE_FILES_CS} ${HEADERS_C} + ${CC} ${CCFLAGS_SO} -o $@ ${SOURCE_FILES_CS} -DOPENSSL ${LDFLAGS_CS} + -ln -s lib$(MQTTLIB_CS).so.${VERSION} ${blddir}/lib$(MQTTLIB_CS).so.${MAJOR_VERSION} + -ln -s lib$(MQTTLIB_CS).so.${MAJOR_VERSION} ${blddir}/lib$(MQTTLIB_CS).so + +${MQTTLIB_A_TARGET}: ${SOURCE_FILES_A} ${HEADERS_A} + ${CC} ${CCFLAGS_SO} -o $@ ${SOURCE_FILES_A} ${LDFLAGS_A} + -ln -s lib$(MQTTLIB_A).so.${VERSION} ${blddir}/lib$(MQTTLIB_A).so.${MAJOR_VERSION} + -ln -s lib$(MQTTLIB_A).so.${MAJOR_VERSION} ${blddir}/lib$(MQTTLIB_A).so + +${MQTTLIB_AS_TARGET}: ${SOURCE_FILES_AS} ${HEADERS_A} + ${CC} ${CCFLAGS_SO} -o $@ ${SOURCE_FILES_AS} -DOPENSSL ${LDFLAGS_AS} + -ln -s lib$(MQTTLIB_AS).so.${VERSION} ${blddir}/lib$(MQTTLIB_AS).so.${MAJOR_VERSION} + -ln -s lib$(MQTTLIB_AS).so.${MAJOR_VERSION} ${blddir}/lib$(MQTTLIB_AS).so + +${MQTTVERSION_TARGET}: $(srcdir)/MQTTVersion.c $(srcdir)/MQTTAsync.h + ${CC} ${FLAGS_EXE} -o $@ -l${MQTTLIB_A} $(srcdir)/MQTTVersion.c -ldl + +strip_options: + $(eval INSTALL_OPTS := -s) + +install-strip: build strip_options install + +install: build + $(INSTALL_DATA) ${INSTALL_OPTS} ${MQTTLIB_C_TARGET} $(DESTDIR)${libdir} + $(INSTALL_DATA) ${INSTALL_OPTS} ${MQTTLIB_CS_TARGET} $(DESTDIR)${libdir} + $(INSTALL_DATA) ${INSTALL_OPTS} ${MQTTLIB_A_TARGET} $(DESTDIR)${libdir} + $(INSTALL_DATA) ${INSTALL_OPTS} ${MQTTLIB_AS_TARGET} $(DESTDIR)${libdir} + $(INSTALL_PROGRAM) ${INSTALL_OPTS} ${MQTTVERSION_TARGET} $(DESTDIR)${bindir} + /sbin/ldconfig $(DESTDIR)${libdir} + ln -s lib$(MQTTLIB_C).so.${MAJOR_VERSION} $(DESTDIR)${libdir}/lib$(MQTTLIB_C).so + ln -s lib$(MQTTLIB_CS).so.${MAJOR_VERSION} $(DESTDIR)${libdir}/lib$(MQTTLIB_CS).so + ln -s lib$(MQTTLIB_A).so.${MAJOR_VERSION} $(DESTDIR)${libdir}/lib$(MQTTLIB_A).so + ln -s lib$(MQTTLIB_AS).so.${MAJOR_VERSION} $(DESTDIR)${libdir}/lib$(MQTTLIB_AS).so + $(INSTALL_DATA) ${srcdir}/MQTTAsync.h $(DESTDIR)${includedir} + $(INSTALL_DATA) ${srcdir}/MQTTClient.h $(DESTDIR)${includedir} + $(INSTALL_DATA) ${srcdir}/MQTTClientPersistence.h $(DESTDIR)${includedir} + +uninstall: + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_C).so.${VERSION} + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_CS).so.${VERSION} + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_A).so.${VERSION} + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_AS).so.${VERSION} + rm $(DESTDIR)${bindir}/MQTTVersion + /sbin/ldconfig $(DESTDIR)${libdir} + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_C).so + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_CS).so + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_A).so + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_AS).so + rm $(DESTDIR)${includedir}/MQTTAsync.h + rm $(DESTDIR)${includedir}/MQTTClient.h + rm $(DESTDIR)${includedir}/MQTTClientPersistence.h + +html: + -mkdir -p ${blddir}/doc + cd ${srcdir}; $(DOXYGEN_COMMAND) ../doc/DoxyfileV3ClientAPI + cd ${srcdir}; $(DOXYGEN_COMMAND) ../doc/DoxyfileV3AsyncAPI + cd ${srcdir}; $(DOXYGEN_COMMAND) ../doc/DoxyfileV3ClientInternal + +endif + + + +ifeq ($(OSTYPE),Darwin) + +CC ?= gcc + +ifndef INSTALL +INSTALL = install +endif +INSTALL_PROGRAM = $(INSTALL) +INSTALL_DATA = $(INSTALL) -m 644 +DOXYGEN_COMMAND = doxygen + +MAJOR_VERSION = 1 +MINOR_VERSION = 0 +VERSION = ${MAJOR_VERSION}.${MINOR_VERSION} + +MQTTLIB_C_TARGET = ${blddir}/lib${MQTTLIB_C}.so.${VERSION} +MQTTLIB_CS_TARGET = ${blddir}/lib${MQTTLIB_CS}.so.${VERSION} +MQTTLIB_A_TARGET = ${blddir}/lib${MQTTLIB_A}.so.${VERSION} +MQTTLIB_AS_TARGET = ${blddir}/lib${MQTTLIB_AS}.so.${VERSION} +MQTTVERSION_TARGET = ${blddir}/MQTTVersion + +CCFLAGS_SO = -g -fPIC -Os -Wall -fvisibility=hidden -Wno-deprecated-declarations -DUSE_NAMED_SEMAPHORES +FLAGS_EXE = -I ${srcdir} -lpthread -L ${blddir} + +LDFLAGS_C = -shared -Wl,-install_name,lib$(MQTTLIB_C).so.${MAJOR_VERSION} -Wl,-init,_MQTTClient_init -lpthread +LDFLAGS_CS = -shared -Wl,-install_name,lib$(MQTTLIB_CS).so.${MAJOR_VERSION} -lpthread -ldl -lssl -lcrypto -Wl,-init,_MQTTClient_init +LDFLAGS_A = -shared -Wl,-install_name,lib${MQTTLIB_A}.so.${MAJOR_VERSION} -Wl,-init,_MQTTAsync_init -lpthread +LDFLAGS_AS = -shared -Wl,-install_name,lib${MQTTLIB_AS}.so.${MAJOR_VERSION} -lpthread -ldl -lssl -lcrypto -Wl,-init,_MQTTAsync_init + +all: build + +build: | mkdir ${MQTTLIB_C_TARGET} ${MQTTLIB_CS_TARGET} ${MQTTLIB_A_TARGET} ${MQTTLIB_AS_TARGET} ${MQTTVERSION_TARGET} ${SYNC_SAMPLES} ${ASYNC_SAMPLES} ${SYNC_TESTS} ${SYNC_SSL_TESTS} ${ASYNC_TESTS} ${ASYNC_SSL_TESTS} + +clean: + rm -rf ${blddir}/* + +mkdir: + -mkdir -p ${blddir}/samples + -mkdir -p ${blddir}/test + +${SYNC_TESTS}: ${blddir}/test/%: ${srcdir}/../test/%.c + ${CC} -g -o ${blddir}/test/${basename ${+F}} $< -l${MQTTLIB_C} ${FLAGS_EXE} + +${SYNC_SSL_TESTS}: ${blddir}/test/%: ${srcdir}/../test/%.c + ${CC} -g -o ${blddir}/test/${basename ${+F}} $< -l${MQTTLIB_CS} ${FLAGS_EXE} -lssl + +${ASYNC_TESTS}: ${blddir}/test/%: ${srcdir}/../test/%.c + ${CC} -g -o ${blddir}/test/${basename ${+F}} $< -l${MQTTLIB_A} ${FLAGS_EXE} + +${ASYNC_SSL_TESTS}: ${blddir}/test/%: ${srcdir}/../test/%.c + ${CC} -g -o ${blddir}/test/${basename ${+F}} $< -l${MQTTLIB_AS} ${FLAGS_EXE} -lssl + +${SYNC_SAMPLES}: ${blddir}/samples/%: ${srcdir}/samples/%.c + ${CC} -o ${blddir}/samples/${basename ${+F}} $< -l${MQTTLIB_C} ${FLAGS_EXE} + +${ASYNC_SAMPLES}: ${blddir}/samples/%: ${srcdir}/samples/%.c + ${CC} -o ${blddir}/samples/${basename ${+F}} $< -l${MQTTLIB_A} ${FLAGS_EXE} + +${MQTTLIB_C_TARGET}: ${SOURCE_FILES_C} ${HEADERS_C} + ${CC} ${CCFLAGS_SO} -o $@ ${SOURCE_FILES_C} ${LDFLAGS_C} + -ln -s lib$(MQTTLIB_C).so.${VERSION} ${blddir}/lib$(MQTTLIB_C).so.${MAJOR_VERSION} + -ln -s lib$(MQTTLIB_C).so.${MAJOR_VERSION} ${blddir}/lib$(MQTTLIB_C).so + +${MQTTLIB_CS_TARGET}: ${SOURCE_FILES_CS} ${HEADERS_C} + ${CC} ${CCFLAGS_SO} -o $@ ${SOURCE_FILES_CS} -DOPENSSL ${LDFLAGS_CS} + -ln -s lib$(MQTTLIB_CS).so.${VERSION} ${blddir}/lib$(MQTTLIB_CS).so.${MAJOR_VERSION} + -ln -s lib$(MQTTLIB_CS).so.${MAJOR_VERSION} ${blddir}/lib$(MQTTLIB_CS).so + +${MQTTLIB_A_TARGET}: ${SOURCE_FILES_A} ${HEADERS_A} + ${CC} ${CCFLAGS_SO} -o $@ ${SOURCE_FILES_A} ${LDFLAGS_A} + -ln -s lib$(MQTTLIB_A).so.${VERSION} ${blddir}/lib$(MQTTLIB_A).so.${MAJOR_VERSION} + -ln -s lib$(MQTTLIB_A).so.${MAJOR_VERSION} ${blddir}/lib$(MQTTLIB_A).so + +${MQTTLIB_AS_TARGET}: ${SOURCE_FILES_AS} ${HEADERS_A} + ${CC} ${CCFLAGS_SO} -o $@ ${SOURCE_FILES_AS} -DOPENSSL ${LDFLAGS_AS} + -ln -s lib$(MQTTLIB_AS).so.${VERSION} ${blddir}/lib$(MQTTLIB_AS).so.${MAJOR_VERSION} + -ln -s lib$(MQTTLIB_AS).so.${MAJOR_VERSION} ${blddir}/lib$(MQTTLIB_AS).so + +${MQTTVERSION_TARGET}: $(srcdir)/MQTTVersion.c $(srcdir)/MQTTAsync.h + ${CC} ${FLAGS_EXE} -o $@ -l${MQTTLIB_A} $(srcdir)/MQTTVersion.c -ldl + +strip_options: + $(eval INSTALL_OPTS := -s) + +install-strip: build strip_options install + +install: build + $(INSTALL_DATA) ${INSTALL_OPTS} ${MQTTLIB_C_TARGET} $(DESTDIR)${libdir} + $(INSTALL_DATA) ${INSTALL_OPTS} ${MQTTLIB_CS_TARGET} $(DESTDIR)${libdir} + $(INSTALL_DATA) ${INSTALL_OPTS} ${MQTTLIB_A_TARGET} $(DESTDIR)${libdir} + $(INSTALL_DATA) ${INSTALL_OPTS} ${MQTTLIB_AS_TARGET} $(DESTDIR)${libdir} + $(INSTALL_PROGRAM) ${INSTALL_OPTS} ${MQTTVERSION_TARGET} $(DESTDIR)${bindir} + /sbin/ldconfig $(DESTDIR)${libdir} + ln -s lib$(MQTTLIB_C).so.${MAJOR_VERSION} $(DESTDIR)${libdir}/lib$(MQTTLIB_C).so + ln -s lib$(MQTTLIB_CS).so.${MAJOR_VERSION} $(DESTDIR)${libdir}/lib$(MQTTLIB_CS).so + ln -s lib$(MQTTLIB_A).so.${MAJOR_VERSION} $(DESTDIR)${libdir}/lib$(MQTTLIB_A).so + ln -s lib$(MQTTLIB_AS).so.${MAJOR_VERSION} $(DESTDIR)${libdir}/lib$(MQTTLIB_AS).so + $(INSTALL_DATA) ${srcdir}/MQTTAsync.h $(DESTDIR)${includedir} + $(INSTALL_DATA) ${srcdir}/MQTTClient.h $(DESTDIR)${includedir} + $(INSTALL_DATA) ${srcdir}/MQTTClientPersistence.h $(DESTDIR)${includedir} + +uninstall: + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_C).so.${VERSION} + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_CS).so.${VERSION} + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_A).so.${VERSION} + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_AS).so.${VERSION} + rm $(DESTDIR)${bindir}/MQTTVersion + /sbin/ldconfig $(DESTDIR)${libdir} + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_C).so + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_CS).so + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_A).so + rm $(DESTDIR)${libdir}/lib$(MQTTLIB_AS).so + rm $(DESTDIR)${includedir}/MQTTAsync.h + rm $(DESTDIR)${includedir}/MQTTClient.h + rm $(DESTDIR)${includedir}/MQTTClientPersistence.h + +html: + -mkdir -p ${blddir}/doc + cd ${srcdir}; $(DOXYGEN_COMMAND) ../doc/DoxyfileV3ClientAPI + cd ${srcdir}; $(DOXYGEN_COMMAND) ../doc/DoxyfileV3AsyncAPI + cd ${srcdir}; $(DOXYGEN_COMMAND) ../doc/DoxyfileV3ClientInternal + +endif diff --git a/Sources/paho/README.md b/Sources/paho/README.md new file mode 100644 index 0000000..4a28c68 --- /dev/null +++ b/Sources/paho/README.md @@ -0,0 +1,88 @@ +# Eclipse Paho MQTT C client + + +This repository contains the source code for the [Eclipse Paho](http://eclipse.org/paho) MQTT C client library. + +This code builds libraries which enable applications to connect to an [MQTT](http://mqtt.org) broker to publish messages, and to subscribe to topics and receive published messages. + +Both synchronous and asynchronous modes of operation are supported. + +## Build requirements / compilation + +The build process requires GNU Make and gcc, and also requires OpenSSL libraries and includes to be available. There are make rules for a number of Linux "flavors" including ARM and s390, OS X, AIX and Solaris. + +The documentation requires doxygen and optionally graphviz. + +Before compiling, set some environment variables (or pass these values to the make command directly) in order to configure library locations and other options. + +Specify the location of OpenSSL using `SSL_DIR` + +e.g. using [homebrew](http://mxcl.github.com/homebrew/) on OS X: + +`export SSL_DIR=/usr/local/Cellar/openssl/1.0.1e` + +Specify where the source files are in relation to where the make command is being executed. + +``export MQTTCLIENT_DIR=../src`` + +To build the samples, enable the option (`BUILD_SAMPLES`), and specify the location of the code: + +``` +export BUILD_SAMPLES=YES +export SAMPLES_DIR=../src/samples +``` + +One more useful environment variable is ``TARGET_PLATFORM``. This provides for cross-compilation. Currently the only recognised value is "Arm" - for instance to cross-compile a Linux ARM version of the libraries: + +``export TARGET_PLATFORM=Arm`` + +## Libraries + +The Paho C client comprises four shared libraries: + + * libmqttv3a.so - asynchronous + * libmqttv3as.so - asynchronous with SSL + * libmqttv3c.so - "classic" / synchronous + * libmqttv3cs.so - "classic" / synchronous with SSL + +## Usage and API + +Detailed API documentation is available by building the Doxygen docs in the ``doc`` directory. A [snapshot is also available online](http://www.eclipse.org/paho/files/mqttdoc/Cclient/index.html). + +Samples are available in the Doxygen docs and also in ``src/samples`` for reference. + +Note that using the C headers from a C++ program requires the following declaration as part of the C++ code: + +``` + extern "C" { + #include "MQTTClient.h" + #include "MQTTClientPersistence.h" + } +``` + +## Runtime tracing + +A number of environment variables control runtime tracing of the C library. + +Tracing is switched on using ``MQTT_C_CLIENT_TRACE`` (a value of ON traces to stdout, any other value should specify a file to trace to). + +The verbosity of the output is controlled using the ``MQTT_C_CLIENT_TRACE_LEVEL`` environment variable - valid values are ERROR, PROTOCOL, MINIMUM, MEDIUM and MAXIMUM (from least to most verbose). + +The variable ``MQTT_C_CLIENT_TRACE_MAX_LINES`` limits the number of lines of trace that are output. + +``` +export MQTT_C_CLIENT_TRACE=ON +export MQTT_C_CLIENT_TRACE_LEVEL=PROTOCOL +``` + +## Reporting bugs + +Please report bugs under the "MQTT-C" Component in [Eclipse Bugzilla](http://bugs.eclipse.org/bugs/) for the Paho Technology project. + +## More information + +Discussion of the Paho clients takes place on the [Eclipse paho-dev mailing list](https://dev.eclipse.org/mailman/listinfo/paho-dev). + +General questions about the MQTT protocol are discussed in the [MQTT Google Group](https://groups.google.com/forum/?hl=en-US&fromgroups#!forum/mqtt). + +There is much more information available via the [MQTT community site](http://mqtt.org). diff --git a/Sources/paho/Windows Build/MQTTVersion/MQTTVersion.vcxproj b/Sources/paho/Windows Build/MQTTVersion/MQTTVersion.vcxproj new file mode 100644 index 0000000..d567a44 --- /dev/null +++ b/Sources/paho/Windows Build/MQTTVersion/MQTTVersion.vcxproj @@ -0,0 +1,98 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {6EFC1F3B-CEE1-4DD2-80B4-CEC37954D468} + Win32Proj + ConsoleApplication1 + MQTTVersion + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + 4996 + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + 4996 + + + Console + true + true + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/MQTTVersion/MQTTVersion.vcxproj.filters b/Sources/paho/Windows Build/MQTTVersion/MQTTVersion.vcxproj.filters new file mode 100644 index 0000000..2442da5 --- /dev/null +++ b/Sources/paho/Windows Build/MQTTVersion/MQTTVersion.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/MQTTVersion/MQTTVersion.vcxproj.user b/Sources/paho/Windows Build/MQTTVersion/MQTTVersion.vcxproj.user new file mode 100644 index 0000000..ef5ff2a --- /dev/null +++ b/Sources/paho/Windows Build/MQTTVersion/MQTTVersion.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/Paho C MQTT APIs.sln b/Sources/paho/Windows Build/Paho C MQTT APIs.sln new file mode 100644 index 0000000..b56fd75 --- /dev/null +++ b/Sources/paho/Windows Build/Paho C MQTT APIs.sln @@ -0,0 +1,91 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "paho-mqtt3c", "paho-mqtt3c\paho-mqtt3c.vcxproj", "{172F8995-C780-44A1-996C-C7949B4DB35A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "paho-mqtt3a", "paho-mqtt3a\paho-mqtt3a.vcxproj", "{B479B6EF-787D-4716-912A-E0F6F7BDA7A9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stdoutsub", "stdoutsub\stdoutsub.vcxproj", "{DFDF6238-DA97-4474-84C2-D313E8B985AE}" + ProjectSection(ProjectDependencies) = postProject + {172F8995-C780-44A1-996C-C7949B4DB35A} = {172F8995-C780-44A1-996C-C7949B4DB35A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stdoutsuba", "stdoutsuba\stdoutsuba.vcxproj", "{AF322561-C692-43D3-8502-CC1E6CD2869A}" + ProjectSection(ProjectDependencies) = postProject + {B479B6EF-787D-4716-912A-E0F6F7BDA7A9} = {B479B6EF-787D-4716-912A-E0F6F7BDA7A9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MQTTVersion", "MQTTVersion\MQTTVersion.vcxproj", "{6EFC1F3B-CEE1-4DD2-80B4-CEC37954D468}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "paho-mqtt3as", "paho-mqtt3as\paho-mqtt3as.vcxproj", "{DEF21D1B-CB65-4A78-805F-CF421249EB83}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "paho-mqtt3cs", "paho-mqtt3cs\paho-mqtt3cs.vcxproj", "{17F07F98-AA5F-4373-9877-992A341D650A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test1", "test1\test1.vcxproj", "{4E643090-289D-487D-BCA8-685EA2210480}" + ProjectSection(ProjectDependencies) = postProject + {172F8995-C780-44A1-996C-C7949B4DB35A} = {172F8995-C780-44A1-996C-C7949B4DB35A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test3", "test3\test3.vcxproj", "{0CBDD939-F0C9-4887-8C7E-9E645C34FF94}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test4", "test4\test4.vcxproj", "{29D6A4E9-5A39-4CD3-8A24-348A34832405}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test5", "test5\test5.vcxproj", "{B8A895EA-C8DE-4235-B4B4-06889BBBDC93}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {172F8995-C780-44A1-996C-C7949B4DB35A}.Debug|Win32.ActiveCfg = Debug|Win32 + {172F8995-C780-44A1-996C-C7949B4DB35A}.Debug|Win32.Build.0 = Debug|Win32 + {172F8995-C780-44A1-996C-C7949B4DB35A}.Release|Win32.ActiveCfg = Release|Win32 + {172F8995-C780-44A1-996C-C7949B4DB35A}.Release|Win32.Build.0 = Release|Win32 + {B479B6EF-787D-4716-912A-E0F6F7BDA7A9}.Debug|Win32.ActiveCfg = Debug|Win32 + {B479B6EF-787D-4716-912A-E0F6F7BDA7A9}.Debug|Win32.Build.0 = Debug|Win32 + {B479B6EF-787D-4716-912A-E0F6F7BDA7A9}.Release|Win32.ActiveCfg = Release|Win32 + {B479B6EF-787D-4716-912A-E0F6F7BDA7A9}.Release|Win32.Build.0 = Release|Win32 + {DFDF6238-DA97-4474-84C2-D313E8B985AE}.Debug|Win32.ActiveCfg = Debug|Win32 + {DFDF6238-DA97-4474-84C2-D313E8B985AE}.Debug|Win32.Build.0 = Debug|Win32 + {DFDF6238-DA97-4474-84C2-D313E8B985AE}.Release|Win32.ActiveCfg = Release|Win32 + {DFDF6238-DA97-4474-84C2-D313E8B985AE}.Release|Win32.Build.0 = Release|Win32 + {AF322561-C692-43D3-8502-CC1E6CD2869A}.Debug|Win32.ActiveCfg = Debug|Win32 + {AF322561-C692-43D3-8502-CC1E6CD2869A}.Debug|Win32.Build.0 = Debug|Win32 + {AF322561-C692-43D3-8502-CC1E6CD2869A}.Release|Win32.ActiveCfg = Release|Win32 + {AF322561-C692-43D3-8502-CC1E6CD2869A}.Release|Win32.Build.0 = Release|Win32 + {6EFC1F3B-CEE1-4DD2-80B4-CEC37954D468}.Debug|Win32.ActiveCfg = Debug|Win32 + {6EFC1F3B-CEE1-4DD2-80B4-CEC37954D468}.Debug|Win32.Build.0 = Debug|Win32 + {6EFC1F3B-CEE1-4DD2-80B4-CEC37954D468}.Release|Win32.ActiveCfg = Release|Win32 + {6EFC1F3B-CEE1-4DD2-80B4-CEC37954D468}.Release|Win32.Build.0 = Release|Win32 + {DEF21D1B-CB65-4A78-805F-CF421249EB83}.Debug|Win32.ActiveCfg = Debug|Win32 + {DEF21D1B-CB65-4A78-805F-CF421249EB83}.Debug|Win32.Build.0 = Debug|Win32 + {DEF21D1B-CB65-4A78-805F-CF421249EB83}.Release|Win32.ActiveCfg = Release|Win32 + {DEF21D1B-CB65-4A78-805F-CF421249EB83}.Release|Win32.Build.0 = Release|Win32 + {17F07F98-AA5F-4373-9877-992A341D650A}.Debug|Win32.ActiveCfg = Debug|Win32 + {17F07F98-AA5F-4373-9877-992A341D650A}.Debug|Win32.Build.0 = Debug|Win32 + {17F07F98-AA5F-4373-9877-992A341D650A}.Release|Win32.ActiveCfg = Release|Win32 + {17F07F98-AA5F-4373-9877-992A341D650A}.Release|Win32.Build.0 = Release|Win32 + {4E643090-289D-487D-BCA8-685EA2210480}.Debug|Win32.ActiveCfg = Debug|Win32 + {4E643090-289D-487D-BCA8-685EA2210480}.Debug|Win32.Build.0 = Debug|Win32 + {4E643090-289D-487D-BCA8-685EA2210480}.Release|Win32.ActiveCfg = Release|Win32 + {4E643090-289D-487D-BCA8-685EA2210480}.Release|Win32.Build.0 = Release|Win32 + {0CBDD939-F0C9-4887-8C7E-9E645C34FF94}.Debug|Win32.ActiveCfg = Debug|Win32 + {0CBDD939-F0C9-4887-8C7E-9E645C34FF94}.Debug|Win32.Build.0 = Debug|Win32 + {0CBDD939-F0C9-4887-8C7E-9E645C34FF94}.Release|Win32.ActiveCfg = Release|Win32 + {0CBDD939-F0C9-4887-8C7E-9E645C34FF94}.Release|Win32.Build.0 = Release|Win32 + {29D6A4E9-5A39-4CD3-8A24-348A34832405}.Debug|Win32.ActiveCfg = Debug|Win32 + {29D6A4E9-5A39-4CD3-8A24-348A34832405}.Debug|Win32.Build.0 = Debug|Win32 + {29D6A4E9-5A39-4CD3-8A24-348A34832405}.Release|Win32.ActiveCfg = Release|Win32 + {29D6A4E9-5A39-4CD3-8A24-348A34832405}.Release|Win32.Build.0 = Release|Win32 + {B8A895EA-C8DE-4235-B4B4-06889BBBDC93}.Debug|Win32.ActiveCfg = Debug|Win32 + {B8A895EA-C8DE-4235-B4B4-06889BBBDC93}.Debug|Win32.Build.0 = Debug|Win32 + {B8A895EA-C8DE-4235-B4B4-06889BBBDC93}.Release|Win32.ActiveCfg = Release|Win32 + {B8A895EA-C8DE-4235-B4B4-06889BBBDC93}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Sources/paho/Windows Build/paho-mqtt3a/paho-mqtt3a.vcxproj b/Sources/paho/Windows Build/paho-mqtt3a/paho-mqtt3a.vcxproj new file mode 100644 index 0000000..105982a --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3a/paho-mqtt3a.vcxproj @@ -0,0 +1,134 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {B479B6EF-787D-4716-912A-E0F6F7BDA7A9} + Win32Proj + pahomqtt3a + + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;PAHOMQTT3A_EXPORTS;%(PreprocessorDefinitions) + 4996 + + + Windows + true + ws2_32.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;PAHOMQTT3A_EXPORTS;%(PreprocessorDefinitions) + 4996 + + + Windows + true + true + true + ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/paho-mqtt3a/paho-mqtt3a.vcxproj.filters b/Sources/paho/Windows Build/paho-mqtt3a/paho-mqtt3a.vcxproj.filters new file mode 100644 index 0000000..b142753 --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3a/paho-mqtt3a.vcxproj.filters @@ -0,0 +1,153 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/paho-mqtt3a/paho-mqtt3a.vcxproj.user b/Sources/paho/Windows Build/paho-mqtt3a/paho-mqtt3a.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3a/paho-mqtt3a.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/paho-mqtt3as/paho-mqtt3as.vcxproj b/Sources/paho/Windows Build/paho-mqtt3as/paho-mqtt3as.vcxproj new file mode 100644 index 0000000..f23b42c --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3as/paho-mqtt3as.vcxproj @@ -0,0 +1,134 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {DEF21D1B-CB65-4A78-805F-CF421249EB83} + Win32Proj + pahomqtt3as + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\ + + + + NotUsing + Level3 + Disabled + WIN32_LEAN_AND_MEAN;OPENSSL;WIN32;_DEBUG;_WINDOWS;_USRDLL;PAHOMQTT3AS_EXPORTS;%(PreprocessorDefinitions) + 4996 + $(OpenSSLDir)\include;%(AdditionalIncludeDirectories) + + + Windows + true + ws2_32.lib;libeay32.lib;ssleay32.lib;%(AdditionalDependencies) + $(OpenSSLDir)\lib;%(AdditionalLibraryDirectories) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32_LEAN_AND_MEAN;OPENSSL;WIN32;NDEBUG;_WINDOWS;_USRDLL;PAHOMQTT3AS_EXPORTS;%(PreprocessorDefinitions) + 4996 + $(OpenSSLDir)\include;%(AdditionalIncludeDirectories) + + + Windows + false + true + true + ws2_32.lib;libeay32.lib;ssleay32.lib;%(AdditionalDependencies) + $(OpenSSLDir)\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/paho/Windows Build/paho-mqtt3as/paho-mqtt3as.vcxproj.filters b/Sources/paho/Windows Build/paho-mqtt3as/paho-mqtt3as.vcxproj.filters new file mode 100644 index 0000000..87d749a --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3as/paho-mqtt3as.vcxproj.filters @@ -0,0 +1,147 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/paho-mqtt3as/paho-mqtt3as.vcxproj.user b/Sources/paho/Windows Build/paho-mqtt3as/paho-mqtt3as.vcxproj.user new file mode 100644 index 0000000..ef5ff2a --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3as/paho-mqtt3as.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/paho-mqtt3c/paho-mqtt3c.vcxproj b/Sources/paho/Windows Build/paho-mqtt3c/paho-mqtt3c.vcxproj new file mode 100644 index 0000000..2c52e34 --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3c/paho-mqtt3c.vcxproj @@ -0,0 +1,106 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {172F8995-C780-44A1-996C-C7949B4DB35A} + Win32Proj + pahomqtt3c + + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\ + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;PAHOMQTT3C_EXPORTS;%(PreprocessorDefinitions) + 4996 + + + Windows + true + ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;PAHOMQTT3C_EXPORTS;%(PreprocessorDefinitions) + 4996 + + + Windows + true + true + true + ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/paho-mqtt3c/paho-mqtt3c.vcxproj.filters b/Sources/paho/Windows Build/paho-mqtt3c/paho-mqtt3c.vcxproj.filters new file mode 100644 index 0000000..18988cc --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3c/paho-mqtt3c.vcxproj.filters @@ -0,0 +1,79 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/paho-mqtt3c/paho-mqtt3c.vcxproj.user b/Sources/paho/Windows Build/paho-mqtt3c/paho-mqtt3c.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3c/paho-mqtt3c.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/paho-mqtt3cs/paho-mqtt3cs.vcxproj b/Sources/paho/Windows Build/paho-mqtt3cs/paho-mqtt3cs.vcxproj new file mode 100644 index 0000000..d41c0f6 --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3cs/paho-mqtt3cs.vcxproj @@ -0,0 +1,134 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {17F07F98-AA5F-4373-9877-992A341D650A} + Win32Proj + pahomqtt3as + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\ + + + + NotUsing + Level3 + Disabled + WIN32_LEAN_AND_MEAN;OPENSSL;WIN32;_DEBUG;_WINDOWS;_USRDLL;PAHOMQTT3AS_EXPORTS;%(PreprocessorDefinitions) + 4996 + $(OpenSSLDir)\include;%(AdditionalIncludeDirectories) + + + Windows + true + ws2_32.lib;libeay32.lib;ssleay32.lib;%(AdditionalDependencies) + $(OpenSSLDir)\lib;%(AdditionalLibraryDirectories) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32_LEAN_AND_MEAN;OPENSSL;WIN32;NDEBUG;_WINDOWS;_USRDLL;PAHOMQTT3AS_EXPORTS;%(PreprocessorDefinitions) + 4996 + $(OpenSSLDir)\include;%(AdditionalIncludeDirectories) + + + Windows + false + true + true + ws2_32.lib;libeay32.lib;ssleay32.lib;%(AdditionalDependencies) + $(OpenSSLDir)\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/paho/Windows Build/paho-mqtt3cs/paho-mqtt3cs.vcxproj.filters b/Sources/paho/Windows Build/paho-mqtt3cs/paho-mqtt3cs.vcxproj.filters new file mode 100644 index 0000000..95f68ae --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3cs/paho-mqtt3cs.vcxproj.filters @@ -0,0 +1,147 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/paho-mqtt3cs/paho-mqtt3cs.vcxproj.user b/Sources/paho/Windows Build/paho-mqtt3cs/paho-mqtt3cs.vcxproj.user new file mode 100644 index 0000000..ef5ff2a --- /dev/null +++ b/Sources/paho/Windows Build/paho-mqtt3cs/paho-mqtt3cs.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/stdoutsub/stdoutsub.vcxproj b/Sources/paho/Windows Build/stdoutsub/stdoutsub.vcxproj new file mode 100644 index 0000000..66dabf5 --- /dev/null +++ b/Sources/paho/Windows Build/stdoutsub/stdoutsub.vcxproj @@ -0,0 +1,97 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {DFDF6238-DA97-4474-84C2-D313E8B985AE} + Win32Proj + stdoutsub + + + + Application + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\samples\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)\..\src + 4996 + + + Console + true + $(SolutionDir)\Debug + paho-mqtt3c.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)\..\src + 4996 + + + Console + true + true + true + paho-mqtt3c.lib;%(AdditionalDependencies) + $(SolutionDir)..\build\output\ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/stdoutsub/stdoutsub.vcxproj.filters b/Sources/paho/Windows Build/stdoutsub/stdoutsub.vcxproj.filters new file mode 100644 index 0000000..5bc7eaa --- /dev/null +++ b/Sources/paho/Windows Build/stdoutsub/stdoutsub.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/stdoutsub/stdoutsub.vcxproj.user b/Sources/paho/Windows Build/stdoutsub/stdoutsub.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/Sources/paho/Windows Build/stdoutsub/stdoutsub.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/stdoutsuba/stdoutsuba.vcxproj b/Sources/paho/Windows Build/stdoutsuba/stdoutsuba.vcxproj new file mode 100644 index 0000000..673b87b --- /dev/null +++ b/Sources/paho/Windows Build/stdoutsuba/stdoutsuba.vcxproj @@ -0,0 +1,97 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {AF322561-C692-43D3-8502-CC1E6CD2869A} + Win32Proj + stdoutsuba + + + + Application + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\samples\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)\..\src + 4996 + + + Console + true + paho-mqtt3a.lib;%(AdditionalDependencies) + $(SolutionDir)\Debug + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)\..\src + 4996 + + + Console + true + true + true + paho-mqtt3a.lib;%(AdditionalDependencies) + $(SolutionDir)..\build\output\ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/stdoutsuba/stdoutsuba.vcxproj.filters b/Sources/paho/Windows Build/stdoutsuba/stdoutsuba.vcxproj.filters new file mode 100644 index 0000000..817e9d8 --- /dev/null +++ b/Sources/paho/Windows Build/stdoutsuba/stdoutsuba.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/stdoutsuba/stdoutsuba.vcxproj.user b/Sources/paho/Windows Build/stdoutsuba/stdoutsuba.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/Sources/paho/Windows Build/stdoutsuba/stdoutsuba.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/test1/test1.vcxproj b/Sources/paho/Windows Build/test1/test1.vcxproj new file mode 100644 index 0000000..f519910 --- /dev/null +++ b/Sources/paho/Windows Build/test1/test1.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {4E643090-289D-487D-BCA8-685EA2210480} + Win32Proj + test1 + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\test\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + _WINDOWS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + $(SolutionDir)\..\src + 4996 + + + Console + true + true + true + $(SolutionDir)..\build\output\;%(AdditionalLibraryDirectories) + ws2_32.lib;paho-mqtt3c.lib;%(AdditionalDependencies) + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/test1/test1.vcxproj.filters b/Sources/paho/Windows Build/test1/test1.vcxproj.filters new file mode 100644 index 0000000..f5ae8ca --- /dev/null +++ b/Sources/paho/Windows Build/test1/test1.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/test3/test3.vcxproj b/Sources/paho/Windows Build/test3/test3.vcxproj new file mode 100644 index 0000000..75b69d5 --- /dev/null +++ b/Sources/paho/Windows Build/test3/test3.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {0CBDD939-F0C9-4887-8C7E-9E645C34FF94} + Win32Proj + test1 + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\test\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + _WINDOWS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + $(SolutionDir)\..\src + 4996 + + + Console + true + true + true + $(SolutionDir)..\build\output\;%(AdditionalLibraryDirectories) + ws2_32.lib;paho-mqtt3cs.lib;%(AdditionalDependencies) + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/test3/test3.vcxproj.filters b/Sources/paho/Windows Build/test3/test3.vcxproj.filters new file mode 100644 index 0000000..a63cda7 --- /dev/null +++ b/Sources/paho/Windows Build/test3/test3.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/test4/test4.vcxproj b/Sources/paho/Windows Build/test4/test4.vcxproj new file mode 100644 index 0000000..67b7d3c --- /dev/null +++ b/Sources/paho/Windows Build/test4/test4.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {29D6A4E9-5A39-4CD3-8A24-348A34832405} + Win32Proj + test1 + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\test\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + _WINDOWS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + $(SolutionDir)\..\src + 4996 + + + Console + true + true + true + $(SolutionDir)..\build\output\;%(AdditionalLibraryDirectories) + ws2_32.lib;paho-mqtt3a.lib;%(AdditionalDependencies) + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/test4/test4.vcxproj.filters b/Sources/paho/Windows Build/test4/test4.vcxproj.filters new file mode 100644 index 0000000..6ea29d7 --- /dev/null +++ b/Sources/paho/Windows Build/test4/test4.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/test5/test5.vcxproj b/Sources/paho/Windows Build/test5/test5.vcxproj new file mode 100644 index 0000000..588fa35 --- /dev/null +++ b/Sources/paho/Windows Build/test5/test5.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {B8A895EA-C8DE-4235-B4B4-06889BBBDC93} + Win32Proj + test1 + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + $(SolutionDir)..\build\output\test\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32_LEAN_AND_MEAN;_WINDOWS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + $(SolutionDir)\..\src + 4996 + + + Console + true + true + true + $(SolutionDir)..\build\output\;%(AdditionalLibraryDirectories) + ws2_32.lib;paho-mqtt3as.lib;%(AdditionalDependencies) + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sources/paho/Windows Build/test5/test5.vcxproj.filters b/Sources/paho/Windows Build/test5/test5.vcxproj.filters new file mode 100644 index 0000000..a9e956e --- /dev/null +++ b/Sources/paho/Windows Build/test5/test5.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/Sources/paho/about.html b/Sources/paho/about.html new file mode 100644 index 0000000..6555a44 --- /dev/null +++ b/Sources/paho/about.html @@ -0,0 +1,28 @@ + + + +About + + +

About This Content

+ +

December 9, 2013

+

License

+ +

The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise +indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 1.0 ("EPL") and Eclipse Distribution License Version 1.0 ("EDL"). +A copy of the EPL is available at +http://www.eclipse.org/legal/epl-v10.html +and a copy of the EDL is available at +http://www.eclipse.org/org/documents/edl-v10.php. +For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the Eclipse Foundation, the Content is +being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was +provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at http://www.eclipse.org.

+ + diff --git a/Sources/paho/build.xml b/Sources/paho/build.xml new file mode 100644 index 0000000..27d89a0 --- /dev/null +++ b/Sources/paho/build.xml @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/paho/build/.jazzignore b/Sources/paho/build/.jazzignore new file mode 100644 index 0000000..7bf6d41 --- /dev/null +++ b/Sources/paho/build/.jazzignore @@ -0,0 +1,21 @@ +### Jazz Ignore 0 +# The property core.ignore specifies a list of file patterns that will be +# ignored in this directory. +# +# The value of core.ignore.recursive will be ignored. +# +# Ignored files and folders will not be committed, but may be modified during +# accept or update. +# Ignore properties should contain a space separated list of filename patterns. +# Each pattern is case sensitive and surrounded by braces ('{' and '}'). +# "*" matches zero or more characters, and "?" matches single characters. +# +# e.g: {*.sh} {\.*} ignores shell scripts and hidden files + +# NOTE: modifying ignore files will not change the ignore status of derived +# resources. + +core.ignore.recursive= + +core.ignore= \ + {output} \ No newline at end of file diff --git a/Sources/paho/build/run_test2am b/Sources/paho/build/run_test2am new file mode 100644 index 0000000..5cafeae --- /dev/null +++ b/Sources/paho/build/run_test2am @@ -0,0 +1 @@ +LD_LIBRARY_PATH=. ./test3 --test_no 3 --hostname localhost --server_key /home/icraggs/nobackup/mosquitto-1.2/pkeys/test-root-ca.crt --client_key /home/icraggs/nobackup/mosquitto-1.2/pkeys/client.pem diff --git a/Sources/paho/build/run_test2as b/Sources/paho/build/run_test2as new file mode 100644 index 0000000..6e1ab25 --- /dev/null +++ b/Sources/paho/build/run_test2as @@ -0,0 +1 @@ +LD_LIBRARY_PATH=. ./test3 --test_no 2 --connection ssl://localhost:8883 --server_key /home/icraggs/nobackup/mosquitto-1.2/pkeys/server.pem --client_key /home/icraggs/nobackup/mosquitto-1.2/pkeys/client.pem diff --git a/Sources/paho/build/run_test3s b/Sources/paho/build/run_test3s new file mode 100644 index 0000000..ef4db53 --- /dev/null +++ b/Sources/paho/build/run_test3s @@ -0,0 +1 @@ +LD_LIBRARY_PATH=. ./test3 --test_no 7 --connection ssl://localhost:8885 --server_key /home/icraggs/nobackup/mosquitto-1.2/pkeys/server.pem diff --git a/Sources/paho/doc/DoxyfileV3AsyncAPI b/Sources/paho/doc/DoxyfileV3AsyncAPI new file mode 100644 index 0000000..91aea10 --- /dev/null +++ b/Sources/paho/doc/DoxyfileV3AsyncAPI @@ -0,0 +1,1803 @@ +# Doxyfile 1.8.1.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "Paho Asynchronous MQTT C Client Library" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = "../doc/pahologo.png" + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = "../build/output/doc/MQTTAsync/" + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = /Users/dimitri/doxygen/mail/1.5.7/doxywizard/ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = MQTTAsync_main + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = MQTTAsync.h \ + MQTTClientPersistence.h + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NONE + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = __attribute__(x)= + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/Sources/paho/doc/DoxyfileV3ClientAPI b/Sources/paho/doc/DoxyfileV3ClientAPI new file mode 100644 index 0000000..7026856 --- /dev/null +++ b/Sources/paho/doc/DoxyfileV3ClientAPI @@ -0,0 +1,1803 @@ +# Doxyfile 1.8.1.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "Paho MQTT C Client Library" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = "../doc/pahologo.png" + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = "../build/output/doc/MQTTClient/" + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = /Users/dimitri/doxygen/mail/1.5.7/doxywizard/ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = MQTTClient_main + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = MQTTClient.h \ + MQTTClientPersistence.h + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NONE + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = __attribute__(x)= + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/Sources/paho/doc/DoxyfileV3ClientInternal b/Sources/paho/doc/DoxyfileV3ClientInternal new file mode 100644 index 0000000..22a0fc5 --- /dev/null +++ b/Sources/paho/doc/DoxyfileV3ClientInternal @@ -0,0 +1,1850 @@ +# Doxyfile 1.8.1.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "MQTT C Client Libraries Internals" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = "../doc/pahologo.png" + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = "../build/output/doc/MQTTClient_internal/" + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = MQTTClient_internal + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = "." + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.vhd \ + *.vhdl \ + *.C \ + *.CC \ + *.C++ \ + *.II \ + *.I++ \ + *.H \ + *.HH \ + *.H++ \ + *.CS \ + *.PHP \ + *.PHP3 \ + *.M \ + *.MM \ + *.PY \ + *.F90 \ + *.F \ + *.VHD \ + *.VHDL \ + *.c + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NONE + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = NO + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 1000 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/Sources/paho/doc/pahologo.png b/Sources/paho/doc/pahologo.png new file mode 100644 index 0000000..27f197d Binary files /dev/null and b/Sources/paho/doc/pahologo.png differ diff --git a/Sources/paho/edl-v10 b/Sources/paho/edl-v10 new file mode 100644 index 0000000..cf989f1 --- /dev/null +++ b/Sources/paho/edl-v10 @@ -0,0 +1,15 @@ + +Eclipse Distribution License - v 1.0 + +Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/Sources/paho/epl-v10 b/Sources/paho/epl-v10 new file mode 100644 index 0000000..79e486c --- /dev/null +++ b/Sources/paho/epl-v10 @@ -0,0 +1,70 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and +b) in the case of each subsequent Contributor: +i) changes to the Program, and +ii) additions to the Program; +where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. +b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. +c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. +d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and +b) its license agreement: +i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; +ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; +iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and +iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and +b) a copy of this Agreement must be included with each copy of the Program. +Contributors may not remove or alter any copyright notices contained within the Program. + +Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. diff --git a/Sources/paho/include/Clients.h b/Sources/paho/include/Clients.h new file mode 100644 index 0000000..1b887b0 --- /dev/null +++ b/Sources/paho/include/Clients.h @@ -0,0 +1,205 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - add SSL support + * Ian Craggs - fix for bug 413429 - connectionLost not called + *******************************************************************************/ + +#if !defined(CLIENTS_H) +#define CLIENTS_H + +#include +#if defined(OPENSSL) +#if defined(WIN32) || defined(WIN64) +#include "winsock2.h" +#endif +#include +#endif +#include "MQTTClient.h" +#include "LinkedList.h" +#include "MQTTClientPersistence.h" +/*BE +include "LinkedList" +BE*/ + +/*BE +def PUBLICATIONS +{ + n32 ptr STRING open "topic" + n32 ptr DATA "payload" + n32 dec "payloadlen" + n32 dec "refcount" +} +BE*/ +/** + * Stored publication data to minimize copying + */ +typedef struct +{ + char *topic; + int topiclen; + char* payload; + int payloadlen; + int refcount; +} Publications; + +/*BE +// This should get moved to MQTTProtocol, but the includes don't quite work yet +map MESSAGE_TYPES +{ + "PUBREC" 5 + "PUBREL" . + "PUBCOMP" . +} + + +def MESSAGES +{ + n32 dec "qos" + n32 map bool "retain" + n32 dec "msgid" + n32 ptr PUBLICATIONS "publish" + n32 time "lastTouch" + n8 map MESSAGE_TYPES "nextMessageType" + n32 dec "len" +} +defList(MESSAGES) +BE*/ +/** + * Client publication message data + */ +typedef struct +{ + int qos; + int retain; + int msgid; + Publications *publish; + time_t lastTouch; /**> used for retry and expiry */ + char nextMessageType; /**> PUBREC, PUBREL, PUBCOMP */ + int len; /**> length of the whole structure+data */ +} Messages; + + +/*BE +def WILLMESSAGES +{ + n32 ptr STRING open "topic" + n32 ptr DATA open "msg" + n32 dec "retained" + n32 dec "qos" +} +BE*/ + +/** + * Client will message data + */ +typedef struct +{ + char *topic; + char *msg; + int retained; + int qos; +} willMessages; + +/*BE +map CLIENT_BITS +{ + "cleansession" 1 : . + "connected" 2 : . + "good" 4 : . + "ping_outstanding" 8 : . +} +def CLIENTS +{ + n32 ptr STRING open "clientID" + n32 ptr STRING open "username" + n32 ptr STRING open "password" + n32 map CLIENT_BITS "bits" + at 4 n8 bits 7:6 dec "connect_state" + at 8 + n32 dec "socket" + n32 ptr "SSL" + n32 dec "msgID" + n32 dec "keepAliveInterval" + n32 dec "maxInflightMessages" + n32 ptr BRIDGECONNECTIONS "bridge_context" + n32 time "lastContact" + n32 ptr WILLMESSAGES "will" + n32 ptr MESSAGESList open "inboundMsgs" + n32 ptr MESSAGESList open "outboundMsgs" + n32 ptr MESSAGESList open "messageQueue" + n32 dec "discardedMsgs" +} + +defList(CLIENTS) + +BE*/ + +typedef struct +{ + int socket; + time_t lastSent; + time_t lastReceived; +#if defined(OPENSSL) + SSL* ssl; + SSL_CTX* ctx; +#endif +} networkHandles; + +/** + * Data related to one client + */ +typedef struct +{ + char* clientID; /**< the string id of the client */ + const char* username; /**< MQTT v3.1 user name */ + const char* password; /**< MQTT v3.1 password */ + unsigned int cleansession : 1; /**< MQTT clean session 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; + int connect_state : 4; + networkHandles net; + int msgID; + int keepAliveInterval; + int retryInterval; + int maxInflightMessages; + willMessages* will; + List* inboundMsgs; + List* outboundMsgs; /**< in flight */ + List* messageQueue; + 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; +#if defined(OPENSSL) + MQTTClient_SSLOptions *sslopts; + 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 diff --git a/Sources/paho/include/Heap.h b/Sources/paho/include/Heap.h new file mode 100644 index 0000000..a3d8453 --- /dev/null +++ b/Sources/paho/include/Heap.h @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - use tree data structure instead of list + *******************************************************************************/ + + +#if !defined(HEAP_H) +#define HEAP_H + +#if defined(HIGH_PERFORMANCE) +#define NO_HEAP_TRACKING 1 +#endif + +#include +#include +#include + +#if !defined(NO_HEAP_TRACKING) +/** + * redefines malloc to use "mymalloc" so that heap allocation can be tracked + * @param x the size of the item to be allocated + * @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 +{ + int current_size; /**< current size of the heap in bytes */ + int max_size; /**< max size the heap has reached in bytes */ +} heap_info; + + +void* mymalloc(char*, int, size_t size); +void* myrealloc(char*, int, void* p, size_t size); +void myfree(char*, int, void* p); + +void Heap_scan(FILE* file); +int Heap_initialize(void); +void Heap_terminate(void); +heap_info* Heap_get_info(void); +int HeapDump(FILE* file); +int HeapDumpString(FILE* file, char* str); +void* Heap_findItem(void* p); +void Heap_unlink(char* file, int line, void* p); + +/** + * Allow us to pass our own memory allocation functions + * so that Paho native memory usage is tracked with the + * other allocations the agent makes. + */ +#if defined(WIN32) || defined(WIN64) + #define DLLExport __declspec(dllexport) +#else + #define DLLExport __attribute__ ((visibility ("default"))) +#endif + +typedef unsigned int uint32; + +typedef unsigned char* (*alloc_func)(uint32); +typedef void (*dealloc_func)(unsigned char**); +DLLExport void Heap_set_allocator(alloc_func allocator, dealloc_func deallocator); + +#endif diff --git a/Sources/paho/include/LinkedList.h b/Sources/paho/include/LinkedList.h new file mode 100644 index 0000000..eeae408 --- /dev/null +++ b/Sources/paho/include/LinkedList.h @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - updates for the async client + *******************************************************************************/ + +#if !defined(LINKEDLIST_H) +#define LINKEDLIST_H + +/*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 */ + struct ListElementStruct *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; /**< heap storage used */ +} List; + +void ListZero(List*); +List* ListInitialize(void); + +void ListAppend(List* aList, void* content, int size); +void ListAppendNoMalloc(List* aList, void* content, ListElement* newel, int size); +void ListInsert(List* aList, void* content, int size, ListElement* index); + +int ListRemove(List* aList, void* content); +int ListRemoveItem(List* aList, void* content, int(*callback)(void*, void*)); +void* ListDetachHead(List* aList); +int ListRemoveHead(List* aList); +void* ListPopTail(List* aList); + +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 diff --git a/Sources/paho/include/Log.h b/Sources/paho/include/Log.h new file mode 100644 index 0000000..48660c3 --- /dev/null +++ b/Sources/paho/include/Log.h @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - 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 { + TRACE_MAXIMUM = 1, + TRACE_MEDIUM, + TRACE_MINIMUM, + TRACE_PROTOCOL, + LOG_ERROR, + LOG_SEVERE, + LOG_FATAL, +} Log_levels; + + +/*BE +def trace_settings_type +{ + n32 map LOG_LEVELS "trace_level" + n32 dec "max_trace_entries" + n32 dec "trace_output_level" +} +BE*/ +typedef struct +{ + int trace_level; /**< trace level */ + int max_trace_entries; /**< max no of entries in the trace buffer */ + int 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 Log(int, int, char *, ...); +void Log_stackTrace(int, int, int, int, const char*, int, int*); + +typedef void Log_traceCallback(enum LOG_LEVELS level, char* message); +void Log_setTraceCallback(Log_traceCallback* callback); +void Log_setTraceLevel(enum LOG_LEVELS level); + +#endif diff --git a/Sources/paho/include/MQTTAsync.h b/Sources/paho/include/MQTTAsync.h new file mode 100644 index 0000000..c5d4d8f --- /dev/null +++ b/Sources/paho/include/MQTTAsync.h @@ -0,0 +1,1521 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation + * Ian Craggs, Allan Stockdill-Mander - SSL connections + * Ian Craggs - multiple server connection support + * Ian Craggs - MQTT 3.1.1 support + *******************************************************************************/ + +/********************************************************************/ + +/** + * @cond MQTTAsync_main + * @mainpage Asynchronous MQTT client library for C + * + * © Copyright IBM Corp. 2009, 2014 + * + * @brief An Asynchronous MQTT client library for C. + * + * An MQTT client application connects to MQTT-capable servers. + * A typical client is responsible for collecting information from a telemetry + * device and publishing the information to the server. It can also subscribe + * to topics, receive messages, and use this information to control the + * telemetry device. + * + * MQTT clients implement the published MQTT v3 protocol. You can write your own + * API to the MQTT protocol using the programming language and platform of your + * choice. This can be time-consuming and error-prone. + * + * To simplify writing MQTT client applications, this library encapsulates + * the MQTT v3 protocol for you. Using this library enables a fully functional + * MQTT client application to be written in a few lines of code. + * The information presented here documents the API provided + * by the Asynchronous MQTT Client library for C. + * + * Using the client
+ * Applications that use the client library typically use a similar structure: + *
    + *
  • Create a client object
  • + *
  • Set the options to connect to an MQTT server
  • + *
  • Set up callback functions
  • + *
  • Connect the client to an MQTT server
  • + *
  • Subscribe to any topics the client needs to receive
  • + *
  • Repeat until finished:
  • + *
      + *
    • Publish any messages the client needs to
    • + *
    • Handle any incoming messages
    • + *
    + *
  • Disconnect the client
  • + *
  • Free any memory being used by the client
  • + *
+ * Some simple examples are shown here: + *
    + *
  • @ref publish
  • + *
  • @ref subscribe
  • + *
+ * Additional information about important concepts is provided here: + *
    + *
  • @ref async
  • + *
  • @ref wildcard
  • + *
  • @ref qos
  • + *
  • @ref tracing
  • + *
+ * @endcond + */ + + +/// @cond EXCLUDE +#if defined(__cplusplus) + extern "C" { +#endif + +#if !defined(MQTTASYNC_H) +#define MQTTASYNC_H + +#if defined(WIN32) || defined(WIN64) + #define DLLImport __declspec(dllimport) + #define DLLExport __declspec(dllexport) +#else + #define DLLImport extern + #define DLLExport __attribute__ ((visibility ("default"))) +#endif + +#include +/// @endcond + +#if !defined(NO_PERSISTENCE) +#include "MQTTClientPersistence.h" +#endif + +/** + * Return code: No error. Indicates successful completion of an MQTT client + * operation. + */ +#define MQTTASYNC_SUCCESS 0 +/** + * Return code: A generic error code indicating the failure of an MQTT client + * operation. + */ +#define MQTTASYNC_FAILURE -1 + +/* error code -2 is MQTTAsync_PERSISTENCE_ERROR */ + +#define MQTTASYNC_PERSISTENCE_ERROR -2 + +/** + * Return code: The client is disconnected. + */ +#define MQTTASYNC_DISCONNECTED -3 +/** + * Return code: The maximum number of messages allowed to be simultaneously + * in-flight has been reached. + */ +#define MQTTASYNC_MAX_MESSAGES_INFLIGHT -4 +/** + * Return code: An invalid UTF-8 string has been detected. + */ +#define MQTTASYNC_BAD_UTF8_STRING -5 +/** + * Return code: A NULL parameter has been supplied when this is invalid. + */ +#define MQTTASYNC_NULL_PARAMETER -6 +/** + * Return code: The topic has been truncated (the topic string includes + * embedded NULL characters). String functions will not access the full topic. + * Use the topic length value to access the full topic. + */ +#define MQTTASYNC_TOPICNAME_TRUNCATED -7 +/** + * Return code: A structure parameter does not have the correct eyecatcher + * and version number. + */ +#define MQTTASYNC_BAD_STRUCTURE -8 +/** + * Return code: A qos parameter is not 0, 1 or 2 + */ +#define MQTTASYNC_BAD_QOS -9 +/** + * Return code: All 65535 MQTT msgids are being used + */ +#define MQTTASYNC_NO_MORE_MSGIDS -10 + +/** + * Default MQTT version to connect with. Use 3.1.1 then fall back to 3.1 + */ +#define MQTTVERSION_DEFAULT 0 +/** + * MQTT version to connect with: 3.1 + */ +#define MQTTVERSION_3_1 3 +/** + * MQTT version to connect with: 3.1.1 + */ +#define MQTTVERSION_3_1_1 4 +/** + * Bad return code from subscribe, as defined in the 3.1.1 specification + */ +#define MQTT_BAD_SUBSCRIBE 0x80 + +/** + * A handle representing an MQTT client. A valid client handle is available + * following a successful call to MQTTAsync_create(). + */ +typedef void* MQTTAsync; +/** + * A value representing an MQTT message. A token is returned to the + * client application when a message is published. The token can then be used to + * check that the message was successfully delivered to its destination (see + * MQTTAsync_publish(), + * MQTTAsync_publishMessage(), + * MQTTAsync_deliveryComplete(), and + * MQTTAsync_getPendingTokens()). + */ +typedef int MQTTAsync_token; + +/** + * A structure representing the payload and attributes of an MQTT message. The + * message topic is not part of this structure (see MQTTAsync_publishMessage(), + * MQTTAsync_publish(), MQTTAsync_receive(), MQTTAsync_freeMessage() + * and MQTTAsync_messageArrived()). + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTM. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** The length of the MQTT message payload in bytes. */ + int payloadlen; + /** A pointer to the payload of the MQTT message. */ + void* payload; + /** + * The quality of service (QoS) assigned to the message. + * There are three levels of QoS: + *
+ *
QoS0
+ *
Fire and forget - the message may not be delivered
+ *
QoS1
+ *
At least once - the message will be delivered, but may be + * delivered more than once in some circumstances.
+ *
QoS2
+ *
Once and one only - the message will be delivered exactly once.
+ *
+ */ + int qos; + /** + * The retained flag serves two purposes depending on whether the message + * it is associated with is being published or received. + * + * retained = true
+ * For messages being published, a true setting indicates that the MQTT + * server should retain a copy of the message. The message will then be + * transmitted to new subscribers to a topic that matches the message topic. + * For subscribers registering a new subscription, the flag being true + * indicates that the received message is not a new one, but one that has + * been retained by the MQTT server. + * + * retained = false
+ * For publishers, this ndicates that this message should not be retained + * by the MQTT server. For subscribers, a false setting indicates this is + * a normal message, received as a result of it being published to the + * server. + */ + int retained; + /** + * The dup flag indicates whether or not this message is a duplicate. + * It is only meaningful when receiving QoS1 messages. When true, the + * client application should take appropriate action to deal with the + * duplicate message. + */ + int dup; + /** The message identifier is normally reserved for internal use by the + * MQTT client and server. + */ + int msgid; +} MQTTAsync_message; + +#define MQTTAsync_message_initializer { {'M', 'Q', 'T', 'M'}, 0, 0, NULL, 0, 0, 0, 0 } + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * receipt of messages. The function is registered with the client library by + * passing it as an argument to MQTTAsync_setCallbacks(). It is + * called by the client library when a new message that matches a client + * subscription has been received from the server. This function is executed on + * a separate thread to the one on which the client application is running. + * @param context A pointer to the context value originally passed to + * MQTTAsync_setCallbacks(), which contains any application-specific context. + * @param topicName The topic associated with the received message. + * @param topicLen The length of the topic if there are one + * more NULL characters embedded in topicName, otherwise topicLen + * is 0. If topicLen is 0, the value returned by strlen(topicName) + * can be trusted. If topicLen is greater than 0, the full topic name + * can be retrieved by accessing topicName as a byte array of length + * topicLen. + * @param message The MQTTAsync_message structure for the received message. + * This structure contains the message payload and attributes. + * @return This function must return a boolean value indicating whether or not + * the message has been safely received by the client application. Returning + * true indicates that the message has been successfully handled. + * Returning false indicates that there was a problem. In this + * case, the client library will reinvoke MQTTAsync_messageArrived() to + * attempt to deliver the message to the application again. + */ +typedef int MQTTAsync_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message); + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * notification of delivery of messages to the server. The function is + * registered with the client library by passing it as an argument to MQTTAsync_setCallbacks(). + * It is called by the client library after the client application has + * published a message to the server. It indicates that the necessary + * handshaking and acknowledgements for the requested quality of service (see + * MQTTAsync_message.qos) have been completed. This function is executed on a + * separate thread to the one on which the client application is running. + * @param context A pointer to the context value originally passed to + * MQTTAsync_setCallbacks(), which contains any application-specific context. + * @param token The ::MQTTAsync_token associated with + * the published message. Applications can check that all messages have been + * correctly published by matching the tokens returned from calls to + * MQTTAsync_send() and MQTTAsync_sendMessage() with the tokens passed + * to this callback. + */ +typedef void MQTTAsync_deliveryComplete(void* context, MQTTAsync_token token); + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * notification of the loss of connection to the server. The function is + * registered with the client library by passing it as an argument to + * MQTTAsync_setCallbacks(). It is called by the client library if the client + * loses its connection to the server. The client application must take + * appropriate action, such as trying to reconnect or reporting the problem. + * This function is executed on a separate thread to the one on which the + * client application is running. + * @param context A pointer to the context value originally passed to + * MQTTAsync_setCallbacks(), which contains any application-specific context. + * @param cause The reason for the disconnection. + * Currently, cause is always set to NULL. + */ +typedef void MQTTAsync_connectionLost(void* context, char* cause); + +/** The data returned on completion of an unsuccessful API call in the response callback onFailure. */ +typedef struct +{ + /** A token identifying the failed request. */ + MQTTAsync_token token; + /** A numeric code identifying the error. */ + int code; + /** Optional text explaining the error. Can be NULL. */ + char* message; +} MQTTAsync_failureData; + +/** The data returned on completion of a successful API call in the response callback onSuccess. */ +typedef struct +{ + /** A token identifying the successful request. Can be used to refer to the request later. */ + MQTTAsync_token token; + /** A union of the different values that can be returned for subscribe, unsubscribe and publish. */ + union + { + /** For subscribe, the granted QoS of the subscription returned by the server. */ + int qos; + /** For subscribeMany, the list of granted QoSs of the subscriptions returned by the server. */ + int* qosList; + /** For publish, the message being sent to the server. */ + struct + { + MQTTAsync_message message; + char* destinationName; + } pub; + /* For connect, the server connected to, MQTT version used, and sessionPresent flag */ + struct + { + char* serverURI; + int MQTTVersion; + int sessionPresent; + } connect; + } alt; +} MQTTAsync_successData; + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * notification of the successful completion of an API call. The function is + * registered with the client library by passing it as an argument in + * ::MQTTAsync_responseOptions. + * @param context A pointer to the context value originally passed to + * ::MQTTAsync_responseOptions, which contains any application-specific context. + * @param response Any success data associated with the API completion. + */ +typedef void MQTTAsync_onSuccess(void* context, MQTTAsync_successData* response); + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * notification of the unsuccessful completion of an API call. The function is + * registered with the client library by passing it as an argument in + * ::MQTTAsync_responseOptions. + * @param context A pointer to the context value originally passed to + * ::MQTTAsync_responseOptions, which contains any application-specific context. + * @param response Any failure data associated with the API completion. + */ +typedef void MQTTAsync_onFailure(void* context, MQTTAsync_failureData* response); + +typedef struct +{ + /** The eyecatcher for this structure. Must be MQTR */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** + * A pointer to a callback function to be called if the API call successfully + * completes. Can be set to NULL, in which case no indication of successful + * completion will be received. + */ + MQTTAsync_onSuccess* onSuccess; + /** + * A pointer to a callback function to be called if the API call fails. + * Can be set to NULL, in which case no indication of unsuccessful + * completion will be received. + */ + MQTTAsync_onFailure* onFailure; + /** + * A pointer to any application-specific context. The + * the context pointer is passed to success or failure callback functions to + * provide access to the context information in the callback. + */ + void* context; + MQTTAsync_token token; /* output */ +} MQTTAsync_responseOptions; + +#define MQTTAsync_responseOptions_initializer { {'M', 'Q', 'T', 'R'}, 0, NULL, NULL, 0, 0 } + + +/** + * This function sets the global callback functions for a specific client. + * If your client application doesn't use a particular callback, set the + * relevant parameter to NULL. Any necessary message acknowledgements and + * status communications are handled in the background without any intervention + * from the client application. If you do not set a messageArrived callback + * function, you will not be notified of the receipt of any messages as a + * result of a subscription. + * + * Note: The MQTT client must be disconnected when this function is + * called. + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param context A pointer to any application-specific context. The + * the context pointer is passed to each of the callback functions to + * provide access to the context information in the callback. + * @param cl A pointer to an MQTTAsync_connectionLost() callback + * function. You can set this to NULL if your application doesn't handle + * disconnections. + * @param ma A pointer to an MQTTAsync_messageArrived() callback + * function. You can set this to NULL if your application doesn't handle + * receipt of messages. + * @param dc A pointer to an MQTTAsync_deliveryComplete() callback + * function. You can set this to NULL if you do not want to check + * for successful delivery. + * @return ::MQTTASYNC_SUCCESS if the callbacks were correctly set, + * ::MQTTASYNC_FAILURE if an error occurred. + */ +DLLExport int MQTTAsync_setCallbacks(MQTTAsync handle, void* context, MQTTAsync_connectionLost* cl, + MQTTAsync_messageArrived* ma, MQTTAsync_deliveryComplete* dc); + + +/** + * This function creates an MQTT client ready for connection to the + * specified server and using the specified persistent storage (see + * MQTTAsync_persistence). See also MQTTAsync_destroy(). + * @param handle A pointer to an ::MQTTAsync handle. The handle is + * populated with a valid client reference following a successful return from + * this function. + * @param serverURI A null-terminated string specifying the server to + * which the client will connect. It takes the form protocol://host:port. + * protocol must be tcp or ssl. For host, you can + * specify either an IP address or a domain name. For instance, to connect to + * a server running on the local machines with the default MQTT port, specify + * tcp://localhost:1883. + * @param clientId The client identifier passed to the server when the + * client connects to it. It is a null-terminated UTF-8 encoded string. + * ClientIDs must be no longer than 23 characters according to the MQTT + * specification. + * @param persistence_type The type of persistence to be used by the client: + *
+ * ::MQTTCLIENT_PERSISTENCE_NONE: Use in-memory persistence. If the device or + * system on which the client is running fails or is switched off, the current + * state of any in-flight messages is lost and some messages may not be + * delivered even at QoS1 and QoS2. + *
+ * ::MQTTCLIENT_PERSISTENCE_DEFAULT: Use the default (file system-based) + * persistence mechanism. Status about in-flight messages is held in persistent + * storage and provides some protection against message loss in the case of + * unexpected failure. + *
+ * ::MQTTCLIENT_PERSISTENCE_USER: Use an application-specific persistence + * implementation. Using this type of persistence gives control of the + * persistence mechanism to the application. The application has to implement + * the MQTTClient_persistence interface. + * @param persistence_context If the application uses + * ::MQTTCLIENT_PERSISTENCE_NONE persistence, this argument is unused and should + * be set to NULL. For ::MQTTCLIENT_PERSISTENCE_DEFAULT persistence, it + * should be set to the location of the persistence directory (if set + * to NULL, the persistence directory used is the working directory). + * Applications that use ::MQTTCLIENT_PERSISTENCE_USER persistence set this + * argument to point to a valid MQTTClient_persistence structure. + * @return ::MQTTASYNC_SUCCESS if the client is successfully created, otherwise + * an error code is returned. + */ +DLLExport int MQTTAsync_create(MQTTAsync* handle, const char* serverURI, const char* clientId, + int persistence_type, void* persistence_context); + +/** + * MQTTAsync_willOptions defines the MQTT "Last Will and Testament" (LWT) settings for + * the client. In the event that a client unexpectedly loses its connection to + * the server, the server publishes the LWT message to the LWT topic on + * behalf of the client. This allows other clients (subscribed to the LWT topic) + * to be made aware that the client has disconnected. To enable the LWT + * function for a specific client, a valid pointer to an MQTTAsync_willOptions + * structure is passed in the MQTTAsync_connectOptions structure used in the + * MQTTAsync_connect() call that connects the client to the server. The pointer + * to MQTTAsync_willOptions can be set to NULL if the LWT function is not + * required. + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTW. */ + const char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** The LWT topic to which the LWT message will be published. */ + const char* topicName; + /** The LWT payload. */ + const char* message; + /** + * The retained flag for the LWT message (see MQTTAsync_message.retained). + */ + int retained; + /** + * The quality of service setting for the LWT message (see + * MQTTAsync_message.qos and @ref qos). + */ + int qos; +} MQTTAsync_willOptions; + +#define MQTTAsync_willOptions_initializer { {'M', 'Q', 'T', 'W'}, 0, NULL, NULL, 0, 0 } + +/** +* MQTTAsync_sslProperties defines the settings to establish an SSL/TLS connection using the +* OpenSSL library. It covers the following scenarios: +* - Server authentication: The client needs the digital certificate of the server. It is included +* in a store containting trusted material (also known as "trust store"). +* - Mutual authentication: Both client and server are authenticated during the SSL handshake. In +* addition to the digital certificate of the server in a trust store, the client will need its own +* digital certificate and the private key used to sign its digital certificate stored in a "key store". +* - Anonymous connection: Both client and server do not get authenticated and no credentials are needed +* to establish an SSL connection. Note that this scenario is not fully secure since it is subject to +* man-in-the-middle attacks. +*/ +typedef struct +{ + /** The eyecatcher for this structure. Must be MQTS */ + const char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + + /** The file in PEM format containing the public digital certificates trusted by the client. */ + const char* trustStore; + + /** The file in PEM format containing the public certificate chain of the client. It may also include + * the client's private key. + */ + const char* keyStore; + + /** If not included in the sslKeyStore, this setting points to the file in PEM format containing + * the client's private key. + */ + const char* privateKey; + /** The password to load the client's privateKey if encrypted. */ + const char* privateKeyPassword; + + /** + * The list of cipher suites that the client will present to the server during the SSL handshake. For a + * full explanation of the cipher list format, please see the OpenSSL on-line documentation: + * http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT + * If this setting is ommitted, its default value will be "ALL", that is, all the cipher suites -excluding + * those offering no encryption- will be considered. + * This setting can be used to set an SSL anonymous connection ("aNULL" string value, for instance). + */ + const char* enabledCipherSuites; + + /** True/False option to enable verification of the server certificate **/ + int enableServerCertAuth; + +} MQTTAsync_SSLOptions; + +#define MQTTAsync_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 0, NULL, NULL, NULL, NULL, NULL, 1 } + +/** + * MQTTAsync_connectOptions defines several settings that control the way the + * client connects to an MQTT server. Default values are set in + * MQTTAsync_connectOptions_initializer. + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTC. */ + const char struct_id[4]; + /** The version number of this structure. Must be 0, 1 or 2. + * 0 signifies no SSL options and no serverURIs + * 1 signifies no serverURIs + * 2 signifies no MQTTVersion + */ + int struct_version; + /** The "keep alive" interval, measured in seconds, defines the maximum time + * that should pass without communication between the client and the server + * The client will ensure that at least one message travels across the + * network within each keep alive period. In the absence of a data-related + * message during the time period, the client sends a very small MQTT + * "ping" message, which the server will acknowledge. The keep alive + * interval enables the client to detect when the server is no longer + * available without having to wait for the long TCP/IP timeout. + * Set to 0 if you do not want any keep alive processing. + */ + int keepAliveInterval; + /** + * This is a boolean value. The cleansession setting controls the behaviour + * of both the client and the server at connection and disconnection time. + * The client and server both maintain session state information. This + * information is used to ensure "at least once" and "exactly once" + * delivery, and "exactly once" receipt of messages. Session state also + * includes subscriptions created by an MQTT client. You can choose to + * maintain or discard state information between sessions. + * + * When cleansession is true, the state information is discarded at + * connect and disconnect. Setting cleansession to false keeps the state + * information. When you connect an MQTT client application with + * MQTTAsync_connect(), the client identifies the connection using the + * client identifier and the address of the server. The server checks + * whether session information for this client + * has been saved from a previous connection to the server. If a previous + * session still exists, and cleansession=true, then the previous session + * information at the client and server is cleared. If cleansession=false, + * the previous session is resumed. If no previous session exists, a new + * session is started. + */ + int cleansession; + /** + * This controls how many messages can be in-flight simultaneously. + */ + int maxInflight; + /** + * This is a pointer to an MQTTAsync_willOptions structure. If your + * application does not make use of the Last Will and Testament feature, + * set this pointer to NULL. + */ + MQTTAsync_willOptions* will; + /** + * MQTT servers that support the MQTT v3.1 protocol provide authentication + * and authorisation by user name and password. This is the user name + * parameter. + */ + const char* username; + /** + * MQTT servers that support the MQTT v3.1 protocol provide authentication + * and authorisation by user name and password. This is the password + * parameter. + */ + const char* password; + /** + * The time interval in seconds to allow a connect to complete. + */ + int connectTimeout; + /** + * The time interval in seconds + */ + int retryInterval; + /** + * This is a pointer to an MQTTAsync_SSLOptions structure. If your + * application does not make use of SSL, set this pointer to NULL. + */ + MQTTAsync_SSLOptions* ssl; + /** + * A pointer to a callback function to be called if the connect successfully + * completes. Can be set to NULL, in which case no indication of successful + * completion will be received. + */ + MQTTAsync_onSuccess* onSuccess; + /** + * A pointer to a callback function to be called if the connect fails. + * Can be set to NULL, in which case no indication of unsuccessful + * completion will be received. + */ + MQTTAsync_onFailure* onFailure; + /** + * A pointer to any application-specific context. The + * the context pointer is passed to success or failure callback functions to + * provide access to the context information in the callback. + */ + void* context; + /** + * The number of entries in the serverURIs array. + */ + int serverURIcount; + /** + * An array of null-terminated strings specifying the servers to + * which the client will connect. Each string takes the form protocol://host:port. + * protocol must be tcp or ssl. For host, you can + * specify either an IP address or a domain name. For instance, to connect to + * a server running on the local machines with the default MQTT port, specify + * tcp://localhost:1883. + */ + char* const* serverURIs; + /** + * Sets the version of MQTT to be used on the connect. + * MQTTVERSION_DEFAULT (0) = default: start with 3.1.1, and if that fails, fall back to 3.1 + * MQTTVERSION_3_1 (3) = only try version 3.1 + * MQTTVERSION_3_1_1 (4) = only try version 3.1.1 + */ + int MQTTVersion; +} MQTTAsync_connectOptions; + + +#define MQTTAsync_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 3, 60, 1, 10, NULL, NULL, NULL, 30, 0, NULL, NULL, NULL, NULL, 0, NULL, 0} + +/** + * This function attempts to connect a previously-created client (see + * MQTTAsync_create()) to an MQTT server using the specified options. If you + * want to enable asynchronous message and status notifications, you must call + * MQTTAsync_setCallbacks() prior to MQTTAsync_connect(). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param options A pointer to a valid MQTTAsync_connectOptions + * structure. + * @return ::MQTTASYNC_SUCCESS if the client connect request was accepted. + * If the client was unable to connect to the server, an error code is + * returned via the onFailure callback, if set. + * Error codes greater than 0 are returned by the MQTT protocol:

+ * 1: Connection refused: Unacceptable protocol version
+ * 2: Connection refused: Identifier rejected
+ * 3: Connection refused: Server unavailable
+ * 4: Connection refused: Bad user name or password
+ * 5: Connection refused: Not authorized
+ * 6-255: Reserved for future use
+ */ +DLLExport int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options); + + +typedef struct +{ + /** The eyecatcher for this structure. Must be MQTD. */ + const char struct_id[4]; + /** The version number of this structure. Must be 0 or 1. 0 signifies no SSL options */ + int struct_version; + /** + * The client delays disconnection for up to this time (in + * milliseconds) in order to allow in-flight message transfers to complete. + */ + int timeout; + /** + * A pointer to a callback function to be called if the disconnect successfully + * completes. Can be set to NULL, in which case no indication of successful + * completion will be received. + */ + MQTTAsync_onSuccess* onSuccess; + /** + * A pointer to a callback function to be called if the disconnect fails. + * Can be set to NULL, in which case no indication of unsuccessful + * completion will be received. + */ + MQTTAsync_onFailure* onFailure; + /** + * A pointer to any application-specific context. The + * the context pointer is passed to success or failure callback functions to + * provide access to the context information in the callback. + */ + void* context; +} MQTTAsync_disconnectOptions; + +#define MQTTAsync_disconnectOptions_initializer { {'M', 'Q', 'T', 'D'}, 0, 0, NULL, NULL, NULL } + + +/** + * This function attempts to disconnect the client from the MQTT + * server. In order to allow the client time to complete handling of messages + * that are in-flight when this function is called, a timeout period is + * specified. When the timeout period has expired, the client disconnects even + * if there are still outstanding message acknowledgements. + * The next time the client connects to the same server, any QoS 1 or 2 + * messages which have not completed will be retried depending on the + * cleansession settings for both the previous and the new connection (see + * MQTTAsync_connectOptions.cleansession and MQTTAsync_connect()). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param options The client delays disconnection for up to this time (in + * milliseconds) in order to allow in-flight message transfers to complete. + * @return ::MQTTASYNC_SUCCESS if the client successfully disconnects from + * the server. An error code is returned if the client was unable to disconnect + * from the server + */ +DLLExport int MQTTAsync_disconnect(MQTTAsync handle, const MQTTAsync_disconnectOptions* options); + + +/** + * This function allows the client application to test whether or not a + * client is currently connected to the MQTT server. + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @return Boolean true if the client is connected, otherwise false. + */ +DLLExport int MQTTAsync_isConnected(MQTTAsync handle); + + +/** + * This function attempts to subscribe a client to a single topic, which may + * contain wildcards (see @ref wildcard). This call also specifies the + * @ref qos requested for the subscription + * (see also MQTTAsync_subscribeMany()). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param topic The subscription topic, which may include wildcards. + * @param qos The requested quality of service for the subscription. + * @param response A pointer to a response options structure. Used to set callback functions. + * @return ::MQTTASYNC_SUCCESS if the subscription request is successful. + * An error code is returned if there was a problem registering the + * subscription. + */ +DLLExport int MQTTAsync_subscribe(MQTTAsync handle, const char* topic, int qos, MQTTAsync_responseOptions* response); + + +/** + * This function attempts to subscribe a client to a list of topics, which may + * contain wildcards (see @ref wildcard). This call also specifies the + * @ref qos requested for each topic (see also MQTTAsync_subscribe()). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param count The number of topics for which the client is requesting + * subscriptions. + * @param topic An array (of length count) of pointers to + * topics, each of which may include wildcards. + * @param qos An array (of length count) of @ref qos + * values. qos[n] is the requested QoS for topic[n]. + * @param response A pointer to a response options structure. Used to set callback functions. + * @return ::MQTTASYNC_SUCCESS if the subscription request is successful. + * An error code is returned if there was a problem registering the + * subscriptions. + */ +DLLExport int MQTTAsync_subscribeMany(MQTTAsync handle, int count, char* const* topic, int* qos, MQTTAsync_responseOptions* response); + +/** + * This function attempts to remove an existing subscription made by the + * specified client. + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param topic The topic for the subscription to be removed, which may + * include wildcards (see @ref wildcard). + * @param response A pointer to a response options structure. Used to set callback functions. + * @return ::MQTTASYNC_SUCCESS if the subscription is removed. + * An error code is returned if there was a problem removing the + * subscription. + */ +DLLExport int MQTTAsync_unsubscribe(MQTTAsync handle, const char* topic, MQTTAsync_responseOptions* response); + +/** + * This function attempts to remove existing subscriptions to a list of topics + * made by the specified client. + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param count The number subscriptions to be removed. + * @param topic An array (of length count) of pointers to the topics of + * the subscriptions to be removed, each of which may include wildcards. + * @param response A pointer to a response options structure. Used to set callback functions. + * @return ::MQTTASYNC_SUCCESS if the subscriptions are removed. + * An error code is returned if there was a problem removing the subscriptions. + */ +DLLExport int MQTTAsync_unsubscribeMany(MQTTAsync handle, int count, char* const* topic, MQTTAsync_responseOptions* response); + + +/** + * This function attempts to publish a message to a given topic (see also + * ::MQTTAsync_sendMessage()). An ::MQTTAsync_token is issued when + * this function returns successfully. If the client application needs to + * test for successful delivery of messages, a callback should be set + * (see ::MQTTAsync_onSuccess() and ::MQTTAsync_deliveryComplete()). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param destinationName The topic associated with this message. + * @param payloadlen The length of the payload in bytes. + * @param payload A pointer to the byte array payload of the message. + * @param qos The @ref qos of the message. + * @param retained The retained flag for the message. + * @param response A pointer to an ::MQTTAsync_responseOptions structure. Used to set callback functions. + * This is optional and can be set to NULL. + * @return ::MQTTASYNC_SUCCESS if the message is accepted for publication. + * An error code is returned if there was a problem accepting the message. + */ +DLLExport int MQTTAsync_send(MQTTAsync handle, const char* destinationName, int payloadlen, void* payload, int qos, int retained, + MQTTAsync_responseOptions* response); + + +/** + * This function attempts to publish a message to a given topic (see also + * MQTTAsync_publish()). An ::MQTTAsync_token is issued when + * this function returns successfully. If the client application needs to + * test for successful delivery of messages, a callback should be set + * (see ::MQTTAsync_onSuccess() and ::MQTTAsync_deliveryComplete()). + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param destinationName The topic associated with this message. + * @param msg A pointer to a valid MQTTAsync_message structure containing + * the payload and attributes of the message to be published. + * @param response A pointer to an ::MQTTAsync_responseOptions structure. Used to set callback functions. + * @return ::MQTTASYNC_SUCCESS if the message is accepted for publication. + * An error code is returned if there was a problem accepting the message. + */ +DLLExport int MQTTAsync_sendMessage(MQTTAsync handle, const char* destinationName, const MQTTAsync_message* msg, MQTTAsync_responseOptions* response); + + +/** + * This function sets a pointer to an array of tokens for + * messages that are currently in-flight (pending completion). + * + * Important note: The memory used to hold the array of tokens is + * malloc()'d in this function. The client application is responsible for + * freeing this memory when it is no longer required. + * @param handle A valid client handle from a successful call to + * MQTTAsync_create(). + * @param tokens The address of a pointer to an ::MQTTAsync_token. + * When the function returns successfully, the pointer is set to point to an + * array of tokens representing messages pending completion. The last member of + * the array is set to -1 to indicate there are no more tokens. If no tokens + * are pending, the pointer is set to NULL. + * @return ::MQTTASYNC_SUCCESS if the function returns successfully. + * An error code is returned if there was a problem obtaining the list of + * pending tokens. + */ +DLLExport int MQTTAsync_getPendingTokens(MQTTAsync handle, MQTTAsync_token **tokens); + +#define MQTTASYNC_TRUE 1 +DLLExport int MQTTAsync_isComplete(MQTTAsync handle, MQTTAsync_token dt); + +DLLExport int MQTTAsync_waitForCompletion(MQTTAsync handle, MQTTAsync_token dt, unsigned long timeout); + + +/** + * This function frees memory allocated to an MQTT message, including the + * additional memory allocated to the message payload. The client application + * calls this function when the message has been fully processed. Important + * note: This function does not free the memory allocated to a message + * topic string. It is the responsibility of the client application to free + * this memory using the MQTTAsync_free() library function. + * @param msg The address of a pointer to the ::MQTTAsync_message structure + * to be freed. + */ +DLLExport void MQTTAsync_freeMessage(MQTTAsync_message** msg); + +/** + * This function frees memory allocated by the MQTT C client library, especially the + * topic name. This is needed on Windows when the client libary and application + * program have been compiled with different versions of the C compiler. It is + * thus good policy to always use this function when freeing any MQTT C client- + * allocated memory. + * @param ptr The pointer to the client library storage to be freed. + */ +DLLExport void MQTTAsync_free(void* ptr); + +/** + * This function frees the memory allocated to an MQTT client (see + * MQTTAsync_create()). It should be called when the client is no longer + * required. + * @param handle A pointer to the handle referring to the ::MQTTAsync + * structure to be freed. + */ +DLLExport void MQTTAsync_destroy(MQTTAsync* handle); + + + +enum MQTTASYNC_TRACE_LEVELS +{ + MQTTASYNC_TRACE_MAXIMUM = 1, + MQTTASYNC_TRACE_MEDIUM, + MQTTASYNC_TRACE_MINIMUM, + MQTTASYNC_TRACE_PROTOCOL, + MQTTASYNC_TRACE_ERROR, + MQTTASYNC_TRACE_SEVERE, + MQTTASYNC_TRACE_FATAL, +}; + + +/** + * This function sets the level of trace information which will be + * returned in the trace callback. + * @param level the trace level required + */ +DLLExport void MQTTAsync_setTraceLevel(enum MQTTASYNC_TRACE_LEVELS level); + + +/** + * This is a callback function prototype which must be implemented if you want + * to receive trace information. + * @param level the trace level of the message returned + * @param meesage the trace message. This is a pointer to a static buffer which + * will be overwritten on each call. You must copy the data if you want to keep + * it for later. + */ +typedef void MQTTAsync_traceCallback(enum MQTTASYNC_TRACE_LEVELS level, char* message); + +/** + * This function sets the trace callback if needed. If set to NULL, + * no trace information will be returned. The default trace level is + * MQTTASYNC_TRACE_MINIMUM. + * @param callback a pointer to the function which will handle the trace information + */ +DLLExport void MQTTAsync_setTraceCallback(MQTTAsync_traceCallback* callback); + + +typedef struct +{ + const char* name; + const char* value; +} MQTTAsync_nameValue; + +/** + * This function returns version information about the library. + * no trace information will be returned. The default trace level is + * MQTTASYNC_TRACE_MINIMUM + * @return an array of strings describing the library. The last entry is a NULL pointer. + */ +DLLExport MQTTAsync_nameValue* MQTTAsync_getVersionInfo(); + + +/** + * @cond MQTTAsync_main + * @page async Threading + * The client application runs on several threads. + * Processing of handshaking and maintaining + * the network connection is performed in the background. + * Notifications of status and message reception are provided to the client + * application using callbacks registered with the library by the call to + * MQTTAsync_setCallbacks() (see MQTTAsync_messageArrived(), + * MQTTAsync_connectionLost() and MQTTAsync_deliveryComplete()). + * + * @page wildcard Subscription wildcards + * Every MQTT message includes a topic that classifies it. MQTT servers use + * topics to determine which subscribers should receive messages published to + * the server. + * + * Consider the server receiving messages from several environmental sensors. + * Each sensor publishes its measurement data as a message with an associated + * topic. Subscribing applications need to know which sensor originally + * published each received message. A unique topic is thus used to identify + * each sensor and measurement type. Topics such as SENSOR1TEMP, + * SENSOR1HUMIDITY, SENSOR2TEMP and so on achieve this but are not very + * flexible. If additional sensors are added to the system at a later date, + * subscribing applications must be modified to receive them. + * + * To provide more flexibility, MQTT supports a hierarchical topic namespace. + * This allows application designers to organize topics to simplify their + * management. Levels in the hierarchy are delimited by the '/' character, + * such as SENSOR/1/HUMIDITY. Publishers and subscribers use these + * hierarchical topics as already described. + * + * For subscriptions, two wildcard characters are supported: + *
    + *
  • A '#' character represents a complete sub-tree of the hierarchy and + * thus must be the last character in a subscription topic string, such as + * SENSOR/#. This will match any topic starting with SENSOR/, such as + * SENSOR/1/TEMP and SENSOR/2/HUMIDITY.
  • + *
  • A '+' character represents a single level of the hierarchy and is + * used between delimiters. For example, SENSOR/+/TEMP will match + * SENSOR/1/TEMP and SENSOR/2/TEMP.
  • + *
+ * Publishers are not allowed to use the wildcard characters in their topic + * names. + * + * Deciding on your topic hierarchy is an important step in your system design. + * + * @page qos Quality of service + * The MQTT protocol provides three qualities of service for delivering + * messages between clients and servers: "at most once", "at least once" and + * "exactly once". + * + * Quality of service (QoS) is an attribute of an individual message being + * published. An application sets the QoS for a specific message by setting the + * MQTTAsync_message.qos field to the required value. + * + * A subscribing client can set the maximum quality of service a server uses + * to send messages that match the client subscriptions. The + * MQTTAsync_subscribe() and MQTTAsync_subscribeMany() functions set this + * maximum. The QoS of a message forwarded to a subscriber thus might be + * different to the QoS given to the message by the original publisher. + * The lower of the two values is used to forward a message. + * + * The three levels are: + * + * QoS0, At most once: The message is delivered at most once, or it + * may not be delivered at all. Its delivery across the network is not + * acknowledged. The message is not stored. The message could be lost if the + * client is disconnected, or if the server fails. QoS0 is the fastest mode of + * transfer. It is sometimes called "fire and forget". + * + * The MQTT protocol does not require servers to forward publications at QoS0 + * to a client. If the client is disconnected at the time the server receives + * the publication, the publication might be discarded, depending on the + * server implementation. + * + * QoS1, At least once: The message is always delivered at least once. + * It might be delivered multiple times if there is a failure before an + * acknowledgment is received by the sender. The message must be stored + * locally at the sender, until the sender receives confirmation that the + * message has been published by the receiver. The message is stored in case + * the message must be sent again. + * + * QoS2, Exactly once: The message is always delivered exactly once. + * The message must be stored locally at the sender, until the sender receives + * confirmation that the message has been published by the receiver. The + * message is stored in case the message must be sent again. QoS2 is the + * safest, but slowest mode of transfer. A more sophisticated handshaking + * and acknowledgement sequence is used than for QoS1 to ensure no duplication + * of messages occurs. + + + * @page publish Publication example +@code +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTAsync.h" + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientPub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +volatile MQTTAsync_token deliveredtoken; + +int finished = 0; + +void connlost(void *context, char *cause) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + int rc; + + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); + + printf("Reconnecting\n"); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + finished = 1; + } +} + + +void onDisconnect(void* context, MQTTAsync_successData* response) +{ + printf("Successful disconnection\n"); + finished = 1; +} + + +void onSend(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + printf("Message with token value %d delivery confirmed\n", response->token); + + opts.onSuccess = onDisconnect; + opts.context = client; + + if ((rc = MQTTAsync_disconnect(client, &opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start sendMessage, return code %d\n", rc); + exit(-1); + } +} + + +void onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Connect failed, rc %d\n", response ? response->code : 0); + finished = 1; +} + + +void onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + printf("Successful connection\n"); + + opts.onSuccess = onSend; + opts.context = client; + + pubmsg.payload = PAYLOAD; + pubmsg.payloadlen = strlen(PAYLOAD); + pubmsg.qos = QOS; + pubmsg.retained = 0; + deliveredtoken = 0; + + if ((rc = MQTTAsync_sendMessage(client, TOPIC, &pubmsg, &opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start sendMessage, return code %d\n", rc); + exit(-1); + } +} + + +int main(int argc, char* argv[]) +{ + MQTTAsync client; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_token token; + int rc; + + MQTTAsync_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + MQTTAsync_setCallbacks(client, NULL, connlost, NULL, NULL); + + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + exit(-1); + } + + printf("Waiting for publication of %s\n" + "on topic %s for client with ClientID: %s\n", + PAYLOAD, TOPIC, CLIENTID); + while (!finished) + #if defined(WIN32) || defined(WIN64) + Sleep(100); + #else + usleep(10000L); + #endif + + MQTTAsync_destroy(&client); + return rc; +} + + * @endcode + * @page subscribe Subscription example +@code +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTAsync.h" + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientSub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +volatile MQTTAsync_token deliveredtoken; + +int disc_finished = 0; +int subscribed = 0; +int finished = 0; + +void connlost(void *context, char *cause) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + int rc; + + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); + + printf("Reconnecting\n"); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + finished = 1; + } +} + + +int msgarrvd(void *context, char *topicName, int topicLen, MQTTAsync_message *message) +{ + int i; + char* payloadptr; + + printf("Message arrived\n"); + printf(" topic: %s\n", topicName); + printf(" message: "); + + payloadptr = message->payload; + for(i=0; ipayloadlen; i++) + { + putchar(*payloadptr++); + } + putchar('\n'); + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + return 1; +} + + +void onDisconnect(void* context, MQTTAsync_successData* response) +{ + printf("Successful disconnection\n"); + disc_finished = 1; +} + + +void onSubscribe(void* context, MQTTAsync_successData* response) +{ + printf("Subscribe succeeded\n"); + subscribed = 1; +} + +void onSubscribeFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Subscribe failed, rc %d\n", response ? response->code : 0); + finished = 1; +} + + +void onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Connect failed, rc %d\n", response ? response->code : 0); + finished = 1; +} + + +void onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + printf("Successful connection\n"); + + printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n" + "Press Q to quit\n\n", TOPIC, CLIENTID, QOS); + opts.onSuccess = onSubscribe; + opts.onFailure = onSubscribeFailure; + opts.context = client; + + deliveredtoken = 0; + + if ((rc = MQTTAsync_subscribe(client, TOPIC, QOS, &opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start subscribe, return code %d\n", rc); + exit(-1); + } +} + + +int main(int argc, char* argv[]) +{ + MQTTAsync client; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_disconnectOptions disc_opts = MQTTAsync_disconnectOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_token token; + int rc; + int ch; + + MQTTAsync_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + MQTTAsync_setCallbacks(client, NULL, connlost, msgarrvd, NULL); + + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + exit(-1); + } + + while (!subscribed) + #if defined(WIN32) || defined(WIN64) + Sleep(100); + #else + usleep(10000L); + #endif + + if (finished) + goto exit; + + do + { + ch = getchar(); + } while (ch!='Q' && ch != 'q'); + + disc_opts.onSuccess = onDisconnect; + if ((rc = MQTTAsync_disconnect(client, &disc_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start disconnect, return code %d\n", rc); + exit(-1); + } + while (!disc_finished) + #if defined(WIN32) || defined(WIN64) + Sleep(100); + #else + usleep(10000L); + #endif + +exit: + MQTTAsync_destroy(&client); + return rc; +} + + * @endcode +* @page tracing Tracing + * + * Runtime tracing can be controlled by environment variables or API calls. + * + * #### Environment variables + * + * Tracing is switched on by setting the MQTT_C_CLIENT_TRACE environment variable. + * A value of ON, or stdout, prints to stdout, any other value is interpreted as a file name to use. + * + * The amount of trace detail is controlled with the MQTT_C_CLIENT_TRACE_LEVEL environment + * variable - valid values are ERROR, PROTOCOL, MINIMUM, MEDIUM and MAXIMUM + * (from least to most verbose). + * + * The variable MQTT_C_CLIENT_TRACE_MAX_LINES limits the number of lines of trace that are output + * to a file. Two files are used at most, when they are full, the last one is overwritten with the + * new trace entries. The default size is 1000 lines. + * + * #### Trace API calls + * + * MQTTAsync_traceCallback() is used to set a callback function which is called whenever trace + * information is available. This will be the same information as that printed if the + * environment variables were used to control the trace. + * + * The MQTTAsync_setTraceLevel() calls is used to set the maximum level of trace entries that will be + * passed to the callback function. The levels are: + * 1. ::MQTTASYNC_TRACE_MAXIMUM + * 2. ::MQTTASYNC_TRACE_MEDIUM + * 3. ::MQTTASYNC_TRACE_MINIMUM + * 4. ::MQTTASYNC_TRACE_PROTOCOL + * 5. ::MQTTASYNC_TRACE_ERROR + * 6. ::MQTTASYNC_TRACE_SEVERE + * 7. ::MQTTASYNC_TRACE_FATAL + * + * Selecting ::MQTTASYNC_TRACE_MAXIMUM will cause all trace entries at all levels to be returned. + * Choosing ::MQTTASYNC_TRACE_ERROR will cause ERROR, SEVERE and FATAL trace entries to be returned + * to the callback function. + * + * ### MQTT Packet Tracing + * + * A feature that can be very useful is printing the MQTT packets that are sent and received. To + * achieve this, use the following environment variable settings: + * @code + MQTT_C_CLIENT_TRACE=ON + MQTT_C_CLIENT_TRACE_LEVEL=PROTOCOL + * @endcode + * The output you should see looks like this: + * @code + 20130528 155936.813 3 stdout-subscriber -> CONNECT cleansession: 1 (0) + 20130528 155936.813 3 stdout-subscriber <- CONNACK rc: 0 + 20130528 155936.813 3 stdout-subscriber -> SUBSCRIBE msgid: 1 (0) + 20130528 155936.813 3 stdout-subscriber <- SUBACK msgid: 1 + 20130528 155941.818 3 stdout-subscriber -> DISCONNECT (0) + * @endcode + * where the fields are: + * 1. date + * 2. time + * 3. socket number + * 4. client id + * 5. direction (-> from client to server, <- from server to client) + * 6. packet details + * + * ### Default Level Tracing + * + * This is an extract of a default level trace of a call to connect: + * @code + 19700101 010000.000 (1152206656) (0)> MQTTClient_connect:893 + 19700101 010000.000 (1152206656) (1)> MQTTClient_connectURI:716 + 20130528 160447.479 Connecting to serverURI localhost:1883 + 20130528 160447.479 (1152206656) (2)> MQTTProtocol_connect:98 + 20130528 160447.479 (1152206656) (3)> MQTTProtocol_addressPort:48 + 20130528 160447.479 (1152206656) (3)< MQTTProtocol_addressPort:73 + 20130528 160447.479 (1152206656) (3)> Socket_new:599 + 20130528 160447.479 New socket 4 for localhost, port 1883 + 20130528 160447.479 (1152206656) (4)> Socket_addSocket:163 + 20130528 160447.479 (1152206656) (5)> Socket_setnonblocking:73 + 20130528 160447.479 (1152206656) (5)< Socket_setnonblocking:78 (0) + 20130528 160447.479 (1152206656) (4)< Socket_addSocket:176 (0) + 20130528 160447.479 (1152206656) (4)> Socket_error:95 + 20130528 160447.479 (1152206656) (4)< Socket_error:104 (115) + 20130528 160447.479 Connect pending + 20130528 160447.479 (1152206656) (3)< Socket_new:683 (115) + 20130528 160447.479 (1152206656) (2)< MQTTProtocol_connect:131 (115) + * @endcode + * where the fields are: + * 1. date + * 2. time + * 3. thread id + * 4. function nesting level + * 5. function entry (>) or exit (<) + * 6. function name : line of source code file + * 7. return value (if there is one) + * + * ### Memory Allocation Tracing + * + * Setting the trace level to maximum causes memory allocations and frees to be traced along with + * the default trace entries, with messages like the following: + * @code + 20130528 161819.657 Allocating 16 bytes in heap at file /home/icraggs/workspaces/mqrtc/mqttv3c/src/MQTTPacket.c line 177 ptr 0x179f930 + + 20130528 161819.657 Freeing 16 bytes in heap at file /home/icraggs/workspaces/mqrtc/mqttv3c/src/MQTTPacket.c line 201, heap use now 896 bytes + * @endcode + * When the last MQTT client object is destroyed, if the trace is being recorded + * and all memory allocated by the client library has not been freed, an error message will be + * written to the trace. This can help with fixing memory leaks. The message will look like this: + * @code + 20130528 163909.208 Some memory not freed at shutdown, possible memory leak + 20130528 163909.208 Heap scan start, total 880 bytes + 20130528 163909.208 Heap element size 32, line 354, file /home/icraggs/workspaces/mqrtc/mqttv3c/src/MQTTPacket.c, ptr 0x260cb00 + 20130528 163909.208 Content + 20130528 163909.209 Heap scan end + * @endcode + * @endcond + */ + + +#endif + +#ifdef __cplusplus + } +#endif diff --git a/Sources/paho/include/MQTTClient.h b/Sources/paho/include/MQTTClient.h new file mode 100644 index 0000000..222bea5 --- /dev/null +++ b/Sources/paho/include/MQTTClient.h @@ -0,0 +1,1335 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates + * Ian Craggs - multiple server connection support + * Ian Craggs - MQTT 3.1.1 support + *******************************************************************************/ + +/** + * @cond MQTTClient_internal + * @mainpage MQTT Client Library Internals + * In the beginning there was one MQTT C client library, MQTTClient, as implemented in MQTTClient.c + * This library was designed to be easy to use for applications which didn't mind if some of the calls + * blocked for a while. For instance, the MQTTClient_connect call will block until a successful + * connection has completed, or a connection has failed, which could be as long as the "connection + * timeout" interval, whose default is 30 seconds. + * + * However in mobile devices and other windowing environments, blocking on the GUI thread is a bad + * thing as it causes the user interface to freeze. Hence a new API, MQTTAsync, implemented + * in MQTTAsync.c, was devised. There are no blocking calls in this library, so it is well suited + * to GUI and mobile environments, at the expense of some extra complexity. + * + * Both libraries are designed to be sparing in the use of threads. So multiple client objects are + * handled by one or two threads, with a select call in Socket_getReadySocket(), used to determine + * when a socket has incoming data. + * + * @endcond + * @cond MQTTClient_main + * @mainpage MQTT Client library for C + * © Copyright IBM Corp. 2009, 2014 + * + * @brief An MQTT client library in C. + * + * These pages describe the original more synchronous API which might be + * considered easier to use. Some of the calls will block. For the new + * totally asynchronous API where no calls block, which is especially suitable + * for use in windowed environments, see the + * MQTT C Client Asynchronous API Documentation. + * + * An MQTT client application connects to MQTT-capable servers. + * A typical client is responsible for collecting information from a telemetry + * device and publishing the information to the server. It can also subscribe + * to topics, receive messages, and use this information to control the + * telemetry device. + * + * MQTT clients implement the published MQTT v3 protocol. You can write your own + * API to the MQTT protocol using the programming language and platform of your + * choice. This can be time-consuming and error-prone. + * + * To simplify writing MQTT client applications, this library encapsulates + * the MQTT v3 protocol for you. Using this library enables a fully functional + * MQTT client application to be written in a few lines of code. + * The information presented here documents the API provided + * by the MQTT Client library for C. + * + * Using the client
+ * Applications that use the client library typically use a similar structure: + *
    + *
  • Create a client object
  • + *
  • Set the options to connect to an MQTT server
  • + *
  • Set up callback functions if multi-threaded (asynchronous mode) + * operation is being used (see @ref async).
  • + *
  • Subscribe to any topics the client needs to receive
  • + *
  • Repeat until finished:
  • + *
      + *
    • Publish any messages the client needs to
    • + *
    • Handle any incoming messages
    • + *
    + *
  • Disconnect the client
  • + *
  • Free any memory being used by the client
  • + *
+ * Some simple examples are shown here: + *
    + *
  • @ref pubsync
  • + *
  • @ref pubasync
  • + *
  • @ref subasync
  • + *
+ * Additional information about important concepts is provided here: + *
    + *
  • @ref async
  • + *
  • @ref wildcard
  • + *
  • @ref qos
  • + *
  • @ref tracing
  • + *
+ * @endcond + */ + +/// @cond EXCLUDE +#if defined(__cplusplus) + extern "C" { +#endif +#if !defined(MQTTCLIENT_H) +#define MQTTCLIENT_H + +#if defined(WIN32) || defined(WIN64) + #define DLLImport __declspec(dllimport) + #define DLLExport __declspec(dllexport) +#else + #define DLLImport extern + #define DLLExport __attribute__ ((visibility ("default"))) +#endif + +#include +/// @endcond + +#if !defined(NO_PERSISTENCE) +#include "MQTTClientPersistence.h" +#endif + +/** + * Return code: No error. Indicates successful completion of an MQTT client + * operation. + */ +#define MQTTCLIENT_SUCCESS 0 +/** + * Return code: A generic error code indicating the failure of an MQTT client + * operation. + */ +#define MQTTCLIENT_FAILURE -1 + +/* error code -2 is MQTTCLIENT_PERSISTENCE_ERROR */ + +/** + * Return code: The client is disconnected. + */ +#define MQTTCLIENT_DISCONNECTED -3 +/** + * Return code: The maximum number of messages allowed to be simultaneously + * in-flight has been reached. + */ +#define MQTTCLIENT_MAX_MESSAGES_INFLIGHT -4 +/** + * Return code: An invalid UTF-8 string has been detected. + */ +#define MQTTCLIENT_BAD_UTF8_STRING -5 +/** + * Return code: A NULL parameter has been supplied when this is invalid. + */ +#define MQTTCLIENT_NULL_PARAMETER -6 +/** + * Return code: The topic has been truncated (the topic string includes + * embedded NULL characters). String functions will not access the full topic. + * Use the topic length value to access the full topic. + */ +#define MQTTCLIENT_TOPICNAME_TRUNCATED -7 +/** + * Return code: A structure parameter does not have the correct eyecatcher + * and version number. + */ +#define MQTTCLIENT_BAD_STRUCTURE -8 +/** + * Return code: A QoS value that falls outside of the acceptable range (0,1,2) + */ +#define MQTTCLIENT_BAD_QOS -9 + +/** + * Default MQTT version to connect with. Use 3.1.1 then fall back to 3.1 + */ +#define MQTTVERSION_DEFAULT 0 +/** + * MQTT version to connect with: 3.1 + */ +#define MQTTVERSION_3_1 3 +/** + * MQTT version to connect with: 3.1.1 + */ +#define MQTTVERSION_3_1_1 4 +/** + * Bad return code from subscribe, as defined in the 3.1.1 specification + */ +#define MQTT_BAD_SUBSCRIBE 0x80 + +/** + * A handle representing an MQTT client. A valid client handle is available + * following a successful call to MQTTClient_create(). + */ +typedef void* MQTTClient; +/** + * A value representing an MQTT message. A delivery token is returned to the + * client application when a message is published. The token can then be used to + * check that the message was successfully delivered to its destination (see + * MQTTClient_publish(), + * MQTTClient_publishMessage(), + * MQTTClient_deliveryComplete(), + * MQTTClient_waitForCompletion() and + * MQTTClient_getPendingDeliveryTokens()). + */ +typedef int MQTTClient_deliveryToken; +typedef int MQTTClient_token; + +/** + * A structure representing the payload and attributes of an MQTT message. The + * message topic is not part of this structure (see MQTTClient_publishMessage(), + * MQTTClient_publish(), MQTTClient_receive(), MQTTClient_freeMessage() + * and MQTTClient_messageArrived()). + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTM. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** The length of the MQTT message payload in bytes. */ + int payloadlen; + /** A pointer to the payload of the MQTT message. */ + void* payload; + /** + * The quality of service (QoS) assigned to the message. + * There are three levels of QoS: + *
+ *
QoS0
+ *
Fire and forget - the message may not be delivered
+ *
QoS1
+ *
At least once - the message will be delivered, but may be + * delivered more than once in some circumstances.
+ *
QoS2
+ *
Once and one only - the message will be delivered exactly once.
+ *
+ */ + int qos; + /** + * The retained flag serves two purposes depending on whether the message + * it is associated with is being published or received. + * + * retained = true
+ * For messages being published, a true setting indicates that the MQTT + * server should retain a copy of the message. The message will then be + * transmitted to new subscribers to a topic that matches the message topic. + * For subscribers registering a new subscription, the flag being true + * indicates that the received message is not a new one, but one that has + * been retained by the MQTT server. + * + * retained = false
+ * For publishers, this ndicates that this message should not be retained + * by the MQTT server. For subscribers, a false setting indicates this is + * a normal message, received as a result of it being published to the + * server. + */ + int retained; + /** + * The dup flag indicates whether or not this message is a duplicate. + * It is only meaningful when receiving QoS1 messages. When true, the + * client application should take appropriate action to deal with the + * duplicate message. + */ + int dup; + /** The message identifier is normally reserved for internal use by the + * MQTT client and server. + */ + int msgid; +} MQTTClient_message; + +#define MQTTClient_message_initializer { {'M', 'Q', 'T', 'M'}, 0, 0, NULL, 0, 0, 0, 0 } + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * receipt of messages. The function is registered with the client library by + * passing it as an argument to MQTTClient_setCallbacks(). It is + * called by the client library when a new message that matches a client + * subscription has been received from the server. This function is executed on + * a separate thread to the one on which the client application is running. + * @param context A pointer to the context value originally passed to + * MQTTClient_setCallbacks(), which contains any application-specific context. + * @param topicName The topic associated with the received message. + * @param topicLen The length of the topic if there are one + * more NULL characters embedded in topicName, otherwise topicLen + * is 0. If topicLen is 0, the value returned by strlen(topicName) + * can be trusted. If topicLen is greater than 0, the full topic name + * can be retrieved by accessing topicName as a byte array of length + * topicLen. + * @param message The MQTTClient_message structure for the received message. + * This structure contains the message payload and attributes. + * @return This function must return a boolean value indicating whether or not + * the message has been safely received by the client application. Returning + * true indicates that the message has been successfully handled. + * Returning false indicates that there was a problem. In this + * case, the client library will reinvoke MQTTClient_messageArrived() to + * attempt to deliver the message to the application again. + */ +typedef int MQTTClient_messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* message); + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * notification of delivery of messages. The function is registered with the + * client library by passing it as an argument to MQTTClient_setCallbacks(). + * It is called by the client library after the client application has + * published a message to the server. It indicates that the necessary + * handshaking and acknowledgements for the requested quality of service (see + * MQTTClient_message.qos) have been completed. This function is executed on a + * separate thread to the one on which the client application is running. + * Note:MQTTClient_deliveryComplete() is not called when messages are + * published at QoS0. + * @param context A pointer to the context value originally passed to + * MQTTClient_setCallbacks(), which contains any application-specific context. + * @param dt The ::MQTTClient_deliveryToken associated with + * the published message. Applications can check that all messages have been + * correctly published by matching the delivery tokens returned from calls to + * MQTTClient_publish() and MQTTClient_publishMessage() with the tokens passed + * to this callback. + */ +typedef void MQTTClient_deliveryComplete(void* context, MQTTClient_deliveryToken dt); + +/** + * This is a callback function. The client application + * must provide an implementation of this function to enable asynchronous + * notification of the loss of connection to the server. The function is + * registered with the client library by passing it as an argument to + * MQTTClient_setCallbacks(). It is called by the client library if the client + * loses its connection to the server. The client application must take + * appropriate action, such as trying to reconnect or reporting the problem. + * This function is executed on a separate thread to the one on which the + * client application is running. + * @param context A pointer to the context value originally passed to + * MQTTClient_setCallbacks(), which contains any application-specific context. + * @param cause The reason for the disconnection. + * Currently, cause is always set to NULL. + */ +typedef void MQTTClient_connectionLost(void* context, char* cause); + +/** + * This function sets the callback functions for a specific client. + * If your client application doesn't use a particular callback, set the + * relevant parameter to NULL. Calling MQTTClient_setCallbacks() puts the + * client into multi-threaded mode. Any necessary message acknowledgements and + * status communications are handled in the background without any intervention + * from the client application. See @ref async for more information. + * + * Note: The MQTT client must be disconnected when this function is + * called. + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param context A pointer to any application-specific context. The + * the context pointer is passed to each of the callback functions to + * provide access to the context information in the callback. + * @param cl A pointer to an MQTTClient_connectionLost() callback + * function. You can set this to NULL if your application doesn't handle + * disconnections. + * @param ma A pointer to an MQTTClient_messageArrived() callback + * function. This callback function must be specified when you call + * MQTTClient_setCallbacks(). + * @param dc A pointer to an MQTTClient_deliveryComplete() callback + * function. You can set this to NULL if your application publishes + * synchronously or if you do not want to check for successful delivery. + * @return ::MQTTCLIENT_SUCCESS if the callbacks were correctly set, + * ::MQTTCLIENT_FAILURE if an error occurred. + */ +DLLExport int MQTTClient_setCallbacks(MQTTClient handle, void* context, MQTTClient_connectionLost* cl, + MQTTClient_messageArrived* ma, MQTTClient_deliveryComplete* dc); + + +/** + * This function creates an MQTT client ready for connection to the + * specified server and using the specified persistent storage (see + * MQTTClient_persistence). See also MQTTClient_destroy(). + * @param handle A pointer to an ::MQTTClient handle. The handle is + * populated with a valid client reference following a successful return from + * this function. + * @param serverURI A null-terminated string specifying the server to + * which the client will connect. It takes the form protocol://host:port. + * Currently, protocol must be tcp. For host, you can + * specify either an IP address or a domain name. For instance, to connect to + * a server running on the local machines with the default MQTT port, specify + * tcp://localhost:1883. + * @param clientId The client identifier passed to the server when the + * client connects to it. It is a null-terminated UTF-8 encoded string. + * ClientIDs must be no longer than 23 characters according to the MQTT + * specification. + * @param persistence_type The type of persistence to be used by the client: + *
+ * ::MQTTCLIENT_PERSISTENCE_NONE: Use in-memory persistence. If the device or + * system on which the client is running fails or is switched off, the current + * state of any in-flight messages is lost and some messages may not be + * delivered even at QoS1 and QoS2. + *
+ * ::MQTTCLIENT_PERSISTENCE_DEFAULT: Use the default (file system-based) + * persistence mechanism. Status about in-flight messages is held in persistent + * storage and provides some protection against message loss in the case of + * unexpected failure. + *
+ * ::MQTTCLIENT_PERSISTENCE_USER: Use an application-specific persistence + * implementation. Using this type of persistence gives control of the + * persistence mechanism to the application. The application has to implement + * the MQTTClient_persistence interface. + * @param persistence_context If the application uses + * ::MQTTCLIENT_PERSISTENCE_NONE persistence, this argument is unused and should + * be set to NULL. For ::MQTTCLIENT_PERSISTENCE_DEFAULT persistence, it + * should be set to the location of the persistence directory (if set + * to NULL, the persistence directory used is the working directory). + * Applications that use ::MQTTCLIENT_PERSISTENCE_USER persistence set this + * argument to point to a valid MQTTClient_persistence structure. + * @return ::MQTTCLIENT_SUCCESS if the client is successfully created, otherwise + * an error code is returned. + */ +DLLExport int MQTTClient_create(MQTTClient* handle, const char* serverURI, const char* clientId, + int persistence_type, void* persistence_context); + +/** + * MQTTClient_willOptions defines the MQTT "Last Will and Testament" (LWT) settings for + * the client. In the event that a client unexpectedly loses its connection to + * the server, the server publishes the LWT message to the LWT topic on + * behalf of the client. This allows other clients (subscribed to the LWT topic) + * to be made aware that the client has disconnected. To enable the LWT + * function for a specific client, a valid pointer to an MQTTClient_willOptions + * structure is passed in the MQTTClient_connectOptions structure used in the + * MQTTClient_connect() call that connects the client to the server. The pointer + * to MQTTClient_willOptions can be set to NULL if the LWT function is not + * required. + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTW. */ + const char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** The LWT topic to which the LWT message will be published. */ + const char* topicName; + /** The LWT payload. */ + const char* message; + /** + * The retained flag for the LWT message (see MQTTClient_message.retained). + */ + int retained; + /** + * The quality of service setting for the LWT message (see + * MQTTClient_message.qos and @ref qos). + */ + int qos; +} MQTTClient_willOptions; + +#define MQTTClient_willOptions_initializer { {'M', 'Q', 'T', 'W'}, 0, NULL, NULL, 0, 0 } + +/** +* MQTTClient_sslProperties defines the settings to establish an SSL/TLS connection using the +* OpenSSL library. It covers the following scenarios: +* - Server authentication: The client needs the digital certificate of the server. It is included +* in a store containting trusted material (also known as "trust store"). +* - Mutual authentication: Both client and server are authenticated during the SSL handshake. In +* addition to the digital certificate of the server in a trust store, the client will need its own +* digital certificate and the private key used to sign its digital certificate stored in a "key store". +* - Anonymous connection: Both client and server do not get authenticated and no credentials are needed +* to establish an SSL connection. Note that this scenario is not fully secure since it is subject to +* man-in-the-middle attacks. +*/ +typedef struct +{ + /** The eyecatcher for this structure. Must be MQTS */ + const char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + + /** The file in PEM format containing the public digital certificates trusted by the client. */ + const char* trustStore; + + /** The file in PEM format containing the public certificate chain of the client. It may also include + * the client's private key. + */ + const char* keyStore; + + /** If not included in the sslKeyStore, this setting points to the file in PEM format containing + * the client's private key. + */ + const char* privateKey; + /** The password to load the client's privateKey if encrypted. */ + const char* privateKeyPassword; + + /** + * The list of cipher suites that the client will present to the server during the SSL handshake. For a + * full explanation of the cipher list format, please see the OpenSSL on-line documentation: + * http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT + * If this setting is ommitted, its default value will be "ALL", that is, all the cipher suites -excluding + * those offering no encryption- will be considered. + * This setting can be used to set an SSL anonymous connection ("aNULL" string value, for instance). + */ + const char* enabledCipherSuites; + + /** True/False option to enable verification of the server certificate **/ + int enableServerCertAuth; + +} MQTTClient_SSLOptions; + +#define MQTTClient_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 0, NULL, NULL, NULL, NULL, NULL, 1 } + +/** + * MQTTClient_connectOptions defines several settings that control the way the + * client connects to an MQTT server. + * + * Note: Default values are not defined for members of + * MQTTClient_connectOptions so it is good practice to specify all settings. + * If the MQTTClient_connectOptions structure is defined as an automatic + * variable, all members are set to random values and thus must be set by the + * client application. If the MQTTClient_connectOptions structure is defined + * as a static variable, initialization (in compliant compilers) sets all + * values to 0 (NULL for pointers). A #keepAliveInterval setting of 0 prevents + * correct operation of the client and so you must at least set a value + * for #keepAliveInterval. + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTC. */ + const char struct_id[4]; + /** The version number of this structure. Must be 0, 1, 2, 3 or 4. + * 0 signifies no SSL options and no serverURIs + * 1 signifies no serverURIs + * 2 signifies no MQTTVersion + * 3 signifies no returned values + */ + int struct_version; + /** The "keep alive" interval, measured in seconds, defines the maximum time + * that should pass without communication between the client and the server + * The client will ensure that at least one message travels across the + * network within each keep alive period. In the absence of a data-related + * message during the time period, the client sends a very small MQTT + * "ping" message, which the server will acknowledge. The keep alive + * interval enables the client to detect when the server is no longer + * available without having to wait for the long TCP/IP timeout. + */ + int keepAliveInterval; + /** + * This is a boolean value. The cleansession setting controls the behaviour + * of both the client and the server at connection and disconnection time. + * The client and server both maintain session state information. This + * information is used to ensure "at least once" and "exactly once" + * delivery, and "exactly once" receipt of messages. Session state also + * includes subscriptions created by an MQTT client. You can choose to + * maintain or discard state information between sessions. + * + * When cleansession is true, the state information is discarded at + * connect and disconnect. Setting cleansession to false keeps the state + * information. When you connect an MQTT client application with + * MQTTClient_connect(), the client identifies the connection using the + * client identifier and the address of the server. The server checks + * whether session information for this client + * has been saved from a previous connection to the server. If a previous + * session still exists, and cleansession=true, then the previous session + * information at the client and server is cleared. If cleansession=false, + * the previous session is resumed. If no previous session exists, a new + * session is started. + */ + int cleansession; + /** + * This is a boolean value that controls how many messages can be in-flight + * simultaneously. Setting reliable to true means that a published + * message must be completed (acknowledgements received) before another + * can be sent. Attempts to publish additional messages receive an + * ::MQTTCLIENT_MAX_MESSAGES_INFLIGHT return code. Setting this flag to + * false allows up to 10 messages to be in-flight. This can increase + * overall throughput in some circumstances. + */ + int reliable; + /** + * This is a pointer to an MQTTClient_willOptions structure. If your + * application does not make use of the Last Will and Testament feature, + * set this pointer to NULL. + */ + MQTTClient_willOptions* will; + /** + * MQTT servers that support the MQTT v3.1 protocol provide authentication + * and authorisation by user name and password. This is the user name + * parameter. + */ + const char* username; + /** + * MQTT servers that support the MQTT v3.1 protocol provide authentication + * and authorisation by user name and password. This is the password + * parameter. + */ + const char* password; + /** + * The time interval in seconds to allow a connect to complete. + */ + int connectTimeout; + /** + * The time interval in seconds + */ + int retryInterval; + /** + * This is a pointer to an MQTTClient_SSLOptions structure. If your + * application does not make use of SSL, set this pointer to NULL. + */ + MQTTClient_SSLOptions* ssl; + /** + * The number of entries in the optional serverURIs array. Defaults to 0. + */ + int serverURIcount; + /** + * An optional array of null-terminated strings specifying the servers to + * which the client will connect. Each string takes the form protocol://host:port. + * protocol must be tcp or ssl. For host, you can + * specify either an IP address or a host name. For instance, to connect to + * a server running on the local machines with the default MQTT port, specify + * tcp://localhost:1883. + * If this list is empty (the default), the server URI specified on MQTTClient_create() + * is used. + */ + char* const* serverURIs; + /** + * Sets the version of MQTT to be used on the connect. + * MQTTVERSION_DEFAULT (0) = default: start with 3.1.1, and if that fails, fall back to 3.1 + * MQTTVERSION_3_1 (3) = only try version 3.1 + * MQTTVERSION_3_1_1 (4) = only try version 3.1.1 + */ + int MQTTVersion; + /** + * Returned from the connect when the MQTT version used to connect is 3.1.1 + */ + struct + { + const char* serverURI; /**< the serverURI connected to */ + int MQTTVersion; /**< the MQTT version used to connect with */ + int sessionPresent; /**< if the MQTT version is 3.1.1, the value of sessionPresent returned in the connack */ + } returned; +} MQTTClient_connectOptions; + +#define MQTTClient_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 4, 60, 1, 1, NULL, NULL, NULL, 30, 20, NULL, 0, NULL, 0} + +/** + * MQTTClient_libraryInfo is used to store details relating to the currently used + * library such as the version in use, the time it was built and relevant openSSL + * options. + * There is one static instance of this struct in MQTTClient.c + */ + +typedef struct +{ + const char* name; + const char* value; +} MQTTClient_nameValue; + +/** + * This function returns version information about the library. + * no trace information will be returned. + * @return an array of strings describing the library. The last entry is a NULL pointer. + */ +DLLExport MQTTClient_nameValue* MQTTClient_getVersionInfo(void); + +/** + * This function attempts to connect a previously-created client (see + * MQTTClient_create()) to an MQTT server using the specified options. If you + * want to enable asynchronous message and status notifications, you must call + * MQTTClient_setCallbacks() prior to MQTTClient_connect(). + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param options A pointer to a valid MQTTClient_connectOptions + * structure. + * @return ::MQTTCLIENT_SUCCESS if the client successfully connects to the + * server. An error code is returned if the client was unable to connect to + * the server. + * Error codes greater than 0 are returned by the MQTT protocol:

+ * 1: Connection refused: Unacceptable protocol version
+ * 2: Connection refused: Identifier rejected
+ * 3: Connection refused: Server unavailable
+ * 4: Connection refused: Bad user name or password
+ * 5: Connection refused: Not authorized
+ * 6-255: Reserved for future use
+ */ +DLLExport int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options); + +/** + * This function attempts to disconnect the client from the MQTT + * server. In order to allow the client time to complete handling of messages + * that are in-flight when this function is called, a timeout period is + * specified. When the timeout period has expired, the client disconnects even + * if there are still outstanding message acknowledgements. + * The next time the client connects to the same server, any QoS 1 or 2 + * messages which have not completed will be retried depending on the + * cleansession settings for both the previous and the new connection (see + * MQTTClient_connectOptions.cleansession and MQTTClient_connect()). + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param timeout The client delays disconnection for up to this time (in + * milliseconds) in order to allow in-flight message transfers to complete. + * @return ::MQTTCLIENT_SUCCESS if the client successfully disconnects from + * the server. An error code is returned if the client was unable to disconnect + * from the server + */ +DLLExport int MQTTClient_disconnect(MQTTClient handle, int timeout); + +/** + * This function allows the client application to test whether or not a + * client is currently connected to the MQTT server. + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @return Boolean true if the client is connected, otherwise false. + */ +DLLExport int MQTTClient_isConnected(MQTTClient handle); + + +/* Subscribe is synchronous. QoS list parameter is changed on return to granted QoSs. + Returns return code, MQTTCLIENT_SUCCESS == success, non-zero some sort of error (TBD) */ + +/** + * This function attempts to subscribe a client to a single topic, which may + * contain wildcards (see @ref wildcard). This call also specifies the + * @ref qos requested for the subscription + * (see also MQTTClient_subscribeMany()). + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param topic The subscription topic, which may include wildcards. + * @param qos The requested quality of service for the subscription. + * @return ::MQTTCLIENT_SUCCESS if the subscription request is successful. + * An error code is returned if there was a problem registering the + * subscription. + */ +DLLExport int MQTTClient_subscribe(MQTTClient handle, const char* topic, int qos); + +/** + * This function attempts to subscribe a client to a list of topics, which may + * contain wildcards (see @ref wildcard). This call also specifies the + * @ref qos requested for each topic (see also MQTTClient_subscribe()). + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param count The number of topics for which the client is requesting + * subscriptions. + * @param topic An array (of length count) of pointers to + * topics, each of which may include wildcards. + * @param qos An array (of length count) of @ref qos + * values. qos[n] is the requested QoS for topic[n]. + * @return ::MQTTCLIENT_SUCCESS if the subscription request is successful. + * An error code is returned if there was a problem registering the + * subscriptions. + */ +DLLExport int MQTTClient_subscribeMany(MQTTClient handle, int count, char* const* topic, int* qos); + +/** + * This function attempts to remove an existing subscription made by the + * specified client. + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param topic The topic for the subscription to be removed, which may + * include wildcards (see @ref wildcard). + * @return ::MQTTCLIENT_SUCCESS if the subscription is removed. + * An error code is returned if there was a problem removing the + * subscription. + */ +DLLExport int MQTTClient_unsubscribe(MQTTClient handle, const char* topic); + +/** + * This function attempts to remove existing subscriptions to a list of topics + * made by the specified client. + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param count The number subscriptions to be removed. + * @param topic An array (of length count) of pointers to the topics of + * the subscriptions to be removed, each of which may include wildcards. + * @return ::MQTTCLIENT_SUCCESS if the subscriptions are removed. + * An error code is returned if there was a problem removing the subscriptions. + */ +DLLExport int MQTTClient_unsubscribeMany(MQTTClient handle, int count, char* const* topic); + +/** + * This function attempts to publish a message to a given topic (see also + * MQTTClient_publishMessage()). An ::MQTTClient_deliveryToken is issued when + * this function returns successfully. If the client application needs to + * test for succesful delivery of QoS1 and QoS2 messages, this can be done + * either asynchronously or synchronously (see @ref async, + * ::MQTTClient_waitForCompletion and MQTTClient_deliveryComplete()). + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param topicName The topic associated with this message. + * @param payloadlen The length of the payload in bytes. + * @param payload A pointer to the byte array payload of the message. + * @param qos The @ref qos of the message. + * @param retained The retained flag for the message. + * @param dt A pointer to an ::MQTTClient_deliveryToken. This is populated + * with a token representing the message when the function returns + * successfully. If your application does not use delivery tokens, set this + * argument to NULL. + * @return ::MQTTCLIENT_SUCCESS if the message is accepted for publication. + * An error code is returned if there was a problem accepting the message. + */ +DLLExport int MQTTClient_publish(MQTTClient handle, const char* topicName, int payloadlen, void* payload, int qos, int retained, + MQTTClient_deliveryToken* dt); +/** + * This function attempts to publish a message to a given topic (see also + * MQTTClient_publish()). An ::MQTTClient_deliveryToken is issued when + * this function returns successfully. If the client application needs to + * test for succesful delivery of QoS1 and QoS2 messages, this can be done + * either asynchronously or synchronously (see @ref async, + * ::MQTTClient_waitForCompletion and MQTTClient_deliveryComplete()). + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param topicName The topic associated with this message. + * @param msg A pointer to a valid MQTTClient_message structure containing + * the payload and attributes of the message to be published. + * @param dt A pointer to an ::MQTTClient_deliveryToken. This is populated + * with a token representing the message when the function returns + * successfully. If your application does not use delivery tokens, set this + * argument to NULL. + * @return ::MQTTCLIENT_SUCCESS if the message is accepted for publication. + * An error code is returned if there was a problem accepting the message. + */ +DLLExport int MQTTClient_publishMessage(MQTTClient handle, const char* topicName, MQTTClient_message* msg, MQTTClient_deliveryToken* dt); + + +/** + * This function is called by the client application to synchronize execution + * of the main thread with completed publication of a message. When called, + * MQTTClient_waitForCompletion() blocks execution until the message has been + * successful delivered or the specified timeout has expired. See @ref async. + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param dt The ::MQTTClient_deliveryToken that represents the message being + * tested for successful delivery. Delivery tokens are issued by the + * publishing functions MQTTClient_publish() and MQTTClient_publishMessage(). + * @param timeout The maximum time to wait in milliseconds. + * @return ::MQTTCLIENT_SUCCESS if the message was successfully delivered. + * An error code is returned if the timeout expires or there was a problem + * checking the token. + */ +DLLExport int MQTTClient_waitForCompletion(MQTTClient handle, MQTTClient_deliveryToken dt, unsigned long timeout); + + +/** + * This function sets a pointer to an array of delivery tokens for + * messages that are currently in-flight (pending completion). + * + * Important note: The memory used to hold the array of tokens is + * malloc()'d in this function. The client application is responsible for + * freeing this memory when it is no longer required. + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param tokens The address of a pointer to an ::MQTTClient_deliveryToken. + * When the function returns successfully, the pointer is set to point to an + * array of tokens representing messages pending completion. The last member of + * the array is set to -1 to indicate there are no more tokens. If no tokens + * are pending, the pointer is set to NULL. + * @return ::MQTTCLIENT_SUCCESS if the function returns successfully. + * An error code is returned if there was a problem obtaining the list of + * pending tokens. + */ +DLLExport int MQTTClient_getPendingDeliveryTokens(MQTTClient handle, MQTTClient_deliveryToken **tokens); + +/** + * When implementing a single-threaded client, call this function periodically + * to allow processing of message retries and to send MQTT keepalive pings. + * If the application is calling MQTTClient_receive() regularly, then it is + * not necessary to call this function. + */ +DLLExport void MQTTClient_yield(void); + +/** + * This function performs a synchronous receive of incoming messages. It should + * be used only when the client application has not set callback methods to + * support asynchronous receipt of messages (see @ref async and + * MQTTClient_setCallbacks()). Using this function allows a single-threaded + * client subscriber application to be written. When called, this function + * blocks until the next message arrives or the specified timeout expires + *(see also MQTTClient_yield()). + * + * Important note: The application must free() the memory allocated + * to the topic and the message when processing is complete (see + * MQTTClient_freeMessage()). + * @param handle A valid client handle from a successful call to + * MQTTClient_create(). + * @param topicName The address of a pointer to a topic. This function + * allocates the memory for the topic and returns it to the application + * by setting topicName to point to the topic. + * @param topicLen The length of the topic. If the return code from this + * function is ::MQTTCLIENT_TOPICNAME_TRUNCATED, the topic contains embedded + * NULL characters and the full topic should be retrieved by using + * topicLen. + * @param message The address of a pointer to the received message. This + * function allocates the memory for the message and returns it to the + * application by setting message to point to the received message. + * The pointer is set to NULL if the timeout expires. + * @param timeout The length of time to wait for a message in milliseconds. + * @return ::MQTTCLIENT_SUCCESS or ::MQTTCLIENT_TOPICNAME_TRUNCATED if a + * message is received. ::MQTTCLIENT_SUCCESS can also indicate that the + * timeout expired, in which case message is NULL. An error code is + * returned if there was a problem trying to receive a message. + */ +DLLExport int MQTTClient_receive(MQTTClient handle, char** topicName, int* topicLen, MQTTClient_message** message, + unsigned long timeout); + +/** + * This function frees memory allocated to an MQTT message, including the + * additional memory allocated to the message payload. The client application + * calls this function when the message has been fully processed. Important + * note: This function does not free the memory allocated to a message + * topic string. It is the responsibility of the client application to free + * this memory using the MQTTClient_free() library function. + * @param msg The address of a pointer to the ::MQTTClient_message structure + * to be freed. + */ +DLLExport void MQTTClient_freeMessage(MQTTClient_message** msg); + +/** + * This function frees memory allocated by the MQTT C client library, especially the + * topic name. This is needed on Windows when the client libary and application + * program have been compiled with different versions of the C compiler. It is + * thus good policy to always use this function when freeing any MQTT C client- + * allocated memory. + * @param ptr The pointer to the client library storage to be freed. + */ +DLLExport void MQTTClient_free(void* ptr); + +/** + * This function frees the memory allocated to an MQTT client (see + * MQTTClient_create()). It should be called when the client is no longer + * required. + * @param handle A pointer to the handle referring to the ::MQTTClient + * structure to be freed. + */ +DLLExport void MQTTClient_destroy(MQTTClient* handle); + +#endif +#ifdef __cplusplus + } +#endif + +/** + * @cond MQTTClient_main + * @page async Asynchronous vs synchronous client applications + * The client library supports two modes of operation. These are referred to + * as synchronous and asynchronous modes. If your application + * calls MQTTClient_setCallbacks(), this puts the client into asynchronous + * mode, otherwise it operates in synchronous mode. + * + * In synchronous mode, the client application runs on a single thread. + * Messages are published using the MQTTClient_publish() and + * MQTTClient_publishMessage() functions. To determine that a QoS1 or QoS2 + * (see @ref qos) message has been successfully delivered, the application + * must call the MQTTClient_waitForCompletion() function. An example showing + * synchronous publication is shown in @ref pubsync. Receiving messages in + * synchronous mode uses the MQTTClient_receive() function. Client applicaitons + * must call either MQTTClient_receive() or MQTTClient_yield() relatively + * frequently in order to allow processing of acknowledgements and the MQTT + * "pings" that keep the network connection to the server alive. + * + * In asynchronous mode, the client application runs on several threads. The + * main program calls functions in the client library to publish and subscribe, + * just as for the synchronous mode. Processing of handshaking and maintaining + * the network connection is performed in the background, however. + * Notifications of status and message reception are provided to the client + * application using callbacks registered with the library by the call to + * MQTTClient_setCallbacks() (see MQTTClient_messageArrived(), + * MQTTClient_connectionLost() and MQTTClient_deliveryComplete()). + * + * @page wildcard Subscription wildcards + * Every MQTT message includes a topic that classifies it. MQTT servers use + * topics to determine which subscribers should receive messages published to + * the server. + * + * Consider the server receiving messages from several environmental sensors. + * Each sensor publishes its measurement data as a message with an associated + * topic. Subscribing applications need to know which sensor originally + * published each received message. A unique topic is thus used to identify + * each sensor and measurement type. Topics such as SENSOR1TEMP, + * SENSOR1HUMIDITY, SENSOR2TEMP and so on achieve this but are not very + * flexible. If additional sensors are added to the system at a later date, + * subscribing applications must be modified to receive them. + * + * To provide more flexibility, MQTT supports a hierarchical topic namespace. + * This allows application designers to organize topics to simplify their + * management. Levels in the hierarchy are delimited by the '/' character, + * such as SENSOR/1/HUMIDITY. Publishers and subscribers use these + * hierarchical topics as already described. + * + * For subscriptions, two wildcard characters are supported: + *
    + *
  • A '#' character represents a complete sub-tree of the hierarchy and + * thus must be the last character in a subscription topic string, such as + * SENSOR/#. This will match any topic starting with SENSOR/, such as + * SENSOR/1/TEMP and SENSOR/2/HUMIDITY.
  • + *
  • A '+' character represents a single level of the hierarchy and is + * used between delimiters. For example, SENSOR/+/TEMP will match + * SENSOR/1/TEMP and SENSOR/2/TEMP.
  • + *
+ * Publishers are not allowed to use the wildcard characters in their topic + * names. + * + * Deciding on your topic hierarchy is an important step in your system design. + * + * @page qos Quality of service + * The MQTT protocol provides three qualities of service for delivering + * messages between clients and servers: "at most once", "at least once" and + * "exactly once". + * + * Quality of service (QoS) is an attribute of an individual message being + * published. An application sets the QoS for a specific message by setting the + * MQTTClient_message.qos field to the required value. + * + * A subscribing client can set the maximum quality of service a server uses + * to send messages that match the client subscriptions. The + * MQTTClient_subscribe() and MQTTClient_subscribeMany() functions set this + * maximum. The QoS of a message forwarded to a subscriber thus might be + * different to the QoS given to the message by the original publisher. + * The lower of the two values is used to forward a message. + * + * The three levels are: + * + * QoS0, At most once: The message is delivered at most once, or it + * may not be delivered at all. Its delivery across the network is not + * acknowledged. The message is not stored. The message could be lost if the + * client is disconnected, or if the server fails. QoS0 is the fastest mode of + * transfer. It is sometimes called "fire and forget". + * + * The MQTT protocol does not require servers to forward publications at QoS0 + * to a client. If the client is disconnected at the time the server receives + * the publication, the publication might be discarded, depending on the + * server implementation. + * + * QoS1, At least once: The message is always delivered at least once. + * It might be delivered multiple times if there is a failure before an + * acknowledgment is received by the sender. The message must be stored + * locally at the sender, until the sender receives confirmation that the + * message has been published by the receiver. The message is stored in case + * the message must be sent again. + * + * QoS2, Exactly once: The message is always delivered exactly once. + * The message must be stored locally at the sender, until the sender receives + * confirmation that the message has been published by the receiver. The + * message is stored in case the message must be sent again. QoS2 is the + * safest, but slowest mode of transfer. A more sophisticated handshaking + * and acknowledgement sequence is used than for QoS1 to ensure no duplication + * of messages occurs. + * @page pubsync Synchronous publication example +@code +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTClient.h" + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientPub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +int main(int argc, char* argv[]) +{ + MQTTClient client; + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + MQTTClient_message pubmsg = MQTTClient_message_initializer; + MQTTClient_deliveryToken token; + int rc; + + MQTTClient_create(&client, ADDRESS, CLIENTID, + MQTTCLIENT_PERSISTENCE_NONE, NULL); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + + if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to connect, return code %d\n", rc); + exit(-1); + } + pubmsg.payload = PAYLOAD; + pubmsg.payloadlen = strlen(PAYLOAD); + pubmsg.qos = QOS; + pubmsg.retained = 0; + MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token); + printf("Waiting for up to %d seconds for publication of %s\n" + "on topic %s for client with ClientID: %s\n", + (int)(TIMEOUT/1000), PAYLOAD, TOPIC, CLIENTID); + rc = MQTTClient_waitForCompletion(client, token, TIMEOUT); + printf("Message with delivery token %d delivered\n", token); + MQTTClient_disconnect(client, 10000); + MQTTClient_destroy(&client); + return rc; +} + + * @endcode + * + * @page pubasync Asynchronous publication example +@code{.c} +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTClient.h" + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientPub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +volatile MQTTClient_deliveryToken deliveredtoken; + +void delivered(void *context, MQTTClient_deliveryToken dt) +{ + printf("Message with token value %d delivery confirmed\n", dt); + deliveredtoken = dt; +} + +int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message) +{ + int i; + char* payloadptr; + + printf("Message arrived\n"); + printf(" topic: %s\n", topicName); + printf(" message: "); + + payloadptr = message->payload; + for(i=0; ipayloadlen; i++) + { + putchar(*payloadptr++); + } + putchar('\n'); + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + return 1; +} + +void connlost(void *context, char *cause) +{ + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); +} + +int main(int argc, char* argv[]) +{ + MQTTClient client; + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + MQTTClient_message pubmsg = MQTTClient_message_initializer; + MQTTClient_deliveryToken token; + int rc; + + MQTTClient_create(&client, ADDRESS, CLIENTID, + MQTTCLIENT_PERSISTENCE_NONE, NULL); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + + MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered); + + if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to connect, return code %d\n", rc); + exit(-1); + } + pubmsg.payload = PAYLOAD; + pubmsg.payloadlen = strlen(PAYLOAD); + pubmsg.qos = QOS; + pubmsg.retained = 0; + deliveredtoken = 0; + MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token); + printf("Waiting for publication of %s\n" + "on topic %s for client with ClientID: %s\n", + PAYLOAD, TOPIC, CLIENTID); + while(deliveredtoken != token); + MQTTClient_disconnect(client, 10000); + MQTTClient_destroy(&client); + return rc; +} + + * @endcode + * @page subasync Asynchronous subscription example +@code +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTClient.h" + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientSub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +volatile MQTTClient_deliveryToken deliveredtoken; + +void delivered(void *context, MQTTClient_deliveryToken dt) +{ + printf("Message with token value %d delivery confirmed\n", dt); + deliveredtoken = dt; +} + +int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message) +{ + int i; + char* payloadptr; + + printf("Message arrived\n"); + printf(" topic: %s\n", topicName); + printf(" message: "); + + payloadptr = message->payload; + for(i=0; ipayloadlen; i++) + { + putchar(*payloadptr++); + } + putchar('\n'); + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + return 1; +} + +void connlost(void *context, char *cause) +{ + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); +} + +int main(int argc, char* argv[]) +{ + MQTTClient client; + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + int rc; + int ch; + + MQTTClient_create(&client, ADDRESS, CLIENTID, + MQTTCLIENT_PERSISTENCE_NONE, NULL); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + + MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered); + + if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to connect, return code %d\n", rc); + exit(-1); + } + printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n" + "Press Q to quit\n\n", TOPIC, CLIENTID, QOS); + MQTTClient_subscribe(client, TOPIC, QOS); + + do + { + ch = getchar(); + } while(ch!='Q' && ch != 'q'); + + MQTTClient_disconnect(client, 10000); + MQTTClient_destroy(&client); + return rc; +} + + * @endcode + * @page tracing Tracing + * + * Runtime tracing is controlled by environment variables. + * + * Tracing is switched on by setting MQTT_C_CLIENT_TRACE. A value of ON, or stdout, prints to + * stdout, any other value is interpreted as a file name to use. + * + * The amount of trace detail is controlled with the MQTT_C_CLIENT_TRACE_LEVEL environment + * variable - valid values are ERROR, PROTOCOL, MINIMUM, MEDIUM and MAXIMUM + * (from least to most verbose). + * + * The variable MQTT_C_CLIENT_TRACE_MAX_LINES limits the number of lines of trace that are output + * to a file. Two files are used at most, when they are full, the last one is overwritten with the + * new trace entries. The default size is 1000 lines. + * + * ### MQTT Packet Tracing + * + * A feature that can be very useful is printing the MQTT packets that are sent and received. To + * achieve this, use the following environment variable settings: + * @code + MQTT_C_CLIENT_TRACE=ON + MQTT_C_CLIENT_TRACE_LEVEL=PROTOCOL + * @endcode + * The output you should see looks like this: + * @code + 20130528 155936.813 3 stdout-subscriber -> CONNECT cleansession: 1 (0) + 20130528 155936.813 3 stdout-subscriber <- CONNACK rc: 0 + 20130528 155936.813 3 stdout-subscriber -> SUBSCRIBE msgid: 1 (0) + 20130528 155936.813 3 stdout-subscriber <- SUBACK msgid: 1 + 20130528 155941.818 3 stdout-subscriber -> DISCONNECT (0) + * @endcode + * where the fields are: + * 1. date + * 2. time + * 3. socket number + * 4. client id + * 5. direction (-> from client to server, <- from server to client) + * 6. packet details + * + * ### Default Level Tracing + * + * This is an extract of a default level trace of a call to connect: + * @code + 19700101 010000.000 (1152206656) (0)> MQTTClient_connect:893 + 19700101 010000.000 (1152206656) (1)> MQTTClient_connectURI:716 + 20130528 160447.479 Connecting to serverURI localhost:1883 + 20130528 160447.479 (1152206656) (2)> MQTTProtocol_connect:98 + 20130528 160447.479 (1152206656) (3)> MQTTProtocol_addressPort:48 + 20130528 160447.479 (1152206656) (3)< MQTTProtocol_addressPort:73 + 20130528 160447.479 (1152206656) (3)> Socket_new:599 + 20130528 160447.479 New socket 4 for localhost, port 1883 + 20130528 160447.479 (1152206656) (4)> Socket_addSocket:163 + 20130528 160447.479 (1152206656) (5)> Socket_setnonblocking:73 + 20130528 160447.479 (1152206656) (5)< Socket_setnonblocking:78 (0) + 20130528 160447.479 (1152206656) (4)< Socket_addSocket:176 (0) + 20130528 160447.479 (1152206656) (4)> Socket_error:95 + 20130528 160447.479 (1152206656) (4)< Socket_error:104 (115) + 20130528 160447.479 Connect pending + 20130528 160447.479 (1152206656) (3)< Socket_new:683 (115) + 20130528 160447.479 (1152206656) (2)< MQTTProtocol_connect:131 (115) + * @endcode + * where the fields are: + * 1. date + * 2. time + * 3. thread id + * 4. function nesting level + * 5. function entry (>) or exit (<) + * 6. function name : line of source code file + * 7. return value (if there is one) + * + * ### Memory Allocation Tracing + * + * Setting the trace level to maximum causes memory allocations and frees to be traced along with + * the default trace entries, with messages like the following: + * @code + 20130528 161819.657 Allocating 16 bytes in heap at file /home/icraggs/workspaces/mqrtc/mqttv3c/src/MQTTPacket.c line 177 ptr 0x179f930 + + 20130528 161819.657 Freeing 16 bytes in heap at file /home/icraggs/workspaces/mqrtc/mqttv3c/src/MQTTPacket.c line 201, heap use now 896 bytes + * @endcode + * When the last MQTT client object is destroyed, if the trace is being recorded + * and all memory allocated by the client library has not been freed, an error message will be + * written to the trace. This can help with fixing memory leaks. The message will look like this: + * @code + 20130528 163909.208 Some memory not freed at shutdown, possible memory leak + 20130528 163909.208 Heap scan start, total 880 bytes + 20130528 163909.208 Heap element size 32, line 354, file /home/icraggs/workspaces/mqrtc/mqttv3c/src/MQTTPacket.c, ptr 0x260cb00 + 20130528 163909.208 Content + 20130528 163909.209 Heap scan end + * @endcode + * @endcond + */ diff --git a/Sources/paho/include/MQTTClientPersistence.h b/Sources/paho/include/MQTTClientPersistence.h new file mode 100644 index 0000000..5d44afe --- /dev/null +++ b/Sources/paho/include/MQTTClientPersistence.h @@ -0,0 +1,250 @@ +/******************************************************************************* + * Copyright (c) 2009, 2012 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +/** + * @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 persistence_context 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 persistence_type 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 persistence_type. To implement a + * custom persistence mechanism, the application must pass an initialized + * ::MQTTClient_persistence structure as the persistence_context + * 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 persistence_type value specifies the default file system-based + * persistence mechanism (see MQTTClient_create()). + */ +#define MQTTCLIENT_PERSISTENCE_DEFAULT 0 +/** + * This persistence_type value specifies a memory-based + * persistence mechanism (see MQTTClient_create()). + */ +#define MQTTCLIENT_PERSISTENCE_NONE 1 +/** + * This persistence_type 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 clientid in conjunction with the + * serverURI 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 key. + * @param buflens An array of lengths of the data buffers. buflen[n] + * gives the length of buffer[n]. + * @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 + * buffer 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. keys + * 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 diff --git a/Sources/paho/include/MQTTPacket.h b/Sources/paho/include/MQTTPacket.h new file mode 100644 index 0000000..538e9b0 --- /dev/null +++ b/Sources/paho/include/MQTTPacket.h @@ -0,0 +1,254 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates + * Ian Craggs - MQTT 3.1.1 support + *******************************************************************************/ + +#if !defined(MQTTPACKET_H) +#define MQTTPACKET_H + +#include "Socket.h" +#if defined(OPENSSL) +#include "SSLSocket.h" +#endif +#include "LinkedList.h" +#include "Clients.h" + +/*BE +include "Socket" +include "LinkedList" +include "Clients" +BE*/ + +typedef unsigned int bool; +typedef void* (*pf)(unsigned char, char*, size_t); + +#define BAD_MQTT_PACKET -4 + +enum msgTypes +{ + CONNECT = 1, CONNACK, PUBLISH, PUBACK, PUBREC, PUBREL, + PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK, + PINGREQ, PINGRESP, DISCONNECT +}; + + +/** + * Bitfields for the MQTT header byte. + */ +typedef union +{ + /*unsigned*/ char byte; /**< the whole byte */ +#if defined(REVERSED) + struct + { + unsigned int type : 4; /**< message type nibble */ + 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 */ + char rc; /**< connack return code */ +} Connack; + + +/** + * Data for a packet with header only. + */ +typedef struct +{ + Header header; /**< MQTT header byte */ +} MQTTPacket; + + +/** + * Data for a subscribe packet. + */ +typedef struct +{ + Header header; /**< MQTT header byte */ + int msgId; /**< MQTT message id */ + List* topics; /**< list of topic strings */ + List* qoss; /**< list of corresponding QoSs */ + int noTopics; /**< topic and qos count */ +} Subscribe; + + +/** + * Data for a suback packet. + */ +typedef struct +{ + Header header; /**< MQTT header byte */ + int msgId; /**< MQTT message id */ + List* qoss; /**< list of granted QoSs */ +} Suback; + + +/** + * Data for an unsubscribe packet. + */ +typedef struct +{ + Header header; /**< MQTT header byte */ + int msgId; /**< MQTT message id */ + List* topics; /**< list of topic strings */ + int noTopics; /**< topic count */ +} Unsubscribe; + + +/** + * 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 */ +} Publish; + + +/** + * Data for one of the ack packets. + */ +typedef struct +{ + Header header; /**< MQTT header byte */ + int msgId; /**< MQTT message id */ +} Ack; + +typedef Ack Puback; +typedef Ack Pubrec; +typedef Ack Pubrel; +typedef Ack Pubcomp; +typedef Ack Unsuback; + +int MQTTPacket_encode(char* buf, int length); +int MQTTPacket_decode(networkHandles* net, int* 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); + +char* MQTTPacket_name(int ptype); + +void* MQTTPacket_Factory(networkHandles* net, int* error); +int MQTTPacket_send(networkHandles* net, Header header, char* buffer, size_t buflen, int free); +int MQTTPacket_sends(networkHandles* net, Header header, int count, char** buffers, size_t* buflens, int* frees); + +void* MQTTPacket_header_only(unsigned char aHeader, char* data, size_t datalen); +int MQTTPacket_send_disconnect(networkHandles* net, const char* clientID); + +void* MQTTPacket_publish(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 msgid, networkHandles* net, const char* clientID); +void* MQTTPacket_ack(unsigned char aHeader, char* data, size_t datalen); + +void MQTTPacket_freeSuback(Suback* pack); +int MQTTPacket_send_pubrec(int msgid, networkHandles* net, const char* clientID); +int MQTTPacket_send_pubrel(int msgid, int dup, networkHandles* net, const char* clientID); +int MQTTPacket_send_pubcomp(int msgid, networkHandles* net, const char* clientID); + +void MQTTPacket_free_packet(MQTTPacket* pack); + +#if !defined(NO_BRIDGE) + #include "MQTTPacketOut.h" +#endif + +#endif /* MQTTPACKET_H */ diff --git a/Sources/paho/include/MQTTPacketOut.h b/Sources/paho/include/MQTTPacketOut.h new file mode 100644 index 0000000..700db77 --- /dev/null +++ b/Sources/paho/include/MQTTPacketOut.h @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates + * Ian Craggs - MQTT 3.1.1 support + *******************************************************************************/ + +#if !defined(MQTTPACKETOUT_H) +#define MQTTPACKETOUT_H + +#include "MQTTPacket.h" + +int MQTTPacket_send_connect(Clients* client, int MQTTVersion); +void* MQTTPacket_connack(unsigned char aHeader, char* data, size_t datalen); + +int MQTTPacket_send_pingreq(networkHandles* net, const char* clientID); + +int MQTTPacket_send_subscribe(List* topics, List* qoss, int msgid, int dup, networkHandles* net, const char* clientID); +void* MQTTPacket_suback(unsigned char aHeader, char* data, size_t datalen); + +int MQTTPacket_send_unsubscribe(List* topics, int msgid, int dup, networkHandles* net, const char* clientID); + +#endif diff --git a/Sources/paho/include/MQTTPersistence.h b/Sources/paho/include/MQTTPersistence.h new file mode 100644 index 0000000..9a938ba --- /dev/null +++ b/Sources/paho/include/MQTTPersistence.h @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - async client updates + * Ian Craggs - fix for bug 432903 - queue persistence + *******************************************************************************/ + +#if defined(__cplusplus) + extern "C" { +#endif + +#include "Clients.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 an async client command */ +#define PERSISTENCE_COMMAND_KEY "c-" +/** Stem of the key for an async client message queue */ +#define PERSISTENCE_QUEUE_KEY "q-" +#define PERSISTENCE_MAX_KEY_LENGTH 8 + +int MQTTPersistence_create(MQTTClient_persistence** per, int type, void* pcontext); +int MQTTPersistence_initialize(Clients* c, const char* serverURI); +int MQTTPersistence_close(Clients* c); +int MQTTPersistence_clear(Clients* c); +int MQTTPersistence_restore(Clients* c); +void* MQTTPersistence_restorePacket(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 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; +} 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 diff --git a/Sources/paho/include/MQTTPersistenceDefault.h b/Sources/paho/include/MQTTPersistenceDefault.h new file mode 100644 index 0000000..27fedd6 --- /dev/null +++ b/Sources/paho/include/MQTTPersistenceDefault.h @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +/** 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); + diff --git a/Sources/paho/include/MQTTProtocol.h b/Sources/paho/include/MQTTProtocol.h new file mode 100644 index 0000000..7478103 --- /dev/null +++ b/Sources/paho/include/MQTTProtocol.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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - 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 diff --git a/Sources/paho/include/MQTTProtocolClient.h b/Sources/paho/include/MQTTProtocolClient.h new file mode 100644 index 0000000..a805a51 --- /dev/null +++ b/Sources/paho/include/MQTTProtocolClient.h @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates + * Ian Craggs - MQTT 3.1.1 updates + * Rong Xiang, Ian Craggs - C++ compatibility + *******************************************************************************/ + +#if !defined(MQTTPROTOCOLCLIENT_H) +#define MQTTPROTOCOLCLIENT_H + +#include "LinkedList.h" +#include "MQTTPacket.h" +#include "Log.h" +#include "MQTTProtocol.h" +#include "Messages.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); +Publications* MQTTProtocol_storePublication(Publish* publish, int* len); +int messageIDCompare(void* a, void* b); +int MQTTProtocol_assignMsgId(Clients* client); +void MQTTProtocol_removePublication(Publications* p); + +int MQTTProtocol_handlePublishes(void* pack, int sock); +int MQTTProtocol_handlePubacks(void* pack, int sock); +int MQTTProtocol_handlePubrecs(void* pack, int sock); +int MQTTProtocol_handlePubrels(void* pack, int sock); +int MQTTProtocol_handlePubcomps(void* pack, int sock); + +void MQTTProtocol_keepalive(time_t); +void MQTTProtocol_retry(time_t, 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); +#endif diff --git a/Sources/paho/include/MQTTProtocolOut.h b/Sources/paho/include/MQTTProtocolOut.h new file mode 100644 index 0000000..7cc9a3a --- /dev/null +++ b/Sources/paho/include/MQTTProtocolOut.h @@ -0,0 +1,44 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates + * Ian Craggs - MQTT 3.1.1 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 + +void MQTTProtocol_reconnect(const char* ip_address, Clients* client); +#if defined(OPENSSL) +int MQTTProtocol_connect(const char* ip_address, Clients* acClients, int ssl, int MQTTVersion); +#else +int MQTTProtocol_connect(const char* ip_address, Clients* acClients, int MQTTVersion); +#endif +int MQTTProtocol_handlePingresps(void* pack, int sock); +int MQTTProtocol_subscribe(Clients* client, List* topics, List* qoss, int msgID); +int MQTTProtocol_handleSubacks(void* pack, int sock); +int MQTTProtocol_unsubscribe(Clients* client, List* topics, int msgID); +int MQTTProtocol_handleUnsubacks(void* pack, int sock); + +#endif diff --git a/Sources/paho/include/Messages.h b/Sources/paho/include/Messages.h new file mode 100644 index 0000000..e64b68f --- /dev/null +++ b/Sources/paho/include/Messages.h @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#if !defined(MESSAGES_H) +#define MESSAGES_H + +char* Messages_get(int, int); + +#endif diff --git a/Sources/paho/include/SSLSocket.h b/Sources/paho/include/SSLSocket.h new file mode 100644 index 0000000..465361f --- /dev/null +++ b/Sources/paho/include/SSLSocket.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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs, Allan Stockdill-Mander - initial implementation + *******************************************************************************/ +#if !defined(SSLSOCKET_H) +#define SSLSOCKET_H + +#if defined(WIN32) || defined(WIN64) + #define ssl_mutex_type HANDLE +#else + #include + #include + #define ssl_mutex_type pthread_mutex_t +#endif + +#include +#include "SocketBuffer.h" +#include "Clients.h" + +#define URI_SSL "ssl://" + +int SSLSocket_initialize(); +void SSLSocket_terminate(); +int SSLSocket_setSocketForSSL(networkHandles* net, MQTTClient_SSLOptions* opts); +int SSLSocket_getch(SSL* ssl, int socket, char* c); +char *SSLSocket_getdata(SSL* ssl, int socket, int bytes, int* actual_len); + +int SSLSocket_close(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 socket); + +int SSLSocket_getPendingRead(); +int SSLSocket_continueWrite(pending_writes* pw); + +#endif diff --git a/Sources/paho/include/Socket.h b/Sources/paho/include/Socket.h new file mode 100644 index 0000000..b991fe2 --- /dev/null +++ b/Sources/paho/include/Socket.h @@ -0,0 +1,131 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial implementation and documentation + * Ian Craggs - async client updates + *******************************************************************************/ + +#if !defined(SOCKET_H) +#define SOCKET_H + +#include + +#if defined(WIN32) || defined(WIN64) +#include +#include +#define MAXHOSTNAMELEN 256 +#if !defined(SSLSOCKET_H) +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#define ETIMEDOUT WAIT_TIMEOUT +#endif +#define ioctl ioctlsocket +#define socklen_t int +#else +#define INVALID_SOCKET SOCKET_ERROR +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +/** 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); +int Socket_getch(int socket, char* c); +char *Socket_getdata(int socket, int bytes, int* 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); +int Socket_new(char* addr, int port, int* socket); + +int Socket_noPendingWrites(int socket); +char* Socket_getpeer(int sock); + +void Socket_addPendingWrite(int socket); +void Socket_clearPendingWrite(int socket); + +typedef void Socket_writeComplete(int socket); +void Socket_setWriteCompleteCallback(Socket_writeComplete*); + +#endif /* SOCKET_H */ diff --git a/Sources/paho/include/SocketBuffer.h b/Sources/paho/include/SocketBuffer.h new file mode 100644 index 0000000..47c3f19 --- /dev/null +++ b/Sources/paho/include/SocketBuffer.h @@ -0,0 +1,82 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates + *******************************************************************************/ + +#if !defined(SOCKETBUFFER_H) +#define SOCKETBUFFER_H + +#if defined(WIN32) || defined(WIN64) +#include +#else +#include +#endif + +#if defined(OPENSSL) +#include +#endif + +#if defined(WIN32) || defined(WIN64) + typedef WSABUF iobuf; +#else + typedef struct iovec iobuf; +#endif + +typedef struct +{ + int socket; + int index, headerlen; + char fixed_header[5]; /**< header plus up to 4 length bytes */ + int buflen, /**< total length of the buffer */ + datalen; /**< current length of data in buf */ + char* buf; +} socket_queue; + +typedef struct +{ + int socket, total, count; +#if defined(OPENSSL) + SSL* ssl; +#endif + unsigned long 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 */ + +void SocketBuffer_initialize(void); +void SocketBuffer_terminate(void); +void SocketBuffer_cleanup(int socket); +char* SocketBuffer_getQueuedData(int socket, int bytes, int* actual_len); +int SocketBuffer_getQueuedChar(int socket, char* c); +void SocketBuffer_interrupted(int socket, int actual_len); +char* SocketBuffer_complete(int socket); +void SocketBuffer_queueChar(int socket, char c); + +#if defined(OPENSSL) +void SocketBuffer_pendingWrite(int socket, SSL* ssl, int count, iobuf* iovecs, int* frees, int total, int bytes); +#else +void SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int* frees, int total, int bytes); +#endif +pending_writes* SocketBuffer_getWrite(int socket); +int SocketBuffer_writeComplete(int socket); +pending_writes* SocketBuffer_updateWrite(int socket, char* topic, char* payload); + +#endif diff --git a/Sources/paho/include/StackTrace.h b/Sources/paho/include/StackTrace.h new file mode 100644 index 0000000..564c98a --- /dev/null +++ b/Sources/paho/include/StackTrace.h @@ -0,0 +1,71 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#ifndef STACKTRACE_H_ +#define STACKTRACE_H_ + +#include +#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__, -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, int trace); +void StackTrace_exit(const char* name, int line, void* return_value, int trace); + +void StackTrace_printStack(FILE* dest); +char* StackTrace_get(thread_id_type); + +#endif /* STACKTRACE_H_ */ diff --git a/Sources/paho/include/Thread.h b/Sources/paho/include/Thread.h new file mode 100644 index 0000000..0485088 --- /dev/null +++ b/Sources/paho/include/Thread.h @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial implementation + * Ian Craggs, Allan Stockdill-Mander - async client updates + * Ian Craggs - fix for bug #420851 + *******************************************************************************/ + +#if !defined(THREAD_H) +#define THREAD_H + +#if defined(WIN32) || defined(WIN64) + #include + #define thread_type HANDLE + #define thread_id_type DWORD + #define thread_return_type DWORD + #define thread_fn LPTHREAD_START_ROUTINE + #define mutex_type HANDLE + #define cond_type HANDLE + #define sem_type HANDLE +#else + #include + #include + #define thread_type pthread_t + #define thread_id_type pthread_t + #define thread_return_type void* + typedef thread_return_type (*thread_fn)(void*); + #define mutex_type pthread_mutex_t* + typedef struct { pthread_cond_t cond; pthread_mutex_t mutex; } cond_type_struct; + typedef cond_type_struct *cond_type; + typedef sem_t *sem_type; + + cond_type Thread_create_cond(); + int Thread_signal_cond(cond_type); + int Thread_wait_cond(cond_type condvar, int timeout); + int Thread_destroy_cond(cond_type); +#endif + +thread_type Thread_start(thread_fn, void*); + +mutex_type Thread_create_mutex(); +int Thread_lock_mutex(mutex_type); +int Thread_unlock_mutex(mutex_type); +void Thread_destroy_mutex(mutex_type); + +thread_id_type Thread_getid(); + +sem_type Thread_create_sem(); +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 diff --git a/Sources/paho/include/Tree.h b/Sources/paho/include/Tree.h new file mode 100644 index 0000000..40a39a3 --- /dev/null +++ b/Sources/paho/include/Tree.h @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial implementation and documentation + *******************************************************************************/ + + +#if !defined(TREE_H) +#define TREE_H + +/*BE +defm defTree(T) // macro to define a tree + +def T concat Node +{ + n32 ptr T concat Node "parent" + n32 ptr T concat Node "left" + n32 ptr T concat Node "right" + n32 ptr T id2str(T) + n32 suppress "size" +} + + +def T concat Tree +{ + struct + { + n32 ptr T concat Node suppress "root" + n32 ptr DATA suppress "compare" + } + struct + { + n32 ptr T concat Node suppress "root" + n32 ptr DATA suppress "compare" + } + n32 dec "count" + n32 dec suppress "size" +} + +endm + +defTree(INT) +defTree(STRING) +defTree(TMP) + +BE*/ + +/** + * Structure to hold all data for one list element + */ +typedef struct NodeStruct +{ + struct NodeStruct *parent, /**< pointer to parent tree node, in case we need it */ + *child[2]; /**< pointers to child tree nodes 0 = left, 1 = right */ + void* content; /**< pointer to element content */ + int size; /**< size of content */ + unsigned int red : 1; +} Node; + + +/** + * Structure to hold all data for one tree + */ +typedef struct +{ + struct + { + Node *root; /**< root node pointer */ + int (*compare)(void*, void*, int); /**< comparison function */ + } index[2]; + int indexes, /**< no of indexes into tree */ + count, /**< no of items */ + size; /**< heap storage used */ + unsigned int heap_tracking : 1; /**< switch on heap tracking for this tree? */ + unsigned int allow_duplicates : 1; /**< switch to allow duplicate entries */ +} Tree; + + +Tree* TreeInitialize(int(*compare)(void*, void*, int)); +void TreeInitializeNoMalloc(Tree* aTree, int(*compare)(void*, void*, int)); +void TreeAddIndex(Tree* aTree, int(*compare)(void*, void*, int)); + +void* TreeAdd(Tree* aTree, void* content, int size); + +void* TreeRemove(Tree* aTree, void* content); + +void* TreeRemoveKey(Tree* aTree, void* key); +void* TreeRemoveKeyIndex(Tree* aTree, void* key, int index); + +void* TreeRemoveNodeIndex(Tree* aTree, Node* aNode, int index); + +void TreeFree(Tree* aTree); + +Node* TreeFind(Tree* aTree, void* key); +Node* TreeFindIndex(Tree* aTree, void* key, int index); + +Node* TreeNextElement(Tree* aTree, Node* curnode); + +int TreeIntCompare(void* a, void* b, int); +int TreePtrCompare(void* a, void* b, int); +int TreeStringCompare(void* a, void* b, int); + +#endif diff --git a/Sources/paho/include/utf-8.h b/Sources/paho/include/utf-8.h new file mode 100644 index 0000000..b73caea --- /dev/null +++ b/Sources/paho/include/utf-8.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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#if !defined(UTF8_H) +#define UTF8_H + +int UTF8_validate(int len, char* data); +int UTF8_validateString(const char* string); + +#endif diff --git a/Sources/paho/notice.html b/Sources/paho/notice.html new file mode 100644 index 0000000..f19c483 --- /dev/null +++ b/Sources/paho/notice.html @@ -0,0 +1,108 @@ + + + + + +Eclipse Foundation Software User Agreement + + + +

Eclipse Foundation Software User Agreement

+

February 1, 2011

+ +

Usage Of Content

+ +

THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS + (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND + CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE + OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR + NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND + CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE CONTENT.

+ +

Applicable Licenses

+ +

Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 + ("EPL"). A copy of the EPL is provided with this Content and is also available at http://www.eclipse.org/legal/epl-v10.html. + For purposes of the EPL, "Program" will mean the Content.

+ +

Content includes, but is not limited to, source code, object code, documentation and other files maintained in the Eclipse Foundation source code + repository ("Repository") in software modules ("Modules") and made available as downloadable archives ("Downloads").

+ +
    +
  • Content may be structured and packaged into modules to facilitate delivering, extending, and upgrading the Content. Typical modules may include plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and features ("Features").
  • +
  • Each Plug-in or Fragment may be packaged as a sub-directory or JAR (Java™ ARchive) in a directory named "plugins".
  • +
  • A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Each Feature may be packaged as a sub-directory in a directory named "features". Within a Feature, files named "feature.xml" may contain a list of the names and version numbers of the Plug-ins + and/or Fragments associated with that Feature.
  • +
  • Features may also include other Features ("Included Features"). Within a Feature, files named "feature.xml" may contain a list of the names and version numbers of Included Features.
  • +
+ +

The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). The terms and conditions governing Features and +Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts and Feature Licenses may be located in any directory of a Download or Module +including, but not limited to the following locations:

+ +
    +
  • The top-level (root) directory
  • +
  • Plug-in and Fragment directories
  • +
  • Inside Plug-ins and Fragments packaged as JARs
  • +
  • Sub-directories of the directory named "src" of certain Plug-ins
  • +
  • Feature directories
  • +
+ +

Note: if a Feature made available by the Eclipse Foundation is installed using the Provisioning Technology (as defined below), you must agree to a license ("Feature Update License") during the +installation process. If the Feature contains Included Features, the Feature Update License should either provide you with the terms and conditions governing the Included Features or +inform you where you can locate them. Feature Update Licenses may be found in the "license" property of files named "feature.properties" found within a Feature. +Such Abouts, Feature Licenses, and Feature Update Licenses contain the terms and conditions (or references to such terms and conditions) that govern your use of the associated Content in +that directory.

+ +

THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. SOME OF THESE +OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):

+ + + +

IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License, or Feature Update License is provided, please +contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.

+ + +

Use of Provisioning Technology

+ +

The Eclipse Foundation makes available provisioning software, examples of which include, but are not limited to, p2 and the Eclipse + Update Manager ("Provisioning Technology") for the purpose of allowing users to install software, documentation, information and/or + other materials (collectively "Installable Software"). This capability is provided with the intent of allowing such users to + install, extend and update Eclipse-based products. Information about packaging Installable Software is available at http://eclipse.org/equinox/p2/repository_packaging.html + ("Specification").

+ +

You may use Provisioning Technology to allow other parties to install Installable Software. You shall be responsible for enabling the + applicable license agreements relating to the Installable Software to be presented to, and accepted by, the users of the Provisioning Technology + in accordance with the Specification. By using Provisioning Technology in such a manner and making it available in accordance with the + Specification, you further acknowledge your agreement to, and the acquisition of all necessary rights to permit the following:

+ +
    +
  1. A series of actions may occur ("Provisioning Process") in which a user may execute the Provisioning Technology + on a machine ("Target Machine") with the intent of installing, extending or updating the functionality of an Eclipse-based + product.
  2. +
  3. During the Provisioning Process, the Provisioning Technology may cause third party Installable Software or a portion thereof to be + accessed and copied to the Target Machine.
  4. +
  5. Pursuant to the Specification, you will provide to the user the terms and conditions that govern the use of the Installable + Software ("Installable Software Agreement") and such Installable Software Agreement shall be accessed from the Target + Machine in accordance with the Specification. Such Installable Software Agreement must inform the user of the terms and conditions that govern + the Installable Software and must solicit acceptance by the end user in the manner prescribed in such Installable Software Agreement. Upon such + indication of agreement by the user, the provisioning Technology will complete installation of the Installable Software.
  6. +
+ +

Cryptography

+ +

Content may contain encryption software. The country in which you are currently may have restrictions on the import, possession, and use, and/or re-export to + another country, of encryption software. BEFORE using any encryption software, please check the country's laws, regulations and policies concerning the import, + possession, or use, and re-export of encryption software, to see if this is permitted.

+ +

Java and all Java-based trademarks are trademarks of Oracle Corporation in the United States, other countries, or both.

+ + diff --git a/Sources/paho/src/Clients.c b/Sources/paho/src/Clients.c new file mode 100644 index 0000000..477d248 --- /dev/null +++ b/Sources/paho/src/Clients.c @@ -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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - add SSL support + *******************************************************************************/ + +/** + * @file + * \brief functions which apply to client structures + * */ + + +#include "Clients.h" + +#include +#include + + +/** + * 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; +} diff --git a/Sources/paho/src/Heap.c b/Sources/paho/src/Heap.c new file mode 100644 index 0000000..70e9872 --- /dev/null +++ b/Sources/paho/src/Heap.c @@ -0,0 +1,535 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - use tree data structure instead of list + *******************************************************************************/ + +/** + * @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" +char* Broker_recordFFDC(char* symptoms); + +#include +#include +#include +#include +#include + +#include "Heap.h" + +#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 */ +static int eyecatcher = 0x88888888; + +/** + * 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 */ + int size; /**< size of the allocated storage */ +} storageElement; + +static Tree heap; /**< Tree that holds the allocation records */ +static char* errmsg = "Memory allocation error"; + +static alloc_func hc_allocator; +static dealloc_func hc_deallocator; + +/* + * Allocator function initialization. + */ +DLLExport void Heap_set_allocator(alloc_func allocator, dealloc_func deallocator) +{ + hc_allocator = allocator; + hc_deallocator = deallocator; +} + +/* + * Allocate memory using the allocator passed in hc_mem_initialize + */ +void* allocate(size_t size) +{ + // We need to store the size to support realloc. + unsigned char *p = (*hc_allocator)(size + sizeof(size_t)); + if( NULL == p ) { + return NULL; + } + *((size_t *)p) = size; + return p + sizeof(size_t); +} + +/* + * Free memory using the deallocator passed in hc_mem_initialize + */ +void deallocate(void* block) +{ + unsigned char* p = (unsigned char*)block-sizeof(size_t); + (*hc_deallocator)(&p); +} + +/* + * Reallocates a block of memory. + * @param size the new size of the item + * @return pointer to the allocated item, or NULL if there was an error + */ +void* reallocate(void* p, size_t size) +{ + void* new_p; + size_t old_size = *((size_t *)p-sizeof(size_t)); + size_t min_size = old_size < size?old_size:size; + + // Don't re-allocate identical sized blocks. + // (We will re-allocate smaller ones to ensure memory + // gets freed) + if( size == old_size) { + return p; + } + + new_p = allocate(size); + if( NULL == new_p ) { + return NULL; + } + + memcpy(new_p, p, min_size ); + + deallocate(p); + + return new_p; +} + +/** + * Round allocation size up to a multiple of the size of an int. Apart from possibly reducing fragmentation, + * on the old v3 gcc compilers I was hitting some weird behaviour, which might have been errors in + * sizeof() used on structures and related to packing. In any case, this fixes that too. + * @param size the size actually needed + * @return the rounded up size + */ +int roundup_(int size) +{ + static int multsize = 4*sizeof(int); + + if (size % multsize != 0) + size += multsize - (size % multsize); + return size; +} + + +/** + * List callback function for comparing storage elements + * @param a pointer to the current content in the tree (storageElement*) + * @param b pointer to the memory to free + * @return boolean indicating whether a and b are equal + */ +int ptrCompare(void* a, void* b, int value) +{ + a = ((storageElement*)a)->ptr; + if (value) + b = ((storageElement*)b)->ptr; + + return (a > b) ? -1 : (a == b) ? 0 : 1; +} + + +void Heap_check(char* string, void* ptr) +{ + return; + /*Node* curnode = NULL; + storageElement* prev, *s = NULL; + + printf("Heap_check start %p\n", ptr); + while ((curnode = TreeNextElement(&heap, curnode)) != NULL) + { + prev = s; + s = (storageElement*)(curnode->content); + + if (prev) + { + if (ptrCompare(s, prev, 1) != -1) + { + printf("%s: heap order error %d %p %p\n", string, ptrCompare(s, prev, 1), prev->ptr, s->ptr); + exit(99); + } + else + printf("%s: heap order good %d %p %p\n", string, ptrCompare(s, prev, 1), prev->ptr, s->ptr); + } + }*/ +} + + +/** + * Allocates a block of memory. A direct replacement for malloc, but keeps track of items + * 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; + int space = sizeof(storageElement); + int filenamelen = strlen(file)+1; + + Thread_lock_mutex(heap_mutex); + size = roundup_(size); + if ((s = (storageElement*)allocate(sizeof(storageElement))) == NULL) + { + Log(LOG_ERROR, 13, errmsg); + return NULL; + } + s->size = size; /* size without eyecatchers */ + if ((s->file = (char*)allocate(filenamelen)) == NULL) + { + Log(LOG_ERROR, 13, errmsg); + deallocate(s); + return NULL; + } + space += filenamelen; + strcpy(s->file, file); + s->line = line; + /* Add space for eyecatcher at each end */ + if ((s->ptr = allocate(size + 2*sizeof(int))) == NULL) + { + Log(LOG_ERROR, 13, errmsg); + deallocate(s->file); + deallocate(s); + return NULL; + } + space += size + 2*sizeof(int); + *(int*)(s->ptr) = eyecatcher; /* start eyecatcher */ + *(int*)(((char*)(s->ptr)) + (sizeof(int) + size)) = eyecatcher; /* end eyecatcher */ + Log(TRACE_MAX, -1, "Allocating %d bytes in heap at file %s line %d ptr %p\n", size, file, line, s->ptr); + TreeAdd(&heap, s, space); + state.current_size += size; + if (state.current_size > state.max_size) + state.max_size = state.current_size; + Thread_unlock_mutex(heap_mutex); + return ((int*)(s->ptr)) + 1; /* skip start eyecatcher */ +} + + +void checkEyecatchers(char* file, int line, void* p, int size) +{ + int *sp = (int*)p; + char *cp = (char*)p; + int us; + static char* msg = "Invalid %s eyecatcher %d in heap item at file %s line %d"; + + if ((us = *--sp) != eyecatcher) + Log(LOG_ERROR, 13, msg, "start", us, file, line); + + cp += size; + if ((us = *(int*)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 + */ +int Internal_heap_unlink(char* file, int line, void* p) +{ + Node* e = NULL; + int rc = 0; + + e = TreeFind(&heap, ((int*)p)-1); + if (e == NULL) + Log(LOG_ERROR, 13, "Failed to remove heap item at file %s line %d", file, line); + else + { + storageElement* s = (storageElement*)(e->content); + Log(TRACE_MAX, -1, "Freeing %d bytes in heap at file %s line %d, heap use now %d bytes\n", + s->size, file, line, state.current_size); + checkEyecatchers(file, line, p, s->size); + //free(s->ptr); + deallocate(s->file); + state.current_size -= s->size; + TreeRemoveNodeIndex(&heap, e, 0); + deallocate(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) +{ + Thread_lock_mutex(heap_mutex); + if (Internal_heap_unlink(file, line, p)) + deallocate(((int*)p)-1); + Thread_unlock_mutex(heap_mutex); +} + + +/** + * Remove an item from the recorded heap without actually freeing it. + * Use sparingly! + * @param file use the __FILE__ macro to indicate which file this item was allocated in + * @param line use the __LINE__ macro to indicate which line this item was allocated at + * @param p pointer to the item to be removed + */ +void Heap_unlink(char* file, int line, void* p) +{ + Thread_lock_mutex(heap_mutex); + Internal_heap_unlink(file, line, p); + 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 = (storageElement*)(TreeRemoveKey(&heap, ((int*)p)-1)); + if (s == NULL) + Log(LOG_ERROR, 13, "Failed to reallocate heap item at file %s line %d", file, line); + else + { + int space = sizeof(storageElement); + int filenamelen = strlen(file)+1; + + checkEyecatchers(file, line, p, s->size); + size = roundup_(size); + state.current_size += size - s->size; + if (state.current_size > state.max_size) + state.max_size = state.current_size; + if ((s->ptr = reallocate(s->ptr, size + 2*sizeof(int))) == NULL) + { + Log(LOG_ERROR, 13, errmsg); + return NULL; + } + space += size + 2*sizeof(int) - s->size; + *(int*)(s->ptr) = eyecatcher; /* start eyecatcher */ + *(int*)(((char*)(s->ptr)) + (sizeof(int) + size)) = eyecatcher; /* end eyecatcher */ + s->size = size; + space -= strlen(s->file); + s->file = (char*)(reallocate(s->file, filenamelen)); + space += filenamelen; + strcpy(s->file, file); + s->line = line; + rc = s->ptr; + TreeAdd(&heap, s, space); + } + Thread_unlock_mutex(heap_mutex); + return (rc == NULL) ? NULL : ((int*)(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, ((int*)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. + */ +void HeapScan(int log_level) +{ + Node* current = NULL; + + Thread_lock_mutex(heap_mutex); + Log(log_level, -1, "Heap scan start, total %d bytes", state.current_size); + while ((current = TreeNextElement(&heap, current)) != NULL) + { + storageElement* s = (storageElement*)(current->content); + Log(log_level, -1, "Heap element size %d, line %d, file %s, ptr %p", s->size, s->line, s->file, s->ptr); + Log(log_level, -1, " Content %*.s", (10 > current->size) ? s->size : 10, (char*)(((int*)s->ptr) + 1)); + } + Log(log_level, -1, "Heap scan end"); + Thread_unlock_mutex(heap_mutex); +} + + +/** + * Heap initialization. + */ +int Heap_initialize() +{ + TreeInitializeNoMalloc(&heap, ptrCompare); + heap.heap_tracking = 0; /* no recursive heap tracking! */ + return 0; +} + + +/** + * Heap termination. + */ +void Heap_terminate() +{ + Log(TRACE_MIN, -1, "Maximum heap use was %d bytes", 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() +{ + 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; + int 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; +} + + +#if defined(HEAP_UNIT_TESTS) + +void Log(int 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 diff --git a/Sources/paho/src/LinkedList.c b/Sources/paho/src/LinkedList.c new file mode 100644 index 0000000..7efdae6 --- /dev/null +++ b/Sources/paho/src/LinkedList.c @@ -0,0 +1,497 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - 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 +#include +#include + +#include "Heap.h" + + +/** + * 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)); + /*newl->first = NULL; + newl->last = NULL; + newl->current = NULL; + newl->count = newl->size = 0;*/ +} + + +/** + * Allocates and initializes a new list structure. + * @return a pointer to the new list structure + */ +List* ListInitialize(void) +{ + List* newl = malloc(sizeof(List)); + 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, int 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 + */ +void ListAppend(List* aList, void* content, int size) +{ + ListElement* newel = malloc(sizeof(ListElement)); + ListAppendNoMalloc(aList, content, newel, size); +} + + +/** + * 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. + */ +void ListInsert(List* aList, void* content, int size, ListElement* index) +{ + ListElement* newel = malloc(sizeof(ListElement)); + + 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; + } +} + + +/** + * 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 + */ +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); + 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); + aList->first = first->next; + free(first); + } + aList->count = 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 + + + + + diff --git a/Sources/paho/src/Log.c b/Sources/paho/src/Log.c new file mode 100644 index 0000000..3058e68 --- /dev/null +++ b/Sources/paho/src/Log.c @@ -0,0 +1,561 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - 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 +#include +#include +#include +#include + +#if !defined(WIN32) && !defined(WIN64) +#include +#include +#define GETTIMEOFDAY 1 +#else +#define snprintf _snprintf +#endif + +#if defined(GETTIMEOFDAY) + #include +#else + #include +#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, + -1 +}; + +#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; + int 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 int trace_output_level = -1; +static Log_traceCallback* trace_callback = NULL; +static void Log_output(int log_level, char* msg); + +static int sametime_count = 0; +#if defined(GETTIMEOFDAY) +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 = -1; + char* envval = NULL; + + if ((trace_queue = malloc(sizeof(traceEntry) * trace_settings.max_trace_entries)) == NULL) + return rc; + 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 + { + trace_destination_name = malloc(strlen(envval) + 1); + strcpy(trace_destination_name, envval); + trace_destination_backup_name = malloc(strlen(envval) + 3); + sprintf(trace_destination_backup_name, "%s.0", trace_destination_name); + } + } + if ((envval = getenv("MQTT_C_CLIENT_TRACE_MAX_LINES")) != NULL && strlen(envval) > 0) + { + max_lines_per_file = atoi(envval); + if (max_lines_per_file <= 0) + max_lines_per_file = 1000; + } + if ((envval = getenv("MQTT_C_CLIENT_TRACE_LEVEL")) != NULL && strlen(envval) > 0) + { + 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_MEDIUM") == 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) + struct stat buf; + if (stat("/proc/version", &buf) != -1) + { + FILE* vfile; + + if ((vfile = fopen("/proc/version", "r")) != NULL) + { + int len; + + strcpy(msg_buf, "/proc/version: "); + len = strlen(msg_buf); + if (fgets(&msg_buf[len], sizeof(msg_buf) - len, vfile)) + Log_output(TRACE_MINIMUM, msg_buf); + fclose(vfile); + } + } +#endif + Log_output(TRACE_MINIMUM, "========================================================="); + + return rc; +} + + +void Log_setTraceCallback(Log_traceCallback* callback) +{ + trace_callback = callback; +} + + +void Log_setTraceLevel(enum LOG_LEVELS level) +{ + if (level < TRACE_MINIMUM) /* the lowest we can go is TRACE_MINIMUM*/ + trace_settings.trace_level = level; + trace_output_level = level; +} + + +void Log_terminate() +{ + free(trace_queue); + 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); + if (trace_destination_backup_name) + free(trace_destination_backup_name); + start_index = -1; + next_index = 0; + trace_output_level = -1; + sametime_count = 0; +} + + +static traceEntry* Log_pretrace() +{ + 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); + + 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; + + return cur_entry; +} + + +static char* Log_formatTraceEntry(traceEntry* cur_entry) +{ + struct tm *timeinfo; + int buf_pos = 31; + +#if defined(GETTIMEOFDAY) + timeinfo = localtime(&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 + { + 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(int log_level, 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(int 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(int log_level, 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(int log_level, int msgno, char* format, ...) +{ + if (log_level >= trace_settings.trace_level) + { + char* temp = NULL; + static char msg_buf[512]; + 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); + } + + /*if (log_level >= LOG_ERROR) + { + char* filename = NULL; + Log_recordFFDC(&msg_buf[7]); + } + */ +} + + +/** + * 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(int 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); +} + + +FILE* Log_destToFile(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; +} + + +int Log_compareEntries(char* entry1, 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; +} + + +#if 0 +/** + * 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 + + diff --git a/Sources/paho/src/MQTTAsync.c b/Sources/paho/src/MQTTAsync.c new file mode 100644 index 0000000..cafadfc --- /dev/null +++ b/Sources/paho/src/MQTTAsync.c @@ -0,0 +1,2908 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial implementation and documentation + * Ian Craggs, Allan Stockdill-Mander - SSL support + * Ian Craggs - multiple server connection support + * Ian Craggs - fix for bug 413429 - connectionLost not called + * Ian Craggs - fix for bug 415042 - using already freed structure + * Ian Craggs - fix for bug 419233 - mutexes not reporting errors + * Ian Craggs - fix for bug 420851 + * Ian Craggs - fix for bug 432903 - queue persistence + * Ian Craggs - MQTT 3.1.1 support + * Rong Xiang, Ian Craggs - C++ compatibility + * Ian Craggs - fix for bug 442400: reconnecting after network cable unplugged + * Ian Craggs - fix for bug 444934 - incorrect free in freeCommand1 + * Ian Craggs - fix for bug 445891 - assigning msgid is not thread safe + *******************************************************************************/ + +/** + * @file + * \brief Asynchronous API implementation + * + */ + +#define _GNU_SOURCE /* for pthread_mutexattr_settype */ +#include +#if !defined(WIN32) && !defined(WIN64) + #include +#endif + +#if !defined(NO_PERSISTENCE) +#include "MQTTPersistence.h" +#endif +#include "MQTTAsync.h" +#include "utf-8.h" +#include "MQTTProtocol.h" +#include "MQTTProtocolOut.h" +#include "Thread.h" +#include "SocketBuffer.h" +#include "StackTrace.h" +#include "Heap.h" + +#define URI_TCP "tcp://" + +#define BUILD_TIMESTAMP "##MQTTCLIENT_BUILD_TAG##" +#define CLIENT_VERSION "##MQTTCLIENT_VERSION_TAG##" + +char* client_timestamp_eye = "MQTTAsyncV3_Timestamp " BUILD_TIMESTAMP; +char* client_version_eye = "MQTTAsyncV3_Version " CLIENT_VERSION; + +extern Sockets s; + +static ClientStates ClientState = +{ + CLIENT_VERSION, /* version */ + NULL /* client list */ +}; + +ClientStates* bstate = &ClientState; + +MQTTProtocol state; + +enum MQTTAsync_threadStates +{ + STOPPED, STARTING, RUNNING, STOPPING +}; + +enum MQTTAsync_threadStates sendThread_state = STOPPED; +enum MQTTAsync_threadStates receiveThread_state = STOPPED; +static thread_id_type sendThread_id = 0, + receiveThread_id = 0; + +#if defined(WIN32) || defined(WIN64) +static mutex_type mqttasync_mutex = NULL; +static mutex_type mqttcommand_mutex = NULL; +static sem_type send_sem = NULL; +extern mutex_type stack_mutex; +extern mutex_type heap_mutex; +extern mutex_type log_mutex; +BOOL APIENTRY DllMain(HANDLE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + Log(TRACE_MAX, -1, "DLL process attach"); + if (mqttasync_mutex == NULL) + { + mqttasync_mutex = CreateMutex(NULL, 0, NULL); + mqttcommand_mutex = CreateMutex(NULL, 0, NULL); + send_sem = CreateEvent( + NULL, /* default security attributes */ + FALSE, /* manual-reset event? */ + FALSE, /* initial state is nonsignaled */ + NULL /* object name */ + ); + stack_mutex = CreateMutex(NULL, 0, NULL); + heap_mutex = CreateMutex(NULL, 0, NULL); + log_mutex = CreateMutex(NULL, 0, NULL); + } + case DLL_THREAD_ATTACH: + Log(TRACE_MAX, -1, "DLL thread attach"); + case DLL_THREAD_DETACH: + Log(TRACE_MAX, -1, "DLL thread detach"); + case DLL_PROCESS_DETACH: + Log(TRACE_MAX, -1, "DLL process detach"); + } + return TRUE; +} +#else +static pthread_mutex_t mqttasync_mutex_store = PTHREAD_MUTEX_INITIALIZER; +static mutex_type mqttasync_mutex = &mqttasync_mutex_store; +static pthread_mutex_t mqttcommand_mutex_store = PTHREAD_MUTEX_INITIALIZER; +static mutex_type mqttcommand_mutex = &mqttcommand_mutex_store; +static cond_type_struct send_cond_store = { PTHREAD_COND_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }; +static cond_type send_cond = &send_cond_store; + +void MQTTAsync_init() +{ + pthread_mutexattr_t attr; + int rc; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + if ((rc = pthread_mutex_init(mqttasync_mutex, &attr)) != 0) + printf("MQTTAsync: error %d initializing async_mutex\n", rc); + if ((rc = pthread_mutex_init(mqttcommand_mutex, &attr)) != 0) + printf("MQTTAsync: error %d initializing command_mutex\n", rc); +} + +#define WINAPI +#endif + +static volatile int initialized = 0; +static List* handles = NULL; +static int tostop = 0; +static List* commands = NULL; + +MQTTPacket* MQTTAsync_cycle(int* sock, unsigned long timeout, int* rc); +int MQTTAsync_cleanSession(Clients* client); +void MQTTAsync_stop(); +int MQTTAsync_disconnect_internal(MQTTAsync handle, int timeout); +void MQTTAsync_closeOnly(Clients* client); +void MQTTAsync_closeSession(Clients* client); +void MQTTProtocol_closeSession(Clients* client, int sendwill); +void MQTTAsync_writeComplete(int socket); + +#if defined(WIN32) || defined(WIN64) +#define START_TIME_TYPE DWORD +START_TIME_TYPE MQTTAsync_start_clock(void) +{ + return GetTickCount(); +} +#elif defined(AIX) +#define START_TIME_TYPE struct timespec +START_TIME_TYPE MQTTAsync_start_clock(void) +{ + static struct timespec start; + clock_gettime(CLOCK_REALTIME, &start); + return start; +} +#else +#define START_TIME_TYPE struct timeval +START_TIME_TYPE MQTTAsync_start_clock(void) +{ + static struct timeval start; + gettimeofday(&start, NULL); + return start; +} +#endif + + +#if defined(WIN32) || defined(WIN64) +long MQTTAsync_elapsed(DWORD milliseconds) +{ + return GetTickCount() - milliseconds; +} +#elif defined(AIX) +#define assert(a) +long MQTTAsync_elapsed(struct timespec start) +{ + struct timespec now, res; + + clock_gettime(CLOCK_REALTIME, &now); + ntimersub(now, start, res); + return (res.tv_sec)*1000L + (res.tv_nsec)/1000000L; +} +#else +long MQTTAsync_elapsed(struct timeval start) +{ + struct timeval now, res; + + gettimeofday(&now, NULL); + timersub(&now, &start, &res); + return (res.tv_sec)*1000 + (res.tv_usec)/1000; +} +#endif + + +typedef struct +{ + MQTTAsync_message* msg; + char* topicName; + int topicLen; + unsigned int seqno; /* only used on restore */ +} qEntry; + +typedef struct +{ + int type; + MQTTAsync_onSuccess* onSuccess; + MQTTAsync_onFailure* onFailure; + MQTTAsync_token token; + void* context; + START_TIME_TYPE start_time; + union + { + struct + { + int count; + char** topics; + int* qoss; + } sub; + struct + { + int count; + char** topics; + } unsub; + struct + { + char* destinationName; + int payloadlen; + void* payload; + int qos; + int retained; + } pub; + struct + { + int internal; + int timeout; + } dis; + struct + { + int timeout; + int serverURIcount; + char** serverURIs; + int currentURI; + int MQTTVersion; /**< current MQTT version being used to connect */ + } conn; + } details; +} MQTTAsync_command; + + +typedef struct MQTTAsync_struct +{ + char* serverURI; + int ssl; + Clients* c; + + /* "Global", to the client, callback definitions */ + MQTTAsync_connectionLost* cl; + MQTTAsync_messageArrived* ma; + MQTTAsync_deliveryComplete* dc; + void* context; /* the context to be associated with the main callbacks*/ + + MQTTAsync_command connect; /* Connect operation properties */ + MQTTAsync_command disconnect; /* Disconnect operation properties */ + MQTTAsync_command* pending_write; /* Is there a socket write pending? */ + + List* responses; + unsigned int command_seqno; + + MQTTPacket* pack; + +} MQTTAsyncs; + + +typedef struct +{ + MQTTAsync_command command; + MQTTAsyncs* client; + unsigned int seqno; /* only used on restore */ +} MQTTAsync_queuedCommand; + +void MQTTAsync_freeCommand(MQTTAsync_queuedCommand *command); +void MQTTAsync_freeCommand1(MQTTAsync_queuedCommand *command); +int MQTTAsync_deliverMessage(MQTTAsyncs* m, char* topicName, size_t topicLen, MQTTAsync_message* mm); +#if !defined(NO_PERSISTENCE) +int MQTTAsync_restoreCommands(MQTTAsyncs* client); +#endif + +void MQTTAsync_sleep(long milliseconds) +{ + FUNC_ENTRY; +#if defined(WIN32) || defined(WIN64) + Sleep(milliseconds); +#else + usleep(milliseconds*1000); +#endif + FUNC_EXIT; +} + + +/** + * List callback function for comparing clients by socket + * @param a first integer value + * @param b second integer value + * @return boolean indicating whether a and b are equal + */ +int clientSockCompare(void* a, void* b) +{ + MQTTAsyncs* m = (MQTTAsyncs*)a; + return m->c->net.socket == *(int*)b; +} + + +void MQTTAsync_lock_mutex(mutex_type amutex) +{ + int rc = Thread_lock_mutex(amutex); + if (rc != 0) + Log(LOG_ERROR, 0, "Error %s locking mutex", strerror(rc)); +} + + +void MQTTAsync_unlock_mutex(mutex_type amutex) +{ + int rc = Thread_unlock_mutex(amutex); + if (rc != 0) + Log(LOG_ERROR, 0, "Error %s unlocking mutex", strerror(rc)); +} + + +int MQTTAsync_checkConn(MQTTAsync_command* command, MQTTAsyncs* client) +{ + int rc; + + FUNC_ENTRY; + rc = command->details.conn.currentURI < command->details.conn.serverURIcount || + (command->details.conn.MQTTVersion == 4 && client->c->MQTTVersion == MQTTVERSION_DEFAULT); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_create(MQTTAsync* handle, const char* serverURI, const char* clientId, + int persistence_type, void* persistence_context) +{ + int rc = 0; + MQTTAsyncs *m = NULL; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (serverURI == NULL || clientId == NULL) + { + rc = MQTTASYNC_NULL_PARAMETER; + goto exit; + } + + if (!UTF8_validateString(clientId)) + { + rc = MQTTASYNC_BAD_UTF8_STRING; + goto exit; + } + + if (!initialized) + { + #if defined(HEAP_H) + Heap_initialize(); + #endif + Log_initialize((Log_nameValue*)MQTTAsync_getVersionInfo()); + bstate->clients = ListInitialize(); + Socket_outInitialize(); + Socket_setWriteCompleteCallback(MQTTAsync_writeComplete); + handles = ListInitialize(); + commands = ListInitialize(); +#if defined(OPENSSL) + SSLSocket_initialize(); +#endif + initialized = 1; + } + m = malloc(sizeof(MQTTAsyncs)); + *handle = m; + memset(m, '\0', sizeof(MQTTAsyncs)); + if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0) + serverURI += strlen(URI_TCP); +#if defined(OPENSSL) + else if (strncmp(URI_SSL, serverURI, strlen(URI_SSL)) == 0) + { + serverURI += strlen(URI_SSL); + m->ssl = 1; + } +#endif + m->serverURI = MQTTStrdup(serverURI); + m->responses = ListInitialize(); + ListAppend(handles, m, sizeof(MQTTAsyncs)); + + m->c = malloc(sizeof(Clients)); + memset(m->c, '\0', sizeof(Clients)); + m->c->context = m; + m->c->outboundMsgs = ListInitialize(); + m->c->inboundMsgs = ListInitialize(); + m->c->messageQueue = ListInitialize(); + m->c->clientID = MQTTStrdup(clientId); + +#if !defined(NO_PERSISTENCE) + rc = MQTTPersistence_create(&(m->c->persistence), persistence_type, persistence_context); + if (rc == 0) + { + rc = MQTTPersistence_initialize(m->c, m->serverURI); + if (rc == 0) + { + MQTTAsync_restoreCommands(m); + MQTTPersistence_restoreMessageQueue(m->c); + } + } +#endif + ListAppend(bstate->clients, m->c, sizeof(Clients) + 3*sizeof(List)); + +exit: + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTAsync_terminate(void) +{ + FUNC_ENTRY; + MQTTAsync_stop(); + if (initialized) + { + ListElement* elem = NULL; + ListFree(bstate->clients); + ListFree(handles); + while (ListNextElement(commands, &elem)) + MQTTAsync_freeCommand1((MQTTAsync_queuedCommand*)(elem->content)); + ListFree(commands); + handles = NULL; + Socket_outTerminate(); +#if defined(OPENSSL) + SSLSocket_terminate(); +#endif + #if defined(HEAP_H) + Heap_terminate(); + #endif + Log_terminate(); + initialized = 0; + } + FUNC_EXIT; +} + + +#if !defined(NO_PERSISTENCE) +int MQTTAsync_unpersistCommand(MQTTAsync_queuedCommand* qcmd) +{ + int rc = 0; + char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; + + FUNC_ENTRY; + sprintf(key, "%s%d", PERSISTENCE_COMMAND_KEY, qcmd->seqno); + if ((rc = qcmd->client->c->persistence->premove(qcmd->client->c->phandle, key)) != 0) + Log(LOG_ERROR, 0, "Error %d removing command from persistence", rc); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_persistCommand(MQTTAsync_queuedCommand* qcmd) +{ + int rc = 0; + MQTTAsyncs* aclient = qcmd->client; + MQTTAsync_command* command = &qcmd->command; + int* lens = NULL; + void** bufs = NULL; + int bufindex = 0, i, nbufs = 0; + char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; + + FUNC_ENTRY; + switch (command->type) + { + case SUBSCRIBE: + nbufs = 3 + (command->details.sub.count * 2); + + lens = (int*)malloc(nbufs * sizeof(int)); + bufs = malloc(nbufs * sizeof(char *)); + + bufs[bufindex] = &command->type; + lens[bufindex++] = sizeof(command->type); + + bufs[bufindex] = &command->token; + lens[bufindex++] = sizeof(command->token); + + bufs[bufindex] = &command->details.sub.count; + lens[bufindex++] = sizeof(command->details.sub.count); + + for (i = 0; i < command->details.sub.count; ++i) + { + bufs[bufindex] = command->details.sub.topics[i]; + lens[bufindex++] = strlen(command->details.sub.topics[i]) + 1; + bufs[bufindex] = &command->details.sub.qoss[i]; + lens[bufindex++] = sizeof(command->details.sub.qoss[i]); + } + sprintf(key, "%s%d", PERSISTENCE_COMMAND_KEY, ++aclient->command_seqno); + break; + + case UNSUBSCRIBE: + nbufs = 3 + command->details.unsub.count; + + lens = (int*)malloc(nbufs * sizeof(int)); + bufs = malloc(nbufs * sizeof(char *)); + + bufs[bufindex] = &command->type; + lens[bufindex++] = sizeof(command->type); + + bufs[bufindex] = &command->token; + lens[bufindex++] = sizeof(command->token); + + bufs[bufindex] = &command->details.unsub.count; + lens[bufindex++] = sizeof(command->details.unsub.count); + + for (i = 0; i < command->details.unsub.count; ++i) + { + bufs[bufindex] = command->details.unsub.topics[i]; + lens[bufindex++] = strlen(command->details.unsub.topics[i]) + 1; + } + sprintf(key, "%s%d", PERSISTENCE_COMMAND_KEY, ++aclient->command_seqno); + break; + + case PUBLISH: + nbufs = 7; + + lens = (int*)malloc(nbufs * sizeof(int)); + bufs = malloc(nbufs * sizeof(char *)); + + bufs[bufindex] = &command->type; + lens[bufindex++] = sizeof(command->type); + + bufs[bufindex] = &command->token; + lens[bufindex++] = sizeof(command->token); + + bufs[bufindex] = command->details.pub.destinationName; + lens[bufindex++] = strlen(command->details.pub.destinationName) + 1; + + bufs[bufindex] = &command->details.pub.payloadlen; + lens[bufindex++] = sizeof(command->details.pub.payloadlen); + + bufs[bufindex] = command->details.pub.payload; + lens[bufindex++] = command->details.pub.payloadlen; + + bufs[bufindex] = &command->details.pub.qos; + lens[bufindex++] = sizeof(command->details.pub.qos); + + bufs[bufindex] = &command->details.pub.retained; + lens[bufindex++] = sizeof(command->details.pub.retained); + + sprintf(key, "%s%d", PERSISTENCE_COMMAND_KEY, ++aclient->command_seqno); + break; + } + if (nbufs > 0) + { + if ((rc = aclient->c->persistence->pput(aclient->c->phandle, key, nbufs, (char**)bufs, lens)) != 0) + Log(LOG_ERROR, 0, "Error persisting command, rc %d", rc); + qcmd->seqno = aclient->command_seqno; + } + if (lens) + free(lens); + if (bufs) + free(bufs); + FUNC_EXIT_RC(rc); + return rc; +} + + +MQTTAsync_queuedCommand* MQTTAsync_restoreCommand(char* buffer, int buflen) +{ + MQTTAsync_command* command = NULL; + MQTTAsync_queuedCommand* qcommand = NULL; + char* ptr = buffer; + int i, data_size; + + FUNC_ENTRY; + qcommand = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(qcommand, '\0', sizeof(MQTTAsync_queuedCommand)); + command = &qcommand->command; + + command->type = *(int*)ptr; + ptr += sizeof(int); + + command->token = *(MQTTAsync_token*)ptr; + ptr += sizeof(MQTTAsync_token); + + switch (command->type) + { + case SUBSCRIBE: + command->details.sub.count = *(int*)ptr; + ptr += sizeof(int); + + for (i = 0; i < command->details.sub.count; ++i) + { + data_size = strlen(ptr) + 1; + + command->details.sub.topics[i] = malloc(data_size); + strcpy(command->details.sub.topics[i], ptr); + ptr += data_size; + + command->details.sub.qoss[i] = *(int*)ptr; + ptr += sizeof(int); + } + break; + + case UNSUBSCRIBE: + command->details.sub.count = *(int*)ptr; + ptr += sizeof(int); + + for (i = 0; i < command->details.unsub.count; ++i) + { + int data_size = strlen(ptr) + 1; + + command->details.unsub.topics[i] = malloc(data_size); + strcpy(command->details.unsub.topics[i], ptr); + ptr += data_size; + } + break; + + case PUBLISH: + data_size = strlen(ptr) + 1; + command->details.pub.destinationName = malloc(data_size); + strcpy(command->details.pub.destinationName, ptr); + ptr += data_size; + + command->details.pub.payloadlen = *(int*)ptr; + ptr += sizeof(int); + + data_size = command->details.pub.payloadlen; + command->details.pub.payload = malloc(data_size); + memcpy(command->details.pub.payload, ptr, data_size); + ptr += data_size; + + command->details.pub.qos = *(int*)ptr; + ptr += sizeof(int); + + command->details.pub.retained = *(int*)ptr; + ptr += sizeof(int); + break; + + default: + free(qcommand); + qcommand = NULL; + + } + + FUNC_EXIT; + return qcommand; +} + + +void MQTTAsync_insertInOrder(List* list, void* content, int size) +{ + ListElement* index = NULL; + ListElement* current = NULL; + + FUNC_ENTRY; + while (ListNextElement(list, ¤t) != NULL && index == NULL) + { + if (((MQTTAsync_queuedCommand*)content)->seqno < ((MQTTAsync_queuedCommand*)current->content)->seqno) + index = current; + } + + ListInsert(list, content, size, index); + FUNC_EXIT; +} + + +int MQTTAsync_restoreCommands(MQTTAsyncs* client) +{ + int rc = 0; + char **msgkeys; + int nkeys; + int i = 0; + Clients* c = client->c; + int commands_restored = 0; + + FUNC_ENTRY; + if (c->persistence && (rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0) + { + while (rc == 0 && i < nkeys) + { + char *buffer = NULL; + int buflen; + + if (strncmp(msgkeys[i], PERSISTENCE_COMMAND_KEY, strlen(PERSISTENCE_COMMAND_KEY)) != 0) + ; + else if ((rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0) + { + MQTTAsync_queuedCommand* cmd = MQTTAsync_restoreCommand(buffer, buflen); + + if (cmd) + { + cmd->client = client; + cmd->seqno = atoi(msgkeys[i]+2); + MQTTPersistence_insertInOrder(commands, cmd, sizeof(MQTTAsync_queuedCommand)); + free(buffer); + client->command_seqno = max(client->command_seqno, cmd->seqno); + commands_restored++; + } + } + if (msgkeys[i]) + free(msgkeys[i]); + i++; + } + if (msgkeys != NULL) + free(msgkeys); + } + Log(TRACE_MINIMUM, -1, "%d commands restored for client %s", commands_restored, c->clientID); + FUNC_EXIT_RC(rc); + return rc; +} +#endif + + +int MQTTAsync_addCommand(MQTTAsync_queuedCommand* command, int command_size) +{ + int rc = 0; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttcommand_mutex); + command->command.start_time = MQTTAsync_start_clock(); + if (command->command.type == CONNECT || + (command->command.type == DISCONNECT && command->command.details.dis.internal)) + { + MQTTAsync_queuedCommand* head = NULL; + + if (commands->first) + head = (MQTTAsync_queuedCommand*)(commands->first->content); + + if (head != NULL && head->client == command->client && head->command.type == command->command.type) + MQTTAsync_freeCommand(command); /* ignore duplicate connect or disconnect command */ + else + ListInsert(commands, command, command_size, commands->first); /* add to the head of the list */ + } + else + { + ListAppend(commands, command, command_size); +#if !defined(NO_PERSISTENCE) + if (command->client->c->persistence) + MQTTAsync_persistCommand(command); +#endif + } + MQTTAsync_unlock_mutex(mqttcommand_mutex); +#if !defined(WIN32) && !defined(WIN64) + Thread_signal_cond(send_cond); +#else + if (!Thread_check_sem(send_sem)) + Thread_post_sem(send_sem); +#endif + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTAsync_checkDisconnect(MQTTAsync handle, MQTTAsync_command* command) +{ + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + /* wait for all inflight message flows to finish, up to timeout */; + if (m->c->outboundMsgs->count == 0 || MQTTAsync_elapsed(command->start_time) >= command->details.dis.timeout) + { + int was_connected = m->c->connected; + MQTTAsync_closeSession(m->c); + if (command->details.dis.internal && m->cl && was_connected) + { + Log(TRACE_MIN, -1, "Calling connectionLost for client %s", m->c->clientID); + (*(m->cl))(m->context, NULL); + } + else if (!command->details.dis.internal && command->onSuccess) + { + Log(TRACE_MIN, -1, "Calling disconnect complete for client %s", m->c->clientID); + (*(command->onSuccess))(command->context, NULL); + } + } + FUNC_EXIT; +} + + +/** + * See if any pending writes have been completed, and cleanup if so. + * Cleaning up means removing any publication data that was stored because the write did + * not originally complete. + */ +void MQTTProtocol_checkPendingWrites() +{ + FUNC_ENTRY; + if (state.pending_writes.count > 0) + { + ListElement* le = state.pending_writes.first; + while (le) + { + if (Socket_noPendingWrites(((pending_write*)(le->content))->socket)) + { + MQTTProtocol_removePublication(((pending_write*)(le->content))->p); + state.pending_writes.current = le; + ListRemove(&(state.pending_writes), le->content); /* does NextElement itself */ + le = state.pending_writes.current; + } + else + ListNextElement(&(state.pending_writes), &le); + } + } + FUNC_EXIT; +} + + +void MQTTAsync_freeConnect(MQTTAsync_command command) +{ + if (command.type == CONNECT) + { + int i; + + for (i = 0; i < command.details.conn.serverURIcount; ++i) + free(command.details.conn.serverURIs[i]); + if (command.details.conn.serverURIs) + free(command.details.conn.serverURIs); + } +} + + +void MQTTAsync_freeCommand1(MQTTAsync_queuedCommand *command) +{ + if (command->command.type == SUBSCRIBE) + { + int i; + + for (i = 0; i < command->command.details.sub.count; i++) + free(command->command.details.sub.topics[i]); + + free(command->command.details.sub.topics); + free(command->command.details.sub.qoss); + } + else if (command->command.type == UNSUBSCRIBE) + { + int i; + + for (i = 0; i < command->command.details.unsub.count; i++) + free(command->command.details.unsub.topics[i]); + + free(command->command.details.unsub.topics); + } + else if (command->command.type == PUBLISH) + { + /* qos 1 and 2 topics are freed in the protocol code when the flows are completed */ + if (command->command.details.pub.destinationName) + free(command->command.details.pub.destinationName); + free(command->command.details.pub.payload); + } +} + +void MQTTAsync_freeCommand(MQTTAsync_queuedCommand *command) +{ + MQTTAsync_freeCommand1(command); + free(command); +} + + +void MQTTAsync_writeComplete(int socket) +{ + ListElement* found = NULL; + + FUNC_ENTRY; + /* a partial write is now complete for a socket - this will be on a publish*/ + + MQTTProtocol_checkPendingWrites(); + + /* find the client using this socket */ + if ((found = ListFindItem(handles, &socket, clientSockCompare)) != NULL) + { + MQTTAsyncs* m = (MQTTAsyncs*)(found->content); + + time(&(m->c->net.lastSent)); + + /* see if there is a pending write flagged */ + if (m->pending_write) + { + ListElement* cur_response = NULL; + MQTTAsync_command* command = m->pending_write; + MQTTAsync_queuedCommand* com = NULL; + + while (ListNextElement(m->responses, &cur_response)) + { + com = (MQTTAsync_queuedCommand*)(cur_response->content); + if (com->client->pending_write == m->pending_write) + break; + } + + if (cur_response && command->onSuccess) + { + MQTTAsync_successData data; + + data.token = command->token; + data.alt.pub.destinationName = command->details.pub.destinationName; + data.alt.pub.message.payload = command->details.pub.payload; + data.alt.pub.message.payloadlen = command->details.pub.payloadlen; + data.alt.pub.message.qos = command->details.pub.qos; + data.alt.pub.message.retained = command->details.pub.retained; + Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); + (*(command->onSuccess))(command->context, &data); + } + m->pending_write = NULL; + + ListDetach(m->responses, com); + MQTTAsync_freeCommand(com); + } + } + FUNC_EXIT; +} + + +void MQTTAsync_processCommand() +{ + int rc = 0; + MQTTAsync_queuedCommand* command = NULL; + ListElement* cur_command = NULL; + List* ignored_clients = NULL; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + MQTTAsync_lock_mutex(mqttcommand_mutex); + + /* only the first command in the list must be processed for any particular client, so if we skip + a command for a client, we must skip all following commands for that client. Use a list of + ignored clients to keep track + */ + ignored_clients = ListInitialize(); + + /* don't try a command until there isn't a pending write for that client, and we are not connecting */ + while (ListNextElement(commands, &cur_command)) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(cur_command->content); + + if (ListFind(ignored_clients, cmd->client)) + continue; + + if (cmd->command.type == CONNECT || cmd->command.type == DISCONNECT || (cmd->client->c->connected && + cmd->client->c->connect_state == 0 && Socket_noPendingWrites(cmd->client->c->net.socket))) + { + if ((cmd->command.type == PUBLISH || cmd->command.type == SUBSCRIBE || cmd->command.type == UNSUBSCRIBE) && + cmd->client->c->outboundMsgs->count >= MAX_MSG_ID - 1) + ; /* no more message ids available */ + else + { + command = cmd; + break; + } + } + ListAppend(ignored_clients, cmd->client, sizeof(cmd->client)); + } + ListFreeNoContent(ignored_clients); + if (command) + { + ListDetach(commands, command); +#if !defined(NO_PERSISTENCE) + if (command->client->c->persistence) + MQTTAsync_unpersistCommand(command); +#endif + } + MQTTAsync_unlock_mutex(mqttcommand_mutex); + + if (!command) + goto exit; /* nothing to do */ + + if (command->command.type == CONNECT) + { + if (command->client->c->connect_state != 0 || command->client->c->connected) + rc = 0; + else + { + char* serverURI = command->client->serverURI; + + if (command->command.details.conn.serverURIcount > 0) + { + if (command->client->c->MQTTVersion == MQTTVERSION_DEFAULT) + { + if (command->command.details.conn.MQTTVersion == 3) + { + command->command.details.conn.currentURI++; + command->command.details.conn.MQTTVersion = 4; + } + } + else + command->command.details.conn.currentURI++; + serverURI = command->command.details.conn.serverURIs[command->command.details.conn.currentURI]; + + if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0) + serverURI += strlen(URI_TCP); +#if defined(OPENSSL) + else if (strncmp(URI_SSL, serverURI, strlen(URI_SSL)) == 0) + { + serverURI += strlen(URI_SSL); + command->client->ssl = 1; + } +#endif + } + + if (command->client->c->MQTTVersion == MQTTVERSION_DEFAULT) + { + if (command->command.details.conn.MQTTVersion == 0) + command->command.details.conn.MQTTVersion = MQTTVERSION_3_1_1; + else if (command->command.details.conn.MQTTVersion == MQTTVERSION_3_1_1) + command->command.details.conn.MQTTVersion = MQTTVERSION_3_1; + } + else + command->command.details.conn.MQTTVersion = command->client->c->MQTTVersion; + + Log(TRACE_MIN, -1, "Connecting to serverURI %s with MQTT version %d", serverURI, command->command.details.conn.MQTTVersion); +#if defined(OPENSSL) + rc = MQTTProtocol_connect(serverURI, command->client->c, command->client->ssl, command->command.details.conn.MQTTVersion); +#else + rc = MQTTProtocol_connect(serverURI, command->client->c, command->command.details.conn.MQTTVersion); +#endif + if (command->client->c->connect_state == 0) + rc = SOCKET_ERROR; + + /* if the TCP connect is pending, then we must call select to determine when the connect has completed, + which is indicated by the socket being ready *either* for reading *or* writing. The next couple of lines + make sure we check for writeability as well as readability, otherwise we wait around longer than we need to + in Socket_getReadySocket() */ + if (rc == EINPROGRESS) + Socket_addPendingWrite(command->client->c->net.socket); + } + } + else if (command->command.type == SUBSCRIBE) + { + List* topics = ListInitialize(); + List* qoss = ListInitialize(); + int i; + + for (i = 0; i < command->command.details.sub.count; i++) + { + ListAppend(topics, command->command.details.sub.topics[i], strlen(command->command.details.sub.topics[i])); + ListAppend(qoss, &command->command.details.sub.qoss[i], sizeof(int)); + } + rc = MQTTProtocol_subscribe(command->client->c, topics, qoss, command->command.token); + ListFreeNoContent(topics); + ListFreeNoContent(qoss); + } + else if (command->command.type == UNSUBSCRIBE) + { + List* topics = ListInitialize(); + int i; + + for (i = 0; i < command->command.details.unsub.count; i++) + ListAppend(topics, command->command.details.unsub.topics[i], strlen(command->command.details.unsub.topics[i])); + + rc = MQTTProtocol_unsubscribe(command->client->c, topics, command->command.token); + ListFreeNoContent(topics); + } + else if (command->command.type == PUBLISH) + { + Messages* msg = NULL; + Publish* p = NULL; + + p = malloc(sizeof(Publish)); + + p->payload = command->command.details.pub.payload; + p->payloadlen = command->command.details.pub.payloadlen; + p->topic = command->command.details.pub.destinationName; + p->msgId = command->command.token; + + rc = MQTTProtocol_startPublish(command->client->c, p, command->command.details.pub.qos, command->command.details.pub.retained, &msg); + + if (command->command.details.pub.qos == 0) + { + if (rc == TCPSOCKET_COMPLETE) + { + if (command->command.onSuccess) + { + MQTTAsync_successData data; + + data.token = command->command.token; + data.alt.pub.destinationName = command->command.details.pub.destinationName; + data.alt.pub.message.payload = command->command.details.pub.payload; + data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; + data.alt.pub.message.qos = command->command.details.pub.qos; + data.alt.pub.message.retained = command->command.details.pub.retained; + Log(TRACE_MIN, -1, "Calling publish success for client %s", command->client->c->clientID); + (*(command->command.onSuccess))(command->command.context, &data); + } + } + else + { + command->command.details.pub.destinationName = NULL; /* this will be freed by the protocol code */ + command->client->pending_write = &command->command; + } + } + else + command->command.details.pub.destinationName = NULL; /* this will be freed by the protocol code */ + free(p); /* should this be done if the write isn't complete? */ + } + else if (command->command.type == DISCONNECT) + { + if (command->client->c->connect_state != 0 || command->client->c->connected != 0) + { + command->client->c->connect_state = -2; + MQTTAsync_checkDisconnect(command->client, &command->command); + } + } + + if (command->command.type == CONNECT && rc != SOCKET_ERROR && rc != MQTTASYNC_PERSISTENCE_ERROR) + { + command->client->connect = command->command; + MQTTAsync_freeCommand(command); + } + else if (command->command.type == DISCONNECT) + { + command->client->disconnect = command->command; + MQTTAsync_freeCommand(command); + } + else if (command->command.type == PUBLISH && command->command.details.pub.qos == 0) + { + if (rc == TCPSOCKET_INTERRUPTED) + ListAppend(command->client->responses, command, sizeof(command)); + else + MQTTAsync_freeCommand(command); + } + else if (rc == SOCKET_ERROR || rc == MQTTASYNC_PERSISTENCE_ERROR) + { + if (command->command.type == CONNECT) + { + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + MQTTAsync_disconnect(command->client, &opts); /* not "internal" because we don't want to call connection lost */ + } + else + MQTTAsync_disconnect_internal(command->client, 0); + + if (command->command.type == CONNECT && MQTTAsync_checkConn(&command->command, command->client)) + { + Log(TRACE_MIN, -1, "Connect failed, more to try"); + /* put the connect command back to the head of the command queue, using the next serverURI */ + rc = MQTTAsync_addCommand(command, sizeof(command->command.details.conn)); + } + else + { + if (command->command.onFailure) + { + Log(TRACE_MIN, -1, "Calling command failure for client %s", command->client->c->clientID); + (*(command->command.onFailure))(command->command.context, NULL); + } + MQTTAsync_freeConnect(command->command); + MQTTAsync_freeCommand(command); /* free up the command if necessary */ + } + } + else /* put the command into a waiting for response queue for each client, indexed by msgid */ + ListAppend(command->client->responses, command, sizeof(command)); + +exit: + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT; +} + + +void MQTTAsync_checkTimeouts() +{ + ListElement* current = NULL; + static time_t last = 0L; + time_t now; + + FUNC_ENTRY; + time(&(now)); + if (difftime(now, last) < 3) + goto exit; + + MQTTAsync_lock_mutex(mqttasync_mutex); + last = now; + while (ListNextElement(handles, ¤t)) /* for each client */ + { + ListElement* cur_response = NULL; + int i = 0, + timed_out_count = 0; + + MQTTAsyncs* m = (MQTTAsyncs*)(current->content); + + /* check connect timeout */ + if (m->c->connect_state != 0 && MQTTAsync_elapsed(m->connect.start_time) > (m->connect.details.conn.timeout * 1000)) + { + if (MQTTAsync_checkConn(&m->connect, m)) + { + MQTTAsync_queuedCommand* conn; + + MQTTAsync_closeOnly(m->c); + /* put the connect command back to the head of the command queue, using the next serverURI */ + conn = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); + conn->client = m; + conn->command = m->connect; + Log(TRACE_MIN, -1, "Connect failed with timeout, more to try"); + MQTTAsync_addCommand(conn, sizeof(m->connect)); + } + else + { + MQTTAsync_closeSession(m->c); + MQTTAsync_freeConnect(m->connect); + if (m->connect.onFailure) + { + Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); + (*(m->connect.onFailure))(m->connect.context, NULL); + } + } + continue; + } + + /* check disconnect timeout */ + if (m->c->connect_state == -2) + MQTTAsync_checkDisconnect(m, &m->disconnect); + + timed_out_count = 0; + /* check response timeouts */ + while (ListNextElement(m->responses, &cur_response)) + { + MQTTAsync_queuedCommand* com = (MQTTAsync_queuedCommand*)(cur_response->content); + + if (1 /*MQTTAsync_elapsed(com->command.start_time) < 120000*/) + break; /* command has not timed out */ + else + { + if (com->command.onFailure) + { + Log(TRACE_MIN, -1, "Calling %s failure for client %s", + MQTTPacket_name(com->command.type), m->c->clientID); + (*(com->command.onFailure))(com->command.context, NULL); + } + timed_out_count++; + } + } + for (i = 0; i < timed_out_count; ++i) + ListRemoveHead(m->responses); /* remove the first response in the list */ + } + MQTTAsync_unlock_mutex(mqttasync_mutex); +exit: + FUNC_EXIT; +} + + +thread_return_type WINAPI MQTTAsync_sendThread(void* n) +{ + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + sendThread_state = RUNNING; + sendThread_id = Thread_getid(); + MQTTAsync_unlock_mutex(mqttasync_mutex); + while (!tostop) + { + int rc; + + while (commands->count > 0) + { + int before = commands->count; + MQTTAsync_processCommand(); + if (before == commands->count) + break; /* no commands were processed, so go into a wait */ + } +#if !defined(WIN32) && !defined(WIN64) + rc = Thread_wait_cond(send_cond, 1); + if ((rc = Thread_wait_cond(send_cond, 1)) != 0 && rc != ETIMEDOUT) + Log(LOG_ERROR, -1, "Error %d waiting for condition variable", rc); +#else + if ((rc = Thread_wait_sem(send_sem, 1000)) != 0 && rc != ETIMEDOUT) + Log(LOG_ERROR, -1, "Error %d waiting for semaphore", rc); +#endif + + MQTTAsync_checkTimeouts(); + } + sendThread_state = STOPPING; + MQTTAsync_lock_mutex(mqttasync_mutex); + sendThread_state = STOPPED; + sendThread_id = 0; + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT; + return 0; +} + + +void MQTTAsync_emptyMessageQueue(Clients* client) +{ + FUNC_ENTRY; + /* empty message queue */ + if (client->messageQueue->count > 0) + { + ListElement* current = NULL; + while (ListNextElement(client->messageQueue, ¤t)) + { + qEntry* qe = (qEntry*)(current->content); + free(qe->topicName); + free(qe->msg->payload); + free(qe->msg); + } + ListEmpty(client->messageQueue); + } + FUNC_EXIT; +} + + +void MQTTAsync_removeResponsesAndCommands(MQTTAsyncs* m) +{ + int count = 0; + ListElement* current = NULL; + ListElement *next = NULL; + + FUNC_ENTRY; + if (m->responses) + { + ListElement* elem = NULL; + + while (ListNextElement(m->responses, &elem)) + { + MQTTAsync_freeCommand1((MQTTAsync_queuedCommand*) (elem->content)); + count++; + } + } + ListEmpty(m->responses); + Log(TRACE_MINIMUM, -1, "%d responses removed for client %s", count, m->c->clientID); + + /* remove commands in the command queue relating to this client */ + count = 0; + current = ListNextElement(commands, &next); + ListNextElement(commands, &next); + while (current) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); + + if (cmd->client == m) + { + ListDetach(commands, cmd); + MQTTAsync_freeCommand(cmd); + count++; + } + current = next; + ListNextElement(commands, &next); + } + Log(TRACE_MINIMUM, -1, "%d commands removed for client %s", count, m->c->clientID); + FUNC_EXIT; +} + + +void MQTTAsync_destroy(MQTTAsync* handle) +{ + MQTTAsyncs* m = *handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL) + goto exit; + + MQTTAsync_removeResponsesAndCommands(m); + ListFree(m->responses); + + if (m->c) + { + int saved_socket = m->c->net.socket; + char* saved_clientid = MQTTStrdup(m->c->clientID); +#if !defined(NO_PERSISTENCE) + MQTTPersistence_close(m->c); +#endif + MQTTAsync_emptyMessageQueue(m->c); + MQTTProtocol_freeClient(m->c); + if (!ListRemove(bstate->clients, m->c)) + Log(LOG_ERROR, 0, NULL); + else + Log(TRACE_MIN, 1, NULL, saved_clientid, saved_socket); + free(saved_clientid); + } + + if (m->serverURI) + free(m->serverURI); + if (!ListRemove(handles, m)) + Log(LOG_ERROR, -1, "free error"); + *handle = NULL; + if (bstate->clients->count == 0) + MQTTAsync_terminate(); + +exit: + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT; +} + + +void MQTTAsync_freeMessage(MQTTAsync_message** message) +{ + FUNC_ENTRY; + free((*message)->payload); + free(*message); + *message = NULL; + FUNC_EXIT; +} + + +void MQTTAsync_free(void* memory) +{ + FUNC_ENTRY; + free(memory); + FUNC_EXIT; +} + + +int MQTTAsync_completeConnection(MQTTAsyncs* m, MQTTPacket* pack) +{ + int rc = MQTTASYNC_FAILURE; + + FUNC_ENTRY; + if (m->c->connect_state == 3) /* MQTT connect sent - wait for CONNACK */ + { + Connack* connack = (Connack*)pack; + Log(LOG_PROTOCOL, 1, NULL, m->c->net.socket, m->c->clientID, connack->rc); + if ((rc = connack->rc) == MQTTASYNC_SUCCESS) + { + m->c->connected = 1; + m->c->good = 1; + m->c->connect_state = 0; + if (m->c->cleansession) + rc = MQTTAsync_cleanSession(m->c); + if (m->c->outboundMsgs->count > 0) + { + ListElement* outcurrent = NULL; + + while (ListNextElement(m->c->outboundMsgs, &outcurrent)) + { + Messages* m = (Messages*)(outcurrent->content); + m->lastTouch = 0; + } + MQTTProtocol_retry((time_t)0, 1, 1); + if (m->c->connected != 1) + rc = MQTTASYNC_DISCONNECTED; + } + } + free(connack); + m->pack = NULL; + } + FUNC_EXIT_RC(rc); + return rc; +} + +/* This is the thread function that handles the calling of callback functions if set */ +thread_return_type WINAPI MQTTAsync_receiveThread(void* n) +{ + long timeout = 10L; /* first time in we have a small timeout. Gets things started more quickly */ + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + receiveThread_state = RUNNING; + receiveThread_id = Thread_getid(); + while (!tostop) + { + int rc = SOCKET_ERROR; + int sock = -1; + MQTTAsyncs* m = NULL; + MQTTPacket* pack = NULL; + + MQTTAsync_unlock_mutex(mqttasync_mutex); + pack = MQTTAsync_cycle(&sock, timeout, &rc); + MQTTAsync_lock_mutex(mqttasync_mutex); + if (tostop) + break; + timeout = 1000L; + + if (sock == 0) + continue; + /* find client corresponding to socket */ + if (ListFindItem(handles, &sock, clientSockCompare) == NULL) + { + Log(TRACE_MINIMUM, -1, "Could not find client corresponding to socket %d", sock); + /* Socket_close(sock); - removing socket in this case is not necessary (Bug 442400) */ + continue; + } + m = (MQTTAsyncs*)(handles->current->content); + if (m == NULL) + { + Log(LOG_ERROR, -1, "Client structure was NULL for socket %d - removing socket", sock); + Socket_close(sock); + continue; + } + if (rc == SOCKET_ERROR) + { + Log(TRACE_MINIMUM, -1, "Error from MQTTAsync_cycle() - removing socket %d", sock); + if (m->c->connected == 1) + { + MQTTAsync_unlock_mutex(mqttasync_mutex); + MQTTAsync_disconnect_internal(m, 0); + MQTTAsync_lock_mutex(mqttasync_mutex); + } + else /* calling disconnect_internal won't have any effect if we're already disconnected */ + MQTTAsync_closeOnly(m->c); + } + else + { + if (m->c->messageQueue->count > 0) + { + qEntry* qe = (qEntry*)(m->c->messageQueue->first->content); + int topicLen = qe->topicLen; + + if (strlen(qe->topicName) == topicLen) + topicLen = 0; + + if (m->ma) + rc = MQTTAsync_deliverMessage(m, qe->topicName, topicLen, qe->msg); + else + rc = 1; + + if (rc) + { + ListRemove(m->c->messageQueue, qe); +#if !defined(NO_PERSISTENCE) + if (m->c->persistence) + MQTTPersistence_unpersistQueueEntry(m->c, (MQTTPersistence_qEntry*)qe); +#endif + } + else + Log(TRACE_MIN, -1, "False returned from messageArrived for client %s, message remains on queue", + m->c->clientID); + } + if (pack) + { + if (pack->header.bits.type == CONNACK) + { + int sessionPresent = ((Connack*)pack)->flags.bits.sessionPresent; + int rc = MQTTAsync_completeConnection(m, pack); + + if (rc == MQTTASYNC_SUCCESS) + { + if (m->connect.details.conn.serverURIcount > 0) + Log(TRACE_MIN, -1, "Connect succeeded to %s", + m->connect.details.conn.serverURIs[m->connect.details.conn.currentURI]); + MQTTAsync_freeConnect(m->connect); + if (m->connect.onSuccess) + { + MQTTAsync_successData data; + memset(&data, '\0', sizeof(data)); + Log(TRACE_MIN, -1, "Calling connect success for client %s", m->c->clientID); + if (m->connect.details.conn.serverURIcount > 0) + data.alt.connect.serverURI = m->connect.details.conn.serverURIs[m->connect.details.conn.currentURI]; + else + data.alt.connect.serverURI = m->serverURI; + data.alt.connect.MQTTVersion = m->connect.details.conn.MQTTVersion; + data.alt.connect.sessionPresent = sessionPresent; + (*(m->connect.onSuccess))(m->connect.context, &data); + } + } + else + { + if (MQTTAsync_checkConn(&m->connect, m)) + { + MQTTAsync_queuedCommand* conn; + + MQTTAsync_closeOnly(m->c); + /* put the connect command back to the head of the command queue, using the next serverURI */ + conn = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); + conn->client = m; + conn->command = m->connect; + Log(TRACE_MIN, -1, "Connect failed, more to try"); + MQTTAsync_addCommand(conn, sizeof(m->connect)); + } + else + { + MQTTAsync_closeSession(m->c); + MQTTAsync_freeConnect(m->connect); + if (m->connect.onFailure) + { + MQTTAsync_failureData data; + + data.token = 0; + data.code = rc; + data.message = "CONNACK return code"; + Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); + (*(m->connect.onFailure))(m->connect.context, &data); + } + } + } + } + else if (pack->header.bits.type == SUBACK) + { + ListElement* current = NULL; + + /* use the msgid to find the callback to be called */ + while (ListNextElement(m->responses, ¤t)) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); + if (command->command.token == ((Suback*)pack)->msgId) + { + Suback* sub = (Suback*)pack; + if (!ListDetach(m->responses, command)) /* remove the response from the list */ + Log(LOG_ERROR, -1, "Subscribe command not removed from command list"); + + /* Call the failure callback if there is one subscribe in the MQTT packet and + * the return code is 0x80 (failure). If the MQTT packet contains >1 subscription + * request, then we call onSuccess with the list of returned QoSs, which inelegantly, + * could include some failures, or worse, the whole list could have failed. + */ + if (sub->qoss->count == 1 && *(int*)(sub->qoss->first->content) == MQTT_BAD_SUBSCRIBE) + { + if (command->command.onFailure) + { + MQTTAsync_failureData data; + + data.token = command->command.token; + data.code = *(int*)(sub->qoss->first->content); + Log(TRACE_MIN, -1, "Calling subscribe failure for client %s", m->c->clientID); + (*(command->command.onFailure))(command->command.context, &data); + } + } + else if (command->command.onSuccess) + { + MQTTAsync_successData data; + int* array = NULL; + + if (sub->qoss->count == 1) + data.alt.qos = *(int*)(sub->qoss->first->content); + else if (sub->qoss->count > 1) + { + ListElement* cur_qos = NULL; + int* element = array = data.alt.qosList = malloc(sub->qoss->count * sizeof(int)); + while (ListNextElement(sub->qoss, &cur_qos)) + *element++ = *(int*)(cur_qos->content); + } + data.token = command->command.token; + Log(TRACE_MIN, -1, "Calling subscribe success for client %s", m->c->clientID); + (*(command->command.onSuccess))(command->command.context, &data); + if (array) + free(array); + } + MQTTAsync_freeCommand(command); + break; + } + } + rc = MQTTProtocol_handleSubacks(pack, m->c->net.socket); + } + else if (pack->header.bits.type == UNSUBACK) + { + ListElement* current = NULL; + int handleCalled = 0; + + /* use the msgid to find the callback to be called */ + while (ListNextElement(m->responses, ¤t)) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); + if (command->command.token == ((Unsuback*)pack)->msgId) + { + if (!ListDetach(m->responses, command)) /* remove the response from the list */ + Log(LOG_ERROR, -1, "Unsubscribe command not removed from command list"); + if (command->command.onSuccess) + { + rc = MQTTProtocol_handleUnsubacks(pack, m->c->net.socket); + handleCalled = 1; + Log(TRACE_MIN, -1, "Calling unsubscribe success for client %s", m->c->clientID); + (*(command->command.onSuccess))(command->command.context, NULL); + } + MQTTAsync_freeCommand(command); + break; + } + } + if (!handleCalled) + rc = MQTTProtocol_handleUnsubacks(pack, m->c->net.socket); + } + } + } + } + receiveThread_state = STOPPED; + receiveThread_id = 0; + MQTTAsync_unlock_mutex(mqttasync_mutex); +#if !defined(WIN32) && !defined(WIN64) + if (sendThread_state != STOPPED) + Thread_signal_cond(send_cond); +#else + if (sendThread_state != STOPPED && !Thread_check_sem(send_sem)) + Thread_post_sem(send_sem); +#endif + FUNC_EXIT; + return 0; +} + + +void MQTTAsync_stop() +{ + int rc = 0; + + FUNC_ENTRY; + if (sendThread_state != STOPPED || receiveThread_state != STOPPED) + { + int conn_count = 0; + ListElement* current = NULL; + + if (handles != NULL) + { + /* find out how many handles are still connected */ + while (ListNextElement(handles, ¤t)) + { + if (((MQTTAsyncs*)(current->content))->c->connect_state > 0 || + ((MQTTAsyncs*)(current->content))->c->connected) + ++conn_count; + } + } + Log(TRACE_MIN, -1, "Conn_count is %d", conn_count); + /* stop the background thread, if we are the last one to be using it */ + if (conn_count == 0) + { + int count = 0; + tostop = 1; + while ((sendThread_state != STOPPED || receiveThread_state != STOPPED) && ++count < 100) + { + MQTTAsync_unlock_mutex(mqttasync_mutex); + Log(TRACE_MIN, -1, "sleeping"); + MQTTAsync_sleep(100L); + MQTTAsync_lock_mutex(mqttasync_mutex); + } + rc = 1; + tostop = 0; + } + } + FUNC_EXIT_RC(rc); +} + + +int MQTTAsync_setCallbacks(MQTTAsync handle, void* context, + MQTTAsync_connectionLost* cl, + MQTTAsync_messageArrived* ma, + MQTTAsync_deliveryComplete* dc) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL || ma == NULL || m->c->connect_state != 0) + rc = MQTTASYNC_FAILURE; + else + { + m->context = context; + m->cl = cl; + m->ma = ma; + m->dc = dc; + } + + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTAsync_closeOnly(Clients* client) +{ + FUNC_ENTRY; + client->good = 0; + client->ping_outstanding = 0; + if (client->net.socket > 0) + { + if (client->connected) + MQTTPacket_send_disconnect(&client->net, client->clientID); +#if defined(OPENSSL) + SSLSocket_close(&client->net); +#endif + Socket_close(client->net.socket); + client->net.socket = 0; +#if defined(OPENSSL) + client->net.ssl = NULL; +#endif + } + client->connected = 0; + client->connect_state = 0; + FUNC_EXIT; +} + + +void MQTTAsync_closeSession(Clients* client) +{ + FUNC_ENTRY; + MQTTAsync_closeOnly(client); + + if (client->cleansession) + MQTTAsync_cleanSession(client); + + FUNC_EXIT; +} + + +/** + * List callback function for comparing clients by client structure + * @param a Async structure + * @param b Client structure + * @return boolean indicating whether a and b are equal + */ +int clientStructCompare(void* a, void* b) +{ + MQTTAsyncs* m = (MQTTAsyncs*)a; + return m->c == (Clients*)b; +} + + +int MQTTAsync_cleanSession(Clients* client) +{ + int rc = 0; + ListElement* found = NULL; + + FUNC_ENTRY; +#if !defined(NO_PERSISTENCE) + rc = MQTTPersistence_clear(client); +#endif + MQTTProtocol_emptyMessageList(client->inboundMsgs); + MQTTProtocol_emptyMessageList(client->outboundMsgs); + MQTTAsync_emptyMessageQueue(client); + client->msgID = 0; + + if ((found = ListFindItem(handles, client, clientStructCompare)) != NULL) + { + MQTTAsyncs* m = (MQTTAsyncs*)(found->content); + MQTTAsync_removeResponsesAndCommands(m); + } + else + Log(LOG_ERROR, -1, "cleanSession: did not find client structure in handles list"); + FUNC_EXIT_RC(rc); + return rc; +} + + + + + +int MQTTAsync_deliverMessage(MQTTAsyncs* m, char* topicName, size_t topicLen, MQTTAsync_message* mm) +{ + int rc; + + Log(TRACE_MIN, -1, "Calling messageArrived for client %s, queue depth %d", + m->c->clientID, m->c->messageQueue->count); + rc = (*(m->ma))(m->context, topicName, topicLen, mm); + /* if 0 (false) is returned by the callback then it failed, so we don't remove the message from + * the queue, and it will be retried later. If 1 is returned then the message data may have been freed, + * so we must be careful how we use it. + */ + return rc; +} + + +void Protocol_processPublication(Publish* publish, Clients* client) +{ + MQTTAsync_message* mm = NULL; + int rc = 0; + + FUNC_ENTRY; + mm = malloc(sizeof(MQTTAsync_message)); + + /* If the message is QoS 2, then we have already stored the incoming payload + * in an allocated buffer, so we don't need to copy again. + */ + if (publish->header.bits.qos == 2) + mm->payload = publish->payload; + else + { + mm->payload = malloc(publish->payloadlen); + memcpy(mm->payload, publish->payload, publish->payloadlen); + } + + mm->payloadlen = publish->payloadlen; + mm->qos = publish->header.bits.qos; + mm->retained = publish->header.bits.retain; + if (publish->header.bits.qos == 2) + mm->dup = 0; /* ensure that a QoS2 message is not passed to the application with dup = 1 */ + else + mm->dup = publish->header.bits.dup; + mm->msgid = publish->msgId; + + if (client->messageQueue->count == 0 && client->connected) + { + ListElement* found = NULL; + + if ((found = ListFindItem(handles, client, clientStructCompare)) == NULL) + Log(LOG_ERROR, -1, "processPublication: did not find client structure in handles list"); + else + { + MQTTAsyncs* m = (MQTTAsyncs*)(found->content); + + if (m->ma) + rc = MQTTAsync_deliverMessage(m, publish->topic, publish->topiclen, mm); + } + } + + if (rc == 0) /* if message was not delivered, queue it up */ + { + qEntry* qe = malloc(sizeof(qEntry)); + qe->msg = mm; + qe->topicName = publish->topic; + qe->topicLen = publish->topiclen; + ListAppend(client->messageQueue, qe, sizeof(qe) + sizeof(mm) + mm->payloadlen + strlen(qe->topicName)+1); +#if !defined(NO_PERSISTENCE) + if (client->persistence) + MQTTPersistence_persistQueueEntry(client, (MQTTPersistence_qEntry*)qe); +#endif + } + publish->topic = NULL; + FUNC_EXIT; +} + + +int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) +{ + MQTTAsyncs* m = handle; + int rc = MQTTASYNC_SUCCESS; + MQTTAsync_queuedCommand* conn; + + FUNC_ENTRY; + if (options == NULL) + { + rc = MQTTASYNC_NULL_PARAMETER; + goto exit; + } + + if (strncmp(options->struct_id, "MQTC", 4) != 0 || + (options->struct_version != 0 && options->struct_version != 1 && options->struct_version != 2 && + options->struct_version != 3)) + { + rc = MQTTASYNC_BAD_STRUCTURE; + goto exit; + } + if (options->will) /* check validity of will options structure */ + { + if (strncmp(options->will->struct_id, "MQTW", 4) != 0 || options->will->struct_version != 0) + { + rc = MQTTASYNC_BAD_STRUCTURE; + goto exit; + } + if (options->will->qos < 0 || options->will->qos > 2) + { + rc = MQTTASYNC_BAD_QOS; + goto exit; + } + } + if (options->struct_version != 0 && options->ssl) /* check validity of SSL options structure */ + { + if (strncmp(options->ssl->struct_id, "MQTS", 4) != 0 || options->ssl->struct_version != 0) + { + rc = MQTTASYNC_BAD_STRUCTURE; + goto exit; + } + } + if ((options->username && !UTF8_validateString(options->username)) || + (options->password && !UTF8_validateString(options->password))) + { + rc = MQTTASYNC_BAD_UTF8_STRING; + goto exit; + } + + m->connect.onSuccess = options->onSuccess; + m->connect.onFailure = options->onFailure; + m->connect.context = options->context; + + tostop = 0; + if (sendThread_state != STARTING && sendThread_state != RUNNING) + { + MQTTAsync_lock_mutex(mqttasync_mutex); + sendThread_state = STARTING; + Thread_start(MQTTAsync_sendThread, NULL); + MQTTAsync_unlock_mutex(mqttasync_mutex); + } + if (receiveThread_state != STARTING && receiveThread_state != RUNNING) + { + MQTTAsync_lock_mutex(mqttasync_mutex); + receiveThread_state = STARTING; + Thread_start(MQTTAsync_receiveThread, handle); + MQTTAsync_unlock_mutex(mqttasync_mutex); + } + + m->c->keepAliveInterval = options->keepAliveInterval; + m->c->cleansession = options->cleansession; + m->c->maxInflightMessages = options->maxInflight; + if (options->struct_version == 3) + m->c->MQTTVersion = options->MQTTVersion; + else + m->c->MQTTVersion = 0; + + if (m->c->will) + { + free(m->c->will->msg); + free(m->c->will->topic); + free(m->c->will); + m->c->will = NULL; + } + + if (options->will && options->will->struct_version == 0) + { + m->c->will = malloc(sizeof(willMessages)); + m->c->will->msg = MQTTStrdup(options->will->message); + m->c->will->qos = options->will->qos; + m->c->will->retained = options->will->retained; + m->c->will->topic = MQTTStrdup(options->will->topicName); + } + +#if defined(OPENSSL) + if (m->c->sslopts) + { + if (m->c->sslopts->trustStore) + free((void*)m->c->sslopts->trustStore); + if (m->c->sslopts->keyStore) + free((void*)m->c->sslopts->keyStore); + if (m->c->sslopts->privateKey) + free((void*)m->c->sslopts->privateKey); + if (m->c->sslopts->privateKeyPassword) + free((void*)m->c->sslopts->privateKeyPassword); + if (m->c->sslopts->enabledCipherSuites) + free((void*)m->c->sslopts->enabledCipherSuites); + free((void*)m->c->sslopts); + m->c->sslopts = NULL; + } + + if (options->struct_version != 0 && options->ssl) + { + m->c->sslopts = malloc(sizeof(MQTTClient_SSLOptions)); + memset(m->c->sslopts, '\0', sizeof(MQTTClient_SSLOptions)); + if (options->ssl->trustStore) + m->c->sslopts->trustStore = MQTTStrdup(options->ssl->trustStore); + if (options->ssl->keyStore) + m->c->sslopts->keyStore = MQTTStrdup(options->ssl->keyStore); + if (options->ssl->privateKey) + m->c->sslopts->privateKey = MQTTStrdup(options->ssl->privateKey); + if (options->ssl->privateKeyPassword) + m->c->sslopts->privateKeyPassword = MQTTStrdup(options->ssl->privateKeyPassword); + if (options->ssl->enabledCipherSuites) + m->c->sslopts->enabledCipherSuites = MQTTStrdup(options->ssl->enabledCipherSuites); + m->c->sslopts->enableServerCertAuth = options->ssl->enableServerCertAuth; + } +#endif + + m->c->username = options->username; + m->c->password = options->password; + m->c->retryInterval = options->retryInterval; + + /* Add connect request to operation queue */ + conn = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); + conn->client = m; + if (options) + { + conn->command.onSuccess = options->onSuccess; + conn->command.onFailure = options->onFailure; + conn->command.context = options->context; + conn->command.details.conn.timeout = options->connectTimeout; + + if (options->struct_version >= 2 && options->serverURIcount > 0) + { + int i; + + conn->command.details.conn.serverURIcount = options->serverURIcount; + conn->command.details.conn.serverURIs = malloc(options->serverURIcount * sizeof(char*)); + for (i = 0; i < options->serverURIcount; ++i) + conn->command.details.conn.serverURIs[i] = MQTTStrdup(options->serverURIs[i]); + conn->command.details.conn.currentURI = 0; + } + } + conn->command.type = CONNECT; + rc = MQTTAsync_addCommand(conn, sizeof(conn)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_disconnect1(MQTTAsync handle, const MQTTAsync_disconnectOptions* options, int internal) +{ + MQTTAsyncs* m = handle; + int rc = MQTTASYNC_SUCCESS; + MQTTAsync_queuedCommand* dis; + + FUNC_ENTRY; + if (m == NULL || m->c == NULL) + { + rc = MQTTASYNC_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTASYNC_DISCONNECTED; + goto exit; + } + + /* Add disconnect request to operation queue */ + dis = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(dis, '\0', sizeof(MQTTAsync_queuedCommand)); + dis->client = m; + if (options) + { + dis->command.onSuccess = options->onSuccess; + dis->command.onFailure = options->onFailure; + dis->command.context = options->context; + dis->command.details.dis.timeout = options->timeout; + } + dis->command.type = DISCONNECT; + dis->command.details.dis.internal = internal; + rc = MQTTAsync_addCommand(dis, sizeof(dis)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_disconnect_internal(MQTTAsync handle, int timeout) +{ + MQTTAsync_disconnectOptions options = MQTTAsync_disconnectOptions_initializer; + + options.timeout = timeout; + return MQTTAsync_disconnect1(handle, &options, 1); +} + + +void MQTTProtocol_closeSession(Clients* c, int sendwill) +{ + MQTTAsync_disconnect_internal((MQTTAsync)c->context, 0); +} + + +int MQTTAsync_disconnect(MQTTAsync handle, const MQTTAsync_disconnectOptions* options) +{ + return MQTTAsync_disconnect1(handle, options, 0); +} + + +int MQTTAsync_isConnected(MQTTAsync handle) +{ + MQTTAsyncs* m = handle; + int rc = 0; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + if (m && m->c) + rc = m->c->connected; + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int cmdMessageIDCompare(void* a, void* b) +{ + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)a; + return cmd->command.token == *(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 m a client structure + * @return the next message id to use, or 0 if none available + */ +int MQTTAsync_assignMsgId(MQTTAsyncs* m) +{ + int start_msgid = m->c->msgID; + int msgid = start_msgid; + thread_id_type thread_id = 0; + int locked = 0; + + /* need to check: commands list and response list for a client */ + FUNC_ENTRY; + /* We might be called in a callback. In which case, this mutex will be already locked. */ + thread_id = Thread_getid(); + if (thread_id != sendThread_id && thread_id != receiveThread_id) + { + MQTTAsync_lock_mutex(mqttasync_mutex); + locked = 1; + } + + msgid = (msgid == MAX_MSG_ID) ? 1 : msgid + 1; + while (ListFindItem(commands, &msgid, cmdMessageIDCompare) || + ListFindItem(m->responses, &msgid, cmdMessageIDCompare)) + { + 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) + m->c->msgID = msgid; + if (locked) + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(msgid); + return msgid; +} + + +int MQTTAsync_subscribeMany(MQTTAsync handle, int count, char* const* topic, int* qos, MQTTAsync_responseOptions* response) +{ + MQTTAsyncs* m = handle; + int i = 0; + int rc = MQTTASYNC_FAILURE; + MQTTAsync_queuedCommand* sub; + int msgid = 0; + + FUNC_ENTRY; + if (m == NULL || m->c == NULL) + { + rc = MQTTASYNC_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTASYNC_DISCONNECTED; + goto exit; + } + for (i = 0; i < count; i++) + { + if (!UTF8_validateString(topic[i])) + { + rc = MQTTASYNC_BAD_UTF8_STRING; + goto exit; + } + if (qos[i] < 0 || qos[i] > 2) + { + rc = MQTTASYNC_BAD_QOS; + goto exit; + } + } + if ((msgid = MQTTAsync_assignMsgId(m)) == 0) + { + rc = MQTTASYNC_NO_MORE_MSGIDS; + goto exit; + } + + /* Add subscribe request to operation queue */ + sub = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(sub, '\0', sizeof(MQTTAsync_queuedCommand)); + sub->client = m; + sub->command.token = msgid; + if (response) + { + sub->command.onSuccess = response->onSuccess; + sub->command.onFailure = response->onFailure; + sub->command.context = response->context; + response->token = sub->command.token; + } + sub->command.type = SUBSCRIBE; + sub->command.details.sub.count = count; + sub->command.details.sub.topics = malloc(sizeof(char*) * count); + sub->command.details.sub.qoss = malloc(sizeof(int) * count); + for (i = 0; i < count; ++i) + { + sub->command.details.sub.topics[i] = MQTTStrdup(topic[i]); + sub->command.details.sub.qoss[i] = qos[i]; + } + rc = MQTTAsync_addCommand(sub, sizeof(sub)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_subscribe(MQTTAsync handle, const char* topic, int qos, MQTTAsync_responseOptions* response) +{ + int rc = 0; + char *const topics[] = {(char*)topic}; + FUNC_ENTRY; + rc = MQTTAsync_subscribeMany(handle, 1, topics, &qos, response); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_unsubscribeMany(MQTTAsync handle, int count, char* const* topic, MQTTAsync_responseOptions* response) +{ + MQTTAsyncs* m = handle; + int i = 0; + int rc = SOCKET_ERROR; + MQTTAsync_queuedCommand* unsub; + int msgid = 0; + + FUNC_ENTRY; + if (m == NULL || m->c == NULL) + { + rc = MQTTASYNC_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTASYNC_DISCONNECTED; + goto exit; + } + for (i = 0; i < count; i++) + { + if (!UTF8_validateString(topic[i])) + { + rc = MQTTASYNC_BAD_UTF8_STRING; + goto exit; + } + } + if ((msgid = MQTTAsync_assignMsgId(m)) == 0) + { + rc = MQTTASYNC_NO_MORE_MSGIDS; + goto exit; + } + + /* Add unsubscribe request to operation queue */ + unsub = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(unsub, '\0', sizeof(MQTTAsync_queuedCommand)); + unsub->client = m; + unsub->command.type = UNSUBSCRIBE; + unsub->command.token = msgid; + if (response) + { + unsub->command.onSuccess = response->onSuccess; + unsub->command.onFailure = response->onFailure; + unsub->command.context = response->context; + response->token = unsub->command.token; + } + unsub->command.details.unsub.count = count; + unsub->command.details.unsub.topics = malloc(sizeof(char*) * count); + for (i = 0; i < count; ++i) + unsub->command.details.unsub.topics[i] = MQTTStrdup(topic[i]); + rc = MQTTAsync_addCommand(unsub, sizeof(unsub)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_unsubscribe(MQTTAsync handle, const char* topic, MQTTAsync_responseOptions* response) +{ + int rc = 0; + char *const topics[] = {(char*)topic}; + FUNC_ENTRY; + rc = MQTTAsync_unsubscribeMany(handle, 1, topics, response); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_send(MQTTAsync handle, const char* destinationName, int payloadlen, void* payload, + int qos, int retained, MQTTAsync_responseOptions* response) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + MQTTAsync_queuedCommand* pub; + int msgid = 0; + + FUNC_ENTRY; + if (m == NULL || m->c == NULL) + rc = MQTTASYNC_FAILURE; + else if (m->c->connected == 0) + rc = MQTTASYNC_DISCONNECTED; + else if (!UTF8_validateString(destinationName)) + rc = MQTTASYNC_BAD_UTF8_STRING; + else if (qos < 0 || qos > 2) + rc = MQTTASYNC_BAD_QOS; + else if (qos > 0 && (msgid = MQTTAsync_assignMsgId(m)) == 0) + rc = MQTTASYNC_NO_MORE_MSGIDS; + + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + /* Add publish request to operation queue */ + pub = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(pub, '\0', sizeof(MQTTAsync_queuedCommand)); + pub->client = m; + pub->command.type = PUBLISH; + pub->command.token = msgid; + if (response) + { + pub->command.onSuccess = response->onSuccess; + pub->command.onFailure = response->onFailure; + pub->command.context = response->context; + response->token = pub->command.token; + } + pub->command.details.pub.destinationName = MQTTStrdup(destinationName); + pub->command.details.pub.payloadlen = payloadlen; + pub->command.details.pub.payload = malloc(payloadlen); + memcpy(pub->command.details.pub.payload, payload, payloadlen); + pub->command.details.pub.qos = qos; + pub->command.details.pub.retained = retained; + rc = MQTTAsync_addCommand(pub, sizeof(pub)); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +int MQTTAsync_sendMessage(MQTTAsync handle, const char* destinationName, const MQTTAsync_message* message, + MQTTAsync_responseOptions* response) +{ + int rc = MQTTASYNC_SUCCESS; + + FUNC_ENTRY; + if (message == NULL) + { + rc = MQTTASYNC_NULL_PARAMETER; + goto exit; + } + if (strncmp(message->struct_id, "MQTM", 4) != 0 || message->struct_version != 0) + { + rc = MQTTASYNC_BAD_STRUCTURE; + goto exit; + } + + rc = MQTTAsync_send(handle, destinationName, message->payloadlen, message->payload, + message->qos, message->retained, response); +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTAsync_retry(void) +{ + static time_t last = 0L; + time_t now; + + FUNC_ENTRY; + time(&(now)); + if (difftime(now, last) > 5) + { + time(&(last)); + MQTTProtocol_keepalive(now); + MQTTProtocol_retry(now, 1, 0); + } + else + MQTTProtocol_retry(now, 0, 0); + FUNC_EXIT; +} + + +int MQTTAsync_connecting(MQTTAsyncs* m) +{ + int rc = -1; + + FUNC_ENTRY; + if (m->c->connect_state == 1) /* TCP connect started - check for completion */ + { + int error; + socklen_t len = sizeof(error); + + if ((rc = getsockopt(m->c->net.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0) + rc = error; + + if (rc != 0) + goto exit; + + Socket_clearPendingWrite(m->c->net.socket); + +#if defined(OPENSSL) + if (m->ssl) + { + if (SSLSocket_setSocketForSSL(&m->c->net, m->c->sslopts) != MQTTASYNC_SUCCESS) + { + if (m->c->session != NULL) + if ((rc = SSL_set_session(m->c->net.ssl, m->c->session)) != 1) + Log(TRACE_MIN, -1, "Failed to set SSL session with stored data, non critical"); + rc = SSLSocket_connect(m->c->net.ssl, m->c->net.socket); + if (rc == TCPSOCKET_INTERRUPTED) + { + rc = MQTTCLIENT_SUCCESS; /* the connect is still in progress */ + m->c->connect_state = 2; + } + else if (rc == SSL_FATAL) + { + rc = SOCKET_ERROR; + goto exit; + } + else if (rc == 1) + { + rc = MQTTCLIENT_SUCCESS; + m->c->connect_state = 3; + if (MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion) == SOCKET_ERROR) + { + rc = SOCKET_ERROR; + goto exit; + } + if (!m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + } + } + else + { + rc = SOCKET_ERROR; + goto exit; + } + } + else + { +#endif + m->c->connect_state = 3; /* TCP/SSL connect completed, in which case send the MQTT connect packet */ + if ((rc = MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion)) == SOCKET_ERROR) + goto exit; +#if defined(OPENSSL) + } +#endif + } +#if defined(OPENSSL) + else if (m->c->connect_state == 2) /* SSL connect sent - wait for completion */ + { + if ((rc = SSLSocket_connect(m->c->net.ssl, m->c->net.socket)) != 1) + goto exit; + + if(!m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + m->c->connect_state = 3; /* SSL connect completed, in which case send the MQTT connect packet */ + if ((rc = MQTTPacket_send_connect(m->c, m->connect.details.conn.MQTTVersion)) == SOCKET_ERROR) + goto exit; + } +#endif + +exit: + if ((rc != 0 && rc != TCPSOCKET_INTERRUPTED && m->c->connect_state != 2) || (rc == SSL_FATAL)) + { + if (MQTTAsync_checkConn(&m->connect, m)) + { + MQTTAsync_queuedCommand* conn; + + MQTTAsync_closeOnly(m->c); + /* put the connect command back to the head of the command queue, using the next serverURI */ + conn = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); + conn->client = m; + conn->command = m->connect; + Log(TRACE_MIN, -1, "Connect failed, more to try"); + MQTTAsync_addCommand(conn, sizeof(m->connect)); + } + else + { + MQTTAsync_closeSession(m->c); + MQTTAsync_freeConnect(m->connect); + if (m->connect.onFailure) + { + Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); + (*(m->connect.onFailure))(m->connect.context, NULL); + } + } + } + FUNC_EXIT_RC(rc); + return rc; +} + + +MQTTPacket* MQTTAsync_cycle(int* sock, unsigned long timeout, int* rc) +{ + struct timeval tp = {0L, 0L}; + static Ack ack; + MQTTPacket* pack = NULL; + static int nosockets_count = 0; + + FUNC_ENTRY; + if (timeout > 0L) + { + tp.tv_sec = timeout / 1000; + tp.tv_usec = (timeout % 1000) * 1000; /* this field is microseconds! */ + } + +#if defined(OPENSSL) + if ((*sock = SSLSocket_getPendingRead()) == -1) + { +#endif + /* 0 from getReadySocket indicates no work to do, -1 == error, but can happen normally */ + *sock = Socket_getReadySocket(0, &tp); + if (!tostop && *sock == 0 && (tp.tv_sec > 0L || tp.tv_usec > 0L)) + { + MQTTAsync_sleep(100L); +#if 0 + if (s.clientsds->count == 0) + { + if (++nosockets_count == 50) /* 5 seconds with no sockets */ + tostop = 1; + } +#endif + } + else + nosockets_count = 0; +#if defined(OPENSSL) + } +#endif + MQTTAsync_lock_mutex(mqttasync_mutex); + if (*sock > 0) + { + MQTTAsyncs* m = NULL; + if (ListFindItem(handles, sock, clientSockCompare) != NULL) + m = (MQTTAsync)(handles->current->content); + if (m != NULL) + { + if (m->c->connect_state == 1 || m->c->connect_state == 2) + *rc = MQTTAsync_connecting(m); + else + pack = MQTTPacket_Factory(&m->c->net, rc); + if (m->c->connect_state == 3 && *rc == SOCKET_ERROR) + { + Log(TRACE_MINIMUM, -1, "CONNECT sent but MQTTPacket_Factory has returned SOCKET_ERROR"); + if (MQTTAsync_checkConn(&m->connect, m)) + { + MQTTAsync_queuedCommand* conn; + + MQTTAsync_closeOnly(m->c); + /* put the connect command back to the head of the command queue, using the next serverURI */ + conn = malloc(sizeof(MQTTAsync_queuedCommand)); + memset(conn, '\0', sizeof(MQTTAsync_queuedCommand)); + conn->client = m; + conn->command = m->connect; + Log(TRACE_MIN, -1, "Connect failed, more to try"); + MQTTAsync_addCommand(conn, sizeof(m->connect)); + } + else + { + MQTTAsync_closeSession(m->c); + MQTTAsync_freeConnect(m->connect); + if (m->connect.onFailure) + { + Log(TRACE_MIN, -1, "Calling connect failure for client %s", m->c->clientID); + (*(m->connect.onFailure))(m->connect.context, NULL); + } + } + } + } + if (pack) + { + int freed = 1; + + /* Note that these handle... functions free the packet structure that they are dealing with */ + if (pack->header.bits.type == PUBLISH) + *rc = MQTTProtocol_handlePublishes(pack, *sock); + else if (pack->header.bits.type == PUBACK || pack->header.bits.type == PUBCOMP) + { + int msgid; + + ack = (pack->header.bits.type == PUBCOMP) ? *(Pubcomp*)pack : *(Puback*)pack; + msgid = ack.msgId; + *rc = (pack->header.bits.type == PUBCOMP) ? + MQTTProtocol_handlePubcomps(pack, *sock) : MQTTProtocol_handlePubacks(pack, *sock); + if (!m) + Log(LOG_ERROR, -1, "PUBCOMP or PUBACK received for no client, msgid %d", msgid); + if (m) + { + ListElement* current = NULL; + + if (m->dc) + { + Log(TRACE_MIN, -1, "Calling deliveryComplete for client %s, msgid %d", m->c->clientID, msgid); + (*(m->dc))(m->context, msgid); + } + /* use the msgid to find the callback to be called */ + while (ListNextElement(m->responses, ¤t)) + { + MQTTAsync_queuedCommand* command = (MQTTAsync_queuedCommand*)(current->content); + if (command->command.token == msgid) + { + if (!ListDetach(m->responses, command)) /* then remove the response from the list */ + Log(LOG_ERROR, -1, "Publish command not removed from command list"); + if (command->command.onSuccess) + { + MQTTAsync_successData data; + + data.token = command->command.token; + data.alt.pub.destinationName = command->command.details.pub.destinationName; + data.alt.pub.message.payload = command->command.details.pub.payload; + data.alt.pub.message.payloadlen = command->command.details.pub.payloadlen; + data.alt.pub.message.qos = command->command.details.pub.qos; + data.alt.pub.message.retained = command->command.details.pub.retained; + Log(TRACE_MIN, -1, "Calling publish success for client %s", m->c->clientID); + (*(command->command.onSuccess))(command->command.context, &data); + } + MQTTAsync_freeCommand(command); + break; + } + } + } + } + else if (pack->header.bits.type == PUBREC) + *rc = MQTTProtocol_handlePubrecs(pack, *sock); + else if (pack->header.bits.type == PUBREL) + *rc = MQTTProtocol_handlePubrels(pack, *sock); + else if (pack->header.bits.type == PINGRESP) + *rc = MQTTProtocol_handlePingresps(pack, *sock); + else + freed = 0; + if (freed) + pack = NULL; + } + } + MQTTAsync_retry(); + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(*rc); + return pack; +} + + +int pubCompare(void* a, void* b) +{ + Messages* msg = (Messages*)a; + return msg->publish == (Publications*)b; +} + + +int MQTTAsync_getPendingTokens(MQTTAsync handle, MQTTAsync_token **tokens) +{ + ListElement* current = NULL; + int count = 0; + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + *tokens = NULL; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL) + { + rc = MQTTASYNC_FAILURE; + goto exit; + } + + /* calculate the number of pending tokens - commands plus inflight */ + while (ListNextElement(commands, ¤t)) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); + + if (cmd->client == m) + count++; + } + if (m->c) + count += m->c->outboundMsgs->count; + if (count == 0) + goto exit; /* no tokens to return */ + *tokens = malloc(sizeof(MQTTAsync_token) * (count + 1)); /* add space for sentinel at end of list */ + + /* First add the unprocessed commands to the pending tokens */ + current = NULL; + count = 0; + while (ListNextElement(commands, ¤t)) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); + + if (cmd->client == m) + (*tokens)[count++] = cmd->command.token; + } + + /* Now add the inflight messages */ + if (m->c && m->c->outboundMsgs->count > 0) + { + current = NULL; + while (ListNextElement(m->c->outboundMsgs, ¤t)) + { + Messages* m = (Messages*)(current->content); + (*tokens)[count++] = m->msgid; + } + } + (*tokens)[count] = -1; /* indicate end of list */ + +exit: + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_isComplete(MQTTAsync handle, MQTTAsync_token dt) +{ + int rc = MQTTASYNC_SUCCESS; + MQTTAsyncs* m = handle; + ListElement* current = NULL; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL) + { + rc = MQTTASYNC_FAILURE; + goto exit; + } + + /* First check unprocessed commands */ + current = NULL; + while (ListNextElement(commands, ¤t)) + { + MQTTAsync_queuedCommand* cmd = (MQTTAsync_queuedCommand*)(current->content); + + if (cmd->client == m && cmd->command.token == dt) + goto exit; + } + + /* Now check the inflight messages */ + if (m->c && m->c->outboundMsgs->count > 0) + { + current = NULL; + while (ListNextElement(m->c->outboundMsgs, ¤t)) + { + Messages* m = (Messages*)(current->content); + if (m->msgid == dt) + goto exit; + } + } + rc = MQTTASYNC_TRUE; /* Can't find it, so it must be complete */ + +exit: + MQTTAsync_unlock_mutex(mqttasync_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTAsync_waitForCompletion(MQTTAsync handle, MQTTAsync_token dt, unsigned long timeout) +{ + int rc = MQTTASYNC_FAILURE; + START_TIME_TYPE start = MQTTAsync_start_clock(); + unsigned long elapsed = 0L; + MQTTAsyncs* m = handle; + + FUNC_ENTRY; + MQTTAsync_lock_mutex(mqttasync_mutex); + + if (m == NULL || m->c == NULL) + { + rc = MQTTASYNC_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTASYNC_DISCONNECTED; + goto exit; + } + MQTTAsync_unlock_mutex(mqttasync_mutex); + + if (MQTTAsync_isComplete(handle, dt) == 1) + { + rc = MQTTASYNC_SUCCESS; /* well we couldn't find it */ + goto exit; + } + + elapsed = MQTTAsync_elapsed(start); + while (elapsed < timeout) + { + MQTTAsync_sleep(100); + if (MQTTAsync_isComplete(handle, dt) == 1) + { + rc = MQTTASYNC_SUCCESS; /* well we couldn't find it */ + goto exit; + } + elapsed = MQTTAsync_elapsed(start); + } +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +void MQTTAsync_setTraceLevel(enum MQTTASYNC_TRACE_LEVELS level) +{ + Log_setTraceLevel((enum LOG_LEVELS)level); +} + + +void MQTTAsync_setTraceCallback(MQTTAsync_traceCallback* callback) +{ + Log_setTraceCallback((Log_traceCallback*)callback); +} + + +MQTTAsync_nameValue* MQTTAsync_getVersionInfo() +{ + #define MAX_INFO_STRINGS 8 + static MQTTAsync_nameValue libinfo[MAX_INFO_STRINGS + 1]; + int i = 0; + + libinfo[i].name = "Product name"; + libinfo[i++].value = "Paho Asynchronous MQTT C Client Library"; + + libinfo[i].name = "Version"; + libinfo[i++].value = CLIENT_VERSION; + + libinfo[i].name = "Build level"; + libinfo[i++].value = BUILD_TIMESTAMP; +#if defined(OPENSSL) + libinfo[i].name = "OpenSSL version"; + libinfo[i++].value = SSLeay_version(SSLEAY_VERSION); + + libinfo[i].name = "OpenSSL flags"; + libinfo[i++].value = SSLeay_version(SSLEAY_CFLAGS); + + libinfo[i].name = "OpenSSL build timestamp"; + libinfo[i++].value = SSLeay_version(SSLEAY_BUILT_ON); + + libinfo[i].name = "OpenSSL platform"; + libinfo[i++].value = SSLeay_version(SSLEAY_PLATFORM); + + libinfo[i].name = "OpenSSL directory"; + libinfo[i++].value = SSLeay_version(SSLEAY_DIR); +#endif + libinfo[i].name = NULL; + libinfo[i].value = NULL; + return libinfo; +} diff --git a/Sources/paho/src/MQTTClient.c b/Sources/paho/src/MQTTClient.c new file mode 100644 index 0000000..96899ee --- /dev/null +++ b/Sources/paho/src/MQTTClient.c @@ -0,0 +1,1964 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - bug 384016 - segv setting will message + * Ian Craggs - bug 384053 - v1.0.0.7 - stop MQTTClient_receive on socket error + * Ian Craggs, Allan Stockdill-Mander - add ability to connect with SSL + * Ian Craggs - multiple server connection support + * Ian Craggs - fix for bug 413429 - connectionLost not called + * Ian Craggs - fix for bug 421103 - trying to write to same socket, in publish/retries + * Ian Craggs - fix for bug 419233 - mutexes not reporting errors + * Ian Craggs - fix for bug 420851 + * Ian Craggs - fix for bug 432903 - queue persistence + * Ian Craggs - MQTT 3.1.1 support + * Ian Craggs - fix for bug 438176 - MQTT version selection + * Rong Xiang, Ian Craggs - C++ compatibility + * Ian Craggs - fix for bug 443724 - stack corruption + *******************************************************************************/ + +/** + * @file + * \brief Synchronous API implementation + * + */ + +#define _GNU_SOURCE /* for pthread_mutexattr_settype */ +#include +#if !defined(WIN32) && !defined(WIN64) + #include +#endif + +#include "MQTTClient.h" +#if !defined(NO_PERSISTENCE) +#include "MQTTPersistence.h" +#endif + +#include "utf-8.h" +#include "MQTTProtocol.h" +#include "MQTTProtocolOut.h" +#include "Thread.h" +#include "SocketBuffer.h" +#include "StackTrace.h" +#include "Heap.h" + +#if defined(OPENSSL) +#include +#endif + +#define URI_TCP "tcp://" + +#define BUILD_TIMESTAMP "##MQTTCLIENT_BUILD_TAG##" +#define CLIENT_VERSION "##MQTTCLIENT_VERSION_TAG##" + +char* client_timestamp_eye = "MQTTClientV3_Timestamp " BUILD_TIMESTAMP; +char* client_version_eye = "MQTTClientV3_Version " CLIENT_VERSION; + +static ClientStates ClientState = +{ + CLIENT_VERSION, /* version */ + NULL /* client list */ +}; + +ClientStates* bstate = &ClientState; + +MQTTProtocol state; + +#if defined(WIN32) || defined(WIN64) +static mutex_type mqttclient_mutex = NULL; +extern mutex_type stack_mutex; +extern mutex_type heap_mutex; +extern mutex_type log_mutex; +BOOL APIENTRY DllMain(HANDLE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + Log(TRACE_MAX, -1, "DLL process attach"); + if (mqttclient_mutex == NULL) + { + mqttclient_mutex = CreateMutex(NULL, 0, NULL); + stack_mutex = CreateMutex(NULL, 0, NULL); + heap_mutex = CreateMutex(NULL, 0, NULL); + log_mutex = CreateMutex(NULL, 0, NULL); + } + case DLL_THREAD_ATTACH: + Log(TRACE_MAX, -1, "DLL thread attach"); + case DLL_THREAD_DETACH: + Log(TRACE_MAX, -1, "DLL thread detach"); + case DLL_PROCESS_DETACH: + Log(TRACE_MAX, -1, "DLL process detach"); + } + return TRUE; +} +#else +static pthread_mutex_t mqttclient_mutex_store = PTHREAD_MUTEX_INITIALIZER; +static mutex_type mqttclient_mutex = &mqttclient_mutex_store; + +void MQTTClient_init() +{ + pthread_mutexattr_t attr; + int rc; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + if ((rc = pthread_mutex_init(mqttclient_mutex, &attr)) != 0) + printf("MQTTClient: error %d initializing client_mutex\n", rc); +} + +#define WINAPI +#endif + +static volatile int initialized = 0; +static List* handles = NULL; +static time_t last; +static int running = 0; +static int tostop = 0; +static thread_id_type run_id = 0; + +MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* rc, long timeout); +MQTTPacket* MQTTClient_cycle(int* sock, unsigned long timeout, int* rc); +int MQTTClient_cleanSession(Clients* client); +void MQTTClient_stop(); +int MQTTClient_disconnect_internal(MQTTClient handle, int timeout); +int MQTTClient_disconnect1(MQTTClient handle, int timeout, int internal, int stop); +void MQTTClient_writeComplete(int socket); + +typedef struct +{ + MQTTClient_message* msg; + char* topicName; + int topicLen; + unsigned int seqno; /* only used on restore */ +} qEntry; + + +typedef struct +{ + char* serverURI; +#if defined(OPENSSL) + int ssl; +#endif + Clients* c; + MQTTClient_connectionLost* cl; + MQTTClient_messageArrived* ma; + MQTTClient_deliveryComplete* dc; + void* context; + + sem_type connect_sem; + int rc; /* getsockopt return code in connect */ + sem_type connack_sem; + sem_type suback_sem; + sem_type unsuback_sem; + MQTTPacket* pack; + +} MQTTClients; + +void MQTTClient_sleep(long milliseconds) +{ + FUNC_ENTRY; +#if defined(WIN32) || defined(WIN64) + Sleep(milliseconds); +#else + usleep(milliseconds*1000); +#endif + FUNC_EXIT; +} + + +#if defined(WIN32) || defined(WIN64) +#define START_TIME_TYPE DWORD +START_TIME_TYPE MQTTClient_start_clock(void) +{ + return GetTickCount(); +} +#elif defined(AIX) +#define START_TIME_TYPE struct timespec +START_TIME_TYPE MQTTClient_start_clock(void) +{ + static struct timespec start; + clock_gettime(CLOCK_REALTIME, &start); + return start; +} +#else +#define START_TIME_TYPE struct timeval +START_TIME_TYPE MQTTClient_start_clock(void) +{ + static struct timeval start; + gettimeofday(&start, NULL); + return start; +} +#endif + + +#if defined(WIN32) || defined(WIN64) +long MQTTClient_elapsed(DWORD milliseconds) +{ + return GetTickCount() - milliseconds; +} +#elif defined(AIX) +#define assert(a) +long MQTTClient_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 MQTTClient_elapsed(struct timeval start) +{ + struct timeval now, res; + + gettimeofday(&now, NULL); + timersub(&now, &start, &res); + return (res.tv_sec)*1000 + (res.tv_usec)/1000; +} +#endif + + +int MQTTClient_create(MQTTClient* handle, const char* serverURI, const char* clientId, + int persistence_type, void* persistence_context) +{ + int rc = 0; + MQTTClients *m = NULL; + + FUNC_ENTRY; + rc = Thread_lock_mutex(mqttclient_mutex); + + if (serverURI == NULL || clientId == NULL) + { + rc = MQTTCLIENT_NULL_PARAMETER; + goto exit; + } + + if (!UTF8_validateString(clientId)) + { + rc = MQTTCLIENT_BAD_UTF8_STRING; + goto exit; + } + + if (!initialized) + { + #if defined(HEAP_H) + Heap_initialize(); + #endif + Log_initialize((Log_nameValue*)MQTTClient_getVersionInfo()); + bstate->clients = ListInitialize(); + Socket_outInitialize(); + Socket_setWriteCompleteCallback(MQTTClient_writeComplete); + handles = ListInitialize(); +#if defined(OPENSSL) + SSLSocket_initialize(); +#endif + initialized = 1; + } + m = malloc(sizeof(MQTTClients)); + *handle = m; + memset(m, '\0', sizeof(MQTTClients)); + if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0) + serverURI += strlen(URI_TCP); +#if defined(OPENSSL) + else if (strncmp(URI_SSL, serverURI, strlen(URI_SSL)) == 0) + { + serverURI += strlen(URI_SSL); + m->ssl = 1; + } +#endif + m->serverURI = MQTTStrdup(serverURI); + ListAppend(handles, m, sizeof(MQTTClients)); + + m->c = malloc(sizeof(Clients)); + memset(m->c, '\0', sizeof(Clients)); + m->c->context = m; + m->c->outboundMsgs = ListInitialize(); + m->c->inboundMsgs = ListInitialize(); + m->c->messageQueue = ListInitialize(); + m->c->clientID = MQTTStrdup(clientId); + m->connect_sem = Thread_create_sem(); + m->connack_sem = Thread_create_sem(); + m->suback_sem = Thread_create_sem(); + m->unsuback_sem = Thread_create_sem(); + +#if !defined(NO_PERSISTENCE) + rc = MQTTPersistence_create(&(m->c->persistence), persistence_type, persistence_context); + if (rc == 0) + { + rc = MQTTPersistence_initialize(m->c, m->serverURI); + if (rc == 0) + MQTTPersistence_restoreMessageQueue(m->c); + } +#endif + ListAppend(bstate->clients, m->c, sizeof(Clients) + 3*sizeof(List)); + +exit: + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTClient_terminate(void) +{ + FUNC_ENTRY; + MQTTClient_stop(); + if (initialized) + { + ListFree(bstate->clients); + ListFree(handles); + handles = NULL; + Socket_outTerminate(); +#if defined(OPENSSL) + SSLSocket_terminate(); +#endif + #if defined(HEAP_H) + Heap_terminate(); + #endif + Log_terminate(); + initialized = 0; + } + FUNC_EXIT; +} + + +void MQTTClient_emptyMessageQueue(Clients* client) +{ + FUNC_ENTRY; + /* empty message queue */ + if (client->messageQueue->count > 0) + { + ListElement* current = NULL; + while (ListNextElement(client->messageQueue, ¤t)) + { + qEntry* qe = (qEntry*)(current->content); + free(qe->topicName); + free(qe->msg->payload); + free(qe->msg); + } + ListEmpty(client->messageQueue); + } + FUNC_EXIT; +} + + +void MQTTClient_destroy(MQTTClient* handle) +{ + MQTTClients* m = *handle; + + FUNC_ENTRY; + Thread_lock_mutex(mqttclient_mutex); + + if (m == NULL) + goto exit; + + if (m->c) + { + int saved_socket = m->c->net.socket; + char* saved_clientid = MQTTStrdup(m->c->clientID); +#if !defined(NO_PERSISTENCE) + MQTTPersistence_close(m->c); +#endif + MQTTClient_emptyMessageQueue(m->c); + MQTTProtocol_freeClient(m->c); + if (!ListRemove(bstate->clients, m->c)) + Log(LOG_ERROR, 0, NULL); + else + Log(TRACE_MIN, 1, NULL, saved_clientid, saved_socket); + free(saved_clientid); + } + if (m->serverURI) + free(m->serverURI); + Thread_destroy_sem(m->connect_sem); + Thread_destroy_sem(m->connack_sem); + Thread_destroy_sem(m->suback_sem); + Thread_destroy_sem(m->unsuback_sem); + if (!ListRemove(handles, m)) + Log(LOG_ERROR, -1, "free error"); + *handle = NULL; + if (bstate->clients->count == 0) + MQTTClient_terminate(); + +exit: + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT; +} + + +void MQTTClient_freeMessage(MQTTClient_message** message) +{ + FUNC_ENTRY; + free((*message)->payload); + free(*message); + *message = NULL; + FUNC_EXIT; +} + + +void MQTTClient_free(void* memory) +{ + FUNC_ENTRY; + free(memory); + FUNC_EXIT; +} + + +int MQTTClient_deliverMessage(int rc, MQTTClients* m, char** topicName, int* topicLen, MQTTClient_message** message) +{ + qEntry* qe = (qEntry*)(m->c->messageQueue->first->content); + + FUNC_ENTRY; + *message = qe->msg; + *topicName = qe->topicName; + *topicLen = qe->topicLen; + if (strlen(*topicName) != *topicLen) + rc = MQTTCLIENT_TOPICNAME_TRUNCATED; +#if !defined(NO_PERSISTENCE) + if (m->c->persistence) + MQTTPersistence_unpersistQueueEntry(m->c, (MQTTPersistence_qEntry*)qe); +#endif + ListRemove(m->c->messageQueue, m->c->messageQueue->first->content); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * List callback function for comparing clients by socket + * @param a first integer value + * @param b second integer value + * @return boolean indicating whether a and b are equal + */ +int clientSockCompare(void* a, void* b) +{ + MQTTClients* m = (MQTTClients*)a; + return m->c->net.socket == *(int*)b; +} + + +/** + * Wrapper function to call connection lost on a separate thread. A separate thread is needed to allow the + * connectionLost function to make API calls (e.g. connect) + * @param context a pointer to the relevant client + * @return thread_return_type standard thread return value - not used here + */ +thread_return_type WINAPI connectionLost_call(void* context) +{ + MQTTClients* m = (MQTTClients*)context; + + (*(m->cl))(m->context, NULL); + return 0; +} + + +/* This is the thread function that handles the calling of callback functions if set */ +thread_return_type WINAPI MQTTClient_run(void* n) +{ + long timeout = 10L; /* first time in we have a small timeout. Gets things started more quickly */ + + FUNC_ENTRY; + running = 1; + run_id = Thread_getid(); + + Thread_lock_mutex(mqttclient_mutex); + while (!tostop) + { + int rc = SOCKET_ERROR; + int sock = -1; + MQTTClients* m = NULL; + MQTTPacket* pack = NULL; + + Thread_unlock_mutex(mqttclient_mutex); + pack = MQTTClient_cycle(&sock, timeout, &rc); + Thread_lock_mutex(mqttclient_mutex); + if (tostop) + break; + timeout = 1000L; + + /* find client corresponding to socket */ + if (ListFindItem(handles, &sock, clientSockCompare) == NULL) + { + /* assert: should not happen */ + continue; + } + m = (MQTTClient)(handles->current->content); + if (m == NULL) + { + /* assert: should not happen */ + continue; + } + if (rc == SOCKET_ERROR) + { + if (m->c->connected) + { + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_disconnect_internal(m, 0); + Thread_lock_mutex(mqttclient_mutex); + } + else + { + if (m->c->connect_state == 2 && !Thread_check_sem(m->connect_sem)) + { + Log(TRACE_MIN, -1, "Posting connect semaphore for client %s", m->c->clientID); + Thread_post_sem(m->connect_sem); + } + if (m->c->connect_state == 3 && !Thread_check_sem(m->connack_sem)) + { + Log(TRACE_MIN, -1, "Posting connack semaphore for client %s", m->c->clientID); + Thread_post_sem(m->connack_sem); + } + } + } + else + { + if (m->c->messageQueue->count > 0) + { + qEntry* qe = (qEntry*)(m->c->messageQueue->first->content); + int topicLen = qe->topicLen; + + if (strlen(qe->topicName) == topicLen) + topicLen = 0; + + Log(TRACE_MIN, -1, "Calling messageArrived for client %s, queue depth %d", + m->c->clientID, m->c->messageQueue->count); + Thread_unlock_mutex(mqttclient_mutex); + rc = (*(m->ma))(m->context, qe->topicName, topicLen, qe->msg); + Thread_lock_mutex(mqttclient_mutex); + /* if 0 (false) is returned by the callback then it failed, so we don't remove the message from + * the queue, and it will be retried later. If 1 is returned then the message data may have been freed, + * so we must be careful how we use it. + */ + if (rc) + ListRemove(m->c->messageQueue, qe); + else + Log(TRACE_MIN, -1, "False returned from messageArrived for client %s, message remains on queue", + m->c->clientID); + } + if (pack) + { + if (pack->header.bits.type == CONNACK && !Thread_check_sem(m->connack_sem)) + { + Log(TRACE_MIN, -1, "Posting connack semaphore for client %s", m->c->clientID); + m->pack = pack; + Thread_post_sem(m->connack_sem); + } + else if (pack->header.bits.type == SUBACK) + { + Log(TRACE_MIN, -1, "Posting suback semaphore for client %s", m->c->clientID); + m->pack = pack; + Thread_post_sem(m->suback_sem); + } + else if (pack->header.bits.type == UNSUBACK) + { + Log(TRACE_MIN, -1, "Posting unsuback semaphore for client %s", m->c->clientID); + m->pack = pack; + Thread_post_sem(m->unsuback_sem); + } + } + else if (m->c->connect_state == 1 && !Thread_check_sem(m->connect_sem)) + { + int error; + socklen_t len = sizeof(error); + + if ((m->rc = getsockopt(m->c->net.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0) + m->rc = error; + Log(TRACE_MIN, -1, "Posting connect semaphore for client %s rc %d", m->c->clientID, m->rc); + Thread_post_sem(m->connect_sem); + } +#if defined(OPENSSL) + else if (m->c->connect_state == 2 && !Thread_check_sem(m->connect_sem)) + { + rc = SSLSocket_connect(m->c->net.ssl, m->c->net.socket); + if (rc == 1 || rc == SSL_FATAL) + { + if (rc == 1 && !m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + m->rc = rc; + Log(TRACE_MIN, -1, "Posting connect semaphore for SSL client %s rc %d", m->c->clientID, m->rc); + Thread_post_sem(m->connect_sem); + } + } +#endif + } + } + run_id = 0; + running = tostop = 0; + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT; + return 0; +} + + +void MQTTClient_stop() +{ + int rc = 0; + + FUNC_ENTRY; + if (running == 1 && tostop == 0) + { + int conn_count = 0; + ListElement* current = NULL; + + if (handles != NULL) + { + /* find out how many handles are still connected */ + while (ListNextElement(handles, ¤t)) + { + if (((MQTTClients*)(current->content))->c->connect_state > 0 || + ((MQTTClients*)(current->content))->c->connected) + ++conn_count; + } + } + Log(TRACE_MIN, -1, "Conn_count is %d", conn_count); + /* stop the background thread, if we are the last one to be using it */ + if (conn_count == 0) + { + int count = 0; + tostop = 1; + if (Thread_getid() != run_id) + { + while (running && ++count < 100) + { + Thread_unlock_mutex(mqttclient_mutex); + Log(TRACE_MIN, -1, "sleeping"); + MQTTClient_sleep(100L); + Thread_lock_mutex(mqttclient_mutex); + } + } + rc = 1; + } + } + FUNC_EXIT_RC(rc); +} + + +int MQTTClient_setCallbacks(MQTTClient handle, void* context, MQTTClient_connectionLost* cl, + MQTTClient_messageArrived* ma, MQTTClient_deliveryComplete* dc) +{ + int rc = MQTTCLIENT_SUCCESS; + MQTTClients* m = handle; + + FUNC_ENTRY; + Thread_lock_mutex(mqttclient_mutex); + + if (m == NULL || ma == NULL || m->c->connect_state != 0) + rc = MQTTCLIENT_FAILURE; + else + { + m->context = context; + m->cl = cl; + m->ma = ma; + m->dc = dc; + } + + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTClient_closeSession(Clients* client) +{ + FUNC_ENTRY; + client->good = 0; + client->ping_outstanding = 0; + if (client->net.socket > 0) + { + if (client->connected) + MQTTPacket_send_disconnect(&client->net, client->clientID); +#if defined(OPENSSL) + SSLSocket_close(&client->net); +#endif + Socket_close(client->net.socket); + client->net.socket = 0; +#if defined(OPENSSL) + client->net.ssl = NULL; +#endif + } + client->connected = 0; + client->connect_state = 0; + + if (client->cleansession) + MQTTClient_cleanSession(client); + FUNC_EXIT; +} + + +int MQTTClient_cleanSession(Clients* client) +{ + int rc = 0; + + FUNC_ENTRY; +#if !defined(NO_PERSISTENCE) + rc = MQTTPersistence_clear(client); +#endif + MQTTProtocol_emptyMessageList(client->inboundMsgs); + MQTTProtocol_emptyMessageList(client->outboundMsgs); + MQTTClient_emptyMessageQueue(client); + client->msgID = 0; + FUNC_EXIT_RC(rc); + return rc; +} + + +void Protocol_processPublication(Publish* publish, Clients* client) +{ + qEntry* qe = NULL; + MQTTClient_message* mm = NULL; + + FUNC_ENTRY; + qe = malloc(sizeof(qEntry)); + mm = malloc(sizeof(MQTTClient_message)); + + qe->msg = mm; + + qe->topicName = publish->topic; + qe->topicLen = publish->topiclen; + publish->topic = NULL; + + /* If the message is QoS 2, then we have already stored the incoming payload + * in an allocated buffer, so we don't need to copy again. + */ + if (publish->header.bits.qos == 2) + mm->payload = publish->payload; + else + { + mm->payload = malloc(publish->payloadlen); + memcpy(mm->payload, publish->payload, publish->payloadlen); + } + + mm->payloadlen = publish->payloadlen; + mm->qos = publish->header.bits.qos; + mm->retained = publish->header.bits.retain; + if (publish->header.bits.qos == 2) + mm->dup = 0; /* ensure that a QoS2 message is not passed to the application with dup = 1 */ + else + mm->dup = publish->header.bits.dup; + mm->msgid = publish->msgId; + + ListAppend(client->messageQueue, qe, sizeof(qe) + sizeof(mm) + mm->payloadlen + strlen(qe->topicName)+1); +#if !defined(NO_PERSISTENCE) + if (client->persistence) + MQTTPersistence_persistQueueEntry(client, (MQTTPersistence_qEntry*)qe); +#endif + FUNC_EXIT; +} + + +int MQTTClient_connectURIVersion(MQTTClient handle, MQTTClient_connectOptions* options, const char* serverURI, int MQTTVersion, + START_TIME_TYPE start, long millisecsTimeout) +{ + MQTTClients* m = handle; + int rc = SOCKET_ERROR; + int sessionPresent = 0; + + FUNC_ENTRY; + if (m->ma && !running) + { + Thread_start(MQTTClient_run, handle); + if (MQTTClient_elapsed(start) >= millisecsTimeout) + { + rc = SOCKET_ERROR; + goto exit; + } + MQTTClient_sleep(100L); + } + + Log(TRACE_MIN, -1, "Connecting to serverURI %s with MQTT version %d", serverURI, MQTTVersion); +#if defined(OPENSSL) + rc = MQTTProtocol_connect(serverURI, m->c, m->ssl, MQTTVersion); +#else + rc = MQTTProtocol_connect(serverURI, m->c, MQTTVersion); +#endif + if (rc == SOCKET_ERROR) + goto exit; + + if (m->c->connect_state == 0) + { + rc = SOCKET_ERROR; + goto exit; + } + + if (m->c->connect_state == 1) /* TCP connect started - wait for completion */ + { + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_waitfor(handle, CONNECT, &rc, millisecsTimeout - MQTTClient_elapsed(start)); + Thread_lock_mutex(mqttclient_mutex); + if (rc != 0) + { + rc = SOCKET_ERROR; + goto exit; + } + +#if defined(OPENSSL) + if (m->ssl) + { + if (SSLSocket_setSocketForSSL(&m->c->net, m->c->sslopts) != MQTTCLIENT_SUCCESS) + { + if (m->c->session != NULL) + if ((rc = SSL_set_session(m->c->net.ssl, m->c->session)) != 1) + Log(TRACE_MIN, -1, "Failed to set SSL session with stored data, non critical"); + rc = SSLSocket_connect(m->c->net.ssl, m->c->net.socket); + if (rc == TCPSOCKET_INTERRUPTED) + m->c->connect_state = 2; /* the connect is still in progress */ + else if (rc == SSL_FATAL) + { + rc = SOCKET_ERROR; + goto exit; + } + else if (rc == 1) + { + rc = MQTTCLIENT_SUCCESS; + m->c->connect_state = 3; + if (MQTTPacket_send_connect(m->c, MQTTVersion) == SOCKET_ERROR) + { + rc = SOCKET_ERROR; + goto exit; + } + if (!m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + } + } + else + { + rc = SOCKET_ERROR; + goto exit; + } + } + else + { +#endif + m->c->connect_state = 3; /* TCP connect completed, in which case send the MQTT connect packet */ + if (MQTTPacket_send_connect(m->c, MQTTVersion) == SOCKET_ERROR) + { + rc = SOCKET_ERROR; + goto exit; + } +#if defined(OPENSSL) + } +#endif + } + +#if defined(OPENSSL) + if (m->c->connect_state == 2) /* SSL connect sent - wait for completion */ + { + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_waitfor(handle, CONNECT, &rc, millisecsTimeout - MQTTClient_elapsed(start)); + Thread_lock_mutex(mqttclient_mutex); + if (rc != 1) + { + rc = SOCKET_ERROR; + goto exit; + } + if(!m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + m->c->connect_state = 3; /* TCP connect completed, in which case send the MQTT connect packet */ + if (MQTTPacket_send_connect(m->c, MQTTVersion) == SOCKET_ERROR) + { + rc = SOCKET_ERROR; + goto exit; + } + } +#endif + + if (m->c->connect_state == 3) /* MQTT connect sent - wait for CONNACK */ + { + MQTTPacket* pack = NULL; + + Thread_unlock_mutex(mqttclient_mutex); + pack = MQTTClient_waitfor(handle, CONNACK, &rc, millisecsTimeout - MQTTClient_elapsed(start)); + Thread_lock_mutex(mqttclient_mutex); + if (pack == NULL) + rc = SOCKET_ERROR; + else + { + Connack* connack = (Connack*)pack; + Log(TRACE_PROTOCOL, 1, NULL, m->c->net.socket, m->c->clientID, connack->rc); + if ((rc = connack->rc) == MQTTCLIENT_SUCCESS) + { + m->c->connected = 1; + m->c->good = 1; + m->c->connect_state = 0; + if (MQTTVersion == 4) + sessionPresent = connack->flags.bits.sessionPresent; + if (m->c->cleansession) + rc = MQTTClient_cleanSession(m->c); + if (m->c->outboundMsgs->count > 0) + { + ListElement* outcurrent = NULL; + + while (ListNextElement(m->c->outboundMsgs, &outcurrent)) + { + Messages* m = (Messages*)(outcurrent->content); + m->lastTouch = 0; + } + MQTTProtocol_retry((time_t)0, 1, 1); + if (m->c->connected != 1) + rc = MQTTCLIENT_DISCONNECTED; + } + } + free(connack); + m->pack = NULL; + } + } +exit: + if (rc == MQTTCLIENT_SUCCESS) + { + if (options->struct_version == 4) /* means we have to fill out return values */ + { + options->returned.serverURI = serverURI; + options->returned.MQTTVersion = MQTTVersion; + options->returned.sessionPresent = sessionPresent; + } + } + else + { + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_disconnect1(handle, 0, 0, (MQTTVersion == 3)); /* not "internal" because we don't want to call connection lost */ + Thread_lock_mutex(mqttclient_mutex); + } + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTClient_connectURI(MQTTClient handle, MQTTClient_connectOptions* options, const char* serverURI) +{ + MQTTClients* m = handle; + START_TIME_TYPE start; + long millisecsTimeout = 30000L; + int rc = SOCKET_ERROR; + int MQTTVersion = 0; + + FUNC_ENTRY; + millisecsTimeout = options->connectTimeout * 1000; + start = MQTTClient_start_clock(); + + m->c->keepAliveInterval = options->keepAliveInterval; + m->c->cleansession = options->cleansession; + m->c->maxInflightMessages = (options->reliable) ? 1 : 10; + + if (m->c->will) + { + free(m->c->will->msg); + free(m->c->will->topic); + free(m->c->will); + m->c->will = NULL; + } + + if (options->will && options->will->struct_version == 0) + { + m->c->will = malloc(sizeof(willMessages)); + m->c->will->msg = MQTTStrdup(options->will->message); + m->c->will->qos = options->will->qos; + m->c->will->retained = options->will->retained; + m->c->will->topic = MQTTStrdup(options->will->topicName); + } + +#if defined(OPENSSL) + if (m->c->sslopts) + { + if (m->c->sslopts->trustStore) + free((void*)m->c->sslopts->trustStore); + if (m->c->sslopts->keyStore) + free((void*)m->c->sslopts->keyStore); + if (m->c->sslopts->privateKey) + free((void*)m->c->sslopts->privateKey); + if (m->c->sslopts->privateKeyPassword) + free((void*)m->c->sslopts->privateKeyPassword); + if (m->c->sslopts->enabledCipherSuites) + free((void*)m->c->sslopts->enabledCipherSuites); + free(m->c->sslopts); + m->c->sslopts = NULL; + } + + if (options->struct_version != 0 && options->ssl) + { + m->c->sslopts = malloc(sizeof(MQTTClient_SSLOptions)); + memset(m->c->sslopts, '\0', sizeof(MQTTClient_SSLOptions)); + if (options->ssl->trustStore) + m->c->sslopts->trustStore = MQTTStrdup(options->ssl->trustStore); + if (options->ssl->keyStore) + m->c->sslopts->keyStore = MQTTStrdup(options->ssl->keyStore); + if (options->ssl->privateKey) + m->c->sslopts->privateKey = MQTTStrdup(options->ssl->privateKey); + if (options->ssl->privateKeyPassword) + m->c->sslopts->privateKeyPassword = MQTTStrdup(options->ssl->privateKeyPassword); + if (options->ssl->enabledCipherSuites) + m->c->sslopts->enabledCipherSuites = MQTTStrdup(options->ssl->enabledCipherSuites); + m->c->sslopts->enableServerCertAuth = options->ssl->enableServerCertAuth; + } +#endif + + m->c->username = options->username; + m->c->password = options->password; + m->c->retryInterval = options->retryInterval; + + if (options->struct_version >= 3) + MQTTVersion = options->MQTTVersion; + else + MQTTVersion = MQTTVERSION_DEFAULT; + + if (MQTTVersion == MQTTVERSION_DEFAULT) + { + if ((rc = MQTTClient_connectURIVersion(handle, options, serverURI, 4, start, millisecsTimeout)) != MQTTCLIENT_SUCCESS) + rc = MQTTClient_connectURIVersion(handle, options, serverURI, 3, start, millisecsTimeout); + } + else + rc = MQTTClient_connectURIVersion(handle, options, serverURI, MQTTVersion, start, millisecsTimeout); + + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options) +{ + MQTTClients* m = handle; + int rc = SOCKET_ERROR; + + FUNC_ENTRY; + Thread_lock_mutex(mqttclient_mutex); + + if (options == NULL) + { + rc = MQTTCLIENT_NULL_PARAMETER; + goto exit; + } + + if (strncmp(options->struct_id, "MQTC", 4) != 0 || + (options->struct_version != 0 && options->struct_version != 1 && options->struct_version != 2 + && options->struct_version != 3 && options->struct_version != 4)) + { + rc = MQTTCLIENT_BAD_STRUCTURE; + goto exit; + } + + if (options->will) /* check validity of will options structure */ + { + if (strncmp(options->will->struct_id, "MQTW", 4) != 0 || options->will->struct_version != 0) + { + rc = MQTTCLIENT_BAD_STRUCTURE; + goto exit; + } + } + +#if defined(OPENSSL) + if (options->struct_version != 0 && options->ssl) /* check validity of SSL options structure */ + { + if (strncmp(options->ssl->struct_id, "MQTS", 4) != 0 || options->ssl->struct_version != 0) + { + rc = MQTTCLIENT_BAD_STRUCTURE; + goto exit; + } + } +#endif + + if ((options->username && !UTF8_validateString(options->username)) || + (options->password && !UTF8_validateString(options->password))) + { + rc = MQTTCLIENT_BAD_UTF8_STRING; + goto exit; + } + + if (options->struct_version < 2 || options->serverURIcount == 0) + rc = MQTTClient_connectURI(handle, options, m->serverURI); + else + { + int i; + + for (i = 0; i < options->serverURIcount; ++i) + { + char* serverURI = options->serverURIs[i]; + + if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0) + serverURI += strlen(URI_TCP); +#if defined(OPENSSL) + else if (strncmp(URI_SSL, serverURI, strlen(URI_SSL)) == 0) + { + serverURI += strlen(URI_SSL); + m->ssl = 1; + } +#endif + if ((rc = MQTTClient_connectURI(handle, options, serverURI)) == MQTTCLIENT_SUCCESS) + break; + } + } + +exit: + if (m->c->will) + { + free(m->c->will); + m->c->will = NULL; + } + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTClient_disconnect1(MQTTClient handle, int timeout, int internal, int stop) +{ + MQTTClients* m = handle; + START_TIME_TYPE start; + int rc = MQTTCLIENT_SUCCESS; + int was_connected = 0; + + FUNC_ENTRY; + Thread_lock_mutex(mqttclient_mutex); + + if (m == NULL || m->c == NULL) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + if (m->c->connected == 0 && m->c->connect_state == 0) + { + rc = MQTTCLIENT_DISCONNECTED; + goto exit; + } + was_connected = m->c->connected; /* should be 1 */ + if (m->c->connected != 0) + { + start = MQTTClient_start_clock(); + m->c->connect_state = -2; /* indicate disconnecting */ + while (m->c->inboundMsgs->count > 0 || m->c->outboundMsgs->count > 0) + { /* wait for all inflight message flows to finish, up to timeout */ + if (MQTTClient_elapsed(start) >= timeout) + break; + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_yield(); + Thread_lock_mutex(mqttclient_mutex); + } + } + + MQTTClient_closeSession(m->c); + + while (Thread_check_sem(m->connect_sem)) + Thread_wait_sem(m->connect_sem, 100); + while (Thread_check_sem(m->connack_sem)) + Thread_wait_sem(m->connack_sem, 100); + while (Thread_check_sem(m->suback_sem)) + Thread_wait_sem(m->suback_sem, 100); + while (Thread_check_sem(m->unsuback_sem)) + Thread_wait_sem(m->unsuback_sem, 100); +exit: + if (stop) + MQTTClient_stop(); + if (internal && m->cl && was_connected) + { + Log(TRACE_MIN, -1, "Calling connectionLost for client %s", m->c->clientID); + Thread_start(connectionLost_call, m); + } + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTClient_disconnect_internal(MQTTClient handle, int timeout) +{ + return MQTTClient_disconnect1(handle, timeout, 1, 1); +} + + +void MQTTProtocol_closeSession(Clients* c, int sendwill) +{ + MQTTClient_disconnect_internal((MQTTClient)c->context, 0); +} + + +int MQTTClient_disconnect(MQTTClient handle, int timeout) +{ + return MQTTClient_disconnect1(handle, timeout, 0, 1); +} + + +int MQTTClient_isConnected(MQTTClient handle) +{ + MQTTClients* m = handle; + int rc = 0; + + FUNC_ENTRY; + Thread_lock_mutex(mqttclient_mutex); + if (m && m->c) + rc = m->c->connected; + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTClient_subscribeMany(MQTTClient handle, int count, char* const* topic, int* qos) +{ + MQTTClients* m = handle; + List* topics = ListInitialize(); + List* qoss = ListInitialize(); + int i = 0; + int rc = MQTTCLIENT_FAILURE; + int msgid = 0; + + FUNC_ENTRY; + Thread_lock_mutex(mqttclient_mutex); + + if (m == NULL || m->c == NULL) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTCLIENT_DISCONNECTED; + goto exit; + } + for (i = 0; i < count; i++) + { + if (!UTF8_validateString(topic[i])) + { + rc = MQTTCLIENT_BAD_UTF8_STRING; + goto exit; + } + + if(qos[i] < 0 || qos[i] > 2) + { + rc = MQTTCLIENT_BAD_QOS; + goto exit; + } + } + if ((msgid = MQTTProtocol_assignMsgId(m->c)) == 0) + { + rc = MQTTCLIENT_MAX_MESSAGES_INFLIGHT; + goto exit; + } + + for (i = 0; i < count; i++) + { + ListAppend(topics, topic[i], strlen(topic[i])); + ListAppend(qoss, &qos[i], sizeof(int)); + } + + rc = MQTTProtocol_subscribe(m->c, topics, qoss, msgid); + ListFreeNoContent(topics); + ListFreeNoContent(qoss); + + if (rc == TCPSOCKET_COMPLETE) + { + MQTTPacket* pack = NULL; + + Thread_unlock_mutex(mqttclient_mutex); + pack = MQTTClient_waitfor(handle, SUBACK, &rc, 10000L); + Thread_lock_mutex(mqttclient_mutex); + if (pack != NULL) + { + Suback* sub = (Suback*)pack; + ListElement* current = NULL; + i = 0; + while (ListNextElement(sub->qoss, ¤t)) + { + int* reqqos = (int*)(current->content); + qos[i++] = *reqqos; + } + rc = MQTTProtocol_handleSubacks(pack, m->c->net.socket); + m->pack = NULL; + } + else + rc = SOCKET_ERROR; + } + + if (rc == SOCKET_ERROR) + { + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_disconnect_internal(handle, 0); + Thread_lock_mutex(mqttclient_mutex); + } + else if (rc == TCPSOCKET_COMPLETE) + rc = MQTTCLIENT_SUCCESS; + +exit: + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTClient_subscribe(MQTTClient handle, const char* topic, int qos) +{ + int rc = 0; + char *const topics[] = {(char*)topic}; + + FUNC_ENTRY; + rc = MQTTClient_subscribeMany(handle, 1, topics, &qos); + if (qos == MQTT_BAD_SUBSCRIBE) /* addition for MQTT 3.1.1 - error code from subscribe */ + rc = MQTT_BAD_SUBSCRIBE; + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTClient_unsubscribeMany(MQTTClient handle, int count, char* const* topic) +{ + MQTTClients* m = handle; + List* topics = ListInitialize(); + int i = 0; + int rc = SOCKET_ERROR; + int msgid = 0; + + FUNC_ENTRY; + Thread_lock_mutex(mqttclient_mutex); + + if (m == NULL || m->c == NULL) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTCLIENT_DISCONNECTED; + goto exit; + } + for (i = 0; i < count; i++) + { + if (!UTF8_validateString(topic[i])) + { + rc = MQTTCLIENT_BAD_UTF8_STRING; + goto exit; + } + } + if ((msgid = MQTTProtocol_assignMsgId(m->c)) == 0) + { + rc = MQTTCLIENT_MAX_MESSAGES_INFLIGHT; + goto exit; + } + + for (i = 0; i < count; i++) + ListAppend(topics, topic[i], strlen(topic[i])); + rc = MQTTProtocol_unsubscribe(m->c, topics, msgid); + ListFreeNoContent(topics); + + if (rc == TCPSOCKET_COMPLETE) + { + MQTTPacket* pack = NULL; + + Thread_unlock_mutex(mqttclient_mutex); + pack = MQTTClient_waitfor(handle, UNSUBACK, &rc, 10000L); + Thread_lock_mutex(mqttclient_mutex); + if (pack != NULL) + { + rc = MQTTProtocol_handleUnsubacks(pack, m->c->net.socket); + m->pack = NULL; + } + else + rc = SOCKET_ERROR; + } + + if (rc == SOCKET_ERROR) + { + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_disconnect_internal(handle, 0); + Thread_lock_mutex(mqttclient_mutex); + } + +exit: + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTClient_unsubscribe(MQTTClient handle, const char* topic) +{ + int rc = 0; + char *const topics[] = {(char*)topic}; + FUNC_ENTRY; + rc = MQTTClient_unsubscribeMany(handle, 1, topics); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTClient_publish(MQTTClient handle, const char* topicName, int payloadlen, void* payload, + int qos, int retained, MQTTClient_deliveryToken* deliveryToken) +{ + int rc = MQTTCLIENT_SUCCESS; + MQTTClients* m = handle; + Messages* msg = NULL; + Publish* p = NULL; + int blocked = 0; + int msgid = 0; + + FUNC_ENTRY; + Thread_lock_mutex(mqttclient_mutex); + + if (m == NULL || m->c == NULL) + rc = MQTTCLIENT_FAILURE; + else if (m->c->connected == 0) + rc = MQTTCLIENT_DISCONNECTED; + else if (!UTF8_validateString(topicName)) + rc = MQTTCLIENT_BAD_UTF8_STRING; + if (rc != MQTTCLIENT_SUCCESS) + goto exit; + + /* If outbound queue is full, block until it is not */ + while (m->c->outboundMsgs->count >= m->c->maxInflightMessages || + Socket_noPendingWrites(m->c->net.socket) == 0) /* wait until the socket is free of large packets being written */ + { + if (blocked == 0) + { + blocked = 1; + Log(TRACE_MIN, -1, "Blocking publish on queue full for client %s", m->c->clientID); + } + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_yield(); + Thread_lock_mutex(mqttclient_mutex); + if (m->c->connected == 0) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + } + if (blocked == 1) + Log(TRACE_MIN, -1, "Resuming publish now queue not full for client %s", m->c->clientID); + if (qos > 0 && (msgid = MQTTProtocol_assignMsgId(m->c)) == 0) + { /* this should never happen as we've waited for spaces in the queue */ + rc = MQTTCLIENT_MAX_MESSAGES_INFLIGHT; + goto exit; + } + + p = malloc(sizeof(Publish)); + + p->payload = payload; + p->payloadlen = payloadlen; + p->topic = (char*)topicName; + p->msgId = msgid; + + rc = MQTTProtocol_startPublish(m->c, p, qos, retained, &msg); + + /* If the packet was partially written to the socket, wait for it to complete. + * However, if the client is disconnected during this time and qos is not 0, still return success, as + * the packet has already been written to persistence and assigned a message id so will + * be sent when the client next connects. + */ + if (rc == TCPSOCKET_INTERRUPTED) + { + while (m->c->connected == 1 && SocketBuffer_getWrite(m->c->net.socket)) + { + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_yield(); + Thread_lock_mutex(mqttclient_mutex); + } + rc = (qos > 0 || m->c->connected == 1) ? MQTTCLIENT_SUCCESS : MQTTCLIENT_FAILURE; + } + + if (deliveryToken && qos > 0) + *deliveryToken = msg->msgid; + + free(p); + + if (rc == SOCKET_ERROR) + { + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_disconnect_internal(handle, 0); + Thread_lock_mutex(mqttclient_mutex); + /* Return success for qos > 0 as the send will be retried automatically */ + rc = (qos > 0) ? MQTTCLIENT_SUCCESS : MQTTCLIENT_FAILURE; + } + +exit: + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + + +int MQTTClient_publishMessage(MQTTClient handle, const char* topicName, MQTTClient_message* message, + MQTTClient_deliveryToken* deliveryToken) +{ + int rc = MQTTCLIENT_SUCCESS; + + FUNC_ENTRY; + if (message == NULL) + { + rc = MQTTCLIENT_NULL_PARAMETER; + goto exit; + } + + if (strncmp(message->struct_id, "MQTM", 4) != 0 || message->struct_version != 0) + { + rc = MQTTCLIENT_BAD_STRUCTURE; + goto exit; + } + + rc = MQTTClient_publish(handle, topicName, message->payloadlen, message->payload, + message->qos, message->retained, deliveryToken); +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTClient_retry(void) +{ + time_t now; + + FUNC_ENTRY; + time(&(now)); + if (difftime(now, last) > 5) + { + time(&(last)); + MQTTProtocol_keepalive(now); + MQTTProtocol_retry(now, 1, 0); + } + else + MQTTProtocol_retry(now, 0, 0); + FUNC_EXIT; +} + + +MQTTPacket* MQTTClient_cycle(int* sock, unsigned long timeout, int* rc) +{ + struct timeval tp = {0L, 0L}; + static Ack ack; + MQTTPacket* pack = NULL; + + FUNC_ENTRY; + if (timeout > 0L) + { + tp.tv_sec = timeout / 1000; + tp.tv_usec = (timeout % 1000) * 1000; /* this field is microseconds! */ + } + +#if defined(OPENSSL) + if ((*sock = SSLSocket_getPendingRead()) == -1) + { + /* 0 from getReadySocket indicates no work to do, -1 == error, but can happen normally */ +#endif + *sock = Socket_getReadySocket(0, &tp); +#if defined(OPENSSL) + } +#endif + Thread_lock_mutex(mqttclient_mutex); + if (*sock > 0) + { + MQTTClients* m = NULL; + if (ListFindItem(handles, sock, clientSockCompare) != NULL) + m = (MQTTClient)(handles->current->content); + if (m != NULL) + { + if (m->c->connect_state == 1 || m->c->connect_state == 2) + *rc = 0; /* waiting for connect state to clear */ + else + { + pack = MQTTPacket_Factory(&m->c->net, rc); + if (*rc == TCPSOCKET_INTERRUPTED) + *rc = 0; + } + } + if (pack) + { + int freed = 1; + + /* Note that these handle... functions free the packet structure that they are dealing with */ + if (pack->header.bits.type == PUBLISH) + *rc = MQTTProtocol_handlePublishes(pack, *sock); + else if (pack->header.bits.type == PUBACK || pack->header.bits.type == PUBCOMP) + { + int msgid; + + ack = (pack->header.bits.type == PUBCOMP) ? *(Pubcomp*)pack : *(Puback*)pack; + msgid = ack.msgId; + *rc = (pack->header.bits.type == PUBCOMP) ? + MQTTProtocol_handlePubcomps(pack, *sock) : MQTTProtocol_handlePubacks(pack, *sock); + if (m && m->dc) + { + Log(TRACE_MIN, -1, "Calling deliveryComplete for client %s, msgid %d", m->c->clientID, msgid); + (*(m->dc))(m->context, msgid); + } + } + else if (pack->header.bits.type == PUBREC) + *rc = MQTTProtocol_handlePubrecs(pack, *sock); + else if (pack->header.bits.type == PUBREL) + *rc = MQTTProtocol_handlePubrels(pack, *sock); + else if (pack->header.bits.type == PINGRESP) + *rc = MQTTProtocol_handlePingresps(pack, *sock); + else + freed = 0; + if (freed) + pack = NULL; + } + } + MQTTClient_retry(); + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(*rc); + return pack; +} + + +MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* rc, long timeout) +{ + MQTTPacket* pack = NULL; + MQTTClients* m = handle; + START_TIME_TYPE start = MQTTClient_start_clock(); + + FUNC_ENTRY; + if (((MQTTClients*)handle) == NULL) + { + *rc = MQTTCLIENT_FAILURE; + goto exit; + } + + if (running) + { + if (packet_type == CONNECT) + { + if ((*rc = Thread_wait_sem(m->connect_sem, timeout)) == 0) + *rc = m->rc; + } + else if (packet_type == CONNACK) + *rc = Thread_wait_sem(m->connack_sem, timeout); + else if (packet_type == SUBACK) + *rc = Thread_wait_sem(m->suback_sem, timeout); + else if (packet_type == UNSUBACK) + *rc = Thread_wait_sem(m->unsuback_sem, timeout); + if (*rc == 0 && packet_type != CONNECT && m->pack == NULL) + Log(LOG_ERROR, -1, "waitfor unexpectedly is NULL for client %s, packet_type %d, timeout %ld", m->c->clientID, packet_type, timeout); + pack = m->pack; + } + else + { + *rc = TCPSOCKET_COMPLETE; + while (1) + { + int sock = -1; + pack = MQTTClient_cycle(&sock, 100L, rc); + if (sock == m->c->net.socket) + { + if (*rc == SOCKET_ERROR) + break; + if (pack && (pack->header.bits.type == packet_type)) + break; + if (m->c->connect_state == 1) + { + int error; + socklen_t len = sizeof(error); + + if ((*rc = getsockopt(m->c->net.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0) + *rc = error; + break; + } +#if defined(OPENSSL) + else if (m->c->connect_state == 2) + { + *rc = SSLSocket_connect(m->c->net.ssl, sock); + if (*rc == SSL_FATAL) + break; + else if (*rc == 1) /* rc == 1 means SSL connect has finished and succeeded */ + { + if (!m->c->cleansession && m->c->session == NULL) + m->c->session = SSL_get1_session(m->c->net.ssl); + break; + } + } +#endif + else if (m->c->connect_state == 3) + { + int error; + socklen_t len = sizeof(error); + + if (getsockopt(m->c->net.socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len) == 0) + { + if (error) + { + *rc = error; + break; + } + } + } + } + if (MQTTClient_elapsed(start) > timeout) + { + pack = NULL; + break; + } + } + } + +exit: + FUNC_EXIT_RC(*rc); + return pack; +} + + +int MQTTClient_receive(MQTTClient handle, char** topicName, int* topicLen, MQTTClient_message** message, + unsigned long timeout) +{ + int rc = TCPSOCKET_COMPLETE; + START_TIME_TYPE start = MQTTClient_start_clock(); + unsigned long elapsed = 0L; + MQTTClients* m = handle; + + FUNC_ENTRY; + if (m == NULL || m->c == NULL) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTCLIENT_DISCONNECTED; + goto exit; + } + + *topicName = NULL; + *message = NULL; + + /* if there is already a message waiting, don't hang around but still do some packet handling */ + if (m->c->messageQueue->count > 0) + timeout = 0L; + + elapsed = MQTTClient_elapsed(start); + do + { + int sock = 0; + MQTTClient_cycle(&sock, (timeout > elapsed) ? timeout - elapsed : 0L, &rc); + + if (rc == SOCKET_ERROR) + { + if (ListFindItem(handles, &sock, clientSockCompare) && /* find client corresponding to socket */ + (MQTTClient)(handles->current->content) == handle) + break; /* there was an error on the socket we are interested in */ + } + elapsed = MQTTClient_elapsed(start); + } + while (elapsed < timeout && m->c->messageQueue->count == 0); + + if (m->c->messageQueue->count > 0) + rc = MQTTClient_deliverMessage(rc, m, topicName, topicLen, message); + + if (rc == SOCKET_ERROR) + MQTTClient_disconnect_internal(handle, 0); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +void MQTTClient_yield(void) +{ + START_TIME_TYPE start = MQTTClient_start_clock(); + unsigned long elapsed = 0L; + unsigned long timeout = 100L; + int rc = 0; + + FUNC_ENTRY; + if (running) + { + MQTTClient_sleep(timeout); + goto exit; + } + + elapsed = MQTTClient_elapsed(start); + do + { + int sock = -1; + MQTTClient_cycle(&sock, (timeout > elapsed) ? timeout - elapsed : 0L, &rc); + if (rc == SOCKET_ERROR && ListFindItem(handles, &sock, clientSockCompare)) + { + MQTTClients* m = (MQTTClient)(handles->current->content); + if (m->c->connect_state != -2) + MQTTClient_disconnect_internal(m, 0); + } + elapsed = MQTTClient_elapsed(start); + } + while (elapsed < timeout); +exit: + FUNC_EXIT; +} + + +int pubCompare(void* a, void* b) +{ + Messages* msg = (Messages*)a; + return msg->publish == (Publications*)b; +} + + +int MQTTClient_waitForCompletion(MQTTClient handle, MQTTClient_deliveryToken mdt, unsigned long timeout) +{ + int rc = MQTTCLIENT_FAILURE; + START_TIME_TYPE start = MQTTClient_start_clock(); + unsigned long elapsed = 0L; + MQTTClients* m = handle; + + FUNC_ENTRY; + Thread_lock_mutex(mqttclient_mutex); + + if (m == NULL || m->c == NULL) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + if (m->c->connected == 0) + { + rc = MQTTCLIENT_DISCONNECTED; + goto exit; + } + + if (ListFindItem(m->c->outboundMsgs, &mdt, messageIDCompare) == NULL) + { + rc = MQTTCLIENT_SUCCESS; /* well we couldn't find it */ + goto exit; + } + + elapsed = MQTTClient_elapsed(start); + while (elapsed < timeout) + { + Thread_unlock_mutex(mqttclient_mutex); + MQTTClient_yield(); + Thread_lock_mutex(mqttclient_mutex); + if (ListFindItem(m->c->outboundMsgs, &mdt, messageIDCompare) == NULL) + { + rc = MQTTCLIENT_SUCCESS; /* well we couldn't find it */ + goto exit; + } + elapsed = MQTTClient_elapsed(start); + } + +exit: + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTClient_getPendingDeliveryTokens(MQTTClient handle, MQTTClient_deliveryToken **tokens) +{ + int rc = MQTTCLIENT_SUCCESS; + MQTTClients* m = handle; + *tokens = NULL; + + FUNC_ENTRY; + Thread_lock_mutex(mqttclient_mutex); + + if (m == NULL) + { + rc = MQTTCLIENT_FAILURE; + goto exit; + } + + if (m->c && m->c->outboundMsgs->count > 0) + { + ListElement* current = NULL; + int count = 0; + + *tokens = malloc(sizeof(MQTTClient_deliveryToken) * (m->c->outboundMsgs->count + 1)); + /*Heap_unlink(__FILE__, __LINE__, *tokens);*/ + while (ListNextElement(m->c->outboundMsgs, ¤t)) + { + Messages* m = (Messages*)(current->content); + (*tokens)[count++] = m->msgid; + } + (*tokens)[count] = -1; + } + +exit: + Thread_unlock_mutex(mqttclient_mutex); + FUNC_EXIT_RC(rc); + return rc; +} + +MQTTClient_nameValue* MQTTClient_getVersionInfo() +{ + #define MAX_INFO_STRINGS 8 + static MQTTClient_nameValue libinfo[MAX_INFO_STRINGS + 1]; + int i = 0; + + libinfo[i].name = "Product name"; + libinfo[i++].value = "Paho Synchronous MQTT C Client Library"; + + libinfo[i].name = "Version"; + libinfo[i++].value = CLIENT_VERSION; + + libinfo[i].name = "Build level"; + libinfo[i++].value = BUILD_TIMESTAMP; +#if defined(OPENSSL) + libinfo[i].name = "OpenSSL version"; + libinfo[i++].value = SSLeay_version(SSLEAY_VERSION); + + libinfo[i].name = "OpenSSL flags"; + libinfo[i++].value = SSLeay_version(SSLEAY_CFLAGS); + + libinfo[i].name = "OpenSSL build timestamp"; + libinfo[i++].value = SSLeay_version(SSLEAY_BUILT_ON); + + libinfo[i].name = "OpenSSL platform"; + libinfo[i++].value = SSLeay_version(SSLEAY_PLATFORM); + + libinfo[i].name = "OpenSSL directory"; + libinfo[i++].value = SSLeay_version(SSLEAY_DIR); +#endif + libinfo[i].name = NULL; + libinfo[i].value = NULL; + return libinfo; +} + + +/** + * See if any pending writes have been completed, and cleanup if so. + * Cleaning up means removing any publication data that was stored because the write did + * not originally complete. + */ +void MQTTProtocol_checkPendingWrites() +{ + FUNC_ENTRY; + if (state.pending_writes.count > 0) + { + ListElement* le = state.pending_writes.first; + while (le) + { + if (Socket_noPendingWrites(((pending_write*)(le->content))->socket)) + { + MQTTProtocol_removePublication(((pending_write*)(le->content))->p); + state.pending_writes.current = le; + ListRemove(&(state.pending_writes), le->content); /* does NextElement itself */ + le = state.pending_writes.current; + } + else + ListNextElement(&(state.pending_writes), &le); + } + } + FUNC_EXIT; +} + + +void MQTTClient_writeComplete(int socket) +{ + ListElement* found = NULL; + + FUNC_ENTRY; + /* a partial write is now complete for a socket - this will be on a publish*/ + + MQTTProtocol_checkPendingWrites(); + + /* find the client using this socket */ + if ((found = ListFindItem(handles, &socket, clientSockCompare)) != NULL) + { + MQTTClients* m = (MQTTClients*)(found->content); + + time(&(m->c->net.lastSent)); + } + FUNC_EXIT; +} diff --git a/Sources/paho/src/MQTTPacket.c b/Sources/paho/src/MQTTPacket.c new file mode 100644 index 0000000..be8f80f --- /dev/null +++ b/Sources/paho/src/MQTTPacket.c @@ -0,0 +1,737 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates + * Ian Craggs - MQTT 3.1.1 support + *******************************************************************************/ + +/** + * @file + * \brief functions to deal with reading and writing of MQTT packets from and to sockets + * + * Some other related functions are in the MQTTPacketOut module + */ + +#include "MQTTPacket.h" +#include "Log.h" +#if !defined(NO_PERSISTENCE) + #include "MQTTPersistence.h" +#endif +#include "Messages.h" +#include "StackTrace.h" + +#include +#include + +#include "Heap.h" + +#if !defined(min) +#define min(A,B) ( (A) < (B) ? (A):(B)) +#endif + +/** + * List of the predefined MQTT v3 packet names. + */ +static char* packet_names[] = +{ + "RESERVED", "CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC", "PUBREL", + "PUBCOMP", "SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK", + "PINGREQ", "PINGRESP", "DISCONNECT" +}; + +char** MQTTClient_packet_names = packet_names; + + +/** + * Converts an MQTT packet code into its name + * @param ptype packet code + * @return the corresponding string, or "UNKNOWN" + */ +char* MQTTPacket_name(int ptype) +{ + return (ptype >= 0 && ptype <= DISCONNECT) ? packet_names[ptype] : "UNKNOWN"; +} + +/** + * Array of functions to build packets, indexed according to packet code + */ +pf new_packets[] = +{ + NULL, /**< reserved */ + NULL, /**< MQTTPacket_connect*/ + MQTTPacket_connack, /**< CONNACK */ + MQTTPacket_publish, /**< PUBLISH */ + MQTTPacket_ack, /**< PUBACK */ + MQTTPacket_ack, /**< PUBREC */ + MQTTPacket_ack, /**< PUBREL */ + MQTTPacket_ack, /**< PUBCOMP */ + NULL, /**< MQTTPacket_subscribe*/ + MQTTPacket_suback, /**< SUBACK */ + NULL, /**< MQTTPacket_unsubscribe*/ + MQTTPacket_ack, /**< UNSUBACK */ + MQTTPacket_header_only, /**< PINGREQ */ + MQTTPacket_header_only, /**< PINGRESP */ + MQTTPacket_header_only /**< DISCONNECT */ +}; + + +/** + * Reads one MQTT packet from a socket. + * @param socket a socket from which to read an MQTT packet + * @param error pointer to the error code which is completed if no packet is returned + * @return the packet structure or NULL if there was an error + */ +void* MQTTPacket_Factory(networkHandles* net, int* error) +{ + char* data = NULL; + static Header header; + int remaining_length, ptype; + size_t remaining_length_new; + void* pack = NULL; + int actual_len = 0; + + FUNC_ENTRY; + *error = SOCKET_ERROR; /* indicate whether an error occurred, or not */ + + /* read the packet data from the socket */ +#if defined(OPENSSL) + *error = (net->ssl) ? SSLSocket_getch(net->ssl, net->socket, &header.byte) : Socket_getch(net->socket, &header.byte); +#else + *error = Socket_getch(net->socket, &header.byte); +#endif + if (*error != TCPSOCKET_COMPLETE) /* first byte is the header byte */ + goto exit; /* packet not read, *error indicates whether SOCKET_ERROR occurred */ + + /* now read the remaining length, so we know how much more to read */ + if ((*error = MQTTPacket_decode(net, &remaining_length)) != TCPSOCKET_COMPLETE) + goto exit; /* packet not read, *error indicates whether SOCKET_ERROR occurred */ + + /* now read the rest, the variable header and payload */ +#if defined(OPENSSL) + data = (net->ssl) ? SSLSocket_getdata(net->ssl, net->socket, remaining_length, &actual_len) : + Socket_getdata(net->socket, remaining_length, &actual_len); +#else + data = Socket_getdata(net->socket, remaining_length, &actual_len); +#endif + if (data == NULL) + { + *error = SOCKET_ERROR; + goto exit; /* socket error */ + } + + if (actual_len != remaining_length) + *error = TCPSOCKET_INTERRUPTED; + else + { + ptype = header.bits.type; + if (ptype < CONNECT || ptype > DISCONNECT || new_packets[ptype] == NULL) + Log(TRACE_MIN, 2, NULL, ptype); + else + { + if ((pack = (*new_packets[ptype])(header.byte, data, remaining_length)) == NULL) + *error = BAD_MQTT_PACKET; +#if !defined(NO_PERSISTENCE) + else if (header.bits.type == PUBLISH && header.bits.qos == 2) + { + int buf0len; + char *buf = malloc(10); + buf[0] = header.byte; + buf0len = 1 + MQTTPacket_encode(&buf[1], remaining_length); + remaining_length_new = remaining_length; + *error = MQTTPersistence_put(net->socket, buf, buf0len, 1, + &data, &remaining_length_new, header.bits.type, ((Publish *)pack)->msgId, 1); + free(buf); + } +#endif + } + } + if (pack) + time(&(net->lastReceived)); +exit: + FUNC_EXIT_RC(*error); + return pack; +} + + +/** + * Sends an MQTT packet in one system call write + * @param socket the socket to which to write the data + * @param header the one-byte MQTT header + * @param buffer the rest of the buffer to write (not including remaining length) + * @param buflen the length of the data in buffer to be written + * @return the completion code (TCPSOCKET_COMPLETE etc) + */ +int MQTTPacket_send(networkHandles* net, Header header, char* buffer, size_t buflen, int free) +{ + int rc, buf0len; + char *buf; + + FUNC_ENTRY; + buf = malloc(10); + buf[0] = header.byte; + buf0len = 1 + MQTTPacket_encode(&buf[1], buflen); +#if !defined(NO_PERSISTENCE) + if (header.bits.type == PUBREL) + { + char* ptraux = buffer; + int msgId = readInt(&ptraux); + rc = MQTTPersistence_put(net->socket, buf, buf0len, 1, &buffer, &buflen, + header.bits.type, msgId, 0); + } +#endif + +#if defined(OPENSSL) + if (net->ssl) + rc = SSLSocket_putdatas(net->ssl, net->socket, buf, buf0len, 1, &buffer, &buflen, &free); + else +#endif + rc = Socket_putdatas(net->socket, buf, buf0len, 1, &buffer, &buflen, &free); + + if (rc == TCPSOCKET_COMPLETE) + time(&(net->lastSent)); + + if (rc != TCPSOCKET_INTERRUPTED) + free(buf); + + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Sends an MQTT packet from multiple buffers in one system call write + * @param socket the socket to which to write the data + * @param header the one-byte MQTT header + * @param count the number of buffers + * @param buffers the rest of the buffers to write (not including remaining length) + * @param buflens the lengths of the data in the array of buffers to be written + * @return the completion code (TCPSOCKET_COMPLETE etc) + */ +int MQTTPacket_sends(networkHandles* net, Header header, int count, char** buffers, size_t* buflens, int* frees) +{ + int i, rc, buf0len, total = 0; + char *buf; + + FUNC_ENTRY; + buf = malloc(10); + buf[0] = header.byte; + for (i = 0; i < count; i++) + total += buflens[i]; + buf0len = 1 + MQTTPacket_encode(&buf[1], total); +#if !defined(NO_PERSISTENCE) + if (header.bits.type == PUBLISH && header.bits.qos != 0) + { /* persist PUBLISH QoS1 and Qo2 */ + char *ptraux = buffers[2]; + int msgId = readInt(&ptraux); + rc = MQTTPersistence_put(net->socket, buf, buf0len, count, buffers, buflens, + header.bits.type, msgId, 0); + } +#endif +#if defined(OPENSSL) + if (net->ssl) + rc = SSLSocket_putdatas(net->ssl, net->socket, buf, buf0len, count, buffers, buflens, frees); + else +#endif + rc = Socket_putdatas(net->socket, buf, buf0len, count, buffers, buflens, frees); + + if (rc == TCPSOCKET_COMPLETE) + time(&(net->lastSent)); + + if (rc != TCPSOCKET_INTERRUPTED) + free(buf); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Encodes the message length according to the MQTT algorithm + * @param buf the buffer into which the encoded data is written + * @param length the length to be encoded + * @return the number of bytes written to buffer + */ +int MQTTPacket_encode(char* buf, int length) +{ + int rc = 0; + + FUNC_ENTRY; + do + { + char d = length % 128; + length /= 128; + /* if there are more digits to encode, set the top bit of this digit */ + if (length > 0) + d |= 0x80; + buf[rc++] = d; + } while (length > 0); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Decodes the message length according to the MQTT algorithm + * @param socket the socket from which to read the bytes + * @param value the decoded length returned + * @return the number of bytes read from the socket + */ +int MQTTPacket_decode(networkHandles* net, int* value) +{ + int rc = SOCKET_ERROR; + char c; + int multiplier = 1; + int len = 0; +#define MAX_NO_OF_REMAINING_LENGTH_BYTES 4 + + FUNC_ENTRY; + *value = 0; + do + { + if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES) + { + rc = SOCKET_ERROR; /* bad data */ + goto exit; + } +#if defined(OPENSSL) + rc = (net->ssl) ? SSLSocket_getch(net->ssl, net->socket, &c) : Socket_getch(net->socket, &c); +#else + rc = Socket_getch(net->socket, &c); +#endif + if (rc != TCPSOCKET_COMPLETE) + goto exit; + *value += (c & 127) * multiplier; + multiplier *= 128; + } while ((c & 128) != 0); +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Calculates an integer from two bytes read from the input buffer + * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned + * @return the integer value calculated + */ +int readInt(char** pptr) +{ + char* ptr = *pptr; + int len = 256*((unsigned char)(*ptr)) + (unsigned char)(*(ptr+1)); + *pptr += 2; + return len; +} + + +/** + * Reads a "UTF" string from the input buffer. UTF as in the MQTT v3 spec which really means + * a length delimited string. So it reads the two byte length then the data according to + * that length. The end of the buffer is provided too, so we can prevent buffer overruns caused + * by an incorrect length. + * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned + * @param enddata pointer to the end of the buffer not to be read beyond + * @param len returns the calculcated value of the length bytes read + * @return an allocated C string holding the characters read, or NULL if the length read would + * have caused an overrun. + * + */ +char* readUTFlen(char** pptr, char* enddata, int* len) +{ + char* string = NULL; + + FUNC_ENTRY; + if (enddata - (*pptr) > 1) /* enough length to read the integer? */ + { + *len = readInt(pptr); + if (&(*pptr)[*len] <= enddata) + { + string = malloc(*len+1); + memcpy(string, *pptr, *len); + string[*len] = '\0'; + *pptr += *len; + } + } + FUNC_EXIT; + return string; +} + + +/** + * Reads a "UTF" string from the input buffer. UTF as in the MQTT v3 spec which really means + * a length delimited string. So it reads the two byte length then the data according to + * that length. The end of the buffer is provided too, so we can prevent buffer overruns caused + * by an incorrect length. + * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned + * @param enddata pointer to the end of the buffer not to be read beyond + * @return an allocated C string holding the characters read, or NULL if the length read would + * have caused an overrun. + */ +char* readUTF(char** pptr, char* enddata) +{ + int len; + return readUTFlen(pptr, enddata, &len); +} + + +/** + * Reads one character from the input buffer. + * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned + * @return the character read + */ +unsigned char readChar(char** pptr) +{ + unsigned char c = **pptr; + (*pptr)++; + return c; +} + + +/** + * Writes one character to an output buffer. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param c the character to write + */ +void writeChar(char** pptr, char c) +{ + **pptr = c; + (*pptr)++; +} + + +/** + * Writes an integer as 2 bytes to an output buffer. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param anInt the integer to write + */ +void writeInt(char** pptr, int anInt) +{ + **pptr = (char)(anInt / 256); + (*pptr)++; + **pptr = (char)(anInt % 256); + (*pptr)++; +} + + +/** + * Writes a "UTF" string to an output buffer. Converts C string to length-delimited. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param string the C string to write + */ +void writeUTF(char** pptr, const char* string) +{ + int len = strlen(string); + writeInt(pptr, len); + memcpy(*pptr, string, len); + *pptr += len; +} + + +/** + * Function used in the new packets table to create packets which have only a header. + * @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_header_only(unsigned char aHeader, char* data, size_t datalen) +{ + static unsigned char header = 0; + header = aHeader; + return &header; +} + + +/** + * Send an MQTT disconnect packet down a socket. + * @param socket the open socket to send the data to + * @return the completion code (e.g. TCPSOCKET_COMPLETE) + */ +int MQTTPacket_send_disconnect(networkHandles *net, const char* clientID) +{ + Header header; + int rc = 0; + + FUNC_ENTRY; + header.byte = 0; + header.bits.type = DISCONNECT; + rc = MQTTPacket_send(net, header, NULL, 0, 0); + Log(LOG_PROTOCOL, 28, NULL, net->socket, clientID, rc); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Function used in the new packets table to create publish packets. + * @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_publish(unsigned char aHeader, char* data, size_t datalen) +{ + Publish* pack = malloc(sizeof(Publish)); + char* curdata = data; + char* enddata = &data[datalen]; + + FUNC_ENTRY; + pack->header.byte = aHeader; + if ((pack->topic = readUTFlen(&curdata, enddata, &pack->topiclen)) == NULL) /* Topic name on which to publish */ + { + free(pack); + pack = NULL; + goto exit; + } + if (pack->header.bits.qos > 0) /* Msgid only exists for QoS 1 or 2 */ + pack->msgId = readInt(&curdata); + else + pack->msgId = 0; + pack->payload = curdata; + pack->payloadlen = datalen-(curdata-data); +exit: + FUNC_EXIT; + return pack; +} + + +/** + * Free allocated storage for a publish packet. + * @param pack pointer to the publish packet structure + */ +void MQTTPacket_freePublish(Publish* pack) +{ + FUNC_ENTRY; + if (pack->topic != NULL) + free(pack->topic); + free(pack); + FUNC_EXIT; +} + + +/** + * Send an MQTT acknowledgement packet down a socket. + * @param type the MQTT packet type e.g. SUBACK + * @param msgid the MQTT message id to use + * @param dup boolean - whether to set the MQTT DUP flag + * @param net the network handle to send the data to + * @return the completion code (e.g. TCPSOCKET_COMPLETE) + */ +int MQTTPacket_send_ack(int type, int msgid, int dup, networkHandles *net) +{ + Header header; + int rc; + char *buf = malloc(2); + char *ptr = buf; + + FUNC_ENTRY; + header.byte = 0; + header.bits.type = type; + header.bits.dup = dup; + if (type == PUBREL) + header.bits.qos = 1; + writeInt(&ptr, msgid); + if ((rc = MQTTPacket_send(net, header, buf, 2, 1)) != TCPSOCKET_INTERRUPTED) + free(buf); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Send an MQTT PUBACK packet down a socket. + * @param msgid the MQTT message id to use + * @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_puback(int msgid, networkHandles* net, const char* clientID) +{ + int rc = 0; + + FUNC_ENTRY; + rc = MQTTPacket_send_ack(PUBACK, msgid, 0, net); + Log(LOG_PROTOCOL, 12, NULL, net->socket, clientID, msgid, rc); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Free allocated storage for a suback packet. + * @param pack pointer to the suback packet structure + */ +void MQTTPacket_freeSuback(Suback* pack) +{ + FUNC_ENTRY; + if (pack->qoss != NULL) + ListFree(pack->qoss); + free(pack); + FUNC_EXIT; +} + + +/** + * Send an MQTT PUBREC packet down a socket. + * @param msgid the MQTT message id to use + * @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_pubrec(int msgid, networkHandles* net, const char* clientID) +{ + int rc = 0; + + FUNC_ENTRY; + rc = MQTTPacket_send_ack(PUBREC, msgid, 0, net); + Log(LOG_PROTOCOL, 13, NULL, net->socket, clientID, msgid, rc); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Send an MQTT PUBREL packet down a socket. + * @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_pubrel(int msgid, int dup, networkHandles* net, const char* clientID) +{ + int rc = 0; + + FUNC_ENTRY; + rc = MQTTPacket_send_ack(PUBREL, msgid, dup, net); + Log(LOG_PROTOCOL, 16, NULL, net->socket, clientID, msgid, rc); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Send an MQTT PUBCOMP packet down a socket. + * @param msgid the MQTT message id to use + * @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_pubcomp(int msgid, networkHandles* net, const char* clientID) +{ + int rc = 0; + + FUNC_ENTRY; + rc = MQTTPacket_send_ack(PUBCOMP, msgid, 0, net); + Log(LOG_PROTOCOL, 18, NULL, net->socket, clientID, msgid, rc); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Function used in the new packets table to create acknowledgement packets. + * @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_ack(unsigned char aHeader, char* data, size_t datalen) +{ + Ack* pack = malloc(sizeof(Ack)); + char* curdata = data; + + FUNC_ENTRY; + pack->header.byte = aHeader; + pack->msgId = readInt(&curdata); + FUNC_EXIT; + return pack; +} + + +/** + * Send an MQTT PUBLISH packet down a socket. + * @param pack a structure from which to get some values to use, e.g topic, payload + * @param dup boolean - whether to set the MQTT DUP flag + * @param qos the value to use for the MQTT QoS setting + * @param retained boolean - whether to set the MQTT retained 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_publish(Publish* pack, int dup, int qos, int retained, networkHandles* net, const char* clientID) +{ + Header header; + char *topiclen; + int rc = -1; + + FUNC_ENTRY; + topiclen = malloc(2); + + header.bits.type = PUBLISH; + header.bits.dup = dup; + header.bits.qos = qos; + header.bits.retain = retained; + if (qos > 0) + { + char *buf = malloc(2); + char *ptr = buf; + char* bufs[4] = {topiclen, pack->topic, buf, pack->payload}; + size_t lens[4] = {2, strlen(pack->topic), 2, pack->payloadlen}; + int frees[4] = {1, 0, 1, 0}; + + writeInt(&ptr, pack->msgId); + ptr = topiclen; + writeInt(&ptr, lens[1]); + rc = MQTTPacket_sends(net, header, 4, bufs, lens, frees); + if (rc != TCPSOCKET_INTERRUPTED) + free(buf); + } + else + { + char* ptr = topiclen; + char* bufs[3] = {topiclen, pack->topic, pack->payload}; + size_t lens[3] = {2, strlen(pack->topic), pack->payloadlen}; + int frees[3] = {1, 0, 0}; + + writeInt(&ptr, lens[1]); + rc = MQTTPacket_sends(net, header, 3, bufs, lens, frees); + } + if (rc != TCPSOCKET_INTERRUPTED) + free(topiclen); + if (qos == 0) + Log(LOG_PROTOCOL, 27, NULL, net->socket, clientID, retained, rc); + else + Log(LOG_PROTOCOL, 10, NULL, net->socket, clientID, pack->msgId, qos, retained, rc, + min(20, pack->payloadlen), pack->payload); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Free allocated storage for a various packet tyoes + * @param pack pointer to the suback packet structure + */ +void MQTTPacket_free_packet(MQTTPacket* pack) +{ + FUNC_ENTRY; + if (pack->header.bits.type == PUBLISH) + MQTTPacket_freePublish((Publish*)pack); + /*else if (pack->header.type == SUBSCRIBE) + MQTTPacket_freeSubscribe((Subscribe*)pack, 1); + else if (pack->header.type == UNSUBSCRIBE) + MQTTPacket_freeUnsubscribe((Unsubscribe*)pack);*/ + else + free(pack); + FUNC_EXIT; +} diff --git a/Sources/paho/src/MQTTPacketOut.c b/Sources/paho/src/MQTTPacketOut.c new file mode 100644 index 0000000..9cae93f --- /dev/null +++ b/Sources/paho/src/MQTTPacketOut.c @@ -0,0 +1,268 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates + * Ian Craggs - MQTT 3.1.1 support + * Rong Xiang, Ian Craggs - C++ compatibility + *******************************************************************************/ + +/** + * @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 +#include + +#include "Heap.h" + + +/** + * Send an MQTT CONNECT packet down a socket. + * @param client a structure from which to get all the required values + * @param MQTTVersion the MQTT version to connect with + * @return the completion code (e.g. TCPSOCKET_COMPLETE) + */ +int MQTTPacket_send_connect(Clients* client, int MQTTVersion) +{ + char *buf, *ptr; + Connect packet; + int rc = -1, len; + + FUNC_ENTRY; + packet.header.byte = 0; + packet.header.bits.type = CONNECT; + + len = ((MQTTVersion == 3) ? 12 : 10) + strlen(client->clientID)+2; + if (client->will) + len += strlen(client->will->topic)+2 + strlen(client->will->msg)+2; + if (client->username) + len += strlen(client->username)+2; + if (client->password) + len += strlen(client->password)+2; + + ptr = buf = malloc(len); + if (MQTTVersion == 3) + { + writeUTF(&ptr, "MQIsdp"); + writeChar(&ptr, (char)3); + } + else if (MQTTVersion == 4) + { + writeUTF(&ptr, "MQTT"); + writeChar(&ptr, (char)4); + } + else + goto exit; + + packet.flags.all = 0; + 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); + writeUTF(&ptr, client->clientID); + if (client->will) + { + writeUTF(&ptr, client->will->topic); + writeUTF(&ptr, client->will->msg); + } + if (client->username) + writeUTF(&ptr, client->username); + if (client->password) + writeUTF(&ptr, client->password); + + rc = MQTTPacket_send(&client->net, packet.header, buf, len, 1); + Log(LOG_PROTOCOL, 0, NULL, client->net.socket, client->clientID, client->cleansession, rc); +exit: + if (rc != TCPSOCKET_INTERRUPTED) + free(buf); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Function used in the new packets table to create connack packets. + * @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(unsigned char aHeader, char* data, size_t datalen) +{ + Connack* pack = malloc(sizeof(Connack)); + char* curdata = data; + + FUNC_ENTRY; + pack->header.byte = aHeader; + pack->flags.all = readChar(&curdata); + pack->rc = readChar(&curdata); + FUNC_EXIT; + return pack; +} + + +/** + * 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; + size_t buflen = 0; + + FUNC_ENTRY; + header.byte = 0; + header.bits.type = PINGREQ; + rc = MQTTPacket_send(net, header, NULL, buflen,0); + 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, int msgid, int dup, networkHandles* net, const char* clientID) +{ + Header header; + char *data, *ptr; + int rc = -1; + ListElement *elem = NULL, *qosElem = NULL; + int datalen; + + 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 += strlen((char*)(elem->content)); + ptr = data = malloc(datalen); + + writeInt(&ptr, msgid); + elem = NULL; + while (ListNextElement(topics, &elem)) + { + ListNextElement(qoss, &qosElem); + writeUTF(&ptr, (char*)(elem->content)); + writeChar(&ptr, *(int*)(qosElem->content)); + } + rc = MQTTPacket_send(net, header, data, datalen, 1); + Log(LOG_PROTOCOL, 22, NULL, net->socket, clientID, msgid, rc); + if (rc != TCPSOCKET_INTERRUPTED) + free(data); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Function used in the new packets table to create suback packets. + * @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(unsigned char aHeader, char* data, size_t datalen) +{ + Suback* pack = malloc(sizeof(Suback)); + char* curdata = data; + + FUNC_ENTRY; + pack->header.byte = aHeader; + pack->msgId = readInt(&curdata); + pack->qoss = ListInitialize(); + while ((size_t)(curdata - data) < datalen) + { + int* newint; + newint = malloc(sizeof(int)); + *newint = (int)readChar(&curdata); + ListAppend(pack->qoss, newint, sizeof(int)); + } + 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, int msgid, int dup, networkHandles* net, const char* clientID) +{ + Header header; + char *data, *ptr; + int rc = -1; + 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 += strlen((char*)(elem->content)); + ptr = data = malloc(datalen); + + writeInt(&ptr, msgid); + elem = NULL; + while (ListNextElement(topics, &elem)) + writeUTF(&ptr, (char*)(elem->content)); + rc = MQTTPacket_send(net, header, data, datalen, 1); + Log(LOG_PROTOCOL, 25, NULL, net->socket, clientID, msgid, rc); + if (rc != TCPSOCKET_INTERRUPTED) + free(data); + FUNC_EXIT_RC(rc); + return rc; +} diff --git a/Sources/paho/src/MQTTPersistence.c b/Sources/paho/src/MQTTPersistence.c new file mode 100644 index 0000000..10f965c --- /dev/null +++ b/Sources/paho/src/MQTTPersistence.c @@ -0,0 +1,643 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - async client updates + * Ian Craggs - fix for bug 432903 - queue persistence + *******************************************************************************/ + +/** + * @file + * \brief Functions that apply to persistence operations. + * + */ + +#include +#include + +#include "MQTTPersistence.h" +#include "MQTTPersistenceDefault.h" +#include "MQTTProtocolClient.h" +#include "Heap.h" + + +/** + * 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 ) + { + per->context = malloc(strlen(pcontext) + 1); + 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 = MQTTCLIENT_PERSISTENCE_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; + + 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) + ; + else if (strncmp(msgkeys[i], PERSISTENCE_QUEUE_KEY, strlen(PERSISTENCE_QUEUE_KEY)) == 0) + ; + else if ((rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0) + { + MQTTPacket* pack = MQTTPersistence_restorePacket(buffer, buflen); + if ( pack != NULL ) + { + if ( strstr(msgkeys[i],PERSISTENCE_PUBLISH_RECEIVED) != NULL ) + { + Publish* publish = (Publish*)pack; + Messages* msg = NULL; + msg = MQTTProtocol_createMessage(publish, &msg, publish->header.bits.qos, publish->header.bits.retain); + msg->nextMessageType = PUBREL; + /* order does not matter for persisted received messages */ + ListAppend(c->inboundMsgs, msg, msg->len); + publish->topic = NULL; + MQTTPacket_freePublish(publish); + msgs_rcvd++; + } + else if ( strstr(msgkeys[i],PERSISTENCE_PUBLISH_SENT) != NULL ) + { + Publish* publish = (Publish*)pack; + Messages* msg = NULL; + char *key = malloc(MESSAGE_FILENAME_LENGTH + 1); + sprintf(key, "%s%d", PERSISTENCE_PUBREL, publish->msgId); + msg = MQTTProtocol_createMessage(publish, &msg, publish->header.bits.qos, publish->header.bits.retain); + 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 */ + msg->lastTouch = 0; + MQTTPersistence_insertInOrder(c->outboundMsgs, msg, msg->len); + publish->topic = NULL; + MQTTPacket_freePublish(publish); + free(key); + msgs_sent++; + } + else if ( strstr(msgkeys[i],PERSISTENCE_PUBREL) != NULL ) + { + /* orphaned PUBRELs ? */ + Pubrel* pubrel = (Pubrel*)pack; + char *key = malloc(MESSAGE_FILENAME_LENGTH + 1); + 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); + + 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(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])(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 msgId the message ID. + * @param scr 0 indicates message in the sending direction; 1 indicates message in the + * receiving direction. + * @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 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) + { + key = malloc(MESSAGE_FILENAME_LENGTH + 1); + nbufs = 1 + count; + lens = (int *) malloc(nbufs * sizeof(int)); + bufs = (char **)malloc(nbufs * sizeof(char *)); + lens[0] = buf0len; + bufs[0] = buf0; + for (i = 0; i < count; i++) + { + lens[i+1] = buflens[i]; + bufs[i+1] = buffers[i]; + } + + // key + if ( scr == 0 ) + { /* sending */ + if (htype == PUBLISH) /* PUBLISH QoS1 and QoS2*/ + sprintf(key, "%s%d", PERSISTENCE_PUBLISH_SENT, msgId); + if (htype == PUBREL) /* PUBREL */ + sprintf(key, "%s%d", PERSISTENCE_PUBREL, msgId); + } + if ( scr == 1 ) /* receiving PUBLISH QoS2 */ + sprintf(key, "%s%d", PERSISTENCE_PUBLISH_RECEIVED, msgId); + + rc = client->persistence->pput(client->phandle, key, nbufs, bufs, lens); + + free(key); + free(lens); + free(bufs); + } + + 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 ( (strcmp(type,PERSISTENCE_PUBLISH_SENT) == 0) && qos == 2 ) + { + 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", type, msgId) ; + rc = c->persistence->premove(c->phandle, key); + } + free(key); + } + + 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; + sprintf(key, "%s%d", PERSISTENCE_QUEUE_KEY, qe->seqno); + if ((rc = client->persistence->premove(client->phandle, key)) != 0) + Log(LOG_ERROR, 0, "Error %d removing qEntry from persistence", rc); + FUNC_EXIT_RC(rc); + return rc; +} + + +int MQTTPersistence_persistQueueEntry(Clients* aclient, MQTTPersistence_qEntry* qe) +{ + int rc = 0; + int nbufs = 8; + int bufindex = 0; + char key[PERSISTENCE_MAX_KEY_LENGTH + 1]; + int* lens = NULL; + void** bufs = NULL; + + FUNC_ENTRY; + lens = (int*)malloc(nbufs * sizeof(int)); + bufs = malloc(nbufs * sizeof(char *)); + + bufs[bufindex] = &qe->msg->payloadlen; + lens[bufindex++] = sizeof(qe->msg->payloadlen); + + bufs[bufindex] = qe->msg->payload; + lens[bufindex++] = qe->msg->payloadlen; + + bufs[bufindex] = &qe->msg->qos; + lens[bufindex++] = sizeof(qe->msg->qos); + + bufs[bufindex] = &qe->msg->retained; + lens[bufindex++] = sizeof(qe->msg->retained); + + bufs[bufindex] = &qe->msg->dup; + lens[bufindex++] = sizeof(qe->msg->dup); + + bufs[bufindex] = &qe->msg->msgid; + lens[bufindex++] = sizeof(qe->msg->msgid); + + bufs[bufindex] = qe->topicName; + lens[bufindex++] = strlen(qe->topicName) + 1; + + bufs[bufindex] = &qe->topicLen; + lens[bufindex++] = sizeof(qe->topicLen); + + sprintf(key, "%s%d", PERSISTENCE_QUEUE_KEY, ++aclient->qentry_seqno); + qe->seqno = aclient->qentry_seqno; + + if ((rc = aclient->persistence->pput(aclient->phandle, key, nbufs, (char**)bufs, lens)) != 0) + Log(LOG_ERROR, 0, "Error persisting queue entry, rc %d", rc); + + free(lens); + free(bufs); + + FUNC_EXIT_RC(rc); + return rc; +} + + +MQTTPersistence_qEntry* MQTTPersistence_restoreQueueEntry(char* buffer, size_t buflen) +{ + MQTTPersistence_qEntry* qe = NULL; + char* ptr = buffer; + int data_size; + + FUNC_ENTRY; + qe = malloc(sizeof(MQTTPersistence_qEntry)); + memset(qe, '\0', sizeof(MQTTPersistence_qEntry)); + + qe->msg = malloc(sizeof(MQTTPersistence_message)); + memset(qe->msg, '\0', sizeof(MQTTPersistence_message)); + + qe->msg->payloadlen = *(int*)ptr; + ptr += sizeof(int); + + data_size = qe->msg->payloadlen; + qe->msg->payload = malloc(data_size); + memcpy(qe->msg->payload, ptr, data_size); + ptr += data_size; + + qe->msg->qos = *(int*)ptr; + ptr += sizeof(int); + + qe->msg->retained = *(int*)ptr; + ptr += sizeof(int); + + qe->msg->dup = *(int*)ptr; + ptr += sizeof(int); + + qe->msg->msgid = *(int*)ptr; + ptr += sizeof(int); + + data_size = strlen(ptr) + 1; + qe->topicName = malloc(data_size); + strcpy(qe->topicName, ptr); + ptr += data_size; + + qe->topicLen = *(int*)ptr; + ptr += sizeof(int); + + FUNC_EXIT; + return qe; +} + + +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) + ; + else if ((rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0) + { + MQTTPersistence_qEntry* qe = MQTTPersistence_restoreQueueEntry(buffer, buflen); + + if (qe) + { + qe->seqno = atoi(msgkeys[i]+2); + 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 diff --git a/Sources/paho/src/MQTTPersistenceDefault.c b/Sources/paho/src/MQTTPersistenceDefault.c new file mode 100644 index 0000000..90e1c8b --- /dev/null +++ b/Sources/paho/src/MQTTPersistenceDefault.c @@ -0,0 +1,834 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - async client updates + *******************************************************************************/ + +/** + * @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 +#include +#include + +#if defined(WIN32) || defined(WIN64) + #include + /* 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 + #include + #include + 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 */ + perserverURI = malloc(strlen(serverURI) + 1); + strcpy(perserverURI, serverURI); + ptraux = strstr(perserverURI, ":"); + *ptraux = '-' ; + + /* consider '/' + '-' + '\0' */ + clientDir = malloc(strlen(dataDir) + strlen(clientID) + strlen(perserverURI) + 3); + 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 until the full path name is created.*/ + /* pTokDirName - holds the directory name that gets used by strtok. */ + pCrtDirName = (char*)malloc( strlen(clientDir) + 1 ); + pTokDirName = (char*)malloc( strlen(clientDir) + 1 ); + strcpy( pTokDirName, clientDir ); + + 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 */ + sprintf( pCrtDirName, "%s/%s", pCrtDirName, pToken ); + rc = pstmkdir( pCrtDirName ); + pToken = strtok_r( NULL, "\\/", &save_ptr ); + } + + *handle = clientDir; + + free(perserverURI); + free(pTokDirName); + free(pCrtDirName); + + 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 ( mkdir( pPathname, S_IRWXU | S_IRGRP ) != 0 ) + { +#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; + int bytesWritten = 0; + int 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 ); + sprintf(file, "%s/%s%s", clientDir, key, MESSAGE_FILENAME_EXTENSION); + + fp = fopen(file, "wb"); + if ( fp != NULL ) + { + for(i=0; id_name) + 2); + sprintf(filename, "%s/%s", dirname, dir_entry->d_name); + lstat(filename, &stat_info); + free(filename); + if(S_ISREG(stat_info.st_mode)) + { + filekey = malloc(strlen(dir_entry->d_name) + 1); + 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); + } + } + 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); + 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; + + 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 ) + 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 ) + fkeys = (char **)malloc(nfkeys * sizeof(char *)); + + /* copy the keys */ + hDir = FindFirstFileA(dir, &FileData); + if (hDir != INVALID_HANDLE_VALUE) + { + fFinished = 0; + i = 0; + while (!fFinished) + { + if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) + { + fkeys[i] = malloc(strlen(FileData.cFileName) + 1); + 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; + 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); + + 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); + } else + { + rc = MQTTCLIENT_PERSISTENCE_ERROR; + goto exit; + } + + if (nfkeys != 0) + { + fkeys = (char **)malloc(nfkeys * sizeof(char *)); + + /* 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); + + sprintf(temp, "%s/%s", dirname, dir_entry->d_name); + if (lstat(temp, &stat_info) == 0 && S_ISREG(stat_info.st_mode)) + { + fkeys[i] = malloc(strlen(dir_entry->d_name) + 1); + strcpy(fkeys[i], dir_entry->d_name); + ptraux = strstr(fkeys[i], MESSAGE_FILENAME_EXTENSION); + if ( ptraux != NULL ) + *ptraux = '\0' ; + i++; + } + free(temp); + } + closedir(dp); + } else + { + rc = MQTTCLIENT_PERSISTENCE_ERROR; + goto exit; + } + } + + *nkeys = nfkeys; + *keys = fkeys; + /* the caller must free keys */ + +exit: + 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 = "."; + char *clientID = "TheUTClient"; + 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 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 + +#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 + +void Protocol_processPublication(Publish* publish, Clients* client); +void MQTTProtocol_closeSession(Clients* client, int sendwill); + +extern MQTTProtocol state; +extern ClientStates* bstate; + +/** + * 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; +} + + +void MQTTProtocol_storeQoS0(Clients* pubclient, Publish* publish) +{ + int len; + pending_write* pw = NULL; + + FUNC_ENTRY; + /* store the publication until the write is finished */ + pw = malloc(sizeof(pending_write)); + Log(TRACE_MIN, 12, NULL); + pw->p = MQTTProtocol_storePublication(publish, &len); + pw->socket = pubclient->net.socket; + ListAppend(&(state.pending_writes), pw, sizeof(pending_write)+len); + /* we don't copy QoS 0 messages unless we have to, so now we have to tell the socket buffer where + the saved copy is */ + if (SocketBuffer_updateWrite(pw->socket, pw->p->topic, pw->p->payload) == NULL) + Log(LOG_SEVERE, 0, "Error updating write"); + 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 + */ +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); + 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; + } + 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 + * @return pointer to the message data stored + */ +Messages* MQTTProtocol_createMessage(Publish* publish, Messages **mm, int qos, int retained) +{ + Messages* m = malloc(sizeof(Messages)); + + FUNC_ENTRY; + m->len = sizeof(Messages); + if (*mm == NULL || (*mm)->publish == NULL) + { + int len1; + *mm = m; + m->publish = MQTTProtocol_storePublication(publish, &len1); + m->len += len1; + } + else + { + ++(((*mm)->publish)->refcount); + m->publish = (*mm)->publish; + } + m->msgid = publish->msgId; + m->qos = qos; + m->retain = retained; + time(&(m->lastTouch)); + if (qos == 2) + m->nextMessageType = PUBREC; + 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; + p->refcount = 1; + + *len = strlen(publish->topic)+1; + if (Heap_findItem(publish->topic)) + p->topic = publish->topic; + else + { + p->topic = malloc(*len); + strcpy(p->topic, publish->topic); + } + *len += sizeof(Publications); + + p->topiclen = publish->topiclen; + p->payloadlen = publish->payloadlen; + p->payload = malloc(publish->payloadlen); + memcpy(p->payload, publish->payload, p->payloadlen); + *len += publish->payloadlen; + + ListAppend(&(state.publications), p, *len); + 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->refcount) == 0) + { + free(p->payload); + free(p->topic); + ListRemove(&(state.publications), p); + } + FUNC_EXIT; +} + +/** + * Process an incoming publish 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_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, min(20, publish->payloadlen), publish->payload); + + if (publish->header.bits.qos == 0) + Protocol_processPublication(publish, client); + else if (publish->header.bits.qos == 1) + { + /* send puback before processing the publications because a lot of return publications could fill up the socket buffer */ + rc = MQTTPacket_send_puback(publish->msgId, &client->net, client->clientID); + /* if we get a socket error from sending the puback, should we ignore the publication? */ + Protocol_processPublication(publish, client); + } + else if (publish->header.bits.qos == 2) + { + /* store publication in inbound list */ + int len; + ListElement* listElem = NULL; + Messages* m = malloc(sizeof(Messages)); + Publications* 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->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); + ListInsert(client->inboundMsgs, m, sizeof(Messages) + len, listElem); + ListRemove(client->inboundMsgs, msg); + } else + ListAppend(client->inboundMsgs, m, sizeof(Messages) + len); + rc = MQTTPacket_send_pubrec(publish->msgId, &client->net, client->clientID); + publish->topic = NULL; + } + 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, PERSISTENCE_PUBLISH_SENT, m->qos, puback->msgId); + #endif + MQTTProtocol_removePublication(m->publish); + ListRemove(client->outboundMsgs, m); + } + } + 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 + { + rc = MQTTPacket_send_pubrel(pubrec->msgId, 0, &client->net, client->clientID); + m->nextMessageType = PUBCOMP; + time(&(m->lastTouch)); + } + } + 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 + /* Apparently this is "normal" behaviour, so we don't need to issue a warning */ + rc = MQTTPacket_send_pubcomp(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 + { + Publish publish; + + /* send pubcomp before processing the publications because a lot of return publications could fill up the socket buffer */ + rc = MQTTPacket_send_pubcomp(pubrel->msgId, &client->net, client->clientID); + publish.header.bits.qos = m->qos; + publish.header.bits.retain = m->retain; + publish.msgId = m->msgid; + publish.topic = m->publish->topic; + publish.topiclen = m->publish->topiclen; + publish.payload = m->publish->payload; + publish.payloadlen = m->publish->payloadlen; + Protocol_processPublication(&publish, client); + #if !defined(NO_PERSISTENCE) + rc += MQTTPersistence_remove(client, PERSISTENCE_PUBLISH_RECEIVED, m->qos, pubrel->msgId); + #endif + ListRemove(&(state.publications), m->publish); + ListRemove(client->inboundMsgs, m); + ++(state.msgs_received); + } + } + 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, PERSISTENCE_PUBLISH_SENT, m->qos, pubcomp->msgId); + #endif + MQTTProtocol_removePublication(m->publish); + ListRemove(client->outboundMsgs, m); + (++state.msgs_sent); + } + } + } + free(pack); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * MQTT protocol keepAlive processing. Sends PINGREQ packets as required. + * @param now current time + */ +void MQTTProtocol_keepalive(time_t now) +{ + ListElement* current = NULL; + + FUNC_ENTRY; + ListNextElement(bstate->clients, ¤t); + while (current) + { + Clients* client = (Clients*)(current->content); + ListNextElement(bstate->clients, ¤t); + if (client->connected && client->keepAliveInterval > 0 && + (difftime(now, client->net.lastSent) >= client->keepAliveInterval || + difftime(now, client->net.lastReceived) >= client->keepAliveInterval)) + { + if (client->ping_outstanding == 0) + { + 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.lastSent = now; + client->ping_outstanding = 1; + } + } + } + else + { + 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); + } + } + } + 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) + */ +void MQTTProtocol_retries(time_t 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 || difftime(now, m->lastTouch) > max(client->retryInterval, 10)) + { + 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; + 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); + time(&(m->lastTouch)); + } + } + 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->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 + time(&(m->lastTouch)); + } + /* 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(time_t 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); + if (client->will) + { + free(client->will->msg); + free(client->will->topic); + free(client->will); + } +#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); + free(client->sslopts); + } +#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); + } + 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); + MQTTStrncpy(temp, src, mlen); + return temp; +} diff --git a/Sources/paho/src/MQTTProtocolOut.c b/Sources/paho/src/MQTTProtocolOut.c new file mode 100644 index 0000000..c9d0d10 --- /dev/null +++ b/Sources/paho/src/MQTTProtocolOut.c @@ -0,0 +1,230 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, 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 + *******************************************************************************/ + +/** + * @file + * \brief Functions dealing with the MQTT protocol exchanges + * + * Some other related functions are in the MQTTProtocolClient module + */ + +#include + +#include "MQTTProtocolOut.h" +#include "StackTrace.h" +#include "Heap.h" + +extern MQTTProtocol state; +extern ClientStates* bstate; + + +/** + * Separates an address:port into two separate values + * @param uri the input string - hostname:port + * @param port the returned port integer + * @return the address string + */ +char* MQTTProtocol_addressPort(const char* uri, int* port) +{ + char* colon_pos = strrchr(uri, ':'); /* reverse find to allow for ':' in IPv6 addresses */ + char* buf = (char*)uri; + int 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) + { + int addr_len = colon_pos - uri; + buf = malloc(addr_len + 1); + *port = atoi(colon_pos + 1); + MQTTStrncpy(buf, uri, addr_len+1); + } + else + *port = DEFAULT_PORT; + + len = strlen(buf); + if (buf[len - 1] == ']') + buf[len - 1] = '\0'; + + FUNC_EXIT; + return buf; +} + + +/** + * 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) + * @return return code + */ +#if defined(OPENSSL) +int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int ssl, int MQTTVersion) +#else +int MQTTProtocol_connect(const char* ip_address, Clients* aClient, int MQTTVersion) +#endif +{ + int rc, port; + char* addr; + + FUNC_ENTRY; + aClient->good = 1; + + addr = MQTTProtocol_addressPort(ip_address, &port); + rc = Socket_new(addr, port, &(aClient->net.socket)); + if (rc == EINPROGRESS || rc == EWOULDBLOCK) + aClient->connect_state = 1; /* 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 (SSLSocket_setSocketForSSL(&aClient->net, aClient->sslopts) != 1) + { + rc = SSLSocket_connect(aClient->net.ssl, aClient->net.socket); + if (rc == -1) + aClient->connect_state = 2; /* SSL connect called - wait for completion */ + } + else + rc = SOCKET_ERROR; + } +#endif + + if (rc == 0) + { + /* Now send the MQTT connect packet */ + if ((rc = MQTTPacket_send_connect(aClient, MQTTVersion)) == 0) + aClient->connect_state = 3; /* MQTT Connect sent - wait for CONNACK */ + else + aClient->connect_state = 0; + } + } + if (addr != ip_address) + free(addr); + + 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 + * @return completion code + */ +int MQTTProtocol_subscribe(Clients* client, List* topics, List* qoss, int msgID) +{ + int rc = 0; + + FUNC_ENTRY; + /* we should stack this up for retry processing too */ + rc = MQTTPacket_send_subscribe(topics, qoss, msgID, 0, &client->net, client->clientID); + 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) +{ + int rc = 0; + + FUNC_ENTRY; + /* we should stack this up for retry processing too? */ + rc = MQTTPacket_send_unsubscribe(topics, msgID, 0, &client->net, client->clientID); + 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); + free(unsuback); + FUNC_EXIT_RC(rc); + return rc; +} + diff --git a/Sources/paho/src/MQTTVersion.c b/Sources/paho/src/MQTTVersion.c new file mode 100644 index 0000000..a7a3fd1 --- /dev/null +++ b/Sources/paho/src/MQTTVersion.c @@ -0,0 +1,209 @@ +/******************************************************************************* + * Copyright (c) 2012, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "MQTTAsync.h" + +#if defined(WIN32) || defined(WIN64) +#include +#include +#include +#include +#else +#include +#include +#include +#endif + + +/** + * + * @file + * \brief MQTTVersion - display the version and build information strings for a library. + * + * With no arguments, we try to load and call the version string for the libraries we + * know about: mqttv3c, mqttv3cs, mqttv3a, mqttv3as. + * With an argument: + * 1) we try to load the named library, call getVersionInfo and display those values. + * 2) If that doesn't work, we look through the binary for eyecatchers, and display those. + * This will work if the library is not executable in the current environment. + * + * */ + + + char* libraries[] = {"paho-mqtt3c", "paho-mqtt3cs", "paho-mqtt3a", "paho-mqtt3as"}; + char* eyecatchers[] = {"MQTTAsyncV3_Version", "MQTTAsyncV3_Timestamp", + "MQTTClientV3_Version", "MQTTClientV3_Timestamp"}; + + +/** + * Finds an eyecatcher in a binary file and returns the following value. + * @param filename the name of the file + * @param eyecatcher_input the eyecatcher string to look for + * @return the value found - "" if not found + */ +char* FindString(char* filename, char* eyecatcher_input) +{ + FILE* infile = NULL; + static char value[100]; + char* eyecatcher = eyecatcher_input; + + memset(value, 0, 100); + if ((infile = fopen(filename, "rb")) != NULL) + { + int buflen = strlen(eyecatcher); + char* buffer = (char*) malloc(buflen); + int count = 0; + int c = fgetc(infile); + + while (feof(infile) == 0) + { + buffer[count++] = c; + if (memcmp(eyecatcher, buffer, buflen) == 0) + { + char* ptr = value; + c = fgetc(infile); /* skip space */ + c = fgetc(infile); + while (isprint(c)) + { + *ptr++ = c; + c = fgetc(infile); + } + break; + } + if (count == buflen) + { + memmove(buffer, &buffer[1], buflen - 1); + count--; + } + c = fgetc(infile); + } + free(buffer); + } + return value; +} + + +int printVersionInfo(MQTTAsync_nameValue* info) +{ + int rc = 0; + + while (info->name) + { + printf("%s: %s\n", info->name, info->value); + info++; + rc = 1; /* at least one value printed */ + } + return rc; +} + +typedef MQTTAsync_nameValue* (*func_type)(void); + +int loadandcall(char* libname) +{ + int rc = 0; + MQTTAsync_nameValue* (*func_address)(void) = NULL; +#if defined(WIN32) || defined(WIN64) + wchar_t wlibname[30]; + HMODULE APILibrary; + + mbstowcs(wlibname, libname, strlen(libname) + 1); + if ((APILibrary = LoadLibrary(wlibname)) == NULL) + printf("Error loading library %s, error code %d\n", libname, GetLastError()); + else + { + func_address = (func_type)GetProcAddress(APILibrary, "MQTTAsync_getVersionInfo"); + if (func_address == NULL) + func_address = (func_type)GetProcAddress(APILibrary, "MQTTClient_getVersionInfo"); + if (func_address) + rc = printVersionInfo((*func_address)()); + FreeLibrary(APILibrary); + } +#else + void* APILibrary = dlopen(libname, RTLD_LAZY); /* Open the Library in question */ + char* ErrorOutput = dlerror(); /* Check it opened properly */ + if (ErrorOutput != NULL) + printf("Error loading library %s, error %s\n", libname, ErrorOutput); + else + { + *(void **) (&func_address) = dlsym(APILibrary, "MQTTAsync_getVersionInfo"); + if (func_address == NULL) + func_address = dlsym(APILibrary, "MQTTClient_getVersionInfo"); + if (func_address) + rc = printVersionInfo((*func_address)()); + dlclose(APILibrary); + } +#endif + return rc; +} + + +#if !defined(ARRAY_SIZE) +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#endif + +void printEyecatchers(char* filename) +{ + int i = 0; + + for (i = 0; i < ARRAY_SIZE(eyecatchers); ++i) + { + char* value = FindString(filename, eyecatchers[i]); + if (value[0]) + printf("%s: %s\n", eyecatchers[i], value); + } +} + + +int main(int argc, char** argv) +{ + printf("MQTTVersion: print the version strings of an MQTT client library\n"); + printf("Copyright (c) 2013 IBM Corp.\n"); + + if (argc == 1) + { + int i = 0; + char namebuf[60]; + + printf("Specify a particular library name if it is not in the current directory, or not executable on this platform\n"); + + for (i = 0; i < ARRAY_SIZE(libraries); ++i) + { +#if defined(WIN32) || defined(WIN64) + sprintf(namebuf, "%s.dll", 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; +} diff --git a/Sources/paho/src/Messages.c b/Sources/paho/src/Messages.c new file mode 100644 index 0000000..f17e435 --- /dev/null +++ b/Sources/paho/src/Messages.c @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +/** + * @file + * \brief Trace messages + * + */ + + +#include "Messages.h" +#include "Log.h" + +#include +#include +#include +#include + +#include "Heap.h" + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +#define max_msg_len 120 + +static char* protocol_message_list[] = +{ + "%d %s -> CONNECT cleansession: %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 (%d) payload: %.*s", /* 10, was 42 */ + "%d %s <- PUBLISH msgid: %d qos: %d retained: %d payload: %.*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 (%d)", /* 27 */ + "%d %s -> DISCONNECT (%d)", /* 28 */ + "Socket error for client identifier %s, socket %d, peer address %s; ending connection", /* 29 */ +}; + +static 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 + */ +char* Messages_get(int index, int log_level) +{ + 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; +} + diff --git a/Sources/paho/src/SSLSocket.c b/Sources/paho/src/SSLSocket.c new file mode 100644 index 0000000..20e0933 --- /dev/null +++ b/Sources/paho/src/SSLSocket.c @@ -0,0 +1,823 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs, Allan Stockdill-Mander - initial implementation + * Ian Craggs - fix for bug #409702 + * Ian Craggs - allow compilation for OpenSSL < 1.0 + *******************************************************************************/ + +/** + * @file + * \brief SSL related functions + * + */ + +#if defined(OPENSSL) + +#include "SocketBuffer.h" +#include "MQTTClient.h" +#include "SSLSocket.h" +#include "Log.h" +#include "StackTrace.h" +#include "Socket.h" + +#include "Heap.h" + +#include +#include +#include + +extern Sockets s; + +void SSLSocket_addPendingRead(int sock); + +static ssl_mutex_type* sslLocks = NULL; +static ssl_mutex_type sslCoreMutex; + +#if defined(WIN32) || defined(WIN64) +#define iov_len len +#define iov_base buf +#endif + +/** + * Gets the specific error corresponding to SOCKET_ERROR + * @param aString the function that was being used when the error occurred + * @param sock the socket on which the error occurred + * @return the specific TCP error code + */ +int SSLSocket_error(char* aString, SSL* ssl, int sock, int rc) +{ + int error; + + FUNC_ENTRY; + if (ssl) + error = SSL_get_error(ssl, rc); + else + error = ERR_get_error(); + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) + { + Log(TRACE_MIN, -1, "SSLSocket error WANT_READ/WANT_WRITE"); + } + else + { + static char buf[120]; + + if (strcmp(aString, "shutdown") != 0) + Log(TRACE_MIN, -1, "SSLSocket error %s(%d) in %s for socket %d rc %d errno %d %s\n", buf, error, aString, sock, rc, errno, strerror(errno)); + ERR_print_errors_fp(stderr); + if (error == SSL_ERROR_SSL || error == SSL_ERROR_SYSCALL) + error = SSL_FATAL; + } + FUNC_EXIT_RC(error); + return error; +} + +static struct +{ + int code; + char* string; +} +X509_message_table[] = +{ + { X509_V_OK, "X509_V_OK" }, + { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT" }, + { X509_V_ERR_UNABLE_TO_GET_CRL, "X509_V_ERR_UNABLE_TO_GET_CRL" }, + { X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE, "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE" }, + { X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE, "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE" }, + { X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY" }, + { X509_V_ERR_CERT_SIGNATURE_FAILURE, "X509_V_ERR_CERT_SIGNATURE_FAILURE" }, + { X509_V_ERR_CRL_SIGNATURE_FAILURE, "X509_V_ERR_CRL_SIGNATURE_FAILURE" }, + { X509_V_ERR_CERT_NOT_YET_VALID, "X509_V_ERR_CERT_NOT_YET_VALID" }, + { X509_V_ERR_CERT_HAS_EXPIRED, "X509_V_ERR_CERT_HAS_EXPIRED" }, + { X509_V_ERR_CRL_NOT_YET_VALID, "X509_V_ERR_CRL_NOT_YET_VALID" }, + { X509_V_ERR_CRL_HAS_EXPIRED, "X509_V_ERR_CRL_HAS_EXPIRED" }, + { X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD, "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD" }, + { X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD, "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD" }, + { X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD, "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD" }, + { X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD, "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD" }, + { X509_V_ERR_OUT_OF_MEM, "X509_V_ERR_OUT_OF_MEM" }, + { X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT" }, + { X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN, "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN" }, + { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY" }, + { X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE" }, + { X509_V_ERR_CERT_CHAIN_TOO_LONG, "X509_V_ERR_CERT_CHAIN_TOO_LONG" }, + { X509_V_ERR_CERT_REVOKED, "X509_V_ERR_CERT_REVOKED" }, + { X509_V_ERR_INVALID_CA, "X509_V_ERR_INVALID_CA" }, + { X509_V_ERR_PATH_LENGTH_EXCEEDED, "X509_V_ERR_PATH_LENGTH_EXCEEDED" }, + { X509_V_ERR_INVALID_PURPOSE, "X509_V_ERR_INVALID_PURPOSE" }, + { X509_V_ERR_CERT_UNTRUSTED, "X509_V_ERR_CERT_UNTRUSTED" }, + { X509_V_ERR_CERT_REJECTED, "X509_V_ERR_CERT_REJECTED" }, + { X509_V_ERR_SUBJECT_ISSUER_MISMATCH, "X509_V_ERR_SUBJECT_ISSUER_MISMATCH" }, + { X509_V_ERR_AKID_SKID_MISMATCH, "X509_V_ERR_AKID_SKID_MISMATCH" }, + { X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH, "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH" }, + { X509_V_ERR_KEYUSAGE_NO_CERTSIGN, "X509_V_ERR_KEYUSAGE_NO_CERTSIGN" }, + { X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER, "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER" }, + { X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION" }, + { X509_V_ERR_KEYUSAGE_NO_CRL_SIGN, "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN" }, + { X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION, "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION" }, + { X509_V_ERR_INVALID_NON_CA, "X509_V_ERR_INVALID_NON_CA" }, + { X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED, "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED" }, + { X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE, "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE" }, + { X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED, "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED" }, + { X509_V_ERR_INVALID_EXTENSION, "X509_V_ERR_INVALID_EXTENSION" }, + { X509_V_ERR_INVALID_POLICY_EXTENSION, "X509_V_ERR_INVALID_POLICY_EXTENSION" }, + { X509_V_ERR_NO_EXPLICIT_POLICY, "X509_V_ERR_NO_EXPLICIT_POLICY" }, + { X509_V_ERR_UNNESTED_RESOURCE, "X509_V_ERR_UNNESTED_RESOURCE" }, +#if defined(X509_V_ERR_DIFFERENT_CRL_SCOPE) + { X509_V_ERR_DIFFERENT_CRL_SCOPE, "X509_V_ERR_DIFFERENT_CRL_SCOPE" }, + { X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE, "X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE" }, + { X509_V_ERR_PERMITTED_VIOLATION, "X509_V_ERR_PERMITTED_VIOLATION" }, + { X509_V_ERR_EXCLUDED_VIOLATION, "X509_V_ERR_EXCLUDED_VIOLATION" }, + { X509_V_ERR_SUBTREE_MINMAX, "X509_V_ERR_SUBTREE_MINMAX" }, + { X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE, "X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE" }, + { X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX, "X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX" }, + { X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, "X509_V_ERR_UNSUPPORTED_NAME_SYNTAX" }, +#endif +}; + +#if !defined(ARRAY_SIZE) +/** + * Macro to calculate the number of entries in an array + */ +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#endif + +char* SSL_get_verify_result_string(int rc) +{ + int i; + char* retstring = "undef"; + + for (i = 0; i < ARRAY_SIZE(X509_message_table); ++i) + { + if (X509_message_table[i].code == rc) + { + retstring = X509_message_table[i].string; + break; + } + } + return retstring; +} + + +void SSL_CTX_info_callback(const SSL* ssl, int where, int ret) +{ + if (where & SSL_CB_LOOP) + { + Log(TRACE_PROTOCOL, 1, "SSL state %s:%s:%s", + (where & SSL_ST_CONNECT) ? "connect" : (where & SSL_ST_ACCEPT) ? "accept" : "undef", + SSL_state_string_long(ssl), SSL_get_cipher_name(ssl)); + } + else if (where & SSL_CB_EXIT) + { + Log(TRACE_PROTOCOL, 1, "SSL %s:%s", + (where & SSL_ST_CONNECT) ? "connect" : (where & SSL_ST_ACCEPT) ? "accept" : "undef", + SSL_state_string_long(ssl)); + } + else if (where & SSL_CB_ALERT) + { + Log(TRACE_PROTOCOL, 1, "SSL alert %s:%s:%s", + (where & SSL_CB_READ) ? "read" : "write", + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + } + else if (where & SSL_CB_HANDSHAKE_START) + { + Log(TRACE_PROTOCOL, 1, "SSL handshake started %s:%s:%s", + (where & SSL_CB_READ) ? "read" : "write", + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + } + else if (where & SSL_CB_HANDSHAKE_DONE) + { + Log(TRACE_PROTOCOL, 1, "SSL handshake done %s:%s:%s", + (where & SSL_CB_READ) ? "read" : "write", + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + Log(TRACE_PROTOCOL, 1, "SSL certificate verification: %s", + SSL_get_verify_result_string(SSL_get_verify_result(ssl))); + } + else + { + Log(TRACE_PROTOCOL, 1, "SSL state %s:%s:%s", SSL_state_string_long(ssl), + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + } +} + + +char* SSLSocket_get_version_string(int version) +{ + int i; + static char buf[20]; + char* retstring = NULL; + static struct + { + int code; + char* string; + } + version_string_table[] = + { + { SSL2_VERSION, "SSL 2.0" }, + { SSL3_VERSION, "SSL 3.0" }, + { TLS1_VERSION, "TLS 1.0" }, +#if defined(TLS2_VERSION) + { TLS2_VERSION, "TLS 1.1" }, +#endif +#if defined(TLS3_VERSION) + { TLS3_VERSION, "TLS 1.2" }, +#endif + }; + + for (i = 0; i < ARRAY_SIZE(version_string_table); ++i) + { + if (version_string_table[i].code == version) + { + retstring = version_string_table[i].string; + break; + } + } + + if (retstring == NULL) + { + sprintf(buf, "%i", version); + retstring = buf; + } + return retstring; +} + + +void SSL_CTX_msg_callback(int write_p, int version, int content_type, const void* buf, size_t len, + SSL* ssl, void* arg) +{ + +/* +called by the SSL/TLS library for a protocol message, the function arguments have the following meaning: + +write_p +This flag is 0 when a protocol message has been received and 1 when a protocol message has been sent. + +version +The protocol version according to which the protocol message is interpreted by the library. Currently, this is one of SSL2_VERSION, SSL3_VERSION and TLS1_VERSION (for SSL 2.0, SSL 3.0 and TLS 1.0, respectively). + +content_type +In the case of SSL 2.0, this is always 0. In the case of SSL 3.0 or TLS 1.0, this is one of the ContentType values defined in the protocol specification (change_cipher_spec(20), alert(21), handshake(22); but never application_data(23) because the callback will only be called for protocol messages). + +buf, len +buf points to a buffer containing the protocol message, which consists of len bytes. The buffer is no longer valid after the callback function has returned. + +ssl +The SSL object that received or sent the message. + +arg +The user-defined argument optionally defined by SSL_CTX_set_msg_callback_arg() or SSL_set_msg_callback_arg(). + +*/ + + Log(TRACE_PROTOCOL, -1, "%s %s %d buflen %d", (write_p ? "sent" : "received"), + SSLSocket_get_version_string(version), + content_type, (int)len); +} + + +int pem_passwd_cb(char* buf, int size, int rwflag, void* userdata) +{ + int rc = 0; + + FUNC_ENTRY; + if (!rwflag) + { + strncpy(buf, (char*)(userdata), size); + buf[size-1] = '\0'; + rc = strlen(buf); + } + FUNC_EXIT_RC(rc); + return rc; +} + +int SSL_create_mutex(ssl_mutex_type* mutex) +{ + int rc = 0; + + FUNC_ENTRY; +#if defined(WIN32) || defined(WIN64) + *mutex = CreateMutex(NULL, 0, NULL); +#else + rc = pthread_mutex_init(mutex, NULL); +#endif + FUNC_EXIT_RC(rc); + return rc; +} + +int SSL_lock_mutex(ssl_mutex_type* mutex) +{ + int rc = -1; + + /* don't add entry/exit trace points, as trace gets lock too, and it might happen quite frequently */ +#if defined(WIN32) || defined(WIN64) + if (WaitForSingleObject(*mutex, INFINITE) != WAIT_FAILED) +#else + if ((rc = pthread_mutex_lock(mutex)) == 0) +#endif + rc = 0; + + return rc; +} + +int SSL_unlock_mutex(ssl_mutex_type* mutex) +{ + int rc = -1; + + /* don't add entry/exit trace points, as trace gets lock too, and it might happen quite frequently */ +#if defined(WIN32) || defined(WIN64) + if (ReleaseMutex(*mutex) != 0) +#else + if ((rc = pthread_mutex_unlock(mutex)) == 0) +#endif + rc = 0; + + return rc; +} + +void SSL_destroy_mutex(ssl_mutex_type* mutex) +{ + int rc = 0; + + FUNC_ENTRY; +#if defined(WIN32) || defined(WIN64) + rc = CloseHandle(*mutex); +#else + rc = pthread_mutex_destroy(mutex); + free(mutex); +#endif + FUNC_EXIT_RC(rc); +} + + + +#if (OPENSSL_VERSION_NUMBER >= 0x010000000) +extern void SSLThread_id(CRYPTO_THREADID *id) +{ +#if defined(WIN32) || defined(WIN64) + CRYPTO_THREADID_set_numeric(id, (unsigned long)GetCurrentThreadId()); +#else + CRYPTO_THREADID_set_numeric(id, (unsigned long)pthread_self()); +#endif +} +#else +extern unsigned long SSLThread_id(void) +{ +#if defined(WIN32) || defined(WIN64) + return (unsigned long)GetCurrentThreadId(); +#else + return (unsigned long)pthread_self(); +#endif +} +#endif + +extern void SSLLocks_callback(int mode, int n, const char *file, int line) +{ + if (mode & CRYPTO_LOCK) + SSL_lock_mutex(&sslLocks[n]); + else + SSL_unlock_mutex(&sslLocks[n]); +} + +int SSLSocket_initialize() +{ + int rc = 0; + /*int prc;*/ + int i; + int lockMemSize; + + FUNC_ENTRY; + + if ((rc = SSL_library_init()) != 1) + rc = -1; + + ERR_load_crypto_strings(); + SSL_load_error_strings(); + + /* OpenSSL 0.9.8o and 1.0.0a and later added SHA2 algorithms to SSL_library_init(). + Applications which need to use SHA2 in earlier versions of OpenSSL should call + OpenSSL_add_all_algorithms() as well. */ + + OpenSSL_add_all_algorithms(); + + lockMemSize = CRYPTO_num_locks() * sizeof(ssl_mutex_type); + + sslLocks = malloc(lockMemSize); + if (!sslLocks) + { + rc = -1; + goto exit; + } + else + memset(sslLocks, 0, lockMemSize); + + for (i = 0; i < CRYPTO_num_locks(); i++) + { + /* prc = */SSL_create_mutex(&sslLocks[i]); + } + +#if (OPENSSL_VERSION_NUMBER >= 0x010000000) + CRYPTO_THREADID_set_callback(SSLThread_id); +#else + CRYPTO_set_id_callback(SSLThread_id); +#endif + CRYPTO_set_locking_callback(SSLLocks_callback); + + SSL_create_mutex(&sslCoreMutex); + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + +void SSLSocket_terminate() +{ + FUNC_ENTRY; + free(sslLocks); + FUNC_EXIT; +} + +int SSLSocket_createContext(networkHandles* net, MQTTClient_SSLOptions* opts) +{ + int rc = 1; + const char* ciphers = NULL; + + FUNC_ENTRY; + if (net->ctx == NULL) + if ((net->ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) /* SSLv23 for compatibility with SSLv2, SSLv3 and TLSv1 */ + { + SSLSocket_error("SSL_CTX_new", NULL, net->socket, rc); + goto exit; + } + + if (opts->keyStore) + { + int rc1 = 0; + + if ((rc = SSL_CTX_use_certificate_chain_file(net->ctx, opts->keyStore)) != 1) + { + SSLSocket_error("SSL_CTX_use_certificate_chain_file", NULL, net->socket, rc); + goto free_ctx; /*If we can't load the certificate (chain) file then loading the privatekey won't work either as it needs a matching cert already loaded */ + } + + if (opts->privateKey == NULL) + opts->privateKey = opts->keyStore; /* the privateKey can be included in the keyStore */ + + if (opts->privateKeyPassword != NULL) + { + SSL_CTX_set_default_passwd_cb(net->ctx, pem_passwd_cb); + SSL_CTX_set_default_passwd_cb_userdata(net->ctx, (void*)opts->privateKeyPassword); + } + + /* support for ASN.1 == DER format? DER can contain only one certificate? */ + rc1 = SSL_CTX_use_PrivateKey_file(net->ctx, opts->privateKey, SSL_FILETYPE_PEM); + if (opts->privateKey == opts->keyStore) + opts->privateKey = NULL; + if (rc1 != 1) + { + SSLSocket_error("SSL_CTX_use_PrivateKey_file", NULL, net->socket, rc); + goto free_ctx; + } + } + + if (opts->trustStore) + { + if ((rc = SSL_CTX_load_verify_locations(net->ctx, opts->trustStore, NULL)) != 1) + { + SSLSocket_error("SSL_CTX_load_verify_locations", NULL, net->socket, rc); + goto free_ctx; + } + } + else if ((rc = SSL_CTX_set_default_verify_paths(net->ctx)) != 1) + { + SSLSocket_error("SSL_CTX_set_default_verify_paths", NULL, net->socket, rc); + goto free_ctx; + } + + if (opts->enabledCipherSuites == NULL) + ciphers = "DEFAULT"; + else + ciphers = opts->enabledCipherSuites; + + if ((rc = SSL_CTX_set_cipher_list(net->ctx, ciphers)) != 1) + { + SSLSocket_error("SSL_CTX_set_cipher_list", NULL, net->socket, rc); + goto free_ctx; + } + + SSL_CTX_set_mode(net->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + goto exit; +free_ctx: + SSL_CTX_free(net->ctx); + net->ctx = NULL; + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +int SSLSocket_setSocketForSSL(networkHandles* net, MQTTClient_SSLOptions* opts) +{ + int rc = 1; + + FUNC_ENTRY; + + if (net->ctx != NULL || (rc = SSLSocket_createContext(net, opts)) == 1) + { + int i; + SSL_CTX_set_info_callback(net->ctx, SSL_CTX_info_callback); + SSL_CTX_set_msg_callback(net->ctx, SSL_CTX_msg_callback); + if (opts->enableServerCertAuth) + SSL_CTX_set_verify(net->ctx, SSL_VERIFY_PEER, NULL); + + net->ssl = SSL_new(net->ctx); + + /* Log all ciphers available to the SSL sessions (loaded in ctx) */ + for (i = 0; ;i++) + { + const char* cipher = SSL_get_cipher_list(net->ssl, i); + if (cipher == NULL) + break; + Log(TRACE_PROTOCOL, 1, "SSL cipher available: %d:%s", i, cipher); + } + if ((rc = SSL_set_fd(net->ssl, net->socket)) != 1) + SSLSocket_error("SSL_set_fd", net->ssl, net->socket, rc); + } + + FUNC_EXIT_RC(rc); + return rc; +} + + +int SSLSocket_connect(SSL* ssl, int sock) +{ + int rc = 0; + + FUNC_ENTRY; + + rc = SSL_connect(ssl); + if (rc != 1) + { + int error; + error = SSLSocket_error("SSL_connect", ssl, sock, rc); + if (error == SSL_FATAL) + rc = error; + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) + rc = TCPSOCKET_INTERRUPTED; + } + + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Reads one byte from a socket + * @param socket the socket to read from + * @param c the character read, returned + * @return completion code + */ +int SSLSocket_getch(SSL* ssl, int socket, char* c) +{ + int rc = SOCKET_ERROR; + + FUNC_ENTRY; + if ((rc = SocketBuffer_getQueuedChar(socket, c)) != SOCKETBUFFER_INTERRUPTED) + goto exit; + + if ((rc = SSL_read(ssl, c, (size_t)1)) < 0) + { + int err = SSLSocket_error("SSL_read - getch", ssl, socket, rc); + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) + { + rc = TCPSOCKET_INTERRUPTED; + SocketBuffer_interrupted(socket, 0); + } + } + else if (rc == 0) + rc = SOCKET_ERROR; /* The return value from recv is 0 when the peer has performed an orderly shutdown. */ + else if (rc == 1) + { + SocketBuffer_queueChar(socket, *c); + rc = TCPSOCKET_COMPLETE; + } +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Attempts to read a number of bytes from a socket, non-blocking. If a previous read did not + * finish, then retrieve that data. + * @param socket the socket to read from + * @param bytes the number of bytes to read + * @param actual_len the actual number of bytes read + * @return completion code + */ +char *SSLSocket_getdata(SSL* ssl, int socket, int bytes, int* actual_len) +{ + int rc; + char* buf; + + FUNC_ENTRY; + if (bytes == 0) + { + buf = SocketBuffer_complete(socket); + goto exit; + } + + buf = SocketBuffer_getQueuedData(socket, bytes, actual_len); + + if ((rc = SSL_read(ssl, buf + (*actual_len), (size_t)(bytes - (*actual_len)))) < 0) + { + rc = SSLSocket_error("SSL_read - getdata", ssl, socket, rc); + if (rc != SSL_ERROR_WANT_READ && rc != SSL_ERROR_WANT_WRITE) + { + buf = NULL; + goto exit; + } + } + else if (rc == 0) /* rc 0 means the other end closed the socket */ + { + buf = NULL; + goto exit; + } + else + *actual_len += rc; + + if (*actual_len == bytes) + { + SocketBuffer_complete(socket); + /* if we read the whole packet, there might still be data waiting in the SSL buffer, which + isn't picked up by select. So here we should check for any data remaining in the SSL buffer, and + if so, add this socket to a new "pending SSL reads" list. + */ + if (SSL_pending(ssl) > 0) /* return no of bytes pending */ + SSLSocket_addPendingRead(socket); + } + else /* we didn't read the whole packet */ + { + SocketBuffer_interrupted(socket, *actual_len); + Log(TRACE_MAX, -1, "SSL_read: %d bytes expected but %d bytes now received", bytes, *actual_len); + } +exit: + FUNC_EXIT; + return buf; +} + +void SSLSocket_destroyContext(networkHandles* net) +{ + FUNC_ENTRY; + if (net->ctx) + SSL_CTX_free(net->ctx); + net->ctx = NULL; + FUNC_EXIT; +} + + +int SSLSocket_close(networkHandles* net) +{ + int rc = 1; + FUNC_ENTRY; + if (net->ssl) { + rc = SSL_shutdown(net->ssl); + SSL_free(net->ssl); + net->ssl = NULL; + } + SSLSocket_destroyContext(net); + FUNC_EXIT_RC(rc); + return rc; +} + + +/* No SSL_writev() provided by OpenSSL. Boo. */ +int SSLSocket_putdatas(SSL* ssl, int socket, char* buf0, size_t buf0len, int count, char** buffers, size_t* buflens, int* frees) +{ + int rc = 0; + int i; + char *ptr; + iobuf iovec; + int sslerror; + + FUNC_ENTRY; + iovec.iov_len = buf0len; + for (i = 0; i < count; i++) + iovec.iov_len += buflens[i]; + + ptr = iovec.iov_base = (char *)malloc(iovec.iov_len); + memcpy(ptr, buf0, buf0len); + ptr += buf0len; + for (i = 0; i < count; i++) + { + memcpy(ptr, buffers[i], buflens[i]); + ptr += buflens[i]; + } + + SSL_lock_mutex(&sslCoreMutex); + if ((rc = SSL_write(ssl, iovec.iov_base, iovec.iov_len)) == iovec.iov_len) + rc = TCPSOCKET_COMPLETE; + else + { + sslerror = SSLSocket_error("SSL_write", ssl, socket, rc); + + if (sslerror == SSL_ERROR_WANT_WRITE) + { + int* sockmem = (int*)malloc(sizeof(int)); + int free = 1; + + Log(TRACE_MIN, -1, "Partial write: incomplete write of %d bytes on SSL socket %d", + iovec.iov_len, socket); + SocketBuffer_pendingWrite(socket, ssl, 1, &iovec, &free, iovec.iov_len, 0); + *sockmem = socket; + ListAppend(s.write_pending, sockmem, sizeof(int)); + FD_SET(socket, &(s.pending_wset)); + rc = TCPSOCKET_INTERRUPTED; + } + else + rc = SOCKET_ERROR; + } + SSL_unlock_mutex(&sslCoreMutex); + + if (rc != TCPSOCKET_INTERRUPTED) + free(iovec.iov_base); + else + { + int i; + free(buf0); + for (i = 0; i < count; ++i) + { + if (frees[i]) + free(buffers[i]); + } + } + FUNC_EXIT_RC(rc); + return rc; +} + +static List pending_reads = {NULL, NULL, NULL, 0, 0}; + +void SSLSocket_addPendingRead(int sock) +{ + FUNC_ENTRY; + if (ListFindItem(&pending_reads, &sock, intcompare) == NULL) /* make sure we don't add the same socket twice */ + { + int* psock = (int*)malloc(sizeof(sock)); + *psock = sock; + ListAppend(&pending_reads, psock, sizeof(sock)); + } + else + Log(TRACE_MIN, -1, "SSLSocket_addPendingRead: socket %d already in the list", sock); + + FUNC_EXIT; +} + + +int SSLSocket_getPendingRead() +{ + int sock = -1; + + if (pending_reads.count > 0) + { + sock = *(int*)(pending_reads.first->content); + ListRemoveHead(&pending_reads); + } + return sock; +} + + +int SSLSocket_continueWrite(pending_writes* pw) +{ + int rc = 0; + + FUNC_ENTRY; + if ((rc = SSL_write(pw->ssl, pw->iovecs[0].iov_base, pw->iovecs[0].iov_len)) == pw->iovecs[0].iov_len) + { + /* topic and payload buffers are freed elsewhere, when all references to them have been removed */ + free(pw->iovecs[0].iov_base); + Log(TRACE_MIN, -1, "SSL continueWrite: partial write now complete for socket %d", pw->socket); + rc = 1; + } + else + { + int sslerror = SSLSocket_error("SSL_write", pw->ssl, pw->socket, rc); + if (sslerror == SSL_ERROR_WANT_WRITE) + rc = 0; /* indicate we haven't finished writing the payload yet */ + } + FUNC_EXIT_RC(rc); + return rc; +} +#endif diff --git a/Sources/paho/src/Socket.c b/Sources/paho/src/Socket.c new file mode 100644 index 0000000..8475ae2 --- /dev/null +++ b/Sources/paho/src/Socket.c @@ -0,0 +1,871 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial implementation and documentation + * Ian Craggs - async client updates + *******************************************************************************/ + +/** + * @file + * \brief Socket related functions + * + * Some other related functions are in the SocketBuffer module + */ + + +#include "Socket.h" +#include "Log.h" +#include "SocketBuffer.h" +#include "Messages.h" +#include "StackTrace.h" +#if defined(OPENSSL) +#include "SSLSocket.h" +#endif + +#include +#include +#include +#include + +#include "Heap.h" + +int Socket_close_only(int socket); +int Socket_continueWrites(fd_set* pwset); + +#if defined(WIN32) || defined(WIN64) +#define iov_len len +#define iov_base buf +#endif + +/** + * Structure to hold all socket data for the module + */ +Sockets s; +static fd_set wset; + +/** + * Set a socket non-blocking, OS independently + * @param sock the socket to set non-blocking + * @return TCP call error code + */ +int Socket_setnonblocking(int sock) +{ + int rc; +#if defined(WIN32) || defined(WIN64) + u_long flag = 1L; + + FUNC_ENTRY; + rc = ioctl(sock, FIONBIO, &flag); +#else + int flags; + + FUNC_ENTRY; + if ((flags = fcntl(sock, F_GETFL, 0))) + flags = 0; + rc = fcntl(sock, F_SETFL, flags | O_NONBLOCK); +#endif + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Gets the specific error corresponding to SOCKET_ERROR + * @param aString the function that was being used when the error occurred + * @param sock the socket on which the error occurred + * @return the specific TCP error code + */ +int Socket_error(char* aString, int sock) +{ +#if defined(WIN32) || defined(WIN64) + int errno; +#endif + + FUNC_ENTRY; +#if defined(WIN32) || defined(WIN64) + errno = WSAGetLastError(); +#endif + if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && errno != EWOULDBLOCK) + { + if (strcmp(aString, "shutdown") != 0 || (errno != ENOTCONN && errno != ECONNRESET)) + Log(TRACE_MINIMUM, -1, "Socket error %s in %s for socket %d", strerror(errno), aString, sock); + } + FUNC_EXIT_RC(errno); + return errno; +} + + +/** + * Initialize the socket module + */ +void Socket_outInitialize() +{ +#if defined(WIN32) || defined(WIN64) + WORD winsockVer = 0x0202; + WSADATA wsd; + + FUNC_ENTRY; + WSAStartup(winsockVer, &wsd); +#else + FUNC_ENTRY; + signal(SIGPIPE, SIG_IGN); +#endif + + SocketBuffer_initialize(); + s.clientsds = ListInitialize(); + s.connect_pending = ListInitialize(); + s.write_pending = ListInitialize(); + s.cur_clientsds = NULL; + FD_ZERO(&(s.rset)); /* Initialize the descriptor set */ + FD_ZERO(&(s.pending_wset)); + s.maxfdp1 = 0; + memcpy((void*)&(s.rset_saved), (void*)&(s.rset), sizeof(s.rset_saved)); + FUNC_EXIT; +} + + +/** + * Terminate the socket module + */ +void Socket_outTerminate() +{ + FUNC_ENTRY; + ListFree(s.connect_pending); + ListFree(s.write_pending); + ListFree(s.clientsds); + SocketBuffer_terminate(); +#if defined(WIN32) || defined(WIN64) + WSACleanup(); +#endif + FUNC_EXIT; +} + + +/** + * Add a socket to the list of socket to check with select + * @param newSd the new socket to add + */ +int Socket_addSocket(int newSd) +{ + int rc = 0; + + FUNC_ENTRY; + if (ListFindItem(s.clientsds, &newSd, intcompare) == NULL) /* make sure we don't add the same socket twice */ + { + int* pnewSd = (int*)malloc(sizeof(newSd)); + *pnewSd = newSd; + ListAppend(s.clientsds, pnewSd, sizeof(newSd)); + FD_SET(newSd, &(s.rset_saved)); + s.maxfdp1 = max(s.maxfdp1, newSd + 1); + rc = Socket_setnonblocking(newSd); + } + else + Log(LOG_ERROR, -1, "addSocket: socket %d already in the list", newSd); + + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Don't accept work from a client unless it is accepting work back, i.e. its socket is writeable + * this seems like a reasonable form of flow control, and practically, seems to work. + * @param socket the socket to check + * @param read_set the socket read set (see select doc) + * @param write_set the socket write set (see select doc) + * @return boolean - is the socket ready to go? + */ +int isReady(int socket, fd_set* read_set, fd_set* write_set) +{ + int rc = 1; + + FUNC_ENTRY; + if (ListFindItem(s.connect_pending, &socket, intcompare) && FD_ISSET(socket, write_set)) + ListRemoveItem(s.connect_pending, &socket, intcompare); + else + rc = FD_ISSET(socket, read_set) && FD_ISSET(socket, write_set) && Socket_noPendingWrites(socket); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Returns the next socket ready for communications as indicated by select + * @param more_work flag to indicate more work is waiting, and thus a timeout value of 0 should + * be used for the select + * @param tp the timeout to be used for the select, unless overridden + * @return the socket next ready, or 0 if none is ready + */ +int Socket_getReadySocket(int more_work, struct timeval *tp) +{ + int rc = 0; + static struct timeval zero = {0L, 0L}; /* 0 seconds */ + static struct timeval one = {1L, 0L}; /* 1 second */ + struct timeval timeout = one; + + FUNC_ENTRY; + if (s.clientsds->count == 0) + goto exit; + + if (more_work) + timeout = zero; + else if (tp) + timeout = *tp; + + while (s.cur_clientsds != NULL) + { + if (isReady(*((int*)(s.cur_clientsds->content)), &(s.rset), &wset)) + break; + ListNextElement(s.clientsds, &s.cur_clientsds); + } + + if (s.cur_clientsds == NULL) + { + int rc1; + fd_set pwset; + + memcpy((void*)&(s.rset), (void*)&(s.rset_saved), sizeof(s.rset)); + memcpy((void*)&(pwset), (void*)&(s.pending_wset), sizeof(pwset)); + if ((rc = select(s.maxfdp1, &(s.rset), &pwset, NULL, &timeout)) == SOCKET_ERROR) + { + Socket_error("read select", 0); + goto exit; + } + Log(TRACE_MAX, -1, "Return code %d from read select", rc); + + if (Socket_continueWrites(&pwset) == SOCKET_ERROR) + { + rc = 0; + goto exit; + } + + memcpy((void*)&wset, (void*)&(s.rset_saved), sizeof(wset)); + if ((rc1 = select(s.maxfdp1, NULL, &(wset), NULL, &zero)) == SOCKET_ERROR) + { + Socket_error("write select", 0); + rc = rc1; + goto exit; + } + Log(TRACE_MAX, -1, "Return code %d from write select", rc1); + + if (rc == 0 && rc1 == 0) + goto exit; /* no work to do */ + + s.cur_clientsds = s.clientsds->first; + while (s.cur_clientsds != NULL) + { + int cursock = *((int*)(s.cur_clientsds->content)); + if (isReady(cursock, &(s.rset), &wset)) + break; + ListNextElement(s.clientsds, &s.cur_clientsds); + } + } + + if (s.cur_clientsds == NULL) + rc = 0; + else + { + rc = *((int*)(s.cur_clientsds->content)); + ListNextElement(s.clientsds, &s.cur_clientsds); + } +exit: + FUNC_EXIT_RC(rc); + return rc; +} /* end getReadySocket */ + + +/** + * Reads one byte from a socket + * @param socket the socket to read from + * @param c the character read, returned + * @return completion code + */ +int Socket_getch(int socket, char* c) +{ + int rc = SOCKET_ERROR; + + FUNC_ENTRY; + if ((rc = SocketBuffer_getQueuedChar(socket, c)) != SOCKETBUFFER_INTERRUPTED) + goto exit; + + if ((rc = recv(socket, c, (size_t)1, 0)) == SOCKET_ERROR) + { + int err = Socket_error("recv - getch", socket); + if (err == EWOULDBLOCK || err == EAGAIN) + { + rc = TCPSOCKET_INTERRUPTED; + SocketBuffer_interrupted(socket, 0); + } + } + else if (rc == 0) + rc = SOCKET_ERROR; /* The return value from recv is 0 when the peer has performed an orderly shutdown. */ + else if (rc == 1) + { + SocketBuffer_queueChar(socket, *c); + rc = TCPSOCKET_COMPLETE; + } +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Attempts to read a number of bytes from a socket, non-blocking. If a previous read did not + * finish, then retrieve that data. + * @param socket the socket to read from + * @param bytes the number of bytes to read + * @param actual_len the actual number of bytes read + * @return completion code + */ +char *Socket_getdata(int socket, int bytes, int* actual_len) +{ + int rc; + char* buf; + + FUNC_ENTRY; + if (bytes == 0) + { + buf = SocketBuffer_complete(socket); + goto exit; + } + + buf = SocketBuffer_getQueuedData(socket, bytes, actual_len); + + if ((rc = recv(socket, buf + (*actual_len), (size_t)(bytes - (*actual_len)), 0)) == SOCKET_ERROR) + { + rc = Socket_error("recv - getdata", socket); + if (rc != EAGAIN && rc != EWOULDBLOCK) + { + buf = NULL; + goto exit; + } + } + else if (rc == 0) /* rc 0 means the other end closed the socket, albeit "gracefully" */ + { + buf = NULL; + goto exit; + } + else + *actual_len += rc; + + if (*actual_len == bytes) + SocketBuffer_complete(socket); + else /* we didn't read the whole packet */ + { + SocketBuffer_interrupted(socket, *actual_len); + Log(TRACE_MAX, -1, "%d bytes expected but %d bytes now received", bytes, *actual_len); + } +exit: + FUNC_EXIT; + return buf; +} + + +/** + * Indicate whether any data is pending outbound for a socket. + * @return boolean - true == data pending. + */ +int Socket_noPendingWrites(int socket) +{ + int cursock = socket; + return ListFindItem(s.write_pending, &cursock, intcompare) == NULL; +} + + +/** + * Attempts to write a series of iovec buffers to a socket in *one* system call so that + * they are sent as one packet. + * @param socket the socket to write to + * @param iovecs an array of buffers to write + * @param count number of buffers in iovecs + * @param bytes number of bytes actually written returned + * @return completion code, especially TCPSOCKET_INTERRUPTED + */ +int Socket_writev(int socket, iobuf* iovecs, int count, unsigned long* bytes) +{ + int rc; + + FUNC_ENTRY; +#if defined(WIN32) || defined(WIN64) + rc = WSASend(socket, iovecs, count, (LPDWORD)bytes, 0, NULL, NULL); + if (rc == SOCKET_ERROR) + { + int err = Socket_error("WSASend - putdatas", socket); + if (err == EWOULDBLOCK || err == EAGAIN) + rc = TCPSOCKET_INTERRUPTED; + } +#else + *bytes = 0L; + rc = writev(socket, iovecs, count); + if (rc == SOCKET_ERROR) + { + int err = Socket_error("writev - putdatas", socket); + if (err == EWOULDBLOCK || err == EAGAIN) + rc = TCPSOCKET_INTERRUPTED; + } + else + *bytes = rc; +#endif + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Attempts to write a series of buffers to a socket in *one* system call so that they are + * sent as one packet. + * @param socket the socket to write to + * @param buf0 the first buffer + * @param buf0len the length of data in the first buffer + * @param count number of buffers + * @param buffers an array of buffers to write + * @param buflens an array of corresponding buffer lengths + * @return completion code, especially TCPSOCKET_INTERRUPTED + */ +int Socket_putdatas(int socket, char* buf0, size_t buf0len, int count, char** buffers, size_t* buflens, int* frees) +{ + unsigned long bytes = 0L; + iobuf iovecs[5]; + int frees1[5]; + int rc = TCPSOCKET_INTERRUPTED, i, total = buf0len; + + FUNC_ENTRY; + if (!Socket_noPendingWrites(socket)) + { + Log(LOG_SEVERE, -1, "Trying to write to socket %d for which there is already pending output", socket); + rc = SOCKET_ERROR; + goto exit; + } + + for (i = 0; i < count; i++) + total += buflens[i]; + + iovecs[0].iov_base = buf0; + iovecs[0].iov_len = buf0len; + frees1[0] = 1; + for (i = 0; i < count; i++) + { + iovecs[i+1].iov_base = buffers[i]; + iovecs[i+1].iov_len = buflens[i]; + frees1[i+1] = frees[i]; + } + + if ((rc = Socket_writev(socket, iovecs, count+1, &bytes)) != SOCKET_ERROR) + { + if (bytes == total) + rc = TCPSOCKET_COMPLETE; + else + { + int* sockmem = (int*)malloc(sizeof(int)); + Log(TRACE_MIN, -1, "Partial write: %ld bytes of %d actually written on socket %d", + bytes, total, socket); +#if defined(OPENSSL) + SocketBuffer_pendingWrite(socket, NULL, count+1, iovecs, frees1, total, bytes); +#else + SocketBuffer_pendingWrite(socket, count+1, iovecs, frees1, total, bytes); +#endif + *sockmem = socket; + ListAppend(s.write_pending, sockmem, sizeof(int)); + FD_SET(socket, &(s.pending_wset)); + rc = TCPSOCKET_INTERRUPTED; + } + } +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Add a socket to the pending write list, so that it is checked for writing in select. This is used + * in connect processing when the TCP connect is incomplete, as we need to check the socket for both + * ready to read and write states. + * @param socket the socket to add + */ +void Socket_addPendingWrite(int socket) +{ + FD_SET(socket, &(s.pending_wset)); +} + + +/** + * Clear a socket from the pending write list - if one was added with Socket_addPendingWrite + * @param socket the socket to remove + */ +void Socket_clearPendingWrite(int socket) +{ + if (FD_ISSET(socket, &(s.pending_wset))) + FD_CLR(socket, &(s.pending_wset)); +} + + +/** + * Close a socket without removing it from the select list. + * @param socket the socket to close + * @return completion code + */ +int Socket_close_only(int socket) +{ + int rc; + + FUNC_ENTRY; +#if defined(WIN32) || defined(WIN64) + if (shutdown(socket, SD_BOTH) == SOCKET_ERROR) + Socket_error("shutdown", socket); + if ((rc = closesocket(socket)) == SOCKET_ERROR) + Socket_error("close", socket); +#else + if (shutdown(socket, SHUT_WR) == SOCKET_ERROR) + Socket_error("shutdown", socket); + if ((rc = recv(socket, NULL, (size_t)0, 0)) == SOCKET_ERROR) + Socket_error("shutdown", socket); + if ((rc = close(socket)) == SOCKET_ERROR) + Socket_error("close", socket); +#endif + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Close a socket and remove it from the select list. + * @param socket the socket to close + * @return completion code + */ +void Socket_close(int socket) +{ + FUNC_ENTRY; + Socket_close_only(socket); + FD_CLR(socket, &(s.rset_saved)); + if (FD_ISSET(socket, &(s.pending_wset))) + FD_CLR(socket, &(s.pending_wset)); + if (s.cur_clientsds != NULL && *(int*)(s.cur_clientsds->content) == socket) + s.cur_clientsds = s.cur_clientsds->next; + ListRemoveItem(s.connect_pending, &socket, intcompare); + ListRemoveItem(s.write_pending, &socket, intcompare); + SocketBuffer_cleanup(socket); + + if (ListRemoveItem(s.clientsds, &socket, intcompare)) + Log(TRACE_MIN, -1, "Removed socket %d", socket); + else + Log(LOG_ERROR, -1, "Failed to remove socket %d", socket); + if (socket + 1 >= s.maxfdp1) + { + /* now we have to reset s.maxfdp1 */ + ListElement* cur_clientsds = NULL; + + s.maxfdp1 = 0; + while (ListNextElement(s.clientsds, &cur_clientsds)) + s.maxfdp1 = max(*((int*)(cur_clientsds->content)), s.maxfdp1); + ++(s.maxfdp1); + Log(TRACE_MAX, -1, "Reset max fdp1 to %d", s.maxfdp1); + } + FUNC_EXIT; +} + + +/** + * Create a new socket and TCP connect to an address/port + * @param addr the address string + * @param port the TCP port + * @param sock returns the new socket + * @return completion code + */ +int Socket_new(char* addr, int port, int* sock) +{ + int type = SOCK_STREAM; + struct sockaddr_in address; +#if defined(AF_INET6) + struct sockaddr_in6 address6; +#endif + int rc = SOCKET_ERROR; +#if defined(WIN32) || defined(WIN64) + short family; +#else + sa_family_t family = AF_INET; +#endif + struct addrinfo *result = NULL; + struct addrinfo hints = {0, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL}; + + FUNC_ENTRY; + *sock = -1; + + if (addr[0] == '[') + ++addr; + + if ((rc = getaddrinfo(addr, NULL, &hints, &result)) == 0) + { + struct addrinfo* res = result; + + /* prefer ip4 addresses */ + while (res) + { + if (res->ai_family == AF_INET) + { + result = res; + break; + } + res = res->ai_next; + } + + if (result == NULL) + rc = -1; + else +#if defined(AF_INET6) + if (result->ai_family == AF_INET6) + { + address6.sin6_port = htons(port); + address6.sin6_family = family = AF_INET6; + address6.sin6_addr = ((struct sockaddr_in6*)(result->ai_addr))->sin6_addr; + } + else +#endif + if (result->ai_family == AF_INET) + { + address.sin_port = htons(port); + address.sin_family = family = AF_INET; + address.sin_addr = ((struct sockaddr_in*)(result->ai_addr))->sin_addr; + } + else + rc = -1; + + freeaddrinfo(result); + } + else + Log(LOG_ERROR, -1, "getaddrinfo failed for addr %s with rc %d", addr, rc); + + if (rc != 0) + Log(LOG_ERROR, -1, "%s is not a valid IP address", addr); + else + { + *sock = socket(family, type, 0); + if (*sock == INVALID_SOCKET) + rc = Socket_error("socket", *sock); + else + { +#if defined(NOSIGPIPE) + int opt = 1; + + if (setsockopt(*sock, SOL_SOCKET, SO_NOSIGPIPE, (void*)&opt, sizeof(opt)) != 0) + Log(LOG_ERROR, -1, "Could not set SO_NOSIGPIPE for socket %d", *sock); +#endif + + Log(TRACE_MIN, -1, "New socket %d for %s, port %d", *sock, addr, port); + if (Socket_addSocket(*sock) == SOCKET_ERROR) + rc = Socket_error("setnonblocking", *sock); + else + { + /* this could complete immmediately, even though we are non-blocking */ + if (family == AF_INET) + rc = connect(*sock, (struct sockaddr*)&address, sizeof(address)); + #if defined(AF_INET6) + else + rc = connect(*sock, (struct sockaddr*)&address6, sizeof(address6)); + #endif + if (rc == SOCKET_ERROR) + rc = Socket_error("connect", *sock); + if (rc == EINPROGRESS || rc == EWOULDBLOCK) + { + int* pnewSd = (int*)malloc(sizeof(int)); + *pnewSd = *sock; + ListAppend(s.connect_pending, pnewSd, sizeof(int)); + Log(TRACE_MIN, 15, "Connect pending"); + } + } + } + } + FUNC_EXIT_RC(rc); + return rc; +} + + +static Socket_writeComplete* writecomplete = NULL; + +void Socket_setWriteCompleteCallback(Socket_writeComplete* mywritecomplete) +{ + writecomplete = mywritecomplete; +} + +/** + * Continue an outstanding write for a particular socket + * @param socket that socket + * @return completion code + */ +int Socket_continueWrite(int socket) +{ + int rc = 0; + pending_writes* pw; + unsigned long curbuflen = 0L, /* cumulative total of buffer lengths */ + bytes; + int curbuf = -1, i; + iobuf iovecs1[5]; + + FUNC_ENTRY; + pw = SocketBuffer_getWrite(socket); + +#if defined(OPENSSL) + if (pw->ssl) + { + rc = SSLSocket_continueWrite(pw); + goto exit; + } +#endif + + for (i = 0; i < pw->count; ++i) + { + if (pw->bytes <= curbuflen) + { /* if previously written length is less than the buffer we are currently looking at, + add the whole buffer */ + iovecs1[++curbuf].iov_len = pw->iovecs[i].iov_len; + iovecs1[curbuf].iov_base = pw->iovecs[i].iov_base; + } + else if (pw->bytes < curbuflen + pw->iovecs[i].iov_len) + { /* if previously written length is in the middle of the buffer we are currently looking at, + add some of the buffer */ + int offset = pw->bytes - curbuflen; + iovecs1[++curbuf].iov_len = pw->iovecs[i].iov_len - offset; + iovecs1[curbuf].iov_base = pw->iovecs[i].iov_base + offset; + break; + } + curbuflen += pw->iovecs[i].iov_len; + } + + if ((rc = Socket_writev(socket, iovecs1, curbuf+1, &bytes)) != SOCKET_ERROR) + { + pw->bytes += bytes; + if ((rc = (pw->bytes == pw->total))) + { /* topic and payload buffers are freed elsewhere, when all references to them have been removed */ + for (i = 0; i < pw->count; i++) + { + if (pw->frees[i]) + free(pw->iovecs[i].iov_base); + } + Log(TRACE_MIN, -1, "ContinueWrite: partial write now complete for socket %d", socket); + } + else + Log(TRACE_MIN, -1, "ContinueWrite wrote +%lu bytes on socket %d", bytes, socket); + } +#if defined(OPENSSL) +exit: +#endif + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Continue any outstanding writes for a socket set + * @param pwset the set of sockets + * @return completion code + */ +int Socket_continueWrites(fd_set* pwset) +{ + int rc1 = 0; + ListElement* curpending = s.write_pending->first; + + FUNC_ENTRY; + while (curpending) + { + int socket = *(int*)(curpending->content); + if (FD_ISSET(socket, pwset) && Socket_continueWrite(socket)) + { + if (!SocketBuffer_writeComplete(socket)) + Log(LOG_SEVERE, -1, "Failed to remove pending write from socket buffer list"); + FD_CLR(socket, &(s.pending_wset)); + if (!ListRemove(s.write_pending, curpending->content)) + { + Log(LOG_SEVERE, -1, "Failed to remove pending write from list"); + ListNextElement(s.write_pending, &curpending); + } + curpending = s.write_pending->current; + + if (writecomplete) + (*writecomplete)(socket); + } + else + ListNextElement(s.write_pending, &curpending); + } + FUNC_EXIT_RC(rc1); + return rc1; +} + + +/** + * Convert a numeric address to character string + * @param sa socket numerical address + * @param sock socket + * @return the peer information + */ +char* Socket_getaddrname(struct sockaddr* sa, int sock) +{ +/** + * maximum length of the address string + */ +#define ADDRLEN INET6_ADDRSTRLEN+1 +/** + * maximum length of the port string + */ +#define PORTLEN 10 + static char addr_string[ADDRLEN + PORTLEN]; + +#if defined(WIN32) || defined(WIN64) + int buflen = ADDRLEN*2; + wchar_t buf[ADDRLEN*2]; + if (WSAAddressToString(sa, sizeof(struct sockaddr_in6), NULL, buf, (LPDWORD)&buflen) == SOCKET_ERROR) + Socket_error("WSAAddressToString", sock); + else + wcstombs(addr_string, buf, sizeof(addr_string)); + /* TODO: append the port information - format: [00:00:00::]:port */ + /* strcpy(&addr_string[strlen(addr_string)], "what?"); */ +#else + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + inet_ntop(sin->sin_family, &sin->sin_addr, addr_string, ADDRLEN); + sprintf(&addr_string[strlen(addr_string)], ":%d", ntohs(sin->sin_port)); +#endif + return addr_string; +} + + +/** + * Get information about the other end connected to a socket + * @param sock the socket to inquire on + * @return the peer information + */ +char* Socket_getpeer(int sock) +{ + struct sockaddr_in6 sa; + socklen_t sal = sizeof(sa); + int rc; + + if ((rc = getpeername(sock, (struct sockaddr*)&sa, &sal)) == SOCKET_ERROR) + { + Socket_error("getpeername", sock); + return "unknown"; + } + + return Socket_getaddrname((struct sockaddr*)&sa, sock); +} + + +#if defined(Socket_TEST) + +int main(int argc, char *argv[]) +{ + Socket_connect("127.0.0.1", 1883); + Socket_connect("localhost", 1883); + Socket_connect("loadsadsacalhost", 1883); +} + +#endif + diff --git a/Sources/paho/src/SocketBuffer.c b/Sources/paho/src/SocketBuffer.c new file mode 100644 index 0000000..2a45b3d --- /dev/null +++ b/Sources/paho/src/SocketBuffer.c @@ -0,0 +1,393 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs, Allan Stockdill-Mander - SSL updates + *******************************************************************************/ + +/** + * @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 +#include +#include + +#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; + +/** + * 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. + */ +void SocketBuffer_newDefQ(void) +{ + def_queue = malloc(sizeof(socket_queue)); + def_queue->buflen = 1000; + def_queue->buf = malloc(def_queue->buflen); + def_queue->socket = def_queue->index = def_queue->buflen = def_queue->datalen = 0; +} + + +/** + * Initialize the socketBuffer module + */ +void SocketBuffer_initialize(void) +{ + FUNC_ENTRY; + SocketBuffer_newDefQ(); + queues = ListInitialize(); + ListZero(&writes); + FUNC_EXIT; +} + + +/** + * Free the default queue memory + */ +void SocketBuffer_freeDefQ(void) +{ + free(def_queue->buf); + free(def_queue); +} + + +/** + * 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; + 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 = 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, int bytes, int* 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); + memcpy(newmem, queue->buf, queue->datalen); + free(queue->buf); + queue->buf = newmem; + } + else + queue->buf = realloc(queue->buf, bytes); + queue->buflen = bytes; + } + + 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, 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, int 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; + 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 = def_queue->headerlen = def_queue->datalen = 0; + FUNC_EXIT; + return def_queue->buf; +} + + +/** + * A socket operation had now completed so we can get rid of the queue + * @param socket the socket for which the operation is now complete + * @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 = 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, 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 total total data length to be written + * @param bytes actual data length that was written + */ +#if defined(OPENSSL) +void SocketBuffer_pendingWrite(int socket, SSL* ssl, int count, iobuf* iovecs, int* frees, int total, int bytes) +#else +void SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int* frees, int total, int bytes) +#endif +{ + int i = 0; + pending_writes* pw = NULL; + + FUNC_ENTRY; + /* store the buffers until the whole packet is written */ + pw = malloc(sizeof(pending_writes)); + pw->socket = socket; +#if defined(OPENSSL) + pw->ssl = ssl; +#endif + pw->bytes = bytes; + pw->total = total; + pw->count = count; + for (i = 0; i < count; i++) + { + pw->iovecs[i] = iovecs[i]; + pw->frees[i] = frees[i]; + } + ListAppend(&writes, pw, sizeof(pw) + total); + FUNC_EXIT; +} + + +/** + * 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; +} diff --git a/Sources/paho/src/StackTrace.c b/Sources/paho/src/StackTrace.c new file mode 100644 index 0000000..b4e5b3b --- /dev/null +++ b/Sources/paho/src/StackTrace.c @@ -0,0 +1,204 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + +#include "StackTrace.h" +#include "Log.h" +#include "LinkedList.h" + +#include "Clients.h" +#include "Thread.h" + +#include +#include +#include + +#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" + +static int thread_count = 0; +static threadEntry threads[MAX_THREADS]; +static threadEntry *cur_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 i = -1; + thread_id_type curid = Thread_getid(); + + cur_thread = NULL; + for (i = 0; i < MAX_THREADS && i < thread_count; ++i) + { + if (threads[i].id == curid) + { + cur_thread = &threads[i]; + break; + } + } + + if (cur_thread == NULL && create && thread_count < MAX_THREADS) + { + cur_thread = &threads[thread_count]; + cur_thread->id = curid; + cur_thread->maxdepth = 0; + cur_thread->current_depth = 0; + ++thread_count; + } + return cur_thread != NULL; /* good == 1 */ +} + +void StackTrace_entry(const char* name, int line, int trace_level) +{ + Thread_lock_mutex(stack_mutex); + if (!setStack(1)) + goto exit; + if (trace_level != -1) + Log_stackTrace(trace_level, 9, (int)cur_thread->id, cur_thread->current_depth, name, line, NULL); + strncpy(cur_thread->callstack[cur_thread->current_depth].name, name, sizeof(cur_thread->callstack[0].name)-1); + cur_thread->callstack[(cur_thread->current_depth)++].line = line; + if (cur_thread->current_depth > cur_thread->maxdepth) + cur_thread->maxdepth = cur_thread->current_depth; + if (cur_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, int trace_level) +{ + Thread_lock_mutex(stack_mutex); + if (!setStack(0)) + goto exit; + if (--(cur_thread->current_depth) < 0) + Log(LOG_FATAL, -1, "Minimum stack depth exceeded for thread %lu", cur_thread->id); + if (strncmp(cur_thread->callstack[cur_thread->current_depth].name, name, sizeof(cur_thread->callstack[0].name)-1) != 0) + Log(LOG_FATAL, -1, "Stack mismatch. Entry:%s Exit:%s\n", cur_thread->callstack[cur_thread->current_depth].name, name); + if (trace_level != -1) + { + if (rc == NULL) + Log_stackTrace(trace_level, 10, (int)cur_thread->id, cur_thread->current_depth, name, line, NULL); + else + Log_stackTrace(trace_level, 11, (int)cur_thread->id, cur_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) +{ + int bufsize = 256; + char* buf = NULL; + int t = 0; + + if ((buf = malloc(bufsize)) == NULL) + 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, + " 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; +} + diff --git a/Sources/paho/src/Thread.c b/Sources/paho/src/Thread.c new file mode 100644 index 0000000..50db592 --- /dev/null +++ b/Sources/paho/src/Thread.c @@ -0,0 +1,502 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial implementation + * Ian Craggs, Allan Stockdill-Mander - async client updates + * Ian Craggs - bug #415042 - start Linux thread as disconnected + * Ian Craggs - fix for bug #420851 + *******************************************************************************/ + +/** + * @file + * \brief Threading related functions + * + * Used to create platform independent threading functions + */ + + +#include "Thread.h" +#if defined(THREAD_UNIT_TESTS) +#define NOSTACKTRACE +#endif +#include "StackTrace.h" + +#undef malloc +#undef realloc +#undef free + +#if !defined(WIN32) && !defined(WIN64) +#include +#include +#include +#include +#include +#include +#include +#endif +#include +#include + +/** + * 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() +{ + mutex_type mutex = NULL; + int rc = 0; + + FUNC_ENTRY; + #if defined(WIN32) || defined(WIN64) + mutex = CreateMutex(NULL, 0, NULL); + #else + mutex = malloc(sizeof(pthread_mutex_t)); + rc = pthread_mutex_init(mutex, NULL); + #endif + FUNC_EXIT_RC(rc); + return mutex; +} + + +/** + * Lock a mutex which has already been created, block until ready + * @param mutex the mutex + * @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 + */ +void 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); +} + + +/** + * 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() +{ + #if defined(WIN32) || defined(WIN64) + return GetCurrentThreadId(); + #else + return pthread_self(); + #endif +} + + +#if defined(USE_NAMED_SEMAPHORES) +#define MAX_NAMED_SEMAPHORES 10 + +static int named_semaphore_count = 0; + +static struct +{ + sem_type sem; + char name[NAME_MAX-4]; +} named_semaphores[MAX_NAMED_SEMAPHORES]; + +#endif + + +/** + * Create a new semaphore + * @return the new condition variable + */ +sem_type Thread_create_sem() +{ + sem_type sem = NULL; + int rc = 0; + + FUNC_ENTRY; + #if defined(WIN32) || defined(WIN64) + sem = CreateEvent( + NULL, // default security attributes + FALSE, // manual-reset event? + FALSE, // initial state is nonsignaled + NULL // object name + ); + #elif defined(USE_NAMED_SEMAPHORES) + if (named_semaphore_count == 0) + memset(named_semaphores, '\0', sizeof(named_semaphores)); + char* name = &(strrchr(tempnam("/", "MQTT"), '/'))[1]; /* skip first slash of name */ + if ((sem = sem_open(name, O_CREAT, S_IRWXU, 0)) == SEM_FAILED) + rc = -1; + else + { + int i; + + named_semaphore_count++; + for (i = 0; i < MAX_NAMED_SEMAPHORES; ++i) + { + if (named_semaphores[i].name[0] == '\0') + { + named_semaphores[i].sem = sem; + strcpy(named_semaphores[i].name, name); + break; + } + } + } + #else + #if !defined(__MACH__) && !defined(__APPLE__) + sem = malloc(sizeof(sem_t)); + rc = sem_init(sem, 0, 0); + #endif + #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) +#define USE_TRYWAIT +#if defined(USE_TRYWAIT) + int i = 0; + int 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) + rc = WaitForSingleObject(sem, timeout); + #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 + 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. + * @param sem the semaphore + * @return 0 (false) or 1 (true) + */ +int Thread_check_sem(sem_type sem) +{ +#if defined(WIN32) || defined(WIN64) + return WaitForSingleObject(sem, 0) == WAIT_OBJECT_0; +#else + int semval = -1; + #if !defined(__MACH__) && !defined(__APPLE__) + sem_getvalue(sem, &semval); + #endif + return semval > 0; +#endif +} + + +/** + * Post a semaphore + * @param sem the semaphore + * @return completion code + */ +int Thread_post_sem(sem_type sem) +{ + int rc = 0; + + FUNC_ENTRY; + #if defined(WIN32) || defined(WIN64) + if (SetEvent(sem) == 0) + rc = GetLastError(); + #else + if (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(USE_NAMED_SEMAPHORES) + int i; + rc = sem_close(sem); + for (i = 0; i < MAX_NAMED_SEMAPHORES; ++i) + { + if (named_semaphores[i].sem == sem) + { + rc = sem_unlink(named_semaphores[i].name); + named_semaphores[i].name[0] = '\0'; + break; + } + } + named_semaphore_count--; + #else + #if !defined(__MACH__) && !defined(__APPLE__) + rc = sem_destroy(sem); + free(sem); + #endif + #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() +{ + cond_type condvar = NULL; + int rc = 0; + + FUNC_ENTRY; + condvar = malloc(sizeof(cond_type_struct)); + rc = pthread_cond_init(&condvar->cond, NULL); + rc = pthread_mutex_init(&condvar->mutex, NULL); + + FUNC_EXIT_RC(rc); + return condvar; +} + +/** + * Signal a condition variable + * @return completion code + */ +int Thread_signal_cond(cond_type condvar) +{ + int rc = 0; + + pthread_mutex_lock(&condvar->mutex); + rc = pthread_cond_signal(&condvar->cond); + pthread_mutex_unlock(&condvar->mutex); + + return rc; +} + +/** + * Wait with a timeout (seconds) for condition variable + * @return completion code + */ +int Thread_wait_cond(cond_type condvar, int timeout) +{ + FUNC_ENTRY; + int rc = 0; + struct timespec cond_timeout; + struct timeval cur_time; + + gettimeofday(&cur_time, NULL); + + cond_timeout.tv_sec = cur_time.tv_sec + timeout; + cond_timeout.tv_nsec = cur_time.tv_usec * 1000; + + pthread_mutex_lock(&condvar->mutex); + rc = pthread_cond_timedwait(&condvar->cond, &condvar->mutex, &cond_timeout); + pthread_mutex_unlock(&condvar->mutex); + + FUNC_EXIT_RC(rc); + return rc; +} + +/** + * Destroy a condition variable + * @return completion code + */ +int Thread_destroy_cond(cond_type condvar) +{ + int rc = 0; + + rc = pthread_mutex_destroy(&condvar->mutex); + rc = pthread_cond_destroy(&condvar->cond); + free(condvar); + + return rc; +} +#endif + + +#if defined(THREAD_UNIT_TESTS) + +#include + +thread_return_type secondary(void* n) +{ + int rc = 0; + + /* + cond_type cond = n; + + printf("Secondary thread about to wait\n"); + rc = Thread_wait_cond(cond); + printf("Secondary thread returned from wait %d\n", rc);*/ + + sem_type sem = n; + + printf("Secondary thread about to wait\n"); + rc = Thread_wait_sem(sem); + printf("Secondary thread returned from wait %d\n", rc); + + printf("Secondary thread about to wait\n"); + rc = Thread_wait_sem(sem); + printf("Secondary thread returned from wait %d\n", rc); + printf("Secondary check sem %d\n", Thread_check_sem(sem)); + + return 0; +} + + +int main(int argc, char *argv[]) +{ + int rc = 0; + + sem_type sem = Thread_create_sem(); + + printf("check sem %d\n", Thread_check_sem(sem)); + + printf("post secondary\n"); + rc = Thread_post_sem(sem); + printf("posted secondary %d\n", rc); + + printf("check sem %d\n", Thread_check_sem(sem)); + + printf("Starting secondary thread\n"); + Thread_start(secondary, (void*)sem); + + sleep(3); + printf("check sem %d\n", Thread_check_sem(sem)); + + printf("post secondary\n"); + rc = Thread_post_sem(sem); + printf("posted secondary %d\n", rc); + + sleep(3); + + printf("Main thread ending\n"); +} + +#endif diff --git a/Sources/paho/src/Tree.c b/Sources/paho/src/Tree.c new file mode 100644 index 0000000..d3aafc4 --- /dev/null +++ b/Sources/paho/src/Tree.c @@ -0,0 +1,707 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial implementation and documentation + *******************************************************************************/ + +/** @file + * \brief functions which apply to tree structures. + * + * These trees can hold data of any sort, pointed to by the content pointer of the + * Node structure. + * */ + +#define NO_HEAP_TRACKING 1 + +#include "Tree.h" + +#include +#include +#include +#include + +#include "Heap.h" + + +void TreeInitializeNoMalloc(Tree* aTree, int(*compare)(void*, void*, int)) +{ + memset(aTree, '\0', sizeof(Tree)); + aTree->heap_tracking = 1; + aTree->index[0].compare = compare; + aTree->indexes = 1; +} + +/** + * Allocates and initializes a new tree structure. + * @return a pointer to the new tree structure + */ +Tree* TreeInitialize(int(*compare)(void*, void*, int)) +{ +#if defined(UNIT_TESTS) + Tree* newt = malloc(sizeof(Tree)); +#else + Tree* newt = mymalloc(__FILE__, __LINE__, sizeof(Tree)); +#endif + TreeInitializeNoMalloc(newt, compare); + return newt; +} + + +void TreeAddIndex(Tree* aTree, int(*compare)(void*, void*, int)) +{ + aTree->index[aTree->indexes].compare = compare; + ++(aTree->indexes); +} + + +void TreeFree(Tree* aTree) +{ +#if defined(UNIT_TESTS) + free(aTree); +#else + (aTree->heap_tracking) ? myfree(__FILE__, __LINE__, aTree) : free(aTree); +#endif +} + + +#define LEFT 0 +#define RIGHT 1 +#if !defined(max) +#define max(a, b) (a > b) ? a : b; +#endif + + + +int isRed(Node* aNode) +{ + return (aNode != NULL) && (aNode->red); +} + + +int isBlack(Node* aNode) +{ + return (aNode == NULL) || (aNode->red == 0); +} + + +int TreeWalk(Node* curnode, int depth) +{ + if (curnode) + { + int left = TreeWalk(curnode->child[LEFT], depth+1); + int right = TreeWalk(curnode->child[RIGHT], depth+1); + depth = max(left, right); + if (curnode->red) + { + /*if (isRed(curnode->child[LEFT]) || isRed(curnode->child[RIGHT])) + { + printf("red/black tree violation %p\n", curnode->content); + exit(-99); + }*/; + } + } + return depth; +} + + +int TreeMaxDepth(Tree *aTree) +{ + int rc = TreeWalk(aTree->index[0].root, 0); + /*if (aTree->root->red) + { + printf("root node should not be red %p\n", aTree->root->content); + exit(-99); + }*/ + return rc; +} + + +void TreeRotate(Tree* aTree, Node* curnode, int direction, int index) +{ + Node* other = curnode->child[!direction]; + + curnode->child[!direction] = other->child[direction]; + if (other->child[direction] != NULL) + other->child[direction]->parent = curnode; + other->parent = curnode->parent; + if (curnode->parent == NULL) + aTree->index[index].root = other; + else if (curnode == curnode->parent->child[direction]) + curnode->parent->child[direction] = other; + else + curnode->parent->child[!direction] = other; + other->child[direction] = curnode; + curnode->parent = other; +} + + +Node* TreeBAASub(Tree* aTree, Node* curnode, int which, int index) +{ + Node* uncle = curnode->parent->parent->child[which]; + + if (isRed(uncle)) + { + curnode->parent->red = uncle->red = 0; + curnode = curnode->parent->parent; + curnode->red = 1; + } + else + { + if (curnode == curnode->parent->child[which]) + { + curnode = curnode->parent; + TreeRotate(aTree, curnode, !which, index); + } + curnode->parent->red = 0; + curnode->parent->parent->red = 1; + TreeRotate(aTree, curnode->parent->parent, which, index); + } + return curnode; +} + + +void TreeBalanceAfterAdd(Tree* aTree, Node* curnode, int index) +{ + while (curnode && isRed(curnode->parent) && curnode->parent->parent) + { + if (curnode->parent == curnode->parent->parent->child[LEFT]) + curnode = TreeBAASub(aTree, curnode, RIGHT, index); + else + curnode = TreeBAASub(aTree, curnode, LEFT, index); + } + aTree->index[index].root->red = 0; +} + + +/** + * Add an item to a tree + * @param aTree the list to which the item is to be added + * @param content the list item content itself + * @param size the size of the element + */ +void* TreeAddByIndex(Tree* aTree, void* content, int size, int index) +{ + Node* curparent = NULL; + Node* curnode = aTree->index[index].root; + Node* newel = NULL; + int left = 0; + int result = 1; + void* rc = NULL; + + while (curnode) + { + result = aTree->index[index].compare(curnode->content, content, 1); + left = (result > 0); + if (result == 0) + break; + else + { + curparent = curnode; + curnode = curnode->child[left]; + } + } + + if (result == 0) + { + if (aTree->allow_duplicates) + exit(-99); + { + newel = curnode; + rc = newel->content; + if (index == 0) + aTree->size += (size - curnode->size); + } + } + else + { + #if defined(UNIT_TESTS) + newel = malloc(sizeof(Node)); + #else + newel = (aTree->heap_tracking) ? mymalloc(__FILE__, __LINE__, sizeof(Node)) : malloc(sizeof(Node)); + #endif + memset(newel, '\0', sizeof(Node)); + if (curparent) + curparent->child[left] = newel; + else + aTree->index[index].root = newel; + newel->parent = curparent; + newel->red = 1; + if (index == 0) + { + ++(aTree->count); + aTree->size += size; + } + } + newel->content = content; + newel->size = size; + TreeBalanceAfterAdd(aTree, newel, index); + return rc; +} + + +void* TreeAdd(Tree* aTree, void* content, int size) +{ + void* rc = NULL; + int i; + + for (i = 0; i < aTree->indexes; ++i) + rc = TreeAddByIndex(aTree, content, size, i); + + return rc; +} + + +Node* TreeFindIndex1(Tree* aTree, void* key, int index, int value) +{ + int result = 0; + Node* curnode = aTree->index[index].root; + + while (curnode) + { + result = aTree->index[index].compare(curnode->content, key, value); + if (result == 0) + break; + else + curnode = curnode->child[result > 0]; + } + return curnode; +} + + +Node* TreeFindIndex(Tree* aTree, void* key, int index) +{ + return TreeFindIndex1(aTree, key, index, 0); +} + + +Node* TreeFindContentIndex(Tree* aTree, void* key, int index) +{ + return TreeFindIndex1(aTree, key, index, 1); +} + + +Node* TreeFind(Tree* aTree, void* key) +{ + return TreeFindIndex(aTree, key, 0); +} + + +Node* TreeMinimum(Node* curnode) +{ + if (curnode) + while (curnode->child[LEFT]) + curnode = curnode->child[LEFT]; + return curnode; +} + + +Node* TreeSuccessor(Node* curnode) +{ + if (curnode->child[RIGHT]) + curnode = TreeMinimum(curnode->child[RIGHT]); + else + { + Node* curparent = curnode->parent; + while (curparent && curnode == curparent->child[RIGHT]) + { + curnode = curparent; + curparent = curparent->parent; + } + curnode = curparent; + } + return curnode; +} + + +Node* TreeNextElementIndex(Tree* aTree, Node* curnode, int index) +{ + if (curnode == NULL) + curnode = TreeMinimum(aTree->index[index].root); + else + curnode = TreeSuccessor(curnode); + return curnode; +} + + +Node* TreeNextElement(Tree* aTree, Node* curnode) +{ + return TreeNextElementIndex(aTree, curnode, 0); +} + + +Node* TreeBARSub(Tree* aTree, Node* curnode, int which, int index) +{ + Node* sibling = curnode->parent->child[which]; + + if (isRed(sibling)) + { + sibling->red = 0; + curnode->parent->red = 1; + TreeRotate(aTree, curnode->parent, !which, index); + sibling = curnode->parent->child[which]; + } + if (!sibling) + curnode = curnode->parent; + else if (isBlack(sibling->child[!which]) && isBlack(sibling->child[which])) + { + sibling->red = 1; + curnode = curnode->parent; + } + else + { + if (isBlack(sibling->child[which])) + { + sibling->child[!which]->red = 0; + sibling->red = 1; + TreeRotate(aTree, sibling, which, index); + sibling = curnode->parent->child[which]; + } + sibling->red = curnode->parent->red; + curnode->parent->red = 0; + sibling->child[which]->red = 0; + TreeRotate(aTree, curnode->parent, !which, index); + curnode = aTree->index[index].root; + } + return curnode; +} + + +void TreeBalanceAfterRemove(Tree* aTree, Node* curnode, int index) +{ + while (curnode != aTree->index[index].root && isBlack(curnode)) + { + /* curnode->content == NULL must equal curnode == NULL */ + if (((curnode->content) ? curnode : NULL) == curnode->parent->child[LEFT]) + curnode = TreeBARSub(aTree, curnode, RIGHT, index); + else + curnode = TreeBARSub(aTree, curnode, LEFT, index); + } + curnode->red = 0; +} + + +/** + * Remove an item from a tree + * @param aTree the list to which the item is to be added + * @param curnode the list item content itself + */ +void* TreeRemoveNodeIndex(Tree* aTree, Node* curnode, int index) +{ + Node* redundant = curnode; + Node* curchild = NULL; + int size = curnode->size; + void* content = curnode->content; + + /* if the node to remove has 0 or 1 children, it can be removed without involving another node */ + if (curnode->child[LEFT] && curnode->child[RIGHT]) /* 2 children */ + redundant = TreeSuccessor(curnode); /* now redundant must have at most one child */ + + curchild = redundant->child[(redundant->child[LEFT] != NULL) ? LEFT : RIGHT]; + if (curchild) /* we could have no children at all */ + curchild->parent = redundant->parent; + + if (redundant->parent == NULL) + aTree->index[index].root = curchild; + else + { + if (redundant == redundant->parent->child[LEFT]) + redundant->parent->child[LEFT] = curchild; + else + redundant->parent->child[RIGHT] = curchild; + } + + if (redundant != curnode) + { + curnode->content = redundant->content; + curnode->size = redundant->size; + } + + if (isBlack(redundant)) + { + if (curchild == NULL) + { + if (redundant->parent) + { + Node temp; + memset(&temp, '\0', sizeof(Node)); + temp.parent = (redundant) ? redundant->parent : NULL; + temp.red = 0; + TreeBalanceAfterRemove(aTree, &temp, index); + } + } + else + TreeBalanceAfterRemove(aTree, curchild, index); + } + +#if defined(UNIT_TESTS) + free(redundant); +#else + (aTree->heap_tracking) ? myfree(__FILE__, __LINE__, redundant) : free(redundant); +#endif + if (index == 0) + { + aTree->size -= size; + --(aTree->count); + } + return content; +} + + +/** + * Remove an item from a tree + * @param aTree the list to which the item is to be added + * @param curnode the list item content itself + */ +void* TreeRemoveIndex(Tree* aTree, void* content, int index) +{ + Node* curnode = TreeFindContentIndex(aTree, content, index); + + if (curnode == NULL) + return NULL; + + return TreeRemoveNodeIndex(aTree, curnode, index); +} + + +void* TreeRemove(Tree* aTree, void* content) +{ + int i; + void* rc = NULL; + + for (i = 0; i < aTree->indexes; ++i) + rc = TreeRemoveIndex(aTree, content, i); + + return rc; +} + + +void* TreeRemoveKeyIndex(Tree* aTree, void* key, int index) +{ + Node* curnode = TreeFindIndex(aTree, key, index); + void* content = NULL; + int i; + + if (curnode == NULL) + return NULL; + + content = TreeRemoveNodeIndex(aTree, curnode, index); + for (i = 0; i < aTree->indexes; ++i) + { + if (i != index) + content = TreeRemoveIndex(aTree, content, i); + } + return content; +} + + +void* TreeRemoveKey(Tree* aTree, void* key) +{ + return TreeRemoveKeyIndex(aTree, key, 0); +} + + +int TreeIntCompare(void* a, void* b, int content) +{ + int i = *((int*)a); + int j = *((int*)b); + + //printf("comparing %d %d\n", *((int*)a), *((int*)b)); + return (i > j) ? -1 : (i == j) ? 0 : 1; +} + + +int TreePtrCompare(void* a, void* b, int content) +{ + return (a > b) ? -1 : (a == b) ? 0 : 1; +} + + +int TreeStringCompare(void* a, void* b, int content) +{ + return strcmp((char*)a, (char*)b); +} + + +#if defined(UNIT_TESTS) + +int check(Tree *t) +{ + Node* curnode = NULL; + int rc = 0; + + curnode = TreeNextElement(t, curnode); + while (curnode) + { + Node* prevnode = curnode; + + curnode = TreeNextElement(t, curnode); + + if (prevnode && curnode && (*(int*)(curnode->content) < *(int*)(prevnode->content))) + { + printf("out of order %d < %d\n", *(int*)(curnode->content), *(int*)(prevnode->content)); + rc = 99; + } + } + return rc; +} + + +int traverse(Tree *t, int lookfor) +{ + Node* curnode = NULL; + int rc = 0; + + printf("Traversing\n"); + curnode = TreeNextElement(t, curnode); + //printf("content int %d\n", *(int*)(curnode->content)); + while (curnode) + { + Node* prevnode = curnode; + + curnode = TreeNextElement(t, curnode); + //if (curnode) + // printf("content int %d\n", *(int*)(curnode->content)); + if (prevnode && curnode && (*(int*)(curnode->content) < *(int*)(prevnode->content))) + { + printf("out of order %d < %d\n", *(int*)(curnode->content), *(int*)(prevnode->content)); + } + if (curnode && (lookfor == *(int*)(curnode->content))) + printf("missing item %d actually found\n", lookfor); + } + printf("End traverse %d\n", rc); + return rc; +} + + +int test(int limit) +{ + int i, *ip, *todelete; + Node* current = NULL; + Tree* t = TreeInitialize(TreeIntCompare); + int rc = 0; + + printf("Tree initialized\n"); + + srand(time(NULL)); + + ip = malloc(sizeof(int)); + *ip = 2; + TreeAdd(t, (void*)ip, sizeof(int)); + + check(t); + + i = 2; + void* result = TreeRemove(t, (void*)&i); + if (result) + free(result); + + int actual[limit]; + for (i = 0; i < limit; i++) + { + void* replaced = NULL; + + ip = malloc(sizeof(int)); + *ip = rand(); + replaced = TreeAdd(t, (void*)ip, sizeof(int)); + if (replaced) /* duplicate */ + { + free(replaced); + actual[i] = -1; + } + else + actual[i] = *ip; + if (i==5) + todelete = ip; + printf("Tree element added %d\n", *ip); + if (1 % 1000 == 0) + { + rc = check(t); + printf("%d elements, check result %d\n", i+1, rc); + if (rc != 0) + return 88; + } + } + + check(t); + + for (i = 0; i < limit; i++) + { + int parm = actual[i]; + + if (parm == -1) + continue; + + Node* found = TreeFind(t, (void*)&parm); + if (found) + printf("Tree find %d %d\n", parm, *(int*)(found->content)); + else + { + printf("%d not found\n", parm); + traverse(t, parm); + return -2; + } + } + + check(t); + + for (i = limit -1; i >= 0; i--) + { + int parm = actual[i]; + void *found; + + if (parm == -1) /* skip duplicate */ + continue; + + found = TreeRemove(t, (void*)&parm); + if (found) + { + printf("%d Tree remove %d %d\n", i, parm, *(int*)(found)); + free(found); + } + else + { + int count = 0; + printf("%d %d not found\n", i, parm); + traverse(t, parm); + for (i = 0; i < limit; i++) + if (actual[i] == parm) + ++count; + printf("%d occurs %d times\n", parm, count); + return -2; + } + if (i % 1000 == 0) + { + rc = check(t); + printf("%d elements, check result %d\n", i+1, rc); + if (rc != 0) + return 88; + } + } + printf("finished\n"); + return 0; +} + +int main(int argc, char *argv[]) +{ + int rc = 0; + + while (rc == 0) + rc = test(999999); +} + +#endif + + + + + diff --git a/Sources/paho/src/samples/MQTTAsync_publish.c b/Sources/paho/src/samples/MQTTAsync_publish.c new file mode 100644 index 0000000..2d07a8d --- /dev/null +++ b/Sources/paho/src/samples/MQTTAsync_publish.c @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial contribution + *******************************************************************************/ + +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTAsync.h" + +#if !defined(WIN32) +#include +#endif + +#define ADDRESS "tcp://m2m.eclipse.org:1883" +#define CLIENTID "ExampleClientPub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +volatile MQTTAsync_token deliveredtoken; + +int finished = 0; + +void connlost(void *context, char *cause) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + int rc; + + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); + + printf("Reconnecting\n"); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + finished = 1; + } +} + + +void onDisconnect(void* context, MQTTAsync_successData* response) +{ + printf("Successful disconnection\n"); + finished = 1; +} + + +void onSend(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + printf("Message with token value %d delivery confirmed\n", response->token); + + opts.onSuccess = onDisconnect; + opts.context = client; + + if ((rc = MQTTAsync_disconnect(client, &opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start sendMessage, return code %d\n", rc); + exit(-1); + } +} + + +void onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Connect failed, rc %d\n", response ? response->code : 0); + finished = 1; +} + + +void onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + printf("Successful connection\n"); + + opts.onSuccess = onSend; + opts.context = client; + + pubmsg.payload = PAYLOAD; + pubmsg.payloadlen = strlen(PAYLOAD); + pubmsg.qos = QOS; + pubmsg.retained = 0; + deliveredtoken = 0; + + if ((rc = MQTTAsync_sendMessage(client, TOPIC, &pubmsg, &opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start sendMessage, return code %d\n", rc); + exit(-1); + } +} + + +int main(int argc, char* argv[]) +{ + MQTTAsync client; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_token token; + int rc; + + MQTTAsync_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + MQTTAsync_setCallbacks(client, NULL, connlost, NULL, NULL); + + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + exit(-1); + } + + printf("Waiting for publication of %s\n" + "on topic %s for client with ClientID: %s\n", + PAYLOAD, TOPIC, CLIENTID); + while (!finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + MQTTAsync_destroy(&client); + return rc; +} + diff --git a/Sources/paho/src/samples/MQTTAsync_subscribe.c b/Sources/paho/src/samples/MQTTAsync_subscribe.c new file mode 100644 index 0000000..02acfaf --- /dev/null +++ b/Sources/paho/src/samples/MQTTAsync_subscribe.c @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial contribution + *******************************************************************************/ + +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTAsync.h" + +#if !defined(WIN32) +#include +#endif + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientSub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +volatile MQTTAsync_token deliveredtoken; + +int disc_finished = 0; +int subscribed = 0; +int finished = 0; + +void connlost(void *context, char *cause) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + int rc; + + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); + + printf("Reconnecting\n"); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + finished = 1; + } +} + + +int msgarrvd(void *context, char *topicName, int topicLen, MQTTAsync_message *message) +{ + int i; + char* payloadptr; + + printf("Message arrived\n"); + printf(" topic: %s\n", topicName); + printf(" message: "); + + payloadptr = message->payload; + for(i=0; ipayloadlen; i++) + { + putchar(*payloadptr++); + } + putchar('\n'); + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + return 1; +} + + +void onDisconnect(void* context, MQTTAsync_successData* response) +{ + printf("Successful disconnection\n"); + disc_finished = 1; +} + + +void onSubscribe(void* context, MQTTAsync_successData* response) +{ + printf("Subscribe succeeded\n"); + subscribed = 1; +} + +void onSubscribeFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Subscribe failed, rc %d\n", response ? response->code : 0); + finished = 1; +} + + +void onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Connect failed, rc %d\n", response ? response->code : 0); + finished = 1; +} + + +void onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + printf("Successful connection\n"); + + printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n" + "Press Q to quit\n\n", TOPIC, CLIENTID, QOS); + opts.onSuccess = onSubscribe; + opts.onFailure = onSubscribeFailure; + opts.context = client; + + deliveredtoken = 0; + + if ((rc = MQTTAsync_subscribe(client, TOPIC, QOS, &opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start subscribe, return code %d\n", rc); + exit(-1); + } +} + + +int main(int argc, char* argv[]) +{ + MQTTAsync client; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_disconnectOptions disc_opts = MQTTAsync_disconnectOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_token token; + int rc; + int ch; + + MQTTAsync_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + MQTTAsync_setCallbacks(client, NULL, connlost, msgarrvd, NULL); + + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + exit(-1); + } + + while (!subscribed) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + if (finished) + goto exit; + + do + { + ch = getchar(); + } while (ch!='Q' && ch != 'q'); + + disc_opts.onSuccess = onDisconnect; + if ((rc = MQTTAsync_disconnect(client, &disc_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start disconnect, return code %d\n", rc); + exit(-1); + } + while (!disc_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + +exit: + MQTTAsync_destroy(&client); + return rc; +} diff --git a/Sources/paho/src/samples/pahopub.c b/Sources/paho/src/samples/pahopub.c new file mode 100644 index 0000000..e2866ab --- /dev/null +++ b/Sources/paho/src/samples/pahopub.c @@ -0,0 +1,279 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial contribution + *******************************************************************************/ + + /* + stdin publisher + + compulsory parameters: + + --topic topic to publish on + + defaulted parameters: + + --host localhost + --port 1883 + --qos 0 + --delimiters \n + --clientid stdin_publisher + --maxdatalen 100 + + --userid none + --password none + +*/ + +#include "MQTTClient.h" +#include "MQTTClientPersistence.h" + +#include +#include +#include + + +#if defined(WIN32) +#include +#define sleep Sleep +#else +#include +#include +#endif + + +volatile int toStop = 0; + + +void usage() +{ + printf("MQTT stdin publisher\n"); + printf("Usage: stdinpub topicname , where options are:\n"); + printf(" --host (default is localhost)\n"); + printf(" --port (default is 1883)\n"); + printf(" --qos (default is 0)\n"); + printf(" --retained (default is off)\n"); + printf(" --delimiter (default is \\n)"); + printf(" --clientid (default is hostname+timestamp)"); + printf(" --maxdatalen 100\n"); + printf(" --username none\n"); + printf(" --password none\n"); + exit(-1); +} + + +void myconnect(MQTTClient* client, MQTTClient_connectOptions* opts) +{ + printf("Connecting\n"); + if (MQTTClient_connect(*client, opts) != 0) + { + printf("Failed to connect\n"); + exit(-1); + } + printf("Connected\n"); +} + + +void cfinish(int sig) +{ + signal(SIGINT, NULL); + toStop = 1; +} + + +struct +{ + char* clientid; + char* delimiter; + int maxdatalen; + int qos; + int retained; + char* username; + char* password; + char* host; + char* port; + int verbose; +} opts = +{ + "publisher", "\n", 100, 0, 0, NULL, NULL, "localhost", "1883", 0 +}; + +void getopts(int argc, char** argv); + +int messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m) +{ + /* not expecting any messages */ + return 1; +} + +int main(int argc, char** argv) +{ + MQTTClient client; + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + MQTTClient_SSLOptions ssl_opts = MQTTClient_SSLOptions_initializer; + char* topic = NULL; + char* buffer = NULL; + int rc = 0; + char url[100]; + + if (argc < 2) + usage(); + + getopts(argc, argv); + + sprintf(url, "%s:%s", opts.host, opts.port); + if (opts.verbose) + printf("URL is %s\n", url); + + topic = argv[1]; + printf("Using topic %s\n", topic); + + rc = MQTTClient_create(&client, url, opts.clientid, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + signal(SIGINT, cfinish); + signal(SIGTERM, cfinish); + + rc = MQTTClient_setCallbacks(client, NULL, NULL, messageArrived, NULL); + + conn_opts.keepAliveInterval = 10; + conn_opts.reliable = 0; + conn_opts.cleansession = 1; + conn_opts.username = opts.username; + conn_opts.password = opts.password; + ssl_opts.enableServerCertAuth = 0; + conn_opts.ssl = &ssl_opts; + + myconnect(&client, &conn_opts); + + buffer = malloc(opts.maxdatalen); + + while (!toStop) + { + int data_len = 0; + int delim_len = 0; + + delim_len = strlen(opts.delimiter); + do + { + buffer[data_len++] = getchar(); + if (data_len > delim_len) + { + //printf("comparing %s %s\n", opts.delimiter, &buffer[data_len - delim_len]); + if (strncmp(opts.delimiter, &buffer[data_len - delim_len], delim_len) == 0) + break; + } + } while (data_len < opts.maxdatalen); + + if (opts.verbose) + printf("Publishing data of length %d\n", data_len); + rc = MQTTClient_publish(client, topic, data_len, buffer, opts.qos, opts.retained, NULL); + if (rc != 0) + { + myconnect(&client, &conn_opts); + rc = MQTTClient_publish(client, topic, data_len, buffer, opts.qos, opts.retained, NULL); + } + if (opts.qos > 0) + MQTTClient_yield(); + } + + printf("Stopping\n"); + + free(buffer); + + MQTTClient_disconnect(client, 0); + + MQTTClient_destroy(&client); + + return 0; +} + +void getopts(int argc, char** argv) +{ + int count = 2; + + while (count < argc) + { + if (strcmp(argv[count], "--retained") == 0) + opts.retained = 1; + if (strcmp(argv[count], "--verbose") == 0) + opts.verbose = 1; + else if (strcmp(argv[count], "--qos") == 0) + { + if (++count < argc) + { + if (strcmp(argv[count], "0") == 0) + opts.qos = 0; + else if (strcmp(argv[count], "1") == 0) + opts.qos = 1; + else if (strcmp(argv[count], "2") == 0) + opts.qos = 2; + else + usage(); + } + else + usage(); + } + else if (strcmp(argv[count], "--host") == 0) + { + if (++count < argc) + opts.host = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--port") == 0) + { + if (++count < argc) + opts.port = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--clientid") == 0) + { + if (++count < argc) + opts.clientid = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--username") == 0) + { + if (++count < argc) + opts.username = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--password") == 0) + { + if (++count < argc) + opts.password = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--maxdatalen") == 0) + { + if (++count < argc) + opts.maxdatalen = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--delimiter") == 0) + { + if (++count < argc) + opts.delimiter = argv[count]; + else + usage(); + } + count++; + } + +} + diff --git a/Sources/paho/src/samples/pubasync.c b/Sources/paho/src/samples/pubasync.c new file mode 100644 index 0000000..0120667 --- /dev/null +++ b/Sources/paho/src/samples/pubasync.c @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial contribution + *******************************************************************************/ + +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTClient.h" + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientPub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +volatile MQTTClient_deliveryToken deliveredtoken; + +void delivered(void *context, MQTTClient_deliveryToken dt) +{ + printf("Message with token value %d delivery confirmed\n", dt); + deliveredtoken = dt; +} + +int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message) +{ + int i; + char* payloadptr; + + printf("Message arrived\n"); + printf(" topic: %s\n", topicName); + printf(" message: "); + + payloadptr = message->payload; + for(i=0; ipayloadlen; i++) + { + putchar(*payloadptr++); + } + putchar('\n'); + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + return 1; +} + +void connlost(void *context, char *cause) +{ + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); +} + +int main(int argc, char* argv[]) +{ + MQTTClient client; + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + MQTTClient_message pubmsg = MQTTClient_message_initializer; + MQTTClient_deliveryToken token; + int rc; + + MQTTClient_create(&client, ADDRESS, CLIENTID, + MQTTCLIENT_PERSISTENCE_NONE, NULL); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + + MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered); + + if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to connect, return code %d\n", rc); + exit(-1); + } + pubmsg.payload = PAYLOAD; + pubmsg.payloadlen = strlen(PAYLOAD); + pubmsg.qos = QOS; + pubmsg.retained = 0; + deliveredtoken = 0; + MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token); + printf("Waiting for publication of %s\n" + "on topic %s for client with ClientID: %s\n", + PAYLOAD, TOPIC, CLIENTID); + while(deliveredtoken != token); + MQTTClient_disconnect(client, 10000); + MQTTClient_destroy(&client); + return rc; +} + diff --git a/Sources/paho/src/samples/pubsync.c b/Sources/paho/src/samples/pubsync.c new file mode 100644 index 0000000..231190c --- /dev/null +++ b/Sources/paho/src/samples/pubsync.c @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial contribution + *******************************************************************************/ + +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTClient.h" + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientPub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +int main(int argc, char* argv[]) +{ + MQTTClient client; + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + MQTTClient_message pubmsg = MQTTClient_message_initializer; + MQTTClient_deliveryToken token; + int rc; + + MQTTClient_create(&client, ADDRESS, CLIENTID, + MQTTCLIENT_PERSISTENCE_NONE, NULL); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + + if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to connect, return code %d\n", rc); + exit(-1); + } + pubmsg.payload = PAYLOAD; + pubmsg.payloadlen = strlen(PAYLOAD); + pubmsg.qos = QOS; + pubmsg.retained = 0; + MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token); + printf("Waiting for up to %d seconds for publication of %s\n" + "on topic %s for client with ClientID: %s\n", + (int)(TIMEOUT/1000), PAYLOAD, TOPIC, CLIENTID); + rc = MQTTClient_waitForCompletion(client, token, TIMEOUT); + printf("Message with delivery token %d delivered\n", token); + MQTTClient_disconnect(client, 10000); + MQTTClient_destroy(&client); + return rc; +} diff --git a/Sources/paho/src/samples/stdinpub.c b/Sources/paho/src/samples/stdinpub.c new file mode 100644 index 0000000..9330fe0 --- /dev/null +++ b/Sources/paho/src/samples/stdinpub.c @@ -0,0 +1,275 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial contribution + *******************************************************************************/ + + /* + stdin publisher + + compulsory parameters: + + --topic topic to publish on + + defaulted parameters: + + --host localhost + --port 1883 + --qos 0 + --delimiters \n + --clientid stdin_publisher + --maxdatalen 100 + + --userid none + --password none + +*/ + +#include "MQTTClient.h" +#include "MQTTClientPersistence.h" + +#include +#include +#include + + +#if defined(WIN32) +#include +#define sleep Sleep +#else +#include +#include +#endif + + +volatile int toStop = 0; + + +void usage() +{ + printf("MQTT stdin publisher\n"); + printf("Usage: stdinpub topicname , where options are:\n"); + printf(" --host (default is localhost)\n"); + printf(" --port (default is 1883)\n"); + printf(" --qos (default is 0)\n"); + printf(" --retained (default is off)\n"); + printf(" --delimiter (default is \\n)"); + printf(" --clientid (default is hostname+timestamp)"); + printf(" --maxdatalen 100\n"); + printf(" --username none\n"); + printf(" --password none\n"); + exit(-1); +} + + +void myconnect(MQTTClient* client, MQTTClient_connectOptions* opts) +{ + printf("Connecting\n"); + if (MQTTClient_connect(*client, opts) != 0) + { + printf("Failed to connect\n"); + exit(-1); + } +} + + +void cfinish(int sig) +{ + signal(SIGINT, NULL); + toStop = 1; +} + + +struct +{ + char* clientid; + char* delimiter; + int maxdatalen; + int qos; + int retained; + char* username; + char* password; + char* host; + char* port; + int verbose; +} opts = +{ + "publisher", "\n", 100, 0, 0, NULL, NULL, "localhost", "1883", 0 +}; + +void getopts(int argc, char** argv); + +int messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m) +{ + /* not expecting any messages */ + return 1; +} + +int main(int argc, char** argv) +{ + MQTTClient client; + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + char* topic = NULL; + char* buffer = NULL; + int rc = 0; + char url[100]; + + if (argc < 2) + usage(); + + getopts(argc, argv); + + sprintf(url, "%s:%s", opts.host, opts.port); + if (opts.verbose) + printf("URL is %s\n", url); + + topic = argv[1]; + printf("Using topic %s\n", topic); + + rc = MQTTClient_create(&client, url, opts.clientid, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + signal(SIGINT, cfinish); + signal(SIGTERM, cfinish); + + rc = MQTTClient_setCallbacks(client, NULL, NULL, messageArrived, NULL); + + conn_opts.keepAliveInterval = 10; + conn_opts.reliable = 0; + conn_opts.cleansession = 1; + conn_opts.username = opts.username; + conn_opts.password = opts.password; + + myconnect(&client, &conn_opts); + + buffer = malloc(opts.maxdatalen); + + while (!toStop) + { + int data_len = 0; + int delim_len = 0; + + delim_len = strlen(opts.delimiter); + do + { + buffer[data_len++] = getchar(); + if (data_len > delim_len) + { + //printf("comparing %s %s\n", opts.delimiter, &buffer[data_len - delim_len]); + if (strncmp(opts.delimiter, &buffer[data_len - delim_len], delim_len) == 0) + break; + } + } while (data_len < opts.maxdatalen); + + if (opts.verbose) + printf("Publishing data of length %d\n", data_len); + rc = MQTTClient_publish(client, topic, data_len, buffer, opts.qos, opts.retained, NULL); + if (rc != 0) + { + myconnect(&client, &conn_opts); + rc = MQTTClient_publish(client, topic, data_len, buffer, opts.qos, opts.retained, NULL); + } + if (opts.qos > 0) + MQTTClient_yield(); + } + + printf("Stopping\n"); + + free(buffer); + + MQTTClient_disconnect(client, 0); + + MQTTClient_destroy(&client); + + return 0; +} + +void getopts(int argc, char** argv) +{ + int count = 2; + + while (count < argc) + { + if (strcmp(argv[count], "--retained") == 0) + opts.retained = 1; + if (strcmp(argv[count], "--verbose") == 0) + opts.verbose = 1; + else if (strcmp(argv[count], "--qos") == 0) + { + if (++count < argc) + { + if (strcmp(argv[count], "0") == 0) + opts.qos = 0; + else if (strcmp(argv[count], "1") == 0) + opts.qos = 1; + else if (strcmp(argv[count], "2") == 0) + opts.qos = 2; + else + usage(); + } + else + usage(); + } + else if (strcmp(argv[count], "--host") == 0) + { + if (++count < argc) + opts.host = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--port") == 0) + { + if (++count < argc) + opts.port = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--clientid") == 0) + { + if (++count < argc) + opts.clientid = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--username") == 0) + { + if (++count < argc) + opts.username = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--password") == 0) + { + if (++count < argc) + opts.password = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--maxdatalen") == 0) + { + if (++count < argc) + opts.maxdatalen = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--delimiter") == 0) + { + if (++count < argc) + opts.delimiter = argv[count]; + else + usage(); + } + count++; + } + +} + diff --git a/Sources/paho/src/samples/stdinpuba.c b/Sources/paho/src/samples/stdinpuba.c new file mode 100644 index 0000000..c6da70c --- /dev/null +++ b/Sources/paho/src/samples/stdinpuba.c @@ -0,0 +1,491 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial contribution + *******************************************************************************/ + + /* + stdin publisher + + compulsory parameters: + + --topic topic to publish on + + defaulted parameters: + + --host localhost + --port 1883 + --qos 0 + --delimiters \n + --clientid stdin_publisher + --maxdatalen 100 + + --userid none + --password none + +*/ + +#include "MQTTAsync.h" + +#include +#include +#include + + +#if defined(WIN32) +#include +#define sleep Sleep +#else +#include +#include +#endif + + +volatile int toStop = 0; + + +void usage() +{ + printf("MQTT stdin publisher\n"); + printf("Usage: stdinpub topicname , where options are:\n"); + printf(" --host (default is localhost)\n"); + printf(" --port (default is 1883)\n"); + printf(" --qos (default is 0)\n"); + printf(" --retained (default is off)\n"); + printf(" --delimiter (default is \\n)"); + printf(" --clientid (default is hostname+timestamp)"); + printf(" --maxdatalen 100\n"); + printf(" --username none\n"); + printf(" --password none\n"); + exit(-1); +} + + + +void cfinish(int sig) +{ + signal(SIGINT, NULL); + toStop = 1; +} + + +struct +{ + char* clientid; + char* delimiter; + int maxdatalen; + int qos; + int retained; + char* username; + char* password; + char* host; + char* port; + int verbose; +} opts = +{ + "publisher", "\n", 100, 0, 0, NULL, NULL, "localhost", "1883", 0 +}; + +void getopts(int argc, char** argv); + +int messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* m) +{ + /* not expecting any messages */ + return 1; +} + + +static int disconnected = 0; + +void onDisconnect(void* context, MQTTAsync_successData* response) +{ + disconnected = 1; +} + + +static int connected = 0; + +void onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Connect failed, rc %d\n", response ? -1 : response->code); + connected = -1; +} + + +void onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + int rc; + + printf("Connected\n"); + connected = 1; +} + + +void myconnect(MQTTAsync* client) +{ + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_SSLOptions ssl_opts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + + printf("Connecting\n"); + conn_opts.keepAliveInterval = 10; + conn_opts.cleansession = 1; + conn_opts.username = opts.username; + conn_opts.password = opts.password; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + ssl_opts.enableServerCertAuth = 0; + conn_opts.ssl = &ssl_opts; + connected = 0; + if ((rc = MQTTAsync_connect(*client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + exit(-1); + } + while (connected == 0) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif +} + + +static int published = 0; + +void onPublishFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Publish failed, rc %d\n", response ? -1 : response->code); + published = -1; +} + + +void onPublish(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + + published = 1; +} + + +void connectionLost(void* context, char* cause) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_SSLOptions ssl_opts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + + printf("Connecting\n"); + conn_opts.keepAliveInterval = 10; + conn_opts.cleansession = 1; + conn_opts.username = opts.username; + conn_opts.password = opts.password; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + ssl_opts.enableServerCertAuth = 0; + conn_opts.ssl = &ssl_opts; + connected = 0; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + exit(-1); + } +} + +#if !defined(_WINDOWS) + #include + #include + #include + #include +#else +#include +#include +#define MAXHOSTNAMELEN 256 +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#define setenv(a, b, c) _putenv_s(a, b) +#endif + + +#if !defined(SOCKET_ERROR) +#define SOCKET_ERROR -1 +#endif + + +typedef struct +{ + int socket; + time_t lastContact; +#if defined(OPENSSL) + SSL* ssl; + SSL_CTX* ctx; +#endif +} networkHandles; + + +typedef struct +{ + char* clientID; /**< the string id of the client */ + char* username; /**< MQTT v3.1 user name */ + char* password; /**< MQTT v3.1 password */ + unsigned int cleansession : 1; /**< MQTT clean session 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; + int connect_state : 4; + networkHandles net; +/* ... */ +} Clients; + + +typedef struct MQTTAsync_struct +{ + char* serverURI; + int ssl; + Clients* c; + + /* "Global", to the client, callback definitions */ + MQTTAsync_connectionLost* cl; + MQTTAsync_messageArrived* ma; + MQTTAsync_deliveryComplete* dc; + void* context; /* the context to be associated with the main callbacks*/ + #if 0 + MQTTAsync_command connect; /* Connect operation properties */ + MQTTAsync_command disconnect; /* Disconnect operation properties */ + MQTTAsync_command* pending_write; /* Is there a socket write pending? */ + + List* responses; + unsigned int command_seqno; + + MQTTPacket* pack; +#endif +} MQTTAsyncs; + +int test6_socket_error(char* aString, int sock) +{ +#if defined(WIN32) + int errno; +#endif + +#if defined(WIN32) + errno = WSAGetLastError(); +#endif + if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && errno != EWOULDBLOCK) + { + if (strcmp(aString, "shutdown") != 0 || (errno != ENOTCONN && errno != ECONNRESET)) + printf("Socket error %d in %s for socket %d", errno, aString, sock); + } + return errno; +} + + +int test6_socket_close(int socket) +{ + int rc; + +#if defined(WIN32) + if (shutdown(socket, SD_BOTH) == SOCKET_ERROR) + test6_socket_error("shutdown", socket); + if ((rc = closesocket(socket)) == SOCKET_ERROR) + test6_socket_error("close", socket); +#else + if (shutdown(socket, SHUT_RDWR) == SOCKET_ERROR) + test6_socket_error("shutdown", socket); + if ((rc = close(socket)) == SOCKET_ERROR) + test6_socket_error("close", socket); +#endif + return rc; +} + + +int main(int argc, char** argv) +{ + MQTTAsync_disconnectOptions disc_opts = MQTTAsync_disconnectOptions_initializer; + MQTTAsync_responseOptions pub_opts = MQTTAsync_responseOptions_initializer; + MQTTAsync client; + char* topic = NULL; + char* buffer = NULL; + int rc = 0; + char url[100]; + + if (argc < 2) + usage(); + + getopts(argc, argv); + + sprintf(url, "%s:%s", opts.host, opts.port); + if (opts.verbose) + printf("URL is %s\n", url); + + topic = argv[1]; + printf("Using topic %s\n", topic); + + rc = MQTTAsync_create(&client, url, opts.clientid, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + signal(SIGINT, cfinish); + signal(SIGTERM, cfinish); + + rc = MQTTAsync_setCallbacks(client, client, connectionLost, messageArrived, NULL); + + myconnect(&client); + + buffer = malloc(opts.maxdatalen); + + while (!toStop) + { + int data_len = 0; + int delim_len = 0; + + delim_len = strlen(opts.delimiter); + do + { + buffer[data_len++] = getchar(); + if (data_len > delim_len) + { + //printf("comparing %s %s\n", opts.delimiter, &buffer[data_len - delim_len]); + if (strncmp(opts.delimiter, &buffer[data_len - delim_len], delim_len) == 0) + break; + } + } while (data_len < opts.maxdatalen); + + if (opts.verbose) + printf("Publishing data of length %d\n", data_len); + pub_opts.onSuccess = onPublish; + pub_opts.onFailure = onPublishFailure; + do + { + published = 0; + rc = MQTTAsync_send(client, topic, data_len, buffer, opts.qos, opts.retained, &pub_opts); + while (published == 0) + #if defined(WIN32) + Sleep(100); + #else + usleep(1000L); + #endif + if (published == -1) + myconnect(&client); + test6_socket_close(((MQTTAsyncs*)client)->c->net.socket); + } + while (published != 1); + } + + printf("Stopping\n"); + + free(buffer); + + disc_opts.onSuccess = onDisconnect; + if ((rc = MQTTAsync_disconnect(client, &disc_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start disconnect, return code %d\n", rc); + exit(-1); + } + + while (!disconnected) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + MQTTAsync_destroy(&client); + + return 0; +} + +void getopts(int argc, char** argv) +{ + int count = 2; + + while (count < argc) + { + if (strcmp(argv[count], "--retained") == 0) + opts.retained = 1; + if (strcmp(argv[count], "--verbose") == 0) + opts.verbose = 1; + else if (strcmp(argv[count], "--qos") == 0) + { + if (++count < argc) + { + if (strcmp(argv[count], "0") == 0) + opts.qos = 0; + else if (strcmp(argv[count], "1") == 0) + opts.qos = 1; + else if (strcmp(argv[count], "2") == 0) + opts.qos = 2; + else + usage(); + } + else + usage(); + } + else if (strcmp(argv[count], "--host") == 0) + { + if (++count < argc) + opts.host = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--port") == 0) + { + if (++count < argc) + opts.port = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--clientid") == 0) + { + if (++count < argc) + opts.clientid = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--username") == 0) + { + if (++count < argc) + opts.username = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--password") == 0) + { + if (++count < argc) + opts.password = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--maxdatalen") == 0) + { + if (++count < argc) + opts.maxdatalen = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--delimiter") == 0) + { + if (++count < argc) + opts.delimiter = argv[count]; + else + usage(); + } + count++; + } + +} + diff --git a/Sources/paho/src/samples/stdoutsub.c b/Sources/paho/src/samples/stdoutsub.c new file mode 100644 index 0000000..b6477b6 --- /dev/null +++ b/Sources/paho/src/samples/stdoutsub.c @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial contribution + * Ian Craggs - change delimiter option from char to string + *******************************************************************************/ + +/* + + stdout subscriber + + compulsory parameters: + + --topic topic to subscribe to + + defaulted parameters: + + --host localhost + --port 1883 + --qos 2 + --delimiter \n + --clientid stdout_subscriber + + --userid none + --password none + +*/ +#include "MQTTClient.h" +#include "MQTTClientPersistence.h" + +#include +#include +#include + + +#if defined(WIN32) +#include +#define sleep Sleep +#else +#include +#include +#endif + + +volatile int toStop = 0; + + +void usage() +{ + printf("MQTT stdout subscriber\n"); + printf("Usage: stdoutsub topicname , where options are:\n"); + printf(" --host (default is localhost)\n"); + printf(" --port (default is 1883)\n"); + printf(" --qos (default is 2)\n"); + printf(" --delimiter (default is \\n)\n"); + printf(" --clientid (default is hostname+timestamp)\n"); + printf(" --username none\n"); + printf(" --password none\n"); + printf(" --showtopics (default is on if the topic has a wildcard, else off)\n"); + exit(-1); +} + + +void myconnect(MQTTClient* client, MQTTClient_connectOptions* opts) +{ + int rc = 0; + if ((rc = MQTTClient_connect(*client, opts)) != 0) + { + printf("Failed to connect, return code %d\n", rc); + exit(-1); + } +} + + +void cfinish(int sig) +{ + signal(SIGINT, NULL); + toStop = 1; +} + + +struct opts_struct +{ + char* clientid; + int nodelimiter; + char* delimiter; + int qos; + char* username; + char* password; + char* host; + char* port; + int showtopics; +} opts = +{ + "stdout-subscriber", 0, "\n", 2, NULL, NULL, "localhost", "1883", 0 +}; + +void getopts(int argc, char** argv); + +int main(int argc, char** argv) +{ + MQTTClient client; + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + char* topic = NULL; + int rc = 0; + char url[100]; + + if (argc < 2) + usage(); + + topic = argv[1]; + + if (strchr(topic, '#') || strchr(topic, '+')) + opts.showtopics = 1; + if (opts.showtopics) + printf("topic is %s\n", topic); + + getopts(argc, argv); + sprintf(url, "%s:%s", opts.host, opts.port); + + rc = MQTTClient_create(&client, url, opts.clientid, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + signal(SIGINT, cfinish); + signal(SIGTERM, cfinish); + + conn_opts.keepAliveInterval = 10; + conn_opts.reliable = 0; + conn_opts.cleansession = 1; + conn_opts.username = opts.username; + conn_opts.password = opts.password; + + myconnect(&client, &conn_opts); + + rc = MQTTClient_subscribe(client, topic, opts.qos); + + while (!toStop) + { + char* topicName = NULL; + int topicLen; + MQTTClient_message* message = NULL; + + rc = MQTTClient_receive(client, &topicName, &topicLen, &message, 1000); + if (message) + { + if (opts.showtopics) + printf("%s\t", topicName); + if (opts.nodelimiter) + printf("%.*s", message->payloadlen, (char*)message->payload); + else + printf("%.*s%s", message->payloadlen, (char*)message->payload, opts.delimiter); + fflush(stdout); + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + } + if (rc != 0) + myconnect(&client, &conn_opts); + } + + printf("Stopping\n"); + + MQTTClient_disconnect(client, 0); + + MQTTClient_destroy(&client); + + return 0; +} + +void getopts(int argc, char** argv) +{ + int count = 2; + + while (count < argc) + { + if (strcmp(argv[count], "--qos") == 0) + { + if (++count < argc) + { + if (strcmp(argv[count], "0") == 0) + opts.qos = 0; + else if (strcmp(argv[count], "1") == 0) + opts.qos = 1; + else if (strcmp(argv[count], "2") == 0) + opts.qos = 2; + else + usage(); + } + else + usage(); + } + else if (strcmp(argv[count], "--host") == 0) + { + if (++count < argc) + opts.host = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--port") == 0) + { + if (++count < argc) + opts.port = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--clientid") == 0) + { + if (++count < argc) + opts.clientid = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--username") == 0) + { + if (++count < argc) + opts.username = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--password") == 0) + { + if (++count < argc) + opts.password = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--delimiter") == 0) + { + if (++count < argc) + opts.delimiter = argv[count]; + else + opts.nodelimiter = 1; + } + else if (strcmp(argv[count], "--showtopics") == 0) + { + if (++count < argc) + { + if (strcmp(argv[count], "on") == 0) + opts.showtopics = 1; + else if (strcmp(argv[count], "off") == 0) + opts.showtopics = 0; + else + usage(); + } + else + usage(); + } + count++; + } + +} diff --git a/Sources/paho/src/samples/stdoutsuba.c b/Sources/paho/src/samples/stdoutsuba.c new file mode 100644 index 0000000..37eb426 --- /dev/null +++ b/Sources/paho/src/samples/stdoutsuba.c @@ -0,0 +1,349 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial contribution + * Ian Craggs - fix for bug 413429 - connectionLost not called + *******************************************************************************/ + +/* + + stdout subscriber for the asynchronous client + + compulsory parameters: + + --topic topic to subscribe to + + defaulted parameters: + + --host localhost + --port 1883 + --qos 2 + --delimiter \n + --clientid stdout_subscriber + + --userid none + --password none + +*/ + +#include "MQTTAsync.h" +#include "MQTTClientPersistence.h" + +#include +#include +#include + + +#if defined(WIN32) +#include +#define sleep Sleep +#else +#include +#include +#include +#endif + + +volatile int finished = 0; +char* topic = NULL; +int subscribed = 0; +int disconnected = 0; + + +void cfinish(int sig) +{ + signal(SIGINT, NULL); + finished = 1; +} + + +struct +{ + char* clientid; + int nodelimiter; + char delimiter; + int qos; + char* username; + char* password; + char* host; + char* port; + int showtopics; +} opts = +{ + "stdout-subscriber", 1, '\n', 2, NULL, NULL, "localhost", "1883", 0 +}; + + +void usage() +{ + printf("MQTT stdout subscriber\n"); + printf("Usage: stdoutsub topicname , where options are:\n"); + printf(" --host (default is localhost)\n"); + printf(" --port (default is 1883)\n"); + printf(" --qos (default is 2)\n"); + printf(" --delimiter (default is no delimiter)\n"); + printf(" --clientid (default is hostname+timestamp)\n"); + printf(" --username none\n"); + printf(" --password none\n"); + printf(" --showtopics (default is on if the topic has a wildcard, else off)\n"); + exit(-1); +} + + +void getopts(int argc, char** argv) +{ + int count = 2; + + while (count < argc) + { + if (strcmp(argv[count], "--qos") == 0) + { + if (++count < argc) + { + if (strcmp(argv[count], "0") == 0) + opts.qos = 0; + else if (strcmp(argv[count], "1") == 0) + opts.qos = 1; + else if (strcmp(argv[count], "2") == 0) + opts.qos = 2; + else + usage(); + } + else + usage(); + } + else if (strcmp(argv[count], "--host") == 0) + { + if (++count < argc) + opts.host = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--port") == 0) + { + if (++count < argc) + opts.port = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--clientid") == 0) + { + if (++count < argc) + opts.clientid = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--username") == 0) + { + if (++count < argc) + opts.username = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--password") == 0) + { + if (++count < argc) + opts.password = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--delimiter") == 0) + { + if (++count < argc) + { + if (strcmp("newline", argv[count]) == 0) + opts.delimiter = '\n'; + else + opts.delimiter = argv[count][0]; + opts.nodelimiter = 0; + } + else + usage(); + } + else if (strcmp(argv[count], "--showtopics") == 0) + { + if (++count < argc) + { + if (strcmp(argv[count], "on") == 0) + opts.showtopics = 1; + else if (strcmp(argv[count], "off") == 0) + opts.showtopics = 0; + else + usage(); + } + else + usage(); + } + count++; + } + +} + + +int messageArrived(void *context, char *topicName, int topicLen, MQTTAsync_message *message) +{ + if (opts.showtopics) + printf("%s\t", topicName); + if (opts.nodelimiter) + printf("%.*s", message->payloadlen, (char*)message->payload); + else + printf("%.*s%c", message->payloadlen, (char*)message->payload, opts.delimiter); + fflush(stdout); + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + return 1; +} + + +void onDisconnect(void* context, MQTTAsync_successData* response) +{ + disconnected = 1; +} + + +void onSubscribe(void* context, MQTTAsync_successData* response) +{ + subscribed = 1; +} + + +void onSubscribeFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Subscribe failed, rc %d\n", response->code); + finished = 1; +} + + +void onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + printf("Connect failed, rc %d\n", response->code); + finished = 1; +} + + +void onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_responseOptions ropts = MQTTAsync_responseOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + if (opts.showtopics) + printf("Subscribing to topic %s with client %s at QoS %d\n", topic, opts.clientid, opts.qos); + + ropts.onSuccess = onSubscribe; + ropts.onFailure = onSubscribeFailure; + ropts.context = client; + if ((rc = MQTTAsync_subscribe(client, topic, opts.qos, &ropts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start subscribe, return code %d\n", rc); + finished = 1; + } +} + + +MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + + +void connectionLost(void *context, char *cause) +{ + MQTTAsync client = (MQTTAsync)context; + int rc; + + printf("connectionLost called\n"); + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start reconnect, return code %d\n", rc); + finished = 1; + } +} + + +int main(int argc, char** argv) +{ + MQTTAsync client; + MQTTAsync_disconnectOptions disc_opts = MQTTAsync_disconnectOptions_initializer; + int rc = 0; + char url[100]; + + if (argc < 2) + usage(); + + topic = argv[1]; + + if (strchr(topic, '#') || strchr(topic, '+')) + opts.showtopics = 1; + if (opts.showtopics) + printf("topic is %s\n", topic); + + getopts(argc, argv); + sprintf(url, "%s:%s", opts.host, opts.port); + + rc = MQTTAsync_create(&client, url, opts.clientid, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + MQTTAsync_setCallbacks(client, client, connectionLost, messageArrived, NULL); + + signal(SIGINT, cfinish); + signal(SIGTERM, cfinish); + + conn_opts.keepAliveInterval = 10; + conn_opts.cleansession = 1; + conn_opts.username = opts.username; + conn_opts.password = opts.password; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + exit(-1); + } + + while (!subscribed) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + if (finished) + goto exit; + + while (!finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + disc_opts.onSuccess = onDisconnect; + if ((rc = MQTTAsync_disconnect(client, &disc_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start disconnect, return code %d\n", rc); + exit(-1); + } + + while (!disconnected) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + +exit: + MQTTAsync_destroy(&client); + + return 0; +} + + diff --git a/Sources/paho/src/samples/subasync.c b/Sources/paho/src/samples/subasync.c new file mode 100644 index 0000000..eaeda93 --- /dev/null +++ b/Sources/paho/src/samples/subasync.c @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial contribution + *******************************************************************************/ + +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "MQTTClient.h" + +#define ADDRESS "tcp://localhost:1883" +#define CLIENTID "ExampleClientSub" +#define TOPIC "MQTT Examples" +#define PAYLOAD "Hello World!" +#define QOS 1 +#define TIMEOUT 10000L + +volatile MQTTClient_deliveryToken deliveredtoken; + +void delivered(void *context, MQTTClient_deliveryToken dt) +{ + printf("Message with token value %d delivery confirmed\n", dt); + deliveredtoken = dt; +} + +int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message) +{ + int i; + char* payloadptr; + + printf("Message arrived\n"); + printf(" topic: %s\n", topicName); + printf(" message: "); + + payloadptr = message->payload; + for(i=0; ipayloadlen; i++) + { + putchar(*payloadptr++); + } + putchar('\n'); + MQTTClient_freeMessage(&message); + MQTTClient_free(topicName); + return 1; +} + +void connlost(void *context, char *cause) +{ + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); +} + +int main(int argc, char* argv[]) +{ + MQTTClient client; + MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer; + int rc; + int ch; + + MQTTClient_create(&client, ADDRESS, CLIENTID, + MQTTCLIENT_PERSISTENCE_NONE, NULL); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + + MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered); + + if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) + { + printf("Failed to connect, return code %d\n", rc); + exit(-1); + } + printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n" + "Press Q to quit\n\n", TOPIC, CLIENTID, QOS); + MQTTClient_subscribe(client, TOPIC, QOS); + + do + { + ch = getchar(); + } while(ch!='Q' && ch != 'q'); + + MQTTClient_disconnect(client, 10000); + MQTTClient_destroy(&client); + return rc; +} diff --git a/Sources/paho/src/utf-8.c b/Sources/paho/src/utf-8.c new file mode 100644 index 0000000..04097de --- /dev/null +++ b/Sources/paho/src/utf-8.c @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2009, 2013 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + + +/** + * @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 +#include + +#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} } }, +}; + + +/** + * 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" + */ +const char* UTF8_char_validate(int len, const char* data) +{ + int good = 0; + int charlen = 2; + int i, j; + const char *rc = NULL; + + FUNC_ENTRY; + /* 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: + FUNC_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) + { + rc = 1; + goto exit; + } + curdata = UTF8_char_validate(len, data); + while (curdata && (curdata < data + len)) + curdata = UTF8_char_validate(len, 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; + rc = UTF8_validate(strlen(string), string); + FUNC_EXIT_RC(rc); + return rc; +} + + + +#if defined(UNIT_TESTS) +#include + +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"); + + return 0; +} /* End of main function*/ + +#endif + diff --git a/Sources/paho/test/MQTTTest_v2.c b/Sources/paho/test/MQTTTest_v2.c new file mode 100644 index 0000000..7fd1527 --- /dev/null +++ b/Sources/paho/test/MQTTTest_v2.c @@ -0,0 +1,416 @@ +/* + * ----------------------------------------------------------------- + * IBM Websphere MQ Telemetry + * MQTTV3ASample MQTT v3 Asynchronous Client application + * + * Version: @(#) MQMBID sn=p000-L130522.1 su=_M3QBMsMbEeK31Ln-reX3cg pn=com.ibm.mq.mqxr.listener/SDK/clients/c/samples/MQTTV3ASample.c + * + * + * Licensed Materials - Property of IBM + * + * 5724-H72, + * + * (C) Copyright IBM Corp. 2010, 2012 All Rights Reserved. + * + * US Government Users Restricted Rights - Use, duplication or + * disclosure restricted by GSA ADP Schedule Contract with + * IBM Corp. + * + * ----------------------------------------------------------------- + */ + +/** + * This sample application demonstrates basic usage + * of the MQTT v3 Asynchronous Client api. + * + * It can be run in one of two modes: + * - as a publisher, sending a single message to a topic on the server + * - as a subscriber, listening for messages from the server + * + */ + + +#include +#include +#include +#include +#include +#include + +#if defined(WIN32) +#include +#define sleep Sleep +#else +#include +#include +#endif + + volatile int toStop = 0; + volatile int finished = 0; + volatile int connected = 0; + volatile int quietMode = 0; + volatile int sent = 0; + volatile int delivery = 0; + volatile MQTTAsync_token deliveredtoken; + static char clientId[24]; + struct Options + { + char* action; + char* topic; + char* message; + int qos; + char* broker; + char* port; + int message_count; + } options = + { + "publish", + NULL, + "2", + 2, + "localhost", + "1883", + 100 + }; + + void printHelp() + { + printf("Syntax:\n\n"); + printf(" MQTTV3ASample [-h] [-a publish|subscribe] [-t ] [-m ]\n"); + printf(" [-s 0|1|2] [-b ] [-p ] \n\n"); + printf(" -h Print this help text and quit\n"); + printf(" -q Quiet mode (default is false)\n"); + printf(" -a Perform the relevant action (default is publish)\n"); + printf(" -t Publish/subscribe to instead of the default\n"); + printf(" (publish: \"MQTTV3ASample/C/v3\", subscribe: \"MQTTV3ASample/#\")\n"); + printf(" -m Use this message instead of the default (\"Message from MQTTv3 C asynchronous client\")\n"); + printf(" -s Use this QoS instead of the default (2)\n"); + printf(" -b Use this name/IP address instead of the default (localhost)\n"); + printf(" -p Use this port instead of the default (1883)\n"); + printf("\nDelimit strings containing spaces with \"\"\n"); + printf("\nPublishers transmit a single message then disconnect from the broker.\n"); + printf("Subscribers remain connected to the broker and receive appropriate messages\n"); + printf("until Control-C (^C) is received from the keyboard.\n\n"); + } + + + void handleSignal(int sig) + { + toStop = 1; + } + + int messageArrived(void *context, char *topicName, int topicLen, MQTTAsync_message *message) + { + int i; + char* payloadptr; + if((sent++ % 1000) == 0) + printf("%d messages received\n", sent++); + //printf("Message arrived\n"); + //printf(" topic: %s\n", topicName); + //printf(" message: "); + + //payloadptr = message->payload; + //for(i=0; ipayloadlen; i++) + //{ + // putchar(*payloadptr++); + //} + //putchar('\n'); + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + return 1; + } + + void onSubscribe(void* context, MQTTAsync_successData* response) + { + printf("Subscribe succeeded\n"); + } + + void onSubscribeFailure(void* context, MQTTAsync_failureData* response) + { + printf("Subscribe failed\n"); + finished = 1; + } + + void onDisconnect(void* context, MQTTAsync_successData* response) + { + printf("Successful disconnection\n"); + finished = 1; + } + + + void onSendFailure(void* context, MQTTAsync_failureData* response) + { + printf("onSendFailure: message with token value %d delivery failed\n", response->token); + } + + + + void onSend(void* context, MQTTAsync_successData* response) + { + static last_send = 0; + + if (response->token - last_send != 1) + printf("Error in onSend, token value %d, last_send %d\n", response->token, last_send); + + last_send++; + + if ((response->token % 1000) == 0) + printf("onSend: message with token value %d delivery confirmed\n", response->token); + } + + void deliveryComplete(void* context, MQTTAsync_token token) + { + sent++; + if ((sent % 1000) == 0) + printf("deliveryComplete: message with token value %d delivery confirmed\n", token); + if (sent != token) + printf("Error, sent %d != token %d\n", sent, token); + if (sent == options.message_count) + toStop = 1; + } + + void onConnectFailure(void* context, MQTTAsync_failureData* response) + { + printf("Connect failed\n"); + finished = 1; + } + + + void onConnect(void* context, MQTTAsync_successData* response) + { + printf("Connected\n"); + connected=1; + } + + void connectionLost(void *context, char *cause) + { + MQTTAsync client = (MQTTAsync)context; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + int rc; + + printf("\nConnection lost\n"); + printf(" cause: %s\n", cause); + + printf("Reconnecting\n"); + conn_opts.keepAliveInterval = 20; + conn_opts.cleansession = 1; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + conn_opts.retryInterval = 1000; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + finished = 1; + } + } + + +void handleTrace(enum MQTTASYNC_TRACE_LEVELS level, char* message) +{ + printf("%s\n", message); +} + + +/** + * The main entry point of the sample. + * + * This method handles parsing the arguments specified on the + * command-line before performing the specified action. + */ + int main(int argc, char** argv) + { + int rc = 0; + int ch; + char url[256]; + + // Default settings: + int i=0; + + MQTTAsync client; + MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_token token; + + signal(SIGINT, handleSignal); + signal(SIGTERM, handleSignal); + + quietMode = 0; + // Parse the arguments - + for (i=1; i 2) + { + printf("Invalid QoS: %d\n", options.qos); + printHelp(); + return 255; + } + if (options.topic == NULL || ( options.topic != NULL && strlen(options.topic) == 0) ) + { + // Set the default topic according to the specified action + if (strcmp(options.action, "publish") == 0) + options.topic = "MQTTV3ASample/C/v3"; + else + options.topic = "MQTTV3ASample/#"; + } + + // Construct the full broker URL and clientId + sprintf(url, "tcp://%s:%s", options.broker, options.port); + sprintf(clientId, "SampleCV3A_%s", options.action); + + + MQTTAsync_create(&client, url, clientId, MQTTCLIENT_PERSISTENCE_NONE, NULL); + + MQTTAsync_setTraceCallback(handleTrace); + MQTTAsync_setTraceLevel(MQTTASYNC_TRACE_ERROR); + + MQTTAsync_setCallbacks(client, client, connectionLost, messageArrived, deliveryComplete); + + conn_opts.cleansession = 0; + conn_opts.onSuccess = onConnect; + conn_opts.onFailure = onConnectFailure; + conn_opts.context = client; + conn_opts.keepAliveInterval = 0; + conn_opts.retryInterval = 0; + //conn_opts.maxInflight= 30; + + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start connect, return code %d\n", rc); + goto exit; + } + printf("Waiting for connect\n"); + while (connected == 0 && finished == 0 && toStop == 0) { + printf("Waiting for connect: %d %d %d\n", connected, finished, toStop); + usleep(10000L); + } + + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + printf("Waiting for connect: %d %d %d\n", connected, finished, toStop); + + printf("Successful connection\n"); + + if (connected == 1 && strcmp(options.action, "publish") == 0) + { + unsigned long i; + struct timeval tv; + gettimeofday(&tv,NULL); + printf("start seconds : %ld\n",tv.tv_sec); + for (i = 0; i < options.message_count; i++) + { + opts.onSuccess = onSend; + opts.onFailure = onSendFailure; + opts.context = client; + pubmsg.payload = options.message; + pubmsg.payloadlen = strlen(options.message); + pubmsg.qos = options.qos; + pubmsg.retained = 0; + deliveredtoken = 0; + usleep(100); + + if ((rc = MQTTAsync_sendMessage(client, options.topic, &pubmsg, &opts)) + != MQTTASYNC_SUCCESS) + { + printf("Failed to start sendMessage, return code %d\n", rc); + exit(-1); + } + } + + gettimeofday(&tv,NULL); + + printf("end seconds : %ld\n",tv.tv_sec); + } else if (strcmp(options.action, "subscribe") == 0) { + opts.onSuccess = onSubscribe; + opts.onFailure = onSubscribeFailure; + opts.context = client; + if ((rc = MQTTAsync_subscribe(client, options.topic, options.qos, &opts)) != MQTTASYNC_SUCCESS) { + printf("Failed to subscribe, return code %d\n", rc); + exit(-1); + } + } + + while (!finished) + { +#if defined(WIN32) + Sleep(100); +#else + usleep(1000L); +#endif + if (toStop == 1) + { + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + + opts.onSuccess = onDisconnect; + opts.context = client; + printf("Entering disconnection phase\n"); + if ((rc = MQTTAsync_disconnect(client, &opts)) != MQTTASYNC_SUCCESS) + { + printf("Failed to start disconnect, return code %d\n", rc); + exit(-1); + } + toStop = 0; + } + } + + exit: + printf("calling destroy\n"); + MQTTAsync_destroy(&client); + return rc; + } diff --git a/Sources/paho/test/ssl/all-ca.crt b/Sources/paho/test/ssl/all-ca.crt new file mode 100644 index 0000000..696a5f7 --- /dev/null +++ b/Sources/paho/test/ssl/all-ca.crt @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +MIICnTCCAgagAwIBAgIBATANBgkqhkiG9w0BAQUFADByMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxGjAYBgNVBAoMEU1v +c3F1aXR0byBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdSb290 +IENBMB4XDTEzMDcyNDIzNTExNloXDTE4MDcyMzIzNTExNlowZTELMAkGA1UEBhMC +R0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxGjAYBgNVBAoMEU1vc3F1aXR0byBQcm9q +ZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1Sir65IBDbm7bI4lHDakEoN0Y6DUg/Spz +FoPe4cbixwS1pg3514X2srv9w03Kzp/obDOs/JzbqcPfOBAuiiPlMm1hw9az1B7N +9lg/2DKHL/7Oq8IZUKsFjhbFAMjQd/PAZCkBO1FS1Y0jZes4/1fzlqq4rYItTie+ +YX8tTDc/7QIDAQABo1AwTjAdBgNVHQ4EFgQU5W621SksDwZxSpsZsFkm6/QuAQYw +HwYDVR0jBBgwFoAUq92KK7UYT6V7F1mySt6+LWTPzr4wDAYDVR0TBAUwAwEB/zAN +BgkqhkiG9w0BAQUFAAOBgQBMcwdjElUOhXqoqlX1DWik58X73GHxjE52jao4BHRZ +S+PpwOOjfnq4CfIXF1cMp95cK+Eh566lEJf2udlV1waKew578T86+UsRO/T/a0bb +3FuuZH3TXnO+OjNMTWKMZ0iLQtPwNN4m9lszECrSgJ53yCIB6iq/zfXVSop7XFzd +VQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICqDCCAhGgAwIBAgIJAKrzwmdXIUxsMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEVMBMG +A1UECgwMUGFobyBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdS +b290IENBMB4XDTEzMDcyOTE5MjEyOVoXDTIzMDcyNzE5MjEyOVowbTELMAkGA1UE +BhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNVBAcMBURlcmJ5MRUwEwYD +VQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxEDAOBgNVBAMMB1Jv +b3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKbPzEEWCKsjjwjJ787u +Q32k5EdqoDddMEjSVbZNSNEwUew1L7O8NTbmtCEeVFQjOLAdmdiF3rQbXHV+Zew0 +jt2g4vtPpl1GOG6jA/6YznKAyQdvGCdYfGZUN2tN+mbtVxWqkHZitQDQGaSHnx24 +NX649La2uyFy+7l9o8++xPONAgMBAAGjUDBOMB0GA1UdDgQWBBRKK2nWMR2jaOhG +b/tL8462jVEOvzAfBgNVHSMEGDAWgBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAEd+gW86/W+fisz5PFHAeEw7zn9q +dzLHm7+QZgNLZ9h7/ZbhObRUFMRtU2xm4amyh85h7hUE5R2E2uW2OXumic7/D4ZD +6unjr4m5jwVWDTqTUYIcNSriyoDWAVlPfOWaU5NyUhqS1DM28tvOWVHVLCxmVcZl +tJQqo5eHbQ/+Hjfx +-----END CERTIFICATE----- + diff --git a/Sources/paho/test/ssl/all-ca.crt.text b/Sources/paho/test/ssl/all-ca.crt.text new file mode 100644 index 0000000..a672f77 --- /dev/null +++ b/Sources/paho/test/ssl/all-ca.crt.text @@ -0,0 +1,58 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Mosquitto Project, OU=Testing, CN=Root CA + Validity + Not Before: Jul 24 23:51:16 2013 GMT + Not After : Jul 23 23:51:16 2018 GMT + Subject: C=GB, ST=Derbyshire, O=Mosquitto Project, OU=Testing, CN=Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:b5:4a:2a:fa:e4:80:43:6e:6e:db:23:89:47:0d: + a9:04:a0:dd:18:e8:35:20:fd:2a:73:16:83:de:e1: + c6:e2:c7:04:b5:a6:0d:f9:d7:85:f6:b2:bb:fd:c3: + 4d:ca:ce:9f:e8:6c:33:ac:fc:9c:db:a9:c3:df:38: + 10:2e:8a:23:e5:32:6d:61:c3:d6:b3:d4:1e:cd:f6: + 58:3f:d8:32:87:2f:fe:ce:ab:c2:19:50:ab:05:8e: + 16:c5:00:c8:d0:77:f3:c0:64:29:01:3b:51:52:d5: + 8d:23:65:eb:38:ff:57:f3:96:aa:b8:ad:82:2d:4e: + 27:be:61:7f:2d:4c:37:3f:ed + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + E5:6E:B6:D5:29:2C:0F:06:71:4A:9B:19:B0:59:26:EB:F4:2E:01:06 + X509v3 Authority Key Identifier: + keyid:AB:DD:8A:2B:B5:18:4F:A5:7B:17:59:B2:4A:DE:BE:2D:64:CF:CE:BE + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 4c:73:07:63:12:55:0e:85:7a:a8:aa:55:f5:0d:68:a4:e7:c5: + fb:dc:61:f1:8c:4e:76:8d:aa:38:04:74:59:4b:e3:e9:c0:e3: + a3:7e:7a:b8:09:f2:17:17:57:0c:a7:de:5c:2b:e1:21:e7:ae: + a5:10:97:f6:b9:d9:55:d7:06:8a:7b:0e:7b:f1:3f:3a:f9:4b: + 11:3b:f4:ff:6b:46:db:dc:5b:ae:64:7d:d3:5e:73:be:3a:33: + 4c:4d:62:8c:67:48:8b:42:d3:f0:34:de:26:f6:5b:33:10:2a: + d2:80:9e:77:c8:22:01:ea:2a:bf:cd:f5:d5:4a:8a:7b:5c:5c: + dd:55 +-----BEGIN CERTIFICATE----- +MIICnTCCAgagAwIBAgIBATANBgkqhkiG9w0BAQUFADByMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxGjAYBgNVBAoMEU1v +c3F1aXR0byBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdSb290 +IENBMB4XDTEzMDcyNDIzNTExNloXDTE4MDcyMzIzNTExNlowZTELMAkGA1UEBhMC +R0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxGjAYBgNVBAoMEU1vc3F1aXR0byBQcm9q +ZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1Sir65IBDbm7bI4lHDakEoN0Y6DUg/Spz +FoPe4cbixwS1pg3514X2srv9w03Kzp/obDOs/JzbqcPfOBAuiiPlMm1hw9az1B7N +9lg/2DKHL/7Oq8IZUKsFjhbFAMjQd/PAZCkBO1FS1Y0jZes4/1fzlqq4rYItTie+ +YX8tTDc/7QIDAQABo1AwTjAdBgNVHQ4EFgQU5W621SksDwZxSpsZsFkm6/QuAQYw +HwYDVR0jBBgwFoAUq92KK7UYT6V7F1mySt6+LWTPzr4wDAYDVR0TBAUwAwEB/zAN +BgkqhkiG9w0BAQUFAAOBgQBMcwdjElUOhXqoqlX1DWik58X73GHxjE52jao4BHRZ +S+PpwOOjfnq4CfIXF1cMp95cK+Eh566lEJf2udlV1waKew578T86+UsRO/T/a0bb +3FuuZH3TXnO+OjNMTWKMZ0iLQtPwNN4m9lszECrSgJ53yCIB6iq/zfXVSop7XFzd +VQ== +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/cert.zip b/Sources/paho/test/ssl/cert.zip new file mode 100644 index 0000000..dc13869 Binary files /dev/null and b/Sources/paho/test/ssl/cert.zip differ diff --git a/Sources/paho/test/ssl/client.crt b/Sources/paho/test/ssl/client.crt new file mode 100644 index 0000000..b007d4c --- /dev/null +++ b/Sources/paho/test/ssl/client.crt @@ -0,0 +1,60 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Validity + Not Before: Jul 29 19:21:31 2013 GMT + Not After : Jul 28 19:21:31 2018 GMT + Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:dc:17:82:af:47:dc:71:77:73:c3:69:11:4b:ff: + 27:0e:29:4b:e6:6f:11:78:e4:56:88:c9:34:13:12: + e1:82:ec:24:fe:65:c8:9d:bb:05:54:20:d0:b4:31: + b9:4b:87:f8:4d:e5:c1:ba:99:f8:a2:cc:ff:8e:89: + f2:7a:68:2f:53:42:4d:73:19:5e:ca:7e:b2:fe:3b: + f7:d1:bc:e8:24:fa:77:47:ee:a4:89:cf:d1:dc:e9: + 99:3f:da:0e:d0:1e:c6:40:d2:60:ee:38:83:4e:a4: + dd:46:a3:6a:ac:c9:61:af:d5:23:9d:23:14:b5:31: + d5:ca:66:7a:30:3f:c2:ce:59 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 17:CD:6D:67:FB:7D:77:59:0F:6C:F1:9B:0E:B0:EB:AE:BE:E0:9D:47 + X509v3 Authority Key Identifier: + keyid:29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + + Signature Algorithm: sha1WithRSAEncryption + b4:11:e8:8a:f5:21:d1:88:22:9e:f3:05:e6:47:c9:9d:87:10: + 09:a1:9c:f1:38:5b:a0:5a:b4:f5:fd:8d:cf:ae:01:7d:b4:a8: + 3c:dd:ed:17:b3:02:56:5b:4a:e6:17:58:8f:46:d4:02:97:95: + 0b:00:0e:b4:77:3e:ad:f0:ce:06:25:38:2d:ff:df:a4:0e:3b: + 83:73:f7:a3:da:c1:a1:24:68:a2:18:71:81:4e:3b:26:5a:e2: + 10:9a:27:95:85:a8:3c:47:3a:60:49:21:2f:12:90:fc:4a:f0: + 71:4d:bc:19:2a:06:07:f4:35:d9:8d:1d:b2:85:93:61:17:45: + 26:9a +-----BEGIN CERTIFICATE----- +MIICyTCCAjKgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD +VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTEzMDcyOTE5MjEz +MVoXDTE4MDcyODE5MjEzMVoweDELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp +bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy +MRMwEQYDVQQLDApQcm9kdWN0aW9uMRQwEgYDVQQDDAt0ZXN0IGNsaWVudDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA3BeCr0fccXdzw2kRS/8nDilL5m8ReORW +iMk0ExLhguwk/mXInbsFVCDQtDG5S4f4TeXBupn4osz/jonyemgvU0JNcxleyn6y +/jv30bzoJPp3R+6kic/R3OmZP9oO0B7GQNJg7jiDTqTdRqNqrMlhr9UjnSMUtTHV +ymZ6MD/CzlkCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3Bl +blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFBfNbWf7fXdZD2zx +mw6w666+4J1HMB8GA1UdIwQYMBaAFClNbsfy93Fy2iecnKvaBx1HnNhBMA0GCSqG +SIb3DQEBBQUAA4GBALQR6Ir1IdGIIp7zBeZHyZ2HEAmhnPE4W6BatPX9jc+uAX20 +qDzd7RezAlZbSuYXWI9G1AKXlQsADrR3Pq3wzgYlOC3/36QOO4Nz96PawaEkaKIY +cYFOOyZa4hCaJ5WFqDxHOmBJIS8SkPxK8HFNvBkqBgf0NdmNHbKFk2EXRSaa +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/client.key b/Sources/paho/test/ssl/client.key new file mode 100644 index 0000000..4be1b32 --- /dev/null +++ b/Sources/paho/test/ssl/client.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDcF4KvR9xxd3PDaRFL/ycOKUvmbxF45FaIyTQTEuGC7CT+Zcid +uwVUINC0MblLh/hN5cG6mfiizP+OifJ6aC9TQk1zGV7KfrL+O/fRvOgk+ndH7qSJ +z9Hc6Zk/2g7QHsZA0mDuOINOpN1Go2qsyWGv1SOdIxS1MdXKZnowP8LOWQIDAQAB +AoGAa+NifoXdfAmwR7QzdGuJO5nmyPjdOcPE35yx2D/DKCiWIdbHNvq8q/bCF/Lg +ADSQ9a6Q/uYHSdbv13Gr2XFE8MSOCex5cWe7xcQ4jHM9AR4soMxDLXoEqia6QtFg +RLrVolER/h1QcqJ4pP3QC025JLADXTAvarKAJlkR4nQPigECQQD1xCdxY3mHkl0C +KSVVjyALKrRHoqIxu2w1qivfTqA/S02Ws5tn6g+lkAEUa7Jg2s1/U2HybRAdGz5v +fuIW7eOhAkEA5UGrc2z7TyfKIwO5I6aRLFMqwyMKVdO5v4RZlJGBhtGHLEd5nJMw +ueKLVAUa5/1LaowfLQxYZD+yF8dWdpbvuQJAAbik+hNTR5LL2fcFzuqYs9tRteq6 +rhR89odBlWfMkYTqfzK01O57u5Idn9H9RtZheBHSbss6wKlvL4K4/KYf4QJAZKXk +A5TA8Atj7uNfkIs8CN2qVGk5zFxbm/0a5uLKnsm2MnZeqaLlLXaL/KMRIPBO/8Ps +m/Zjh/9+zHmzN/Uj4QJBAPFmzczJDxDviQcEo7qL9J6JAJtijqDAgv9u1XpqIfIx +GveE+zuKYC2g2Absn1Art3dQgJAsttOF/40HykRLeGc= +-----END RSA PRIVATE KEY----- diff --git a/Sources/paho/test/ssl/client.pem b/Sources/paho/test/ssl/client.pem new file mode 100644 index 0000000..0f89fde --- /dev/null +++ b/Sources/paho/test/ssl/client.pem @@ -0,0 +1,149 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Validity + Not Before: Jul 29 19:21:31 2013 GMT + Not After : Jul 28 19:21:31 2018 GMT + Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:dc:17:82:af:47:dc:71:77:73:c3:69:11:4b:ff: + 27:0e:29:4b:e6:6f:11:78:e4:56:88:c9:34:13:12: + e1:82:ec:24:fe:65:c8:9d:bb:05:54:20:d0:b4:31: + b9:4b:87:f8:4d:e5:c1:ba:99:f8:a2:cc:ff:8e:89: + f2:7a:68:2f:53:42:4d:73:19:5e:ca:7e:b2:fe:3b: + f7:d1:bc:e8:24:fa:77:47:ee:a4:89:cf:d1:dc:e9: + 99:3f:da:0e:d0:1e:c6:40:d2:60:ee:38:83:4e:a4: + dd:46:a3:6a:ac:c9:61:af:d5:23:9d:23:14:b5:31: + d5:ca:66:7a:30:3f:c2:ce:59 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 17:CD:6D:67:FB:7D:77:59:0F:6C:F1:9B:0E:B0:EB:AE:BE:E0:9D:47 + X509v3 Authority Key Identifier: + keyid:29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + + Signature Algorithm: sha1WithRSAEncryption + b4:11:e8:8a:f5:21:d1:88:22:9e:f3:05:e6:47:c9:9d:87:10: + 09:a1:9c:f1:38:5b:a0:5a:b4:f5:fd:8d:cf:ae:01:7d:b4:a8: + 3c:dd:ed:17:b3:02:56:5b:4a:e6:17:58:8f:46:d4:02:97:95: + 0b:00:0e:b4:77:3e:ad:f0:ce:06:25:38:2d:ff:df:a4:0e:3b: + 83:73:f7:a3:da:c1:a1:24:68:a2:18:71:81:4e:3b:26:5a:e2: + 10:9a:27:95:85:a8:3c:47:3a:60:49:21:2f:12:90:fc:4a:f0: + 71:4d:bc:19:2a:06:07:f4:35:d9:8d:1d:b2:85:93:61:17:45: + 26:9a +-----BEGIN CERTIFICATE----- +MIICyTCCAjKgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD +VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTEzMDcyOTE5MjEz +MVoXDTE4MDcyODE5MjEzMVoweDELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp +bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy +MRMwEQYDVQQLDApQcm9kdWN0aW9uMRQwEgYDVQQDDAt0ZXN0IGNsaWVudDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA3BeCr0fccXdzw2kRS/8nDilL5m8ReORW +iMk0ExLhguwk/mXInbsFVCDQtDG5S4f4TeXBupn4osz/jonyemgvU0JNcxleyn6y +/jv30bzoJPp3R+6kic/R3OmZP9oO0B7GQNJg7jiDTqTdRqNqrMlhr9UjnSMUtTHV +ymZ6MD/CzlkCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3Bl +blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFBfNbWf7fXdZD2zx +mw6w666+4J1HMB8GA1UdIwQYMBaAFClNbsfy93Fy2iecnKvaBx1HnNhBMA0GCSqG +SIb3DQEBBQUAA4GBALQR6Ir1IdGIIp7zBeZHyZ2HEAmhnPE4W6BatPX9jc+uAX20 +qDzd7RezAlZbSuYXWI9G1AKXlQsADrR3Pq3wzgYlOC3/36QOO4Nz96PawaEkaKIY +cYFOOyZa4hCaJ5WFqDxHOmBJIS8SkPxK8HFNvBkqBgf0NdmNHbKFk2EXRSaa +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDcF4KvR9xxd3PDaRFL/ycOKUvmbxF45FaIyTQTEuGC7CT+Zcid +uwVUINC0MblLh/hN5cG6mfiizP+OifJ6aC9TQk1zGV7KfrL+O/fRvOgk+ndH7qSJ +z9Hc6Zk/2g7QHsZA0mDuOINOpN1Go2qsyWGv1SOdIxS1MdXKZnowP8LOWQIDAQAB +AoGAa+NifoXdfAmwR7QzdGuJO5nmyPjdOcPE35yx2D/DKCiWIdbHNvq8q/bCF/Lg +ADSQ9a6Q/uYHSdbv13Gr2XFE8MSOCex5cWe7xcQ4jHM9AR4soMxDLXoEqia6QtFg +RLrVolER/h1QcqJ4pP3QC025JLADXTAvarKAJlkR4nQPigECQQD1xCdxY3mHkl0C +KSVVjyALKrRHoqIxu2w1qivfTqA/S02Ws5tn6g+lkAEUa7Jg2s1/U2HybRAdGz5v +fuIW7eOhAkEA5UGrc2z7TyfKIwO5I6aRLFMqwyMKVdO5v4RZlJGBhtGHLEd5nJMw +ueKLVAUa5/1LaowfLQxYZD+yF8dWdpbvuQJAAbik+hNTR5LL2fcFzuqYs9tRteq6 +rhR89odBlWfMkYTqfzK01O57u5Idn9H9RtZheBHSbss6wKlvL4K4/KYf4QJAZKXk +A5TA8Atj7uNfkIs8CN2qVGk5zFxbm/0a5uLKnsm2MnZeqaLlLXaL/KMRIPBO/8Ps +m/Zjh/9+zHmzN/Uj4QJBAPFmzczJDxDviQcEo7qL9J6JAJtijqDAgv9u1XpqIfIx +GveE+zuKYC2g2Absn1Art3dQgJAsttOF/40HykRLeGc= +-----END RSA PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA + Validity + Not Before: Jul 29 19:21:30 2013 GMT + Not After : Jul 28 19:21:30 2018 GMT + Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:dc:26:78:40:ae:b2:ad:2f:26:12:0a:d5:b1:18: + 80:16:d8:88:be:0b:42:ce:32:ad:12:d5:f5:78:1b: + 35:28:f2:13:1b:05:09:fb:7e:d7:d9:a1:8a:0d:4a: + fe:95:37:d4:16:75:83:e4:6a:44:34:33:57:2e:49: + ba:bc:b4:cf:d0:c0:87:e0:bc:f0:60:76:14:00:d6: + eb:cb:f6:db:b3:43:f1:c8:4d:4a:0a:bb:e0:37:7c: + 8e:93:1f:a0:87:68:59:fe:0c:25:40:f3:7c:fd:71: + 90:55:ef:de:18:b4:08:86:c9:75:c2:99:2f:ce:12: + bf:c5:5e:cf:5f:f1:06:53:07 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + X509v3 Authority Key Identifier: + keyid:4A:2B:69:D6:31:1D:A3:68:E8:46:6F:FB:4B:F3:8E:B6:8D:51:0E:BF + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 48:ec:d7:80:8a:8f:82:a6:42:b1:89:2c:b9:4b:6d:0a:37:b8: + 72:19:05:de:75:80:0c:d6:41:97:b2:d7:fe:99:cb:7e:c4:0e: + 77:97:09:a8:9f:87:ff:0b:de:3f:1c:dc:1e:fe:09:36:a7:f5: + 54:9a:85:4e:fb:6f:27:fe:0f:29:45:61:8d:07:c6:0c:da:37: + 3d:a3:69:4b:82:71:e6:24:e0:87:a6:ee:d5:87:61:dd:8f:08: + fe:33:a6:1f:ae:b2:ae:1f:d8:2c:20:c8:a6:fc:33:0e:82:68: + 80:23:61:10:ad:5c:1d:80:d6:b1:5f:e4:af:66:6d:63:10:e4: + 96:e4 +-----BEGIN CERTIFICATE----- +MIICkzCCAfygAwIBAgIBATANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh +aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe +Fw0xMzA3MjkxOTIxMzBaFw0xODA3MjgxOTIxMzBaMGAxCzAJBgNVBAYTAkdCMRMw +EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV +BAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBANwmeECusq0vJhIK1bEYgBbYiL4LQs4yrRLV9XgbNSjyExsF +Cft+19mhig1K/pU31BZ1g+RqRDQzVy5Jury0z9DAh+C88GB2FADW68v227ND8chN +Sgq74Dd8jpMfoIdoWf4MJUDzfP1xkFXv3hi0CIbJdcKZL84Sv8Vez1/xBlMHAgMB +AAGjUDBOMB0GA1UdDgQWBBQpTW7H8vdxctonnJyr2gcdR5zYQTAfBgNVHSMEGDAW +gBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BQUAA4GBAEjs14CKj4KmQrGJLLlLbQo3uHIZBd51gAzWQZey1/6Zy37EDneXCaif +h/8L3j8c3B7+CTan9VSahU77byf+DylFYY0HxgzaNz2jaUuCceYk4Iem7tWHYd2P +CP4zph+usq4f2CwgyKb8Mw6CaIAjYRCtXB2A1rFf5K9mbWMQ5Jbk +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICqDCCAhGgAwIBAgIJAKrzwmdXIUxsMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEVMBMG +A1UECgwMUGFobyBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdS +b290IENBMB4XDTEzMDcyOTE5MjEyOVoXDTIzMDcyNzE5MjEyOVowbTELMAkGA1UE +BhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNVBAcMBURlcmJ5MRUwEwYD +VQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxEDAOBgNVBAMMB1Jv +b3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKbPzEEWCKsjjwjJ787u +Q32k5EdqoDddMEjSVbZNSNEwUew1L7O8NTbmtCEeVFQjOLAdmdiF3rQbXHV+Zew0 +jt2g4vtPpl1GOG6jA/6YznKAyQdvGCdYfGZUN2tN+mbtVxWqkHZitQDQGaSHnx24 +NX649La2uyFy+7l9o8++xPONAgMBAAGjUDBOMB0GA1UdDgQWBBRKK2nWMR2jaOhG +b/tL8462jVEOvzAfBgNVHSMEGDAWgBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAEd+gW86/W+fisz5PFHAeEw7zn9q +dzLHm7+QZgNLZ9h7/ZbhObRUFMRtU2xm4amyh85h7hUE5R2E2uW2OXumic7/D4ZD +6unjr4m5jwVWDTqTUYIcNSriyoDWAVlPfOWaU5NyUhqS1DM28tvOWVHVLCxmVcZl +tJQqo5eHbQ/+Hjfx +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/gen.sh b/Sources/paho/test/ssl/gen.sh new file mode 100644 index 0000000..dec4743 --- /dev/null +++ b/Sources/paho/test/ssl/gen.sh @@ -0,0 +1,72 @@ +# This file generates the keys and certificates used for testing mosquitto. +# None of the keys are encrypted, so do not just use this script to generate +# files for your own use. + +rm -f *.crt *.key *.csr +for a in root signing; do + rm -rf ${a}CA/ + mkdir -p ${a}CA/newcerts + touch ${a}CA/index.txt + echo 01 > ${a}CA/serial + echo 01 > ${a}CA/crlnumber +done +rm -rf certs + +BASESUBJ="/C=GB/ST=Derbyshire/L=Derby/O=Mosquitto Project/OU=Testing" +SBASESUBJ="/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production" +BBASESUBJ="/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Bridge" + +# The root CA +openssl genrsa -out test-root-ca.key 1024 +openssl req -new -x509 -days 3650 -key test-root-ca.key -out test-root-ca.crt -config openssl.cnf -subj "${BASESUBJ}/CN=Root CA/" + +# Another root CA that doesn't sign anything +openssl genrsa -out test-bad-root-ca.key 1024 +openssl req -new -x509 -days 3650 -key test-bad-root-ca.key -out test-bad-root-ca.crt -config openssl.cnf -subj "${BASESUBJ}/CN=Bad Root CA/" + +# This is a root CA that has the exact same details as the real root CA, but is a different key and certificate. Effectively a "fake" CA. +openssl genrsa -out test-fake-root-ca.key 1024 +openssl req -new -x509 -days 3650 -key test-fake-root-ca.key -out test-fake-root-ca.crt -config openssl.cnf -subj "${BASESUBJ}/CN=Root CA/" + +# An intermediate CA, signed by the root CA, used to sign server/client csrs. +openssl genrsa -out test-signing-ca.key 1024 +openssl req -out test-signing-ca.csr -key test-signing-ca.key -new -config openssl.cnf -subj "${BASESUBJ}/CN=Signing CA/" +openssl ca -config openssl.cnf -name CA_root -extensions v3_ca -out test-signing-ca.crt -infiles test-signing-ca.csr + +# An alternative intermediate CA, signed by the root CA, not used to sign anything. +openssl genrsa -out test-alt-ca.key 1024 +openssl req -out test-alt-ca.csr -key test-alt-ca.key -new -config openssl.cnf -subj "${BASESUBJ}/CN=Alternative Signing CA/" +openssl ca -config openssl.cnf -name CA_root -extensions v3_ca -out test-alt-ca.crt -infiles test-alt-ca.csr + +# Valid server key and certificate. +openssl genrsa -out server.key 1024 +openssl req -new -key server.key -out server.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=localhost/" +openssl ca -config openssl.cnf -name CA_signing -out server.crt -infiles server.csr + +# Expired server certificate, based on the above server key. +openssl req -new -days 1 -key server.key -out server-expired.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=localhost/" +openssl ca -config openssl.cnf -name CA_signing -days 1 -startdate 120820000000Z -enddate 120821000000Z -out server-expired.crt -infiles server-expired.csr + +# Valid client key and certificate. +openssl genrsa -out client.key 1024 +openssl req -new -key client.key -out client.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=test client/" +openssl ca -config openssl.cnf -name CA_signing -out client.crt -infiles client.csr + +# Expired client certificate, based on the above client key. +openssl req -new -days 1 -key client.key -out client-expired.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=test client expired/" +openssl ca -config openssl.cnf -name CA_signing -days 1 -startdate 120820000000Z -enddate 120821000000Z -out client-expired.crt -infiles client-expired.csr + +# Revoked client certificate, based on a new client key. +openssl genrsa -out client-revoked.key 1024 +openssl req -new -days 1 -key client-revoked.key -out client-revoked.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=test client revoked/" +openssl ca -config openssl.cnf -name CA_signing -out client-revoked.crt -infiles client-revoked.csr +openssl ca -config openssl.cnf -name CA_signing -revoke client-revoked.crt +openssl ca -config openssl.cnf -name CA_signing -gencrl -out crl.pem + +cat test-signing-ca.crt test-root-ca.crt > all-ca.crt + +cat client.crt client.key all-ca.crt > client.pem +#mkdir certs +#cp test-signing-ca.crt certs/test-signing-ca.pem +#cp test-root-ca.crt certs/test-root.ca.pem +c_rehash certs diff --git a/Sources/paho/test/ssl/mosquitto.conf b/Sources/paho/test/ssl/mosquitto.conf new file mode 100644 index 0000000..a70959e --- /dev/null +++ b/Sources/paho/test/ssl/mosquitto.conf @@ -0,0 +1,40 @@ +log_type error +log_type warning +log_type notice +log_type information +log_type debug + +allow_anonymous true + +# non-SSL listener +listener 18883 + +# listener for mutual authentication +listener 18884 +cafile /etc/mosquitto/tls-testing/keys/all-ca.crt +certfile /etc/mosquitto/tls-testing/keys/server/server.crt +keyfile /etc/mosquitto/tls-testing/keys/server/server.key +require_certificate true + +# server authentication - no client authentication +listener 18885 +cafile /etc/mosquitto/tls-testing/keys/all-ca.crt +certfile /etc/mosquitto/tls-testing/keys/server/server.crt +keyfile /etc/mosquitto/tls-testing/keys/server/server.key +require_certificate false + +listener 18886 +cafile /etc/mosquitto/tls-testing/keys/all-ca.crt +certfile /etc/mosquitto/tls-testing/keys/server/server.crt +keyfile /etc/mosquitto/tls-testing/keys/server/server.key +require_certificate false +ciphers ADH-DES-CBC-SHA + +# server authentication - no client authentication - uses fake hostname to +# simulate mitm attack. Clients should refuse to connect to this listener. +listener 18887 +cafile /etc/mosquitto/tls-testing/keys/all-ca.crt +certfile /etc/mosquitto/tls-testing/keys/server/server-mitm.crt +keyfile /etc/mosquitto/tls-testing/keys/server/server-mitm.key +require_certificate false + diff --git a/Sources/paho/test/ssl/openssl.cnf b/Sources/paho/test/ssl/openssl.cnf new file mode 100644 index 0000000..e0cff18 --- /dev/null +++ b/Sources/paho/test/ssl/openssl.cnf @@ -0,0 +1,406 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca', 'req' and 'ts'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +# Policies used by the TSA examples. +tsa_policy1 = 1.2.3.4.1 +tsa_policy2 = 1.2.3.4.5.6 +tsa_policy3 = 1.2.3.4.5.7 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_signing ] + +dir = ./signingCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = test-signing-ca.crt # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = test-signing-ca.key # The private key +RANDFILE = $dir/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 1825 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_anything + +[ CA_inter ] +dir = ./interCA +certs = $dir/certs +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir/newcerts + +certificate = test-inter-ca.crt +serial = $dir/serial +crlnumber = $dir/crlnumber +crl = $dir/crl.pem +private_key = test-inter-ca.key +RANDFILE = $dir/.rand + +#x509_extensions = v3_ca +x509_extensions = usr_cert + +name_opt = ca_default +cert_opt = ca_default + +default_days = 1825 +default_crl_days = 30 +default_md = default +preserve = no + +policy = policy_match +unique_subject = yes + +[ CA_root ] +dir = ./rootCA +certs = $dir/certs +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir/newcerts + +certificate = test-root-ca.crt +serial = $dir/serial +crlnumber = $dir/crlnumber +crl = $dir/crl.pem +private_key = test-root-ca.key +RANDFILE = $dir/.rand + +x509_extensions = v3_ca + +name_opt = ca_default +cert_opt = ca_default + +default_days = 1825 +default_crl_days = 30 +default_md = default +preserve = no + +policy = policy_match +unique_subject = yes + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString (PKIX recommendation before 2004) +# utf8only: only UTF8Strings (PKIX recommendation after 2004). +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. +string_mask = utf8only + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = GB +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Derbyshire + +localityName = Locality Name (eg, city) +localityName_default = Derby + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Mosquitto Project + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = Testing + +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This is required for TSA certificates. +# extendedKeyUsage = critical,timeStamping + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo + +#################################################################### +[ tsa ] + +default_tsa = tsa_config1 # the default TSA section + +[ tsa_config1 ] + +# These are used by the TSA reply generation only. +dir = ./demoCA # TSA root directory +serial = $dir/tsaserial # The current serial number (mandatory) +crypto_device = builtin # OpenSSL engine to use for signing +signer_cert = $dir/tsacert.pem # The TSA signing certificate + # (optional) +certs = $dir/cacert.pem # Certificate chain to include in reply + # (optional) +signer_key = $dir/private/tsakey.pem # The TSA private key (optional) + +default_policy = tsa_policy1 # Policy if request did not specify it + # (optional) +other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional) +digests = md5, sha1 # Acceptable message digests (mandatory) +accuracy = secs:1, millisecs:500, microsecs:100 # (optional) +clock_precision_digits = 0 # number of digits after dot. (optional) +ordering = yes # Is ordering defined for timestamps? + # (optional, default: no) +tsa_name = yes # Must the TSA name be included in the reply? + # (optional, default: no) +ess_cert_id_chain = no # Must the ESS cert id chain be included? + # (optional, default: no) diff --git a/Sources/paho/test/ssl/server-expired.crt b/Sources/paho/test/ssl/server-expired.crt new file mode 100644 index 0000000..e69de29 diff --git a/Sources/paho/test/ssl/server.crt b/Sources/paho/test/ssl/server.crt new file mode 100644 index 0000000..b0941b9 --- /dev/null +++ b/Sources/paho/test/ssl/server.crt @@ -0,0 +1,60 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Validity + Not Before: Jul 29 19:21:30 2013 GMT + Not After : Jul 28 19:21:30 2018 GMT + Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:be:b7:65:98:5e:e1:e0:68:e7:14:04:e5:40:2d: + d3:b4:f2:b2:dd:6e:5c:97:7a:5b:c5:4f:7a:45:11: + 99:4e:56:30:c6:d6:50:29:88:c3:31:6d:b0:f1:a8: + 5f:f5:fd:cc:d1:52:0f:40:70:04:cc:14:0d:98:45: + 62:a8:f9:88:0a:be:20:32:53:c5:48:fb:b0:e4:25: + db:25:ec:0d:c4:6a:28:dc:af:d7:2d:63:99:b9:f4: + c0:32:54:dc:be:4d:9f:7f:67:7e:2a:be:82:2d:de: + 37:35:0b:0d:7b:b8:9c:55:ff:cf:ab:fe:61:e9:8c: + bf:c4:27:e2:56:2f:1a:73:87 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + A1:8C:9A:D1:28:58:68:C5:46:5B:FA:C5:48:01:96:67:55:97:65:8A + X509v3 Authority Key Identifier: + keyid:29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + + Signature Algorithm: sha1WithRSAEncryption + 78:f6:a1:34:ac:2c:a5:0a:1d:82:97:97:1f:f5:03:44:a7:c0: + 4d:e8:8d:67:e7:71:50:30:3c:8b:77:eb:81:96:78:6b:ab:31: + 5a:ba:7b:1c:ad:ec:fd:a6:5d:73:ef:99:2d:6f:9f:7e:13:ac: + b2:61:2f:e4:56:cc:28:f1:e4:7f:ea:a9:b2:f2:85:87:68:52: + 65:b0:42:54:84:92:2f:fb:45:d4:36:e2:3c:0e:4c:a6:6d:82: + 8f:72:c0:66:0c:5f:b2:a7:7c:9b:be:cd:19:55:5d:40:27:99: + 14:e2:cf:59:cb:4b:40:e4:98:2d:f7:93:14:4a:50:dc:75:9c: + 5c:9d +-----BEGIN CERTIFICATE----- +MIICxzCCAjCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD +VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTEzMDcyOTE5MjEz +MFoXDTE4MDcyODE5MjEzMFowdjELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp +bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy +MRMwEQYDVQQLDApQcm9kdWN0aW9uMRIwEAYDVQQDDAlsb2NhbGhvc3QwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAL63ZZhe4eBo5xQE5UAt07Tyst1uXJd6W8VP +ekURmU5WMMbWUCmIwzFtsPGoX/X9zNFSD0BwBMwUDZhFYqj5iAq+IDJTxUj7sOQl +2yXsDcRqKNyv1y1jmbn0wDJU3L5Nn39nfiq+gi3eNzULDXu4nFX/z6v+YemMv8Qn +4lYvGnOHAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5T +U0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBShjJrRKFhoxUZb+sVI +AZZnVZdlijAfBgNVHSMEGDAWgBQpTW7H8vdxctonnJyr2gcdR5zYQTANBgkqhkiG +9w0BAQUFAAOBgQB49qE0rCylCh2Cl5cf9QNEp8BN6I1n53FQMDyLd+uBlnhrqzFa +unscrez9pl1z75ktb59+E6yyYS/kVswo8eR/6qmy8oWHaFJlsEJUhJIv+0XUNuI8 +DkymbYKPcsBmDF+yp3ybvs0ZVV1AJ5kU4s9Zy0tA5Jgt95MUSlDcdZxcnQ== +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/server.key b/Sources/paho/test/ssl/server.key new file mode 100644 index 0000000..bbf5c60 --- /dev/null +++ b/Sources/paho/test/ssl/server.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQC+t2WYXuHgaOcUBOVALdO08rLdblyXelvFT3pFEZlOVjDG1lAp +iMMxbbDxqF/1/czRUg9AcATMFA2YRWKo+YgKviAyU8VI+7DkJdsl7A3Eaijcr9ct +Y5m59MAyVNy+TZ9/Z34qvoIt3jc1Cw17uJxV/8+r/mHpjL/EJ+JWLxpzhwIDAQAB +AoGAW1dC1UM8M1qKsc/WbHKGXreOavccaYA0y79Q9BuFrTsiiVjDc+EIe3fpsxPN +QeeYXPhMTbRY19US3cb9hahdOtPZc1zKRoloWl995v6X5XufTmgigBRUrRKG6rln +wok6PYwKQmcG+yVaOjPwiJBx+4gfGjD6qO/fhK2sWWtyneECQQDrUEiaWvQE0uli +EI34MhO3As0iYyw1qFHVck4bbFS4RT0gnhWYVeabd5mTKx1ztLlr0ykwaCf9FoMG +U2liyV/VAkEAz3t0v8vZrlpotW9CRzBQ63vYW3+d8m5Hmkvsghrfem52je6MN0oL +2Y7F3JrJh1bC9ZNgtkBF/mIQgv9jGBoP6wJASKTYRQ6fFn4mHmgN6/lJrM3olh0X +oNj9qm9HPaAL53c4j8E92XFrZ8NcXdqJlRbNx0PBC3icH727ZVCK0DxqoQJABTRn +nVgTwdfqwIJl+zsvDHky2Di/UZGKokg9SpY5/OxAdRcC1XA6E98M/5eybn6yrU5h +IrFCEDuNhnu5lKUyuQJAAiNPFWPkl4XeghyzPDA1lUYMwKPr7oEwELqS8fIq/g4K +BI10X7qlpioI4I6jA9lwlIdtR+q620UFZRlQts9nug== +-----END RSA PRIVATE KEY----- diff --git a/Sources/paho/test/ssl/server.pem b/Sources/paho/test/ssl/server.pem new file mode 100644 index 0000000..3436f12 --- /dev/null +++ b/Sources/paho/test/ssl/server.pem @@ -0,0 +1,117 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Validity + Not Before: Jul 29 19:21:30 2013 GMT + Not After : Jul 28 19:21:30 2018 GMT + Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:be:b7:65:98:5e:e1:e0:68:e7:14:04:e5:40:2d: + d3:b4:f2:b2:dd:6e:5c:97:7a:5b:c5:4f:7a:45:11: + 99:4e:56:30:c6:d6:50:29:88:c3:31:6d:b0:f1:a8: + 5f:f5:fd:cc:d1:52:0f:40:70:04:cc:14:0d:98:45: + 62:a8:f9:88:0a:be:20:32:53:c5:48:fb:b0:e4:25: + db:25:ec:0d:c4:6a:28:dc:af:d7:2d:63:99:b9:f4: + c0:32:54:dc:be:4d:9f:7f:67:7e:2a:be:82:2d:de: + 37:35:0b:0d:7b:b8:9c:55:ff:cf:ab:fe:61:e9:8c: + bf:c4:27:e2:56:2f:1a:73:87 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + A1:8C:9A:D1:28:58:68:C5:46:5B:FA:C5:48:01:96:67:55:97:65:8A + X509v3 Authority Key Identifier: + keyid:29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + + Signature Algorithm: sha1WithRSAEncryption + 78:f6:a1:34:ac:2c:a5:0a:1d:82:97:97:1f:f5:03:44:a7:c0: + 4d:e8:8d:67:e7:71:50:30:3c:8b:77:eb:81:96:78:6b:ab:31: + 5a:ba:7b:1c:ad:ec:fd:a6:5d:73:ef:99:2d:6f:9f:7e:13:ac: + b2:61:2f:e4:56:cc:28:f1:e4:7f:ea:a9:b2:f2:85:87:68:52: + 65:b0:42:54:84:92:2f:fb:45:d4:36:e2:3c:0e:4c:a6:6d:82: + 8f:72:c0:66:0c:5f:b2:a7:7c:9b:be:cd:19:55:5d:40:27:99: + 14:e2:cf:59:cb:4b:40:e4:98:2d:f7:93:14:4a:50:dc:75:9c: + 5c:9d +-----BEGIN CERTIFICATE----- +MIICxzCCAjCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEVMBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYD +VQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMB4XDTEzMDcyOTE5MjEz +MFoXDTE4MDcyODE5MjEzMFowdjELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRp +bmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVy +MRMwEQYDVQQLDApQcm9kdWN0aW9uMRIwEAYDVQQDDAlsb2NhbGhvc3QwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAL63ZZhe4eBo5xQE5UAt07Tyst1uXJd6W8VP +ekURmU5WMMbWUCmIwzFtsPGoX/X9zNFSD0BwBMwUDZhFYqj5iAq+IDJTxUj7sOQl +2yXsDcRqKNyv1y1jmbn0wDJU3L5Nn39nfiq+gi3eNzULDXu4nFX/z6v+YemMv8Qn +4lYvGnOHAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5T +U0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBShjJrRKFhoxUZb+sVI +AZZnVZdlijAfBgNVHSMEGDAWgBQpTW7H8vdxctonnJyr2gcdR5zYQTANBgkqhkiG +9w0BAQUFAAOBgQB49qE0rCylCh2Cl5cf9QNEp8BN6I1n53FQMDyLd+uBlnhrqzFa +unscrez9pl1z75ktb59+E6yyYS/kVswo8eR/6qmy8oWHaFJlsEJUhJIv+0XUNuI8 +DkymbYKPcsBmDF+yp3ybvs0ZVV1AJ5kU4s9Zy0tA5Jgt95MUSlDcdZxcnQ== +-----END CERTIFICATE----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA + Validity + Not Before: Jul 29 19:21:30 2013 GMT + Not After : Jul 28 19:21:30 2018 GMT + Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:dc:26:78:40:ae:b2:ad:2f:26:12:0a:d5:b1:18: + 80:16:d8:88:be:0b:42:ce:32:ad:12:d5:f5:78:1b: + 35:28:f2:13:1b:05:09:fb:7e:d7:d9:a1:8a:0d:4a: + fe:95:37:d4:16:75:83:e4:6a:44:34:33:57:2e:49: + ba:bc:b4:cf:d0:c0:87:e0:bc:f0:60:76:14:00:d6: + eb:cb:f6:db:b3:43:f1:c8:4d:4a:0a:bb:e0:37:7c: + 8e:93:1f:a0:87:68:59:fe:0c:25:40:f3:7c:fd:71: + 90:55:ef:de:18:b4:08:86:c9:75:c2:99:2f:ce:12: + bf:c5:5e:cf:5f:f1:06:53:07 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + X509v3 Authority Key Identifier: + keyid:4A:2B:69:D6:31:1D:A3:68:E8:46:6F:FB:4B:F3:8E:B6:8D:51:0E:BF + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 48:ec:d7:80:8a:8f:82:a6:42:b1:89:2c:b9:4b:6d:0a:37:b8: + 72:19:05:de:75:80:0c:d6:41:97:b2:d7:fe:99:cb:7e:c4:0e: + 77:97:09:a8:9f:87:ff:0b:de:3f:1c:dc:1e:fe:09:36:a7:f5: + 54:9a:85:4e:fb:6f:27:fe:0f:29:45:61:8d:07:c6:0c:da:37: + 3d:a3:69:4b:82:71:e6:24:e0:87:a6:ee:d5:87:61:dd:8f:08: + fe:33:a6:1f:ae:b2:ae:1f:d8:2c:20:c8:a6:fc:33:0e:82:68: + 80:23:61:10:ad:5c:1d:80:d6:b1:5f:e4:af:66:6d:63:10:e4: + 96:e4 +-----BEGIN CERTIFICATE----- +MIICkzCCAfygAwIBAgIBATANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh +aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe +Fw0xMzA3MjkxOTIxMzBaFw0xODA3MjgxOTIxMzBaMGAxCzAJBgNVBAYTAkdCMRMw +EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV +BAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBANwmeECusq0vJhIK1bEYgBbYiL4LQs4yrRLV9XgbNSjyExsF +Cft+19mhig1K/pU31BZ1g+RqRDQzVy5Jury0z9DAh+C88GB2FADW68v227ND8chN +Sgq74Dd8jpMfoIdoWf4MJUDzfP1xkFXv3hi0CIbJdcKZL84Sv8Vez1/xBlMHAgMB +AAGjUDBOMB0GA1UdDgQWBBQpTW7H8vdxctonnJyr2gcdR5zYQTAfBgNVHSMEGDAW +gBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BQUAA4GBAEjs14CKj4KmQrGJLLlLbQo3uHIZBd51gAzWQZey1/6Zy37EDneXCaif +h/8L3j8c3B7+CTan9VSahU77byf+DylFYY0HxgzaNz2jaUuCceYk4Iem7tWHYd2P +CP4zph+usq4f2CwgyKb8Mw6CaIAjYRCtXB2A1rFf5K9mbWMQ5Jbk +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/test-alt-ca.crt b/Sources/paho/test/ssl/test-alt-ca.crt new file mode 100644 index 0000000..87f092a --- /dev/null +++ b/Sources/paho/test/ssl/test-alt-ca.crt @@ -0,0 +1,58 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA + Validity + Not Before: Jul 29 19:21:30 2013 GMT + Not After : Jul 28 19:21:30 2018 GMT + Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Alternative Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d3:16:c8:c3:0c:90:e5:68:3d:11:13:a7:8e:fb: + 11:c5:de:aa:3f:4d:ac:95:4f:c4:c2:60:8a:df:95: + b5:db:75:04:76:42:19:5f:d9:63:0e:e4:c0:8e:db: + a5:5f:21:ec:f3:3d:a0:c1:82:8b:61:b4:1a:5b:3c: + 9e:42:bd:5f:5b:b4:a8:00:8d:e1:bf:99:93:c8:45: + 1f:6d:29:ab:67:f0:35:9c:48:0b:a0:a2:18:32:70: + 35:5e:ea:fe:1f:33:ab:b5:85:ef:1d:2a:a9:75:60: + 38:ed:3a:33:be:5d:40:89:cb:0b:b3:25:e8:e7:bc: + 13:6b:62:28:1d:a7:9c:aa:99 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 3A:70:4C:5D:76:C6:B4:CF:E7:BC:4B:F4:CE:C6:B8:46:C2:95:41:9B + X509v3 Authority Key Identifier: + keyid:4A:2B:69:D6:31:1D:A3:68:E8:46:6F:FB:4B:F3:8E:B6:8D:51:0E:BF + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 2f:74:dd:ef:da:03:cf:14:78:ae:6f:0d:04:29:75:db:c5:a2: + c0:fd:1e:46:bf:3c:25:3c:03:3b:a6:f4:f1:3a:89:54:83:e9: + 3a:0f:d7:81:9a:8d:7f:2d:6b:b1:ca:17:7f:ef:93:18:c4:68: + b8:b2:1d:d2:9c:d9:9f:66:9d:18:25:18:b4:4f:72:bf:24:c5: + 0c:2d:fc:cf:ad:c8:ff:25:f1:36:12:72:b4:46:e1:c9:17:19: + c5:1e:f5:26:8a:ae:33:5f:69:16:6f:62:ce:fc:ba:c3:a3:c5: + 50:a3:a5:42:a9:02:6a:25:77:90:3e:e3:b7:e5:ac:7f:3f:bb: + 1c:17 +-----BEGIN CERTIFICATE----- +MIICnzCCAgigAwIBAgIBAjANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh +aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe +Fw0xMzA3MjkxOTIxMzBaFw0xODA3MjgxOTIxMzBaMGwxCzAJBgNVBAYTAkdCMRMw +EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV +BAsMB1Rlc3RpbmcxHzAdBgNVBAMMFkFsdGVybmF0aXZlIFNpZ25pbmcgQ0EwgZ8w +DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANMWyMMMkOVoPRETp477EcXeqj9NrJVP +xMJgit+Vtdt1BHZCGV/ZYw7kwI7bpV8h7PM9oMGCi2G0Gls8nkK9X1u0qACN4b+Z +k8hFH20pq2fwNZxIC6CiGDJwNV7q/h8zq7WF7x0qqXVgOO06M75dQInLC7Ml6Oe8 +E2tiKB2nnKqZAgMBAAGjUDBOMB0GA1UdDgQWBBQ6cExddsa0z+e8S/TOxrhGwpVB +mzAfBgNVHSMEGDAWgBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNVHRMEBTADAQH/ +MA0GCSqGSIb3DQEBBQUAA4GBAC903e/aA88UeK5vDQQpddvFosD9Hka/PCU8Azum +9PE6iVSD6ToP14GajX8ta7HKF3/vkxjEaLiyHdKc2Z9mnRglGLRPcr8kxQwt/M+t +yP8l8TYScrRG4ckXGcUe9SaKrjNfaRZvYs78usOjxVCjpUKpAmold5A+47flrH8/ +uxwX +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/test-alt-ca.key b/Sources/paho/test/ssl/test-alt-ca.key new file mode 100644 index 0000000..a9450e1 --- /dev/null +++ b/Sources/paho/test/ssl/test-alt-ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDTFsjDDJDlaD0RE6eO+xHF3qo/TayVT8TCYIrflbXbdQR2Qhlf +2WMO5MCO26VfIezzPaDBgothtBpbPJ5CvV9btKgAjeG/mZPIRR9tKatn8DWcSAug +ohgycDVe6v4fM6u1he8dKql1YDjtOjO+XUCJywuzJejnvBNrYigdp5yqmQIDAQAB +AoGAFaQtWwnrxQlF0X1hXWBSNyYX8DuHaRtvgboiIsAXj/NUTMeEEHaaGEnNkBfm +wXUZ9OoplA1NOuwbE6WIWDFQGEgma/yLBdy4HYxQpAbJ1qnR7DyoxQ8NHPhBH+cW +GI92g7NqDEphdoHrWYy5YZYCFVr3pTHXbxlBn/VTLBsQnIECQQDr9BcQxEnPfi6e +Kk8cenA/54tGl7Ewpklb8XBrQrm/djfOAFt+CTMexerBv7BnfgriAg5wtlHtTkpK +BLLULE3pAkEA5QXmZ2WvGl0kvgBYGdiOZAruMobOVxxVxF05gvh8Sw6fNj8pI9pn +sbzyFZWIjcuDBfTLx+GVvkhqtQhs6ZYZMQJBAOSfjR3c45veKrNsUV1Jsavp4cST +xMdbyCcDaSc07x/6HxZGuGAF7/d4VABJiVauBUN6NJ23uuhR/J99r/zvtMkCQCQe +qhfkkZk213Sf2UU6QjrE/ow5dpGGhoBRs6BUUEYGKFYF4BcnevMtOYDt9HtofWGT +GhCMI3G/OhUTHxo38gECQG0nSN+QQ4tddHcktz1rnfwbnmTuNloZLC4ahR67lz75 +uP42Ct0dXPjzakzDCGI2CgNk5QGk/IUO6fq4mYVxqRI= +-----END RSA PRIVATE KEY----- diff --git a/Sources/paho/test/ssl/test-bad-root-ca.crt b/Sources/paho/test/ssl/test-bad-root-ca.crt new file mode 100644 index 0000000..7d40475 --- /dev/null +++ b/Sources/paho/test/ssl/test-bad-root-ca.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICsDCCAhmgAwIBAgIJANKB0fFTAhRpMA0GCSqGSIb3DQEBBQUAMHExCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEVMBMG +A1UECgwMUGFobyBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRQwEgYDVQQDDAtC +YWQgUm9vdCBDQTAeFw0xMzA3MjkxOTIxMjlaFw0yMzA3MjcxOTIxMjlaMHExCzAJ +BgNVBAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEV +MBMGA1UECgwMUGFobyBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRQwEgYDVQQD +DAtCYWQgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+nf2D7S +IP42qMVmfAEpKZw22qF0mLVjjL22bWVHwwE1CS5euzD/gBM7i0u7hvFgbvI13Yq4 +Du2ebfjv3n4TAIIQg+UOAY5NbzfUG0A+50J6tPpNtnTij3KXskhQRAlvjDSd3TlU +UiONY2HMwaU56ktqXZzZE7prU0RICZ+DK8cCAwEAAaNQME4wHQYDVR0OBBYEFH/5 +0qkqiFd2x/lspeK61TO4PGF1MB8GA1UdIwQYMBaAFH/50qkqiFd2x/lspeK61TO4 +PGF1MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEARtsgIzT+IVRJHYT1 +wP7C2PuXxbRXFG8a0qqGaA0f4SuICq7NvC3bF5l9zDh4yMvftj8keTiOIa3+alw3 +ucdTz25Jaq/ZER/c68cklMPqcgdwcb/RbxpY5t3PittU2J5wAn/MmFfRiqbsxhgW +hkYbAtnqBXzJ8HdN/HmIyFW7+q4= +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/test-bad-root-ca.key b/Sources/paho/test/ssl/test-bad-root-ca.key new file mode 100644 index 0000000..ffa821e --- /dev/null +++ b/Sources/paho/test/ssl/test-bad-root-ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDr6d/YPtIg/jaoxWZ8ASkpnDbaoXSYtWOMvbZtZUfDATUJLl67 +MP+AEzuLS7uG8WBu8jXdirgO7Z5t+O/efhMAghCD5Q4Bjk1vN9QbQD7nQnq0+k22 +dOKPcpeySFBECW+MNJ3dOVRSI41jYczBpTnqS2pdnNkTumtTREgJn4MrxwIDAQAB +AoGBAJk4o/bqDkX5dfy1gPOHOXnaCNKEzJqmLMrrKIHypuIjdZPJ9yLzFu7TDvhQ +rrJdMTm9vHhwMU0Yza41YW2LSsDpeCI0RkpMxG+Aqaxz+kRYPzwDFFI6YAX0NWpS +O9iie9+sDp0MfOwPlDwtY9T7OegrPH/ngtxWxFp7R0YxVLQJAkEA+Or0TgAklxy/ +2LQV27OPFXc0ejYf67hLNdOC66PhTCO18avjEpDEeA00vF5DkqT+VXJVz2XyXX97 ++cCAf3sYhQJBAPKgM3pmHrhMxr+qgyqiTiKD42kASWLDGEDP0EP4tVaZNdwWH2XG +tSanhf6eOdoHlq0+3c3tIDwJZ+uCr21ACtsCQAiUeLVTle9Lg2Vh17sJ9m2j/UAV +K4aBhL4nO0UKEhMAzB23cg1KxirpMZ8olKWyYD3rwf9zISaN5WUXeJZsVM0CQQC5 +GEhNb0yuUzwoil+ojcvH/w/lUeeqZaXCBAghYsKMvzNcpK/tSAt44sKRfYoq8DEe +F+DEscsuogpanAdS9FGTAkAt8POChqwkCSjXQ9TlPQhdL4bRcENBQz6xp9TEOYT+ +M+FFifLj/ke8sRWXjrar1k45u8VWJJmd/0gmsUSiWoaS +-----END RSA PRIVATE KEY----- diff --git a/Sources/paho/test/ssl/test-fake-root-ca.crt b/Sources/paho/test/ssl/test-fake-root-ca.crt new file mode 100644 index 0000000..239a74b --- /dev/null +++ b/Sources/paho/test/ssl/test-fake-root-ca.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqDCCAhGgAwIBAgIJALWM56dkMt5jMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEVMBMG +A1UECgwMUGFobyBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdS +b290IENBMB4XDTEzMDcyOTE5MjEzMFoXDTIzMDcyNzE5MjEzMFowbTELMAkGA1UE +BhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNVBAcMBURlcmJ5MRUwEwYD +VQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxEDAOBgNVBAMMB1Jv +b3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOpNNgRF6qhcGxndkPFE +1uZVQZ2x9GV3UlARuTnG89MX+6W+fXQ0gfdcbKs1/puhFqvrcqrWmoIgRtM/lZR/ +YDs5EXfpb13V5pDDn8X7AD2+poUb9eHxcB6fKuRbyt1PsS42umwUlpIDtK6p6H8/ +ZfxSiOE73kyY6CUvJfTC4WHrAgMBAAGjUDBOMB0GA1UdDgQWBBSXmasVth7iUHhF +8MDaBnSIGBV4qzAfBgNVHSMEGDAWgBSXmasVth7iUHhF8MDaBnSIGBV4qzAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBANAYCcz14fk3Y+9CBMm/kitCWAkI +Ia54KL0A8ynqrLHssO3Ilq+wb10vSNLxhsdws3zNAfXteFxOvGm24Yu+8oTBQ26K +QfTp/cH9yoF97ONMxg7rqANOJeYv0BeJdDcgjCMgmql5ETEz2cf9tTWBUAtd1ZZC +YPS5aiNsetk+XuS9 +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/test-fake-root-ca.key b/Sources/paho/test/ssl/test-fake-root-ca.key new file mode 100644 index 0000000..4a040e9 --- /dev/null +++ b/Sources/paho/test/ssl/test-fake-root-ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDqTTYEReqoXBsZ3ZDxRNbmVUGdsfRld1JQEbk5xvPTF/ulvn10 +NIH3XGyrNf6boRar63Kq1pqCIEbTP5WUf2A7ORF36W9d1eaQw5/F+wA9vqaFG/Xh +8XAenyrkW8rdT7EuNrpsFJaSA7Suqeh/P2X8UojhO95MmOglLyX0wuFh6wIDAQAB +AoGBAMhOUgu9Kivc8l5eiXd6fq5T3NDQPjwwknJZdJzsda7WJhFAlUgvS50Jqu2E +L7MlOJippVJgPZ9ZsLMQ/PQDIWRdLg2K9VLS4nPl3p7LzHoDmqDnMLPo9fUGBile +EnWwSSCWrz8ATyDO1ct5oJmK/S9QRxdvtw+6SbmorhnzypihAkEA+9LNpjnpuOWf +iF0TGWKhK53WPtiCBnuisXGZEZws9mzFGlfdR98sBDyekl7oHOb+JI0SDpPl3PBE +hZXcF7VPtQJBAO4wA1sxXqfYUazt6SInUTzpaNZ9xPrK0p1PgxZLxJrZV6hZByvW +FGb+cKGnOHIYq4tnCg0cyRe1xX4MJU6wrx8CQGRtNUZNYkAykuS2+Z7uDohucbqu +bWxYchGB1CGJvwSnbBONZtn6znsCEdsdrkOYe1HoUIMvyEPMLgd4NEXgMOECQF+u +y/pbR9IXVSAp5oiA0OKuRR49Id85kQf+xAM15sHp44vOT9ItSr7hIa/etA8pl+gF +OYVw9dtfevmauXX2BjMCQQCrse1jUAp3xmsXwb1JieclSh/C/FcGeo6DYpIcm9bK +RiVCmpzy3hOqYW137l5WvpUwZmN2wPvaKCacF/t75EiG +-----END RSA PRIVATE KEY----- diff --git a/Sources/paho/test/ssl/test-root-ca.crt b/Sources/paho/test/ssl/test-root-ca.crt new file mode 100644 index 0000000..92dd583 --- /dev/null +++ b/Sources/paho/test/ssl/test-root-ca.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqDCCAhGgAwIBAgIJAKrzwmdXIUxsMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEVMBMG +A1UECgwMUGFobyBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdS +b290IENBMB4XDTEzMDcyOTE5MjEyOVoXDTIzMDcyNzE5MjEyOVowbTELMAkGA1UE +BhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNVBAcMBURlcmJ5MRUwEwYD +VQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxEDAOBgNVBAMMB1Jv +b3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKbPzEEWCKsjjwjJ787u +Q32k5EdqoDddMEjSVbZNSNEwUew1L7O8NTbmtCEeVFQjOLAdmdiF3rQbXHV+Zew0 +jt2g4vtPpl1GOG6jA/6YznKAyQdvGCdYfGZUN2tN+mbtVxWqkHZitQDQGaSHnx24 +NX649La2uyFy+7l9o8++xPONAgMBAAGjUDBOMB0GA1UdDgQWBBRKK2nWMR2jaOhG +b/tL8462jVEOvzAfBgNVHSMEGDAWgBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAEd+gW86/W+fisz5PFHAeEw7zn9q +dzLHm7+QZgNLZ9h7/ZbhObRUFMRtU2xm4amyh85h7hUE5R2E2uW2OXumic7/D4ZD +6unjr4m5jwVWDTqTUYIcNSriyoDWAVlPfOWaU5NyUhqS1DM28tvOWVHVLCxmVcZl +tJQqo5eHbQ/+Hjfx +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/test-root-ca.crt.text b/Sources/paho/test/ssl/test-root-ca.crt.text new file mode 100644 index 0000000..bad317d --- /dev/null +++ b/Sources/paho/test/ssl/test-root-ca.crt.text @@ -0,0 +1,59 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + aa:f3:c2:67:57:21:4c:6c + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA + Validity + Not Before: Jul 29 19:21:29 2013 GMT + Not After : Jul 27 19:21:29 2023 GMT + Subject: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a6:cf:cc:41:16:08:ab:23:8f:08:c9:ef:ce:ee: + 43:7d:a4:e4:47:6a:a0:37:5d:30:48:d2:55:b6:4d: + 48:d1:30:51:ec:35:2f:b3:bc:35:36:e6:b4:21:1e: + 54:54:23:38:b0:1d:99:d8:85:de:b4:1b:5c:75:7e: + 65:ec:34:8e:dd:a0:e2:fb:4f:a6:5d:46:38:6e:a3: + 03:fe:98:ce:72:80:c9:07:6f:18:27:58:7c:66:54: + 37:6b:4d:fa:66:ed:57:15:aa:90:76:62:b5:00:d0: + 19:a4:87:9f:1d:b8:35:7e:b8:f4:b6:b6:bb:21:72: + fb:b9:7d:a3:cf:be:c4:f3:8d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 4A:2B:69:D6:31:1D:A3:68:E8:46:6F:FB:4B:F3:8E:B6:8D:51:0E:BF + X509v3 Authority Key Identifier: + keyid:4A:2B:69:D6:31:1D:A3:68:E8:46:6F:FB:4B:F3:8E:B6:8D:51:0E:BF + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 47:7e:81:6f:3a:fd:6f:9f:8a:cc:f9:3c:51:c0:78:4c:3b:ce: + 7f:6a:77:32:c7:9b:bf:90:66:03:4b:67:d8:7b:fd:96:e1:39: + b4:54:14:c4:6d:53:6c:66:e1:a9:b2:87:ce:61:ee:15:04:e5: + 1d:84:da:e5:b6:39:7b:a6:89:ce:ff:0f:86:43:ea:e9:e3:af: + 89:b9:8f:05:56:0d:3a:93:51:82:1c:35:2a:e2:ca:80:d6:01: + 59:4f:7c:e5:9a:53:93:72:52:1a:92:d4:33:36:f2:db:ce:59: + 51:d5:2c:2c:66:55:c6:65:b4:94:2a:a3:97:87:6d:0f:fe:1e: + 37:f1 +-----BEGIN CERTIFICATE----- +MIICqDCCAhGgAwIBAgIJAKrzwmdXIUxsMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEVMBMG +A1UECgwMUGFobyBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdS +b290IENBMB4XDTEzMDcyOTE5MjEyOVoXDTIzMDcyNzE5MjEyOVowbTELMAkGA1UE +BhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNVBAcMBURlcmJ5MRUwEwYD +VQQKDAxQYWhvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxEDAOBgNVBAMMB1Jv +b3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKbPzEEWCKsjjwjJ787u +Q32k5EdqoDddMEjSVbZNSNEwUew1L7O8NTbmtCEeVFQjOLAdmdiF3rQbXHV+Zew0 +jt2g4vtPpl1GOG6jA/6YznKAyQdvGCdYfGZUN2tN+mbtVxWqkHZitQDQGaSHnx24 +NX649La2uyFy+7l9o8++xPONAgMBAAGjUDBOMB0GA1UdDgQWBBRKK2nWMR2jaOhG +b/tL8462jVEOvzAfBgNVHSMEGDAWgBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNV +HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAEd+gW86/W+fisz5PFHAeEw7zn9q +dzLHm7+QZgNLZ9h7/ZbhObRUFMRtU2xm4amyh85h7hUE5R2E2uW2OXumic7/D4ZD +6unjr4m5jwVWDTqTUYIcNSriyoDWAVlPfOWaU5NyUhqS1DM28tvOWVHVLCxmVcZl +tJQqo5eHbQ/+Hjfx +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/test-root-ca.key b/Sources/paho/test/ssl/test-root-ca.key new file mode 100644 index 0000000..3cb7148 --- /dev/null +++ b/Sources/paho/test/ssl/test-root-ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQCmz8xBFgirI48Iye/O7kN9pORHaqA3XTBI0lW2TUjRMFHsNS+z +vDU25rQhHlRUIziwHZnYhd60G1x1fmXsNI7doOL7T6ZdRjhuowP+mM5ygMkHbxgn +WHxmVDdrTfpm7VcVqpB2YrUA0Bmkh58duDV+uPS2trshcvu5faPPvsTzjQIDAQAB +AoGAFVhNqJ5rKYr5SISefPocBL3OwByyt6LjBM51TUiCYtIuCW2c1wDkRkwrDHnX +DJUdMdv3za8DmkROBnLQE/N9vEVhrfrDpBpU6ne/0tbxRlmDi1ihH+zgBUZkIkQo +kP5kQrV6Tfv7zhFv6cZzewRjGYzTwt8xWB54bKFlsJSlj/kCQQDY0AirnfIVyK+0 +mkqwYEiXWCQfkdRtbLBwpE8S/bbMQVb+Qxh8iCEdw3u1/c/GRFG/qUQ/54/Tetlx +ZWTTusuXAkEAxPY1+EyW90I8cDSBsrL+S47meut5Qp1Z/WspKjuZgozT7YnECK1k +JWyXIfBixMIqeQp+pVfVRtYSumvnVhAuewJAA3ylBw2NPShzGvZ4SQnjYPu76P4R +aoka9VTPKMEH1ZUfbwtpM2eFENN6A91HICstHWX9gQGaYI5TPO2ih30zlQJBAIRH +06FqVu3DJ3I4YW8R9eXrGHIvmaYapeikQuZhVs0uJdtf7i/hu+PClZIurzb0LLBU +UxBa+Bt2BOf9NkY/4ecCQQCYLGMiKrfckXC6VtQalLuEXkeE8spcdh/NV22Qpim5 +xfir6M2ZcPDxaFpPmSDSS1TRTaeulX/djUE35EdNPVP8 +-----END RSA PRIVATE KEY----- diff --git a/Sources/paho/test/ssl/test-signing-ca.crt b/Sources/paho/test/ssl/test-signing-ca.crt new file mode 100644 index 0000000..3a10151 --- /dev/null +++ b/Sources/paho/test/ssl/test-signing-ca.crt @@ -0,0 +1,57 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Paho Project, OU=Testing, CN=Root CA + Validity + Not Before: Jul 29 19:21:30 2013 GMT + Not After : Jul 28 19:21:30 2018 GMT + Subject: C=GB, ST=Derbyshire, O=Paho Project, OU=Testing, CN=Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:dc:26:78:40:ae:b2:ad:2f:26:12:0a:d5:b1:18: + 80:16:d8:88:be:0b:42:ce:32:ad:12:d5:f5:78:1b: + 35:28:f2:13:1b:05:09:fb:7e:d7:d9:a1:8a:0d:4a: + fe:95:37:d4:16:75:83:e4:6a:44:34:33:57:2e:49: + ba:bc:b4:cf:d0:c0:87:e0:bc:f0:60:76:14:00:d6: + eb:cb:f6:db:b3:43:f1:c8:4d:4a:0a:bb:e0:37:7c: + 8e:93:1f:a0:87:68:59:fe:0c:25:40:f3:7c:fd:71: + 90:55:ef:de:18:b4:08:86:c9:75:c2:99:2f:ce:12: + bf:c5:5e:cf:5f:f1:06:53:07 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 29:4D:6E:C7:F2:F7:71:72:DA:27:9C:9C:AB:DA:07:1D:47:9C:D8:41 + X509v3 Authority Key Identifier: + keyid:4A:2B:69:D6:31:1D:A3:68:E8:46:6F:FB:4B:F3:8E:B6:8D:51:0E:BF + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 48:ec:d7:80:8a:8f:82:a6:42:b1:89:2c:b9:4b:6d:0a:37:b8: + 72:19:05:de:75:80:0c:d6:41:97:b2:d7:fe:99:cb:7e:c4:0e: + 77:97:09:a8:9f:87:ff:0b:de:3f:1c:dc:1e:fe:09:36:a7:f5: + 54:9a:85:4e:fb:6f:27:fe:0f:29:45:61:8d:07:c6:0c:da:37: + 3d:a3:69:4b:82:71:e6:24:e0:87:a6:ee:d5:87:61:dd:8f:08: + fe:33:a6:1f:ae:b2:ae:1f:d8:2c:20:c8:a6:fc:33:0e:82:68: + 80:23:61:10:ad:5c:1d:80:d6:b1:5f:e4:af:66:6d:63:10:e4: + 96:e4 +-----BEGIN CERTIFICATE----- +MIICkzCCAfygAwIBAgIBATANBgkqhkiG9w0BAQUFADBtMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxFTATBgNVBAoMDFBh +aG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGluZzEQMA4GA1UEAwwHUm9vdCBDQTAe +Fw0xMzA3MjkxOTIxMzBaFw0xODA3MjgxOTIxMzBaMGAxCzAJBgNVBAYTAkdCMRMw +EQYDVQQIDApEZXJieXNoaXJlMRUwEwYDVQQKDAxQYWhvIFByb2plY3QxEDAOBgNV +BAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBANwmeECusq0vJhIK1bEYgBbYiL4LQs4yrRLV9XgbNSjyExsF +Cft+19mhig1K/pU31BZ1g+RqRDQzVy5Jury0z9DAh+C88GB2FADW68v227ND8chN +Sgq74Dd8jpMfoIdoWf4MJUDzfP1xkFXv3hi0CIbJdcKZL84Sv8Vez1/xBlMHAgMB +AAGjUDBOMB0GA1UdDgQWBBQpTW7H8vdxctonnJyr2gcdR5zYQTAfBgNVHSMEGDAW +gBRKK2nWMR2jaOhGb/tL8462jVEOvzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB +BQUAA4GBAEjs14CKj4KmQrGJLLlLbQo3uHIZBd51gAzWQZey1/6Zy37EDneXCaif +h/8L3j8c3B7+CTan9VSahU77byf+DylFYY0HxgzaNz2jaUuCceYk4Iem7tWHYd2P +CP4zph+usq4f2CwgyKb8Mw6CaIAjYRCtXB2A1rFf5K9mbWMQ5Jbk +-----END CERTIFICATE----- diff --git a/Sources/paho/test/ssl/test-signing-ca.key b/Sources/paho/test/ssl/test-signing-ca.key new file mode 100644 index 0000000..2826af0 --- /dev/null +++ b/Sources/paho/test/ssl/test-signing-ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDcJnhArrKtLyYSCtWxGIAW2Ii+C0LOMq0S1fV4GzUo8hMbBQn7 +ftfZoYoNSv6VN9QWdYPkakQ0M1cuSbq8tM/QwIfgvPBgdhQA1uvL9tuzQ/HITUoK +u+A3fI6TH6CHaFn+DCVA83z9cZBV794YtAiGyXXCmS/OEr/FXs9f8QZTBwIDAQAB +AoGAEEMDNPvylNpbvI9yU3+Uzps2FpusVqDlqfOGC1YvKhQflypbH2myNhA5q1uz +zH/wOax6jp/O4/A6619k3NWaWBUSDeD1jczdzzDB6Eq1+6oj1szwLBA5EQHz5tuM +0BIWVGv12bqY/LGBbYsIABBTr584rA3QSgM3K4SPxKKiyYECQQD6ELRf6hfd5qhs +8RJY5f3yXaV6rSpz8meht4VwMguiYwNBHrHAHxgumMfLiJ2PWa+6aFUxcWs93RfL +5Tzn2DtHAkEA4WADib1R05V3X2XcU9ursA0va5nPEtQ0fNJAUm4iJOtEElk61Ku4 +0KFokloTovpAgno+QxQdy1trwBz/ov2KQQJAaNeaGGCYUxPC57IHBDihSP1UROPX +Wbd3FYlRK+H/mLy0f5fz5F3lEJxDoCUOEi0DDT9zAIDR+qT4tibNa1LwPwJAQDtT +BtCUH487pE6tiqDSv6wiVbJSV/VuuBxcBKIqzQbYMbqIj9AZLiyyVvOhIRPditI4 +KHn1O93kSa56FQPZgQJAV0mCqYciPBU4z3qtLGIDqdzTszBh4U5cTu5M+TICrg20 +dtH2X0dETx7c2+7FDkr1ktVq9skJAXMw6mWM8FMYFg== +-----END RSA PRIVATE KEY----- diff --git a/Sources/paho/test/sync_client_test.c b/Sources/paho/test/sync_client_test.c new file mode 100644 index 0000000..31c776d --- /dev/null +++ b/Sources/paho/test/sync_client_test.c @@ -0,0 +1,1122 @@ +/******************************************************************* + Copyright (c) 2013, 2014 IBM Corp. + + All rights reserved. This program and the accompanying materials + are made available under the terms of the Eclipse Public License v1.0 + and Eclipse Distribution License v1.0 which accompany this distribution. + + The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html + and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + + Contributors: + Ian Craggs - initial implementation and/or documentation +*******************************************************************/ + +#include "MQTTClient.h" +#include +#include + +#if !defined(_WINDOWS) + #include + #include + #include + #include +#else +#include +#include +#define MAXHOSTNAMELEN 256 +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#endif + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + + +char* topics[] = {"TopicA", "TopicA/B", "Topic/C", "TopicA/C", "/TopicA"}; +char* wildtopics[] = {"TopicA/+", "+/C", "#", "/#", "/+", "+/+", "TopicA/#"}; +char* nosubscribe_topics[] = {"nosubscribe",}; + +struct Options +{ + char* connection; /**< connection to system under test. */ + char* clientid1; + char* clientid2; + char* username; + char* password; + int verbose; + int MQTTVersion; + int iterations; + int run_dollar_topics_test; + int run_subscribe_failure_test; +} options = +{ + "tcp://localhost:1883", + "myclientid", + "myclientid2", + NULL, + NULL, + 0, + MQTTVERSION_3_1_1, + 1, + 0, + 0, +}; + + +void usage() +{ + printf("options:\n connection, clientid1, clientid2, username, password, MQTTversion, iterations, verbose\n"); + exit(-1); +} + +void getopts(int argc, char** argv) +{ + int count = 1; + + while (count < argc) + { + if (strcmp(argv[count], "--dollar_topics_test") == 0 || strcmp(argv[count], "--$") == 0) + { + options.run_dollar_topics_test = 1; + printf("Running $ topics test\n"); + } + else if (strcmp(argv[count], "--subscribe_failure_test") == 0 || strcmp(argv[count], "-s") == 0) + { + options.run_subscribe_failure_test = 1; + printf("Running subscribe failure test\n"); + } + else if (strcmp(argv[count], "--connection") == 0) + { + if (++count < argc) + { + options.connection = argv[count]; + printf("Setting connection to %s\n", options.connection); + } + else + usage(); + } + else if (strcmp(argv[count], "--clientid1") == 0) + { + if (++count < argc) + { + options.clientid1 = argv[count]; + printf("Setting clientid1 to %s\n", options.clientid1); + } + else + usage(); + } + else if (strcmp(argv[count], "--clientid2") == 0) + { + if (++count < argc) + { + options.clientid2 = argv[count]; + printf("Setting clientid2 to %s\n", options.clientid2); + } + else + usage(); + } + else if (strcmp(argv[count], "--username") == 0) + { + if (++count < argc) + { + options.username = argv[count]; + printf("Setting username to %s\n", options.username); + } + else + usage(); + } + else if (strcmp(argv[count], "--password") == 0) + { + if (++count < argc) + { + options.password = argv[count]; + printf("Setting password to %s\n", options.password); + } + else + usage(); + } + else if (strcmp(argv[count], "--MQTTversion") == 0) + { + if (++count < argc) + { + options.MQTTVersion = atoi(argv[count]); + printf("Setting MQTT version to %d\n", options.MQTTVersion); + } + else + usage(); + } + else if (strcmp(argv[count], "--iterations") == 0) + { + if (++count < argc) + { + options.iterations = atoi(argv[count]); + printf("Setting iterations to %d\n", options.iterations); + } + else + usage(); + } + else if (strcmp(argv[count], "--verbose") == 0) + { + options.verbose = 1; + printf("\nSetting verbose on\n"); + } + count++; + } +} + + +#if defined(WIN32) || defined(_WINDOWS) +#define msleep Sleep +#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 msleep(A) usleep(A*1000) +#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 + +#define LOGA_DEBUG 0 +#define LOGA_INFO 1 +#include +#include +#include +void MyLog(int LOGA_level, char* format, ...) +{ + static char msg_buf[256]; + va_list args; + struct timeb ts; + + struct tm *timeinfo; + + if (LOGA_level == LOGA_DEBUG && options.verbose == 0) + return; + + ftime(&ts); + timeinfo = localtime(&ts.time); + strftime(msg_buf, 80, "%Y%m%d %H%M%S", timeinfo); + + sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm); + + va_start(args, format); + vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args); + va_end(args); + + printf("%s\n", msg_buf); + fflush(stdout); +} + + +int tests = 0; +int failures = 0; + + +void myassert(char* filename, int lineno, char* description, int value, char* format, ...) +{ + ++tests; + if (!value) + { + int count; + va_list args; + + ++failures; + printf("Assertion failed, file %s, line %d, description: %s\n", filename, lineno, description); + + va_start(args, format); + count = vprintf(format, args); + va_end(args); + if (count) + printf("\n"); + + //cur_output += sprintf(cur_output, "file %s, line %d \n", + // description, filename, lineno); + } + else + MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", 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) + +typedef struct +{ + char* topicName; + int topicLen; + MQTTClient_message* m; +} messageStruct; + +messageStruct messagesArrived[1000]; +int messageCount = 0; + +int messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m) +{ + messagesArrived[messageCount].topicName = topicName; + messagesArrived[messageCount].topicLen = topicLen; + messagesArrived[messageCount++].m = m; + MyLog(LOGA_DEBUG, "Callback: %d message received on topic %s is %.*s.", + messageCount, topicName, m->payloadlen, (char*)(m->payload)); + return 1; +} + + +void clearMessages() +{ + int i; + + for (i = 0; i < messageCount; ++i) + { + MQTTClient_free(messagesArrived[i].topicName); + MQTTClient_freeMessage(&messagesArrived[i].m); + } + messageCount = 0; +} + +void cleanup() +{ + // clean all client state + char* clientids[] = {options.clientid1, options.clientid2}; + int i, rc; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient aclient; + + MyLog(LOGA_INFO, "Cleaning up"); + + opts.keepAliveInterval = 20; + opts.cleansession = 1; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + for (i = 0; i < 2; ++i) + { + rc = MQTTClient_create(&aclient, options.connection, clientids[i], MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_disconnect(aclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + MQTTClient_destroy(&aclient); + } + + // clean retained messages + rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_subscribe(aclient, "#", 0); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(2000); // wait for all retained messages to arrive + + rc = MQTTClient_unsubscribe(aclient, "#"); + assert("Good rc from unsubscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + for (i = 0; i < messageCount; ++i) + { + if (messagesArrived[i].m->retained) + { + MyLog(LOGA_INFO, "Deleting retained message for topic %s", (char*)messagesArrived[i].topicName); + rc = MQTTClient_publish(aclient, messagesArrived[i].topicName, 0, "", 0, 1, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + } + } + + rc = MQTTClient_disconnect(aclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + MQTTClient_destroy(&aclient); + + clearMessages(); + + MyLog(LOGA_INFO, "Finished cleaning up"); +} + + +int basic_test() +{ + int i, rc; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient aclient; + + MyLog(LOGA_INFO, "Starting basic test"); + + tests = failures = 0; + + opts.keepAliveInterval = 20; + opts.cleansession = 1; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_disconnect(aclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_subscribe(aclient, topics[0], 0); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[0], 5, "qos 0", 0, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[0], 5, "qos 1", 1, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[0], 5, "qos 2", 2, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(1000); + + rc = MQTTClient_disconnect(aclient, 10000); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + assert("3 Messages received", messageCount == 3, "messageCount was %d", messageCount); + clearMessages(); + + /*opts.MQTTVersion = MQTTVERSION_3_1; + rc = MQTTClient_connect(aclient, &opts); // should fail - wrong protocol version + assert("Bad rc from connect", rc == MQTTCLIENT_FAILURE, "rc was %d", rc);*/ + + MQTTClient_destroy(&aclient); + + MyLog(LOGA_INFO, "Basic test %s", (failures == 0) ? "succeeded" : "failed"); + return failures; +} + + + +int offline_message_queueing_test() +{ + int i, rc; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient aclient; + MQTTClient bclient; + + MyLog(LOGA_INFO, "Offline message queueing test"); + + tests = failures = 0; + + opts.keepAliveInterval = 20; + opts.cleansession = 0; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_subscribe(aclient, wildtopics[5], 2); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_disconnect(aclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + opts.cleansession = 1; + rc = MQTTClient_connect(bclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(bclient, topics[1], 5, "qos 0", 0, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(bclient, topics[2], 5, "qos 1", 1, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(bclient, topics[3], 5, "qos 2", 2, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(2000); + + rc = MQTTClient_disconnect(bclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + MQTTClient_destroy(&bclient); + + opts.cleansession = 0; + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(1000); // receive the queued messages + + rc = MQTTClient_disconnect(aclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + MQTTClient_destroy(&aclient); + + assert("2 or 3 messages received", messageCount == 3 || messageCount == 2, "messageCount was %d", messageCount); + + MyLog(LOGA_INFO, "This server %s queueing QoS 0 messages for offline clients", (messageCount == 3) ? "is" : "is not"); + + clearMessages(); + + MyLog(LOGA_INFO, "Offline message queueing test %s", (failures == 0) ? "succeeded" : "failed"); + return failures; +} + + +int retained_message_test() +{ + int i, rc; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient aclient; + + MyLog(LOGA_INFO, "Retained message test"); + + tests = failures = 0; + + opts.keepAliveInterval = 20; + opts.cleansession = 1; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + assert("0 messages received", messageCount == 0, "messageCount was %d", messageCount); + + // set retained messages + rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[1], 5, "qos 0", 0, 1, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[2], 5, "qos 1", 1, 1, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[3], 5, "qos 2", 2, 1, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(1000); + + rc = MQTTClient_subscribe(aclient, wildtopics[5], 2); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(2000); + + rc = MQTTClient_disconnect(aclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + assert("3 messages received", messageCount == 3, "messageCount was %d", messageCount); + + for (i = 0; i < messageCount; ++i) + { + assert("messages should be retained", messagesArrived[i].m->retained, "retained was %d", + messagesArrived[i].m->retained); + MQTTClient_free(messagesArrived[i].topicName); + MQTTClient_freeMessage(&messagesArrived[i].m); + } + messageCount = 0; + + // clear retained messages + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[1], 0, "", 0, 1, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[2], 0, "", 1, 1, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[3], 0, "", 2, 1, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(200); // wait for QoS 2 exchange to be completed + rc = MQTTClient_subscribe(aclient, wildtopics[5], 2); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(200); + + rc = MQTTClient_disconnect(aclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + assert("0 messages received", messageCount == 0, "messageCount was %d", messageCount); + + MQTTClient_destroy(&aclient); + + MyLog(LOGA_INFO, "Retained message test %s", (failures == 0) ? "succeeded" : "failed"); + return failures; +} + +#define SOCKET_ERROR -1 + +int test6_socket_error(char* aString, int sock) +{ +#if defined(WIN32) + int errno; +#endif + +#if defined(WIN32) + errno = WSAGetLastError(); +#endif + if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && errno != EWOULDBLOCK) + { + if (strcmp(aString, "shutdown") != 0 || (errno != ENOTCONN && errno != ECONNRESET)) + printf("Socket error %d in %s for socket %d", errno, aString, sock); + } + return errno; +} + +int test6_socket_close(int socket) +{ + int rc; + +#if defined(WIN32) + if (shutdown(socket, SD_BOTH) == SOCKET_ERROR) + test6_socket_error("shutdown", socket); + if ((rc = closesocket(socket)) == SOCKET_ERROR) + test6_socket_error("close", socket); +#else + if (shutdown(socket, SHUT_RDWR) == SOCKET_ERROR) + test6_socket_error("shutdown", socket); + if ((rc = close(socket)) == SOCKET_ERROR) + test6_socket_error("close", socket); +#endif + return rc; +} + +typedef struct +{ + int socket; + time_t lastContact; +#if defined(OPENSSL) + SSL* ssl; + SSL_CTX* ctx; +#endif +} networkHandles; + + +typedef struct +{ + char* clientID; /**< the string id of the client */ + char* username; /**< MQTT v3.1 user name */ + char* password; /**< MQTT v3.1 password */ + unsigned int cleansession : 1; /**< MQTT clean session 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; + int connect_state : 4; + networkHandles net; +/* ... */ +} Clients; + + +typedef struct +{ + char* serverURI; + Clients* c; + MQTTClient_connectionLost* cl; + MQTTClient_messageArrived* ma; + MQTTClient_deliveryComplete* dc; + void* context; + + int connect_sem; + int rc; /* getsockopt return code in connect */ + int connack_sem; + int suback_sem; + int unsuback_sem; + void* pack; +} MQTTClients; + + +int will_message_test() +{ + int i, rc, count = 0; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient aclient, bclient; + + MyLog(LOGA_INFO, "Will message test"); + + tests = failures = 0; + + opts.keepAliveInterval = 2; + opts.cleansession = 1; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + opts.will = &wopts; + opts.will->message = "client not disconnected"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = topics[2]; + + rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_setCallbacks(bclient, NULL, NULL, messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + opts.keepAliveInterval = 20; + opts.will = NULL; + rc = MQTTClient_connect(bclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_subscribe(bclient, topics[2], 2); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(100); + + test6_socket_close(((MQTTClients*)aclient)->c->net.socket); + + while (messageCount == 0 && ++count < 10) + msleep(1000); + + rc = MQTTClient_disconnect(bclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + MQTTClient_destroy(&bclient); + + assert("will message received", messageCount == 1, "messageCount was %d", messageCount); + + rc = MQTTClient_disconnect(aclient, 100); + + MQTTClient_destroy(&aclient); + + MyLog(LOGA_INFO, "Will message test %s", (failures == 0) ? "succeeded" : "failed"); + return failures; +} + + +int overlapping_subscriptions_test() +{ + /* overlapping subscriptions. When there is more than one matching subscription for the same client for a topic, + the server may send back one message with the highest QoS of any matching subscription, or one message for + each subscription with a matching QoS. */ + + int i, rc; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient aclient; + char* topicList[] = {wildtopics[6], wildtopics[0]}; + int qosList[] = {2, 1}; + + MyLog(LOGA_INFO, "Starting overlapping subscriptions test"); + + clearMessages(); + tests = failures = 0; + + opts.keepAliveInterval = 20; + opts.cleansession = 1; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_subscribeMany(aclient, 2, topicList, qosList); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[3], strlen("overlapping topic filters") + 1, + "overlapping topic filters", 2, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(1000); + + assert("1 or 2 messages received", messageCount == 1 || messageCount == 2, "messageCount was %d", messageCount); + + if (messageCount == 1) + { + MyLog(LOGA_INFO, "This server is publishing one message for all matching overlapping subscriptions, not one for each."); + assert("QoS should be 2", messagesArrived[0].m->qos == 2, "QoS was %d", messagesArrived[0].m->qos); + } + else + { + MyLog(LOGA_INFO, "This server is publishing one message per each matching overlapping subscription."); + assert1("QoSs should be 1 and 2", + (messagesArrived[0].m->qos == 2 && messagesArrived[1].m->qos == 1) || + (messagesArrived[0].m->qos == 1 && messagesArrived[1].m->qos == 2), + "QoSs were %d %d", messagesArrived[0].m->qos, messagesArrived[1].m->qos); + } + + rc = MQTTClient_disconnect(aclient, 100); + + MQTTClient_destroy(&aclient); + + MyLog(LOGA_INFO, "Overlapping subscription test %s", (failures == 0) ? "succeeded" : "failed"); + return failures; +} + + +int keepalive_test() +{ + /* keepalive processing. We should be kicked off by the server if we don't send or receive any data, and don't send + any pings either. */ + + int i, rc, count = 0; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient aclient, bclient; + + MyLog(LOGA_INFO, "Starting keepalive test"); + + tests = failures = 0; + clearMessages(); + + opts.cleansession = 1; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + opts.will = &wopts; + opts.will->message = "keepalive expiry"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = topics[4]; + + opts.keepAliveInterval = 20; + rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_setCallbacks(bclient, NULL, NULL, messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_connect(bclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_subscribe(bclient, topics[4], 2); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + opts.keepAliveInterval = 2; + rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + while (messageCount == 0 && ++count < 20) + msleep(1000); + + rc = MQTTClient_disconnect(bclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + assert("Should have will message", messageCount == 1, "messageCount was %d", messageCount); + + rc = MQTTClient_disconnect(aclient, 100); + + MQTTClient_destroy(&aclient); + + MyLog(LOGA_INFO, "Keepalive test %s", (failures == 0) ? "succeeded" : "failed"); + return failures; +} + + + +int redelivery_on_reconnect_test() +{ + /* redelivery on reconnect. When a QoS 1 or 2 exchange has not been completed, the server should retry the + appropriate MQTT packets */ + + int i, rc, count = 0; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient aclient; + + MyLog(LOGA_INFO, "Starting redelivery on reconnect test"); + + tests = failures = 0; + clearMessages(); + + opts.keepAliveInterval = 0; + opts.cleansession = 0; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_subscribe(aclient, wildtopics[6], 2); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + MQTTClient_yield(); + + // no background processing because no callback has been set + rc = MQTTClient_publish(aclient, topics[1], 6, "qos 1", 2, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, topics[3], 6, "qos 2", 2, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_disconnect(aclient, 0); + + assert("No messages should have been received yet", messageCount == 0, "messageCount was %d", messageCount); + + rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + while (messageCount < 2 && ++count < 5) + msleep(1000); + + assert("Should have 2 messages", messageCount == 2, "messageCount was %d", messageCount); + + rc = MQTTClient_disconnect(aclient, 100); + + MQTTClient_destroy(&aclient); + + MyLog(LOGA_INFO, "Redelivery on reconnect test %s", (failures == 0) ? "succeeded" : "failed"); + return failures; +} + + + +int zero_length_clientid_test() +{ + int i, rc, count = 0; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient aclient; + + MyLog(LOGA_INFO, "Starting zero length clientid test"); + + tests = failures = 0; + clearMessages(); + + opts.keepAliveInterval = 0; + opts.cleansession = 0; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + rc = MQTTClient_create(&aclient, options.connection, "", MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("rc 2 from connect", rc == 2, "rc was %d", rc); // this should always fail + + opts.cleansession = 1; + rc = MQTTClient_connect(aclient, &opts); + assert("Connack rc should be 0 or 2", rc == MQTTCLIENT_SUCCESS || rc == 2, "rc was %d", rc); + + MyLog(LOGA_INFO, "This server %s support zero length clientids", (rc == 2) ? "does not" : "does"); + + if (rc == MQTTCLIENT_SUCCESS) + rc = MQTTClient_disconnect(aclient, 100); + + MQTTClient_destroy(&aclient); + + MyLog(LOGA_INFO, "Zero length clientid test %s", (failures == 0) ? "succeeded" : "failed"); + return failures; +} + + +int dollar_topics_test() +{ + /* $ topics. The specification says that a topic filter which starts with a wildcard does not match topic names that + begin with a $. Publishing to a topic which starts with a $ may not be allowed on some servers (which is entirely valid), + so this test will not work and should be omitted in that case. + */ + int i, rc, count = 0; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient aclient; + char dollartopic[20]; + + MyLog(LOGA_INFO, "Starting $ topics test"); + + sprintf(dollartopic, "$%s", topics[1]); + + clearMessages(); + + opts.keepAliveInterval = 5; + opts.cleansession = 1; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_subscribe(aclient, wildtopics[5], 2); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(1000); // wait for any retained messages, hopefully + clearMessages(); + + rc = MQTTClient_publish(aclient, topics[1], 20, "not sent to dollar topic", 1, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_publish(aclient, dollartopic, 20, "sent to dollar topic", 1, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + msleep(1000); + assert("Should have 1 message", messageCount == 1, "messageCount was %d", messageCount); + + rc = MQTTClient_disconnect(aclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + MQTTClient_destroy(&aclient); + + MyLog(LOGA_INFO, "$ topics test %s", (failures == 0) ? "succeeded" : "failed"); + return failures; +} + + +int subscribe_failure_test() +{ + /* Subscribe failure. A new feature of MQTT 3.1.1 is the ability to send back negative reponses to subscribe + requests. One way of doing this is to subscribe to a topic which is not allowed to be subscribed to. + */ + int i, rc, count = 0; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient aclient; + int subqos = 2; + + MyLog(LOGA_INFO, "Starting subscribe failure test"); + + clearMessages(); + + opts.keepAliveInterval = 5; + opts.cleansession = 1; + opts.username = options.username; + opts.password = options.password; + opts.MQTTVersion = options.MQTTVersion; + + rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_connect(aclient, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_subscribeMany(aclient, 1, &nosubscribe_topics[0], &subqos); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + assert("0x80 rc from subscribe", subqos == 0x80, "subqos was %d", subqos); + + rc = MQTTClient_disconnect(aclient, 100); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + MQTTClient_destroy(&aclient); + + MyLog(LOGA_INFO, "Subscribe failure test %s", (failures == 0) ? "succeeded" : "failed"); + return failures; +} + + +int main(int argc, char** argv) +{ + int i; + int all_failures = 0; + + getopts(argc, argv); + + for (i = 0; i < options.iterations; ++i) + { + cleanup(); + all_failures += basic_test() + + offline_message_queueing_test() + + retained_message_test() + + will_message_test() + + overlapping_subscriptions_test() + + keepalive_test() + + redelivery_on_reconnect_test() + + zero_length_clientid_test(); + + if (options.run_dollar_topics_test) + all_failures += dollar_topics_test(); + + if (options.run_subscribe_failure_test) + all_failures += subscribe_failure_test(); + } + + MyLog(LOGA_INFO, "Test suite %s", (all_failures == 0) ? "succeeded" : "failed"); +} + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/paho/test/test1.c b/Sources/paho/test/test1.c new file mode 100644 index 0000000..b0e18e7 --- /dev/null +++ b/Sources/paho/test/test1.c @@ -0,0 +1,1168 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - MQTT 3.1.1 support + *******************************************************************************/ + + +/** + * @file + * Tests for the MQ Telemetry MQTT C client + */ + + +/* +#if !defined(_RTSHEADER) + #include +#endif +*/ + +#include "MQTTClient.h" +#include +#include + +#if !defined(_WINDOWS) + #include + #include + #include + #include +#else +#include +#include +#define MAXHOSTNAMELEN 256 +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#define setenv(a, b, c) _putenv_s(a, b) +#endif + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +void usage() +{ + printf("help!!\n"); + exit(-1); +} + +struct Options +{ + char* connection; /**< connection to system under test. */ + char** haconnections; + int hacount; + int verbose; + int test_no; + int MQTTVersion; + int iterations; +} options = +{ + "tcp://m2m.eclipse.org:1883", + NULL, + 0, + 0, + 0, + MQTTVERSION_DEFAULT, + 1, +}; + +void getopts(int argc, char** argv) +{ + int count = 1; + + while (count < argc) + { + if (strcmp(argv[count], "--test_no") == 0) + { + if (++count < argc) + options.test_no = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--connection") == 0) + { + if (++count < argc) + { + options.connection = argv[count]; + printf("\nSetting connection to %s\n", options.connection); + } + else + usage(); + } + else if (strcmp(argv[count], "--haconnections") == 0) + { + if (++count < argc) + { + char* tok = strtok(argv[count], " "); + options.hacount = 0; + options.haconnections = malloc(sizeof(char*) * 5); + while (tok) + { + options.haconnections[options.hacount] = malloc(strlen(tok) + 1); + strcpy(options.haconnections[options.hacount], tok); + options.hacount++; + tok = strtok(NULL, " "); + } + } + else + usage(); + } + else if (strcmp(argv[count], "--MQTTversion") == 0) + { + if (++count < argc) + { + options.MQTTVersion = atoi(argv[count]); + printf("setting MQTT version to %d\n", options.MQTTVersion); + } + else + usage(); + } + else if (strcmp(argv[count], "--iterations") == 0) + { + if (++count < argc) + options.iterations = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--verbose") == 0) + { + options.verbose = 1; + printf("\nSetting verbose on\n"); + } + count++; + } +} + + +#define LOGA_DEBUG 0 +#define LOGA_INFO 1 +#include +#include +#include +void MyLog(int LOGA_level, char* format, ...) +{ + static char msg_buf[256]; + va_list args; + struct timeb ts; + + struct tm *timeinfo; + + if (LOGA_level == LOGA_DEBUG && options.verbose == 0) + return; + + ftime(&ts); + timeinfo = localtime(&ts.time); + strftime(msg_buf, 80, "%Y%m%d %H%M%S", timeinfo); + + sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm); + + va_start(args, format); + vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args); + va_end(args); + + printf("%s\n", msg_buf); + fflush(stdout); +} + + +#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 + + +#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) + +int tests = 0; +int failures = 0; +FILE* xml; +START_TIME_TYPE global_start_time; +char output[3000]; +char* cur_output = output; + + +void write_test_result() +{ + long duration = elapsed(global_start_time); + + fprintf(xml, " time=\"%ld.%.3ld\" >\n", duration / 1000, duration % 1000); + if (cur_output != output) + { + fprintf(xml, "%s", output); + cur_output = output; + } + fprintf(xml, "\n"); +} + + +void myassert(char* filename, int lineno, char* description, int value, char* format, ...) +{ + ++tests; + if (!value) + { + va_list args; + + ++failures; + MyLog(LOGA_INFO, "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, "file %s, line %d \n", + description, filename, lineno); + } + else + MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description); +} + + +/********************************************************************* + +Test1: single-threaded client + +*********************************************************************/ +void test1_sendAndReceive(MQTTClient* c, int qos, char* test_topic) +{ + MQTTClient_deliveryToken dt; + MQTTClient_message pubmsg = MQTTClient_message_initializer; + MQTTClient_message* m = NULL; + char* topicName = NULL; + int topicLen; + int i = 0; + int iterations = 50; + int rc; + + MyLog(LOGA_DEBUG, "%d messages at QoS %d", iterations, qos); + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = qos; + pubmsg.retained = 0; + + for (i = 0; i< iterations; ++i) + { + if (i % 10 == 0) + rc = MQTTClient_publish(c, test_topic, pubmsg.payloadlen, pubmsg.payload, pubmsg.qos, pubmsg.retained, &dt); + else + rc = MQTTClient_publishMessage(c, test_topic, &pubmsg, &dt); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + if (qos > 0) + { + rc = MQTTClient_waitForCompletion(c, dt, 1000L); + assert("Good rc from waitforCompletion", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + } + + rc = MQTTClient_receive(c, &topicName, &topicLen, &m, 1000); + assert("Good rc from receive", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + if (topicName) + { + MyLog(LOGA_DEBUG, "Message received on topic %s is %.*s", topicName, m->payloadlen, (char*)(m->payload)); + if (pubmsg.payloadlen != m->payloadlen || + memcmp(m->payload, pubmsg.payload, m->payloadlen) != 0) + { + failures++; + MyLog(LOGA_INFO, "Error: wrong data - received lengths %d %d", pubmsg.payloadlen, m->payloadlen); + break; + } + MQTTClient_free(topicName); + MQTTClient_freeMessage(&m); + } + else + printf("No message received within timeout period\n"); + } + + /* receive any outstanding messages */ + MQTTClient_receive(c, &topicName, &topicLen, &m, 1000); + while (topicName) + { + printf("Message received on topic %s is %.*s.\n", topicName, m->payloadlen, (char*)(m->payload)); + MQTTClient_free(topicName); + MQTTClient_freeMessage(&m); + MQTTClient_receive(c, &topicName, &topicLen, &m, 1000); + } +} + + +int test1(struct Options options) +{ + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test1"; + + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTClient_connect(c, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + if (rc != MQTTCLIENT_SUCCESS) + goto exit; + + rc = MQTTClient_subscribe(c, test_topic, subsqos); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + test1_sendAndReceive(c, 0, test_topic); + test1_sendAndReceive(c, 1, test_topic); + test1_sendAndReceive(c, 2, test_topic); + + MyLog(LOGA_DEBUG, "Stopping\n"); + + rc = MQTTClient_unsubscribe(c, test_topic); + assert("Unsubscribe successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + rc = MQTTClient_disconnect(c, 0); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + /* Just to make sure we can connect again */ + rc = MQTTClient_connect(c, &opts); + assert("Connect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + rc = MQTTClient_disconnect(c, 0); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + MQTTClient_destroy(&c); + +exit: + MyLog(LOGA_INFO, "TEST1: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + write_test_result(); + return failures; +} + + +/********************************************************************* + +Test2: multi-threaded client using callbacks + +*********************************************************************/ +volatile int test2_arrivedcount = 0; +int test2_deliveryCompleted = 0; +MQTTClient_message test2_pubmsg = MQTTClient_message_initializer; + +void test2_deliveryComplete(void* context, MQTTClient_deliveryToken dt) +{ + ++test2_deliveryCompleted; +} + +int test2_messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m) +{ + ++test2_arrivedcount; + MyLog(LOGA_DEBUG, "Callback: %d message received on topic %s is %.*s.", + test2_arrivedcount, topicName, m->payloadlen, (char*)(m->payload)); + if (test2_pubmsg.payloadlen != m->payloadlen || + memcmp(m->payload, test2_pubmsg.payload, m->payloadlen) != 0) + { + failures++; + MyLog(LOGA_INFO, "Error: wrong data received lengths %d %d\n", test2_pubmsg.payloadlen, m->payloadlen); + } + MQTTClient_free(topicName); + MQTTClient_freeMessage(&m); + return 1; +} + + +void test2_sendAndReceive(MQTTClient* c, int qos, char* test_topic) +{ + MQTTClient_deliveryToken dt; + int i = 0; + int iterations = 50; + int rc = 0; + int wait_seconds = 0; + + test2_deliveryCompleted = 0; + + MyLog(LOGA_INFO, "%d messages at QoS %d", iterations, qos); + test2_pubmsg.payload = "a much longer message that we can shorten to the extent that we need to"; + test2_pubmsg.payloadlen = 27; + test2_pubmsg.qos = qos; + test2_pubmsg.retained = 0; + + for (i = 1; i <= iterations; ++i) + { + if (i % 10 == 0) + rc = MQTTClient_publish(c, test_topic, test2_pubmsg.payloadlen, test2_pubmsg.payload, + test2_pubmsg.qos, test2_pubmsg.retained, NULL); + else + rc = MQTTClient_publishMessage(c, test_topic, &test2_pubmsg, &dt); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + #if defined(WIN32) + Sleep(100); + #else + usleep(100000L); + #endif + + wait_seconds = 10; + while ((test2_arrivedcount < i) && (wait_seconds-- > 0)) + { + MyLog(LOGA_DEBUG, "Arrived %d count %d", test2_arrivedcount, i); + #if defined(WIN32) + Sleep(1000); + #else + usleep(1000000L); + #endif + } + assert("Message Arrived", wait_seconds > 0, + "Time out waiting for message %d\n", i ); + } + if (qos > 0) + { + /* MQ Telemetry can send a message to a subscriber before the server has + completed the QoS 2 handshake with the publisher. For QoS 1 and 2, + allow time for the final delivery complete callback before checking + that all expected callbacks have been made */ + wait_seconds = 10; + while ((test2_deliveryCompleted < iterations) && (wait_seconds-- > 0)) + { + MyLog(LOGA_DEBUG, "Delivery Completed %d count %d", test2_deliveryCompleted, i); + #if defined(WIN32) + Sleep(1000); + #else + usleep(1000000L); + #endif + } + assert("All Deliveries Complete", wait_seconds > 0, + "Number of deliveryCompleted callbacks was %d\n", + test2_deliveryCompleted); + } +} + + +int test2(struct Options options) +{ + char* testname = "test2"; + int subsqos = 2; + /* TODO - usused - remove ? MQTTClient_deliveryToken* dt = NULL; */ + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + int rc = 0; + char* test_topic = "C client test2"; + + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic";*/ + rc = MQTTClient_connect(c, &opts); + //assert("Not authorized", rc == 5, "rc was %d\n", rc); + +#if 0 + /* successful connection (RC = 0) */ + opts.username = "Admin"; + opts.password = "Admin"; + opts.will = NULL; + rc = MQTTClient_connect(c, &opts); + assert("successful connection", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + MQTTClient_disconnect(c, 0); + MQTTClient_destroy(&c); +#endif + +/* TODO - unused - remove ? exit: */ + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + + +/********************************************************************* + +Test 4: client persistence 1 + + +*********************************************************************/ +int test4_run(int qos) +{ + char* testname = "test 4"; + char* topic = "Persistence test 1"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_message* m = NULL; + char* topicName = NULL; + int topicLen; + MQTTClient_deliveryToken* tokens = NULL; + int mytoken = -99; + char buffer[100]; + int count = 3; + int i, rc; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 4 - persistence, qos %d", qos); + + MQTTClient_create(&c, options.connection, "xrctest1_test_4", MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + + opts.keepAliveInterval = 20; + opts.reliable = 0; + opts.MQTTVersion = options.MQTTVersion; + if (options.haconnections != NULL) + { + opts.serverURIs = options.haconnections; + opts.serverURIcount = options.hacount; + } + + MyLog(LOGA_DEBUG, "Cleanup by connecting clean session\n"); + opts.cleansession = 1; + if ((rc = MQTTClient_connect(c, &opts)) != 0) + { + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + return -1; + } + opts.cleansession = 0; + MQTTClient_disconnect(c, 0); + + MyLog(LOGA_DEBUG, "Connecting\n"); + if ((rc = MQTTClient_connect(c, &opts)) != 0) + { + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + return -1; + } + + /* subscribe so we can get messages back */ + rc = MQTTClient_subscribe(c, topic, subsqos); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + /* send messages so that we can receive the same ones */ + for (i = 0; i < count; ++i) + { + sprintf(buffer, "Message sequence no %d", i); + rc = MQTTClient_publish(c, topic, 10, buffer, qos, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + } + + /* disconnect immediately without receiving the incoming messages */ + MQTTClient_disconnect(c, 0); /* now there should be "orphaned" publications */ + + rc = MQTTClient_getPendingDeliveryTokens(c, &tokens); + assert("getPendingDeliveryTokens rc == 0", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + assert("should get some tokens back", tokens != NULL, "tokens was %p", tokens); + if (tokens) + { + int i = 0; + + while (tokens[i] != -1) + MyLog(LOGA_DEBUG, "Pending delivery token %d", tokens[i++]); + MQTTClient_free(tokens); + assert1("no of tokens should be count", i == count, "no of tokens %d count %d", i, count); + mytoken = tokens[0]; + } + + MQTTClient_destroy(&c); /* force re-reading persistence on create */ + + MQTTClient_create(&c, options.connection, "xrctest1_test_4", MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + + rc = MQTTClient_getPendingDeliveryTokens(c, &tokens); + assert("getPendingDeliveryTokens rc == 0", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + assert("should get some tokens back", tokens != NULL, "tokens was %p", tokens); + if (tokens) + { + int i = 0; + while (tokens[i] != -1) + MyLog(LOGA_DEBUG, "Pending delivery token %d", tokens[i++]); + MQTTClient_free(tokens); + assert1("no of tokens should be count", i == count, "no of tokens %d count %d", i, count); + } + + MyLog(LOGA_DEBUG, "Reconnecting"); + if (MQTTClient_connect(c, &opts) != 0) + { + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + return -1; + } + + for (i = 0; i < count; ++i) + { + int dup = 0; + do + { + dup = 0; + MQTTClient_receive(c, &topicName, &topicLen, &m, 1000); + if (m && m->dup) + { + assert("No duplicates should be received for qos 2", qos == 1, "qos is %d", qos); + MyLog(LOGA_DEBUG, "Duplicate message id %d", m->msgid); + MQTTClient_freeMessage(&m); + MQTTClient_free(topicName); + dup = 1; + } + } while (dup == 1); + assert("should get a message", m != NULL, "m was %p", m); + if (m) + { + MyLog(LOGA_DEBUG, "Received message id %d", m->msgid); + assert("topicName is correct", strcmp(topicName, topic) == 0, "topicName is %s", topicName); + MQTTClient_freeMessage(&m); + MQTTClient_free(topicName); + } + } + + MQTTClient_yield(); /* allow any unfinished protocol exchanges to finish */ + + rc = MQTTClient_getPendingDeliveryTokens(c, &tokens); + assert("getPendingDeliveryTokens rc == 0", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + assert("should get no tokens back", tokens == NULL, "tokens was %p", tokens); + + MQTTClient_disconnect(c, 0); + + MQTTClient_destroy(&c); + +/* TODO - unused -remove? exit: */ + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + + return failures; +} + + +int test4(struct Options options) +{ + int rc = 0; + fprintf(xml, "\n", elapsed(global_start_time) / 1000); + if (cur_output != output) + { + fprintf(xml, "%s", output); + cur_output = output; + } + fprintf(xml, "\n"); + return rc; +} + + +/********************************************************************* + +Test 5: disconnect with quiesce timeout should allow exchanges to complete + +*********************************************************************/ +int test5(struct Options options) +{ + char* testname = "test 5"; + /* TODO - unused -remove? char summaryname[50]; */ + /* TODO - unused -remove? FILE *outfile = NULL; */ + char* topic = "Persistence test 2"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + /* TODO - unused -remove? MQTTClient_message* m = NULL; */ + /* TODO - unused -remove? char* topicName = NULL; */ + MQTTClient_deliveryToken* tokens = NULL; + /* TODO - unused -remove? int mytoken = -99; */ + char buffer[100]; + int count = 5; + int i, rc; + + fprintf(xml, " Callback: connection lost\n", (c == test6_c1) ? "Client-1" : "Client-2"); + test6_connection_lost_called = 1; +} + +void test6_deliveryComplete(void* context, MQTTClient_deliveryToken token) +{ + printf("Client-2 -> Callback: publish complete for token %d\n", token); +} + +char* test6_will_topic = "C Test 2: will topic"; +char* test6_will_message = "will message from Client-1"; + +int test6_messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m) +{ + MQTTClient c = (MQTTClient)context; + printf("%s -> Callback: message received on topic '%s' is '%.*s'.\n", + (c == test6_c1) ? "Client-1" : "Client-2", topicName, m->payloadlen, (char*)(m->payload)); + if (c == test6_c2 && strcmp(topicName, test6_will_topic) == 0 && memcmp(m->payload, test6_will_message, m->payloadlen) == 0) + test6_will_message_arrived = 1; + MQTTClient_free(topicName); + MQTTClient_freeMessage(&m); + return 1; +} + + +int test6_socket_error(char* aString, int sock) +{ +#if defined(WIN32) + int errno; +#endif + +#if defined(WIN32) + errno = WSAGetLastError(); +#endif + if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && errno != EWOULDBLOCK) + { + if (strcmp(aString, "shutdown") != 0 || (errno != ENOTCONN && errno != ECONNRESET)) + printf("Socket error %d in %s for socket %d", errno, aString, sock); + } + return errno; +} + +#if !defined(SOCKET_ERROR) +#define SOCKET_ERROR -1 +#endif + +int test6_socket_close(int socket) +{ + int rc; + +#if defined(WIN32) + if (shutdown(socket, SD_BOTH) == SOCKET_ERROR) + test6_socket_error("shutdown", socket); + if ((rc = closesocket(socket)) == SOCKET_ERROR) + test6_socket_error("close", socket); +#else + if (shutdown(socket, SHUT_RDWR) == SOCKET_ERROR) + test6_socket_error("shutdown", socket); + if ((rc = close(socket)) == SOCKET_ERROR) + test6_socket_error("close", socket); +#endif + return rc; +} + +typedef struct +{ + int socket; + time_t lastContact; +#if defined(OPENSSL) + SSL* ssl; + SSL_CTX* ctx; +#endif +} networkHandles; + + +typedef struct +{ + char* clientID; /**< the string id of the client */ + char* username; /**< MQTT v3.1 user name */ + char* password; /**< MQTT v3.1 password */ + unsigned int cleansession : 1; /**< MQTT clean session 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; + int connect_state : 4; + networkHandles net; +/* ... */ +} Clients; + + +typedef struct +{ + char* serverURI; + Clients* c; + MQTTClient_connectionLost* cl; + MQTTClient_messageArrived* ma; + MQTTClient_deliveryComplete* dc; + void* context; + + int connect_sem; + int rc; /* getsockopt return code in connect */ + int connack_sem; + int suback_sem; + int unsuback_sem; + void* pack; +} MQTTClients; + + +int test6(struct Options options) +{ + char* testname = "test6"; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_connectOptions opts2 = MQTTClient_connectOptions_initializer; + int rc, count; + char* mqttsas_topic = "MQTTSAS topic"; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 6 - connectionLost and will messages"); + fprintf(xml, "message = test6_will_message; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = test6_will_topic; + if (options.haconnections != NULL) + { + opts.serverURIs = options.haconnections; + opts.serverURIcount = options.hacount; + } + + /* Client-1 with Will options */ + rc = MQTTClient_create(&test6_c1, options.connection, "Client_1", MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + if (rc != MQTTCLIENT_SUCCESS) + goto exit; + + rc = MQTTClient_setCallbacks(test6_c1, (void*)test6_c1, test6_connectionLost, test6_messageArrived, test6_deliveryComplete); + assert("good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + if (rc != MQTTCLIENT_SUCCESS) + goto exit; + + /* Connect to the broker */ + rc = MQTTClient_connect(test6_c1, &opts); + assert("good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + if (rc != MQTTCLIENT_SUCCESS) + goto exit; + + /* Client - 2 (multi-threaded) */ + rc = MQTTClient_create(&test6_c2, options.connection, "Client_2", MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + /* Set the callback functions for the client */ + rc = MQTTClient_setCallbacks(test6_c2, (void*)test6_c2, test6_connectionLost, test6_messageArrived, test6_deliveryComplete); + assert("good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + /* Connect to the broker */ + opts2.keepAliveInterval = 20; + opts2.cleansession = 1; + MyLog(LOGA_INFO, "Connecting Client_2 ..."); + rc = MQTTClient_connect(test6_c2, &opts2); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + rc = MQTTClient_subscribe(test6_c2, test6_will_topic, 2); + assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc); + + /* now send the command which will break the connection and cause the will message to be sent */ + /*rc = MQTTClient_publish(test6_c1, mqttsas_topic, strlen("TERMINATE"), "TERMINATE", 0, 0, NULL); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);*/ + test6_socket_close(((MQTTClients*)test6_c1)->c->net.socket); + + MyLog(LOGA_INFO, "Waiting to receive the will message"); + count = 0; + while (++count < 40) + { + #if defined(WIN32) + Sleep(1000L); + #else + sleep(1); + #endif + if (test6_will_message_arrived == 1 && test6_connection_lost_called == 1) + break; + } + assert("will message arrived", test6_will_message_arrived == 1, + "will_message_arrived was %d\n", test6_will_message_arrived); + assert("connection lost called", test6_connection_lost_called == 1, + "connection_lost_called %d\n", test6_connection_lost_called); + + rc = MQTTClient_unsubscribe(test6_c2, test6_will_topic); + assert("Good rc from unsubscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + rc = MQTTClient_isConnected(test6_c2); + assert("Client-2 still connected", rc == 1, "isconnected is %d", rc); + + rc = MQTTClient_isConnected(test6_c1); + assert("Client-1 not connected", rc == 0, "isconnected is %d", rc); + + /* rc = MQTTClient_disconnect(test6_c2, 100L); */ + /* assert("Good rc from disconnect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); */ + + MQTTClient_destroy(&test6_c1); + /* MQTTClient_destroy(&test6_c2); */ + +exit: + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.\n", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +int main(int argc, char** argv) +{ + int rc = 0; + int (*tests[])() = {NULL, test1, test2, test3, test4, test5, test6}; + int i; + + xml = fopen("TEST-test1.xml", "w"); + fprintf(xml, "\n", (int)(ARRAY_SIZE(tests) - 1)); + + setenv("MQTT_C_CLIENT_TRACE", "ON", 1); + setenv("MQTT_C_CLIENT_TRACE_LEVEL", "ERROR", 1); + + getopts(argc, argv); + + for (i = 0; i < options.iterations; ++i) + { + if (options.test_no == 0) + { /* run all the tests */ + for (options.test_no = 1; options.test_no < ARRAY_SIZE(tests); ++options.test_no) + rc += tests[options.test_no](options); /* return number of failures. 0 = test succeeded */ + } + else + rc = tests[options.test_no](options); /* run just the selected test */ + } + + if (rc == 0) + MyLog(LOGA_INFO, "verdict pass"); + else + MyLog(LOGA_INFO, "verdict fail"); + + fprintf(xml, "\n"); + fclose(xml); + return rc; +} diff --git a/Sources/paho/test/test3.c b/Sources/paho/test/test3.c new file mode 100644 index 0000000..a6fc291 --- /dev/null +++ b/Sources/paho/test/test3.c @@ -0,0 +1,1553 @@ +/******************************************************************************* + * Copyright (c) 2012, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Allan Stockdill-Mander - initial API and implementation and/or initial documentation + *******************************************************************************/ + + +/** + * @file + * SSL tests for the MQ Telemetry MQTT C client + */ + +#include "MQTTClient.h" +#include +#include + +#if !defined(_WINDOWS) + #include + #include + #include + #include +#else +#include +#include +#define MAXHOSTNAMELEN 256 +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#define snprintf _snprintf +#define setenv(a, b, c) _putenv_s(a, b) +#endif + +#if defined(IOS) +char skeytmp[1024]; +char ckeytmp[1024]; +char persistenceStore[1024]; +#else +char* persistenceStore = NULL; +#endif + +#if 0 +#include /* For general log messages */ +#define MyLog logaLine +#else +#define LOGA_DEBUG 0 +#define LOGA_INFO 1 +#include +#include +#include + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +void usage() +{ + printf("Options:\n"); + printf("\t--test_no - Run test number \n"); + printf("\t--connection - Connect to for tests\n"); + printf("\t--haconnections \"\" - Use \"\" as the list of servers for HA tests (space separated)\n"); + printf("\t--client_key - Use as the client certificate for SSL authentication\n"); + printf("\t--client_key_pass - Use to access the private key in the client certificate\n"); + printf("\t--client_privatekey - Client private key file if not in certificate file\n"); + printf("\t--server_key - Use as the trusted certificate for server\n"); + printf("\t--verbose - Enable verbose output \n"); + printf("\tserver connection URLs should be in the form; (tcp|ssl)://hostname:port\n"); + printf("\t--help - This help output\n"); + exit(-1); +} + +struct Options +{ + char connection[100]; + char mutual_auth_connection[100]; /**< connection to system under test. */ + char nocert_mutual_auth_connection[100]; + char server_auth_connection[100]; + char anon_connection[100]; + char** haconnections; /**< connection to system under test. */ + int hacount; + char* client_key_file; + char* client_key_pass; + char* server_key_file; + char* client_private_key_file; + int verbose; + int test_no; +} options = +{ + "ssl://m2m.eclipse.org:18883", + "ssl://m2m.eclipse.org:18884", + "ssl://m2m.eclipse.org:18887", + "ssl://m2m.eclipse.org:18885", + "ssl://m2m.eclipse.org:18886", + NULL, + 0, + "../../../test/ssl/client.pem", + NULL, + "../../../test/ssl/test-root-ca.crt", + NULL, + 0, + 0, +}; + + +char* test_map[] = +{ + "none", + "1", // 1 + "2a_s", // 2 + "2a_m", // 3 + "2b", // 4 + "2c", // 5 + "3a_s", // 6 + "3a_m", // 7 + "3b", // 8 + "4s", // 9 + "4m", // 10 + "5a", // 11 + "5b", // 12 + "5c", // 13 +}; + + +void getopts(int argc, char** argv) +{ + int count = 1; + + while (count < argc) + { + if (strcmp(argv[count], "--help") == 0) + usage(); + else if (strcmp(argv[count], "--test_no") == 0) + { + if (++count < argc) + { + int i; + + for (i = 1; i < ARRAY_SIZE(test_map); ++i) + { + if (strcmp(argv[count], test_map[i]) == 0) + { + options.test_no = i; + break; + } + } + if (options.test_no == 0) + options.test_no = atoi(argv[count]); + + } + else + usage(); + } + else if (strcmp(argv[count], "--hostname") == 0) + { + if (++count < argc) + { + sprintf(options.connection, "ssl://%s:18883", argv[count]); + printf("Setting connection to %s\n", options.connection); + sprintf(options.mutual_auth_connection, "ssl://%s:18884", argv[count]); + printf("Setting mutual_auth_connection to %s\n", options.mutual_auth_connection); + sprintf(options.nocert_mutual_auth_connection, "ssl://%s:18887", argv[count]); + printf("Setting nocert_mutual_auth_connection to %s\n", + options.nocert_mutual_auth_connection); + sprintf(options.server_auth_connection, "ssl://%s:18885", argv[count]); + printf("Setting server_auth_connection to %s\n", options.server_auth_connection); + sprintf(options.anon_connection, "ssl://%s:18886", argv[count]); + printf("Setting anon_connection to %s\n", options.anon_connection); + } + else + usage(); + } + else if (strcmp(argv[count], "--haconnections") == 0) + { + if (++count < argc) + { + char* tok = strtok(argv[count], " "); + options.hacount = 0; + options.haconnections = malloc(sizeof(char*) * 5); + while (tok) + { + options.haconnections[options.hacount] = malloc(strlen(tok) + 1); + strcpy(options.haconnections[options.hacount], tok); + options.hacount++; + tok = strtok(NULL, " "); + } + } + else + usage(); + } + else if (strcmp(argv[count], "--client_key") == 0) + { + if (++count < argc) + { +#if defined(IOS) + strcat(ckeytmp, getenv("HOME")); + strcat(ckeytmp, argv[count]); + options.client_key_file = ckeytmp; +#else + options.client_key_file = argv[count]; +#endif + } + else + usage(); + } + else if (strcmp(argv[count], "--client_privatekey") == 0) + { + if (++count < argc) + { +#if defined(IOS) + strcat(ckeytmp, getenv("HOME")); + strcat(ckeytmp, argv[count]); + options.client_private_key_file = ckeytmp; +#else + options.client_private_key_file = argv[count]; +#endif + } + else + usage(); + } + else if (strcmp(argv[count], "--client_key_pass") == 0) + { + if (++count < argc) + options.client_key_pass = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--server_key") == 0) + { + if (++count < argc) + { +#if defined(IOS) + strcat(skeytmp, getenv("HOME")); + strcat(skeytmp, argv[count]); + options.server_key_file = skeytmp; +#else + options.server_key_file = argv[count]; +#endif + } + else + usage(); + } + else if (strcmp(argv[count], "--verbose") == 0) + { + options.verbose = 1; + //TODO + printf("\nSetting verbose on\n"); + } + count++; + } +#if defined(IOS) + strcpy(persistenceStore, getenv("HOME")); + strcat(persistenceStore, "/Library/Caches"); +#endif +} + +void MyLog(int LOGA_level, char* format, ...) +{ + static char msg_buf[256]; + va_list args; + struct timeb ts; + + struct tm *timeinfo; + + if (LOGA_level == LOGA_DEBUG && options.verbose == 0) + return; + + ftime(&ts); + timeinfo = localtime(&ts.time); + strftime(msg_buf, 80, "%Y%m%d %H%M%S", timeinfo); + + sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm); + + va_start(args, format); + vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args); + va_end(args); + + printf("%s\n", msg_buf); + fflush(stdout); +} +#endif + + +#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 + + +#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) + + +int tests = 0; +int failures = 0; +FILE* xml; +START_TIME_TYPE global_start_time; +char output[3000]; +char* cur_output = output; + + +void write_test_result() +{ + long duration = elapsed(global_start_time); + + fprintf(xml, " time=\"%ld.%.3ld\" >\n", duration / 1000, duration % 1000); + if (cur_output != output) + { + fprintf(xml, "%s", output); + cur_output = output; + } + fprintf(xml, "\n"); +} + + +int 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, "file %s, line %d \n", + description, filename, lineno); + } + else + MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description); + return value; +} + + +/********************************************************************* + +Test: single-threaded client + +*********************************************************************/ +void singleThread_sendAndReceive(MQTTClient* c, int qos, char* test_topic) +{ + MQTTClient_deliveryToken dt; + MQTTClient_message pubmsg = MQTTClient_message_initializer; + MQTTClient_message* m = NULL; + char* topicName = NULL; + int topicLen; + int i = 0; + int iterations = 50; + int rc; + + MyLog(LOGA_DEBUG, "%d messages at QoS %d", iterations, qos); + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = qos; + pubmsg.retained = 0; + + for (i = 0; i< iterations; ++i) + { + if (i % 10 == 0) + rc = MQTTClient_publish(c, test_topic, pubmsg.payloadlen, pubmsg.payload, pubmsg.qos, pubmsg.retained, NULL); + else + rc = MQTTClient_publishMessage(c, test_topic, &pubmsg, &dt); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + if (qos > 0) + { + rc = MQTTClient_waitForCompletion(c, dt, 20000L); + assert("Good rc from waitforCompletion", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + } + + rc = MQTTClient_receive(c, &topicName, &topicLen, &m, 10000); + assert("Good rc from receive", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + if (topicName) + { + MyLog(LOGA_DEBUG, "Message received on topic %s is %.*s", topicName, m->payloadlen, (char*)(m->payload)); + if (pubmsg.payloadlen != m->payloadlen || + memcmp(m->payload, pubmsg.payload, m->payloadlen) != 0) + { + failures++; + MyLog(LOGA_INFO, "Error: wrong data - received lengths %d %d", pubmsg.payloadlen, m->payloadlen); + break; + } + MQTTClient_free(topicName); + MQTTClient_freeMessage(&m); + } + else + printf("No message received within timeout period\n"); + } + + /* receive any outstanding messages */ + MQTTClient_receive(c, &topicName, &topicLen, &m, 1000); + while (topicName) + { + printf("Message received on topic %s is %.*s.\n", topicName, m->payloadlen, (char*)(m->payload)); + MQTTClient_free(topicName); + MQTTClient_freeMessage(&m); + MQTTClient_receive(c, &topicName, &topicLen, &m, 1000); + } +} + +/********************************************************************* + +Test: multi-threaded client using callbacks + +*********************************************************************/ +volatile int multiThread_arrivedcount = 0; +int multiThread_deliveryCompleted = 0; +MQTTClient_message multiThread_pubmsg = MQTTClient_message_initializer; + +void multiThread_deliveryComplete(void* context, MQTTClient_deliveryToken dt) +{ + ++multiThread_deliveryCompleted; +} + +int multiThread_messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m) +{ + ++multiThread_arrivedcount; + MyLog(LOGA_DEBUG, "Callback: %d message received on topic %s is %.*s.", + multiThread_arrivedcount, topicName, m->payloadlen, (char*)(m->payload)); + if (multiThread_pubmsg.payloadlen != m->payloadlen || + memcmp(m->payload, multiThread_pubmsg.payload, m->payloadlen) != 0) + { + failures++; + MyLog(LOGA_INFO, "Error: wrong data received lengths %d %d\n", multiThread_pubmsg.payloadlen, m->payloadlen); + } + MQTTClient_free(topicName); + MQTTClient_freeMessage(&m); + return 1; +} + + +void multiThread_sendAndReceive(MQTTClient* c, int qos, char* test_topic) +{ + MQTTClient_deliveryToken dt; + int i = 0; + int iterations = 50; + int rc = 0; + int wait_seconds = 0; + + multiThread_deliveryCompleted = 0; + multiThread_arrivedcount = 0; + + MyLog(LOGA_DEBUG, "%d messages at QoS %d", iterations, qos); + multiThread_pubmsg.payload = "a much longer message that we can shorten to the extent that we need to"; + multiThread_pubmsg.payloadlen = 27; + multiThread_pubmsg.qos = qos; + multiThread_pubmsg.retained = 0; + + for (i = 1; i <= iterations; ++i) + { + if (i % 10 == 0) + rc = MQTTClient_publish(c, test_topic, multiThread_pubmsg.payloadlen, multiThread_pubmsg.payload, + multiThread_pubmsg.qos, multiThread_pubmsg.retained, NULL); + else + rc = MQTTClient_publishMessage(c, test_topic, &multiThread_pubmsg, &dt); + assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + #if defined(WIN32) + Sleep(100); + #else + usleep(100000L); + #endif + + wait_seconds = 20; + while ((multiThread_arrivedcount < i) && (wait_seconds-- > 0)) + { + MyLog(LOGA_DEBUG, "Arrived %d count %d", multiThread_arrivedcount, i); + #if defined(WIN32) + Sleep(1000); + #else + usleep(1000000L); + #endif + } + assert("Message Arrived", wait_seconds > 0, + "Time out waiting for message %d\n", i ); + } + if (qos > 0) + { + /* MQ Telemetry can send a message to a subscriber before the server has + completed the QoS 2 handshake with the publisher. For QoS 1 and 2, + allow time for the final delivery complete callback before checking + that all expected callbacks have been made */ + wait_seconds = 10; + while ((multiThread_deliveryCompleted < iterations) && (wait_seconds-- > 0)) + { + MyLog(LOGA_DEBUG, "Delivery Completed %d count %d", multiThread_deliveryCompleted, i); + #if defined(WIN32) + Sleep(1000); + #else + usleep(1000000L); + #endif + } + assert("All Deliveries Complete", wait_seconds > 0, + "Number of deliveryCompleted callbacks was %d\n", + multiThread_deliveryCompleted); + } +} + + +/********************************************************************* + +Test1: SSL connection to non SSL MQTT server + +*********************************************************************/ +int test1(struct Options options) +{ + char* testname = "test1"; + char* test_topic = "C client SSL test1"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting SSL test 1 - connection to nonSSL MQTT server"); + fprintf(xml, "trustStore = options.server_key_file; /*file of certificates trusted by client*/ + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Connect should fail", rc == MQTTCLIENT_FAILURE, "rc was %d ", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + + +/********************************************************************* + +Test2a: Mutual SSL Authentication - Certificates in place on client and server - single threaded + +*********************************************************************/ + +int test2a_s(struct Options options) +{ + char* testname = "test2a_s"; + char* test_topic = "C client test2a_s"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 2a_s - Mutual SSL authentication - single threaded client using receive"); + fprintf(xml, "trustStore = options.server_key_file; /*file of certificates trusted by client*/ + opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + if (options.client_key_pass) + opts.ssl->privateKeyPassword = options.client_key_pass; + if (options.client_private_key_file) + opts.ssl->privateKey = options.client_private_key_file; + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + rc = MQTTClient_subscribe(c, test_topic, subsqos); + if (!(assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + singleThread_sendAndReceive(c, 0, test_topic); + singleThread_sendAndReceive(c, 1, test_topic); + singleThread_sendAndReceive(c, 2, test_topic); + + MyLog(LOGA_DEBUG, "Stopping\n"); + + rc = MQTTClient_unsubscribe(c, test_topic); + if (!(assert("Unsubscribe successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + /* Just to make sure we can connect again */ + rc = MQTTClient_connect(c, &opts); + if (!(assert("Connect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + +Test2a: Mutual SSL Authentication - Certificates in place on client and server - multi threaded + +*********************************************************************/ + +int test2a_m(struct Options options) +{ + char* testname = "test2a_m"; + char* test_topic = "C client test2a_m"; + int subsqos = 2; + /* TODO - usused - remove ? MQTTClient_deliveryToken* dt = NULL; */ + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 2a_m - Mutual SSL authentication - multi-threaded client using callbacks"); + fprintf(xml, "trustStore = options.server_key_file; /*file of certificates trusted by client*/ + opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + if (options.client_key_pass) + opts.ssl->privateKeyPassword = options.client_key_pass; + if (options.client_private_key_file) + opts.ssl->privateKey = options.client_private_key_file; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + //opts.ssl->enabledServerCertAuth = 1; + + rc = MQTTClient_setCallbacks(c, NULL, NULL, multiThread_messageArrived, multiThread_deliveryComplete); + if (!(assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + rc = MQTTClient_subscribe(c, test_topic, subsqos); + if (!(assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + multiThread_sendAndReceive(c, 0, test_topic); + multiThread_sendAndReceive(c, 1, test_topic); + multiThread_sendAndReceive(c, 2, test_topic); + + MyLog(LOGA_DEBUG, "Stopping"); + + rc = MQTTClient_unsubscribe(c, test_topic); + if (!(assert("Unsubscribe successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + +Test2b: Mutual SSL Authentication - Server does not have Client cert, connection fails + +*********************************************************************/ +int test2b(struct Options options) +{ + char* testname = "test2b"; + char* test_topic = "C client test2b"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 2b - connection to SSL MQTT server with clientauth=req but server does not have client cert"); + fprintf(xml, "trustStore = options.server_key_file; /*file of certificates trusted by client*/ + opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + if (options.client_key_pass) + opts.ssl->privateKeyPassword = options.client_key_pass; + if (options.client_private_key_file) + opts.ssl->privateKey = options.client_private_key_file; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + //opts.ssl->enabledServerCertAuth = 0; + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Bad rc from connect", rc == MQTTCLIENT_FAILURE, "rc was %d\n", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + +Test2c: Mutual SSL Authentication - Client does not have Server cert + +*********************************************************************/ +int test2c(struct Options options) +{ + char* testname = "test2c"; + char* test_topic = "C client test2c"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 2c - connection to SSL MQTT server, server auth enabled but unknown cert"); + fprintf(xml, "trustStore = options.server_key_file; /*file of certificates trusted by client*/ + opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + if (options.client_key_pass) + opts.ssl->privateKeyPassword = options.client_key_pass; + if (options.client_private_key_file) + opts.ssl->privateKey = options.client_private_key_file; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + //opts.ssl->enabledServerCertAuth = 0; + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Good rc from connect", rc == MQTTCLIENT_FAILURE, "rc was %d", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + +Test3a: Server Authentication - server certificate in client trust store - single threaded + +*********************************************************************/ + +int test3a_s(struct Options options) +{ + char* testname = "test3a_s"; + char* test_topic = "C client test3a_s"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 3a_s - Server authentication - single threaded client using receive"); + fprintf(xml, "trustStore = options.server_key_file; /*file of certificates trusted by client*/ + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + rc = MQTTClient_subscribe(c, test_topic, subsqos); + if (!(assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + + singleThread_sendAndReceive(c, 0, test_topic); + singleThread_sendAndReceive(c, 1, test_topic); + singleThread_sendAndReceive(c, 2, test_topic); + + MyLog(LOGA_DEBUG, "Stopping\n"); + + rc = MQTTClient_unsubscribe(c, test_topic); + if (!(assert("Unsubscribe successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + /* Just to make sure we can connect again */ + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Connect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + +Test3a: Server Authentication - server certificate in client trust store - multi threaded + +*********************************************************************/ + +int test3a_m(struct Options options) +{ + char* testname = "test3a_m"; + char* test_topic = "C client test3a_m"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 3a_m - Server authentication - multi-threaded client using callbacks"); + fprintf(xml, "trustStore = options.server_key_file; /*file of certificates trusted by client*/ + + rc = MQTTClient_setCallbacks(c, NULL, NULL, multiThread_messageArrived, multiThread_deliveryComplete); + if (!(assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + rc = MQTTClient_subscribe(c, test_topic, subsqos); + if (!(assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + multiThread_sendAndReceive(c, 0, test_topic); + multiThread_sendAndReceive(c, 1, test_topic); + multiThread_sendAndReceive(c, 2, test_topic); + + MyLog(LOGA_DEBUG, "Stopping\n"); + + rc = MQTTClient_unsubscribe(c, test_topic); + if (!(assert("Unsubscribe successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + +Test3b: Server Authentication - Client does not have server cert + +*********************************************************************/ +int test3b(struct Options options) +{ + char* testname = "test3b"; + char* test_topic = "C client test3b"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 3b - connection to SSL MQTT server with clientauth=opt but client does not have server cert"); + fprintf(xml, "enableServerCertAuth = 0; + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_subscribe(c, test_topic, subsqos); + if (!(assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + singleThread_sendAndReceive(c, 0, test_topic); + singleThread_sendAndReceive(c, 1, test_topic); + singleThread_sendAndReceive(c, 2, test_topic); + + MyLog(LOGA_DEBUG, "Stopping\n"); + + rc = MQTTClient_unsubscribe(c, test_topic); + if (!(assert("Unsubscribe successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + /* Just to make sure we can connect again */ + rc = MQTTClient_connect(c, &opts); + if (!(assert("Connect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + +Test4_m: Accept invalid server certificates - multi threaded + +*********************************************************************/ + +int test4_m(struct Options options) +{ + char* testname = "test4_m"; + char* test_topic = "C client test4_m"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 4_m - accept invalid server certificates - multi-threaded"); + fprintf(xml, "enableServerCertAuth = 0; + + rc = MQTTClient_setCallbacks(c, NULL, NULL, multiThread_messageArrived, multiThread_deliveryComplete); + if (!(assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_subscribe(c, test_topic, subsqos); + if (!(assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + multiThread_sendAndReceive(c, 0, test_topic); + multiThread_sendAndReceive(c, 1, test_topic); + multiThread_sendAndReceive(c, 2, test_topic); + + MyLog(LOGA_DEBUG, "Stopping"); + + rc = MQTTClient_unsubscribe(c, test_topic); + if (!(assert("Unsubscribe successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + +Test5a: Anonymous ciphers - server auth disabled + +*********************************************************************/ + +int test5a(struct Options options) +{ + char* testname = "test5a"; + char* test_topic = "C client SSL test5a"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting SSL test 5a - Anonymous ciphers - server authentication disabled"); + fprintf(xml, "enabledCipherSuites = "aNULL"; + opts.ssl->enableServerCertAuth = 0; + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_subscribe(c, test_topic, subsqos); + if (!(assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + singleThread_sendAndReceive(c, 0, test_topic); + singleThread_sendAndReceive(c, 1, test_topic); + singleThread_sendAndReceive(c, 2, test_topic); + + MyLog(LOGA_DEBUG, "Stopping\n"); + + rc = MQTTClient_unsubscribe(c, test_topic); + if (!(assert("Unsubscribe successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + /* Just to make sure we can connect again */ + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Connect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + +Test5b: Anonymous ciphers - server auth enabled + +*********************************************************************/ + +int test5b(struct Options options) +{ + char* testname = "test5b"; + char* test_topic = "C client SSL test5b"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting SSL test 5b - Anonymous ciphers - server authentication enabled"); + fprintf(xml, "trustStore = /*file of certificates trusted by client*/ + //opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + //if (options.client_key_pass != NULL) opts.ssl->privateKeyPassword = options.client_key_pass; + opts.ssl->enabledCipherSuites = "aNULL"; + opts.ssl->enableServerCertAuth = 1; + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_subscribe(c, test_topic, subsqos); + if (!(assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + singleThread_sendAndReceive(c, 0, test_topic); + singleThread_sendAndReceive(c, 1, test_topic); + singleThread_sendAndReceive(c, 2, test_topic); + + MyLog(LOGA_DEBUG, "Stopping\n"); + + rc = MQTTClient_unsubscribe(c, test_topic); + if (!(assert("Unsubscribe successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + + /* Just to make sure we can connect again */ + rc = MQTTClient_connect(c, &opts); + if (!(assert("Connect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + rc = MQTTClient_disconnect(c, 0); + if (!(assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + + +/********************************************************************* + +Test5c: Anonymous ciphers - client not using anonymous ciphers + +*********************************************************************/ + +int test5c(struct Options options) +{ + char* testname = "test5c"; + char* test_topic = "C client SSL test5c"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + MQTTClient_SSLOptions sslopts = MQTTClient_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting SSL test 5c - Anonymous ciphers - client not using anonymous cipher"); + fprintf(xml, "trustStore = /*file of certificates trusted by client*/ + //opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + //if (options.client_key_pass != NULL) opts.ssl->privateKeyPassword = options.client_key_pass; + opts.ssl->enabledCipherSuites = "DEFAULT"; + opts.ssl->enableServerCertAuth = 0; + + MyLog(LOGA_DEBUG, "Connecting"); + + rc = MQTTClient_connect(c, &opts); + if (!(assert("Good rc from connect", rc == MQTTCLIENT_FAILURE, "rc was %d", rc))) + goto exit; + +exit: + MQTTClient_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +typedef struct +{ + char* clientID; /**< the string id of the client */ + char* username; /**< MQTT v3.1 user name */ + char* password; /**< MQTT v3.1 password */ + unsigned int cleansession : 1; /**< MQTT clean session 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; + unsigned int connect_state : 2; + int socket; + int msgID; + int keepAliveInterval; + int retryInterval; + int maxInflightMessages; + time_t lastContact; + void* will; + void* inboundMsgs; + void* outboundMsgs; /**< in flight */ + void* messageQueue; + void* phandle; /* the persistence handle */ + MQTTClient_persistence* persistence; /* a persistence implementation */ + int connectOptionsVersion; +} Clients; + +typedef struct +{ + char* serverURI; + Clients* c; + MQTTClient_connectionLost* cl; + MQTTClient_messageArrived* ma; + MQTTClient_deliveryComplete* dc; + void* context; + + int connect_sem; + int rc; /* getsockopt return code in connect */ + int connack_sem; + int suback_sem; + int unsuback_sem; + void* pack; +} MQTTClients; + + +int main(int argc, char** argv) +{ + int* numtests = &tests; + int rc = 0; + int (*tests[])() = {NULL, test1, test2a_s, test2a_m, test2b, test2c, test3a_s, test3a_m, test3b, test4_s, test4_m, /*test5a, test5b,test5c */}; + //MQTTClient_nameValue* info; + + xml = fopen("TEST-test3.xml", "w"); + fprintf(xml, "\n", (int)(ARRAY_SIZE(tests) - 1)); + + setenv("MQTT_C_CLIENT_TRACE", "ON", 1); + setenv("MQTT_C_CLIENT_TRACE_LEVEL", "ERROR", 1); + getopts(argc, argv); + if (options.test_no == 0) + { /* run all the tests */ + for (options.test_no = 1; options.test_no < ARRAY_SIZE(tests); ++options.test_no) + rc += tests[options.test_no](options); /* return number of failures. 0 = test succeeded */ + } + else + rc = tests[options.test_no](options); /* run just the selected test */ + + MyLog(LOGA_INFO, "Total tests run: %d", *numtests); + if (rc == 0) + MyLog(LOGA_INFO, "verdict pass"); + else + MyLog(LOGA_INFO, "verdict fail"); + + fprintf(xml, "\n"); + fclose(xml); + + return rc; +} diff --git a/Sources/paho/test/test4.c b/Sources/paho/test/test4.c new file mode 100644 index 0000000..31edbf4 --- /dev/null +++ b/Sources/paho/test/test4.c @@ -0,0 +1,1499 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - MQTT 3.1.1 support + *******************************************************************************/ + + +/** + * @file + * Tests for the Paho Asynchronous MQTT C client + */ + + +/* +#if !defined(_RTSHEADER) + #include +#endif +*/ + +#include "MQTTAsync.h" +#include +#include + +#if !defined(_WINDOWS) + #include + #include + #include + #include +#else +#include +#include +#define MAXHOSTNAMELEN 256 +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#endif + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +void usage() +{ + printf("help!!\n"); + exit(-1); +} + +struct Options +{ + char* connection; /**< connection to system under test. */ + int verbose; + int test_no; + int size; /**< size of big message */ + int MQTTVersion; + int iterations; +} options = +{ + "iot.eclipse.org:1883", + 0, + -1, + 10000, + MQTTVERSION_DEFAULT, + 1, +}; + +void getopts(int argc, char** argv) +{ + int count = 1; + + while (count < argc) + { + if (strcmp(argv[count], "--test_no") == 0) + { + if (++count < argc) + options.test_no = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--size") == 0) + { + if (++count < argc) + options.size = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--connection") == 0) + { + if (++count < argc) + options.connection = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--MQTTversion") == 0) + { + if (++count < argc) + { + options.MQTTVersion = atoi(argv[count]); + printf("setting MQTT version to %d\n", options.MQTTVersion); + } + else + usage(); + } + else if (strcmp(argv[count], "--iterations") == 0) + { + if (++count < argc) + options.iterations = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--verbose") == 0) + options.verbose = 1; + count++; + } +} + +#if 0 +#include /* For general log messages */ +#define MyLog logaLine +#else +#define LOGA_DEBUG 0 +#define LOGA_INFO 1 +#include +#include +#include +void MyLog(int LOGA_level, char* format, ...) +{ + static char msg_buf[256]; + va_list args; + struct timeb ts; + + struct tm *timeinfo; + + if (LOGA_level == LOGA_DEBUG && options.verbose == 0) + return; + + ftime(&ts); + timeinfo = localtime(&ts.time); + strftime(msg_buf, 80, "%Y%m%d %H%M%S", timeinfo); + + sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm); + + va_start(args, format); + vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args); + va_end(args); + + printf("%s\n", msg_buf); + fflush(stdout); +} +#endif + + +#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 + +#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) + +int tests = 0; +int failures = 0; +FILE* xml; +START_TIME_TYPE global_start_time; +char output[3000]; +char* cur_output = output; + +void write_test_result() +{ + long duration = elapsed(global_start_time); + + fprintf(xml, " time=\"%ld.%.3ld\" >\n", duration / 1000, duration % 1000); + if (cur_output != output) + { + fprintf(xml, "%s", output); + cur_output = output; + } + fprintf(xml, "\n"); +} + +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, "file %s, line %d \n", + description, filename, lineno); + } + else + MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description); +} + +volatile int test_finished = 0; + +char* test_topic = "async test topic"; + + +void test1_onDisconnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MyLog(LOGA_DEBUG, "In onDisconnect callback %p", c); + test_finished = 1; +} + + +void test1_onUnsubscribe(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In onUnsubscribe onSuccess callback %p", c); + opts.onSuccess = test1_onDisconnect; + opts.context = c; + + rc = MQTTAsync_disconnect(c, &opts); + assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +int test1_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) +{ + MQTTAsync c = (MQTTAsync)context; + static int message_count = 0; + int rc; + + MyLog(LOGA_DEBUG, "In messageArrived callback %p", c); + + if (++message_count == 1) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = 2; + pubmsg.retained = 0; + rc = MQTTAsync_sendMessage(c, test_topic, &pubmsg, &opts); + } + else + { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + opts.onSuccess = test1_onUnsubscribe; + opts.context = c; + rc = MQTTAsync_unsubscribe(c, test_topic, &opts); + assert("Unsubscribe successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + + return 1; +} + +void test1_onSubscribe(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In subscribe onSuccess callback %p granted qos %d", c, response->alt.qos); + + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = 2; + pubmsg.retained = 0; + + rc = MQTTAsync_send(c, test_topic, pubmsg.payloadlen, pubmsg.payload, pubmsg.qos, pubmsg.retained, NULL); +} + + +void test1_onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + opts.onSuccess = test1_onSubscribe; + opts.context = c; + + rc = MQTTAsync_subscribe(c, test_topic, 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished = 1; +} + + +/********************************************************************* + +Test1: Basic connect, subscribe send and receive. + +*********************************************************************/ +int test1(struct Options options) +{ + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test1"; + + MyLog(LOGA_INFO, "Starting test 1 - asynchronous connect"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test1_onConnect; + opts.onFailure = NULL; + opts.context = c; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + rc = 0; + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + MQTTAsync_destroy(&c); + +exit: + MyLog(LOGA_INFO, "TEST1: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + write_test_result(); + return failures; +} + +int test2_onFailure_called = 0; + +void test2_onFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In connect onFailure callback, context %p", context); + + test2_onFailure_called++; + test_finished = 1; +} + + +void test2_onConnect(void* context, MQTTAsync_successData* response) +{ + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p\n", context); + + assert("Connect should not succeed", 0, "connect success callback was called", 0); + + test_finished = 1; +} + +/********************************************************************* + +Test2: connect timeout + +*********************************************************************/ +int test2(struct Options options) +{ + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test2"; + + test_finished = 0; + + MyLog(LOGA_INFO, "Starting test 2 - connect timeout"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test2_onConnect; + opts.onFailure = test2_onFailure; + opts.context = c; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + rc = 0; + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + MQTTAsync_destroy(&c); + +exit: + assert("Connect onFailure should be called once", test2_onFailure_called == 1, + "connect onFailure was called %d times", test2_onFailure_called); + + MyLog(LOGA_INFO, "TEST2: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + write_test_result(); + return failures; +} + + +typedef struct +{ + MQTTAsync c; + int index; + char clientid[24]; + char test_topic[100]; + int message_count; +} client_data; + + +void test3_onDisconnect(void* context, MQTTAsync_successData* response) +{ + client_data* cd = (client_data*)context; + MyLog(LOGA_DEBUG, "In onDisconnect callback for client \"%s\"", cd->clientid); + test_finished++; +} + + +void test3_onPublish(void* context, MQTTAsync_successData* response) +{ + client_data* cd = (client_data*)context; + MyLog(LOGA_DEBUG, "In QoS 0 onPublish callback for client \"%s\"", cd->clientid); +} + + +void test3_onUnsubscribe(void* context, MQTTAsync_successData* response) +{ + client_data* cd = (client_data*)context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In onUnsubscribe onSuccess callback \"%s\"", cd->clientid); + opts.onSuccess = test3_onDisconnect; + opts.context = cd; + + rc = MQTTAsync_disconnect(cd->c, &opts); + assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +int test3_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) +{ + client_data* cd = (client_data*)context; + int rc; + + MyLog(LOGA_DEBUG, "In messageArrived callback \"%s\" message count ", cd->clientid); + + if (++cd->message_count == 1) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 25; + pubmsg.qos = 1; + pubmsg.retained = 0; + rc = MQTTAsync_sendMessage(cd->c, cd->test_topic, &pubmsg, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + else if (cd->message_count == 2) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + pubmsg.payload = "a QoS 0 message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 29; + pubmsg.qos = 0; + pubmsg.retained = 0; + opts.context = cd; + opts.onSuccess = test3_onPublish; + + rc = MQTTAsync_sendMessage(cd->c, cd->test_topic, &pubmsg, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + else + { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + opts.onSuccess = test3_onUnsubscribe; + opts.context = cd; + rc = MQTTAsync_unsubscribe(cd->c, cd->test_topic, &opts); + assert("Unsubscribe successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + return 1; +} + +void test3_onSubscribe(void* context, MQTTAsync_successData* response) +{ + client_data* cd = (client_data*)context; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In subscribe onSuccess callback \"%s\"", cd->clientid); + + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = 2; + pubmsg.retained = 0; + + rc = MQTTAsync_send(cd->c, cd->test_topic, pubmsg.payloadlen, pubmsg.payload, pubmsg.qos, pubmsg.retained, NULL); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +void test3_onConnect(void* context, MQTTAsync_successData* response) +{ + client_data* cd = (client_data*)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, \"%s\"", cd->clientid); + opts.onSuccess = test3_onSubscribe; + opts.context = cd; + + rc = MQTTAsync_subscribe(cd->c, cd->test_topic, 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished++; +} + + +void test3_onFailure(void* context, MQTTAsync_failureData* response) +{ + client_data* cd = (client_data*)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + assert("Should have connected", 0, "%s failed to connect\n", cd->clientid); + MyLog(LOGA_DEBUG, "In connect onFailure callback, \"%s\" rc %d\n", cd->clientid, response ? response->code : -999); + if (response && response->message) + MyLog(LOGA_DEBUG, "In connect onFailure callback, \"%s\"\n", response->message); + + test_finished++; +} + + +/********************************************************************* + +Test3: More than one client object - simultaneous working. + +*********************************************************************/ +int test3(struct Options options) +{ + #define num_clients 10 + int subsqos = 2; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + int i; + client_data clientdata[num_clients]; + + test_finished = 0; + MyLog(LOGA_INFO, "Starting test 3 - multiple connections"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.onSuccess = test3_onConnect; + opts.onFailure = test3_onFailure; + opts.context = &clientdata[i]; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(clientdata[i].c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + while (test_finished < num_clients) + { + MyLog(LOGA_DEBUG, "num_clients %d test_finished %d\n", num_clients, test_finished); + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + } + + MyLog(LOGA_DEBUG, "TEST3: destroying clients"); + + for (i = 0; i < num_clients; ++i) + MQTTAsync_destroy(&clientdata[i].c); + +//exit: + MyLog(LOGA_INFO, "TEST3: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + write_test_result(); + return failures; +} + + +void* test4_payload = NULL; +int test4_payloadlen = 0; + +void test4_onPublish(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + + MyLog(LOGA_DEBUG, "In publish onSuccess callback, context %p", context); +} + +int test4_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) +{ + MQTTAsync c = (MQTTAsync)context; + static int message_count = 0; + int rc, i; + + MyLog(LOGA_DEBUG, "In messageArrived callback %p", c); + + assert("Message size correct", message->payloadlen == test4_payloadlen, + "message size was %d", message->payloadlen); + + for (i = 0; i < options.size; ++i) + { + if (((char*)test4_payload)[i] != ((char*)message->payload)[i]) + { + assert("Message contents correct", ((char*)test4_payload)[i] != ((char*)message->payload)[i], + "message content was %c", ((char*)message->payload)[i]); + break; + } + } + + if (++message_count == 1) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + pubmsg.payload = test4_payload; + pubmsg.payloadlen = test4_payloadlen; + pubmsg.qos = 1; + pubmsg.retained = 0; + opts.onSuccess = test4_onPublish; + opts.context = c; + + rc = MQTTAsync_sendMessage(c, test_topic, &pubmsg, &opts); + } + else if (message_count == 2) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + pubmsg.payload = test4_payload; + pubmsg.payloadlen = test4_payloadlen; + pubmsg.qos = 0; + pubmsg.retained = 0; + opts.onSuccess = test4_onPublish; + opts.context = c; + rc = MQTTAsync_sendMessage(c, test_topic, &pubmsg, &opts); + } + else + { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + opts.onSuccess = test1_onUnsubscribe; + opts.context = c; + rc = MQTTAsync_unsubscribe(c, test_topic, &opts); + assert("Unsubscribe successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + + return 1; +} + + +void test4_onSubscribe(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc, i; + + MyLog(LOGA_DEBUG, "In subscribe onSuccess callback %p", c); + + pubmsg.payload = test4_payload = malloc(options.size); + pubmsg.payloadlen = test4_payloadlen = options.size; + + srand(33); + for (i = 0; i < options.size; ++i) + ((char*)pubmsg.payload)[i] = rand() % 256; + + pubmsg.qos = 2; + pubmsg.retained = 0; + + rc = MQTTAsync_send(c, test_topic, pubmsg.payloadlen, pubmsg.payload, pubmsg.qos, pubmsg.retained, NULL); +} + + +void test4_onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + opts.onSuccess = test4_onSubscribe; + opts.context = c; + + rc = MQTTAsync_subscribe(c, test_topic, 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished = 1; +} + + +/********************************************************************* + +Test4: Send and receive big messages + +*********************************************************************/ +int test4(struct Options options) +{ + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test4"; + + test_finished = failures = 0; + MyLog(LOGA_INFO, "Starting test 4 - big messages"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test4_onConnect; + opts.onFailure = NULL; + opts.context = c; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + rc = 0; + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(1000L); + #endif + + MQTTAsync_destroy(&c); + +exit: + MyLog(LOGA_INFO, "TEST4: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + write_test_result(); + return failures; +} + + +void test5_onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + MyLog(LOGA_DEBUG, "In connect onFailure callback, context %p", context); + + MyLog(LOGA_INFO, "Connack rc is %d", response ? response->code : -999); + + test_finished = 1; +} + + +void test5_onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + MyLog(LOGA_DEBUG, "In connect onFailure callback, context %p", context); + + test_finished = 1; +} + + +/******************************************************************** + +Test5: Connack return codes + +*********************************************************************/ +int test5(struct Options options) +{ + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test1"; + + test_finished = failures = 0; + MyLog(LOGA_INFO, "Starting test 5 - connack return codes"); + fprintf(xml, "code); + + assert("Should fail to connect", cinfo.should_fail, "should_fail was %d", cinfo.should_fail); + + test_finished = 1; +} + + +void test6_onConnect(void* context, MQTTAsync_successData* response) +{ + test6_client_info cinfo = *(test6_client_info*)context; + + MyLog(LOGA_DEBUG, "In connect success callback, context %p", context); + + assert("Should connect correctly", !cinfo.should_fail, "should_fail was %d", cinfo.should_fail); + + test_finished = 1; +} + + +/******************************************************************** + +Test6: HA connections + +*********************************************************************/ +int test6(struct Options options) +{ + int subsqos = 2; + test6_client_info cinfo; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test1"; + char* uris[2] = {options.connection, options.connection}; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 6 - HA connections"); + fprintf(xml, "msgid); + + test7_messageCount++; + + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + + return 1; +} + + +static int test7_subscribed = 0; + +void test7_onSubscribe(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + + MyLog(LOGA_DEBUG, "In subscribe onSuccess callback %p granted qos %d", c, response->alt.qos); + + test7_subscribed = 1; +} + + +void test7_onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + opts.onSuccess = test7_onSubscribe; + opts.context = c; + + rc = MQTTAsync_subscribe(c, test7_topic, 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished = 1; +} + + +void test7_onConnectOnly(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_disconnectOptions dopts = MQTTAsync_disconnectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + dopts.context = context; + dopts.timeout = 1000; + dopts.onSuccess = test7_onDisconnect; + rc = MQTTAsync_disconnect(c, &dopts); + + assert("Good rc from disconnect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished = 1; +} + + +/********************************************************************* + +Test7: Pending tokens + +*********************************************************************/ +int test7(struct Options options) +{ + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions ropts = MQTTAsync_responseOptions_initializer; + MQTTAsync_disconnectOptions dopts = MQTTAsync_disconnectOptions_initializer; + MQTTAsync_token* tokens = NULL; + int msg_count = 6; + + MyLog(LOGA_INFO, "Starting test 7 - pending tokens"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + + opts.onFailure = NULL; + opts.context = c; + + opts.cleansession = 1; + opts.onSuccess = test7_onConnectOnly; + MyLog(LOGA_DEBUG, "Connecting to clean up"); + rc = MQTTAsync_connect(c, &opts); + rc = 0; + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + test_finished = 0; + MyLog(LOGA_DEBUG, "Connecting"); + opts.cleansession = 0; + opts.onSuccess = test7_onConnect; + rc = MQTTAsync_connect(c, &opts); + rc = 0; + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test7_subscribed) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = 2; + pubmsg.retained = 0; + rc = MQTTAsync_send(c, test_topic, pubmsg.payloadlen, pubmsg.payload, pubmsg.qos, pubmsg.retained, &ropts); + MyLog(LOGA_DEBUG, "Token was %d", ropts.token); + rc = MQTTAsync_isComplete(c, ropts.token); + assert("0 rc from isComplete", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + rc = MQTTAsync_waitForCompletion(c, ropts.token, 5000L); + assert("Good rc from waitForCompletion", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + rc = MQTTAsync_isComplete(c, ropts.token); + assert("1 rc from isComplete", rc == 1, "rc was %d", rc); + + test7_messageCount = 0; + int i = 0; + pubmsg.qos = 2; + for (i = 0; i < msg_count; ++i) + { + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + //pubmsg.qos = (pubmsg.qos == 2) ? 1 : 2; + pubmsg.retained = 0; + rc = MQTTAsync_sendMessage(c, test_topic, &pubmsg, &ropts); + } + /* disconnect immediately without receiving the incoming messages */ + dopts.timeout = 0; + dopts.onSuccess = test7_onDisconnect; + dopts.context = c; + MQTTAsync_disconnect(c, &dopts); /* now there should be "orphaned" publications */ + + while (!test_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + test_finished = 0; + + rc = MQTTAsync_getPendingTokens(c, &tokens); + assert("getPendingTokens rc == 0", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + assert("should get some tokens back", tokens != NULL, "tokens was %p", tokens); + MQTTAsync_free(tokens); + + MQTTAsync_destroy(&c); /* force re-reading persistence on create */ + + MQTTAsync_setTraceLevel(MQTTASYNC_TRACE_ERROR); + rc = MQTTAsync_create(&c, options.connection, "async_test7", MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTASYNC_SUCCESS, "rc was %d\n", rc); + if (rc != MQTTASYNC_SUCCESS) + { + MQTTAsync_destroy(&c); + goto exit; + } + + rc = MQTTAsync_getPendingTokens(c, &tokens); + assert("getPendingTokens rc == 0", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + assert("should get some tokens back", tokens != NULL, "tokens was %p", tokens); + if (tokens) + { + int i = 0; + while (tokens[i] != -1) + MyLog(LOGA_DEBUG, "Delivery token %d", tokens[i++]); + MQTTAsync_free(tokens); + //The following assertion should work, does with RSMB, but not Mosquitto + //assert1("no of tokens should be count", i == msg_count, "no of tokens %d count %d", i, msg_count); + } + + rc = MQTTAsync_setCallbacks(c, c, NULL, test7_messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + MyLog(LOGA_DEBUG, "Reconnecting"); + opts.context = c; + if (MQTTAsync_connect(c, &opts) != 0) + { + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + goto exit; + } + + #if defined(WIN32) + Sleep(5000); + #else + usleep(5000000L); + #endif + + rc = MQTTAsync_getPendingTokens(c, &tokens); + assert("getPendingTokens rc == 0", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + /* assert("should get no tokens back", tokens == NULL, "tokens was %p", tokens); + + assert1("no of messages should be count", test7_messageCount == msg_count, "no of tokens %d count %d", + test7_messageCount, msg_count); + + assertions fail against Mosquitto - needs testing */ + + dopts.onFailure = test7_onDisconnectFailure; + dopts.onSuccess = test7_onDisconnect; + dopts.timeout = 1000; + MQTTAsync_disconnect(c, &dopts); + + while (!test_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + MQTTAsync_destroy(&c); + +exit: + MyLog(LOGA_INFO, "TEST7: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + write_test_result(); + return failures; +} + + + +void trace_callback(enum MQTTASYNC_TRACE_LEVELS level, char* message) +{ + printf("Trace : %d, %s\n", level, message); +} + + + + +int main(int argc, char** argv) +{ + int rc = 0; + int (*tests[])() = {NULL, test1, test2, test3, test4, test5, test6, test7}; /* indexed starting from 1 */ + MQTTAsync_nameValue* info; + int i; + + xml = fopen("TEST-test4.xml", "w"); + fprintf(xml, "\n", (int)(ARRAY_SIZE(tests)) - 1); + + getopts(argc, argv); + + MQTTAsync_setTraceCallback(trace_callback); + + info = MQTTAsync_getVersionInfo(); + while (info->name) + { + MyLog(LOGA_INFO, "%s: %s", info->name, info->value); + info++; + } + + for (i = 0; i < options.iterations; ++i) + { + if (options.test_no == -1) + { /* run all the tests */ + for (options.test_no = 1; options.test_no < ARRAY_SIZE(tests); ++options.test_no) + { + failures = 0; + MQTTAsync_setTraceLevel(MQTTASYNC_TRACE_ERROR); + rc += tests[options.test_no](options); /* return number of failures. 0 = test succeeded */ + } + } + else + { + MQTTAsync_setTraceLevel(MQTTASYNC_TRACE_ERROR); + rc = tests[options.test_no](options); /* run just the selected test */ + } + } + + if (rc == 0) + MyLog(LOGA_INFO, "verdict pass"); + else + MyLog(LOGA_INFO, "verdict fail"); + + fprintf(xml, "\n"); + fclose(xml); + + return rc; +} diff --git a/Sources/paho/test/test5.c b/Sources/paho/test/test5.c new file mode 100644 index 0000000..9502434 --- /dev/null +++ b/Sources/paho/test/test5.c @@ -0,0 +1,2074 @@ +/******************************************************************************* + * Copyright (c) 2012, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Allan Stockdill-Mander - initial API and implementation and/or initial documentation + *******************************************************************************/ + + +/** + * @file + * SSL tests for the MQ Telemetry Asynchronous MQTT C client + */ + +/* + #if !defined(_RTSHEADER) + #include + #endif + */ + +#include "MQTTAsync.h" +#include +#include +#include "Thread.h" + +#if !defined(_WINDOWS) +#include +#include +#include +#include +#else +#include +#include +#define MAXHOSTNAMELEN 256 +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#define snprintf _snprintf +#endif + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +void usage() +{ + printf("Options:\n"); + printf("\t--test_no - Run test number \n"); + printf("\t--server - Connect to for tests\n"); + printf("\t--client_key - Use as the client certificate for SSL authentication\n"); + printf("\t--client_key_pass - Use to access the private key in the client certificate\n"); + printf("\t--server_key - Use as the trusted certificate for server\n"); + printf("\t--verbose - Enable verbose output \n"); + printf("\t--help - This help output\n"); + exit(-1); +} + +struct Options +{ + char connection[100]; /**< connection to system under test. */ + char mutual_auth_connection[100]; /**< connection to system under test. */ + char nocert_mutual_auth_connection[100]; + char server_auth_connection[100]; + char anon_connection[100]; + char* client_key_file; + char* client_key_pass; + char* server_key_file; + char* client_private_key_file; + int verbose; + int test_no; + int size; +} options = +{ + "ssl://m2m.eclipse.org:18883", + "ssl://m2m.eclipse.org:18884", + "ssl://m2m.eclipse.org:18887", + "ssl://m2m.eclipse.org:18885", + "ssl://m2m.eclipse.org:18886", + "../../../test/ssl/client.pem", + NULL, + "../../../test/ssl/test-root-ca.crt", + NULL, + 0, + 0, + 5000000 +}; + +typedef struct +{ + MQTTAsync client; + char clientid[24]; + char topic[100]; + int maxmsgs; + int rcvdmsgs[3]; + int sentmsgs[3]; + int testFinished; + int subscribed; +} AsyncTestClient; + +#define AsyncTestClient_initializer {NULL, "\0", "\0", 0, {0, 0, 0}, {0, 0, 0}, 0, 0} + +void getopts(int argc, char** argv) +{ + int count = 1; + + while (count < argc) + { + if (strcmp(argv[count], "--help") == 0) + { + usage(); + } + else if (strcmp(argv[count], "--test_no") == 0) + { + if (++count < argc) + options.test_no = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--client_key") == 0) + { + if (++count < argc) + options.client_key_file = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--client_key_pass") == 0) + { + if (++count < argc) + options.client_key_pass = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--server_key") == 0) + { + if (++count < argc) + options.server_key_file = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--verbose") == 0) + { + options.verbose = 1; + printf("\nSetting verbose on\n"); + } + else if (strcmp(argv[count], "--hostname") == 0) + { + if (++count < argc) + { + sprintf(options.connection, "ssl://%s:18883", argv[count]); + printf("Setting connection to %s\n", options.connection); + sprintf(options.mutual_auth_connection, "ssl://%s:18884", argv[count]); + printf("Setting mutual_auth_connection to %s\n", options.mutual_auth_connection); + sprintf(options.nocert_mutual_auth_connection, "ssl://%s:18887", argv[count]); + printf("Setting nocert_mutual_auth_connection to %s\n", + options.nocert_mutual_auth_connection); + sprintf(options.server_auth_connection, "ssl://%s:18885", argv[count]); + printf("Setting server_auth_connection to %s\n", options.server_auth_connection); + sprintf(options.anon_connection, "ssl://%s:18886", argv[count]); + printf("Setting anon_connection to %s\n", options.anon_connection); + } + else + usage(); + } + count++; + } +} + +#if 0 +#include /* For general log messages */ +#define MyLog logaLine +#else +#define LOGA_DEBUG 0 +#define LOGA_INFO 1 +#include +#include +#include +void MyLog(int LOGA_level, char* format, ...) +{ + static char msg_buf[256]; + va_list args; + struct timeb ts; + + struct tm *timeinfo; + + if (LOGA_level == LOGA_DEBUG && options.verbose == 0) + return; + + ftime(&ts); + timeinfo = localtime(&ts.time); + strftime(msg_buf, 80, "%Y%m%d %H%M%S", timeinfo); + + sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm); + + va_start(args, format); + vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), + format, args); + va_end(args); + + printf("%s\n", msg_buf); + fflush(stdout); +} +#endif + +#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 + +#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) + +#define MAXMSGS 30; + +int tests = 0; +int failures = 0; +FILE* xml; +START_TIME_TYPE global_start_time; +char output[3000]; +char* cur_output = output; + + +void write_test_result() +{ + long duration = elapsed(global_start_time); + + fprintf(xml, " time=\"%ld.%.3ld\" >\n", duration / 1000, duration % 1000); + if (cur_output != output) + { + fprintf(xml, "%s", output); + cur_output = output; + } + fprintf(xml, "\n"); +} + +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", filename, + lineno, description); + + va_start(args, format); + vprintf(format, args); + va_end(args); + + cur_output += sprintf(cur_output, "file %s, line %d \n", + description, filename, lineno); + } + else + MyLog(LOGA_DEBUG, + "Assertion succeeded, file %s, line %d, description: %s", + filename, lineno, description); +} + +/********************************************************************* + + Test: multi-threaded client using callbacks + + *********************************************************************/ +volatile int multiThread_arrivedcount = 0; +int multiThread_deliveryCompleted = 0; +MQTTAsync_message multiThread_pubmsg = MQTTAsync_message_initializer; + +void multiThread_deliveryComplete(void* context, MQTTAsync_token dt) +{ + ++multiThread_deliveryCompleted; +} + +int multiThread_messageArrived(void* context, char* topicName, int topicLen, + MQTTAsync_message* m) +{ + ++multiThread_arrivedcount; + MyLog(LOGA_DEBUG, "Callback: %d message received on topic %s is %.*s.", + multiThread_arrivedcount, topicName, m->payloadlen, + (char*) (m->payload)); + if (multiThread_pubmsg.payloadlen != m->payloadlen || memcmp(m->payload, + multiThread_pubmsg.payload, m->payloadlen) != 0) + { + failures++; + MyLog(LOGA_INFO, "Error: wrong data received lengths %d %d\n", + multiThread_pubmsg.payloadlen, m->payloadlen); + } + MQTTAsync_free(topicName); + MQTTAsync_freeMessage(&m); + return 1; +} + +void sendAndReceive(MQTTAsync* c, int qos, char* test_topic) +{ + MQTTAsync_responseOptions ropts; + int i = 0; + int iterations = 50; + int rc = 0; + int wait_seconds = 0; + + multiThread_deliveryCompleted = 0; + multiThread_arrivedcount = 0; + + MyLog(LOGA_DEBUG, "%d messages at QoS %d", iterations, qos); + multiThread_pubmsg.payload + = "a much longer message that we can shorten to the extent that we need to"; + multiThread_pubmsg.payloadlen = 27; + multiThread_pubmsg.qos = qos; + multiThread_pubmsg.retained = 0; + + for (i = 1; i <= iterations; ++i) + { + if (i % 10 == 0) + rc = MQTTAsync_send(c, test_topic, multiThread_pubmsg.payloadlen, + multiThread_pubmsg.payload, multiThread_pubmsg.qos, + multiThread_pubmsg.retained, NULL); + else + rc = MQTTAsync_sendMessage(c, test_topic, &multiThread_pubmsg, + &ropts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + +#if defined(WIN32) + Sleep(100); +#else + usleep(100000L); +#endif + + wait_seconds = 10; + while ((multiThread_arrivedcount < i) && (wait_seconds-- > 0)) + { + MyLog(LOGA_DEBUG, "Arrived %d count %d", multiThread_arrivedcount, + i); +#if defined(WIN32) + Sleep(1000); +#else + usleep(1000000L); +#endif + } + assert("Message Arrived", wait_seconds > 0, + "Time out waiting for message %d\n", i ); + } + if (qos > 0) + { + /* MQ Telemetry can send a message to a subscriber before the server has + completed the QoS 2 handshake with the publisher. For QoS 1 and 2, + allow time for the final delivery complete callback before checking + that all expected callbacks have been made */ + wait_seconds = 10; + while ((multiThread_deliveryCompleted < iterations) && (wait_seconds-- + > 0)) + { + MyLog(LOGA_DEBUG, "Delivery Completed %d count %d", + multiThread_deliveryCompleted, i); +#if defined(WIN32) + Sleep(1000); +#else + usleep(1000000L); +#endif + } + assert("All Deliveries Complete", wait_seconds > 0, + "Number of deliveryCompleted callbacks was %d\n", + multiThread_deliveryCompleted); + } +} + +/********************************************************************* + + Async Callbacks - generic callbacks for send/receive tests + + *********************************************************************/ + +//static mutex_type client_mutex = NULL; +//static pthread_mutex_t client_mutex_store = PTHREAD_MUTEX_INITIALIZER; +//static mutex_type client_mutex = &client_mutex_store; + +void asyncTestOnDisconnect(void* context, MQTTAsync_successData* response) +{ + //int rc; + + AsyncTestClient* tc = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In asyncTestOnDisconnect callback, %s", tc->clientid); + //rc = Thread_lock_mutex(client_mutex); + tc->testFinished = 1; + //rc = Thread_unlock_mutex(client_mutex); +} + +void asyncTestOnSend(void* context, MQTTAsync_successData* response) +{ + AsyncTestClient* tc = (AsyncTestClient*) context; + //int rc; + int qos = response->alt.pub.message.qos; + MyLog(LOGA_DEBUG, "In asyncTestOnSend callback, %s", tc->clientid); + //rc = Thread_lock_mutex(client_mutex); + tc->sentmsgs[qos]++; + //rc = Thread_unlock_mutex(client_mutex); +} + +void asyncTestOnSubscribeFailure(void* context, MQTTAsync_failureData* response) +{ + AsyncTestClient* tc = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In asyncTestOnSubscribeFailure callback, %s", + tc->clientid); + + assert("There should be no failures in this test. ", 0, "asyncTestOnSubscribeFailure callback was called\n", 0); +} + +void asyncTestOnUnsubscribe(void* context, MQTTAsync_successData* response) +{ + AsyncTestClient* tc = (AsyncTestClient*) context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In asyncTestOnUnsubscribe callback, %s", tc->clientid); + opts.onSuccess = asyncTestOnDisconnect; + opts.context = tc; + + rc = MQTTAsync_disconnect(tc->client, &opts); +} + +void asyncTestOnSubscribe(void* context, MQTTAsync_successData* response) +{ + AsyncTestClient* tc = (AsyncTestClient*) context; + int rc, i; + MyLog(LOGA_DEBUG, "In asyncTestOnSubscribe callback, %s", tc->clientid); + //rc = Thread_lock_mutex(client_mutex); + tc->subscribed = 1; + //rc = Thread_unlock_mutex(client_mutex); + for (i = 0; i < 3; i++) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + + pubmsg.payload + = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = i; + pubmsg.retained = 0; + + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + //opts.onSuccess = asyncTestOnSend; + opts.context = &tc; + + rc = MQTTAsync_send(tc->client, tc->topic, pubmsg.payloadlen, + pubmsg.payload, pubmsg.qos, pubmsg.retained, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + tc->sentmsgs[i]++; + MyLog(LOGA_DEBUG, "Maxmsgs %d", tc->maxmsgs); + } +} + +int asyncTestMessageArrived(void* context, char* topicName, int topicLen, + MQTTAsync_message* m) +{ + AsyncTestClient* tc = (AsyncTestClient*) context; + int rc; + //rc = Thread_lock_mutex(client_mutex); + tc->rcvdmsgs[m->qos]++; + + //printf("Received messages: %d\n", tc->rcvdmsgs[m->qos]); + + MyLog( + LOGA_DEBUG, + "In asyncTestMessageArrived callback, %s total to exit %d, total received %d,%d,%d", + tc->clientid, (tc->maxmsgs * 3), tc->rcvdmsgs[0], tc->rcvdmsgs[1], + tc->rcvdmsgs[2]); + + if (tc->sentmsgs[m->qos] < tc->maxmsgs) + { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + //opts.onSuccess = asyncTestOnSend; + opts.context = tc; + + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + pubmsg.payload + = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = m->qos; + pubmsg.retained = 0; + + rc = MQTTAsync_send(tc->client, tc->topic, pubmsg.payloadlen, + pubmsg.payload, pubmsg.qos, pubmsg.retained, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d messages sent %d,%d,%d", rc); + MyLog(LOGA_DEBUG, "Messages sent %d,%d,%d", tc->sentmsgs[0], + tc->sentmsgs[1], tc->sentmsgs[2]); + tc->sentmsgs[m->qos]++; + } + if ((tc->rcvdmsgs[0] + tc->rcvdmsgs[1] + tc->rcvdmsgs[2]) == (tc->maxmsgs + * 3)) + { + MyLog(LOGA_DEBUG, "Ready to unsubscribe"); + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + opts.onSuccess = asyncTestOnUnsubscribe; + opts.context = tc; + rc = MQTTAsync_unsubscribe(tc->client, tc->topic, &opts); + assert("Unsubscribe successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + //rc = Thread_unlock_mutex(client_mutex); + MyLog(LOGA_DEBUG, "Leaving asyncTestMessageArrived callback"); + MQTTAsync_freeMessage(&m); + MQTTAsync_free(topicName); + return 1; +} + +void asyncTestOnDeliveryComplete(void* context, MQTTAsync_token token) +{ + +} + +void asyncTestOnConnect(void* context, MQTTAsync_successData* response) +{ + AsyncTestClient* tc = (AsyncTestClient*) context; + int subsqos = 2; + int rc; + MyLog(LOGA_DEBUG, "In asyncTestOnConnect callback, %s", tc->clientid); + + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + opts.onSuccess = asyncTestOnSubscribe; + opts.onFailure = asyncTestOnSubscribeFailure; + opts.context = tc; + + rc = MQTTAsync_subscribe(tc->client, tc->topic, subsqos, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + +/********************************************************************* + + Test1: SSL connection to non SSL MQTT server + + *********************************************************************/ + +int test1Finished = 0; + +int test1OnFailureCalled = 0; + +void test1OnFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In connect onFailure callback, context %p", context); + + test1OnFailureCalled++; + test1Finished = 1; +} + +void test1OnConnect(void* context, MQTTAsync_successData* response) +{ + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p\n", context); + + assert("Connect should not succeed", 0, "connect success callback was called", 0); + + test1Finished = 1; +} + +int test1(struct Options options) +{ + char* testname = "test1"; + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + char* test_topic = "C client SSL test1"; + int count = 0; + + test1Finished = 0; + failures = 0; + MyLog(LOGA_INFO, "Starting SSL test 1 - connection to nonSSL MQTT server"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test1OnConnect; + opts.onFailure = test1OnFailure; + opts.context = c; + + opts.ssl = &sslopts; + opts.ssl->enableServerCertAuth = 0; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d ", rc); + if (rc != MQTTASYNC_SUCCESS) + { + failures++; + goto exit; + } + + /* wait for success or failure callback */ + while (!test1Finished && ++count < 10000) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + exit: MQTTAsync_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test2a: Mutual SSL Authentication - Certificates in place on client and server + + *********************************************************************/ + +void test2aOnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + AsyncTestClient* client = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In test2aOnConnectFailure callback, %s", + client->clientid); + + assert("There should be no failures in this test. ", 0, "test2aOnConnectFailure callback was called\n", 0); + client->testFinished = 1; +} + +void test2aOnPublishFailure(void* context, MQTTAsync_failureData* response) +{ + AsyncTestClient* client = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In test2aOnPublishFailure callback, %s", + client->clientid); + + assert("There should be no failures in this test. ", 0, "test2aOnPublishFailure callback was called\n", 0); +} + +int test2a(struct Options options) +{ + char* testname = "test2a"; + + AsyncTestClient tc = + AsyncTestClient_initializer; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 2a - Mutual SSL authentication"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = asyncTestOnConnect; + opts.onFailure = test2aOnConnectFailure; + opts.context = &tc; + + opts.ssl = &sslopts; + if (options.server_key_file != NULL) + opts.ssl->trustStore = options.server_key_file; /*file of certificates trusted by client*/ + opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + if (options.client_key_pass != NULL) + opts.ssl->privateKeyPassword = options.client_key_pass; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + //opts.ssl->enabledServerCertAuth = 1; + + rc = MQTTAsync_setCallbacks(c, &tc, NULL, asyncTestMessageArrived, + asyncTestOnDeliveryComplete); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!tc.subscribed && !tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + if (tc.testFinished) + goto exit; + + while (!tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + MyLog(LOGA_DEBUG, "Stopping"); + + exit: MQTTAsync_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test2b: Mutual SSL Authentication - Server does not have Client cert + + *********************************************************************/ + +int test2bFinished; + +void test2bOnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In test2bOnConnectFailure callback, context %p", context); + + assert("This test should call test2bOnConnectFailure. ", 1, "test2bOnConnectFailure callback was called\n", 1); + test2bFinished = 1; +} + +void test2bOnConnect(void* context, MQTTAsync_successData* response) +{ + MyLog(LOGA_DEBUG, "In test2bOnConnectFailure callback, context %p", context); + + assert("This connect should not succeed. ", 0, "test2bOnConnect callback was called\n", 0); + test2bFinished = 1; +} + +int test2b(struct Options options) +{ + char* testname = "test2b"; + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + int count = 0; + + test2bFinished = 0; + failures = 0; + MyLog(LOGA_INFO, + "Starting test 2b - connection to SSL MQTT server with clientauth=req but server does not have client cert"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test2bOnConnect; + opts.onFailure = test2bOnConnectFailure; + opts.context = c; + + opts.ssl = &sslopts; + if (options.server_key_file != NULL) + opts.ssl->trustStore = options.server_key_file; /*file of certificates trusted by client*/ + opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + if (options.client_key_pass != NULL) + opts.ssl->privateKeyPassword = options.client_key_pass; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + //opts.ssl->enabledServerCertAuth = 0; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test2bFinished && ++count < 10000) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + exit: MQTTAsync_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test2c: Mutual SSL Authentication - Client does not have Server cert + + *********************************************************************/ + +int test2cFinished; + +void test2cOnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In test2cOnConnectFailure callback, context %p", context); + + assert("This test should call test2cOnConnectFailure. ", 1, "test2cOnConnectFailure callback was called\n", 0); + test2cFinished = 1; +} + +void test2cOnConnect(void* context, MQTTAsync_successData* response) +{ + MyLog(LOGA_DEBUG, "In test2cOnConnectFailure callback, context %p", context); + + assert("This connect should not succeed. ", 0, "test2cOnConnect callback was called\n", 0); + test2cFinished = 1; +} + +int test2c(struct Options options) +{ + char* testname = "test2c"; + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + char* test_topic = "C client test2c"; + int count = 0; + + failures = 0; + MyLog( + LOGA_INFO, + "Starting test 2c - connection to SSL MQTT server, server auth enabled but unknown cert"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test2cOnConnect; + opts.onFailure = test2cOnConnectFailure; + opts.context = c; + + opts.ssl = &sslopts; + //if (options.server_key_file != NULL) opts.ssl->trustStore = options.server_key_file; /*file of certificates trusted by client*/ + opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + if (options.client_key_pass != NULL) + opts.ssl->privateKeyPassword = options.client_key_pass; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + //opts.ssl->enabledServerCertAuth = 0; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + { + failures++; + goto exit; + } + + while (!test2cFinished && ++count < 10000) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + exit: MQTTAsync_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test3a: Server Authentication - server certificate in client trust store + + *********************************************************************/ + +void test3aOnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + AsyncTestClient* client = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In test3aOnConnectFailure callback, context %p", context); + + assert("There should be no failures in this test. ", 0, "test3aOnConnectFailure callback was called\n", 0); + client->testFinished = 1; +} + +int test3a(struct Options options) +{ + char* testname = "test3a"; + int subsqos = 2; + /* TODO - usused - remove ? MQTTAsync_deliveryToken* dt = NULL; */ + AsyncTestClient tc = + AsyncTestClient_initializer; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + int i; + + failures = 0; + + MyLog(LOGA_INFO, "Starting test 3a - Server authentication"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = asyncTestOnConnect; + opts.onFailure = test3aOnConnectFailure; + opts.context = &tc; + + opts.ssl = &sslopts; + if (options.server_key_file != NULL) + opts.ssl->trustStore = options.server_key_file; /*file of certificates trusted by client*/ + //opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + //if (options.client_key_pass != NULL) opts.ssl->privateKeyPassword = options.client_key_pass; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + //opts.ssl->enabledServerCertAuth = 1; + + rc = MQTTAsync_setCallbacks(c, &tc, NULL, asyncTestMessageArrived, + asyncTestOnDeliveryComplete); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!tc.subscribed && !tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + if (tc.testFinished) + goto exit; + + for (i = 0; i < 3; i++) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + + pubmsg.payload + = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = i; + pubmsg.retained = 0; + + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + opts.onSuccess = asyncTestOnSend; + opts.context = &tc; + + rc = MQTTAsync_send(c, tc.topic, pubmsg.payloadlen, pubmsg.payload, + pubmsg.qos, pubmsg.retained, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + while (!tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + MyLog(LOGA_DEBUG, "Stopping"); + + MQTTAsync_destroy(&c); + + exit: MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", (failures + == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test3b: Server Authentication - Client does not have server cert + + *********************************************************************/ + +int test3bFinished; + +void test3bOnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In test3bOnConnectFailure callback, context %p", context); + + assert("This test should call test3bOnConnectFailure. ", 1, "test3bOnConnectFailure callback was called\n", 1); + test3bFinished = 1; +} + +void test3bOnConnect(void* context, MQTTAsync_successData* response) +{ + MyLog(LOGA_DEBUG, "In test3bOnConnectFailure callback, context %p", context); + + assert("This connect should not succeed. ", 0, "test3bOnConnect callback was called\n", 0); + test3bFinished = 1; +} + +int test3b(struct Options options) +{ + char* testname = "test3b"; + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + int count = 0; + + test3bFinished = 0; + failures = 0; + MyLog( + LOGA_INFO, + "Starting test 3b - connection to SSL MQTT server with clientauth=opt but client does not have server cert"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test3bOnConnect; + opts.onFailure = test3bOnConnectFailure; + opts.context = c; + + opts.ssl = &sslopts; + //if (options.server_key_file != NULL) opts.ssl->trustStore = options.server_key_file; /*file of certificates trusted by client*/ + //opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + //if (options.client_key_pass != NULL) opts.ssl->privateKeyPassword = options.client_key_pass; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + //opts.ssl->enabledServerCertAuth = 0; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test3bFinished && ++count < 10000) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + exit: MQTTAsync_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test4: Accept invalid server certificates + + *********************************************************************/ + +void test4OnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + AsyncTestClient* client = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In test4OnConnectFailure callback, context %p", context); + + assert("There should be no failures in this test. ", 0, "test4OnConnectFailure callback was called\n", 0); + client->testFinished = 1; +} + +void test4OnPublishFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In test4OnPublishFailure callback, context %p", context); + + assert("There should be no failures in this test. ", 0, "test4OnPublishFailure callback was called\n", 0); +} + +int test4(struct Options options) +{ + char* testname = "test4"; + int subsqos = 2; + /* TODO - usused - remove ? MQTTAsync_deliveryToken* dt = NULL; */ + AsyncTestClient tc = + AsyncTestClient_initializer; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + int i; + + failures = 0; + + MyLog(LOGA_INFO, "Starting test 4 - accept invalid server certificates"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = asyncTestOnConnect; + opts.onFailure = test4OnConnectFailure; + opts.context = &tc; + + opts.ssl = &sslopts; + //if (options.server_key_file != NULL) opts.ssl->trustStore = options.server_key_file; /*file of certificates trusted by client*/ + //opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + //if (options.client_key_pass != NULL) opts.ssl->privateKeyPassword = options.client_key_pass; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + opts.ssl->enableServerCertAuth = 0; + + rc = MQTTAsync_setCallbacks(c, &tc, NULL, asyncTestMessageArrived, + asyncTestOnDeliveryComplete); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!tc.subscribed && !tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + if (tc.testFinished) + goto exit; + + for (i = 0; i < 3; i++) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + + pubmsg.payload + = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = i; + pubmsg.retained = 0; + + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + opts.onSuccess = asyncTestOnSend; + opts.context = &tc; + + rc = MQTTAsync_send(c, tc.topic, pubmsg.payloadlen, pubmsg.payload, + pubmsg.qos, pubmsg.retained, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + while (!tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + MyLog(LOGA_DEBUG, "Stopping"); + + exit: MQTTAsync_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test5a: Anonymous ciphers - server auth disabled + + *********************************************************************/ + +void test5aOnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + AsyncTestClient* client = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In test5aOnConnectFailure callback, context %p", context); + + assert("There should be no failures in this test. ", 0, "test5aOnConnectFailure callback was called\n", 0); + client->testFinished = 1; +} + +void test5aOnPublishFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In test5aOnPublishFailure callback, context %p", context); + + assert("There should be no failures in this test. ", 0, "test5aOnPublishFailure callback was called\n", 0); +} + +int test5a(struct Options options) +{ + char* testname = "test5a"; + + AsyncTestClient tc = + AsyncTestClient_initializer; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + int i; + + failures = 0; + + MyLog(LOGA_INFO, + "Starting SSL test 5a - Anonymous ciphers - server authentication disabled"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = asyncTestOnConnect; + opts.onFailure = test5aOnConnectFailure; + opts.context = &tc; + + opts.ssl = &sslopts; + //opts.ssl->trustStore = /*file of certificates trusted by client*/ + //opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + //if (options.client_key_pass != NULL) opts.ssl->privateKeyPassword = options.client_key_pass; + opts.ssl->enabledCipherSuites = "aNULL"; + opts.ssl->enableServerCertAuth = 0; + + rc = MQTTAsync_setCallbacks(c, &tc, NULL, asyncTestMessageArrived, + asyncTestOnDeliveryComplete); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!tc.subscribed && !tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + if (tc.testFinished) + goto exit; + + for (i = 0; i < 3; i++) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + + pubmsg.payload + = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = i; + pubmsg.retained = 0; + + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + opts.onSuccess = asyncTestOnSend; + opts.context = &tc; + + rc = MQTTAsync_send(c, tc.topic, pubmsg.payloadlen, pubmsg.payload, + pubmsg.qos, pubmsg.retained, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + while (!tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + MyLog(LOGA_DEBUG, "Stopping"); + + exit: MQTTAsync_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test5b: Anonymous ciphers - server auth enabled + + ********************************************************************/ + +void test5bOnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + AsyncTestClient* client = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In test5bOnConnectFailure callback, context %p", context); + + assert("There should be no failures in this test. ", 0, "test5bOnConnectFailure callback was called\n", 0); + client->testFinished = 1; +} + +void test5bOnPublishFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In test5bOnPublishFailure callback, context %p", context); + + assert("There should be no failures in this test. ", 0, "test5bOnPublishFailure callback was called\n", 0); +} + +int test5b(struct Options options) +{ + char* testname = "test5b"; + + AsyncTestClient tc = + AsyncTestClient_initializer; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + int i; + + failures = 0; + + MyLog(LOGA_INFO, + "Starting SSL test 5b - Anonymous ciphers - server authentication enabled"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = asyncTestOnConnect; + opts.onFailure = test5bOnConnectFailure; + opts.context = &tc; + + opts.ssl = &sslopts; + //opts.ssl->trustStore = /*file of certificates trusted by client*/ + //opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + //if (options.client_key_pass != NULL) opts.ssl->privateKeyPassword = options.client_key_pass; + opts.ssl->enabledCipherSuites = "aNULL"; + opts.ssl->enableServerCertAuth = 1; + + rc = MQTTAsync_setCallbacks(c, &tc, NULL, asyncTestMessageArrived, + asyncTestOnDeliveryComplete); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!tc.subscribed && !tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + if (tc.testFinished) + goto exit; + + for (i = 0; i < 3; i++) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + + pubmsg.payload + = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = i; + pubmsg.retained = 0; + + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + opts.onSuccess = asyncTestOnSend; + opts.context = &tc; + + rc = MQTTAsync_send(c, tc.topic, pubmsg.payloadlen, pubmsg.payload, + pubmsg.qos, pubmsg.retained, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + while (!tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + + MyLog(LOGA_DEBUG, "Stopping"); + + exit: MQTTAsync_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test5c: Anonymous ciphers - client not using anonymous ciphers + + *********************************************************************/ + +int test5cFinished; + +void test5cOnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In test5cOnConnectFailure callback, context %p", context); + + assert("This test should call test5cOnConnectFailure. ", 1, "test5cOnConnectFailure callback was called\n", 1); + test5cFinished = 1; +} + +void test5cOnConnect(void* context, MQTTAsync_successData* response) +{ + MyLog(LOGA_DEBUG, "In test5cOnConnectFailure callback, context %p", context); + + assert("This connect should not succeed. ", 0, "test5cOnConnect callback was called\n", 0); + test5cFinished = 1; +} + +int test5c(struct Options options) +{ + char* testname = "test5c"; + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + int count = 0; + + test5cFinished = 0; + failures = 0; + MyLog(LOGA_INFO, + "Starting SSL test 5c - Anonymous ciphers - client not using anonymous cipher"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test5cOnConnect; + opts.onFailure = test5cOnConnectFailure; + opts.context = c; + + opts.ssl = &sslopts; + //opts.ssl->trustStore = /*file of certificates trusted by client*/ + //opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + //if (options.client_key_pass != NULL) opts.ssl->privateKeyPassword = options.client_key_pass; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + opts.ssl->enableServerCertAuth = 0; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d\n", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test5cFinished && ++count < 10000) +#if defined(WIN32) + Sleep(100); +#else + usleep(10000L); +#endif + +exit: + MQTTAsync_destroy(&c); + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test6: More than one client object - simultaneous working. + + *********************************************************************/ + +void test6OnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + AsyncTestClient* client = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In test6OnConnectFailure callback, context %p", context); + + assert("There should be no failures in this test. ", 0, "test6OnConnectFailure callback was called\n", 0); + client->testFinished = 1; +} + +void test6OnPublishFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In test6OnPublishFailure callback, context %p", context); + + assert("There should be no failures in this test. ", 0, "test6OnPublishFailure callback was called\n", 0); +} + +int test6(struct Options options) +{ + char* testname = "test6"; +#define num_clients 10 + int subsqos = 2; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + int i; + AsyncTestClient tc[num_clients]; + int test6finished = 0; + + MyLog(LOGA_INFO, "Starting test 6 - multiple connections"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.onSuccess = asyncTestOnConnect; + opts.onFailure = test6OnConnectFailure; + opts.context = &tc[i]; + + opts.ssl = &sslopts; + if (options.server_key_file != NULL) + opts.ssl->trustStore = options.server_key_file; /*file of certificates trusted by client*/ + opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + if (options.client_key_pass != NULL) + opts.ssl->privateKeyPassword = options.client_key_pass; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + //opts.ssl->enabledServerCertAuth = 1; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(tc[i].client, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + while (test6finished < num_clients) + { + MyLog(LOGA_DEBUG, "num_clients %d test_finished %d\n", num_clients, + test6finished); +#if defined(WIN32) + Sleep(100); + +#else + usleep(10000L); +#endif + for (i = 0; i < num_clients; ++i) + { + if (tc[i].testFinished) + { + test6finished++; + tc[i].testFinished = 0; + } + } + } + + MyLog(LOGA_DEBUG, "test6: destroying clients"); + + for (i = 0; i < num_clients; ++i) + MQTTAsync_destroy(&tc[i].client); + +//exit: + MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +/********************************************************************* + + Test7: Send and receive big messages + + *********************************************************************/ + +void* test7_payload = NULL; +int test7_payloadlen = 0; + +void test7OnConnectFailure(void* context, MQTTAsync_failureData* response) +{ + AsyncTestClient* client = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In test7OnConnectFailure callback, %s", client->clientid); + + assert("There should be no failures in this test. ", 0, "test7OnConnectFailure callback was called\n", 0); + client->testFinished = 1; +} + +void test7OnPublishFailure(void* context, MQTTAsync_failureData* response) +{ + AsyncTestClient* client = (AsyncTestClient*) context; + MyLog(LOGA_DEBUG, "In test7OnPublishFailure callback, %s", client->clientid); + + assert("There should be no failures in this test. ", 0, "test7OnPublishFailure callback was called\n", 0); + client->testFinished = 1; +} + +int test7MessageArrived(void* context, char* topicName, int topicLen, + MQTTAsync_message* message) +{ + AsyncTestClient* tc = (AsyncTestClient*) context; + static int message_count = 0; + int rc, i; + + MyLog(LOGA_DEBUG, "In messageArrived callback %p", tc); + + assert("Message size correct", message->payloadlen == test7_payloadlen, + "message size was %d", message->payloadlen); + + for (i = 0; i < options.size; ++i) + { + if (((char*) test7_payload)[i] != ((char*) message->payload)[i]) + { + assert("Message contents correct", ((char*)test7_payload)[i] != ((char*)message->payload)[i], + "message content was %c", ((char*)message->payload)[i]); + break; + } + } + + if (++message_count == 1) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + pubmsg.payload = test7_payload; + pubmsg.payloadlen = test7_payloadlen; + pubmsg.qos = 1; + pubmsg.retained = 0; + opts.onSuccess = NULL; + opts.onFailure = test7OnPublishFailure; + opts.context = tc; + + rc = MQTTAsync_sendMessage(tc->client, tc->topic, &pubmsg, &opts); + } + else if (message_count == 2) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + pubmsg.payload = test7_payload; + pubmsg.payloadlen = test7_payloadlen; + pubmsg.qos = 0; + pubmsg.retained = 0; + opts.onSuccess = NULL; + opts.onFailure = test7OnPublishFailure; + opts.context = tc; + rc = MQTTAsync_sendMessage(tc->client, tc->topic, &pubmsg, &opts); + } + else + { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + opts.onSuccess = asyncTestOnUnsubscribe; + opts.context = tc; + rc = MQTTAsync_unsubscribe(tc->client, tc->topic, &opts); + assert("Unsubscribe successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + + return 1; +} + +void test7OnSubscribe(void* context, MQTTAsync_successData* response) +{ + AsyncTestClient* tc = (AsyncTestClient*) context; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc, i; + + MyLog(LOGA_DEBUG, "In subscribe onSuccess callback %p", tc); + + pubmsg.payload = test7_payload = malloc(options.size); + pubmsg.payloadlen = test7_payloadlen = options.size; + + srand(33); + for (i = 0; i < options.size; ++i) + ((char*) pubmsg.payload)[i] = rand() % 256; + + pubmsg.qos = 2; + pubmsg.retained = 0; + + rc = MQTTAsync_send(tc->client, tc->topic, pubmsg.payloadlen, pubmsg.payload, + pubmsg.qos, pubmsg.retained, NULL); +} + +void test7OnConnect(void* context, MQTTAsync_successData* response) +{ + AsyncTestClient* tc = (AsyncTestClient*) context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + opts.onSuccess = test7OnSubscribe; + opts.context = tc; + + rc = MQTTAsync_subscribe(tc->client, tc->topic, 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + tc->testFinished = 1; +} + +int test7(struct Options options) +{ + char* testname = "test7"; + int subsqos = 2; + AsyncTestClient tc = + AsyncTestClient_initializer; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + MQTTAsync_SSLOptions sslopts = MQTTAsync_SSLOptions_initializer; + int rc = 0; + char* test_topic = "C client test7"; + int test_finished; + + test_finished = failures = 0; + + MyLog(LOGA_INFO, "Starting test 7 - big messages"); + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test7OnConnect; + opts.onFailure = test7OnConnectFailure; + opts.context = &tc; + + opts.ssl = &sslopts; + if (options.server_key_file != NULL) + opts.ssl->trustStore = options.server_key_file; /*file of certificates trusted by client*/ + opts.ssl->keyStore = options.client_key_file; /*file of certificate for client to present to server*/ + if (options.client_key_pass != NULL) + opts.ssl->privateKeyPassword = options.client_key_pass; + //opts.ssl->enabledCipherSuites = "DEFAULT"; + //opts.ssl->enabledServerCertAuth = 1; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + rc = 0; + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!tc.testFinished) +#if defined(WIN32) + Sleep(100); +#else + usleep(1000L); +#endif + + MQTTAsync_destroy(&c); + + exit: MyLog(LOGA_INFO, "%s: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", testname, tests, failures); + write_test_result(); + return failures; +} + +void handleTrace(enum MQTTASYNC_TRACE_LEVELS level, char* message) +{ + printf("%s\n", message); +} + +int main(int argc, char** argv) +{ + int* numtests = &tests; + int rc = 0; + int (*tests[])() = + { NULL, test1, test2a, test2b, test2c, test3a, test3b, test4, /* test5a, + test5b, test5c, */ test6, test7 }; + + xml = fopen("TEST-test5.xml", "w"); + fprintf(xml, "\n", ARRAY_SIZE(tests) - 1); + + MQTTAsync_setTraceCallback(handleTrace); + getopts(argc, argv); + + if (options.test_no == 0) + { /* run all the tests */ + for (options.test_no = 1; options.test_no < ARRAY_SIZE(tests); ++options.test_no) + { + failures = 0; + MQTTAsync_setTraceLevel(MQTTASYNC_TRACE_ERROR); + rc += tests[options.test_no](options); /* return number of failures. 0 = test succeeded */ + } + } + else + { + MQTTAsync_setTraceLevel(MQTTASYNC_TRACE_ERROR); + rc = tests[options.test_no](options); /* run just the selected test */ + } + + MyLog(LOGA_INFO, "Total tests run: %d", *numtests); + if (rc == 0) + MyLog(LOGA_INFO, "verdict pass"); + else + MyLog(LOGA_INFO, "verdict fail"); + + fprintf(xml, "\n"); + fclose(xml); + + return rc; +} + diff --git a/Sources/paho/test/test6.c b/Sources/paho/test/test6.c new file mode 100644 index 0000000..8ddd13c --- /dev/null +++ b/Sources/paho/test/test6.c @@ -0,0 +1,1002 @@ +/******************************************************************************* + * Copyright (c) 2011, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + *******************************************************************************/ + + +/** + * @file + * Async C client program for the MQTT v3 restart/recovery test suite. + */ + +/* +#if !defined(_RTSHEADER) + #include +#endif +*/ + +#include "MQTTAsync.h" +#include +#include + +#if !defined(_WINDOWS) + #include + #include + #include +#else + #include +#endif + +void usage() +{ + printf("help!!\n"); + exit(-1); +} + +static char pub_topic[200]; +static char sub_topic[200]; + +struct +{ + char* connection; /**< connection to system under test. */ + char** connections; /**< HA connection list */ + int connection_count; + char* control_connection; /**< MQTT control connection, for test sync */ + char* topic; + char* control_topic; + char* clientid; + int slot_no; + int qos; + int retained; + char* username; + char* password; + int verbose; + int persistence; +} opts = +{ + "tcp://localhost:1885", + NULL, + 0, + "tcp://localhost:7777", + "XR9TT3", + "XR9TT3/control", + "C_broken_client", + 1, + 2, + 0, + NULL, + NULL, + 0, + 0, +}; + +void getopts(int argc, char** argv) +{ + int count = 1; + + while (count < argc) + { + if (strcmp(argv[count], "--qos") == 0) + { + if (++count < argc) + { + if (strcmp(argv[count], "0") == 0) + opts.qos = 0; + else if (strcmp(argv[count], "1") == 0) + opts.qos = 1; + else if (strcmp(argv[count], "2") == 0) + opts.qos = 2; + else + usage(); + } + else + usage(); + } + else if (strcmp(argv[count], "--slot_no") == 0) + { + if (++count < argc) + opts.slot_no = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--connection") == 0) + { + if (++count < argc) + opts.connection = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--connections") == 0) + { + if (++count < argc) + { + opts.connection_count = 0; + opts.connections = malloc(sizeof(char*) * 5); + char* tok = strtok(argv[count], " "); + while (tok) + { + opts.connections[opts.connection_count] = malloc(strlen(tok)+1); + strcpy(opts.connections[opts.connection_count], tok); + opts.connection_count++; + tok = strtok(NULL, " "); + } + } + else + usage(); + } + else if (strcmp(argv[count], "--control_connection") == 0) + { + if (++count < argc) + opts.control_connection = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--clientid") == 0) + { + if (++count < argc) + opts.clientid = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--username") == 0) + { + if (++count < argc) + opts.username = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--password") == 0) + { + if (++count < argc) + opts.password = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--persistent") == 0) + opts.persistence = 1; + else if (strcmp(argv[count], "--verbose") == 0) + opts.verbose = 1; + count++; + } +} + +#if 0 +#include /* For general log messages */ +#define MyLog logaLine +#else +#define LOGA_DEBUG 0 +#define LOGA_ALWAYS 1 +#define LOGA_INFO 2 +#include +#include +#include +void MyLog(int log_level, char* format, ...) +{ + static char msg_buf[256]; + va_list args; + struct timeb ts; + + struct tm *timeinfo; + + if (log_level == LOGA_DEBUG && opts.verbose == 0) + return; + + ftime(&ts); + timeinfo = localtime(&ts.time); + strftime(msg_buf, 80, "%Y%m%d %H%M%S", timeinfo); + + sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm); + + sprintf(&msg_buf[strlen(msg_buf)], "%s ", opts.clientid); + + va_start(args, format); + vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args); + va_end(args); + + printf("%s\n", msg_buf); + fflush(stdout); +} +#endif + + +#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 +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 + +MQTTAsync control_client; +MQTTAsync_connectOptions conn_opts = MQTTAsync_connectOptions_initializer; +MQTTAsync client; +int arrivedCount = 0; +int expectedCount = 0; +int measuring = 0; +long roundtrip_time = 0L; +int errors = 0; +int stopping = 0; +int connection_lost = 0; /* for use with the persistence option */ +int recreated = 0; +int client_cleaned = 0; + +char* wait_message = NULL; +char* wait_message2 = NULL; +int control_found = 0; +long last_completion_time = -1; +int test_count = 1000; + +void control_connectionLost(void* context, char* cause) +{ + MyLog(LOGA_ALWAYS, "Control connection lost - stopping"); + + stopping = 1; +} + +/**----------------------------------------------------------------------------- + * Callback which receives messages from the control connection + * @param context + * @param topicName the name of the topic on which the message is received + * @param topicLen the length of the topic name (in case of embedded nulls) + * @param m pointer to the message received + * @return boolean + */ +int control_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* m) +{ + MyLog(LOGA_DEBUG, "Control message arrived: %.*s %s", + m->payloadlen, m->payload, wait_message); + if (strcmp(m->payload, "stop") == 0) + stopping = 1; + else if (wait_message != NULL && strncmp(wait_message, m->payload, + strlen(wait_message)) == 0) + { + control_found = 1; + wait_message = NULL; + } + else if (wait_message2 != NULL && strncmp(wait_message2, m->payload, + strlen(wait_message2)) == 0) + { + control_found = 2; + wait_message2 = NULL; + } + + MQTTAsync_free(topicName); + MQTTAsync_freeMessage(&m); + return 1; +} + + +/* wait for a specific message on the control topic. */ +int control_wait(char* message) +{ + int count = 0; + char buf[120]; + + control_found = 0; + wait_message = message; + + sprintf(buf, "waiting for: %s", message); + control_send(buf); + + while (control_found == 0 && stopping == 0) + { + if (++count == 300) + { + stopping = 1; + MyLog(LOGA_ALWAYS, "Failed to receive message %s, stopping ", message); + return 0; /* time out and tell the caller the message was not found */ + } + mqsleep(1); + } + return control_found; +} + + +/* wait for a specific message on the control topic. */ +int control_which(char* message1, char* message2) +{ + int count = 0; + control_found = 0; + wait_message = message1; + wait_message2 = message2; + + while (control_found == 0) + { + if (++count == 300) + return 0; /* time out and tell the caller the message was not found */ + mqsleep(1); + } + return control_found; +} + + +int control_send(char* message) +{ + char buf[156]; + int rc = 0; + MQTTAsync_responseOptions ropts = MQTTAsync_responseOptions_initializer; + + sprintf(buf, "%s: %s", opts.clientid, message); + rc = MQTTAsync_send(control_client, pub_topic, strlen(buf), + buf, 1, 0, &ropts); + MyLog(LOGA_DEBUG, "Control message sent: %s", buf); + + return rc; +} + +START_TIME_TYPE global_start_time; + +int messageArrived(void* context, char* topicName, int topicLen, + MQTTAsync_message* m) +{ + int seqno = -1; + char* token = NULL; + + token = strtok(m->payload, " "); + token = strtok(NULL, " "); + token = strtok(NULL, " "); + + if (token) + seqno = atoi(token); + if (m->qos != opts.qos) + { + MyLog(LOGA_ALWAYS, "Error, expecting QoS %d but got %d", opts.qos, + m->qos); + errors++; + } else if (seqno != arrivedCount + 1) + { + if (m->qos == 2 || (m->qos == 1 && seqno > arrivedCount + 1)) + { + if (seqno == -1) + MyLog(LOGA_ALWAYS, + "Error, expecting sequence number %d but got message id %d, payload was %.*s", + arrivedCount + 1, m->msgid, m->payloadlen, m->payload); + else + MyLog(LOGA_ALWAYS, + "Error, expecting sequence number %d but got %d message id %d", + arrivedCount + 1, seqno, m->msgid); + errors++; + } + } + arrivedCount++; + MQTTAsync_free(topicName); + MQTTAsync_freeMessage(&m); + + if (measuring && arrivedCount == test_count) + roundtrip_time = elapsed(global_start_time); + return 1; +} + + +void client_onReconnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + + MyLog(LOGA_ALWAYS, "Successfully reconnected"); +} + + +void client_onReconnectFailure(void* context, MQTTAsync_failureData* response) +{ + MQTTAsync c = (MQTTAsync)context; + int rc; + + MyLog(LOGA_ALWAYS, "Failed to reconnect with return code %d", (response) ? response->code : -9999); + + conn_opts.context = context; + conn_opts.keepAliveInterval = 10; + conn_opts.username = opts.username; + conn_opts.password = opts.password; + conn_opts.cleansession = 0; + conn_opts.onSuccess = client_onReconnect; + conn_opts.onFailure = client_onReconnectFailure; + rc = MQTTAsync_connect(c, &conn_opts); + if (rc != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "Failed to start reconnect with return code %d", rc); + stopping = 1; + } +} + + +void connectionLost(void* context, char* cause) +{ + MQTTAsync c = (MQTTAsync)context; + int rc = 0; + + MyLog(LOGA_ALWAYS, "Connection lost when %d messages arrived out of %d expected", + arrivedCount, expectedCount); + //dotrace = 1; + + if (opts.persistence) + connection_lost = 1; + else + { + conn_opts.context = context; + conn_opts.keepAliveInterval = 10; + conn_opts.username = opts.username; + conn_opts.password = opts.password; + conn_opts.cleansession = 0; + conn_opts.onSuccess = client_onReconnect; + conn_opts.onFailure = client_onReconnectFailure; + if (opts.connections) + { + conn_opts.serverURIcount = opts.connection_count; + conn_opts.serverURIs = opts.connections; + } + else + { + conn_opts.serverURIcount = 0; + conn_opts.serverURIs = NULL; + } + printf("reconnecting to first serverURI %s\n", conn_opts.serverURIs[0]); + rc = MQTTAsync_connect(context, &conn_opts); + if (rc != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "Failed to start reconnect with return code %d", rc); + stopping = 1; + } + } +} + + +int recreateReconnect() +{ + int rc; + + if (recreated == 0) + { + MyLog(LOGA_ALWAYS, "Recreating client"); + + MQTTAsync_destroy(&client); /* destroy the client object so that we force persistence to be read on recreate */ + + rc = MQTTAsync_create(&client, opts.connection, opts.clientid, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + if (rc != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "MQTTAsync_create failed, rc %d", rc); + goto exit; + } + + if ((rc = MQTTAsync_setCallbacks(client, client, connectionLost, + messageArrived, NULL)) != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "MQTTAsync_setCallbacks failed, rc %d", rc); + goto exit; + } + recreated = 1; + } + + MyLog(LOGA_ALWAYS, "Reconnecting client"); + conn_opts.keepAliveInterval = 10; + conn_opts.username = opts.username; + conn_opts.password = opts.password; + conn_opts.cleansession = 0; + conn_opts.context = client; + conn_opts.onSuccess = client_onReconnect; + conn_opts.onFailure = client_onReconnectFailure; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + MyLog(LOGA_ALWAYS, "MQTTAsync_connect failed, rc %d", rc); + else + connection_lost = 0; + +exit: + return rc; +} + + +int success(int count) +{ + int rc = 1; + + if (errors) + { + MyLog(LOGA_ALWAYS, "Workload test failed because the callback had errors"); + rc = 0; + } + if (arrivedCount != count) + { + if (opts.qos == 2 || (opts.qos == 1 && arrivedCount < count)) + { + MyLog(LOGA_ALWAYS, + "Workload test failed because the wrong number of messages" + " was received: %d whereas %d were expected", + arrivedCount, count); + rc = 0; + } + } + if (rc == 1) + control_send("verdict: pass"); + else + control_send("verdict: fail"); + return rc; +} + + +int waitForCompletion(START_TIME_TYPE start_time) +{ + int lastreport = 0; + int wait_count = 0; + int limit = 120; + + mqsleep(1); + while (arrivedCount < expectedCount) + { + if (arrivedCount > lastreport) + { + MyLog(LOGA_ALWAYS, "%d messages arrived out of %d expected, in %d seconds", + arrivedCount, expectedCount, elapsed(start_time) / 1000); + lastreport = arrivedCount; + } + mqsleep(1); + if (opts.persistence && connection_lost) + recreateReconnect(); + if (++wait_count > limit || stopping) + break; + } + last_completion_time = elapsed(start_time) / 1000; + MyLog(LOGA_ALWAYS, "Extra wait to see if any duplicates arrive"); + mqsleep(10); /* check if any duplicate messages arrive */ + MyLog(LOGA_ALWAYS, "%d messages arrived out of %d expected, in %d seconds", + arrivedCount, expectedCount, elapsed(start_time) / 1000); + return success(expectedCount); +} + +int messagesSent = 0; + +void messageSent(void* context, MQTTAsync_successData* response) +{ + messagesSent++; +} + + +void one_iteration() +{ + int interval = 0; + int i = 0; + int seqno = 0; + int rc = 0; + START_TIME_TYPE start_time; + int last_expected_count = expectedCount; + int test_interval = 30; + + if (control_wait("start_measuring") == 0) + goto exit; + + connection_lost = 0; + recreated = 0; + + /* find the time for evaluation_count round-trip messages */ + MyLog(LOGA_INFO, "Evaluating how many messages needed"); + expectedCount = arrivedCount = 0; + measuring = 1; + global_start_time = start_clock(); + for (i = 1; i <= test_count; ++i) + { + char payload[128]; + + sprintf(payload, "message number %d", i); + + rc = MQTTAsync_send(client, opts.topic, strlen(payload)+1, payload, + opts.qos, opts.retained, NULL); + while (rc != MQTTASYNC_SUCCESS) + { + if (opts.persistence && (connection_lost || rc == MQTTASYNC_DISCONNECTED)) + recreateReconnect(); + if (stopping) + goto exit; + mqsleep(1); + rc = MQTTAsync_send(client, opts.topic, strlen(payload)+1, payload, + opts.qos, opts.retained, NULL); + } + } + MyLog(LOGA_INFO, "Messages sent... waiting for echoes"); + while (arrivedCount < test_count) + { + if (stopping) + goto exit; + mqsleep(1); + printf("arrivedCount %d\n", arrivedCount); + } + measuring = 0; + + /* Now set a target of 30 seconds total round trip */ + if (last_completion_time == -1) + { + MyLog(LOGA_ALWAYS, "Round trip time for %d messages is %d ms", test_count, roundtrip_time); + expectedCount = 1000 * test_count * test_interval / roundtrip_time / 2; + } + else + { + MyLog(LOGA_ALWAYS, "Last time, %d messages took %d s.", last_expected_count, last_completion_time); + expectedCount = last_expected_count * test_interval / last_completion_time; + } + MyLog(LOGA_ALWAYS, "Therefore %d messages needed for 30 seconds", expectedCount); + + if (control_wait("start_test") == 0) /* now synchronize the test interval */ + goto exit; + + MyLog(LOGA_ALWAYS, "Starting 30 second test run with %d messages", expectedCount); + arrivedCount = 0; + messagesSent = 0; + start_time = start_clock(); + while (seqno < expectedCount) + { + MQTTAsync_responseOptions ropts = MQTTAsync_responseOptions_initializer; + char payload[128]; + + ropts.onSuccess = messageSent; + seqno++; + sprintf(payload, "message number %d", seqno); + rc = MQTTAsync_send(client, opts.topic, strlen(payload)+1, payload, + opts.qos, opts.retained, &ropts); + while (rc != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_DEBUG, "Rc %d from publish with payload %s, retrying", rc, payload); + if (opts.persistence && (connection_lost || rc == MQTTASYNC_DISCONNECTED)) + recreateReconnect(); + if (stopping) + goto exit; + mqsleep(1); + rc = MQTTAsync_send(client, opts.topic, strlen(payload)+1, payload, + opts.qos, opts.retained, &ropts); + } + //MyLog(LOGA_DEBUG, "Successful publish with payload %s", payload); + while (seqno - messagesSent > 2000) + mqsleep(1); + } + MyLog(LOGA_ALWAYS, "%d messages sent in %d seconds", expectedCount, elapsed(start_time) / 1000); + + waitForCompletion(start_time); + control_wait("test finished"); +exit: + ; /* dummy statement for target of exit */ +} + + +static int client_subscribed = 0; + +void client_onSubscribe(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + + MyLog(LOGA_DEBUG, "In client subscribe onSuccess callback %p granted qos %d", c, response->alt.qos); + + client_subscribed = 1; +} + +void client_onFailure(void* context, MQTTAsync_failureData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MyLog(LOGA_DEBUG, "In failure callback"); + + client_subscribed = -1; +} + + +void client_onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions ropts = MQTTAsync_responseOptions_initializer; + int rc; + + sprintf(sub_topic, "%s/send", opts.control_topic); + sprintf(pub_topic, "%s/receive", opts.control_topic); + ropts.context = context; + ropts.onSuccess = client_onSubscribe; + ropts.onFailure = client_onFailure; + ropts.context = c; + if ((rc = MQTTAsync_subscribe(c, opts.topic, opts.qos, &ropts)) != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "client MQTTAsync_subscribe failed, rc %d", rc); + client_subscribed = -1; + } +} + + +void client_onCleanedDisconnected(void* context, MQTTAsync_successData* response) +{ + client_cleaned = 1; +} + + +void client_onCleaned(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_disconnectOptions dopts = MQTTAsync_disconnectOptions_initializer; + int rc; + + dopts.context = context; + dopts.onSuccess = client_onCleanedDisconnected; + dopts.onFailure = client_onFailure; + dopts.context = c; + if ((rc = MQTTAsync_disconnect(c, &dopts)) != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "client MQTTAsync_disconnect failed, rc %d", rc); + stopping = 1; + } +} + + +int sendAndReceive(void) +{ + int rc = 0; + int persistence = MQTTCLIENT_PERSISTENCE_NONE; + + MyLog(LOGA_ALWAYS, "v3 async C client topic workload using QoS %d", opts.qos); + MyLog(LOGA_DEBUG, "Connecting to %s", opts.connection); + + if (opts.persistence) + persistence = MQTTCLIENT_PERSISTENCE_DEFAULT; + + rc = MQTTAsync_create(&client, opts.connection, opts.clientid, persistence, NULL); + if (rc != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "MQTTAsync_create failed, rc %d", rc); + rc = 99; + goto exit; + } + + if ((rc = MQTTAsync_setCallbacks(client, client, connectionLost, + messageArrived, NULL)) != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "MQTTAsync_setCallbacks failed, rc %d", rc); + rc = 99; + goto destroy_exit; + } + + /* wait to know that the controlling process is running before connecting to the SUT */ + control_wait("who is ready?"); + + /* connect cleansession, and then disconnect, to clean up */ + conn_opts.keepAliveInterval = 10; + conn_opts.username = opts.username; + conn_opts.password = opts.password; + conn_opts.cleansession = 1; + conn_opts.context = client; + conn_opts.onSuccess = client_onCleaned; + conn_opts.onFailure = client_onFailure; + if (opts.connections) + { + conn_opts.serverURIcount = opts.connection_count; + conn_opts.serverURIs = opts.connections; + } + else + { + conn_opts.serverURIcount = 0; + conn_opts.serverURIs = NULL; + } + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "MQTTAsync_connect failed, rc %d", rc); + rc = 99; + goto destroy_exit; + } + + while (client_cleaned == 0) + mqsleep(1); + + MyLog(LOGA_ALWAYS, "Client state cleaned up"); + + conn_opts.cleansession = 0; + conn_opts.context = client; + conn_opts.onSuccess = client_onConnect; + conn_opts.onFailure = client_onFailure; + conn_opts.retryInterval = 1; + if ((rc = MQTTAsync_connect(client, &conn_opts)) != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "MQTTAsync_connect failed, rc %d", rc); + rc = 99; + goto destroy_exit; + } + + /* wait until subscribed */ + while (client_subscribed == 0) + mqsleep(1); + + if (client_subscribed != 1) + goto disconnect_exit; + + while (1) + { + control_send("Ready"); + if (control_which("who is ready?", "continue") == 2) + break; + control_send("Ready"); + } + + while (!stopping) + { + one_iteration(client); + } + +disconnect_exit: + MQTTAsync_disconnect(client, 0); + +destroy_exit: + MQTTAsync_destroy(&client); + +exit: + return rc; +} + + +static int control_subscribed = 0; + +void control_onSubscribe(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + + MyLog(LOGA_DEBUG, "In control subscribe onSuccess callback %p granted qos %d", c, response->alt.qos); + + control_subscribed = 1; +} + +void control_onFailure(void* context, MQTTAsync_failureData* response) +{ + MQTTAsync c = (MQTTAsync)context; + + control_subscribed = -1; +} + + +void control_onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions ropts = MQTTAsync_responseOptions_initializer; + int rc; + + sprintf(sub_topic, "%s/send", opts.control_topic); + sprintf(pub_topic, "%s/receive", opts.control_topic); + ropts.onSuccess = control_onSubscribe; + ropts.onFailure = control_onFailure; + ropts.context = c; + if ((rc = MQTTAsync_subscribe(c, sub_topic, 2, &ropts)) != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "control MQTTAsync_subscribe failed, rc %d", rc); + control_subscribed = -1; + } +} + +void trace_callback(enum MQTTASYNC_TRACE_LEVELS level, char* message) +{ + if (level == MQTTASYNC_TRACE_ERROR || strstr(message, "Connect") || strstr(message, "failed")) + printf("Trace : %d, %s\n", level, message); +} + +int main(int argc, char** argv) +{ + MQTTAsync_connectOptions control_conn_opts = MQTTAsync_connectOptions_initializer; + int rc = 0; + static char topic_buf[200]; + static char clientid[40]; + +#if !defined(WIN32) + signal(SIGPIPE, SIG_IGN); +#endif + + MQTTAsync_nameValue* info = MQTTAsync_getVersionInfo(); + + while (info->name) + { + MyLog(LOGA_ALWAYS, "%s: %s\n", info->name, info->value); + info++; + } + + getopts(argc, argv); + + sprintf(topic_buf, "%s_%d", opts.topic, opts.slot_no); + opts.topic = topic_buf; + + sprintf(clientid, "%s_%d", opts.clientid, opts.slot_no); + opts.clientid = clientid; + + MyLog(LOGA_ALWAYS, "Starting with clientid %s", opts.clientid); + + //MQTTAsync_setTraceLevel(MQTTASYNC_TRACE_MAXIMUM); + MQTTAsync_setTraceCallback(trace_callback); + + rc = MQTTAsync_create(&control_client, opts.control_connection, + opts.clientid, MQTTCLIENT_PERSISTENCE_NONE, NULL); + if (rc != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "control MQTTAsync_create failed, rc %d", rc); + rc = 99; + goto exit; + } + + if ((rc = MQTTAsync_setCallbacks(control_client, control_client, control_connectionLost, + control_messageArrived, NULL)) != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "control MQTTAsync_setCallbacks failed, rc %d", rc); + rc = 99; + goto destroy_exit; + } + + control_subscribed = 0; + control_conn_opts.context = control_client; + control_conn_opts.keepAliveInterval = 10; + control_conn_opts.onSuccess = control_onConnect; + control_conn_opts.onFailure = control_onFailure; + if ((rc = MQTTAsync_connect(control_client, &control_conn_opts)) + != MQTTASYNC_SUCCESS) + { + MyLog(LOGA_ALWAYS, "control MQTTAsync_connect failed, rc %d", rc); + rc = 99; + goto destroy_exit; + } + + while (control_subscribed == 0) + mqsleep(1); + + if (control_subscribed != 1) + goto destroy_exit; + + sendAndReceive(); + +exit: + MQTTAsync_disconnect(control_client, 0); + +destroy_exit: + MQTTAsync_destroy(&control_client); + + return 0; +} diff --git a/Sources/paho/test/test8.c b/Sources/paho/test/test8.c new file mode 100644 index 0000000..4ea3564 --- /dev/null +++ b/Sources/paho/test/test8.c @@ -0,0 +1,934 @@ +/*--------------------------------------------------------------------*/ +/* [Platforms]UNIX NT[/Platforms] */ +/* [Title]MQ Telemetry MQTT Async C client tests - HA and connect */ +/* failures */ +/* [/Title] */ +/* [Testclasses]stcom1 stmqcom1[/Category] */ +/* [Category]MQ Telemetry[/Category] */ +/* */ +/* Copyright IBM 2013 */ +/* All rights reserved. */ +/*--------------------------------------------------------------------*/ + + +/** + * @file + * Tests for the MQ Telemetry MQTT Async C client + */ + + +/* +#if !defined(_RTSHEADER) + #include +#endif +*/ + +#include "MQTTAsync.h" +#include +#include + +#if !defined(_WINDOWS) + #include + #include + #include + #include +#else +#include +#include +#define MAXHOSTNAMELEN 256 +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#endif + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +void usage() +{ + printf("help!!\n"); + exit(-1); +} + +struct Options +{ + char* connection; /**< connection to system under test. */ + int verbose; + int test_no; + int size; /**< size of big message */ +} options = +{ + "tcp://localhost:1883", + 0, + -1, + 5000000, +}; + +void getopts(int argc, char** argv) +{ + int count = 1; + + while (count < argc) + { + if (strcmp(argv[count], "--test_no") == 0) + { + if (++count < argc) + options.test_no = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--size") == 0) + { + if (++count < argc) + options.size = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--connection") == 0) + { + if (++count < argc) + options.connection = argv[count]; + else + usage(); + } + else if (strcmp(argv[count], "--verbose") == 0) + options.verbose = 1; + count++; + } +} + +#if 0 +#include /* For general log messages */ +#define MyLog logaLine +#else +#define LOGA_DEBUG 0 +#define LOGA_INFO 1 +#include +#include +#include +void MyLog(int LOGA_level, char* format, ...) +{ + static char msg_buf[256]; + va_list args; + struct timeb ts; + + struct tm *timeinfo; + + if (LOGA_level == LOGA_DEBUG && options.verbose == 0) + return; + + ftime(&ts); + timeinfo = localtime(&ts.time); + strftime(msg_buf, 80, "%Y%m%d %H%M%S", timeinfo); + + sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm); + + va_start(args, format); + vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args); + va_end(args); + + printf("%s\n", msg_buf); + fflush(stdout); +} +#endif + + +#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 + + +START_TIME_TYPE global_start_time; + + +#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) + + +int tests = 0; +int 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); + } + else + MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description); +} + +volatile int test_finished = 0; + +char* test_topic = "async test topic"; + + +void test1_onDisconnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MyLog(LOGA_DEBUG, "In onDisconnect callback %p", c); + test_finished = 1; +} + + +void test1_onUnsubscribe(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In onUnsubscribe onSuccess callback %p", c); + opts.onSuccess = test1_onDisconnect; + opts.context = c; + + rc = MQTTAsync_disconnect(c, &opts); + assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +int test1_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) +{ + MQTTAsync c = (MQTTAsync)context; + static int message_count = 0; + int rc; + + MyLog(LOGA_DEBUG, "In messageArrived callback %p", c); + + if (++message_count == 1) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = 2; + pubmsg.retained = 0; + rc = MQTTAsync_sendMessage(c, test_topic, &pubmsg, &opts); + } + else + { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + opts.onSuccess = test1_onUnsubscribe; + opts.context = c; + rc = MQTTAsync_unsubscribe(c, test_topic, &opts); + assert("Unsubscribe successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + + return 1; +} + +void test1_onSubscribe(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In subscribe onSuccess callback %p granted qos %d", c, response->alt.qos); + + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = 2; + pubmsg.retained = 0; + + rc = MQTTAsync_send(c, test_topic, pubmsg.payloadlen, pubmsg.payload, pubmsg.qos, pubmsg.retained, NULL); +} + + +void test1_onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + opts.onSuccess = test1_onSubscribe; + opts.context = c; + + rc = MQTTAsync_subscribe(c, test_topic, 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished = 1; +} + + +void test1_onConnectFailure(void* context, MQTTAsync_failureData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onFailure callback, context %p", context); + + test_finished = 1; +} + + +/********************************************************************* + +Test1: Basic connect, subscribe send and receive. + +*********************************************************************/ +int test1(struct Options options) +{ + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test1"; + char* serverURIs[2] = {"tcp://localhost:1882", options.connection}; + + failures = 0; + MyLog(LOGA_INFO, "Starting test 1 - asynchronous connect"); + + rc = MQTTAsync_create(&c, options.connection, "async_test", + MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTASYNC_SUCCESS, "rc was %d\n", rc); + if (rc != MQTTASYNC_SUCCESS) + { + MQTTAsync_destroy(&c); + goto exit; + } + + rc = MQTTAsync_setCallbacks(c, c, NULL, test1_messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + opts.keepAliveInterval = 20; + opts.cleansession = 1; + opts.username = "testuser"; + opts.password = "testpassword"; + + opts.will = &wopts; + opts.will->message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test1_onConnect; + opts.onFailure = test1_onConnectFailure; + opts.context = c; + opts.serverURIcount = 2; + opts.serverURIs = serverURIs; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + rc = 0; + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + MQTTAsync_destroy(&c); + +exit: + MyLog(LOGA_INFO, "TEST1: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + + return failures; +} + +int test2_onFailure_called = 0; + +void test2_onFailure(void* context, MQTTAsync_failureData* response) +{ + MyLog(LOGA_DEBUG, "In connect onFailure callback, context %p", context); + + test2_onFailure_called++; + test_finished = 1; +} + + +void test2_onConnect(void* context, MQTTAsync_successData* response) +{ + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p\n", context); + + assert("Connect should not succeed", 0, "connect success callback was called", 0); + + test_finished = 1; +} + +/********************************************************************* + +Test2: connect timeout + +*********************************************************************/ +int test2(struct Options options) +{ + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test2"; + + test_finished = 0; + + MyLog(LOGA_INFO, "Starting test 2 - connect timeout"); + + rc = MQTTAsync_create(&c, "tcp://9.20.96.160:66", "connect timeout", + MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTASYNC_SUCCESS, "rc was %d\n", rc); + if (rc != MQTTASYNC_SUCCESS) + { + MQTTAsync_destroy(&c); + goto exit; + } + + rc = MQTTAsync_setCallbacks(c, c, NULL, test1_messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + opts.connectTimeout = 5; + opts.keepAliveInterval = 20; + opts.cleansession = 1; + opts.username = "testuser"; + opts.password = "testpassword"; + + opts.will = &wopts; + opts.will->message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test2_onConnect; + opts.onFailure = test2_onFailure; + opts.context = c; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + rc = 0; + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + + MQTTAsync_destroy(&c); + +exit: + assert("Connect onFailure should be called once", test2_onFailure_called == 1, + "connect onFailure was called %d times", test2_onFailure_called); + + MyLog(LOGA_INFO, "TEST2: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + + return failures; +} + + +typedef struct +{ + MQTTAsync c; + int index; + char clientid[24]; + char test_topic[100]; + int message_count; +} client_data; + + +void test3_onDisconnect(void* context, MQTTAsync_successData* response) +{ + client_data* cd = (client_data*)context; + MyLog(LOGA_DEBUG, "In onDisconnect callback for client \"%s\"", cd->clientid); + test_finished++; +} + + +void test3_onPublish(void* context, MQTTAsync_successData* response) +{ + client_data* cd = (client_data*)context; + MyLog(LOGA_DEBUG, "In QoS 0 onPublish callback for client \"%s\"", cd->clientid); +} + + +void test3_onUnsubscribe(void* context, MQTTAsync_successData* response) +{ + client_data* cd = (client_data*)context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In onUnsubscribe onSuccess callback \"%s\"", cd->clientid); + opts.onSuccess = test3_onDisconnect; + opts.context = cd; + + rc = MQTTAsync_disconnect(cd->c, &opts); + assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +int test3_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) +{ + client_data* cd = (client_data*)context; + int rc; + + MyLog(LOGA_DEBUG, "In messageArrived callback \"%s\" message count ", cd->clientid); + + if (++cd->message_count == 1) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 25; + pubmsg.qos = 1; + pubmsg.retained = 0; + rc = MQTTAsync_sendMessage(cd->c, cd->test_topic, &pubmsg, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + else if (cd->message_count == 2) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + pubmsg.payload = "a QoS 0 message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 29; + pubmsg.qos = 0; + pubmsg.retained = 0; + opts.context = cd; + opts.onSuccess = test3_onPublish; + + rc = MQTTAsync_sendMessage(cd->c, cd->test_topic, &pubmsg, &opts); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + else + { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + opts.onSuccess = test3_onUnsubscribe; + opts.context = cd; + rc = MQTTAsync_unsubscribe(cd->c, cd->test_topic, &opts); + assert("Unsubscribe successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + return 1; +} + +void test3_onSubscribe(void* context, MQTTAsync_successData* response) +{ + client_data* cd = (client_data*)context; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In subscribe onSuccess callback \"%s\"", cd->clientid); + + pubmsg.payload = "a much longer message that we can shorten to the extent that we need to payload up to 11"; + pubmsg.payloadlen = 11; + pubmsg.qos = 2; + pubmsg.retained = 0; + + rc = MQTTAsync_send(cd->c, cd->test_topic, pubmsg.payloadlen, pubmsg.payload, pubmsg.qos, pubmsg.retained, NULL); + assert("Good rc from publish", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +void test3_onConnect(void* context, MQTTAsync_successData* response) +{ + client_data* cd = (client_data*)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, \"%s\"", cd->clientid); + opts.onSuccess = test3_onSubscribe; + opts.context = cd; + + rc = MQTTAsync_subscribe(cd->c, cd->test_topic, 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished++; +} + + +void test3_onFailure(void* context, MQTTAsync_failureData* response) +{ + client_data* cd = (client_data*)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + assert("Should have connected", 0, "failed to connect", NULL); + MyLog(LOGA_DEBUG, "In connect onFailure callback, \"%s\" rc %d\n", cd->clientid, response->code); + if (response->message) + MyLog(LOGA_DEBUG, "In connect onFailure callback, \"%s\"\n", response->message); + + test_finished++; +} + + +/********************************************************************* + +Test3: More than one client object - simultaneous working. + +*********************************************************************/ +int test3(struct Options options) +{ + #define TEST3_CLIENTS 10 + int num_clients = TEST3_CLIENTS; + int subsqos = 2; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + int i; + client_data clientdata[TEST3_CLIENTS]; + + test_finished = 0; + MyLog(LOGA_INFO, "Starting test 3 - multiple connections"); + + for (i = 0; i < num_clients; ++i) + { + sprintf(clientdata[i].clientid, "async_test3_num_%d", i); + sprintf(clientdata[i].test_topic, "async test3 topic num %d", i); + clientdata[i].index = i; + clientdata[i].message_count = 0; + + rc = MQTTAsync_create(&(clientdata[i].c), options.connection, clientdata[i].clientid, + MQTTCLIENT_PERSISTENCE_NONE, NULL); + assert("good rc from create", rc == MQTTASYNC_SUCCESS, "rc was %d\n", rc); + + rc = MQTTAsync_setCallbacks(clientdata[i].c, &clientdata[i], NULL, test3_messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + opts.keepAliveInterval = 20; + opts.cleansession = 1; + opts.username = "testuser"; + opts.password = "testpassword"; + + opts.will = &wopts; + opts.will->message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.onSuccess = test3_onConnect; + opts.onFailure = test3_onFailure; + opts.context = &clientdata[i]; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(clientdata[i].c, &opts); + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + while (test_finished < num_clients) + { + MyLog(LOGA_DEBUG, "num_clients %d test_finished %d\n", num_clients, test_finished); + #if defined(WIN32) + Sleep(100); + #else + usleep(10000L); + #endif + } + + MyLog(LOGA_DEBUG, "TEST3: destroying clients"); + + for (i = 0; i < num_clients; ++i) + MQTTAsync_destroy(&clientdata[i].c); + +exit: + MyLog(LOGA_INFO, "TEST3: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + + return failures; +} + + +void* test4_payload = NULL; +int test4_payloadlen = 0; + +void test4_onPublish(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + + MyLog(LOGA_DEBUG, "In publish onSuccess callback, context %p", context); +} + +int test4_messageArrived(void* context, char* topicName, int topicLen, MQTTAsync_message* message) +{ + MQTTAsync c = (MQTTAsync)context; + static int message_count = 0; + int rc, i; + + MyLog(LOGA_DEBUG, "In messageArrived callback %p", c); + + assert("Message size correct", message->payloadlen == test4_payloadlen, + "message size was %d", message->payloadlen); + + for (i = 0; i < options.size; ++i) + { + if (((char*)test4_payload)[i] != ((char*)message->payload)[i]) + { + assert("Message contents correct", ((char*)test4_payload)[i] != ((char*)message->payload)[i], + "message content was %c", ((char*)message->payload)[i]); + break; + } + } + + if (++message_count == 1) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + pubmsg.payload = test4_payload; + pubmsg.payloadlen = test4_payloadlen; + pubmsg.qos = 1; + pubmsg.retained = 0; + opts.onSuccess = test4_onPublish; + opts.context = c; + + rc = MQTTAsync_sendMessage(c, test_topic, &pubmsg, &opts); + } + else if (message_count == 2) + { + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + pubmsg.payload = test4_payload; + pubmsg.payloadlen = test4_payloadlen; + pubmsg.qos = 0; + pubmsg.retained = 0; + opts.onSuccess = test4_onPublish; + opts.context = c; + rc = MQTTAsync_sendMessage(c, test_topic, &pubmsg, &opts); + } + else + { + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + + opts.onSuccess = test1_onUnsubscribe; + opts.context = c; + rc = MQTTAsync_unsubscribe(c, test_topic, &opts); + assert("Unsubscribe successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + } + + MQTTAsync_freeMessage(&message); + MQTTAsync_free(topicName); + + return 1; +} + + +void test4_onSubscribe(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_message pubmsg = MQTTAsync_message_initializer; + int rc, i; + + MyLog(LOGA_DEBUG, "In subscribe onSuccess callback %p", c); + + pubmsg.payload = test4_payload = malloc(options.size); + pubmsg.payloadlen = test4_payloadlen = options.size; + + srand(33); + for (i = 0; i < options.size; ++i) + ((char*)pubmsg.payload)[i] = rand() % 256; + + pubmsg.qos = 2; + pubmsg.retained = 0; + + rc = MQTTAsync_send(c, test_topic, pubmsg.payloadlen, pubmsg.payload, pubmsg.qos, pubmsg.retained, NULL); +} + + +void test4_onConnect(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + opts.onSuccess = test4_onSubscribe; + opts.context = c; + + rc = MQTTAsync_subscribe(c, test_topic, 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished = 1; +} + + +/********************************************************************* + +Test4: Send and receive big messages + +*********************************************************************/ +int test4(struct Options options) +{ + int subsqos = 2; + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + MQTTAsync_willOptions wopts = MQTTAsync_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test4"; + + test_finished = failures = 0; + MyLog(LOGA_INFO, "Starting test 4 - big messages"); + + rc = MQTTAsync_create(&c, options.connection, "async_test_4", + MQTTCLIENT_PERSISTENCE_DEFAULT, NULL); + assert("good rc from create", rc == MQTTASYNC_SUCCESS, "rc was %d\n", rc); + if (rc != MQTTASYNC_SUCCESS) + { + MQTTAsync_destroy(&c); + goto exit; + } + + rc = MQTTAsync_setCallbacks(c, c, NULL, test4_messageArrived, NULL); + assert("Good rc from setCallbacks", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + + opts.keepAliveInterval = 20; + opts.cleansession = 1; + opts.username = "testuser"; + opts.password = "testpassword"; + + opts.will = &wopts; + opts.will->message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + opts.onSuccess = test4_onConnect; + opts.onFailure = NULL; + opts.context = c; + + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTAsync_connect(c, &opts); + rc = 0; + assert("Good rc from connect", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + goto exit; + + while (!test_finished) + #if defined(WIN32) + Sleep(100); + #else + usleep(1000L); + #endif + + MQTTAsync_destroy(&c); + +exit: + MyLog(LOGA_INFO, "TEST4: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + + return failures; +} + + +void trace_callback(enum MQTTASYNC_TRACE_LEVELS level, char* message) +{ + if (strstr(message, "onnect") && !strstr(message, "isconnect")) + printf("Trace : %d, %s\n", level, message); +} + + +int main(int argc, char** argv) +{ + int rc = 0; + int (*tests[])() = {NULL, test1, test2, test3, test4}; /* indexed starting from 1 */ + MQTTAsync_nameValue* info; + + getopts(argc, argv); + + MQTTAsync_setTraceCallback(trace_callback); + + info = MQTTAsync_getVersionInfo(); + + while (info->name) + { + MyLog(LOGA_INFO, "%s: %s", info->name, info->value); + info++; + } + + if (options.test_no == -1) + { /* run all the tests */ + for (options.test_no = 1; options.test_no < ARRAY_SIZE(tests); ++options.test_no) + { + failures = 0; + //MQTTAsync_setTraceLevel(MQTTASYNC_TRACE_ERROR); + rc += tests[options.test_no](options); /* return number of failures. 0 = test succeeded */ + } + } + else + { + //MQTTAsync_setTraceLevel(MQTTASYNC_TRACE_ERROR); + rc = tests[options.test_no](options); /* run just the selected test */ + } + + if (failures == 0) + MyLog(LOGA_INFO, "verdict pass"); + else + MyLog(LOGA_INFO, "verdict fail"); + + return rc; +} diff --git a/Sources/paho/test/test_mqtt4async.c b/Sources/paho/test/test_mqtt4async.c new file mode 100644 index 0000000..603abd5 --- /dev/null +++ b/Sources/paho/test/test_mqtt4async.c @@ -0,0 +1,628 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - MQTT 3.1.1 support + *******************************************************************************/ + + +/** + * @file + * MQTT 3.1.1 Tests for the asynchronous Paho MQTT C client + */ + + +/* +#if !defined(_RTSHEADER) + #include +#endif +*/ + +#include "MQTTAsync.h" +#include +#include + +#if !defined(_WINDOWS) + #include + #include + #include + #include +#else +#include +#include +#define MAXHOSTNAMELEN 256 +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#define setenv(a, b, c) _putenv_s(a, b) +#endif + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +void usage() +{ + printf("help!!\n"); + exit(-1); +} + +struct Options +{ + char* connection; /**< connection to system under test. */ + char** haconnections; + int hacount; + int verbose; + int test_no; + int iterations; +} options = +{ + "m2m.eclipse.org:1883", + NULL, + 0, + 0, + 0, + 1, +}; + +void getopts(int argc, char** argv) +{ + int count = 1; + + while (count < argc) + { + if (strcmp(argv[count], "--test_no") == 0) + { + if (++count < argc) + options.test_no = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--connection") == 0) + { + if (++count < argc) + { + options.connection = argv[count]; + printf("\nSetting connection to %s\n", options.connection); + } + else + usage(); + } + else if (strcmp(argv[count], "--haconnections") == 0) + { + if (++count < argc) + { + char* tok = strtok(argv[count], " "); + options.hacount = 0; + options.haconnections = malloc(sizeof(char*) * 5); + while (tok) + { + options.haconnections[options.hacount] = malloc(strlen(tok) + 1); + strcpy(options.haconnections[options.hacount], tok); + options.hacount++; + tok = strtok(NULL, " "); + } + } + else + usage(); + } + else if (strcmp(argv[count], "--iterations") == 0) + { + if (++count < argc) + options.iterations = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--verbose") == 0) + { + options.verbose = 1; + printf("\nSetting verbose on\n"); + } + count++; + } +} + + +#define LOGA_DEBUG 0 +#define LOGA_INFO 1 +#include +#include +#include +void MyLog(int LOGA_level, char* format, ...) +{ + static char msg_buf[256]; + va_list args; + struct timeb ts; + + struct tm *timeinfo; + + if (LOGA_level == LOGA_DEBUG && options.verbose == 0) + return; + + ftime(&ts); + timeinfo = localtime(&ts.time); + strftime(msg_buf, 80, "%Y%m%d %H%M%S", timeinfo); + + sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm); + + va_start(args, format); + vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args); + va_end(args); + + printf("%s\n", msg_buf); + fflush(stdout); +} + + +#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 + + +#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) + +int tests = 0; +int failures = 0; +FILE* xml; +START_TIME_TYPE global_start_time; +char output[3000]; +char* cur_output = output; +int test_finished = 0; + + +void write_test_result() +{ + long duration = elapsed(global_start_time); + + fprintf(xml, " time=\"%ld.%.3ld\" >\n", duration / 1000, duration % 1000); + if (cur_output != output) + { + fprintf(xml, "%s", output); + cur_output = output; + } + fprintf(xml, "\n"); +} + + +void myassert(char* filename, int lineno, char* description, int value, char* format, ...) +{ + ++tests; + if (!value) + { + va_list args; + + ++failures; + MyLog(LOGA_INFO, "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, "file %s, line %d \n", + description, filename, lineno); + } + else + MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description); +} + + +void test1_onDisconnect3(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + + MyLog(LOGA_DEBUG, "In onDisconnect callback %p", c); + test_finished = 1; +} + + +void test1_onConnect3(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + opts.onSuccess = test1_onDisconnect3; + opts.context = c; + + assert("Correct serverURI returned", strstr(response->alt.connect.serverURI, options.connection) != NULL, + "serverURI was %s", response->alt.connect.serverURI); + assert("Correct MQTTVersion returned", response->alt.connect.MQTTVersion == 4, + "MQTTVersion was %d", response->alt.connect.MQTTVersion); + assert("Correct sessionPresent returned", response->alt.connect.sessionPresent == 1, + "sessionPresent was %d", response->alt.connect.sessionPresent); + + rc = MQTTAsync_disconnect(c, &opts); + assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +void test1_onDisconnect2(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In onDisconnect callback %p", c); + + opts.MQTTVersion = 4; + opts.cleansession = 0; + if (options.haconnections != NULL) + { + opts.serverURIs = options.haconnections; + opts.serverURIcount = options.hacount; + } + opts.onSuccess = test1_onConnect3; + opts.onFailure = NULL; + opts.context = c; + + opts.cleansession = 0; + rc = MQTTAsync_connect(c, &opts); + assert("Connect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +void test1_onConnect2(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + opts.onSuccess = test1_onDisconnect2; + opts.context = c; + + assert("Correct serverURI returned", strcmp(response->alt.connect.serverURI, options.connection) == 0, + "serverURI was %s", response->alt.connect.serverURI); + assert("Correct MQTTVersion returned", response->alt.connect.MQTTVersion == 4, + "MQTTVersion was %d", response->alt.connect.MQTTVersion); + assert("Correct sessionPresent returned", response->alt.connect.sessionPresent == 0, + "sessionPresent was %d", response->alt.connect.sessionPresent); + + rc = MQTTAsync_disconnect(c, &opts); + assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +void test1_onDisconnect1(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In onDisconnect callback %p", c); + + opts.MQTTVersion = 4; + opts.cleansession = 0; + if (options.haconnections != NULL) + { + opts.serverURIs = options.haconnections; + opts.serverURIcount = options.hacount; + } + opts.onSuccess = test1_onConnect2; + opts.onFailure = NULL; + opts.context = c; + + opts.cleansession = 0; + rc = MQTTAsync_connect(c, &opts); + assert("Connect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +void test1_onConnect1(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_disconnectOptions opts = MQTTAsync_disconnectOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback 1, context %p", context); + opts.onSuccess = test1_onDisconnect1; + opts.context = c; + + assert("Correct serverURI returned", strcmp(response->alt.connect.serverURI, options.connection) == 0, + "serverURI was %s", response->alt.connect.serverURI); + assert("Correct MQTTVersion returned", response->alt.connect.MQTTVersion == 4, + "MQTTVersion was %d", response->alt.connect.MQTTVersion); + assert("Correct sessionPresent returned", response->alt.connect.sessionPresent == 0, + "sessionPresent was %d", response->alt.connect.sessionPresent); + + rc = MQTTAsync_disconnect(c, &opts); + assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +/********************************************************************* + +Test1: sessionPresent + +*********************************************************************/ +int test1(struct Options options) +{ + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + int rc = 0; + char* test_topic = "C client test1"; + + fprintf(xml, "code == MQTT_BAD_SUBSCRIBE, + "qos was %d", response->code); + + opts.onSuccess = test2_onDisconnect; + rc = MQTTAsync_disconnect(c, &opts); + assert("Disconnect successful", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); +} + + +void test2_onSubscribe1(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In subscribe onSuccess callback, context %p", context); + + assert("Correct subscribe return code", response->alt.qos == 2, + "qos was %d", response->alt.qos); +} + + +void test2_onConnect1(void* context, MQTTAsync_successData* response) +{ + MQTTAsync c = (MQTTAsync)context; + MQTTAsync_responseOptions opts = MQTTAsync_responseOptions_initializer; + int rc; + + MyLog(LOGA_DEBUG, "In connect onSuccess callback 1, context %p", context); + + assert("Correct serverURI returned", strcmp(response->alt.connect.serverURI, options.connection) == 0, + "serverURI was %s", response->alt.connect.serverURI); + assert("Correct MQTTVersion returned", response->alt.connect.MQTTVersion == 4, + "MQTTVersion was %d", response->alt.connect.MQTTVersion); + assert("Correct sessionPresent returned", response->alt.connect.sessionPresent == 0, + "sessionPresent was %d", response->alt.connect.sessionPresent); + + MyLog(LOGA_DEBUG, "In connect onSuccess callback, context %p", context); + opts.context = c; + + opts.onSuccess = test2_onSubscribe1; + rc = MQTTAsync_subscribe(c, "a topic I can subscribe to", 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished = 1; + + opts.onSuccess = NULL; + opts.onFailure = test2_onSubscribe2; + rc = MQTTAsync_subscribe(c, "nosubscribe", 2, &opts); + assert("Good rc from subscribe", rc == MQTTASYNC_SUCCESS, "rc was %d", rc); + if (rc != MQTTASYNC_SUCCESS) + test_finished = 1; +} + + + +/********************************************************************* + +Test1: 0x80 from subscribe + +*********************************************************************/ +int test2(struct Options options) +{ + MQTTAsync c; + MQTTAsync_connectOptions opts = MQTTAsync_connectOptions_initializer; + int rc = 0; + char* test_topic = "C client test1"; + + fprintf(xml, "\n", (int)(ARRAY_SIZE(tests) - 1)); + + setenv("MQTT_C_CLIENT_TRACE", "ON", 1); + setenv("MQTT_C_CLIENT_TRACE_LEVEL", "ERROR", 1); + + getopts(argc, argv); + + for (i = 0; i < options.iterations; ++i) + { + if (options.test_no == 0) + { /* run all the tests */ + for (options.test_no = 1; options.test_no < ARRAY_SIZE(tests); ++options.test_no) + rc += tests[options.test_no](options); /* return number of failures. 0 = test succeeded */ + } + else + rc = tests[options.test_no](options); /* run just the selected test */ + } + + if (rc == 0) + MyLog(LOGA_INFO, "verdict pass"); + else + MyLog(LOGA_INFO, "verdict fail"); + + fprintf(xml, "\n"); + fclose(xml); + return rc; +} diff --git a/Sources/paho/test/test_mqtt4sync.c b/Sources/paho/test/test_mqtt4sync.c new file mode 100644 index 0000000..b6bae8d --- /dev/null +++ b/Sources/paho/test/test_mqtt4sync.c @@ -0,0 +1,498 @@ +/******************************************************************************* + * 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 v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - MQTT 3.1.1 support + *******************************************************************************/ + + +/** + * @file + * MQTT 3.1.1 Tests for the synchronous Paho MQTT C client + */ + + +/* +#if !defined(_RTSHEADER) + #include +#endif +*/ + +#include "MQTTClient.h" +#include +#include + +#if !defined(_WINDOWS) + #include + #include + #include + #include +#else +#include +#include +#define MAXHOSTNAMELEN 256 +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#define setenv(a, b, c) _putenv_s(a, b) +#endif + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +void usage() +{ + printf("help!!\n"); + exit(-1); +} + +struct Options +{ + char* connection; /**< connection to system under test. */ + char** haconnections; + int hacount; + int verbose; + int test_no; + int iterations; +} options = +{ + "tcp://m2m.eclipse.org:1883", + NULL, + 0, + 0, + 0, + 1, +}; + +void getopts(int argc, char** argv) +{ + int count = 1; + + while (count < argc) + { + if (strcmp(argv[count], "--test_no") == 0) + { + if (++count < argc) + options.test_no = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--connection") == 0) + { + if (++count < argc) + { + options.connection = argv[count]; + printf("\nSetting connection to %s\n", options.connection); + } + else + usage(); + } + else if (strcmp(argv[count], "--haconnections") == 0) + { + if (++count < argc) + { + char* tok = strtok(argv[count], " "); + options.hacount = 0; + options.haconnections = malloc(sizeof(char*) * 5); + while (tok) + { + options.haconnections[options.hacount] = malloc(strlen(tok) + 1); + strcpy(options.haconnections[options.hacount], tok); + options.hacount++; + tok = strtok(NULL, " "); + } + } + else + usage(); + } + else if (strcmp(argv[count], "--iterations") == 0) + { + if (++count < argc) + options.iterations = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--verbose") == 0) + { + options.verbose = 1; + printf("\nSetting verbose on\n"); + } + count++; + } +} + + +#define LOGA_DEBUG 0 +#define LOGA_INFO 1 +#include +#include +#include +void MyLog(int LOGA_level, char* format, ...) +{ + static char msg_buf[256]; + va_list args; + struct timeb ts; + + struct tm *timeinfo; + + if (LOGA_level == LOGA_DEBUG && options.verbose == 0) + return; + + ftime(&ts); + timeinfo = localtime(&ts.time); + strftime(msg_buf, 80, "%Y%m%d %H%M%S", timeinfo); + + sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm); + + va_start(args, format); + vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args); + va_end(args); + + printf("%s\n", msg_buf); + fflush(stdout); +} + + +#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 + + +#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) + +int tests = 0; +int failures = 0; +FILE* xml; +START_TIME_TYPE global_start_time; +char output[3000]; +char* cur_output = output; + + +void write_test_result() +{ + long duration = elapsed(global_start_time); + + fprintf(xml, " time=\"%ld.%.3ld\" >\n", duration / 1000, duration % 1000); + if (cur_output != output) + { + fprintf(xml, "%s", output); + cur_output = output; + } + fprintf(xml, "\n"); +} + + +void myassert(char* filename, int lineno, char* description, int value, char* format, ...) +{ + ++tests; + if (!value) + { + va_list args; + + ++failures; + MyLog(LOGA_INFO, "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, "file %s, line %d \n", + description, filename, lineno); + } + else + MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description); +} + + +/********************************************************************* + +Test1: sessionPresent + +*********************************************************************/ +int test1(struct Options options) +{ + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test1"; + + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + + /* Connect cleansession */ + opts.cleansession = 1; + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTClient_connect(c, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + if (rc != MQTTCLIENT_SUCCESS) + goto exit; + + assert("Correct serverURI returned", strcmp(opts.returned.serverURI, options.connection) == 0, "serverURI was %s", + opts.returned.serverURI); + assert("Correct MQTTVersion returned", opts.returned.MQTTVersion == 4, "MQTTVersion was %d", + opts.returned.MQTTVersion); + assert("Correct sessionPresent returned", opts.returned.sessionPresent == 0, "sessionPresent was %d", + opts.returned.sessionPresent); + + rc = MQTTClient_disconnect(c, 0); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + /* Connect again, non-cleansession */ + opts.cleansession = 0; + rc = MQTTClient_connect(c, &opts); + assert("Connect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + assert("Correct serverURI returned", strcmp(opts.returned.serverURI, options.connection) == 0, "serverURI was %s", + opts.returned.serverURI); + assert("Correct MQTTVersion returned", opts.returned.MQTTVersion == 4, "MQTTVersion was %d", + opts.returned.MQTTVersion); + assert("Correct sessionPresent returned", opts.returned.sessionPresent == 0, "sessionPresent was %d", + opts.returned.sessionPresent); + + rc = MQTTClient_disconnect(c, 0); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + /* Connect again, non-cleansession */ + opts.cleansession = 0; + rc = MQTTClient_connect(c, &opts); + assert("Connect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + assert("Correct serverURI returned", strcmp(opts.returned.serverURI, options.connection) == 0, "serverURI was %s", + opts.returned.serverURI); + assert("Correct MQTTVersion returned", opts.returned.MQTTVersion == 4, "MQTTVersion was %d", + opts.returned.MQTTVersion); + assert("Correct sessionPresent returned", opts.returned.sessionPresent == 1, "sessionPresent was %d", + opts.returned.sessionPresent); + rc = MQTTClient_disconnect(c, 0); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + MQTTClient_destroy(&c); + +exit: + MyLog(LOGA_INFO, "TEST1: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + write_test_result(); + return failures; +} + + +/********************************************************************* + +Test2: 0x80 return code from subscribe + +*********************************************************************/ +volatile int test2_arrivedcount = 0; +int test2_deliveryCompleted = 0; +MQTTClient_message test2_pubmsg = MQTTClient_message_initializer; + +void test2_deliveryComplete(void* context, MQTTClient_deliveryToken dt) +{ + ++test2_deliveryCompleted; +} + +int test2_messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m) +{ + ++test2_arrivedcount; + MyLog(LOGA_DEBUG, "Callback: %d message received on topic %s is %.*s.", + test2_arrivedcount, topicName, m->payloadlen, (char*)(m->payload)); + MQTTClient_free(topicName); + MQTTClient_freeMessage(&m); + return 1; +} + +int test2(struct Options options) +{ + char* testname = "test2"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + int rc = 0; + char* test_topic = "C client test2"; + char* topics[2] = {"test_topic", "nosubscribe"}; + int qoss[2] = {2, 2}; + + fprintf(xml, "\n", (int)(ARRAY_SIZE(tests) - 1)); + + setenv("MQTT_C_CLIENT_TRACE", "ON", 1); + setenv("MQTT_C_CLIENT_TRACE_LEVEL", "ERROR", 1); + + getopts(argc, argv); + + for (i = 0; i < options.iterations; ++i) + { + if (options.test_no == 0) + { /* run all the tests */ + for (options.test_no = 1; options.test_no < ARRAY_SIZE(tests); ++options.test_no) + rc += tests[options.test_no](options); /* return number of failures. 0 = test succeeded */ + } + else + rc = tests[options.test_no](options); /* run just the selected test */ + } + + if (rc == 0) + MyLog(LOGA_INFO, "verdict pass"); + else + MyLog(LOGA_INFO, "verdict fail"); + + fprintf(xml, "\n"); + fclose(xml); + return rc; +}