/* net6 - Library providing IPv4/IPv6 network access
 * Copyright (C) 2005 Armin Burgmeier / 0x539 dev group
 *
 * This library 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 2 of the License, or (at your option) any later version.
 *
 * This library 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 library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <errno.h>
#include <netdb.h>
#endif

#include <gnutls/gnutls.h>

#include "error.hpp"
#include "common.hpp"

// Login error codes
const net6::login::error net6::login::ERROR_NAME_INVALID = 0x01;
const net6::login::error net6::login::ERROR_NAME_IN_USE  = 0x02;
const net6::login::error net6::login::ERROR_MAX          = 0xff;

std::string net6::login::errstring(error err)
{
	switch(err)
	{
	case net6::login::ERROR_NAME_INVALID:
		return _("Invalid name");
	case net6::login::ERROR_NAME_IN_USE:
		return _("Name is already in use");
	default:
		return _("An unknown login error occured");
	}
}

namespace
{
	/** Translates a message without being in the net6 namespace.
	 */
	const char* _(const char* msgid)
	{
		return net6::_(msgid);
	}

	/** Translates a system dependant error value to net6 error codes.
	 */
	net6::error::code system_to_net6(int error_code)
	{
		switch(error_code)
		{
#ifdef WIN32
		case WSAEINTR:
			return net6::error::INTERRUPTED;
		case WSAEACCES:
			return net6::error::ACCESS_DENIED;
		case WSAEFAULT:
			return net6::error::BAD_ADDRESS;
		case WSAEINVAL:
			return net6::error::INVALID_ARGUMENT;
		case WSAEMFILE:
			return net6::error::TOO_MANY_FILES;
		case WSAEWOULDBLOCK:
			return net6::error::WOULD_BLOCK;
		case WSAEALREADY:
			return net6::error::OPERATION_IN_PROGRESS;
		case WSAENOTSOCK:
			return net6::error::NOT_SOCKET;
		case WSAEDESTADDRREQ:
			return net6::error::DESTINATION_ADDRESS_REQUIRED;
		case WSAEMSGSIZE:
			return net6::error::MESSAGE_TOO_LONG;
		case WSAEPROTOTYPE:
			return net6::error::WRONG_PROTOCOL_TYPE;
		case WSAENOPROTOOPT:
			return net6::error::BAD_PROTOCOL_OPTION;
		case WSAEPROTONOSUPPORT: 
			return net6::error::PROTOCOL_NOT_SUPPORTED;
		case WSAESOCKTNOSUPPORT:
			return net6::error::SOCKET_NOT_SUPPORTED;
		case WSAEOPNOTSUPP:
			return net6::error::OPERATION_NOT_SUPPORTED;
		case WSAEPFNOSUPPORT:
			return net6::error::PROTOCOL_FAMILY_NOT_SUPPORTED;
		case WSAEAFNOSUPPORT:
			return net6::error::ADDRESS_FAMILY_NOT_SUPPORTED;
		case WSAEADDRINUSE:
			return net6::error::ADDRESS_IN_USE;
		case WSAEADDRNOTAVAIL:
			return net6::error::ADDRESS_UNAVAILABLE;
		case WSAENETDOWN:
			return net6::error::NETWORK_DOWN;
		case WSAENETUNREACH:
			return net6::error::NETWORK_UNREACHABLE;
		case WSAENETRESET:
			return net6::error::NETWORK_RESET;
		case WSAECONNABORTED:
			return net6::error::CONNECTION_ABORTED;
		case WSAECONNRESET:
			return net6::error::CONNECTION_RESET;
		case WSAENOBUFS:
			return net6::error::NO_BUFFER_SPACE;
		case WSAEISCONN:
			return net6::error::SOCKET_IS_CONNECTED;
		case WSAENOTCONN:
			return net6::error::SOCKET_NOT_CONNECTED;
		case WSAESHUTDOWN:
			return net6::error::SOCKET_SHUTDOWN;
		case WSAETIMEDOUT:
			return net6::error::CONNECTION_TIMEOUT;
		case WSAECONNREFUSED:
			return net6::error::CONNECTION_REFUSED;
		case WSAEHOSTDOWN:
			return net6::error::HOST_DOWN;
		case WSAEHOSTUNREACH:
			return net6::error::HOST_UNREACHABLE;
		case WSAEPROCLIM:
			return net6::error::TOO_MANY_PROCESSES;
		case WSASYSNOTREADY:
			return net6::error::SYSTEM_NOT_READY;
		case WSAVERNOTSUPPORTED:
			return net6::error::VERSION_NOT_SUPPORTED;
		case WSANOTINITIALISED:
			return net6::error::NOT_INITIALISED;
		case WSAEDISCON:
			return net6::error::DISCONNECTED;
		case WSATYPE_NOT_FOUND:
			return net6::error::TYPE_NOT_FOUND;
		case WSAHOST_NOT_FOUND:
			return net6::error::HOSTNAME_NOT_FOUND;
		case WSATRY_AGAIN:
			return net6::error::TEMPORARY_FAILURE;
		case WSANO_DATA:
			return net6::error::NO_DATA_RECORD;
		case WSA_INVALID_HANDLE:
			return net6::error::INVALID_HANDLE;
		case WSA_INVALID_PARAMETER:
			return net6::error::INVALID_PARAMETER;
#else
		case EACCES:
			return net6::error::ACCESS_DENIED;
		case EADDRINUSE:
			return net6::error::ADDRESS_IN_USE;
		case EADDRNOTAVAIL:
			return net6::error::ADDRESS_UNAVAILABLE;
		case EAFNOSUPPORT:
			return net6::error::ADDRESS_FAMILY_NOT_SUPPORTED;
		case EAGAIN:
			return net6::error::WOULD_BLOCK;
		case EALREADY:
			return net6::error::OPERATION_IN_PROGRESS;
		case EBADF:
			return net6::error::NOT_SOCKET;
		// TODO: What is bad message..?
		// deactivated 2005-04-15 by phil: not BSD-compatible
//		case EBADMSG:
//			return net6::error::MESSAGE_TOO_LONG;
		case ECONNABORTED:
			return net6::error::CONNECTION_ABORTED;
		case ECONNREFUSED:
			return net6::error::CONNECTION_REFUSED;
		case ECONNRESET:
			return net6::error::CONNECTION_RESET;
		case EDESTADDRREQ:
			return net6::error::DESTINATION_ADDRESS_REQUIRED;
		case EFAULT:
			return net6::error::BAD_ADDRESS;
		case EHOSTUNREACH:
			return net6::error::HOST_UNREACHABLE;
		case EINPROGRESS:
			return net6::error::OPERATION_IN_PROGRESS;
		case EINTR:
			return net6::error::INTERRUPTED;
		case EINVAL:
			return net6::error::INVALID_ARGUMENT;
		case EISCONN:
			return net6::error::SOCKET_IS_CONNECTED;
		case EMFILE:
			return net6::error::TOO_MANY_FILES;
		case EMSGSIZE:
			return net6::error::MESSAGE_TOO_LONG;
		case ENETDOWN:
			return net6::error::NETWORK_DOWN;
		case ENETRESET:
			return net6::error::NETWORK_RESET;
		case ENETUNREACH:
			return net6::error::NETWORK_UNREACHABLE;
		case ENFILE:
			return net6::error::TOO_MANY_FILES;
		case ENOBUFS:
			return net6::error::NO_BUFFER_SPACE;
		// deactivated 2005-04-15 by phil: not BSD-compatible
//		case ENODATA:
//			return net6::error::NO_DATA_RECORD;
		case ENODEV:
			return net6::error::NO_DEVICE;
		case ENOMEM:
			return net6::error::NO_MEMORY;
		case ENOPROTOOPT:
			return net6::error::BAD_PROTOCOL_OPTION;
		case ENOTCONN:
			return net6::error::SOCKET_NOT_CONNECTED;
		case ENOTSOCK:
			return net6::error::NOT_SOCKET;
		case EOPNOTSUPP:
			return net6::error::OPERATION_NOT_SUPPORTED;
		case EPERM:
			return net6::error::ACCESS_DENIED;
		case EPIPE:
			return net6::error::BROKEN_PIPE;
		case EPROTONOSUPPORT:
			return net6::error::PROTOCOL_NOT_SUPPORTED;
		case EPROTOTYPE:
			return net6::error::WRONG_PROTOCOL_TYPE;
		case ETIMEDOUT:
			return net6::error::CONNECTION_TIMEOUT;
//		case EWOULDBLOCK: // Same as EAGAIN
//			return net6::error::WOULD_BLOCK;
#endif
		default:
			return net6::error::UNKNOWN;
		}
	}

	/** Translates an error code reported by getaddrinfo to a net6 error
	 * code.
	 */
	net6::error::code gai_to_net6(int code)
	{
		using net6::error;
		switch(code)
		{
		case EAI_FAMILY:
			return net6::error::ADDRESS_FAMILY_NOT_SUPPORTED;
		case EAI_SOCKTYPE:
			return net6::error::SOCKET_NOT_SUPPORTED;
		case EAI_BADFLAGS:
			return net6::error::INVALID_ARGUMENT;
		case EAI_NONAME:
			return net6::error::HOSTNAME_NOT_FOUND;
		case EAI_SERVICE:
			return net6::error::TYPE_NOT_FOUND;
		// These #ifdef'd values seem not to exist on all systems.
#ifdef EAI_ADDRFAMILY
		case EAI_ADDRFAMILY: // TODO: Do we want HOST_NOT_FOUND here?
			return net6::error::ADDRESS_UNAVAILABLE;
#endif
#if EAI_NODATA != EAI_NONAME
#ifdef EAI_NODATA
		case EAI_NODATA:
			return net6::error::NO_DATA_RECORD;
#endif
#endif
		case EAI_MEMORY:
			return net6::error::NO_MEMORY;
		case EAI_AGAIN:
			return net6::error::TEMPORARY_FAILURE;
#ifdef EAI_SYSTEM
		case EAI_SYSTEM:
			return system_to_net6(errno);
#endif
		default:
			return net6::error::UNKNOWN;
		}
	}

	/** Translates an error code reported by gethostbyname into a net6
	 * error code.
	 */
	net6::error::code ghbn_to_net6(int code)
	{
#ifdef WIN32
		return system_to_net6(code);
#else
		switch(code)
		{
		case HOST_NOT_FOUND:
			return net6::error::HOSTNAME_NOT_FOUND;
		case NO_ADDRESS:
		//case NO_DATA: // same value as NO_ADDRESS
			return net6::error::NO_DATA_RECORD;
		case TRY_AGAIN:
			return net6::error::TEMPORARY_FAILURE;
		default:
			return net6::error::UNKNOWN;
		}
#endif
	}

	net6::error::code tls_to_net6(int code)
	{
		// TODO: Add more, or, better, remove error code and replace
		// by gnutls_strerror().
		switch(code)
		{
		case GNUTLS_E_AGAIN:
			return net6::error::WOULD_BLOCK;
		case GNUTLS_E_DECRYPTION_FAILED:
			return net6::error::DECRYPTION_FAILED;
		case GNUTLS_E_DH_PRIME_UNACCEPTABLE:
			return net6::error::PRIME_UNACCEPTABLE;
		case GNUTLS_E_ENCRYPTION_FAILED:
			return net6::error::ENCRYPTION_FAILED;
		case GNUTLS_E_GOT_APPLICATION_DATA:
			return net6::error::GOT_APPLICATION_DATA;
		case GNUTLS_E_INSUFFICIENT_CREDENTIALS:
			return net6::error::INSUFFICIENT_CREDENTIALS;
		case GNUTLS_E_INTERRUPTED:
			return net6::error::INTERRUPTED;
		case GNUTLS_E_INVALID_REQUEST:
			return net6::error::INVALID_REQUEST;
		case GNUTLS_E_KEY_USAGE_VIOLATION:
			return net6::error::KEY_USAGE_VIOLATION;
		case GNUTLS_E_MAC_VERIFY_FAILED:
			return net6::error::MAC_VERIFY_FAILED;
		case GNUTLS_E_NO_CERTIFICATE_FOUND:
			return net6::error::NO_CERTIFICATE;
		case GNUTLS_E_NO_TEMPORARY_DH_PARAMS:
			return net6::error::NO_TEMPORARY_DH_PARAMS;
		case GNUTLS_E_NO_TEMPORARY_RSA_PARAMS:
			return net6::error::NO_TEMPORARY_RSA_PARAMS;
		case GNUTLS_E_PK_DECRYPTION_FAILED:
			return net6::error::DECRYPTION_FAILED;
		case GNUTLS_E_PK_ENCRYPTION_FAILED:
			return net6::error::ENCRYPTION_FAILED;
		case GNUTLS_E_PULL_ERROR:
			return net6::error::PULL_ERROR;
		case GNUTLS_E_PUSH_ERROR:
			return net6::error::PUSH_ERROR;
#ifdef GNUTLS_E_RANDOM_FAILED
		case GNUTLS_E_RANDOM_FAILED:
			return net6::error::RANDOM_FAILED;
#endif
		case GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER:
			return net6::error::INVALID_ARGUMENT;
		case GNUTLS_E_REHANDSHAKE:
			return net6::error::REHANDSHAKE;
		case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET:
			return net6::error::UNEXPECTED_HANDSHAKE;
		case GNUTLS_E_UNEXPECTED_PACKET:
			return net6::error::UNEXPECTED_PACKET;
		case GNUTLS_E_UNEXPECTED_PACKET_LENGTH:
			return net6::error::UNEXPECTED_PACKET_LENGTH;
		default:
			return net6::error::UNKNOWN;
		}
	}

	/** Translates a system dependant error value from a given domain
	 * into a net6 error code
	 */
	net6::error::code domain_to_net6(net6::error::domain error_domain,
	                                 int error_code)
	{
		switch(error_domain)
		{
		case net6::error::SYSTEM:
			return system_to_net6(error_code);
		case net6::error::GETADDRINFO:
			return gai_to_net6(error_code);
		case net6::error::GETHOSTBYNAME:
			return ghbn_to_net6(error_code);
		case net6::error::GNUTLS:
			return tls_to_net6(error_code);
		default:
			throw std::logic_error(
				"domain_to_net6:\n"
				"Unknown error domain"
			);
		}
	}

	/** Translates an error code into a human-readable error message
	 */
	const char* net6_strerror(net6::error::code error_code)
	{
		using net6::error;
		switch(error_code)
		{
		case net6::error::INTERRUPTED:
			return _("Interrupted function call");
		case net6::error::ACCESS_DENIED:
			return _("Access denied");
		case net6::error::BAD_ADDRESS:
			return _("Bad address");
		case net6::error::INVALID_ARGUMENT:
			return _("Invalid argument");
		case net6::error::TOO_MANY_FILES:
			return _("Too many open files");
		case net6::error::WOULD_BLOCK:
			return _("Resource temporarily unavailable");
		case net6::error::OPERATION_IN_PROGRESS:
			return _("Operation already in progress");
		case net6::error::NOT_SOCKET:
			return _("Socket operation on non-socket");
		case net6::error::DESTINATION_ADDRESS_REQUIRED:
			return _("Destination address required");
		case net6::error::MESSAGE_TOO_LONG:
			return _("Message too long");
		case net6::error::WRONG_PROTOCOL_TYPE:
			return _("Protocol wrong type for socket");
		case net6::error::BAD_PROTOCOL_OPTION:
			return _("Bad protocol option");
		case net6::error::PROTOCOL_NOT_SUPPORTED:
			return _("Protocol not supported");
		case net6::error::SOCKET_NOT_SUPPORTED:
			return _("Socket type not supported");
		case net6::error::OPERATION_NOT_SUPPORTED:
			return _("Operation not supported");
		case net6::error::PROTOCOL_FAMILY_NOT_SUPPORTED:
			return _("Protocol family not supported");
		case net6::error::ADDRESS_FAMILY_NOT_SUPPORTED:
			return _("Address family not supported");
		case net6::error::ADDRESS_IN_USE:
			return _("Address is already in use");
		case net6::error::ADDRESS_UNAVAILABLE:
			return _("Cannot assign requested address");
		case net6::error::NETWORK_DOWN:
			return _("Network is down");
		case net6::error::NETWORK_UNREACHABLE:
			return _("Network is unreachable");
		case net6::error::NETWORK_RESET:
			return _("Network dropped connection on reset");
		case net6::error::CONNECTION_ABORTED:
			return _("Software caused connection abort");
		case net6::error::CONNECTION_RESET:
			return _("Connection reset by peer");
		case net6::error::NO_BUFFER_SPACE:
			return _("No buffer space available");
		case net6::error::SOCKET_IS_CONNECTED:
			return _("Socket is already connected");
		case net6::error::SOCKET_NOT_CONNECTED:
			return _("Socket is not connected");
		case net6::error::SOCKET_SHUTDOWN:
			return _("Cannot send after socket shutdown");
		case net6::error::CONNECTION_TIMEOUT:
			return _("Connection timed out");
		case net6::error::CONNECTION_REFUSED:
			return _("Connection refused");
		case net6::error::HOST_DOWN:
			return _("Host is down");
		case net6::error::HOST_UNREACHABLE:
			return _("No route to host");
		case net6::error::TOO_MANY_PROCESSES:
			return _("Too many processes");
		case net6::error::SYSTEM_NOT_READY:
			return _("Network subsystem is unavailable");
		case net6::error::VERSION_NOT_SUPPORTED:
			return _("Winsock.dll version out of range");
		case net6::error::NOT_INITIALISED:
			return _("Successful WSAStartup not yet performed");
		case net6::error::DISCONNECTED:
			return _("Graceful shutdown in progress");
		case net6::error::TYPE_NOT_FOUND:
			return _("Class type not found");
		case net6::error::HOSTNAME_NOT_FOUND:
			return _("Host not found");
		case net6::error::TEMPORARY_FAILURE:
			return _("Nonauthoritative host not found");
		case net6::error::NO_DATA_RECORD:
			return _("No data record of requested type");
		case net6::error::INVALID_HANDLE:
			return _("Specified event object handle is invalid");
		case net6::error::INVALID_PARAMETER:
			return _("One or more parameters are invalid");
		case net6::error::NO_MEMORY:
			return _("No more memory is available");
		case net6::error::BROKEN_PIPE:
			return _("Broken pipe");
		case net6::error::NO_DEVICE:
			return _("No such device");
		case net6::error::DECRYPTION_FAILED:
			return _("Decryption has failed");
		case net6::error::PRIME_UNACCEPTABLE:
			return _("The Diffie Hellman prime sent by the server "
			         "is not acceptable (not long enough)");
		case net6::error::ENCRYPTION_FAILED:
			return _("Encryption has failed");
		case net6::error::GOT_APPLICATION_DATA:
			return _("TLS Application data were received, while "
			         "expecting handshake data");
		case net6::error::INSUFFICIENT_CREDENTIALS:
			return _("Insufficient credentials for that request");
		case net6::error::INVALID_REQUEST:
			return _("The request is invalid");
		case net6::error::KEY_USAGE_VIOLATION:
			return _("Key usage violation in certificate has "
			         "been detected");
		case net6::error::MAC_VERIFY_FAILED:
			return _("The Message Authentication Code "
			         "verification failed");
		case net6::error::NO_CERTIFICATE:
			return _("The peer did not send any certificate");
		case net6::error::NO_TEMPORARY_DH_PARAMS:
			return _("No temporary DH parameters were found");
		case net6::error::NO_TEMPORARY_RSA_PARAMS:
			return _("No temporary RSA parameters were found");
		case net6::error::PULL_ERROR:
			return _("Error in the pull function");
		case net6::error::PUSH_ERROR:
			return _("Error in the push function");
		case net6::error::RANDOM_FAILED:
			return _("Failed to acquire random data");
		case net6::error::REHANDSHAKE:
			return _("Rehandshake was requested by the peer");
		case net6::error::UNEXPECTED_HANDSHAKE:
			return _("An unexpected TLS handshake packet "
			         "was received");
		case net6::error::UNEXPECTED_PACKET:
			return _("An unexpected TLS packet was received");
		case net6::error::UNEXPECTED_PACKET_LENGTH:
			return _("A TLS packet with unexpected length "
			         "was received.");
		case net6::error::UNKNOWN:
			return _("A nonrecoverable error has occured");
		default:
			throw std::logic_error(
				"net6_strerror:\n"
				"Unknown error code"
			);
		}
	}

	int last_error(net6::error::domain error_domain)
	{
		switch(error_domain)
		{
		case net6::error::SYSTEM:
#ifdef WIN32
			return WSAGetLastError();
#else
			return errno;
#endif
		default:
			return -1;
		}
	}
}

net6::error::error(domain error_domain, int error_code)
 : std::runtime_error(net6_strerror(domain_to_net6(error_domain, error_code)) ),
   errcode(domain_to_net6(error_domain, error_code) )
{
}

net6::error::error(domain error_domain):
	std::runtime_error(
		net6_strerror(
			errcode = domain_to_net6(
				error_domain,
				last_error(error_domain)
			)
		)
	)
{
}

net6::error::error(code error_code)
 : std::runtime_error(net6_strerror(error_code) ), errcode(error_code)
{
}

net6::error::code net6::error::get_code() const
{
	return errcode;
}

