diff --git a/src/ebusd/CMakeLists.txt b/src/ebusd/CMakeLists.txt index 95eb8933..7166bd08 100644 --- a/src/ebusd/CMakeLists.txt +++ b/src/ebusd/CMakeLists.txt @@ -3,6 +3,7 @@ add_definitions(-Wconversion -Wno-unused-parameter) set(ebusd_SOURCES bushandler.h bushandler.cpp datahandler.h datahandler.cpp + request.h request.cpp network.h network.cpp mainloop.h mainloop.cpp scan.h scan.cpp diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 290fd817..ee2faf0c 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -31,6 +31,7 @@ #include #include #include "ebusd/mainloop.h" +#include "ebusd/network.h" #include "lib/utils/log.h" #include "lib/utils/httpclient.h" #include "ebusd/scan.h" @@ -143,9 +144,15 @@ static MessageMap* s_messageMap = nullptr; /** the @a ScanHelper instance, or nullptr. */ static ScanHelper* s_scanHelper = nullptr; +/** the @a Request @a Queue instance, or nullptr. */ +static Queue* s_requestQueue = nullptr; + /** the @a MainLoop instance, or nullptr. */ static MainLoop* s_mainLoop = nullptr; +/** the @a Network instance, or nullptr. */ +static Network* s_network = nullptr; + /** the (optionally corrected) config path for retrieving configuration files from. */ static string s_configPath = CONFIG_PATH; @@ -735,15 +742,27 @@ void daemonize() { * Clean up all dynamically allocated and stop main loop and all dependent components. */ void cleanup() { - if (s_mainLoop) { + if (s_network != nullptr) { + delete s_network; + s_network = nullptr; + } + if (s_mainLoop != nullptr) { delete s_mainLoop; s_mainLoop = nullptr; } - if (s_messageMap) { + if (s_requestQueue != nullptr) { + Request* msg; + while ((msg = s_requestQueue->pop()) != nullptr) { + delete msg; + } + delete s_requestQueue; + s_requestQueue = nullptr; + } + if (s_messageMap != nullptr) { delete s_messageMap; s_messageMap = nullptr; } - if (s_scanHelper) { + if (s_scanHelper != nullptr) { delete s_scanHelper; s_scanHelper = nullptr; } @@ -1030,8 +1049,10 @@ int main(int argc, char* argv[], char* envp[]) { // load configuration files s_scanHelper->loadConfigFiles(!s_opt.scanConfig); + s_requestQueue = new Queue(); + // create the MainLoop and start it - s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper); + s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper, s_requestQueue); if (s_opt.injectMessages) { BusHandler* busHandler = s_mainLoop->getBusHandler(); int scanAdrCount = 0; @@ -1064,6 +1085,9 @@ int main(int argc, char* argv[], char* envp[]) { } s_mainLoop->start("mainloop"); + s_network = new Network(s_opt.localOnly, s_opt.port, s_opt.httpPort, s_requestQueue); + s_network->start("network"); + // wait for end of MainLoop s_mainLoop->join(); diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 03ae9651..ddcda4eb 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -105,12 +105,13 @@ result_t UserList::addFromFile(const string& filename, unsigned int lineNo, map< #define VERBOSITY_4 (VERBOSITY_3 | OF_ALL_ATTRS) -MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper) +MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper, + Queue* requestQueue) : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), m_scanHelper(scanHelper), m_address(opt.address), m_scanConfig(opt.scanConfig), m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_scanStatus(SCAN_STATUS_NONE), m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), - m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient() { + m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient(), m_requestQueue(requestQueue) { m_device->setListener(this); // open Device result_t result = m_device->open(); @@ -160,8 +161,6 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag // create network m_htmlPath = opt.htmlPath; - m_network = new Network(opt.localOnly, opt.port, opt.httpPort, &m_netQueue); - m_network->start("network"); logInfo(lf_main, "registering data handlers"); if (datahandler_register(&m_userList, m_busHandler, messages, &m_dataHandlers)) { logInfo(lf_main, "registered data handlers"); @@ -192,10 +191,6 @@ MainLoop::~MainLoop() { delete m_logRawFile; m_logRawFile = nullptr; } - if (m_network != nullptr) { - delete m_network; - m_network = nullptr; - } if (m_busHandler != nullptr) { delete m_busHandler; m_busHandler = nullptr; @@ -204,10 +199,6 @@ MainLoop::~MainLoop() { delete m_device; m_device = nullptr; } - NetMessage* msg; - while ((msg = m_netQueue.pop()) != nullptr) { - delete msg; - } if (m_newlyDefinedMessages) { delete m_newlyDefinedMessages; m_newlyDefinedMessages = nullptr; @@ -245,8 +236,8 @@ void MainLoop::run() { dataHandler->startHandler(); } while (!m_shutdown) { - // pick the next message to handle - NetMessage* netMessage = m_netQueue.pop(taskDelay); + // pick the next request to handle + Request* req = m_requestQueue->pop(taskDelay); time(&now); if (now < lastTaskRun) { // clock skew @@ -410,66 +401,62 @@ void MainLoop::run() { m_messages->unlock(); sinkSince = now; } - if (netMessage == nullptr) { + if (req == nullptr) { continue; } if (m_shutdown) { - netMessage->setResult("ERR: shutdown", "", nullptr, now, true); + req->setResult("ERR: shutdown", "", nullptr, now, true); break; } - string request = netMessage->getRequest(); - string user = netMessage->getUser(); - ClientSettings settings = netMessage->getSettings(&since); - if (!netMessage->isListeningMode()) { + string user = req->getUser(); + RequestMode reqMode = req->getMode(&since); + if (reqMode.listenMode == lm_none) { since = now; } ostringstream ostream; bool connected = true; - if (request.length() > 0) { - logDebug(lf_main, ">>> %s", request.c_str()); - result_t result = decodeMessage(request, netMessage->isHttp(), &connected, &settings, &user, &reload, &ostream); - if (!netMessage->isHttp() && (ostream.tellp() == 0 || result != RESULT_OK)) { - if (settings.mode != cm_direct) { + if (!req->empty()) { + req->log(); + result_t result = decodeRequest(req, &connected, &reqMode, &user, &reload, &ostream); + if (!req->isHttp() && (ostream.tellp() == 0 || result != RESULT_OK)) { + if (reqMode.listenMode != lm_direct) { ostream.str(""); } ostream << getResultCode(result); } - if (ostream.tellp() > 100) { - logDebug(lf_main, "<<< %s ...", ostream.str().substr(0, 100).c_str()); - } else { - logDebug(lf_main, "<<< %s", ostream.str().c_str()); - } + const auto resp = ostream.str(); + req->log(&resp); if (ostream.tellp() == 0) { ostream << "\n"; // only for HTTP - } else if (!netMessage->isHttp()) { - ostream << (settings.mode == cm_direct ? "\n" : "\n\n"); + } else if (!req->isHttp()) { + ostream << (reqMode.listenMode == lm_direct ? "\n" : "\n\n"); } } - if (settings.mode == cm_listen) { - if (!settings.listenOnlyUnknown) { + if (reqMode.listenMode == lm_listen) { + if (!reqMode.listenOnlyUnknown) { string levels = getUserLevels(user); messages.clear(); m_messages->findAll("", "", levels, false, true, true, true, true, true, since, now, true, &messages); for (const auto message : messages) { ostream << message->getCircuit() << " " << message->getName() << " = " << dec; - message->decodeLastData(false, nullptr, -1, settings.format, &ostream); + message->decodeLastData(false, nullptr, -1, reqMode.format, &ostream); ostream << endl; } } - if (settings.listenWithUnknown || settings.listenOnlyUnknown) { + if (reqMode.listenWithUnknown || reqMode.listenOnlyUnknown) { if (m_busHandler->isGrabEnabled()) { m_busHandler->formatGrabResult(true, OF_NONE, &ostream, true, since, now); } else { m_busHandler->enableGrab(true); // needed for listening to all messages } } - } else if (settings.mode == cm_direct) { + } else if (reqMode.listenMode == lm_direct) { if (m_busHandler->isGrabEnabled()) { m_busHandler->formatGrabResult(false, OF_NONE, &ostream, true, since, now); } } // send result to client - netMessage->setResult(ostream.str(), user, &settings, now, !connected); + req->setResult(ostream.str(), user, &reqMode, now, !connected); } } @@ -529,49 +516,18 @@ void MainLoop::notifyStatus(bool error, const char* message) { } } -result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connected, ClientSettings* settings, +result_t MainLoop::decodeRequest(Request* req, bool* connected, RequestMode* reqMode, string* user, bool* reload, ostringstream* ostream) { - string token, previous; - istringstream stream(data); vector args; - char escaped = 0; - - char delim = ' '; - while (getline(stream, token, delim)) { - if (!isHttp) { - if (escaped) { - args.pop_back(); - if (token.length() > 0 && token[token.length()-1] == escaped) { - token.erase(token.length() - 1, 1); - escaped = 0; - } - token = previous + " " + token; - } else if (token.length() == 0) { // allow multiple space chars for a single delimiter - continue; - } else if (token[0] == '"' || token[0] == '\'') { - escaped = token[0]; - token.erase(0, 1); - if (token.length() > 0 && token[token.length()-1] == escaped) { - token.erase(token.length() - 1, 1); - escaped = 0; - } - } - } - args.push_back(token); - previous = token; - if (isHttp) { - delim = (args.size() == 1) ? '?' : '\n'; - } - } - - if (isHttp) { + req->split(&args); + string cmd = args.size() > 0 ? args[0] : ""; + if (req->isHttp()) { if (args.size() < 2) { *connected = false; *ostream << "HTTP/1.0 400 Bad Request\r\n\r\n"; return RESULT_OK; } - const char* str = args.size() > 0 ? args[0].c_str() : ""; - if (strcmp(str, "GET") == 0) { + if (cmd == "GET") { return executeGet(args, connected, ostream); } *connected = false; @@ -579,7 +535,6 @@ result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connecte return RESULT_OK; } - string cmd = args.size() > 0 ? args[0] : ""; transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper); if (cmd == "?" || cmd == "H" || cmd == "HELP") { // found "HELP CMD" @@ -587,8 +542,8 @@ result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connecte transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper); args.clear(); // empty args is used as command help indicator } - if (settings->mode == cm_direct) { - return executeDirect(args, &settings->mode, ostream); + if (reqMode->listenMode == lm_direct) { + return executeDirect(args, reqMode, ostream); } if (cmd.empty() && args.size() == 0) { return executeHelp(ostream); @@ -620,10 +575,10 @@ result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connecte return executeFind(args, getUserLevels(*user), ostream); } if (cmd == "L" || cmd == "LISTEN") { - return executeListen(args, settings, ostream); + return executeListen(args, reqMode, ostream); } if (cmd == "DIRECT") { - return executeDirect(args, &settings->mode, ostream); + return executeDirect(args, reqMode, ostream); } if (cmd == "S" || cmd == "STATE") { return executeState(args, ostream); @@ -1293,10 +1248,10 @@ result_t MainLoop::executeHex(const vector& args, ostringstream* ostream return RESULT_OK; } -result_t MainLoop::executeDirect(const vector& args, ClientMode* mode, ostringstream* ostream) { - if (*mode != cm_direct) { +result_t MainLoop::executeDirect(const vector& args, RequestMode* reqMode, ostringstream* ostream) { + if (reqMode->listenMode != lm_direct) { if (args.size() == 1) { - *mode = cm_direct; + reqMode->listenMode = lm_direct; m_busHandler->enableGrab(true); // needed for listening to all messages *ostream << "direct mode started"; return RESULT_OK; @@ -1308,7 +1263,7 @@ result_t MainLoop::executeDirect(const vector& args, ClientMode* mode, o if (args.size() > 0) { string firstArg = args[0]; if (firstArg == "stop") { - *mode = cm_normal; + reqMode->listenMode = lm_none; *ostream << "direct mode stopped"; return RESULT_OK; } @@ -1321,7 +1276,7 @@ result_t MainLoop::executeDirect(const vector& args, ClientMode* mode, o } *ostream << ":"; if (!m_enableHex) { - *ostream << "ERR: command not enabled"; + *ostream << "ERR: hex command not enabled"; return RESULT_OK; } size_t argPos = 0; @@ -1560,7 +1515,7 @@ result_t MainLoop::executeFind(const vector& args, const string& levels, return RESULT_OK; } -result_t MainLoop::executeListen(const vector& args, ClientSettings* settings, ostringstream* ostream) { +result_t MainLoop::executeListen(const vector& args, RequestMode* reqMode, ostringstream* ostream) { size_t argPos = 1; OutputFormat verbosity = OF_NONE; bool listenWithUnknown = false; @@ -1594,17 +1549,17 @@ result_t MainLoop::executeListen(const vector& args, ClientSettings* set argPos++; } if (argPos > 0 && args.size() == argPos) { - settings->format = verbosity; - settings->listenWithUnknown = listenWithUnknown; - settings->listenOnlyUnknown = listenOnlyUnknown; + reqMode->format = verbosity; + reqMode->listenWithUnknown = listenWithUnknown; + reqMode->listenOnlyUnknown = listenOnlyUnknown; if (listenWithUnknown || listenOnlyUnknown) { m_busHandler->enableGrab(true); // needed for listening to all messages } - if (settings->mode == cm_listen) { + if (reqMode->listenMode == lm_listen) { *ostream << "listen continued"; return RESULT_OK; } - settings->mode = cm_listen; + reqMode->listenMode = lm_listen; *ostream << "listen started"; return RESULT_OK; } @@ -1620,7 +1575,7 @@ result_t MainLoop::executeListen(const vector& args, ClientSettings* set " -U only show unknown messages"; return RESULT_OK; } - settings->mode = cm_normal; + reqMode->listenMode = lm_none; *ostream << "listen stopped"; return RESULT_OK; } diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index a2743fe4..b1e8c234 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -26,7 +26,7 @@ #include #include "ebusd/bushandler.h" #include "ebusd/datahandler.h" -#include "ebusd/network.h" +#include "ebusd/request.h" #include "ebusd/scan.h" #include "lib/ebus/filereader.h" #include "lib/ebus/message.h" @@ -102,13 +102,15 @@ class UserList : public UserInfo, public MappedFileReader { class MainLoop : public Thread, DeviceListener { public: /** - * Construct the main loop and create network and bus handling components. + * Construct the main loop and create bus handling components. * @param opt the program options. * @param device the @a Device instance. * @param messages the @a MessageMap instance. * @param scanHelper the @a ScanHelper instance. + * @param requestQueue the reference to the @a Request @a Queue. */ - MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper); + MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper, + Queue* requestQueue); /** * Destructor. @@ -126,12 +128,6 @@ class MainLoop : public Thread, DeviceListener { */ BusHandler* getBusHandler() { return m_busHandler; } - /** - * Add a client @a NetMessage to the queue. - * @param message the client @a NetMessage to handle. - */ - void addMessage(NetMessage* message) { m_netQueue.push(message); } - // @copydoc void notifyDeviceData(symbol_t symbol, bool received) override; @@ -146,17 +142,16 @@ class MainLoop : public Thread, DeviceListener { private: /** - * Decode and execute client message. - * @param data the data string to decode (may be empty). + * Decode and execute client request. + * @param req the @a Request to decode. * @param connected set to false when the client connection shall be closed. - * @param isHttp true for HTTP message. - * @param settings set to the new client settings. + * @param reqMode the @a RequestMode to use and update. * @param user set to the new user name when changed by authentication. * @param reload set to true when the configuration files were reloaded. * @param ostream the @a ostringstream to format the result string to. * @return the result code. */ - result_t decodeMessage(const string& data, bool isHttp, bool* connected, ClientSettings* settings, + result_t decodeRequest(Request* req, bool* connected, RequestMode* reqMode, string* user, bool* reload, ostringstream* ostream); /** @@ -227,11 +222,11 @@ class MainLoop : public Thread, DeviceListener { /** * Execute the direct command. * @param args the arguments passed to the command (starting with the command itself), or empty for help. - * @param mode set to the new client mode. + * @param reqMode the @a RequestMode to use and update. * @param ostream the @a ostringstream to format the result string to. * @return the result code. */ - result_t executeDirect(const vector& args, ClientMode* mode, ostringstream* ostream); + result_t executeDirect(const vector& args, RequestMode* reqMode, ostringstream* ostream); /** * Execute the find command. @@ -245,11 +240,11 @@ class MainLoop : public Thread, DeviceListener { /** * Execute the listen command. * @param args the arguments passed to the command (starting with the command itself), or empty for help. - * @param settings set to the new client settings. + * @param reqMode the @a RequestMode to use and update. * @param ostream the @a ostringstream to format the result string to. * @return the result code. */ - result_t executeListen(const vector& args, ClientSettings* settings, ostringstream* ostream); + result_t executeListen(const vector& args, RequestMode* reqMode, ostringstream* ostream); /** * Execute the state command. @@ -445,11 +440,8 @@ class MainLoop : public Thread, DeviceListener { /** the created @a BusHandler instance. */ BusHandler* m_busHandler; - /** the created @a Network instance. */ - Network* m_network; - - /** the @a NetMessage @a Queue. */ - Queue m_netQueue; + /** the reference to the @a Request @a Queue. */ + Queue* m_requestQueue; /** the path for HTML files served by the HTTP port. */ string m_htmlPath; diff --git a/src/ebusd/network.cpp b/src/ebusd/network.cpp index a2cdacaa..31cc445d 100644 --- a/src/ebusd/network.cpp +++ b/src/ebusd/network.cpp @@ -37,39 +37,6 @@ int Connection::m_ids = 0; #define POLLRDHUP 0 #endif -bool NetMessage::add(const char* request) { - if (request && request[0]) { - string add = request; - add.erase(remove(add.begin(), add.end(), '\r'), add.end()); - m_request.append(add); - } - size_t pos = m_request.find(m_isHttp ? "\n\n" : "\n"); - if (pos != string::npos) { - if (m_isHttp) { - pos = m_request.find("\n"); - m_request.resize(pos); // reduce to first line - // typical first line: GET /ehp/outsidetemp HTTP/1.1 - pos = m_request.rfind(" HTTP/"); - if (pos != string::npos) { - m_request.resize(pos); // remove "HTTP/x.x" suffix - } - pos = 0; - while ((pos=m_request.find('%', pos)) != string::npos && pos+2 <= m_request.length()) { - unsigned int value1, value2; - if (sscanf("%1x%1x", m_request.c_str()+pos+1, &value1, &value2) < 2) { - break; - } - m_request[pos] = static_cast(((value1&0x0f) << 4) | (value2&0x0f)); - m_request.erase(pos+1, 2); - } - } else if (pos+1 == m_request.length()) { - m_request.resize(pos); // reduce to complete lines - } - return true; - } - return m_request.length() == 0 && isListeningMode(); -} - void Connection::run() { int ret; @@ -108,7 +75,7 @@ void Connection::run() { #endif bool closed = false; - NetMessage message(m_isHttp); + RequestImpl req(m_isHttp); while (!closed) { #ifdef HAVE_PPOLL @@ -146,7 +113,7 @@ void Connection::run() { #endif } - if (newData || message.isListeningMode()) { + if (newData || req.getMode().listenMode != lm_none) { char data[256]; if (!m_socket->isValid()) { @@ -165,21 +132,24 @@ void Connection::run() { } // decode client data - if (message.add(data)) { - m_netQueue->push(&message); + if (req.add(data)) { + m_requestQueue->push(&req); // wait for result logDebug(lf_network, "[%05d] wait for result", getID()); string result; - message.getResult(&result); + bool disconnect = req.waitResponse(&result); if (!m_socket->isValid()) { break; } m_socket->send(result.c_str(), result.size()); + if (disconnect) { + break; + } } - if (message.isDisconnect() || !m_socket->isValid()) { + if (!m_socket->isValid()) { break; } } @@ -193,8 +163,8 @@ void Connection::run() { } -Network::Network(const bool local, const uint16_t port, const uint16_t httpPort, Queue* netQueue) - : Thread(), m_netQueue(netQueue), m_listening(false) { +Network::Network(const bool local, const uint16_t port, const uint16_t httpPort, Queue* requestQueue) + : Thread(), m_requestQueue(requestQueue), m_listening(false) { m_tcpServer = new TCPServer(port, local ? "127.0.0.1" : "0.0.0.0"); if (m_tcpServer != nullptr && m_tcpServer->start() == 0) { @@ -214,9 +184,9 @@ Network::Network(const bool local, const uint16_t port, const uint16_t httpPort, Network::~Network() { stop(); - NetMessage* netMsg; - while ((netMsg = m_netQueue->pop()) != nullptr) { - netMsg->setResult("ERR: shutdown", "", nullptr, 0, true); + Request* req; + while ((req = m_requestQueue->pop()) != nullptr) { + req->setResult("ERR: shutdown", "", nullptr, 0, true); } while (!m_connections.empty()) { Connection* connection = m_connections.back(); @@ -333,7 +303,7 @@ void Network::run() { if (socket == nullptr) { continue; } - Connection* connection = new Connection(socket, isHttp, m_netQueue); + Connection* connection = new Connection(socket, isHttp, m_requestQueue); string ip = socket->getIP(); connection->start("connection"); m_connections.push_back(connection); diff --git a/src/ebusd/network.h b/src/ebusd/network.h index 22206bd9..9f21735c 100644 --- a/src/ebusd/network.h +++ b/src/ebusd/network.h @@ -23,6 +23,7 @@ #include #include #include +#include "ebusd/request.h" #include "lib/ebus/datatype.h" #include "lib/utils/tcpsocket.h" #include "lib/utils/queue.h" @@ -35,188 +36,9 @@ namespace ebusd { * The TCP and HTTP client request handling. */ -/** Forward declaration for @a Connection. */ -class Connection; - -/** the possible client modes. */ -enum ClientMode { - cm_normal, //!< normal mode - cm_listen, //!< listening mode - cm_direct, //!< direct mode -}; /** - * Combination of client settings. - */ -struct ClientSettings { - ClientMode mode; //!< the current client mode - OutputFormat format; //!< the output format settings for listen mode - bool listenWithUnknown; //!< include unknown messages in listen mode - bool listenOnlyUnknown; //!< only print unknown messages in listen mode -}; - -/** - * Class for data/message transfer between @a Connection and @a MainLoop. - */ -class NetMessage { - public: - /** - * Constructor. - * @param isHttp whether this is a HTTP message. - */ - explicit NetMessage(bool isHttp) - : m_isHttp(isHttp), m_resultSet(false), m_disconnect(false), m_listenSince(0) { - m_settings.mode = cm_normal; - m_settings.format = OF_NONE; - m_settings.listenWithUnknown = false; - m_settings.listenOnlyUnknown = false; - pthread_mutex_init(&m_mutex, nullptr); - pthread_cond_init(&m_cond, nullptr); - } - - /** - * Destructor. - */ - ~NetMessage() { - m_resultSet = true; - pthread_mutex_destroy(&m_mutex); - pthread_cond_destroy(&m_cond); - } - - - private: - /** - * Hidden copy constructor. - * @param src the object to copy from. - */ - NetMessage(const NetMessage& src); - - - public: - /** - * Add request data received from the client. - * @param request the request data from the client. - * @return true when the request is complete and the response shall be prepared. - */ - bool add(const char* request); - - /** - * Return whether this is a HTTP message. - * @return whether this is a HTTP message. - */ - bool isHttp() const { return m_isHttp; } - - /** - * Return the request string. - * @return the request string. - */ - const string& getRequest() const { return m_request; } - - /** - * Return the current user name. - * @return the current user name. - */ - const string& getUser() const { return m_user; } - - /** - * Wait for the result being set and return the result string. - * @param result the variable in which to store the result string. - */ - void getResult(string* result) { - pthread_mutex_lock(&m_mutex); - - if (!m_resultSet) { - pthread_cond_wait(&m_cond, &m_mutex); - } - m_request.clear(); - *result = m_result; - m_result.clear(); - m_resultSet = false; - pthread_mutex_unlock(&m_mutex); - } - - /** - * Set the result string and notify the waiting thread. - * @param result the result string. - * @param user the new user name. - * @param settings the new client settings. - * @param listenUntil the end time to which to updates were added (exclusive). - * @param disconnect true when the client shall be disconnected. - */ - void setResult(const string& result, const string& user, ClientSettings* settings, time_t listenUntil, - bool disconnect) { - pthread_mutex_lock(&m_mutex); - m_result = result; - m_user = user; - m_disconnect = disconnect; - if (settings) { - m_settings = *settings; - } - m_listenSince = listenUntil; - m_resultSet = true; - pthread_cond_signal(&m_cond); - pthread_mutex_unlock(&m_mutex); - } - - /** - * Return the client settings. - * @param listenSince set listening to the specified start time from which to add updates (inclusive). - * @return the client settings. - */ - ClientSettings getSettings(time_t* listenSince = nullptr) { - if (listenSince) { - *listenSince = m_listenSince; - } - return m_settings; - } - - /** - * Return whether this instance is in one of the listening modes. - * @return whether this instance is in one of the listening modes. - */ - bool isListeningMode() { return m_settings.mode == cm_listen || m_settings.mode == cm_direct; } - - /** - * Return whether the client shall be disconnected. - * @return true when the client shall be disconnected. - */ - bool isDisconnect() { return m_disconnect; } - - - private: - /** whether this is a HTTP message. */ - const bool m_isHttp; - - /** the request string. */ - string m_request; - - /** the current user name. */ - string m_user; - - /** whether the result was already set. */ - bool m_resultSet; - - /** the result string. */ - string m_result; - - /** set to true when the client shall be disconnected. */ - bool m_disconnect; - - /** mutex variable for exclusive lock. */ - pthread_mutex_t m_mutex; - - /** condition variable for exclusive lock. */ - pthread_cond_t m_cond; - - /** the client settings. */ - ClientSettings m_settings; - - /** start timestamp of listening update. */ - time_t m_listenSince; -}; - -/** - * class connection which handle client and baseloop communication. + * Instance of a connected client, either TCP or HTTP. */ class Connection : public Thread { public: @@ -224,10 +46,10 @@ class Connection : public Thread { * Constructor. * @param socket the @a TCPSocket for communication. * @param isHttp whether this is a HTTP message. - * @param netQueue the reference to the @a NetMessage @a Queue. + * @param requestQueue the reference to the @a Request @a Queue. */ - Connection(TCPSocket* socket, const bool isHttp, Queue* netQueue) - : Thread(), m_isHttp(isHttp), m_socket(socket), m_netQueue(netQueue), m_endedAt(0) { + Connection(TCPSocket* socket, const bool isHttp, Queue* requestQueue) + : Thread(), m_isHttp(isHttp), m_socket(socket), m_requestQueue(requestQueue), m_endedAt(0) { m_id = ++m_ids; } @@ -267,8 +89,8 @@ class Connection : public Thread { /** the @a TCPSocket for communication. */ TCPSocket* m_socket; - /** the reference to the @a NetMessage @a Queue. */ - Queue* m_netQueue; + /** the reference to the @a Request @a Queue. */ + Queue* m_requestQueue; /** notification object for shutdown procedure. */ Notify m_notify; @@ -284,7 +106,7 @@ class Connection : public Thread { }; /** - * class network which listening on tcp socket for incoming connections. + * Handler for all TCP and HTTP client connections and registry of active connections. */ class Network : public Thread { public: @@ -293,9 +115,9 @@ class Network : public Thread { * @param local true to accept connections only for local host. * @param port the port to listen for command line connections. * @param httpPort the port to listen for HTTP connections, or 0. - * @param netQueue the reference to the @a NetMessage @a Queue. + * @param requestQueue the reference to the @a Request @a Queue. */ - Network(const bool local, const uint16_t port, const uint16_t httpPort, Queue* netQueue); + Network(const bool local, const uint16_t port, const uint16_t httpPort, Queue* requestQueue); /** * destructor. @@ -317,8 +139,8 @@ class Network : public Thread { /** the list of active @a Connection instances. */ list m_connections; - /** the reference to the @a NetMessage @a Queue. */ - Queue* m_netQueue; + /** the reference to the @a Request @a Queue. */ + Queue* m_requestQueue; /** the command line @a TCPServer instance. */ TCPServer* m_tcpServer; diff --git a/src/ebusd/request.cpp b/src/ebusd/request.cpp new file mode 100644 index 00000000..383742a4 --- /dev/null +++ b/src/ebusd/request.cpp @@ -0,0 +1,143 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "ebusd/request.h" +#include +#include +#include +#include "lib/utils/log.h" + +namespace ebusd { + +RequestImpl::RequestImpl(bool isHttp) + : Request(), m_isHttp(isHttp), m_resultSet(false), m_disconnect(false), m_listenSince(0) { + m_mode.listenMode = lm_none; + m_mode.format = OF_NONE; + m_mode.listenWithUnknown = false; + m_mode.listenOnlyUnknown = false; + pthread_mutex_init(&m_mutex, nullptr); + pthread_cond_init(&m_cond, nullptr); +} + +RequestImpl::~RequestImpl() { + m_resultSet = true; + pthread_mutex_destroy(&m_mutex); + pthread_cond_destroy(&m_cond); +} + +bool RequestImpl::add(const char* request) { + if (request && request[0]) { + string add = request; + add.erase(remove(add.begin(), add.end(), '\r'), add.end()); + m_request.append(add); + } + size_t pos = m_request.find(m_isHttp ? "\n\n" : "\n"); + if (pos != string::npos) { + if (m_isHttp) { + pos = m_request.find("\n"); + m_request.resize(pos); // reduce to first line + // typical first line: GET /ehp/outsidetemp HTTP/1.1 + pos = m_request.rfind(" HTTP/"); + if (pos != string::npos) { + m_request.resize(pos); // remove "HTTP/x.x" suffix + } + pos = 0; + while ((pos=m_request.find('%', pos)) != string::npos && pos+2 <= m_request.length()) { + unsigned int value1, value2; + if (sscanf("%1x%1x", m_request.c_str()+pos+1, &value1, &value2) < 2) { + break; + } + m_request[pos] = static_cast(((value1&0x0f) << 4) | (value2&0x0f)); + m_request.erase(pos+1, 2); + } + } else if (pos+1 == m_request.length()) { + m_request.resize(pos); // reduce to complete lines + } + return true; + } + return m_request.length() == 0 && m_mode.listenMode != lm_none; +} + +void RequestImpl::split(vector* args) { + string token, previous; + istringstream stream(m_request); + char escaped = 0; + + char delim = ' '; + while (getline(stream, token, delim)) { + if (!m_isHttp) { + if (escaped) { + args->pop_back(); + if (token.length() > 0 && token[token.length()-1] == escaped) { + token.erase(token.length() - 1, 1); + escaped = 0; + } + token = previous + " " + token; + } else if (token.length() == 0) { // allow multiple space chars for a single delimiter + continue; + } else if (token[0] == '"' || token[0] == '\'') { + escaped = token[0]; + token.erase(0, 1); + if (token.length() > 0 && token[token.length()-1] == escaped) { + token.erase(token.length() - 1, 1); + escaped = 0; + } + } + } + args->push_back(token); + previous = token; + if (m_isHttp) { + delim = (args->size() == 1) ? '?' : '\n'; + } + } +} + +bool RequestImpl::waitResponse(string* result) { + pthread_mutex_lock(&m_mutex); + + if (!m_resultSet) { + pthread_cond_wait(&m_cond, &m_mutex); + } + m_request.clear(); + *result = m_result; + m_result.clear(); + m_resultSet = false; + pthread_mutex_unlock(&m_mutex); + return m_disconnect; +} + +void RequestImpl::setResult(const string& result, const string& user, RequestMode* mode, time_t listenUntil, + bool disconnect) { + pthread_mutex_lock(&m_mutex); + m_result = result; + m_user = user; + m_disconnect = disconnect; + if (mode) { + m_mode = *mode; + } + m_listenSince = listenUntil; + m_resultSet = true; + pthread_cond_signal(&m_cond); + pthread_mutex_unlock(&m_mutex); +} + +} // namespace ebusd diff --git a/src/ebusd/request.h b/src/ebusd/request.h new file mode 100644 index 00000000..7bab1248 --- /dev/null +++ b/src/ebusd/request.h @@ -0,0 +1,230 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EBUSD_REQUEST_H_ +#define EBUSD_REQUEST_H_ + +#include +#include +#include +#include +#include "lib/ebus/datatype.h" +#include "lib/utils/queue.h" +#include "lib/utils/notify.h" +#include "lib/utils/thread.h" +#include "lib/utils/log.h" + +namespace ebusd { + +/** \file ebusd/request.h + * Abstraction of ebusd client requests. + */ + +/** the request listen mode. */ +enum ListenMode { + lm_none, //!< normal mode (no listening) + lm_listen, //!< listening mode + lm_direct, //!< direct mode +}; + +/** + * Request mode info. + */ +struct RequestMode { + ListenMode listenMode; //!< whether in listening or direct mode + OutputFormat format; //!< the output format settings for listen mode + bool listenWithUnknown; //!< include unknown messages in listen/direct mode + bool listenOnlyUnknown; //!< only print unknown messages in listen/direct mode +}; + +/** + * Abstract class for request/response. + */ +class Request { + public: + /** + * Destructor. + */ + virtual ~Request() { } + + /** + * Add request data from the client. + * @param request the request data from the client. + * @return true when the request is complete and the response shall be prepared. + */ + virtual bool add(const char* request) = 0; + + /** + * @return whether the request is still empty. + */ + virtual bool empty() const = 0; + + /** + * Split the request into arguments. + * @param args the @a vector to push the arguments to. + */ + virtual void split(vector* args) = 0; + + /** + * Return whether this is a HTTP request. + * @return whether this is a HTTP request. + */ + virtual bool isHttp() const = 0; + + /** + * Log the request or the given response in debug level. + */ + virtual void log(const string* response = nullptr) const = 0; + + /** + * Return the current user name. + * @return the current user name. + */ + virtual const string& getUser() const = 0; + + /** + * Wait for the response being set and return the result string. + * @param result the variable in which to store the result string. + * @return true when the client shall be disconnected. + */ + virtual bool waitResponse(string* result) = 0; + + /** + * Set the result string and notify a waiting thread. + * @param result the result string. + * @param user the new user name. + * @param newMode the new @a RequestMode. + * @param listenUntil the end time to which to updates were added (exclusive). + * @param disconnect true when the client shall be disconnected. + */ + virtual void setResult(const string& result, const string& user, RequestMode* newMode, time_t listenUntil, + bool disconnect) = 0; + + /** + * Return the @a RequestMode. + * @param listenSince set listening to the specified start time from which to add updates (inclusive). + * @return the @a RequestMode. + */ + virtual RequestMode getMode(time_t* listenSince = nullptr) = 0; +}; + +/** + * Default @a Request implementation. + */ +class RequestImpl : public Request { + public: + /** + * Constructor. + * @param isHttp whether this is a HTTP request. + */ + explicit RequestImpl(bool isHttp); + + /** + * Destructor. + */ + virtual ~RequestImpl(); + + + private: + /** + * Hidden copy constructor. + * @param src the object to copy from. + */ + RequestImpl(const RequestImpl& src); + + + public: + // @copydoc + bool add(const char* request) override; + + // @copydoc + bool empty() const override { return m_request.empty(); } + + // @copydoc + void split(vector* args) override; + + // @copydoc + bool isHttp() const override { return m_isHttp; } + + // @copydoc + void log(const string* response = nullptr) const override { + if (response) { + if (response->length() > 100) { + logDebug(lf_main, "<<< %s ...", response->substr(0, 100).c_str()); + } else { + logDebug(lf_main, "<<< %s", response->c_str()); + } + } else { + logDebug(lf_main, ">>> %s", m_request.c_str()); + } + } + + // @copydoc + const string& getUser() const override { return m_user; } + + // @copydoc + bool waitResponse(string* result) override; + + // @copydoc + void setResult(const string& result, const string& user, RequestMode* mode, time_t listenUntil, + bool disconnect) override; + + // @copydoc + RequestMode getMode(time_t* listenSince = nullptr) override { + if (listenSince) { + *listenSince = m_listenSince; + } + return m_mode; + } + + + private: + /** whether this is a HTTP message. */ + const bool m_isHttp; + + /** the request string. */ + string m_request; + + /** the current user name. */ + string m_user; + + /** whether the result was already set. */ + bool m_resultSet; + + /** the result string. */ + string m_result; + + /** set to true when the client shall be disconnected. */ + bool m_disconnect; + + /** mutex variable for exclusive lock. */ + pthread_mutex_t m_mutex; + + /** condition variable for exclusive lock. */ + pthread_cond_t m_cond; + + /** the @a RequestMode. */ + RequestMode m_mode; + + /** start timestamp of listening update. */ + time_t m_listenSince; +}; + +} // namespace ebusd + +#endif // EBUSD_REQUEST_H_