// blocksock.cpp (CBlockingSocketException, CBlockingSocket, CHttpBlockingSocket)
#include <stdafx.h>
#include "blocksock.h"

// Class CBlockingSocketException
IMPLEMENT_DYNAMIC(CBlockingSocketException, CException)

CBlockingSocketException::CBlockingSocketException(char* pchMessage)
{
	m_strMessage = pchMessage;
	m_nError = WSAGetLastError();
}

BOOL CBlockingSocketException::GetErrorMessage(LPTSTR lpstrError, UINT nMaxError,
		PUINT pnHelpContext /*= NULL*/)
{

	char text[200];
	if(m_nError == 0) {
		wsprintf(text, "%s error", (const char*) m_strMessage);
	}
	else {
		wsprintf(text, "%s error #%d", (const char*) m_strMessage, m_nError);
	}
	strncpy(lpstrError, text, nMaxError - 1);
	return TRUE;
}

// Class CBlockingSocket
IMPLEMENT_DYNAMIC(CBlockingSocket, CObject)

void CBlockingSocket::Cleanup()
{
	// doesn't throw an exception because it's called in a catch block
	if(m_hSocket == NULL) return;
	VERIFY(closesocket(m_hSocket) != SOCKET_ERROR);
	m_hSocket = NULL;
}

int CBlockingSocket::Create(int nType /*= SOCK_STREAM */)
{
	ASSERT(m_hSocket == NULL);
	if((m_hSocket = socket(AF_INET, nType, IPPROTO_TCP)) == INVALID_SOCKET) {
		return -1;
	}
	return 0;
}

void CBlockingSocket::Bind(LPCSOCKADDR psa)
{
	ASSERT(m_hSocket != NULL);
	if(bind(m_hSocket, psa, sizeof(SOCKADDR)) == SOCKET_ERROR) {
		throw new CBlockingSocketException("Bind");
	}
}

void CBlockingSocket::Listen()
{
	ASSERT(m_hSocket != NULL);
	if(listen(m_hSocket, 5) == SOCKET_ERROR) {
		throw new CBlockingSocketException("Listen");
	}
}

BOOL CBlockingSocket::Accept(CBlockingSocket& sConnect, LPSOCKADDR psa)
{
	ASSERT(m_hSocket != NULL);
	ASSERT(sConnect.m_hSocket == NULL);
	int nLengthAddr = sizeof(SOCKADDR);
	sConnect.m_hSocket = accept(m_hSocket, psa, &nLengthAddr);
	if(sConnect == INVALID_SOCKET) {
		// no exception if the listen was canceled
		if(WSAGetLastError() != WSAEINTR) {
			throw new CBlockingSocketException("Accept");
		}
		return FALSE;
	}
	return TRUE;
}

int CBlockingSocket::Close()
{
	if (NULL == m_hSocket)
		return -1;

	if(closesocket(m_hSocket) == SOCKET_ERROR)
		return -1;
	m_hSocket = NULL;
	return 0;
}

int CBlockingSocket::Connect(LPCSOCKADDR psa)
{
	ASSERT(m_hSocket != NULL);
	// should timeout by itself
	return connect(m_hSocket, psa, sizeof(SOCKADDR));
}

//fellowing is the I/O operation of CBlockingSocket

int CBlockingSocket::Write(const char* pch, const int nSize)
{
	int nBytesSent = 0;
	int nBytesThisTime;
	const char* pch1 = pch;
	do {
		nBytesThisTime = Send(pch1, nSize - nBytesSent);
		if(nBytesThisTime == SOCKET_ERROR)
			return SOCKET_ERROR;
		nBytesSent += nBytesThisTime;
		pch1 += nBytesThisTime;
	} while(nBytesSent < nSize);
	return nBytesSent;
}

int CBlockingSocket::Send(const char* pch, const int nSize)
{
	ASSERT(m_hSocket != NULL);
	// returned value will be less than nSize if client cancels the reading
	FD_SET fd = {1, m_hSocket};
	if(select(0, NULL, &fd, NULL, NULL) == 0) {
		return SOCKET_ERROR;
	}
	return send(m_hSocket, pch, nSize, 0);
}

int CBlockingSocket::Read(char* pch, const int nSize){
	int nBytesSent = 0;
	int nBytesThisTime;
	char* pch1 = pch;
	do {
		nBytesThisTime = Receive(pch, nSize - nBytesSent);
		if(nBytesThisTime == SOCKET_ERROR)
			return SOCKET_ERROR;
		nBytesSent += nBytesThisTime;
		pch1 += nBytesThisTime;
	} while(nBytesSent < nSize);
	return nBytesSent;
}
	
