From 6446927fab9454449668ce35cbcc8aa6f0c18c56 Mon Sep 17 00:00:00 2001 From: Arjan van Vught Date: Fri, 10 Jan 2025 18:52:00 +0100 Subject: [PATCH] Improved embedded HTTP server --- lib-remoteconfig/.cproject | 10 + .../.settings/language.settings.xml | 2 +- lib-remoteconfig/Makefile.Linux | 2 +- lib-remoteconfig/include/httpd/httpd.h | 49 ++- .../include/httpd/httpdhandlerequest.h | 18 +- .../include/tftp/tftpfileserver.h | 24 +- lib-remoteconfig/src/h3/shellcmd.cpp | 6 +- lib-remoteconfig/src/h3/tftpfileserver.cpp | 4 +- .../src/httpd/http_status_messages.h | 37 ++ lib-remoteconfig/src/httpd/httpd.cpp | 10 +- .../src/httpd/httpdhandlerequest.cpp | 316 ++++++++++++------ lib-remoteconfig/src/linux/firmware.cpp | 1 + lib-remoteconfig/src/linux/ubootheader.cpp | 83 +++++ lib-remoteconfig/src/tftp/remoteconfig.cpp | 1 + lib-remoteconfig/src/tftp/tftpfileserver.cpp | 5 +- 15 files changed, 427 insertions(+), 141 deletions(-) create mode 100755 lib-remoteconfig/src/httpd/http_status_messages.h create mode 120000 lib-remoteconfig/src/linux/firmware.cpp create mode 100755 lib-remoteconfig/src/linux/ubootheader.cpp diff --git a/lib-remoteconfig/.cproject b/lib-remoteconfig/.cproject index 4ded30592..99760edf8 100644 --- a/lib-remoteconfig/.cproject +++ b/lib-remoteconfig/.cproject @@ -30,6 +30,7 @@ + @@ -184,6 +186,7 @@ + @@ -205,6 +208,7 @@ + @@ -333,11 +337,13 @@ + @@ -357,11 +363,13 @@ + @@ -384,6 +392,7 @@ + diff --git a/lib-remoteconfig/.settings/language.settings.xml b/lib-remoteconfig/.settings/language.settings.xml index dbef11a21..c0d3b45da 100644 --- a/lib-remoteconfig/.settings/language.settings.xml +++ b/lib-remoteconfig/.settings/language.settings.xml @@ -14,7 +14,7 @@ - + diff --git a/lib-remoteconfig/Makefile.Linux b/lib-remoteconfig/Makefile.Linux index 33594e0b5..35121d3ee 100755 --- a/lib-remoteconfig/Makefile.Linux +++ b/lib-remoteconfig/Makefile.Linux @@ -1,4 +1,4 @@ -DEFINES =ENABLE_CONTENT +DEFINES =ENABLE_CONTENT ENABLE_FIRMWARE_UPLOAD #DEFINES+=NDEBUG diff --git a/lib-remoteconfig/include/httpd/httpd.h b/lib-remoteconfig/include/httpd/httpd.h index 7f579c55e..492425d13 100755 --- a/lib-remoteconfig/include/httpd/httpd.h +++ b/lib-remoteconfig/include/httpd/httpd.h @@ -1,8 +1,12 @@ /** * @file httpd.h + * @brief HTTP daemon class for managing HTTP server tasks. * + * This class handles HTTP requests and integrates with the network and mDNS subsystems. + * It uses placement new to construct and destruct request handlers explicitly. + */ -/* Copyright (C) 2021-2024 by Arjan van Vught mailto:info@gd32-dmx.org +/* Copyright (C) 2021-2025 by Arjan van Vught mailto:info@gd32-dmx.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -35,11 +39,38 @@ #include "../../lib-network/config/net_config.h" +#include "debug.h" + +/** + * @class HttpDaemon + * @brief A class that manages an HTTP server with support for multiple connections. + * + * The HttpDaemon class sets up an HTTP server, handles incoming requests, and integrates with mDNS. + */ class HttpDaemon { public: + /** + * @brief Constructor for HttpDaemon. + * + * Initializes the HTTP daemon, sets up the TCP listener on port 80, + * creates request handlers, and registers the service with mDNS. + */ HttpDaemon(); + + /** + * @brief Destructor for HttpDaemon. + * + * Cleans up resources, unregisters the mDNS service, + * destroys request handlers, and stops the TCP listener. + */ ~HttpDaemon(); + /** + * @brief Main loop function to handle HTTP requests. + * + * Reads incoming TCP data, determines the connection handle, + * and dispatches the request to the appropriate handler. + */ void Run() { uint32_t nConnectionHandle; const auto nBytesReceived = Network::Get()->TcpRead(m_nHandle, const_cast(reinterpret_cast(&m_pReceiveBuffer)), nConnectionHandle); @@ -50,11 +81,23 @@ class HttpDaemon { DEBUG_PRINTF("nConnectionHandle=%u", nConnectionHandle); - pHandleRequest[nConnectionHandle]->HandleRequest(nBytesReceived, m_pReceiveBuffer); + handleRequest[nConnectionHandle].HandleRequest(nBytesReceived, m_pReceiveBuffer); } private: - HttpDeamonHandleRequest *pHandleRequest[TCP_MAX_TCBS_ALLOWED]; + /** + * https://www.gd32-dmx.org/memory.html + */ +#if defined (GD32F207RG) || defined (GD32F450VE) || defined (GD32F470ZK) +# define SECTION_HTTPD __attribute__ ((section (".httpd"))) +#else +# define SECTION_HTTPD +#endif + /* + * Each handler corresponds to a connection handle. Objects are constructed + * using placement new and must be explicitly destructed. + */ + static inline HttpDeamonHandleRequest handleRequest[TCP_MAX_TCBS_ALLOWED] __attribute__ ((aligned (4))) SECTION_HTTPD; int32_t m_nHandle { -1 }; char *m_pReceiveBuffer { nullptr }; }; diff --git a/lib-remoteconfig/include/httpd/httpdhandlerequest.h b/lib-remoteconfig/include/httpd/httpdhandlerequest.h index 9ed39c127..0a005c0c6 100755 --- a/lib-remoteconfig/include/httpd/httpdhandlerequest.h +++ b/lib-remoteconfig/include/httpd/httpdhandlerequest.h @@ -27,6 +27,7 @@ #define HTTPD_HTTPDHANDLEREQUEST_H_ #include +#include #include "http.h" #include "net/protocol/tcp.h" @@ -42,7 +43,13 @@ static constexpr uint32_t BUFSIZE = HTTPD_CONTENT_SIZE; class HttpDeamonHandleRequest { public: - HttpDeamonHandleRequest(uint32_t nConnectionHandle, int32_t nHandle) : m_nConnectionHandle(nConnectionHandle), m_nHandle(nHandle) { + HttpDeamonHandleRequest() : m_nConnectionHandle(0), m_nHandle(-1) { + DEBUG_ENTRY + DEBUG_EXIT + + } + + HttpDeamonHandleRequest(uint32_t nConnectionHandle, int32_t nHandle) : m_nConnectionHandle(nConnectionHandle), m_nHandle(nHandle) { DEBUG_ENTRY DEBUG_PRINTF("[%u] m_nConnectionHandle=%u, m_nHandle=%d", httpd::BUFSIZE, m_nConnectionHandle, m_nHandle); DEBUG_EXIT @@ -58,6 +65,7 @@ class HttpDeamonHandleRequest { http::Status HandleGetTxt(); http::Status HandlePost(const bool hasDataOnly); http::Status HandleDelete(const bool hasDataOnly); + http::Status HandlePostJSON(); private: uint32_t m_nConnectionHandle; @@ -69,16 +77,18 @@ class HttpDeamonHandleRequest { char *m_pUri { nullptr }; char *m_pFileData { nullptr }; - const char *m_pContent { nullptr }; + char *m_pFirmwareFilename { nullptr }; char *m_pReceiveBuffer { nullptr }; + const char *m_pContent { nullptr }; http::Status m_Status { http::Status::UNKNOWN_ERROR }; http::RequestMethod m_RequestMethod { http::RequestMethod::UNKNOWN }; http::contentTypes m_RequestContentType { http::contentTypes::NOT_DEFINED }; - bool m_IsAction { false }; + bool m_isAction { false }; + - static char m_DynamicContent[httpd::BUFSIZE]; + char m_DynamicContent[httpd::BUFSIZE]; }; diff --git a/lib-remoteconfig/include/tftp/tftpfileserver.h b/lib-remoteconfig/include/tftp/tftpfileserver.h index 16a4c7d32..b3388bb20 100755 --- a/lib-remoteconfig/include/tftp/tftpfileserver.h +++ b/lib-remoteconfig/include/tftp/tftpfileserver.h @@ -2,7 +2,7 @@ * @file tftpfileserver.h * */ -/* Copyright (C) 2019-2024 by Arjan van Vught mailto:info@gd32-dmx.org +/* Copyright (C) 2019-2025 by Arjan van Vught mailto:info@gd32-dmx.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -37,28 +37,6 @@ namespace tftpfileserver { bool is_valid(const void *pBuffer); -#if defined(__linux__) || defined (__APPLE__) -#else -# if defined (H3) -# if defined(ORANGE_PI) - static constexpr char FILE_NAME[] = "orangepi_zero.uImage"; -# else - static constexpr char FILE_NAME[] = "orangepi_one.uImage"; -# endif -# elif defined (GD32) -# if defined (GD32F10X) - static constexpr char FILE_NAME[] = "gd32f107.bin"; -# elif defined (GD32F20X) - static constexpr char FILE_NAME[] = "gd32f207.bin"; -# elif defined (GD32F4XX) - static constexpr char FILE_NAME[] = "gd32f4xx.bin"; -# elif defined (GD32H7XX) - static constexpr char FILE_NAME[] = "gd32h7xx.bin"; -# else -# error FAMILY is not defined -# endif -# endif -#endif } // namespace tftpfileserver class TFTPFileServer final: public TFTPDaemon { diff --git a/lib-remoteconfig/src/h3/shellcmd.cpp b/lib-remoteconfig/src/h3/shellcmd.cpp index cc02271bb..6270c5831 100644 --- a/lib-remoteconfig/src/h3/shellcmd.cpp +++ b/lib-remoteconfig/src/h3/shellcmd.cpp @@ -3,7 +3,7 @@ * */ /* Copyright (C) 2020 by hippy mailto:dmxout@gmail.com - * Copyright (C) 2020-2024 by Arjan van Vught mailto:info@gd32-dmx.org + * Copyright (C) 2020-2025 by Arjan van Vught mailto:info@gd32-dmx.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,15 +31,15 @@ #include "debug.h" +void h3_board_dump(); + extern "C" { -void h3_board_dump(void); void h3_dump_memory_mapping(void); void h3_ccu_pll_dump(void); void arm_dump_memmap(void); void arm_dump_page_table(void); } - namespace shell::dump { namespace arg { static constexpr char BOARD[] = "board"; diff --git a/lib-remoteconfig/src/h3/tftpfileserver.cpp b/lib-remoteconfig/src/h3/tftpfileserver.cpp index d71de3c4f..80d8e2924 100644 --- a/lib-remoteconfig/src/h3/tftpfileserver.cpp +++ b/lib-remoteconfig/src/h3/tftpfileserver.cpp @@ -2,7 +2,7 @@ * @file tftpfileserver.cpp * */ -/* Copyright (C) 2019-2021 by Arjan van Vught mailto:info@orangepi-dmx.nl +/* Copyright (C) 2019-2025 by Arjan van Vught mailto:info@gd32-dmx.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,7 +26,7 @@ #include #include -#include "h3/ubootheader.h" +#include "ubootheader.h" #include "remoteconfig.h" #include "debug.h" diff --git a/lib-remoteconfig/src/httpd/http_status_messages.h b/lib-remoteconfig/src/httpd/http_status_messages.h new file mode 100755 index 000000000..f0bd7201b --- /dev/null +++ b/lib-remoteconfig/src/httpd/http_status_messages.h @@ -0,0 +1,37 @@ +/** + * @file http_status_messages.h + * + */ +/* Copyright (C) 2025 by Arjan van Vught mailto:info@gd32-dmx.org + * + * 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 HTTP_STATUS_MESSAGES_H_ +#define HTTP_STATUS_MESSAGES_H_ + +namespace httpd { +static constexpr const char STATUS_400[] = "{\"code\":400,\"status\":\"Bad Request\",\"message\":\"%s\"}\n"; +static constexpr auto STATUS_400_LENGTH = sizeof(STATUS_400) - 1; +static constexpr const char STATUS_404[] = "{\"code\":404,\"status\":\"Not found\",\"message\":\"%s\"}\n"; +static constexpr auto STATUS_404_LENGTH = sizeof(STATUS_404) - 1; +} // namespace httpd + +#endif /* HTTP_STATUS_MESSAGES_H_ */ diff --git a/lib-remoteconfig/src/httpd/httpd.cpp b/lib-remoteconfig/src/httpd/httpd.cpp index e11545e01..e03f424a7 100755 --- a/lib-remoteconfig/src/httpd/httpd.cpp +++ b/lib-remoteconfig/src/httpd/httpd.cpp @@ -2,7 +2,7 @@ * @file httpd.cpp * */ -/* Copyright (C) 2021-2024 by Arjan van Vught mailto:info@gd32-dmx.org +/* Copyright (C) 2021-2025 by Arjan van Vught mailto:info@gd32-dmx.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -47,8 +47,7 @@ HttpDaemon::HttpDaemon() { assert(m_nHandle != -1); for (uint32_t nIndex = 0; nIndex < TCP_MAX_TCBS_ALLOWED; nIndex++) { - pHandleRequest[nIndex] = new HttpDeamonHandleRequest(nIndex, m_nHandle); - assert(pHandleRequest[nIndex] != nullptr); + new (&handleRequest[nIndex]) HttpDeamonHandleRequest(nIndex, m_nHandle); } mdns_service_record_add(nullptr, mdns::Services::HTTP); @@ -62,9 +61,8 @@ HttpDaemon::~HttpDaemon() { mdns_service_record_delete(mdns::Services::HTTP); for (uint32_t nIndex = 0; nIndex < TCP_MAX_TCBS_ALLOWED; nIndex++) { - if (pHandleRequest[nIndex] != nullptr) { - delete pHandleRequest[nIndex]; - } + // Explicitly calling the destructor because objects were constructed with placement new. + handleRequest[nIndex].~HttpDeamonHandleRequest(); } Network::Get()->TcpEnd(m_nHandle); diff --git a/lib-remoteconfig/src/httpd/httpdhandlerequest.cpp b/lib-remoteconfig/src/httpd/httpdhandlerequest.cpp index 9d5676a24..fd8400a73 100755 --- a/lib-remoteconfig/src/httpd/httpdhandlerequest.cpp +++ b/lib-remoteconfig/src/httpd/httpdhandlerequest.cpp @@ -2,7 +2,7 @@ * @file httpdhandlerequest.cpp * */ -/* Copyright (C) 2024 by Arjan van Vught mailto:info@gd32-dmx.org +/* Copyright (C) 2024-2025 by Arjan van Vught mailto:info@gd32-dmx.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,7 +27,7 @@ # undef NDEBUG #endif -#if !defined(__clang__) +#if defined(__GNUC__) && !defined(__clang__) # pragma GCC push_options # pragma GCC optimize ("O2") #endif @@ -39,7 +39,7 @@ #include #include "httpd/httpdhandlerequest.h" - +#include "http_status_messages.h" #include "../http/content/json_switch.h" #include "remoteconfig.h" @@ -70,14 +70,16 @@ # define ENABLE_METHOD_DELETE #endif +#if defined (ENABLE_FIRMWARE_UPLOAD) +# include "firmware.h" +#endif + #include "debug.h" #if defined ENABLE_CONTENT const char *get_file_content(const char *fileName, uint32_t& nSize, http::contentTypes& contentType); #endif -char HttpDeamonHandleRequest::m_DynamicContent[httpd::BUFSIZE]; - #ifndef NDEBUG static constexpr char s_request_method[][8] = {"GET", "POST", "DELETE", "UNKNOWN" }; #endif @@ -105,10 +107,10 @@ void HttpDeamonHandleRequest::HandleRequest(const uint32_t nBytesReceived, char if (m_Status == http::Status::OK) { // It is a supported request - if (m_RequestMethod == http::RequestMethod::GET) { m_Status = HandleGet(); } else if (m_RequestMethod == http::RequestMethod::POST) { + // Handling headers and body data for the first chunk m_Status = HandlePost(false); if ((m_Status == http::Status::OK) && (m_nRequestDataLength == 0)) { @@ -116,6 +118,11 @@ void HttpDeamonHandleRequest::HandleRequest(const uint32_t nBytesReceived, char DEBUG_EXIT return; } + + if (m_nRequestDataLength < m_nRequestContentLength) { + DEBUG_EXIT + return; + } } #if defined (ENABLE_METHOD_DELETE) else if (m_RequestMethod == http::RequestMethod::DELETE) { @@ -130,7 +137,13 @@ void HttpDeamonHandleRequest::HandleRequest(const uint32_t nBytesReceived, char #endif } } else if ((m_Status == http::Status::OK) && (m_RequestMethod == http::RequestMethod::POST)) { + // Handling body data only for subsequent chunks m_Status = HandlePost(true); + + if (m_nRequestDataLength < m_nRequestContentLength) { + DEBUG_EXIT + return; + } } #if defined (ENABLE_METHOD_DELETE) else if ((m_Status == http::Status::OK) && (m_RequestMethod == http::RequestMethod::DELETE)) { @@ -156,13 +169,17 @@ void HttpDeamonHandleRequest::HandleRequest(const uint32_t nBytesReceived, char pStatusMsg = "Internal Server Error"; break; case http::Status::METHOD_NOT_IMPLEMENTED: - pStatusMsg = "Method Not Implemented"; - break; + __attribute__ ((fallthrough)); + /* no break */ case http::Status::VERSION_NOT_SUPPORTED: - pStatusMsg = "Version Not Supported"; - break; + __attribute__ ((fallthrough)); + /* no break */ default: - pStatusMsg = "Unknown Error"; + Network::Get()->TcpAbort(m_nHandle, m_nConnectionHandle); + m_Status = http::Status::UNKNOWN_ERROR; + m_RequestMethod = http::RequestMethod::UNKNOWN; + DEBUG_EXIT + return; break; } @@ -186,6 +203,7 @@ void HttpDeamonHandleRequest::HandleRequest(const uint32_t nBytesReceived, char Network::Get()->TcpWrite(m_nHandle, reinterpret_cast(m_pReceiveBuffer), nHeaderLength, m_nConnectionHandle); Network::Get()->TcpWrite(m_nHandle, reinterpret_cast(m_pContent), m_nContentSize, m_nConnectionHandle); + DEBUG_PRINTF("m_nContentLength=%u", m_nContentSize); m_Status = http::Status::UNKNOWN_ERROR; @@ -197,10 +215,11 @@ void HttpDeamonHandleRequest::HandleRequest(const uint32_t nBytesReceived, char http::Status HttpDeamonHandleRequest::ParseRequest() { char *pLine = m_pReceiveBuffer; uint32_t nLine = 0; - http::Status status = http::Status::UNKNOWN_ERROR; + auto httpStatus = http::Status::UNKNOWN_ERROR; m_RequestContentType = http::contentTypes::NOT_DEFINED; m_nRequestContentLength = 0; m_nRequestDataLength = 0; + m_pFirmwareFilename = nullptr; for (uint32_t i = 0; i < m_nBytesReceived; i++) { if (m_pReceiveBuffer[i] == '\n') { @@ -208,7 +227,7 @@ http::Status HttpDeamonHandleRequest::ParseRequest() { m_pReceiveBuffer[i - 1] = '\0'; if (nLine++ == 0) { - status = ParseMethod(pLine); + httpStatus = ParseMethod(pLine); } else { if (pLine[0] == '\0') { assert((i + 1) <= m_nBytesReceived); @@ -221,11 +240,11 @@ http::Status HttpDeamonHandleRequest::ParseRequest() { return http::Status::OK; } - status = ParseHeaderField(pLine); + httpStatus = ParseHeaderField(pLine); } - if (status != http::Status::OK) { - return status; + if (httpStatus != http::Status::OK) { + return httpStatus; } pLine = &m_pReceiveBuffer[++i]; @@ -284,7 +303,7 @@ http::Status HttpDeamonHandleRequest::ParseMethod(char *pLine) { } /** - * Only interested in "Content-Type" and "Content-Length" + * Only interested in "Content-Type", "Content-Length", and "X-Filename" * Where we check for "Content-Type: application/json" and "Content-Type: application/octet-stream" */ @@ -298,20 +317,29 @@ http::Status HttpDeamonHandleRequest::ParseHeaderField(char *pLine) { return http::Status::BAD_REQUEST; } + DEBUG_PUTS(pToken); + if (strcasecmp(pToken, "Content-Type") == 0) { if ((pToken = strtok(nullptr, " ;")) == nullptr) { + DEBUG_EXIT return http::Status::BAD_REQUEST; } if (memcmp(pToken, "application/", 12) == 0) { if (strcmp(&pToken[12], "json") == 0) { m_RequestContentType = http::contentTypes::APPLICATION_JSON; - DEBUG_ENTRY + DEBUG_EXIT return http::Status::OK; } + if (strcmp(&pToken[12], "octet-stream") == 0) { +#if defined (ENABLE_FIRMWARE_UPLOAD) m_RequestContentType = http::contentTypes::APPLICATION_OCTET_STREAM; - DEBUG_ENTRY + DEBUG_EXIT return http::Status::OK; +#else + DEBUG_EXIT + return http::Status::BAD_REQUEST; +#endif } } } else if (strcasecmp(pToken, "Content-Length") == 0) { @@ -330,8 +358,36 @@ http::Status HttpDeamonHandleRequest::ParseHeaderField(char *pLine) { nTmp += nDigit; } +#if defined ENABLE_FIRMWARE_UPLOAD + if (nTmp > FIRMWARE_MAX_SIZE) { + DEBUG_EXIT + return http::Status::REQUEST_ENTITY_TOO_LARGE; + } +#endif + m_nRequestContentLength = nTmp; + DEBUG_EXIT + return http::Status::OK; + } +#if defined (ENABLE_FIRMWARE_UPLOAD) + else if (strcasecmp(pToken, "X-Filename") == 0) { + if ((pToken = strtok(nullptr, " ")) == nullptr) { + DEBUG_EXIT + return http::Status::BAD_REQUEST; + } + + if (strncmp(pToken, firmware::FILE_NAME, firmware::FILE_NAME_LENGTH) != 0) { + DEBUG_EXIT + return http::Status::BAD_REQUEST; + } + + m_pFirmwareFilename = pToken; + m_pFirmwareFilename[firmware::FILE_NAME_LENGTH] = '\0'; + + DEBUG_EXIT + return http::Status::OK; } +#endif DEBUG_EXIT return http::Status::OK; @@ -590,15 +646,116 @@ http::Status HttpDeamonHandleRequest::HandlePost(const bool hasDataOnly) { DEBUG_ENTRY DEBUG_PRINTF("m_nBytesReceived=%d, m_nFileDataLength=%u, m_nRequestContentLength=%u -> hasDataOnly=%c", m_nBytesReceived, m_nRequestDataLength, m_nRequestContentLength, hasDataOnly ? 'Y' : 'N'); + if (hasDataOnly) { + // Accumulate body data only + m_pFileData = m_pReceiveBuffer; + m_nRequestDataLength += m_nBytesReceived; + + // Check if we are still waiting for more data + if (m_nRequestDataLength < m_nRequestContentLength) { + DEBUG_PUTS("Partial body data received, waiting for more..."); +#if defined (ENABLE_FIRMWARE_UPLOAD) + if (m_RequestContentType == http::contentTypes::APPLICATION_OCTET_STREAM) { + if (!firmware::firmware_install_continue(reinterpret_cast(m_pFileData), m_nBytesReceived)) { + DEBUG_EXIT + return http::Status::BAD_REQUEST; + } + } +#endif + DEBUG_EXIT + return http::Status::OK; + } + } else { + // Handle HTTP headers and content type (only in the first chunk) + if (!((m_RequestContentType == http::contentTypes::APPLICATION_JSON) || (m_RequestContentType == http::contentTypes::APPLICATION_OCTET_STREAM))) { + DEBUG_EXIT + return http::Status::BAD_REQUEST; + } + + if (m_RequestContentType == http::contentTypes::APPLICATION_JSON) { + if (memcmp(m_pUri, "/json", 5) != 0) { + DEBUG_EXIT + return http::Status::NOT_FOUND; + } + + m_isAction = (memcmp(m_pUri + 5, "/action", 7) == 0); + } + +#if defined (ENABLE_FIRMWARE_UPLOAD) + if (m_RequestContentType == http::contentTypes::APPLICATION_OCTET_STREAM) { + if (m_pFirmwareFilename == nullptr) { + DEBUG_EXIT + return http::Status::NOT_FOUND; + } else { + DEBUG_PRINTF("Firmware: Filename = %s, Size = %u", m_pFirmwareFilename, m_nRequestContentLength); + if (!firmware::firmware_install_start(reinterpret_cast(m_pFileData), m_nRequestDataLength)) { + DEBUG_EXIT + return http::Status::BAD_REQUEST; + } + } + } +#endif + // Check if full body data is received + if (m_nRequestDataLength < m_nRequestContentLength) { + DEBUG_PUTS("Headers processed, waiting for body data..."); + DEBUG_EXIT + return http::Status::OK; + } + } + + // Process the fully received POST body + + if (m_RequestContentType == http::contentTypes::APPLICATION_JSON) { + return HandlePostJSON(); + } + +#if defined (ENABLE_FIRMWARE_UPLOAD) + DEBUG_PRINTF("Firmware: Total bytes received = %u", m_nRequestDataLength); + + + if (m_nRequestDataLength != m_nRequestContentLength) { + m_RequestContentType = http::contentTypes::APPLICATION_JSON; + m_pContent = &m_DynamicContent[0]; + m_nContentSize = static_cast(snprintf(m_DynamicContent, sizeof(m_DynamicContent) - 1U, + "{\"status\": \"failed\", \"message\": \"File upload not completed\"}\n")); + + DEBUG_EXIT + return http::Status::BAD_REQUEST; + } + + if (!firmware::firmware_install_end(reinterpret_cast(m_pFileData), m_nBytesReceived)) { + DEBUG_EXIT + return http::Status::BAD_REQUEST; + } + + m_RequestContentType = http::contentTypes::APPLICATION_JSON; + m_pContent = &m_DynamicContent[0]; + m_nContentSize = static_cast(snprintf(m_DynamicContent, sizeof(m_DynamicContent) - 1U, + "{\"status\": \"success\", \"message\": \"File uploaded successfully\"}\n")); + + DEBUG_EXIT + return http::Status::OK; +#endif + DEBUG_EXIT + return http::Status::INTERNAL_SERVER_ERROR; +} + +/** + * DELETE + */ + +http::Status HttpDeamonHandleRequest::HandleDelete(const bool hasDataOnly) { + DEBUG_PRINTF("hasDataOnly=%c -> m_nBytesReceived=%d, m_nRequestDataLength=%u, m_nRequestContentLength=%u", hasDataOnly ? 'Y' : 'N', m_nBytesReceived, m_nRequestDataLength, m_nRequestContentLength); + if (!hasDataOnly) { if (m_RequestContentType != http::contentTypes::APPLICATION_JSON) { DEBUG_EXIT return http::Status::BAD_REQUEST; } - m_IsAction = (strcmp(m_pUri, "/json/action") == 0); + m_isAction = (strcmp(m_pUri, "/json/action") == 0); - if (!m_IsAction && (strcmp(m_pUri, "/json") != 0)) { + if (!m_isAction) { DEBUG_EXIT return http::Status::NOT_FOUND; } @@ -614,12 +771,50 @@ http::Status HttpDeamonHandleRequest::HandlePost(const bool hasDataOnly) { if (hasDataOnly) { m_pFileData = m_pReceiveBuffer; - m_nRequestDataLength = static_cast(m_nBytesReceived); + m_nRequestDataLength = m_nBytesReceived; + } + + DEBUG_PRINTF("%d|%.*s|->%d", m_nRequestDataLength, m_nRequestDataLength, m_pFileData, m_isAction); + + auto const nJsonLength = properties::convert_json_file(m_pFileData, m_nRequestDataLength, true); + + if (nJsonLength <= 0) { + DEBUG_PUTS("Status::BAD_REQUEST"); + DEBUG_EXIT + return http::Status::BAD_REQUEST; + } + + m_pFileData[nJsonLength - 1] = '\0'; + debug_dump(m_pFileData, nJsonLength); + +#if defined (NODE_SHOWFILE) + if (memcmp(m_pFileData, "show=", 5) == 0) { + remoteconfig::showfile::json_delete(m_pFileData, nJsonLength); + } else +#endif + { + DEBUG_PUTS("Status::BAD_REQUEST"); + DEBUG_EXIT + return http::Status::BAD_REQUEST; } - DEBUG_PRINTF("%d|%.*s|->%c", m_nRequestDataLength, m_nRequestDataLength, m_pFileData, m_IsAction ? 'Y' : 'N'); + m_RequestContentType = http::contentTypes::TEXT_HTML; + m_pContent = &m_DynamicContent[0]; + m_nContentSize = static_cast(snprintf(m_DynamicContent, sizeof(m_DynamicContent) - 1U, + "\n" + "\n" + "Submit\n" + "

