//++
//
//Copyright (c) Microsoft Corporation
//
//Abstract:
//
//  User Mode Server Test program for the LPC subcomponent of the NTOS project
//
//--  

#include "ulpc.h"

#define MAX_REQUEST_THREADS 9
#define MAX_CONNECTIONS 4

HANDLE ServerConnectionPortHandle;
HANDLE ServerThreadHandles[ MAX_REQUEST_THREADS ];
DWORD  ServerThreadClientIds[ MAX_REQUEST_THREADS ];

HANDLE ServerClientPortHandles[ MAX_CONNECTIONS ];
ULONG CountServerClientPortHandles = 0;
ULONG CountClosedServerClientPortHandles = 0;

BOOLEAN TestCallBacks;

VOID
ServerHandleConnectionRequest(
    IN PTLPC_PORTMSG Msg
    )
{
    BOOLEAN AcceptConnection;
    LPSTR ConnectionInformation;
    ULONG ConnectionInformationLength;
    NTSTATUS Status;
    PORT_VIEW ServerView;
    REMOTE_PORT_VIEW ClientView;
    ULONG i;
    PULONG p;

    ConnectionInformation = (LPSTR)&Msg->Data[ 0 ];
    ConnectionInformationLength = Msg->h.u1.s1.DataLength;
    AcceptConnection = FALSE;
    fprintf( stderr, "\nConnection Request Received from CLIENT_ID 0x%08lx.0x%08lx:\n",
              Msg->h.ClientId.UniqueProcess,
              Msg->h.ClientId.UniqueThread
            );
    fprintf( stderr, "    MessageId: %ld\n",
              Msg->h.MessageId
            );
    fprintf( stderr, "    ClientViewSize: 0x%08lx\n",
              Msg->h.ClientViewSize
            );
    fprintf( stderr, "    ConnectionInfo: (%ld) '%.*s'\n",
              ConnectionInformationLength,
              ConnectionInformationLength,
              (PSZ)&ConnectionInformation[0]
            );

    ClientView.Length = sizeof( ClientView );
    ClientView.ViewSize = 0;
    ClientView.ViewBase = 0;
    if (CountServerClientPortHandles >= MAX_CONNECTIONS) {
        AcceptConnection = FALSE;
        }
    else {
        AcceptConnection = TRUE;
        }

    if (AcceptConnection) {
        LARGE_INTEGER MaximumSize;

        fprintf( stderr, "Creating Port Memory Section" );
        MaximumSize.QuadPart = 0x4000;
        Status = NtCreateSection( &ServerView.SectionHandle,
                                  SECTION_MAP_READ | SECTION_MAP_WRITE,
                                  NULL,
                                  &MaximumSize,
                                  PAGE_READWRITE,
                                  SEC_COMMIT,
                                  NULL
                                );

        if (ShowHandleOrStatus( Status, ServerView.SectionHandle )) {
            ServerView.Length = sizeof( ServerView );
            ServerView.SectionOffset = 0;
            ServerView.ViewSize = 0x4000;
            ServerView.ViewBase = 0;
            ServerView.ViewRemoteBase = 0;
            }
        else {
            AcceptConnection = FALSE;
            }
        }

    fprintf( stderr, "Server calling NtAcceptConnectPort( AcceptConnection = %ld )",
              AcceptConnection
            );

    if (AcceptConnection) {
        strcpy( ConnectionInformation, "Server Accepting Connection" );
        }
    else {
        strcpy( ConnectionInformation, "Server Rejecting Connection" );
        }

    Msg->h.u1.s1.DataLength = strlen( ConnectionInformation ) + 1;
    Msg->h.u1.s1.TotalLength = Msg->h.u1.s1.DataLength + sizeof( Msg->h );
    Status = NtAcceptConnectPort( &ServerClientPortHandles[ CountServerClientPortHandles ],
                                  (PVOID)(CountServerClientPortHandles+1),
                                  &Msg->h,
                                  AcceptConnection,
                                  &ServerView,
                                  &ClientView
                                );

    if (ShowHandleOrStatus( Status, ServerClientPortHandles[ CountServerClientPortHandles ] )) {
        fprintf( stderr, "    ServerView: Base=%lx, Size=%lx, RemoteBase: %lx\n",
                  ServerView.ViewBase,
                  ServerView.ViewSize,
                  ServerView.ViewRemoteBase
                );
        fprintf( stderr, "    ClientView: Base=%lx, Size=%lx\n",
                  ClientView.ViewBase,
                  ClientView.ViewSize
                );

        ClientMemoryBase = ServerView.ViewBase;
        ClientMemorySize = ServerView.ViewSize;
        ServerMemoryBase = ServerView.ViewRemoteBase;
        ServerMemoryDelta = (ULONG)ServerMemoryBase -
                            (ULONG)ClientMemoryBase;
        p = (PULONG)(ClientView.ViewBase);
        i =ClientView.ViewSize;
        while (i) {
            *p = (ULONG)p;
            fprintf( stderr, "Server setting ClientView[ %lx ] = %lx\n",
                      p,
                      *p
                    );
            p += (0x1000/sizeof(ULONG));
            i -= 0x1000;
            }

        p = (PULONG)(ServerView.ViewBase);
        i = ServerView.ViewSize;
        while (i) {
            *p = (ULONG)p - ServerMemoryDelta;
            fprintf( stderr, "Server setting ServerView[ %lx ] = %lx\n",
                      p,
                      *p
                    );
            p += (0x1000/sizeof(ULONG));
            i -= 0x1000;
            }
        Status = NtCompleteConnectPort( ServerClientPortHandles[ CountServerClientPortHandles ] );
        CountServerClientPortHandles++;
        }

    return;
}

