/*******************************************************************************
 * libretroshare/src/util: rsnet_ss.cc                                         *
 *                                                                             *
 * libretroshare: retroshare core library                                      *
 *                                                                             *
 * Copyright 2004-2006 Robert Fernie <retroshare@lunamutt.com>                 *
 * Copyright 2015-2023 Gioacchino Mazzurco <gio@eigenlab.org>                  *
 *                                                                             *
 * This program is free software: you can redistribute it and/or modify        *
 * it under the terms of the GNU Lesser General Public License as              *
 * published by the Free Software Foundation, either version 3 of the          *
 * License, or (at your option) any later version.                             *
 *                                                                             *
 * This program is distributed in the hope that it will be useful,             *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of              *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                *
 * GNU Lesser General Public License for more details.                         *
 *                                                                             *
 * You should have received a copy of the GNU Lesser General Public License    *
 * along with this program. If not, see <https://www.gnu.org/licenses/>.       *
 *                                                                             *
 *******************************************************************************/

#include "util/rsurl.h"
#include "util/rsdebug.h"

#include <sstream>
#include <iomanip>
#include <cstdlib>

#ifdef WINDOWS_SYS
#	include <Winsock2.h>
/** Provides Linux like accessor for in6_addr.s6_addr16 for Windows.
 * Yet Windows doesn't provide 32 bits accessors so there is no way to use
 * in6_addr.s6_addr32 crossplatform.
 */
#	define s6_addr16 u.Word
#else
#	include <netinet/in.h>
#	include <sys/socket.h>
#	include <sys/types.h>
#	include <net/if.h>
#	ifdef __APPLE__
/// Provides Linux like accessor for in6_addr.s6_addr16 for Mac.
#		define	s6_addr16 __u6_addr.__u6_addr16
#endif // __APPLE__
#endif // WINDOWS_SYS

#include "util/rsnet.h"
#include "util/rsstring.h"

#ifdef SS_DEBUG
#	include "util/stacktrace.h"
#endif //def SS_DEBUG

/***************************** Internal Helper Fns ******************************/

/******************************** Casting  **************************************/
struct sockaddr_in *to_ipv4_ptr(struct sockaddr_storage &addr);
struct sockaddr_in6 *to_ipv6_ptr(struct sockaddr_storage &addr);

const struct sockaddr_in *to_const_ipv4_ptr(const struct sockaddr_storage &addr);
const struct sockaddr_in6 *to_const_ipv6_ptr(const struct sockaddr_storage &addr);


/******************************** Set / Clear ***********************************/

bool sockaddr_storage_ipv4_zeroip(struct sockaddr_storage &addr);
bool sockaddr_storage_ipv4_copyip(struct sockaddr_storage &dst, const struct sockaddr_storage &src);

uint16_t sockaddr_storage_ipv4_port(const struct sockaddr_storage &addr);
bool sockaddr_storage_ipv4_setport(struct sockaddr_storage &addr, uint16_t port);

bool sockaddr_storage_ipv6_zeroip(struct sockaddr_storage &addr);
bool sockaddr_storage_ipv6_copyip(struct sockaddr_storage &dst, const struct sockaddr_storage &src);

uint16_t sockaddr_storage_ipv6_port(const struct sockaddr_storage &addr);
bool sockaddr_storage_ipv6_setport(struct sockaddr_storage &addr, uint16_t port);

/******************************** Comparisions **********************************/

bool sockaddr_storage_ipv4_lessthan(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2);
bool sockaddr_storage_ipv4_same(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2);
bool sockaddr_storage_ipv4_sameip(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2);

bool sockaddr_storage_ipv6_lessthan(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2);
bool sockaddr_storage_ipv6_same(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2);
bool sockaddr_storage_ipv6_sameip(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2);


/********************************* Output     ***********************************/
std::string sockaddr_storage_ipv4_iptostring(const struct sockaddr_storage &addr);
std::string sockaddr_storage_ipv6_iptostring(const struct sockaddr_storage &addr);

/********************************* Net Checks ***********************************/
bool sockaddr_storage_ipv4_isnull(const struct sockaddr_storage &addr);
bool sockaddr_storage_ipv4_isValidNet(const struct sockaddr_storage &addr);
bool sockaddr_storage_ipv4_isLoopbackNet(const struct sockaddr_storage &addr);
bool sockaddr_storage_ipv4_isPrivateNet(const struct sockaddr_storage &addr);
bool sockaddr_storage_ipv4_isExternalNet(const struct sockaddr_storage &addr);

bool sockaddr_storage_ipv6_isnull(const struct sockaddr_storage &addr);
bool sockaddr_storage_ipv6_isValidNet(const struct sockaddr_storage &addr);
bool sockaddr_storage_ipv6_isLoopbackNet(const struct sockaddr_storage &addr);
bool sockaddr_storage_ipv6_isPrivateNet(const struct sockaddr_storage &addr);
bool sockaddr_storage_ipv6_isExternalNet(const struct sockaddr_storage &addr);


/***************************/

