diff options
| author | flu0r1ne <flu0r1ne@flu0r1ne.net> | 2023-08-17 15:56:11 -0500 | 
|---|---|---|
| committer | flu0r1ne <flu0r1ne@flu0r1ne.net> | 2023-08-17 15:56:11 -0500 | 
| commit | 34779155d5b69b51aa301e7c111f95ea9c840589 (patch) | |
| tree | 7289232dc3c4cd187e065792191dd707f08d6e79 | |
| download | wg2nd-34779155d5b69b51aa301e7c111f95ea9c840589.tar.xz wg2nd-34779155d5b69b51aa301e7c111f95ea9c840589.zip | |
init commit
| -rw-r--r-- | .ycm_extra_conf.py | 9 | ||||
| -rw-r--r-- | makefile | 73 | ||||
| -rw-r--r-- | src/main.cpp | 202 | ||||
| -rw-r--r-- | src/wg2sd.cpp | 503 | ||||
| -rw-r--r-- | src/wg2sd.hpp | 134 | ||||
| -rw-r--r-- | test/utest.h | 1623 | ||||
| -rw-r--r-- | test/wg2sd_test.cpp | 219 | 
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() | 
