/*++

Copyright (c) 2000 Microsoft Corporation

Module Name:

    send.c

Abstract:

    Implementation of data transmission related Winsock APIs:
        send
        sendto
        WSASend
        WSASendTo

Revision History:

    06/02/2000 davidx
        Created it.

--*/

#include "precomp.h"

// Disable unreferenced label warning
#pragma warning(disable: 4102)


PRIVATE NTSTATUS
SockSend(
    PCB* pcb,
    SENDREQ* sendreq
    )

/*++

Routine Description:

    Internal function for sending out data from a socket

Arguments:

    pcb - Points to the protocol control block
    sendreq - Points to send user request information

Return Value:

    Status code

--*/

{
    NTSTATUS status;
    WSAOVERLAPPED* overlapped;

    if (!IsPcbConnected(pcb) && !sendreq->toaddr)
        return NETERR(WSAENOTCONN);

    if (IsPcbSendShutdown(pcb)) {
        return !NT_SUCCESS(PcbGetErrStatus(pcb)) ?
                    PcbGetErrStatus(pcb) :
                    NETERR(WSAESHUTDOWN);
    }

    if ((overlapped = sendreq->overlapped) != NULL) {
        sendreq->overlappedEvent = GetKernelEventObject(overlapped->hEvent);
        if (!sendreq->overlappedEvent)
            return NETERR(WSASYSCALLFAILURE);
    }

    // Check if we have room in the send buffer
    if (IsPcbSendBufFull(pcb)) {
        if (overlapped) {
            // Queue the overlapped send request.
            KeClearEvent(sendreq->overlappedEvent);
            status = PcbQueueOverlappedSend(pcb, sendreq);

            // The overlapped request was successfully queued up
            if (status == NETERR_PENDING)
                return status;

            // The overlapped send request wasn't queued
            // because of an error.
            if (!NT_SUCCESS(status)) goto exit;

            // The send buffer has opened up and
            // the overlapped send request wasn't queued.
        } else if (pcb->nonblocking || HasOverlappedSend(pcb)) {
            return NETERR_WOULDBLOCK;
        } else {
            status = PcbWaitForEvent(pcb, PCBEVENT_WRITE, pcb->sendTimeout);
            if (!NT_SUCCESS(status)) return status;
        }
    }

    status = IsDgramPcb(pcb) ?
                PcbSendDgram(pcb, sendreq) :
                TcbSend((TCB*) pcb, sendreq);

exit:
    if (overlapped) {
        // An overlapped call was completed immediately
        overlapped->_ioflags = 0;
        overlapped->_ioxfercnt = sendreq->sendtotal;
        overlapped->_iostatus = status;

        // It would seem to be a waste to signal the event here.
        // But that's win2k behavior.
        KeSetEvent(sendreq->overlappedEvent, 0, FALSE);
        ObDereferenceObject(sendreq->overlappedEvent);
    }

    return status;
}


int WSAAPI
send(
    SOCKET s,              
    const char* buf,  
    int len,               
    int flags              
    )

/*++

Routine Description:

    Refer to XAPI SDK documentation.

--*/

{
    WSABUF wsabuf;
    SENDREQ sendreq;
    WinsockApiPrologSockLock_(send, SOCKET_ERROR);

    WinsockApiParamCheck_(
        (len > 0 && buf != NULL || len == 0) &&
        (flags  == 0));

    wsabuf.len = len;
    wsabuf.buf = (char*) buf;
    sendreq.overlapped = NULL;
    sendreq.bufs = &wsabuf;
    sendreq.bufcnt = 1;
    sendreq.sendtotal = len;
    sendreq.toaddr = NULL;

    err = SockSend(pcb, &sendreq);
    MapNtStatusToWinsockError_(err);
    WinsockApiExitSockUnlock_(sendreq.sendtotal, SOCKET_ERROR);
}


//
// Count the total number of bytes to send
//
INLINE UINT SockCountSendTotal(WSABUF* bufs, UINT bufcnt) {
    UINT total = 0;
    while (bufcnt--) {
        total += bufs->len;
        bufs++;
    }
    return total;
}

INLINE BOOL SockCheckSendWsaBuf(WSABUF* bufs, UINT bufcnt) {
    if (bufs == NULL || bufcnt == 0)
        return FALSE;

    while (bufcnt--) {
        if (bufs->len && bufs->buf == NULL)
            return FALSE;
        bufs++;
    }
    return TRUE;
}

