From 99048c1cbf0e97b4906a75462ece3f7db4979387 Mon Sep 17 00:00:00 2001 From: Steven de Ridder Date: Mon, 24 Jan 2022 16:09:21 +0100 Subject: [PATCH] Initial commit. dependencies not resolved yet. --- .gitignore | 2 ++ CMakeLists.txt | 25 +++++++++++++++++++++++++ README.md | 0 src/CMakeLists.txt | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/msgparser.cpp | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/msgparser.h | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/poolmanager.cpp | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/poolmanager.h | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/socketcontainer.cpp | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/socketcontainer.h | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tcpinterface.cpp | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tcpinterface.h | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tcpsocket.cpp | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tcpsocket.h | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 30 ++++++++++++++++++++++++++++++ 15 files changed, 1661 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 src/CMakeLists.txt create mode 100644 src/msgparser.cpp create mode 100644 src/msgparser.h create mode 100644 src/poolmanager.cpp create mode 100644 src/poolmanager.h create mode 100644 src/socketcontainer.cpp create mode 100644 src/socketcontainer.h create mode 100644 src/tcpinterface.cpp create mode 100644 src/tcpinterface.h create mode 100644 src/tcpsocket.cpp create mode 100644 src/tcpsocket.h create mode 100644 tests/CMakeLists.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ff047c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +CMakeLists.txt.user diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..adbffc0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.0) + +# Check to see where cmake is located. +if( IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cmake ) + LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +elseif( IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../cmake ) + LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake) +else() + return() +endif() + +# Check to see if there is versioning information available +if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/osdev_versioning/cmake) + LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/osdev_versioning/cmake) + include(osdevversion) +endif() + +include(projectheader) +project_header(osdev_network) + +add_subdirectory(src) +add_subdirectory(tests) + +# include(packaging) +# package_component() diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/README.md diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..494db1d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.0) +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake) +include(projectheader) +project_header(network) + +find_package( Qt5Core REQUIRED ) +find_package( Qt5Network REQUIRED ) + +include_directories( SYSTEM + ${Qt5Core_INCLUDE_DIRS} + ${Qt5Network_INCLUDE_DIRS} +) + +include(compiler) + +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../config + ${CMAKE_CURRENT_SOURCE_DIR}/../global + ${CMAKE_CURRENT_SOURCE_DIR}/../logutils + ${CMAKE_CURRENT_SOURCE_DIR}/../datatypes +) + +set(SRC_LIST + ${CMAKE_CURRENT_SOURCE_DIR}/poolmanager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/socketcontainer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tcpinterface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tcpsocket.cpp + +) + +include(qtmoc) +create_mocs( SRC_LIST MOC_LIST + ${CMAKE_CURRENT_SOURCE_DIR}/poolmanager.h + ${CMAKE_CURRENT_SOURCE_DIR}/tcpinterface.h + ${CMAKE_CURRENT_SOURCE_DIR}/tcpsocket.h +) + +link_directories( + ${CMAKE_BINARY_DIR}/lib +) + +include(library) +add_libraries( + ${Qt5Core_LIBRARIES} + ${Qt5Network_LIBRARIES} + global + logutils + datatypes +) + +include(installation) +install_component() diff --git a/src/msgparser.cpp b/src/msgparser.cpp new file mode 100644 index 0000000..ec329d0 --- /dev/null +++ b/src/msgparser.cpp @@ -0,0 +1,143 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ +#include "msgparser.h" + +namespace osdev { +namespace components { + +const QString MsgParser::s_messageElementTag = "message"; +const QString MsgParser::s_sourceElementTag = "source"; +const QString MsgParser::s_destElementTag = "dest"; +const QString MsgParser::s_actionElementTag = "action"; +const QString MsgParser::s_dataElementTag = "data"; +const QString MsgParser::s_idAttributeTag = "id"; + +MsgParser::MsgParser(QObject* _parent) + : QObject(_parent) + , m_message() + , m_sourceId() + , m_destId() + , m_actionId(-1) + , m_actionData() +{ +} + +MsgParser::~MsgParser() +{ +} + +void MsgParser::createMsg(const QString& sourceId, const QString& destId, + int actionId, const QString& actionData) +{ + static const QString xmlVar("<%1 %2='%3'/>"); + static const QString xmlData("<%1>%2"); + static const QString xmlMessage("<%1>%2 %3 %4 %5"); + + QString src = xmlVar.arg(s_sourceElementTag).arg(s_idAttributeTag).arg(sourceId); + QString dst = xmlVar.arg(s_destElementTag).arg(s_idAttributeTag).arg(destId); + QString action = xmlVar.arg(s_actionElementTag).arg(s_idAttributeTag).arg(actionId); + QString data = xmlData.arg(s_dataElementTag).arg(actionData); + + m_message = xmlMessage.arg(s_messageElementTag).arg(src).arg(dst).arg(action).arg(data); +} + +void MsgParser::createMsg(const QString& sourceId, const QString& destId, + int actionId, xercesc_2_7::DOMDocument* actionData) +{ + QString msgData; + + CXmlParser* pSerializer = new CXmlParser(); + // Store the complete message. + msgData = pSerializer->serialize(actionData); + delete pSerializer; + + static const QString xmlVar("<%1 %2='%3'/>"); + static const QString xmlData("<%1>%2"); + static const QString xmlMessage("<%1>%2 %3 %4 %5"); + + QString src = xmlVar.arg(s_sourceElementTag).arg(s_idAttributeTag).arg(sourceId); + QString dst = xmlVar.arg(s_destElementTag).arg(s_idAttributeTag).arg(destId); + QString action = xmlVar.arg(s_actionElementTag).arg(s_idAttributeTag).arg(actionId); + QString data = xmlData.arg(s_dataElementTag).arg(msgData); + + m_message = xmlMessage.arg(s_messageElementTag).arg(src).arg(dst).arg(action).arg(data); +} + +void MsgParser::parseMsg(const QString& msg) +{ + CXmlParser parser; + + DOMDocument* domDocument = parser.parse(msg); + DOMNode* rootNode = domDocument->getDocumentElement(); + + DOMNode* node = rootNode->getFirstChild(); + if (CXerces::nodeName(node) == sourceElementTag) + { + m_sourceId = CXerces::getNodeAttribute(node, qPrintable(s_idAttributeTag)); + } + + node = node->getNextSibling(); + if (CXerces::nodeName(node) == destElementTag) + { + m_destId = CXerces::getNodeAttribute(node, qPrintable(s_idAttributeTag)); + } + + node = node->getNextSibling(); + if (CXerces::nodeName(node) == s_actionElementTag) + { + m_actionId = CXerces::getNodeAttribute(node, qPrintable(s_idAttributeTag)).toInt(); + } + + node = node->getNextSibling(); + if (CXerces::nodeName(node) == s_dataElementTag) + { + m_actionData = parser.serialize(node); + } +} + +QString MsgParser::getMsg() const +{ + return m_message; +} + +QString MsgParser::getSourceId() const +{ + return m_sourceId; +} + +QString MsgParser::getDestId() const +{ + return m_destId; +} + +int MsgParser::getActionId() const +{ + return m_actionId; +} + +QString MsgParser::getActionData() const +{ + return m_actionData; +} + +} +} diff --git a/src/msgparser.h b/src/msgparser.h new file mode 100644 index 0000000..fd32c8e --- /dev/null +++ b/src/msgparser.h @@ -0,0 +1,103 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ +// +// Description : +// Revision : +// Confidentiality : +// Dependencies : +// + +#ifndef OSDEV_COMPONENTS_CMSGPARSER_H +#define OSDEV_COMPONENTS_CMSGPARSER_H + +#include +#include +#include + +namespace osdev { +namespace components { + +/** + * @brief The MsgParser class + */ +class MsgParser : public QObject +{ + Q_OBJECT + +public: + /** + * @brief Constructor + * @param _parent Parent QObject + */ + MsgParser(QObject* _parent = nullptr); + /// @brief Destructor + ~MsgParser(); + + /** + * @brief Create a message with the given parameters + * \param sourceID The source ID of the interface calling this function. + * \param destID The destination ID of the interface receiving the data. + * \param actionId The action to be performed on the data. + * \param actionData The actual data (This must be XML formatted) + */ + void createMsg(const QString& sourceID, const QString& destID, + int actionId, const QString& actionData); + + /** + * @brief Retrieve the fields from the specified message + * @param msg Message to parse + */ + void parseMsg(const QString& msg); + + /// @return Message to parse + QString getMsg() const; + /// @return Source ID from the message + QString getSourceId() const; + /// @return Destination ID from the message + QString getDestId() const; + /// @return Action ID from the message + int getActionId() const; + /// @return Action data from the message + QString getActionData() const; + +// void createMsg(const QString& sourceID, const QString& destID, +// int actionId, xercesc_2_7::DOMDocument* actionData); + +private: + QString m_message; ///< Full message + QString m_sourceId; ///< Source ID in the message + QString m_destId; ///< Destination ID in the message + int m_actionId; ///< Action ID in the message + QString m_actionData; ///< Action Data in the message + + static const QString s_messageElementTag; + static const QString s_sourceElementTag; + static const QString s_destElementTag; + static const QString s_actionElementTag; + static const QString s_dataElementTag; + static const QString s_idAttributeTag; +}; + +} // End namespace components +} // End namespace osdev + +#endif /* OSDEV_COMPONENTS_CMSGPARSER_H */ diff --git a/src/poolmanager.cpp b/src/poolmanager.cpp new file mode 100644 index 0000000..ce758de --- /dev/null +++ b/src/poolmanager.cpp @@ -0,0 +1,243 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ +#include "poolmanager.h" +#include "log.h" +#include + +namespace osdev { +namespace components { + +PoolManager::PoolManager(bool bServer, + const QString& ipAddress, + int portNumber, + int maxTcpConnections, + int maxUdpConnections, + QObject *_parent ) + : QObject( _parent ) + , m_pServer( nullptr ) + , m_pSocketContainer( new SocketContainer() ) + , m_pSocket( nullptr ) + , m_inputBuffer() + , m_outputBuffer() + , m_bServer( bServer ) + , m_ipAddress( ipAddress ) + , m_portNumber( portNumber ) + , m_maxTcpConnections( maxTcpConnections ) + , m_maxUdpConnections( maxUdpConnections ) +{ +} + +PoolManager::~PoolManager() +{ + delete m_pSocketContainer; + m_pSocketContainer = nullptr; +} +/*************************************************************************** + * Server Section + ***************************************************************************/ +void PoolManager::slotNewConnection() +{ + // Create the socket, pass the pendingconnection and add it to our channelList. + + QTcpSocket* nextConnection = m_pServer->nextPendingConnection(); + if( nextConnection ) + { + m_pSocket = new TcpSocket( nextConnection ); + m_pSocketContainer->addSocket( m_pSocket ); + + // Connect the socket to the poolmanager + this->connectSocketSignals( m_pSocket ); + } +} + +bool PoolManager::startNetworkLayer() +{ + bool bResult = false; + + if( m_bServer ) + { + m_pServer = new QTcpServer; // Create our server object + + connect( m_pServer, SIGNAL( newConnection() ), + this, SLOT( slotNewConnection() ) ); + + m_pServer->setMaxPendingConnections( m_maxTcpConnections ); + bResult = m_pServer->listen( QHostAddress(m_ipAddress), static_cast(m_portNumber) ); + if( bResult ) + { + LogDebug("interfaceplugin", "Server started"); + } + else + { + LogError("interfaceplugin", "Server error : " + m_pServer->errorString()); + } + } + else + { + // Here we start a couple of client connections. + for( int nCounter = 0; nCounter < m_maxTcpConnections; nCounter++ ) + { + m_pSocket = new TcpSocket( m_ipAddress, m_portNumber ); + bResult = m_pSocket->isConnected(); + if( bResult ) + { + m_pSocketContainer->addSocket( m_pSocket ); + this->connectSocketSignals( m_pSocket ); + emit message( 0, 0, osdev::components::gEventConnectionEstablished, 0 ); + } + else + { + LogDebug("[PoolManager::startNetworkLayer()]", QString( "Socket Error : %1" ).arg( m_pSocket->showError() ) ); + delete m_pSocket; + } + } + } + return bResult; +} + +void PoolManager::slotDataSent( TcpSocket *pSocket ) +{ + m_pSocketContainer->setChannelFree( pSocket ); + + // Check for delayed messages and send the first one. + this->resendDelayedMessages(); +} + +void PoolManager::slotReceivingData( TcpSocket *pSocket ) +{ + m_pSocketContainer->setChannelBusy( pSocket ); +} + +void PoolManager::slotDataReceived( const QString &sData, TcpSocket *pSocket ) +{ + // Create a unique ticket + QString l_sTicket = createBufferTicket(); + + // Store the dat with the ticket as key + m_outputBuffer.insert( l_sTicket, sData ); + + // Free the channel. + m_pSocketContainer->setChannelFree( pSocket ); + + // Signal the upper layer we have new data available. + emit dataPresent( l_sTicket ); + + // Resend first delayed messages if there are any. + this->resendDelayedMessages(); +} + +void PoolManager::sendData( const QString &sData ) +{ + // Get a free channel and signal the data we want to send. + TcpSocket *pSocket = m_pSocketContainer->getFreeSocket(); + if( pSocket ) + { + m_pSocketContainer->setChannelBusy( pSocket ); + emit signalSendData( sData, pSocket ); + QCoreApplication::processEvents(); + } + else + { + LogError("NetworkInterface", QString("No free channel available. Storing for delayed send. Message number: %1").arg(m_inputBuffer.size() + 1)); + m_inputBuffer.append( sData ); + } +} + +QString PoolManager::createBufferTicket() +{ + return QUuid::createUuid().toString(); +} + +QString PoolManager::getData(const QString& sTicket ) +{ + if( m_outputBuffer.contains( sTicket ) ) + { + return m_outputBuffer.take( sTicket ); + } + else + { + return QString( "[ERROR : ] No data present in outputbuffer under ticket : %1 " ).arg( sTicket ); + } +} + +void PoolManager::connectSocketSignals( TcpSocket* l_pSocket) +{ + // First we connect the socket -> poolmanager + connect( l_pSocket, SIGNAL( signalDataReceived( const QString&, TcpSocket* ) ), + this, SLOT( slotDataReceived( const QString&, TcpSocket* ) ) ); + + connect( l_pSocket, SIGNAL( signalDataSent( TcpSocket* ) ), + this, SLOT( slotDataSent( TcpSocket* ) ) ); + + connect( l_pSocket, SIGNAL( signalReceivingData( TcpSocket* ) ), + this, SLOT( slotReceivingData( TcpSocket* ) ) ); + + connect( l_pSocket, SIGNAL( signalConnected( TcpSocket* ) ), + this, SLOT( slotConnected( TcpSocket* ) ) ); + + connect( l_pSocket, SIGNAL( signalDisconnected( TcpSocket* ) ), + this, SLOT( slotDisconnected( TcpSocket* ) ) ); + + // And now the poolmanager -> socket + connect( this, SIGNAL( signalSendData( const QString&, TcpSocket* ) ), + l_pSocket, SLOT( slotSendData( const QString&, TcpSocket* ) ) ); + +} + +void PoolManager::resendDelayedMessages() +{ + while( m_inputBuffer.size() > 0 ) // Check for delayed messages + { + if( m_pSocketContainer->getFreeSocket() ) // Check if there is a free channel + { + // Always send the first available message (FIFO) + this->sendData( m_inputBuffer.takeFirst() ); + LogDebug("[PoolManager::resendDelayedMessages]", QString( "Number of queued messages : %1 ").arg( m_inputBuffer.size() ) ); + } + else + { + // Warn if there is a socket available.... + LogDebug( "[PoolManager::resendDelayedMessages]", QString( "No free Socket Available (yet)" ) ); + } + } +} + +void PoolManager::slotConnected( TcpSocket *pSocket ) +{ + Q_UNUSED( pSocket ); + + emit message( 0, 0, osdev::components::gEventConnectionEstablished, 0 ); +} + +void PoolManager::slotDisconnected( TcpSocket *pSocket ) +{ + LogError("NetworkInterface", "Socket disconnected unexpectantly."); + LogError("NetworkInterface", pSocket->showError()); + + // emit message( 0, 0, gEventConnectionLost, 0 ); + + m_pSocketContainer->removeSocket( pSocket ); + delete pSocket; +} + +} /* End namespace components */ +} /* End namespace osdev */ diff --git a/src/poolmanager.h b/src/poolmanager.h new file mode 100644 index 0000000..cb3a895 --- /dev/null +++ b/src/poolmanager.h @@ -0,0 +1,199 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ +#ifndef OSDEV_COMPONENTS_CPOOLMANAGER_H +#define OSDEV_COMPONENTS_CPOOLMANAGER_H + +/*************************************************************************** + * Global Includes + ***************************************************************************/ +#include +#include +#include +#include +#include +#include + +/*************************************************************************** + * Local Includes + ***************************************************************************/ +#include "eventcodes.h" // Make the codes known. + +#include "socketcontainer.h" +#include "tcpsocket.h" + +namespace osdev { +namespace components { + +/** + * @brief Handles a pool of network connections + */ +class PoolManager : public QObject +{ + Q_OBJECT + +public: + /** + * @brief Constructor + * @param bServer True for server-mode, false for client-mode (Server is default). + * @param ipAddress IP-address to listen at (server-mode) or to connect to + * (client-mode) + * @param portNumber Portnumber to listen at (server-mode) or to connect to + * (server-mode) + * @param maxTcpConnections Maximum number of TCP connections accepted + * @param maxUdpConnections Maximum number of UDP connections accepted + * (currently unused) + * @param _parent Parent object + */ + PoolManager(bool bServer = false, + const QString& ipAddress = "0.0.0.0", + int portNumber = 3500, + int maxTcpConnections = 10, // The maximum number of connections we accept. + int maxUdpConnections = 10, + QObject *_parent = nullptr ); + + /// @brief Destructor + ~PoolManager(); + + /// Deleted copy-constructor + PoolManager(const PoolManager&) = delete; + /// Deleted assignment operator + PoolManager& operator=(const PoolManager&) = delete; + /// Deleted move-constructor + PoolManager(PoolManager&&) = delete; + /// Deleted move operator + PoolManager& operator=(PoolManager&&) = delete; + + /** + * @brief Get the data for a specified ticket + * @param sTicket Ticket ID + * @return Available data + */ + QString getData( const QString& sTicket ); + + /** + * @brief Start the actual server (server-mode) or build the client + * connection (client-mode) + * @return True on success, false on failure + */ + bool startNetworkLayer(); + + /** + * @brief Send data through the interface + * @param sData Data to send + */ + void sendData( const QString &sData ); + +public slots: + /// @brief Slot called when a new server-connection is available + void slotNewConnection(); + + /** + * @brief Slot called when data was sent through the specified socket + * @param pSocket Socket through which data was sent + */ + void slotDataSent( TcpSocket *pSocket ); + + /** + * @brief Slot called when data is being received by the specified socket + * @param pSocket Socket which is receiving data + */ + void slotReceivingData( TcpSocket *pSocket ); + + /** + * @brief Slot called when data was received through the specified socket + * @param sData Data received + * @param pSocket Socket through which data was sent + */ + void slotDataReceived( const QString &sData, TcpSocket *pSocket ); + + /** + * @brief Slot called when the specified socket was connected + * @param pSocket Socket through which data was sent + */ + void slotConnected( TcpSocket *pSocket ); + + /** + * @brief Slot called when the specified socket was disconnected + * @param pSocket Socket that was disconnected + * + * Also removes the socket from the list of known sockets and deletes it + */ + void slotDisconnected( TcpSocket *pSocket ); + +signals: + /** + * @brief Signal emitted when data is sent through a specified socket + * @param sData Data to be sent + * @param pSocket Socket that sends the data + */ + void signalSendData( const QString &sData, TcpSocket *pSocket ); + + /** + * @brief Signal emitted when data is present for the specified ticket + * @param sTicket Ticket ID + */ + void dataPresent( const QString &sTicket ); + + /** + * @brief Relay the message signal to the eventmanager + * @todo Check parameter meanings! + * @param i1 sourceID ? + * @param i2 DestID ? + * @param i3 Connection status ? + * @param i4 data ? + */ + void message( int i1, int i2, int i3, int i4 ); + +private: + /** + * @brief Create a new unique buffer ticket ID + * @return New ticket ID + */ + QString createBufferTicket(); + + /** + * @brief Connect all signals and slots for the socket + * @param l_pSocket Socket to connect + */ + void connectSocketSignals( TcpSocket *l_pSocket ); + + /// @brief Resend messages that are still in the buffer + void resendDelayedMessages(); + + QTcpServer *m_pServer; ///< The pointer to the server object. + SocketContainer *m_pSocketContainer; ///< Administration which keeps track of the Sockets. + TcpSocket *m_pSocket; + + QList< QString > m_inputBuffer; ///< Messages that can not be send yet will be stored here. + QHash< QString, QString > m_outputBuffer; ///< The hashtable we use for databuffering. + + bool m_bServer; ///< True for server-mode, false for client-mode + QString m_ipAddress; ///< The ipAddress the clients are connecting to. + int m_portNumber; ///< The portnumber our server is listening on. + int m_maxTcpConnections;///< The number of TCP connections we accept and make. + int m_maxUdpConnections;///< The number of UDP connections we accept and make. +}; + +} // End namespace components +} // End namespace osdev + +#endif /* OSDEV_COMPONENTS_CPOOLMANAGER_H */ diff --git a/src/socketcontainer.cpp b/src/socketcontainer.cpp new file mode 100644 index 0000000..a23e074 --- /dev/null +++ b/src/socketcontainer.cpp @@ -0,0 +1,105 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ + +#include "socketcontainer.h" + +#include "tcpsocket.h" +#include "log.h" + +namespace osdev { +namespace components { + +SocketContainer::SocketContainer() + : m_hshFreeBySocket() +{ +} + +SocketContainer::~SocketContainer() +{ + // qDeleteAll( hshFreeBySocket.begin(), hshFreeBySocket.end() ); +} + +void SocketContainer::addSocket( TcpSocket *pSocket ) +{ + if( pSocket ) + { + // We store a channel in a ready state. + m_hshFreeBySocket.insert( pSocket, true ); + } +} + +void SocketContainer::removeSocket( TcpSocket *pSocket ) +{ + if( pSocket ) + { + // Remove the channel from the list. + m_hshFreeBySocket.remove( pSocket ); + } +} + +void SocketContainer::setChannelFree( TcpSocket *pSocket ) +{ + if( pSocket ) + { + auto socketIt = m_hshFreeBySocket.find(pSocket); + if( m_hshFreeBySocket.end() != socketIt ) + { + socketIt.value() = true; + } + } +} + +void SocketContainer::setChannelBusy(TcpSocket *pSocket) +{ + if( pSocket ) + { + auto socketIt = m_hshFreeBySocket.find(pSocket); + if( m_hshFreeBySocket.end() != socketIt ) + { + socketIt.value() = false; + + // printStatus(); + } + } +} + +TcpSocket* SocketContainer::getFreeSocket() const +{ + // printStatus(); + + // Return a random socket with a free channel + return m_hshFreeBySocket.key( true ); +} + +void SocketContainer::printStatus() const +{ + LogDebug("interfaceplugin", QString("getFreeSocket counts : ") + m_hshFreeBySocket.count()); + LogDebug("interfaceplugin", QString("===========================================================")); + + for(auto it = m_hshFreeBySocket.begin(); it != m_hshFreeBySocket.end(); ++it) + { + LogDebug("interfaceplugin", it.key()->objectName()+" : "+it.value()); + } +} + +} // End namespace components +} // End namespace osdev diff --git a/src/socketcontainer.h b/src/socketcontainer.h new file mode 100644 index 0000000..44e5592 --- /dev/null +++ b/src/socketcontainer.h @@ -0,0 +1,92 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ + +#ifndef OSDEV_COMPONENTS_CTHREADCONTAINER_H +#define OSDEV_COMPONENTS_CTHREADCONTAINER_H + +/*************************************************************************** + * Global Includes + ***************************************************************************/ +#include + +/*************************************************************************** + * Local Includes + ***************************************************************************/ + +namespace osdev { +namespace components { + +class TcpSocket; + +/** + * @brief Maintains a collection of sockets + * + * @note This class does NOT take ownership of the provided sockets + */ +class SocketContainer +{ +public: + /// Constructor + SocketContainer(); + /// Destructor + ~SocketContainer(); + + /// Deleted copy-constructor + SocketContainer(const SocketContainer&) = delete; + /// Deleted assignment operator + SocketContainer& operator=(const SocketContainer&) = delete; + /// Deleted move-constructor + SocketContainer(SocketContainer&&) = delete; + /// Deleted move operator + SocketContainer& operator=(SocketContainer&&) = delete; + + //! This function will set the given thread to free + //! \param pSocket Socket to set to free + void setChannelFree( TcpSocket *pSocket ); + + //! This function will set the given thread to busy + //! \param pSocket Socket to set to busy + void setChannelBusy( TcpSocket *pSocket ); + + //! This function will add a newly created thread to the container + //! \param pSocket Add a new socket to the list + void addSocket( TcpSocket *pSocket ); + + //! This function removes a thread from the list for whatever reason there might be + //! \param pSocket Removes the socket from the list + void removeSocket( TcpSocket *pSocket ); + + //! @return This function will return the first free thread. + TcpSocket* getFreeSocket() const; + +private: + /// Dump the status to debug log + void printStatus() const; + + /// Contains all managed sockets + QHash< TcpSocket*, bool > m_hshFreeBySocket; +}; + +} // End namespace components +} // End namespace osdev + +#endif /* OSDEV_COMPONENTS_CTHREADCONTAINER_H */ diff --git a/src/tcpinterface.cpp b/src/tcpinterface.cpp new file mode 100644 index 0000000..b1990f3 --- /dev/null +++ b/src/tcpinterface.cpp @@ -0,0 +1,176 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ + +#include "tcpinterface.h" +#include "log.h" + +#include +#include +#include +#include + +namespace osdev { +namespace components { + +TcpInterface::TcpInterface(const QString& hostName, int port, bool i_bServer) + : m_bServer( i_bServer ), + m_active(false), + m_tcpServer( nullptr ), + m_tcpSocket( nullptr ), + m_clientConnection( nullptr ), + m_blockSize( 0 ), + m_dataList() +{ + QHostAddress hostAddress(hostName); + + if (m_bServer) + { // This interface is a server + m_tcpServer = new QTcpServer(this); + connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection())); + m_active = m_tcpServer->listen(hostAddress, static_cast(port)); + if (!m_active) + { + LogWarning("interfaceplugin", + "Unable to start the server: " + m_tcpServer->errorString()); + } + } + else + { // This interface is a client + m_tcpSocket = new QTcpSocket(this); + m_tcpSocket->connectToHost(hostAddress, static_cast(port)); + m_active = m_tcpSocket->waitForConnected(1000); + if (m_active) + { + connect(m_tcpSocket, SIGNAL(readyRead()), this, SLOT(readData())); + } + } +} + + +TcpInterface::~TcpInterface() +{ +} + +QString TcpInterface::getData() +{ + QString qsResult; + if(!m_dataList.isEmpty()) + { + qsResult = m_dataList.takeFirst(); + } + return qsResult; +} + +void TcpInterface::readData() +{ + QTcpSocket* pSocket = nullptr; + /* Retrieve the correct Socket, depending on connection-type */ + if (m_bServer) + { + pSocket = m_clientConnection; + } + else + { + pSocket = m_tcpSocket; + } + + /* Construct a DataStream from the Socket */ + QDataStream in(pSocket); + in.setVersion(QDataStream::Qt_5_4); + + // Process while there is a following message in this same buffer. + do { + LogDebug("TcpInterface", "read bytes"); + /* The size of the message in bytes is required */ + if (m_blockSize == 0) + { + /* + * The size of the message should be available as the first four + * blocks of the message; i.e. (sizeof(quint32)) + */ + if (pSocket->bytesAvailable() < static_cast(sizeof(quint32))) + { + return; + } + /* Retrieve the blocksize if it is fully available from the socket */ + in >> m_blockSize; + LogDebug("TcpInterface", QString("block size is %1").arg(m_blockSize)); + } + + if (pSocket->bytesAvailable() < m_blockSize) + { + return; + } + + /* + * Retrieve the message if it is completely available from the socket. + * I.e.: The number of available bytes must be at least the retrieved + * message-size. + */ + QByteArray pDataStore; + /* + * Using an empty QByteArray the operator>> can be used, because it then + * reads in everything until the first '\0' character. + * It is then required that all messages are ended by a '\0'. This is + * standard when sending QByteArrays. + */ + in >> pDataStore; + /* + * An entire message is stored in a new QByteArray and must be pushed on + * the stack of available messages + */ + m_dataList.append( pDataStore ); + m_blockSize = 0; // reset blocksize for next read + emit dataPresent(); + } while (pSocket->bytesAvailable() > 0); +} + +void TcpInterface::sendData(const QString& i_qsData) +{ + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + + out.setVersion(QDataStream::Qt_5_4); + + out << block.size(); + out << i_qsData.toLatin1(); + + if (m_bServer) + { + m_clientConnection->write(block); + m_clientConnection->flush(); + } + else + { + m_tcpSocket->write(block); + m_tcpSocket->flush(); + } +} + +void TcpInterface::newConnection() +{ + m_clientConnection = m_tcpServer->nextPendingConnection(); + connect(m_clientConnection, SIGNAL(readyRead()), this, SLOT(readData())); +} + +} +} diff --git a/src/tcpinterface.h b/src/tcpinterface.h new file mode 100644 index 0000000..87e81e5 --- /dev/null +++ b/src/tcpinterface.h @@ -0,0 +1,120 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ + +#ifndef OSDEV_COMPONENTS_CTCPINTERFACE_H +#define OSDEV_COMPONENTS_CTCPINTERFACE_H + +#include +#include + +class QTcpServer; +class QTcpSocket; + +namespace osdev { +namespace components { + +/** + * @brief Handles a generic client or server point-to-point connection + * + * This class can be instantiated as a client or a server. + * + * @note The server can currently only handle one client at a time. + */ +class TcpInterface : public QObject +{ + Q_OBJECT + +public: + /** + * @brief Constructor + * @param hostName Host to connect with (client-mode), or interface to + * connect at (server-mode) + * @param port Selected port-number + * @param i_bServer False for client-mode, true for server-mode + */ + TcpInterface(const QString& hostName, int port = 2776, bool i_bServer = false); + /// @brief Destructor + ~TcpInterface(); + + /// Deleted copy-constructor + TcpInterface(const TcpInterface&) = delete; + /// Deleted assignment operator + TcpInterface& operator=(const TcpInterface&) = delete; + /// Deleted move-constructor + TcpInterface(TcpInterface&&) = delete; + /// Deleted move operator + TcpInterface& operator=(TcpInterface&&) = delete; + + /** + * @return if the interface is active. + * In server mode the interface is listening or connected to and in + * client mode the interface is connected. + */ + bool active() const + { + return m_active; + } + + /** + * @brief Reads the first block of data from the received data + * @return First block of data, as a QString + */ + QString getData(); + +public slots: + + /** + * @brief Called when data is ready to be read into the buffer + */ + void readData(); + + /** + * @brief Send a block of data to the other party + * @param i_qsData Data to be sent + */ + void sendData(const QString& i_qsData); + +signals: + /// @brief Emitted when data is present on the connection + void dataPresent(); + +private slots: + /// @brief Slot called when a new connection is made + void newConnection(); + +private: + bool m_bServer; ///< True for server-mode, false for client-mode + bool m_active; ///< True if in server mode the server is listening or connected to and in client mode when the client is connected. + + QTcpServer* m_tcpServer; ///< Server object instance + QTcpSocket* m_tcpSocket; ///< Client object instance + + QTcpSocket* m_clientConnection; ///< Current client connection + + quint32 m_blockSize; ///< Maximum size of a data-block + QList m_dataList; ///< Blocks of data read from the counterpart +}; + +} // End namespace components +} // End namespace osdev + +#endif /* OSDEV_COMPONENTS_CTCPINTERFACE_H */ diff --git a/src/tcpsocket.cpp b/src/tcpsocket.cpp new file mode 100644 index 0000000..007a078 --- /dev/null +++ b/src/tcpsocket.cpp @@ -0,0 +1,207 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ +#include "tcpsocket.h" + +#include "log.h" + +#include +#include +#include +#include + +namespace osdev { +namespace components { + +TcpSocket::TcpSocket( const QString& ipAddress, int portNumber ) + : m_pSocket(new QTcpSocket(this)) + , m_blockSize(0) + , m_dataBuffer() + , m_pTimer( new QTimer() ) +{ + // First we connect the timer to this class + connect(m_pTimer.get(), SIGNAL(timeout()), this, SLOT(chkSendBuffer())); + + this->connectSocketSignals(); + + m_pSocket->connectToHost( QHostAddress( ipAddress ), static_cast(portNumber) ); + if( !m_pSocket->waitForConnected( 3000 ) ) + { + LogDebug("interfaceplugin", "Socket failed to connect."); + LogObject("interfaceplugin", this); + } + else + { + LogDebug("interfaceplugin", "Client connected : "); + LogObject("interfaceplugin", m_pSocket); + } +} + +TcpSocket::TcpSocket(QTcpSocket *pSocket) + : m_pSocket(pSocket) + , m_blockSize(0) + , m_dataBuffer() + , m_pTimer( new QTimer() ) +{ + Q_ASSERT(pSocket); + + connect(m_pTimer.get(), SIGNAL(timeout()), this, SLOT(chkSendBuffer())); + + this->connectSocketSignals(); +} + +TcpSocket::~TcpSocket() +{ + // Implicitly deletes m_pSocket if this object is its parent +} + +void TcpSocket::slotReadyRead() +{ + // Let the poolmanager know we are busy receiving data at the moment and are + // unavailable for anything else. + emit signalReceivingData( this ); + + QDataStream in( m_pSocket ); + in.setVersion( QDataStream::Qt_5_4 ); + + if( m_blockSize == 0 ) + { + if( m_pSocket->bytesAvailable() < static_cast(sizeof( quint32 ) ) ) + { + return; + } + + in >> m_blockSize; + } + + if( m_pSocket->bytesAvailable() < m_blockSize ) + { + return; + } + + in >> m_dataBuffer; + + emit signalDataReceived( m_dataBuffer, this ); + + m_blockSize = 0; // Reset the blockSize for next read. + m_dataBuffer.clear(); // Reset the buffer for the next read. +} + +void TcpSocket::slotSendData( const QString &sData, TcpSocket *pSocket ) +{ + if( pSocket != this || sData.isNull() ) { + return; + } + + QByteArray block; + QDataStream out( &block, QIODevice::WriteOnly ); + + out.setVersion( QDataStream::Qt_5_4 ); + out << block.size(); + out << sData; + + m_pSocket->write( block ); + chkSendBuffer(); +} + +void TcpSocket::slotSetSocket(QTcpSocket* pSocket ) +{ + if(m_pSocket) { + disconnectSocketSignals(); + } + m_pSocket = pSocket; + connectSocketSignals(); +} + +void TcpSocket::chkSendBuffer() +{ + m_pTimer->stop(); + + if( m_pSocket->bytesToWrite() > 0 ) + { + m_pSocket->flush(); + m_pTimer->start( 500 ); + } + else + { + emit signalDataSent( this ); + } +} + +void TcpSocket::slotConnected() +{ + emit signalConnected( this ); +} + +void TcpSocket::slotDisconnected() +{ + emit signalDisconnected( this ); +} + +QString TcpSocket::showError() +{ + if( m_pSocket ) + { + return m_pSocket->errorString(); + } + else + { + return "No socket available"; + } +} + +void TcpSocket::connectSocketSignals() +{ + Q_ASSERT(m_pSocket); + // Connect the required signals and slots to gain control. + // First for receiving data. + connect( m_pSocket, SIGNAL( readyRead() ), + this, SLOT( slotReadyRead() ) ); + + connect( m_pSocket, SIGNAL( connected() ), + this, SLOT( slotConnected() ) ); + + connect( m_pSocket, SIGNAL( disconnected() ), + this, SLOT( slotDisconnected() ) ); +} + +void TcpSocket::disconnectSocketSignals() +{ + Q_ASSERT(m_pSocket); + // Connect the required signals and slots to gain control. + // First for receiving data. + disconnect( m_pSocket, SIGNAL( readyRead() ), + this, SLOT( slotReadyRead() ) ); + + disconnect( m_pSocket, SIGNAL( connected() ), + this, SLOT( slotConnected() ) ); + + disconnect( m_pSocket, SIGNAL( disconnected() ), + this, SLOT( slotDisconnected() ) ); +} + +bool TcpSocket::isConnected() +{ + return m_pSocket && m_pSocket->state() == QTcpSocket::ConnectedState; +} + +} +} diff --git a/src/tcpsocket.h b/src/tcpsocket.h new file mode 100644 index 0000000..5de09f1 --- /dev/null +++ b/src/tcpsocket.h @@ -0,0 +1,164 @@ +/* **************************************************************************** + * Copyright 2019 Open Systems Development BV * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * + * DEALINGS IN THE SOFTWARE. * + * ***************************************************************************/ + +#ifndef OSDEV_COMPONENTS_CTCPSOCKET_H +#define OSDEV_COMPONENTS_CTCPSOCKET_H + +#include +//#include +//#include +//#include +//#include +//#include + +#include + +class QTcpSocket; +class QTimer; + +namespace osdev { +namespace components { + +/** + * @brief Wrapper around QTcpSocket + */ +class TcpSocket : public QObject +{ + Q_OBJECT + +public: + /** + * @brief Create a TcpSocket connecting to the given address + * @param ipAddress Host to connect to + * @param portNumber Port number to connect at + */ + TcpSocket( const QString& ipAddress, int portNumber ); + + /** + * @brief Create a TcpSocket based on an existing QTcpSocket + * @param pSocket Socket used. This class does NOT take ownership of the + * provided socket. + */ + TcpSocket( QTcpSocket *pSocket ); + + /// Deleted copy-constructor + TcpSocket(const TcpSocket&) = delete; + /// Deleted assignment operator + TcpSocket& operator=(const TcpSocket&) = delete; + /// Deleted move-constructor + TcpSocket(TcpSocket&&) = delete; + /// Deleted move operator + TcpSocket& operator=(TcpSocket&&) = delete; + + /// @brief Destructor + ~TcpSocket(); + + /** + * @brief Retrieve the last error registered in the socket + * @return Error string + */ + QString showError(); + + /** + * @brief See if a connection is available + * @return True if a connection is available, false otherwise + */ + bool isConnected(); + +public slots: + /** + * @brief Slot called to send data + * @param sData Data to send + * @param pSocket Socket to which to send. If it is not equal to this, the + * slot does nothing and returns + */ + void slotSendData( const QString &sData, TcpSocket *pSocket ); + + /** + * @brief Updates the current QTcpSocket used + * @param pSocket New socket + */ + void slotSetSocket( QTcpSocket *pSocket ); + + /// @brief Slot called when a socket gets connected + void slotConnected(); + + /// @brief Slot called when a socket gets disconnected + void slotDisconnected(); + +signals: + /** + * @brief Signal emitted when data is received + * @param sData Block of data received + * @param pSocket Receiving socket + */ + void signalDataReceived( const QString &sData, TcpSocket *pSocket ); + + /** + * @brief Signal emitted when data was sent + * @param pSocket Sending socket + */ + void signalDataSent( TcpSocket *pSocket ); + + /** + * @brief Signal emitted when data is being received + * @param pSocket Receiving socket + */ + void signalReceivingData( TcpSocket *pSocket ); + + /** + * @brief Signal emitted when a socket gets connected + * @param pSocket Socket that gets connected + */ + void signalConnected( TcpSocket *pSocket ); + + /** + * @brief Signal emitted when a socket gets disconnected + * @param pSocket Socket that gets disconnected + */ + void signalDisconnected( TcpSocket *pSocket ); + +private slots: + /// @brief Slot called when a socket is ready to read + void slotReadyRead(); + + /// @brief Emits the signalDataSent when all data was sent + void chkSendBuffer(); + +private: + /// @brief Connects the socket signals to the slots of this class + void connectSocketSignals(); + + /// @brief Disconnects the socket signals to the slots of this class + void disconnectSocketSignals(); + + QTcpSocket *m_pSocket; ///< Current communication socket + int m_blockSize; ///< Maximum block size + QString m_dataBuffer; ///< Data to be sent + + std::unique_ptr m_pTimer; ///< Periodic check if all data was sent +}; + +} // End namespace components +} // End namespace osdev + +#endif // OSDEV_COMPONENTS_CTCPSOCKET_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..3635320 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.0) +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake) + +include(projectheader) +project_header(test_logutils) + +include_directories( SYSTEM + ${CMAKE_CURRENT_SOURCE_DIR}/../../src +) + +include(compiler) +set(SRC_LIST +) + +# add_executable( ${PROJECT_NAME} +# ${SRC_LIST} +# ) + +# target_link_libraries( +# ${PROJECT_NAME} +# ) + +# set_target_properties( ${PROJECT_NAME} PROPERTIES +# RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin +# LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib +# ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/archive +# ) + +# include(installation) +# install_application() -- libgit2 0.21.4