aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorflu0r1ne <flu0r1ne@flu0r1ne.net>2023-08-17 15:56:11 -0500
committerflu0r1ne <flu0r1ne@flu0r1ne.net>2023-08-17 15:56:11 -0500
commit34779155d5b69b51aa301e7c111f95ea9c840589 (patch)
tree7289232dc3c4cd187e065792191dd707f08d6e79
downloadwg2nd-34779155d5b69b51aa301e7c111f95ea9c840589.tar.xz
wg2nd-34779155d5b69b51aa301e7c111f95ea9c840589.zip
init commit
-rw-r--r--.ycm_extra_conf.py9
-rw-r--r--makefile73
-rw-r--r--src/main.cpp202
-rw-r--r--src/wg2sd.cpp503
-rw-r--r--src/wg2sd.hpp134
-rw-r--r--test/utest.h1623
-rw-r--r--test/wg2sd_test.cpp219
7 files changed, 2763 insertions, 0 deletions
diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py
new file mode 100644
index 0000000..da9fe01
--- /dev/null
+++ b/.ycm_extra_conf.py
@@ -0,0 +1,9 @@
+def Settings( **kwargs ):
+ return {
+ 'flags': [
+ '-Wall', '-Wextra', '-Werror',
+ '-Isrc', '-Itest',
+ '-std=c++20',
+ '-Wno-unused-function',
+ ],
+ }
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..c08daf5
--- /dev/null
+++ b/makefile
@@ -0,0 +1,73 @@
+# Compiler
+CXX = g++
+
+# Compiler flags
+CXXFLAGS = -Wall -Wextra -Werror
+CXXFLAGS += -Isrc -std=c++20 -Wno-unused-function
+
+# Release flags
+RELEASE_FLAGS = -O3 -lrt
+
+# Debug flags
+DEBUGFLAGS = -ggdb -O0
+
+# Linking flags
+LDFLAGS = -largon2
+
+# Object files
+OBJECTS := wg2sd.o
+OBJECTS += main.o
+
+# Source directory
+SRC_DIR = src
+TEST_DIR = test
+
+TEST_FILES := $(wildcard $(TEST_DIR)/*.cpp)
+TEST_TARGETS := $(patsubst $(TEST_DIR)/%.cpp, $(TEST_DIR)/%, $(TEST_FILES))
+
+SRC_FILES := $(patsubst %.o,$(SRC_DIR)/%.cpp,$(OBJECTS))
+
+# Object directory
+OBJ_DIR = obj
+DEBUG_OBJ_DIR = obj/debug
+
+# Target executable
+TARGET = wg2sd
+
+# Build rules
+all: CXXFLAGS += $(RELEASE_FLAGS)
+all: targets
+
+targets: $(TARGET)
+
+tests: $(TEST_TARGETS)
+
+debug: CXXFLAGS += $(DEBUGFLAGS)
+debug: OBJ_DIR = $(DEBUG_OBJ_DIR)
+debug: tests targets
+
+$(TARGET): $(addprefix $(OBJ_DIR)/, $(OBJECTS))
+ $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
+
+$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp | $(OBJ_DIR)
+ $(CXX) $(CXXFLAGS) -c $< -o $@
+
+$(TEST_DIR)/%: $(TEST_DIR)/%.cpp $(addprefix $(OBJ_DIR)/, wg2sd.o) | $(OBJ_DIR)
+ $(CXX) $(CXXFLAGS) $^ $(LDFLAGS) -o $@
+
+$(OBJ_DIR) $(DEBUG_OBJ_DIR):
+ mkdir -p $@
+
+# Clean rule
+clean:
+ rm -rf $(OBJ_DIR) $(DEBUG_OBJ_DIR) $(TARGET) $(TEST_TARGETS)
+
+# Help rule
+help:
+ @echo "Available targets:"
+ @echo " all (default) : Build the project"
+ @echo " release : Build the project with release flags"
+ @echo " tests : Build the tests"
+ @echo " debug : Build the project and tests with debug flags"
+ @echo " clean : Remove all build artifacts"
+ @echo " help : Display this help message"
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..724b479
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,202 @@
+// =====================================
+// ERROR HANDING - FROM FCUTILS
+// =====================================
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define FORMAT_MAX_SIZE 1024
+extern int errno;
+
+void format_with_errno(char * buf, size_t n, char const * format) {
+ char const * errstr = strerror(errno);
+ char fmt_err[256];
+
+ size_t i = 0,
+ j = 0;
+ while(errstr[j] && i < sizeof(fmt_err) - 1) {
+ if((fmt_err[i++] = errstr[j++]) != '%')
+ continue;
+
+ if(i < sizeof(fmt_err) - 1) {
+ fmt_err[i++] = '%';
+ } else {
+ i--;
+ }
+ }
+
+ fmt_err[i] = '\0';
+
+ snprintf(buf, n, "%s: %s", format, fmt_err);
+}
+
+
+[[noreturn]] void die_errno(char const * format, ...) {
+ va_list vargs;
+ va_start (vargs, format);
+
+ char buf[FORMAT_MAX_SIZE];
+ format_with_errno(buf, sizeof(buf), format);
+ vfprintf(stderr, buf, vargs);
+ fprintf(stderr, "\n");
+ exit(1);
+
+ va_end(vargs);
+}
+
+[[noreturn]] void die(char const * format, ...) {
+ va_list vargs;
+ va_start (vargs, format);
+
+ vfprintf(stderr, format, vargs);
+ fprintf(stderr, "\n");
+
+ va_end(vargs);
+ exit(1);
+}
+
+void err(char const * format, ...) {
+ va_list vargs;
+ va_start (vargs, format);
+
+ vfprintf(stderr, format, vargs);
+ fprintf(stderr, "\n");
+
+ va_end(vargs);
+}
+
+// =============================================
+// COMMAND LINE UTILITY
+// =============================================
+
+
+#include "wg2sd.hpp"
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <grp.h>
+
+[[noreturn]] void die_usage(char const * prog) {
+ err("Usage: %s [ -o OUTPUT_PATH ] CONFIG_FILE", prog);
+ die("Use -h for help");
+}
+
+void print_help(char const * prog) {
+ err("Usage: %s [ -o OUTPUT_PATH ] CONFIG_FILE", prog);
+ err("Options:");
+ err("-o OUTPUT_PATH\tSet the output path (default is /etc/systemd/network)");
+ err("-h\t\tDisplay this help message");
+ exit(EXIT_SUCCESS);
+}
+
+using namespace wg2sd;
+
+void write_systemd_file(SystemdFilespec const & filespec, std::string output_path, bool secure) {
+ std::string full_path = output_path + "/" + filespec.name;
+ std::ofstream ofs;
+
+ if (secure) {
+ // Set permissions to 0640 before writing the file
+ umask(0027);
+
+ // Open the file
+ ofs.open(full_path, std::ios::out | std::ios::trunc);
+ if (!ofs.is_open()) {
+ die("Failed to open file %s for writing", full_path.c_str());
+ }
+
+ // Change ownership to root:systemd-network
+ struct group *grp;
+ grp = getgrnam("systemd-network");
+ if (grp == nullptr) {
+ die_errno("Failed to find the 'systemd-network' group");
+ }
+ if (chown(full_path.c_str(), 0, grp->gr_gid) != 0) {
+ die_errno("Failed to change ownership of file %s", full_path.c_str());
+ }
+
+ // Set permissions
+ if (chmod(full_path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP) != 0) {
+ die_errno("Failed to set permissions for file %s", full_path.c_str());
+ }
+ } else {
+ ofs.open(full_path, std::ios::out | std::ios::trunc);
+ if (!ofs.is_open()) {
+ die_errno("Failed to open file %s for writing", full_path.c_str());
+ }
+ }
+
+ // Write the contents to the file
+ ofs << filespec.contents;
+
+ if (ofs.fail()) {
+ die("Failed to write to file %s", full_path.c_str());
+ }
+}
+
+int main(int argc, char ** argv) {
+ int opt;
+ std::filesystem::path output_path = "/etc/systemd/network";
+
+ while ((opt = getopt(argc, argv, "o:h")) != -1) {
+ switch (opt) {
+ case 'o':
+ output_path = optarg;
+ break;
+ case 'h':
+ print_help(argv[0]);
+ break;
+ default: /* '?' */
+ die_usage(argv[0]);
+ }
+ }
+
+ if (optind >= argc) {
+ die_usage(argv[0]);
+ }
+
+ std::filesystem::path config_path = argv[optind];
+ std::fstream cfg_stream { config_path };
+
+ if(!cfg_stream.is_open()) {
+ die("Failed to open config file %s", config_path.string().c_str());
+ }
+
+ std::string interface_name = interface_name_from_filename(config_path);
+
+ SystemdConfig cfg;
+
+ try {
+ cfg = wg2sd::wg2sd(interface_name, cfg_stream, output_path);
+ } catch(ConfigurationException const & cex) {
+
+ const ParsingException * pex = dynamic_cast<const ParsingException *>(&cex);
+ if(pex && pex->line_no().has_value()) {
+ die("parsing error (line %llu): %s", pex->line_no().value(), pex->what());
+ } else {
+ die("configuration error: %s", cex.what());
+ }
+
+ }
+
+ if(!std::filesystem::path(output_path).is_absolute()) {
+ output_path = std::filesystem::absolute(output_path);
+ }
+
+ write_systemd_file(cfg.netdev, output_path, false);
+ write_systemd_file(cfg.network, output_path, false);
+ write_systemd_file(cfg.private_keyfile, output_path, true);
+
+ for(SystemdFilespec const & spec : cfg.symmetric_keyfiles) {
+ write_systemd_file(spec, output_path, true);
+ }
+
+ return 0;
+}
diff --git a/src/wg2sd.cpp b/src/wg2sd.cpp
new file mode 100644
index 0000000..2571908
--- /dev/null
+++ b/src/wg2sd.cpp
@@ -0,0 +1,503 @@
+#include "wg2sd.hpp"
+
+#include <exception>
+#include <sstream>
+#include <random>
+#include <regex>
+
+#include <argon2.h>
+
+std::string hashed_keyfile_name(std::string const & priv_key) {
+ constexpr uint8_t const SALT[] = {
+ 0x1, 0x6, 0x1, 0x5, 0x5, 0x8, 0x3, 0xd, 0x2, 0x7,
+ 0x5, 0xc, 0x8, 0x8, 0x7, 0x2, 0x7, 0xa, 0xf, 0x5,
+ 0xa, 0x6, 0xc, 0x5, 0xf, 0xe, 0x6, 0x7, 0xf, 0xd,
+ 0x1, 0x5
+ };
+
+ uint8_t const * key = reinterpret_cast<uint8_t const *>(priv_key.c_str());
+ uint32_t keylen = priv_key.size();
+
+ uint8_t t_cost = 2; // 2-pass computation
+ uint32_t m_cost = 1 << 17; // 128 mebibytes memory
+ uint32_t parallelism = 1; // single thread
+
+ constexpr size_t HASHLEN = 32;
+
+ uint8_t hash[HASHLEN];
+
+ argon2i_hash_raw(t_cost, m_cost, parallelism, key, keylen, SALT, sizeof(SALT), hash, HASHLEN);
+
+ constexpr char KEYFILE_EXT[] = ".keyfile";
+
+ char filename[HASHLEN + sizeof(KEYFILE_EXT)];
+
+ constexpr char const HEX[] = "0123456789abcdefghijklmnopqrstuv";
+
+ for(size_t i = 0; i < HASHLEN; i++) {
+ filename[i] = HEX[hash[i] & 0x1F];
+ }
+
+ // copy null terminator
+ for(size_t i = 0; i < sizeof(KEYFILE_EXT); i++) {
+ filename[HASHLEN + i] = KEYFILE_EXT[i];
+ }
+
+ return std::string { filename } ;
+}
+
+
+namespace wg2sd {
+
+ std::string interface_name_from_filename(std::filesystem::path config_path) {
+ std::string interface_name = config_path.filename().string();
+ interface_name = interface_name.substr(
+ 0, interface_name.size() - config_path.extension().string().size()
+ );
+ return interface_name;
+ }
+
+ bool _is_default_route(std::string const & cidr) {
+ static std::regex ipv4_wildcard("0(\\.0){0,3}\\/0");
+ static std::regex ipv6_wildcard("(0{0,4}:){0,7}0{0,4}\\/0{1,4}");
+
+ return std::regex_match(cidr, ipv4_wildcard) or std::regex_match(cidr, ipv6_wildcard);
+ }
+
+ bool _is_ipv4_route(std::string const & cidr) {
+ static std::regex ipv4("\\d{1,3}(\\.\\d{1,3}){0,3}(\\/\\d{1,2})?");
+
+ return std::regex_match(cidr, ipv4);
+ }
+
+ constexpr uint32_t MAIN_TABLE = 254;
+ constexpr uint32_t LOCAL_TABLE = 255;
+
+ // Parse the wireguard configuration from an input stream
+ // into a Config object. If an invalid key or section occurs,
+ // exists, a ParsingException is thrown.
+ Config parse_config(std::string const & interface_name, std::istream & stream) {
+ Config cfg;
+
+ cfg.intf.name = interface_name;
+ cfg.has_default_route = false;
+ cfg.intf.should_create_routes = true;
+
+ std::string line;
+ uint64_t line_no = 0;
+
+ enum class Section {
+ Interface,
+ Peer,
+ None
+ };
+
+ Section section = Section::None;
+
+ bool peer_has_default_route = false;
+
+ while (std::getline(stream, line)) {
+ ++line_no;
+
+ // Strip whitespace (\t) from line in-place
+ {
+ size_t i = 0, j = 0;
+ for(; i < line.size(); i++) {
+ if(line[i] != ' ' and line[i] != '\t') {
+ line[j] = line[i];
+ j++;
+ }
+ }
+ line.erase(j);
+ }
+
+ // Remove content exceeding a comment
+ size_t comment_start = line.find('#');
+ if(comment_start != std::string::npos) {
+ line.erase(comment_start);
+ }
+
+ // Ignore empty lines
+ if (line.empty()) {
+ continue;
+ }
+
+ // Handle section: [Interface] or [Peer] specifies further
+ // configuration concerns an interface or peer respectively
+
+ bool interface_sec_wanted = line == "[Interface]";
+ bool peer_sec_wanted = line == "[Peer]";
+
+ if(interface_sec_wanted || peer_sec_wanted) {
+ cfg.has_default_route = cfg.has_default_route or peer_has_default_route;
+ peer_has_default_route = false;
+ }
+
+ if (interface_sec_wanted) {
+ section = Section::Interface;
+ continue;
+ } else if (peer_sec_wanted) {
+ section = Section::Peer;
+ cfg.peers.emplace_back();
+ continue;
+ }
+
+ // Split key
+ size_t pos = line.find('=');
+ if(pos == std::string::npos) {
+ throw ParsingException("Expected key-value pair, got \"" + line + "\"", line_no);
+ }
+
+ std::string key = line.substr(0, pos);
+ std::string value = line.substr(pos + 1);
+
+ // Read keys according to corresponding section
+ switch (section) {
+ case Section::Interface: {
+ if (key == "PrivateKey") {
+ cfg.intf.private_key = value;
+ } else if (key == "DNS") {
+ std::istringstream dnsStream(value);
+ std::string dnsIp;
+ while (std::getline(dnsStream, dnsIp, ',')) {
+ cfg.intf.DNS.push_back(dnsIp);
+ }
+ } else if (key == "Address") {
+ std::istringstream addressStream(value);
+ std::string address;
+ while (std::getline(addressStream, address, ',')) {
+ cfg.intf.addresses.push_back(address);
+ }
+ } else if (key == "Table") {
+ if(value == "off") {
+ cfg.intf.table = 0;
+ cfg.intf.should_create_routes = false;
+ } else {
+ cfg.intf.should_create_routes = true;
+ if(value == "auto") {
+ cfg.intf.table = 0;
+ } else if(value == "main") {
+ cfg.intf.table = MAIN_TABLE;
+ } else if(value == "local") {
+ cfg.intf.table = LOCAL_TABLE;
+ } else {
+ long long table;
+ try {
+ table = std::stoll(value);
+ } catch(std::exception const & e) {
+ table = -1;
+ }
+
+ if(table < 1 || table > UINT32_MAX) {
+ throw ParsingException("Invalid option to \"Table\", must be one of \"off\", \"auto\" or a table number", line_no);
+ }
+
+ cfg.intf.table = table;
+ }
+ }
+ } else if (key == "ListenPort") {
+ int port;
+ try {
+ port = std::stoi(value);
+ } catch(std::exception & e) {
+ port = -1;
+ }
+
+ if(port < 0 || port > UINT16_MAX) {
+ throw ParsingException("Invalid port: " + key, line_no);
+ }
+
+ cfg.intf.listen_port = port;
+ } else {
+ throw ParsingException("Invalid key in [Interface] section: " + key, line_no);
+ }
+ break;
+ }
+ case Section::Peer: {
+ if (key == "Endpoint") {
+ cfg.peers.back().endpoint = value;
+ } else if (key == "AllowedIPs") {
+ std::istringstream allowedIpsStream(value);
+ std::string allowedIp;
+
+ while (std::getline(allowedIpsStream, allowedIp, ',')) {
+ bool is_default_route = _is_default_route(allowedIp);
+
+ if(is_default_route and cfg.has_default_route) {
+ throw ParsingException("Default routes exist on multiple peers");
+ }
+
+ cfg.peers.back().allowed_ips.push_back(Cidr {
+ .route = allowedIp,
+ .is_default_route = is_default_route,
+ .is_ipv4 = _is_ipv4_route(allowedIp),
+ });
+
+ peer_has_default_route = peer_has_default_route or is_default_route;
+ }
+
+ } else if (key == "PublicKey") {
+ cfg.peers.back().public_key = value;
+ } else if (key == "PersistentKeepalive") {
+ cfg.peers.back().persistent_keepalive = value;
+ } else if (key == "PresharedKey") {
+ cfg.peers.back().preshared_key = value;
+ } else {
+ throw ParsingException("Invalid key in [Peer] section: " + key, line_no);
+ }
+ break;
+ }
+ case Section::None:
+ throw ParsingException("Unexpected key outside of section: " + key, line_no);
+ }
+ }
+
+ cfg.has_default_route = cfg.has_default_route or peer_has_default_route;
+
+#define MissingField(section, key) \
+ ConfigurationException("[" section "] section missing essential field \"" key "\"")
+
+ // Ensure PrivateKey, Address, PublicKey, and AllowedIPs are present
+
+ if(cfg.intf.private_key.empty()) {
+ throw MissingField("Interface", "PrivateKey");
+ }
+
+ if(cfg.intf.addresses.empty()) {
+ throw MissingField("Interface", "Address");
+ }
+
+ for(Peer const & peer : cfg.peers) {
+ if(peer.public_key.empty()) {
+ throw MissingField("Peer", "PublicKey");
+ }
+
+ if(peer.allowed_ips.empty()) {
+ throw MissingField("Peer", "AllowedIPs");
+ }
+ }
+
+#undef MissingField
+
+ return cfg;
+ }
+
+ static std::string _gen_netdev_cfg(Config const & cfg, uint32_t fwd_table, std::string const & private_keyfile,
+ std::vector<SystemdFilespec> & symmetric_keyfiles, std::string const & output_path) {
+ std::stringstream netdev;
+
+ netdev << "# Autogenerated by wg2sd\n";
+ netdev << "[NetDev]\n";
+ netdev << "Name = " << cfg.intf.name << "\n";
+ netdev << "Kind = wireguard\n";
+ netdev << "Description = " << cfg.intf.name << " - wireguard tunnel\n";
+
+ if(!cfg.intf.mtu.empty()) {
+ netdev << "MTUBytes = " << cfg.intf.mtu << "\n";
+ }
+
+ netdev << "\n";
+
+ netdev << "[WireGuard]\n";
+ netdev << "PrivateKeyFile = " << output_path;
+
+ if(output_path.back() != '/') {
+ netdev << "/";
+ }
+
+ netdev << private_keyfile << "\n";
+
+ if(cfg.intf.should_create_routes and cfg.intf.table != 0) {
+ netdev << "RouteTable = ";
+
+ switch(cfg.intf.table) {
+ case LOCAL_TABLE:
+ netdev << "local";
+ break;
+ case MAIN_TABLE:
+ netdev << "main";
+ break;
+ default:
+ netdev << cfg.intf.table;
+ break;
+ }
+
+ netdev << "\n";
+ }
+
+ if(cfg.intf.should_create_routes and cfg.has_default_route) {
+ netdev << "FirewallMark = 0x" << std::hex << fwd_table << std::dec << "\n";
+ }
+
+ netdev << "\n";
+
+ for(Peer const & peer : cfg.peers) {
+ netdev << "[WireGuardPeer]\n";
+
+ netdev << "Endpoint = " << peer.endpoint << "\n";
+ netdev << "PublicKey = " << peer.public_key << "\n";
+
+ if(!peer.preshared_key.empty()) {
+ std::string filename = hashed_keyfile_name(peer.preshared_key);
+
+ symmetric_keyfiles.push_back(SystemdFilespec {
+ .name = filename,
+ .contents = peer.preshared_key + "\n",
+ });
+
+ netdev << "PresharedKeyFile = " << filename << "\n";
+ }
+
+ for(Cidr const & cidr : peer.allowed_ips) {
+ netdev << "AllowedIPs = " << cidr.route << "\n";
+ }
+
+ if(!peer.persistent_keepalive.empty()) {
+ netdev << "PersistentKeepalive = " << peer.persistent_keepalive << "\n";
+ }
+
+ netdev << "\n";
+ }
+
+ return netdev.str();
+ }
+
+ static std::string _gen_network_cfg(Config const & cfg, uint32_t fwd_table) {
+ std::stringstream network;
+
+ network << "# Autogenerated by wg2sd\n";
+ network << "[Match]\n";
+ network << "Name = " << cfg.intf.name << "\n";
+ network << "\n";
+
+ network << "[Network]\n";
+ for(std::string const & addr : cfg.intf.addresses) {
+ network << "Address = " << addr << "\n";
+ }
+
+ for(std::string const & dns : cfg.intf.DNS) {
+ network << "DNS = " << dns << "\n";
+ }
+
+ if(cfg.has_default_route and cfg.intf.DNS.size() > 0) {
+ network << "Domains = ~." << "\n";
+ }
+
+ network << "\n";
+
+ if(!cfg.intf.should_create_routes) {
+ return network.str();
+ }
+
+ constexpr uint8_t POLICY_ROUTE_NONE = 0;
+ constexpr uint8_t POLICY_ROUTE_V4 = 1 << 0;
+ constexpr uint8_t POLICY_ROUTE_V6 = 1 << 1;
+ constexpr uint8_t POLICY_ROUTE_BOTH = POLICY_ROUTE_V4 | POLICY_ROUTE_V6;
+
+ uint8_t policy_route = POLICY_ROUTE_NONE;
+
+ for(Peer const & peer : cfg.peers) {
+ for(Cidr const & cidr : peer.allowed_ips) {
+ if(cidr.is_default_route) {
+ policy_route |= cidr.is_ipv4 ? POLICY_ROUTE_V4 : POLICY_ROUTE_V6;
+ }
+
+ network << "[Route]\n";
+ network << "Destination = " << cidr.route << "\n";
+ uint32_t table = cfg.has_default_route ? fwd_table : cfg.intf.table;
+ if(table) {
+ network << "Table = " << table << "\n";
+ }
+ network << "\n";
+ }
+ }
+
+ if(policy_route != POLICY_ROUTE_NONE) {
+
+ char const * family = nullptr;
+
+ switch(policy_route) {
+ case POLICY_ROUTE_V4:
+ family = "ipv6";
+ break;
+ case POLICY_ROUTE_V6:
+ family = "ipv4";
+ break;
+ case POLICY_ROUTE_BOTH:
+ family = "both";
+ break;
+ }
+
+ network << "[RoutingPolicyRule]\n";
+ network << "SuppressPrefixLength = 0\n";
+ network << "Family = " << family << "\n";
+ network << "Priority = 32764\n";
+ network << "\n";
+
+ network << "[RoutingPolicyRule]\n";
+ network << "FirewallMark = 0x" << std::hex << fwd_table << std::dec << "\n";
+ network << "InvertRule = true\n";
+ network << "Table = " << fwd_table << "\n";
+ network << "Family = " << family << "\n";
+ network << "Priority = 32765\n";
+ network << "\n";
+
+ }
+
+ return network.str();
+ }
+
+ static uint32_t _random_table() {
+ std::random_device rd;
+ std::mt19937 rng { rd() };
+
+ uint32_t table = 0;
+ while(table == 0 or table == MAIN_TABLE or table == LOCAL_TABLE) {
+ table = rng() % UINT32_MAX;
+ }
+
+ return table;
+ }
+
+ SystemdConfig gen_systemd_config(Config const & cfg, std::string const & output_path) {
+
+ // If the table is explicitly specified with Table=<number>,
+ // all routes are added to this table.
+ //
+ // If Table=auto and a default route exists, this
+ // table is used by the default route to supersede
+ // non-encrypted traffic traveling to /0 routes in the
+ // main routing table by using suppress_prefix policy rules.
+ // These routes match a fwmark which is identical to the
+ // table name. All other routes are placed in the main routing
+ // table.
+ //
+ // If Table=off, no routes are added.
+ uint32_t fwd_table = _random_table();
+
+ std::vector<SystemdFilespec> symmetric_keyfiles;
+
+ std::string private_keyfile = hashed_keyfile_name(cfg.intf.private_key);
+
+ return SystemdConfig {
+ .netdev = {
+ .name = cfg.intf.name + ".netdev",
+ .contents = _gen_netdev_cfg(cfg, fwd_table, private_keyfile, symmetric_keyfiles, output_path),
+ },
+ .network = {
+ .name = cfg.intf.name + ".network",
+ .contents = _gen_network_cfg(cfg, fwd_table)
+ },
+ .private_keyfile = {
+ .name = private_keyfile,
+ .contents = cfg.intf.private_key + "\n",
+ },
+ .symmetric_keyfiles = std::move(symmetric_keyfiles)
+ };
+ }
+
+ SystemdConfig wg2sd(std::string const & interface_name, std::istream & stream, std::string const & output_path) {
+ return gen_systemd_config(parse_config(interface_name, stream), output_path);
+ }
+
+}
diff --git a/src/wg2sd.hpp b/src/wg2sd.hpp
new file mode 100644
index 0000000..00028b4
--- /dev/null
+++ b/src/wg2sd.hpp
@@ -0,0 +1,134 @@
+#pragma once
+
+#include <istream>
+#include <exception>
+#include <optional>
+#include <string>
+#include <vector>
+#include <filesystem>
+
+#include <cstdint>
+
+namespace wg2sd {
+
+ struct Interface {
+ // File name, or defaults to "wg"
+ std::string name;
+ // Address=...
+ // List of ip addresses to be assigned to the interface
+ std::vector<std::string> addresses;
+ // PrivateKey=...
+ // Base64-encoded private key string
+ std::string private_key;
+ // MTu=..
+ std::string mtu;
+ // DNS=...
+ // DNS consists of a comma-separated list of IP addresses of DNS servers
+ std::vector<std::string> DNS;
+ // Table=...
+ // By default, wireguard creates routes. This is disabled, when Table=off
+ bool should_create_routes;
+ // Table number (if specific), 0 if auto
+ uint32_t table;
+ // ListenPort=...
+ // The port number on which the interface will listen
+ std::optional<uint16_t> listen_port;
+
+ Interface()
+ : should_create_routes { false }
+ , table { 0 }
+ , listen_port { }
+ { }
+ };
+
+ struct Cidr {
+ std::string route;
+ bool is_default_route;
+ bool is_ipv4;
+ };
+
+ struct Peer {
+ // Endpoint=...
+ // IP and port of the peer
+ std::string endpoint;
+ // PublicKey=...
+ std::string public_key;
+ // AllowedIPs=...
+ // Comma separated list of allowed ips
+ // Each allowed ip is a CIDR block
+ std::vector<Cidr> allowed_ips;
+ // PersistentAlive=...
+ std::string persistent_keepalive;
+ // PresharedKey=...
+ std::string preshared_key;
+ };
+
+ struct Config {
+ // [Interface]
+ Interface intf;
+ // [Peer]
+ std::vector<Peer> peers;
+ // If one of the peers has a default route
+ bool has_default_route;
+
+ Config()
+ : has_default_route { false }
+ { }
+ };
+
+ class ConfigurationException : public std::exception {
+
+ public:
+
+ ConfigurationException(std::string const & message)
+ : _message { message }
+ {}
+
+ char const * what() const noexcept override {
+ return _message.c_str();
+ }
+
+ private:
+ std::string _message;
+ };
+
+ class ParsingException : public ConfigurationException {
+
+ public:
+
+ ParsingException(std::string const & message, std::optional<uint64_t> line_no = {})
+ : ConfigurationException(message)
+ , _line_no { line_no }
+ {}
+
+
+ std::optional<uint64_t> line_no() const noexcept {
+ return _line_no;
+ }
+
+ private:
+ std::string _message;
+ std::optional<uint64_t> _line_no;
+ };
+
+ struct SystemdFilespec {
+ std::string name;
+ std::string contents;
+ };
+
+ struct SystemdConfig {
+ SystemdFilespec netdev;
+ SystemdFilespec network;
+ SystemdFilespec private_keyfile;
+ std::vector<SystemdFilespec> symmetric_keyfiles;
+ };
+
+ std::string interface_name_from_filename(std::filesystem::path config_path);
+
+ Config parse_config(std::string const & interface_name, std::istream & stream);
+
+ SystemdConfig gen_systemd_config(Config const & cfg, std::string const & output_path);
+
+ SystemdConfig wg2sd(std::string const & interface_name, std::istream & stream, std::string const & output_path);
+
+};
diff --git a/test/utest.h b/test/utest.h
new file mode 100644
index 0000000..ca996ea
--- /dev/null
+++ b/test/utest.h
@@ -0,0 +1,1623 @@
+/*
+ The latest version of this library is available on GitHub;
+ https://github.com/sheredom/utest.h
+*/
+
+/*
+ This is free and unencumbered software released into the public domain.
+
+ Anyone is free to copy, modify, publish, use, compile, sell, or
+ distribute this software, either in source code form or as a compiled
+ binary, for any purpose, commercial or non-commercial, and by any
+ means.
+
+ In jurisdictions that recognize copyright laws, the author or authors
+ of this software dedicate any and all copyright interest in the
+ software to the public domain. We make this dedication for the benefit
+ of the public at large and to the detriment of our heirs and
+ successors. We intend this dedication to be an overt act of
+ relinquishment in perpetuity of all present and future rights to this
+ software under copyright law.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
+ For more information, please refer to <http://unlicense.org/>
+*/
+
+#ifndef SHEREDOM_UTEST_H_INCLUDED
+#define SHEREDOM_UTEST_H_INCLUDED
+
+#ifdef _MSC_VER
+/*
+ Disable warning about not inlining 'inline' functions.
+*/
+#pragma warning(disable : 4710)
+
+/*
+ Disable warning about inlining functions that are not marked 'inline'.
+*/
+#pragma warning(disable : 4711)
+
+/*
+ Disable warning for alignment padding added
+*/
+#pragma warning(disable : 4820)
+
+#if _MSC_VER > 1900
+/*
+ Disable warning about preprocessor macros not being defined in MSVC headers.
+*/
+#pragma warning(disable : 4668)
+
+/*
+ Disable warning about no function prototype given in MSVC headers.
+*/
+#pragma warning(disable : 4255)
+
+/*
+ Disable warning about pointer or reference to potentially throwing function.
+*/
+#pragma warning(disable : 5039)
+
+/*
+ Disable warning about macro expansion producing 'defined' has undefined
+ behavior.
+*/
+#pragma warning(disable : 5105)
+#endif
+
+#if _MSC_VER > 1930
+/*
+ Disable warning about 'const' variable is not used.
+*/
+#pragma warning(disable : 5264)
+#endif
+
+#pragma warning(push, 1)
+#endif
+
+#if defined(_MSC_VER) && (_MSC_VER < 1920)
+typedef __int64 utest_int64_t;
+typedef unsigned __int64 utest_uint64_t;
+typedef unsigned __int32 utest_uint32_t;
+#else
+#include <stdint.h>
+typedef int64_t utest_int64_t;
+typedef uint64_t utest_uint64_t;
+typedef uint32_t utest_uint32_t;
+#endif
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#if defined(__cplusplus)
+#if defined(_MSC_VER) && !defined(_CPPUNWIND)
+/* We're on MSVC and the compiler is compiling without exception support! */
+#elif !defined(_MSC_VER) && !defined(__EXCEPTIONS)
+/* We're on a GCC/Clang compiler that doesn't have exception support! */
+#else
+#define UTEST_HAS_EXCEPTIONS 1
+#endif
+#endif
+
+#if defined(UTEST_HAS_EXCEPTIONS)
+#include <stdexcept>
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#if defined(__cplusplus)
+#define UTEST_C_FUNC extern "C"
+#else
+#define UTEST_C_FUNC
+#endif
+
+#define UTEST_TEST_PASSED (0)
+#define UTEST_TEST_FAILURE (1)
+#define UTEST_TEST_SKIPPED (2)
+
+#if defined(__TINYC__)
+#define UTEST_ATTRIBUTE(a) __attribute((a))
+#else
+#define UTEST_ATTRIBUTE(a) __attribute__((a))
+#endif
+
+#if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__)
+
+#if defined(__MINGW64__) || defined(__MINGW32__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wunknown-pragmas"
+#endif
+
+#if defined(_WINDOWS_) || defined(_WINDOWS_H)
+typedef LARGE_INTEGER utest_large_integer;
+#else
+// use old QueryPerformanceCounter definitions (not sure is this needed in some
+// edge cases or not) on Win7 with VS2015 these extern declaration cause "second
+// C linkage of overloaded function not allowed" error
+typedef union {
+ struct {
+ unsigned long LowPart;
+ long HighPart;
+ } DUMMYSTRUCTNAME;
+ struct {
+ unsigned long LowPart;
+ long HighPart;
+ } u;
+ utest_int64_t QuadPart;
+} utest_large_integer;
+
+UTEST_C_FUNC __declspec(dllimport) int __stdcall QueryPerformanceCounter(
+ utest_large_integer *);
+UTEST_C_FUNC __declspec(dllimport) int __stdcall QueryPerformanceFrequency(
+ utest_large_integer *);
+
+#if defined(__MINGW64__) || defined(__MINGW32__)
+#pragma GCC diagnostic pop
+#endif
+#endif
+
+#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
+ defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun__) || \
+ defined(__HAIKU__)
+/*
+ slightly obscure include here - we need to include glibc's features.h, but
+ we don't want to just include a header that might not be defined for other
+ c libraries like musl. Instead we include limits.h, which we know on all
+ glibc distributions includes features.h
+*/
+#include <limits.h>
+
+#if defined(__GLIBC__) && defined(__GLIBC_MINOR__)
+#include <time.h>
+
+#if ((2 < __GLIBC__) || ((2 == __GLIBC__) && (17 <= __GLIBC_MINOR__)))
+/* glibc is version 2.17 or above, so we can just use clock_gettime */
+#define UTEST_USE_CLOCKGETTIME
+#else
+#include <sys/syscall.h>
+#include <unistd.h>
+#endif
+#else // Other libc implementations
+#include <time.h>
+#define UTEST_USE_CLOCKGETTIME
+#endif
+
+#elif defined(__APPLE__)
+#include <time.h>
+#endif
+
+#if defined(_MSC_VER) && (_MSC_VER < 1920)
+#define UTEST_PRId64 "I64d"
+#define UTEST_PRIu64 "I64u"
+#else
+#include <inttypes.h>
+
+#define UTEST_PRId64 PRId64
+#define UTEST_PRIu64 PRIu64
+#endif
+
+#if defined(__cplusplus)
+#define UTEST_INLINE inline
+
+#if defined(__clang__)
+#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wglobal-constructors\"")
+
+#define UTEST_INITIALIZER_END_DISABLE_WARNINGS _Pragma("clang diagnostic pop")
+#else
+#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS
+#define UTEST_INITIALIZER_END_DISABLE_WARNINGS
+#endif
+
+#define UTEST_INITIALIZER(f) \
+ struct f##_cpp_struct { \
+ f##_cpp_struct(); \
+ }; \
+ UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS static f##_cpp_struct \
+ f##_cpp_global UTEST_INITIALIZER_END_DISABLE_WARNINGS; \
+ f##_cpp_struct::f##_cpp_struct()
+#elif defined(_MSC_VER)
+#define UTEST_INLINE __forceinline
+
+#if defined(_WIN64)
+#define UTEST_SYMBOL_PREFIX
+#else
+#define UTEST_SYMBOL_PREFIX "_"
+#endif
+
+#if defined(__clang__)
+#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wmissing-variable-declarations\"")
+
+#define UTEST_INITIALIZER_END_DISABLE_WARNINGS _Pragma("clang diagnostic pop")
+#else
+#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS
+#define UTEST_INITIALIZER_END_DISABLE_WARNINGS
+#endif
+
+#pragma section(".CRT$XCU", read)
+#define UTEST_INITIALIZER(f) \
+ static void __cdecl f(void); \
+ UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \
+ __pragma(comment(linker, "/include:" UTEST_SYMBOL_PREFIX #f "_")) \
+ UTEST_C_FUNC __declspec(allocate(".CRT$XCU")) void(__cdecl * \
+ f##_)(void) = f; \
+ UTEST_INITIALIZER_END_DISABLE_WARNINGS \
+ static void __cdecl f(void)
+#else
+#if defined(__linux__)
+#if defined(__clang__)
+#if __has_warning("-Wreserved-id-macro")
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#endif
+#endif
+
+#define __STDC_FORMAT_MACROS 1
+
+#if defined(__clang__)
+#if __has_warning("-Wreserved-id-macro")
+#pragma clang diagnostic pop
+#endif
+#endif
+#endif
+
+#define UTEST_INLINE inline
+
+#define UTEST_INITIALIZER(f) \
+ static void f(void) UTEST_ATTRIBUTE(constructor); \
+ static void f(void)
+#endif
+
+#if defined(__cplusplus)
+#define UTEST_CAST(type, x) static_cast<type>(x)
+#define UTEST_PTR_CAST(type, x) reinterpret_cast<type>(x)
+#define UTEST_EXTERN extern "C"
+#define UTEST_NULL NULL
+#else
+#define UTEST_CAST(type, x) ((type)(x))
+#define UTEST_PTR_CAST(type, x) ((type)(x))
+#define UTEST_EXTERN extern
+#define UTEST_NULL 0
+#endif
+
+#ifdef _MSC_VER
+/*
+ io.h contains definitions for some structures with natural padding. This is
+ uninteresting, but for some reason MSVC's behaviour is to warn about
+ including this system header. That *is* interesting
+*/
+#pragma warning(disable : 4820)
+#pragma warning(push, 1)
+#include <io.h>
+#pragma warning(pop)
+#define UTEST_COLOUR_OUTPUT() (_isatty(_fileno(stdout)))
+#else
+#if defined(__EMSCRIPTEN__)
+#include <emscripten/html5.h>
+#define UTEST_COLOUR_OUTPUT() false
+#else
+#include <unistd.h>
+#define UTEST_COLOUR_OUTPUT() (isatty(STDOUT_FILENO))
+#endif
+#endif
+
+static UTEST_INLINE void *utest_realloc(void *const pointer, size_t new_size) {
+ void *const new_pointer = realloc(pointer, new_size);
+
+ if (UTEST_NULL == new_pointer) {
+ free(new_pointer);
+ }
+
+ return new_pointer;
+}
+
+static UTEST_INLINE utest_int64_t utest_ns(void) {
+#if defined(_MSC_VER) || defined(__MINGW64__) || defined(__MINGW32__)
+ utest_large_integer counter;
+ utest_large_integer frequency;
+ QueryPerformanceCounter(&counter);
+ QueryPerformanceFrequency(&frequency);
+ return UTEST_CAST(utest_int64_t,
+ (counter.QuadPart * 1000000000) / frequency.QuadPart);
+#elif defined(__linux__) && defined(__STRICT_ANSI__)
+ return UTEST_CAST(utest_int64_t, clock()) * 1000000000 / CLOCKS_PER_SEC;
+#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
+ defined(__NetBSD__) || defined(__DragonFly__) || defined(__sun__) || \
+ defined(__HAIKU__)
+ struct timespec ts;
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \
+ !defined(__HAIKU__)
+ timespec_get(&ts, TIME_UTC);
+#else
+ const clockid_t cid = CLOCK_REALTIME;
+#if defined(UTEST_USE_CLOCKGETTIME)
+ clock_gettime(cid, &ts);
+#else
+ syscall(SYS_clock_gettime, cid, &ts);
+#endif
+#endif
+ return UTEST_CAST(utest_int64_t, ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec;
+#elif __APPLE__
+ return UTEST_CAST(utest_int64_t, clock_gettime_nsec_np(CLOCK_UPTIME_RAW));
+#elif __EMSCRIPTEN__
+ return emscripten_performance_now() * 1000000.0;
+#else
+#error Unsupported platform!
+#endif
+}
+
+typedef void (*utest_testcase_t)(int *, size_t);
+
+struct utest_test_state_s {
+ utest_testcase_t func;
+ size_t index;
+ char *name;
+};
+
+struct utest_state_s {
+ struct utest_test_state_s *tests;
+ size_t tests_length;
+ FILE *output;
+};
+
+/* extern to the global state utest needs to execute */
+UTEST_EXTERN struct utest_state_s utest_state;
+
+#if defined(_MSC_VER)
+#define UTEST_WEAK __forceinline
+#elif defined(__MINGW32__) || defined(__MINGW64__)
+#define UTEST_WEAK static UTEST_ATTRIBUTE(used)
+#elif defined(__clang__) || defined(__GNUC__) || defined(__TINYC__)
+#define UTEST_WEAK UTEST_ATTRIBUTE(weak)
+#else
+#error Non clang, non gcc, non MSVC, non tcc compiler found!
+#endif
+
+#if defined(_MSC_VER)
+#define UTEST_UNUSED
+#else
+#define UTEST_UNUSED UTEST_ATTRIBUTE(unused)
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wvariadic-macros"
+#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#endif
+#define UTEST_PRINTF(...) \
+ if (utest_state.output) { \
+ fprintf(utest_state.output, __VA_ARGS__); \
+ } \
+ printf(__VA_ARGS__)
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wvariadic-macros"
+#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#endif
+
+#ifdef _MSC_VER
+#define UTEST_SNPRINTF(BUFFER, N, ...) _snprintf_s(BUFFER, N, N, __VA_ARGS__)
+#else
+#define UTEST_SNPRINTF(...) snprintf(__VA_ARGS__)
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#if defined(__cplusplus)
+/* if we are using c++ we can use overloaded methods (its in the language) */
+#define UTEST_OVERLOADABLE
+#elif defined(__clang__)
+/* otherwise, if we are using clang with c - use the overloadable attribute */
+#define UTEST_OVERLOADABLE UTEST_ATTRIBUTE(overloadable)
+#endif
+
+#if defined(__cplusplus) && (__cplusplus >= 201103L)
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#endif
+
+#include <type_traits>
+
+template <typename T, bool is_enum = std::is_enum<T>::value>
+struct utest_type_deducer final {
+ static void _(const T t);
+};
+
+template <> struct utest_type_deducer<signed char, false> {
+ static void _(const signed char c) {
+ UTEST_PRINTF("%d", static_cast<int>(c));
+ }
+};
+
+template <> struct utest_type_deducer<unsigned char, false> {
+ static void _(const unsigned char c) {
+ UTEST_PRINTF("%u", static_cast<unsigned int>(c));
+ }
+};
+
+template <> struct utest_type_deducer<short, false> {
+ static void _(const short s) { UTEST_PRINTF("%d", static_cast<int>(s)); }
+};
+
+template <> struct utest_type_deducer<unsigned short, false> {
+ static void _(const unsigned short s) {
+ UTEST_PRINTF("%u", static_cast<int>(s));
+ }
+};
+
+template <> struct utest_type_deducer<float, false> {
+ static void _(const float f) { UTEST_PRINTF("%f", static_cast<double>(f)); }
+};
+
+template <> struct utest_type_deducer<double, false> {
+ static void _(const double d) { UTEST_PRINTF("%f", d); }
+};
+
+template <> struct utest_type_deducer<long double, false> {
+ static void _(const long double d) {
+#if defined(__MINGW32__) || defined(__MINGW64__)
+ /* MINGW is weird - doesn't like LF at all?! */
+ UTEST_PRINTF("%f", (double)d);
+#else
+ UTEST_PRINTF("%Lf", d);
+#endif
+ }
+};
+
+template <> struct utest_type_deducer<int, false> {
+ static void _(const int i) { UTEST_PRINTF("%d", i); }
+};
+
+template <> struct utest_type_deducer<unsigned int, false> {
+ static void _(const unsigned int i) { UTEST_PRINTF("%u", i); }
+};
+
+template <> struct utest_type_deducer<long, false> {
+ static void _(const long i) { UTEST_PRINTF("%ld", i); }
+};
+
+template <> struct utest_type_deducer<unsigned long, false> {
+ static void _(const unsigned long i) { UTEST_PRINTF("%lu", i); }
+};
+
+template <> struct utest_type_deducer<long long, false> {
+ static void _(const long long i) { UTEST_PRINTF("%lld", i); }
+};
+
+template <> struct utest_type_deducer<unsigned long long, false> {
+ static void _(const unsigned long long i) { UTEST_PRINTF("%llu", i); }
+};
+
+template <typename T> struct utest_type_deducer<const T *, false> {
+ static void _(const T *t) {
+ UTEST_PRINTF("%p", static_cast<void *>(const_cast<T *>(t)));
+ }
+};
+
+template <typename T> struct utest_type_deducer<T *, false> {
+ static void _(T *t) { UTEST_PRINTF("%p", static_cast<void *>(t)); }
+};
+
+template <typename T> struct utest_type_deducer<T, true> {
+ static void _(const T t) {
+ UTEST_PRINTF("%llu", static_cast<unsigned long long>(t));
+ }
+};
+
+template <typename T>
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const T t) {
+ utest_type_deducer<T>::_(t);
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#elif defined(UTEST_OVERLOADABLE)
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(signed char c);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(signed char c) {
+ UTEST_PRINTF("%d", UTEST_CAST(int, c));
+}
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned char c);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned char c) {
+ UTEST_PRINTF("%u", UTEST_CAST(unsigned int, c));
+}
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(float f);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(float f) {
+ UTEST_PRINTF("%f", UTEST_CAST(double, f));
+}
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(double d);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(double d) {
+ UTEST_PRINTF("%f", d);
+}
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long double d);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long double d) {
+#if defined(__MINGW32__) || defined(__MINGW64__)
+ /* MINGW is weird - doesn't like LF at all?! */
+ UTEST_PRINTF("%f", (double)d);
+#else
+ UTEST_PRINTF("%Lf", d);
+#endif
+}
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(int i);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(int i) {
+ UTEST_PRINTF("%d", i);
+}
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned int i);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(unsigned int i) {
+ UTEST_PRINTF("%u", i);
+}
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long int i);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long int i) {
+ UTEST_PRINTF("%ld", i);
+}
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long unsigned int i);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long unsigned int i) {
+ UTEST_PRINTF("%lu", i);
+}
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const void *p);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(const void *p) {
+ UTEST_PRINTF("%p", p);
+}
+
+/*
+ long long is a c++11 extension
+*/
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) || \
+ defined(__cplusplus) && (__cplusplus >= 201103L) || \
+ (defined(__MINGW32__) || defined(__MINGW64__))
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
+#endif
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long int i);
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long int i) {
+ UTEST_PRINTF("%lld", i);
+}
+
+UTEST_WEAK UTEST_OVERLOADABLE void utest_type_printer(long long unsigned int i);
+UTEST_WEAK UTEST_OVERLOADABLE void
+utest_type_printer(long long unsigned int i) {
+ UTEST_PRINTF("%llu", i);
+}
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif
+#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \
+ !(defined(__MINGW32__) || defined(__MINGW64__)) || \
+ defined(__TINYC__)
+#define utest_type_printer(val) \
+ UTEST_PRINTF(_Generic((val), signed char \
+ : "%d", unsigned char \
+ : "%u", short \
+ : "%d", unsigned short \
+ : "%u", int \
+ : "%d", long \
+ : "%ld", long long \
+ : "%lld", unsigned \
+ : "%u", unsigned long \
+ : "%lu", unsigned long long \
+ : "%llu", float \
+ : "%f", double \
+ : "%f", long double \
+ : "%Lf", default \
+ : _Generic((val - val), ptrdiff_t \
+ : "%p", default \
+ : "undef")), \
+ (val))
+#else
+/*
+ we don't have the ability to print the values we got, so we create a macro
+ to tell our users we can't do anything fancy
+*/
+#define utest_type_printer(...) UTEST_PRINTF("undef")
+#endif
+
+#ifdef _MSC_VER
+#define UTEST_SURPRESS_WARNING_BEGIN \
+ __pragma(warning(push)) __pragma(warning(disable : 4127)) \
+ __pragma(warning(disable : 4571)) __pragma(warning(disable : 4130))
+#define UTEST_SURPRESS_WARNING_END __pragma(warning(pop))
+#else
+#define UTEST_SURPRESS_WARNING_BEGIN
+#define UTEST_SURPRESS_WARNING_END
+#endif
+
+#if defined(__cplusplus) && (__cplusplus >= 201103L)
+#define UTEST_AUTO(x) auto
+#elif !defined(__cplusplus)
+
+#if defined(__clang__)
+/* clang-format off */
+/* had to disable clang-format here because it malforms the pragmas */
+#define UTEST_AUTO(x) \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wgnu-auto-type\"") __auto_type \
+ _Pragma("clang diagnostic pop")
+/* clang-format on */
+#else
+#define UTEST_AUTO(x) __typeof__(x + 0)
+#endif
+
+#else
+#define UTEST_AUTO(x) typeof(x + 0)
+#endif
+
+#if defined(__clang__)
+#define UTEST_STRNCMP(x, y, size) \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wdisabled-macro-expansion\"") \
+ strncmp(x, y, size) _Pragma("clang diagnostic pop")
+#else
+#define UTEST_STRNCMP(x, y, size) strncmp(x, y, size)
+#endif
+
+#define UTEST_SKIP(msg) \
+ do { \
+ UTEST_PRINTF(" Skipped : '%s'\n", (msg)); \
+ *utest_result = UTEST_TEST_SKIPPED; \
+ return; \
+ } while (0)
+
+#if defined(__clang__)
+#define UTEST_EXPECT(x, y, cond) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wlanguage-extension-token\"") \
+ _Pragma("clang diagnostic ignored \"-Wc++98-compat-pedantic\"") \
+ _Pragma("clang diagnostic ignored \"-Wfloat-equal\"") \
+ UTEST_AUTO(x) xEval = (x); \
+ UTEST_AUTO(y) yEval = (y); \
+ if (!((xEval)cond(yEval))) { \
+ _Pragma("clang diagnostic pop") \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : ("); \
+ UTEST_PRINTF(#x ") " #cond " (" #y); \
+ UTEST_PRINTF(")\n"); \
+ UTEST_PRINTF(" Actual : "); \
+ utest_type_printer(xEval); \
+ UTEST_PRINTF(" vs "); \
+ utest_type_printer(yEval); \
+ UTEST_PRINTF("\n"); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+#elif defined(__GNUC__) || defined(__TINYC__)
+#define UTEST_EXPECT(x, y, cond) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ UTEST_AUTO(x) xEval = (x); \
+ UTEST_AUTO(y) yEval = (y); \
+ if (!((xEval)cond(yEval))) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : ("); \
+ UTEST_PRINTF(#x ") " #cond " (" #y); \
+ UTEST_PRINTF(")\n"); \
+ UTEST_PRINTF(" Actual : "); \
+ utest_type_printer(xEval); \
+ UTEST_PRINTF(" vs "); \
+ utest_type_printer(yEval); \
+ UTEST_PRINTF("\n"); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+#else
+#define UTEST_EXPECT(x, y, cond) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ if (!((x)cond(y))) { \
+ UTEST_PRINTF("%s:%i: Failure (Expected " #cond " Actual)\n", __FILE__, \
+ __LINE__); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+#endif
+
+#define EXPECT_TRUE(x) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const int xEval = !!(x); \
+ if (!(xEval)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : true\n"); \
+ UTEST_PRINTF(" Actual : %s\n", (xEval) ? "true" : "false"); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define EXPECT_FALSE(x) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const int xEval = !!(x); \
+ if (xEval) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : false\n"); \
+ UTEST_PRINTF(" Actual : %s\n", (xEval) ? "true" : "false"); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define EXPECT_EQ(x, y) UTEST_EXPECT(x, y, ==)
+#define EXPECT_NE(x, y) UTEST_EXPECT(x, y, !=)
+#define EXPECT_LT(x, y) UTEST_EXPECT(x, y, <)
+#define EXPECT_LE(x, y) UTEST_EXPECT(x, y, <=)
+#define EXPECT_GT(x, y) UTEST_EXPECT(x, y, >)
+#define EXPECT_GE(x, y) UTEST_EXPECT(x, y, >=)
+
+#define EXPECT_STREQ(x, y) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const char *xEval = (x); \
+ const char *yEval = (y); \
+ if (UTEST_NULL == xEval || UTEST_NULL == yEval || \
+ 0 != strcmp(xEval, yEval)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : \"%s\"\n", xEval); \
+ UTEST_PRINTF(" Actual : \"%s\"\n", yEval); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define EXPECT_STRNE(x, y) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const char *xEval = (x); \
+ const char *yEval = (y); \
+ if (UTEST_NULL == xEval || UTEST_NULL == yEval || \
+ 0 == strcmp(xEval, yEval)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : \"%s\"\n", xEval); \
+ UTEST_PRINTF(" Actual : \"%s\"\n", yEval); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define EXPECT_STRNEQ(x, y, n) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const char *xEval = (x); \
+ const char *yEval = (y); \
+ const size_t nEval = UTEST_CAST(size_t, n); \
+ if (UTEST_NULL == xEval || UTEST_NULL == yEval || \
+ 0 != UTEST_STRNCMP(xEval, yEval, nEval)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, nEval), xEval); \
+ UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, nEval), yEval); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define EXPECT_STRNNE(x, y, n) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const char *xEval = (x); \
+ const char *yEval = (y); \
+ const size_t nEval = UTEST_CAST(size_t, n); \
+ if (UTEST_NULL == xEval || UTEST_NULL == yEval || \
+ 0 == UTEST_STRNCMP(xEval, yEval, nEval)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, nEval), xEval); \
+ UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, nEval), yEval); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define EXPECT_NEAR(x, y, epsilon) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const double diff = \
+ utest_fabs(UTEST_CAST(double, x) - UTEST_CAST(double, y)); \
+ if (diff > UTEST_CAST(double, epsilon) || utest_isnan(diff)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : %f\n", UTEST_CAST(double, x)); \
+ UTEST_PRINTF(" Actual : %f\n", UTEST_CAST(double, y)); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#if defined(UTEST_HAS_EXCEPTIONS)
+#define EXPECT_EXCEPTION(x, exception_type) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ int exception_caught = 0; \
+ try { \
+ x; \
+ } catch (const exception_type &) { \
+ exception_caught = 1; \
+ } catch (...) { \
+ exception_caught = 2; \
+ } \
+ if (exception_caught != 1) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : %s exception\n", #exception_type); \
+ UTEST_PRINTF(" Actual : %s\n", (exception_caught == 2) \
+ ? "Unexpected exception" \
+ : "No exception"); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+#endif
+
+#if defined(__clang__)
+#define UTEST_ASSERT(x, y, cond) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wlanguage-extension-token\"") \
+ _Pragma("clang diagnostic ignored \"-Wc++98-compat-pedantic\"") \
+ _Pragma("clang diagnostic ignored \"-Wfloat-equal\"") \
+ UTEST_AUTO(x) xEval = (x); \
+ UTEST_AUTO(y) yEval = (y); \
+ if (!((xEval)cond(yEval))) { \
+ _Pragma("clang diagnostic pop") \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : ("); \
+ UTEST_PRINTF(#x ") " #cond " (" #y); \
+ UTEST_PRINTF(")\n"); \
+ UTEST_PRINTF(" Actual : "); \
+ utest_type_printer(xEval); \
+ UTEST_PRINTF(" vs "); \
+ utest_type_printer(yEval); \
+ UTEST_PRINTF("\n"); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+#elif defined(__GNUC__) || defined(__TINYC__)
+#define UTEST_ASSERT(x, y, cond) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ UTEST_AUTO(x) xEval = (x); \
+ UTEST_AUTO(y) yEval = (y); \
+ if (!((xEval)cond(yEval))) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : ("); \
+ UTEST_PRINTF(#x ") " #cond " (" #y); \
+ UTEST_PRINTF(")\n"); \
+ UTEST_PRINTF(" Actual : "); \
+ utest_type_printer(xEval); \
+ UTEST_PRINTF(" vs "); \
+ utest_type_printer(yEval); \
+ UTEST_PRINTF("\n"); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+#else
+#define UTEST_ASSERT(x, y, cond) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ if (!((x)cond(y))) { \
+ UTEST_PRINTF("%s:%i: Failure (Expected " #cond " Actual)\n", __FILE__, \
+ __LINE__); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+#endif
+
+#define ASSERT_TRUE(x) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const int xEval = !!(x); \
+ if (!(xEval)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : true\n"); \
+ UTEST_PRINTF(" Actual : %s\n", (xEval) ? "true" : "false"); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define ASSERT_FALSE(x) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const int xEval = !!(x); \
+ if (xEval) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : false\n"); \
+ UTEST_PRINTF(" Actual : %s\n", (xEval) ? "true" : "false"); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define ASSERT_EQ(x, y) UTEST_ASSERT(x, y, ==)
+#define ASSERT_NE(x, y) UTEST_ASSERT(x, y, !=)
+#define ASSERT_LT(x, y) UTEST_ASSERT(x, y, <)
+#define ASSERT_LE(x, y) UTEST_ASSERT(x, y, <=)
+#define ASSERT_GT(x, y) UTEST_ASSERT(x, y, >)
+#define ASSERT_GE(x, y) UTEST_ASSERT(x, y, >=)
+
+#define ASSERT_STREQ(x, y) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const char *xEval = (x); \
+ const char *yEval = (y); \
+ if (UTEST_NULL == xEval || UTEST_NULL == yEval || \
+ 0 != strcmp(xEval, yEval)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : \"%s\"\n", xEval); \
+ UTEST_PRINTF(" Actual : \"%s\"\n", yEval); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define ASSERT_STRNE(x, y) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const char *xEval = (x); \
+ const char *yEval = (y); \
+ if (UTEST_NULL == xEval || UTEST_NULL == yEval || \
+ 0 == strcmp(xEval, yEval)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : \"%s\"\n", xEval); \
+ UTEST_PRINTF(" Actual : \"%s\"\n", yEval); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define ASSERT_STRNEQ(x, y, n) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const char *xEval = (x); \
+ const char *yEval = (y); \
+ const size_t nEval = UTEST_CAST(size_t, n); \
+ if (UTEST_NULL == xEval || UTEST_NULL == yEval || \
+ 0 != UTEST_STRNCMP(xEval, yEval, nEval)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, nEval), xEval); \
+ UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, nEval), yEval); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define ASSERT_STRNNE(x, y, n) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const char *xEval = (x); \
+ const char *yEval = (y); \
+ const size_t nEval = UTEST_CAST(size_t, n); \
+ if (UTEST_NULL == xEval || UTEST_NULL == yEval || \
+ 0 == UTEST_STRNCMP(xEval, yEval, nEval)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : \"%.*s\"\n", UTEST_CAST(int, nEval), xEval); \
+ UTEST_PRINTF(" Actual : \"%.*s\"\n", UTEST_CAST(int, nEval), yEval); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#define ASSERT_NEAR(x, y, epsilon) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ const double diff = \
+ utest_fabs(UTEST_CAST(double, x) - UTEST_CAST(double, y)); \
+ if (diff > UTEST_CAST(double, epsilon) || utest_isnan(diff)) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : %f\n", UTEST_CAST(double, x)); \
+ UTEST_PRINTF(" Actual : %f\n", UTEST_CAST(double, y)); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+
+#if defined(UTEST_HAS_EXCEPTIONS)
+#define ASSERT_EXCEPTION(x, exception_type) \
+ UTEST_SURPRESS_WARNING_BEGIN do { \
+ int exception_caught = 0; \
+ try { \
+ x; \
+ } catch (const exception_type &) { \
+ exception_caught = 1; \
+ } catch (...) { \
+ exception_caught = 2; \
+ } \
+ if (exception_caught != 1) { \
+ UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \
+ UTEST_PRINTF(" Expected : %s exception\n", #exception_type); \
+ UTEST_PRINTF(" Actual : %s\n", (exception_caught == 2) \
+ ? "Unexpected exception" \
+ : "No exception"); \
+ *utest_result = UTEST_TEST_FAILURE; \
+ return; \
+ } \
+ } \
+ while (0) \
+ UTEST_SURPRESS_WARNING_END
+#endif
+
+#define UTEST(SET, NAME) \
+ UTEST_EXTERN struct utest_state_s utest_state; \
+ static void utest_run_##SET##_##NAME(int *utest_result); \
+ static void utest_##SET##_##NAME(int *utest_result, size_t utest_index) { \
+ (void)utest_index; \
+ utest_run_##SET##_##NAME(utest_result); \
+ } \
+ UTEST_INITIALIZER(utest_register_##SET##_##NAME) { \
+ const size_t index = utest_state.tests_length++; \
+ const char *name_part = #SET "." #NAME; \
+ const size_t name_size = strlen(name_part) + 1; \
+ char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \
+ utest_state.tests = UTEST_PTR_CAST( \
+ struct utest_test_state_s *, \
+ utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \
+ sizeof(struct utest_test_state_s) * \
+ utest_state.tests_length)); \
+ if (utest_state.tests) { \
+ utest_state.tests[index].func = &utest_##SET##_##NAME; \
+ utest_state.tests[index].name = name; \
+ utest_state.tests[index].index = 0; \
+ UTEST_SNPRINTF(name, name_size, "%s", name_part); \
+ } else if (name) { \
+ free(name); \
+ } \
+ } \
+ void utest_run_##SET##_##NAME(int *utest_result)
+
+#define UTEST_F_SETUP(FIXTURE) \
+ static void utest_f_setup_##FIXTURE(int *utest_result, \
+ struct FIXTURE *utest_fixture)
+
+#define UTEST_F_TEARDOWN(FIXTURE) \
+ static void utest_f_teardown_##FIXTURE(int *utest_result, \
+ struct FIXTURE *utest_fixture)
+
+#if defined(__GNUC__) && __GNUC__ >= 8 && defined(__cplusplus)
+#define UTEST_FIXTURE_SURPRESS_WARNINGS_BEGIN \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wclass-memaccess\"")
+#define UTEST_FIXTURE_SURPRESS_WARNINGS_END _Pragma("GCC diagnostic pop")
+#else
+#define UTEST_FIXTURE_SURPRESS_WARNINGS_BEGIN
+#define UTEST_FIXTURE_SURPRESS_WARNINGS_END
+#endif
+
+#define UTEST_F(FIXTURE, NAME) \
+ UTEST_FIXTURE_SURPRESS_WARNINGS_BEGIN \
+ UTEST_EXTERN struct utest_state_s utest_state; \
+ static void utest_f_setup_##FIXTURE(int *, struct FIXTURE *); \
+ static void utest_f_teardown_##FIXTURE(int *, struct FIXTURE *); \
+ static void utest_run_##FIXTURE##_##NAME(int *, struct FIXTURE *); \
+ static void utest_f_##FIXTURE##_##NAME(int *utest_result, \
+ size_t utest_index) { \
+ struct FIXTURE fixture; \
+ (void)utest_index; \
+ memset(&fixture, 0, sizeof(fixture)); \
+ utest_f_setup_##FIXTURE(utest_result, &fixture); \
+ if (UTEST_TEST_PASSED != *utest_result) { \
+ return; \
+ } \
+ utest_run_##FIXTURE##_##NAME(utest_result, &fixture); \
+ utest_f_teardown_##FIXTURE(utest_result, &fixture); \
+ } \
+ UTEST_INITIALIZER(utest_register_##FIXTURE##_##NAME) { \
+ const size_t index = utest_state.tests_length++; \
+ const char *name_part = #FIXTURE "." #NAME; \
+ const size_t name_size = strlen(name_part) + 1; \
+ char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \
+ utest_state.tests = UTEST_PTR_CAST( \
+ struct utest_test_state_s *, \
+ utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \
+ sizeof(struct utest_test_state_s) * \
+ utest_state.tests_length)); \
+ if (utest_state.tests) { \
+ utest_state.tests[index].func = &utest_f_##FIXTURE##_##NAME; \
+ utest_state.tests[index].name = name; \
+ UTEST_SNPRINTF(name, name_size, "%s", name_part); \
+ } else if (name) { \
+ free(name); \
+ } \
+ } \
+ UTEST_FIXTURE_SURPRESS_WARNINGS_END \
+ void utest_run_##FIXTURE##_##NAME(int *utest_result, \
+ struct FIXTURE *utest_fixture)
+
+#define UTEST_I_SETUP(FIXTURE) \
+ static void utest_i_setup_##FIXTURE( \
+ int *utest_result, struct FIXTURE *utest_fixture, size_t utest_index)
+
+#define UTEST_I_TEARDOWN(FIXTURE) \
+ static void utest_i_teardown_##FIXTURE( \
+ int *utest_result, struct FIXTURE *utest_fixture, size_t utest_index)
+
+#define UTEST_I(FIXTURE, NAME, INDEX) \
+ UTEST_EXTERN struct utest_state_s utest_state; \
+ static void utest_run_##FIXTURE##_##NAME##_##INDEX(int *, struct FIXTURE *); \
+ static void utest_i_##FIXTURE##_##NAME##_##INDEX(int *utest_result, \
+ size_t index) { \
+ struct FIXTURE fixture; \
+ memset(&fixture, 0, sizeof(fixture)); \
+ utest_i_setup_##FIXTURE(utest_result, &fixture, index); \
+ if (UTEST_TEST_PASSED != *utest_result) { \
+ return; \
+ } \
+ utest_run_##FIXTURE##_##NAME##_##INDEX(utest_result, &fixture); \
+ utest_i_teardown_##FIXTURE(utest_result, &fixture, index); \
+ } \
+ UTEST_INITIALIZER(utest_register_##FIXTURE##_##NAME##_##INDEX) { \
+ size_t i; \
+ utest_uint64_t iUp; \
+ for (i = 0; i < (INDEX); i++) { \
+ const size_t index = utest_state.tests_length++; \
+ const char *name_part = #FIXTURE "." #NAME; \
+ const size_t name_size = strlen(name_part) + 32; \
+ char *name = UTEST_PTR_CAST(char *, malloc(name_size)); \
+ utest_state.tests = UTEST_PTR_CAST( \
+ struct utest_test_state_s *, \
+ utest_realloc(UTEST_PTR_CAST(void *, utest_state.tests), \
+ sizeof(struct utest_test_state_s) * \
+ utest_state.tests_length)); \
+ if (utest_state.tests) { \
+ utest_state.tests[index].func = &utest_i_##FIXTURE##_##NAME##_##INDEX; \
+ utest_state.tests[index].index = i; \
+ utest_state.tests[index].name = name; \
+ iUp = UTEST_CAST(utest_uint64_t, i); \
+ UTEST_SNPRINTF(name, name_size, "%s/%" UTEST_PRIu64, name_part, iUp); \
+ } else if (name) { \
+ free(name); \
+ } \
+ } \
+ } \
+ void utest_run_##FIXTURE##_##NAME##_##INDEX(int *utest_result, \
+ struct FIXTURE *utest_fixture)
+
+UTEST_WEAK
+double utest_fabs(double d);
+UTEST_WEAK
+double utest_fabs(double d) {
+ union {
+ double d;
+ utest_uint64_t u;
+ } both;
+ both.d = d;
+ both.u &= 0x7fffffffffffffffu;
+ return both.d;
+}
+
+UTEST_WEAK
+int utest_isnan(double d);
+UTEST_WEAK
+int utest_isnan(double d) {
+ union {
+ double d;
+ utest_uint64_t u;
+ } both;
+ both.d = d;
+ both.u &= 0x7fffffffffffffffu;
+ return both.u > 0x7ff0000000000000u;
+}
+
+UTEST_WEAK
+int utest_should_filter_test(const char *filter, const char *testcase);
+UTEST_WEAK int utest_should_filter_test(const char *filter,
+ const char *testcase) {
+ if (filter) {
+ const char *filter_cur = filter;
+ const char *testcase_cur = testcase;
+ const char *filter_wildcard = UTEST_NULL;
+
+ while (('\0' != *filter_cur) && ('\0' != *testcase_cur)) {
+ if ('*' == *filter_cur) {
+ /* store the position of the wildcard */
+ filter_wildcard = filter_cur;
+
+ /* skip the wildcard character */
+ filter_cur++;
+
+ while (('\0' != *filter_cur) && ('\0' != *testcase_cur)) {
+ if ('*' == *filter_cur) {
+ /*
+ we found another wildcard (filter is something like *foo*) so we
+ exit the current loop, and return to the parent loop to handle
+ the wildcard case
+ */
+ break;
+ } else if (*filter_cur != *testcase_cur) {
+ /* otherwise our filter didn't match, so reset it */
+ filter_cur = filter_wildcard;
+ }
+
+ /* move testcase along */
+ testcase_cur++;
+
+ /* move filter along */
+ filter_cur++;
+ }
+
+ if (('\0' == *filter_cur) && ('\0' == *testcase_cur)) {
+ return 0;
+ }
+
+ /* if the testcase has been exhausted, we don't have a match! */
+ if ('\0' == *testcase_cur) {
+ return 1;
+ }
+ } else {
+ if (*testcase_cur != *filter_cur) {
+ /* test case doesn't match filter */
+ return 1;
+ } else {
+ /* move our filter and testcase forward */
+ testcase_cur++;
+ filter_cur++;
+ }
+ }
+ }
+
+ if (('\0' != *filter_cur) ||
+ (('\0' != *testcase_cur) &&
+ ((filter == filter_cur) || ('*' != filter_cur[-1])))) {
+ /* we have a mismatch! */
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static UTEST_INLINE FILE *utest_fopen(const char *filename, const char *mode) {
+#ifdef _MSC_VER
+ FILE *file;
+ if (0 == fopen_s(&file, filename, mode)) {
+ return file;
+ } else {
+ return UTEST_NULL;
+ }
+#else
+ return fopen(filename, mode);
+#endif
+}
+
+static UTEST_INLINE int utest_main(int argc, const char *const argv[]);
+int utest_main(int argc, const char *const argv[]) {
+ utest_uint64_t failed = 0;
+ utest_uint64_t skipped = 0;
+ size_t index = 0;
+ size_t *failed_testcases = UTEST_NULL;
+ size_t failed_testcases_length = 0;
+ size_t *skipped_testcases = UTEST_NULL;
+ size_t skipped_testcases_length = 0;
+ const char *filter = UTEST_NULL;
+ utest_uint64_t ran_tests = 0;
+ int enable_mixed_units = 0;
+ int random_order = 0;
+ utest_uint32_t seed = 0;
+
+ enum colours { RESET, GREEN, RED, YELLOW };
+
+ const int use_colours = UTEST_COLOUR_OUTPUT();
+ const char *colours[] = {"\033[0m", "\033[32m", "\033[31m", "\033[33m"};
+
+ if (!use_colours) {
+ for (index = 0; index < sizeof colours / sizeof colours[0]; index++) {
+ colours[index] = "";
+ }
+ }
+ /* loop through all arguments looking for our options */
+ for (index = 1; index < UTEST_CAST(size_t, argc); index++) {
+ /* Informational switches */
+ const char help_str[] = "--help";
+ const char list_str[] = "--list-tests";
+ /* Test config switches */
+ const char filter_str[] = "--filter=";
+ const char output_str[] = "--output=";
+ const char enable_mixed_units_str[] = "--enable-mixed-units";
+ const char random_order_str[] = "--random-order";
+ const char random_order_with_seed_str[] = "--random-order=";
+
+ if (0 == UTEST_STRNCMP(argv[index], help_str, strlen(help_str))) {
+ printf("utest.h - the single file unit testing solution for C/C++!\n"
+ "Command line Options:\n"
+ " --help Show this message and exit.\n"
+ " --filter=<filter> Filter the test cases to run (EG. "
+ "MyTest*.a would run MyTestCase.a but not MyTestCase.b).\n"
+ " --list-tests List testnames, one per line. Output "
+ "names can be passed to --filter.\n");
+ printf(" --output=<output> Output an xunit XML file to the file "
+ "specified in <output>.\n"
+ " --enable-mixed-units Enable the per-test output to contain "
+ "mixed units (s/ms/us/ns).\n"
+ " --random-order[=<seed>] Randomize the order that the tests are "
+ "ran in. If the optional <seed> argument is not provided, then a "
+ "random starting seed is used.\n");
+ goto cleanup;
+ } else if (0 ==
+ UTEST_STRNCMP(argv[index], filter_str, strlen(filter_str))) {
+ /* user wants to filter what test cases run! */
+ filter = argv[index] + strlen(filter_str);
+ } else if (0 ==
+ UTEST_STRNCMP(argv[index], output_str, strlen(output_str))) {
+ utest_state.output = utest_fopen(argv[index] + strlen(output_str), "w+");
+ } else if (0 == UTEST_STRNCMP(argv[index], list_str, strlen(list_str))) {
+ for (index = 0; index < utest_state.tests_length; index++) {
+ UTEST_PRINTF("%s\n", utest_state.tests[index].name);
+ }
+ /* when printing the test list, don't actually run the tests */
+ return 0;
+ } else if (0 == UTEST_STRNCMP(argv[index], enable_mixed_units_str,
+ strlen(enable_mixed_units_str))) {
+ enable_mixed_units = 1;
+ } else if (0 == UTEST_STRNCMP(argv[index], random_order_with_seed_str,
+ strlen(random_order_with_seed_str))) {
+ seed =
+ UTEST_CAST(utest_uint32_t,
+ strtoul(argv[index] + strlen(random_order_with_seed_str),
+ UTEST_NULL, 10));
+ random_order = 1;
+ } else if (0 == UTEST_STRNCMP(argv[index], random_order_str,
+ strlen(random_order_str))) {
+ const utest_int64_t ns = utest_ns();
+
+ // Some really poor pseudo-random using the current time. I do this
+ // because I really want to avoid using C's rand() because that'd mean our
+ // random would be affected by any srand() usage by the user (which I
+ // don't want).
+ seed = UTEST_CAST(utest_uint32_t, ns >> 32) * 31 +
+ UTEST_CAST(utest_uint32_t, ns & 0xffffffff);
+ random_order = 1;
+ }
+ }
+
+ if (random_order) {
+ // Use Fisher-Yates with the Durstenfield's version to randomly re-order the
+ // tests.
+ for (index = utest_state.tests_length; index > 1; index--) {
+ // For the random order we'll use PCG.
+ const utest_uint32_t state = seed;
+ const utest_uint32_t word =
+ ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
+ const utest_uint32_t next =
+ ((word >> 22u) ^ word) % UTEST_CAST(utest_uint32_t, index);
+
+ // Swap the randomly chosen element into the last location.
+ const struct utest_test_state_s copy = utest_state.tests[index - 1];
+ utest_state.tests[index - 1] = utest_state.tests[next];
+ utest_state.tests[next] = copy;
+
+ // Move the seed onwards.
+ seed = seed * 747796405u + 2891336453u;
+ }
+ }
+
+ for (index = 0; index < utest_state.tests_length; index++) {
+ if (utest_should_filter_test(filter, utest_state.tests[index].name)) {
+ continue;
+ }
+
+ ran_tests++;
+ }
+
+ printf("%s[==========]%s Running %" UTEST_PRIu64 " test cases.\n",
+ colours[GREEN], colours[RESET], UTEST_CAST(utest_uint64_t, ran_tests));
+
+ if (utest_state.output) {
+ fprintf(utest_state.output, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ fprintf(utest_state.output,
+ "<testsuites tests=\"%" UTEST_PRIu64 "\" name=\"All\">\n",
+ UTEST_CAST(utest_uint64_t, ran_tests));
+ fprintf(utest_state.output,
+ "<testsuite name=\"Tests\" tests=\"%" UTEST_PRIu64 "\">\n",
+ UTEST_CAST(utest_uint64_t, ran_tests));
+ }
+
+ for (index = 0; index < utest_state.tests_length; index++) {
+ int result = UTEST_TEST_PASSED;
+ utest_int64_t ns = 0;
+
+ if (utest_should_filter_test(filter, utest_state.tests[index].name)) {
+ continue;
+ }
+
+ printf("%s[ RUN ]%s %s\n", colours[GREEN], colours[RESET],
+ utest_state.tests[index].name);
+
+ if (utest_state.output) {
+ fprintf(utest_state.output, "<testcase name=\"%s\">",
+ utest_state.tests[index].name);
+ }
+
+ ns = utest_ns();
+ errno = 0;
+#if defined(UTEST_HAS_EXCEPTIONS)
+ UTEST_SURPRESS_WARNING_BEGIN
+ try {
+ utest_state.tests[index].func(&result, utest_state.tests[index].index);
+ } catch (const std::exception &err) {
+ printf(" Exception : %s\n", err.what());
+ result = UTEST_TEST_FAILURE;
+ } catch (...) {
+ printf(" Exception : Unknown\n");
+ result = UTEST_TEST_FAILURE;
+ }
+ UTEST_SURPRESS_WARNING_END
+#else
+ utest_state.tests[index].func(&result, utest_state.tests[index].index);
+#endif
+ ns = utest_ns() - ns;
+
+ if (utest_state.output) {
+ fprintf(utest_state.output, "</testcase>\n");
+ }
+
+ // Record the failing test.
+ if (UTEST_TEST_FAILURE == result) {
+ const size_t failed_testcase_index = failed_testcases_length++;
+ failed_testcases = UTEST_PTR_CAST(
+ size_t *, utest_realloc(UTEST_PTR_CAST(void *, failed_testcases),
+ sizeof(size_t) * failed_testcases_length));
+ if (UTEST_NULL != failed_testcases) {
+ failed_testcases[failed_testcase_index] = index;
+ }
+ failed++;
+ } else if (UTEST_TEST_SKIPPED == result) {
+ const size_t skipped_testcase_index = skipped_testcases_length++;
+ skipped_testcases = UTEST_PTR_CAST(
+ size_t *, utest_realloc(UTEST_PTR_CAST(void *, skipped_testcases),
+ sizeof(size_t) * skipped_testcases_length));
+ if (UTEST_NULL != skipped_testcases) {
+ skipped_testcases[skipped_testcase_index] = index;
+ }
+ skipped++;
+ }
+
+ {
+ const char *const units[] = {"ns", "us", "ms", "s", UTEST_NULL};
+ unsigned int unit_index = 0;
+ utest_int64_t time = ns;
+
+ if (enable_mixed_units) {
+ for (unit_index = 0; UTEST_NULL != units[unit_index]; unit_index++) {
+ if (10000 > time) {
+ break;
+ }
+
+ time /= 1000;
+ }
+ }
+
+ if (UTEST_TEST_FAILURE == result) {
+ printf("%s[ FAILED ]%s %s (%" UTEST_PRId64 "%s)\n", colours[RED],
+ colours[RESET], utest_state.tests[index].name, time,
+ units[unit_index]);
+ } else if (UTEST_TEST_SKIPPED == result) {
+ printf("%s[ SKIPPED ]%s %s (%" UTEST_PRId64 "%s)\n", colours[YELLOW],
+ colours[RESET], utest_state.tests[index].name, time,
+ units[unit_index]);
+ } else {
+ printf("%s[ OK ]%s %s (%" UTEST_PRId64 "%s)\n", colours[GREEN],
+ colours[RESET], utest_state.tests[index].name, time,
+ units[unit_index]);
+ }
+ }
+ }
+
+ printf("%s[==========]%s %" UTEST_PRIu64 " test cases ran.\n", colours[GREEN],
+ colours[RESET], ran_tests);
+ printf("%s[ PASSED ]%s %" UTEST_PRIu64 " tests.\n", colours[GREEN],
+ colours[RESET], ran_tests - failed - skipped);
+
+ if (0 != skipped) {
+ printf("%s[ SKIPPED ]%s %" UTEST_PRIu64 " tests, listed below:\n",
+ colours[YELLOW], colours[RESET], skipped);
+ for (index = 0; index < skipped_testcases_length; index++) {
+ printf("%s[ SKIPPED ]%s %s\n", colours[YELLOW], colours[RESET],
+ utest_state.tests[skipped_testcases[index]].name);
+ }
+ }
+
+ if (0 != failed) {
+ printf("%s[ FAILED ]%s %" UTEST_PRIu64 " tests, listed below:\n",
+ colours[RED], colours[RESET], failed);
+ for (index = 0; index < failed_testcases_length; index++) {
+ printf("%s[ FAILED ]%s %s\n", colours[RED], colours[RESET],
+ utest_state.tests[failed_testcases[index]].name);
+ }
+ }
+
+ if (utest_state.output) {
+ fprintf(utest_state.output, "</testsuite>\n</testsuites>\n");
+ }
+
+cleanup:
+ for (index = 0; index < utest_state.tests_length; index++) {
+ free(UTEST_PTR_CAST(void *, utest_state.tests[index].name));
+ }
+
+ free(UTEST_PTR_CAST(void *, skipped_testcases));
+ free(UTEST_PTR_CAST(void *, failed_testcases));
+ free(UTEST_PTR_CAST(void *, utest_state.tests));
+
+ if (utest_state.output) {
+ fclose(utest_state.output);
+ }
+
+ return UTEST_CAST(int, failed);
+}
+
+/*
+ we need, in exactly one source file, define the global struct that will hold
+ the data we need to run utest. This macro allows the user to declare the
+ data without having to use the UTEST_MAIN macro, thus allowing them to write
+ their own main() function.
+*/
+#define UTEST_STATE() struct utest_state_s utest_state = {0, 0, 0}
+
+/*
+ define a main() function to call into utest.h and start executing tests! A
+ user can optionally not use this macro, and instead define their own main()
+ function and manually call utest_main. The user must, in exactly one source
+ file, use the UTEST_STATE macro to declare a global struct variable that
+ utest requires.
+*/
+#define UTEST_MAIN() \
+ UTEST_STATE(); \
+ int main(int argc, const char *const argv[]) { \
+ return utest_main(argc, argv); \
+ }
+
+#endif /* SHEREDOM_UTEST_H_INCLUDED */
diff --git a/test/wg2sd_test.cpp b/test/wg2sd_test.cpp
new file mode 100644
index 0000000..5d57ac9
--- /dev/null
+++ b/test/wg2sd_test.cpp
@@ -0,0 +1,219 @@
+#include "utest.h"
+
+#include "wg2sd.hpp"
+#include <sstream>
+#include <array>
+
+namespace wg2sd {
+ extern bool _is_default_route(std::string const & cidr);
+ extern bool _is_ipv4_route(std::string const & cidr);
+};
+
+using namespace wg2sd;
+
+UTEST(wg2sd, ip_helpers) {
+
+ std::array<std::string, 8> default_routes = {
+ "0/0",
+ "0.0/0",
+ "0.0.0/0",
+ "0.0.0.0/0",
+ "::/0",
+ "::0/0",
+ "::0/0",
+ "0000:0000:0000:0000::/0",
+ };
+
+ for(std::string const & s : default_routes) {
+ ASSERT_TRUE(_is_default_route(s));
+ }
+
+ std::array<std::string, 8> non_default_routes = {
+ "192.168.0.1/24",
+ "0.0.0.1/0",
+ "0.0.0.0/32",
+ "1/1",
+ "2001:db8::1/64",
+ "fe80::1/128",
+ "::1/128",
+ "2001:0db8:0000:0000::/64",
+ };
+
+ for(std::string const & s : non_default_routes) {
+ ASSERT_FALSE(_is_default_route(s));
+ }
+
+ std::array<std::string, 4> ipv4_route = {
+ "192.168.1.1",
+ "0.0.0.0/0",
+ "255.255.255.255/32",
+ "1.2.3.4/0"
+ };
+
+ for(std::string const & s : ipv4_route) {
+ ASSERT_TRUE(_is_ipv4_route(s));
+ }
+
+ std::array<std::string, 4> ipv6_route = {
+ "2001:db8::1/64",
+ "fe80::1/128",
+ "::1/128",
+ "2001:0db8:0000:0000::/64",
+ };
+
+ for(std::string const & s : ipv6_route) {
+ ASSERT_FALSE(_is_ipv4_route(s));
+ }
+}
+
+// Typical configuration, similar to that provided by mullvad.net
+char const * CONFIG1 = (
+ "[Interface]\n"
+ " # Device: Fast Mink\n"
+ " PrivateKey = APmSX97Yww7WyHrQGG3u7oUJAKRazSyXVu9lD+A3aW8=\n"
+ "Address = 10.14.123.142/32,fc00:aaaa:aaaa:aa01::6:ad78/128\n"
+ "DNS =10.0.0.2\n"
+ "[Peer]\n"
+ "PublicKey = kMIIVxitU3/1AnAGwdL5KazDQ97MnkuEVz2sWihALnQ= \n"
+ "AllowedIPs = 0.0.0.0/0,::0/0 # comment\n"
+ "Endpoint = 194.36.25.33:51820\n"
+);
+
+// Configuration with multiple peers, interface peer tables are reversed
+const char * CONFIG2 = (
+ "[Peer]\n"
+ "PublicKey = sMYYPASxJslAuszh5PgUPysrzZHHBOzawJ8PFbRQrHI=\n"
+ "AllowedIPs = 192.168.1.2/32\n"
+ "Endpoint = 203.0.113.1:51820\n"
+ "\n"
+ "[Peer]\n"
+ "PublicKey = kB9CSPsPS5irR0ZpVAHZKPNHLQKjIFjmgc6MSCAiWUs=\n"
+ "AllowedIPs = 192.168.1.3/32\n"
+ "Endpoint = 203.0.113.2:51820\n"
+ "\n"
+ "[Interface]\n"
+ "PrivateKey = ED3TF8deMhmXHa7Jrp024uv5T7jKl7611vFV3C1P+EY=\n"
+ "Address = 192.168.1.1/24\n"
+);
+
+// Configuration with persistant keepalive, psk, and multiple DNS entries
+const char * CONFIG3 = (
+ "[Interface]\n"
+ "PrivateKey = cJgeEfHUay0aKpV+k1lFK9nq9JJcqzKm8+Wh3EGtg1c=\n"
+ " Table=5\n"
+ "\t\tAddress = 192.168.1.1/24\n"
+ "DNS = 8.8.8.8,\t 8.8.4.4\n"
+ "ListenPort = 4444\n"
+ "Table = 42\n"
+ "\n"
+ "[Peer]\n"
+ " PublicKey = kB9CSPsPS5irR0ZpVAHZKPNHLQKjIFjmgc6MSCAiWUs=\n"
+ " AllowedIPs = 192.168.1.2/32\n"
+ "Endpoint = 203.0.113.1:51820\n"
+ "PersistentKeepalive = 25\n"
+ " PresharedKey = KIst3pK+YVHmM5k7NbNULKd2px9vaRsFi/y4E7NDWDQ=\n"
+);
+
+const char * INVALID_CONFIG = (
+ "PrivateKey = kPvfTBQxgHpaXI9wVj6JrtYKIJLVXrf0zg6ON7qUxl8=\n"
+ "Address = 192.168.1.1/24\n"
+ "[Interface]\n"
+);
+
+
+UTEST(wg2sd, parses_config) {
+
+ // CONFIG1
+ std::istringstream ss { CONFIG1 };
+
+ Config cfg = parse_config("wg", ss);
+
+ ASSERT_STREQ(cfg.intf.name.c_str(), "wg");
+ ASSERT_STREQ(cfg.intf.private_key.c_str(), "APmSX97Yww7WyHrQGG3u7oUJAKRazSyXVu9lD+A3aW8=");
+ ASSERT_EQ(cfg.intf.table, 0ul);
+ ASSERT_FALSE(cfg.intf.listen_port.has_value());
+ ASSERT_EQ(cfg.intf.DNS.size(), 1ull);
+ ASSERT_STREQ(cfg.intf.DNS.back().c_str(), "10.0.0.2");
+ ASSERT_EQ(cfg.peers.size(), 1ull);
+
+ const Peer & peer = cfg.peers[0];
+
+ ASSERT_STREQ(peer.endpoint.c_str(), "194.36.25.33:51820");
+ ASSERT_STREQ(peer.allowed_ips[0].route.c_str(), "0.0.0.0/0");
+ ASSERT_TRUE(peer.allowed_ips[0].is_ipv4);
+ ASSERT_TRUE(peer.allowed_ips[0].is_default_route);
+ ASSERT_STREQ(peer.allowed_ips[1].route.c_str(), "::0/0");
+ ASSERT_FALSE(peer.allowed_ips[1].is_ipv4);
+ ASSERT_TRUE(peer.allowed_ips[1].is_default_route);
+
+ ASSERT_STREQ(peer.public_key.c_str(), "kMIIVxitU3/1AnAGwdL5KazDQ97MnkuEVz2sWihALnQ=");
+ ASSERT_STREQ(peer.preshared_key.c_str(), "");
+ ASSERT_STREQ(peer.persistent_keepalive.c_str(), "");
+
+ // CONFIG2
+ std::istringstream ss2 { CONFIG2 };
+
+ Config cfg2 = parse_config("wg", ss2);
+
+ ASSERT_STREQ(cfg2.intf.name.c_str(), "wg");
+ ASSERT_FALSE(cfg2.intf.listen_port.has_value());
+ ASSERT_STREQ(cfg2.intf.private_key.c_str(), "ED3TF8deMhmXHa7Jrp024uv5T7jKl7611vFV3C1P+EY=");
+ ASSERT_EQ(cfg2.intf.table, 0ul);
+ ASSERT_EQ(cfg2.intf.DNS.size(), 0ull);
+
+ ASSERT_EQ(cfg2.peers.size(), 2ull);
+
+ const Peer &peer2_1 = cfg2.peers[0];
+
+ ASSERT_STREQ(peer2_1.endpoint.c_str(), "203.0.113.1:51820");
+ ASSERT_STREQ(peer2_1.allowed_ips[0].route.c_str(), "192.168.1.2/32");
+ ASSERT_TRUE(peer2_1.allowed_ips[0].is_ipv4);
+ ASSERT_FALSE(peer2_1.allowed_ips[0].is_default_route);
+ ASSERT_STREQ(peer2_1.public_key.c_str(), "sMYYPASxJslAuszh5PgUPysrzZHHBOzawJ8PFbRQrHI=");
+ ASSERT_STREQ(peer2_1.preshared_key.c_str(), "");
+ ASSERT_STREQ(peer2_1.persistent_keepalive.c_str(), "");
+
+ const Peer &peer2_2 = cfg2.peers[1];
+
+ ASSERT_STREQ(peer2_2.endpoint.c_str(), "203.0.113.2:51820");
+ ASSERT_STREQ(peer2_2.allowed_ips[0].route.c_str(), "192.168.1.3/32");
+ ASSERT_TRUE(peer2_2.allowed_ips[0].is_ipv4);
+ ASSERT_FALSE(peer2_2.allowed_ips[0].is_default_route);
+ ASSERT_STREQ(peer2_2.public_key.c_str(), "kB9CSPsPS5irR0ZpVAHZKPNHLQKjIFjmgc6MSCAiWUs=");
+ ASSERT_STREQ(peer2_2.preshared_key.c_str(), "");
+ ASSERT_STREQ(peer2_2.persistent_keepalive.c_str(), "");
+
+ // CONFIG3
+ std::istringstream ss3 { CONFIG3 };
+
+ Config cfg3 = parse_config("wg", ss3);
+
+ ASSERT_STREQ(cfg3.intf.name.c_str(), "wg");
+ ASSERT_STREQ(cfg3.intf.private_key.c_str(), "cJgeEfHUay0aKpV+k1lFK9nq9JJcqzKm8+Wh3EGtg1c=");
+ ASSERT_EQ(cfg3.intf.table, 42ul);
+ uint16_t port = cfg3.intf.listen_port.value();
+ ASSERT_EQ(port, 4444);
+ ASSERT_EQ(cfg3.intf.DNS.size(), 2ull);
+ ASSERT_STREQ(cfg3.intf.DNS[0].c_str(), "8.8.8.8");
+ ASSERT_STREQ(cfg3.intf.DNS[1].c_str(), "8.8.4.4");
+
+ ASSERT_EQ(cfg3.peers.size(), 1ull);
+
+ const Peer &peer3 = cfg3.peers[0];
+
+ ASSERT_STREQ(peer3.endpoint.c_str(), "203.0.113.1:51820");
+ ASSERT_STREQ(peer3.allowed_ips[0].route.c_str(), "192.168.1.2/32");
+ ASSERT_TRUE(peer3.allowed_ips[0].is_ipv4);
+ ASSERT_FALSE(peer3.allowed_ips[0].is_default_route);
+ ASSERT_STREQ(peer3.public_key.c_str(), "kB9CSPsPS5irR0ZpVAHZKPNHLQKjIFjmgc6MSCAiWUs=");
+ ASSERT_STREQ(peer3.preshared_key.c_str(), "KIst3pK+YVHmM5k7NbNULKd2px9vaRsFi/y4E7NDWDQ=");
+ ASSERT_STREQ(peer3.persistent_keepalive.c_str(), "25");
+
+
+ // INVALID_CONFIG
+ std::istringstream ss4 { INVALID_CONFIG };
+
+ ASSERT_EXCEPTION(parse_config("wg", ss4), ParsingException);
+}
+
+UTEST_MAIN()