/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3.0 only,
 * as published by the Free Software Foundation.
 *
 * 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
 * version 3.0 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3.0 along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#ifndef _XOPEN_SOURCE_EXTENDED
# define _XOPEN_SOURCE_EXTENDED
#endif
#define _HPUX_ALT_XOPEN_SOCKET_API

#ifdef OF_NINTENDO_3DS
# include <malloc.h>  /* For memalign() */
#endif

#include <errno.h>

#import "OFArray.h"
#import "OFCharacterSet.h"
#import "OFLocale.h"
#ifdef OF_HAVE_THREADS
# import "OFMutex.h"
#endif
#import "OFOnce.h"
#import "OFSocket.h"
#import "OFSocket+Private.h"
#import "OFString.h"
#ifdef OF_HAVE_THREADS
# import "OFTLSKey.h"
#endif

#import "OFException.h"  /* For some E* -> WSAE* defines */
#import "OFInitializationFailedException.h"
#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFLockFailedException.h"
#import "OFOutOfRangeException.h"
#import "OFUnlockFailedException.h"

#ifdef HAVE_NET_IF_H
# include <net/if.h>
#endif

#ifdef OF_AMIGAOS
# define Class IntuitionClass
# include <proto/exec.h>
# undef Class
#endif

#ifdef OF_NINTENDO_3DS
# include <3ds/types.h>
# include <3ds/services/soc.h>
#endif

#ifdef OF_NINTENDO_SWITCH
# define id nx_id
# include <switch.h>
# undef id
#endif

#if defined(OF_HAVE_THREADS) && (!defined(OF_AMIGAOS) || defined(OF_MORPHOS))
static OFMutex *mutex;

static void
releaseMutex(void)
{
	[mutex release];
}
#endif
#if !defined(OF_AMIGAOS) || defined(OF_MORPHOS) || !defined(OF_HAVE_THREADS)
static bool initSuccessful = false;
#endif

#ifdef OF_AMIGAOS
# if defined(OF_HAVE_THREADS) && !defined(OF_MORPHOS)
OFTLSKey _OFSocketBaseKey;
#  ifdef OF_AMIGAOS4
OFTLSKey _OFSocketInterfaceKey;
#  endif
# else
struct Library *SocketBase;
#  ifdef OF_AMIGAOS4
struct SocketIFace *ISocket = NULL;
#  endif
# endif
#endif

#if defined(OF_HAVE_THREADS) && defined(OF_AMIGAOS) && !defined(OF_MORPHOS)
OF_CONSTRUCTOR()
{
	if (OFTLSKeyNew(&_OFSocketBaseKey) != 0)
		@throw [OFInitializationFailedException exception];

# ifdef OF_AMIGAOS4
	if (OFTLSKeyNew(&_OFSocketInterfaceKey) != 0)
		@throw [OFInitializationFailedException exception];
# endif
}
#endif

#if !defined(OF_AMIGAOS) || defined(OF_MORPHOS) || !defined(OF_HAVE_THREADS)
static void
init(void)
{
# if defined(OF_WINDOWS)
	WSADATA wsa;

	if (WSAStartup(MAKEWORD(2, 0), &wsa))
		return;
# elif defined(OF_AMIGAOS)
	if ((SocketBase = OpenLibrary("bsdsocket.library", 4)) == NULL)
		return;

#  ifdef OF_AMIGAOS4
	if ((ISocket = (struct SocketIFace *)
	    GetInterface(SocketBase, "main", 1, NULL)) == NULL) {
		CloseLibrary(SocketBase);
		return;
	}
#  endif
# elif defined(OF_WII)
	if (net_init() < 0)
		return;
# elif defined(OF_NINTENDO_3DS)
	void *ctx;

	if ((ctx = memalign(0x1000, 0x100000)) == NULL)
		return;

	if (socInit(ctx, 0x100000) != 0)
		return;

	atexit((void (*)(void))socExit);
# elif defined(OF_NINTENDO_SWITCH)
	if (R_FAILED(socketInitializeDefault()))
		return;

	atexit(socketExit);
# endif

# if defined(OF_HAVE_THREADS) && (!defined(OF_AMIGAOS) || defined(OF_MORPHOS))
	mutex = [[OFMutex alloc] init];
	atexit(releaseMutex);
# endif

	initSuccessful = true;
}

OF_DESTRUCTOR()
{
# ifdef OF_AMIGAOS
#  ifdef OF_AMIGAOS4
	if (ISocket != NULL)
		DropInterface((struct Interface *)ISocket);
#  endif

	if (SocketBase != NULL)
		CloseLibrary(SocketBase);
# endif
}
#endif