DWORD
ServerThread(
    LPVOID Context
    )
{
    NTSTATUS Status;
    CHAR ThreadName[ 64 ];
    TLPC_PORTMSG Msg;
    PTLPC_PORTMSG ReplyMsg;
    HANDLE ReplyPortHandle;
    ULONG PortContext;
    PTEB Teb = NtCurrentTeb();

    Teb->ActiveRpcHandle = NULL;

    strcpy( ThreadName, "Server Thread Id: " );
    RtlIntegerToChar( (ULONG)Teb->ClientId.UniqueProcess, 16, 9,
                    ThreadName + strlen( ThreadName )
                  );
    strcat( ThreadName, "." );
    RtlIntegerToChar( (ULONG)Teb->ClientId.UniqueThread, 16, 9,
                    ThreadName + strlen( ThreadName )
                  );

    EnterThread( ThreadName, (ULONG)Context );

    ReplyMsg = NULL;
    ReplyPortHandle = ServerConnectionPortHandle;
    while (TRUE) {
        //        fprintf( stderr, "%s waiting for message...\n", ThreadName );   
        Status = NtReplyWaitReceivePort( ReplyPortHandle,
                                         (PVOID)&PortContext,
                                         (PPORT_MESSAGE)ReplyMsg,
                                         (PPORT_MESSAGE)&Msg
                                       );

        ReplyMsg = NULL;
        ReplyPortHandle = ServerConnectionPortHandle;
        //
        //fprintf( stderr, "%s Receive (%s)  Id: %u", ThreadName, LpcMsgTypes[ Msg.h.u2.s2.Type ], Msg.h.MessageId );
        //
        PortContext -= 1;
        if (!NT_SUCCESS( Status )) {
            fprintf( stderr, " (Status == %08x)\n", Status );
            }
        else
        if (Msg.h.u2.s2.Type == LPC_CONNECTION_REQUEST) {
            ServerHandleConnectionRequest( &Msg );
            continue;
            }
        else
        if (PortContext >= CountServerClientPortHandles) {
            fprintf( stderr, "*** Invalid PortContext (%lx) received\n",
                     PortContext
                   );
            }
        else
        if (Msg.h.u2.s2.Type == LPC_PORT_CLOSED ||
            Msg.h.u2.s2.Type == LPC_CLIENT_DIED
           ) {
            fprintf( stderr, " - disconnect for client %08x\n", PortContext );
            CloseHandle( ServerClientPortHandles[ (ULONG)PortContext ] );
            CountClosedServerClientPortHandles += 1;
            if (CountClosedServerClientPortHandles == CountServerClientPortHandles) {
                break;
                }
            }
        else
        if (Msg.h.u2.s2.Type == LPC_REQUEST) {
            CheckTlpcMsg( Status, &Msg );
            ReplyMsg = &Msg;
            ReplyPortHandle = ServerClientPortHandles[ PortContext ];
            if (TestCallBacks && (Msg.h.u1.s1.DataLength > 30)) {
                Status = SendRequest( 1,
                                      ThreadName,
                                      ReplyPortHandle,
                                      Context,
                                      Msg.h.u1.s1.DataLength >> 1,
                                      ReplyMsg,
                                      TRUE
                                    );
                }
            }
        }

    fprintf( stderr, "Exiting %s\n", ThreadName );

    return RtlNtStatusToDosError( Status );
}



