//****************************************************************************
//
//  Module:     Unimdm
//  File:       mdmutil.c
//
//  Copyright (c) 1992-1993, Microsoft Corporation, all rights reserved
//
//  Revision History
//
//
//  6/15/93     Nick Manson             Revised OpenModem and CloseModem calls
//  1/6/93      Viroon Touranachun      Revised for RNA
//
//
//  Description: All Initialization code for rasman component lives here.
//
//****************************************************************************

#include "unimdm.h"
#include "umdmspi.h"
#include <devioctl.h>
#include <ntddser.h>
#include <slot.h>

#define STOP_TIMER_EVENT        0
#define RECALC_TIMER_EVENT      1
#define TSP_NOTIFICATION_EVENT  2
#define MAX_TIMER_EVENTS        3
#define NUM_TIMER_EVENTS(_tlist) (((_tlist).hN)?3:2)

// Timer list
//
typedef struct tagMdmTimer {
    struct tagMdmTimer  *pNext;           // pointer to next CB
    DWORD               dwCompletionKey;  // for PostQueuedCompletionStatus
    LPOVERLAPPED        lpOverlapped;     // for PostQueuedCompletionStatus
    DWORD               dwWakeup;         // wake-up time
}   MDMTIMER, *PMDMTIMER;

typedef struct tagMdmTimerList  {
    PMDMTIMER           pList;
    HANDLE              hEvent[MAX_TIMER_EVENTS];
    CRITICAL_SECTION    hSem;
    HNOTIFICATION       hN;

}   TIMERLIST, *PTIMERLIST;

// LIGHTS application name
//
#define LIGHTSAPP_EXE_NAME   TEXT("lights.exe")

/*****************************************************************************
* Global Parameters
*****************************************************************************/

MDMLIST     gMdmList;
TIMERLIST   gTimerList;
HANDLE      ghtdTimer;
DWORD       gtidTimerMdm;

// ******* SOME PRIVATES *************
void ProcessNotification(HNOTIFICATION hN);
BOOL ValidateFrame(PNOTIFICATION_FRAME pnf, DWORD dwTrueSize);
void ProcessFrame(PNOTIFICATION_FRAME pnf);

//****************************************************************************
// BOOL InitCBList()
//
// Function: This function initilaizes the CB list
//
// Returns:  TRUE always
//
// Fri 14-Apr-1995 12:47:26  -by-  Viroon  Touranachun [viroont]
//  created
//****************************************************************************

BOOL InitCBList (HINSTANCE hInstance)
{
  // Initialize the modem list
  //
  INITCRITICALSECTION(gMdmList.hSem);
  gMdmList.pList = NULL;
  gMdmList.cModems = 0;
  return TRUE;
}

//****************************************************************************
// void DeinitCBList()
//
// Function: This function deinitilaizes the CB list
//
// Returns:  None
//
// Fri 14-Apr-1995 12:47:26  -by-  Viroon  Touranachun [viroont]
//  created
//****************************************************************************

void DeinitCBList (HINSTANCE hInstance)
{
  // Do nothing
  //
  DELETECRITICALSECTION(gMdmList.hSem);
  return;
}

//****************************************************************************
// BOOL MdmInitTracing()
//
// Function: Performs tracing-related initialization.
//
// Returns:  None
//
// 3/29/96 JosephJ Created
//****************************************************************************
void MdmInitTracing(void)
{
    traceRegisterObject(
        &gMdmList,
        TSP_MODEM_LIST_GUID,
        TSP_MODEM_LIST_VERSION,
        0,
        0
    );
}


//****************************************************************************
// BOOL MdmDeinitTracing()
//
// Function: Performs tracing-related de-initialization.
//
// Returns:  None
//
// 3/29/96 JosephJ Created
//****************************************************************************
void MdmDeinitTracing(void)
{
    traceUnRegisterObject(&gMdmList, 0, 0);
}

//****************************************************************************
// PLINEDEV AllocateCB (UINT cbSize)
//
// Function: Allocates a line device control block
//
// Returns: The pointer to the control block if successful, otherwise NULL.
//
// Fri 14-Apr-1995 12:47:57  -by-  Viroon  Touranachun [viroont]
//   created
//****************************************************************************

PLINEDEV AllocateCB(UINT cbSize)
{
  PLINEDEV pLineDev;

  // Allocate from the process heap
  //
  pLineDev = (PLINEDEV)LocalAlloc(LPTR, cbSize);

  if (pLineDev == NULL)
    return NULL;

  // Ininitialize the initial contents
  //
  pLineDev->pNext       = (PLINEDEV)NULL;
  pLineDev->dwVersion   = UMDM_VERSION;
  INITCRITICALSECTION(pLineDev->hSem);

  return pLineDev;
}

//****************************************************************************
// DWORD AddCBToList(PLINEDEV)
//
// Function: Inserts a line control block to the global modem list
//
// Returns: SUCCESS or an error code
//
// Fri 14-Apr-1995 12:47:57  -by-  Viroon  Touranachun [viroont]
//   created
//****************************************************************************

DWORD AddCBToList(PLINEDEV pLineDev)
{
  // Validate the structure
  //
  if (!ISLINEDEV(pLineDev))
    return ERROR_INVALID_HANDLE;

  // Exclusively access the modem list
  //
  ENTERCRITICALSECTION(gMdmList.hSem);

  // Insert the new node into the global list
  //
  pLineDev->pNext = gMdmList.pList;
  gMdmList.pList  = pLineDev;
  gMdmList.cModems++;

  // Release the modem list
  //
  LEAVECRITICALSECTION(gMdmList.hSem);

  return ERROR_SUCCESS;
}