bool
_OFSocketInit(void)
{
#if !defined(OF_AMIGAOS) || defined(OF_MORPHOS) || !defined(OF_HAVE_THREADS)
	static OFOnceControl onceControl = OFOnceControlInitValue;
	OFOnce(&onceControl, init);

	return initSuccessful;
#else
	struct Library *socketBase;
# ifdef OF_AMIGAOS4
	struct SocketIFace *socketInterface;
# endif

# ifdef OF_AMIGAOS4
	if ((socketInterface = OFTLSKeyGet(_OFSocketInterfaceKey)) != NULL)
# else
	if ((socketBase = OFTLSKeyGet(_OFSocketBaseKey)) != NULL)
# endif
		return true;

	if ((socketBase = OpenLibrary("bsdsocket.library", 4)) == NULL)
		return false;

# ifdef OF_AMIGAOS4
	if ((socketInterface = (struct SocketIFace *)
	    GetInterface(socketBase, "main", 1, NULL)) == NULL) {
		CloseLibrary(socketBase);
		return false;
	}
# endif

	if (OFTLSKeySet(_OFSocketBaseKey, socketBase) != 0) {
		CloseLibrary(socketBase);
# ifdef OF_AMIGAOS4
		DropInterface((struct Interface *)socketInterface);
# endif
		return false;
	}

# ifdef OF_AMIGAOS4
	if (OFTLSKeySet(_OFSocketInterfaceKey, socketInterface) != 0) {
		CloseLibrary(socketBase);
		DropInterface((struct Interface *)socketInterface);
		return false;
	}
# endif

	return true;
#endif
}

#if defined(OF_HAVE_THREADS) && defined(OF_AMIGAOS) && !defined(OF_MORPHOS)
void
_OFSocketDeinit(void)
{
	struct Library *socketBase = OFTLSKeyGet(_OFSocketBaseKey);
# ifdef OF_AMIGAOS4
	struct SocketIFace *socketInterface =
	    OFTLSKeyGet(_OFSocketInterfaceKey);

	if (socketInterface != NULL)
		DropInterface((struct Interface *)socketInterface);
# endif
	if (socketBase != NULL)
		CloseLibrary(socketBase);
}
#endif

int
_OFSocketErrNo(void)
{
#if defined(OF_WINDOWS)
	switch (WSAGetLastError()) {
	case WSAEACCES:
		return EACCES;
	case WSAEADDRINUSE:
		return EADDRINUSE;
	case WSAEADDRNOTAVAIL:
		return EADDRNOTAVAIL;
	case WSAEAFNOSUPPORT:
		return EAFNOSUPPORT;
	case WSAEALREADY:
		return EALREADY;
	case WSAEBADF:
		return EBADF;
	case WSAECONNABORTED:
		return ECONNABORTED;
	case WSAECONNREFUSED:
		return ECONNREFUSED;
	case WSAECONNRESET:
		return ECONNRESET;
	case WSAEDESTADDRREQ:
		return EDESTADDRREQ;
	case WSAEDISCON:
		return EPIPE;
	case WSAEDQUOT:
		return EDQUOT;
	case WSAEFAULT:
		return EFAULT;
	case WSAEHOSTDOWN:
		return EHOSTDOWN;
	case WSAEHOSTUNREACH:
		return EHOSTUNREACH;
	case WSAEINPROGRESS:
		return EINPROGRESS;
	case WSAEINTR:
		return EINTR;
	case WSAEINVAL:
		return EINVAL;
	case WSAEISCONN:
		return EISCONN;
	case WSAELOOP:
		return ELOOP;
	case WSAEMSGSIZE:
		return EMSGSIZE;
	case WSAENAMETOOLONG:
		return ENAMETOOLONG;
	case WSAENETDOWN:
		return ENETDOWN;
	case WSAENETRESET:
		return ENETRESET;
	case WSAENETUNREACH:
		return ENETUNREACH;
	case WSAENOBUFS:
		return ENOBUFS;
	case WSAENOPROTOOPT:
		return ENOPROTOOPT;
	case WSAENOTCONN:
		return ENOTCONN;
	case WSAENOTEMPTY:
		return ENOTEMPTY;
	case WSAENOTSOCK:
		return ENOTSOCK;
	case WSAEOPNOTSUPP:
		return EOPNOTSUPP;
	case WSAEPFNOSUPPORT:
		return EPFNOSUPPORT;
	case WSAEPROCLIM:
		return EPROCLIM;
	case WSAEPROTONOSUPPORT:
		return EPROTONOSUPPORT;
	case WSAEPROTOTYPE:
		return EPROTOTYPE;
	case WSAEREMOTE:
		return EREMOTE;
	case WSAESHUTDOWN:
		return ESHUTDOWN;
	case WSAESOCKTNOSUPPORT:
		return ESOCKTNOSUPPORT;
	case WSAESTALE:
		return ESTALE;
	case WSAETIMEDOUT:
		return ETIMEDOUT;
	case WSAETOOMANYREFS:
		return ETOOMANYREFS;
	case WSAEUSERS:
		return EUSERS;
	case WSAEWOULDBLOCK:
		return EWOULDBLOCK;
	}

	return 0;
#elif defined(OF_AMIGAOS)
	return Errno();
#else
	return errno;
#endif
}

