#include "precomp.h"
DEBUG_FILEZONE(ZONE_T120_MSMCSTCP);
/*	Socket.cpp
 *
 *	Copyright (c) 1996 by Microsoft Corporation
 *
 *	Abstract:
 *		This is the implementation of our socket constructor/destructor functions.
 *
 */
#include "socket.h"
#include "plgxprt.h"

/* Size of listen queue */
#define	LISTEN_QUEUE_SIZE	3

/* External definitions */
extern HWND					TCP_Window_Handle;
extern PTransportInterface	g_Transport;

/*	
 *	void CreateAndConfigureListenSocket (VOID)
 *	
 *	Functional Description
 *		This function sets up a listening socket.
 *	returns INVALID_SOCKET if there is any error.
 */
SOCKET CreateAndConfigureListenSocket (VOID)
{
	SOCKADDR_IN		socket_control;
	SOCKET Socket;

	// Create the listening socket.
	Socket = socket (AF_INET, SOCK_STREAM, 0);

	if (Socket == INVALID_SOCKET) {
		WARNING_OUT (("Socket: error creating listening socket (errno = %d)", WSAGetLastError()));
		goto Error;
	}

	// The listen socket only waits for FD_ACCEPT msgs.
	ASSERT(TCP_Window_Handle);
	if (WSAAsyncSelect (Socket,
						TCP_Window_Handle,
						WM_SOCKET_NOTIFICATION,
						FD_ACCEPT) != 0)	{

		WARNING_OUT (("CreateAndConfigureListenSocket: Error on WSAAsyncSelect = %d", WSAGetLastError()));
		goto Error;
	}
	
	/*
	 * Load the socket control structure with the parameters necessary.
	 *	- Internet socket
	 *	- Let it assign any address to this socket
	 *	- Assign our port number
	 */
	socket_control.sin_family = AF_INET;
	socket_control.sin_addr.s_addr = INADDR_ANY;
	socket_control.sin_port = htons ( DEFAULT_LISTEN_PORT );

	/* Issue the bind call */
	if (bind (Socket, (LPSOCKADDR) &socket_control, sizeof(SOCKADDR_IN)) != 0) {
		WARNING_OUT (("Socket::Listen: bind failed:  Unable to use WinSock"));
		goto Error;
	}

	/*
	 * Issue a listen to WinSock to tell it we are willing to accept calls.
	 * This is a non-blocking listen, therefore we will receive FD_ACCEPT
	 * if someone is trying to call us.
	 */
	if (listen (Socket, LISTEN_QUEUE_SIZE) != 0) {
		WARNING_OUT (("Socket::Listen: listen failed:  Unable to use WinSock"));
		goto Error;
	}
	ASSERT(Socket != INVALID_SOCKET);

	return Socket;

Error:

    if (INVALID_SOCKET != Socket)
    {
        ::closesocket(Socket);
    }

	return INVALID_SOCKET;
}


/*
 *	PSocket	newSocket (SOCKET socket_number)
 *
 *	Functional Description:
 *		This is a constructor for the Socket object.  It allocates the
 *		send and receive buffers and sets up internal variables.
 */
PSocket	newSocket(TransportConnection XprtConn, PSecurityContext pSC)
{
    if (IS_SOCKET(XprtConn))
    {
        return ::newSocketEx(XprtConn, pSC);
    }
    return g_pSocketList->FindByTransportConnection(XprtConn, TRUE);
}


PSocket	newPluggableSocket(TransportConnection XprtConn)
{
    PSocket pSocket = ::newSocketEx(XprtConn, NULL);
    if (NULL != pSocket)
    {
    	g_pSocketList->SafeAppend(pSocket);
    }
    return pSocket;
}


