#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include "ntddmodm.h"

#include "windows.h"
#include "t2prot.h"


VOID
SndProt(
    IN HANDLE Pipe,
    IN UCHAR TokenToSend,
    IN ULONG CurrentLine
    );

BOOL
RcvProt(
    IN HANDLE Pipe,
    IN UCHAR ExpectedProt,
    IN ULONG CurrentLine
    );

int _CRTAPI1 main(int argc,char *argv[]) {

    HANDLE hFile1;
    HANDLE hFile2;
    HANDLE pipeHandle;
    HANDLE remoteHandle;
    HANDLE targetProcessHandle;
    DWORD targetProcessId;
    DWORD numberOfBytesRead;
    DWORD numberOfBytesWritten1;
    DWORD numberOfBytesWritten2;
    DWORD numberOfBytesWritten3;
    UCHAR protocolToken;
    DWORD lastError;
    DWORD waitResult;
    HANDLE duplicatedHandle;
    DWORD numberOfBytesWritten;
    DWORD modemStatus;
    char *MyPort = "\\\\.\\Hayes Optima 144";
    OVERLAPPED olControl;
    OVERLAPPED ol1;
    OVERLAPPED ol2;
    OVERLAPPED ol3;
    OVERLAPPED olMask;
    HANDLE eventArray[3];
    DWORD whatState;
    char writeBuffer1[10];
    char writeBuffer2[10];
    char writeBuffer3[10];
    COMMTIMEOUTS timeOuts = {0};
    DCB hFile1Dcb;
    DWORD repititions = 1000;
    DWORD satisfiedMask;


    //
    // Get the number of types to attempt the test.
    //

    if (argc > 1) {

        sscanf(argv[1],"%d",&repititions);

    }

    if (!(olControl.hEvent = CreateEvent(
                          NULL,
                          FALSE,
                          FALSE,
                          NULL
                          ))) {

        FAILURE(0);

    } else {

        olControl.Internal = 0;
        olControl.InternalHigh = 0;
        olControl.Offset = 0;
        olControl.OffsetHigh = 0;

    }

    if (!(ol1.hEvent = CreateEvent(
                          NULL,
                          FALSE,
                          FALSE,
                          NULL
                          ))) {

        FAILURE(0);

    } else {

        ol1.Internal = 0;
        ol1.InternalHigh = 0;
        ol1.Offset = 0;
        ol1.OffsetHigh = 0;

    }

    if (!(ol2.hEvent = CreateEvent(
                          NULL,
                          FALSE,
                          FALSE,
                          NULL
                          ))) {

        FAILURE(0);

    } else {

        ol2.Internal = 0;
        ol2.InternalHigh = 0;
        ol2.Offset = 0;
        ol2.OffsetHigh = 0;

    }

    if (!(ol3.hEvent = CreateEvent(
                          NULL,
                          FALSE,
                          FALSE,
                          NULL
                          ))) {

        FAILURE(0);

    } else {

        ol3.Internal = 0;
        ol3.InternalHigh = 0;
        ol3.Offset = 0;
        ol3.OffsetHigh = 0;

    }

    if (!(olMask.hEvent = CreateEvent(
                              NULL,
                              FALSE,
                              FALSE,
                              NULL
                              ))) {

        FAILURE(0);

    } else {

        olMask.Internal = 0;
        olMask.InternalHigh = 0;
        olMask.Offset = 0;
        olMask.OffsetHigh = 0;

    }

    //
    // Create/Open the named pipe.
    //

    if ((pipeHandle = CreateNamedPipe(
                          "\\\\.\\pipe\\unitest",
                          PIPE_ACCESS_DUPLEX,
                          0,
                          PIPE_UNLIMITED_INSTANCES,
                          1000,
                          1000,
                          MYPIPETIMEOUT,
                          0
                          )) == INVALID_HANDLE_VALUE) {

        FAILURE(0);

    }

    //
    // Open the named pipe to the remote machine that will
    // set lines for us.
    //
    if ((remoteHandle = CreateFile(
                            "\\\\.\\pipe\\uniremote",
                            GENERIC_READ | GENERIC_WRITE,
                            0,
                            NULL,
                            CREATE_ALWAYS,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL
                            )) == ((HANDLE)-1)) {

        FAILURE(GetLastError());

    }


    //
    // We created the named pipe.  Now connect to it so that
    // we can wait for the client to start up.
    //

    if (!ConnectNamedPipe(
             pipeHandle,
             NULL
             )) {

        FAILURE(GetLastError());

    }

    eventArray[0] = ol1.hEvent;
    eventArray[1] = ol2.hEvent;
    eventArray[2] = ol3.hEvent;

    //
    // Read the process handle for the process that wants the
    // duplicate
    //

    if (!ReadFile(
             pipeHandle,
             &targetProcessId,
             sizeof(targetProcessId),
             &numberOfBytesRead,
             NULL
             )) {

        FAILURE(GetLastError());

    }

    //
    // Get the target process Handle.
    //

    targetProcessHandle = OpenProcess(
                              PROCESS_DUP_HANDLE,
                              FALSE,
                              targetProcessId
                              );

    if (targetProcessHandle == NULL) {

        FAILURE(GetLastError());

    }

    //
    // We opened the modem twice.  Get our current process handle.
    //
    // Open the pipe \\.\pipe\unitest
    //
    // Read from the pipe the process handle of the client
    //
    // Create a duplicate of the second handle using the process
    // handle of the client.
    //
    // Send the duplicate file handle to the client.  Wait on the
    // process handle of the client to go away.  Then we can exit
    // also.
    //

    do {

        if ((hFile1 = CreateFile(
                         MyPort,
                         GENERIC_READ | GENERIC_WRITE,
                         FILE_SHARE_WRITE | FILE_SHARE_READ,
                         NULL,
                         CREATE_ALWAYS,
                         FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                         NULL
                         )) == ((HANDLE)-1)) {

            FAILURE(GetLastError());

        }

        if (!GetCommState(
                 hFile1,
                 &hFile1Dcb
                 )) {

            FAILURE(GetLastError());

        }

        //
        // Set the state just how we want it.
        //

        hFile1Dcb.BaudRate = 9600;
        hFile1Dcb.ByteSize = 8;
        hFile1Dcb.Parity = NOPARITY;
        hFile1Dcb.StopBits = ONESTOPBIT;

        //
        // Make sure that no flow control is turned on.
        //

        hFile1Dcb.fOutxDsrFlow = FALSE;
        hFile1Dcb.fOutxCtsFlow = FALSE;
        hFile1Dcb.fDsrSensitivity = FALSE;
        hFile1Dcb.fOutX = FALSE;
        hFile1Dcb.fInX = FALSE;
        hFile1Dcb.fDtrControl = DTR_CONTROL_ENABLE;
        hFile1Dcb.fRtsControl = RTS_CONTROL_ENABLE;

        if (!SetCommState(
                 hFile1,
                 &hFile1Dcb
                 )) {

            FAILURE(GetLastError());

        }

        if (!SetCommTimeouts(
                 hFile1,
                 &timeOuts
                 )) {

            FAILURE(GetLastError());

        }

        if ((hFile2 = CreateFile(
                         MyPort,
                         GENERIC_READ | GENERIC_WRITE,
                         FILE_SHARE_WRITE | FILE_SHARE_READ,
                         NULL,
                         CREATE_ALWAYS,
                         FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                         NULL
                         )) == ((HANDLE)-1)) {

            FAILURE(GetLastError());

        }

        if (!DuplicateHandle(
                 GetCurrentProcess(),
                 hFile2,
                 targetProcessHandle,
                 &duplicatedHandle,
                 0,
                 TRUE,
                 DUPLICATE_SAME_ACCESS
                 )) {

            FAILURE(GetLastError());

        }

        //
        // We have the duplicated handle.  Close the original one.
        //

        if (!CloseHandle(hFile2)) {

            FAILURE(GetLastError());

        }

        //
        // Send the handle back to the client.
        //

        if (!WriteFile(
                 pipeHandle,
                 &duplicatedHandle,
                 sizeof(duplicatedHandle),
                 &numberOfBytesWritten,
                 NULL
                 )) {

            FAILURE(GetLastError());

        }

        //
        // Wait for the next byte from the client.  This tells us
        // that the client has queued off a bunch of IO's.
        //

        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_STRTED_1_IOS
            );

        //
        // At this point we are going to put the device into no-passthrough.
        // This should cause all of the io's to complete.
        //

        whatState = MODEM_NOPASSTHROUGH;
        if (!DeviceIoControl(
                 hFile1,
                 IOCTL_MODEM_SET_PASSTHROUGH,
                 &whatState,
                 sizeof(whatState),
                 NULL,
                 0,
                 &numberOfBytesWritten,
                 &olControl
                 )) {

            lastError = GetLastError();

            if (lastError != ERROR_IO_PENDING) {

                FAILURE(lastError);

            }

            waitResult = WaitForSingleObject(
                             olControl.hEvent,
                             10000
                             );
            if (waitResult == WAIT_FAILED) {

                FAILURE(waitResult);

            } else {

                if (waitResult != WAIT_OBJECT_0) {

                    FAILURE(waitResult);

                }

            }

        }

        //
        // Read that the clients are all done with the io.
        //

        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_DONE_1_IOS
            );

        //
        // Write out a byte to the client.  This will be our indication that
        // we've gone into no passthrough mode and it should try a doomed write.
        //

        SNDPROT(
            pipeHandle,
            TPROT_SRVR_SET_NOPASS
            );

        //
        // All of the IO's are done from the client.  send io's to
        // the device to make sure we the owner can still do io.
        //

        if (!WriteFile(
                 hFile1,
                 &writeBuffer1[0],
                 sizeof(writeBuffer1),
                 &numberOfBytesWritten1,
                 &ol1
                 )) {

            lastError = GetLastError();
            if (lastError != ERROR_IO_PENDING) {

                FAILURE(lastError);

            }

        }
        if (!WriteFile(
                 hFile1,
                 &writeBuffer2[0],
                 sizeof(writeBuffer2),
                 &numberOfBytesWritten2,
                 &ol2
                 )) {

            lastError = GetLastError();
            if (lastError != ERROR_IO_PENDING) {

                FAILURE(lastError);

            }

        }
        if (!WriteFile(
                 hFile1,
                 &writeBuffer3[0],
                 sizeof(writeBuffer3),
                 &numberOfBytesWritten3,
                 &ol3
                 )) {

            lastError = GetLastError();
            if (lastError != ERROR_IO_PENDING) {

                FAILURE(lastError);

            }

        }

        //
        // Wait for all the writes to complete.
        //

        waitResult = WaitForMultipleObjects(
                         3,
                         &eventArray[0],
                         TRUE,
                         10000
                         );

        if (waitResult == WAIT_FAILED) {

            FAILURE(GetLastError());

        }

        if ((waitResult < WAIT_OBJECT_0) ||
            (waitResult > (WAIT_OBJECT_0 + 2))) {

            FAILURE(waitResult);

        }

        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_TRIED_DOOM_WRITE
            );

        //
        // Put it back into non-sniffing passthrough mode.
        //
        whatState = MODEM_PASSTHROUGH;
        if (!DeviceIoControl(
                 hFile1,
                 IOCTL_MODEM_SET_PASSTHROUGH,
                 &whatState,
                 sizeof(whatState),
                 NULL,
                 0,
                 &numberOfBytesWritten,
                 &olControl
                 )) {


            lastError = GetLastError();

            if (lastError != ERROR_IO_PENDING) {

                FAILURE(lastError);

            }

            waitResult = WaitForSingleObject(
                             olControl.hEvent,
                             10000
                             );
            if (waitResult == WAIT_FAILED) {

                FAILURE(waitResult);

            } else {

                if (waitResult != WAIT_OBJECT_0) {

                    FAILURE(waitResult);

                }

            }
        }

        //
        // Send a byte to the client so that it will try to do some
        // io's cause we are back in passthrough mode again.
        //

        SNDPROT(
            pipeHandle,
            TPROT_SRVR_SET_PASS
            );

        //
        // Wait for a byte from the client telling us that it strted ios.
        //
        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_STRTED_2_IOS
            );

        //
        // Wait for a byte from the client telling us that it finished ios.
        //
        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_DONE_2_IOS
            );

        //
        // Put the device into dcd sniff mode.
        //

        whatState = MODEM_DCDSNIFF;
        if (!DeviceIoControl(
                 hFile1,
                 IOCTL_MODEM_SET_PASSTHROUGH,
                 &whatState,
                 sizeof(whatState),
                 NULL,
                 0,
                 &numberOfBytesWritten,
                 &olControl
                 )) {

            lastError = GetLastError();

            if (lastError != ERROR_IO_PENDING) {

                FAILURE(lastError);

            }

            waitResult = WaitForSingleObject(
                             olControl.hEvent,
                             10000
                             );
            if (waitResult == WAIT_FAILED) {

                FAILURE(waitResult);

            } else {

                if (waitResult != WAIT_OBJECT_0) {

                    FAILURE(waitResult);

                }

            }

        }

        //
        // Tell the client we are sniff mode.
        //

        SNDPROT(
            pipeHandle,
            TPROT_SRVR_SET_SNIFF
            );

        //
        // Wait for a byte from the client telling us that it knows.
        // about the sniff and that it queued operations
        //
        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_ACK_SNIFF
            );

        //
        // Wait for a byte from the client telling us that it
        // is done doing various setmasks.
        //

        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_DID_SETMASKS
            );

        if (!SetCommMask(
                 hFile1,
                 EV_RXFLAG
                 )) {

            FAILURE(GetLastError());

        }

        if (!SetCommMask(
                 hFile1,
                 EV_RXFLAG | EV_DSR | EV_RLSD
                 )) {

            FAILURE(GetLastError());

        }

        //
        // Tell the client we are done with setmasks
        //

        SNDPROT(
            pipeHandle,
            TPROT_SRVR_SET_SETMASKS
            );

        //
        // Tell the remote end that we are done setting masks
        // and that we want a dcd transition.
        //
        SNDPROT(
            remoteHandle,
            TPROT_SRVR_DO_DCDTRANS
            );

        //
        // Wait for the client to tell us it did the
        // transition.  We should be able to check the
        // modem state and find our that we are in
        // no passthrough.  We should tell our local
        // client to check that it's ios are all done.
        //

        RCVPROT(
            remoteHandle,
            TPROT_CLIENT_DID_DCDTRANS
            );

        if (!DeviceIoControl(
                 hFile1,
                 IOCTL_MODEM_GET_PASSTHROUGH,
                 &whatState,
                 sizeof(whatState),
                 &whatState,
                 sizeof(whatState),
                 &numberOfBytesWritten,
                 &olControl
                 )) {

            lastError = GetLastError();

            if (lastError != ERROR_IO_PENDING) {

                FAILURE(lastError);

            }

            waitResult = WaitForSingleObject(
                             olControl.hEvent,
                             10000
                             );
            if (waitResult == WAIT_FAILED) {

                FAILURE(waitResult);

            } else {

                if (waitResult != WAIT_OBJECT_0) {

                    FAILURE(waitResult);

                }

            }

            if (whatState != MODEM_NOPASSTHROUGH) {

                FAILURE(0);

            }

        }

        //
        // Now tell the local client to check that its ios are all
        // done.
        //

        SNDPROT(
            pipeHandle,
            TPROT_CLIENT_DID_DCDTRANS
            );

        //
        // The local client should tell us it's done with dcd trans
        // ios.
        //

        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_DONE_DCDTRANS
            );

        //
        // Tell the remote client all done with dcd trans
        //

        SNDPROT(
            remoteHandle,
            TPROT_CLIENT_DONE_DCDTRANS
            );

        //
        // The local client no should ask us for a break.
        //

        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_WANTS_BREAK
            );

        SNDPROT(
            remoteHandle,
            TPROT_CLIENT_WANTS_BREAK
            );

        RCVPROT(
            remoteHandle,
            TPROT_CLIENT_SHOULDA_BROKE
            );

        SNDPROT(
            pipeHandle,
            TPROT_CLIENT_SHOULDA_BROKE
            );

        RCVPROT(
            pipeHandle,
            TPROT_DONE_BREAK
            );

        //
        // Put us into passthrough for the next test.
        //

        whatState = MODEM_PASSTHROUGH;
        if (!DeviceIoControl(
                 hFile1,
                 IOCTL_MODEM_SET_PASSTHROUGH,
                 &whatState,
                 sizeof(whatState),
                 NULL,
                 0,
                 &numberOfBytesWritten,
                 &olControl
                 )) {

            lastError = GetLastError();

            if (lastError != ERROR_IO_PENDING) {

                FAILURE(lastError);

            }

            waitResult = WaitForSingleObject(
                             olControl.hEvent,
                             10000
                             );
            if (waitResult == WAIT_FAILED) {

                FAILURE(waitResult);

            } else {

                if (waitResult != WAIT_OBJECT_0) {

                    FAILURE(waitResult);

                }

            }

        }

        //
        // Wait for the client to tell us it wants to set it mask up.
        //

        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_WANTS_NEWMASK
            );

        if (!SetCommMask(
                 hFile1,
                 0
                 )) {

            FAILURE(GetLastError());

        }

        if (!SetCommMask(
                 hFile1,
                 EV_DSR | EV_RLSD
                 )) {

            FAILURE(GetLastError());

        }

        SNDPROT(
            pipeHandle,
            TPROT_SRVR_SAYS_DONEWMASK
            );

        //
        // After the client sets up its mask, we queue off a wait so
        // that we can make sure that we are the passed down wait
        // in the modem driver.
        //

        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_DONE_NEWMASK
            );

        //
        // Start off the wait.
        //

        satisfiedMask = 0;
        if (!WaitCommEvent(
                 hFile1,
                 &satisfiedMask,
                 &olMask
                 )) {

            lastError = GetLastError();
            if (lastError != ERROR_IO_PENDING) {

                FAILURE(lastError);

            }

        } else {

            FAILURE(satisfiedMask);

        }

        //
        // Tell the client to go ahead.
        //

        SNDPROT(
            pipeHandle,
            TPROT_SRVR_HEARD_DONENEWMASK
            );

        //
        // Wait for the client to tell us he set up the wait.
        //

        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_DONE_NEWWAITMASK
            );

        //
        // Make sure that our wait is still pending.
        //

        waitResult = WaitForSingleObject(
                         olMask.hEvent,
                         0
                         );

        if (waitResult != WAIT_TIMEOUT) {

            FAILURE(waitResult);

        }

        //
        // Tell the remote pipe to go ahead and send the break.
        //

        SNDPROT(
            remoteHandle,
            TPROT_REMOTE_SEND_NEW_BREAK
            );

        //
        // Wait for the remote to tell us that it set and then cleared
        // the break.
        //

        RCVPROT(
            remoteHandle,
            TPROT_REMOTE_DONE_NEW_BREAK
            );

        //
        // Make sure that our wait is still pending.
        //

        waitResult = WaitForSingleObject(
                         olMask.hEvent,
                         0
                         );

        if (waitResult != WAIT_TIMEOUT) {

            FAILURE(satisfiedMask);

        }

        //
        // Tell the client to make sure that it's wait is done.
        //

        SNDPROT(
            pipeHandle,
            TPROT_SRVR_HEARD_DONENEWWAITMASK
            );

        RCVPROT(
            pipeHandle,
            TPROT_CLIENT_DONE_NEWGETRESULTS
            );

        //
        // Make sure ours is still not finished.
        //
        // Do a setmask to make sure ours is done.
        //

        waitResult = WaitForSingleObject(
                         olMask.hEvent,
                         0
                         );

        if (waitResult != WAIT_TIMEOUT) {

            FAILURE(satisfiedMask);

        }

        if (!SetCommMask(
                 hFile1,
                 0
                 )) {

            FAILURE(GetLastError());

        }

        waitResult = WaitForSingleObject(
                         olMask.hEvent,
                         0
                         );

        if (waitResult != WAIT_OBJECT_0) {

            FAILURE(waitResult);

        }


        //
        // Make sure that it's return value is zero.
        //

        if (!GetOverlappedResult(
                 hFile1,
                 &olMask,
                 &numberOfBytesWritten,
                 FALSE
                 )) {

            FAILURE(GetLastError());

        }

        if (numberOfBytesWritten != sizeof(DWORD)) {

            FAILURE(numberOfBytesWritten);

        }

        if (satisfiedMask) {

            FAILURE(satisfiedMask);

        }

        //
        // Close the main file handle
        //

        if (!CloseHandle(hFile1)) {

            FAILURE(GetLastError());

        }

        repititions--;

        if (repititions > 0) {

            //
            // Pipe through that we are going to do another round.
            //

            SNDPROT(
                pipeHandle,
                TPROT_SRVR_ANOTHER_TEST
                );

            SNDPROT(
                remoteHandle,
                TPROT_SRVR_ANOTHER_TEST
                );

            //
            // Wait for acknowledgement of another round.
            //
            RCVPROT(
                pipeHandle,
                TPROT_CLIENT_ACK_ANOTHER_TEST
                );
            RCVPROT(
                remoteHandle,
                TPROT_CLIENT_ACK_ANOTHER_TEST
                );

        } else {

            SNDPROT(
                pipeHandle,
                TPROT_SRVR_DONE_TEST
                );
            SNDPROT(
                remoteHandle,
                TPROT_SRVR_DONE_TEST
                );

            //
            // Wait for acknowledgement of no more rounds.
            //
            RCVPROT(
                pipeHandle,
                TPROT_CLIENT_ACK_DONE_TEST
                );
            RCVPROT(
                remoteHandle,
                TPROT_CLIENT_ACK_DONE_TEST
                );

            break;

        }

    } while (TRUE);

    if (!CloseHandle(pipeHandle)) {

        FAILURE(GetLastError());

    }

    exit(1);
    return 1;
}
VOID
SndProt(
    IN HANDLE Pipe,
    IN UCHAR TokenToSend,
    IN ULONG CurrentLine
    )