#ifndef OF_WII
int
_OFGetSockName(OFSocketHandle sock, struct sockaddr *restrict addr,
    socklen_t *restrict addrLen)
{
	int ret;

# if defined(OF_HAVE_THREADS) && (!defined(OF_AMIGAOS) || defined(OF_MORPHOS))
	[mutex lock];
# endif
	ret = getsockname(sock, addr, addrLen);
# if defined(OF_HAVE_THREADS) && (!defined(OF_AMIGAOS) || defined(OF_MORPHOS))
	[mutex unlock];
# endif

	return ret;
}
#endif

OFSocketAddress
OFSocketAddressParseIPv4(OFString *IPv4, uint16_t port)
{
	void *pool = objc_autoreleasePoolPush();
	OFCharacterSet *whitespaceCharacterSet =
	    [OFCharacterSet whitespaceCharacterSet];
	OFSocketAddress ret;
	struct sockaddr_in *addrIn = &ret.sockaddr.in;
	OFArray OF_GENERIC(OFString *) *components;
	uint32_t addr;

	memset(&ret, '\0', sizeof(ret));
	ret.family = OFSocketAddressFamilyIPv4;
#if defined(OF_WII) || defined(OF_NINTENDO_3DS)
	ret.length = 8;
#else
	ret.length = sizeof(ret.sockaddr.in);
#endif

	addrIn->sin_family = AF_INET;
	addrIn->sin_port = OFToBigEndian16(port);
#ifdef OF_WII
	addrIn->sin_len = ret.length;
#endif

	components = [IPv4 componentsSeparatedByString: @"."];

	if (components.count != 4)
		@throw [OFInvalidFormatException exception];

	addr = 0;

	for (OFString *component in components) {
		unsigned long long number;

		if (component.length == 0)
			@throw [OFInvalidFormatException exception];

		if ([component indexOfCharacterFromSet:
		    whitespaceCharacterSet] != OFNotFound)
			@throw [OFInvalidFormatException exception];

		number = component.unsignedLongLongValue;

		if (number > UINT8_MAX)
			@throw [OFInvalidFormatException exception];

		addr = (addr << 8) | ((uint32_t)number & 0xFF);
	}

	addrIn->sin_addr.s_addr = OFToBigEndian32(addr);

	objc_autoreleasePoolPop(pool);

	return ret;
}

static uint16_t
parseIPv6Component(OFString *component)
{
	unsigned long long number;

	if ([component indexOfCharacterFromSet:
	    [OFCharacterSet whitespaceCharacterSet]] != OFNotFound)
		@throw [OFInvalidFormatException exception];

	number = [component unsignedLongLongValueWithBase: 16];

	if (number > UINT16_MAX)
		@throw [OFInvalidFormatException exception];

	return (uint16_t)number;
}

static OFString *
transformEmbeddedIPv4(OFString *IPv6)
{
	size_t lastColon = [IPv6
	    rangeOfString: @":"
		  options: OFStringSearchBackwards].location;
	OFString *IPv4;
	OFSocketAddress address;
	const struct sockaddr_in *addrIn;
	uint32_t addr;

	if (lastColon == OFNotFound)
		@throw [OFInvalidFormatException exception];

	IPv4 = [IPv6 substringWithRange:
	    OFMakeRange(lastColon + 1, IPv6.length - lastColon - 1)];
	IPv6 = [IPv6 substringWithRange: OFMakeRange(0, lastColon + 1)];

	address = OFSocketAddressParseIPv4(IPv4, 0);
	addrIn = &address.sockaddr.in;
	addr = OFFromBigEndian32(addrIn->sin_addr.s_addr);

	return [IPv6 stringByAppendingString:
	    [OFString stringWithFormat: @"%x%02x:%x%02x",
	    (addr & 0xFF000000) >> 24, (addr & 0x00FF0000) >> 16,
	    (addr & 0x0000FF00) >>  8, addr & 0x000000FF]];
}

