/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS WinSock 2 API
 * FILE:        dll/win32/ws2_32_new/src/sockctrl.c
 * PURPOSE:     Socket Control/State Support
 * PROGRAMMER:  Alex Ionescu (alex@relsoft.net)
 */

/* INCLUDES ******************************************************************/

#include <ws2_32.h>

#define NDEBUG
#include <debug.h>

/* FUNCTIONS *****************************************************************/

/*
 * @implemented
 */
INT
WSAAPI
connect(IN SOCKET s,
        IN CONST struct sockaddr *name,
        IN INT namelen)
{
    PWSPROCESS Process;
    PWSTHREAD Thread;
    PWSSOCKET Socket;
    INT ErrorCode, OldErrorCode = ERROR_SUCCESS;
    INT Status;
    BOOLEAN TryAgain = TRUE;
    DPRINT("connect: %lx, %p, %lx\n", s, name, namelen);

    /* Enter prolog */
    ErrorCode = WsApiProlog(&Process, &Thread);
    if (ErrorCode == ERROR_SUCCESS)
    {
        /* Get the Socket Context */
        if ((Socket = WsSockGetSocket(s)))
        {
            while (TRUE)
            {
                /* Make the call */
                Status = Socket->Provider->Service.lpWSPConnect(s,
                                                                name,
                                                                namelen,
                                                                NULL,
                                                                NULL,
                                                                NULL,
                                                                NULL,
                                                                &ErrorCode);

                /* Check if error code was due to the host not being found */
                if ((Status == SOCKET_ERROR) &&
                    ((ErrorCode == WSAEHOSTUNREACH) ||
                     (ErrorCode == WSAENETUNREACH)))
                {
                    /* Check if we can try again */
                    if (TryAgain)
                    {
                        /* Save the old error code */
                        OldErrorCode = ErrorCode;

                        /* Make sure we don't retry 3 times */
                        TryAgain = FALSE;

                        /* Make the RAS Auto-dial attempt */
                        if (WSAttemptAutodialAddr(name, namelen)) continue;
                    }
                    else
                    {
                        /* Restore the error code */
                        ErrorCode = OldErrorCode;
                    }
                }

                /* Break out of the loop */
                break;
            }

            /* Deference the Socket Context */
            WsSockDereference(Socket);

            /* Return Provider Value */
            if (Status == ERROR_SUCCESS) return Status;

            /* If everything seemed fine, then the WSP call failed itself */
            if (ErrorCode == NO_ERROR) ErrorCode = WSASYSCALLFAILURE;
        }
        else
        {
            /* No Socket Context Found */
            ErrorCode = WSAENOTSOCK;
        }
    }

    /* If this is Winsock 1.1, normalize the error code */
    if ((ErrorCode == WSAEALREADY) && (LOBYTE(Process->Version) == 1))
    {
        /* WS 1.1 apps expect this */
        ErrorCode = WSAEINVAL;
    }

    /* Return with an Error */
    SetLastError(ErrorCode);
    return SOCKET_ERROR;
}

/*
 * @implemented
 */
INT
WSAAPI
listen(IN SOCKET s,
       IN INT backlog)
{
    PWSSOCKET Socket;
    INT Status;
    INT ErrorCode;
    DPRINT("connect: %lx, %lx\n", s, backlog);

    /* Check for WSAStartup */
    if ((ErrorCode = WsQuickProlog()) == ERROR_SUCCESS)
    {
        /* Get the Socket Context */
        if ((Socket = WsSockGetSocket(s)))
        {
            /* Make the call */
            Status = Socket->Provider->Service.lpWSPListen(s,
                                                           backlog,
                                                           &ErrorCode);
            /* Deference the Socket Context */
            WsSockDereference(Socket);

            /* Return Provider Value */
            if (Status == ERROR_SUCCESS) return Status;

            /* If everything seemed fine, then the WSP call failed itself */
            if (ErrorCode == NO_ERROR) ErrorCode = WSASYSCALLFAILURE;
        }
        else
        {
            /* No Socket Context Found */
            ErrorCode = WSAENOTSOCK;
        }
    }

    /* Return with an Error */
    SetLastError(ErrorCode);
    return SOCKET_ERROR;
}

/*
 * @implemented
 */