//****************************************************************************
// DWORD DeleteCB(PLINEDEV pLineDev )
//
// Function: Removes a line control block to the global modem list and
//           deallocate the buffer.
//
// Returns: SUCCESS or an error code
//
// Fri 14-Apr-1995 12:47:57  -by-  Viroon  Touranachun [viroont]
//   created
//****************************************************************************

DWORD DeleteCB(PLINEDEV pLineDev)
{
  PLINEDEV pCurCB, pPrevCB;

  // Validate the structure
  //
  if (!ISLINEDEV(pLineDev))
    return ERROR_INVALID_HANDLE;

  // Exclusively access the modem list
  //
  ENTERCRITICALSECTION(gMdmList.hSem);

  // Start from the head of the CB list
  //
  pPrevCB = NULL;
  pCurCB  = gMdmList.pList;

  // traverse the list to find the specified CB
  //
  while (pCurCB != NULL)
  {
    if (pCurCB == pLineDev)
    {
      // Decrement the modem count
      //
      gMdmList.cModems--;
        
      // Is there a previous control block?
      //
      if (pPrevCB == NULL)
      {
        // head of the list
        //
        gMdmList.pList = pCurCB->pNext;
      }
      else
      {
        pPrevCB->pNext = pCurCB->pNext;
      };
      break;
    };

    pPrevCB = pCurCB;
    pCurCB  = pCurCB->pNext;
  };

  // Finish accessing the modem list
  //
  LEAVECRITICALSECTION(gMdmList.hSem);

  // Wait until no one else is using the line
  //
  CLAIM_LINEDEV(pLineDev);
  DELETECRITICALSECTION(pLineDev->hSem);
  LocalFree(pLineDev);

  return ERROR_SUCCESS;
}

//****************************************************************************
// PLINEDEV GetFirstCB()
//
// Function: Get the first modem device in the list
//
// Returns: SUCCESS or an error code
//
// Fri 14-Apr-1995 12:47:57  -by-  Viroon  Touranachun [viroont]
//   created
//****************************************************************************

PLINEDEV GetFirstCB()
{
  PLINEDEV pLineDev;

  // Exclusively access the modem list
  //
  ENTERCRITICALSECTION(gMdmList.hSem);

  // Get the next head of the CB list
  //
  if ((pLineDev = gMdmList.pList) != NULL)
  {
    CLAIM_LINEDEV(pLineDev);
  };

  // Finish accessing the modem list
  //
  LEAVECRITICALSECTION(gMdmList.hSem);

  return pLineDev;
}

//****************************************************************************
// PLINEDEV GetCBfromHandle()
//
// Function: This function gets the CB from a handle
//
// Returns:  a pointer to PLINEDEV structure if the handle is valid, or
//           NULL otherwise
//
// Fri 14-Apr-1995 12:47:57  -by-  Viroon  Touranachun [viroont]
//   created
//****************************************************************************

PLINEDEV  GetCBfromHandle (DWORD handle)
{
#if 0
  PLINEDEV pLineDev;

  // Exclusively access the modem list
  //
  ENTERCRITICALSECTION(gMdmList.hSem);
  pLineDev = gMdmList.pList;

  // Walk the modem list to find the line
  //
  while (pLineDev != NULL)
  {
    // BUGBUG: Chris Caputo - 1/24/96
    // BUGBUG: pLineDev could be modified as we are scanning.  The possibility
    // BUGBUG: is that pLineDev->dwVersion gets changed.
    if ((pLineDev == (PLINEDEV)handle) && ISLINEDEV(pLineDev))
    {
      // Exclusively accessing the line CB
      //
      CLAIM_LINEDEV(pLineDev);
      ASSERT((pLineDev == (PLINEDEV)handle) && ISLINEDEV(pLineDev));
      break;
    }

    pLineDev = pLineDev->pNext;
  };


  // Finish accessing the modem list
  //
  LEAVECRITICALSECTION(gMdmList.hSem);

  return pLineDev;

#endif

  __try {

      PLINEDEV pLineDev;

      pLineDev=(PLINEDEV)handle;

      if (pLineDev->dwVersion == UMDM_VERSION) {

          CLAIM_LINEDEV(pLineDev);

          return pLineDev;

      }

  } __except(EXCEPTION_EXECUTE_HANDLER) {



  }

  return NULL;

}

//****************************************************************************
// PLINEDEV GetCBfromID()
//
// Function: This function looks for the CB owning the device
//
// Returns:  TRUE (if valid)
//         FALSE
//
// Fri 14-Apr-1995 12:47:57  -by-  Viroon  Touranachun [viroont]
//   created
//****************************************************************************

PLINEDEV  GetCBfromID (DWORD dwDeviceID)
{
  PLINEDEV pLineDev;

  // Exclusively access the modem list
  //
  ENTERCRITICALSECTION(gMdmList.hSem);
  pLineDev = gMdmList.pList;

  // Walk the modem list to find the line
  //
  while (pLineDev != NULL)
  {
    // BUGBUG: Chris Caputo - 1/24/96
    // BUGBUG: pLineDev could be modified as we are scanning.  The possibility
    // BUGBUG: is that pLineDev->dwID gets changed.
    if (pLineDev->dwID == dwDeviceID)
    {
      // Exclusively accessing the line CB
      //
      CLAIM_LINEDEV(pLineDev);
      ASSERT(pLineDev->dwID == dwDeviceID);
      break;
    }

    pLineDev = pLineDev->pNext;
  };

  // Finish accessing the modem list
  //
  LEAVECRITICALSECTION(gMdmList.hSem);
  return pLineDev;
}