{

    UCHAR protocolToken = TokenToSend;
    DWORD numberOfBytesWritten;

    if (!WriteFile(
             Pipe,
             &protocolToken,
             sizeof(protocolToken),
             &numberOfBytesWritten,
             NULL
             )) {

        printf(
            "\nCouldn't send token: %d - at line:%d - error: %d\n",
            protocolToken,
            CurrentLine,
            GetLastError()
            );
        exit(1);

    }
}
BOOL
RcvProt(
    IN HANDLE Pipe,
    IN UCHAR ExpectedProt,
    IN ULONG CurrentLine
    )

{
    UCHAR protocolToken = ExpectedProt;
    DWORD numberOfBytesPiped;

    if (!ReadFile(
             Pipe,
             &protocolToken,
             sizeof(protocolToken),
             &numberOfBytesPiped,
             NULL
             )) {

        printf(
            "\nCouldn't read the protocol value at line %d - error: %d\n",
            CurrentLine,
            GetLastError()
            );
        exit(1);

    }

    if (protocolToken != ExpectedProt) {

        //
        // If it isn't the expected protocol perhaps it's the
        // terminating protocol.  If it is return TRUE.
        //

        if (protocolToken == TPROT_SRVR_DONE_TEST) {

            return TRUE;

        } else {

            printf(
                "Protocol out of sync: %d/%d (token/required) at line: %d\n",
                protocolToken,
                ExpectedProt,
                CurrentLine
                );
            exit(1);

        }

    }

    return FALSE;

}