OFSocketAddress
OFSocketAddressParseIPv6(OFString *IPv6, uint16_t port)
{
	void *pool = objc_autoreleasePoolPush();
	OFSocketAddress ret;
	struct sockaddr_in6 *addrIn6 = &ret.sockaddr.in6;
	size_t doubleColon, percent;

	memset(&ret, '\0', sizeof(ret));
	ret.family = OFSocketAddressFamilyIPv6;
	ret.length = sizeof(ret.sockaddr.in6);

#ifdef AF_INET6
	addrIn6->sin6_family = AF_INET6;
#else
	addrIn6->sin6_family = AF_UNSPEC;
#endif
	addrIn6->sin6_port = OFToBigEndian16(port);

	if ((percent = [IPv6 rangeOfString: @"%"].location) != OFNotFound) {
		OFString *interface = [IPv6 substringFromIndex: percent + 1];
		IPv6 = [IPv6 substringToIndex: percent];

		@try {
			addrIn6->sin6_scope_id = (uint32_t)[interface
			    unsignedLongLongValueWithBase: 10];
		} @catch (OFInvalidFormatException *e) {
#if defined(HAVE_IF_NAMETOINDEX) && !defined(OF_WINDOWS)
			addrIn6->sin6_scope_id = if_nametoindex([interface
			    cStringWithEncoding: [OFLocale encoding]]);
#endif
		}

		if (addrIn6->sin6_scope_id == 0)
			@throw [OFInvalidArgumentException exception];
	}

	if ([IPv6 rangeOfString: @"."].location != OFNotFound)
		IPv6 = transformEmbeddedIPv4(IPv6);

	doubleColon = [IPv6 rangeOfString: @"::"].location;
	if (doubleColon != OFNotFound) {
		OFString *left = [IPv6 substringToIndex: doubleColon];
		OFString *right = [IPv6 substringFromIndex: doubleColon + 2];
		OFArray OF_GENERIC(OFString *) *leftComponents;
		OFArray OF_GENERIC(OFString *) *rightComponents;
		size_t i;

		if ([right hasPrefix: @":"] || [right containsString: @"::"])
			@throw [OFInvalidFormatException exception];

		leftComponents = [left componentsSeparatedByString: @":"];
		rightComponents = [right componentsSeparatedByString: @":"];

		if (leftComponents.count + rightComponents.count > 7)
			@throw [OFInvalidFormatException exception];

		i = 0;
		for (OFString *component in leftComponents) {
			uint16_t number = parseIPv6Component(component);

			addrIn6->sin6_addr.s6_addr[i++] = number >> 8;
			addrIn6->sin6_addr.s6_addr[i++] = number;
		}

		i = 16;
		for (OFString *component in rightComponents.reversedArray) {
			uint16_t number = parseIPv6Component(component);

			addrIn6->sin6_addr.s6_addr[--i] = number;
			addrIn6->sin6_addr.s6_addr[--i] = number >> 8;
		}
	} else {
		OFArray OF_GENERIC(OFString *) *components =
		    [IPv6 componentsSeparatedByString: @":"];
		size_t i;

		if (components.count != 8)
			@throw [OFInvalidFormatException exception];

		i = 0;
		for (OFString *component in components) {
			uint16_t number;

			if (component.length == 0)
				@throw [OFInvalidFormatException exception];

			number = parseIPv6Component(component);

			addrIn6->sin6_addr.s6_addr[i++] = number >> 8;
			addrIn6->sin6_addr.s6_addr[i++] = number;
		}
	}

	objc_autoreleasePoolPop(pool);

	return ret;
}

OFSocketAddress
OFSocketAddressParseIP(OFString *IP, uint16_t port)
{
	OFSocketAddress ret;

	@try {
		ret = OFSocketAddressParseIPv6(IP, port);
	} @catch (OFInvalidFormatException *e) {
		ret = OFSocketAddressParseIPv4(IP, port);
	}

	return ret;
}

OFSocketAddress
OFSocketAddressMakeUNIX(OFString *path)
{
	void *pool = objc_autoreleasePoolPush();
	OFStringEncoding encoding = [OFLocale encoding];
	size_t length = [path cStringLengthWithEncoding: encoding];
	OFSocketAddress ret;

	if (length > sizeof(ret.sockaddr.un.sun_path))
		@throw [OFOutOfRangeException exception];

	memset(&ret, '\0', sizeof(ret));
	ret.family = OFSocketAddressFamilyUNIX;
	ret.length = (socklen_t)
	    (offsetof(struct sockaddr_un, sun_path) + length);

#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
	ret.sockaddr.un.sun_len = (uint8_t)length;
#endif
#ifdef AF_UNIX
	ret.sockaddr.un.sun_family = AF_UNIX;
#else
	ret.sockaddr.un.sun_family = AF_UNSPEC;
#endif
	memcpy(ret.sockaddr.un.sun_path,
	    [path cStringWithEncoding: encoding], length);

	objc_autoreleasePoolPop(pool);

	return ret;
}

OFSocketAddress
OFSocketAddressMakeIPX(uint32_t network, const unsigned char node[IPX_NODE_LEN],
    uint16_t port)
{
	OFSocketAddress ret;

	memset(&ret, '\0', sizeof(ret));
	ret.family = OFSocketAddressFamilyIPX;
	ret.length = sizeof(ret.sockaddr.ipx);

#ifdef AF_IPX
	ret.sockaddr.ipx.sipx_family = AF_IPX;
#else
	ret.sockaddr.ipx.sipx_family = AF_UNSPEC;
#endif
	network = OFToBigEndian32(network);
	memcpy(&ret.sockaddr.ipx.sipx_network, &network,
	    sizeof(ret.sockaddr.ipx.sipx_network));
	memcpy(ret.sockaddr.ipx.sipx_node, node, IPX_NODE_LEN);
	ret.sockaddr.ipx.sipx_port = OFToBigEndian16(port);

	return ret;
}

