Initial Commit

This commit is contained in:
2025-11-06 15:51:56 +01:00
commit 0f7191ad54
648 changed files with 170981 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
// tcp_client.hpp - TCP Client for ColumnLynx
// Copyright (C) 2025 DcruBro
// Distributed under the GPLv3 license. See LICENSE for details.
#pragma once
#include <asio/asio.hpp>
#include <columnlynx/common/net/tcp/tcp_message_handler.hpp>
#include <columnlynx/common/net/tcp/tcp_message_type.hpp>
#include <columnlynx/common/net/tcp/net_helper.hpp>
#include <columnlynx/common/utils.hpp>
using asio::ip::tcp;
namespace ColumnLynx::Net::TCP {
class TCPClient : public std::enable_shared_from_this<TCPClient> {
public:
TCPClient(asio::io_context& ioContext,
const std::string& host,
const std::string& port)
: mResolver(ioContext), mSocket(ioContext), mHost(host), mPort(port) {}
void start() {
auto self = shared_from_this();
mResolver.async_resolve(mHost, mPort,
[this, self](asio::error_code ec, tcp::resolver::results_type endpoints) {
if (!ec) {
asio::async_connect(mSocket, endpoints,
[this, self](asio::error_code ec, const tcp::endpoint&) {
if (!NetHelper::isExpectedDisconnect(ec)) {
mConnected = true;
Utils::log("Client connected.");
mHandler = std::make_shared<MessageHandler>(std::move(mSocket));
mHandler->onMessage([this](AnyMessageType type, const std::string& data) {
mHandleMessage(static_cast<ServerMessageType>(MessageHandler::toUint8(type)), data);
});
mHandler->start();
// Init connection handshake
Utils::log("Sending handshake init to server.");
mHandler->sendMessage(ClientMessageType::HANDSHAKE_INIT, "Hello, I am " + Utils::getHostname());
} else {
Utils::error("Client connect failed: " + ec.message());
}
});
} else {
Utils::error("Client resolve failed: " + ec.message());
}
});
}
void sendMessage(ClientMessageType type, const std::string& data = "") {
if (!mConnected) {
Utils::error("Cannot send message, client not connected.");
return;
}
if (mHandler) {
asio::post(mHandler->socket().get_executor(), [self = shared_from_this(), type, data]() {
self->mHandler->sendMessage(type, data);
});
}
}
void disconnect() {
if (mConnected && mHandler) {
mHandler->sendMessage(ClientMessageType::GRACEFUL_DISCONNECT, "Goodbye");
asio::error_code ec;
mHandler->socket().shutdown(tcp::socket::shutdown_both, ec);
if (ec) {
Utils::error("Error during socket shutdown: " + ec.message());
}
mHandler->socket().close(ec);
if (ec) {
Utils::error("Error during socket close: " + ec.message());
}
mConnected = false;
Utils::log("Client disconnected.");
}
}
private:
void mHandleMessage(ServerMessageType type, const std::string& data) {
switch (type) {
case ServerMessageType::HANDSHAKE_IDENTIFY:
Utils::log("Received server identity: " + data);
//std::memcpy(mServerPublicKey, data.data(), std::min(data.size(), sizeof(mServerPublicKey)));
break;
case ServerMessageType::GRACEFUL_DISCONNECT:
Utils::log("Server is disconnecting: " + data);
if (mConnected) { // Prevent Recursion
disconnect();
}
break;
default:
Utils::log("Received unknown message type from server.");
break;
}
}
bool mConnected = false;
tcp::resolver mResolver;
tcp::socket mSocket;
std::shared_ptr<MessageHandler> mHandler;
std::string mHost, mPort;
uint8_t mServerPublicKey[32]; // Assuming 256-bit public key
};
}

View File