INT
WSAAPI
getpeername(IN SOCKET s,
            OUT LPSOCKADDR name,
            IN OUT INT FAR* namelen)
{
    PWSSOCKET Socket;
    INT Status;
    INT ErrorCode;
    DPRINT("getpeername: %lx, %p, %lx\n", s, name, namelen);

    /* Check for WSAStartup */
    if ((ErrorCode = WsQuickProlog()) == ERROR_SUCCESS)
    {
        /* Get the Socket Context */
        if ((Socket = WsSockGetSocket(s)))
        {
            /* Make the call */
            Status = Socket->Provider->Service.lpWSPGetPeerName(s,
                                                                name,
                                                                namelen,
                                                                &ErrorCode);
            /* Deference the Socket Context */
            WsSockDereference(Socket);

            /* Return Provider Value */
            if (Status == ERROR_SUCCESS) return Status;

            /* If everything seemed fine, then the WSP call failed itself */
            if (ErrorCode == NO_ERROR) ErrorCode = WSASYSCALLFAILURE;
        }
        else
        {
            /* No Socket Context Found */
            ErrorCode = WSAENOTSOCK;
        }
    }

    /* Return with an Error */
    SetLastError(ErrorCode);
    return SOCKET_ERROR;
}

/*
 * @implemented
 */
INT
WSAAPI
getsockname(IN SOCKET s,
            OUT LPSOCKADDR name,
            IN OUT INT FAR* namelen)
{
    PWSSOCKET Socket;
    INT Status;
    INT ErrorCode;
    DPRINT("getsockname: %lx, %p, %lx\n", s, name, namelen);

    /* Check for WSAStartup */
    if ((ErrorCode = WsQuickProlog()) == ERROR_SUCCESS)
    {
        /* Get the Socket Context */
        if ((Socket = WsSockGetSocket(s)))
        {
            /* Make the call */
            Status = Socket->Provider->Service.lpWSPGetSockName(s,
                                                                name,
                                                                namelen,
                                                                &ErrorCode);

            /* Deference the Socket Context */
            WsSockDereference(Socket);

            /* Return Provider Value */
            if (Status == ERROR_SUCCESS) return Status;

            /* If everything seemed fine, then the WSP call failed itself */
            if (ErrorCode == NO_ERROR) ErrorCode = WSASYSCALLFAILURE;
        }
        else
        {
            /* No Socket Context Found */
            ErrorCode = WSAENOTSOCK;
        }
    }

    /* Return with an Error */
    SetLastError(ErrorCode);
    return SOCKET_ERROR;
}

/*
 * @implemented
 */
