Skip to content
9 changes: 3 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.15)
cmake_minimum_required(VERSION 3.20)

project(KV_Database LANGUAGES CXX)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# C++17 Configuration
set(CMAKE_CXX_STANDARD 17)
# C++23 Configuration
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Turn our core logic into a LIBRARY so both the App and the Tests can use it
Expand All @@ -17,10 +17,7 @@ add_library(KV_Core
src/Worker/SnapshotScheduler.cpp
src/Limit/Limiter.cpp
src/UserSession/UserSession.cpp
src/UserSession/UserSession.h
src/UserSession/Connection.h
src/Worker/UserSessionBackgroundWorker.cpp
src/Worker/UserSessionBackgroundWorker.h
src/Networking/EpollEventLoop.cpp
src/Networking/IocpEventLoop.cpp
)
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

_Redis-inspired · event-driven · cross-platform · containerized_

[![C++](https://img.shields.io/badge/C%2B%2B-17-00599C?style=flat-square&logo=cplusplus&logoColor=white)](https://en.cppreference.com/w/cpp/17)
[![CMake](https://img.shields.io/badge/CMake-3.15%2B-064F8C?style=flat-square&logo=cmake&logoColor=white)](https://cmake.org/)
[![C++](https://img.shields.io/badge/C%2B%2B-23-00599C?style=flat-square&logo=cplusplus&logoColor=white)](https://en.cppreference.com/w/cpp/23)
[![CMake](https://img.shields.io/badge/CMake-3.20%2B-064F8C?style=flat-square&logo=cmake&logoColor=white)](https://cmake.org/)
[![Docker](https://img.shields.io/badge/Docker-ready-2496ED?style=flat-square&logo=docker&logoColor=white)](https://www.docker.com/)
[![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20Windows-informational?style=flat-square&logo=linux&logoColor=white)](#)
[![Tests](https://img.shields.io/badge/tests-GoogleTest-4285F4?style=flat-square&logo=google&logoColor=white)](https://github.com/google/googletest)
Expand Down Expand Up @@ -148,8 +148,8 @@ ByteForge uses the **Token Bucket** algorithm for rate limiting — the same app

### Prerequisites

- C++17 compiler
- CMake 3.15+
- C++23 compiler
- CMake 3.20+
- Docker (optional)
- [nmap/ncat](https://nmap.org/download.html) for testing

Expand Down Expand Up @@ -183,13 +183,13 @@ The server starts on port `6625` by default. Type `stop` to shut it down cleanly
Build the image:

```bash
docker build -t byteforce:latest .
docker build -t byteforge:latest .
```

Run the container:

```bash
docker run -d -p 6625:6625 --name my-byteforce-db byteforce-db:latest
docker run -d -p 6625:6625 --name my-byteforge-db byteforge:latest
```

Connect using:
Expand Down
45 changes: 23 additions & 22 deletions src/Limit/Limiter.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,28 @@
*/
class RateLimiter
{

public:
/**
* @brief Constructs the rate limiter.
* @param maxTokens Max burst capacity per IP (default: 10).
* @param refillRate Tokens refilled per second per IP (default: 5).
* @param globalLimit Max total requests per second (default: 1000).
*/
RateLimiter(double maxTokens = 10, double refillRate = 5, int globalLimit = 1000);

/**
* @brief Destroys the rate limiter and clears all state.
*/
~RateLimiter();

/**
* @brief Checks if a request from the given IP should be allowed.
* @param ip The raw client IP address
* @return true if allowed, false if rate limited.
*/
bool isAllowed(uint32_t ip);

private:
/**
* @brief Token bucket state for a single IP address.
Expand All @@ -26,7 +48,7 @@ class RateLimiter
};

std::unordered_map<uint32_t, IPRecord> ipRecords; /**< Per-IP bucket state. */
std::mutex mapMutex; /**< Protects ipRecords from concurrent access. */
std::mutex mapMutex; /**< Protects ipRecords from concurrent access. */

std::atomic<int> globalCount; /**< Total requests in current global window. */
std::chrono::steady_clock::time_point globalWindowStart; /**< Start of current global window. */
Expand All @@ -49,27 +71,6 @@ class RateLimiter
* allowed, false if the IP is rate limited.
*/
bool isAllowedPrivate(uint32_t ip);

public:
/**
* @brief Constructs the rate limiter.
* @param maxTokens Max burst capacity per IP (default: 10).
* @param refillRate Tokens refilled per second per IP (default: 5).
* @param globalLimit Max total requests per second (default: 1000).
*/
RateLimiter(double maxTokens = 10, double refillRate = 5, int globalLimit = 1000);

/**
* @brief Destroys the rate limiter and clears all state.
*/
~RateLimiter();

/**
* @brief Checks if a request from the given IP should be allowed.
* @param ip The raw client IP address
* @return true if allowed, false if rate limited.
*/
bool isAllowed(uint32_t ip);
};

#endif
73 changes: 37 additions & 36 deletions src/RAM/HashMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,6 @@
*/
class HashMap
{
private:
/**
* @brief A node in the hash map's linked list.
*
* Stores the key-value pair and a pointer to the next node
* to handle bucket collisions via separate chaining.
*/
struct Node
{
std::string key; /**< The unique string key identifier (e.g., Name). */
std::string value; /**< The string data value associated with the key (e.g., Phone Number). */
Node *next; /**< Pointer to the next node in the chain (nullptr if last). */

/**
* @brief Constructs a new Node object.
* @param k The string key.
* @param v The string value.
*/
Node(const std::string &k, const std::string &v);
};

/** * @brief Dynamic array of Node pointers representing the hash table buckets.
*
* Since it points to an array of pointers, it requires double pointer syntax (Node**).
*/
Node **table;

int capacity; /**< The total size of the table array (number of available buckets). */
mutable std::shared_mutex sm; /** A mutex that protects the hashmap from 2 writer threads writing at the same time */

/**
* @brief Converts a string key into an array index.
* @param key The string key to be hashed.
* @return An integer index between 0 and capacity - 1.
*/
int hashFunction(const std::string& key) const;

public:
/**
Expand Down Expand Up @@ -88,6 +52,43 @@ class HashMap
* @param callback A function to call for each key-value pair.
*/
void forEach(std::function<void(const std::string &, const std::string &)> callback) const;

private:
/**
* @brief A node in the hash map's linked list.
*
* Stores the key-value pair and a pointer to the next node
* to handle bucket collisions via separate chaining.
*/
struct Node
{
std::string key; /**< The unique string key identifier (e.g., Name). */
std::string value; /**< The string data value associated with the key (e.g., Phone Number). */
Node *next; /**< Pointer to the next node in the chain (nullptr if last). */

/**
* @brief Constructs a new Node object.
* @param k The string key.
* @param v The string value.
*/
Node(const std::string &k, const std::string &v);
};

/** * @brief Dynamic array of Node pointers representing the hash table buckets.
*
* Since it points to an array of pointers, it requires double pointer syntax (Node**).
*/
Node **table;

int capacity; /**< The total size of the table array (number of available buckets). */
mutable std::shared_mutex sm; /** A mutex that protects the hashmap from 2 writer threads writing at the same time */

/**
* @brief Converts a string key into an array index.
* @param key The string key to be hashed.
* @return An integer index between 0 and capacity - 1.
*/
int hashFunction(const std::string &key) const;
};

#endif
11 changes: 8 additions & 3 deletions src/Server/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,21 @@ Server::~Server()
std::cout << "[SERVER] Cleanup complete\n";
}

void Server::start()
std::expected<void, std::string> Server::start()
{
std::cout << "[SERVER] Binding socket...\n";

if (bind(serverSocket, (sockaddr *)&serverAddr, sizeof(serverAddr)) != 0)
{
throw std::runtime_error("Server Failed to Start using bind");
return std::unexpected("Server Failed to Start using bind");
}

std::cout << "[SERVER] Bind successful!\n";
std::cout << "[SERVER] Listening for clients...\n";

if (listen(serverSocket, SOMAXCONN) != 0)
{
throw std::runtime_error("Server Failed to Listen using listen");
return std::unexpected("Server Failed to Listen using listen");
}

std::cout << "[SERVER] Server is now accepting connections!\n";
Expand All @@ -78,6 +78,8 @@ void Server::start()

// Start the event loop on its own thread now that the server is live.
eventLoopThread = std::thread(&Server::runEventLoop, this);

return {};
}

void Server::acceptClients()
Expand Down Expand Up @@ -234,6 +236,9 @@ void Server::messageHandler(SocketType clientSocket, const SessionKey &sessionKe
closeConnection(clientSocket);
return;
}

// Tell compiler that bytesReceived is greater than 0 so it wont do optimizations.
[[assume(bytesReceived > 0)]];
userSessionManager.update_activity(sessionKey);

std::cout << "[CLIENT] Received " << bytesReceived << " bytes\n";
Expand Down
73 changes: 39 additions & 34 deletions src/Server/Server.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
#include <thread>
#include <vector>
#include <mutex>
#include <unordered_set>
#include <expected>
#include <string>
#include <flat_map>
#include <flat_set>

#include "UserSessionBackgroundWorker.h"
#include "../RAM/HashMap.h"
Expand All @@ -23,38 +26,6 @@
*/
class Server
{
private:
SocketType serverSocket; /**< The main listening socket */
sockaddr_in serverAddr; /**< Server address structure */
int port; /**< Port the server listens on */

std::atomic<bool> running; /**< Controls whether the server is running */

HashMap hashMap; /** Server owns an instance of the hashmap */
Persistence pers; /** Server owns an instance of persistance class */
ThreadPool tpool; /** Server owns an instance of ThreadPool class */
SnapshotScheduler ss; /** Server owns an instance of SnapshotScheduler class */
RateLimiter rt; /** Server owns an instance of RateLimter class */
UserSessionManager userSessionManager; /** Managing User Sessions */
UserSessionBackgroundWorker user_session_background_worker; /** Background worker that sweeps expired sessions*/
std::unordered_map<SocketType, Connection> connections; /** Client connections, keyed by socket */
std::unique_ptr<IEventLoop> eventLoop; /** Watches all client sockets for readiness */
std::thread eventLoopThread; /** Thread that runs runEventLoop() */
std::mutex busyMutex; /** Guards busySockets */
std::unordered_set<SocketType> busySockets; /** Sockets with a recv job already queued/running */

/**
* @brief Runs continuously on eventLoopThread: waits for socket readiness
* and dispatches ready clients to the thread pool.
*/
void runEventLoop();

/**
* @brief Fully tears down a client connection: stops watching it, removes
* its session and Connection entry, and closes the socket.
* @param sock The socket to close.
*/
void closeConnection(SocketType sock);

public:
/**
Expand All @@ -71,8 +42,9 @@ class Server
/**
* @brief Starts the server, binds and begins listening.
* @throws std::runtime_error if socket setup fails.
* @return on success returns void, on failure a string
*/
void start();
std::expected<void, std::string> start();

/**
* @brief Accepts incoming client connections in a loop.
Expand All @@ -91,6 +63,39 @@ class Server
* @param sessionKey The session tied to this client.
*/
void messageHandler(SocketType clientSocket, const SessionKey &sessionKey);

private:
SocketType serverSocket; /**< The main listening socket */
sockaddr_in serverAddr; /**< Server address structure */
int port; /**< Port the server listens on */

std::atomic<bool> running; /**< Controls whether the server is running */

HashMap hashMap; /** Server owns an instance of the hashmap */
Persistence pers; /** Server owns an instance of persistance class */
ThreadPool tpool; /** Server owns an instance of ThreadPool class */
SnapshotScheduler ss; /** Server owns an instance of SnapshotScheduler class */
RateLimiter rt; /** Server owns an instance of RateLimter class */
UserSessionManager userSessionManager; /** Managing User Sessions */
UserSessionBackgroundWorker user_session_background_worker; /** Background worker that sweeps expired sessions*/
std::flat_map<SocketType, Connection> connections; /** Client connections, keyed by socket */
std::unique_ptr<IEventLoop> eventLoop; /** Watches all client sockets for readiness */
std::thread eventLoopThread; /** Thread that runs runEventLoop() */
std::mutex busyMutex; /** Guards busySockets */
std::flat_set<SocketType> busySockets; /** Sockets with a recv job already queued/running */

/**
* @brief Runs continuously on eventLoopThread: waits for socket readiness
* and dispatches ready clients to the thread pool.
*/
void runEventLoop();

/**
* @brief Fully tears down a client connection: stops watching it, removes
* its session and Connection entry, and closes the socket.
* @param sock The socket to close.
*/
void closeConnection(SocketType sock);
};

#endif
11 changes: 6 additions & 5 deletions src/Storage/Persistence.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@
*/
class Persistence
{
private:
std::ofstream aof_stream; /**< Persistent file stream handle for real-time logging. */
const std::string aof_path = "appendonly.log"; /**< File path for the transaction log file. */
const std::string rdb_path = "snapshot.log"; /**< File path for the point-in-time snapshot file. */
std::mutex aof_mutex; /**< Protects aof_stream from concurrent access. */

public:
/**
Expand Down Expand Up @@ -71,6 +66,12 @@ class Persistence
* @param hashmap A constant reference to the in-memory HashMap data source.
*/
void createSnapshot(const HashMap &hashmap);

private:
std::ofstream aof_stream; /**< Persistent file stream handle for real-time logging. */
const std::string aof_path = "appendonly.log"; /**< File path for the transaction log file. */
const std::string rdb_path = "snapshot.log"; /**< File path for the point-in-time snapshot file. */
std::mutex aof_mutex; /**< Protects aof_stream from concurrent access. */
};

#endif
Loading