OK

\n" + "\n")); + + DEBUG_EXIT + return http::Status::OK; +} + +http::Status HttpDeamonHandleRequest::HandlePostJSON() { + DEBUG_PRINTF("%d|%.*s|->%c", m_nRequestDataLength, m_nRequestDataLength, m_pFileData, m_isAction ? 'Y' : 'N'); - if (m_IsAction) { + if (m_isAction) { auto const nJsonLength = properties::convert_json_file(m_pFileData, m_nRequestDataLength, true); if (nJsonLength <= 0) { @@ -687,80 +882,11 @@ http::Status HttpDeamonHandleRequest::HandlePost(const bool hasDataOnly) { } m_RequestContentType = http::contentTypes::TEXT_HTML; + m_pContent = &m_DynamicContent[0]; m_nContentSize = static_cast(snprintf(m_DynamicContent, sizeof(m_DynamicContent) - 1U, "\n" "\n" - "Submit\n" - "

OK

\n" - "\n")); - - DEBUG_EXIT - return http::Status::OK; -} - -/** - * DELETE - */ - -http::Status HttpDeamonHandleRequest::HandleDelete(const bool hasDataOnly) { - DEBUG_PRINTF("hasDataOnly=%c -> m_nBytesReceived=%d, m_nRequestDataLength=%u, m_nRequestContentLength=%u", hasDataOnly ? 'Y' : 'N', m_nBytesReceived, m_nRequestDataLength, m_nRequestContentLength); - - if (!hasDataOnly) { - if (m_RequestContentType != http::contentTypes::APPLICATION_JSON) { - DEBUG_EXIT - return http::Status::BAD_REQUEST; - } - - m_IsAction = (strcmp(m_pUri, "/json/action") == 0); - - if (!m_IsAction) { - DEBUG_EXIT - return http::Status::NOT_FOUND; - } - } - - const auto hasHeadersOnly = (!hasDataOnly && ((m_nBytesReceived < m_nRequestContentLength) || m_nRequestDataLength == 0)); - - if (hasHeadersOnly) { - DEBUG_PUTS("hasHeadersOnly"); - DEBUG_EXIT - return http::Status::OK; - } - - if (hasDataOnly) { - m_pFileData = m_pReceiveBuffer; - m_nRequestDataLength = static_cast(m_nBytesReceived); - } - - DEBUG_PRINTF("%d|%.*s|->%d", m_nRequestDataLength, m_nRequestDataLength, m_pFileData, m_IsAction); - - auto const nJsonLength = properties::convert_json_file(m_pFileData, m_nRequestDataLength, true); - - if (nJsonLength <= 0) { - DEBUG_PUTS("Status::BAD_REQUEST"); - DEBUG_EXIT - return http::Status::BAD_REQUEST; - } - - m_pFileData[nJsonLength - 1] = '\0'; - debug_dump(m_pFileData, nJsonLength); - -#if defined (NODE_SHOWFILE) - if (memcmp(m_pFileData, "show=", 5) == 0) { - remoteconfig::showfile::json_delete(m_pFileData, nJsonLength); - } else -#endif - { - DEBUG_PUTS("Status::BAD_REQUEST"); - DEBUG_EXIT - return http::Status::BAD_REQUEST; - } - - m_RequestContentType = http::contentTypes::TEXT_HTML; - m_nContentSize = static_cast(snprintf(m_DynamicContent, sizeof(m_DynamicContent) - 1U, - "\n" - "\n" - "Submit\n" + "Submit JSON\n" "