#if 0

//****************************************************************************
// PLINEDEV GetCBfromDeviceHandle()
//
// Function: This function looks for the CB owning the device
//
// Returns:  TRUE (if valid)
//         FALSE
//
// Fri 14-Apr-1995 12:47:57  -by-  Viroon  Touranachun [viroont]
//   created
//****************************************************************************

PLINEDEV  GetCBfromDeviceHandle (DWORD hDevice)
{
  PLINEDEV pLineDev;

  // Exclusively access the modem list
  //
  ENTERCRITICALSECTION(gMdmList.hSem);
  pLineDev = gMdmList.pList;

  // Trace the list of modem port control block
  //
  while (pLineDev != NULL)
  {
    // BUGBUG: Chris Caputo - 1/24/96
    // BUGBUG: pLineDev could be modified as we are scanning.  The possibility
    // BUGBUG: is that pLineDev->hDevice gets changed.
    if (pLineDev->hDevice == (HANDLE)hDevice)
    {
      // Exclusively accessing the line CB
      //
      CLAIM_LINEDEV(pLineDev);
      ASSERT(pLineDev->hDevice == (HANDLE)hDevice);
      break;
    }

    pLineDev = pLineDev->pNext;
  };    

  // Finish accessing the modem list
  //
  LEAVECRITICALSECTION(gMdmList.hSem);
  return pLineDev;
}

#endif

//****************************************************************************
// PLINEDEV GetCBfromName()
//
// Function: This function looks for the CB owning the device
//
// Returns:  TRUE (if valid)
//         FALSE
//
// Fri 14-Apr-1995 12:47:57  -by-  Viroon  Touranachun [viroont]
//   created
//****************************************************************************

PLINEDEV  GetCBfromName (LPTSTR pszName)
{
  PLINEDEV pLineDev;

  // Exclusively access the modem list
  //
  ENTERCRITICALSECTION(gMdmList.hSem);
  pLineDev = gMdmList.pList;

  // Trace the list of modem port control block
  //
  while (pLineDev != NULL)
  {
    // Exclusively accessing the line CB
    //
    CLAIM_LINEDEV(pLineDev);
    if (!lstrcmp(pLineDev->szDeviceName, pszName))
      break;

    RELEASE_LINEDEV(pLineDev);
    pLineDev = pLineDev->pNext;
  };    

  // Finish accessing the modem list
  //
  LEAVECRITICALSECTION(gMdmList.hSem);
  return pLineDev;
}

#ifdef DYNA_ADDREMOVE
//****************************************************************************
// void DisableStaleModems(void)
//
// Function: Disable all modems that do not have the fReinit flag set.
//
// Returns:  TRUE (if valid)
//           FALSE
//
// 4/24/96 JosephJ Created
//****************************************************************************
void  DisableStaleModems(void)
{
  PLINEDEV pLineDev;

  // Exclusively access the modem list
  //
  ENTERCRITICALSECTION(gMdmList.hSem);
  pLineDev = gMdmList.pList;

  // Trace the list of modem port control block
  //
  while (pLineDev != NULL)
  {
    // Exclusively accessing the line CB
    //
    CLAIM_LINEDEV(pLineDev);

    if (!(pLineDev->fdwResources&LINEDEVFLAGS_REINIT))
    {
        DPRINTF1("WARNING: MARKING MODEM OUT-OF-SERVICE: [%s]",
                    pLineDev->szDeviceName);
        pLineDev->fdwResources|= LINEDEVFLAGS_OUTOFSERVICE;
    }
    else
    {
        pLineDev->fdwResources&=~LINEDEVFLAGS_REINIT;
        pLineDev->fdwResources&=~LINEDEVFLAGS_OUTOFSERVICE;
    }

    RELEASE_LINEDEV(pLineDev);
    pLineDev = pLineDev->pNext;
  };    

  // Finish accessing the modem list
  //
  LEAVECRITICALSECTION(gMdmList.hSem);

}
#endif // DYNA_ADDREMOVE

//****************************************************************************
// DWORD NullifyLineDevice(PLINEDEV pLineDev)
//
// Functions: Clean up the contents of the modem CB
//
// Return:    ERROR_SUCCESS always
//****************************************************************************

DWORD NullifyLineDevice (PLINEDEV pLineDev)
{
  // Turn the line device back to its initiali state
  //
  pLineDev->fdwResources = 0L;
  pLineDev->hDevice      = INVALID_DEVICE;
  pLineDev->htLine       = NULL;
  pLineDev->lpfnEvent    = NULL;
  pLineDev->DevState     = DEVST_DISCONNECTED;
  pLineDev->szAddress[0] = '\0';
  pLineDev->htCall       = NULL;
  pLineDev->dwCall       = 0L;
  pLineDev->dwCallState  = LINECALLSTATE_IDLE;
  pLineDev->dwCallStateMode = 0L;
  pLineDev->dwCurMediaModes = 0L;
  pLineDev->dwDetMediaModes = 0L;
  pLineDev->fTakeoverMode   = FALSE;
  pLineDev->dwMediaModes    = pLineDev->dwDefaultMediaModes;
  pLineDev->dwRingCount     = 0L;
  pLineDev->dwRingTick      = 0L;
  pLineDev->dwNegotiatedRate = 0L;

  // Async operation
  //
  pLineDev->dwPendingID                     = INVALID_PENDINGID;
  pLineDev->dwPendingType                   = INVALID_PENDINGOP;
  pLineDev->dwVxdPendingID                  = MDM_ID_NULL;

  return ERROR_SUCCESS;
}