@@ -0,0 +1,27 @@
// libsodium_wrapper.hpp - Libsodium Wrapper for ColumnLynx
// Copyright (C) 2025 DcruBro
// Distributed under the GPLv3 license. See LICENSE for details.
#pragma once
#include <sodium.h>
#include <stdexcept>
#include <string>
#include <cstdint>
#include <columnlynx/common/utils.hpp>
namespace ColumnLynx::Utils {
class LibSodiumWrapper {
public:
LibSodiumWrapper();
uint8_t* getPublicKey();
uint8_t* getPrivateKey();
uint8_t generateRandomAESKey();
private:
uint8_t mPublicKey[crypto_kx_PUBLICKEYBYTES];
uint8_t mPrivateKey[crypto_kx_SECRETKEYBYTES];
};
}

View File

@@ -0,0 +1,19 @@
// net_helper.hpp - Network Helper Functions for ColumnLynx
// Copyright (C) 2025 DcruBro
// Distributed under the GPLv3 license. See LICENSE for details.
#pragma once
#include <asio/asio.hpp>
namespace ColumnLynx::Net::TCP {
class NetHelper {
public:
inline static bool isExpectedDisconnect(const asio::error_code& ec) {
using asio::error::operation_aborted;
using asio::error::bad_descriptor;
using asio::error::eof;
return ec == operation_aborted || ec == bad_descriptor || ec == eof;
}
};
}

View File

@@ -0,0 +1,44 @@
// tcp_message_handler.hpp - TCP Message Handler for ColumnLynx
// Copyright (C) 2025 DcruBro
// Distributed under the GPLv3 license. See LICENSE for details.
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <functional>
#include <asio/asio.hpp>
#include <columnlynx/common/net/tcp/tcp_message_type.hpp>
#include <columnlynx/common/utils.hpp>
namespace ColumnLynx::Net::TCP {
class MessageHandler : public std::enable_shared_from_this<MessageHandler> {
public:
MessageHandler(asio::ip::tcp::socket socket)
: mSocket(std::move(socket)) {}
asio::ip::tcp::socket &socket() { return mSocket; }
void start();
void sendMessage(AnyMessageType type, const std::string &payload = "");
void onMessage(std::function<void(AnyMessageType, std::string)> callback);
void onDisconnect(std::function<void(const asio::error_code&)> callback) {
mOnDisconnect = std::move(callback);
}
static AnyMessageType decodeMessageType(uint8_t code);
static uint8_t toUint8(const AnyMessageType& type);
private:
void mReadHeader();
void mReadBody(uint16_t length);
asio::ip::tcp::socket mSocket;
AnyMessageType mCurrentType = ServerMessageType::KILL_CONNECTION; // Doesn't matter initial value
std::array<uint8_t, 3> mHeader{}; // [type][lenHigh][lenLow]
std::vector<uint8_t> mBody;
std::function<void(AnyMessageType, std::string)> mOnMessage;
std::function<void(asio::error_code&)> mOnDisconnect;
};
}

View File

@@ -0,0 +1,32 @@
// tcp_message_type.hpp - TCP Message Types for ColumnLynx
// Copyright (C) 2025 DcruBro
// Distributed under the GPLv3 license. See LICENSE for details.
#pragma once
#include <cstdint>
#include <variant>
namespace ColumnLynx::Net::TCP {
enum class ServerMessageType : uint8_t { // Server to Client
HANDSHAKE_IDENTIFY = 0x02, // Send server identity (public key, server name, etc)
HANDSHAKE_CHALLENGE_RESPONSE = 0x04, // Response to client's challenge
HANDSHAKE_EXCHANGE_KEY = 0x06, // If accepted, send encrypted AES key and session ID
GRACEFUL_DISCONNECT = 0xFE, // Notify client of impending disconnection
KILL_CONNECTION = 0xFF, // Forecefully terminate the connection (with cleanup if possible), reserved for unrecoverable errors
};
enum class ClientMessageType : uint8_t { // Client to Server
HANDSHAKE_INIT = 0xA1, // Request connection
HANDSHAKE_CHALLENGE = 0xA3, // Challenge ownership of private key
HANDSHAKE_CONFIRM = 0xA5, // Accept or reject identity, can kill the connection
HANDSHAKE_EXCHANGE_KEY_CONFIRM = 0xA7, // Confirm receipt of AES key and session ID
GRACEFUL_DISCONNECT = 0xFE, // Notify server of impending disconnection
KILL_CONNECTION = 0xFF, // Forecefully terminate the connection (with cleanup if possible), reserved for unrecoverable errors
};
// Make a variant type for either message type
using AnyMessageType = std::variant<ServerMessageType, ClientMessageType>;
}