PSocket	newSocketEx(TransportConnection XprtConn, PSecurityContext pSC)
{
    BOOL fRet;
	DBG_SAVE_FILE_LINE
	PSocket pSocket = new CSocket(&fRet, XprtConn, pSC);
	if (NULL != pSocket)
	{
	    if (fRet)
	    {
	        return pSocket;
	    }
	    pSocket->Release();
	}
	ERROR_OUT(("newSocket: Unable to allocate memory for Socket struct, pSocket=0x%x", pSocket));
	return NULL;
}


CSocket::CSocket(BOOL *_pfRet, TransportConnection _XprtConn, PSecurityContext _pSC)
:
    CRefCount(MAKE_STAMP_ID('S','o','c','k')),
    State(IS_SOCKET(_XprtConn) ? NOT_CONNECTED : SOCKET_CONNECTED),
    SecState((NULL == _pSC) ? SC_UNDETERMINED : SC_SECURE),
    pSC(_pSC),
    Max_Packet_Length(DEFAULT_MAX_X224_SIZE),
    Current_Length(0),
    Data_Indication_Buffer(NULL),
    Data_Indication_Length(0),
    Read_State(READ_HEADER),
    X224_Length(0),
    bSpaceAllocated(FALSE),
    Data_Memory(NULL),
    fExtendedX224(FALSE),
    fIncomingSecure(FALSE),
    XprtConn(_XprtConn)
{
    // assume failure
    *_pfRet = FALSE;

    // zero out sub structures
    ::ZeroMemory(&X224_Header, sizeof(X224_Header));
    ::ZeroMemory(&Retry_Info, sizeof(Retry_Info));
	Remote_Address[0] = '\0';

    if (IS_SOCKET(XprtConn))
    {
    	if (INVALID_SOCKET == XprtConn.nLogicalHandle)
    	{
    		/* Create a STREAM socket (fully reliable, full duplex, and sequenced) */
    		if ((XprtConn.nLogicalHandle = ::socket(AF_INET, SOCK_STREAM, 0))
    		    == INVALID_SOCKET)
    		{
    			ERROR_OUT (("CSocket: error acquiring INET socket # (errno = %d)", WSAGetLastError()));
    			return;
    		}
    	}

    	/* Enable Tx and Rx messages to the window */
    	ASSERT(TCP_Window_Handle);
    	if (::WSAAsyncSelect(XprtConn.nLogicalHandle, TCP_Window_Handle,
    	        WM_SOCKET_NOTIFICATION, 
    			FD_READ | FD_WRITE | FD_CLOSE | FD_CONNECT) != 0)
        {
    		WARNING_OUT (("CSocket: Error on WSAAsyncSelect = %d", WSAGetLastError()));
    	}
	}
    else
    {
        ASSERT(IS_PLUGGABLE(XprtConn));
        CPluggableConnection *p = ::GetPluggableConnection(this);
        if (NULL == p)
        {
    		ERROR_OUT(("newSocket: Unable to find plugable transport (%d, %d)",
    		        XprtConn.eType, XprtConn.nLogicalHandle));
    		return;
        }
    }

    // success
    *_pfRet = TRUE;
}


/*
 *	void freeSocket (PSocket, TransportConnection)
 *
 *	Functional Description:
 *		This is a destructor for the Socket object.  It frees the send
 *		and receive buffers and connection structure.
 *		It will also cleanup the listening socket. In this case, 
 *		"pSocket" is set to NULL and "trash_packets" should be set to TRUE.
 */
void freeSocket(PSocket pSocket, TransportConnection XprtConn)
{
    if (IS_SOCKET(XprtConn))
    {
        if (NULL != g_pSocketList)
        {
            g_pSocketList->SafeRemove(pSocket);
        }
        freeSocketEx(pSocket, XprtConn);
    }
}


void freeListenSocket(TransportConnection XprtConn)
{
    ASSERT(IS_SOCKET(XprtConn));
    freeSocketEx(NULL, XprtConn);
}


void freePluggableSocket(PSocket pSocket)
{
    freeSocketEx(pSocket, pSocket->XprtConn);
    if (NULL != g_pSocketList)
    {
        g_pSocketList->SafeRemove(pSocket);
    }
}