int CBlockingSocket::Receive(char* pch, const int nSize)
{
	ASSERT(m_hSocket != NULL);
	fd_set fd = {1, m_hSocket};
	if(select(0, &fd, NULL, NULL, NULL) == 0) {
		return SOCKET_ERROR;
	}
	return recv(m_hSocket, pch, nSize, 0);
}

int CBlockingSocket::ReceiveDatagram(char* pch, const int nSize, LPSOCKADDR psa)
{
	ASSERT(m_hSocket != NULL);
	FD_SET fd = {1, m_hSocket};
	if(select(0, &fd, NULL, NULL, NULL) == 0) {
		throw new CBlockingSocketException("Receive timeout");
	}

	// input buffer should be big enough for the entire datagram
	int nFromSize = sizeof(SOCKADDR);
	int nBytesReceived = recvfrom(m_hSocket, pch, nSize, 0, psa, &nFromSize);
	if(nBytesReceived == SOCKET_ERROR) {
		throw new CBlockingSocketException("ReceiveDatagram");
	}
	return nBytesReceived;
}

int CBlockingSocket::SendDatagram(const char* pch, const int nSize, LPCSOCKADDR psa)
{
	ASSERT(m_hSocket != NULL);
	FD_SET fd = {1, m_hSocket};
	if(select(0, NULL, &fd, NULL, NULL) == 0) {
		throw new CBlockingSocketException("Send timeout");
	}

	int nBytesSent = sendto(m_hSocket, pch, nSize, 0, psa, sizeof(SOCKADDR));
	if(nBytesSent == SOCKET_ERROR) {
		throw new CBlockingSocketException("SendDatagram");
	}
	return nBytesSent;
}



int CBlockingSocket::GetPeerAddr(LPSOCKADDR psa)
{
	ASSERT(m_hSocket != NULL);
	// gets the address of the socket at the other end
	int nLengthAddr = sizeof(SOCKADDR);
	return getpeername(m_hSocket, psa, &nLengthAddr);
}

void CBlockingSocket::GetSockAddr(LPSOCKADDR psa)
{
	ASSERT(m_hSocket != NULL);
	// gets the address of the socket at this end
	int nLengthAddr = sizeof(SOCKADDR);
	if(getsockname(m_hSocket, psa, &nLengthAddr) == SOCKET_ERROR) {
		throw new CBlockingSocketException("GetSockName");
	}
}

//static
CSockAddr CBlockingSocket::GetHostByName(const char* pchName, const USHORT ushPort /* = 0 */)
{
	hostent* pHostEnt = gethostbyname(pchName);
	if(pHostEnt == NULL) {
		throw new CBlockingSocketException("GetHostByName");
	}
	ULONG* pulAddr = (ULONG*) pHostEnt->h_addr_list[0];
	SOCKADDR_IN sockTemp;
	sockTemp.sin_family = AF_INET;
	sockTemp.sin_port = htons(ushPort);
	sockTemp.sin_addr.s_addr = *pulAddr; // address is already in network byte order
	return sockTemp;
}

//static
const char* CBlockingSocket::GetHostByAddr(LPCSOCKADDR psa)
{
	hostent* pHostEnt = gethostbyaddr((char*) &((LPSOCKADDR_IN) psa)
				->sin_addr.s_addr, 4, PF_INET);
	if(pHostEnt == NULL) {
		throw new CBlockingSocketException("GetHostByAddr");
	}
	return pHostEnt->h_name; // caller shouldn't delete this memory
}

void LogBlockingSocketException(LPVOID pParam, char* pch, CBlockingSocketException* pe)
{	// pParam holds the HWND for the destination window (in another thread)
	CString strGmt = CTime::GetCurrentTime().FormatGmt("%m/%d/%y %H:%M:%S GMT");
	char text1[200], text2[50];
	pe->GetErrorMessage(text2, 49);
	wsprintf(text1, "WINSOCK ERROR--%s %s -- %s\r\n", pch, text2, (const char*) strGmt);
	::SendMessage((HWND) pParam, EM_SETSEL, (WPARAM) 65534, 65535);
	::SendMessage((HWND) pParam, EM_REPLACESEL, (WPARAM) 0, (LPARAM) text1);
}
