/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples.
*       Copyright 1995 - 1997 Microsoft Corporation.
*       All rights reserved.
*       This source code is only intended as a supplement to
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the
*       Microsoft samples programs.
\******************************************************************************/

/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    SrvStoC.c

Abstract:

    This file implements the server-to-client flow
    of data for remote server.  The data is the output
    of the child program intermingled with client input.

Author:

    Dave Hart  30 May 1997

Environment:

    Console App. User mode.

Revision History:

--*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include "Remote.h"
#include "Server.h"


VOID
FASTCALL
StartServerToClientFlow(
    VOID
    )
{
    PREMOTE_CLIENT pClient;

    //
    // Start read operations against the temp file for
    // all active clients that aren't currently doing
    // read temp/write client operations and that are
    // fully connected.
    //

    for (pClient = (PREMOTE_CLIENT) ClientListHead.Flink;
         pClient != (PREMOTE_CLIENT) &ClientListHead;
         pClient = (PREMOTE_CLIENT) pClient->Links.Flink ) {


        if (! pClient->cbWrite) {

            StartReadTempFile( pClient );
        }
    }
}


VOID
FASTCALL
StartReadTempFile(
    PREMOTE_CLIENT pClient
    )
{
    //
    // pClient->cbWrite is used dually.  WriteSessionOutputCompleted
    // uses it when 0 bytes are written to know how much to ask
    // to write when it resubmits the request.  We use it to
    // indicate whether a read temp/write session chain of I/Os
    // is currently active for this client.
    //

    if (pClient->cbWrite) {

        ErrorExit("StartReadTempFile entered with nonzero cbWrite.");
    }

    if (dwWriteFilePointer > pClient->dwFilePos) {

        pClient->cbWrite = min(BUFFSIZE,
                               dwWriteFilePointer - pClient->dwFilePos);

        pClient->WriteOverlapped.OffsetHigh = 0;
        pClient->WriteOverlapped.Offset = pClient->dwFilePos;

        if ( ! ReadFileEx(
                   pClient->rSaveFile,
                   pClient->ReadTempBuffer,
                   pClient->cbWrite,
                   &pClient->WriteOverlapped,
                   ReadTempFileCompleted
                   )) {

            if (ERROR_HANDLE_EOF == GetLastError()) {

                pClient->cbWrite = 0;

            } else {

                TRACE(SESSION, ("ReadFileEx for temp file failed error %d, closing client.\n", GetLastError()));

                CloseClient(pClient);
            }
        }

    }
}

VOID
WINAPI
ReadTempFileCompleted(
    DWORD dwError,
    DWORD cbRead,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, WriteOverlapped);

    if (HandleSessionError(pClient, dwError)) {

        return;
    }


    if (cbRead != pClient->cbWrite) {

        TRACE(SESSION, ("Read %d from temp file asked for %d\n", cbRead, pClient->cbWrite));
    }

    if (cbRead) {

        pClient->cbReadTempBuffer = cbRead;
        pClient->dwFilePos += cbRead;

        StartWriteSessionOutput(pClient);

    } else {

        //
        // Note that the server to client flow is halting for now
        // for this client.
        //

        pClient->cbWrite = 0;
    }
}


