//=================================================================
//
//        eCosTestSocket.cpp
//
//        Socket test class
//
//=================================================================
//####COPYRIGHTBEGIN####

// -------------------------------------------
// The contents of this file are subject to the Cygnus eCos Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://sourceware.cygnus.com/ecos
// 
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
// License for the specific language governing rights and limitations under
// the License.
// 
// The Original Code is eCos - Embedded Cygnus Operating System, released
// September 30, 1998.
// 
// The Initial Developer of the Original Code is Cygnus.  Portions created
// by Cygnus are Copyright (C) 1998, 1999 Cygnus Solutions.
// All Rights Reserved.
// -------------------------------------------
//
//####COPYRIGHTEND####
//=================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):     sdf
// Contributors:  sdf
// Date:          1999-04-01
// Description:   This class abstracts tcp/ip sockets for use in the testing infrastructure
// Usage:
//
//####DESCRIPTIONEND####

#include "stdafx.h"
#include "eCosTestSocket.h"
#include "eCosTestSerial.h"

enum {ERR_TIMEOUT=20000, ERR_READ_AFTER_CLOSE=20001};

int CeCosTestSocket::nDefaultSocketTimeout=10*1000; // milliseconds

// Non-blocking read on one or other of the data sources:
// Result:  0 - error occurred
//          1 - data read from socket
//          2 - data read from serial
int SSRead (CeCosTestSerial &serial,CeCosTestSocket &socket,void *pBuf,unsigned int nSize,unsigned int &nRead)
{
    int rc=0;
    bool bBlocking=serial.GetBlockingReads();
    bool bBlockingModified=false;
    for(;;){
        CeCosTestUtils::Time ft0=CeCosTestUtils::Time::Now();
        if(!socket.Peek(nRead)){
            break;
        } else if(nRead){
            nRead=min(nRead,nSize);
            rc=socket.recv(pBuf,nRead)?1:0;
            break;
        } else {
            if(bBlocking){
                serial.SetBlockingReads(false);
                bBlockingModified=true;
                bBlocking=false;
            }
            if(serial.Read(pBuf,nSize,nRead)){
                if(nRead>0){
                    rc=2;
                    break;
                }
            } else {
                break;
            }
        }
        CeCosTestUtils::Sleep(1);
    }
    if(bBlockingModified){
        serial.SetBlockingReads(true);
    }
    return rc;
}

// ctors and dtors

CeCosTestSocket::CeCosTestSocket ():
	m_nSock(-1),
    m_nClient(0)
{
}

CeCosTestSocket::CeCosTestSocket (int sock /*result of previous call of Listen*/, bool *pbStop):
	m_nSock(-1),
    m_nClient(0)
{
    Accept(sock,pbStop);
}

CeCosTestSocket::CeCosTestSocket (CeCosTestUtils::String strHost,int port,Duration dTimeout):
	m_nSock(-1),
    m_nClient(0)
{
    Connect(strHost,port,dTimeout);
}

bool CeCosTestSocket::Accept(int sock /*result of previous call of Listen*/, bool *pbStop)
{
    m_nSock=-1;
    while(-1==m_nSock && (0==pbStop||!*pbStop)){
        struct sockaddr cli_addr;
        #ifndef _WIN32
        unsigned 
        #endif
	    int clilen=sizeof(struct sockaddr);
        m_nSock=::accept(sock, (struct sockaddr *) &cli_addr, &clilen);
        SaveError();
        if(-1==m_nSock){ 
            if(WOULDBLOCK==SocketError()){
                CeCosTestUtils::Sleep(100);
                continue;
            }
        } 

        unsigned char ip[4];
        memcpy(ip,cli_addr.sa_data+2,4);
        memcpy(&m_nClient,ip,4);
        
        struct hostent *he=::gethostbyaddr((char *)ip,4,AF_INET);
        SaveError();
        if(he){
            TRACE("Connection accepted from %s - socket %d\n",he->h_name,m_nSock);
        } else {
            TRACE("Connection accepted from %u.%u.%u.%u\n - socket %d",ip[0],ip[1],ip[2],ip[3],m_nSock);
        }
        SetSocketOptions();
	} 
    m_nOpenrc=0;
    return -1!=m_nSock;
}