INT
WSAAPI
getsockopt(IN SOCKET s,
           IN INT level,
           IN INT optname,
           OUT CHAR FAR* optval,
           IN OUT INT FAR* optlen)
{
    PWSPROCESS Process;
    PWSTHREAD Thread;
    PWSSOCKET Socket;
    INT ErrorCode;
    INT Status;
    WSAPROTOCOL_INFOW ProtocolInfo;
    PCHAR OldOptVal = NULL;
    INT OldOptLen = 0;
    DPRINT("getsockopt: %lx, %lx, %lx\n", s, level, optname);

    /* Enter prolog */
    if ((ErrorCode = WsApiProlog(&Process, &Thread)) == ERROR_SUCCESS)
    {
        /* Check if we're getting the open type */
        if ((level == SOL_SOCKET) && (optname == SO_OPENTYPE))
        {
            /* Validate size */
            Status = ERROR_SUCCESS;
            _SEH2_TRY
            {
                if (!(optlen) || (*optlen < sizeof(DWORD)))
                {
                    /* Fail */
                    Status = SOCKET_ERROR;
                    SetLastError(WSAEFAULT);
                    _SEH2_LEAVE;
                }

                /* Set the open type */
                *(DWORD*)optval = Thread->OpenType;
                *optlen = sizeof(DWORD);
            }
            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
            {
                Status = SOCKET_ERROR;
                SetLastError(WSAEFAULT);
            }
            _SEH2_END;

            return Status;
        }

        /* Get the Socket Context */
        if ((Socket = WsSockGetSocket(s)))
        {
            /* Check if ANSI data was requested */
            if ((level == SOL_SOCKET) && (optname == SO_PROTOCOL_INFOA))
            {
                /* Validate size and pointers */
                ErrorCode = NO_ERROR;
                _SEH2_TRY
                {
                    if (!(optval) ||
                        !(optlen) ||
                        (*optlen < sizeof(WSAPROTOCOL_INFOA)))
                    {
                        /* Set return size and error code */
                        *optlen = sizeof(WSAPROTOCOL_INFOA);
                        ErrorCode = WSAEFAULT;
                        _SEH2_LEAVE;
                    }

                    /* It worked. Save the values */
                    OldOptLen = *optlen;
                    OldOptVal = optval;

                    /* Hack them so WSP will know how to deal with it */
                    *optlen = sizeof(WSAPROTOCOL_INFOW);
                    optval = (PCHAR)&ProtocolInfo;
                    optname = SO_PROTOCOL_INFOW;
                }
                _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
                {
                    ErrorCode = WSAEFAULT;
                }
                _SEH2_END;

                /* Did we encounter invalid parameters? */
                if (ErrorCode != NO_ERROR)
                {
                    /* Dereference the socket and fail */
                    WsSockDereference(Socket);
                    SetLastError(ErrorCode);
                    return SOCKET_ERROR;
                }
            }

            /* Make the call */
            Status = Socket->Provider->Service.lpWSPGetSockOpt(s,
                                                               level,
                                                               optname,
                                                               optval,
                                                               optlen,
                                                               &ErrorCode);

            /* Deference the Socket Context */
            WsSockDereference(Socket);

            /* Check provider value */
            if (Status == ERROR_SUCCESS)
            {
                /* Did we use the A->W hack? */
                if (!OldOptVal) return Status;

                /* We did, so we have to convert the unicode info to ansi */
                ErrorCode = MapUnicodeProtocolInfoToAnsi(&ProtocolInfo,
                                                         (LPWSAPROTOCOL_INFOA)
                                                         OldOptVal);

                /* Return the length */
                _SEH2_TRY
                {
                    *optlen = OldOptLen;
                }
                _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
                {
                    ErrorCode = WSAEFAULT;
                }
                _SEH2_END;

                /* Return success if this worked */
                if (ErrorCode == ERROR_SUCCESS) return Status;
            }

            /* If everything seemed fine, then the WSP call failed itself */
            if (ErrorCode == NO_ERROR) ErrorCode = WSASYSCALLFAILURE;
        }
        else
        {
            /* No Socket Context Found */
            ErrorCode = WSAENOTSOCK;
        }
    }

    /* Return with an Error */
    SetLastError(ErrorCode);
    return SOCKET_ERROR;
}

/*
 * @implemented
 */
INT
WSAAPI
setsockopt(IN SOCKET s,
           IN INT level,
           IN INT optname,
           IN CONST CHAR FAR* optval,
           IN INT optlen)
{
    PWSPROCESS Process;
    PWSTHREAD Thread;
    PWSSOCKET Socket;
    INT ErrorCode;
    INT Status;
    DPRINT("setsockopt: %lx, %lx, %lx\n", s, level, optname);

    /* Enter prolog */
    if ((ErrorCode = WsApiProlog(&Process, &Thread)) == ERROR_SUCCESS)
    {
        /* Check if we're changing the open type */
        if (level == SOL_SOCKET && optname == SO_OPENTYPE)
        {
            /* Validate size */
            if (optlen < sizeof(DWORD))
            {
                /* Fail */
                SetLastError(WSAEFAULT);
                return SOCKET_ERROR;
            }

            /* Set the open type */
            Status = ERROR_SUCCESS;
            _SEH2_TRY
            {
                Thread->OpenType = *(DWORD*)optval;
            }
            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
            {
                Status = SOCKET_ERROR;
                SetLastError(WSAEFAULT);
            }
            _SEH2_END;

            return Status;
        }

        /* Get the Socket Context */
        if ((Socket = WsSockGetSocket(s)))
        {
            /* Make the call */
            Status = Socket->Provider->Service.lpWSPSetSockOpt(s,
                                                               level,
                                                               optname,
                                                               optval,
                                                               optlen,
                                                               &ErrorCode);

            /* Deference the Socket Context */
            WsSockDereference(Socket);

            /* Return Provider Value */
            if (Status == ERROR_SUCCESS) return Status;

            /* If everything seemed fine, then the WSP call failed itself */
            if (ErrorCode == NO_ERROR) ErrorCode = WSASYSCALLFAILURE;
        }
        else
        {
            /* No Socket Context Found */
            ErrorCode = WSAENOTSOCK;
        }
    }

    /* Return with an Error */
    SetLastError(ErrorCode);
    return SOCKET_ERROR;
}