View File

@@ -0,0 +1,214 @@
// panic_handler.hpp - Panic Handler for ColumnLynx
// Copyright (C) 2025 DcruBro
// Distributed under the GPLv3 license. See LICENSE for details.
#pragma once
#include <iostream>
#include <fstream>
#include <csignal>
#include <cstdlib>
#include <exception>
#include <ctime>
#include <string>
#include <chrono>
#ifdef _WIN32
#include <windows.h>
#include <dbghelp.h>
#include <psapi.h>
#include <processthreadsapi.h>
#pragma comment(lib, "dbghelp.lib")
#else
#include <execinfo.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <sys/resource.h>
#endif
namespace ColumnLynx::Utils {
class PanicHandler {
public:
static void init() {
std::set_terminate(terminateHandler);
std::signal(SIGSEGV, signalHandler);
std::signal(SIGABRT, signalHandler);
std::signal(SIGFPE, signalHandler);
std::signal(SIGILL, signalHandler);
std::signal(SIGTERM, signalHandler);
}
private:
static void signalHandler(int signal) {
std::string reason;
switch (signal) {
case SIGSEGV: reason = "Segmentation Fault"; break;
case SIGABRT: reason = "Abort (SIGABRT)"; break;
case SIGFPE: reason = "Floating Point Exception"; break;
case SIGILL: reason = "Illegal Instruction"; break;
case SIGTERM: reason = "Termination Signal"; break;
default: reason = "Unknown Fatal Signal"; break;
}
panic(reason);
std::_Exit(EXIT_FAILURE);
}
static void terminateHandler() {
if (auto eptr = std::current_exception()) {
try {
std::rethrow_exception(eptr);
} catch (const std::exception& e) {
panic(std::string("Unhandled exception: ") + e.what());
} catch (...) {
panic("Unhandled non-standard exception");
}
} else {
panic("Unknown termination cause");
}
std::_Exit(EXIT_FAILURE);
}
static void dumpStack(std::ostream& out) {
#ifdef _WIN32
void* stack[64];
HANDLE process = GetCurrentProcess();
SymInitialize(process, NULL, TRUE);
USHORT frames = CaptureStackBackTrace(0, 64, stack, NULL);
SYMBOL_INFO* symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256, 1);
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
#if DEBUG || _DEBUG
IMAGEHLP_LINE64 line;
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
DWORD displacement = 0;
#endif
for (USHORT i = 0; i < frames; i++) {
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
out << i << ": " << symbol->Name << " - 0x"
<< std::hex << symbol->Address << std::dec;
#if DEBUG || _DEBUG
// Try to resolve file and line info if available
if (SymGetLineFromAddr64(process, (DWORD64)(stack[i]), &displacement, &line)) {
out << " (" << line.FileName << ":" << line.LineNumber << ")";
}
#endif
out << "\n";
}
free(symbol);
#else
void* buffer[50];
int nptrs = backtrace(buffer, 50);
char** strings = backtrace_symbols(buffer, nptrs);
// --- NEW: Detect base address for PIE binaries ---
uintptr_t base_addr = 0;
{
FILE* maps = fopen("/proc/self/maps", "r");
if (maps) {
char line[256];
if (fgets(line, sizeof(line), maps)) {
// First line usually looks like: "55b6d71a0000-55b6d71c0000 r--p ..."
sscanf(line, "%lx-", &base_addr);
}
fclose(maps);
}
}
if (strings) {
for (int i = 0; i < nptrs; ++i) {
out << i << ": " << strings[i] << "\n";
#if DEBUG || _DEBUG
// Adjust address for PIE executables
uintptr_t addr = (uintptr_t)buffer[i] - base_addr;
// Use addr2line to resolve file and line number
char cmd[512];
snprintf(cmd, sizeof(cmd),
"addr2line -e /proc/%d/exe %p 2>/dev/null",
getpid(), (void*)addr);
FILE* fp = popen(cmd, "r");
if (fp) {
char line[256];
if (fgets(line, sizeof(line), fp)) {
// addr2line adds a newline already, no need to trim
out << " " << line;
} else {
out << " ??\n";
}
pclose(fp);
}
#endif
}
free(strings);
}
#endif
}
static void dumpSystemInfo(std::ostream& out) {
out << "---- System Info ----\n";
#ifdef _WIN32
out << "Platform: Windows\n";
out << "Process ID: " << GetCurrentProcessId() << "\n";
char cwd[MAX_PATH];
GetCurrentDirectoryA(MAX_PATH, cwd);
out << "Working Dir: " << cwd << "\n";
MEMORYSTATUSEX memInfo;
memInfo.dwLength = sizeof(memInfo);
GlobalMemoryStatusEx(&memInfo);
out << "Memory Load: " << memInfo.dwMemoryLoad << "%\n";
out << "Total Phys: " << memInfo.ullTotalPhys / (1024*1024) << " MB\n";
out << "Avail Phys: " << memInfo.ullAvailPhys / (1024*1024) << " MB\n";
#else
struct utsname unameData;
uname(&unameData);
out << "Platform: " << unameData.sysname << " " << unameData.release
<< " (" << unameData.machine << ")\n";
out << "Process ID: " << getpid() << "\n";
char cwd[256];
if (getcwd(cwd, sizeof(cwd)))
out << "Working Dir: " << cwd << "\n";
struct rusage usage;
getrusage(RUSAGE_SELF, &usage);
out << "Memory usage: " << usage.ru_maxrss << " KB\n";
#endif
out << "----------------------\n";
}
//Panic the main thread and instantly halt execution. This produces a stack trace dump. Do not use by itself, throw an error instead.
static void panic(const std::string& reason) {
std::cerr << "\n***\033[31m MAIN THREAD PANIC! \033[0m***\n";
std::cerr << "Reason: " << reason << "\n";
std::cerr << "Dumping panic trace...\n";
std::ofstream dump("panic_dump.txt", std::ios::trunc);
if (dump.is_open()) {
dump << "==== PANIC DUMP ====\n";
dump << "Time: " << currentTime() << "\n";
dump << "Reason: " << reason << "\n";
dumpSystemInfo(dump);
dump << "---- Stack Trace ----\n";
dumpStack(dump);
dump << "====================\n\n";
}
std::cerr << "Panic trace written to panic_dump.txt\n";
}
static std::string currentTime() {
std::time_t t = std::time(nullptr);
char buf[64];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
return buf;
}
};
}

