/**************************************************************************/
/*  test_ip_address.cpp                                                   */
/**************************************************************************/
/*                         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.                 */
/**************************************************************************/

#include "tests/test_macros.h"

TEST_FORCE_LINK(test_ip_address)

#include "core/io/ip_address.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("1::1", { 1, 0, 0, 0, 0, 0, 0, 1 }));
	CHECK(test_ip("1:1:1:1:1::1", { 1, 1, 1, 1, 1, 0, 0, 1 }));
	CHECK(test_ip("1:1:1::1:1:1", { 1, 1, 1, 0, 0, 1, 1, 1 }));
	CHECK(test_ip("1::1:1:1:1:1", { 1, 0, 0, 1, 1, 1, 1, 1 }));

	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