/******************************** Socket Fns  ***********************************/
// Standard bind, on OSX anyway will not accept a longer socklen for IPv4.
// so hidding details behind function.
int rs_bind(int fd, const sockaddr_storage& addr)
{
#ifdef SS_DEBUG
	std::cerr << __PRETTY_FUNCTION__ << std::endl;
#endif

	socklen_t len = 0;
	switch (addr.ss_family)
	{
		case AF_INET:
			len = sizeof(struct sockaddr_in);
			break;
		case AF_INET6:
			len = sizeof(struct sockaddr_in6);
			break;
	}

	return bind(fd, reinterpret_cast<const struct sockaddr *>(&addr), len);
}




/******************************** Set / Clear ***********************************/

void sockaddr_storage_clear(struct sockaddr_storage &addr)
{
	memset(&addr, 0, sizeof(addr));
}

// mods.
bool sockaddr_storage_zeroip(struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_zeroip()";
	std::cerr << std::endl;
#endif

	switch(addr.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_zeroip(addr);
			break;
		case AF_INET6:
			return sockaddr_storage_ipv6_zeroip(addr);
			break;
		default:
			std::cerr << "sockaddr_storage_zeroip() invalid addr.ss_family clearing whole address";
			std::cerr << std::endl;
			sockaddr_storage_clear(addr);
			break;
	}
	return false;
}

bool sockaddr_storage_copyip(struct sockaddr_storage &dst, const struct sockaddr_storage &src)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_copyip()" << std::endl;
#endif

	switch(src.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_copyip(dst, src);
			break;
		case AF_INET6:
			return sockaddr_storage_ipv6_copyip(dst, src);
			break;
		default:
#ifdef SS_DEBUG
			std::cerr << "sockaddr_storage_copyip() Unknown ss_family: " << src.ss_family << std::endl;
#endif
			break;
	}
	return false;
}


uint16_t sockaddr_storage_port(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_port()" << std::endl;
#endif
	switch(addr.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_port(addr);
		case AF_INET6:
			return sockaddr_storage_ipv6_port(addr);
		default:
			std::cerr << "sockaddr_storage_port() invalid addr.ss_family" << std::endl;
#ifdef SS_DEBUG
			sockaddr_storage_dump(addr);
			print_stacktrace();
#endif
			break;
	}
	return 0;
}

bool sockaddr_storage_setport(struct sockaddr_storage &addr, uint16_t port)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_setport()" << std::endl;
#endif

	switch(addr.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_setport(addr, port);
			break;
		case AF_INET6:
			return sockaddr_storage_ipv6_setport(addr, port);
			break;
		default:
			std::cerr << "sockaddr_storage_setport() invalid addr.ss_family" << std::endl;
			break;
	}
	return false;
}


bool sockaddr_storage_setipv4(struct sockaddr_storage &addr, const sockaddr_in *addr_ipv4)
{
#ifdef SS_DEBUG
	RS_ERR();
#endif

	sockaddr_storage_clear(addr);
	struct sockaddr_in *ipv4_ptr = to_ipv4_ptr(addr);

	ipv4_ptr->sin_family = AF_INET;
	ipv4_ptr->sin_addr = addr_ipv4->sin_addr;
	ipv4_ptr->sin_port = addr_ipv4->sin_port;

	return true;
}

bool sockaddr_storage_setipv6(struct sockaddr_storage &addr, const sockaddr_in6 *addr_ipv6)
{
	sockaddr_storage_clear(addr);
	struct sockaddr_in6 *ipv6_ptr = to_ipv6_ptr(addr);

	ipv6_ptr->sin6_family = AF_INET6;
	ipv6_ptr->sin6_addr = addr_ipv6->sin6_addr;
	ipv6_ptr->sin6_port = addr_ipv6->sin6_port;

	return true;
}

