diff --git a/core/io/ip_address.cpp b/core/io/ip_address.cpp index 174bbb21fa8..2918f51f413 100644 --- a/core/io/ip_address.cpp +++ b/core/io/ip_address.cpp @@ -44,7 +44,7 @@ IPAddress::operator String() const { } if (is_ipv4()) { - // IPv4 address mapped to IPv6 + // IPv4 address mapped to IPv6. return itos(field8[12]) + "." + itos(field8[13]) + "." + itos(field8[14]) + "." + itos(field8[15]); } String ret; @@ -59,103 +59,134 @@ IPAddress::operator String() const { return ret; } -static void _parse_hex(const String &p_string, int p_start, uint8_t *p_dst) { - uint16_t ret = 0; - for (int i = p_start; i < p_start + 4; i++) { - if (i >= p_string.length()) { - break; - } +bool IPAddress::_parse_ipv6(const String &p_string, IPAddress &r_ip) { + int len = p_string.length(); + const char32_t *buf = p_string.ptr(); - int n = 0; - char32_t c = p_string[i]; - if (is_digit(c)) { - n = c - '0'; - } else if (c >= 'a' && c <= 'f') { - n = 10 + (c - 'a'); - } else if (c >= 'A' && c <= 'F') { - n = 10 + (c - 'A'); - } else if (c == ':') { - break; - } else { - ERR_FAIL_MSG("Invalid character in IPv6 address: " + p_string + "."); + int cur = 0; + int shift = -1; + for (int i = 0; i < len; i++) { + for (int j = i; j < len; j++) { + char32_t c = buf[j]; + if (c == ':') { + if (j + 1 == len) { + return false; // Can't end with a column (unless part of shortening). + } + if (buf[j + 1] == ':') { + if (shift > -1) { + return false; // Only one shortening allowed. + } else if (j == 0) { + shift = cur; + } else { + shift = cur + 1; + } + j++; + } else if (i == j) { + return false; // Stray column. + } + i = j; + break; + } + if (j - i > 3) { + return false; + } + if (c >= '0' && c <= '9') { + r_ip.field16[cur] = r_ip.field16[cur] << 4; + r_ip.field16[cur] |= c - '0'; + } else if (c >= 'a' && c <= 'f') { + r_ip.field16[cur] = r_ip.field16[cur] << 4; + r_ip.field16[cur] |= 10 + (c - 'a'); + } else if (c >= 'A' && c <= 'F') { + r_ip.field16[cur] = r_ip.field16[cur] << 4; + r_ip.field16[cur] |= 10 + (c - 'A'); + } else if (c == '.') { + // IPv4 mapped IPv6 (e.g. "::FFFF:127.0.0.1"). + if (cur < 1 || r_ip.field16[cur - 1] != 0xFFFF) { + return false; // IPv6 part must end with FFFF. + } + if (shift < 0 && cur != 6) { + return false; // Needs 5 zeros, and FFFF "0:0:0:0:0:FFFF:127.0.0.1". + } + // Only empty bytes allowed before FFFF. + r_ip.field16[cur] = 0; + r_ip.field16[cur - 1] = 0; + while (cur > 0) { + cur--; + if (r_ip.field16[cur] != 0) { + return false; + } + } + r_ip.field16[5] = 0xFFFF; + return _parse_ipv4(p_string, i, &r_ip.field8[12]); + } else { + return false; + } + if (j + 1 == len) { + i = j; + } + } + r_ip.field16[cur] = BSWAP16(r_ip.field16[cur]); + cur += 1; + if (cur > 8 || (cur == 8 && i + 1 != len)) { + return false; } - ret = ret << 4; - ret += n; } - - p_dst[0] = ret >> 8; - p_dst[1] = ret & 0xff; + if (shift < 0) { + return cur == 8; // Should have parsed 8 16-bits ints. + } else if (shift > 7) { + return false; // Can't shorten more than this. + } else if (shift == cur) { + return true; // Nothing to do, end is assumed zeroized. + } + // Shift bytes. + int pad = 8 - cur; + int blank_end = shift + pad; + for (int i = 7; i > shift; i--) { + if (i < blank_end) { + r_ip.field16[i] = 0; + } else { + r_ip.field16[i] = r_ip.field16[i - pad]; + } + } + return true; } -void IPAddress::_parse_ipv6(const String &p_string) { - static const int parts_total = 8; - int parts[parts_total] = { 0 }; - int parts_count = 0; - bool part_found = false; - bool part_skip = false; - bool part_ipv4 = false; - int parts_idx = 0; +bool IPAddress::_parse_ipv4(const String &p_string, int p_start, uint8_t *r_dest) { + int len = p_string.length(); + const char32_t *buf = p_string.ptr(); - for (int i = 0; i < p_string.length(); i++) { - char32_t c = p_string[i]; - if (c == ':') { - if (i == 0) { - continue; // next must be a ":" + int cur = 0; + uint16_t next = 0; + bool parsed = false; + for (int i = p_start; i < len; i++) { + char32_t c = buf[i]; + if (c == '.') { + if (!parsed) { + return false; } - if (!part_found) { - part_skip = true; - parts[parts_idx++] = -1; + parsed = false; + r_dest[cur] = next; + next = 0; + cur++; + if (cur > 3) { + return false; } - part_found = false; - } else if (c == '.') { - part_ipv4 = true; - - } else if (is_hex_digit(c)) { - if (!part_found) { - parts[parts_idx++] = i; - part_found = true; - ++parts_count; + } else if (c >= '0' && c <= '9') { + parsed = true; + next *= 10; + next += c - '0'; + if (next > 255) { + return false; } } else { - ERR_FAIL_MSG("Invalid character in IPv6 address: " + p_string + "."); + return false; // Invalid char. } } - - int parts_extra = 0; - if (part_skip) { - parts_extra = parts_total - parts_count; - } - - int idx = 0; - for (int i = 0; i < parts_idx; i++) { - if (parts[i] == -1) { - for (int j = 0; j < parts_extra; j++) { - field16[idx++] = 0; - } - continue; - } - - if (part_ipv4 && i == parts_idx - 1) { - _parse_ipv4(p_string, parts[i], (uint8_t *)&field16[idx]); // should be the last one - } else { - _parse_hex(p_string, parts[i], (uint8_t *)&(field16[idx++])); - } - } -} - -void IPAddress::_parse_ipv4(const String &p_string, int p_start, uint8_t *p_ret) { - String ip; - if (p_start != 0) { - ip = p_string.substr(p_start); - } else { - ip = p_string; - } - - int slices = ip.get_slice_count("."); - ERR_FAIL_COND_MSG(slices != 4, "Invalid IP address string: " + ip + "."); - for (int i = 0; i < 4; i++) { - p_ret[i] = ip.get_slicec('.', i).to_int(); + if (!parsed) { + return false; } + r_dest[cur] = next; + return parsed && cur == 3; } void IPAddress::clear() { @@ -192,23 +223,33 @@ void IPAddress::set_ipv6(const uint8_t *p_buf) { } } +bool IPAddress::is_valid_ip_address(const String &p_string) { + IPAddress addr; + if (p_string.length() < IPV6_MAX_STRING_LENGTH && p_string.contains_char(':')) { + return _parse_ipv6(p_string, addr); + } else if (p_string.length() < IPV4_MAX_STRING_LENGTH) { // Try IPv4. + return _parse_ipv4(p_string, 0, &addr.field8[12]); + } + return false; +} + IPAddress::IPAddress(const String &p_string) { clear(); if (p_string == "*") { - // Wildcard (not a valid IP) + // Wildcard (not a valid IP). wildcard = true; - } else if (p_string.contains_char(':')) { - // IPv6 - _parse_ipv6(p_string); - valid = true; + } else if (p_string.length() < IPV6_MAX_STRING_LENGTH && p_string.contains_char(':')) { + // IPv6. + valid = _parse_ipv6(p_string, *this); + ERR_FAIL_COND_MSG(!valid, "Invalid IPv6 address: " + p_string); - } else if (p_string.get_slice_count(".") == 4) { - // IPv4 (mapped to IPv6 internally) + } else if (p_string.length() < IPV4_MAX_STRING_LENGTH) { + // IPv4 (mapped to IPv6 internally). field16[5] = 0xffff; - _parse_ipv4(p_string, 0, &field8[12]); - valid = true; + valid = _parse_ipv4(p_string, 0, &field8[12]); + ERR_FAIL_COND_MSG(!valid, "Invalid IPv4 address: " + p_string); } else { ERR_PRINT("Invalid IP address."); @@ -226,7 +267,7 @@ IPAddress::IPAddress(uint32_t p_a, uint32_t p_b, uint32_t p_c, uint32_t p_d, boo clear(); valid = true; if (!is_v6) { - // Mapped to IPv6 + // Mapped to IPv6. field16[5] = 0xffff; field8[12] = p_a; field8[13] = p_b; diff --git a/core/io/ip_address.h b/core/io/ip_address.h index 210f3c053e3..6f85b5cc75a 100644 --- a/core/io/ip_address.h +++ b/core/io/ip_address.h @@ -40,12 +40,17 @@ private: uint32_t field32[4]; }; + enum { + IPV4_MAX_STRING_LENGTH = 16, + IPV6_MAX_STRING_LENGTH = 40, + }; + bool valid; bool wildcard; protected: - void _parse_ipv6(const String &p_string); - void _parse_ipv4(const String &p_string, int p_start, uint8_t *p_ret); + static bool _parse_ipv6(const String &p_string, IPAddress &r_ip); + static bool _parse_ipv4(const String &p_string, int p_start, uint8_t *r_dest); public: //operator Variant() const; @@ -79,6 +84,8 @@ public: return false; } + static bool is_valid_ip_address(const String &p_string); + void clear(); bool is_wildcard() const { return wildcard; } bool is_valid() const { return valid; } diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 7163ef0cb21..cf614b09143 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -35,6 +35,7 @@ STATIC_ASSERT_INCOMPLETE_TYPE(class, Dictionary); STATIC_ASSERT_INCOMPLETE_TYPE(class, Object); #include "core/crypto/crypto_core.h" +#include "core/io/ip_address.h" #include "core/math/color.h" #include "core/math/math_funcs.h" #include "core/object/object.h" @@ -4940,43 +4941,7 @@ String String::validate_filename() const { } bool String::is_valid_ip_address() const { - if (find_char(':') >= 0) { - Vector ip = split(":"); - for (int i = 0; i < ip.size(); i++) { - const String &n = ip[i]; - if (n.is_empty()) { - continue; - } - if (n.is_valid_hex_number(false)) { - int64_t nint = n.hex_to_int(); - if (nint < 0 || nint > 0xffff) { - return false; - } - continue; - } - if (!n.is_valid_ip_address()) { - return false; - } - } - - } else { - Vector ip = split("."); - if (ip.size() != 4) { - return false; - } - for (int i = 0; i < ip.size(); i++) { - const String &n = ip[i]; - if (!n.is_valid_int()) { - return false; - } - int val = n.to_int(); - if (val < 0 || val > 255) { - return false; - } - } - } - - return true; + return IPAddress::is_valid_ip_address(*this); } bool String::is_resource_file() const { diff --git a/tests/core/io/test_ip_address.h b/tests/core/io/test_ip_address.h new file mode 100644 index 00000000000..c9bcde3fe98 --- /dev/null +++ b/tests/core/io/test_ip_address.h @@ -0,0 +1,283 @@ +/**************************************************************************/ +/* test_ip_address.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* 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 OR COPYRIGHT HOLDERS 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. */ +/**************************************************************************/ + +#pragma once + +#include "core/io/ip_address.h" + +#include "tests/test_macros.h" + +namespace TestIPAddress { + +struct IPTester : public IPAddress { +public: + static bool _test_v4(const char *p_ip) { + uint8_t ip[4]; + return _parse_ipv4(String::utf8(p_ip), 0, ip); + } + + static bool _test_v6(const char *p_ip) { + IPAddress addr; + return _parse_ipv6(String::utf8(p_ip), addr); + } + + static bool test_v4(const char *p_ip, bool p_valid) { + bool is_6 = _test_v6(p_ip); + bool is_4 = _test_v4(p_ip); + bool is_valid = IPAddress::is_valid_ip_address(String::utf8(p_ip)); + IPAddress ip(p_ip); + return is_4 == p_valid && is_6 == false && is_valid == p_valid && ip.is_valid() == p_valid && ip.is_wildcard() == false; + } + + static bool test_v6(const char *p_ip, bool p_valid) { + bool is_6 = _test_v6(p_ip); + bool is_4 = _test_v4(p_ip); + bool is_valid = is_valid_ip_address(String::utf8(p_ip)); + IPAddress ip(p_ip); + return is_4 == false && is_6 == p_valid && is_valid == p_valid && ip.is_valid() == p_valid && ip.is_wildcard() == false; + } + + static bool test_wildcard(const char *p_ip) { + bool is_6 = _test_v6(p_ip); + bool is_4 = _test_v4(p_ip); + bool is_valid = is_valid_ip_address(String::utf8(p_ip)); + IPAddress ip(p_ip); + return is_6 == false && is_4 == false && ip.is_valid() == false && is_valid == false && ip.is_wildcard() == true; + } +}; + +TEST_CASE("[IPAddress] Wildcard and misc") { + ERR_PRINT_OFF; + + auto test_ip = [](const char *l_ip, bool l_valid) { + return IPAddress(l_ip).is_wildcard() == false && l_valid == (IPTester::test_v4(l_ip, true) || IPTester::test_v6(l_ip, true)); + }; + + CHECK(IPTester::test_wildcard("*")); + + CHECK(test_ip("", false)); + + CHECK(test_ip(" ", false)); + + CHECK(test_ip("::", true)); + CHECK(test_ip("0.0.0.0", true)); + + CHECK(test_ip("not an ip", false)); + CHECK(test_ip("surely.not:an:ip", false)); + + ERR_PRINT_ON; +} + +TEST_CASE("[IPAddress] IPv4 is_valid") { + ERR_PRINT_OFF; + + auto test_ip = [](const char *l_ip, bool l_valid) { + return IPTester::test_v4(l_ip, l_valid); + }; + + // Valid IPs + CHECK(test_ip("127.0.0.1", true)); + CHECK(test_ip("255.255.255.255", true)); + CHECK(test_ip("0.0.0.0", true)); + + // Invalid IPs + CHECK(test_ip(" 127.0.0.1", false)); + CHECK(test_ip("127.0.0.1 ", false)); + CHECK(test_ip(" 127.0.0.1 ", false)); + CHECK(test_ip("127.0.0.-1", false)); + CHECK(test_ip("127.0.0.256", false)); + CHECK(test_ip("127.0.0.", false)); + CHECK(test_ip(".0.0.1", false)); + CHECK(test_ip("127.0.0.1.", false)); + CHECK(test_ip(".127.0.0.1", false)); + CHECK(test_ip("0.127.0.0.1", false)); + CHECK(test_ip("127.0.0.1.0", false)); + CHECK(test_ip(".....", false)); + CHECK(test_ip("....", false)); + CHECK(test_ip("...", false)); + CHECK(test_ip("..", false)); + CHECK(test_ip(".", false)); + CHECK(test_ip("", false)); + ERR_PRINT_ON; +} + +TEST_CASE("[IPAddress] IPv4 Parsing") { + auto test_ip = [](const char *l_ip, uint32_t l_test) { + l_test = BSWAP32(l_test); + return memcmp(IPAddress(l_ip).get_ipv4(), &l_test, 4) == 0 && IPTester::test_v4(l_ip, true); + }; + CHECK(test_ip("127.0.0.1", 2130706433)); + CHECK(test_ip("255.0.0.0", 255 << 24)); + CHECK(test_ip("0.255.0.0", 255 << 16)); + CHECK(test_ip("0.0.255.0", 255 << 8)); + CHECK(test_ip("0.0.0.255", 255)); + CHECK(test_ip("127.0.0.0", 127 << 24)); + CHECK(test_ip("0.127.0.0", 127 << 16)); + CHECK(test_ip("0.0.127.0", 127 << 8)); + CHECK(test_ip("0.0.0.127", 127)); + CHECK(test_ip("1.0.0.0", 1 << 24)); + CHECK(test_ip("0.1.0.0", 1 << 16)); + CHECK(test_ip("0.0.1.0", 1 << 8)); + CHECK(test_ip("0.0.0.1", 1)); +} + +TEST_CASE("[IPAddress] IPv6 is_valid") { + ERR_PRINT_OFF; + + auto test_ip = [](const char *l_ip, bool l_valid) { + return IPTester::test_v6(l_ip, l_valid); + }; + + // Valid IPs + CHECK(test_ip("::", true)); + + CHECK(test_ip("::1", true)); + CHECK(test_ip("::1:1", true)); + CHECK(test_ip("::1:1:1", true)); + CHECK(test_ip("::1:1:1:1", true)); + CHECK(test_ip("::1:1:1:1:1", true)); + CHECK(test_ip("::1:1:1:1:1:1", true)); + CHECK(test_ip("::1:1:1:1:1:1:1", true)); + CHECK(test_ip("1:1:1:1:1:1:1:1", true)); + CHECK(test_ip("1:1:1:1:1:1:1::", true)); + CHECK(test_ip("1:1:1:1:1:1::", true)); + CHECK(test_ip("1:1:1:1:1::", true)); + CHECK(test_ip("1:1:1:1::", true)); + CHECK(test_ip("1:1:1::", true)); + CHECK(test_ip("1:1::", true)); + CHECK(test_ip("1::", true)); + + CHECK(test_ip("1::1:1:1:1:1:1", true)); + CHECK(test_ip("1:1::1:1:1:1:1", true)); + CHECK(test_ip("1:1:1::1:1:1:1", true)); + CHECK(test_ip("1:1:1:1::1:1:1", true)); + CHECK(test_ip("1:1:1:1:1::1:1", true)); + CHECK(test_ip("1:1:1:1:1:1::1", true)); + CHECK(test_ip("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true)); + CHECK(test_ip("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", true)); + CHECK(test_ip("::ffff", true)); + CHECK(test_ip("::FFFF", true)); + CHECK(test_ip("ffff::", true)); + CHECK(test_ip("FFFF::", true)); + + // IPv4-mapped address + CHECK(test_ip("::ffff:127.0.0.1", true)); + CHECK(test_ip("::FFFF:127.0.0.1", true)); + CHECK(test_ip("0::ffff:127.0.0.1", true)); + CHECK(test_ip("0:0::ffff:127.0.0.1", true)); + CHECK(test_ip("0:0:0::ffff:127.0.0.1", true)); + CHECK(test_ip("0:0:0:0::ffff:127.0.0.1", true)); + CHECK(test_ip("0:0:0:0:0:ffff:127.0.0.1", true)); + + // Invalid IPs + CHECK(test_ip(" ::", false)); + CHECK(test_ip(":: ", false)); + CHECK(test_ip("::-0", false)); + CHECK(test_ip("1:1:1:1:1:1:1:g", false)); + CHECK(test_ip(" 1:1:1:1:1:1:1:1", false)); + CHECK(test_ip("1:1:1:1:1:1:1:1 ", false)); + CHECK(test_ip(" 1:1:1:1:1:1:1:1 ", false)); + CHECK(test_ip("1:1:1:1:1:1:1:1:1", false)); + CHECK(test_ip("1:1:1:1:1:1:1:1::", false)); + CHECK(test_ip("::1:1:1:1:1:1:1:1", false)); + CHECK(test_ip("::1::1", false)); + CHECK(test_ip(":", false)); + CHECK(test_ip(":::", false)); + CHECK(test_ip("::::", false)); + CHECK(test_ip(":::::", false)); + CHECK(test_ip("::::::", false)); + CHECK(test_ip(":::::::", false)); + CHECK(test_ip("::::::::", false)); + + // IPv4-mapped address + CHECK(test_ip("1::ffff:127.0.0.1", false)); + CHECK(test_ip("::1:ffff:127.0.0.1", false)); + CHECK(test_ip("::ffff:127.0.256.1", false)); + CHECK(test_ip("::ffff:127.0.0.256", false)); + CHECK(test_ip("::ffff:256.0.0.1", false)); + CHECK(test_ip("::ffff:127.0.0.1.1", false)); + CHECK(test_ip("::ffff:127.0.0.1.", false)); + CHECK(test_ip("::ffff:127.0.0.", false)); + CHECK(test_ip("::ffff:127.0.0", false)); + CHECK(test_ip("::ffff:127.0.", false)); + CHECK(test_ip("::ffff:127.0", false)); + CHECK(test_ip("::ffff:127.", false)); + CHECK(test_ip("::ffff:", false)); + + // This is a valid IPv6 address (non IPv4-mapped) + CHECK(test_ip("::ffff:127", true)); + + ERR_PRINT_ON; +} + +TEST_CASE("[IPAddress] IPv6 Parsing") { + struct InitIP { + uint16_t data[8]; + }; + auto test_ip = [](const char *l_ip, InitIP l_test) { + for (int i = 0; i < 8; i++) { + l_test.data[i] = BSWAP16(l_test.data[i]); + } + return memcmp(IPAddress(l_ip).get_ipv6(), l_test.data, 16) == 0 && IPTester::test_v6(l_ip, true); + }; + CHECK(IPAddress("::").is_valid() == true); + + CHECK(test_ip("::1", { 0, 0, 0, 0, 0, 0, 0, 1 })); + CHECK(test_ip("::1:1", { 0, 0, 0, 0, 0, 0, 1, 1 })); + CHECK(test_ip("::1:1:1", { 0, 0, 0, 0, 0, 1, 1, 1 })); + CHECK(test_ip("::1:1:1:1", { 0, 0, 0, 0, 1, 1, 1, 1 })); + CHECK(test_ip("::1:1:1:1:1", { 0, 0, 0, 1, 1, 1, 1, 1 })); + CHECK(test_ip("::1:1:1:1:1:1", { 0, 0, 1, 1, 1, 1, 1, 1 })); + CHECK(test_ip("::1:1:1:1:1:1:1", { 0, 1, 1, 1, 1, 1, 1, 1 })); + CHECK(test_ip("1:1:1:1:1:1:1:1", { 1, 1, 1, 1, 1, 1, 1, 1 })); + CHECK(test_ip("1:1:1:1:1:1:1::", { 1, 1, 1, 1, 1, 1, 1, 0 })); + CHECK(test_ip("1:1:1:1:1:1::", { 1, 1, 1, 1, 1, 1, 0, 0 })); + CHECK(test_ip("1:1:1:1:1::", { 1, 1, 1, 1, 1, 0, 0, 0 })); + CHECK(test_ip("1:1:1:1::", { 1, 1, 1, 1, 0, 0, 0, 0 })); + CHECK(test_ip("1:1:1::", { 1, 1, 1, 0, 0, 0, 0, 0 })); + CHECK(test_ip("1:1::", { 1, 1, 0, 0, 0, 0, 0, 0 })); + CHECK(test_ip("1::", { 1, 0, 0, 0, 0, 0, 0, 0 })); + + CHECK(test_ip("ffff::", { 0xFFFF, 0, 0, 0, 0, 0, 0, 0 })); + CHECK(test_ip("::ffff", { 0, 0, 0, 0, 0, 0, 0, 0xFFFF })); + CHECK(test_ip("::fffe:0", { 0, 0, 0, 0, 0, 0, 0xFFFE, 0 })); + CHECK(test_ip("0:fffe::", { 0, 0xFFFE, 0, 0, 0, 0, 0, 0 })); + + // IPv4-mapped address + CHECK(test_ip("::ffff:127.0.0.1", { 0, 0, 0, 0, 0, 0xFFFF, 0x7F00, 1 })); + CHECK(test_ip("::FFFF:127.0.0.1", { 0, 0, 0, 0, 0, 0xFFFF, 0x7F00, 1 })); + CHECK(test_ip("0::ffff:127.0.0.1", { 0, 0, 0, 0, 0, 0xFFFF, 0x7F00, 1 })); + CHECK(test_ip("0:0::ffff:127.0.0.1", { 0, 0, 0, 0, 0, 0xFFFF, 0x7F00, 1 })); + CHECK(test_ip("0:0:0::ffff:127.0.0.1", { 0, 0, 0, 0, 0, 0xFFFF, 0x7F00, 1 })); + CHECK(test_ip("0:0:0:0::ffff:127.0.0.1", { 0, 0, 0, 0, 0, 0xFFFF, 0x7F00, 1 })); + CHECK(test_ip("0:0:0:0:0:ffff:127.0.0.1", { 0, 0, 0, 0, 0, 0xFFFF, 0x7F00, 1 })); +} + +} // namespace TestIPAddress diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 2069b94a746..704c75fa9ca 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -49,6 +49,7 @@ #include "tests/core/io/test_http_client.h" #include "tests/core/io/test_image.h" #include "tests/core/io/test_ip.h" +#include "tests/core/io/test_ip_address.h" #include "tests/core/io/test_json.h" #include "tests/core/io/test_json_native.h" #include "tests/core/io/test_logger.h"