/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    lpcqueue.c

Abstract:

    Local Inter-Process Communication (LPC) queue support routines.

--*/

#include "lpcp.h"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT,LpcpInitializePortZone)
#pragma alloc_text(PAGE,LpcpInitializePortQueue)
#pragma alloc_text(PAGE,LpcpDestroyPortQueue)
#pragma alloc_text(PAGE,LpcpExtendPortZone)
#pragma alloc_text(PAGE,LpcpFreeToPortZone)
#pragma alloc_text(PAGE,LpcpSaveDataInfoMessage)
#pragma alloc_text(PAGE,LpcpFreeDataInfoMessage)
#pragma alloc_text(PAGE,LpcpFindDataInfoMessage)
#pragma alloc_text(PAGE,LpcDisconnectPort)
#endif

#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGEDATA")
#endif // ALLOC_DATA_PRAGMA

ULONG LpcpTotalNumberOfMessages = 0;
ULONG LpcpMaxMessageSize = 0;
PAGED_LOOKASIDE_LIST LpcpMessagesLookaside;


#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg()
#endif // ALLOC_DATA_PRAGMA


NTSTATUS
LpcpInitializePortQueue (
    IN PLPCP_PORT_OBJECT Port
    )

/*++

Routine Description:

    This routine is used to initialize the message queue for a port object.

Arguments:

    Port - Supplies the port object being initialized

Return Value:

    NTSTATUS - An appropriate status value

--*/