#ifdef WINDOWS_SYS
#ifndef InetPtonA
int inet_pton(int af, const char *src, void *dst)
{
	sockaddr_storage ss;
	int size = sizeof(ss);
	char src_copy[INET6_ADDRSTRLEN+1];

	ZeroMemory(&ss, sizeof(ss));
	/* stupid non-const API */
	strncpy (src_copy, src, INET6_ADDRSTRLEN+1);
	src_copy[INET6_ADDRSTRLEN] = 0;

	if (WSAStringToAddressA(src_copy, af, NULL, (sockaddr *)&ss, &size) == 0)
	{
		switch(af)
		{
		case AF_INET:
			*(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr;
			return 1;
		case AF_INET6:
			*(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr;
			return 1;
		}
	}
	return 0;
}
#endif
#endif

bool sockaddr_storage_inet_pton( sockaddr_storage& addr,
                                 const std::string& ipStr )
{
#ifdef SS_DEBUG
	std::cerr << __PRETTY_FUNCTION__ << std::endl;
#endif
	sockaddr_storage_clear(addr);

	struct sockaddr_in6 * addrv6p = (struct sockaddr_in6 *) &addr;
	struct sockaddr_in * addrv4p = (struct sockaddr_in *) &addr;

	if ( 1 == inet_pton(AF_INET6, ipStr.c_str(), &(addrv6p->sin6_addr)) )
	{
		addr.ss_family = AF_INET6;
		return true;
	}

	if ( 1 == inet_pton(AF_INET, ipStr.c_str(), &(addrv4p->sin_addr)) )
	{
		addr.ss_family = AF_INET;
		return sockaddr_storage_ipv4_to_ipv6(addr);
	}

	/* An IPv6 address may contain scope/interface indication after a % sign
	 * Usually needed for link local address, after % either the interface
	 * literal name is indicated or a numeric id, in case of literal name
	 * conversion to id is needed.
	 * Examples:
	 * fe80::0123:4567:89ab:cdef%eth0
	 * fe80::0123:4567:89ab:cdef%3 */
	auto pos = ipStr.find("%");
	if ( (pos != std::string::npos) && // % Not found
	     (pos+1 < ipStr.length()) && // We need to have something after %
	     pos > 3  ) // Exclude more pathological cases
	{
		if(!sockaddr_storage_inet_pton(addr, ipStr.substr(0, pos)))
			return false;

		/* Use std::atoi instead of std::stoul to avoid exceptions, in our
		 * case 0 is an error anyway */
		auto& scopeId = addrv6p->sin6_scope_id;
		scopeId = std::atoi(&ipStr[pos+1]);

#ifndef WINDOWS_SYS
		/* if_nametoindex should be available on Windows including netioapi.h
		 * but I have no way to test ATM */
		// Attempt conversion from literal interface name
		if(!scopeId) scopeId = if_nametoindex(ipStr.substr(pos+1).c_str());
#endif // ndef WINDOWS_SYS

		if(!scopeId)
		{
			RS_ERR( rs_errno_to_condition(errno),
			        " Invalid scope id or interface in address string: ",
			        ipStr );
			return false;
		}

		return true;
	}


	return false;
}


bool sockaddr_storage_ipv4_aton(struct sockaddr_storage &addr, const char *name)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_aton()";
	std::cerr << std::endl;
#endif

	struct sockaddr_in *ipv4_ptr = to_ipv4_ptr(addr);
	ipv4_ptr->sin_family = AF_INET;
	return (1 == inet_aton(name, &(ipv4_ptr->sin_addr)));
}

bool sockaddr_storage_ipv4_to_ipv6(sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << __PRETTY_FUNCTION__ << std::endl;
#endif

	if ( addr.ss_family == AF_INET6 ) return true;

	if ( addr.ss_family == AF_INET )
	{
		sockaddr_in & addr_ipv4 = (sockaddr_in &) addr;
		sockaddr_in6 & addr_ipv6 = (sockaddr_in6 &) addr;

		uint32_t ip = addr_ipv4.sin_addr.s_addr;
		uint16_t port = addr_ipv4.sin_port;

		sockaddr_storage_clear(addr);
		addr_ipv6.sin6_family = AF_INET6;
		addr_ipv6.sin6_port = port;
		addr_ipv6.sin6_addr.s6_addr16[5] = htons(0xffff);
		memmove( reinterpret_cast<void*>(&(addr_ipv6.sin6_addr.s6_addr16[6])),
		         reinterpret_cast<void*>(&ip), 4 );
		return true;
	}

	return false;
}

bool sockaddr_storage_ipv6_to_ipv4(sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << __PRETTY_FUNCTION__ << std::endl;
#endif

	if ( addr.ss_family == AF_INET ) return true;

	if ( addr.ss_family == AF_INET6 )
	{
		sockaddr_in6 & addr_ipv6 =  (sockaddr_in6 &) addr;
		bool ipv4m = addr_ipv6.sin6_addr.s6_addr16[5] == htons(0xffff);
		for ( int i = 0; ipv4m && i < 5 ; ++i )
			ipv4m &= addr_ipv6.sin6_addr.s6_addr16[i] == htons(0x0000);

		if(ipv4m)
		{
			uint32_t ip;
			memmove( reinterpret_cast<void*>(&ip),
			         reinterpret_cast<void*>(&(addr_ipv6.sin6_addr.s6_addr16[6])),
			         4 );
			uint16_t port = addr_ipv6.sin6_port;

			sockaddr_in & addr_ipv4 = (sockaddr_in &) addr;

			sockaddr_storage_clear(addr);
			addr_ipv4.sin_family = AF_INET;
			addr_ipv4.sin_port = port;
			addr_ipv4.sin_addr.s_addr = ip;

			return true;
		}
	}

	return false;
}


/******************************** Comparisions **********************************/

bool operator<(const struct sockaddr_storage &a, const struct sockaddr_storage &b)
{
	if (!sockaddr_storage_samefamily(a, b))
	{
		return (a.ss_family < b.ss_family);
	}

	switch(a.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_lessthan(a, b);
			break;
		case AF_INET6:
			return sockaddr_storage_ipv6_lessthan(a, b);
			break;
		default:
#ifdef SS_DEBUG
            std::cerr << "sockaddr_storage_operator<() INVALID Family - error";
            std::cerr << std::endl;
#endif
			break;
	}
	return false;
}


bool sockaddr_storage_same(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_same()";
	std::cerr << std::endl;
#endif

	if (!sockaddr_storage_samefamily(addr, addr2))
		return false;

	switch(addr.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_same(addr, addr2);
			break;
		case AF_INET6:
			return sockaddr_storage_ipv6_same(addr, addr2);
			break;
		default:
#ifdef SS_DEBUG
            std::cerr << "sockaddr_storage_same() INVALID Family - error";
            std::cerr << std::endl;
#endif
			break;
	}
	return false;
}


bool sockaddr_storage_samefamily(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_samefamily()";
	std::cerr << std::endl;
#endif

	return (addr.ss_family == addr2.ss_family);
}

bool sockaddr_storage_sameip(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_sameip()";
	std::cerr << std::endl;
#endif

	if (!sockaddr_storage_samefamily(addr, addr2))
		return false;

	switch(addr.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_sameip(addr, addr2);
			break;
		case AF_INET6:
			return sockaddr_storage_ipv6_sameip(addr, addr2);
			break;
		default:
#ifdef SS_DEBUG
            std::cerr << "sockaddr_storage_sameip() INVALID Family - error";
            std::cerr << std::endl;
#endif
			break;
	}
	return false;
}

/********************************* Output     ***********************************/

std::string sockaddr_storage_tostring(const struct sockaddr_storage &addr)
{
	RsUrl url;

	switch(addr.ss_family)
	{
	case AF_INET:
		url.setScheme("ipv4");
		break;
	case AF_INET6:
		url.setScheme("ipv6");
		break;
	default:
		return AF_INVALID_STR;
	}

	url.setHost(sockaddr_storage_iptostring(addr))
	   .setPort(sockaddr_storage_port(addr));

	return url.toString();
}

bool sockaddr_storage_fromString(const std::string& str, sockaddr_storage &addr)
{
	RsUrl url(str);
	bool valid = sockaddr_storage_inet_pton(addr, url.host());
	if(url.hasPort()) sockaddr_storage_setport(addr, url.port());
	return valid;
}

void sockaddr_storage_dump(const sockaddr_storage & addr, std::string * outputString)
{
	// This function must not rely on others sockaddr_storage_*

	std::stringstream output;
	output << "sockaddr_storage_dump(addr) ";

	switch (addr.ss_family)
	{
	case AF_INET:
	{
		const sockaddr_in * in = (const sockaddr_in *) & addr;
		output << "addr.ss_family = AF_INET";
		output << " in->sin_addr = ";
		output << inet_ntoa(in->sin_addr);
		output << " in->sin_port = ";
		output << in->sin_port;
		break;
	}
	case AF_INET6:
	{
		const sockaddr_in6 * in6 = (const sockaddr_in6 *) & addr;
		std::string addrStr = "INVALID_IPV6";
		sockaddr_storage_inet_ntop(addr, addrStr);
		output << "addr.ss_family = AF_INET6";
		output << " in6->sin6_addr = ";
		output << addrStr;
		output << " in6->sin6_scope_id = ";
		output << in6->sin6_scope_id;
		output << " in6->sin6_port = ";
		output << in6->sin6_port;
		break;
	}
	default:
	{
		output << "unknown addr.ss_family ";
		const uint8_t * buf = reinterpret_cast<const uint8_t *>(&addr);
		for( uint32_t i = 0; i < sizeof(addr); ++i )
			output << std::setw(2) << std::setfill('0') << std::hex << +buf[i];
		/* The unary +buf[i] operation forces a no-op type conversion to an int
		 * with the correct sign */
	}
	}

	if(outputString)
	{
		outputString->append(output.str() + "\n");
#ifdef SS_DEBUG
		std::cerr << output.str() << std::endl;
#endif
	}
	else
		std::cerr << output.str() << std::endl;
}

std::string sockaddr_storage_familytostring(const struct sockaddr_storage &addr)
{
	std::string output;
	switch(addr.ss_family)
	{
	case AF_INET:
		output = "IPv4";
		break;
	case AF_INET6:
		output = "IPv6";
		break;
	default:
		output = AF_INVALID_STR;
#ifdef SS_DEBUG
		std::cerr << __PRETTY_FUNCTION__ << " Got invalid address!"
		          << std::endl;
		sockaddr_storage_dump(addr);
		print_stacktrace();
#endif
		break;
	}
	return output;
}

std::string sockaddr_storage_iptostring(const struct sockaddr_storage &addr)
{
	std::string output;
	switch(addr.ss_family)
	{
	case AF_INET:
		output = sockaddr_storage_ipv4_iptostring(addr);
		break;
	case AF_INET6:
		output = sockaddr_storage_ipv6_iptostring(addr);
		break;
	default:
		output = "INVALID_IP";
#ifdef SS_DEBUG
		std::cerr << __PRETTY_FUNCTION__ << " Got invalid IP!" << std::endl;
		sockaddr_storage_dump(addr);
		print_stacktrace();
#endif
		break;
	}
	return output;
}

std::string sockaddr_storage_porttostring(const struct sockaddr_storage &addr)
{
	std::string output;
	uint16_t port = sockaddr_storage_port(addr);
	rs_sprintf(output, "%u", port);
	return output;
}


/********************************* Net Checks ***********************************/
bool sockaddr_storage_isnull(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_isnull()";
	std::cerr << std::endl;
#endif

	if (addr.ss_family == 0)
		return true;

	switch(addr.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_isnull(addr);
			break;
		case AF_INET6:
			return sockaddr_storage_ipv6_isnull(addr);
			break;
		default:
			return true;
			break;
	}
	return true;
}

bool sockaddr_storage_isValidNet(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_isValidNet()" << std::endl;
#endif

	switch(addr.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_isValidNet(addr);
		case AF_INET6:
			return sockaddr_storage_ipv6_isValidNet(addr);
		default:
#ifdef SS_DEBUG
			std::cerr << "sockaddr_storage_isValidNet() INVALID Family" << std::endl;
			sockaddr_storage_dump(addr);
#endif
			break;
	}
	return false;
}

bool sockaddr_storage_isLoopbackNet(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_isLoopbackNet()";
	std::cerr << std::endl;
#endif

	switch(addr.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_isLoopbackNet(addr);
			break;
		case AF_INET6:
			return sockaddr_storage_ipv6_isLoopbackNet(addr);
			break;
		default:
#ifdef SS_DEBUG
            std::cerr << "sockaddr_storage_isLoopbackNet() INVALID Family - error: " << sockaddr_storage_iptostring(addr);
            std::cerr << std::endl;
#endif
			break;
	}
	return false;
}


bool sockaddr_storage_isPrivateNet(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_isPrivateNet()";
	std::cerr << std::endl;
#endif

	switch(addr.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_isPrivateNet(addr);
			break;
		case AF_INET6:
			return sockaddr_storage_ipv6_isPrivateNet(addr);
			break;
		default:
#ifdef SS_DEBUG
            std::cerr << "sockaddr_storage_isPrivateNet() INVALID Family - error: " << sockaddr_storage_iptostring(addr);
            std::cerr << std::endl;
#endif
			break;
	}
	return false;
}

bool sockaddr_storage_isLinkLocalNet(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << __PRETTY_FUNCTION__ << std::endl;
#endif

	switch(addr.ss_family)
	{
	case AF_INET:
		return isLinkLocalNet(&(to_const_ipv4_ptr(addr)->sin_addr));
	case AF_INET6:
		return sockaddr_storage_ipv6_isLinkLocalNet(addr);
	default:
#ifdef SS_DEBUG
		std::cerr << __PRETTY_FUNCTION__ <<" INVALID Family:" << std::endl;
		sockaddr_storage_dump(addr);
#endif
		break;
	}

	return false;
}

bool sockaddr_storage_ipv6_isLinkLocalNet(const sockaddr_storage &addr)
{
	if(addr.ss_family != AF_INET6) return false;

	const sockaddr_in6 * addr6 = (const sockaddr_in6 *) &addr;
	uint16_t mask = htons(0xffc0);
	uint16_t llPrefix = htons(0xfe80);
	return ((addr6->sin6_addr.s6_addr16[0] & mask ) == llPrefix);
}


bool sockaddr_storage_isExternalNet(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_isExternalNet()";
	std::cerr << std::endl;
#endif

	switch(addr.ss_family)
	{
		case AF_INET:
			return sockaddr_storage_ipv4_isExternalNet(addr);
			break;
		case AF_INET6:
			return sockaddr_storage_ipv6_isExternalNet(addr);
			break;
		default:
#ifdef SS_DEBUG
            std::cerr << "sockaddr_storage_isExternalNet() INVALID Family - error";
            std::cerr << std::endl;
#endif
			break;
	}
	return false;
}





/***************************** Internal Helper Fns ******************************/


/******************************** Casting  **************************************/

struct sockaddr_in *to_ipv4_ptr(struct sockaddr_storage &addr)
{
	struct sockaddr_in *ipv4_ptr = (struct sockaddr_in *) &addr;
	return ipv4_ptr;
}

struct sockaddr_in6 *to_ipv6_ptr(struct sockaddr_storage &addr)
{
	struct sockaddr_in6 *ipv6_ptr = (struct sockaddr_in6 *) &addr;
	return ipv6_ptr;
}

const struct sockaddr_in *to_const_ipv4_ptr(const struct sockaddr_storage &addr)
{
	const struct sockaddr_in *ipv4_ptr = (const struct sockaddr_in *) &addr;
	return ipv4_ptr;
}

const struct sockaddr_in6 *to_const_ipv6_ptr(const struct sockaddr_storage &addr)
{
	const struct sockaddr_in6 *ipv6_ptr = (const struct sockaddr_in6 *) &addr;
	return ipv6_ptr;
}


/******************************** Set / Clear ***********************************/

bool sockaddr_storage_copy(const sockaddr_storage& src, sockaddr_storage& dst)
{
	if(&src == &dst) return true;

	switch(src.ss_family)
	{
	case AF_INET:
	{
		sockaddr_storage_clear(dst);
		const sockaddr_in& ins(reinterpret_cast<const sockaddr_in&>(src));
		sockaddr_in& ind(reinterpret_cast<sockaddr_in&>(dst));

		ind.sin_family = AF_INET;
		ind.sin_addr.s_addr = ins.sin_addr.s_addr;
		ind.sin_port = ins.sin_port;

		return true;
	}
	case AF_INET6:
	{
		sockaddr_storage_clear(dst);
		const sockaddr_in6& ins6(reinterpret_cast<const sockaddr_in6&>(src));
		sockaddr_in6& ind6(reinterpret_cast<sockaddr_in6&>(dst));

		ind6.sin6_family = AF_INET6;
		for(int i=0; i<8; ++i)
			ind6.sin6_addr.s6_addr16[i] = ins6.sin6_addr.s6_addr16[i];
		ind6.sin6_flowinfo = ins6.sin6_flowinfo;
		ind6.sin6_port = ins6.sin6_port;
		ind6.sin6_scope_id = ins6.sin6_scope_id;

		return true;
	}
	default:
#ifdef SS_DEBUG
		std::cerr << __PRETTY_FUNCTION__ << " Attempt to copy unknown family! "
		          << src.ss_family << " defaulting to memmove!" << std::endl;
		sockaddr_storage_dump(src);
		print_stacktrace();
#endif // SS_DEBUG
		memmove(&dst, &src, sizeof(sockaddr_storage));
		return true;
	}
}

bool sockaddr_storage_ipv4_zeroip(struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_zeroip()";
	std::cerr << std::endl;
#endif

	struct sockaddr_in *ipv4_ptr = to_ipv4_ptr(addr);
	memset(&(ipv4_ptr->sin_addr), 0, sizeof(ipv4_ptr->sin_addr));
	return true;
}


bool sockaddr_storage_ipv4_copyip(struct sockaddr_storage &dst, const struct sockaddr_storage &src)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_copyip()";
	std::cerr << std::endl;
#endif

	struct sockaddr_in *dst_ptr = to_ipv4_ptr(dst);
	const struct sockaddr_in *src_ptr = to_const_ipv4_ptr(src);

	dst_ptr->sin_family = AF_INET;
	memcpy(&(dst_ptr->sin_addr), &(src_ptr->sin_addr), sizeof(src_ptr->sin_addr));
	return true;
}

