/****************************************************************************
* C++ Header file:  ANCOM.H
* Description: Analyzer communication driver.
*
****************************************************************************/
// Requires windows.h, process.h

#define DllExport __declspec(dllexport)
#define DllImport __declspec(dllimport) 

#ifdef _EXPORTING
   #define CLASS_DECLSPEC   DllExport
#else
   #define CLASS_DECLSPEC   DllImport
#endif

struct ThrowAbort 
{ 
    int abortType; 
    ThrowAbort( int abt ) { abortType = abt; }
};

#ifndef ANCOM_CPP
DllImport short     linkSelection;
DllImport char      *quitMessages[];
DllImport short     ancomTimeoutTypes;
DllImport char      ancomDriverName[];
DllImport DvrStat   *dvrStat;
#endif

enum { QM_SELF, QM_MASTER, QM_GETFAIL, QM_GET, QM_SENDFAIL, QM_SEND, 
    QM_TXUNINIT, QM_RXUNINIT }; // quitMessages IDs.

/****************************************************************************
* Class:        DvrApi
* Description:  This provides a shell around the unstructured CreateFile/
* DeviceIoControl API presented to applications by a ring 0 driver. 
* Authors:      David McCracken
* ............... notes ................................................
*- DvrApi's constructor doesn't call open or init, both of which are needed
* before the object can be used. This allows greater application and user
* involvement in configuration but also leaves the program vulnerable to 
* attempts to use an initialized driver. This is cheaply caught in the 
* RxClient constructor. However, since there is only one TxServer per app,
* this is usually static and can't be constructed using an initialized driver.
* Therefore, some TxServer functions need to verify that the driver is
* initialized.
*.......................................................................*/
class CLASS_DECLSPEC DvrApi
{
public:
    struct  { ToDvr toDvr; FromDvr fromDvr; };
    DvrApi( int toForceDllSegment = 0 );
    void    command( UINT cmd );
    ULONG getTimeout( char *dest, short max, short idx ) 
    {
        toDvr.gto.dest = dest;
        toDvr.gto.max = max;
        toDvr.gto.idx = idx;
        command( CDXDVR_GETTIMEOUT );
        return fromDvr.gto;
    }
    void setTimeout( short idx, ULONG timeout )
    {
        toDvr.sto.idx = idx;
        toDvr.sto.timeout = timeout;
        command( CDXDVR_SETTIMEOUT );
    }
    void clearStatus( UCHAR clearFailMap, UCHAR clearStatusMap )
    {
        toDvr.cs.clearFailMap = clearFailMap;
        toDvr.cs.clearStatusMap = clearStatusMap;
        command( CDXDVR_CLEARSTATUS );
    }
/* command( CDXDVR_GETSTATUS ) must preceed statusMap, failMap, debugVal functions.
e.g.
        comDvr.command( CDXDVR_GETSTATUS );
        if( comDvr.debugVal() != 0 )
            breakpoint here;
*/
    USHORT  statusMap( void )   { return fromDvr.gs.statusMap; }
    USHORT  failMap( void )     { return fromDvr.gs.failMap; }
    ULONG   debugVal( void )    { return fromDvr.gs.debugVal; }
    
    void getStatusName( char *dest, short max, short idx )
    {
        toDvr.gsn.dest = dest;
        toDvr.gsn.max = max;
        toDvr.gsn.idx = idx;
        command( CDXDVR_GETSTATUSNAME );
    }
    void    reinit( void ) { command( CDXDVR_REINIT ); }
    void    test( UINT op = 0, UINT val = 0 )
    {
        toDvr.test.op = op;
        toDvr.test.val = val;
        command( CDXDVR_TEST );
    }
};
/****************************************************************************
* Class:        TxServer
* Description:  Interfaces app threads to the driver's transmit queue. There
* is only one TxServer per app. Threads are able to share it because it uses a
* MUTEX around class data manipulation. A MUTEX is used instead of
* CriticalSection in order to protect the ring 0 driver from multiple app's
* TxServers. Like DvrApi, a TxServer instance can be instantiated before the
* link type is known.
* Authors:      David McCracken
*.......................................................................*/
class CLASS_DECLSPEC TxServer  // One of these per app. 
{
    enum { EVENT_ABORT = 0, EVENT_TX = 1 }; // events indices.
    HANDLE  txMutex; // Process-relative handle to the multi-process MUTEX.
    HANDLE  events[2]; // ABORT (process-specific) and TX (multi-process).
public:
            TxServer( void ) { txMutex = 0; } // Use as uninitialized indicator.
    void    init( DvrApi *dvr, HANDLE abortEvent );
    void    reinit( void );
    bool    isInitialized( void ) { return txMutex != 0; }
    ULONG   sendMsg( UCHAR *msg, DWORD wait = INFINITE );
    bool    isReceived( ULONG msgId );
};
/****************************************************************************
* Class:        RxClient
* Description:  Interfaces app threads to the driver's message input mechanism.
* Each thread needs its own instance because it is uniquely registered with
* the driver. Unlike DvrApi and TxServer, an RxClient cannot be instantiated
* until the driver has been selected and initialized.
* Authors:      David McCracken
* ............... notes ................................................
* - acceptRange and rejectRange check for a valid object (this != 0) to avoid
* crashing if called through an unassigned RxClient *. Normally, RxClients are
* not instantiated by new, which is the only way that this situation could
* occur, as the user program has no means of calling the functions without
* either an inherently valid instance or a questionable pointer. I put the
* test in because the debugger program (cdxdbg) does instantiate its one
* rxClient using new. Probably no other program has any legitimate reason to
* do this, but the test is very cheap insurance. See cdxdbg.cpp rxThreadProc
* and CaptureRxDlgProc notes. 
*.......................................................................*/
enum 
{   
    RCGM_NORMAL = 0,    // Discard previous (if arg matches) and waits for next.
    RCGM_ONLY_DISCARD,  // Discard previous and return 0.
    RCGM_NO_WAIT        // Discard previous and immediately return next or 0
};  // RxClient.getMsg modes

