diff --git a/common/ipc/ScopedFileLock.h b/common/ipc/ScopedFileLock.h index d722f5d..203559c 100644 --- a/common/ipc/ScopedFileLock.h +++ b/common/ipc/ScopedFileLock.h @@ -6,7 +6,7 @@ #include "exception.h" #include "const.h" - +#include "String.h" namespace Exchange::Ipc { @@ -18,23 +18,23 @@ namespace Exchange::Ipc { */ class ScopedFileLock { int mFd; - std::string mPath; + Core::String mPath; public: /** * The constructor ensures that Producers only fight other Producers, and Consumers only fight other Consumers */ - ScopedFileLock(const std::string& name, bool isProducer) { + ScopedFileLock(const Core::String& name, bool isProducer) { - mPath = std::string(LOCK_BASE_PATH) + name + (isProducer ? ".prod.lock" : ".cons.lock"); + mPath = Core::String(LOCK_BASE_PATH) + name + (isProducer ? ".prod.lock" : ".cons.lock"); // Open lock file (create if needs be) // O_RDWR: read and write // O_CREAT: create if file does not exsits // O_CLOEXEC: close the file desciptoo when process exists - mFd = open(mPath.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666); + mFd = open(mPath.get(), O_RDWR | O_CREAT | O_CLOEXEC, 0666); if (mFd < 0) { - ENG_THROW("Failed to open lock file: %s", mPath.c_str()); + ENG_THROW("Failed to open lock file: %s", mPath.get()); } // LOCK_EX (Exclusive): This tells the Linux Kernel: "Only one file descriptor can hold a @@ -43,7 +43,7 @@ namespace Exchange::Ipc { // Fail immediately." if (flock(mFd, LOCK_EX | LOCK_NB) < 0) { close(mFd); - ENG_THROW("Highlander Rule Violation: Another process holds the lock %s", mPath.c_str()); + ENG_THROW("Highlander Rule Violation: Another process holds the lock %s", mPath.get()); } } diff --git a/common/ipc/SharedMemory.cpp b/common/ipc/SharedMemory.cpp index c34938d..b8aaac0 100644 --- a/common/ipc/SharedMemory.cpp +++ b/common/ipc/SharedMemory.cpp @@ -4,7 +4,7 @@ namespace Exchange::Ipc { - SharedMemory::SharedMemory(const std::string& name, uint32_t capacity, bool create) + SharedMemory::SharedMemory(const Core::String& name, uint32_t capacity, bool create) : mName("/" + name), mIsOwner(create) { // Calculate total size @@ -22,13 +22,13 @@ namespace Exchange::Ipc { // reference counting. The physical RAM for that segment is not actually freed until every process (including // any lingering Consumers) has closed it or exited. However, since the name is gone, the Producer is free to // create a brand new segment with the same name immediately. - shm_unlink(name.c_str()); + shm_unlink(name.get()); // Creates a new shared memory object. // O_RDWR — open read/write. // 0666 — permissions: read/write for all users. // Returns a file descriptor in shmFd. - mFd = shm_open(name.c_str(), O_CREAT | O_RDWR, 0666); + mFd = shm_open(name.get(), O_CREAT | O_RDWR, 0666); if (mFd == -1) { ENG_THROW("Producer: shm_open failed (create)"); } @@ -41,7 +41,7 @@ namespace Exchange::Ipc { } else { // Consumer: Open existing - mFd = shm_open(name.c_str(), O_RDWR, 0666); + mFd = shm_open(name.get(), O_RDWR, 0666); if (mFd == -1) { ENG_THROW("Consumer: shm_open failed (open existing)"); } diff --git a/common/ipc/SharedMemory.h b/common/ipc/SharedMemory.h index 861d0a2..9b415cd 100644 --- a/common/ipc/SharedMemory.h +++ b/common/ipc/SharedMemory.h @@ -7,6 +7,7 @@ #include "const.h" #include "ScopedFileLock.h" +#include "String.h" namespace Exchange::Ipc { @@ -33,12 +34,12 @@ namespace Exchange::Ipc { }; // class SharedHeader // Helper to generate uuid - inline std::string generateUuid() { + inline Core::String generateUuid() { static std::random_device rd; static std::mt19937 gen(rd()); static std::uniform_int_distribution<> dis(0, 15); static const char* v = "0123456789abcdef"; - std::string uuid; + Core::String uuid; for (int i = 0; i < 36; ++i) { if (i == 8 || i == 13 || i == 18 || i == 23) { uuid += '-'; @@ -52,7 +53,7 @@ namespace Exchange::Ipc { class SharedMemory { protected: - std::string mName; ///> Name of the shared memory object + Core::String mName; ///> Name of the shared memory object int mFd; ///> File descriptor for the shared memory object size_t mTotalSize; ///> Total size of the shared memory segment void* mBasePtr; ///> Base pointer to the mapped shared memory @@ -66,7 +67,7 @@ namespace Exchange::Ipc { * @note * POSIX shared memory objects must start with '/' */ - SharedMemory(const std::string& name, uint32_t capacity, bool create); + SharedMemory(const Core::String& name, uint32_t capacity, bool create); }; // class SharedMemory @@ -81,7 +82,7 @@ namespace Exchange::Ipc { * SharedMemory(.., true) means this is the creator (producer) of the shared memory segment. * mLock(true) means this is a producer lock. */ - Producer(const std::string& name, uint32_t capacity = BUFFER_CAPACITY); + Producer(const Core::String& name, uint32_t capacity = BUFFER_CAPACITY); /** * @brief Write data into the ring buffer. @@ -110,12 +111,12 @@ namespace Exchange::Ipc { * SharedMemory(.., false) means this is the creator (producer) of the shared memory segment. * mLock(false) means this is a consumer lock. */ - Consumer(const std::string& name, uint32_t capacity = BUFFER_CAPACITY); + Consumer(const Core::String& name, uint32_t capacity = BUFFER_CAPACITY); // Returns bytes read, or 0 if empty uint32_t read(void* buffer, uint32_t bufferSize); - std::string getSessionUuid() const; + Core::String getSessionUuid() const; }; // class Consumer diff --git a/common/ipc/SharedMemory_Consumer.cpp b/common/ipc/SharedMemory_Consumer.cpp index 6961f5d..6c88167 100644 --- a/common/ipc/SharedMemory_Consumer.cpp +++ b/common/ipc/SharedMemory_Consumer.cpp @@ -3,7 +3,7 @@ namespace Exchange::Ipc { - Consumer::Consumer(const std::string& name, uint32_t capacity) + Consumer::Consumer(const Core::String& name, uint32_t capacity) : SharedMemory(name, capacity, false), mLock(name, false) { @@ -12,7 +12,7 @@ namespace Exchange::Ipc { ENG_THROW("Invalid IPC Header Signature"); } - const std::string uuidPath = "/tmp/"+mName+".uuid"; + const Core::String uuidPath = "/tmp/" + mName +".uuid"; std::ifstream f(uuidPath); if (!f.is_open()) { ENG_THROW("UUID file not found"); @@ -51,8 +51,8 @@ namespace Exchange::Ipc { return msgLen; } - std::string Consumer::getSessionUuid() const { - return std::string(mHeader->uuid); + Core::String Consumer::getSessionUuid() const { + return Core::String(mHeader->uuid); } } // namespace Exchange::Ipc \ No newline at end of file diff --git a/common/ipc/SharedMemory_Producer.cpp b/common/ipc/SharedMemory_Producer.cpp index e21c337..6d8ff6c 100644 --- a/common/ipc/SharedMemory_Producer.cpp +++ b/common/ipc/SharedMemory_Producer.cpp @@ -3,20 +3,20 @@ namespace Exchange::Ipc { - Producer::Producer(const std::string& name, uint32_t capacity) : SharedMemory(name, capacity, true), mLock(name, true) { + Producer::Producer(const Core::String& name, uint32_t capacity) : SharedMemory(name, capacity, true), mLock(name, true) { // Initialize header std::memset(mHeader, 0, sizeof(SharedHeader)); std::strncpy(mHeader->signature, MAGIC, 32); // Set magic signature // Generate and set UUID - std::string sessionUuid = generateUuid(); + Core::String sessionUuid = generateUuid(); - const std::string uuidPath = "/tmp/"+mName+".uuid"; - std::ofstream f(uuidPath, std::ios::trunc); + const Core::String uuidPath = "/tmp/" + mName + ".uuid"; + std::ofstream f(uuidPath.get(), std::ios::trunc); f << sessionUuid; f.close(); - std::strncpy(mHeader->uuid, sessionUuid.c_str(), 37); + std::strncpy(mHeader->uuid, sessionUuid.get(), 37); mHeader->capacity = capacity; mHeader->maxMsgSize = MAX_MSG_SIZE; mHeader->writeIdx = 0; diff --git a/common/ipc/String.h b/common/ipc/String.h new file mode 100644 index 0000000..599fb30 --- /dev/null +++ b/common/ipc/String.h @@ -0,0 +1,399 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Exchange::Core { + + class String { + public: + using sizeType = std::size_t; + static constexpr sizeType npos = static_cast(-1); + + private: + char* mBuffer; + sizeType mSize; + + static const char& nullChar() + { + static const char null = '\0'; + return null; + } + + public: + + // ------------------------------------ ------------------------------------ + + /** @brief Constructor */ + String() : mBuffer(nullptr), mSize(0) {} + + /** @brief Constructor */ + String(const char* str) { + if (str && *str) { + mSize = std::strlen(str); + mBuffer = new char[mSize + 1]; + std::memcpy(mBuffer, str, mSize + 1); + } + else { + mBuffer = nullptr; + mSize = 0; + } + } + + /** @brief Constructor */ + String(const char* str, sizeType len) { + if (str && len > 0) { + mSize = len; + mBuffer = new char[mSize + 1]; + std::memcpy(mBuffer, str, mSize); + mBuffer[mSize] = nullChar(); + } + else { + mBuffer = nullptr; + mSize = 0; + } + } + + /** @brief Constructor */ + String(const std::string& str) { + mSize = std::strlen(str.c_str()); + if (mSize > 0) { + mBuffer = new char[mSize + 1]; + std::memcpy(mBuffer, str.c_str(), mSize + 1); + } + else { + mBuffer = nullptr; + } + } + + /** @brief Copy constructor */ + String(const String& other) { + if (other.mBuffer) { + mSize = other.mSize; + mBuffer = new char [mSize + 1]; + std::memcpy(mBuffer, other.mBuffer, mSize + 1); + } + else { + mBuffer = nullptr; + mSize = 0; + } + } + + /** @brief Move constructor */ + String(String&& other) : mBuffer(other.mBuffer), mSize(other.mSize) { + other.mBuffer = nullptr; + other.mSize = 0; + } + + /** @brief Fill constructor */ + String(sizeType count, char ch) { + if (count > 0) { + mSize = count; + mBuffer = new char[mSize + 1]; + std::memset(mBuffer, ch, mSize); + mBuffer[mSize] = '\0'; + } else { + mBuffer = nullptr; + mSize = 0; + } + } + + /** @brief Destructor */ + ~String() { + delete[] mBuffer; + mSize = 0; + } + + // ------------------------------------ ------------------------------------ + + /** @brief Copy assignment */ + String& operator=(const String& other) { + if (this != &other) { + String temp(other); + swap(temp); + } + return *this; + } + + /** @brief Move assignment */ + String& operator=(String&& other) { + if (this != &other) { + delete[] mBuffer; + mBuffer = other.mBuffer; + mSize = other.mSize; + other.mBuffer = nullptr; + other.mSize = 0; + } + return *this; + } + + /** @brief From C-string */ + String& operator=(const char* str) { + String temp(str); + swap(temp); + return *this; + } + + // ------------------------------------ ------------------------------------ + + operator const char*() const { + return mBuffer ? mBuffer : ""; + } + + const char* get() const { + return mBuffer ? mBuffer : ""; + } + + char* get() { + return mBuffer; + } + + std::string toString() const { + return static_cast(mBuffer); + } + + // ------------------------------------ ------------------------------------ + + sizeType size() const noexcept { + return mSize; + } + + bool empty() const noexcept { + return mSize == 0; + } + + // ------------------------------------ ------------------------------------ + + char& operator[](sizeType pos) noexcept { + return mBuffer[pos]; + } + + const char& operator[](sizeType pos) const noexcept { + return mBuffer ? mBuffer[pos] : nullChar(); + } + + char& at(sizeType pos) { + if (pos >= mSize) { + throw std::out_of_range("String::at: index out of range"); + } + return mBuffer[pos]; + } + + const char& at(sizeType pos) const { + if (pos >= mSize) { + throw std::out_of_range("String::at: index out of range"); + } + return mBuffer[pos]; + } + + char& front() noexcept { + return mBuffer[0]; + } + + const char& front() const noexcept { + return mBuffer ? mBuffer[0] : nullChar(); + } + + char& back() noexcept { + return mBuffer[mSize - 1]; + } + + const char& back() const noexcept { + return mBuffer && mSize > 0 ? mBuffer[mSize - 1] : nullChar(); + } + + // ------------------------------------ ------------------------------------ + + void clear() noexcept { + delete[] mBuffer; + mBuffer = nullptr; + mSize = 0; + } + + String& append(const char* str) { + if (str && *str) { + sizeType str_len = std::strlen(str); + sizeType new_size = mSize + str_len; + char* new_buffer = new char[new_size + 1]; + + if (mBuffer) { + std::memcpy(new_buffer, mBuffer, mSize); + } + std::memcpy(new_buffer + mSize, str, str_len + 1); + + delete[] mBuffer; + mBuffer = new_buffer; + mSize = new_size; + } + return *this; + } + + String& append(const String& str) { + return append(str.mBuffer); + } + + String& append(char ch) { + char temp[2] = {ch, nullChar()}; + return append(temp); + } + + String& append(const char* str, sizeType count) { + if (str && count > 0) { + sizeType new_size = mSize + count; + char* new_buffer = new char[new_size + 1]; + + if (mBuffer) { + std::memcpy(new_buffer, mBuffer, mSize); + } + std::memcpy(new_buffer + mSize, str, count); + new_buffer[new_size] = nullChar(); + + delete[] mBuffer; + mBuffer = new_buffer; + mSize = new_size; + } + return *this; + } + + String& operator+=(const String& str) { + return append(str); + } + + String& operator+=(const char* str) { + return append(str); + } + + String& operator+=(char ch) { + return append(ch); + } + + + + friend String operator+(const String& lhs, const String& rhs) { + String result; + result.mSize = lhs.mSize + rhs.mSize; + result.mBuffer = new char[result.mSize + 1]; + + if (lhs.mBuffer) { + std::memcpy(result.mBuffer, lhs.mBuffer, lhs.mSize); + } + if (rhs.mBuffer) { + std::memcpy(result.mBuffer + lhs.mSize, rhs.mBuffer, rhs.mSize); + } + result.mBuffer[result.mSize] = '\0'; + + return result; + } + + friend String operator+(String&& lhs, const String& rhs) { + lhs.append(rhs); + return std::move(lhs); + } + + friend String operator+(const String& lhs, String&& rhs) { + String result(lhs); + result.append(rhs); + return result; + } + + friend String operator+(String&& lhs, String&& rhs) { + lhs.append(rhs); + return std::move(lhs); + } + + friend String operator+(const String& lhs, const char* rhs) { + String result(lhs); + result.append(rhs); + return result; + } + + friend String operator+(const char* lhs, const String& rhs) { + String result(lhs); + result.append(rhs); + return result; + } + + friend String operator+(const String& lhs, char rhs) { + String result(lhs); + result.append(rhs); + return result; + } + + friend String operator+(char lhs, const String& rhs) { + String result(1, lhs); + result.append(rhs); + return result; + } + + friend String operator+(String&& lhs, const char* rhs) { + lhs.append(rhs); + return std::move(lhs); + } + + friend String operator+(String&& lhs, char rhs) { + lhs.append(rhs); + return std::move(lhs); + } + + // ------------------------------------ ------------------------------------ + + int compare(const String& other) const noexcept { + if (!mBuffer && !other.mBuffer) return 0; + if (!mBuffer) return -1; + if (!other.mBuffer) return 1; + return std::strcmp(mBuffer, other.mBuffer); + } + + int compare(const char* str) const noexcept { + if (!mBuffer && !str) return 0; + if (!mBuffer) return -1; + if (!str) return 1; + return std::strcmp(mBuffer, str); + } + + friend bool operator==(const String& lhs, const String& rhs) noexcept { + return lhs.compare(rhs) == 0; + } + + friend bool operator==(const String& lhs, const char* rhs) noexcept { + return lhs.compare(rhs) == 0; + } + + friend bool operator==(const char* lhs, const String& rhs) noexcept { + return rhs.compare(lhs) == 0; + } + + + + // ------------------------------------ ------------------------------------ + + sizeType find(const char* str, sizeType pos = 0) const noexcept { + if (!mBuffer || !str || pos >= mSize) { + return npos; + } + const char* result = std::strstr(mBuffer + pos, str); + return result ? static_cast(result - mBuffer) : npos; + } + + sizeType find(char ch, sizeType pos = 0) const noexcept { + if (!mBuffer || pos >= mSize) { + return npos; + } + const char* result = std::strchr(mBuffer + pos, ch); + return result ? static_cast(result - mBuffer) : npos; + } + + sizeType find(const String& str, sizeType pos = 0) const noexcept { + return find(str.mBuffer, pos); + } + + void swap(String& other) { + std::swap(mBuffer, other.mBuffer); + std::swap(mSize, other.mSize); + } + }; +} \ No newline at end of file diff --git a/tests/test_ipc_crash.cpp b/tests/test_ipc_crash.cpp index cc0b98a..c903142 100644 --- a/tests/test_ipc_crash.cpp +++ b/tests/test_ipc_crash.cpp @@ -173,11 +173,11 @@ bool TEST3_producerCrashRecovery() { std::this_thread::sleep_for(std::chrono::milliseconds(200)); // Connect to first producer - std::string firstUuid; + Exchange::Core::String firstUuid; { Consumer consumer(queueName, 64); firstUuid = consumer.getSessionUuid(); - log("Consumer[Child]", "Connected to producer 1, UUID: " + firstUuid, GREEN); + log("Consumer[Child]", "Connected to producer 1, UUID: " + firstUuid.toString(), GREEN); // Read a message uint8_t buffer[4096]; @@ -206,13 +206,13 @@ bool TEST3_producerCrashRecovery() { try { Consumer consumer(queueName, 64); // If we get here, either the UUID matches (bad) or we're connecting to new session (good) - std::string secondUuid = consumer.getSessionUuid(); + Exchange::Core::String secondUuid = consumer.getSessionUuid(); if (secondUuid == firstUuid) { log("Consumer[Child]", "ERROR: Still on old UUID!", RED); exit(1); } } catch (const Engine::EngException& e) { - std::string errMsg = e.what(); + Exchange::Core::String errMsg = e.what(); if (errMsg.find("Stale") != std::string::npos) { detectedStale = true; log("Consumer[Child]", "Detected stale queue as expected", GREEN); @@ -223,8 +223,8 @@ bool TEST3_producerCrashRecovery() { std::this_thread::sleep_for(std::chrono::milliseconds(300)); { Consumer consumer(queueName, 64); - std::string secondUuid = consumer.getSessionUuid(); - log("Consumer[Child]", "Connected to producer 2, UUID: " + secondUuid, GREEN); + Exchange::Core::String secondUuid = consumer.getSessionUuid(); + log("Consumer[Child]", "Connected to producer 2, UUID: " + secondUuid.toString(), GREEN); if (secondUuid == firstUuid) { log("Consumer[Child]", "ERROR: UUIDs should be different!", RED);