uint16_t sockaddr_storage_ipv4_port(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_port()";
	std::cerr << std::endl;
#endif

	const struct sockaddr_in *ipv4_ptr = to_const_ipv4_ptr(addr);
	uint16_t port = ntohs(ipv4_ptr->sin_port);
	return port;
}

bool sockaddr_storage_ipv4_setport(struct sockaddr_storage &addr, uint16_t port)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_setport()";
	std::cerr << std::endl;
#endif

	struct sockaddr_in *ipv4_ptr = to_ipv4_ptr(addr);
	ipv4_ptr->sin_port = htons(port);
	return true;
}

bool sockaddr_storage_ipv6_zeroip(struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
    std::cerr << "sockaddr_storage_ipv6_zeroip()";
    std::cerr << std::endl;
#endif

	struct sockaddr_in6 *ipv6_ptr = to_ipv6_ptr(addr);
	memset(&(ipv6_ptr->sin6_addr), 0, sizeof(ipv6_ptr->sin6_addr));
	return true;
}

bool sockaddr_storage_ipv6_copyip(struct sockaddr_storage &dst, const struct sockaddr_storage &src)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv6_copyip()" << std::endl;
#endif

	struct sockaddr_in6 *dst_ptr = to_ipv6_ptr(dst);
	const struct sockaddr_in6 *src_ptr = to_const_ipv6_ptr(src);

	dst_ptr->sin6_family = AF_INET6;
	memcpy(&(dst_ptr->sin6_addr), &(src_ptr->sin6_addr), sizeof(src_ptr->sin6_addr));
	return true;
}

