From d4d14e05fe541917c7b02d9e553fc353752c33e8 Mon Sep 17 00:00:00 2001 From: vasu <92209437+vaasu2002@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:15:11 +0000 Subject: [PATCH 1/4] [Exchange-3.1.1] feat: added blocking queue --- common/BlockingQueue/IBlockingQueue.h | 30 ++++++ common/BlockingQueue/MutexBlockingQueue.h | 125 ++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 common/BlockingQueue/IBlockingQueue.h create mode 100644 common/BlockingQueue/MutexBlockingQueue.h diff --git a/common/BlockingQueue/IBlockingQueue.h b/common/BlockingQueue/IBlockingQueue.h new file mode 100644 index 0000000..b64e7a6 --- /dev/null +++ b/common/BlockingQueue/IBlockingQueue.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + + +namespace Exchange::Core { + + /* + * @class IBlockingQueue + * @brief + * A thread-safe blocking queue with a fixed capacity. It automates synchronization + * between producer and consumer threads. + */ + template + class IBlockingQueue { + public: + virtual ~IBlockingQueue() = default; + + // blocking + virtual void push(const T& value) = 0; + virtual void push(T&& value) = 0; + virtual T pop() = 0; + virtual bool pop(T& out) = 0; + + // lifecycle + virtual void close() = 0; + virtual bool isClosed() const = 0; + }; +} \ No newline at end of file diff --git a/common/BlockingQueue/MutexBlockingQueue.h b/common/BlockingQueue/MutexBlockingQueue.h new file mode 100644 index 0000000..82dbe06 --- /dev/null +++ b/common/BlockingQueue/MutexBlockingQueue.h @@ -0,0 +1,125 @@ +#pragma once + +#include "IBlockingQueue.h" +#include "Exception.h" + +namespace Exchange::Core { + + /** + * @class MutexBlockingQueue + * @brief A thread-safe blocking queue with a fixed capacity. It automates synchronization between producer and consumer threads. + * + * All are write operations The condition check is not a pure read, because it controls a write Even the act of deciding whether to block is tied to shared state so always unique lock. + */ + template + class MutexBlockingQueue : public IBlockingQueue { + std::queue m_Queue; ///> Internal queue to hold elements + std::size_t m_Capacity; ///> Maximum capacity of the queue + bool m_Closed; ///> Flag indicating if the queue is closed for pushing new elements + + mutable std::mutex m_Mutex; ///> Mutex for synchronizing access to the queue + std::condition_variable m_NotFullCv; ///> Condition variable to signal when the queue is not full + std::condition_variable m_NotEmptyCv; ///> Condition variable to signal when the queue is not empty + public: + + explicit MutexBlockingQueue (std::size_t capacity) : m_Capacity(capacity) { + if (m_Capacity == 0) { + ENG_THROW("Capacity must be > 0"); + } + } + + // Disabling copy constructor + MutexBlockingQueue (const MutexBlockingQueue &) = delete; + // Disabling copy operation + MutexBlockingQueue & operator=(const MutexBlockingQueue &) = delete; + + /** + * @brief Puts an item(l value) into a queue. blocks if the queue is full + * @throws std::runtime_error if queue is closed. + */ + void push(const T& value) override { + + std::unique_lock lock(m_Mutex); + + // Wait until there is space or closed is triggered + m_NotFullCv.wait(lock, [&] { + return (m_Queue.size() < m_Capacity) || m_Closed; // wake-up condition + }); + + if (m_Closed) { + ENG_THROW("Push on closed."); + } + + m_Queue.push(value); + m_NotEmptyCv.notify_one(); + } + + /** + * @brief Puts an item(r value) into a queue. blocks if the queue is full + * @throws std::runtime_error if queue is closed. + */ + void push(T&& value) override { + std::unique_lock lock(m_Mutex); + + // Wait until there is space or closed is triggered + m_NotFullCv.wait(lock, [&] { + return (m_Queue.size() < m_Capacity) || m_Closed; // wake-up condition + }); + + if (m_Closed) { + ENG_THROW("Push on closed."); + } + + m_Queue.push(std::move(value)); + m_NotEmptyCv.notify_one(); + } + + + /** + * @brief Closes the queue. No further push operations are allowed. + * Notifies all waiting threads. + * @param out The variable to store the popped item. + * @return true if an item was successfully popped, false if the queue is closed and empty. + */ + bool pop(T& out) override { + std::unique_lock lock(m_Mutex); + + // Wait until here is data in the ueue or closed is triggered + m_NotEmptyCv.wait(lock, [&] { + return (!m_Queue.empty() || m_Closed); + }); + + // To check if we have data to pop after being notified coz of closed + if (m_Queue.empty()) { + // closed and empty + return false; + } + + out = std::move(m_Queue.front()); + m_Queue.pop(); + m_NotFullCv.notify_one(); + return true; + } + + + T pop() override { + T value; + if (!pop(value)) { + ENG_THROW("pop on closed and empty"); + } + return value; + } + + void close() override { + std::lock_guard lock(m_Mutex); + m_Closed = true; + m_NotEmptyCv.notify_all(); + m_NotFullCv.notify_all(); + } + + bool isClosed() const override { + std::lock_guard lock(m_Mutex); + return m_Closed; + } + }; +} \ No newline at end of file From 5a5ef5aee3c883d088b7454ea5c7450bd405d342 Mon Sep 17 00:00:00 2001 From: vasu <92209437+vaasu2002@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:16:11 +0000 Subject: [PATCH 2/4] [Exchange-3] refactor: Made logging and exception better --- common/{exception.h => Exception.h} | 0 common/Logger/Appender.h | 44 +++++++++++++++ common/Logger/Config.h | 28 ++++++++++ common/Logger/Filter.h | 24 +++++++++ common/Logger/Log.h | 83 +++++++++++++++++++++++++++++ common/ipc/ScopedFileLock.h | 2 +- process2/main.cpp | 2 +- 7 files changed, 181 insertions(+), 2 deletions(-) rename common/{exception.h => Exception.h} (100%) create mode 100644 common/Logger/Appender.h create mode 100644 common/Logger/Config.h create mode 100644 common/Logger/Filter.h create mode 100644 common/Logger/Log.h diff --git a/common/exception.h b/common/Exception.h similarity index 100% rename from common/exception.h rename to common/Exception.h diff --git a/common/Logger/Appender.h b/common/Logger/Appender.h new file mode 100644 index 0000000..8991271 --- /dev/null +++ b/common/Logger/Appender.h @@ -0,0 +1,44 @@ +#pragma once +#include "Log.h" + +namespace Exchange::Core { + + + class IAppender { + public: + virtual ~IAppender() = default; + virtual void append(const Log& log) = 0; + }; + + + class ConsoleAppender : public IAppender { + void append(const Log& log) override { + std::printf(log.toString()); + } + }; + + class FileAppender : public IAppender { + String path; + String name; + public: + /** @brief Constructor */ + FileAppender(String path, String name) + : path(std::move(path)), name(std::move(name)) {} + + void append(const Log& log) override { + // todo + } + }; + + + class DatabaseAppender : public IAppender { + Core::String url; + Core::String dbName; + Core::String password; + Core::String tableName; + public: + void append(const Log& log) override { + // todo + } + }; +} \ No newline at end of file diff --git a/common/Logger/Config.h b/common/Logger/Config.h new file mode 100644 index 0000000..f2959b2 --- /dev/null +++ b/common/Logger/Config.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include "Appender.h" +#include "Filter.h" + +namespace Exchange::Core { + + class Config { + std::map mAppenders; + std::map> mFilters; + public: + Config() = default; + void addAppender(IAppender* appender) { + mAppenders.insert({mAppenders.size(),std::move(appender)}); + } + void addAppender(IAppender* appender, IFilter* filter) { + mAppenders.insert({mAppenders.size(),std::move(appender)}); + mFilters.insert({mAppenders.size()-1, std::vector{filter}}); + } + void addAppender(IAppender* appender, std::vector filters) { + mAppenders.insert({mAppenders.size(),std::move(appender)}); + mFilters.insert({mAppenders.size()-1, filters}); + } + }; + +} \ No newline at end of file diff --git a/common/Logger/Filter.h b/common/Logger/Filter.h new file mode 100644 index 0000000..3ad9bf3 --- /dev/null +++ b/common/Logger/Filter.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Log.h" + + +namespace Exchange::Core { + + class IFilter { + public: + virtual ~IFilter() = default; + virtual bool filter(const Log& log) = 0; + }; + + + class LevelFilter : public IFilter { + LogLevel minLevel; + public: + /** @brief Constructor */ + LevelFilter(LogLevel minLevel): minLevel(minLevel) {} + bool filter(const Log& log) override { + return log.getLevel() > minLevel; + } + }; +}; diff --git a/common/Logger/Log.h b/common/Logger/Log.h new file mode 100644 index 0000000..473bd35 --- /dev/null +++ b/common/Logger/Log.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "String.h" + +namespace Exchange::Core { + + enum class LogLevel : uint8_t { + TRACE, + DEBUG, + INFO, + WARNING, + ERROR, + FATAL + }; + + class LogHelper { + public: + static std::string levelToString(LogLevel level) { + switch(level) { + case LogLevel::TRACE: return "TRACE"; + case LogLevel::DEBUG: return "DEBUG"; + case LogLevel::INFO: return "INFO"; + case LogLevel::WARNING: return "WARNING"; + case LogLevel::ERROR: return "ERROR"; + case LogLevel::FATAL: return "FATAL"; + } + return levelToString(LogLevel::INFO); // default + } + }; + + class Log { + std::tm mTimestamp; + LogLevel mLevel; + Core::String mMessage; + Core::String mFile; + Core::String mFunc; + uint64_t mLine; + + public: + + /** @brief Constructor */ + Log(LogLevel level, const std::string& message, const std::string& file, const std::string& function, uint64_t line) + :mLevel(level), mMessage(message), mFile(file), mFunc(function), mLine(line) { + + // Timestamp + auto now = std::chrono::system_clock::now(); + std::time_t t = std::chrono::system_clock::to_time_t(now); + + #ifdef _WIN32 + localtime_s(&mTimestamp, &t); + #else + localtime_r(&t, &mTimestamp); + #endif + } + + Core::String toString() const { + std::ostringstream oss; + oss << "[" << std::put_time(&mTimestamp, "%Y-%m-%d %H:%M:%S") << "]" + << "[" << LogHelper::levelToString(mLevel) << "]" + << "[" << mFunc << "]" + << mFile << ":" << mLine << " - " + << mMessage; + return oss.str(); + } + + LogLevel getLevel() const { + return mLevel; + } + }; + + +} \ No newline at end of file diff --git a/common/ipc/ScopedFileLock.h b/common/ipc/ScopedFileLock.h index 203559c..8751dde 100644 --- a/common/ipc/ScopedFileLock.h +++ b/common/ipc/ScopedFileLock.h @@ -4,7 +4,7 @@ #include #include -#include "exception.h" +#include "Exception.h" #include "const.h" #include "String.h" diff --git a/process2/main.cpp b/process2/main.cpp index 756415c..c63e3f6 100644 --- a/process2/main.cpp +++ b/process2/main.cpp @@ -6,7 +6,7 @@ #include #include "messaging.h" #include "enum.h" -#include "exception.h" +#include "Exception.h" #include "ipc/SharedMemory.h" int main(int argc, char* argv[]) { From 3fa27de223586e8d961df517b0846e7867610d7d Mon Sep 17 00:00:00 2001 From: vasu <92209437+vaasu2002@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:17:02 +0000 Subject: [PATCH 3/4] [Exchange-3] feat(prototyping): Add gateway working prototype with test cases. --- .github/workflows/build.yml | 49 ++++- .gitignore | 3 +- CMakeLists.txt | 27 ++- Gateway/Fix.h | 268 ++++++++++++++++++++++++ Gateway/main.cpp | 63 ++++++ process1/Sequencer.h | 12 -- process1/main.cpp | 68 ------- process1/process1 | Bin 16456 -> 0 bytes process2/process2 | Bin 16456 -> 0 bytes run_tests.sh | 104 ++++++++++ tests/test_gateway.cpp | 396 ++++++++++++++++++++++++++++++++++++ 11 files changed, 897 insertions(+), 93 deletions(-) create mode 100644 Gateway/Fix.h create mode 100644 Gateway/main.cpp delete mode 100644 process1/Sequencer.h delete mode 100644 process1/main.cpp delete mode 100755 process1/process1 delete mode 100755 process2/process2 create mode 100755 run_tests.sh create mode 100644 tests/test_gateway.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e90f5f8..2a5567b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,13 +27,48 @@ jobs: run: | cmake --build build --config Release -- -j$(nproc) - # If you use CTest (add_test in CMake), you can enable this step. - # - name: Run CTest - # run: | - # cd build - # ctest --output-on-failure - - - name: Run test_ipc_crash + - name: Run IPC Crash Recovery Test run: | chmod +x build/test_ipc_crash || true ./build/test_ipc_crash + + - name: Start Gateway in Background + run: | + chmod +x build/Gateway || true + ./build/Gateway & + GATEWAY_PID=$! + echo "GATEWAY_PID=$GATEWAY_PID" >> $GITHUB_ENV + # Wait for gateway to start listening + sleep 2 + + - name: Run Gateway Network Tests + run: | + chmod +x build/test_gateway || true + ./build/test_gateway + TEST_RESULT=$? + exit $TEST_RESULT + + - name: Cleanup Gateway Process + if: always() + run: | + if [ ! -z "$GATEWAY_PID" ]; then + kill $GATEWAY_PID || true + wait $GATEWAY_PID 2>/dev/null || true + fi + # Kill any remaining Gateway processes + pkill -f "./build/Gateway" || true + + - name: Run CTest (Optional) + if: false # Disabled for now, enable if you want to use CTest + run: | + cd build + ctest --output-on-failure + + - name: Upload Test Logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-logs + path: | + build/Testing/Temporary/ + *.log \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1899660..28d9a27 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build -.vscode \ No newline at end of file +.vscode +*.log \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ce18bf..632fe68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,11 +14,12 @@ set(IPC_SOURCES common/ipc/SharedMemory_Consumer.cpp ) -# Process 1 executable -add_executable(process1 process1/main.cpp ${IPC_SOURCES}) -target_include_directories(process1 PRIVATE ${CMAKE_SOURCE_DIR}/common) -target_include_directories(process1 PRIVATE ${CMAKE_SOURCE_DIR}/common/ipc) -target_include_directories(process1 PRIVATE ${CMAKE_SOURCE_DIR}/common/Logger) +# Process 1 executable (Gateway) +add_executable(Gateway Gateway/main.cpp ${IPC_SOURCES}) +target_include_directories(Gateway PRIVATE ${CMAKE_SOURCE_DIR}/common) +target_include_directories(Gateway PRIVATE ${CMAKE_SOURCE_DIR}/common/ipc) +target_include_directories(Gateway PRIVATE ${CMAKE_SOURCE_DIR}/common/Logger) +target_link_libraries(Gateway pthread) # Process 2 executable add_executable(process2 process2/main.cpp ${IPC_SOURCES}) @@ -30,3 +31,19 @@ target_include_directories(process2 PRIVATE ${CMAKE_SOURCE_DIR}/common/Logger) add_executable(test_ipc_crash tests/test_ipc_crash.cpp ${IPC_SOURCES}) target_include_directories(test_ipc_crash PRIVATE ${CMAKE_SOURCE_DIR}/common) target_include_directories(test_ipc_crash PRIVATE ${CMAKE_SOURCE_DIR}/common/ipc) + +# Test executable - Gateway Network and FIX Protocol Test +add_executable(test_gateway tests/test_gateway.cpp) +target_include_directories(test_gateway PRIVATE ${CMAKE_SOURCE_DIR}/common) +target_include_directories(test_gateway PRIVATE ${CMAKE_SOURCE_DIR}/common/Logger) + +# Enable testing +enable_testing() + +# Add tests to CTest +add_test(NAME IPC_Crash_Recovery COMMAND test_ipc_crash) +add_test(NAME Gateway_Network_Tests COMMAND test_gateway) + +# Set test properties +set_tests_properties(IPC_Crash_Recovery PROPERTIES TIMEOUT 30) +set_tests_properties(Gateway_Network_Tests PROPERTIES TIMEOUT 60) \ No newline at end of file diff --git a/Gateway/Fix.h b/Gateway/Fix.h new file mode 100644 index 0000000..13e9c27 --- /dev/null +++ b/Gateway/Fix.h @@ -0,0 +1,268 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Linux specific headers for networking and threading +#include +#include +#include +#include +#include +#include + +#include "Logger/Logger.h" +#include "BlockingQueue/MutexBlockingQueue.h" +#include "Exception.h" + +namespace Exchange::Gateway { + + constexpr char FIX_DELIMITER = '\x01'; // SOH character standard in FIX + constexpr int MAX_EVENTS = 100; + constexpr int PORT = 9000; + constexpr int BUFFER_SIZE = 4096; + + // void pinThreadToCpuCore(std::thread &t, int coreId) { + // cpu_set_t cpuset; + // CPU_ZERO(&cpuset); + // CPU_SET(coreId, &cpuset); + // pthread_t current_thread = pthread_self(); + // pthread_setaffinity_np(t.native_handle(), sizeof(cpu_set_t), &cpuset); + // LOG_INFO("Thread pinned to Core %d",coreId); + // } + + void pinThreadToCore(int coreId) { + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(coreId, &cpuset); + pthread_t current_thread = pthread_self(); + pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset); + LOG_INFO("Thread pinned to Core %d",coreId); + } + // Global queue to pass raw data from Network to Logic + struct RawPacket { + int clientSocket; + Core::String data; + }; + + Core::IBlockingQueue* gIngressQueue = new Core::MutexBlockingQueue(4096); + + struct FixMessage { + std::string msgType; // Tag 35 + std::string symbol; // Tag 55 + std::string side; // Tag 54 + double price; // Tag 44 + int quantity; // Tag 38 + bool isValid; + }; + + // A "Zero-Copy" style parser would be faster, but string split is easier to understand + FixMessage parseFix(const std::string& raw) { + + LOG_TRACE(static_cast(raw)); + + FixMessage msg; + msg.isValid = false; + + std::stringstream ss(raw); + std::string segment; + + // Split by SOH delimiter (\x01) + while (std::getline(ss, segment, FIX_DELIMITER)) { + size_t eq_pos = segment.find('='); + if (eq_pos == std::string::npos) continue; + + std::string tag = segment.substr(0, eq_pos); + std::string value = segment.substr(eq_pos + 1); + + if (tag == "35") msg.msgType = value; + else if (tag == "55") msg.symbol = value; + else if (tag == "54") msg.side = value; // 1=Buy, 2=Sell + else if (tag == "44") msg.price = std::stod(value); + else if (tag == "38") msg.quantity = std::stoi(value); + } + + // Basic validation: Must have MsgType + if (!msg.msgType.empty()) { + msg.isValid = true; + } + return msg; + } + + void networkLoop() { + // Force this specific thread to run only on CPU Core #1 + // If the Operating System moves this thread to a different core (context switch), + // the CPU cache has to be reloaded, costing microseconds. In trading, we want + // "Hot Cache" at all times. + // pinThreadToCore(1); + + // Address Family: AF_INET, tells OS we are using IPv4 + // Socket Type: SOCK_STREAM, tells OS we want TCP + // Protocol: 0, lets OS choose the default protocol for given family and type + int serverFd = socket(AF_INET, SOCK_STREAM, 0); + + // Setting the strcuture that holds the configration + // - sockaddr_in: A struct specifically designed for IPv4 addresses. + // - INADDR_ANY: This is a shortcut. It tells the server to listen on all available network interfaces. + // If your server has a Local IP (127.0.0.1) and a Public Wi-Fi IP (192.168.1.5), + // INADDR_ANY will accept connections arriving at either IP. + // - htons(PORT): Host to Network Short + // Computers store numbers in different orders (Endianness). Intel CPUs are "Little Endian" (start with + // the smallest byte). The Internet is "Big Endian" (start with the largest byte). If you don't use htons, + // Port 9000 might be interpreted by the network as Port 36899. This function flips the bytes so the network + // understands the number correctly. + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(PORT); + + // Sets REUSEADDR + // When you stop a server (or it crashes), the OS puts the port (e.g., 9000) into a TIME_WAIT state for + // about 60 seconds to ensure all lingering packets are flushed. During this time, you cannot restart + // your server; you will get an "Address already in use" error. + // SO_REUSEADDR tells the OS: "I know this port is technically busy cleaning up, but let me bind to + // it anyway immediately." + // Without this, every time you re-compile and run your code, you'd have to wait 1 minute for the port + // to free up. + int opt = 1; + setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + // Connects the abstract socket ID (serverFd) to the specific IP and Port configuration (address) + if (bind(serverFd, (struct sockaddr*)&address, sizeof(address)) < 0) { + // Another program is already using that port. (as bind returns < 0) + // Or You are trying to use a restricted port (0–1023) without + // Root/Administrator privileges. + perror("Bind failed"); return; + } + + // When the socket is made the socket could be used to make outgoing calls. + // listen flips the switch telling the OS "This socket is not making calls, but waiting for them." + // Puts the socket in passive mode, ready to accept incoming connection requests. + // Backlog queue size: 10 + // If 15 clients try to connect at the exact same nanosecond, The first 10 go into a holding queue. + // The 11th through 15th receive a "Connection Refused" error immediately. + listen(serverFd, 10); + + /* + This block sets up the "Traffic Control Tower" for your network thread. Instead of checking + every connection one by one to see if they have data (which is slow), you are asking the Linux + Kernel to watch them for you. + You can think of this ID as a handle to a "To-Do List" managed by the Operating System. + */ + // Create Epoll Instance + int epollFd = epoll_create1(0); // ID of that epoll instance + + // This declares two variables: + // - event (Singular): This is a Configuration Form. We use this temporarily to describe what we want to + // watch (e.g., "Watch Socket 5 for incoming data"). + // - events (Array): This is the Inbox. When we later call epoll_wait(), the Kernel will + // fill this array with the list of sockets that actually have activity. If 5 clients + // send orders at once, this array will contain 5 items. + epoll_event event{}, events[MAX_EVENTS]; + + // This stands for "Epoll Input". It tells the Kernel: "Wake me up if there is data to read." + // - For a Client Socket: "Data to read" means they sent a FIX message. + // - For the Server Socket: "Data to read" means a new user is trying to connect (the TCP 3-way handshake is complete). + event.events = EPOLLIN; // Read available + + // We attach the ID of the socket (serverFd) to this event. When the event + // fires later, the Kernel will hand this ID back to us so we know who triggered the event. + event.data.fd = serverFd; + + // Registering with the Kernel + // This is the actual system call that submits the form. + // - EPOLL_CTL_ADD: The Command. We are Adding a new socket to the watch list. + // - epollFd: The Traffic Tower instance we created in step 1. + // - serverFd: The target socket we want to watch. + // - &event: The rules we defined in step 3 (Watch for EPOLLIN). + // "Hey Linux (epollFd), please add (ADD) the Main Server Phone (serverFd) to your + // watch list. Use the rules written on this form (&event), which say you should notify + // me whenever it rings (EPOLLIN)." + epoll_ctl(epollFd, EPOLL_CTL_ADD, serverFd, &event); + + LOG_INFO("Gateway is listening on port, %d", PORT); + + // This creates a raw memory array (usually 4KB or 8KB) on the stack. + // When data eventually arrives, we will use read() to pour the raw bytes from the + // network card into this bucket so our application can process them. + // Without epoll, you would have to loop through every client constantly: + // Are you sending data? No. Are you sending data? No. Are you sending data? No. + // With epoll, your code does nothing (consumes 0 CPU) until the Kernel taps it on the shoulder and says: + // - "Socket 7 and Socket 12 have data. Here is the list." + char buffer[BUFFER_SIZE]; + + // Main event loop (Engine of your network thread) + // This loop runs millions of times a day. It sleeps when the market is quiet and + // wakes up instantly when activity happens. + while (true) { + // Wait for events + // Blocking call, wakes up on network activity + // The pause button + // The Linux Kernel holds the thread here until at least one interesting thing happens (e.g., a client sends an order, or a new user tries to connect). + + int eventcount = epoll_wait(epollFd, events, MAX_EVENTS, -1); + + for (int i = 0; i < eventcount; i++) { + + // New Connection + if (events[i].data.fd == serverFd) { + int newSocket = accept(serverFd, nullptr, nullptr); + + // Set non-blocking for new client + fcntl(newSocket, F_SETFL, O_NONBLOCK); + + event.events = EPOLLIN | EPOLLET; // Edge Triggered + event.data.fd = newSocket; + epoll_ctl(epollFd, EPOLL_CTL_ADD, newSocket, &event); + LOG_INFO("New Client Connected:, %d", newSocket); + } + // Data arrived + else { + int clientFd = events[i].data.fd; + int bytesRead = read(clientFd, buffer, BUFFER_SIZE); + + if (bytesRead <= 0) { + // Client disconnected + close(clientFd); + epoll_ctl(epollFd, EPOLL_CTL_DEL, clientFd, nullptr); + } + else { + std::string rawData(buffer, bytesRead); + gIngressQueue->push({clientFd, rawData}); + } + } + } + } + } + + + void consumerLoop() { + // pinThreadToCore(2); + LOG_INFO("Consumer started.."); + + while(true) { + RawPacket packet; + gIngressQueue->pop(packet); + FixMessage fix = parseFix(packet.data.toString()); + + if (fix.isValid) { + if (fix.msgType == "D") { // New Order Single + LOG_INFO("ORDER RECEIVED Client: %d BUY: %d %s @ %.2f", packet.clientSocket, fix.quantity, fix.symbol.c_str(), fix.price); + // todo: send to scheduler process using IPC + } + else if (fix.msgType == "A") { // Logon + LOG_INFO("LOGON Request from Client %d", packet.clientSocket); + } + } + else { + LOG_WARN("Noise or Partial Packet"); + } + } + } +} diff --git a/Gateway/main.cpp b/Gateway/main.cpp new file mode 100644 index 0000000..776d2ea --- /dev/null +++ b/Gateway/main.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include "messaging.h" +#include "enum.h" +#include "Exception.h" +#include "ipc/SharedMemory.h" +#include "Logger/Logger.h" +#include "Fix.h" + +// Producer +int main(int argc, char* argv[]) { + + int port = 8001; // Default port + // Parse command line argument for port + if (argc > 1) { + port = std::atoi(argv[1]); + } + + + + try{ + LOG_INFO("Launching producer..."); + + // // Initialize the queue + // Exchange::Ipc::Producer producer("queue"); // name: "queue" + // Exchange::Ipc::Msg::IpcMessage newOrder; + // newOrder.setMsgType(Exchange::Ipc::Msg::MsgType::NEW_ORDER); + // newOrder.addString(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_SYMBOL), "RIL"); + // newOrder.addUint64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_SIDE), static_cast(Exchange::Order::Side::BUY)); + // newOrder.addInt64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_PRICE), 123450); + // newOrder.addUint64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_QTY), 100); + // newOrder.addUint64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_CLIENT_ID), 42); + // newOrder.addUint64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_ORDER_ID), 1001); + // newOrder.addUint64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_TIF), static_cast(Exchange::Order::TIF::DAY)); + // newOrder.finalize(); + + // std::vector buf; + // newOrder.encode(buf); + + // bool success = producer.write(buf.data(), static_cast(buf.size())); + // if (success) { + // LOG_INFO("Message sent..."); + // printMessage(newOrder); + // } + // else { + // LOG_WARN("IPC Queue Full! Waiting..."); + // } + } + catch (Engine::EngException& ex) { + ex.log(); + return 1; + } + + // Start Threads + std::thread net_thread(Exchange::Gateway::networkLoop); + std::thread app_thread(Exchange::Gateway::consumerLoop); + // Wait forever + net_thread.join(); + app_thread.join(); + return 0; +} diff --git a/process1/Sequencer.h b/process1/Sequencer.h deleted file mode 100644 index fd96cc6..0000000 --- a/process1/Sequencer.h +++ /dev/null @@ -1,12 +0,0 @@ - - -/** - * @class Sequencer - * @brief Assigning a global sequence number to all incoming orders received from the gateway - * Write read append logs - * listen to gatewat - * trnaerf ordrer with sequence to macthing engine - */ -class Sequencer{ - -}; \ No newline at end of file diff --git a/process1/main.cpp b/process1/main.cpp deleted file mode 100644 index 207f9c9..0000000 --- a/process1/main.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include -#include -#include -#include "messaging.h" -#include "enum.h" -#include "exception.h" -#include "ipc/SharedMemory.h" -#include "Logger/Logger.h" - -// Producer -int main(int argc, char* argv[]) { - - int port = 8001; // Default port - // Parse command line argument for port - if (argc > 1) { - port = std::atoi(argv[1]); - } - - - // using namespace Engine; - - try{ - LOG_INFO("Launching producer..."); - - // Initialize the queue - Exchange::Ipc::Producer producer("queue"); // name: "queue" - Exchange::Ipc::Msg::IpcMessage newOrder; - newOrder.setMsgType(Exchange::Ipc::Msg::MsgType::NEW_ORDER); - newOrder.addString(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_SYMBOL), "RIL"); - newOrder.addUint64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_SIDE), static_cast(Exchange::Order::Side::BUY)); - newOrder.addInt64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_PRICE), 123450); - newOrder.addUint64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_QTY), 100); - newOrder.addUint64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_CLIENT_ID), 42); - newOrder.addUint64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_ORDER_ID), 1001); - newOrder.addUint64(static_cast(Exchange::Ipc::Msg::FieldId::FIELD_TIF), static_cast(Exchange::Order::TIF::DAY)); - newOrder.finalize(); - - std::vector buf; - newOrder.encode(buf); - - bool success = producer.write(buf.data(), static_cast(buf.size())); - if (success) { - LOG_INFO("Message sent..."); - printMessage(newOrder); - } - else { - LOG_WARN("IPC Queue Full! Waiting..."); - } - } - catch (Engine::EngException& ex) { - ex.log(); - return 1; - } - - return 0; - - - // printMessage(newOrder); - - // try{ - // temp(); - // } - // catch(Engine::EngException& ex){ - // std::cout<<"Some exception thrown"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 diff --git a/process2/process2 b/process2/process2 deleted file mode 100755 index 988270a0135c6cd533e659810a15c6bf372ea48a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16456 zcmeHOeQXrR6`!*)IK^P|)fm&lhJ*@Ay!Z9W3C+pC`M+`B!n zqlQ{?THz8Tr)m?VQmL&frBeS0RQ&_gsv%IIq-sm5Dj`axP+BCTk`&q|B8kfNy_xsc zdz-t6(yD*$Si5iD?|sbd+nM$3?VGPgx;slkA;G0od{!WLrAi@T5**q<6(C`;LCk~m z3b9rlsv2mNL9I9l26MCF9nKr)mSM&Kc-;8lzWI2?TVFgYJjPr>p;6QG$P(l z&rv^_vfOP?Lt`kKJiD|G6`(Nn+F@+QYo%C>x8&0)jtgBs+aZj0Q)D+qc1#7?G39t- zOz_x1{`|BKWLTK`?S{zCPdk*EV2W*E>AmK}@1?ySvg_8Q8{wx-3KmScy&YhOak<;V zw|OVESL{BFQ#_f{?G)=0c7Jozxxf~wWc_w{$vfu}WIWLo>vihuV?$=zaMGsj zWV&LJ$WFi-`^}6UGg28RZJEPeeMVoM5$SD)rpZZ9=g!_#uMv^H8tUy-#sCw`fRURv z(_@C6w4MD@n8l|79$UK>=SnlOF;&~r-PP8J_OmcG%0wGs&YEw+Bd%2Jp!nUaodB>_24|N@wqk6ga3&1=K#jfFccs6;P`yva?*p# z|EnZ8?ZMUaNeO=C!8u0kE1*FHf(Qf=2qF+fAc(;KHv<2tT>f5k;zoJ&aM_>M2@(C; zX{RK2DLV1X^0Trdx%Fv)bE_OUSFQ*P)E`30^{csDZeU9CxPZC-`xzb=F4xb@@VKD3 z{*xIV7ZTTh;PSauo#1_-az%%b{&TD5`}o;D{*Ion|BjD;E03>#vupC@FGeS?MkoGq zv!^dof4cr+^hnD~(2LxX*Fp841A66(CxL|k9BFwMkm!-Jza!ar+gSpk`32S?H23AM zRIV6Dy|Y9?|Dx1ie+J2#JJHFT(HGy^9DVV2X*6^``tluTF*rCv4$5;^)OE5w#&5jk z99*BsuH6-#XxRjsB071^sfZqK831PbwL7`obR42}zU;f87upN9Zu@9|{QwBDvJ32b z;ciw#_gBwHH|L?Y2iu;!Fnt32L?_QqUjzz*$v38dCy_Itvxu}OU!DF2f`^y<41R!! zPQHSMhXA<{xq+Ac*Dg?=hJ~YYeMc%?nEt$SbU1PY9ltugU->vNL?DPj5P={9K?H&b1Q93}0sJl^deW(wmC0xgn*6+F#kJKvT^*Y=v1UfJNo!AK zlkllaYtd#0D&~|=!FwX&`0YOo`kz3Bjyd?LM?3@vej6SF|NDT7`Bk0s zs~@SHd!T$=Y_8e($;O9PB8>K5fa80hCk+vVg17_6QNVi;prQfAex8E%-b5@izv}BH z?ek_wCQ3`*fKDM6lpq2@1cC?z5eOm>L?DPj5P={9K?H&b{1*}6yfV%s<6JPzZNtJm z=B{C>P{hq+hHq!GoTTK+rNndo+A`w#{iTL@&SS%OEG#U4`%W%}{BcSG;AOF%J#P{fdZk>LBCQRN;Q#Bt6cJ ziVF&(iFZc%IiHz7Y1KaCC~kW*Fni2Bl)|;^L3Eh{~2-fynP<)=p{Bx zw1?v zEbEZ?sGHX*K{$N{4s4g_C*y~p5`t09^NaE4pi-#+$EE&4F-udL+-|Y!{k7CzEFOM9 z>3h$G{J$m@7P)!!tbYS4A(%b>@$|ODtKIp>`g5UURbr{A3NxbUE|>@!;KGM51pNh= z(XA=HS&E1U0AERUFZm4M3!#OJbid?pETGUTaj&MT9|C+q$x^}Nk~O}K`XvhlKkpd- zP7yyRi{N|Uy0AZ2Xx_-*n84XfMfB%OeQs@ru0^~Ap31)YR}VP$-yhEw#0%t?_X3Xb z7vUM-SlcJ@8nL@IAGZLfM}D4Ql=Q>lJXkzgME`gZ{5;@Vz6;4+ErQ=Ff{Wf*+Nsx5 zkS1>S+lFHf3IH-rc3?n{iJ1g?!x=VWm_nZc&3MWfOr-kFgb{aA>5O4!M}_-Cqh427 z15?m#!%U~mF~dqa=`k^oHis=Eo*f<@0}~G?Q{(egF_T@7jf}{&_wM?QBeoT@4%m=Q zA4?@613w#cz(wcI)~yjEvaJI*02r`E!-zx)jCSl4M#r9Qty{a=g|Vf3ds}O_vAwgi zHv)>Au(0B$W5Vu$?T|hnx06OT13#PQr_YCFT0Eq}WB$8JufOMw0?L=lxQAxq znUpbP!j1#jpn#zQ$d}6RHV%3CGU?w1us674;T|>;US)Z;k#;hIPPAvHcNV&$umtH&@tR!H4hmD6lc4;N}mZXU2vd zvmdCFR&wXLfbr;}74GBZO$ zkB=px2}PZ>lI*wA89SBCV+@d`t%QjNR2xY+LYFr{hpIl90v0P7D<*Wu8U2g?u5;nh4ga%M@$W0- zbuNjeF4vJh2nQ}H*}mBRIn>3%>rTZ9(F_%@y?-Aaez))z{(s3Bu1~!7ygp*8u_95w z{iA@t18Y<6Kd;}Jt|mKPzx(@df*jZGY|ra^rac&3DA-4=vOTY}ZNP9z$M*cbz?9z) zKt&dE$E^4=)Nt+1_PpL^%J+}^&w5M`f<3O~ndkL9(|yzu$C&$1us8x0j49jm`vudR zq{!{FJzxK`WZy=5{Jz4J-*3hn3XdO# zuR#TJc-_5Q{C>jwUj6#aGra}2l^%Qkec(_nfKXUZaWg#Q{{jJyU4Ad-_YU62%z?rA zhlTCXmynff7ejk`xhuT7nDB_;kp6P5b_S%ausn#jV?XUy3W4;265##p5VI`>| z`)YLVDPd0?s|%=oQg@|nzt%-mh2426j~%aSm>g;}U_K5)e0Svj^YaJatGVx9`+YR< c=K6%(F6-iJnNLNEzVs=jf%|2>#m9<&1I86j@c;k- diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..c7087e5 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +# Color codes +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +MAGENTA='\033[0;35m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +echo -e "${MAGENTA}========================================${NC}" +echo -e "${MAGENTA} Building and Running All Tests${NC}" +echo -e "${MAGENTA}========================================${NC}\n" + +# Build the project +echo -e "${CYAN}[BUILD]${NC} Configuring CMake..." +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + +if [ $? -ne 0 ]; then + echo -e "${RED}[ERROR]${NC} CMake configuration failed!" + exit 1 +fi + +echo -e "${CYAN}[BUILD]${NC} Building project..." +cmake --build build --config Release -- -j$(nproc) + +if [ $? -ne 0 ]; then + echo -e "${RED}[ERROR]${NC} Build failed!" + exit 1 +fi + +echo -e "${GREEN}[BUILD]${NC} Build successful!\n" + +# Test 1: IPC Crash Recovery +echo -e "${MAGENTA}========================================${NC}" +echo -e "${MAGENTA} Running IPC Crash Recovery Tests${NC}" +echo -e "${MAGENTA}========================================${NC}\n" + +./build/test_ipc_crash +IPC_RESULT=$? + +echo "" + +# Test 2: Gateway Network Tests +echo -e "${MAGENTA}========================================${NC}" +echo -e "${MAGENTA} Running Gateway Network Tests${NC}" +echo -e "${MAGENTA}========================================${NC}\n" + +# Start Gateway in background +echo -e "${YELLOW}[SETUP]${NC} Starting Gateway on port 9000..." +./build/Gateway > gateway.log 2>&1 & +GATEWAY_PID=$! + +# Wait for gateway to start +sleep 2 + +# Check if gateway is running +if ! kill -0 $GATEWAY_PID 2>/dev/null; then + echo -e "${RED}[ERROR]${NC} Gateway failed to start! Check gateway.log" + cat gateway.log + exit 1 +fi + +echo -e "${GREEN}[SETUP]${NC} Gateway started (PID: $GATEWAY_PID)\n" + +# Run gateway tests +./build/test_gateway +GATEWAY_RESULT=$? + +echo "" + +# Cleanup +echo -e "${YELLOW}[CLEANUP]${NC} Stopping Gateway..." +kill $GATEWAY_PID 2>/dev/null +wait $GATEWAY_PID 2>/dev/null + +# Summary +echo -e "${MAGENTA}========================================${NC}" +echo -e "${MAGENTA} Test Summary${NC}" +echo -e "${MAGENTA}========================================${NC}" + +if [ $IPC_RESULT -eq 0 ]; then + echo -e "${GREEN}✓${NC} IPC Crash Recovery Tests: PASSED" +else + echo -e "${RED}✗${NC} IPC Crash Recovery Tests: FAILED" +fi + +if [ $GATEWAY_RESULT -eq 0 ]; then + echo -e "${GREEN}✓${NC} Gateway Network Tests: PASSED" +else + echo -e "${RED}✗${NC} Gateway Network Tests: FAILED" +fi + +echo -e "${MAGENTA}========================================${NC}\n" + +# Exit with failure if any test failed +if [ $IPC_RESULT -ne 0 ] || [ $GATEWAY_RESULT -ne 0 ]; then + echo -e "${RED}Some tests failed. Check output above.${NC}" + exit 1 +else + echo -e "${GREEN}All tests passed!${NC}" + exit 0 +fi \ No newline at end of file diff --git a/tests/test_gateway.cpp b/tests/test_gateway.cpp new file mode 100644 index 0000000..21cc1cc --- /dev/null +++ b/tests/test_gateway.cpp @@ -0,0 +1,396 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ANSI color codes +#define RESET "\033[0m" +#define RED "\033[31m" +#define GREEN "\033[32m" +#define YELLOW "\033[33m" +#define BLUE "\033[34m" +#define MAGENTA "\033[35m" +#define CYAN "\033[36m" + +constexpr int TEST_PORT = 9000; +constexpr char FIX_DELIMITER = '\x01'; + +void log(const std::string& prefix, const std::string& msg, const std::string& color = RESET) { + std::cout << color << "[" << prefix << "] " << msg << RESET << std::endl; +} + +std::string buildFixMessage(const std::string& msgType, const std::string& symbol, + const std::string& side, int quantity, double price) { + std::string msg = "8=FIX.4.2" + std::string(1, FIX_DELIMITER); + msg += "35=" + msgType + FIX_DELIMITER; + msg += "55=" + symbol + FIX_DELIMITER; + msg += "54=" + side + FIX_DELIMITER; + msg += "38=" + std::to_string(quantity) + FIX_DELIMITER; + msg += "44=" + std::to_string(price) + FIX_DELIMITER; + return msg; +} + +int connectToGateway() { + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + log("ERROR", "Failed to create socket", RED); + return -1; + } + + sockaddr_in serverAddr{}; + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(TEST_PORT); + serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + // Try to connect with retries + int attempts = 0; + while (attempts < 5) { + if (connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == 0) { + log("Client", "Connected to gateway", GREEN); + return sock; + } + attempts++; + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + + log("ERROR", "Failed to connect after 5 attempts", RED); + close(sock); + return -1; +} + +/** + * @brief Test 1: Single client connection and basic FIX order + * + * GIVEN: Gateway is running and listening on port 9000 + * WHEN: A client connects and sends a valid FIX New Order Single (35=D) + * THEN: + * - Connection is established successfully + * - Order is received and parsed correctly + * - Gateway processes the message without errors + */ +bool TEST1_singleOrderSubmission() { + log("TEST 1", "Testing single order submission...", CYAN); + + int sock = connectToGateway(); + if (sock < 0) { + log("TEST 1", "FAILED - Cannot connect to gateway", RED); + return false; + } + + // Send a New Order Single + std::string order = buildFixMessage("D", "AAPL", "1", 100, 150.50); + + ssize_t sent = send(sock, order.c_str(), order.length(), 0); + if (sent < 0) { + log("TEST 1", "FAILED - Cannot send order", RED); + close(sock); + return false; + } + + log("Client", "Sent FIX order: BUY 100 AAPL @ 150.50", GREEN); + + // Give gateway time to process + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + close(sock); + log("TEST 1", "PASSED - Order sent successfully", GREEN); + return true; +} + +/** + * @brief Test 2: Multiple concurrent client connections + * + * GIVEN: Gateway is running with epoll-based event handling + * WHEN: Multiple clients (5) connect simultaneously and send orders + * THEN: + * - All clients connect successfully + * - Gateway handles concurrent connections without blocking + * - All orders are received and processed + * - No connection is dropped or refused + */ +bool TEST2_multipleClients() { + log("TEST 2", "Testing multiple concurrent clients...", CYAN); + + const int numClients = 5; + std::vector threads; + std::atomic successCount{0}; + + for (int i = 0; i < numClients; i++) { + threads.emplace_back([i, &successCount]() { + int sock = connectToGateway(); + if (sock < 0) { + log("Client-" + std::to_string(i), "Failed to connect", RED); + return; + } + + std::string order = buildFixMessage("D", "MSFT", "2", 50 * (i + 1), 300.25); + + ssize_t sent = send(sock, order.c_str(), order.length(), 0); + if (sent > 0) { + log("Client-" + std::to_string(i), "Sent order successfully", GREEN); + successCount++; + } else { + log("Client-" + std::to_string(i), "Failed to send order", RED); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + close(sock); + }); + } + + for (auto& t : threads) { + t.join(); + } + + if (successCount == numClients) { + log("TEST 2", "PASSED - All " + std::to_string(numClients) + " clients succeeded", GREEN); + return true; + } else { + log("TEST 2", "FAILED - Only " + std::to_string(successCount.load()) + "/" + + std::to_string(numClients) + " clients succeeded", RED); + return false; + } +} + +/** + * @brief Test 3: FIX logon message handling + * + * GIVEN: Gateway supports FIX Logon messages (35=A) + * WHEN: Client sends a Logon message + * THEN: + * - Message is parsed correctly + * - Gateway logs the logon request + * - Connection remains active after logon + */ +bool TEST3_fixLogon() { + log("TEST 3", "Testing FIX logon message...", CYAN); + + int sock = connectToGateway(); + if (sock < 0) { + log("TEST 3", "FAILED - Cannot connect", RED); + return false; + } + + // Send a Logon message (35=A) + std::string logon = "8=FIX.4.2" + std::string(1, FIX_DELIMITER) + + "35=A" + FIX_DELIMITER + + "49=CLIENT" + FIX_DELIMITER + + "56=GATEWAY" + FIX_DELIMITER; + + ssize_t sent = send(sock, logon.c_str(), logon.length(), 0); + if (sent < 0) { + log("TEST 3", "FAILED - Cannot send logon", RED); + close(sock); + return false; + } + + log("Client", "Sent FIX Logon message", GREEN); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + close(sock); + log("TEST 3", "PASSED - Logon message sent", GREEN); + return true; +} + +/** + * @brief Test 4: Malformed/invalid FIX message handling + * + * GIVEN: Gateway has FIX message validation + * WHEN: Client sends invalid/malformed FIX data + * THEN: + * - Gateway doesn't crash + * - Invalid message is logged as noise/partial packet + * - Connection remains stable for subsequent valid messages + */ +bool TEST4_malformedMessage() { + log("TEST 4", "Testing malformed message handling...", CYAN); + + int sock = connectToGateway(); + if (sock < 0) { + log("TEST 4", "FAILED - Cannot connect", RED); + return false; + } + + // Send garbage data + std::string garbage = "THIS_IS_NOT_FIX_DATA_12345"; + ssize_t sent = send(sock, garbage.c_str(), garbage.length(), 0); + + if (sent < 0) { + log("TEST 4", "FAILED - Cannot send data", RED); + close(sock); + return false; + } + + log("Client", "Sent malformed data", YELLOW); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Now send valid message to verify connection still works + std::string validOrder = buildFixMessage("D", "GOOG", "1", 25, 2800.00); + sent = send(sock, validOrder.c_str(), validOrder.length(), 0); + + if (sent < 0) { + log("TEST 4", "FAILED - Cannot send valid order after garbage", RED); + close(sock); + return false; + } + + log("Client", "Sent valid order after malformed data", GREEN); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + close(sock); + log("TEST 4", "PASSED - Gateway handled malformed data gracefully", GREEN); + return true; +} + +/** + * @brief Test 5: Rapid order submission (stress test) + * + * GIVEN: Gateway uses lock-free queue for message passing + * WHEN: Client rapidly sends 100 orders in quick succession + * THEN: + * - All orders are accepted + * - No messages are lost + * - Gateway's queue doesn't overflow + * - Performance remains stable + */ +bool TEST5_rapidOrderSubmission() { + log("TEST 5", "Testing rapid order submission (100 orders)...", CYAN); + + int sock = connectToGateway(); + if (sock < 0) { + log("TEST 5", "FAILED - Cannot connect", RED); + return false; + } + + const int numOrders = 100; + int sentCount = 0; + + for (int i = 0; i < numOrders; i++) { + std::string order = buildFixMessage("D", "TSLA", "1", i + 1, 700.00 + i); + ssize_t sent = send(sock, order.c_str(), order.length(), 0); + + if (sent > 0) { + sentCount++; + } + + // Minimal delay to simulate high-frequency trading + std::this_thread::sleep_for(std::chrono::microseconds(100)); + } + + log("Client", "Sent " + std::to_string(sentCount) + "/" + std::to_string(numOrders) + " orders", + sentCount == numOrders ? GREEN : YELLOW); + + // Give gateway time to process all messages + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + close(sock); + + if (sentCount == numOrders) { + log("TEST 5", "PASSED - All orders sent successfully", GREEN); + return true; + } else { + log("TEST 5", "PARTIAL - " + std::to_string(sentCount) + "/" + + std::to_string(numOrders) + " sent", YELLOW); + return sentCount >= numOrders * 0.95; // 95% success rate acceptable + } +} + +/** + * @brief Test 6: Client disconnect and reconnect + * + * GIVEN: Gateway uses epoll with EPOLL_CTL_DEL for cleanup + * WHEN: Client connects, disconnects, then reconnects + * THEN: + * - First connection succeeds + * - Disconnect is detected by gateway + * - Reconnection succeeds without port exhaustion + * - Socket resources are properly cleaned up + */ +bool TEST6_disconnectReconnect() { + log("TEST 6", "Testing disconnect and reconnect...", CYAN); + + // First connection + int sock1 = connectToGateway(); + if (sock1 < 0) { + log("TEST 6", "FAILED - Initial connection failed", RED); + return false; + } + + std::string order1 = buildFixMessage("D", "AMZN", "1", 10, 3300.00); + send(sock1, order1.c_str(), order1.length(), 0); + log("Client", "Sent order on first connection", GREEN); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + close(sock1); + log("Client", "Closed first connection", YELLOW); + + // Wait for gateway to detect disconnect + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + // Reconnect + int sock2 = connectToGateway(); + if (sock2 < 0) { + log("TEST 6", "FAILED - Reconnection failed", RED); + return false; + } + + std::string order2 = buildFixMessage("D", "NFLX", "2", 20, 500.00); + ssize_t sent = send(sock2, order2.c_str(), order2.length(), 0); + + if (sent < 0) { + log("TEST 6", "FAILED - Cannot send on reconnection", RED); + close(sock2); + return false; + } + + log("Client", "Sent order on reconnection", GREEN); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + close(sock2); + log("TEST 6", "PASSED - Disconnect and reconnect successful", GREEN); + return true; +} + +int main() { + std::cout << "\n" << MAGENTA << "========================================" << RESET << std::endl; + std::cout << MAGENTA << " Gateway Network & FIX Protocol Tests" << RESET << std::endl; + std::cout << MAGENTA << "========================================" << RESET << "\n" << std::endl; + + log("INFO", "Waiting for gateway to start...", YELLOW); + std::this_thread::sleep_for(std::chrono::seconds(1)); + + int passed = 0; + int total = 6; + + if (TEST1_singleOrderSubmission()) passed++; + std::cout << std::endl; + + if (TEST2_multipleClients()) passed++; + std::cout << std::endl; + + if (TEST3_fixLogon()) passed++; + std::cout << std::endl; + + if (TEST4_malformedMessage()) passed++; + std::cout << std::endl; + + if (TEST5_rapidOrderSubmission()) passed++; + std::cout << std::endl; + + if (TEST6_disconnectReconnect()) passed++; + std::cout << std::endl; + + // Summary + std::cout << MAGENTA << "========================================" << RESET << std::endl; + std::cout << "Test Results: " << (passed == total ? GREEN : (passed > 0 ? YELLOW : RED)) + << passed << "/" << total << " passed" << RESET << std::endl; + std::cout << MAGENTA << "========================================" << RESET << std::endl; + + return (passed == total) ? 0 : 1; +} \ No newline at end of file From 8fcc50d823d7aeec4d65b9199e2a0924605c458f Mon Sep 17 00:00:00 2001 From: vasu <92209437+vaasu2002@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:21:39 +0000 Subject: [PATCH 4/4] chore: remove IPC queue tests workflow --- .github/workflows/ipc-tests.yml | 61 --------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 .github/workflows/ipc-tests.yml diff --git a/.github/workflows/ipc-tests.yml b/.github/workflows/ipc-tests.yml deleted file mode 100644 index 225ee2f..0000000 --- a/.github/workflows/ipc-tests.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: IPC Queue Tests - -# Trigger on pushes to main and on pull requests -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - test: - name: Run IPC Connection & Crash Recovery Tests - runs-on: ubuntu-latest - - steps: - # Checkout the repository - - name: Checkout code - uses: actions/checkout@v4 - - # Install dependencies - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y build-essential cmake g++ - - # Create build directory - - name: Create build directory - run: mkdir -p build - - # Configure with CMake - - name: Configure CMake - run: | - cd build - cmake .. - - # Build the test executable - - name: Build test executable - run: | - cd build - make test_ipc_crash - - # Run the tests - - name: Run IPC crash recovery tests - run: | - cd build - ./test_ipc_crash - - # Optional: Build other executables to ensure nothing broke - - name: Build all executables - run: | - cd build - make process1 - make process2 - - # Clean up shared memory and lock files after tests - - name: Cleanup test artifacts - if: always() - run: | - sudo rm -f /dev/shm/test_queue_* || true - sudo rm -f /tmp/test_queue_*.lock || true - sudo rm -f /tmp/test_queue_*.uuid || true \ No newline at end of file