View File

@@ -0,0 +1,25 @@
// utils.hpp - Utility functions for ColumnLynx
// Copyright (C) 2025 DcruBro
// Distributed under the GPLv3 license. See LICENSE for details.
#pragma once
#include <iostream>
#include <string>
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <sys/utsname.h>
#include <unistd.h>
#endif
namespace ColumnLynx::Utils {
void log(const std::string &msg);
void warn(const std::string &msg);
void error(const std::string &msg);
std::string getHostname();
std::string getVersion();
unsigned short serverPort();
};

View File

@@ -0,0 +1,101 @@
// tcp_connection.hpp - TCP Connection for ColumnLynx
// Copyright (C) 2025 DcruBro
// Distributed under the GPLv3 license. See LICENSE for details.
#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <ctime>
#include <cstdint>
#include <asio/asio.hpp>
#include <columnlynx/common/net/tcp/tcp_message_type.hpp>
#include <columnlynx/common/net/tcp/tcp_message_handler.hpp>
#include <columnlynx/common/utils.hpp>
#include <columnlynx/common/libsodium_wrapper.hpp>
namespace ColumnLynx::Net::TCP {
class TCPConnection : public std::enable_shared_from_this<TCPConnection> {
public:
using pointer = std::shared_ptr<TCPConnection>;
static pointer create(
asio::ip::tcp::socket socket,
Utils::LibSodiumWrapper *libsodium,
std::function<void(pointer)> onDisconnect)
{
auto conn = pointer(new TCPConnection(std::move(socket), libsodium));
conn->mOnDisconnect = std::move(onDisconnect);
return conn;
}
void start() {
mHandler->onMessage([this](AnyMessageType type, const std::string& data) {
mHandleMessage(static_cast<ClientMessageType>(MessageHandler::toUint8(type)), data);
});
mHandler->onDisconnect([this](const asio::error_code& ec) {
Utils::log("Client disconnected: " + mHandler->socket().remote_endpoint().address().to_string() + " - " + ec.message());
disconnect();
});
mHandler->start();
// Placeholder for message handling setup
Utils::log("Client connected: " + mHandler->socket().remote_endpoint().address().to_string());
}
void sendMessage(ServerMessageType type, const std::string& data = "") {
if (mHandler) {
mHandler->sendMessage(type, data);
}
}
void setDisconnectCallback(std::function<void(std::shared_ptr<TCPConnection>)> cb) {
mOnDisconnect = std::move(cb);
}
void disconnect() {
std::string ip = mHandler->socket().remote_endpoint().address().to_string();
asio::error_code ec;
mHandler->socket().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
mHandler->socket().close(ec);
Utils::log("Closed connection to " + ip);
if (mOnDisconnect) {
mOnDisconnect(shared_from_this());
}
}
private:
TCPConnection(asio::ip::tcp::socket socket, Utils::LibSodiumWrapper *libsodium)
: mHandler(std::make_shared<MessageHandler>(std::move(socket))), mLibSodiumWrapper(libsodium) {}
void mHandleMessage(ClientMessageType type, const std::string& data) {
std::string reqAddr = mHandler->socket().remote_endpoint().address().to_string();
switch (type) {
case ClientMessageType::HANDSHAKE_INIT: {
Utils::log("Received HANDSHAKE_INIT from " + reqAddr + ": " + data);
mHandler->sendMessage(ServerMessageType::HANDSHAKE_IDENTIFY, std::string(reinterpret_cast<const char*>(mLibSodiumWrapper->getPublicKey())));
break;
}
case ClientMessageType::GRACEFUL_DISCONNECT: {
Utils::log("Received GRACEFUL_DISCONNECT from " + reqAddr + ": " + data);
disconnect();
break;
}
default:
Utils::warn("Unhandled message type from " + reqAddr);
break;
}
}
std::shared_ptr<MessageHandler> mHandler;
std::function<void(std::shared_ptr<TCPConnection>)> mOnDisconnect;
Utils::LibSodiumWrapper *mLibSodiumWrapper;
};
}

View File

@@ -0,0 +1,38 @@
// tcp_server.hpp - TCP Server for ColumnLynx
// Copyright (C) 2025 DcruBro
// Distributed under the GPLv3 license. See LICENSE for details.
#pragma once
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
#include <memory>
#include <unordered_set>
#include <asio/asio.hpp>
#include <columnlynx/common/net/tcp/tcp_message_type.hpp>
#include <columnlynx/common/utils.hpp>
#include <columnlynx/server/net/tcp/tcp_connection.hpp>
#include <columnlynx/common/libsodium_wrapper.hpp>
namespace ColumnLynx::Net::TCP {
class TCPServer {
public:
TCPServer(asio::io_context& ioContext, uint16_t port, Utils::LibSodiumWrapper* sodiumWrapper)
: mIoContext(ioContext), mAcceptor(ioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)), mSodiumWrapper(sodiumWrapper)
{
Utils::log("Started TCP server on port " + std::to_string(port));
mStartAccept();
}
private:
void mStartAccept();
asio::io_context &mIoContext;
asio::ip::tcp::acceptor mAcceptor;
std::unordered_set<TCPConnection::pointer> mClients;
Utils::LibSodiumWrapper *mSodiumWrapper;
};
}