/* =============================================================================
 *  scpp_socket.cpp
 *  Copyright (c)  2003 - 2011  Nomura Kei
 *  LICENSE :
 *  LGPL (GNU Lesser General General Public License - Version 3,29 June 2007)
 *  http://www.gnu.org/copyleft/lesser.html
 * =============================================================================
 *
 *  ライブラリの動的ロードを行うモジュール
 *
 */
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <sstream>

#include <scpp_socket.hpp>


namespace scpp
{


namespace
{

/** 最大ソケットアドレス長.			*/
const int MAXSOCKADDR			= 128;

/** close 時の read バッファサイズ.	*/
const int SOCKET_BUFFER_SIZE	= 1024;


/**
 * ソケットをクリーンアップします.
 */
void cleanupSocket()
{
#if (SCPP_IS_WINDOWS)
	WSACleanup();
#endif
}


/**
 * ソケットを初期化します.
 */
void initSocket()
{
#if (SCPP_IS_WINDOWS)
	static bool isInit = false;
	if (!isInit)
	{
		WSADATA wsaData;
		WSAStartup(MAKEWORD(2, 0), &wsaData);
		atexit(cleanupSocket);
	}
#endif
}

}	// namespace 無名名前空間



////////////////////////////////////////////////////////////////////////////////
//
// SocketException
//


/**
 * 最後に発生した errno に対するメッセージをもつ SocketException を構築します.
 * エラーメッセージを取得できない場合, 空文字がメッセージに設定されます.
 */
SocketException::SocketException() throw() : Exception()
{
#if (SCPP_WINDOWS)
	int errnum = WSAGetLastError();
	Errno::getErrorMessage(&this->message, errnum);
#endif
}


/**
 * コピーコンストラクタ.
 *
 * @param t コピー元
 */
SocketException::SocketException(const SocketException& t) throw() : Exception(t)
{
}


/**
 * 指定されたメッセージをもつ SocketException を構築します.
 *
 * @param msg メッセージ
 */
SocketException::SocketException(const std::string& msg) throw() : Exception(msg)
{
}


/**
 * デストラクタ.
 */
SocketException::~SocketException() throw()
{
}



////////////////////////////////////////////////////////////////////////////////
//
// AbstractSocket
//


/**
 * AbstractSocket を構築します.
 * 
 * @param type   SOCK_STREAM/SOCK_DGRAM/SOCK_RAW
 * @param family AF_UNSPEC/AF_INET/AF_INET6)
 */
AbstractSocket::AbstractSocket(int type, int family)
	: sockFd(INVALID_SOCKET), sockType(type), sockFamily(family)
{
	initSocket();
	memset(&remoteAddr, 0x00, sizeof(sockaddr_storage));
	memset(&localAddr,  0x00, sizeof(sockaddr_storage));
}


/**
 * AbstractSocket を破棄します.
 */
AbstractSocket::~AbstractSocket()
{
	// NOP
}


/**
 * ソケットをコピーします.
 *
 * @param sock コピー元ソケット
 * @return コピー後のソケット
 */
AbstractSocket& AbstractSocket::operator=(const AbstractSocket& sock)
{
	clone(sock);
	return *this;
}


/**
 * 本ソケットの接続先アドレスを指定された addr に格納します.
 * 通常 addr のバッファサイズは, NI_MAXHOST とすべきです.
 * 取得に成功した場合は, 接続先アドレスを格納した addr のポインタを返します.
 * 失敗した場合は, null を返します.
 *
 * @param addr    アドレス格納用バッファ
 * @param addrLen アドレス格納用バッファ長
 * @return addr へのポインタ
 */
const char* AbstractSocket::getAddress(char* addr, size_t addrLen) const
{
	int ret = ::getnameinfo(reinterpret_cast<const sockaddr*>(&remoteAddr), remoteAddrLen,
			addr, addrLen, 0, 0, NI_NUMERICHOST);
	if (ret != 0)
	{
		return 0;
	}
	return addr;
}


/**
 * 本ソケットの接続先ポート番号を返します.
 * 接続先ポート番号が取得できなかった場合, -1 を返します.
 *
 * @return ポート番号
 */
int AbstractSocket::getPort() const
{
	char tmpPort[NI_MAXSERV];
	int ret = ::getnameinfo(reinterpret_cast<const sockaddr*>(&remoteAddr), remoteAddrLen,
			0, 0, tmpPort, sizeof(tmpPort), NI_NUMERICSERV);
	if (ret != 0)
	{
		return -1;
	}
	return atoi(tmpPort);
}


/**
 * 本ソケットのローカルアドレスを指定された addr に格納します.
 * 通常 addr のバッファサイズは, NI_MAXHOST とすべきです.
 * 取得に成功した場合は, 接続先アドレスを格納した addr のポインタを返します.
 * 失敗した場合は, null を返します.
 *
 * @param addr    アドレス格納用バッファ
 * @param addrLen アドレス格納用バッファ長
 * @return addr へのポインタ
 */
const char* AbstractSocket::getLocalAddress(char* addr, size_t addrLen) const
{
	int ret = ::getnameinfo(reinterpret_cast<const sockaddr*>(&localAddr), localAddrLen,
			addr, addrLen, 0, 0, NI_NUMERICHOST);
	if (ret != 0)
	{
		return 0;
	}
	return addr;
}


/**
 * 本ソケットのローカルポート番号を返します.
 * ローカルポート番号が取得できなかった場合, -1 を返します.
 *
 * @return ポート番号
 */
int AbstractSocket::getLocalPort() const
{
	char tmpPort[NI_MAXSERV];
	int ret = ::getnameinfo(reinterpret_cast<const sockaddr*>(&localAddr), localAddrLen,
			0, 0, tmpPort, sizeof(tmpPort), NI_NUMERICSERV);
	if (ret != 0)
	{
		return -1;
	}
	return atoi(tmpPort);
}


/**
 * ソケットディスクリプタを返します.
 *
 * @return ソケットディスクリプタ
 */
const socket_t AbstractSocket::getSocket() const
{
	return sockFd;
}


/**
 * ソケットタイプを返します.
 *
 * @return ソケットタイプ
 */
const int AbstractSocket::getSocketType() const
{
	return sockType;
}


/**
 * ソケットファミリを返します.
 * 本メソッドで返されるソケットファミリは,
 * 実際にバインドあるいは, 接続されているソケットのファミリとなります.
 *
 * @return ソケットファミリ
 */
const int AbstractSocket::getSocketFamily() const
{
	union { sockaddr sa; char data[MAXSOCKADDR]; } un;
	socklen_t len = MAXSOCKADDR;
	if (::getsockname(sockFd, reinterpret_cast<sockaddr*>(un.data), &len) < 0)
	{
		return INVALID_SOCKET;
	}
	return (un.sa.sa_family);
}


/**
 * 指定されたアドレス, ポートにバインドします.
 * ANY アドレスを指定する場合, アドレスに 0 (null) を指定します.
 * ANY ポート番号を指定する場合, ポート番号に 不の値を指定します.
 *
 * @param addr アドレス
 * @param port ポート番号
 */
void AbstractSocket::bind(const char* addr, int port)
{
	std::stringstream ss;
	ss << port;
	bind(addr, ss.str().c_str());
}


/**
 * 指定されたアドレス, サービスのアドレス情報を取得します.
 *
 * @param result    アドレス情報格納用のポインタ
 * @param addr      アドレス
 * @param service   サービス
 * @param isPassive サーバソケット用の場合 true を指定してください
 */
void AbstractSocket::getAddrInfo(
		addrinfo** result, const char* addr, const char* service, bool isPassive)
{
	addrinfo hints;
	memset(&hints, 0x00, sizeof(addrinfo));
	hints.ai_family   = sockFamily;
	hints.ai_socktype = sockType;
	if (isPassive)
	{
		hints.ai_flags = AI_PASSIVE;
	}
	int ret = ::getaddrinfo(addr, service, &hints, result);
	if (ret != 0)
	{
		throw SocketException();
	}
}


/**
 * 指定されたソケットをコピーします.
 *
 * @param sock ソケット
 */
void AbstractSocket::clone(const AbstractSocket& sock)
{
	this->sockFd     = sock.sockFd;
	this->sockType   = sock.sockType;
	this->sockFamily = sock.sockFamily;

	if (sock.remoteAddrLen != 0)
	{
		memcpy(&(this->remoteAddr), &(sock.remoteAddr), sock.remoteAddrLen);
	}
	if (sock.localAddrLen != 0)
	{
		memcpy(&(this->localAddr), &(sock.localAddr), sock.localAddrLen);
	}

}



////////////////////////////////////////////////////////////////////////////////
//
// Socket
//


/**
 * TCP用ソケットを生成します.
 *
 * @param family AF_UNSPEC/AF_INET/AF_INET6
 */
Socket::Socket(int family) : AbstractSocket(SOCK_STREAM, family)
{
	// NOP
}


/**
 * TCP用ソケットのコピーコンストラクタです.
 *
 * @param sock ソケット
 */
Socket::Socket(const Socket& sock) : AbstractSocket(sock.sockType, sock.sockFamily)
{
	clone(sock);
}


/**
 * Socket を破棄します.
 */
Socket::~Socket()
{
	// NOP
}


/**
 * TCP用ソケットをコピーします.
 *
 * @param sock コピー元ソケット
 * @return コピー後のソケット
 */
Socket& Socket::operator=(const Socket& sock)
{
	clone(sock);
	return *this;
}


/**
 * 指定されたアドレス, サービスにバインドします.
 * 指定されたアドレスとサービスにより複数候補がある場合は,
 * 最初に見つかったアドレスとポートにバインドします.
 *
 * @param addr    アドレス
 * @param service サービス
 */
void Socket::bind(const char* addr, const char* service)
{
	addrinfo* result;
	getAddrInfo(&result, addr, service, true);

	socket_t  tmpSock;
	addrinfo* rp;
	int       ret;

	for (rp = result; rp != 0; rp = rp->ai_next)
	{
		tmpSock = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (tmpSock < 0) { continue; }		// 生成失敗

		// bind 前の setsockopt は常に成功のためノーチェック
		const int on = 1;
		::setsockopt(tmpSock, SOL_SOCKET, SO_REUSEADDR, (const char*) &on, sizeof(on));
		ret = ::bind(tmpSock, rp->ai_addr, rp->ai_addrlen);
		if (ret == 0) { break; }			// Bind 成功
		
		// bind 失敗したのでソケットをクローズしておく
		::socket_close(tmpSock);
	}

	if (rp == 0)
	{
		freeaddrinfo(result);
		throw SocketException();
	}

	sockFd = tmpSock;
	memcpy(&localAddr, rp->ai_addr, rp->ai_addrlen);
	localAddrLen = rp->ai_addrlen;
	freeaddrinfo(result);
}


/**
 * ソケットを接続待ちソケットとしてマークをつけます.
 * 保留中の接続のキュー最大長を指定します.
 *
 * @param backlog バックログ
 */
void Socket::listen(int backlog)
{
	int ret = ::listen(sockFd, backlog);
	if (ret == SOCKET_ERROR)
	{
		throw SocketException();
	}
}


/**
 * ソケットへの接続を受け付けます.
 *
 * @return 受け付けたソケット
 */
void Socket::accept(Socket* clientSocket)
{
	sockaddr_storage addr;
	socklen_t        addrLen = sizeof(sockaddr_storage);
	clientSocket->sockFd = ::accept(sockFd, reinterpret_cast<sockaddr*>(&addr), &addrLen);

	if (clientSocket->sockFd < 0)
	{
		throw SocketException();
	}

	memcpy(&clientSocket->remoteAddr, &addr, addrLen);
	clientSocket->remoteAddrLen = addrLen;
	clientSocket->sockType   = SOCK_STREAM;
	clientSocket->sockFamily = addr.ss_family;
}


/**
 * 指定されたアドレス, ポートに接続します.
 * loAddr に 0 を指定した場合, 自動的にローカルのアドレスが設定されます.
 * loPort に -1 を指定した場合, 自動的にローカルのポート番号が割り付けられます.
 *
 * @param addr      アドレス
 * @param port      ポート
 * @param loAddr ローカルアドレス
 * @param loPort ローカルポート
 */
void Socket::connect(const char* addr, int port, const char* loAddr, int loPort)
{
		std::stringstream ssPort;
		ssPort << port;

		if ((0 <= loPort) && (loPort < 65535))
		{
			std::stringstream ssLocalPort;
			ssLocalPort << loPort;
			ssLocalPort.str().c_str();
			connect(addr, ssPort.str().c_str(), loAddr, ssLocalPort.str().c_str());
		}
		else
		{
			connect(addr, ssPort.str().c_str(), loAddr, 0);
		}

}


/**
 * 指定されたアドレス, ポートに接続します.
 * loAddr に 0 を指定した場合, 自動的にローカルのアドレスが設定されます.
 * loService に 0 を指定した場合, 自動的にローカルのポート番号が割り付けられます.
 *
 * @param addr      アド/sockレス
 * @param service   ポート
 * @param loAddr    ローカルアドレス
 * @param loService ローカルポート
 */
void Socket::connect(
		const char* addr, const char* service,
		const char* loAddr, const char* loService)
{
	if ((loAddr != 0) || (loService != 0))
	{	// ローカルアドレス, サービスの指定あり
		bindAndConnect(addr, service, loAddr, loService);
		return;
	}

	addrinfo* result;
	getAddrInfo(&result, addr, service, false);

	socket_t  tmpSock;
	addrinfo* rp;
	int       ret;
	for (rp = result; rp != 0; rp = rp->ai_next)
	{
		tmpSock = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (tmpSock < 0) { continue; }		// 生成失敗

		ret = ::connect(tmpSock, rp->ai_addr, rp->ai_addrlen);
		if (ret != SOCKET_ERROR) { break; }	// 接続成功

		// 接続失敗したのでソケットをクローズしておく
		::socket_close(tmpSock);
	}

	if (rp == 0)
	{
		freeaddrinfo(result);
		throw SocketException();
	}

	sockFd = tmpSock;
	memcpy(&remoteAddr, rp->ai_addr, rp->ai_addrlen);
	remoteAddrLen = rp->ai_addrlen;
	freeaddrinfo(result);
}


/**
 * ソケットをクローズします.
 */
void Socket::close()
{
	int ret = ::shutdown(sockFd, SHUT_WR);
	if (ret == 0)
	{
		int  readSize;
		char buff[SOCKET_BUFFER_SIZE];
		do
		{	// 届いているデータを全て読み取る
			readSize = ::recv(sockFd, buff, sizeof(buff), 0);
		} while (readSize > 0);
	}
	ret = ::socket_close(sockFd);

}


/**
 * ソケットへメッセージを送信します.
 *
 *
 * @param buff バッファ
 * @param size サイズ
 * @param flag フラグ (MSG_CONFIRM/MSG_DONTROUTE/MSG_DONTWAIT/MSG_EOR/MSG_MORE/MSG_NOSIGNAL/MSG_OOB)
 * @return 書き込んだデータサイズ
 */
ssize_t Socket::send(const char* buff, size_t size, int flag)
{
	return ::send(sockFd, buff, size, flag);
}


/**
 * ソケットからメッセージを受信します.
 *
 * @param buff バッファ
 * @param size サイズ
 * @param flag フラグ (MSG_CMSG_CLOEXEC/MSG_DONTWAIT/MSG_ERRQUEUE/MSG_OOB/MSG_PEEK/MSG_TRUNC/MSG_WAITALL)
 * @return 読み取ったデータサイズ
 */
ssize_t Socket::recv(char* buff, size_t size, int flag)
{
	return ::recv(sockFd, buff, size, flag);
}


/**
 * ソケットへデータを書き込みます.
 *
 * @param buff バッファ
 * @param size サイズ
 * @return 書き込んだサイズ
 */
int Socket::write(const char* buff, size_t size)
{
	ssize_t writeSize;
	int     sockErr;
	do
	{	// SCPP_EINTR の場合は再度書き込む
		Errno::setError(0);
		sockErr = 0;
		writeSize = send(buff, size);
		if (writeSize < 0)
		{
			sockErr = Errno::getError();
		}
	} while (sockErr == SCPP_EINTR);
	return writeSize;
}


/**
 * ソケットからデータを読み取ります.
 *
 * @param buff バッファ
 * @param size サイズ
 * @return 読み取ったサイズ
 */
int Socket::read(char* buff, size_t size)
{
	ssize_t readSize;
	int     sockErr;
	do
	{	// SCPP_EINTR の場合は再度読み取る
		Errno::setError(0);
		sockErr = 0;
		readSize = recv(buff, size);
		if (readSize < 0)
		{
			sockErr = Errno::getError();
		}
	} while (sockErr == SCPP_EINTR);
	return readSize;
}


/**
 * 指定されたローカルのアドレス, サービスにバインドし,
 * 指定されたアドレス, サービスに接続します.
 *
 * @param addr      接続先アドレス
 * @param service   接続先サービス
 * @param loAddr    接続元アドレス
 * @param loService 接続元サービス
 */
void Socket::bindAndConnect(
		const char* addr, const char* service,
		const char* loAddr, const char* loService)
{
	addrinfo* result;
	getAddrInfo(&result, loAddr, loService, false);

	socket_t  tmpSock;
	addrinfo* rp;
	int       ret;
	bool      isConnected;
	for (rp = result; rp != 0; rp = rp->ai_next)
	{
		tmpSock = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (tmpSock < 0) { continue; }		// 生成失敗

		// bind 前の setsockopt は常に成功のためノーチェック
		const int on = 1;
		::setsockopt(tmpSock, SOL_SOCKET, SO_REUSEADDR, (const char*) &on, sizeof(on));
		ret = ::bind(tmpSock, rp->ai_addr, rp->ai_addrlen);
		if (ret != 0)
		{	// bind 失敗
			::socket_close(tmpSock);
			continue;
		}

		// addr, service に接続
		isConnected = connect(tmpSock, addr, service);
		if (isConnected) { break; }			// 接続成功

		// 接続失敗したのでソケットをクローズしておく
		::socket_close(tmpSock);
	}

	if (rp == 0)
	{
		freeaddrinfo(result);
		throw SocketException();
	}

	sockFd = tmpSock;
	memcpy(&localAddr, rp->ai_addr, rp->ai_addrlen);
	localAddrLen = rp->ai_addrlen;
	freeaddrinfo(result);
}


/**
 * 指定されたソケットを用いて, 指定されたアドレス, サービスに接続します.
 * 接続に成功した場合, true を返します.
 *
 * @param sock    ソケット
 * @param addr    接続先アドレス
 * @param service 接続先サービス
 * @return true/false (接続成功/失敗)
 */
bool Socket::connect(socket_t sock, const char* addr, const char* service)
{
	addrinfo* result;
	getAddrInfo(&result, addr, service, false);

	addrinfo* rp;
	int       ret;
	for (rp = result; rp != 0; rp = rp->ai_next)
	{
		ret = ::connect(sock, rp->ai_addr, rp->ai_addrlen);
		if (ret != SOCKET_ERROR) { break; }	// 接続成功
	}

	if (rp == 0)
	{
		freeaddrinfo(result);
		return false;
	}

	memcpy(&remoteAddr, rp->ai_addr, rp->ai_addrlen);
	remoteAddrLen = rp->ai_addrlen;
	freeaddrinfo(result);
	return true;
}



////////////////////////////////////////////////////////////////////////////////
//
// DatagramSocket
//

/**
 * データグラムソケットを生成します.
 *
 * @param family AF_UNSPEC/AF_INET/AF_INET6 の何れかを指定します.
 */
DatagramSocket::DatagramSocket(int family) : AbstractSocket(SOCK_DGRAM, family)
{
	// NOP
}


/**
 * UDP用ソケットのコピーコンストラクタです.
 *
 * @param sock ソケット
 */
DatagramSocket::DatagramSocket(const DatagramSocket& sock) : AbstractSocket(sock.sockType, sock.sockFamily)
{
	clone(sock);
}


/**
 * DatagramSocket を破棄します.
 */
DatagramSocket::~DatagramSocket()
{
	// NOP
}


/**
 * UDP用ソケットをコピーします.
 *
 * @param sock コピー元のソケット
 * @return コピー後のソケット
 */
DatagramSocket& DatagramSocket::operator=(const DatagramSocket& sock)
{
	clone(sock);
	return *this;
}


/**
 * 指定されたアドレス, サービスにバインドします.
 * 指定されたアドレスとサービスにより複数候補がある場合は,
 * 最初に見つかったアドレスとポートにバインドします.
 *
 * @param addr    アドレス
 * @param service サービス
 */
void DatagramSocket::bind(const char* addr, const char* service)
{
	addrinfo* result;
	getAddrInfo(&result, addr, service, true);

	socket_t  tmpSock;
	addrinfo* rp;
	int       ret;

	for (rp = result; rp != 0; rp = rp->ai_next)
	{
		tmpSock = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (tmpSock < 0) { continue; }		// 生成失敗

		// bind 前の setsockopt は常に成功のためノーチェック
		const int on = 1;
		::setsockopt(tmpSock, SOL_SOCKET, SO_REUSEADDR, (const char*) &on, sizeof(on));
		ret = ::bind(tmpSock, rp->ai_addr, rp->ai_addrlen);
		if (ret == 0) { break; }			// Bind 成功
		
		// bind 失敗したのでソケットをクローズしておく
		::socket_close(tmpSock);
	}


	if (rp == 0)
	{
		freeaddrinfo(result);
		throw SocketException();
	}

	sockFd = tmpSock;
	memcpy(&localAddr, rp->ai_addr, rp->ai_addrlen);
	localAddrLen = rp->ai_addrlen;
	freeaddrinfo(result);
}


/**
 * 指定されたアドレス, ポートにメッセージを送信します.
 *
 * @param buff  送信メッセージ
 * @param len   送信メッセージサイズ
 * @param flags フラグ
 * @param addr  アドレス
 * @param port  ポート番号
 * @return 送信メッセージサイズ, エラー発生時は -1 
 */
ssize_t DatagramSocket::sendto(
		const char* buff, size_t len, int flags,
		const char* addr, int port)
{
	std::stringstream ss;
	ss << port;
	return sendto(buff, len,flags, addr, ss.str().c_str());
}


/**
 * 指定されたアドレス, サービスにメッセージを送信します.
 *
 * @param buff    送信メッセージ
 * @param len     送信メッセージのサイズ
 * @param flags   フラグ
 * @param addr    アドレス
 * @param service サービス名
 * @return 送信メッセージサイズ, エラー発生時は -1
 */
ssize_t DatagramSocket::sendto(
		const char* buff, size_t len, int flags,
		const char* addr, const char* service)
{
	addrinfo* result;
	getAddrInfo(&result, addr, service, false);

	addrinfo* rp;
	ssize_t   ret;
	for (rp = result; rp != 0; rp = rp->ai_next)
	{
		ret = ::sendto(sockFd, buff, len, flags, rp->ai_addr, rp->ai_addrlen);
		if (ret != SOCKET_ERROR) { break; }	// 接続成功
	}

	if (rp == 0)
	{
		freeaddrinfo(result);
		return ret;
	}

	memcpy(&remoteAddr, rp->ai_addr, rp->ai_addrlen);
	remoteAddrLen = rp->ai_addrlen;
	freeaddrinfo(result);
	return ret;
}


/**
 * 指定されたアドレス, サービスにメッセージを送信します.
 *
 * @param buff    送信メッセージ
 * @param len     送信メッセージのサイズ
 * @param flags   フラグ
 * @param addr    アドレス
 * @param addrLen アドレス長
 * @return 送信メッセージサイズ, エラー発生時 -1
 */
ssize_t DatagramSocket::sendto(
		const char* buff, size_t len, int flags,
		const sockaddr* addr, socklen_t addrLen)
{
	return ::sendto(sockFd, buff, len, flags, addr, addrLen);
}


/**
 * メッセージを受信します.
 * srcAddr, srcPort がどちらも null でない場合,
 * 送信元のアドレスとポートが srcAddr, srcPort に格納されます.
 *
 * @param buff       受信メッセージバッファ
 * @param len        受信メッセージバッファのサイズ
 * @param flags      フラグ
 * @param srcAddr    送信元アドレス格納用バッファ
 * @param srcAddrLen 送信元アドレス格納用バッファサイズ
 * @param srcPort    送信元ポート格納用バッファ
 * @param srcPortLen 送信元ポート格納用バッファサイズ
 * @return 受信メッセージサイズ, エラー発生時は -1, 接続先が正しく shutdown した場合 0
 */
ssize_t DatagramSocket::recvfrom(
		char* buff, size_t len, int flags,
		char* srcAddr, size_t srcAddrLen,
		char* srcPort, size_t srcPortLen)
{
	sockaddr_storage srcAddrStorage;
	socklen_t srcAddrStorageLen = sizeof(sockaddr_storage);
	ssize_t   size    = ::recvfrom(sockFd, buff, len, flags,
							reinterpret_cast<sockaddr*>(&srcAddrStorage), &srcAddrStorageLen);

	if ((srcAddr != 0) && (srcPort != 0))
	{
		int ret = ::getnameinfo(reinterpret_cast<sockaddr*>(&srcAddrStorage), srcAddrStorageLen,
				srcAddr, srcAddrLen,
				srcPort, srcPortLen,
				(NI_NUMERICHOST | NI_NUMERICSERV));

		if (ret != 0)
		{
			srcAddr[0] = '\0';
			srcPort[0] = '\0';
		}
	}
	return size;
}


/**
 * メッセージを受信します.
 * srcAddr, srcPort がどちらも null でない場合,
 * 送信元のアドレスとポートが srcAddr, srcPort に格納されます.
 *
 * @param buff    受信メッセージバッファ
 * @param len     受信メッセージバッファのサイズ
 * @param flags   フラグ
 * @param addr    送信元アドレス格納用
 * @param addrLen 送信元アドレスサイズ
 * @return 受信メッセージサイズ, エラー発生時は -1, 接続先が正しく shutdown した場合 0
 */
ssize_t DatagramSocket::recvfrom(
		char* buff, size_t len, int flags,
		sockaddr* addr, socklen_t* addrLen)
{
	return ::recvfrom(sockFd, buff, len, flags, addr, addrLen);
}


/**
 * ソケットをクローズします.
 */
void DatagramSocket::close()
{
	// UDP なのでいきなり閉じちゃう
	::socket_close(sockFd);

}


/**
 * TTL を設定します.
 *
 * @param val 値
 */
void DatagramSocket::setTTL(int val)
{
	int family = getSocketFamily();
	int ret    = -1;
	switch (family)
	{
		case AF_INET:
			unsigned char ttl;
			ttl = static_cast<unsigned char>(val);
			ret = setsockopt(sockFd, IPPROTO_IP, IP_MULTICAST_TTL, reinterpret_cast<char*>(&ttl), sizeof(ttl));
			break;

		case AF_INET6:
			int hop;
			hop = val;
			ret = setsockopt(sockFd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, reinterpret_cast<char*>(&hop), sizeof(hop));
			break;

		default:
			throw SocketException("unsupported socket family");
	}
	if (ret != 0);
	{
		throw SocketException();
	}
}


/**
 * マルチキャストグループに参加するために JOIN します.
 *
 * @param sa      マルチキャストアドレス
 * @param salen   マルチキャストアドレス構造体のサイズ
 * @param ifname  インタフェース名
 * @param ifindex インタフェースインデックス
 */
void DatagramSocket::join(const sockaddr* sa, socklen_t salen, const char* ifname, unsigned int ifindex)
{
	int family = getSocketFamily();
	switch (family)
	{
		case AF_INET:
			joinIPv4(reinterpret_cast<const sockaddr_in*>(sa), salen, ifname, ifindex);
			break;

		case AF_INET6:
			joinIPv6(reinterpret_cast<const sockaddr_in6*>(sa), salen, ifname, ifindex);
			break;

		default:
			// NOP
			break;
	}
}


/**
 * IPv4 マルチキャストグループに参加するために JOIN します.
 *
 * @param sa      マルチキャストアドレス
 * @param salen   マルチキャストアドレス構造体のサイズ
 * @param ifname  インタフェース名
 * @param ifindex インタフェースインデックス
 *
 */
void DatagramSocket::joinIPv4(const sockaddr_in* grp, socklen_t grplen , const char* ifname, unsigned int ifindex)
{
	ip_mreq mreq;
	memcpy(&mreq.imr_multiaddr, &(reinterpret_cast<const sockaddr_in*>(grp))->sin_addr, sizeof(in_addr));
	mreq.imr_interface.s_addr = htonl(INADDR_ANY);
	int ret = setsockopt(sockFd, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast<char*>(&mreq), sizeof(mreq));
	if (ret != 0);
	{
		throw SocketException();
	}
}


/**
 * IPv6 マルチキャストグループに参加するために JOIN します.
 *
 * @param sa      マルチキャストアドレス
 * @param salen   マルチキャストアドレス構造体のサイズ
 * @param ifname  インタフェース名
 * @param ifindex インタフェースインデックス
 *
 */
void DatagramSocket::joinIPv6(const sockaddr_in6* grp, socklen_t grplen, const char* ifname, unsigned int ifindex)
{
	ipv6_mreq mreq6;
	memcpy(&mreq6.ipv6mr_multiaddr, &grp->sin6_addr, sizeof(in6_addr));
	if (ifindex > 0)
	{
		mreq6.ipv6mr_interface = ifindex;
	}
	else
	{
		mreq6.ipv6mr_interface = 0;
	}
	int ret = setsockopt(sockFd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, reinterpret_cast<char*>(&mreq6), sizeof(mreq6));
	if (ret != 0)
	{
		throw SocketException();
	}
}


/**
 * マルチキャストグループから LEAVE します.
 *
 * @param sa      マルチキャストアドレス
 * @param salen   マルチキャストアドレス構造体のサイズ
 */
void DatagramSocket::leave(const sockaddr* sa, socklen_t salen)
{
	int family = getSocketFamily();
	switch (family)
	{
		case AF_INET:
			leaveIPv4(reinterpret_cast<const sockaddr_in*>(sa), salen);
			break;

		case AF_INET6:
			leaveIPv6(reinterpret_cast<const sockaddr_in6*>(sa), salen);
			break;

		default:
			// NOP
			break;
	}
}


/**
 * IPv4 マルチキャストグループから LEAVE します.
 *
 * @param sa      マルチキャストアドレス
 * @param salen   マルチキャストアドレス構造体のサイズ
 *
 */
void DatagramSocket::leaveIPv4(const sockaddr_in* grp, socklen_t grplen)
{
	ip_mreq mreq;
	memcpy(&mreq.imr_multiaddr, &(reinterpret_cast<const sockaddr_in*>(grp))->sin_addr, sizeof(in_addr));
	mreq.imr_interface.s_addr = htonl(INADDR_ANY);
	int ret = setsockopt(sockFd, IPPROTO_IP, IP_DROP_MEMBERSHIP, reinterpret_cast<char*>(&mreq), sizeof(mreq));
	if (ret != 0);
	{
		throw SocketException();
	}
}


/**
 * IPv6 マルチキャストグループから LEAVE します.
 *
 * @param sa      マルチキャストアドレス
 * @param salen   マルチキャストアドレス構造体のサイズ
 *
 */
void DatagramSocket::leaveIPv6(const sockaddr_in6* grp, socklen_t grplen)
{
	ipv6_mreq mreq6;
	memcpy(&mreq6.ipv6mr_multiaddr, &grp->sin6_addr, sizeof(in6_addr));
	mreq6.ipv6mr_interface = 0;
	int ret = setsockopt(sockFd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, reinterpret_cast<char*>(&mreq6), sizeof(mreq6));
	if (ret != 0)
	{
		throw SocketException();
	}
}



////////////////////////////////////////////////////////////////////////////////
//
// SocketSet
//

/**
 * Select で用いるためのソケット集合を構築します.
 */
SocketSet::SocketSet()
{
	reset();
}


/**
 * SoketSet をリセットして, 保持ソケットを 0個の状態にします.
 */
void SocketSet::reset()
{
	FD_ZERO(&fdset);
	maxfd = -1;
}


/**
 * ソケットを追加します.
 * 最大値を超えて追加しないでください.
 *
 * @param s ソケット
 */
void SocketSet::add(const AbstractSocket& s)
{
	add(s.getSocket());
}


/**
 * ソケットを追加します.
 * 最大値を超えて追加しないでください.
 *
 * @param sockfd ソケット
 */
void SocketSet::add(socket_t sockfd)
{
	FD_SET(sockfd, &fdset);
	int sockfdVal = static_cast<int>(sockfd);
	if (sockfdVal > maxfd)
	{
		maxfd = sockfdVal;
	}
}


/**
 * ソケットを取り除きます.
 *
 * @param s 取り除くソケット
 */
void SocketSet::remove(const AbstractSocket& s)
{
	remove(s.getSocket());
}


/**
 * ソケットを取り除きます.
 *
 * @param sockfd 取り除くソケット
 */
void SocketSet::remove(socket_t sockfd)
{
	FD_CLR(sockfd, &fdset);
}


/**
 * ソケットがこの集合に含まれていれば非0を返します.
 *
 * @param s ソケット
 * @return 非0/0 (含まれている/含まれていない)
 */
int SocketSet::isSet(const AbstractSocket& s) const
{
	return isSet(s.getSocket());
}


/**
 * ソケットがこの集合に含まれていれば非0を返します.
 *
 * @param sockfd ソケット
 * @return 非0/0 (含まれている/含まれていない)
 */
int SocketSet::isSet(socket_t sockfd) const
{
	return FD_ISSET(sockfd, &fdset);
}


/**
 * SocketSet が保持しているファイルディスクリプタの最大値 + 1 の値を返します.
 * 本値は, select の第一引数で使用する値となります.
 *
 * @return ファイルディスクリプタの最大値 + 1
 */
int SocketSet::selectn() const
{
	return (maxfd + 1);
}

fd_set* SocketSet::getFdSet()
{
	return &fdset;
}



////////////////////////////////////////////////////////////////////////////////
//
// Select
//


/**
 * select.
 *
 * @param readSet   読み取り用セット
 * @param writeSet  書き込み用セット
 * @param errSet    エラー発生用セット
 * @param timeout   タイムアウト
 * @return 変化のあったディスクリプタ数, タイムアウト時には 0 を返す場合がある. エラーの場合 -1 を返す.
 */
int Select::select(
		SocketSet* readSet, SocketSet* writeSet, SocketSet* errSet, timeval* timeout)
{
	fd_set* readfd  = 0;
	fd_set* writefd = 0;
	fd_set* errfd   = 0;

	int maxfd = 0;

	if (readSet != 0)
	{
		maxfd   = readSet->selectn();
		readfd  = readSet->getFdSet();
	}

	if (writeSet != 0)
	{
		maxfd   = (writeSet->selectn() > maxfd) ? writeSet->selectn() : maxfd;
		writefd = writeSet->getFdSet();
	}

	if (errSet != 0)
	{
		maxfd = (errSet->selectn() > maxfd) ? errSet->selectn() : maxfd;
		errfd = errSet->getFdSet();
	}

	return ::select(maxfd, readfd, writefd, errfd, timeout);
}



}	// namespace scpp