//****************************************************************************
// BOOL ValidateDevCfgClass(LPCSTR lpszDeviceClass)
//
// Functions: Validate the supported device class
//
// Return:    TRUE if the device class is supported
//            FALSE otherwise
//****************************************************************************

BOOL ValidateDevCfgClass (LPCTSTR lpszDeviceClass)
{
  UINT        idClass;

  // Need the device class
  //
  if (lpszDeviceClass == NULL)
    return FALSE;

  // Determine the device class
  //
  for (idClass = 0; idClass < MAX_SUPPORT_CLASS; idClass++)
  {
    if (lstrcmpi(lpszDeviceClass, aGetID[idClass].szClassName) == 0)
      break;
  };

  // Do we support the requested class?
  //
  switch (idClass)
  {
    case TAPILINE:
    case COMM:
    case COMMMODEM:
    case COMMMODEMPORTNAME:
      return TRUE;

    default:
      return FALSE;
  };
}

//****************************************************************************
// ValidateAddress()
//
// Function: This function validates a tapi address and creates a version of
//           it to pass to the VxD.  In addition, it returns the address in
//           ANSI form, rather than Unicode.
//
// Returns:  SUCCESS or LINEERR_xxx depending on the failure reason
//
//****************************************************************************

LONG ValidateAddress(PLINEDEV pLineDev,
#ifdef UNICODE
                     LPCTSTR  lpszUnicodeInAddress,
#else // UNICODE
                     LPCSTR   lpszInAddress,
#endif // UNICODE
                     LPSTR    lpszOutAddress)
{
    LPCSTR  lpszSrc;
    int     cbOutLen = MAXADDRESSLEN;
#ifdef UNICODE
    LPSTR   lpszInAddress;   // ANSI version of lpszUnicodeInAddress
    DWORD   dwInAddressLen;  // in bytes
#endif // UNICODE

    ASSERT(lpszOutAddress);

#ifdef UNICODE
    // is lpszUnicodeInAddress NULL?
    //
    if (lpszUnicodeInAddress == NULL || *lpszUnicodeInAddress == 0)
    {
        *lpszOutAddress = 0;
        return ERROR_SUCCESS;
    }

    // Convert lpszUnicodeInAddress to lpszInAddress (ANSI)
    dwInAddressLen = WideCharToMultiByte(CP_ACP,
                                         0,
                     lpszUnicodeInAddress,
                     -1,
                     NULL,
                     0,
                     NULL,
                     NULL);

    if (dwInAddressLen == 0)
    {
        TSPPRINTF1("ValidateAddress:WideCharToMultiByte returned %d",
               GetLastError());
    return LINEERR_INVALADDRESS;
    }

    lpszInAddress = (LPSTR)LocalAlloc(LPTR, dwInAddressLen);

    if (lpszInAddress == NULL)
    {
        TSPPRINTF1("ValidateAddress:WideCharToMultiByte returned %d",
               GetLastError());
    return LINEERR_NOMEM;
    }

    dwInAddressLen = WideCharToMultiByte(CP_ACP,
                                         0,
                     lpszUnicodeInAddress,
                     -1,
                     lpszInAddress,
                     dwInAddressLen,
                     NULL,
                     NULL);
    
    if (dwInAddressLen == 0)
    {
        TSPPRINTF1("ValidateAddress:WideCharToMultiByte returned %d",
               GetLastError());
    LocalFree(lpszInAddress);
    return LINEERR_INVALADDRESS;
    }
#endif // UNICODE

    // Verify that the first char is a valid single-byte char.
    //
    if (CharNextA(lpszInAddress) - lpszInAddress != 1)
    {
#ifdef UNICODE
    LocalFree(lpszInAddress);
#endif // UNICODE
        return LINEERR_INVALADDRESS;
    }

    // tone or pulse?  set dwDialOptions appropriately
    // also, set lpszSrc
    //
    if (*lpszInAddress == 'T' || *lpszInAddress == 't')  // tone
    {
        lpszSrc = lpszInAddress + 1;
        pLineDev->dwDialOptions |= MDM_TONE_DIAL;
    }
    else
    {
        if (*lpszInAddress == 'P' || *lpszInAddress == 'p')  // pulse
        {
            lpszSrc = lpszInAddress + 1;
            pLineDev->dwDialOptions &= ~MDM_TONE_DIAL;
        }
        else
        {
            lpszSrc = lpszInAddress;
        }
    }

    // copy In to Out scanning for various dialoptions, returning error if we
    // don't support something.
    //
    while (*lpszSrc && cbOutLen)
    {
        switch (*lpszSrc)
        {
        case '$':
            if (!(pLineDev->dwDevCapFlags & LINEDEVCAPFLAGS_DIALBILLING))
            {
              UINT  cCommas;

              // Get the wait-for-bong period
              //
              cCommas = GETWAITBONG(pLineDev->pDevCfg);

              // Calculate the number of commas we need to insert
              //
              cCommas = (cCommas/INC_WAIT_BONG) +
                        (cCommas%INC_WAIT_BONG ? 1 : 0);

              // Insert the strings of commas
              //
              while (cbOutLen && cCommas)
              {
                *lpszOutAddress++ = ',';
                cbOutLen--;
                cCommas--;
              };
              goto Skip_This_Character;
            }
            break;

        case '@':
            if (!(pLineDev->dwDevCapFlags & LINEDEVCAPFLAGS_DIALQUIET))
            {
#ifdef UNICODE
            LocalFree(lpszInAddress);
#endif // UNICODE
                return LINEERR_DIALQUIET;
            }
            break;

        case 'W':
        case 'w':
            if (!(pLineDev->dwDevCapFlags & LINEDEVCAPFLAGS_DIALDIALTONE))
            {
#ifdef UNICODE
            LocalFree(lpszInAddress);
#endif // UNICODE
                return LINEERR_DIALDIALTONE;
            }
            break;

        case '?':
#ifdef UNICODE
            LocalFree(lpszInAddress);
#endif // UNICODE
            return LINEERR_DIALPROMPT;

        case '|':  // subaddress
        case '^':  // name field
            goto Skip_The_Rest;

        case ';':
            if (!pLineDev->fPartialDialing)
            {
#ifdef UNICODE
              LocalFree(lpszInAddress);
#endif // UNICODE
                return LINEERR_INVALADDRESS;
            }

            // This signifies the end of a dialable address.
            // Use it and skip the rest.
            //
            *lpszOutAddress++ = *lpszSrc;
            goto Skip_The_Rest;

        case ' ':
        case '-':
            // skip these characters
            //
            goto Skip_This_Character;
        }

        // Copy this character
        //
        *lpszOutAddress++ = *lpszSrc;
        cbOutLen--;

Skip_This_Character:
        // Verify that the next char is a valid single-byte char.
        //
        if (CharNextA(lpszSrc) - lpszSrc != 1)
        {
#ifdef UNICODE
            LocalFree(lpszInAddress);
#endif // UNICODE
            return LINEERR_INVALADDRESS;
        }
        lpszSrc++;
    }

    // Did we run out of space in the outgoing buffer?
    //
    if (*lpszSrc && cbOutLen == 0)
    {
        // yes
        //
#ifdef UNICODE
    LocalFree(lpszInAddress);
#endif // UNICODE
        return LINEERR_INVALADDRESS;
    }

Skip_The_Rest:
    *lpszOutAddress = 0;
#ifdef UNICODE
    LocalFree(lpszInAddress);
#endif // UNICODE
    return ERROR_SUCCESS;
}