uint16_t sockaddr_storage_ipv6_port(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv6_port()";
    std::cerr << std::endl;
#endif

	const struct sockaddr_in6 *ipv6_ptr = to_const_ipv6_ptr(addr);
	uint16_t port = ntohs(ipv6_ptr->sin6_port);
	return port;
}

bool sockaddr_storage_ipv6_setport(struct sockaddr_storage &addr, uint16_t port)
{
#ifdef SS_DEBUG
    std::cerr << "sockaddr_storage_ipv6_setport()";
    std::cerr << std::endl;
#endif

	struct sockaddr_in6 *ipv6_ptr = to_ipv6_ptr(addr);
	ipv6_ptr->sin6_port = htons(port);
	return true;
}


/******************************** Comparisions **********************************/

bool sockaddr_storage_ipv4_lessthan(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_lessthan()";
	std::cerr << std::endl;
#endif

	const struct sockaddr_in *ptr1 = to_const_ipv4_ptr(addr);
	const struct sockaddr_in *ptr2 = to_const_ipv4_ptr(addr2);

	if (ptr1->sin_addr.s_addr == ptr2->sin_addr.s_addr) 
	{
		return	ptr1->sin_port < ptr2->sin_port;
	}
	return (ptr1->sin_addr.s_addr < ptr2->sin_addr.s_addr);
}