VOID
FASTCALL
StartWriteSessionOutput(
    PREMOTE_CLIENT pClient
    )
{
    DWORD cbRead;
    char *pch;

    cbRead = pClient->cbReadTempBuffer;

    //
    // We need to split commands from other text read
    // from the temp file and hold off on writing them
    // to the client until we make sure we're not the
    // client that submitted it.  This isn't perfect
    // since we match on client name which can be
    // duplicated but it solves the problem of
    // duplicated input most of the time.
    //

    for (pch = pClient->ReadTempBuffer;
         pch < pClient->ReadTempBuffer + cbRead;
         pch++) {

        if ( ! (pClient->ServerFlags & SFLG_READINGCOMMAND) ) {

            if (BEGINMARK == *pch) {

                pClient->ServerFlags |= SFLG_READINGCOMMAND;

                if (pch != pClient->ReadTempBuffer &&
                    pClient->cbWriteBuffer) {

                    //
                    // Start a write of everything we've come across
                    // before the start of this command, with
                    // WriteSessionOutputCompletedWriteNext specified
                    // so we can continue processing the remainder
                    // of pReadTempBuffer.
                    //

                    pClient->cbReadTempBuffer -= (DWORD)( pch - pClient->ReadTempBuffer) + 1;
                    cbRead = pClient->cbReadTempBuffer;

                    #if DBG
                        if (pClient->cbReadTempBuffer == (DWORD)-1) {
                            ErrorExit("cbReadTempBuffer underflow.");
                        }
                    #endif

                    MoveMemory(pClient->ReadTempBuffer, pch + 1, cbRead);

                    pClient->cbWrite = pClient->cbWriteBuffer;

                    pClient->WriteOverlapped.OffsetHigh = 0;
                    pClient->WriteOverlapped.Offset = 0;

                    if ( ! WriteFileEx(
                               pClient->PipeWriteH,
                               pClient->WriteBuffer,
                               pClient->cbWrite,
                               &pClient->WriteOverlapped,
                               WriteSessionOutputCompletedWriteNext
                               )) {

                        CloseClient(pClient);
                    }

                    TRACE(SESSION, ("%p Wrote %d bytes pre-command output\n", pClient, pClient->cbWrite));

                    pClient->cbWriteBuffer = 0;

                    return;
                }

            } else {

                if (pClient->cbWriteBuffer == BUFFSIZE) {

                    ErrorExit("cbWriteBuffer overflow");
                }

                pClient->WriteBuffer[ pClient->cbWriteBuffer++ ] = *pch;
            }

        } else {

            if (ENDMARK == *pch ||
                pClient->cbCommandBuffer == BUFFSIZE) {

                pClient->ServerFlags &= ~SFLG_READINGCOMMAND;

                //
                // Preceding ENDMARK is the pClient in hex ascii of the
                // client that generated the command, not null terminated.
                //

                if (ENDMARK == *pch) {

                    pClient->cbCommandBuffer -=
                        min(pClient->cbCommandBuffer, sizeof(pClient->HexAsciiId));

                }

                //
                // We hide each client's input from their output pipe
                // because their local remote.exe has already displayed it.
                //

                if ( pClient->cbCommandBuffer &&
                     ! (ENDMARK == *pch &&
                        ! memcmp(
                              pch - sizeof(pClient->HexAsciiId),
                              pClient->HexAsciiId,
                              sizeof(pClient->HexAsciiId)))) {

                    //
                    // Start a write of the accumulated command with
                    // WriteSessionOutputCompletedWriteNext specified
                    // so we can continue processing the remainder
                    // of pReadTempBuffer.
                    //

                    pClient->cbReadTempBuffer -= (DWORD)(pch - pClient->ReadTempBuffer) + 1;
                    MoveMemory(pClient->ReadTempBuffer, pch + 1, pClient->cbReadTempBuffer);

                    pClient->cbWrite = pClient->cbCommandBuffer;
                    pClient->cbCommandBuffer = 0;

                    pClient->WriteOverlapped.OffsetHigh = 0;
                    pClient->WriteOverlapped.Offset = 0;

                    if ( ! WriteFileEx(
                               pClient->PipeWriteH,
                               pClient->CommandBuffer,
                               pClient->cbWrite,
                               &pClient->WriteOverlapped,
                               WriteSessionOutputCompletedWriteNext
                               )) {

                        CloseClient(pClient);
                        return;

                    } else {

                        TRACE(SESSION, ("%p Wrote %d bytes command\n", pClient, pClient->cbWrite));

                        return;

                    }

                } else {

                    //
                    // We're eating this command for this session.
                    //

                    pClient->cbCommandBuffer = 0;
                }

            } else {

                pClient->CommandBuffer[ pClient->cbCommandBuffer++ ] = *pch;

            }
        }
    }

    //
    // We're done with the ReadTempBuffer.
    //

    pClient->cbReadTempBuffer = 0;

    if (pClient->cbWriteBuffer) {

        pClient->cbWrite = pClient->cbWriteBuffer;

        pClient->WriteOverlapped.OffsetHigh = 0;
        pClient->WriteOverlapped.Offset = 0;

        if ( ! WriteFileEx(
                   pClient->PipeWriteH,
                   pClient->WriteBuffer,
                   pClient->cbWrite,
                   &pClient->WriteOverlapped,
                   WriteSessionOutputCompletedReadNext
                   )) {

            CloseClient(pClient);
            return;

        } else {

            TRACE(SESSION, ("%p Wrote %d bytes normal\n", pClient, pClient->cbWrite));

            pClient->cbWriteBuffer = 0;
        }

    } else {

        //
        // Write buffer is empty.
        //

        pClient->cbWrite = 0;

        StartReadTempFile(pClient);

    }
}


BOOL
FASTCALL
WriteSessionOutputCompletedCommon(
    PREMOTE_CLIENT pClient,
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
    )
{
    if (HandleSessionError(pClient, dwError)) {

        return TRUE;
    }

    if (!pClient->cbWrite) {

        ErrorExit("Zero cbWrite in WriteSessionOutputCompletedCommon");
    }

    if (!cbWritten && pClient->cbWrite) {

        printf("WriteSessionOutput zero bytes written of %d.\n", pClient->cbWrite);
        ErrorExit("WriteSessionOutputCompletedCommon failure");

        return TRUE;
    }

    #if DBG
        if (cbWritten != pClient->cbWrite) {
            printf("%p cbWritten %d cbWrite %d\n", pClient, cbWritten, pClient->cbWrite);
        }
    #endif

    return FALSE;
}


VOID
WINAPI
WriteSessionOutputCompletedWriteNext(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, WriteOverlapped);

    if (WriteSessionOutputCompletedCommon(
            pClient,
            dwError,
            cbWritten,
            WriteSessionOutputCompletedWriteNext
            )) {

        return;
    }

    StartWriteSessionOutput(pClient);
}


VOID
WINAPI
WriteSessionOutputCompletedReadNext(
    DWORD dwError,
    DWORD cbWritten,
    LPOVERLAPPED lpO
    )
{
    PREMOTE_CLIENT pClient;

    pClient = CONTAINING_RECORD(lpO, REMOTE_CLIENT, WriteOverlapped);

    if (WriteSessionOutputCompletedCommon(
            pClient,
            dwError,
            cbWritten,
            WriteSessionOutputCompletedReadNext
            )) {

        return;
    }

    //
    // Start another temp file read.
    //

    pClient->cbWrite = 0;

    StartReadTempFile(pClient);
}