//****************************************************************************
// IsOriginateAddress()
//
// Function: Figures out whether a string is an originate address or not.
//           An originate address is one that doesn't have a semi-colon at
//           the end.
//
// Note: lpszAddress is not a DBCS string.  AnsiNext is not used.
//
// Returns:  TRUE if it is an originate address.
//           FALSE if it is not. (ie. semi-colon at the end of the address)
//
//****************************************************************************

BOOL IsOriginateAddress(LPCSTR lpszAddress)
{
  BOOL fRet = TRUE; // assume this is an originate string

  // try to prove this isn't an originate string by finding a semi-colon
  //
  while (*lpszAddress)
  {
    if (';' == *lpszAddress)
    {
      fRet = FALSE;
      break;
    }
    lpszAddress++;
  };
  return fRet;
}

//****************************************************************************
// SetMdmTimer(LPOVERLAPPED, DWORD, DWORD)
//
// Function: Set a timer to post to the completion port after the specified
//           time elapsed.
//
// Returns:  ERROR_SUCCESS if success
//           other error code for failure
//
//****************************************************************************

DWORD SetMdmTimer (DWORD dwCompletionKey,
           LPOVERLAPPED lpOverlapped,
           DWORD dwTime)
{
  PMDMTIMER pTimer, pPrev, pNext;
  DWORD tcNow = GETTICKCOUNT();

  ASSERT(dwTime<GTC_MAXDELTA);

  // Allocate a timer block
  //
  if ((pTimer = (PMDMTIMER)LocalAlloc(LMEM_FIXED, sizeof(*pTimer))) == NULL)
  {
    return ERROR_OUTOFMEMORY;
  };

  // Calculate the wake-up time
  //
  pTimer->pNext  = NULL;
  pTimer->dwCompletionKey = dwCompletionKey;
  pTimer->lpOverlapped = lpOverlapped;
  GTC_AequalsBplusC(pTimer->dwWakeup, tcNow, dwTime);

  // Insert the timer block into the timer list
  //
// DPRINTF1("before SetMdmTimer crit sect (%d/%d)", dwCompletionKey, lpOverlapped);
  ENTERCRITICALSECTION(gTimerList.hSem);
// DPRINTF1("in SetMdmTimer crit sect (%d/%d)", dwCompletionKey, lpOverlapped);

#ifdef DEBUG

  pNext = gTimerList.pList;

  while (pNext != NULL) {

      ASSERT(!(pNext->dwCompletionKey == dwCompletionKey &&
               pNext->lpOverlapped == lpOverlapped));

      pNext=pNext->pNext;
  }

#endif //DEBUG

  pPrev = NULL;
  pNext = gTimerList.pList;

  while(pNext != NULL)
  {
    if (GTC_AleB(pTimer->dwWakeup, pNext->dwWakeup))
    {
      // Found a place to insert
      //
      pTimer->pNext = pNext;
      if (pPrev == NULL)
      {
        // Head of the list
        //
        gTimerList.pList = pTimer;
      }
      else
      {
        pPrev->pNext = pTimer;
      };
      break;
    }
    else
    {
      // Next timer block
      //
      pPrev = pNext;
      pNext = pNext->pNext;
    };
  };

  // If we are at the end of the list, append the new timer to the end
  //
  if (pNext == NULL)
  {
    if (pPrev == NULL)
    {
      gTimerList.pList = pTimer;
    }
    else
    {
      pPrev->pNext = pTimer;
    };
  };

  // If we insert it in front of the list
  // Wake up the timer thread to recalculate the sleep time
  //
  if (gTimerList.pList == pTimer)
  {
    SetEvent(gTimerList.hEvent[RECALC_TIMER_EVENT]);
  };

  LEAVECRITICALSECTION(gTimerList.hSem);
// DPRINTF1("after SetMdmTimer crit sect (%d/%d)", dwCompletionKey, lpOverlapped);

  return ERROR_SUCCESS;
}