void freeSocketEx(PSocket pSocket, TransportConnection XprtConn)
{
	// Either "pSocket" is NULL, or the socket is not invalid.
    #ifdef _DEBUG
    if (IS_SOCKET(XprtConn))
    {
        if (NULL != pSocket)
        {
	        ASSERT(INVALID_SOCKET != pSocket->XprtConn.nLogicalHandle);
	    }
	    else
	    {
	        // it is a listen socket
	        ASSERT(INVALID_SOCKET != XprtConn.nLogicalHandle);
	    }
	}
	#endif

	// Determine the socket number to use... Either the socket is the
	// socket indicated in the PSocket structure, or it is a structure-less
	// listen socket. Note: both cannot be valid!

    if (IS_SOCKET(XprtConn))
    {
    	SOCKET socket = (pSocket) ? pSocket->XprtConn.nLogicalHandle : XprtConn.nLogicalHandle;
        XprtConn.nLogicalHandle = socket;

    	/* Disable notifications to our window */
    	if (::IsWindow(TCP_Window_Handle))
    	{
    	    ::WSAAsyncSelect(socket, TCP_Window_Handle, 0, 0);
    	}
    }

	if (pSocket != NULL)
	{
	    pSocket->Release();
	}
	else
	{
		// This is the listening socket
		::ShutdownAndClose (XprtConn, FALSE, 0);
	}
}


CSocket::~CSocket(void)
{
	switch (State)
	{
	case SOCKET_CONNECTED:
	// case WAITING_FOR_DISCONNECT:
		/* All physically connected states issue a shutdown() first */
		::ShutdownAndClose(XprtConn, TRUE, SD_BOTH);
		break;

	case X224_CONNECTED:
		// Shutdown disable reception only.
		::ShutdownAndClose(XprtConn, TRUE, SD_RECEIVE);
		break;

	default:
		::ShutdownAndClose(XprtConn, FALSE, 0);
		break;
	}

	/* Free the structures */
	FreeTransportBuffer();
	delete pSC;
}


void CSocket::FreeTransportBuffer(void)
{
    if (NULL != Data_Memory)
    {
        ::FreeMemory(Data_Memory);
        Data_Memory = NULL;
        Data_Indication_Buffer = NULL;
    }
}



void CSocketList::SafeAppend(PSocket pSocket)
{
    ::EnterCriticalSection(&g_csTransport);
    if (! Find(pSocket))
    {
        Append(pSocket);
    }
    ::LeaveCriticalSection(&g_csTransport);
}


BOOL CSocketList::SafeRemove(PSocket pSocket)
{
    ::EnterCriticalSection(&g_csTransport);
    BOOL fRet = Remove(pSocket);
    ::LeaveCriticalSection(&g_csTransport);
    return fRet;
}


PSocket CSocketList::FindByTransportConnection(TransportConnection XprtConn, BOOL fNoAddRef)
{
    PSocket pSocket;
    ::EnterCriticalSection(&g_csTransport);
    Reset();
    while (NULL != (pSocket = Iterate()))
    {
        if (IS_SAME_TRANSPORT_CONNECTION(pSocket->XprtConn, XprtConn))
        {
            if (! fNoAddRef)
            {
                pSocket->AddRef();
            }
            break;
        }
    }
    ::LeaveCriticalSection(&g_csTransport);
    return pSocket;
}


PSocket CSocketList::RemoveByTransportConnection(TransportConnection XprtConn)
{
    PSocket pSocket;
    ::EnterCriticalSection(&g_csTransport);
    Reset();
    while (NULL != (pSocket = Iterate()))
    {
        if (IS_SAME_TRANSPORT_CONNECTION(pSocket->XprtConn, XprtConn))
        {
            Remove(pSocket);
            break;
        }
    }
    ::LeaveCriticalSection(&g_csTransport);
    return pSocket;
}