bool sockaddr_storage_ipv4_same(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_same()";
	std::cerr << std::endl;
#endif

	const struct sockaddr_in *ptr1 = to_const_ipv4_ptr(addr);
	const struct sockaddr_in *ptr2 = to_const_ipv4_ptr(addr2);

	return (ptr1->sin_addr.s_addr == ptr2->sin_addr.s_addr) &&
		(ptr1->sin_port == ptr2->sin_port);
}

bool sockaddr_storage_ipv4_sameip(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_sameip()";
	std::cerr << std::endl;
#endif

	const struct sockaddr_in *ptr1 = to_const_ipv4_ptr(addr);
	const struct sockaddr_in *ptr2 = to_const_ipv4_ptr(addr2);

	return (ptr1->sin_addr.s_addr == ptr2->sin_addr.s_addr);
}

// IPV6
bool sockaddr_storage_ipv6_lessthan(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2)
{
#ifdef SS_DEBUG
    std::cerr << "sockaddr_storage_ipv6_lessthan()";
    std::cerr << std::endl;
#endif

	const struct sockaddr_in6 *ptr1 = to_const_ipv6_ptr(addr);
	const struct sockaddr_in6 *ptr2 = to_const_ipv6_ptr(addr2);

        uint32_t *ip6addr1 = (uint32_t *) ptr1->sin6_addr.s6_addr;
        uint32_t *ip6addr2 = (uint32_t *) ptr2->sin6_addr.s6_addr;
        for(int i = 0; i < 4; i++)
        {
		if (ip6addr1[i] == ip6addr2[i])
		{
			continue;
		}

		return (ip6addr1[i] < ip6addr2[i]);
        }

	return (ptr1->sin6_port < ptr2->sin6_port);
}