//****************************************************************************
// KillMdmTimer(DWORD, LPOVERLAPPED)
//
// Function: Kill a timer
//
// Returns:  TRUE is timeout was found and deleted.
//           FLASE if timeout was not found (maybe because it alread fired).
//
//****************************************************************************

BOOL KillMdmTimer (DWORD dwCompletionKey,
                    LPOVERLAPPED lpOverlapped)
{
  PMDMTIMER pCurCB, pPrevCB;
  BOOL bRet = FALSE;

// DPRINTF1("KillMdmTimer entered (%d/%d)", dwCompletionKey, lpOverlapped);

  // Exclusively access the timer list
  //
  ENTERCRITICALSECTION(gTimerList.hSem);
// DPRINTF1("KillMdmTimer in crit sect (%d/%d)", dwCompletionKey, lpOverlapped);

  // Start from the head of the CB list
  //
  pPrevCB = NULL;
  pCurCB  = gTimerList.pList;

  // traverse the list to find the specified CB
  //
  while (pCurCB != NULL)
  {
    if (pCurCB->dwCompletionKey == dwCompletionKey &&
        pCurCB->lpOverlapped == lpOverlapped)
    {
      bRet = TRUE;

      // Is there a previous control block?
      //
      if (pPrevCB == NULL)
      {
        // head of the list
        //
        gTimerList.pList = pCurCB->pNext;
      }
      else
      {
        pPrevCB->pNext = pCurCB->pNext;
      };
      LocalFree(pCurCB);
      break;
    };

    pPrevCB = pCurCB;
    pCurCB  = pCurCB->pNext;
  };

#ifdef DEBUG
  if (pCurCB == NULL)
  {
    D_TRACE(TspDpf(666,TEXT("KillMdmTimer: Did not find event on list.\n"));)
//    DPRINTF("KillMdmTimer() did not fine event on its list.");
  }
#endif // DEBUG

  // Finish accessing the timer list
  //
  LEAVECRITICALSECTION(gTimerList.hSem);

// DPRINTF1("KillMdmTimer exit (%d/%d)", dwCompletionKey/lpOverlapped);
  return bRet;
}

//****************************************************************************
// DWORD InitializeMdmTimer()
//
// Function: Initialize a timer utility
//
// Returns:  ERROR_SUCCESS if success
//           other error code for failure
//
//****************************************************************************

DWORD InitializeMdmTimer()
{
  // Initialize the timer list critical section
  //
  INITCRITICALSECTION(gTimerList.hSem);
  gTimerList.pList = NULL;
  
  // Create the recalc event
  //
  if (gTimerList.hEvent[RECALC_TIMER_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL))
  {
    // Create the stop event
    //
    if (gTimerList.hEvent[STOP_TIMER_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL))
    {
        // Create the notification handle and event...
        gTimerList.hN = notifCreate(TRUE, SLOTNAME_UNIMODEM_NOTIFY_TSP,
                                MAX_NOTIFICATION_FRAME_SIZE, 10);
        if (!gTimerList.hN)
        {
            DPRINTF3("WARNING: notifServerCreate(\"%s\", %lu) failed. GetLastError=0x%lx.\n",
                    (LPCTSTR) SLOTNAME_UNIMODEM_NOTIFY_TSP,
                    (unsigned long) MAX_NOTIFICATION_FRAME_SIZE,
                    (unsigned long) GetLastError());
            // Well, we go on, not a fatal error...
        }
        else
        {
            gTimerList.hEvent[TSP_NOTIFICATION_EVENT] =
                                                notifGetObj(gTimerList.hN);
            ASSERT(gTimerList.hEvent[TSP_NOTIFICATION_EVENT]);
        }
      // Start the timer thread  
      //
      ghtdTimer  = CreateThread(
                    NULL,                                   // default security
                    0,                                      // default stack size
                    (LPTHREAD_START_ROUTINE)MdmTimerThread, // thread entry point
                    NULL,                                   // no parameter
                    0,                                      // Start immediately
                    &gtidTimerMdm);                         // thread id

      if (ghtdTimer)
      {
        // We started the timer services
        //

        // Register the timer list with the
        // tracing system.
        traceRegisterObject(
                    &gTimerList,
                    TSP_TIMER_LIST_GUID,
                    TSP_TIMER_LIST_VERSION,
                    0,
                    0
        );
        return ERROR_SUCCESS;
      };
    };
  };  

  // Cannot start the timer, clean up resources
  //

    if (gTimerList.hN)
    {
        // the notification event is owned by the notif object, hN, so we don't
        // CloseHandle it here.
        gTimerList.hEvent[TSP_NOTIFICATION_EVENT]=NULL;
        notifFree(gTimerList.hN);
        gTimerList.hN=0;
    }

  if (gTimerList.hEvent[STOP_TIMER_EVENT])
  {
    CloseHandle(gTimerList.hEvent[STOP_TIMER_EVENT]);
  };

  if (gTimerList.hEvent[RECALC_TIMER_EVENT])
  {
    CloseHandle(gTimerList.hEvent[RECALC_TIMER_EVENT]);
  };  
  
  DELETECRITICALSECTION(gTimerList.hSem);
  return ERROR_OUTOFMEMORY;    
}

//****************************************************************************
// DWORD DeinitializeMdmTimer()
//
// Function: Deinitialize a timer utility
//
// Returns:  ERROR_SUCCESS if success
//           other error code for failure
//
//****************************************************************************