OFSocketAddress
OFSocketAddressMakeAppleTalk(uint16_t network, uint8_t node, uint8_t port)
{
	OFSocketAddress ret;

	memset(&ret, '\0', sizeof(ret));
	ret.family = OFSocketAddressFamilyAppleTalk;
	ret.length = sizeof(ret.sockaddr.at);

#ifdef AF_APPLETALK
	ret.sockaddr.at.sat_family = AF_APPLETALK;
#else
	ret.sockaddr.at.sat_family = AF_UNSPEC;
#endif
#ifdef OF_WINDOWS
	ret.sockaddr.at.sat_net = network;
#else
	ret.sockaddr.at.sat_net = OFToBigEndian16(network);
#endif
	ret.sockaddr.at.sat_node = node;
	ret.sockaddr.at.sat_port = port;

	return ret;
}

bool
OFSocketAddressEqual(const OFSocketAddress *address1,
    const OFSocketAddress *address2)
{
	const struct sockaddr_in *addrIn1, *addrIn2;
	const struct sockaddr_in6 *addrIn6_1, *addrIn6_2;
	const struct sockaddr_ipx *addrIPX1, *addrIPX2;
	const struct sockaddr_at *addrAT1, *addrAT2;
	void *pool;
	OFString *path1, *path2;
	bool ret;

	if (address1->family != address2->family)
		return false;

	switch (address1->family) {
	case OFSocketAddressFamilyIPv4:
#if defined(OF_WII) || defined(OF_NINTENDO_3DS)
		if (address1->length < 8 || address2->length < 8)
			@throw [OFInvalidArgumentException exception];
#else
		if (address1->length < (socklen_t)sizeof(struct sockaddr_in) ||
		    address2->length < (socklen_t)sizeof(struct sockaddr_in))
			@throw [OFInvalidArgumentException exception];
#endif

		addrIn1 = &address1->sockaddr.in;
		addrIn2 = &address2->sockaddr.in;

		if (addrIn1->sin_port != addrIn2->sin_port)
			return false;
		if (addrIn1->sin_addr.s_addr != addrIn2->sin_addr.s_addr)
			return false;

		return true;
	case OFSocketAddressFamilyIPv6:
		if (address1->length < (socklen_t)sizeof(struct sockaddr_in6) ||
		    address2->length < (socklen_t)sizeof(struct sockaddr_in6))
			@throw [OFInvalidArgumentException exception];

		addrIn6_1 = &address1->sockaddr.in6;
		addrIn6_2 = &address2->sockaddr.in6;

		if (addrIn6_1->sin6_port != addrIn6_2->sin6_port)
			return false;
		if (memcmp(addrIn6_1->sin6_addr.s6_addr,
		    addrIn6_2->sin6_addr.s6_addr,
		    sizeof(addrIn6_1->sin6_addr.s6_addr)) != 0)
			return false;

		return true;
	case OFSocketAddressFamilyUNIX:
		pool = objc_autoreleasePoolPush();

		path1 = OFSocketAddressUNIXPath(address1);
		path2 = OFSocketAddressUNIXPath(address2);

		if (path1 == nil || path2 == nil) {
			objc_autoreleasePoolPop(pool);

			return false;
		}

		ret = [path1 isEqual: path2];

		objc_autoreleasePoolPop(pool);

		return ret;
	case OFSocketAddressFamilyIPX:
		if (address1->length < (socklen_t)sizeof(struct sockaddr_ipx) ||
		    address2->length < (socklen_t)sizeof(struct sockaddr_ipx))
			@throw [OFInvalidArgumentException exception];

		addrIPX1 = &address1->sockaddr.ipx;
		addrIPX2 = &address2->sockaddr.ipx;

		if (addrIPX1->sipx_port != addrIPX2->sipx_port)
			return false;
		if (memcmp(&addrIPX1->sipx_network, &addrIPX2->sipx_network,
		    4) != 0)
			return false;
		if (memcmp(addrIPX1->sipx_node, addrIPX2->sipx_node,
		    IPX_NODE_LEN) != 0)
			return false;

		return true;
	case OFSocketAddressFamilyAppleTalk:
		if (address1->length < (socklen_t)sizeof(struct sockaddr_at) ||
		    address2->length < (socklen_t)sizeof(struct sockaddr_at))
			@throw [OFInvalidArgumentException exception];

		addrAT1 = &address1->sockaddr.at;
		addrAT2 = &address2->sockaddr.at;

		if (addrAT1->sat_net != addrAT2->sat_net)
			return false;
		if (addrAT1->sat_node != addrAT2->sat_node)
			return false;
		if (addrAT1->sat_port != addrAT2->sat_port)
			return false;

		return true;
	default:
		@throw [OFInvalidArgumentException exception];
	}
}