class CLASS_DECLSPEC RxClient 
{
    enum    { EVENT_ABORT = 0, EVENT_RX = 1 };   // events indices
    HANDLE  events[2];      // ABORT and RX
    DvrApi  *dvr;
    Qctl    *qp; 
public:
    RxClient( DvrApi *dvr, HANDLE abortEvent, UCHAR begin = 0, 
      UCHAR end = 255 ); // Default filter accepts every message (0-255).
    ~RxClient( void ) { unregister(); }
    void    unregister( void ); 
    void    setRxRange( UCHAR begin, UCHAR end, UINT acceptReject ); 
    UCHAR   *getMsg( UCHAR *prev, short mode = RCGM_NORMAL );
    USHORT  *msgCounter( void ) { return &qp->mcount; }
    void    testMultiple( int msgCount );
};

/****************************************************************************
* Class:        ComThreadApi
* Description:  provides a wrapper for the unstructured beginthread interface.
* Authors:      David McCracken
* .............. notes ........................................................
* - The constructor only assigns MB_UNINIT to the mailbox to prevent an
* incompletely initialized instance from being used. Since nothing else is
* done, a ComThreadApi object can be instantiated before the driver is known.
* However, the driver must be known before the object can be used (this is the
* purpose of MB_UNINIT)
*
* - The parent must have a unique ComThreadApi for each simultaneous thread in
* order to have a unique mailbox and quitFor. Currently, each ComThreadApi
* object contains an embedded ComThreadArg, which adds 20 bytes to its
* instance.  This could be declared static and one ComThreadArg could be
* defined for the entire application. However, this approach requires each
* thread to make local copies of ComThreadArg elements and the parent to be
* careful not to start one thread before a previously started one has been
* born. Meeting these requirements reduces the memory savings and increases
* complexity. The resulting memory saving doesn't seem worth the trouble. 
*.......................................................................*/

enum { MB_UNINIT, MB_DEAD, MB_BORN, MB_UNBORN, MB_KILL }; // mailbox values.
typedef struct { 
    DvrApi  *pDvr; 
    int     *pMailbox;
    char    **pQuitFor;
    HANDLE  abortEvent;
    long    extra;  /* Optional application information, typically a pointer to
    data, which must be global to be passed between threads. Unless the child
    thread specifically uses it, this is not needed and can default to 0. */
}   ComThreadArg;