int CeCosTestSocket::Listen(int nTcpPort)
{
   // Create socket
    int sock=::socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        TRACE("Couldn't create socket\n");
    } else {
        TRACE("Created socket %d listening on port %d\n",sock,nTcpPort);
        // Bind socket to address
        struct sockaddr_in serv_addr;
        memset(&serv_addr, 0, sizeof serv_addr);

        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port=::htons((short)nTcpPort);
        serv_addr.sin_addr.s_addr = INADDR_ANY;
        
        if (::bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
            TRACE("Couldn't bind socket on port %d\n",nTcpPort);
            CloseSocket(sock);
        } else if (-1==::listen(sock, 5)){
            CloseSocket(sock);
            TRACE("socket error on listen - port %d\n",nTcpPort);
        } else {
	        #ifdef _WIN32
            int nTrue=1;
            bool rc=(0==::ioctlsocket(sock, FIONBIO, (unsigned long *)&nTrue));
	        #else
            int flags=::fcntl(sock,F_GETFL);
	        flags|=O_NONBLOCK;
            bool rc=(0==::fcntl (sock, F_SETFL, flags));
	        #endif
	        if(!rc){
                TRACE("Failed to set socket options on socket %d\n",sock);
            }
        }
    }
    return sock;
}

bool CeCosTestSocket::Connect(CeCosTestUtils::String strHost,int port,Duration dTimeout)
{
    struct sockaddr_in serv_addr;

    struct hostent* host_dat;

    char ip[16]; // for nnn.nnn.nnn.nnn
    m_nOpenrc=0;

    VTRACE("Connect: %s:%d timeout=%d\n",(const char *)strHost,port,dTimeout);

    // Get the target host address
    CeCosTestUtils::String strErr;
    if (0==(host_dat=::gethostbyname((const char *)strHost))) {
        SaveError();
        TRACE("Could not get IP address for host %s - %s\n",(const char *)strHost,(const char *)SocketErrString());
        m_nOpenrc=SocketError();
    } else {
        const char *c=(char*)inet_ntoa( *( (struct in_addr *)host_dat->h_addr_list[0] )  );
        if(0==c){
            SaveError();
            TRACE("Could inet_ntoa %s - %s\n",(const char *)strHost,(const char *)SocketErrString());
            m_nOpenrc=SocketError();
        } else {

            strcpy(ip, c);
            memset(&serv_addr, 0, sizeof serv_addr);

            // Create socket
            m_nSock = ::socket(AF_INET, SOCK_STREAM, 0);
            if (-1 == m_nSock) {
                TRACE("Could not create socket [%s:%d]\n",(const char *)strHost,port);
                m_nOpenrc=SocketError();
            } else {
                #ifdef _WIN32
                SetSocketOptions();
                #endif
                TRACE("Created socket %d connected to %s:%d\n",m_nSock,(const char *)strHost,port);
                // Bind socket to address
                serv_addr.sin_family = AF_INET;
                serv_addr.sin_port=::htons((short)port);
                SaveError();
                serv_addr.sin_addr.s_addr = inet_addr(ip);

                // Connect to server
                VTRACE("Connect() : connecting to server\n");
                int cc=::connect(m_nSock, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
                SaveError();
                CeCosTestUtils::String strMsg;
                if(-1==cc){
                    m_nOpenrc=SocketError();
                    if(
    #ifdef _WIN32
                       WOULDBLOCK==m_nOpenrc
    #else
                       EINPROGRESS==m_nOpenrc
    #endif
                       ){
                        // Allow 5 seconds for connect to complete
                        fd_set set;
                        FD_ZERO(&set);
                        FD_SET((unsigned)m_nSock, &set);
                        struct timeval tv;
                        tv.tv_sec = dTimeout/1000;
                        tv.tv_usec = 0;
                        switch(::select(m_nSock, NULL, &set , NULL, &tv)){
                            case 0:
                                SaveError();
                                strMsg.Format("attempt timed out after %d seconds",dTimeout/1000);
                                m_nOpenrc=-1;
                                cc=-1;
                                break;
                            case -1:
                                SaveError();
                                m_nOpenrc=SocketError();
                                strMsg=SocketErrString();
                                break;
                            default:
                                m_nOpenrc=0;
                                cc=0;
                        }
                    } else {
                        strMsg=SocketErrString();
                    }
                }

                if(-1==cc){
                    TRACE("Could not connect to %s:%d - %s\n",(const char *)strHost,port,(const char *)strMsg);
                    CloseSocket(m_nSock);
                }  else {
                    #ifndef _WIN32
                    SetSocketOptions();
                    #endif
                }
            }
        }
    }
    return -1!=m_nSock;
}

bool CeCosTestSocket::sendrecv(bool bSend,const void *pData,unsigned int nLength,
	const char *pszMsg,CeCosTestSocket::Duration dTimeout,CeCosTestSocket::StopFunc pFnStop,void *pParam)
{
	const char *pszSR=(bSend?"sending":"receiving");
//TRACE("%s %s [%d bytes]\n",pszSR,pszMsg,nLength);
    char *c=(char *)pData;
    CeCosTestUtils::Time ft0=CeCosTestUtils::Time::Now();
    int nTodo=nLength;
    enum { RETRYDELAY=100 };
    while((nTodo>0) && ((0==pFnStop) || (!pFnStop(pParam)))){
        int s=bSend?::send(m_nSock, c, nTodo, 0): ::recv(m_nSock, c, nTodo, 0);
        if(0==s && !bSend){
            m_nErr=ERR_READ_AFTER_CLOSE;
        } else {
            SaveError();
        }
        if(-1==s && WOULDBLOCK==SocketError()){
            CeCosTestUtils::Duration d=CeCosTestUtils::Time::Now()-ft0;
            if(d>dTimeout){
				TRACE("%d/%d mSec timeout on socket %d %s %s - processed %d/%d bytes\n" ,
                    d,dTimeout,m_nSock,pszSR,pszMsg, 
                    nLength-nTodo,nLength);
                m_nErr=ERR_TIMEOUT;
				break;
			}
            CeCosTestUtils::Sleep(100);
       } else if (s>0) {
			c+=s;
			nTodo-=s;
            ft0=CeCosTestUtils::Time::Now();
        } else {
			TRACE("Error on socket %d %s %s - %s\n" ,m_nSock, pszSR, pszMsg, (const char *)SocketErrString());
			break;
		}
    }
//TRACE("Done %s %s rc=%d\n",pszSR,pszMsg,0==nTodo);
    return 0==nTodo;
}

// Graceful socket closedown
CeCosTestSocket::~CeCosTestSocket()
{
	CloseSocket(m_nSock);
}

bool CeCosTestSocket::CloseSocket(int &sock)
{
    if(-1!=sock){
TRACE("Closing socket %d\n",sock);
        shutdown(sock,0);// SD_BOTH
#ifdef _WIN32
        closesocket(sock);
#else
        close(sock);
#endif
        sock=-1;
    }
	return true;
}

bool CeCosTestSocket::SetSocketOptions()
{
    bool rc;
	#ifdef _WIN32
    int nTrue=1;
    rc=(0==::ioctlsocket(m_nSock, FIONBIO, (unsigned long *)&nTrue));
    SaveError();
	#else
    int flags=::fcntl(m_nSock,F_GETFL);
    SaveError();
	flags|=O_NONBLOCK;
    rc=(0==::fcntl (m_nSock, F_SETFL, flags));
    SaveError();
	#endif
    //LINGER linger;
    //setsockopt(m_nSock,SOL_SOCKET,SO_LINGER,(const char *)linger, sizeof(LINGER));
	if(!rc){
        TRACE("Failed to set socket options socket %d - %s\n",m_nSock,(const char *)SocketErrString());
    }
TRACE("SetSocketOptions %d rc=%d\n",m_nSock,rc);
    return rc;
}

CeCosTestUtils::String CeCosTestSocket::SocketErrString()
{
    CeCosTestUtils::String str;
    int nErr;
    #ifdef _WIN32
    nErr=WSAGetLastError();
    switch(nErr){
        case ERR_TIMEOUT: str="Read operation timed out";break;
        case ERR_READ_AFTER_CLOSE: str="Read operation after socket closed";break;

        case WSAEACCES: str="Permission denied";break;
        case WSAEADDRINUSE: str="Address already in use";break;
        case WSAEADDRNOTAVAIL: str="Cannot assign requested address";break;
        case WSAEAFNOSUPPORT: str="Address family not supported by protocol family";break;
        case WSAEALREADY: str="Operation already in progress";break;
        case WSAECONNABORTED: str="Software caused connection abort";break;
        case WSAECONNREFUSED: str="Connection refused";break;
        case WSAECONNRESET: str="Connection reset by peer";break;
        case WSAEDESTADDRREQ: str="Destination address required";break;
        case WSAEFAULT: str="Bad address";break;
        case WSAEHOSTDOWN: str="Host is down";break;
        case WSAEHOSTUNREACH: str="No route to host";break;
        case WSAEINPROGRESS: str="Operation now in progress";break;
        case WSAEINTR: str="Interrupted function call";break;
        case WSAEINVAL: str="Invalid argument";break;
        case WSAEISCONN: str="Socket is already connected";break;
        case WSAEMFILE: str="Too many open files";break;
        case WSAEMSGSIZE: str="Message too long";break;
        case WSAENETDOWN: str="Network is down";break;
        case WSAENETRESET: str="Network dropped connection on reset";break;
        case WSAENETUNREACH: str="Network is unreachable";break;
        case WSAENOBUFS: str="No buffer space available";break;
        case WSAENOPROTOOPT: str="Bad protocol option";break;
        case WSAENOTCONN: str="Socket is not connected";break;
        case WSAENOTSOCK: str="Socket operation on non-socket";break;
        case WSAEOPNOTSUPP: str="Operation not supported";break;
        case WSAEPFNOSUPPORT: str="Protocol family not supported";break;
        case WSAEPROCLIM: str="Too many processes";break;
        case WSAEPROTONOSUPPORT: str="Protocol not supported";break;
        case WSAEPROTOTYPE: str="Protocol wrong type for socket";break;
        case WSAESHUTDOWN: str="Cannot send after socket shutdown";break;
        case WSAESOCKTNOSUPPORT: str="Socket type not supported";break;
        case WSAETIMEDOUT: str="Connection timed out";break;
        case WSATYPE_NOT_FOUND: str="Class type not found";break;
        case WSAEWOULDBLOCK: str="Resource temporarily unavailable";break;
        case WSAHOST_NOT_FOUND: str="Host not found";break;
        case WSA_INVALID_HANDLE: str="Specified event object handle is invalid";break;
        case WSA_INVALID_PARAMETER: str="One or more parameters are invalid";break;
        //case WSAINVALIDPROCTABLE: str="Invalid procedure table from service provider";break;
        //case WSAINVALIDPROVIDER: str="Invalid service provider version number";break;
        case WSA_IO_INCOMPLETE: str="Overlapped I/O event object not in signaled state";break;
        case WSA_IO_PENDING: str="Overlapped operations will complete later";break;
        case WSA_NOT_ENOUGH_MEMORY: str="Insufficient memory available";break;
        case WSANOTINITIALISED: str="Successful case WSAStartup not yet:performed";break;
        case WSANO_DATA: str="Valid name, no data record of requested type";break;
        case WSANO_RECOVERY: str="This is a non-recoverable error";break;
        //case WSAPROVIDERFAILEDINIT: str="Unable to initialize a service provider";break;
        case WSASYSCALLFAILURE: str="System call failure";break;
        case WSASYSNOTREADY: str="Network subsystem is unavailable";break;
        case WSATRY_AGAIN: str="Non-authoritative host not found";break;
        case WSAVERNOTSUPPORTED: str="WINSOCK.DLL version out of range";break;
        case WSAEDISCON: str="Graceful shutdown in progress";break;
        case WSA_OPERATION_ABORTED: str="Overlapped operation aborted";break;
        default:
            str.Format("Unknown error %d (0x%08x)",nErr,nErr);
    }
#else
   switch(errno){
        case ERR_TIMEOUT: str="Read operation timed out";break;
        case ERR_READ_AFTER_CLOSE: str="Read operation after socket closed";break;
        default:
            str=strerror(errno);
   }
#endif
    return str;
}

bool CeCosTestSocket::sendInteger(int n,const char *pszMsg,CeCosTestUtils::Duration dTimeout)
{
    // This has to be more sophisticated if we support cross-architectural (endianness)
    return send (&n, sizeof n,pszMsg,dTimeout);
}

bool CeCosTestSocket::recvInteger(int & n,const char *pszMsg,CeCosTestUtils::Duration dTimeout)
{
    // This has to be more sophisticated if we support cross-architectural (endianness)
    return recv (&n, sizeof n,pszMsg,dTimeout);
}


bool CeCosTestSocket::recvString  (CeCosTestUtils::String &str,const char * const pszMsg,Duration dTimeout)
{
    int nLength;
    bool rc=false;
    if(recvInteger(nLength,pszMsg,dTimeout)){
        rc=(0==nLength || recv(str.GetBuffer(nLength),nLength,pszMsg,dTimeout));
        str.ReleaseBuffer();
    }
    return rc;
}


bool CeCosTestSocket::sendString  (const char *pszString,const char * const pszMsg,Duration dTimeout)
{
    int nLength=strlen(pszString);
    return sendInteger(nLength,pszMsg,dTimeout) && (0==nLength || send(pszString,nLength,pszMsg,dTimeout));
}


// Give indication of bytes available to be read (but don't read them)
bool CeCosTestSocket::Peek (unsigned int &nAvail)
{
    char buf[8192];
    int n=::recv(m_nSock, buf, sizeof buf, MSG_PEEK);
    nAvail=0;
    bool rc=false;
    switch(n) {
        case -1:
            SaveError();
            if(WOULDBLOCK==SocketError()){
                rc=true; // nAvail stays==0
            } else {
                ERROR("Peek: err=%d %s\n",SocketError(),(const char *)SocketErrString());
            }
            break;
        case 0:
            m_nErr=ERR_READ_AFTER_CLOSE;
            break;
        default:
            rc=true;
            nAvail=n;
    }
    return rc;
}

// Connect tcp/ip port and serial port together.
// Traffic is passed through pFunc, passed parameter pParam.
// The pFunc function:
//    may reallocate pBuf (using malloc/realloc etc...)
//    must leave pBuf allocated on exit
//    should not close either serial or socket
//    should leave writing to its caller
//    should return false if it wishes to terminate the connection (after caller has written output)
bool ConnectSocketToSerial (
    int nListenSock,const char *pszPort, int nBaud,
    FilterFunc *pSerialToSocketFilterFunc/*=0*/,FilterFunc *pSocketToSerialFilterFunc/*=0*/,void *pParam/*=0*/,
    bool *pbStop)
{

    TRACE("ConnectSocketToSerial : socket %d <--> %s\n",nListenSock,pszPort);

    CeCosTestSerial serial;
    serial.SetBlockingReads(false);
    bool rc=false;
    // Open serial device.
    if (!serial.Open(pszPort,nBaud)){
        ERROR("Couldn't open port %s\n",pszPort);
    } else {
        // Flush the serial buffer.
        serial.Flush();

        TRACE("ConnectSocketToSerial: waiting for connection...\n");
        CeCosTestSocket socket;
        if(!socket.Accept(nListenSock,pbStop)){
            ERROR("ConnectSocketToSerial - couldn't accept\n");
        } else {    
            serial.ClearError();
            enum {BUFSIZE=8192};
            void *pBuf=malloc(BUFSIZE);
            TRACE("ConnectSocketToSerial: connected\n");
            rc=true;
            while(rc && (0==pbStop || !(*pbStop))){
                unsigned int nRead=0;
                switch(SSRead (serial,socket,pBuf,BUFSIZE,nRead)){
                    case 2:
                        VTRACE("Serial:%d\n",nRead);
                        if(pSerialToSocketFilterFunc){
                            rc=pSerialToSocketFilterFunc(pBuf,nRead,serial,socket,pParam);
                        }
                        if(nRead && !socket.send(pBuf,nRead)){
                            TRACE("Failed to write to socket\n");
                            rc=false;
                        }
                        break;
                    case 1:
                        unsigned int nWritten;
                        VTRACE("Socket:%d\n",nRead);
                        if(pSocketToSerialFilterFunc){
                            rc=pSocketToSerialFilterFunc(pBuf,nRead,serial,socket,pParam);
                        }
                        {
                            char *c=(char *)pBuf;
                            int nToWrite=nRead;
                            while(nToWrite>0){
                                if(!serial.Write(pBuf,nRead,nWritten)){
                                    TRACE("Failed to write to serial\n");
                                    rc=false;
                                    break;
                                }
                                nToWrite-=nWritten;
                                c+=nWritten;
                            }
                        }
                        break;
                    case 0:
                        TRACE("SSRead returned error indication\n");
                        rc=false;
                        break;
                        // Error
                }
            }
            free(pBuf);
        }
    }
    TRACE("ConnectSocketToSerial : done\n");
    return rc;
}
    