bool sockaddr_storage_ipv6_same(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2)
{
#ifdef SS_DEBUG
    std::cerr << "sockaddr_storage_ipv6_same()";
    std::cerr << std::endl;
#endif
	const struct sockaddr_in6 *ptr1 = to_const_ipv6_ptr(addr);
	const struct sockaddr_in6 *ptr2 = to_const_ipv6_ptr(addr2);

	return sockaddr_storage_ipv6_sameip(addr, addr2) && (ptr1->sin6_port == ptr2->sin6_port);
}

bool sockaddr_storage_ipv6_sameip(const struct sockaddr_storage &addr, const struct sockaddr_storage &addr2)
{
#ifdef SS_DEBUG
    std::cerr << "sockaddr_storage_ipv6_sameip()";
    std::cerr << std::endl;
#endif

	const struct sockaddr_in6 *ptr1 = to_const_ipv6_ptr(addr);
	const struct sockaddr_in6 *ptr2 = to_const_ipv6_ptr(addr2);

        uint32_t *ip6addr1 = (uint32_t *) ptr1->sin6_addr.s6_addr;
        uint32_t *ip6addr2 = (uint32_t *) ptr2->sin6_addr.s6_addr;

        for(int i = 0; i < 4; i++)
        {
		if (ip6addr1[i] != ip6addr2[i])
		{
			return false;
		}
	}
	return true;
}

/********************************* Output     ***********************************/
std::string sockaddr_storage_ipv4_iptostring(const struct sockaddr_storage &addr)
{
	const struct sockaddr_in *ptr = to_const_ipv4_ptr(addr);
	std::string output;
	output = rs_inet_ntoa(ptr->sin_addr);
	return output;
}

std::string sockaddr_storage_ipv6_iptostring(const struct sockaddr_storage & addr)
{
	std::string addrStr;
	sockaddr_storage_inet_ntop(addr, addrStr);
	return addrStr;
}


/********************************* Net Checks ***********************************/
bool sockaddr_storage_ipv4_isnull(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_isnull()";
	std::cerr << std::endl;
#endif

	const struct sockaddr_in *ptr1 = to_const_ipv4_ptr(addr);
	if (ptr1->sin_family != AF_INET)
	{
		return true;
	}
	if ((ptr1->sin_addr.s_addr == 0) || (ptr1->sin_addr.s_addr == 1))
	{
		return true;
	}
	return false;
}

bool sockaddr_storage_ipv4_isValidNet(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_isValidNet()";
	std::cerr << std::endl;
#endif

	const struct sockaddr_in *ptr1 = to_const_ipv4_ptr(addr);
	if (ptr1->sin_family != AF_INET)
	{
		return false;
	}
	return isValidNet(&(ptr1->sin_addr));
}


bool sockaddr_storage_ipv4_isLoopbackNet(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_isLoopbackNet()";
	std::cerr << std::endl;
#endif


	const struct sockaddr_in *ptr1 = to_const_ipv4_ptr(addr);
	if (ptr1->sin_family != AF_INET)
	{
		return false;
	}
	return isLoopbackNet(&(ptr1->sin_addr));
}

bool sockaddr_storage_ipv4_isPrivateNet(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_isPrivateNet()";
	std::cerr << std::endl;
#endif


	const struct sockaddr_in *ptr1 = to_const_ipv4_ptr(addr);
	if (ptr1->sin_family != AF_INET)
	{
		return false;
	}
	return isPrivateNet(&(ptr1->sin_addr));
}

bool sockaddr_storage_ipv4_isExternalNet(const struct sockaddr_storage &addr)
{
#ifdef SS_DEBUG
	std::cerr << "sockaddr_storage_ipv4_isExternalNet()";
	std::cerr << std::endl;
#endif

	const struct sockaddr_in *ptr1 = to_const_ipv4_ptr(addr);
	if (ptr1->sin_family != AF_INET)
	{
		return false;
	}
	return isExternalNet(&(ptr1->sin_addr));
}