DWORD DeinitializeMdmTimer()
{
  // Un-register the timer list with the
  // tracing system.
  traceUnRegisterObject(&gTimerList, 0, 0);

  // Signal the stop event
  //
  SetEvent(gTimerList.hEvent[STOP_TIMER_EVENT]);
  
  // Wait until the the timer thread terminates
  //
  WaitForSingleObject(ghtdTimer, INFINITE);

  //
  //  close thread handle
  //
  CloseHandle(ghtdTimer);

    // Destroy the notification object, if we allocated it...
    if (gTimerList.hN)
    {
        // the notification event is owned by the notif object, hN.
        gTimerList.hEvent[TSP_NOTIFICATION_EVENT]=NULL;
        notifFree(gTimerList.hN);
        gTimerList.hN=0;
    }
  // Destroy the recalc and the stop events
  //
  CloseHandle(gTimerList.hEvent[STOP_TIMER_EVENT]);
  CloseHandle(gTimerList.hEvent[RECALC_TIMER_EVENT]);

  // Deinitialize the timer list critical section
  //
  DELETECRITICALSECTION(gTimerList.hSem);
  return ERROR_SUCCESS;
}

//****************************************************************************
// DWORD APIENTRY MdmTimerThread(DWORD)
//
// Function: timer thread
//
// Returns:  None
//
//****************************************************************************

DWORD APIENTRY MdmTimerThread(DWORD dwParam)
{
  DWORD dwWait;

  // Start waiting for the new timer infinitely
  //
  dwWait = INFINITE;

  // Wait for the recalc event for the specified time
  //
  while (TRUE)
  {
    switch (WaitForMultipleObjects(NUM_TIMER_EVENTS(gTimerList),
                                    gTimerList.hEvent, FALSE, dwWait))
    {
      // If the waittime is expired, some timer block needs to wake up
      //
      case WAIT_TIMEOUT:
      {
        PMDMTIMER pTimer, pNext;
        DWORD dwCurrent;

        ENTERCRITICALSECTION(gTimerList.hSem);

        dwCurrent = GETTICKCOUNT();

        // Start signalling from the head of the list
        //
        pNext = gTimerList.pList;

        while(pNext && GTC_AleB(pNext->dwWakeup, dwCurrent))
        {
// DPRINTF1("MdmTimerThread queuing %d/%d", pNext->dwCompletionKey, pNext->lpOverlapped);
          PostQueuedCompletionStatus(ghCompletionPort,
                                 1,
                     pNext->dwCompletionKey,
                     pNext->lpOverlapped);
          pTimer = pNext;
          pNext  = pTimer->pNext;
          LocalFree(pTimer);
        };

        // Recalculate the wait time
        // If nothing is in the list, the wake time is infinite
        //
        if (pNext)
        {
          dwWait = GTC_DELTA(dwCurrent, pNext->dwWakeup);
        }
        else
        {
          dwWait = INFINITE;
        };

        gTimerList.pList = pNext;
        LEAVECRITICALSECTION(gTimerList.hSem);
        break;
      }

      // If it is the recalc event
      // we need to recalc the wait time from the head of the list
      //
      case WAIT_OBJECT_0+RECALC_TIMER_EVENT:
      {
        DWORD dwCurrent;

        ENTERCRITICALSECTION(gTimerList.hSem);

        dwCurrent = GETTICKCOUNT();

        if (gTimerList.pList
                && GTC_AleB(dwCurrent, gTimerList.pList->dwWakeup))
        {
          dwWait = GTC_DELTA(dwCurrent,gTimerList.pList->dwWakeup);
        }
        else
        {
          dwWait = 0;
        };
        LEAVECRITICALSECTION(gTimerList.hSem);
        break;
      }

      case WAIT_OBJECT_0+TSP_NOTIFICATION_EVENT:
      {

        ENTERCRITICALSECTION(gTimerList.hSem);
        ProcessNotification(gTimerList.hN);
        LEAVECRITICALSECTION(gTimerList.hSem);
        break;
      }

      // Otherwise terminate the timer thread
      //
      case WAIT_OBJECT_0+STOP_TIMER_EVENT:
      {
        PMDMTIMER pNextTimer, pTimer;

        // Free all the timer block
        //
        pNextTimer = gTimerList.pList;
        while(pNextTimer)
        {
          pTimer = pNextTimer;
          pNextTimer = pTimer->pNext;
          LocalFree(pTimer);
        };
        gTimerList.pList = NULL;

        ExitThread(ERROR_SUCCESS);
        return ERROR_SUCCESS;
      }

      default:
        DPRINTF("Got unknown notification!\n");
        break;
    };
  };
}

//****************************************************************************
// DWORD LaunchModemLight (LPTSTR szModemName, HANDLE hModem, LPHANDLE lphLight)
//
// Function: Lauch the modem lights applet
//
// Returns:  ERROR_SUCCESS if success otherwise ERROR_OPEN_FAILED
//
//****************************************************************************/

