Adds the sdi_toolBox library (temporary version)

This commit is contained in:
Sylvain Schneider
2026-03-12 16:31:18 +01:00
parent 0601326e2b
commit b5d0fef4d9
23 changed files with 28422 additions and 0 deletions

View File

@@ -0,0 +1,430 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <curl/curl.h>
#include <filesystem>
#include <memory>
#include <mutex>
#include <ostream>
#include <sdi_toolBox/logs/reLog.h>
#include <string>
#include <unordered_map>
namespace sdi_ToolBox::communication
{
//--------------------------------------------------------------
class CurlGlobalInitializer
{
public:
static void init() // Initialize libcurl globally
{
static auto instance = std::unique_ptr<CurlGlobalInitializer>(new CurlGlobalInitializer());
}
public:
virtual ~CurlGlobalInitializer() // Default destructor
{
curl_global_cleanup();
}
CurlGlobalInitializer(const CurlGlobalInitializer &obj) = delete; // Copy constructor
CurlGlobalInitializer(CurlGlobalInitializer &&obj) noexcept = delete; // Move constructor
CurlGlobalInitializer &operator=(const CurlGlobalInitializer &obj) = delete; // Copy assignment operator
CurlGlobalInitializer &operator=(CurlGlobalInitializer &&obj) noexcept = delete; // Move assignment operator
protected:
CurlGlobalInitializer() // Default constructor
{
if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK)
throw std::runtime_error("ERROR: Failed to initialize cURL");
}
};
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
class CurlHttpClient
{
public:
enum class Method : uint8_t
{
http_GET,
http_POST,
http_PUT,
http_DELETE,
};
struct Settings
{
bool enableSSL = true;
bool ssl_verifyPeer = true;
bool ssl_verifyHost = true;
std::filesystem::path ssl_caInfo;
std::string ssl_certBlob;
std::chrono::seconds timeout{ 10 };
bool operator==(const Settings &other) const
{
// Compare all fields against the other object
return (enableSSL == other.enableSSL &&
ssl_verifyPeer == other.ssl_verifyPeer &&
ssl_verifyHost == other.ssl_verifyHost &&
ssl_caInfo == other.ssl_caInfo &&
ssl_certBlob == other.ssl_certBlob);
}
};
using Headers = std::unordered_map<std::string, std::string>;
struct Request
{
std::string url;
Method method = Method::http_GET;
std::string body;
Headers headers;
Settings settings;
bool operator==(const Request &other) const
{
// Compare all fields against the other object
return (url == other.url &&
method == other.method &&
body == other.body &&
headers == other.headers &&
settings == other.settings);
}
};
struct Response
{
int returnCode = -1;
std::string content;
};
public:
CurlHttpClient(); // Default constructor
virtual ~CurlHttpClient() = default; // Default destructor
CurlHttpClient(const CurlHttpClient &obj) = delete; // Copy constructor
CurlHttpClient(CurlHttpClient &&obj) noexcept = delete; // Move constructor
CurlHttpClient &operator=(const CurlHttpClient &obj) = delete; // Copy assignment operator
CurlHttpClient &operator=(CurlHttpClient &&obj) noexcept = delete; // Move assignment operator
const Response &performRequest(const Request &request = {}); // Perform HTTP request
const Response &performRequest_nothrow(const Request &request = {}); // Perform HTTP request
const Request &getRequest() const; // Retrieves curl request
const Response &getResponse() const; // Retrieves curl response
protected:
mutable std::mutex m_mtx;
CURL *m_curl = nullptr;
curl_slist *m_headerList = nullptr;
Request m_request;
Response m_response;
private:
CURLcode setRequestOptions(); // Set cURL request options
static size_t write_callback(void *contents, // Callback for writing received data
size_t size,
size_t itemCount,
void *userData);
};
//--------------------------------------------------------------
/* Default constructor */
inline CurlHttpClient::CurlHttpClient()
{
CurlGlobalInitializer::init();
}
//--------------------------------------------------------------
/* Perform HTTP request */
inline const CurlHttpClient::Response &CurlHttpClient::performRequest(const Request &request)
{
std::lock_guard lock(m_mtx);
if (const Request default_req = {}; request != default_req)
m_request = request;
try
{
m_curl = curl_easy_init();
if (!m_curl)
throw std::runtime_error("unable to initialize curl library");
// Set request options
if (const auto res = setRequestOptions(); res != CURLE_OK)
throw std::runtime_error(std::format("ERROR: Failed to set up CURL options: {}", curl_easy_strerror(res)));
// Perform request
if (const auto res = curl_easy_perform(m_curl); res != CURLE_OK)
throw std::runtime_error(std::format("ERROR: curl_easy_perform() failed: {}", curl_easy_strerror(res)));
if (const auto res = curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &m_response.returnCode); res != CURLE_OK)
throw std::runtime_error(std::format("ERROR: Failed to get content code: {}", curl_easy_strerror(res)));
}
catch (const std::exception &e)
{
m_response.returnCode = -1;
m_response.content = e.what();
}
// Free resources
if (m_curl)
{
curl_easy_cleanup(m_curl); // free the easy curl handle
m_curl = nullptr;
}
if (m_headerList)
{
curl_slist_free_all(m_headerList); // free the custom headers
m_curl = nullptr;
}
if (m_response.returnCode == -1)
throw std::runtime_error(m_response.content);
return m_response;
}
//--------------------------------------------------------------
/* Perform HTTP request */
inline const CurlHttpClient::Response &CurlHttpClient::performRequest_nothrow(const Request &request)
{
try
{
performRequest(request);
}
catch (const std::exception &e)
{
LogError() << e.what() << std::endl;
}
return m_response;
}
//--------------------------------------------------------------
/* Retrieves curl request */
inline const CurlHttpClient::Request &CurlHttpClient::getRequest() const
{
std::lock_guard lock(m_mtx);
return m_request;
}
//--------------------------------------------------------------
/* Retrieves curl response */
inline const CurlHttpClient::Response &CurlHttpClient::getResponse() const
{
std::lock_guard lock(m_mtx);
return m_response;
}
//--------------------------------------------------------------
/* Set cURL request options */
inline CURLcode CurlHttpClient::setRequestOptions()
{
// --- Common Options ---
// Set the target URL
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_URL, m_request.url.c_str()); res != CURLE_OK)
return res;
// Set the write callback function
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, write_callback); res != CURLE_OK)
return res;
// Set the user data pointer for the callback (our content buffer)
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &m_response.content); res != CURLE_OK)
return res;
// Set custom headers if any
if (m_headerList)
{
// Prepare header list
for (const auto &[key, value] : m_request.headers)
{
const auto header = format("{0:}: {1:}", key, value);
m_headerList = curl_slist_append(m_headerList, header.c_str());
}
// Append headers to the request
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_headerList); res != CURLE_OK)
return res;
}
// Set the max time in seconds for the entire operation (including connection and transfer)
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, m_request.settings.timeout.count()); res != CURLE_OK)
return res;
// --- SSL/TLS Options ---
// curl documentation recommends setting both for full control
if (m_request.settings.enableSSL)
{
// Enable peer certificate verification
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L); res != CURLE_OK)
return res;
// Enable host name verification (prevents 'Man-in-the-middle' attacks)
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 2L); res != CURLE_OK)
return res;
if (!m_request.settings.ssl_caInfo.empty())
{
// Optionally, set the path to a CA bundle file (often not needed
// if curl is built with a system-wide CA store)
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_CAINFO, m_request.settings.ssl_caInfo.c_str()); res != CURLE_OK)
return res;
}
else if (!m_request.settings.ssl_certBlob.empty())
{
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSLKEY_BLOB, &m_request.settings.ssl_certBlob); res != CURLE_OK)
return res;
}
}
else
{
// Disable verification (USE WITH CAUTION: Insecure!)
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); res != CURLE_OK)
return res;
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 0L); res != CURLE_OK)
return res;
}
// --- Method-Specific Options ---
switch (m_request.method)
{
using enum Method;
case http_GET:
{
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_HTTPGET, 1L); res != CURLE_OK)
return res;
break;
}
case http_POST:
{
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_POST, 1L); res != CURLE_OK)
return res;
// Set the data to be sent
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_request.body.c_str()); res != CURLE_OK)
return res;
break;
}
case http_PUT:
{
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_PUT, 1L); res != CURLE_OK)
return res;
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, "PUT"); res != CURLE_OK)
return res;
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_request.body.c_str()); res != CURLE_OK)
return res;
break;
}
case http_DELETE:
{
if (const auto res = curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, "DELETE"); res != CURLE_OK)
return res;
break;
}
}
return CURLE_OK;
}
//--------------------------------------------------------------
/* Callback for writing received data */
inline size_t CurlHttpClient::write_callback(void *contents, const size_t size, const size_t itemCount, void *userData)
{
// Total size of the incoming buffer
const auto realSize = size * itemCount;
// Append the received data to the content string
const auto &response = static_cast<std::string *>(userData);
response->append(static_cast<char *>(contents), realSize);
return realSize;
}
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
class CurlInfo
{
public:
CurlInfo(); // Default constructor
virtual ~CurlInfo() = default; // Default destructor
CurlInfo(const CurlInfo &obj) = delete; // Copy constructor
CurlInfo(CurlInfo &&obj) noexcept = delete; // Move constructor
CurlInfo &operator=(const CurlInfo &obj) = delete; // Copy assignment operator
CurlInfo &operator=(CurlInfo &&obj) noexcept = delete; // Move assignment operator
void toStream(std::ostream &stream); // Print cURL information to stream object
void print(); // Print cURL information to cout
std::string version;
uint32_t features;
bool ssl_support = false;
std::string ssl_version;
std::vector<std::string> availableProtocols;
};
//--------------------------------------------------------------
/* Default constructor */
inline CurlInfo::CurlInfo()
{
CurlGlobalInitializer::init();
// Retrieves information from the library
const auto info = curl_version_info(CURLVERSION_NOW);
version = info->version;
features = info->features;
ssl_support = info->features & CURL_VERSION_SSL;
ssl_version = info->ssl_version;
if (info->protocols)
{
for (const char *const *proto = info->protocols; *proto; ++proto)
availableProtocols.emplace_back(*proto);
}
}
//--------------------------------------------------------------
/* Print cURL information to stream object */
inline void CurlInfo::toStream(std::ostream &stream)
{
stream << "--- libcurl Version Info ---" << "\n";
stream << "Version : " << version << "\n";
stream << "Features (bitmask) : " << std::format("0x{0:X}", features) << "\n";
stream << "SSL/TLS support : " << ((ssl_support) ? std::format("**Yes** [{0:}]", ssl_version) : "No") << "\n";
if (!availableProtocols.empty())
{
stream << "Available protocols: ";
bool first = true;
for (const auto protocol : availableProtocols)
{
if (!first)
stream << ", ";
stream << protocol;
first = false;
}
stream << "\n";
}
}
//--------------------------------------------------------------
/* Print cURL information to cout */
inline void CurlInfo::print()
{
toStream(std::cout);
std::cout << std::flush;
}
//--------------------------------------------------------------
} // namespace sdi_ToolBox::communication

View File

@@ -0,0 +1,6 @@
#pragma once
/**
* @namespace sdi_ToolBox::comm
* @brief Communication protocols and binary handling.
*/

View File