VOID
Usage( VOID )
{
    fprintf( stderr, "usage: USERVER #threads\n" );
    ExitProcess( 1 );
}


int
_cdecl
main(
    int argc,
    char *argv[]
    )
{
    NTSTATUS Status;
    DWORD rc;
    ULONG i, NumberOfThreads;
    OBJECT_ATTRIBUTES ObjectAttributes;

    Status = STATUS_SUCCESS;

    fprintf( stderr, "Entering USERVER User Mode LPC Test Program\n" );

    TestCallBacks = FALSE;
    if (argc < 2) {
        NumberOfThreads = 1;
        }
    else {
        NumberOfThreads = xatoi( argv[ 1 ] );
        if (NumberOfThreads >= MAX_REQUEST_THREADS) {
            Usage();
            }

        if (argc > 2) {
            TestCallBacks = TRUE;
            }
        }

    RtlInitUnicodeString( &PortName, PORT_NAME );
    fprintf( stderr, "Creating %wZ connection port", (PUNICODE_STRING)&PortName );
    InitializeObjectAttributes( &ObjectAttributes, &PortName, 0, NULL, NULL );
    Status = NtCreatePort( &ServerConnectionPortHandle,
                           &ObjectAttributes,
                           40,
                           sizeof( TLPC_PORTMSG ),
                           sizeof( TLPC_PORTMSG ) * 32
                         );
    ShowHandleOrStatus( Status, ServerConnectionPortHandle );
    rc = RtlNtStatusToDosError( Status );
    if (rc == NO_ERROR) {
        ServerThreadHandles[ 0 ] = GetCurrentThread();
        ServerThreadClientIds[ 0 ] = GetCurrentThreadId();
        for (i=1; i<NumberOfThreads; i++) {
            fprintf( stderr, "Creating Server Request Thread %ld\n", i+1 );
            rc = NO_ERROR;
            ServerThreadHandles[ i ] = CreateThread( NULL,
                                                     0,
                                                     (LPTHREAD_START_ROUTINE)ServerThread,
                                                     (LPVOID)(i+1),
                                                     CREATE_SUSPENDED,
                                                     &ServerThreadClientIds[ i ]
                                                   );
            if (ServerThreadHandles[ i ] == NULL) {
                rc = GetLastError();
                break;
                }
            }

        if (rc == NO_ERROR) {
            for (i=1; i<NumberOfThreads; i++) {
                ResumeThread( ServerThreadHandles[ i ] );
                }

            ServerThread( 0 );
            }
        }

    if (rc != NO_ERROR) {
        fprintf( stderr, "USERVER: Initialization Failed - %u\n", rc );
        }

    ExitProcess( rc );
    return( rc );
}