unsigned long
OFSocketAddressHash(const OFSocketAddress *address)
{
	unsigned long hash;

	OFHashInit(&hash);
	OFHashAddByte(&hash, address->family);

	switch (address->family) {
	case OFSocketAddressFamilyIPv4:
#if defined(OF_WII) || defined(OF_NINTENDO_3DS)
		if (address->length < 8)
			@throw [OFInvalidArgumentException exception];
#else
		if (address->length < (socklen_t)sizeof(struct sockaddr_in))
			@throw [OFInvalidArgumentException exception];
#endif

		OFHashAddByte(&hash, address->sockaddr.in.sin_port >> 8);
		OFHashAddByte(&hash, address->sockaddr.in.sin_port);
		OFHashAddByte(&hash,
		    address->sockaddr.in.sin_addr.s_addr >> 24);
		OFHashAddByte(&hash,
		    address->sockaddr.in.sin_addr.s_addr >> 16);
		OFHashAddByte(&hash, address->sockaddr.in.sin_addr.s_addr >> 8);
		OFHashAddByte(&hash, address->sockaddr.in.sin_addr.s_addr);

		break;
	case OFSocketAddressFamilyIPv6:
		if (address->length < (socklen_t)sizeof(struct sockaddr_in6))
			@throw [OFInvalidArgumentException exception];

		OFHashAddByte(&hash, address->sockaddr.in6.sin6_port >> 8);
		OFHashAddByte(&hash, address->sockaddr.in6.sin6_port);

		for (size_t i = 0;
		    i < sizeof(address->sockaddr.in6.sin6_addr.s6_addr); i++)
			OFHashAddByte(&hash,
			    address->sockaddr.in6.sin6_addr.s6_addr[i]);

		break;
	case OFSocketAddressFamilyUNIX:;
		void *pool = objc_autoreleasePoolPush();
		OFString *path = OFSocketAddressUNIXPath(address);

		hash = path.hash;

		objc_autoreleasePoolPop(pool);

		return hash;
	case OFSocketAddressFamilyIPX:;
		unsigned char network[
		    sizeof(address->sockaddr.ipx.sipx_network)];

		if (address->length < (socklen_t)sizeof(struct sockaddr_ipx))
			@throw [OFInvalidArgumentException exception];

		OFHashAddByte(&hash, address->sockaddr.ipx.sipx_port >> 8);
		OFHashAddByte(&hash, address->sockaddr.ipx.sipx_port);

		memcpy(network, &address->sockaddr.ipx.sipx_network,
		    sizeof(network));

		for (size_t i = 0; i < sizeof(network); i++)
			OFHashAddByte(&hash, network[i]);

		for (size_t i = 0; i < IPX_NODE_LEN; i++)
			OFHashAddByte(&hash,
			    address->sockaddr.ipx.sipx_node[i]);

		break;
	case OFSocketAddressFamilyAppleTalk:
		if (address->length < (socklen_t)sizeof(struct sockaddr_at))
			@throw [OFInvalidArgumentException exception];

		OFHashAddByte(&hash, address->sockaddr.at.sat_net >> 8);
		OFHashAddByte(&hash, address->sockaddr.at.sat_net);
		OFHashAddByte(&hash, address->sockaddr.at.sat_port);

		break;
	default:
		@throw [OFInvalidArgumentException exception];
	}

	OFHashFinalize(&hash);

	return hash;
}

static OFString *
IPv4String(const OFSocketAddress *address)
{
	const struct sockaddr_in *addrIn = &address->sockaddr.in;
	uint32_t addr = OFFromBigEndian32(addrIn->sin_addr.s_addr);
	OFString *string;

	string = [OFString stringWithFormat: @"%u.%u.%u.%u",
	    (addr & 0xFF000000) >> 24, (addr & 0x00FF0000) >> 16,
	    (addr & 0x0000FF00) >>  8, addr & 0x000000FF];

	return string;
}

