network: Normalize IP parsing, fix IPv6, tests

This commit is contained in:
Fabio Alessandrelli
2026-01-09 14:48:21 +01:00
parent 1910c58c49
commit 65ce1360b4
5 changed files with 431 additions and 134 deletions

View File

@@ -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;

View File

@@ -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; }

View File

@@ -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<String> 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<String> 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 {

View File

@@ -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

View File

@@ -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"