bool sockaddr_storage_ipv6_isnull(const struct sockaddr_storage& addr)
{
	const sockaddr_in6& addr6 = reinterpret_cast<const sockaddr_in6&>(addr);

	uint16_t nZero = htons(0); // anyway 0 should be the same in host and net
	bool isZero = (addr6.sin6_addr.s6_addr16[7] == nZero);
	for (int i=0; isZero && i<7; ++i)
		isZero &= (addr6.sin6_addr.s6_addr16[i] == nZero);

	return nZero;
}

bool sockaddr_storage_ipv6_isValidNet(const struct sockaddr_storage& addr)
{
	return !sockaddr_storage_ipv6_isnull(addr);
}

bool sockaddr_storage_ipv6_isLoopbackNet(const struct sockaddr_storage& addr)
{
	const sockaddr_in6& addr6 = reinterpret_cast<const sockaddr_in6&>(addr);
	bool isLoopBack = (addr6.sin6_addr.s6_addr16[7] == htons(0x0001));
	uint16_t nZero = htons(0); // anyway 0 should be the same in host and net
	for (int i=0; isLoopBack && i<7; ++i)
		isLoopBack &= (addr6.sin6_addr.s6_addr16[i] == nZero);

#ifdef SS_DEBUG
	std::cerr << __PRETTY_FUNCTION__ << " " << sockaddr_storage_tostring(addr)
	          << " " << isLoopBack << std::endl;
#endif

	return isLoopBack;
}

bool sockaddr_storage_ipv6_isPrivateNet(const struct sockaddr_storage &/*addr*/)
{
	/* It is unlikely that we end up connecting to an IPv6 address behind NAT
	 * W.R.T. RS it is probably better to consider all IPv6 as internal/local
	 * addresses as direct connection should be always possible. */

	return true;
}

bool sockaddr_storage_ipv6_isExternalNet(const struct sockaddr_storage &/*addr*/)
{
	/* It is unlikely that we end up connecting to an IPv6 address behind NAT
	 * W.R.T. RS it is probably better to consider all IPv6 as internal/local
	 * addresses as direct connection should be always possible. */

	return false;
}

bool sockaddr_storage_inet_ntop (const sockaddr_storage &addr, std::string &dst)
{
#ifdef WINDOWS_SYS
	// Use WSAAddressToString instead of InetNtop because the latter is missing
	// on XP and is present only on Vista and newers
	long unsigned int len = 255;
	wchar_t wIpStr[255];
	char ipStr[255];
	sockaddr_storage tmp;
	sockaddr_storage_clear(tmp);
	sockaddr_storage_copyip(tmp, addr);
	sockaddr * sptr = (sockaddr *) &tmp;
	bool success = (0 == WSAAddressToString(
	                    sptr, sizeof(sockaddr_storage), NULL, wIpStr, &len ));
	wcstombs(ipStr, wIpStr, len);
	dst = ipStr;
	return success;
#else // def WINDOWS_SYS
	switch(addr.ss_family)
	{
	case AF_INET:
	{
		const sockaddr_in * addrv4p = (const struct sockaddr_in *) &addr;
		char ipStr[INET_ADDRSTRLEN];
		if(! inet_ntop(
		            addr.ss_family, (const void *) &(addrv4p->sin_addr),
		            ipStr, INET_ADDRSTRLEN ))
			return false;
		dst = ipStr;
		return true;
	}
	case AF_INET6:
	{
		const sockaddr_in6 * addrv6p = (const struct sockaddr_in6 *) &addr;
		char ipStr[INET6_ADDRSTRLEN];
		if(! inet_ntop(
		            addr.ss_family, (const void *) &(addrv6p->sin6_addr),
		            ipStr, INET6_ADDRSTRLEN ))
			return false;

		dst = ipStr;

		if(addrv6p->sin6_scope_id)
		{
			/* An IPv6 address may contain scope/interface indication after a %
			 * sign. Usually needed for link local address, after % either the
			 * interface literal name (when possible) is indicated or a numeric
			 * id. Examples:
			 * fe80::0123:4567:89ab:cdef%eth0
			 * fe80::0123:4567:89ab:cdef%3 */

			// Attempt conversion to literal interface name
			char ifName[IF_NAMESIZE+1] = {0};
			if(if_indextoname(addrv6p->sin6_scope_id, ifName))
			{
				dst += IPV6_SCOPE_ID_SEPARATOR;
				dst += ifName;
			}
			else
			{
				dst += IPV6_SCOPE_ID_SEPARATOR;
				dst += std::to_string(addrv6p->sin6_scope_id);
			}
		}

		return true;
	}
	default:
		dst = AF_INVALID_STR;
		return false;
	}
#endif // def WINDOWS_SYS
}

int rs_setsockopt( int sockfd, int level, int optname,
                   const uint8_t *optval, uint32_t optlen )
{
#ifdef WINDOWS_SYS
	return setsockopt( sockfd, level, optname,
	                   reinterpret_cast<const char*>(optval), optlen );
#else
	return setsockopt( sockfd, level, optname,
	                   reinterpret_cast<const void*>(optval), optlen );
#endif // WINDOWS_SYS
}