/*
 * @implemented
 */
INT
WSAAPI
shutdown(IN SOCKET s,
         IN INT how)
{
    PWSSOCKET Socket;
    INT Status;
    INT ErrorCode;
    DPRINT("shutdown: %lx, %lx\n", s, how);

    /* Check for WSAStartup */
    if ((ErrorCode = WsQuickProlog()) == ERROR_SUCCESS)
    {
        /* Get the Socket Context */
        if ((Socket = WsSockGetSocket(s)))
        {
            /* Make the call */
            Status = Socket->Provider->Service.lpWSPShutdown(s, how, &ErrorCode);

            /* Deference the Socket Context */
            WsSockDereference(Socket);

            /* Return Provider Value */
            if (Status == ERROR_SUCCESS) return Status;

            /* If everything seemed fine, then the WSP call failed itself */
            if (ErrorCode == NO_ERROR) ErrorCode = WSASYSCALLFAILURE;
        }
        else
        {
            /* No Socket Context Found */
            ErrorCode = WSAENOTSOCK;
        }
    }

    /* Return with an Error */
    SetLastError(ErrorCode);
    return SOCKET_ERROR;
}

/*
 * @implemented
 */
INT
WSAAPI
WSAConnect(IN SOCKET s,
           IN CONST struct sockaddr *name,
           IN INT namelen,
           IN LPWSABUF lpCallerData,
           OUT LPWSABUF lpCalleeData,
           IN LPQOS lpSQOS,
           IN LPQOS lpGQOS)
{
    PWSSOCKET Socket;
    INT Status;
    INT ErrorCode;
    DPRINT("WSAConnect: %lx, %lx, %lx, %p\n", s, name, namelen, lpCallerData);

    /* Check for WSAStartup */
    if ((ErrorCode = WsQuickProlog()) == ERROR_SUCCESS)
    {
        /* Get the Socket Context */
        if ((Socket = WsSockGetSocket(s)))
        {
            /* Make the call */
            Status = Socket->Provider->Service.lpWSPConnect(s,
                                                            name,
                                                            namelen,
                                                            lpCallerData,
                                                            lpCalleeData,
                                                            lpSQOS,
                                                            lpGQOS,
                                                            &ErrorCode);
            /* Deference the Socket Context */
            WsSockDereference(Socket);

            /* Return Provider Value */
            if (Status == ERROR_SUCCESS) return Status;

            /* If everything seemed fine, then the WSP call failed itself */
            if (ErrorCode == NO_ERROR) ErrorCode = WSASYSCALLFAILURE;
        }
        else
        {
            /* No Socket Context Found */
            ErrorCode = WSAENOTSOCK;
        }
    }

    /* Return with an Error */
    SetLastError(ErrorCode);
    return SOCKET_ERROR;
}

/*
 * @implemented
 */
BOOL
WSAAPI
WSAGetOverlappedResult(IN SOCKET s,
                       IN LPWSAOVERLAPPED lpOverlapped,
                       OUT LPDWORD lpcbTransfer,
                       IN BOOL fWait,
                       OUT LPDWORD lpdwFlags)
{
    PWSSOCKET Socket;
    INT Status;
    INT ErrorCode;
    DPRINT("WSAGetOverlappedResult: %lx, %lx\n", s, lpOverlapped);

    /* Check for WSAStartup */
    if ((ErrorCode = WsQuickProlog()) == ERROR_SUCCESS)
    {
        /* Get the Socket Context */
        if ((Socket = WsSockGetSocket(s)))
        {
            /* Make the call */
            Status = Socket->Provider->Service.lpWSPGetOverlappedResult(s,
                                                                        lpOverlapped,
                                                                        lpcbTransfer,
                                                                        fWait,
                                                                        lpdwFlags,
                                                                        &ErrorCode);
            /* Deference the Socket Context */
            WsSockDereference(Socket);

            /* Return Provider Value */
            if (Status) return Status;
        }
        else
        {
            /* No Socket Context Found */
            ErrorCode = WSAENOTSOCK;
        }
    }

    /* Return with an Error */
    SetLastError(ErrorCode);
    return FALSE;
}