@@ -0,0 +1,784 @@
//{{copyright}}
//{{version}}
//{{license}}
#pragma once
#include "../generic/crc.h"
#include <array>
#include <cstring>
#include <span>
#include <string>
/**
* @namespace sdi_ToolBox::comm::uBinaryFrame
* @brief Utilities for robust binary frame serialization, deserialization, and protocol handling.
*
* This namespace provides classes and constants to manage binary communication frames with automatic
* escaping, CRC calculation, and protocol validation. The supported frame format is:
* [SOF1][SOF2][SIZE][PAYLOAD][CRC16][EOF]
* where special bytes are escaped as needed.
*
* Main components:
* - @ref FrameReceiver : Receives and extracts a complete binary frame from a byte stream.
* - @ref MessageParser : Parses and validates a binary frame, providing typed access to the payload.
* - @ref MessageBuilder : Builds a binary message with CRC calculation and escaping.
* - @ref FramePacker : Packs a message into a complete frame ready for transmission (header, CRC, escaping, etc.).
*/
namespace sdi_ToolBox::comm::uBinaryFrame
{
constexpr uint8_t SOF1_ = 0xAA; // Start of Frame byte (0b10101010 - useful for bus synchronisation)
constexpr uint8_t SOF2_ = 0x55; // Start of Frame byte (0b01010101 - useful for bus synchronisation)
constexpr uint8_t EOF_ = 0x03; // End of Frame byte (End of Text code in ASCII table)
constexpr uint8_t ESC_ = 0x1B; // Escape byte (Escape code in ASCII table)
//--------------------------------------------------------------
/**
* @class FrameReceiver
* @brief Stateful receiver for extracting a complete binary frame from a byte stream.
*
* Handles frame synchronization, byte un-escaping, and buffering.
* The frame is considered complete when the protocol's end-of-frame byte is detected.
*
* @tparam BUFFER_SIZE Maximum size of the internal receive buffer.
*
* Usage:
* - Call push(byte) for each received byte.
* - When push() returns true or isFrameAvailable() is true, use getFrame() to access the frame data.
*
* @code
* FrameReceiver<128> receiver;
* for (uint8_t byte : incomingBytes) {
* if (receiver.push(byte)) {
* auto frame = receiver.getFrame();
* // Process frame...
*
* receiver.reset();
* }
* }
* @endcode
*/
template<size_t BUFFER_SIZE>
class FrameReceiver
{
enum class State : uint8_t
{
WAIT_SOF1,
WAIT_SOF2,
RECEIVE_DATA,
WAIT_EOF,
ESCAPE_NEXT,
END,
};
public:
FrameReceiver() = default; // Default constructor
~FrameReceiver() = default; // Default destructor
FrameReceiver(const FrameReceiver &obj) = default; // Copy constructor
FrameReceiver(FrameReceiver &&obj) noexcept = default; // Move constructor
FrameReceiver &operator=(const FrameReceiver &obj) = default; // Copy assignment operator
FrameReceiver &operator=(FrameReceiver &&obj) noexcept = default; // Move assignment operator
/**
* @brief Resets the receiver state and clears the internal buffer.
*
* After calling this method, the receiver is ready to process a new frame.
*/
void reset();
/**
* @brief Pushes a byte into the receiver buffer and updates the state machine.
* @param byte The received byte to process.
* @return true if a complete frame has been received and is available, false otherwise.
*
* @note If a frame is available, use getFrame() to access its content.
*/
[[nodiscard]] bool push(uint8_t byte);
/**
* @brief Checks if a complete frame is available in the buffer.
* @return true if a frame is available, false otherwise.
*/
[[nodiscard]] bool isFrameAvailable() const;
/**
* @brief Returns a span to the complete frame data.
* @return A span containing the frame bytes, or an empty span if no frame is available.
*
* @note The returned span is only valid until the next reset() or push().
*/
[[nodiscard]] std::span<const uint8_t> getFrame() const;
protected:
std::array<uint8_t, BUFFER_SIZE> m_buffer{}; // Receiver buffer
size_t m_index = 0; // Current index in the buffer
State m_state = State::WAIT_SOF1; // Current state of the receiver
private:
void appendByte(uint8_t byte); // Append a byte to the buffer
};
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline void FrameReceiver<BUFFER_SIZE>::reset()
{
m_index = 0;
m_state = State::WAIT_SOF1;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline bool FrameReceiver<BUFFER_SIZE>::push(const uint8_t byte)
{
switch (m_state)
{
// Initial state: waiting for SOF1
case State::WAIT_SOF1:
{
if (byte == SOF1_)
{
m_index = 0;
m_state = State::WAIT_SOF2; // Next byte should be SOF2
}
break;
}
// Waiting for SOF2
case State::WAIT_SOF2:
{
if (byte == SOF2_)
m_state = State::RECEIVE_DATA; // Start receiving data
else
m_state = State::WAIT_SOF1; // Abort frame reception
break;
}
// Receiving data
case State::RECEIVE_DATA:
{
if (byte == ESC_)
m_state = State::ESCAPE_NEXT; // Next byte is escaped
else if (byte == SOF1_)
m_state = State::WAIT_SOF2; // Restart frame reception
else if (byte == SOF2_)
m_state = State::WAIT_SOF1; // Abort frame reception
else if (byte == EOF_)
{
m_state = State::END; // Frame complete
return true;
}
else
appendByte(byte);
break;
}
// Handling escaped byte
case State::ESCAPE_NEXT:
{
appendByte(byte);
m_state = State::RECEIVE_DATA; // Return to data reception
break;
}
// Frame complete
case State::END:
default:
return true;
}
return false;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline bool FrameReceiver<BUFFER_SIZE>::isFrameAvailable() const
{
return m_state == State::END;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline std::span<const uint8_t> FrameReceiver<BUFFER_SIZE>::getFrame() const
{
if (m_state == State::END)
return { m_buffer.data(), m_index };
return {};
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline void FrameReceiver<BUFFER_SIZE>::appendByte(const uint8_t byte)
{
if (m_index < BUFFER_SIZE)
m_buffer[m_index++] = byte;
}
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
/**
* @class MessageParser
* @brief Parses and validates a binary frame, providing typed access to the payload.
*
* Checks frame structure, size, CRC, and end-of-frame marker.
* Allows extraction of typed values or raw data from the payload.
*
* Usage:
* - Construct with a frame buffer.
* - Check validity with isValid() or getStatus().
* - Use get<T>(offset, value) to extract typed data from the payload.
*
* @code
* MessageParser parser(frame);
* if (parser.isValid()) {
* uint16_t id;
* parser.get(0, id);
* std::array<uint8_t, 8> data;
* parser.get(2, std::span<uint8_t>(data));
* // Use id and data...
* }
* @endcode
*/
class MessageParser
{
public:
enum class MessageStatus : uint8_t
{
VALID,
INVALID,
BAD_SIZE,
BAD_CRC,
};
public:
MessageParser() = delete; // Default constructor
~MessageParser() = default; // Default destructor
MessageParser(const MessageParser &obj) = default; // Copy constructor
MessageParser(MessageParser &&obj) noexcept = default; // Move constructor
MessageParser &operator=(const MessageParser &obj) = default; // Copy assignment operator
MessageParser &operator=(MessageParser &&obj) noexcept = default; // Move assignment operator
/**
* @brief Constructs a MessageParser and checks the integrity of the provided frame.
* @param buffer The buffer containing the frame to parse.
*/
explicit MessageParser(std::span<const uint8_t> buffer);
/**
* @brief Checks if the parsed message is valid.
* @return true if the message is valid, false otherwise.
*/
[[nodiscard]] bool isValid() const;
/**
* @brief Gets the current message validity status.
* @return The status as a MessageStatus enum value.
*/
[[nodiscard]] MessageStatus getStatus() const;
/**
* @brief Gets the message payload buffer.
* @return A span containing the payload bytes.
*/
[[nodiscard]] std::span<const uint8_t> getBuffer() const;
/**
* @brief Gets the size of the message payload.
* @return The size of the payload in bytes.
*/
[[nodiscard]] size_t size() const; // Get message payload size
/**
* @brief Extracts a value of type T from the message payload at the specified offset.
* @tparam T The type of the value to extract (POD type or std::span<uint8_t>).
* @param offset The offset in the payload to read from.
* @param value Reference to store the extracted value.
* @return The new offset after reading the value, or the original offset if reading failed.
*
* @note No exception is thrown. If extraction fails, value is not modified.
*/
template<class T>
[[nodiscard]] size_t get(size_t offset, T &value) const;
protected:
std::span<const uint8_t> m_buffer; // Message buffer
MessageStatus m_messageStatus = MessageStatus::INVALID; // Message validity status
private:
void messageCheck(); // Check message integrity
};
//--------------------------------------------------------------
/* Constructor */
inline MessageParser::MessageParser(const std::span<const uint8_t> buffer)
{
m_buffer = buffer;
messageCheck();
}
//--------------------------------------------------------------
inline bool MessageParser::isValid() const
{
return m_messageStatus == MessageStatus::VALID;
}
//--------------------------------------------------------------
/* Get message validity status */
inline MessageParser::MessageStatus MessageParser::getStatus() const
{
return m_messageStatus;
}
//--------------------------------------------------------------
/* Get message payload buffer */
inline std::span<const uint8_t> MessageParser::getBuffer() const
{
return m_buffer;
}
//--------------------------------------------------------------
/* Get message payload size */
inline size_t MessageParser::size() const
{
return m_buffer.size();
}
//--------------------------------------------------------------
template<class T>
inline size_t MessageParser::get(size_t offset, T &value) const
{
static_assert((std::is_standard_layout_v<T> && std::is_trivial_v<T>) || // POD type
std::is_same_v<T, std::span<uint8_t>> || // std::span<uint8_t> type
std::is_same_v<T, std::string>); // std::string type
if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
{
// POD type
constexpr size_t typeSize = sizeof(T);
if (offset + typeSize <= m_buffer.size())
{
std::memcpy(&value, m_buffer.data() + offset, typeSize);
return offset + typeSize; // Return new offset
}
}
else if constexpr (std::is_same_v<T, std::span<uint8_t>>)
{
// std::span<uint8_t> type
if (offset + value.size() <= m_buffer.size())
{
std::memcpy(value.data(), m_buffer.data() + offset, value.size());
return offset + value.size(); // Return new offset
}
}
else if constexpr (std::is_same_v<T, std::string>)
{
// std::string type
uint8_t strSize;
offset = get(offset, strSize);
value.resize(strSize);
std::span<uint8_t> strSpan(reinterpret_cast<uint8_t *>(value.data()), strSize);
offset = get(offset, strSpan);
}
return offset; // Unable to read value, return original offset
}
//--------------------------------------------------------------
/* Check message integrity */
inline void MessageParser::messageCheck()
{
/* Message format : [SOF1(1)][SOF2(1)][SIZE(1)][PAYLOAD(n)][CRC16(2)][EOF(1)] */
// Check SIZE
const auto size = m_buffer[0];
if (constexpr size_t CRCSize = 2; m_buffer.size() != CRCSize + size + sizeof(size))
{
m_messageStatus = MessageStatus::BAD_SIZE;
return;
}
// Check CRC16
const auto loCRC = *(m_buffer.rbegin() + 1);
const auto hiCRC = *m_buffer.rbegin();
const uint16_t frameCRC = static_cast<uint16_t>(hiCRC << 8) | static_cast<uint16_t>(loCRC);
const uint16_t computedCRC = generic::CRC16_modbus::computeCRC(m_buffer.subspan(1, m_buffer.size() - 3));
if (frameCRC != computedCRC)
{
m_messageStatus = MessageStatus::BAD_CRC;
return;
}
m_buffer = m_buffer.subspan(1, size);
m_messageStatus = MessageStatus::VALID;
}
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
/**
* @class MessageBuilderHelper
* @brief Utility class to simplify the usage of MessageBuilder templates.
*
* This helper provides convenient methods and type aliases to work with MessageBuilder
* instances without exposing template parameters in function signatures.
*/
class MessageBuilderHelper
{
public:
MessageBuilderHelper() = default; // Default constructor
virtual ~MessageBuilderHelper() = default; // Default destructor
MessageBuilderHelper(const MessageBuilderHelper &obj) = default; // Copy constructor
MessageBuilderHelper(MessageBuilderHelper &&obj) noexcept = default; // Move constructor
MessageBuilderHelper &operator=(const MessageBuilderHelper &obj) = default; // Copy assignment operator
MessageBuilderHelper &operator=(MessageBuilderHelper &&obj) noexcept = default; // Move assignment operator
/**
* @brief Gets the current message buffer as a span.
* @return A span containing the current message bytes.
*/
[[nodiscard]] virtual std::span<const uint8_t> getBuffer() const = 0;
/**
* @brief Checks if the message buffer is full.
* @return true if the buffer is full, false otherwise.
*/
[[nodiscard]] virtual bool isFull() const = 0;
/**
* @brief Gets the current size of the message buffer.
* @return The number of bytes currently in the message buffer.
*/
[[nodiscard]] virtual size_t getBufferSize() const = 0;
/**
* @brief Gets the current CRC16 value of the message.
* @return The current CRC16 value.
*/
[[nodiscard]] virtual uint16_t getCRC() const = 0;
};
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
/**
* @class MessageBuilder
* @brief Builds a binary message payload with CRC calculation and escaping support.
*
* Supports appending POD types or byte spans, and computes CRC incrementally.
* Provides methods to reset, append, and retrieve the current buffer and CRC.
*
* @tparam BUFFER_SIZE Maximum size of the internal message buffer.
*
* Usage:
* - Use append() or operator<< to add data.
* - Retrieve the buffer with getBuffer() and CRC with getCRC().
*
* @code
* MessageBuilder<64> builder;
* builder << uint16_t(0x1234) << std::span<const uint8_t>(data, dataLen);
* auto buffer = builder.getBuffer();
* auto crc = builder.getCRC();
* @endcode
*/
template<size_t BUFFER_SIZE>
class MessageBuilder : public MessageBuilderHelper
{
public:
MessageBuilder(); // Default constructor
virtual ~MessageBuilder() = default; // Default destructor
MessageBuilder(const MessageBuilder &obj) = default; // Copy constructor
MessageBuilder(MessageBuilder &&obj) noexcept = default; // Move constructor
MessageBuilder &operator=(const MessageBuilder &obj) = default; // Copy assignment operator
MessageBuilder &operator=(MessageBuilder &&obj) noexcept = default; // Move assignment operator
/**
* @brief Resets the message builder to an empty state.
* @return Reference to the MessageBuilder for chaining.
*/
MessageBuilder &reset(); // Reset the message builder
/**
* @brief Appends a value of type T to the message buffer.
* @tparam T The type of the value to append (POD type or std::span<uint8_t>).
* @param value The value to append.
* @return Reference to the MessageBuilder for chaining.
*
* @note If appending the value would exceed the buffer size, it is ignored.
*/
template<class T>
MessageBuilder &append(const T &value); // Append value of type T to the message
/**
* @brief Overloads the << operator to append a value of type T to the message buffer.
* @tparam T The type of the value to append (POD type or std::span<uint8_t>).
* @param value The value to append.
* @return Reference to the MessageBuilder for chaining.
*
* @note If appending the value would exceed the buffer size, it is ignored.
*/
template<class T>
MessageBuilder &operator<<(const T &value);
[[nodiscard]] std::span<const uint8_t> getBuffer() const override;
[[nodiscard]] bool isFull() const override;
[[nodiscard]] size_t getBufferSize() const override;
[[nodiscard]] uint16_t getCRC() const override;
protected:
std::array<uint8_t, BUFFER_SIZE> m_buffer{}; // Receiver buffer
size_t m_index = 0; // Current index in the buffer
generic::CRC16_modbus m_CRC; // Current CRC value
};
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline MessageBuilder<BUFFER_SIZE>::MessageBuilder()
{
m_CRC.init();
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::reset()
{
m_index = 0;
m_CRC.init();
return *this; // Return reference to self for chaining
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
template<class T>
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::append(const T &value)
{
static_assert((std::is_standard_layout_v<T> && std::is_trivial_v<T>) || // POD type
std::is_same_v<T, std::span<uint8_t>> || // std::span<uint8_t> type
std::is_same_v<T, std::span<const uint8_t>> || // std::span<const uint8_t> type
std::is_same_v<T, std::string> || // std::string type
std::is_same_v<T, const std::string>); // const std::string type
if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
{
// POD type
constexpr size_t typeSize = sizeof(T);
if (m_index + typeSize <= BUFFER_SIZE)
{
std::array<uint8_t, typeSize> dataBuffer;
std::memcpy(dataBuffer.data(), &value, typeSize);
std::memcpy(m_buffer.data() + m_index, dataBuffer.data(), dataBuffer.size());
m_CRC.update(dataBuffer);
m_index += typeSize;
}
}
else if constexpr (std::is_same_v<T, std::span<uint8_t>> ||
std::is_same_v<T, std::span<const uint8_t>>)
{
// std::span<uint8_t> type
if (m_index + value.size() <= BUFFER_SIZE)
{
std::memcpy(m_buffer.data() + m_index, value.data(), value.size());
m_CRC.update(value);
m_index += value.size();
}
}
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, const std::string>)
{
// std::string type
if (m_index + (value.size() + sizeof(uint8_t)) <= BUFFER_SIZE)
{
append(static_cast<uint8_t>(value.size()));
std::memcpy(m_buffer.data() + m_index, value.data(), value.size());
m_CRC.update(std::span(reinterpret_cast<const uint8_t *>(value.data()), value.size()));
m_index += value.size();
}
}
return *this; // Return reference to self for chaining
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
template<class T>
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::operator<<(const T &value)
{
append(value);
return *this; // Return reference to self for chaining
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline std::span<const uint8_t> MessageBuilder<BUFFER_SIZE>::getBuffer() const
{
return { m_buffer.data(), m_index };
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline bool MessageBuilder<BUFFER_SIZE>::isFull() const
{
return m_index >= BUFFER_SIZE;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline size_t MessageBuilder<BUFFER_SIZE>::getBufferSize() const
{
return m_index;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline uint16_t MessageBuilder<BUFFER_SIZE>::getCRC() const
{
return m_CRC.getCRC();
}
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
/**
* @class FramePacker
* @brief Packs a message payload into a complete binary frame ready for transmission.
*
* Adds protocol headers, escapes special bytes, appends CRC and end-of-frame marker.
* Ensures the packed frame fits in the provided buffer size.
*
* @tparam BUFFER_SIZE Maximum size of the output frame buffer.
*
* Usage:
* - Call packMessage(payload, crc) to build the frame.
* - Use isValid() to check for overflow.
* - Retrieve the packed frame with getPackedFrame().
*
* @code
* FramePacker<256> packer;
* if (packer.packMessage(payload, crc)) {
* auto frame = packer.getPackedFrame();
* // Send frame...
* }
* @endcode
*/
template<size_t BUFFER_SIZE>
class FramePacker
{
static constexpr size_t CRC_SIZE = 4; // CRC16(2 x2) --> (CRC is 4 bytes to support escaped CRC)
public:
FramePacker() = default; // Default constructor
explicit FramePacker(std::span<const uint8_t> message, uint16_t crc); // Constructor
~FramePacker() = default; // Default destructor
FramePacker(const FramePacker &obj) = default; // Copy constructor
FramePacker(FramePacker &&obj) noexcept = default; // Move constructor
FramePacker &operator=(const FramePacker &obj) = default; // Copy assignment operator
FramePacker &operator=(FramePacker &&obj) noexcept = default; // Move assignment operator
/**
* @brief Packs the input message into a complete frame with headers, CRC, and escaping.
* @param message The message payload to pack.
* @param crc The CRC16 value to append to the frame.
* @return true if packing was successful without overflow, false otherwise.
*/
bool packMessage(std::span<const uint8_t> message, uint16_t crc);
/**
* @brief Checks if the packed frame is valid (no overflow occurred).
* @return true if the packed frame is valid, false otherwise.
*/
[[nodiscard]] bool isValid() const;
/**
* @brief Gets the packed frame buffer as a span.
* @return A span containing the packed frame bytes.
*/
[[nodiscard]] std::span<const uint8_t> getPackedFrame() const;
protected:
std::array<uint8_t, BUFFER_SIZE> m_frame{}; // Output buffer
size_t m_index = 0; // Current index in output buffer
std::array<uint8_t, CRC_SIZE> m_CRCBuffer{}; // CRC buffer with escaping
size_t m_CRCSize = 0; // Current size of CRC buffer with escaping
bool m_overflow = false; // Flag indicating if overflow occurred during packing
private:
void appendByte(uint8_t byte); // Append byte with escaping to output buffer
void appendRawByte(uint8_t byte); // Append raw byte to output buffer
void packCRC(uint16_t crc); // Pack CRC into CRC buffer with escaping
};
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline FramePacker<BUFFER_SIZE>::FramePacker(const std::span<const uint8_t> message, const uint16_t crc)
{
packMessage(message, crc);
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline bool FramePacker<BUFFER_SIZE>::packMessage(const std::span<const uint8_t> message, const uint16_t crc)
{
// Reset state
m_index = 0;
m_overflow = false;
// Prepare CRC buffer with escaping
packCRC(crc);
// Append header
appendRawByte(SOF1_); // Append SOF1
appendRawByte(SOF2_); // Append SOF2
appendByte(static_cast<uint8_t>(message.size())); // Append payload size
// Append payload
for (const auto &byte : message)
appendByte(byte);
// Append CRC
for (size_t i = 0; i < m_CRCSize; i++)
appendRawByte(m_CRCBuffer[i]);
// Append EOF
appendRawByte(EOF_);
return !m_overflow; // Return true if packing was successful
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline bool FramePacker<BUFFER_SIZE>::isValid() const
{
return !m_overflow;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline std::span<const uint8_t> FramePacker<BUFFER_SIZE>::getPackedFrame() const
{
return std::span<const uint8_t>(m_frame.data(), m_index);
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline void FramePacker<BUFFER_SIZE>::appendByte(const uint8_t byte)
{
if (byte == SOF1_ || byte == SOF2_ || byte == EOF_ || byte == ESC_)
{
appendRawByte(ESC_); // Append escape byte
}
appendRawByte(byte); // Append actual byte
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline void FramePacker<BUFFER_SIZE>::appendRawByte(const uint8_t byte)
{
if (m_index < BUFFER_SIZE)
m_frame[m_index++] = byte;
else
m_overflow = true;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline void FramePacker<BUFFER_SIZE>::packCRC(const uint16_t crc)
{
m_CRCSize = 0;
const uint8_t loCRC = static_cast<uint8_t>(crc & 0x00FF);
const uint8_t hiCRC = static_cast<uint8_t>((crc >> 8) & 0x00FF);
if (loCRC == SOF1_ || loCRC == SOF2_ || loCRC == EOF_ || loCRC == ESC_)
m_CRCBuffer[m_CRCSize++] = ESC_;
m_CRCBuffer[m_CRCSize++] = loCRC;
if (hiCRC == SOF1_ || hiCRC == SOF2_ || hiCRC == EOF_ || hiCRC == ESC_)
m_CRCBuffer[m_CRCSize++] = ESC_;
m_CRCBuffer[m_CRCSize++] = hiCRC;
}
//--------------------------------------------------------------
} // namespace sdi_ToolBox::comm::uBinaryFrame

View File

@@ -0,0 +1,84 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <cstdint>
#include <string>
namespace sdi_toolBox::console::ANSI
{
//--------------------------------------------------------------
class EscapeCommand
{
public:
enum class Color : uint8_t
{
Black = 0,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
};
static constexpr uint8_t LIGHT = 0x01 << 0; // 0x01
static constexpr uint8_t BRIGHT = 0x01 << 1; // 0x02
static constexpr uint8_t DIM = 0x01 << 2; // 0x04
static constexpr uint8_t UNDERLINE = 0x01 << 3; // 0x08
static constexpr uint8_t BLINK = 0x01 << 4; // 0x10
static constexpr uint8_t REVERSE = 0x01 << 5; // 0x20
public:
static std::string clear();
static std::string get(const Color foregroundColor,
const uint8_t foregroundStyle = 0,
const Color backgroundColor = Color::Black,
const uint8_t backgroundStyle = 0);
};
//--------------------------------------------------------------
inline std::string EscapeCommand::clear()
{
return "\x1b[0m";
}
//--------------------------------------------------------------
inline std::string EscapeCommand::get(const Color foregroundColor, const uint8_t foregroundStyle, const Color backgroundColor, const uint8_t backgroundStyle)
{
std::string command = "\x1b[";
int foregroundColorValue = static_cast<int>(foregroundColor) + 30;
if (foregroundStyle & LIGHT)
foregroundColorValue += 60;
command += std::to_string(foregroundColorValue) + ";";
int backgroundColorValue = static_cast<int>(backgroundColor) + 40;
if (backgroundStyle & LIGHT)
backgroundColorValue += 60;
command += std::to_string(backgroundColorValue);
if (foregroundStyle & BRIGHT)
command += ";1";
if (foregroundStyle & DIM)
command += ";2";
if (foregroundStyle & UNDERLINE)
command += ";4";
if (foregroundStyle & BLINK)
command += ";5";
if (foregroundStyle & REVERSE)
command += ";7";
command += "m";
return command;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::console::ANSI

View File

@@ -0,0 +1,152 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <string>
#include <vector>
namespace sdi_toolBox::console
{
//--------------------------------------------------------------
class ConsoleTable
{
public:
using Row = std::vector<std::string>;
using ColumnWidths = std::vector<size_t>;
public:
ConsoleTable() = default; // Constructor
virtual ~ConsoleTable() = default; // Destructor
ConsoleTable(const ConsoleTable &other) = delete; // Copy constructor
ConsoleTable(ConsoleTable &&other) noexcept = delete; // Move constructor
ConsoleTable &operator=(const ConsoleTable &other) = delete; // Copy assignment
ConsoleTable &operator=(ConsoleTable &&other) noexcept = delete; // Move assignment
void setHeaders(const Row &headers); // Set table headers
void addRow(const Row &row); // Add a row to the table
[[nodiscard]] std::string render() const; // Render the table as a string
protected:
Row m_headers; // Table headers
std::vector<Row> m_rows; // Table rows
size_t m_padding = 2; // Padding between columns
private:
[[nodiscard]] ColumnWidths calculateColumnWidths() const; // Calculate column widths
[[nodiscard]] static std::string drawSeparator(const ColumnWidths &columnWidths); // Draw a separator line (+---+---+---+)
[[nodiscard]] static std::string drawRow(const Row &row, // Draw a single row
const ColumnWidths &columnWidths);
};
//--------------------------------------------------------------
/* Set table headers */
inline void ConsoleTable::setHeaders(const Row &headers)
{
m_headers = headers;
}
//--------------------------------------------------------------
/* Add a row to the table */
inline void ConsoleTable::addRow(const Row &row)
{
m_rows.push_back(row);
}
//--------------------------------------------------------------
/* Render the table as a string */
inline std::string ConsoleTable::render() const
{
// Calculate column widths
const auto columnWidths = calculateColumnWidths();
// Initialize the output string
std::string output;
// Draw the top separator
output += drawSeparator(columnWidths);
// Draw the headers
output += drawRow(m_headers, columnWidths);
// Draw the separator between headers and data
output += drawSeparator(columnWidths);
// Draw the data rows
for (const auto &row : m_rows)
{
output += drawRow(row, columnWidths);
}
// Draw the bottom separator
output += drawSeparator(columnWidths);
return output;
}
//--------------------------------------------------------------
/* Calculate column widths */
inline ConsoleTable::ColumnWidths ConsoleTable::calculateColumnWidths() const
{
// Determine the number of columns (based on headers or first row)
const auto numColumns = m_headers.empty() ? (m_rows.empty() ? 0 : m_rows[0].size()) : m_headers.size();
// Initialize column widths
ColumnWidths widths(numColumns, 0);
// Calculate widths based on headers
for (size_t i = 0; i < m_headers.size() && i < numColumns; ++i)
{
widths[i] = std::max(widths[i], m_headers[i].length() + m_padding);
}
// Calculate widths based on rows
for (const auto &row : m_rows)
{
for (size_t i = 0; i < row.size() && i < numColumns; ++i)
{
widths[i] = std::max(widths[i], row[i].length() + m_padding);
}
}
return widths;
}
//--------------------------------------------------------------
/* Draw a separator line (+---+---+---+) */
inline std::string ConsoleTable::drawSeparator(const ColumnWidths &columnWidths)
{
std::string output;
output += "+";
for (const auto &width : columnWidths)
{
output += std::string(width, '-') + "+";
}
output += "\n";
return output;
}
//--------------------------------------------------------------
/* Draw a single row */
inline std::string ConsoleTable::drawRow(const Row &row, const ColumnWidths &columnWidths)
{
std::string output;
output += "|";
for (size_t i = 0; i < columnWidths.size(); ++i)
{
const auto cellContent = (i < row.size()) ? row[i] : "";
output += " " + cellContent;
// Add padding spaces
const size_t paddingSpaces = columnWidths[i] - cellContent.length() - 1;
output += std::string(paddingSpaces, ' ');
output += "|";
}
output += "\n";
return output;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::console

View File

@@ -0,0 +1,124 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#ifdef _WIN32
# include <Windows.h>
#endif // defined WIN32
#include <iostream>
#include <memory>
namespace sdi_toolBox::logs
{
//--------------------------------------------------------------
class Win32Console
{
public:
static void initConsole(); // Init application console and attach debug console under MSW
static void releaseConsole(); // Release application console
public:
virtual ~Win32Console(); // Default destructor
Win32Console(const Win32Console &obj) = delete; // Copy constructor
Win32Console(Win32Console &&obj) noexcept = delete; // Move constructor
Win32Console &operator=(const Win32Console &obj) = delete; // Copy assignment operator
Win32Console &operator=(Win32Console &&obj) noexcept = delete; // Move assignment operator
static bool hasAttachedConsole(); // Returns true if console is attached, false otherwise
protected:
static inline std::unique_ptr<Win32Console> m_singleton;
Win32Console(); // Default constructor
FILE *m_stdoutFile; // Reopened stdout file pointer
FILE *m_stderrFile; // Reopened stderr file pointer
};
//--------------------------------------------------------------
/* Init application console and attach debug console under MSW */
inline void Win32Console::initConsole()
{
#ifndef _WIN32
# error This calss is only available under Windows systems
#endif
if (!m_singleton)
m_singleton = std::unique_ptr<Win32Console>(new Win32Console());
}
//--------------------------------------------------------------
/* Release application console */
inline void Win32Console::releaseConsole()
{
if (m_singleton)
m_singleton.reset();
}
//--------------------------------------------------------------
/* Default constructor */
inline Win32Console::Win32Console()
{
#ifdef _WIN32
bool consoleIsCreated = false;
// Try to attach application to the current console
AttachConsole(ATTACH_PARENT_PROCESS);
if (!GetConsoleWindow()) // No console was available
{
// Create console and attach application to it
if (!AllocConsole())
throw std::logic_error("Unable to attach application to debug console"); // Error during creating console
consoleIsCreated = true;
}
// Reopen stdout and stderr streams to console window
if (freopen_s(&m_stdoutFile, "CONOUT$", "w", stdout) != 0)
throw std::logic_error("Unable to reopen stdout"); // Error during reopen on stdout
if (freopen_s(&m_stderrFile, "CONOUT$", "w", stderr) != 0)
throw std::logic_error("Unable to reopen stderr"); // Error during reopen on stderr
std::cout.clear();
std::cerr.clear();
if (!consoleIsCreated)
std::cout << std::endl; // Add a new line if console was already existing
#endif
}
//--------------------------------------------------------------
/* Default destructor */
inline Win32Console::~Win32Console()
{
#ifdef _WIN32
std::cout.clear();
std::cerr.clear();
// Free console
FreeConsole();
// Close reopened stdout and stderr streams
(void)fclose(m_stdoutFile);
(void)fclose(m_stderrFile);
#endif
}
//--------------------------------------------------------------
/* Returns true if console is attached, false otherwise */
inline bool Win32Console::hasAttachedConsole()
{
#ifdef _WIN32
if (GetConsoleWindow())
return true;
#endif
return false;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::logs

View File

@@ -0,0 +1,81 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <openssl/evp.h>
#include <span>
#include <vector>
namespace sdi_toolBox::crypto
{
//--------------------------------------------------------------
class Base64
{
public:
Base64() = default; // Default constructor
virtual ~Base64() = default; // Default destructor
Base64(const Base64 &obj) = delete; // Copy constructor
Base64(Base64 &&obj) noexcept = delete; // Move constructor
Base64 &operator=(const Base64 &obj) = delete; // Copy assignment operator
Base64 &operator=(Base64 &&obj) noexcept = delete; // Move assignment operator
static std::string encode(const std::span<uint8_t> &binary_data); // Encodes binary data into a Base64 string
static std::vector<uint8_t> decode(const std::string &base64_string); // Decodes a Base64 string into binary data
};
//--------------------------------------------------------------
/* Encodes binary data into a Base64 string */
inline std::string Base64::encode(const std::span<uint8_t> &binary_data)
{
if (binary_data.empty())
return "";
// Calculate the length of the encoded data
const size_t output_len_max = EVP_ENCODE_LENGTH(binary_data.size());
// Allocate output buffer (with null terminator)
std::vector<char> output_buffer(output_len_max + 1, 0);
// OpenSSL encoding
const auto final_len = EVP_EncodeBlock(reinterpret_cast<uint8_t *>(output_buffer.data()),
binary_data.data(),
binary_data.size());
if (final_len < 0)
throw std::runtime_error("Error: Base64 encoding failed");
return std::string(output_buffer.data());
}
//--------------------------------------------------------------
/* Decodes a Base64 string into binary data */
inline std::vector<uint8_t> Base64::decode(const std::string &base64_string)
{
if (base64_string.empty())
return {};
// Calculate the length of the decoded data
const auto output_len_max = EVP_DECODE_LENGTH(base64_string.size());
// Allocate output buffer
std::vector<uint8_t> output_buffer(output_len_max);
// OpenSSL decoding
const auto final_len = EVP_DecodeBlock(output_buffer.data(),
reinterpret_cast<const unsigned char *>(base64_string.data()),
static_cast<int>(base64_string.size()));
if (final_len < 0)
throw std::runtime_error("Error: Base64 decoding failed: Invalid data");
output_buffer.resize(final_len);
return output_buffer;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::crypto

View File

@@ -0,0 +1,71 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <span>
#include <string>
#include <vector>
namespace sdi_toolBox::crypto
{
//--------------------------------------------------------------
class Data8
{
public:
Data8() = default; // Default constructor
explicit Data8(const std::string &data); // Constructor from string
explicit Data8(const std::span<const uint8_t> &data); // Constructor from byte span
virtual ~Data8() = default; // Default destructor
Data8(const Data8 &obj) = delete; // Copy constructor
Data8(Data8 &&obj) noexcept = delete; // Move constructor
Data8 &operator=(const Data8 &obj) = delete; // Copy assignment operator
Data8 &operator=(Data8 &&obj) noexcept = delete; // Move assignment operator
[[nodiscard]] std::string getString() const; // Get data as string
[[nodiscard]] std::span<const uint8_t> getBytes() const; // Get data as byte span
[[nodiscard]] size_t getSize() const; // Get data size
protected:
std::vector<uint8_t> m_data; // Raw data storage
};
//--------------------------------------------------------------
/* Constructor from string */
inline Data8::Data8(const std::string &data)
{
m_data = std::vector<uint8_t>(data.begin(), data.end());
}
//--------------------------------------------------------------
inline Data8::Data8(const std::span<const uint8_t> &data)
{
m_data = std::vector<uint8_t>(data.begin(), data.end());
}
//--------------------------------------------------------------
/* Get data as string */
inline std::string Data8::getString() const
{
return std::string(m_data.begin(), m_data.end());
}
//--------------------------------------------------------------
/* Get data as byte span */
inline std::span<const uint8_t> Data8::getBytes() const
{
return m_data;
}
//--------------------------------------------------------------
/* Get data size */
inline size_t Data8::getSize() const
{
return m_data.size();
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::crypto

View File

@@ -0,0 +1,78 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <filesystem>
#include <openssl/evp.h>
#include <span>
#include <string>
namespace sdi_toolBox::crypto
{
//--------------------------------------------------------------
class DigitalHash
{
public:
DigitalHash() = default; // Default constructor
virtual ~DigitalHash() = default; // Default destructor
DigitalHash(const DigitalHash &obj) = delete; // Copy constructor
DigitalHash(DigitalHash &&obj) noexcept = delete; // Move constructor
DigitalHash &operator=(const DigitalHash &obj) = delete; // Copy assignment operator
DigitalHash &operator=(DigitalHash &&obj) noexcept = delete; // Move assignment operator
static std::string calculateDataHash(const std::span<const uint8_t> &data, const EVP_MD *md_type); // Calculate digital hash from data buffer
};
//--------------------------------------------------------------
/* Calculate digital hash from data buffer */
inline std::string DigitalHash::calculateDataHash(const std::span<const uint8_t> &data, const EVP_MD *md_type)
{
// Initialize OpenSSL digest context
EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
if (!mdctx)
throw std::runtime_error("Error: Failed to create EVP_MD_CTX");
// Initialize digest operation
if (EVP_DigestInit_ex(mdctx, md_type, nullptr) != 1)
{
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("Error: EVP_DigestInit_ex failed");
}
// Update digest with data
if (EVP_DigestUpdate(mdctx, data.data(), data.size()) != 1)
{
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("Error: EVP_DigestUpdate failed");
}
// Finalize digest computation
std::vector<uint8_t> hash(EVP_MD_size(md_type));
unsigned int hash_len = 0;
if (EVP_DigestFinal_ex(mdctx, hash.data(), &hash_len) != 1)
{
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("Error: EVP_DigestFinal_ex failed");
}
// Clean up
EVP_MD_CTX_free(mdctx);
// Convert hash to hexadecimal string
std::string hash_hex;
for (unsigned int i = 0; i < hash_len; ++i)
hash_hex += std::format("{:02x}", hash[i]);
return hash_hex;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::crypto

View File

@@ -0,0 +1,255 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include "data.h"
#include <filesystem>
#include <fstream>
#include <memory>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <span>
#include <string>
#include <vector>
namespace sdi_toolBox::crypto
{
//--------------------------------------------------------------
class DigitalSign
{
// Custom deleter for EVP_PKEY to ensure it's automatically freed
struct EVP_PKEY_Deleter
{
void operator()(EVP_PKEY *p) const
{
if (p)
{
EVP_PKEY_free(p);
}
}
};
using pEVP_PKEY = std::unique_ptr<EVP_PKEY, EVP_PKEY_Deleter>;
struct BIO_Deleter
{
void operator()(BIO *b) const
{
if (b)
{
BIO_free(b);
}
}
};
using pBIO = std::unique_ptr<BIO, BIO_Deleter>;
struct EVP_MD_CTX_Deleter
{
void operator()(EVP_MD_CTX *ctx) const
{
if (ctx)
{
EVP_MD_CTX_free(ctx);
}
}
};
using pEVP_MD_CTX = std::unique_ptr<EVP_MD_CTX, EVP_MD_CTX_Deleter>;
public:
DigitalSign() = default; // Default constructor
virtual ~DigitalSign() = default; // Default destructor
DigitalSign(const DigitalSign &obj) = delete; // Copy constructor
DigitalSign(DigitalSign &&obj) noexcept = delete; // Move constructor
DigitalSign &operator=(const DigitalSign &obj) = delete; // Copy assignment operator
DigitalSign &operator=(DigitalSign &&obj) noexcept = delete; // Move assignment operator
void loadPEMKeyFromFile(const std::filesystem::path &filepath, bool is_private); // Load key from PEM file
void loadPEMKeyFromMemory(const std::string &key, bool is_private); // Load key from memory
std::vector<uint8_t> sign(const Data8 &data, const EVP_MD *digest_type = EVP_sha256()) const; // Sign data with private key
std::string signHex(const Data8 &data, const EVP_MD *digest_type = EVP_sha256()) const; // Sign data with private key
bool verify(const Data8 &data, const std::span<const uint8_t> &signature, const EVP_MD *digest_type = EVP_sha256()) const; // Verify signature with public key
bool verify(const Data8 &data, const std::string &signature, const EVP_MD *digest_type = EVP_sha256()) const; // Verify signature with public key
protected:
pEVP_PKEY m_privateKey;
pEVP_PKEY m_publicKey;
private:
static std::vector<uint8_t> hexToBytes(const std::string &hexString); // Converts a hexadecimal string into a vector of bytes
static std::string bytesToHex(const std::span<const uint8_t> &bytes); // Converts a span of bytes into a hexadecimal string representation
};
//--------------------------------------------------------------
/* Load key from PEM file */
inline void DigitalSign::loadPEMKeyFromFile(const std::filesystem::path &filepath, const bool is_private)
{
std::ifstream file(filepath, std::ifstream::binary);
if (!file.is_open())
throw std::runtime_error("Error: Could not open file " + filepath.string());
const std::string key((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
loadPEMKeyFromMemory(key, is_private);
}
//--------------------------------------------------------------
/* Load key from memory */
inline void DigitalSign::loadPEMKeyFromMemory(const std::string &key, const bool is_private)
{
if (key.empty())
throw std::runtime_error("Error: Input stream is empty or could not be read");
// Create a memory BIO (Basic Input/Output) for reading the key
const auto bio = static_cast<pBIO>(BIO_new_mem_buf(key.data(), static_cast<int>(key.size())));
if (!bio)
throw std::runtime_error("Error: Failed to create memory BIO");
// Use the BIO to read the key
if (is_private)
{
m_privateKey = static_cast<pEVP_PKEY>(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr));
if (!m_privateKey)
throw std::runtime_error("Error: Failed to read private key from memory buffer");
}
else
{
m_publicKey = static_cast<pEVP_PKEY>(PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr));
if (!m_publicKey)
throw std::runtime_error("Error: Failed to read private key from memory buffer");
}
}
//--------------------------------------------------------------
/* Sign data with private key */
inline std::vector<uint8_t> DigitalSign::sign(const Data8 &data, const EVP_MD *digest_type) const
{
if (!m_privateKey)
throw std::runtime_error("Error: Private key is not loaded for signing");
// Initialize the context for signing
const auto md_ctx = static_cast<pEVP_MD_CTX>(EVP_MD_CTX_new());
if (!md_ctx)
throw std::runtime_error("Error: Failed to create EVP_MD_CTX");
// Set the context for signing with the private key and digest
if (EVP_DigestSignInit(md_ctx.get(), nullptr, digest_type, nullptr, m_privateKey.get()) != 1)
throw std::runtime_error("Error: EVP_DigestSignInit failed");
// Provide the data to be signed
if (EVP_DigestSignUpdate(md_ctx.get(), data.getBytes().data(), data.getSize()) != 1)
throw std::runtime_error("Error: EVP_DigestSignUpdate failed");
// Determine the required signature size
size_t sigLen = 0;
if (EVP_DigestSignFinal(md_ctx.get(), nullptr, &sigLen) != 1)
throw std::runtime_error("Error: EVP_DigestSignFinal (size) failed");
// Allocate space and generate the signature
std::vector<uint8_t> signature(sigLen);
if (EVP_DigestSignFinal(md_ctx.get(), signature.data(), &sigLen) != 1)
throw std::runtime_error("Error: EVP_DigestSignFinal (signature) failed");
// Resize signature to the actual length used
signature.resize(sigLen);
return signature;
}
//--------------------------------------------------------------
/* Sign data */
inline std::string DigitalSign::signHex(const Data8 &data, const EVP_MD *digest_type) const
{
return bytesToHex(sign(data, digest_type));
}
//--------------------------------------------------------------
/* Verify signature with public key */
inline bool DigitalSign::verify(const Data8 &data, const std::span<const uint8_t> &signature, const EVP_MD *digest_type) const
{
if (!m_publicKey)
throw std::runtime_error("Error: Public key is not loaded for verifying");
// Initialize the context for verifying
const auto md_ctx = static_cast<pEVP_MD_CTX>(EVP_MD_CTX_new());
if (!md_ctx)
throw std::runtime_error("Error: Failed to create EVP_MD_CTX");
// Set the context for verifying with the public key and digest
if (EVP_DigestVerifyInit(md_ctx.get(), nullptr, digest_type, nullptr, m_publicKey.get()) != 1)
throw std::runtime_error("Error: EVP_DigestVerifyInit failed");
// Provide the original data
if (EVP_DigestVerifyUpdate(md_ctx.get(), data.getBytes().data(), data.getSize()) != 1)
throw std::runtime_error("Error: EVP_DigestVerifyUpdate failed");
// Verify the signature
// Returns 1 for a good signature, 0 for a bad signature, and -1 on error
const auto result = EVP_DigestVerifyFinal(md_ctx.get(), signature.data(), signature.size());
if (result == 1)
return true; // Signature is valid
if (result == 0)
return false; // Signature is NOT valid
throw std::runtime_error("Error: EVP_DigestVerifyFinal failed during execution");
}
//--------------------------------------------------------------
/* Verify signature with public key */
inline bool DigitalSign::verify(const Data8 &data, const std::string &signature, const EVP_MD *digest_type) const
{
return verify(data, hexToBytes(signature), digest_type);
}
//--------------------------------------------------------------
/* Converts a hexadecimal string into a vector of bytes */
inline std::vector<uint8_t> DigitalSign::hexToBytes(const std::string &hexString)
{
// A valid hex string must have an even length
if (hexString.length() % 2 != 0)
throw std::invalid_argument("Hex string must have an even length");
std::vector<uint8_t> bytes;
bytes.reserve(hexString.length() / 2);
for (size_t i = 0; i < hexString.length(); i += 2)
{
std::string byteString = hexString.substr(i, 2);
unsigned long byteVal;
try
{
byteVal = std::stoul(byteString, nullptr, 16);
}
catch (const std::exception & /*e*/)
{
throw std::invalid_argument("Invalid hexadecimal character sequence: " + byteString); // Non-hex characters ?
}
// Check if the value fits in a uint8_t (should always be true for 2 chars)
if (byteVal > 0xFF)
throw std::invalid_argument("Internal error: Converted value exceeds 0xFF");
bytes.push_back(static_cast<uint8_t>(byteVal));
}
return bytes;
}
//--------------------------------------------------------------
/* Converts a span of bytes into a hexadecimal string representation */
inline std::string DigitalSign::bytesToHex(const std::span<const uint8_t> &bytes)
{
std::stringstream ss;
ss << std::hex << std::uppercase << std::setfill('0');
for (const uint8_t byte : bytes)
ss << std::format("{0:02X}", byte);
return ss.str();
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::crypto

View File

@@ -0,0 +1,175 @@
#pragma once
#include <cassert>
#include <chrono>
#include <iostream>
//--------------------------------------------------------------
class Age
{
public:
Age() = default; // Default constructor
virtual ~Age() = default; // Default destructor
Age(const Age &obj) = default; // Copy constructor
Age(Age &&obj) noexcept = default; // Move constructor
Age &operator=(const Age &obj) = default; // Copy assignment operator
Age &operator=(Age &&obj) noexcept = default; // Move assignment operator
explicit Age(std::chrono::nanoseconds age); // Constructor
void set(std::chrono::nanoseconds age); // Convert age in microseconds to days, hours, minutes, seconds, milliseconds and microseconds
std::chrono::microseconds getAge() const; // Return age in microseconds
std::chrono::days getDays() const; // Return days part in age
std::chrono::hours getHours() const; // Return hours part in age
std::chrono::minutes getMinutes() const; // Return minutes part in age
std::chrono::seconds getSeconds() const; // Return seconds part in age
std::chrono::milliseconds getMilliseconds() const; // Return milliseconds part in age
std::chrono::microseconds getMicroseconds() const; // Return microseconds part in age
std::chrono::nanoseconds getNanoseconds() const; // Return nanoseconds part in age
std::string toString(bool show_ms = true, bool show_us = false, bool show_ns = false) const; // Return age as a string in format "[d] H:m:s.ms.us.ns"
void testAge() const; // Test function to verify the correctness of the Age class implementation
protected:
std::chrono::nanoseconds m_age = std::chrono::nanoseconds(0);
std::chrono::days m_days = std::chrono::days(0);
std::chrono::hours m_hours = std::chrono::hours(0);
std::chrono::minutes m_minutes = std::chrono::minutes(0);
std::chrono::seconds m_seconds = std::chrono::seconds(0);
std::chrono::milliseconds m_milliseconds = std::chrono::milliseconds(0);
std::chrono::microseconds m_microseconds = std::chrono::microseconds(0);
std::chrono::nanoseconds m_nanoseconds = std::chrono::nanoseconds(0);
};
//--------------------------------------------------------------
/* Constructor */
inline Age::Age(const std::chrono::nanoseconds age)
{
set(age);
}
//--------------------------------------------------------------
/* Convert age in microseconds to days, hours, minutes, seconds, milliseconds and microseconds */
inline void Age::set(std::chrono::nanoseconds age)
{
constexpr auto max_ns = std::chrono::nanoseconds::max().count(); // For an int64_t storage, the maximum value corresponds to more than 106752 days
constexpr auto min_ns = std::chrono::nanoseconds::min().count();
if (age.count() > max_ns || age.count() < min_ns)
throw std::overflow_error("Duration exceeds the limits of std::chrono::nanoseconds");
if (age < std::chrono::nanoseconds(0))
throw std::invalid_argument("Age cannot be negative");
m_age = age;
m_days = std::chrono::duration_cast<std::chrono::days>(age);
age -= m_days;
m_hours = std::chrono::duration_cast<std::chrono::hours>(age);
age -= m_hours;
m_minutes = std::chrono::duration_cast<std::chrono::minutes>(age);
age -= m_minutes;
m_seconds = std::chrono::duration_cast<std::chrono::seconds>(age);
age -= m_seconds;
m_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(age);
age -= m_milliseconds;
m_microseconds = std::chrono::duration_cast<std::chrono::microseconds>(age);
age -= m_microseconds;
m_nanoseconds = age;
}
//--------------------------------------------------------------
/* Return age in microseconds */
inline std::chrono::microseconds Age::getAge() const
{
return std::chrono::duration_cast<std::chrono::microseconds>(m_age);
}
//--------------------------------------------------------------
/* Return days part in age */
inline std::chrono::days Age::getDays() const
{
return m_days;
}
//--------------------------------------------------------------
/* Return hours part in age */
inline std::chrono::hours Age::getHours() const
{
return m_hours;
}
//--------------------------------------------------------------
/* Return minutes part in age */
inline std::chrono::minutes Age::getMinutes() const
{
return m_minutes;
}
//--------------------------------------------------------------
/* Return seconds part in age */
inline std::chrono::seconds Age::getSeconds() const
{
return m_seconds;
}
//--------------------------------------------------------------
/* Return milliseconds part in age */
inline std::chrono::milliseconds Age::getMilliseconds() const
{
return m_milliseconds;
}
//--------------------------------------------------------------
/* Return microseconds part in age */
inline std::chrono::microseconds Age::getMicroseconds() const
{
return m_microseconds;
}
//--------------------------------------------------------------
/* Return nanoseconds part in age */
inline std::chrono::nanoseconds Age::getNanoseconds() const
{
return m_nanoseconds;
}
//--------------------------------------------------------------
/* Return age as a string in format "[d] H:m:s.ms.us.ns".
* The show_ms, show_us and show_ns parameters control whether
* to include milliseconds, microseconds and nanoseconds in the
* output string. */
inline std::string Age::toString(const bool show_ms, const bool show_us, const bool show_ns) const
{
std::ostringstream oss;
// Add days if greater than 0
if (m_days.count() > 0)
oss << std::format("[{}] ", m_days.count());
// Add hours, minutes and seconds
oss << std::format("{:02}:{:02}:{:02}", m_hours.count(), m_minutes.count(), m_seconds.count());
// Add milliseconds, microseconds and nanoseconds if requested
if (show_ms || show_us || show_ns)
oss << std::format(".{:03}", m_milliseconds.count());
if (show_us || show_ns)
oss << std::format("{:03}", m_microseconds.count());
if (show_ns)
oss << std::format("{:03}", m_nanoseconds.count());
return oss.str();
}
//--------------------------------------------------------------
/* Test function to verify the correctness of the Age class implementation */
inline void Age::testAge() const
{
const Age age(std::chrono::nanoseconds(90061001001)); // 1 day, 1 hour, 1 minute, 1 second, 1 millisecond, 1 microsecond, 1 nanosecond
assert(age.getDays().count() == 1);
assert(age.getHours().count() == 1);
assert(age.getMinutes().count() == 1);
assert(age.getSeconds().count() == 1);
assert(age.getMilliseconds().count() == 1);
assert(age.getMicroseconds().count() == 1);
assert(age.getNanoseconds().count() == 1);
std::cout << "All tests passed!" << std::endl;
}
//--------------------------------------------------------------

View File

@@ -0,0 +1,49 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <chrono>
namespace sdi_toolBox::dateTime
{
//--------------------------------------------------------------
/**
* @brief Abstract interface for pause functionality.
*
* Provides a common interface for implementing pause or delay mechanisms.
* Derived classes must implement the wait() method to pause execution
* for a specified duration.
*/
class IPause
{
public:
using Duration = std::chrono::milliseconds;
public:
~IPause() = default; // Default destructor
IPause(const IPause &obj) = default; // Copy constructor
IPause(IPause &&obj) noexcept = default; // Move constructor
IPause &operator=(const IPause &obj) = default; // Copy assignment operator
IPause &operator=(IPause &&obj) noexcept = default; // Move assignment operator
/**
* @brief Pause execution for the specified duration.
* @param duration Duration of the pause.
*/
virtual void wait(const Duration &duration) const = 0;
protected:
IPause() = default; // Default constructor
};
//--------------------------------------------------------------
} // namespace sdi_toolBox::dateTime

View File

@@ -0,0 +1,100 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <chrono>
namespace sdi_toolBox::dateTime
{
//--------------------------------------------------------------
/**
* @brief Abstract interface for timer functionality.
*
* Provides a common interface for timer implementations, allowing
* measurement of elapsed time and checking for timeouts.
* Derived classes must implement the now() method to provide the
* current time point.
*/
class ITimer
{
public:
using Clock = std::chrono::high_resolution_clock;
using TimePoint = Clock::time_point;
using Duration = std::chrono::milliseconds;
public:
virtual ~ITimer() = default; // Default destructor
ITimer(const ITimer &obj) = default; // Copy constructor
ITimer(ITimer &&obj) noexcept = default; // Move constructor
ITimer &operator=(const ITimer &obj) = default; // Copy assignment operator
ITimer &operator=(ITimer &&obj) noexcept = default; // Move assignment operator
/**
* @brief Resets the reference time point to the current time.
*/
void reset();
/**
* @brief Returns the current time point.
* @return The current time point as defined by the derived class.
*/
[[nodiscard]] virtual TimePoint now() const = 0;
/**
* @brief Returns the elapsed duration since the last reset.
* @return Duration since the last reset.
*/
[[nodiscard]] Duration getElapsed() const;
/**
* @brief Checks if the specified duration has elapsed since the last reset.
* @param duration The duration to check.
* @param autoReset If true, automatically resets the timer when the duration has elapsed.
* @return True if the duration has elapsed, false otherwise.
*/
[[nodiscard]] bool isElapsed(const Duration duration,
bool autoReset = false);
protected:
ITimer() = default; // Default constructor
TimePoint m_t0; // Timepoint t0 for elapsed time measurement
};
//--------------------------------------------------------------
/* Reset the timepoint t0 */
inline void ITimer::reset()
{
m_t0 = now();
}
//--------------------------------------------------------------
/* Get the elapsed time */
inline ITimer::Duration ITimer::getElapsed() const
{
return std::chrono::duration_cast<Duration>(now() - m_t0);
}
//--------------------------------------------------------------
/* Check if the specified delay has elapsed */
inline bool ITimer::isElapsed(const Duration duration, bool autoReset)
{
Duration elapsed = getElapsed();
if (elapsed >= duration)
{
if (autoReset)
reset();
return true;
}
return false;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::dateTime

View File

@@ -0,0 +1,59 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include "iPause.h"
#include <thread>
namespace sdi_toolBox::dateTime
{
//--------------------------------------------------------------
/**
* @brief Standard pause implementation.
*
* Implements IPause to provide a blocking pause using standard C++ mechanisms.
* The actual delay is performed using std::this_thread::sleep_for.
*/
class Pause : public IPause
{
public:
Pause() = default; // Default constructor
~Pause() = default; // Default destructor
Pause(const Pause &obj) = default; // Copy constructor
Pause(Pause &&obj) noexcept = default; // Move constructor
Pause &operator=(const Pause &obj) = default; // Copy assignment operator
Pause &operator=(Pause &&obj) noexcept = default; // Move assignment operator
explicit Pause(const Duration &duration); // Constructor
/**
* @brief Pause execution for the specified duration.
* @param duration Duration of the pause.
*/
void wait(const Duration &duration) const override;
};
//--------------------------------------------------------------
/* Constructor */
inline Pause::Pause(const Duration &duration)
{
wait(duration);
}
//--------------------------------------------------------------
/* Pause execution for the specified duration */
inline void Pause::wait(const Duration &duration) const
{
std::this_thread::sleep_for(duration);
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::dateTime

View File

@@ -0,0 +1,55 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include "iTimer.h"
namespace sdi_toolBox::dateTime
{
//--------------------------------------------------------------
/**
* @brief Standard timer implementation using std::chrono.
*
* Implements ITimer using std::chrono::high_resolution_clock for
* high-precision timing in standard C++ environments.
*/
class Timer : public ITimer
{
public:
Timer(); // Default constructor
~Timer() = default; // Default destructor
Timer(const Timer &obj) = default; // Copy constructor
Timer(Timer &&obj) noexcept = default; // Move constructor
Timer &operator=(const Timer &obj) = default; // Copy assignment operator
Timer &operator=(Timer &&obj) noexcept = default; // Move assignment operator
/**
* @brief Returns the current time point using std::chrono::high_resolution_clock.
* @return The current time point.
*/
[[nodiscard]] TimePoint now() const override;
};
//--------------------------------------------------------------
/* Default constructor */
inline Timer::Timer()
{
reset();
}
//--------------------------------------------------------------
/* Get the current timepoint */
inline Timer::TimePoint Timer::now() const
{
return Clock::now();
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::dateTime

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,176 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
// #include <nlohmann/detail/abi_macros.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file contains all macro definitions affecting or depending on the ABI
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3
#warning "Already included a different version of the library!"
#endif
#endif
#endif
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum)
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 0
#endif
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
#else
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
_v ## major ## _ ## minor ## _ ## patch
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_VERSION
#else
#define NLOHMANN_JSON_NAMESPACE_VERSION \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
NLOHMANN_JSON_VERSION_MINOR, \
NLOHMANN_JSON_VERSION_PATCH)
#endif
// Combine namespace components
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
#ifndef NLOHMANN_JSON_NAMESPACE
#define NLOHMANN_JSON_NAMESPACE \
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION)
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
namespace nlohmann \
{ \
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION) \
{
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_END
#define NLOHMANN_JSON_NAMESPACE_END \
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
} // namespace nlohmann
#endif
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;
/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;
/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;
/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;
NLOHMANN_JSON_NAMESPACE_END
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

View File

@@ -0,0 +1,105 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <array>
#include <cstdint>
#include <optional>
namespace sdi_toolBox::generic
{
//--------------------------------------------------------------
template<class T, size_t BUFFER_SIZE>
class CircularBuffer final
{
public:
CircularBuffer() = default; // Constructor
~CircularBuffer() = default; // Destructor
CircularBuffer(const CircularBuffer &other) = delete; // Copy constructor
CircularBuffer(CircularBuffer &&other) noexcept = delete; // Move constructor
CircularBuffer &operator=(const CircularBuffer &other) = delete; // Copy assignment
CircularBuffer &operator=(CircularBuffer &&other) noexcept = delete; // Move assignment
bool push(const T &item); // Add an element to the buffer
std::optional<T> pop(); // Retrieve an element from the buffer
bool isEmpty() const; // Check if the buffer is empty
bool isFull() const; // Check if the buffer is full
size_t size() const; // Get the current number of elements
protected:
std::array<T, BUFFER_SIZE> m_buffer;
volatile size_t m_head = 0; // Index for the next write (push)
volatile size_t m_tail = 0; // Index for the next read (pop)
volatile size_t m_size = 0; // Current number of elements stored
};
//--------------------------------------------------------------
//--------------------------------------------------------------
/* Add an element to the buffer */
template<class T, size_t BUFFER_SIZE>
inline bool CircularBuffer<T, BUFFER_SIZE>::push(const T &item)
{
if (isFull())
return false; // Buffer is full
// Write the item at the current head position
m_buffer[m_head] = item;
// Move head pointer, wrapping around using modulo
m_head = (m_head + 1) % BUFFER_SIZE;
// Increase the current size of the data in the buffer
m_size = m_size + 1;
return true;
}
//--------------------------------------------------------------
/* Retrieve an element from the buffer */
template<class T, size_t BUFFER_SIZE>
inline std::optional<T> CircularBuffer<T, BUFFER_SIZE>::pop()
{
if (isEmpty())
return {}; // Buffer is empty, cannot pop
// Read (copy) the item at the current tail position
T item = m_buffer[m_tail];
// Move tail pointer, wrapping around using modulo
m_tail = (m_tail + 1) % BUFFER_SIZE;
// Decrease the current size of the data in the buffer
m_size = m_size - 1;
return item;
}
//--------------------------------------------------------------
/* Check if the buffer is empty */
template<class T, size_t BUFFER_SIZE>
inline bool CircularBuffer<T, BUFFER_SIZE>::isEmpty() const
{
return m_size == 0;
}
//--------------------------------------------------------------
/* Check if the buffer is full */
template<class T, size_t BUFFER_SIZE>
inline bool CircularBuffer<T, BUFFER_SIZE>::isFull() const
{
return m_size == BUFFER_SIZE;
}
//--------------------------------------------------------------
/* Get the current number of elements */
template<class T, size_t BUFFER_SIZE>
inline size_t CircularBuffer<T, BUFFER_SIZE>::size() const
{
return m_size;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::generic

View File

@@ -0,0 +1,163 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <cstdint>
#include <span>
namespace sdi_ToolBox::generic
{
//--------------------------------------------------------------
/**
* @class CRC16_modbus
* @brief CRC16 calculator for the Modbus protocol.
*
* Provides both static and incremental interfaces to compute the CRC16 checksum
* as used in the Modbus protocol (polynomial 0xA001, initial value 0xFFFF).
*
* Usage examples:
* @code
* // One-shot CRC calculation
* std::array<uint8_t, 8> data = { ... };
* uint16_t crc = Crc16_modbus::computeCRC(data);
*
* // Incremental CRC calculation
* Crc16_modbus crcHandle;
* crcHandle.init();
* for (uint8_t b : data) {
* crcHandle.update(b);
* }
* uint16_t crc = crcHandle.finalize();
* @endcode
*/
class CRC16_modbus
{
static constexpr uint16_t INITIAL_VALUE = 0xFFFF;
public:
/**
* @brief Computes the CRC16 of the given data in one call.
* @param data The data buffer to compute the CRC for.
* @return The computed CRC16 value.
*/
static uint16_t computeCRC(const std::span<const uint8_t> &data);
public:
CRC16_modbus() = default; // Default constructor
virtual ~CRC16_modbus() = default; // Default destructor
CRC16_modbus(const CRC16_modbus &other) = default; // Copy constructor
CRC16_modbus(CRC16_modbus &&other) noexcept = default; // Move constructor
CRC16_modbus &operator=(const CRC16_modbus &other) = default; // Copy assignment
CRC16_modbus &operator=(CRC16_modbus &&other) noexcept = default; // Move assignment
/**
* @brief Constructs and initializes the CRC with the given data.
* @param data The data buffer to initialize and update the CRC with.
*/
explicit CRC16_modbus(const std::span<const uint8_t> &data);
/**
* @brief Initializes or re-initializes the CRC to the default value (0xFFFF).
*/
void init();
/**
* @brief Updates the CRC with a single byte.
* @param val The byte value to update the CRC with.
*/
void update(const uint8_t &val);
/**
* @brief Updates the CRC with a data buffer.
* @param data The data buffer to update the CRC with.
*/
void update(const std::span<const uint8_t> &data);
/**
* @brief Finalizes the CRC calculation and returns the CRC value.
* @return The finalized CRC16 value.
*/
uint16_t finalize() const;
/**
* @brief Returns the current CRC value.
* @return The current CRC16 value.
*/
[[nodiscard]] uint16_t getCRC() const;
protected:
uint16_t m_crc = INITIAL_VALUE; // Current CRC value
};
//--------------------------------------------------------------
//--------------------------------------------------------------
/* Compute CRC in one go */
inline uint16_t CRC16_modbus::computeCRC(const std::span<const uint8_t> &data)
{
const CRC16_modbus crc(data);
return crc.finalize();
}
//--------------------------------------------------------------
/* Constructor */
inline CRC16_modbus::CRC16_modbus(const std::span<const uint8_t> &data)
{
init();
update(data);
}
//--------------------------------------------------------------
/* Reinit crc handle */
inline void CRC16_modbus::init()
{
m_crc = INITIAL_VALUE;
}
//--------------------------------------------------------------
/* Update CRC */
inline void CRC16_modbus::update(const uint8_t &val)
{
constexpr uint16_t PolynomialValue = 0xA001;
m_crc ^= static_cast<uint16_t>(val); // XOR byte into least sig. byte of crc
for (int i = 8; i != 0; i--) // Loop over each bit
{
if ((m_crc & 0x0001) != 0) // If the LSB is set
{
m_crc >>= 1; // Shift right and XOR PolynomialValue
m_crc ^= PolynomialValue;
}
else // Else LSB is not set
{
m_crc >>= 1; // Just shift right
}
}
}
//--------------------------------------------------------------
/* Update CRC */
inline void CRC16_modbus::update(const std::span<const uint8_t> &data)
{
for (const auto &c : data)
update(c);
}
//--------------------------------------------------------------
/* Finalize and return CRC value */
inline uint16_t CRC16_modbus::finalize() const
{
return getCRC();
}
//--------------------------------------------------------------
/* Return CRC value */
inline uint16_t CRC16_modbus::getCRC() const
{
return m_crc;
}
//--------------------------------------------------------------
} // namespace sdi_ToolBox::generic

View File

@@ -0,0 +1,79 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <boost/uuid/uuid.hpp> // Pour boost::uuids::uuid
#include <boost/uuid/uuid_generators.hpp> // Pour boost::uuids::random_generator
#include <boost/uuid/uuid_io.hpp> // Pour boost::uuids::to_string
#include <format>
#include <string>
namespace sdi_toolBox::generic
{
//--------------------------------------------------------------
class UuidGenerator
{
public:
static std::string uuid_v4(); // Generate a random UUID v4 string
static std::string uniqid(bool moreEntropy = false); // Generate a unique identifier string (as in PHP)
protected:
UuidGenerator() = default; // Default constructor
~UuidGenerator() = default; // Default destructor
UuidGenerator(const UuidGenerator &) = default; // Copy constructor
UuidGenerator(UuidGenerator &&) noexcept = default; // Move constructor
UuidGenerator &operator=(const UuidGenerator &) = default; // Copy assignment operator
UuidGenerator &operator=(UuidGenerator &&) noexcept = default; // Move assignment operator
};
//--------------------------------------------------------------
/* Generate a random UUID v4 string */
inline std::string UuidGenerator::uuid_v4()
{
boost::uuids::random_generator gen; // Create a random UUID generator
boost::uuids::uuid u = gen(); // Generate a random UUID
return boost::uuids::to_string(u); // Convert the UUID to a string and return it
}
//--------------------------------------------------------------
/* Generate a unique identifier string (as in PHP) */
inline std::string UuidGenerator::uniqid(const bool moreEntropy)
{
const auto now = std::chrono::high_resolution_clock::now(); // Get current time point
const auto epoch = now.time_since_epoch(); // Get duration since epoch
const auto us_since_epoch = std::chrono::duration_cast<std::chrono::microseconds>(epoch).count();
// Format the time part into a hexadecimal string
const auto time_part = us_since_epoch / 16;
std::string result = std::format("{0:x}", time_part);
// PHP's uniqid pads the result to 13 characters if needed. We replicate the core logic.
// If the time part is shorter than 13 characters (highly unlikely today), it should be padded.
if (result.length() < 13)
result.insert(0, 13 - result.length(), '0');
// Add more entropy if requested (similar to PHP's second argument = true)
if (moreEntropy)
{
// Generate 5 bytes of random data (10 characters hex)
static std::random_device rd;
static std::mt19937 generator(rd());
unsigned int rand_val = generator(); // Generate a random 32-bit number and use a portion of a second random number
// Append the random data in hexadecimal format
result = result + std::format(".{0:08x}", rand_val);
}
return result;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::generic

View File

@@ -0,0 +1,531 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include "../console/ansi.h"
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN // Disable windows mess
# endif
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <Windows.h>
# undef GetMessage // To avoid conflict with other libraries
#endif
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <mutex>
#include <source_location>
#include <sstream>
#include <string>
/*
* Usage :
*
* {
* int value = 42;
* std::string user = "Alice";
*
* // Usage example:
* LogMessage() << "System ready" << std::endl;
* LogDebug() << "Checking configuration file..." << std::endl;
* LogInfo() << "User " << user << " logged in. (Value: " << value << ")" << std::endl;
* LogWarning() << "Device connection lost..." << std::endl;
* LogError() << "Failed to connect to server on port " << 8080 << "!" << std::endl;
* LogComm() << "<< 0123456789" << std::endl;
* LogDev() << "<< 0123456789" << std::endl;
* }
*/
namespace sdi_toolBox::logs
{
//--------------------------------------------------------------
enum class LogLevel : uint8_t
{
None,
Dev,
Comm,
Debug,
Info,
Warning,
Error,
Message,
};
//--------------------------------------------------------------
//--------------------------------------------------------------
class ReLog
{
public:
struct Log
{
LogLevel level;
std::chrono::system_clock::time_point timestamp;
std::string message;
};
using ColorLogMap = std::map<LogLevel, std::string>;
using VisibleLogMap = std::map<LogLevel, bool>;
static VisibleLogMap getVisibleLogMap(); // Return the map of visible log messages
static void setVisibleLogMap(const VisibleLogMap &logMap); // Define the map of visible log messages
static ReLog &systemInit(const std::filesystem::path &filepath = ""); // Initialize log system
static ReLog &getInstance(); // Gets singleton instance
public:
virtual ~ReLog() = default; // Default destructor
ReLog(const ReLog &obj) = delete; // Copy constructor
ReLog(ReLog &&obj) noexcept = delete; // Move constructor
ReLog &operator=(const ReLog &obj) = delete; // Copy assignment operator
ReLog &operator=(ReLog &&obj) noexcept = delete; // Move assignment operator
void openFile(const std::filesystem::path &filepath); // Open or create log file
void closeFile(); // Close log file
void log(const Log &log); // Core log method
void printColorTest() const; // Print the different configurations and colors on the terminal
protected:
ReLog(); // Default constructor
bool m_vtModeEnabled = false;
std::mutex m_mtx;
std::ofstream m_file;
static inline ColorLogMap m_colorLogMap;
static inline VisibleLogMap m_visibleLogMap;
private:
void enableVTMode();
static void init(); // Settings initialization
static std::string createLogMessage(const Log &log, const bool isFile = false); // Print log messages
void printDate();
void saveToFile(const std::string &message); // Save log messages in file
};
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
/* Return the map of visible log messages */
inline ReLog::VisibleLogMap ReLog::getVisibleLogMap()
{
return m_visibleLogMap;
}
//--------------------------------------------------------------
/* Define the map of visible log messages */
inline void ReLog::setVisibleLogMap(const VisibleLogMap &logMap)
{
m_visibleLogMap = logMap;
}
//--------------------------------------------------------------
/* Initialize log system */
inline ReLog &ReLog::systemInit(const std::filesystem::path &filepath)
{
auto &logSystem = getInstance();
// Post initialization
if (!filepath.empty())
logSystem.openFile(filepath);
logSystem.printDate();
return logSystem;
}
//--------------------------------------------------------------
/* Gets singleton instance */
inline ReLog &ReLog::getInstance()
{
static ReLog instance;
return instance;
}
//--------------------------------------------------------------
/* Constructor */
inline ReLog::ReLog()
{
// Initialization
init();
enableVTMode();
// Test on debug mode
#ifdef DEBUG
// printColorTest();
#endif
}
//--------------------------------------------------------------
/* Open or create log file */
inline void ReLog::openFile(const std::filesystem::path &filepath)
{
std::lock_guard lock(m_mtx);
m_file = std::ofstream(filepath, std::ofstream::out | std::ofstream::app | std::ofstream::binary);
if (!m_file)
throw std::runtime_error("unable to open log file");
m_file << "\n-----------------------------------------------------------------" << std::endl;
}
//--------------------------------------------------------------
/* Close log file */
inline void ReLog::closeFile()
{
std::lock_guard lock(m_mtx);
if (m_file.is_open())
m_file.close();
}
//--------------------------------------------------------------
/* Core log method */
inline void ReLog::log(const Log &log)
{
std::lock_guard lock(m_mtx);
const auto termMessage = createLogMessage(log);
if (!termMessage.empty())
{
if (log.level == LogLevel::Error)
std::cerr << termMessage << std::endl;
else
std::cout << termMessage << std::endl;
}
const auto fileMessage = createLogMessage(log, true);
if (!fileMessage.empty())
saveToFile(fileMessage);
}
//--------------------------------------------------------------
/* Print the different configurations and colors on the terminal */
inline void ReLog::printColorTest() const
{
const auto coutTest = [&](const auto &color, const std::string &label)
{
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::UNDERLINE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::DIM | sdi_toolBox::console::ANSI::EscapeCommand::UNDERLINE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT | sdi_toolBox::console::ANSI::EscapeCommand::UNDERLINE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::BLINK) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::BRIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::DIM) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, sdi_toolBox::console::ANSI::EscapeCommand::REVERSE) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << " | ";
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Black) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Red) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Green) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Blue) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Cyan) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::White) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << " | ";
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Black, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Red, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Green, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Blue, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::Cyan, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << sdi_toolBox::console::ANSI::EscapeCommand::get(color, 0, sdi_toolBox::console::ANSI::EscapeCommand::Color::White, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT) << label << sdi_toolBox::console::ANSI::EscapeCommand::clear();
std::cout << std::endl;
};
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Black, " B ");
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Red, " R ");
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Green, " G ");
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow, " Y ");
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Blue, " B ");
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta, " M ");
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::Cyan, " C ");
coutTest(sdi_toolBox::console::ANSI::EscapeCommand::Color::White, " W ");
std::cout << std::endl;
}
//--------------------------------------------------------------
inline void ReLog::enableVTMode()
{
if (m_vtModeEnabled)
return;
m_vtModeEnabled = false;
// 1. Get the handle to the standard output
const HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut == INVALID_HANDLE_VALUE)
return;
// 2. Get the current console mode
DWORD dwMode = 0;
if (!GetConsoleMode(hOut, &dwMode))
return;
// 3. Set the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag (using bitwise OR)
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
// 4. Apply the new console mode
if (!SetConsoleMode(hOut, dwMode)) // This is often the point of failure on older Windows versions (pre-Win10 v1511)
return;
m_vtModeEnabled = true;
}
//--------------------------------------------------------------
/* Settings initialization */
inline void ReLog::init()
{
m_colorLogMap[LogLevel::None] = sdi_toolBox::console::ANSI::EscapeCommand::clear();
m_colorLogMap[LogLevel::Dev] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Green, sdi_toolBox::console::ANSI::EscapeCommand::DIM);
m_colorLogMap[LogLevel::Comm] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Magenta);
m_colorLogMap[LogLevel::Debug] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow, sdi_toolBox::console::ANSI::EscapeCommand::DIM);
m_colorLogMap[LogLevel::Info] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::White);
m_colorLogMap[LogLevel::Warning] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Yellow);
m_colorLogMap[LogLevel::Error] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::Red, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT | sdi_toolBox::console::ANSI::EscapeCommand::BRIGHT);
m_colorLogMap[LogLevel::Message] = sdi_toolBox::console::ANSI::EscapeCommand::get(sdi_toolBox::console::ANSI::EscapeCommand::Color::White, sdi_toolBox::console::ANSI::EscapeCommand::LIGHT);
m_visibleLogMap[LogLevel::Dev] = true;
m_visibleLogMap[LogLevel::Comm] = true;
m_visibleLogMap[LogLevel::Debug] = true;
m_visibleLogMap[LogLevel::Info] = true;
m_visibleLogMap[LogLevel::Warning] = true;
m_visibleLogMap[LogLevel::Error] = true;
m_visibleLogMap[LogLevel::Message] = true;
}
//--------------------------------------------------------------
/* Print log messages */
inline std::string ReLog::createLogMessage(const Log &log, const bool isFile)
{
// Check if the message should be visible or not
if (m_visibleLogMap.contains(log.level) && m_visibleLogMap[log.level] == false)
return "";
std::ostringstream stream;
// Prepare message color
const auto levelStyle = (m_colorLogMap.contains(log.level)) ? m_colorLogMap[log.level] : sdi_toolBox::console::ANSI::EscapeCommand::clear();
const auto messageStyle = (!isFile) ? levelStyle : "";
if (!log.message.empty())
{
switch (log.level)
{
case LogLevel::Error:
stream << messageStyle << "[E] ";
break;
case LogLevel::Warning:
stream << messageStyle << "[W] ";
break;
case LogLevel::Info:
stream << messageStyle << "[I] ";
break;
case LogLevel::Debug:
stream << messageStyle << "[D] ";
break;
case LogLevel::Comm:
stream << messageStyle << "[C] ";
break;
case LogLevel::Dev:
stream << messageStyle << "[-] ";
break;
case LogLevel::Message:
default:;
stream << messageStyle;
break;
}
if (log.level != LogLevel::Message)
{
const auto local_tz = std::chrono::current_zone();
const auto local_time = std::chrono::zoned_time(local_tz, log.timestamp);
stream << std::format("{0:%T} - ", local_time);
}
}
// Print message
stream << log.message;
// Reset style
if (!messageStyle.empty())
stream << sdi_toolBox::console::ANSI::EscapeCommand::clear();
return stream.str();
}
//--------------------------------------------------------------
inline void ReLog::printDate()
{
const auto now = std::chrono::system_clock::now();
const auto local_tz = std::chrono::current_zone();
const auto local_time = std::chrono::zoned_time(local_tz, now);
const Log logMessage{ .level = LogLevel::Info,
.timestamp = now,
.message = std::format("current date: {0:%F}", local_time) };
log(logMessage);
}
//--------------------------------------------------------------
/* Save log messages in file */
inline void ReLog::saveToFile(const std::string &message)
{
// Remove escape sequences
std::string messageResult;
bool in_escape_sequence = false;
constexpr char ANSI_ESCAPE_CHAR = '\x1b';
for (const char c : message)
{
if (in_escape_sequence)
{
if (c == 'm')
in_escape_sequence = false; // End of the escape sequence
}
else if (c == ANSI_ESCAPE_CHAR)
in_escape_sequence = true; // Begin of the escape sequence
else
messageResult += c;
}
if (m_file.is_open())
m_file << messageResult << "\n";
}
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
class ReLogStreamProxy
{
public:
ReLogStreamProxy() = delete; // Constructor
explicit ReLogStreamProxy(const LogLevel &level); // Constructor
virtual ~ReLogStreamProxy(); // Destructor
ReLogStreamProxy(ReLogStreamProxy &obj) = delete; // Copy constructor
ReLogStreamProxy(ReLogStreamProxy &&obj) = delete; // Move constructor
ReLogStreamProxy &operator=(const ReLogStreamProxy &obj) = delete; // Copy assignment operator
ReLogStreamProxy &operator=(ReLogStreamProxy &&obj) = delete; // Move assignment operator
template<typename T>
ReLogStreamProxy &operator<<(const T &data); // Overload for various data types (int, string, double, etc.)
ReLogStreamProxy &operator<<(std::ostream &(*manip)(std::ostream &)); // Overload for std::endl and other manipulators
protected:
LogLevel m_level;
std::chrono::system_clock::time_point m_timestamp;
std::stringstream m_buffer;
private:
void flush(); // Pass the buffered content to the central Logger
};
//--------------------------------------------------------------
/* Constructor */
inline ReLogStreamProxy::ReLogStreamProxy(const LogLevel &level)
{
// Initialization
m_level = level;
}
//--------------------------------------------------------------
/* Destructor */
inline ReLogStreamProxy::~ReLogStreamProxy()
{
if (!m_buffer.str().empty())
flush();
}
//--------------------------------------------------------------
/* Overload for various data types (int, string, double, etc.) */
template<typename T>
inline ReLogStreamProxy &ReLogStreamProxy::operator<<(const T &data)
{
const auto now = std::chrono::system_clock::now();
if (m_buffer.str().empty())
m_timestamp = now;
m_buffer << data;
return *this;
}
//--------------------------------------------------------------
/* Overload for std::endl and other manipulators */
inline ReLogStreamProxy &ReLogStreamProxy::operator<<(std::ostream &(*manip)(std::ostream &))
{
if (manip == static_cast<std::ostream &(*)(std::ostream &)>(std::endl))
{
flush();
}
else
{
// Handle other manipulators if necessary (e.g., std::hex)
m_buffer << manip;
}
return *this;
}
//--------------------------------------------------------------
/* Pass the buffered content to the central Logger */
inline void ReLogStreamProxy::flush()
{
// Pass the buffered content to the central Logger
ReLog::getInstance().log(ReLog::Log{ .level = m_level,
.timestamp = m_timestamp,
.message = m_buffer.str() });
// Reset stream content
m_buffer.str("");
m_buffer.clear();
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::logs
//--------------------------------------------------------------
/*
* User interface
*/
inline sdi_toolBox::logs::ReLogStreamProxy LogError()
{
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Error);
}
//--------------------------------------------------------------
inline sdi_toolBox::logs::ReLogStreamProxy LogWarning()
{
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Warning);
}
//--------------------------------------------------------------
inline sdi_toolBox::logs::ReLogStreamProxy LogInfo()
{
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Info);
}
//--------------------------------------------------------------
inline sdi_toolBox::logs::ReLogStreamProxy LogDebug()
{
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Debug);
}
//--------------------------------------------------------------
inline sdi_toolBox::logs::ReLogStreamProxy LogComm()
{
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Comm);
}
//--------------------------------------------------------------
inline sdi_toolBox::logs::ReLogStreamProxy LogMessage()
{
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Message);
}
//--------------------------------------------------------------
inline sdi_toolBox::logs::ReLogStreamProxy LogDev()
{
return sdi_toolBox::logs::ReLogStreamProxy(sdi_toolBox::logs::LogLevel::Dev);
}
//--------------------------------------------------------------
inline void LogCode(const std::string &msg = "", const std::source_location &location = std::source_location::current())
{
LogDev() << msg << ((!msg.empty()) ? " - " : "") << "(" << location.file_name() << " l." << location.line() << ")" << std::endl;
}
//--------------------------------------------------------------

View File

@@ -0,0 +1,6 @@
#pragma once
/**
* @namespace sdi_ToolBox
* @brief Root namespace for SD-Innovation ToolBox utilities.
*/

View File

@@ -0,0 +1,94 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#include <wx/statline.h>
#include <wx/wx.h>
//--------------------------------------------------------------
inline wxStaticText *createTitleCtrl(wxWindow *parentWindow, const wxString &title, const long style = 0)
{
const auto ctrl = new wxStaticText(parentWindow,
wxID_ANY,
title,
wxDefaultPosition,
wxDefaultSize,
style);
ctrl->SetFont(ctrl->GetFont().Italic());
return ctrl;
}
//--------------------------------------------------------------
inline wxStaticText *createLabelCtrl(wxWindow *parentWindow, const wxString &title = wxEmptyString, const long style = 0)
{
const auto ctrl = new wxStaticText(parentWindow,
wxID_ANY,
title,
wxDefaultPosition,
wxDefaultSize,
style);
return ctrl;
}
//--------------------------------------------------------------
inline wxTextCtrl *createTextCtrl(wxWindow *parentWindow, const wxString &value = wxEmptyString, const long style = 0)
{
const auto ctrl = new wxTextCtrl(parentWindow,
wxID_ANY,
value,
wxDefaultPosition,
wxDefaultSize,
style);
return ctrl;
}
//--------------------------------------------------------------
inline wxButton *createButtonCtrl(wxWindow *parentWindow, const wxString &label, const long style = 0)
{
const auto ctrl = new wxButton(parentWindow,
wxID_ANY,
label,
wxDefaultPosition,
wxDefaultSize,
style);
return ctrl;
}
//--------------------------------------------------------------
inline wxBitmapButton *createBitmapButtonCtrl(wxWindow *parentWindow, const wxBitmap &bmp, const long style = 0)
{
const auto ctrl = new wxBitmapButton(parentWindow,
wxID_ANY,
bmp,
wxDefaultPosition,
wxDefaultSize,
style);
return ctrl;
}
//--------------------------------------------------------------
inline wxStaticLine *createVLineCtrl(wxWindow *parentWindow)
{
const auto ctrl = new wxStaticLine(parentWindow,
wxID_ANY,
wxDefaultPosition,
wxDefaultSize,
wxVERTICAL);
return ctrl;
}
//--------------------------------------------------------------
inline wxStaticLine *createHLineCtrl(wxWindow *parentWindow)
{
const auto ctrl = new wxStaticLine(parentWindow,
wxID_ANY,
wxDefaultPosition,
wxDefaultSize,
wxHORIZONTAL);
return ctrl;
}
//--------------------------------------------------------------