From 4fc730ae0650c8b722ad91579b2dd6c9b82c1703 Mon Sep 17 00:00:00 2001 From: AlexandreRouma Date: Sat, 14 Mar 2026 22:18:46 -0400 Subject: [PATCH] initial commit --- .gitignore | 3 + CMakeLists.txt | 14 + license | 1 + src/commands/daemon/daemon.cpp | 70 ++++ src/commands/daemon/daemon.h | 6 + src/commands/daemon/routing_worker.cpp | 23 ++ src/commands/daemon/routing_worker.h | 29 ++ src/commands/daemon/udp_worker.cpp | 63 ++++ src/commands/daemon/udp_worker.h | 40 ++ src/main.cpp | 51 +++ src/version.h | 13 + vendor/cli/cli.cpp | 483 +++++++++++++++++++++++++ vendor/cli/cli.h | 195 ++++++++++ vendor/flog/flog.cpp | 259 +++++++++++++ vendor/flog/flog.h | 77 ++++ vendor/net/net.cpp | 428 ++++++++++++++++++++++ vendor/net/net.h | 282 +++++++++++++++ 17 files changed, 2037 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 license create mode 100644 src/commands/daemon/daemon.cpp create mode 100644 src/commands/daemon/daemon.h create mode 100644 src/commands/daemon/routing_worker.cpp create mode 100644 src/commands/daemon/routing_worker.h create mode 100644 src/commands/daemon/udp_worker.cpp create mode 100644 src/commands/daemon/udp_worker.h create mode 100644 src/main.cpp create mode 100644 src/version.h create mode 100644 vendor/cli/cli.cpp create mode 100644 vendor/cli/cli.h create mode 100644 vendor/flog/flog.cpp create mode 100644 vendor/flog/flog.h create mode 100644 vendor/net/net.cpp create mode 100644 vendor/net/net.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb7cd97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +.vscode/ +.old/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2abbcd3 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.13) +project(vpn) + +file(GLOB_RECURSE SRC "src/*.cpp" "vendor/*.cpp") + +add_executable(${PROJECT_NAME} ${SRC}) + +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) + +target_include_directories(${PROJECT_NAME} PRIVATE "vendor/") + +target_compile_definitions(${PROJECT_NAME} PRIVATE VERSION_MAJOR=0) +target_compile_definitions(${PROJECT_NAME} PRIVATE VERSION_MINOR=1) +target_compile_definitions(${PROJECT_NAME} PRIVATE VERSION_BUILD=0) \ No newline at end of file diff --git a/license b/license new file mode 100644 index 0000000..edac388 --- /dev/null +++ b/license @@ -0,0 +1 @@ +Copyright Alexandre Rouma 2026, All rights reserved. \ No newline at end of file diff --git a/src/commands/daemon/daemon.cpp b/src/commands/daemon/daemon.cpp new file mode 100644 index 0000000..9c2870e --- /dev/null +++ b/src/commands/daemon/daemon.cpp @@ -0,0 +1,70 @@ +#include "daemon.h" +#include "net/net.h" +#include +#include +#include +#include "flog/flog.h" +#include "../../version.h" +#include "signal.h" +#include "openssl/evp.h" +#include "openssl/rsa.h" +#include "openssl/aes.h" +#include "udp_worker.h" + +namespace cmd::daemon { + // Sigint handling variables + bool stop = false; + std::mutex stopMtx; + std::condition_variable stopCond; + + void sigintHandler(int sig) { + // Set the stop flag + stopMtx.lock(); + stop = true; + stopMtx.unlock(); + + // Notify the main thread + stopCond.notify_all(); + } + + int main(std::shared_ptr cmd) { + // Show the information + flog::info("VPN v{}.{}.{}", VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD); + + // Register the sigint handler + signal(SIGINT, sigintHandler); + + // Start the network worker + UDPWorker udpWorker((*cmd)["port"]); + udpWorker.start(); + + // Show the status + flog::info("Ready."); + + // Wait for sigint + while (true) { + // Wait for a notification + std::unique_lock lck(stopMtx); + stopCond.wait(lck); + + // Check if required to stop + if (stop) { break; } + } + + // Remove the sigint handler + signal(SIGINT, NULL); + + // Show a confirmation message + printf("\n"); + flog::info("SIGINT received, stopping..."); + + // Stop the workers + udpWorker.stop(); + + // Final info message + flog::info("All done! Exiting."); + + // Return successfully + return 0; + } +} \ No newline at end of file diff --git a/src/commands/daemon/daemon.h b/src/commands/daemon/daemon.h new file mode 100644 index 0000000..23e5093 --- /dev/null +++ b/src/commands/daemon/daemon.h @@ -0,0 +1,6 @@ +#pragma once +#include "cli/cli.h" + +namespace cmd::daemon { + int main(std::shared_ptr cmd); +} \ No newline at end of file diff --git a/src/commands/daemon/routing_worker.cpp b/src/commands/daemon/routing_worker.cpp new file mode 100644 index 0000000..c275a5c --- /dev/null +++ b/src/commands/daemon/routing_worker.cpp @@ -0,0 +1,23 @@ +#include "routing_worker.h" + +namespace cmd::daemon { + RoutingWorker::RoutingWorker() {} + + + RoutingWorker::~RoutingWorker() { + // Stop if running + stop(); + } + + void RoutingWorker::start() { + + } + + void RoutingWorker::stop() { + + } + + void RoutingWorker::worker() { + + } +} \ No newline at end of file diff --git a/src/commands/daemon/routing_worker.h b/src/commands/daemon/routing_worker.h new file mode 100644 index 0000000..74aac7d --- /dev/null +++ b/src/commands/daemon/routing_worker.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include + +namespace cmd::daemon { + class RoutingWorker { + public: + // Default constructor + RoutingWorker(); + + // Destructor + ~RoutingWorker(); + + /** + * Start the worker. + */ + void start(); + + /** + * Stop the worker. + */ + void stop(); + + private: + void worker(); + + std::thread workerThread; + }; +} \ No newline at end of file diff --git a/src/commands/daemon/udp_worker.cpp b/src/commands/daemon/udp_worker.cpp new file mode 100644 index 0000000..f3aeaa1 --- /dev/null +++ b/src/commands/daemon/udp_worker.cpp @@ -0,0 +1,63 @@ +#include "udp_worker.h" +#include "flog/flog.h" + +#define DGRAM_MAX_SIZE 0x10000 + +namespace cmd::daemon { + UDPWorker::UDPWorker() {} + + UDPWorker::~UDPWorker() { + // Stop if running + stop(); + } + + void UDPWorker::start() { + // If already running, do nothing + if (running) { return; } + + // Open the socket + sock = net::openudp(net::Address(), net::Address("0.0.0.0", port)); + + // Set the run flag + running = true; + + // Start the thread + workerThread = std::thread(&UDPWorker::worker, this); + } + + void UDPWorker::stop() { + // If not running, do nothing + if (!running) { return; } + + // Clear the run flag + running = false; + + // Close the socket + sock->close(); + + // Wait for the worker to exit + workerThread.join(); + + // Free the socket + sock.reset(); + } + + void UDPWorker::worker() { + // Allocate a buffer for the datagram + uint8_t* dgram = new uint8_t[DGRAM_MAX_SIZE]; + + // Receive loop + while (running) { + // Receive a datagram + net::Address raddr; + int len = sock->recv(dgram, DGRAM_MAX_SIZE, false, net::NO_TIMEOUT, &raddr); + if (len <= 0) { break; } + + // Send it to the proccesing worker + // TODO + } + + // Free the datagram buffer + delete[] dgram; + } +} \ No newline at end of file diff --git a/src/commands/daemon/udp_worker.h b/src/commands/daemon/udp_worker.h new file mode 100644 index 0000000..b89433a --- /dev/null +++ b/src/commands/daemon/udp_worker.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include "net/net.h" + +namespace cmd::daemon { + class UDPWorker { + public: + // Default constructor + UDPWorker(); + + /** + * Create a UDP worker. + * @param port Port on which to listen for datagrams. + */ + UDPWorker(int port); + + // Destructor + ~UDPWorker(); + + /** + * Start the worker. + */ + void start(); + + /** + * Stop the worker. + */ + void stop(); + + private: + void worker(); + + int port = -1; + std::shared_ptr sock; + std::atomic_bool running = false; + std::thread workerThread; + }; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..7a5a136 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,51 @@ +#include +#include +#include "cli/cli.h" +#include "commands/daemon/daemon.h" + +int main(int argc, char** argv) { + try { + // Define the daemon interface + cli::Interface daemonCLI; + daemonCLI.arg("port", 'p', 4269, "Port on which to run the VPN"); + + // Define the root command line interface + cli::Interface rootCLI; + rootCLI.arg("help", 'h', false, "Show help information"); + rootCLI.subcmd("daemon", daemonCLI, "Run the VPN daemon"); + + // Parse the command line + auto cmd = cli::parse(rootCLI, argc, argv); + + // If there is no subcommand, show help + if (!cmd.subcommand) { + // Show help + // TODO + + // Return unsuccessfully + return -1; + } + + // Execute the command + if (cmd.subcommand->command == "daemon") { + cmd::daemon::main(cmd.subcommand); + } + else { + // Show help + // TODO + + // Return unsuccessfully + return -1; + } + + // Return successfully + return 0; + } + catch (const std::exception& e) { + // Show the error + fprintf(stderr, "ERROR: %s\n", e.what()); + + // Return unsuccessfully + return -1; + } +} \ No newline at end of file diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..e4a71e4 --- /dev/null +++ b/src/version.h @@ -0,0 +1,13 @@ +#pragma once + +#ifndef VERSION_MAJOR +#define VERSION_MAJOR 0 +#endif + +#ifndef VERSION_MINOR +#define VERSION_MINOR 0 +#endif + +#ifndef VERSION_BUILD +#define VERSION_BUILD 0 +#endif \ No newline at end of file diff --git a/vendor/cli/cli.cpp b/vendor/cli/cli.cpp new file mode 100644 index 0000000..a523710 --- /dev/null +++ b/vendor/cli/cli.cpp @@ -0,0 +1,483 @@ +#include "cli.h" +#include +#include +#include + +namespace cli { + const std::vector trueStrings = { "TRUE", "Y", "YES", "ON", "1" }; + const std::vector falseStrings = { "FALSE", "N", "NO", "OFF", "0" }; + + Value::Value() : _type(VAL_TYPE_INVALID) {} + + Value::Value(const char* value) : _type(VAL_TYPE_STRING) { string = value; } + Value::Value(const std::string& value) : _type(VAL_TYPE_STRING) { string = value; } + Value::Value(uint8_t value) : _type(VAL_TYPE_UNSIGNED_INTEGER) { uinteger = value; } + Value::Value(uint16_t value) : _type(VAL_TYPE_UNSIGNED_INTEGER) { uinteger = value; } + Value::Value(uint32_t value) : _type(VAL_TYPE_UNSIGNED_INTEGER) { uinteger = value; } + Value::Value(uint64_t value) : _type(VAL_TYPE_UNSIGNED_INTEGER) { uinteger = value; } + Value::Value(int8_t value) : _type(VAL_TYPE_SIGNED_INTEGER) { sinteger = value; } + Value::Value(int16_t value) : _type(VAL_TYPE_SIGNED_INTEGER) { sinteger = value; } + Value::Value(int32_t value) : _type(VAL_TYPE_SIGNED_INTEGER) { sinteger = value; } + Value::Value(int64_t value) : _type(VAL_TYPE_SIGNED_INTEGER) { sinteger = value; } + Value::Value(float value) : _type(VAL_TYPE_FLOATING) { floating = value; } + Value::Value(double value) : _type(VAL_TYPE_FLOATING) { floating = value;; } + Value::Value(bool value) : _type(VAL_TYPE_BOOLEAN) { boolean = value; } + + Value::Value(ValueType type, const std::string& str) : _type(type) { + std::string upperStr; + + switch (type) { + case VAL_TYPE_STRING: + // Copy the string directly + string = str; + return; + case VAL_TYPE_UNSIGNED_INTEGER: + // Parse signed integer or throw error + try { + uinteger = std::stoull(str); + } + catch (const std::exception& e) { + throw std::runtime_error("Expected an unsigned integer value"); + } + return; + case VAL_TYPE_SIGNED_INTEGER: + // Parse signed integer or throw error + try { + sinteger = std::stoll(str); + } + catch (const std::exception& e) { + throw std::runtime_error("Expected a signed integer value"); + } + return; + case VAL_TYPE_FLOATING: + // Parse float or throw error + try { + floating = std::stod(str); + } + catch (const std::exception& e) { + throw std::runtime_error("Expected a floating point value"); + } + return; + case VAL_TYPE_BOOLEAN: + // Convert to upper case + for (char c : str) { upperStr += std::toupper(c); } + + // Check for a true value + if (std::find(trueStrings.begin(), trueStrings.end(), upperStr) != trueStrings.end()) { + boolean = true; + return; + } + + // Check for false strings + if (std::find(falseStrings.begin(), falseStrings.end(), upperStr) != falseStrings.end()) { + boolean = false; + return; + } + + // Invalid, throw error + throw std::runtime_error("Expected a boolean value"); + return; + default: + throw std::runtime_error("Unsupported type"); + } + } + + Value::Value(const Value& b) : _type(b._type) { + // Copy the member of the designated type + switch (_type) { + case VAL_TYPE_STRING: + string = b.string; break; + case VAL_TYPE_UNSIGNED_INTEGER: + uinteger = b.uinteger; break; + case VAL_TYPE_SIGNED_INTEGER: + sinteger = b.sinteger; break; + case VAL_TYPE_FLOATING: + floating = b.floating; break; + case VAL_TYPE_BOOLEAN: + boolean = b.boolean; break; + default: + break; + } + } + + Value::Value(Value&& b) : _type(b._type) { + // Move the member of the designated type + switch (_type) { + case VAL_TYPE_STRING: + string = std::move(b.string); break; + case VAL_TYPE_UNSIGNED_INTEGER: + uinteger = b.uinteger; break; + case VAL_TYPE_SIGNED_INTEGER: + sinteger = b.sinteger; break; + case VAL_TYPE_FLOATING: + floating = b.floating; break; + case VAL_TYPE_BOOLEAN: + boolean = b.boolean; break; + default: + break; + } + } + + Value& Value::operator=(const Value& b) { + // Update the type + _type = b._type; + + // Copy the member of the designated type + switch (_type) { + case VAL_TYPE_STRING: + string = b.string; break; + case VAL_TYPE_UNSIGNED_INTEGER: + uinteger = b.uinteger; string.clear(); break; + case VAL_TYPE_SIGNED_INTEGER: + sinteger = b.sinteger; string.clear(); break; + case VAL_TYPE_FLOATING: + floating = b.floating; string.clear(); break; + case VAL_TYPE_BOOLEAN: + boolean = b.boolean; string.clear(); break; + default: + break; + } + + // Return self + return *this; + } + + Value& Value::operator=(Value&& b) { + // Update the type + _type = b._type; + + // Move the member of the designated type + switch (_type) { + case VAL_TYPE_STRING: + string = std::move(b.string); break; + case VAL_TYPE_UNSIGNED_INTEGER: + uinteger = b.uinteger; string.clear(); break; + case VAL_TYPE_SIGNED_INTEGER: + sinteger = b.sinteger; string.clear(); break; + case VAL_TYPE_FLOATING: + floating = b.floating; string.clear(); break; + case VAL_TYPE_BOOLEAN: + boolean = b.boolean; string.clear(); break; + default: + break; + } + + // Return self + return *this; + } + + Value::operator const std::string&() const { + if (_type != VAL_TYPE_STRING) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return string; + } + + Value::operator uint8_t() const { + if (_type != VAL_TYPE_UNSIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return (uint8_t)uinteger; + } + + Value::operator uint16_t() const { + if (_type != VAL_TYPE_UNSIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return (uint16_t)uinteger; + } + + Value::operator uint32_t() const { + if (_type != VAL_TYPE_UNSIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return (uint32_t)uinteger; + } + + Value::operator uint64_t() const { + if (_type != VAL_TYPE_UNSIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return uinteger; + } + + Value::operator int8_t() const { + if (_type != VAL_TYPE_SIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return (int8_t)sinteger; + } + + Value::operator int16_t() const { + if (_type != VAL_TYPE_SIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return (int16_t)sinteger; + } + + Value::operator int32_t() const { + if (_type != VAL_TYPE_SIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return (int32_t)sinteger; + } + + Value::operator int64_t() const { + if (_type != VAL_TYPE_SIGNED_INTEGER) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return sinteger; + } + + Value::operator float() const { + if (_type != VAL_TYPE_FLOATING) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return (float)floating; + } + + Value::operator double() const { + if (_type != VAL_TYPE_FLOATING) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return floating; + } + + Value::operator bool() const { + if (_type != VAL_TYPE_BOOLEAN) { throw std::runtime_error("Cannot cast value due to type mismatch"); } + return boolean; + } + + Interface::Interface() {} + + Interface::Interface(const Interface& b) { + // Copy all members + aliases = b.aliases; + arguments = b.arguments; + subcommands = b.subcommands; + } + + Interface::Interface(Interface&& b) { + // Move all members + aliases = std::move(b.aliases); + arguments = std::move(b.arguments); + subcommands = std::move(b.subcommands); + } + + Interface& Interface::operator=(const Interface& b) { + // Copy all members + aliases = b.aliases; + arguments = b.arguments; + subcommands = b.subcommands; + + // Return self + return *this; + } + + Interface& Interface::operator=(Interface&& b) { + // Move all members + aliases = std::move(b.aliases); + arguments = std::move(b.arguments); + subcommands = std::move(b.subcommands); + + // Return self + return *this; + } + + void Interface::arg(const std::string& name, char alias, Value defValue, const std::string& description) { + // Check if an argument with that name already exists + if (arguments.find(name) != arguments.end()) { + throw std::runtime_error("An argument with the given name already exists"); + } + + // If an alias was given + if (alias) { + // Check if an alias with that character already exists + if (aliases.find(alias) != aliases.end()) { + throw std::runtime_error("An argument with the given alias already exists"); + } + + // Save the alias + aliases[alias] = name; + } + + // Save the argument + Argument arg = { defValue, description }; + arguments[name] = arg; + } + + void Interface::subcmd(const std::string& name, const Interface& interface, const std::string& description) { + // Check if a subcommand of that name does not exist yet + if (subcommands.find(name) != subcommands.end()) { + throw std::runtime_error("A subcommand with the given name already exists"); + } + + // Save the interface of the subcommand + SubCommand scmd = { std::make_shared(interface), description }; + subcommands[name] = scmd; + } + + Command::Command() {} + + Command::Command(const std::string& command, const Interface& interface) { + // Save the command + this->command = command; + + // Go through all defined arguments + for (const auto& [name, arg] : interface.arguments) { + // Initialize the argument with the default value + arguments[name] = arg.defValue; + } + } + + Command::Command(const Command& b) { + // Copy all members + subcommand = b.subcommand; + command = b.command; + arguments = b.arguments; + values = b.values; + } + + Command::Command(Command&& b) { + // Move all members + subcommand = std::move(b.subcommand); + command = std::move(b.command); + arguments = std::move(b.arguments); + values = std::move(b.values); + } + + Command& Command::operator=(const Command& b) { + // Copy b since it could be allocated by this->subcommand + Command bcpy = b; + + // Move all members from the copy to self + subcommand = std::move(bcpy.subcommand); + command = std::move(bcpy.command); + arguments = std::move(bcpy.arguments); + values = std::move(bcpy.values); + + // Return self + return *this; + } + + Command& Command::operator=(Command&& b) { + // Move b since it could be allocated by this->subcommand + Command bcpy = std::move(b); + + // Move all members from the copy to self + subcommand = std::move(bcpy.subcommand); + command = std::move(bcpy.command); + arguments = std::move(bcpy.arguments); + values = std::move(bcpy.values); + + // Return self + return *this; + } + + Command::operator const std::string&() const { + return command; + } + + bool Command::operator==(const std::string& b) const { + return (command == b); + } + + bool Command::operator==(const char* b) const { + return !strcmp(command.c_str(), b); + } + + const Value& Command::operator[](const std::string& arg) const { + // Get value from argument list + return arguments.at(arg); + } + + bool isValidBoolean(const std::string& str) { + // Convert to upper case + std::string upperStr; + for (char c : str) { upperStr += std::toupper(c); } + return std::find(trueStrings.begin(), trueStrings.end(), upperStr) != trueStrings.end() || + std::find(falseStrings.begin(), falseStrings.end(), upperStr) != falseStrings.end(); + } + + void parseArgument(Command& cmd, std::string& argName, ValueType type, int& argc, char**& argv) { + // If the argument is a boolean + if (type == VAL_TYPE_BOOLEAN) { + // If no value follows it's not a valid boolean + if (!argc || !isValidBoolean(*argv)) { + // Assume the value is true + cmd.arguments[argName] = true; + return; + } + } + + // Expect a value + if (!argc) { throw std::runtime_error("Expected a value"); } + + // Pop the value + std::string value = *(argv++); argc--; + + // Parse and set the value + cmd.arguments[argName] = Value(type, value); + } + + Command parse(const Interface& interface, int argc, char** argv) { + // Pop the command name + std::string cmdName = *(argv++); argc--; + + // Initialize the command + Command cmd(cmdName, interface); + + // Process until no arguments are left + while (argc) { + // Pop the argument + std::string arg = *(argv++); argc--; + + // If the argument gives a long name + if (arg.starts_with("--")) { + // Verify that the command admits the given argument + std::string argName = arg.substr(2); + auto it = interface.arguments.find(argName); + if (it == interface.arguments.end()) { + throw std::runtime_error("Unknown argument"); + } + + // Parse the argument + parseArgument(cmd, argName, it->second.defValue.type, argc, argv); + continue; + } + + // Otherwise if the argument gives aliases + if (arg.starts_with("-")) { + // Iterate through each alias + for (int i = 1; i < arg.size(); i++) { + // Get the alias + char c = arg[i]; + + // Verify that the command admits the given alias + auto it = interface.aliases.find(c); + if (it == interface.aliases.end()) { + throw std::runtime_error("Unknown argument"); + } + + // Fetch the argument descriptor + std::string argName = it->second; + const auto& desc = interface.arguments.at(argName); + + // If the argument is not at the end of the compound argument + if (i < arg.size()-1) { + // if the argument is a boolean + if (desc.defValue.type == VAL_TYPE_BOOLEAN) { + // Assume the value is true + cmd.arguments[argName] = true; + continue; + } + + // Non-boolean arguments are not allowed before the end of the compound argument + throw std::runtime_error("Non boolean argumentcan only be at the end of a compound argument"); + } + + // Parse the argument + parseArgument(cmd, argName, desc.defValue.type, argc, argv); + } + continue; + } + + // If the interface needs a subcommand + if (!interface.subcommands.empty()) { + // Verify that the command admits the given subcommand + auto it = interface.subcommands.find(arg); + if (it == interface.subcommands.end()) { + throw std::runtime_error("Unknown sub-command"); + } + + // Parse the subcommand and finish + cmd.subcommand = std::make_shared(parse(*(it->second.iface), ++argc, --argv)); + break; + } + + // Just append the argument to the value list + cmd.values.push_back(arg); + } + + // Return the newly parsed command + return cmd; + } +} \ No newline at end of file diff --git a/vendor/cli/cli.h b/vendor/cli/cli.h new file mode 100644 index 0000000..75b2fc1 --- /dev/null +++ b/vendor/cli/cli.h @@ -0,0 +1,195 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace cli { + enum ValueType { + VAL_TYPE_INVALID = -1, + VAL_TYPE_STRING, + VAL_TYPE_UNSIGNED_INTEGER, + VAL_TYPE_SIGNED_INTEGER, + VAL_TYPE_FLOATING, + VAL_TYPE_BOOLEAN + }; + + class Value { + public: + // Default constructor + Value(); + + /** + * Create a Value object from a common type. + * @param value Value to create the object from. + */ + Value(const char* value); + Value(const std::string& value); + Value(uint8_t value); + Value(uint16_t value); + Value(uint32_t value); + Value(uint64_t value); + Value(int8_t value); + Value(int16_t value); + Value(int32_t value); + Value(int64_t value); + Value(float value); + Value(double value); + Value(bool value); + + /** + * Create value by parsing a string. + * @param type Expected value type. + * @param str String to parse. + */ + Value(ValueType type, const std::string& str); + + // Copy constructor + Value(const Value& b); + + // Move constructor + Value(Value&& b); + + // Copy assignment operator + Value& operator=(const Value& b); + + // Move assignment operator + Value& operator=(Value&& b); + + // Cast operator + operator const std::string&() const; + operator uint8_t() const; + operator uint16_t() const; + operator uint32_t() const; + operator uint64_t() const; + operator int8_t() const; + operator int16_t() const; + operator int32_t() const; + operator int64_t() const; + operator float() const; + operator double() const; + operator bool() const; + + // Type of the value + const ValueType& type = _type; + + private: + ValueType _type; + std::string string; + uint64_t uinteger; + int64_t sinteger; + double floating; + bool boolean; + }; + + class Command; + + class Interface { + public: + // Default constructor + Interface(); + + // Copy constructor + Interface(const Interface& b); + + // Move constructor + Interface(Interface&& b); + + // Copy assignment operator + Interface& operator=(const Interface& b); + + // Move assignment operator + Interface& operator=(Interface&& b); + + /** + * Define an argument. + * @param name Long name of the argument. + * @param alias Short name of the argument. Zero if none. + * @param defValue Default value that the argument has if not given by the user. + * @param description Description of the argument. + */ + void arg(const std::string& name, char alias, Value defValue, const std::string& description); + + /** + * Define a sub-command. + * @param name Name of the subcommand. + * @param interface Interface definition of the subcommand. + * @param description Description of the subcommand. + */ + void subcmd(const std::string& name, const Interface& interface, const std::string& description); + + // Friends + friend Command; + friend Command parse(const Interface& interface, int argc, char** argv); + + private: + struct Argument { + Value defValue; + std::string desc; + }; + + struct SubCommand { + std::shared_ptr iface; + std::string desc; + }; + + std::map aliases; + std::unordered_map arguments; + std::unordered_map subcommands; + }; + + class Command { + public: + // Default constructor + Command(); + + /** + * Create a command. + * @param command Command run by the user. + * @param interface Interface of the command. + */ + Command(const std::string& command, const Interface& interface); + + // Copy constructor + Command(const Command& b); + + // Move constructor + Command(Command&& b); + + // Copy assignment operator + Command& operator=(const Command& b); + + // Move assignment operator + Command& operator=(Command&& b); + + // Cast to string operator + operator const std::string&() const; + + // Equality operators + bool operator==(const std::string& b) const; + bool operator==(const char* b) const; + + const Value& operator[](const std::string& arg) const; + + // Friends + friend Command parse(const Interface& interface, int argc, char** argv); + + std::shared_ptr subcommand; + + //private: + std::string command; + std::unordered_map arguments; + std::vector values; + }; + + /** + * Parse a command line. + * @param argc Argument count. + * @param argv Argument list. + * @param interface Command line interface. + * @return User command. + */ + Command parse(const Interface& interface, int argc, char** argv); +} \ No newline at end of file diff --git a/vendor/flog/flog.cpp b/vendor/flog/flog.cpp new file mode 100644 index 0000000..d19c6e9 --- /dev/null +++ b/vendor/flog/flog.cpp @@ -0,0 +1,259 @@ +#include "flog.h" +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef __ANDROID__ +#include +#ifndef FLOG_ANDROID_TAG +#define FLOG_ANDROID_TAG "flog" +#endif +#endif + + +#define FORMAT_BUF_SIZE 16 +#define ESCAPE_CHAR '\\' + +namespace flog { + std::mutex outMtx; + + const char* TYPE_STR[_TYPE_COUNT] = { + "DEBUG", + "INFO", + "WARN", + "ERROR" + }; + +#ifdef _WIN32 +#define COLOR_WHITE (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) + const WORD TYPE_COLORS[_TYPE_COUNT] = { + FOREGROUND_GREEN | FOREGROUND_BLUE, + FOREGROUND_GREEN, + FOREGROUND_RED | FOREGROUND_GREEN, + FOREGROUND_RED + }; +#else +#define COLOR_WHITE "\x1B[0m" + const char* TYPE_COLORS[_TYPE_COUNT] = { + "\x1B[36m", + "\x1B[32m", + "\x1B[33m", + "\x1B[31m", + }; +#endif + +#ifdef __ANDROID__ + const android_LogPriority TYPE_PRIORITIES[_TYPE_COUNT] = { + ANDROID_LOG_DEBUG, + ANDROID_LOG_INFO, + ANDROID_LOG_WARN, + ANDROID_LOG_ERROR + }; +#endif + + void __log__(Type type, const char* fmt, const std::vector& args) { + // Reserve a buffer for the final output + int argCount = args.size(); + int fmtLen = strlen(fmt) + 1; + int totSize = fmtLen; + for (const auto& a : args) { totSize += a.size(); } + std::string out; + out.reserve(totSize); + + // Get output stream depending on type + FILE* outStream = (type == TYPE_ERROR) ? stderr : stdout; + + // Parse format string + bool escaped = false; + int formatCounter = 0; + bool inFormat = false; + int formatLen = 0; + char formatBuf[FORMAT_BUF_SIZE+1]; + for (int i = 0; i < fmtLen; i++) { + // Get char + const char c = fmt[i]; + + // If this character is escaped, don't try to parse it + if (escaped) { + escaped = false; + out += c; + continue; + } + + // State machine + if (!inFormat && c != '{') { + // Write to formatted output if not escape character + if (c == ESCAPE_CHAR) { + escaped = true; + } + else { + out += c; + } + } + else if (!inFormat) { + // Start format mode + inFormat = true; + } + else if (c == '}') { + // Stop format mode + inFormat = false; + + // Insert string value or error + if (!formatLen) { + // Use format counter as ID if available or print wrong format string + if (formatCounter < argCount) { + out += args[formatCounter++]; + } + else { + out += "{}"; + } + } + else { + // Parse number + formatBuf[formatLen] = 0; + formatCounter = std::atoi(formatBuf); + + // Use ID if available or print wrong format string + if (formatCounter < argCount) { + out += args[formatCounter]; + } + else { + out += '{'; + out += formatBuf; + out += '}'; + } + + // Increment format counter + formatCounter++; + } + + // Reset format counter + formatLen = 0; + } + else { + // Add to format buffer + if (formatLen < FORMAT_BUF_SIZE) { formatBuf[formatLen++] = c; } + } + } + + // Get time + auto now = std::chrono::system_clock::now(); + auto nowt = std::chrono::system_clock::to_time_t(now); + auto nowc = std::localtime(&nowt); // TODO: This is not threadsafe + + // Write to output + { + std::lock_guard lck(outMtx); +#if defined(_WIN32) + // Get output handle and return if invalid + int wOutStream = (type == TYPE_ERROR) ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE; + HANDLE conHndl = GetStdHandle(wOutStream); + if (!conHndl || conHndl == INVALID_HANDLE_VALUE) { return; } + + // Print beginning of log line + SetConsoleTextAttribute(conHndl, COLOR_WHITE); + fprintf(outStream, "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [", nowc->tm_mday, nowc->tm_mon + 1, nowc->tm_year + 1900, nowc->tm_hour, nowc->tm_min, nowc->tm_sec, 0); + + // Switch color to the log color, print log type and + SetConsoleTextAttribute(conHndl, TYPE_COLORS[type]); + fputs(TYPE_STR[type], outStream); + + + // Switch back to default color and print rest of log string + SetConsoleTextAttribute(conHndl, COLOR_WHITE); + fprintf(outStream, "] %s\n", out.c_str()); +#elif defined(__ANDROID__) + // Print format string + __android_log_print(TYPE_PRIORITIES[type], FLOG_ANDROID_TAG, COLOR_WHITE "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [%s%s" COLOR_WHITE "] %s\n", + nowc->tm_mday, nowc->tm_mon + 1, nowc->tm_year + 1900, nowc->tm_hour, nowc->tm_min, nowc->tm_sec, 0, TYPE_COLORS[type], TYPE_STR[type], out.c_str()); +#else + // Print format string + fprintf(outStream, COLOR_WHITE "[%02d/%02d/%02d %02d:%02d:%02d.%03d] [%s%s" COLOR_WHITE "] %s\n", + nowc->tm_mday, nowc->tm_mon + 1, nowc->tm_year + 1900, nowc->tm_hour, nowc->tm_min, nowc->tm_sec, 0, TYPE_COLORS[type], TYPE_STR[type], out.c_str()); +#endif + } + } + + std::string __toString__(bool value) { + return value ? "true" : "false"; + } + + std::string __toString__(char value) { + return std::string("")+value; + } + + std::string __toString__(int8_t value) { + char buf[8]; + sprintf(buf, "%" PRId8, value); + return buf; + } + + std::string __toString__(int16_t value) { + char buf[16]; + sprintf(buf, "%" PRId16, value); + return buf; + } + + std::string __toString__(int32_t value) { + char buf[32]; + sprintf(buf, "%" PRId32, value); + return buf; + } + + std::string __toString__(int64_t value) { + char buf[64]; + sprintf(buf, "%" PRId64, value); + return buf; + } + + std::string __toString__(uint8_t value) { + char buf[8]; + sprintf(buf, "%" PRIu8, value); + return buf; + } + + std::string __toString__(uint16_t value) { + char buf[16]; + sprintf(buf, "%" PRIu16, value); + return buf; + } + + std::string __toString__(uint32_t value) { + char buf[32]; + sprintf(buf, "%" PRIu32, value); + return buf; + } + + std::string __toString__(uint64_t value) { + char buf[64]; + sprintf(buf, "%" PRIu64, value); + return buf; + } + + std::string __toString__(float value) { + char buf[256]; + sprintf(buf, "%f", value); + return buf; + } + + std::string __toString__(double value) { + char buf[256]; + sprintf(buf, "%lf", value); + return buf; + } + + std::string __toString__(const char* value) { + return value; + } + + std::string __toString__(const void* value) { + char buf[32]; + sprintf(buf, "0x%p", value); + return buf; + } +} \ No newline at end of file diff --git a/vendor/flog/flog.h b/vendor/flog/flog.h new file mode 100644 index 0000000..e178973 --- /dev/null +++ b/vendor/flog/flog.h @@ -0,0 +1,77 @@ +#pragma once +#include +#include +#include + +namespace flog { + enum Type { + TYPE_DEBUG, + TYPE_INFO, + TYPE_WARNING, + TYPE_ERROR, + _TYPE_COUNT + }; + + // IO functions + void __log__(Type type, const char* fmt, const std::vector& args); + + // Conversion functions + std::string __toString__(bool value); + std::string __toString__(char value); + std::string __toString__(int8_t value); + std::string __toString__(int16_t value); + std::string __toString__(int32_t value); + std::string __toString__(int64_t value); + std::string __toString__(uint8_t value); + std::string __toString__(uint16_t value); + std::string __toString__(uint32_t value); + std::string __toString__(uint64_t value); + std::string __toString__(float value); + std::string __toString__(double value); + std::string __toString__(const char* value); + std::string __toString__(const void* value); + template + std::string __toString__(const T& value) { + return (std::string)value; + } + + // Utility to generate a list from arguments + inline void __genArgList__(std::vector& args) {} + template + inline void __genArgList__(std::vector& args, First first, Others... others) { + // Add argument + args.push_back(__toString__(first)); + + // Recursive call that will be unrolled since the function is inline + __genArgList__(args, others...); + } + + // Logging functions + template + void log(Type type, const char* fmt, Args... args) { + std::vector _args; + _args.reserve(sizeof...(args)); + __genArgList__(_args, args...); + __log__(type, fmt, _args); + } + + template + inline void debug(const char* fmt, Args... args) { + log(TYPE_DEBUG, fmt, args...); + } + + template + inline void info(const char* fmt, Args... args) { + log(TYPE_INFO, fmt, args...); + } + + template + inline void warn(const char* fmt, Args... args) { + log(TYPE_WARNING, fmt, args...); + } + + template + inline void error(const char* fmt, Args... args) { + log(TYPE_ERROR, fmt, args...); + } +} \ No newline at end of file diff --git a/vendor/net/net.cpp b/vendor/net/net.cpp new file mode 100644 index 0000000..2abd623 --- /dev/null +++ b/vendor/net/net.cpp @@ -0,0 +1,428 @@ +#include "net.h" +#include +#include +#include + +#ifdef _WIN32 +#define WOULD_BLOCK (WSAGetLastError() == WSAEWOULDBLOCK) +#else +#define WOULD_BLOCK (errno == EWOULDBLOCK) +#endif + +namespace net { + bool _init = false; + + // === Private functions === + + void init() { + if (_init) { return; } +#ifdef _WIN32 + // Initialize WinSock2 + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 2), &wsa)) { + throw std::runtime_error("Could not initialize WinSock2"); + return; + } +#else + // Disable SIGPIPE to avoid closing when the remote host disconnects + signal(SIGPIPE, SIG_IGN); +#endif + _init = true; + } + + bool queryHost(uint32_t* addr, std::string host) { + hostent* ent = gethostbyname(host.c_str()); + if (!ent || !ent->h_addr_list[0]) { return false; } + *addr = *(uint32_t*)ent->h_addr_list[0]; + return true; + } + + void closeSocket(SockHandle_t sock) { +#ifdef _WIN32 + shutdown(sock, SD_BOTH); + closesocket(sock); +#else + shutdown(sock, SHUT_RDWR); + close(sock); +#endif + } + + void setNonblocking(SockHandle_t sock) { +#ifdef _WIN32 + u_long enabled = 1; + ioctlsocket(sock, FIONBIO, &enabled); +#else + fcntl(sock, F_SETFL, O_NONBLOCK); +#endif + } + + // === Address functions === + + Address::Address() { + memset(&addr, 0, sizeof(addr)); + } + + Address::Address(const std::string& host, int port) { + // Initialize WSA if needed + init(); + + // Lookup host + hostent* ent = gethostbyname(host.c_str()); + if (!ent || !ent->h_addr_list[0]) { + throw std::runtime_error("Unknown host"); + } + + // Build address + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = *(uint32_t*)ent->h_addr_list[0]; + addr.sin_port = htons(port); + } + + Address::Address(IP_t ip, int port) { + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(ip); + addr.sin_port = htons(port); + } + + std::string Address::getIPStr() const { + char buf[128]; + IP_t ip = getIP(); + sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); + return buf; + } + + IP_t Address::getIP() const { + return htonl(addr.sin_addr.s_addr); + } + + void Address::setIP(IP_t ip) { + addr.sin_addr.s_addr = htonl(ip); + } + + int Address::getPort() const { + return htons(addr.sin_port); + } + + void Address::setPort(int port) { + addr.sin_port = htons(port); + } + + // === Socket functions === + + Socket::Socket(SockHandle_t sock, const Address* raddr) { + this->sock = sock; + if (raddr) { + this->raddr = new Address(*raddr); + } + } + + Socket::~Socket() { + close(); + if (raddr) { delete raddr; } + } + + void Socket::close() { + if (!open) { return; } + open = false; + closeSocket(sock); + } + + bool Socket::isOpen() { + return open; + } + + SocketType Socket::type() { + return raddr ? SOCKET_TYPE_UDP : SOCKET_TYPE_TCP; + } + + int Socket::send(const uint8_t* data, size_t len, const Address* dest) { + // Send data + int err = sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in)); + + // On error, close socket + if (err <= 0 && !WOULD_BLOCK) { + close(); + return err; + } + + return err; + } + + int Socket::sendstr(const std::string& str, const Address* dest) { + return send((const uint8_t*)str.c_str(), str.length(), dest); + } + + int Socket::recv(uint8_t* data, size_t maxLen, bool forceLen, int timeout, Address* dest) { + // Create FD set + fd_set set; + FD_ZERO(&set); + + int read = 0; + bool blocking = (timeout != NONBLOCKING); + do { + // Wait for data or error if + if (blocking) { + // Enable FD in set + FD_SET(sock, &set); + + // Set timeout + timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000; + + // Wait for data + int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL); + if (err <= 0) { return err; } + } + + // Receive + int addrLen = sizeof(sockaddr_in); + int err = ::recvfrom(sock, (char*)&data[read], maxLen - read, 0,(sockaddr*)(dest ? &dest->addr : NULL), (socklen_t*)(dest ? &addrLen : NULL)); + if (err <= 0 && !WOULD_BLOCK) { + close(); + return err; + } + read += err; + } + while (blocking && forceLen && read < maxLen); + return read; + } + + int Socket::recvline(std::string& str, int maxLen, int timeout, Address* dest) { + // Disallow nonblocking mode + if (!timeout) { return -1; } + + str.clear(); + int read = 0; + while (!maxLen || read < maxLen) { + char c; + int err = recv((uint8_t*)&c, 1, false, timeout, dest); + if (err <= 0) { return err; } + read++; + if (c == '\n') { break; } + str += c; + } + return read; + } + + // === Listener functions === + + Listener::Listener(SockHandle_t sock) { + this->sock = sock; + } + + Listener::~Listener() { + stop(); + } + + void Listener::stop() { + closeSocket(sock); + open = false; + } + + bool Listener::listening() { + return open; + } + + std::shared_ptr Listener::accept(Address* dest, int timeout) { + // Create FD set + fd_set set; + FD_ZERO(&set); + FD_SET(sock, &set); + + // Define timeout + timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000; + + // Wait for data or error + if (timeout != NONBLOCKING) { + int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL); + if (err <= 0) { return NULL; } + } + + // Accept + int addrLen = sizeof(sockaddr_in); + SockHandle_t s = ::accept(sock, (sockaddr*)(dest ? &dest->addr : NULL), (socklen_t*)(dest ? &addrLen : NULL)); + if ((int)s < 0) { + if (!WOULD_BLOCK) { stop(); } + return NULL; + } + + // Enable nonblocking mode + setNonblocking(s); + + return std::make_shared(s); + } + + // === Creation functions === + + std::map listInterfaces() { + // Init library if needed + init(); + + std::map ifaces; +#ifdef _WIN32 + // Pre-allocate buffer + ULONG size = sizeof(IP_ADAPTER_ADDRESSES); + PIP_ADAPTER_ADDRESSES addresses = (PIP_ADAPTER_ADDRESSES)malloc(size); + + // Reallocate to real size + if (GetAdaptersAddresses(AF_INET, 0, NULL, addresses, &size) == ERROR_BUFFER_OVERFLOW) { + addresses = (PIP_ADAPTER_ADDRESSES)realloc(addresses, size); + if (GetAdaptersAddresses(AF_INET, 0, NULL, addresses, &size)) { + throw std::exception("Could not list network interfaces"); + } + } + + // Save data + std::wstring_convert> utfConv; + for (auto iface = addresses; iface; iface = iface->Next) { + InterfaceInfo info; + auto ip = iface->FirstUnicastAddress; + if (!ip || ip->Address.lpSockaddr->sa_family != AF_INET) { continue; } + info.address = ntohl(*(uint32_t*)&ip->Address.lpSockaddr->sa_data[2]); + info.netmask = ~((1 << (32 - ip->OnLinkPrefixLength)) - 1); + info.broadcast = info.address | (~info.netmask); + ifaces[utfConv.to_bytes(iface->FriendlyName)] = info; + } + + // Free tables + free(addresses); +#else + // Get iface list + struct ifaddrs* addresses = NULL; + getifaddrs(&addresses); + + // Save data + for (auto iface = addresses; iface; iface = iface->ifa_next) { + if (!iface->ifa_addr || !iface->ifa_netmask) { continue; } + if (iface->ifa_addr->sa_family != AF_INET) { continue; } + InterfaceInfo info; + info.address = ntohl(*(uint32_t*)&iface->ifa_addr->sa_data[2]); + info.netmask = ntohl(*(uint32_t*)&iface->ifa_netmask->sa_data[2]); + info.broadcast = info.address | (~info.netmask); + ifaces[iface->ifa_name] = info; + } + + // Free iface list + freeifaddrs(addresses); +#endif + + return ifaces; + } + + std::shared_ptr listen(const Address& addr) { + // Init library if needed + init(); + + // Create socket + SockHandle_t s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + // TODO: Support non-blockign mode + +#ifndef _WIN32 + // Allow port reusing if the app was killed or crashed + // and the socket is stuck in TIME_WAIT state. + // This option has a different meaning on Windows, + // so we use it only for non-Windows systems + int enable = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + closeSocket(s); + throw std::runtime_error("Could not configure socket"); + return NULL; + } +#endif + + // Bind socket to the port + if (bind(s, (sockaddr*)&addr.addr, sizeof(sockaddr_in))) { + closeSocket(s); + throw std::runtime_error("Could not bind socket"); + return NULL; + } + + // Enable listening + if (::listen(s, SOMAXCONN) != 0) { + throw std::runtime_error("Could start listening for connections"); + return NULL; + } + + // Enable nonblocking mode + setNonblocking(s); + + // Return listener class + return std::make_shared(s); + } + + std::shared_ptr listen(std::string host, int port) { + return listen(Address(host, port)); + } + + std::shared_ptr connect(const Address& addr) { + // Init library if needed + init(); + + // Create socket + SockHandle_t s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + // Connect to server + if (::connect(s, (sockaddr*)&addr.addr, sizeof(sockaddr_in))) { + closeSocket(s); + throw std::runtime_error("Could not connect"); + return NULL; + } + + // Enable nonblocking mode + setNonblocking(s); + + // Return socket class + return std::make_shared(s); + } + + std::shared_ptr connect(std::string host, int port) { + return connect(Address(host, port)); + } + + std::shared_ptr openudp(const Address& raddr, const Address& laddr, bool allowBroadcast) { + // Init library if needed + init(); + + // Create socket + SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + // If the remote address is multicast, allow multicast connections + #ifdef _WIN32 + const char enable = allowBroadcast; + #else + int enable = allowBroadcast; + #endif + if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int)) < 0) { + closeSocket(s); + throw std::runtime_error("Could not enable broadcast on socket"); + return NULL; + } + + // Bind socket to local port + if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) { + closeSocket(s); + throw std::runtime_error("Could not bind socket"); + return NULL; + } + + // Return socket class + return std::make_shared(s, &raddr); + } + + std::shared_ptr openudp(std::string rhost, int rport, const Address& laddr, bool allowBroadcast) { + return openudp(Address(rhost, rport), laddr, allowBroadcast); + } + + std::shared_ptr openudp(const Address& raddr, std::string lhost, int lport, bool allowBroadcast) { + return openudp(raddr, Address(lhost, lport), allowBroadcast); + } + + std::shared_ptr openudp(std::string rhost, int rport, std::string lhost, int lport, bool allowBroadcast) { + return openudp(Address(rhost, rport), Address(lhost, lport), allowBroadcast); + } +} diff --git a/vendor/net/net.h b/vendor/net/net.h new file mode 100644 index 0000000..72d6d07 --- /dev/null +++ b/vendor/net/net.h @@ -0,0 +1,282 @@ +#pragma once +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +namespace net { +#ifdef _WIN32 + typedef SOCKET SockHandle_t; + typedef int socklen_t; +#else + typedef int SockHandle_t; +#endif + typedef uint32_t IP_t; + + class Socket; + class Listener; + + struct InterfaceInfo { + IP_t address; + IP_t netmask; + IP_t broadcast; + }; + + class Address { + friend Socket; + friend Listener; + public: + /** + * Default constructor. Corresponds to 0.0.0.0:0. + */ + Address(); + + /** + * Do not instantiate this class manually. Use the provided functions. + * @param host Hostname or IP. + * @param port TCP/UDP port. + */ + Address(const std::string& host, int port); + + /** + * Do not instantiate this class manually. Use the provided functions. + * @param ip IP in host byte order. + * @param port TCP/UDP port. + */ + Address(IP_t ip, int port); + + /** + * Get the IP address. + * @return IP address in standard string format. + */ + std::string getIPStr() const; + + /** + * Get the IP address. + * @return IP address in host byte order. + */ + IP_t getIP() const; + + /** + * Set the IP address. + * @param ip IP address in host byte order. + */ + void setIP(IP_t ip); + + /** + * Get the TCP/UDP port. + * @return TCP/UDP port number. + */ + int getPort() const; + + /** + * Set the TCP/UDP port. + * @param port TCP/UDP port number. + */ + void setPort(int port); + + struct sockaddr_in addr; + }; + + enum { + NO_TIMEOUT = -1, + NONBLOCKING = 0 + }; + + enum SocketType { + SOCKET_TYPE_TCP, + SOCKET_TYPE_UDP + }; + + class Socket { + public: + /** + * Do not instantiate this class manually. Use the provided functions. + */ + Socket(SockHandle_t sock, const Address* raddr = NULL); + ~Socket(); + + /** + * Close socket. The socket can no longer be used after this. + */ + void close(); + + /** + * Check if the socket is open. + * @return True if open, false if closed. + */ + bool isOpen(); + + /** + * Get socket type. Either TCP or UDP. + * @return Socket type. + */ + SocketType type(); + + /** + * Send data on socket. + * @param data Data to be sent. + * @param len Number of bytes to be sent. + * @param dest Destination address. NULL to use the default remote address. + * @return Number of bytes sent. + */ + int send(const uint8_t* data, size_t len, const Address* dest = NULL); + + /** + * Send string on socket. Terminating NULL byte is not sent, include one in the string if you need it. + * @param str String to be sent. + * @param dest Destination address. NULL to use the default remote address. + * @return Number of bytes sent. + */ + int sendstr(const std::string& str, const Address* dest = NULL); + + /** + * Receive data from socket. + * @param data Buffer to read the data into. + * @param maxLen Maximum number of bytes to read. + * @param forceLen Read the maximum number of bytes even if it requires multiple receive operations. + * @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed. + * @param dest Destination address. If multiple packets, this will contain the address of the last one. NULL if not used. + * @return Number of bytes read. 0 means timed out or closed. -1 means would block or error. + */ + int recv(uint8_t* data, size_t maxLen, bool forceLen = false, int timeout = NO_TIMEOUT, Address* dest = NULL); + + /** + * Receive line from socket. + * @param str String to read the data into. + * @param maxLen Maximum line length allowed, 0 for no limit. + * @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed. + * @param dest Destination address. If multiple packets, this will contain the address of the last one. NULL if not used. + * @return Length of the returned string. 0 means timed out or closed. -1 means would block or error. + */ + int recvline(std::string& str, int maxLen = 0, int timeout = NO_TIMEOUT, Address* dest = NULL); + + private: + Address* raddr = NULL; + SockHandle_t sock; + bool open = true; + + }; + + class Listener { + public: + /** + * Do not instantiate this class manually. Use the provided functions. + */ + Listener(SockHandle_t sock); + ~Listener(); + + /** + * Stop listening. The listener can no longer be used after this. + */ + void stop(); + + /** + * CHeck if the listener is still listening. + * @return True if listening, false if not. + */ + bool listening(); + + /** + * Accept connection. + * @param timeout Timeout in milliseconds. Use NO_TIMEOUT or NONBLOCKING here if needed. + * @return Socket of the connection. NULL means timed out, would block or closed. + */ + std::shared_ptr accept(Address* dest = NULL, int timeout = NO_TIMEOUT); + + private: + SockHandle_t sock; + bool open = true; + + }; + + /** + * Get a list of the network interface. + * @return List of network interfaces and their addresses. + */ + std::map listInterfaces(); + + /** + * Create TCP listener. + * @param addr Address to listen on. + * @return Listener instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr listen(const Address& addr); + + /** + * Create TCP listener. + * @param host Hostname or IP to listen on ("0.0.0.0" for Any). + * @param port Port to listen on. + * @return Listener instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr listen(std::string host, int port); + + /** + * Create TCP connection. + * @param addr Remote address. + * @return Socket instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr connect(const Address& addr); + + /** + * Create TCP connection. + * @param host Remote hostname or IP address. + * @param port Remote port. + * @return Socket instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr connect(std::string host, int port); + + /** + * Create UDP socket. + * @param raddr Remote address. Set to a multicast address to allow multicast. + * @param laddr Local address to bind the socket to. + * @return Socket instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr openudp(const Address& raddr, const Address& laddr, bool allowBroadcast = false); + + /** + * Create UDP socket. + * @param rhost Remote hostname or IP address. Set to a multicast address to allow multicast. + * @param rport Remote port. + * @param laddr Local address to bind the socket to. + * @return Socket instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr openudp(std::string rhost, int rport, const Address& laddr, bool allowBroadcast = false); + + /** + * Create UDP socket. + * @param raddr Remote address. Set to a multicast or broadcast address to allow multicast. + * @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any). + * @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically). + * @return Socket instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0, bool allowBroadcast = false); + + /** + * Create UDP socket. + * @param rhost Remote hostname or IP address. Set to a multicast or broadcast address to allow multicast. + * @param rport Remote port. + * @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any). + * @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically). + * @return Socket instance on success, Throws runtime_error otherwise. + */ + std::shared_ptr openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0, bool allowBroadcast = false); +} \ No newline at end of file