int WSAAPI
WSASend(
    SOCKET s,
    LPWSABUF bufs,
    DWORD bufcnt,
    LPDWORD bytesSent,
    DWORD flags, 
    LPWSAOVERLAPPED overlapped,                           
    LPWSAOVERLAPPED_COMPLETION_ROUTINE completionproc  
    )

/*++

Routine Description:

    Refer to XAPI SDK documentation.

--*/

{
    SENDREQ sendreq;
    WinsockApiPrologSockLock_(WSASend, SOCKET_ERROR);

    WinsockApiParamCheck_(
        SockCheckSendWsaBuf(bufs, bufcnt) &&
        bytesSent != NULL &&
        flags == 0 &&
        completionproc == NULL);

    sendreq.overlapped = overlapped;
    sendreq.bufs = bufs;
    sendreq.bufcnt = bufcnt;
    sendreq.sendtotal = SockCountSendTotal(bufs, bufcnt);
    sendreq.toaddr = NULL;

    err = SockSend(pcb, &sendreq);
    if (NT_SUCCESS(err)) *bytesSent = sendreq.sendtotal;
    MapNtStatusToWinsockError_(err);
    WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}


int WSAAPI
sendto(
    SOCKET s,                        
    const char* buf,            
    int len,                         
    int flags,                       
    const struct sockaddr* to,  
    int tolen                        
    )

/*++

Routine Description:

    Refer to XAPI SDK documentation.

--*/

{
    DWORD count;
    INT err;
    WSABUF wsabuf;

    WinsockApiPrologLight_(sendto);
    WinsockApiParamCheck_(len > 0 && buf != NULL || len == 0);

    wsabuf.len = len;
    wsabuf.buf = (char*) buf;
    err = WSASendTo(s, &wsabuf, 1, &count, flags, to, tolen, NULL, NULL);
    return (err == NO_ERROR) ? count : SOCKET_ERROR;
}


int WSAAPI
WSASendTo(
    SOCKET s,                   
    LPWSABUF bufs,           
    DWORD bufcnt,       
    LPDWORD bytesSent, 
    DWORD flags,             
    const struct sockaddr* toaddr,               
    int tolen,              
    LPWSAOVERLAPPED overlapped,        
    LPWSAOVERLAPPED_COMPLETION_ROUTINE completionproc  
    )

/*++

Routine Description:

    Refer to XAPI SDK documentation.

--*/

{
    struct sockaddr_in* sin = (struct sockaddr_in*) toaddr;
    SENDREQ sendreq;
    WinsockApiPrologSockLock_(WSASendTo, SOCKET_ERROR);

    WinsockApiParamCheck_(
        SockCheckSendWsaBuf(bufs, bufcnt) &&
        bytesSent != NULL &&
        flags == 0 &&
        (toaddr == NULL ||
         tolen >= SOCKADDRLEN && sin->sin_family == AF_INET) &&
        completionproc == NULL);

    sendreq.overlapped = overlapped;
    sendreq.bufs = bufs;
    sendreq.bufcnt = bufcnt;
    sendreq.sendtotal = SockCountSendTotal(bufs, bufcnt);

    if (IsTcb(pcb) || toaddr == NULL) {
        // For TCP sockets, WSASendTo is equivalent to WSASend.
        // We simply ignore lpTo and iToLen parameters.
        //
        // Also, if toaddr parameter is NULL, we treat WSASendTo
        // the same way as WSASend.

        sendreq.toaddr = NULL;
        err = SockSend(pcb, &sendreq);
    } else {
        // Must do this check because downstream code
        // doesn't expect the destination address to be 0.
        // Also, we consider sending to UDP port 0 an error.
        if (sin->sin_addr.s_addr == 0 ||
            sin->sin_port == 0 && pcb->type == SOCK_DGRAM) {
            WinsockApiGotoExit_(WSAEADDRNOTAVAIL);
        }

        // Is this socket allowed to send broadcast
        // datagrams on this socket?
        if (IS_BCAST_IPADDR(sin->sin_addr.s_addr) && !pcb->broadcast) {
            WinsockApiGotoExit_(WSAEACCES);
        }

        // If the socket is not bound, bind it here
        if (!IsPcbBound(pcb)) {
            err = PcbBind(pcb, 0, 0);
            if (!NT_SUCCESS(err)) {
                MapNtStatusToWinsockError_(err);
                goto exit;
            }
        }

        // Send to the new destination
        sendreq.toaddr = sin;
        err = SockSend(pcb, &sendreq);
    }
        
    if (NT_SUCCESS(err)) *bytesSent = sendreq.sendtotal;
    MapNtStatusToWinsockError_(err);
    WinsockApiExitSockUnlock_(NO_ERROR, SOCKET_ERROR);
}