OK

\n" "\n")); diff --git a/lib-remoteconfig/src/linux/firmware.cpp b/lib-remoteconfig/src/linux/firmware.cpp new file mode 120000 index 000000000..47c0d2c6e --- /dev/null +++ b/lib-remoteconfig/src/linux/firmware.cpp @@ -0,0 +1 @@ +../../../lib-flashcodeinstall/src/firmware.cpp \ No newline at end of file diff --git a/lib-remoteconfig/src/linux/ubootheader.cpp b/lib-remoteconfig/src/linux/ubootheader.cpp new file mode 100755 index 000000000..894991811 --- /dev/null +++ b/lib-remoteconfig/src/linux/ubootheader.cpp @@ -0,0 +1,83 @@ +/** + * @file ubootheader.cpp + * + */ +/* Copyright (C) 2025 by Arjan van Vught mailto:info@gd32-dmx.org + * + * 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 +#include +#include +#include +#include +#include + +#include "ubootheader.h" +#include "firmware.h" + +UBootHeader::UBootHeader(const uint8_t *pHeader): m_pHeader(pHeader) { + assert(pHeader != nullptr); + + auto *pImageHeader = reinterpret_cast(pHeader); + + m_bIsValid = (pImageHeader->ih_magic == __builtin_bswap32(IH_MAGIC)); + m_bIsValid &= (pImageHeader->ih_load == __builtin_bswap32(IH_LOAD)); + m_bIsValid &= (pImageHeader->ih_ep == __builtin_bswap32(IH_EP)); + m_bIsValid &= (pImageHeader->ih_os == IH_OS_U_BOOT); + m_bIsValid &= (pImageHeader->ih_arch == IH_ARCH_ARM); + m_bIsValid &= (pImageHeader->ih_type == IH_TYPE_STANDALONE); + m_bIsValid &= (strncmp(reinterpret_cast(pImageHeader->ih_name), "http://www.orangepi-dmx.org", IH_NMLEN) == 0); + + m_bIsCompressed = (pImageHeader->ih_comp == IH_COMP_GZIP); + +#if !defined (CONFIG_UBOOT_COMPRESSED) + m_bIsValid &= !m_bIsCompressed; +#endif + + TImageHeader ih; + memcpy(&ih, pImageHeader, sizeof(struct TImageHeader)); + ih.ih_hcrc = 0; + const auto hcrc = crc32(0, reinterpret_cast(&ih), sizeof(struct TImageHeader)); + m_bIsValid &= (__builtin_bswap32(pImageHeader->ih_hcrc) == hcrc); +} + +void UBootHeader::Dump() { + if (!m_bIsValid) { + printf("* Not a valid header! *\n"); + } + + auto *pImageHeader = reinterpret_cast(m_pHeader); + const auto rawtime = static_cast(__builtin_bswap32(pImageHeader->ih_time)); + auto *info = localtime(&rawtime); + + printf("Magic Number : %.8x\n", __builtin_bswap32(pImageHeader->ih_magic)); + printf("Header CRC Checksum : %.8x\n", __builtin_bswap32(pImageHeader->ih_hcrc)); + printf("Creation Timestamp : %.8x - %s", __builtin_bswap32(pImageHeader->ih_time), asctime(info)); + printf("Data Size : %.8x - %d kBytes\n", __builtin_bswap32(pImageHeader->ih_size), __builtin_bswap32(pImageHeader->ih_size) / 1024); + printf("Data Load Address : %.8x\n", __builtin_bswap32(pImageHeader->ih_load)); + printf("Entry Point Address : %.8x\n", __builtin_bswap32(pImageHeader->ih_ep)); + printf("Image CRC Checksum : %.8x\n", __builtin_bswap32(pImageHeader->ih_dcrc)); + printf("Operating System : %d - %s\n", pImageHeader->ih_os, pImageHeader->ih_os == IH_OS_U_BOOT ? "Firmware" : "Not supported"); + printf("CPU architecture : %d - %s\n", pImageHeader->ih_arch, pImageHeader->ih_arch == IH_ARCH_ARM ? "Arm" : "Not supported"); + printf("Image type : %d - %s\n", pImageHeader->ih_type, pImageHeader->ih_type == IH_TYPE_STANDALONE ? "Standalone Program" : "Not supported"); + printf("Compression : %d - %s\n", pImageHeader->ih_comp, pImageHeader->ih_comp == IH_COMP_NONE ? "none" : (pImageHeader->ih_comp == IH_COMP_GZIP ? "gzip" : "Not supported")); + printf("Image Name : %s\n", pImageHeader->ih_name); +} diff --git a/lib-remoteconfig/src/tftp/remoteconfig.cpp b/lib-remoteconfig/src/tftp/remoteconfig.cpp index 6824b8463..80494dc9d 100644 --- a/lib-remoteconfig/src/tftp/remoteconfig.cpp +++ b/lib-remoteconfig/src/tftp/remoteconfig.cpp @@ -37,6 +37,7 @@ #include "tftp/tftpfileserver.h" #include "flashcodeinstall.h" +#include "firmware.h" #include "display.h" diff --git a/lib-remoteconfig/src/tftp/tftpfileserver.cpp b/lib-remoteconfig/src/tftp/tftpfileserver.cpp index 88592e403..f5af99721 100644 --- a/lib-remoteconfig/src/tftp/tftpfileserver.cpp +++ b/lib-remoteconfig/src/tftp/tftpfileserver.cpp @@ -35,13 +35,12 @@ #include "tftp/tftpfileserver.h" #include "remoteconfig.h" #include "display.h" +#include "firmware.h" #include "debug.h" using namespace tftpfileserver; -static constexpr auto FILE_NAME_LENGTH = sizeof(FILE_NAME) - 1; - TFTPFileServer::TFTPFileServer(uint8_t *pBuffer, uint32_t nSize): m_pBuffer(pBuffer), m_nSize(nSize) { DEBUG_ENTRY @@ -77,7 +76,7 @@ bool TFTPFileServer::FileCreate(const char* pFileName, tftp::Mode mode) { return false; } - if (strncmp(FILE_NAME, pFileName, FILE_NAME_LENGTH) != 0) { + if (strncmp(firmware::FILE_NAME, pFileName, firmware::FILE_NAME_LENGTH) != 0) { DEBUG_EXIT return false; }