class CLASS_DECLSPEC ComThreadApi
{
    ComThreadArg arg; 
public:
    int     mailbox;
    char    *quitFor;
    ComThreadApi( void )        { mailbox = MB_UNINIT; }
    bool    isInit( void )      { return mailbox != MB_UNINIT; }
    bool    isActive( void )    { return mailbox > MB_DEAD; }
    bool    isRunning( void )   { return mailbox == MB_BORN; }
    bool    isInLimbo( void )   { return mailbox > MB_BORN; }
    void    start( void (*func)( ComThreadArg * ), DvrApi *dvr, 
                      HANDLE abortEvent, long extra = 0 )
    {
        arg.pDvr = dvr;
        arg.pMailbox = &mailbox;
        arg.pQuitFor = &quitFor;
        arg.abortEvent = abortEvent;
        mailbox = MB_UNBORN;
        arg.extra = extra;
        _beginthread( (void (__cdecl *)(void *))func, 0, (void *)&arg );
    }
    void kill( HANDLE abort );
};
/****************************************************************************
* Class:        TxMsgThread
* Description:  Com thread specialized for single message transmitter. By 
* sending a message through this instead of directly to TxServer, a main (user
* interface) thread retains the ability to abort on transmit pipe hangup.
* Functions:
*- send = pass a message to the thread for transmitting.
*- kill = abort the thread.
* Authors:      David McCracken
* ............. notes ....................................................
* - The kill function is used by the parent (presumably) to kill the thread.
* This doesn't affect the TxMsgThread object in any way other than changing
* the contents of msg and sigEvent. First a NULL message is sent to the thread
* in case it is blocking on message input. Then ComThreadApi.kill is called.
* ComThreadApi must be called, even though the thread is usually blocking on
* message input, because the thread may occasionally be blocking on access to
* the transmit queue, but it would report failure of the thread to wake up and
* die if the thread were blocking on message input. Sending a NULL message
* when the thread is blocked on queue access doesn't evoke an error message,
* because it is either awakened from the block by exception, in which case it
* never sees the message, or the queue clears, the thread wakes up (sends the
* blocked message), sees the NULL message and dies.
...........................................................................*/
class TxMsgThread: public ComThreadApi 
{
public:
    UCHAR   *msg;
    HANDLE  sigEvent; // Signals the thread to pick up msg for transmit.
    TxMsgThread( void )
    {
        sigEvent = CreateEvent( 
            NULL,   // Default security for 95/98. NT and 2000 require SYNCHRONIZE. 
            FALSE,  // Automatic reset.
            FALSE,  // Initially not signalled.
            NULL ); // No name.
    }
    ~TxMsgThread() { CloseHandle( sigEvent ); }
    void send( UCHAR *src ) { msg = src; SetEvent( sigEvent ); }
    void kill( HANDLE abort )
    { 
        send( 0 ); // Do before ComThreadApi::kill.
        ComThreadApi::kill( abort ); 
    }
};
/****************************************************************************
* Class:        ComThread
* Authors:      David McCracken
* Description:  Shell for any communication thread, whether transmit, receive,
* or both. This only serves to avoid cut and pasting and to ensure that
* certain things are done by any thread. 
* Functions:
*- Constructor
*   Set the default exit message to direct request by master.
*   Set mailbox to MB_BORN.
*- Destructor
*   Set mailbox to MB_DEAD.
*   endthread.
*- quitFor: set the exit message to the one selected by throw. This is normally
*   used only in catch blocks, as the default serves otherwise.
*
* ............. notes ....................................................
* - ComThread exists to ensure that communication threads are well behaved.  A
* thread can operate without doing these things but other parts of the program
* would then be unable to determine the status of the thread. It would be
* slightly more efficient to let each thread do these jobs itself, because the
* ComThread object requires an otherwise unnecessary copy of the ComThreadArg
* pointer to use in class functions other than the constructor.
*
* - This version assumes a ComThreadArg embedded in each ComThreadApi object.
* This adds 20 bytes to each instance but it enables successive threads to be
* started without waiting for each to be born. The ComThreadApi class could be
* defined with a static ComThreadArg to save some memory at the expense of
* additional launching complexity, i.e. wait for born before starting another
* thread. However, then it is necessary to make local copies of the mailbox
* and quitFor pointers (and the driver pointer unless a local DvrApi instance
* is used). ComThread would contain these and assign them in the constructor.
* However, assigning MB_BORN to the mailbox would have to be deferred until
* the driver were used within the try block, as it is not possible to begin
* the try block in the constructor (even though it is inline) and close it in
* the thread function.  
*
...........................................................................*/
class ComThread 
{
    ComThreadArg *cta;
public:
    ComThread( ComThreadArg *ctap, char *defaultQuitFor )
    {
        cta = ctap; // Copy for class functions.
        *ctap->pQuitFor = defaultQuitFor;
        *ctap->pMailbox = MB_BORN;
    }
    ~ComThread( void )
    {
        *cta->pMailbox = MB_DEAD;
        _endthread();
    } 
    int& mailbox( void )            { return *cta->pMailbox; }
    void quitFor( char *reason )    { *cta->pQuitFor = reason; }
}; 

enum { LINK_HSL, LINK_ECP, LINK_USB };

enum { SHOW_LINK = 1, SHOW_LINKMATCH = 2, SHOW_LINKFAIL = 4, 
    SHOW_LINKALL = 0xFFFF }; // openDriver:show.
short   CLASS_DECLSPEC openDriver( DvrApi *pDvr, short linkSel, HWND hwnd,    
    char *pathexe, USHORT show, FindPorts *fp );
void    CLASS_DECLSPEC closeDriver( void );
int     CLASS_DECLSPEC  getOsVersion( void );
void    CLASS_DECLSPEC  simulateRx( UCHAR *msg );