/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    sendrecv.c

Abstract:

    This module contains the SPUDSendAndRecv service.

Author:

    John Ballard (jballard)     21-Oct-1996

Revision History:

    Keith Moore (keithmo)       04-Feb-1998
        Cleanup, added much needed comments.

--*/


#include "spudp.h"


#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, SPUDSendAndRecv )
#endif


//
// Public functions.
//


NTSTATUS
SPUDSendAndRecv(
    HANDLE hSocket,
    struct _AFD_SEND_INFO *sendInfo,
    struct _AFD_RECV_INFO *recvInfo,
    PSPUD_REQ_CONTEXT reqContext
    )

/*++

Routine Description:

    Batch send & receive request.

Arguments:

    hSocket - The target socket for the request.

    sendInfo - Information describing the send.

    recvInfo - Information describing the receive.

    reqContext - The user-mode context for the request.

Return Value:

    NTSTATUS - Completion status.

--*/

{

    NTSTATUS status;
    PFILE_OBJECT fileObject;
    IO_STATUS_BLOCK localIoStatus;
    PIRP irp;
    PIO_STACK_LOCATION irpSp;
    PSPUD_AFD_REQ_CONTEXT SpudReqContext;
    PVOID completionPort;

    //
    // Sanity check.
    //

    PAGED_CODE();

    status = SPUD_ENTER_SERVICE( "SPUDSendAndRecv", TRUE );

    if( !NT_SUCCESS(status) ) {
        return status;
    }

    BumpCount( CtrSendAndRecv );

    //
    // SPUD doesn't support kernel-mode callers. In fact, we don't
    // even build the "system stubs" necessary to invoke SPUD from
    // kernel-mode.
    //

    ASSERT( ExGetPreviousMode() == UserMode );

    try {

        //
        // Make sure we can write to reqContext
        //

        ProbeForWrite(
            reqContext,
            sizeof(SPUD_REQ_CONTEXT),
            sizeof(ULONG)
            );

        //
        // Make initial status invalid
        //

        reqContext->IoStatus1.Status = 0xffffffff;
        reqContext->IoStatus1.Information = 0;
        reqContext->IoStatus2.Status = 0xffffffff;
        reqContext->IoStatus2.Information = 0;
        reqContext->ReqType = SendAndRecv;
        reqContext->KernelReqInfo = SPUD_INVALID_REQ_HANDLE;

        //
        // Make sure the buffer looks good
        //

        ProbeForRead(
            recvInfo,
            sizeof(*recvInfo),
            sizeof(ULONG)
            );

        if( recvInfo->BufferCount < 1 ) {
            ExRaiseStatus( STATUS_INVALID_PARAMETER );
        }

        ProbeForRead(
            recvInfo->BufferArray,
            sizeof(*recvInfo->BufferArray),
            sizeof(ULONG)
            );

        ProbeForRead(
            recvInfo->BufferArray->buf,
            recvInfo->BufferArray->len,
            sizeof(UCHAR)
            );

    } except( EXCEPTION_EXECUTE_HANDLER ) {
        status = GetExceptionCode();
        SPUD_LEAVE_SERVICE( "SPUDSendAndRecv", status, FALSE );
        return status;
    }

    //
    // Reference the socket handle
    //

    status = ObReferenceObjectByHandle(
                 hSocket,
                 0L,
                 *IoFileObjectType,
                 UserMode,
                 (PVOID *)&fileObject,
                 NULL
                 );

    if( !NT_SUCCESS(status) ) {
        SPUD_LEAVE_SERVICE( "SPUDSendAndRecv", status, FALSE );
        return status;
    }

    TRACE_OB_REFERENCE( fileObject );

    //
    // If we haven't already cached the Device Object and FastIoControl
    // pointers, then do so now.
    //

    if( !SpudAfdDeviceObject ) {

        status = SpudGetAfdDeviceObject( fileObject );

        if( !NT_SUCCESS(status) ) {
            TRACE_OB_DEREFERENCE( fileObject );
            ObDereferenceObject( fileObject );
            status = STATUS_INVALID_DEVICE_REQUEST;
            SPUD_LEAVE_SERVICE( "SPUDSendAndRecv", status, FALSE );
            return status;
        }

    }

    //
    // Reference the completion port.
    //

    completionPort = SpudReferenceCompletionPort();

    if( completionPort == NULL ) {
        TRACE_OB_DEREFERENCE( fileObject );
        ObDereferenceObject( fileObject );
        status = STATUS_INVALID_DEVICE_REQUEST;
        SPUD_LEAVE_SERVICE( "SPUDSendAndRecv", status, FALSE );
        return status;
    }

    //
    // Let's check to see if fast io will work
    //

    if( SpudAfdFastIoDeviceControl(
            fileObject,
            TRUE,
            (PVOID)sendInfo,
            sizeof(AFD_SEND_INFO),
            NULL,
            0,
            IOCTL_AFD_SEND,
            &localIoStatus,
            SpudAfdDeviceObject
            )) {

        BumpCount( CtrSendRecvFastSend );

        //
        // Lets remember the completion status for this operation
        //

        try {
            reqContext->IoStatus1 = localIoStatus;
        } except( EXCEPTION_EXECUTE_HANDLER) {
            localIoStatus.Status = GetExceptionCode();
            localIoStatus.Information = 0;
        }

        if( localIoStatus.Status == STATUS_SUCCESS ) {
            localIoStatus.Status = SpudAfdRecvFastReq(
                                       fileObject,
                                       recvInfo,
                                       reqContext
                                       );
        }

        //
        // If everything completed without pending then we can queue
        // a completion packet to the port now.
        //
        // Note that we must not queue a completion packet if the
        // request is failing in-line.
        //

        if( localIoStatus.Status != STATUS_PENDING ) {

            if( NT_SUCCESS(localIoStatus.Status) ) {
                localIoStatus.Status = IoSetIoCompletion(
                                           SpudCompletionPort,  // IoCompletion
                                           reqContext,          // KeyContext
                                           NULL,                // ApcContext
                                           STATUS_SUCCESS,      // IoStatus
                                           0xFFFFFFFF,          // IoStatusInformation
                                           TRUE                 // Quota
                                           );
            }

            TRACE_OB_DEREFERENCE( fileObject );
            ObDereferenceObject( fileObject );

        }

        //
        // At this point, we know the fast-path send has completed
        // in-line and the receive either completed in-line or has pended.
        // Since it is the receive code's responsibility to add any necessary
        // references to the completion port (and since we know the send
        // has not pended) we can remove the reference we added above.
        //

        SPUD_LEAVE_SERVICE( "SPUDSendAndRecv", localIoStatus.Status, TRUE );
        return localIoStatus.Status;

    }

    BumpCount( CtrSendRecvSlowSend );

    //
    // It looks like we will have to it the hard way.
    // We will now build an IRP for AFD.
    //

    KeClearEvent( &fileObject->Event );

    //
    // Allocate and initialize the IRP.
    //

    irp = IoAllocateIrp( SpudAfdDeviceObject->StackSize, TRUE );

    if( !irp ) {
        TRACE_OB_DEREFERENCE( fileObject );
        ObDereferenceObject( fileObject );
        status = STATUS_INSUFFICIENT_RESOURCES;
        SPUD_LEAVE_SERVICE( "SPUDSendAndRecv", status, TRUE );
        return status;
    }

    status = SpudAllocateRequestContext(
                 &SpudReqContext,
                 reqContext,
                 recvInfo,
                 irp,
                 NULL
                 );

    if( !NT_SUCCESS(status) ) {
        TRACE_OB_DEREFERENCE( fileObject );
        ObDereferenceObject( fileObject );
        IoFreeIrp( irp );
        SPUD_LEAVE_SERVICE( "SPUDSendAndRecv", status, TRUE );
        return status;
    }

    BumpCount( CtrSendRecvSlowRecv );

    irp->RequestorMode = UserMode;
    irp->Tail.Overlay.OriginalFileObject = fileObject;
    irp->Tail.Overlay.Thread = PsGetCurrentThread();
    IoQueueThreadIrp( irp );

    irpSp = IoGetNextIrpStackLocation( irp );

    irpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
    irpSp->FileObject = fileObject;
    irpSp->DeviceObject = SpudAfdDeviceObject;

    // irpSp->Parameters.DeviceIoControl.OutputBufferLength = 0;
    irpSp->Parameters.DeviceIoControl.InputBufferLength = sizeof(AFD_SEND_INFO);
    irpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_AFD_SEND;
    irpSp->Parameters.DeviceIoControl.Type3InputBuffer = sendInfo;

    IoSetCompletionRoutine(
        irp,
        SpudAfdContinueRecv,
        SpudReqContext,
        TRUE,
        TRUE,
        TRUE
        );

    IoCallDriver( SpudAfdDeviceObject, irp );

    status = STATUS_PENDING;
    SPUD_LEAVE_SERVICE( "SPUDSendAndRecv", status, FALSE );
    return status;

}   // SPUDSendAndRecv