{
    PLPCP_NONPAGED_PORT_QUEUE NonPagedPortQueue;

    PAGED_CODE();

    //
    //  Allocate space for the port queue
    //

    NonPagedPortQueue = ExAllocatePoolWithTag( NonPagedPool,
                                               sizeof(LPCP_NONPAGED_PORT_QUEUE),
                                               'troP' );

    if (NonPagedPortQueue == NULL) {

        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    //  Initialize the fields in the non paged port queue
    //

    KeInitializeSemaphore( &NonPagedPortQueue->Semaphore, 0, 0x7FFFFFFF );

    NonPagedPortQueue->BackPointer = Port;

    //
    //  Have the port msg queue point to the non nonpaged port queue
    //

    Port->MsgQueue.Semaphore = &NonPagedPortQueue->Semaphore;

    //
    //  Initialize the port msg queue to be empty
    //

    InitializeListHead( &Port->MsgQueue.ReceiveHead );

    //
    //  And return to our caller
    //

    return STATUS_SUCCESS;
}


VOID
LpcpDestroyPortQueue (
    IN PLPCP_PORT_OBJECT Port,
    IN BOOLEAN CleanupAndDestroy
    )

/*++

Routine Description:

    This routine is used to teardown the message queue of a port object.
    After running this message will either be empty (like it was just
    initialized) or completely gone (needs to be initialized)

Arguments:

    Port - Supplies the port containing the message queue being modified

    CleanupAndDestroy - Specifies if the message queue should be set back
        to the freshly initialized state (value of FALSE) or completely
        torn down (value of TRUE)

Return Value:

    None.

--*/

{
    PLIST_ENTRY Next, Head;
    PETHREAD ThreadWaitingForReply;
    PLPCP_MESSAGE Msg;
    PLPCP_PORT_OBJECT ConnectionPort = NULL;

    PAGED_CODE();

    //
    //  If this port is connected to another port, then disconnect it.
    //  Protect this with a lock in case the other side is going away
    //  at the same time.
    //

    LpcpAcquireLpcpLock();

    if ( ((Port->Flags & PORT_TYPE) != UNCONNECTED_COMMUNICATION_PORT)
            &&
         (Port->ConnectedPort != NULL) ) {

        Port->ConnectedPort->ConnectedPort = NULL;
        
        //
        //  Disconnect the connection port
        //

        if (Port->ConnectedPort->ConnectionPort) {

            ConnectionPort = Port->ConnectedPort->ConnectionPort;

            Port->ConnectedPort->ConnectionPort = NULL;
        }
    }

    //
    //  If connection port, then mark name as deleted
    //

    if ((Port->Flags & PORT_TYPE) == SERVER_CONNECTION_PORT) {

        Port->Flags |= PORT_NAME_DELETED;
    }

    //
    //  Walk list of threads waiting for a reply to a message sent to this
    //  port.  Signal each thread's LpcReplySemaphore to wake them up.  They
    //  will notice that there was no reply and return
    //  STATUS_PORT_DISCONNECTED
    //

    Head = &Port->LpcReplyChainHead;
    Next = Head->Flink;

    while ((Next != NULL) && (Next != Head)) {

        ThreadWaitingForReply = CONTAINING_RECORD( Next, ETHREAD, LpcReplyChain );

        //
        //  If the thread is exiting, in the location of LpcReplyChain is stored the ExitTime
        //  We'll stop to search through the list.

        if ( ThreadWaitingForReply->LpcExitThreadCalled ) {
            
            break;
        }

        Next = Next->Flink;

        RemoveEntryList( &ThreadWaitingForReply->LpcReplyChain );

        InitializeListHead( &ThreadWaitingForReply->LpcReplyChain );

        if (!KeReadStateSemaphore( &ThreadWaitingForReply->LpcReplySemaphore )) {

            //
            //  Thread is waiting on a message.  Signal the semaphore and free
            //  the message
            //

            Msg = LpcpGetThreadMessage(ThreadWaitingForReply);

            if ( Msg ) {

                //
                //  If the message is a connection request and has a section object
                //  attached, then dereference that section object
                //

                if ((Msg->Request.u2.s2.Type & ~LPC_KERNELMODE_MESSAGE) == LPC_CONNECTION_REQUEST) {

                    PLPCP_CONNECTION_MESSAGE ConnectMsg;
                
                    ConnectMsg = (PLPCP_CONNECTION_MESSAGE)(Msg + 1);

                    if ( ConnectMsg->SectionToMap != NULL ) {

                        ObDereferenceObject( ConnectMsg->SectionToMap );
                    }
                }

                ThreadWaitingForReply->LpcReplyMessage = NULL;

                LpcpFreeToPortZone( Msg, LPCP_MUTEX_OWNED );
                Next = Port->LpcReplyChainHead.Flink; // Lock has been dropped
            }

            ThreadWaitingForReply->LpcReplyMessageId = 0;

            KeReleaseSemaphore( &ThreadWaitingForReply->LpcReplySemaphore,
                                0,
                                1L,
                                FALSE );
        }
    }

    InitializeListHead( &Port->LpcReplyChainHead );

    //
    //  Walk list of messages queued to this port.  Remove each message from
    //  the list and free it.
    //

    while (Port->MsgQueue.ReceiveHead.Flink && !IsListEmpty (&Port->MsgQueue.ReceiveHead)) {

        Msg  = CONTAINING_RECORD( Port->MsgQueue.ReceiveHead.Flink, LPCP_MESSAGE, Entry );

        RemoveEntryList (&Msg->Entry);

        InitializeListHead( &Msg->Entry );

        LpcpFreeToPortZone( Msg, LPCP_MUTEX_OWNED );
        
    }

    LpcpReleaseLpcpLock();

    if ( ConnectionPort ) {

        ObDereferenceObject( ConnectionPort );
    }

    //
    //  Check if the caller wants it all to go away
    //

    if ( CleanupAndDestroy ) {

        //
        //  Free semaphore associated with the queue.
        //

        if (Port->MsgQueue.Semaphore != NULL) {

            ExFreePool( CONTAINING_RECORD( Port->MsgQueue.Semaphore,
                                           LPCP_NONPAGED_PORT_QUEUE,
                                           Semaphore ));
        }
    }

    //
    //  And return to our caller
    //

    return;
}


NTSTATUS
LpcDisconnectPort (
    IN PVOID Port
    )

/*++

Routine Description:

    This routine is used to disconnect an LPC port so no more messages can be sent and anybody waiting for a message
    is woken up with an error.

Arguments:

    Port - Supplies the port to be disconnected

Return Value:

    NTSTATUS - Status of operation

--*/
{
    LpcpDestroyPortQueue (Port, FALSE);
    return STATUS_SUCCESS;
}


VOID
LpcpInitializePortZone (
    IN ULONG MaxEntrySize
    )
{
    LpcpMaxMessageSize = MaxEntrySize;

    ExInitializePagedLookasideList( &LpcpMessagesLookaside,
                                    NULL,
                                    NULL,
                                    0,
                                    MaxEntrySize,
                                    'McpL',
                                    32 
                                    );
}


VOID
FASTCALL
LpcpFreeToPortZone (
    IN PLPCP_MESSAGE Msg,
    IN ULONG MutexFlags
    )
{
    PLPCP_CONNECTION_MESSAGE ConnectMsg;
    PETHREAD RepliedToThread = NULL;
    PLPCP_PORT_OBJECT ClientPort = NULL;

    PAGED_CODE();

    //
    //  Acquire the global lock if necessary
    //

    if ((MutexFlags & LPCP_MUTEX_OWNED) == 0) {

        LpcpAcquireLpcpLock();
    }

    //
    //  A entry field connects the message to the message queue of the
    //  owning port object.  If not already removed then remove this
    //  message
    //

    if (!IsListEmpty( &Msg->Entry )) {
        RemoveEntryList( &Msg->Entry );
        InitializeListHead( &Msg->Entry );
    }

    //
    //  If the replied to thread is not null then we have a reference
    //  to the thread that we should now remove
    //

    if (Msg->RepliedToThread != NULL) {
        RepliedToThread = Msg->RepliedToThread;
        Msg->RepliedToThread = NULL;
    }

    //
    //  If the msg was for a connection request then we know that
    //  right after the lpcp message is a connection message whose
    //  client port field might need to be dereferenced
    //

    if ((Msg->Request.u2.s2.Type & ~LPC_KERNELMODE_MESSAGE) == LPC_CONNECTION_REQUEST) {

        ConnectMsg = (PLPCP_CONNECTION_MESSAGE)(Msg + 1);

        if (ConnectMsg->ClientPort) {

            //
            //  Capture a pointer to the client port then null it
            //  out so that no one else can use it, then release
            //  lpcp lock before we dereference the client port
            //

            ClientPort = ConnectMsg->ClientPort;

            ConnectMsg->ClientPort = NULL;
        }
    }

    LpcpReleaseLpcpLock();

    if ( ClientPort ) {
        
        ObDereferenceObject( ClientPort );
    }

    if ( RepliedToThread ) {

        ObDereferenceObject( RepliedToThread );
    }

    ExFreeToPagedLookasideList(&LpcpMessagesLookaside, Msg);

    if ((MutexFlags & LPCP_MUTEX_OWNED) &&
        ((MutexFlags & LPCP_MUTEX_RELEASE_ON_RETURN) == 0)) {

        LpcpAcquireLpcpLock();
    }

}



VOID
LpcpSaveDataInfoMessage (
    IN PLPCP_PORT_OBJECT Port,
    IN PLPCP_MESSAGE Msg,
    IN ULONG MutexFlags
    )

/*++

Routine Description:

    This routine is used in place of freeing a message and instead saves the
    message off a separate queue from the port.

Arguments:

    Port - Specifies the port object under which to save this message

    Msg - Supplies the message being saved

    MutexFlags - Supplies whether the mutex is owned.

Return Value:

    None.

--*/

{
    PAGED_CODE();

    //
    //  Take out the global lock if our caller didn't already.
    //

    if ((MutexFlags & LPCP_MUTEX_OWNED) == 0) {
        LpcpAcquireLpcpLock();
    }

    //
    //  Make sure we get to the connection port object of this port
    //

    if ((Port->Flags & PORT_TYPE) > UNCONNECTED_COMMUNICATION_PORT) {

        Port = Port->ConnectionPort;

        if (Port == NULL) {

            if ((MutexFlags & LPCP_MUTEX_OWNED) == 0) {
                LpcpReleaseLpcpLock();
            }

            return;
        }
    }

    LpcpTrace(( "%s Saving DataInfo Message %lx (%u.%u)  Port: %lx\n",
                PsGetCurrentProcess()->ImageFileName,
                Msg,
                Msg->Request.MessageId,
                Msg->Request.CallbackId,
                Port ));

    //
    //  Enqueue this message onto the data info chain for the port
    //

    InsertTailList( &Port->LpcDataInfoChainHead, &Msg->Entry );

    //
    //  Free the global lock
    //

    if ((MutexFlags & LPCP_MUTEX_OWNED) == 0) {
        LpcpReleaseLpcpLock();
    }

    //
    //  And return to our caller
    //

    return;
}


VOID
LpcpFreeDataInfoMessage (
    IN PLPCP_PORT_OBJECT Port,
    IN ULONG MessageId,
    IN ULONG CallbackId,
    IN LPC_CLIENT_ID ClientId
    )

/*++

Routine Description:

    This routine is used to free up a saved message in a port

Arguments:

    Port - Supplies the port being manipulated

    MessageId - Supplies the id of the message being freed

    CallbackId - Supplies the callback id of the message being freed

Return Value:

    None.

--*/

{
    PLPCP_MESSAGE Msg;
    PLIST_ENTRY Head, Next;

    PAGED_CODE();

    //
    //  Make sure we get to the connection port object of this port
    //

    if ((Port->Flags & PORT_TYPE) > UNCONNECTED_COMMUNICATION_PORT) {

        Port = Port->ConnectionPort;

        if (Port == NULL) {

            return;
        }
    }

    //
    //  Zoom down the data info chain for the connection port object
    //

    Head = &Port->LpcDataInfoChainHead;
    Next = Head->Flink;

    while (Next != Head) {

        Msg = CONTAINING_RECORD( Next, LPCP_MESSAGE, Entry );

        //
        //  If this message matches the callers specification then remove
        //  this message, free it back to the port zone, and return back
        //  to our caller
        //

        if ((Msg->Request.MessageId == MessageId) &&
            (Msg->Request.ClientId.UniqueProcess == ClientId.UniqueProcess) &&
            (Msg->Request.ClientId.UniqueThread == ClientId.UniqueThread)) {

            LpcpTrace(( "%s Removing DataInfo Message %lx (%u.%u) Port: %lx\n",
                        PsGetCurrentProcess()->ImageFileName,
                        Msg,
                        Msg->Request.MessageId,
                        Msg->Request.CallbackId,
                        Port ));

            RemoveEntryList( &Msg->Entry );

            InitializeListHead( &Msg->Entry );

            LpcpFreeToPortZone( Msg, LPCP_MUTEX_OWNED );

            return;

        } else {
            
            //
            //  Keep on going down the data info chain
            //

            Next = Next->Flink;
        }
    }

    //
    //  We didn't find a match so just return to our caller
    //

    LpcpTrace(( "%s Unable to find DataInfo Message (%u.%u)  Port: %lx\n",
                PsGetCurrentProcess()->ImageFileName,
                MessageId,
                CallbackId,
                Port ));

    return;
}


PLPCP_MESSAGE
LpcpFindDataInfoMessage (
    IN PLPCP_PORT_OBJECT Port,
    IN ULONG MessageId,
    IN ULONG CallbackId,
    IN LPC_CLIENT_ID ClientId
    )

/*++

Routine Description:

    This routine is used to locate a specific message stored off the
    data info chain of a port

Arguments:

    Port - Supplies the port being examined

    MessageId - Supplies the ID of the message being searched for

    CallbackId - Supplies the callback ID being searched for

Return Value:

    PLPCP_MESSAGE - returns a pointer to the message satisfying the
        search criteria or NULL of none was found

--*/

{
    PLPCP_MESSAGE Msg;
    PLIST_ENTRY Head, Next;

    PAGED_CODE();

    //
    //  Make sure we get to the connection port object of this port
    //

    if ((Port->Flags & PORT_TYPE) > UNCONNECTED_COMMUNICATION_PORT) {

        Port = Port->ConnectionPort;

        if (Port == NULL) {

            return NULL;
        }
    }

    //
    //  Zoom down the data info chain for the connection port object looking
    //  for a match
    //

    Head = &Port->LpcDataInfoChainHead;
    Next = Head->Flink;

    while (Next != Head) {

        Msg = CONTAINING_RECORD( Next, LPCP_MESSAGE, Entry );

        if ((Msg->Request.MessageId == MessageId) &&
            (Msg->Request.ClientId.UniqueProcess == ClientId.UniqueProcess) &&
            (Msg->Request.ClientId.UniqueThread == ClientId.UniqueThread)) {

            LpcpTrace(( "%s Found DataInfo Message %lx (%u.%u)  Port: %lx\n",
                        PsGetCurrentProcess()->ImageFileName,
                        Msg,
                        Msg->Request.MessageId,
                        Msg->Request.CallbackId,
                        Port ));

            return Msg;

        } else {

            Next = Next->Flink;
        }
    }

    //
    //  We did not find a match so return null to our caller
    //

    LpcpTrace(( "%s Unable to find DataInfo Message (%u.%u)  Port: %lx\n",
                PsGetCurrentProcess()->ImageFileName,
                MessageId,
                CallbackId,
                Port ));

    return NULL;
}