static OFString *
IPv6String(const OFSocketAddress *address)
{
	OFMutableString *string = [OFMutableString string];
	const struct sockaddr_in6 *addrIn6 = &address->sockaddr.in6;
	int_fast8_t zerosStart = -1, maxZerosStart = -1;
	uint_fast8_t zerosCount = 0, maxZerosCount = 0;
	bool first = true;

	for (uint_fast8_t i = 0; i < 16; i += 2) {
		if (addrIn6->sin6_addr.s6_addr[i] == 0 &&
		    addrIn6->sin6_addr.s6_addr[i + 1] == 0) {
			if (zerosStart >= 0)
				zerosCount++;
			else {
				zerosStart = i;
				zerosCount = 1;
			}
		} else {
			if (zerosCount > maxZerosCount) {
				maxZerosStart = zerosStart;
				maxZerosCount = zerosCount;
			}

			zerosStart = -1;
		}
	}
	if (zerosCount > maxZerosCount) {
		maxZerosStart = zerosStart;
		maxZerosCount = zerosCount;
	}

	if (maxZerosCount >= 2) {
		for (int_fast8_t i = 0; i < maxZerosStart; i += 2) {
			[string appendFormat:
			    (first ? @"%x" : @":%x"),
			    (addrIn6->sin6_addr.s6_addr[(uint_fast8_t)i] << 8) |
			    addrIn6->sin6_addr.s6_addr[(uint_fast8_t)i + 1]];
			first = false;
		}

		[string appendString: @"::"];
		first = true;

		for (int_fast8_t i = maxZerosStart + (maxZerosCount * 2);
		    i < 16; i += 2) {
			[string appendFormat:
			    (first ? @"%x" : @":%x"),
			    (addrIn6->sin6_addr.s6_addr[(uint_fast8_t)i] << 8) |
			    addrIn6->sin6_addr.s6_addr[(uint_fast8_t)i + 1]];
			first = false;
		}
	} else {
		for (uint_fast8_t i = 0; i < 16; i += 2) {
			[string appendFormat:
			    (first ? @"%x" : @":%x"),
			    (addrIn6->sin6_addr.s6_addr[i] << 8) |
			    addrIn6->sin6_addr.s6_addr[i + 1]];
			first = false;
		}
	}

	if (addrIn6->sin6_scope_id != 0) {
#if defined(HAVE_IF_INDEXTONAME) && !defined(OF_WINDOWS)
		char interface[IF_NAMESIZE];

		if (if_indextoname(addrIn6->sin6_scope_id, interface) != NULL)
			[string appendFormat: @"%%%s", interface];
		else
# endif
			[string appendFormat: @"%%%u", addrIn6->sin6_scope_id];
	}

	[string makeImmutable];

	return string;
}

static OFString *
IPXString(const OFSocketAddress *address)
{
	const struct sockaddr_ipx *addrIPX = &address->sockaddr.ipx;
	uint32_t network;
	uint64_t node;

	memcpy(&network, &addrIPX->sipx_network, sizeof(addrIPX->sipx_network));
	node = ((uint64_t)addrIPX->sipx_node[0] << 40) |
	    ((uint64_t)addrIPX->sipx_node[1] << 32) |
	    ((uint64_t)addrIPX->sipx_node[2] << 24) |
	    ((uint64_t)addrIPX->sipx_node[3] << 16) |
	    ((uint64_t)addrIPX->sipx_node[4] << 8) |
	    (uint64_t)addrIPX->sipx_node[5];

	return [OFString stringWithFormat: @"%" PRIX32 ".%" PRIX64,
	    OFFromBigEndian32(network), node];
}

static OFString *
appleTalkString(const OFSocketAddress *address)
{
	const struct sockaddr_at *addrAT = &address->sockaddr.at;

	return [OFString stringWithFormat: @"%" PRIu8 ".%" PRIu8,
	    OFFromBigEndian16(addrAT->sat_net), addrAT->sat_node];
}

OFString *
OFSocketAddressString(const OFSocketAddress *address)
{
	switch (address->family) {
	case OFSocketAddressFamilyIPv4:
		return IPv4String(address);
	case OFSocketAddressFamilyIPv6:
		return IPv6String(address);
	case OFSocketAddressFamilyUNIX:
		return OFSocketAddressUNIXPath(address);
	case OFSocketAddressFamilyIPX:
		return IPXString(address);
	case OFSocketAddressFamilyAppleTalk:
		return appleTalkString(address);
	default:
		@throw [OFInvalidArgumentException exception];
	}
}

OFString *
OFSocketAddressDescription(const OFSocketAddress *address)
{
	switch (address->family) {
	case OFSocketAddressFamilyIPv4:
		return [OFString
		    stringWithFormat: @"%@:%" PRIu16,
				      IPv4String(address),
				      OFSocketAddressIPPort(address)];
	case OFSocketAddressFamilyIPv6:
		return [OFString
		    stringWithFormat: @"[%@]:%" PRIu16,
				      IPv6String(address),
				      OFSocketAddressIPPort(address)];
	case OFSocketAddressFamilyIPX:
		return [OFString
		    stringWithFormat: @"%@.%" PRIX16,
				      IPXString(address),
				      OFSocketAddressIPXPort(address)];
	case OFSocketAddressFamilyAppleTalk:
		return [OFString
		    stringWithFormat: @"%@." PRIu8,
				      appleTalkString(address),
				      OFSocketAddressAppleTalkPort(address)];
	default:
		return OFSocketAddressString(address);
	}
}

void
OFSocketAddressSetIPPort(OFSocketAddress *address, uint16_t port)
{
	switch (address->family) {
	case OFSocketAddressFamilyIPv4:
		address->sockaddr.in.sin_port = OFToBigEndian16(port);
		break;
	case OFSocketAddressFamilyIPv6:
		address->sockaddr.in6.sin6_port = OFToBigEndian16(port);
		break;
	default:
		@throw [OFInvalidArgumentException exception];
	}
}

