diff --git a/test/unittests/test_generators.h b/test/unittests/test_generators.h index fce52f5c..d6ee532d 100644 --- a/test/unittests/test_generators.h +++ b/test/unittests/test_generators.h @@ -10,6 +10,7 @@ #endif #include +#include #include "openvpn/addr/ip.hpp" #include "openvpn/tun/builder/capture.hpp" @@ -140,20 +141,18 @@ inline auto hexChar(const bool valid = true) -> Gen { if (valid) { - static constexpr int ASCII_zero_position = 48; // '0' - static constexpr int ASCII_nine_position = 57; // '9' - const auto numbers = gen::inRange(ASCII_zero_position, ASCII_nine_position + 1); + const auto alphapositives = gen::elementOf(std::string("abcdefABCDEF123456789")); - static constexpr int ASCII_uppercase_a_position = 65; // represents 'A' - static constexpr int ASCII_uppercase_f_position = 70; // represents 'F' - const auto uppercase = gen::inRange(ASCII_uppercase_a_position, ASCII_uppercase_f_position + 1); + static constexpr int probability_weight_of_0 = 23; + static constexpr int probability_weight_of_alphapositives = 1; - static constexpr int ASCII_lowercase_a_position = 97; // represents 'a' - static constexpr int ASCII_lowercase_f_position = 102; // represents 'f' - const auto lowercase = gen::inRange(ASCII_lowercase_a_position, ASCII_lowercase_f_position + 1); - - return gen::map(gen::oneOf(numbers, uppercase, lowercase), [](const auto &c) - { return std::string{static_cast(c)}; }); + // "0" should be generated times more often + return gen::map(gen::weightedOneOf({{probability_weight_of_0, gen::just('0')}, + {probability_weight_of_alphapositives, alphapositives}}), + [](const auto &c) + { + return std::string{static_cast(c)}; + }); } // Generate invalid hexadecimal characters @@ -164,6 +163,7 @@ inline auto hexChar(const bool valid = true) -> Gen return std::string{static_cast(c)}; }); } + /** * @brief Generates a hextet value of an IPv6 address. * @details This function generates a hextet (4 characters) value of an IPv6 address, @@ -196,6 +196,94 @@ inline auto IPv6HextetValue(const bool valid = true) -> Gen }); } +/** + * @brief Removes leading zeros from a hextet (IPv6 segment). + * @details This function trims all leading zeros from a given hextet string. + * If the hextet only contains zeros (or is empty), it returns "0". + * @param hextet The input string representing a hextet (e.g., "0001", "0ABC"). + * @return A string without leading zeros. + */ +inline std::string removeLeadingZerosFromHextet(const std::string &hextet) +{ + const auto first_nonzero = hextet.find_first_not_of('0'); + return first_nonzero == std::string::npos ? "0" : hextet.substr(first_nonzero); +} + +/** + * @brief Removes leading zeros from a vector of hextets. + * @details This function modifies a vector of IPv6 hextets by removing leading zeros + * from each hextet using the `removeLeadingZerosFromHextet` transformation. + * The operation is performed in-place. + * @param hextets A reference to a vector of strings representing IPv6 hextets. + * Each hextet will be stripped of its leading zeros. + */ +inline void removeLeadingZerosFromHextets(std::vector &hextets) +{ + std::transform(hextets.begin(), hextets.end(), hextets.begin(), removeLeadingZerosFromHextet); +} + +/** + * @brief Replaces the longest sequence of consecutive "0" strings in a vector with "::". + * @details This function finds the longest contiguous sequence of "0" strings + * in the provided `hextets` vector, replaces it with a single "::", + * and removes the other "0" strings in the sequence. + * If there are multiple sequences of the same length, it acts on the first one found. + * @param hextets A vector of strings representing the hextets (subdivisions) of an IPv6 address. + */ +inline void replaceSequenceOfZerosWithDoubleColon(std::vector &hextets) +{ + for (auto longest_zero_sequence = hextets.size(); longest_zero_sequence > 1; --longest_zero_sequence) + { + auto position = std::search_n(hextets.begin(), hextets.end(), longest_zero_sequence, std::string{"0"}); + if (position != hextets.end()) + { + const auto it = hextets.insert(position, "::"); + hextets.erase(it + 1, std::next(it + 1, longest_zero_sequence)); + return; + } + } +} + +/** + * @brief Converts a vector of hextets to an IPv6 address string with colons. + * @details This function takes a vector of strings representing hextets (parts of an IPv6 address) + * and constructs a colon-separated IPv6 address. The function ensures that colons are + * correctly placed between the hextets, and it avoids adding redundant colons around "::" + * which represents a compressed sequence of zeroes in an IPv6 address. + * + * @param hextets A vector of strings, where each string represents a hextet or "::". + * @return A string representation of the IPv6 address with colons separating the hextets. + */ +inline std::string stringifyHextetsToAddressWithColons(const std::vector &hextets) +{ + std::string result; + for (size_t i = 0; i < hextets.size(); ++i) + { + if (i > 0 && hextets[i] != "::" && hextets[i - 1] != "::") + { + result += ":"; + } + result += hextets[i]; + } + return result; +} + +/** + * @brief Compress an IPv6 address by simplifying its representation. + * @details This function takes a list of hextets (hexadecimal segments of an IPv6 address), + * removes leading zeros from each hextet, replaces the largest contiguous sequence + * of zeroed hextets with a double colon (::), and converts the simplified hextets + * back into a string representation of the IPv6 address. + * @param hextets A vector of strings, where each string represents one hextet of an IPv6 address. + * @return A string containing the compressed IPv6 address. + */ +inline std::string compressIPv6Address(std::vector hextets) +{ + removeLeadingZerosFromHextets(hextets); + replaceSequenceOfZerosWithDoubleColon(hextets); + return stringifyHextetsToAddressWithColons(hextets); +} + /** * @brief Generates a random IPv6 address. * @details This function generates a random IPv6 address. The validity of the hextets @@ -219,21 +307,34 @@ inline auto IPv6Address(const bool valid = true) -> Gen static constexpr std::array all_true = {true, true, true, true, true, true, true, true}; const auto hextet_validity = valid ? all_true : *atLeastOneFalse().as("first,second,third,fourth,fifth,sixth,seventh,eighth hextet valid"); - return gen::oneOf(gen::map( - gen::tuple( - IPv6HextetValue(hextet_validity[0]), - IPv6HextetValue(hextet_validity[1]), - IPv6HextetValue(hextet_validity[2]), - IPv6HextetValue(hextet_validity[3]), - IPv6HextetValue(hextet_validity[4]), - IPv6HextetValue(hextet_validity[5]), - IPv6HextetValue(hextet_validity[6]), - IPv6HextetValue(hextet_validity[7])), - [](const auto &hextets) - { - const auto& [first_hextet, second_hextet, third_hextet, fourth_hextet, fifth_hextet, sixth_hextet, seventh_hextet, eighth_hextet] = hextets; - return first_hextet + ":" + second_hextet + ":" + third_hextet + ":" + fourth_hextet + ":" + fifth_hextet + ":" + sixth_hextet + ":" + seventh_hextet + ":" + eighth_hextet; }), - gen::just(std::string{"::0"}).as("valid IPv6 address")); + auto convert_hextets_to_compressed_address = [](std::string first_hextet, + std::string second_hextet, + std::string third_hextet, + std::string fourth_hextet, + std::string fifth_hextet, + std::string sixth_hextet, + std::string seventh_hextet, + std::string eighth_hextet) + { + return compressIPv6Address({std::move(first_hextet), + std::move(second_hextet), + std::move(third_hextet), + std::move(fourth_hextet), + std::move(fifth_hextet), + std::move(sixth_hextet), + std::move(seventh_hextet), + std::move(eighth_hextet)}); + }; + + return gen::apply(convert_hextets_to_compressed_address, + IPv6HextetValue(hextet_validity[0]), + IPv6HextetValue(hextet_validity[1]), + IPv6HextetValue(hextet_validity[2]), + IPv6HextetValue(hextet_validity[3]), + IPv6HextetValue(hextet_validity[4]), + IPv6HextetValue(hextet_validity[5]), + IPv6HextetValue(hextet_validity[6]), + IPv6HextetValue(hextet_validity[7])); } using RedirectGatewayFlagsValues = openvpn::RedirectGatewayFlags::Flags;