DWORD LaunchModemLight (LPTSTR szModemName, HANDLE hModem, LPHANDLE lphLight)
{
    HANDLE              hEvent;
    PROCESS_INFORMATION pi;
    STARTUPINFO         sti;
    TCHAR               szCmdline[256];
    SERIALPERF_STATS    serialstats;
    DWORD               dwBytes;
    DWORD               dwRet;
    OVERLAPPED          ov;

    // Check to see if any bytes have been transferred or receive.  If none
    // has, there is no need to launch lights because this is probably a
    // port driver that doesn't support this ioctl.
    //
    ov.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    if (ov.hEvent == NULL)
      {
        return ERROR_OPEN_FAILED;
      }

    ov.hEvent = (HANDLE)((DWORD)ov.hEvent | 1);
  
    dwRet = DeviceIoControl(hModem,
                            IOCTL_SERIAL_GET_STATS,
                            &serialstats,
                            sizeof(SERIALPERF_STATS),
                            &serialstats,
                            sizeof(SERIALPERF_STATS),
                            &dwBytes,
                            &ov);
 
    if (!dwRet)
      {
        if (ERROR_IO_PENDING == GetLastError())
          {
            dwRet = GetOverlappedResult(hModem,
                          &ov,
                        &dwBytes,
                        TRUE);
      }
      }

    ov.hEvent = (HANDLE)((DWORD)ov.hEvent & 0xfffffffe);
    CloseHandle(ov.hEvent);

    

    if (!dwRet ||
        (serialstats.ReceivedCount == 0 &&
         serialstats.TransmittedCount == 0))
      {
        return ERROR_OPEN_FAILED;
      }


    // OK, the GET_STATS ioctl seems to work, so let's really launch lights.


    // Create the lights shutdown event handle.
    if ((hEvent = CreateEvent( NULL, FALSE, FALSE, NULL )) != NULL)
    {
      // Create a global handle for use in other processes and close the
      // local handle.
      *lphLight = hEvent;

      // Compose a modem lights process command line
      //
      wsprintf( szCmdline, LIGHTSAPP_EXE_NAME TEXT(" %lu %lu %lu %s"),
                GetCurrentProcessId(), hEvent, hModem, szModemName );

      // Create the modem lights process and store ID for use in CloseModem.
      ZeroMemory(&sti, sizeof(sti));
      sti.cb = sizeof(STARTUPINFO);
      if ( !CreateProcess(NULL, szCmdline,    // Start up command line
                          NULL, NULL, FALSE, 0, NULL, NULL, &sti, &pi) )
      {
        DPRINTF1("LaunchModemLight: CreateProcess failed (%d).",
                 GetLastError());

        CloseHandle(hEvent);
        *lphLight = (DWORD)NULL;

        return ERROR_OPEN_FAILED;
      }
      CloseHandle(pi.hProcess);
      CloseHandle(pi.hThread);
      DPRINTF("LaunchModemLight: Succeeded.");
      return ERROR_SUCCESS;
    }
    DPRINTF1("LaunchModemLight: CreateEvent failed (%d).",
             GetLastError());
    return ERROR_OPEN_FAILED;
}

//****************************************************************************
// DWORD TerminateModemLight (HANDLE hLight)
//
// Function: Terminate the modem lights applet
//
// Returns:  ERROR_SUCCESS always
//
//****************************************************************************

DWORD TerminateModemLight (HANDLE hLight)
{
  SetEvent(hLight);
  CloseHandle(hLight);
  return ERROR_SUCCESS;
}

//****************************************************************************
// Function: Processes an external TSP notification
//            (produced as a result of some process loading unimdm.tsp and
//             calling UnimodemNotifyTSP(...))
//    WARNING: This function is called with the timer critical section still
//             held -- better return quickly!
//****************************************************************************
void ProcessNotification(HNOTIFICATION hN)
{
    BOOL fRet;
    struct {
        DWORD dw0;
        BYTE rgb[MAX_NOTIFICATION_FRAME_SIZE];
    }  EmptyFr;
    PNOTIFICATION_FRAME pnf = (PNOTIFICATION_FRAME) &EmptyFr;
    DWORD dwcbMax=sizeof(EmptyFr);
    DWORD dwcbRead=0;

    pnf->dwSig=pnf->dwSize=0;

    fRet=notifReadMsg(hN, (LPBYTE) pnf, dwcbMax, &dwcbRead);
    if (!fRet)
    {
        DPRINTF1("notifReadFrame(...) failed. GetLastError=0x%lx.\n",
                (unsigned long) GetLastError());
        goto end;
    }

    // Verify validity of msg...
    if (!ValidateFrame(pnf, dwcbRead))
    {
        DPRINTF("Invalid frame\n");
        goto end;
    }
    ProcessFrame(pnf);
    
end:
    return;
}


//****************************************************************************
// Function: Validates a frame -- checks signature, etc...
//****************************************************************************
BOOL ValidateFrame(PNOTIFICATION_FRAME pnf, DWORD dwTrueSize)
{
    return (pnf && pnf->dwSig==dwNFRAME_SIG && pnf->dwSize>=sizeof(*pnf) &&
            pnf->dwSize==dwTrueSize &&
            pnf->dwSize<=MAX_NOTIFICATION_FRAME_SIZE);
}


//****************************************************************************
// Function: Processes a received notification frame
//            (received as a result of some process loading unimdm.tsp and
//             calling UnimodemNotifyTSP(...))
//    WARNING: This function is called with the timer critical section still
//             held -- better return quickly!
//****************************************************************************
void ProcessFrame(PNOTIFICATION_FRAME pnf)
{
    void    cplProcessNotification(PNOTIFICATION_FRAME pnf);

    switch(pnf->dwType)
    {

    case TSPNOTIF_TYPE_CPL:
        DPRINTF("ProcessFrame: Got CPL notification!\n");
        cplProcessNotification(pnf);
        break;
    case TSPNOTIF_TYPE_DEBUG:
        DPRINTF("ProcessFrame: Got DEBUG notifcation.\n");
        traceProcessNotification(pnf);
        break;
    default:
        DPRINTF1("WARNING:Got unknown notif type 0x%lu.\n", pnf->dwType);
        break;
    }
}