uint16_t
OFSocketAddressIPPort(const OFSocketAddress *address)
{
	switch (address->family) {
	case OFSocketAddressFamilyIPv4:
		return OFFromBigEndian16(address->sockaddr.in.sin_port);
	case OFSocketAddressFamilyIPv6:
		return OFFromBigEndian16(address->sockaddr.in6.sin6_port);
	default:
		@throw [OFInvalidArgumentException exception];
	}
}

OFString *
OFSocketAddressUNIXPath(const OFSocketAddress *_Nonnull address)
{
	socklen_t length;

	if (address->family != OFSocketAddressFamilyUNIX)
		@throw [OFInvalidArgumentException exception];

	length = address->length - offsetof(struct sockaddr_un, sun_path);

	for (socklen_t i = 0; i < length; i++)
		if (address->sockaddr.un.sun_path[i] == 0)
			length = i;

	return [OFString stringWithCString: address->sockaddr.un.sun_path
				  encoding: [OFLocale encoding]
				    length: length];
}

void
OFSocketAddressSetIPXNetwork(OFSocketAddress *address, uint32_t network)
{
	if (address->family != OFSocketAddressFamilyIPX)
		@throw [OFInvalidArgumentException exception];

	network = OFToBigEndian32(network);
	memcpy(&address->sockaddr.ipx.sipx_network, &network,
	    sizeof(address->sockaddr.ipx.sipx_network));
}

uint32_t
OFSocketAddressIPXNetwork(const OFSocketAddress *address)
{
	uint32_t network;

	if (address->family != OFSocketAddressFamilyIPX)
		@throw [OFInvalidArgumentException exception];

	memcpy(&network, &address->sockaddr.ipx.sipx_network, sizeof(network));

	return OFFromBigEndian32(network);
}

void
OFSocketAddressSetIPXNode(OFSocketAddress *address,
    const unsigned char node[IPX_NODE_LEN])
{
	if (address->family != OFSocketAddressFamilyIPX)
		@throw [OFInvalidArgumentException exception];

	memcpy(address->sockaddr.ipx.sipx_node, node, IPX_NODE_LEN);
}

void
OFSocketAddressGetIPXNode(const OFSocketAddress *address,
    unsigned char node[IPX_NODE_LEN])
{
	if (address->family != OFSocketAddressFamilyIPX)
		@throw [OFInvalidArgumentException exception];

	memcpy(node, address->sockaddr.ipx.sipx_node, IPX_NODE_LEN);
}

void
OFSocketAddressSetIPXPort(OFSocketAddress *address, uint16_t port)
{
	if (address->family != OFSocketAddressFamilyIPX)
		@throw [OFInvalidArgumentException exception];

	address->sockaddr.ipx.sipx_port = OFToBigEndian16(port);
}

uint16_t
OFSocketAddressIPXPort(const OFSocketAddress *address)
{
	if (address->family != OFSocketAddressFamilyIPX)
		@throw [OFInvalidArgumentException exception];

	return OFFromBigEndian16(address->sockaddr.ipx.sipx_port);
}

void
OFSocketAddressSetAppleTalkNetwork(OFSocketAddress *address, uint16_t network)
{
	if (address->family != OFSocketAddressFamilyAppleTalk)
		@throw [OFInvalidArgumentException exception];

#ifdef OF_WINDOWS
	address->sockaddr.at.sat_net = network;
#else
	address->sockaddr.at.sat_net = OFToBigEndian16(network);
#endif
}

uint16_t
OFSocketAddressAppleTalkNetwork(const OFSocketAddress *address)
{
	if (address->family != OFSocketAddressFamilyAppleTalk)
		@throw [OFInvalidArgumentException exception];

#ifdef OF_WINDOWS
	return address->sockaddr.at.sat_net;
#else
	return OFFromBigEndian16(address->sockaddr.at.sat_net);
#endif
}

void
OFSocketAddressSetAppleTalkNode(OFSocketAddress *address, uint8_t node)
{
	if (address->family != OFSocketAddressFamilyAppleTalk)
		@throw [OFInvalidArgumentException exception];

	address->sockaddr.at.sat_node = node;
}

uint8_t
OFSocketAddressAppleTalkNode(const OFSocketAddress *address)
{
	if (address->family != OFSocketAddressFamilyAppleTalk)
		@throw [OFInvalidArgumentException exception];

	return address->sockaddr.at.sat_node;
}

void
OFSocketAddressSetAppleTalkPort(OFSocketAddress *address, uint8_t port)
{
	if (address->family != OFSocketAddressFamilyAppleTalk)
		@throw [OFInvalidArgumentException exception];

	address->sockaddr.at.sat_port = port;
}

uint8_t
OFSocketAddressAppleTalkPort(const OFSocketAddress *address)
{
	if (address->family != OFSocketAddressFamilyAppleTalk)
		@throw [OFInvalidArgumentException exception];

	return address->sockaddr.at.sat_port;
}
