/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

  sndthrd.c

Abstract:

  This module:
    1) Verifies a tiff
    2) Enables or disables the FaxRcv Routing Extension
    3) Callback function for the change of state during the RAS connection process
    4) Sends a RAS call
    5) Sends a fax
    6) Receives a fax
    7) Thread to handle the Send logic

Author:

  Steven Kehrli (steveke) 11/15/1997

--*/

#ifndef _SNDTHRD_C
#define _SNDTHRD_C

#include <tifflib.h>
#include <tifflibp.h>

#define SUSPECT_RAS_SPEED    28800

#define IMAGEWIDTH_REF       1728
#define IMAGELENGTH_200_REF  2200
#define IMAGELENGTH_100_REF  1100
#define SOFTWARE_REF         "Windows NT Fax Server"

UINT
fnVerifyTiff(
    LPWSTR   szTiffFile,
    LPDWORD  pdwPages
)
/*++

Routine Description:

  Verifies a tiff

Arguments:

  szTiffFile - tiff file name
  pdwPages - pointer to the number of pages

Return Value:

  TRUE on success

--*/
{
    HANDLE               hTiffFile;
    TIFF_INFO            TiffInfo;
    PTIFF_INSTANCE_DATA  pTiffInstanceData;
    DWORD                IFDOffset;
    WORD                 wNumDirEntries;
    PTIFF_TAG            pTiffTag;
    WORD                 wIndex;

    DWORD                ImageLengthRef;

    DWORD                ImageWidth;
    DWORD                ImageLength;
    DWORD                Compression;
    DWORD                Photometric;
    DWORD                XResolution;
    DWORD                YResolution;
    LPSTR                SoftwareBuffer;
    LPSTR                Software;

    UINT                 uFlags;

    uFlags = ERROR_SUCCESS;
    *pdwPages = 0;

    // Open the tiff file
    hTiffFile = TiffOpen(szTiffFile, &TiffInfo, TRUE, FILLORDER_MSB2LSB);
    if (hTiffFile == NULL) {
        TiffClose(hTiffFile);
        return IDS_TIFF_INVALID_TIFF;
    }

    pTiffInstanceData = (PTIFF_INSTANCE_DATA) hTiffFile;
    // Get the next IFD offset
    IFDOffset = pTiffInstanceData->TiffHdr.IFDOffset;

    while (IFDOffset) {

        // Increment the number of pages
        (*pdwPages)++;

        // Get the number of tags in this IFD
        wNumDirEntries = (WORD) *(LPWORD) (pTiffInstanceData->fPtr + IFDOffset);

        // Set the tag pointer
        pTiffTag = (PTIFF_TAG) (pTiffInstanceData->fPtr +IFDOffset + sizeof(WORD));

        // Get the tiff tags
        ImageWidth = -1;
        ImageLength = -1;
        Compression = -1;
        Photometric = -1;
        XResolution = -1;
        YResolution = -1;
        Software = NULL;

        for (wIndex = 0; wIndex < wNumDirEntries; wIndex++) {
            switch (pTiffTag[wIndex].TagId) {
                case TIFFTAG_IMAGEWIDTH:
                    ImageWidth = GetTagData(pTiffInstanceData->fPtr, 0, &pTiffTag[wIndex]);
                    break;

                case TIFFTAG_IMAGELENGTH:
                    ImageLength = GetTagData(pTiffInstanceData->fPtr, 0, &pTiffTag[wIndex]);
                    break;

                case TIFFTAG_COMPRESSION:
                    Compression = GetTagData(pTiffInstanceData->fPtr, 0, &pTiffTag[wIndex]);
                    break;

                case TIFFTAG_PHOTOMETRIC:
                    Photometric = GetTagData(pTiffInstanceData->fPtr, 0, &pTiffTag[wIndex]);
                    break;

                case TIFFTAG_XRESOLUTION:
                    XResolution = GetTagData(pTiffInstanceData->fPtr, 0, &pTiffTag[wIndex]);
                    break;

                case TIFFTAG_YRESOLUTION:
                    YResolution = GetTagData(pTiffInstanceData->fPtr, 0, &pTiffTag[wIndex]);
                    break;

                case TIFFTAG_SOFTWARE:
                    SoftwareBuffer = (LPSTR) (DWORD UNALIGNED *) (pTiffInstanceData->fPtr + pTiffTag[wIndex].DataOffset);
                    Software = MemAllocMacro((lstrlenA(SOFTWARE_REF) + 1) * sizeof(CHAR));
                    lstrcpynA(Software, SoftwareBuffer, (lstrlenA(SOFTWARE_REF) + 1));
                    break;
            }
        }

        if ((ImageWidth < (IMAGEWIDTH_REF - (IMAGEWIDTH_REF * .1))) || (ImageWidth > (IMAGEWIDTH_REF + (IMAGEWIDTH_REF * .1)))) {
            uFlags = IDS_TIFF_INVALID_IMAGEWIDTH;
            goto InvalidFax;
        }

        if (YResolution == 196) {
            ImageLengthRef = IMAGELENGTH_200_REF;
        }
        else if (YResolution == 98) {
            ImageLengthRef = IMAGELENGTH_100_REF;
        }
        else {
            uFlags = IDS_TIFF_INVALID_YRESOLUTION;
            goto InvalidFax;
        }
        if ((ImageLength < (ImageLengthRef - (ImageLengthRef * .1))) || (ImageLength > (ImageLengthRef + (ImageLengthRef * .1)))) {
            uFlags = IDS_TIFF_INVALID_IMAGELENGTH;
            goto InvalidFax;
        }

        if (Compression != 4) {
            uFlags = IDS_TIFF_INVALID_COMPRESSION;
            goto InvalidFax;
        }

        if (Photometric != 0) {
            uFlags = IDS_TIFF_INVALID_PHOTOMETRIC;
            goto InvalidFax;
        }

        if (XResolution != 204) {
            uFlags = IDS_TIFF_INVALID_XRESOLUTION;
            goto InvalidFax;
        }

        if ((YResolution != 196) && (YResolution != 98)) {
            uFlags = IDS_TIFF_INVALID_YRESOLUTION;
            goto InvalidFax;
        }

        if (lstrcmpA(SOFTWARE_REF, Software)) {
            uFlags = IDS_TIFF_INVALID_SOFTWARE;
            goto InvalidFax;
        }

        MemFreeMacro(Software);

        // Get the next IFD offset
        IFDOffset = (DWORD) *(DWORD UNALIGNED *) (pTiffInstanceData->fPtr + (wNumDirEntries * sizeof(TIFF_TAG)) + IFDOffset + sizeof(WORD));
    }

    TiffClose(hTiffFile);
    return ERROR_SUCCESS;

InvalidFax:
    if (Software) {
        MemFreeMacro(Software);
    }

    TiffClose(hTiffFile);
    return uFlags;
}

VOID
fnEnableFaxRcv(
    BOOL  bEnable
)
/*++

Routine Description:

  Enables or disables the FaxRcv Routing Extension

Arguments:

  bEnable - indicates whether to enable or disable the FaxRcv Routing Extension
            TRUE enables the FaxRcv Routing Extension
            FALSE enables the FaxRcv Routing Extension

Return Value:

  None

--*/
{
    // hFaxRcvExtKey is the handle to the FaxRcv Extension Registry key
    HKEY  hFaxRcvExtKey;

    // Open the FaxRcv Extension Registry key
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, FAXRCV_EXT_REGKEY, 0, KEY_ALL_ACCESS, &hFaxRcvExtKey) != ERROR_SUCCESS) {
        return;
    }

    // Set the FaxRcv Extension bEnable Registry value
    if (RegSetValueEx(hFaxRcvExtKey, BENABLE_EXT_REGVAL, 0, REG_DWORD, (LPBYTE) &bEnable, sizeof(BOOL)) != ERROR_SUCCESS) {
        // Close the FaxRcv Extension Registry key
        RegCloseKey(hFaxRcvExtKey);
        return;
    }

    // Close the FaxRcv Extension Registry key
    RegCloseKey(hFaxRcvExtKey);
}

VOID WINAPI
fnRasDialCallback(
    UINT          uMsg,
    RASCONNSTATE  RasConnState,
    DWORD         dwError
)
/*++

Routine Description:

  Callback function for the change of state during the RAS connection process

Arguments:

  uMsg - type of event
  RasConnState - state
  dwError - error

Return Value:

  None

--*/
{
    static BOOL    bError = FALSE;

    if ((dwError) && (!bError)) {
        bError = TRUE;
        // Update the status
        SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_FAILED, dwError);
        SetEvent(g_hRasFailedEvent);
    }
    else if (RasConnState == RASCS_OpenPort) {
        bError = FALSE;
    }
    else if (RasConnState == RASCS_ConnectDevice) {
        SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_DIALING, 0);
    }
    else if (RasConnState == RASCS_Authenticate) {
        SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_AUTHENTICATING, 0);
    }
    else if (RasConnState == RASCS_Connected) {
        SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_CONNECTED, 0);
        SetEvent(g_hRasPassedEvent);
    }

    return;
}

UINT
fnSendRas(
    LPHANDLE  phFaxStopRasPassFailEvents
)
/*++

Routine Description:

  Sends a RAS call

Arguments:

  phFaxStopSendPassFailEvents - pointer to the g_hFaxEvent, g_hStopEvent, g_hRasPassedEvent and g_hRasFailedEvent

Return Value:

  UINT - resource id

--*/
{
    DWORD_PTR  dwRslt;
    UINT   uRslt;

    // RasDialParams is the RAS dial params
    RASDIALPARAMS  RasDialParams;
    // hRasConn is the handle to the RAS connection
    HRASCONN       hRasConn;
    // RasConnStatus is the RAS connection status
    RASCONNSTATUS  RasConnStatus;
    // RasStats are the RAS connection statistics
    RAS_STATS      RasStats;
    // RasInfo is the RAS connection info
    RAS_INFO       RasInfo;

    // Initialize hRasConn
    hRasConn = NULL;

    // Initialize RasDialParams
    ZeroMemory(&RasDialParams, sizeof(RASDIALPARAMS));

    // Set RasDialParams
    RasDialParams.dwSize = sizeof(RASDIALPARAMS);
    lstrcpy(RasDialParams.szPhoneNumber, g_szSndNumber);
    lstrcpy(RasDialParams.szUserName, g_szRasUserName);
    lstrcpy(RasDialParams.szPassword, g_szRasPassword);
    lstrcpy(RasDialParams.szDomain, g_szRasDomain);

    // Update the status
    SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_STARTING, (LPARAM) g_szSndNumber);

    // Start the RAS session
    ResetEvent(g_hRasPassedEvent);
    ResetEvent(g_hRasFailedEvent);
    dwRslt = g_RasApi.RasDial(NULL, NULL, &RasDialParams, 0, fnRasDialCallback, &hRasConn);

    if (dwRslt != 0) {
        // Update the status
        SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_FAILED, dwRslt);

        return IDS_STATUS_RAS_FAILED;
    }


    // Wait for Fax, Stop, RAS Passed or RAS Failed event
    dwRslt = WaitForMultipleObjects(4, phFaxStopRasPassFailEvents, FALSE, INFINITE);

    switch (dwRslt) {
        case WAIT_OBJECT_0:
            uRslt = IDS_STATUS_FAXSVC_ENDED;
            break;

        case (WAIT_OBJECT_0 + 1):
            uRslt = IDS_STATUS_ITERATION_STOPPED;
            break;

        case (WAIT_OBJECT_0 + 2):
            uRslt = IDS_STATUS_RAS_PASSED;
            break;

        case (WAIT_OBJECT_0 + 3):
            uRslt = IDS_STATUS_RAS_FAILED;
            break;
    }

    if (uRslt != IDS_STATUS_RAS_PASSED) {
        // Update the status
        SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_STOPPING, 0);
        // Stop the RAS session
        g_RasApi.RasHangUp(hRasConn);
        if (uRslt != IDS_STATUS_RAS_FAILED) {
            WaitForSingleObject(g_hRasFailedEvent, INFINITE);
        }

        return uRslt;
    }

    // Initialize RasConnStatus
    ZeroMemory(&RasConnStatus, sizeof(RASCONNSTATUS));
    // Set RasConnStatus
    RasConnStatus.dwSize = sizeof(RASCONNSTATUS);
    // Get the RAS connection status
    g_RasApi.RasGetConnectStatus(hRasConn, &RasConnStatus);

    // Initialize RasStats
    ZeroMemory(&RasStats, sizeof(RAS_STATS));
    // Set RasStats
    RasStats.dwSize = sizeof(RAS_STATS);
    // Get the connection statistics
    g_RasApi.RasGetConnectionStatistics(hRasConn, &RasStats);

    // Set the line speed
    RasInfo.dwBps = RasStats.dwBps;
    // Set the port name
    RasInfo.szDeviceName = RasConnStatus.szDeviceName;

    // Update the status
    SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_LINESPEED, (LPARAM) &RasInfo);

    if (RasStats.dwBps < SUSPECT_RAS_SPEED) {
        SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_SPEED_SUSPECT, SUSPECT_RAS_SPEED);
    }
    else {
        SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_PASSED, 0);
    }

    SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_RAS_STOPPING, 0);

    // Stop the RAS session
    g_RasApi.RasHangUp(hRasConn);

    return IDS_STATUS_RAS_PASSED;
}

UINT
fnSendFax(
    FAX_JOB_PARAM  FaxJobParams,
    LPWSTR         szDocumentName,
    LPHANDLE       phFaxStopSendPassFailEvents,
    DWORD          dwSendTimeout
)
/*++

Routine Description:

  Sends a fax

Arguments:

  FaxJobParams - fax job parameters
  szDocumentName - name of document to send
  phFaxStopSendPassFailEvents - pointer to the g_hFaxEvent, g_hStopEvent, g_hSendPassedEvent and g_hSendFailedEvent
  dwSendTimeout - send timeout interval

Return Value:

  UINT - resource id

--*/
{
    DWORD  dwRslt;
    UINT   uRslt;

    // Update the status
    SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_FAX_STARTING, (LPARAM) FaxJobParams.RecipientNumber);

    if (!g_bNoCheck) {
        // Update the status
        SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_FAX_ID, (LPARAM) FaxJobParams.Tsid);
    }

    // Send a fax
    if (!FaxSendDocument(g_hFaxSvcHandle, szDocumentName, &FaxJobParams, NULL, &g_dwFaxId)) {
        return IDS_STATUS_FAX_SEND_FAILED;
    }

    g_bFaxSndInProgress = TRUE;
    g_dwAttempt = 0;

    // Wait for Fax, Stop, Send Passed or Send Failed event
    dwRslt = WaitForMultipleObjects(4, phFaxStopSendPassFailEvents, FALSE, dwSendTimeout);

    g_bFaxSndInProgress = FALSE;

    switch (dwRslt) {
        case WAIT_TIMEOUT:
            uRslt = IDS_STATUS_TIMEOUT_ENDED;
            break;

        case WAIT_OBJECT_0:
            uRslt = IDS_STATUS_FAXSVC_ENDED;
            break;

        case (WAIT_OBJECT_0 + 1):
            uRslt = IDS_STATUS_ITERATION_STOPPED;
            break;

        case (WAIT_OBJECT_0 + 2):
            uRslt = IDS_STATUS_FAX_SEND_PASSED;
            break;

        case (WAIT_OBJECT_0 + 3):
            uRslt = IDS_STATUS_FAX_SEND_FAILED;
            break;
    }

    if (uRslt != IDS_STATUS_FAX_SEND_PASSED) {
        if (uRslt == IDS_STATUS_ITERATION_STOPPED) {
            SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_FAX_STOPPING, 0);
        }

        FaxAbort(g_hFaxSvcHandle, g_dwFaxId);

        return uRslt;
    }

    return IDS_STATUS_FAX_SEND_PASSED;
}

UINT
fnReceiveFax(
    LPHANDLE  phFaxStopRcvEvents,
    DWORD     dwReceiveTimeout,
    LPWSTR    szTsid,
    DWORD     dwTsidSize,
    LPWSTR    szCopyTiffFile,
    DWORD     dwCopyTiffFileSize,
    LPWSTR    szCopyTiffName,
    DWORD     dwCopyTiffNameSize
)
/*++

Routine Description:

  Receives a fax

Arguments:

  phFaxStopRcvEvents - pointer to the g_hFaxEvent, g_hStopEvent and hFaxRcvEvent
  dwReceiveTimeout - receive timeout interval
  szTsid - TSID of the received fax
  dwTsidSize - size of szTsid buffer, in bytes
  szCopyTiffFile - name of the copy of the received fax
  dwCopyTiffFileSize - size of szCopyTiffFile buffer, in bytes
  szCopyTiffName - name of the copy of the received fax
  dwCopyTiffNameSize - size of szCopyTiffName buffer, in bytes

Return Value:

  UINT - resource id

--*/
{
    // hFaxRcvEvent is the handle to the FaxRcv named event
    HANDLE            hFaxRcvEvent;
    // hFaxRcvMutex is the handle to the FaxRcv named mutex
    HANDLE            hFaxRcvMutex;

    // hFaxRcvMap is the handle to the FaxRcv memory map
    HANDLE            hFaxRcvMap;
    // pFaxRcvView is the pointer to the FaxRcv memory map view
    LPBYTE            pFaxRcvView;

    // szTiffFile is the name of the received fax
    LPWSTR            szTiffFile;
    // szFile is the name of the received fax
    WCHAR             szFile[_MAX_FNAME];
    // szExt is the extension of the received fax
    WCHAR             szExt[_MAX_EXT];

    // FaxReceiveInfo is the fax receive info
    FAX_RECEIVE_INFO  FaxReceiveInfo;

    DWORD             dwRslt;
    UINT              uRslt;
    UINT_PTR          upOffset;

    // Update the status
    SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_FAX_WAITING, 0);

    // Open FaxRcv named event
    hFaxRcvEvent = OpenEvent(SYNCHRONIZE, FALSE, FAXRCV_EVENT);
    // Open FaxRcv named mutex
    hFaxRcvMutex = OpenMutex(SYNCHRONIZE, FALSE, FAXRCV_MUTEX);

    if ((!hFaxRcvEvent) || (!hFaxRcvMutex)) {
        if (hFaxRcvEvent) {
            CloseHandle(hFaxRcvEvent);
        }
        if (hFaxRcvMutex) {
            CloseHandle(hFaxRcvMutex);
        }

        return IDS_STATUS_FAXSVC_ENDED;
    }

    // Update phFaxStopRcvEvents
    // hFaxRcvEvent
    phFaxStopRcvEvents[2] = hFaxRcvEvent;

    // Wait for FaxRcv named mutex
    WaitForSingleObject(hFaxRcvMutex, INFINITE);
    // Enable the FaxRcv Routing Extension
    fnEnableFaxRcv(TRUE);
    // Release access to the FaxRcv named mutex
    ReleaseMutex(hFaxRcvMutex);

    g_bFaxRcvInProgress = TRUE;

    // Wait for Fax, Stop or FaxRcv named event
    dwRslt = WaitForMultipleObjects(3, phFaxStopRcvEvents, FALSE, dwReceiveTimeout);

    g_bFaxRcvInProgress = FALSE;

    // Wait for FaxRcv named mutex
    WaitForSingleObject(hFaxRcvMutex, INFINITE);
    // Disable the FaxRcv Routing Extension
    fnEnableFaxRcv(FALSE);

    switch (dwRslt) {
        case WAIT_TIMEOUT:
            uRslt = IDS_STATUS_TIMEOUT_ENDED;
            break;

        case WAIT_OBJECT_0:
            uRslt = IDS_STATUS_FAXSVC_ENDED;
            break;

        case (WAIT_OBJECT_0 + 1):
            uRslt = IDS_STATUS_ITERATION_STOPPED;
            break;

        case (WAIT_OBJECT_0 + 2):
            uRslt = IDS_STATUS_FAX_RECEIVED;
            break;

    }

    if (uRslt != IDS_STATUS_FAX_RECEIVED) {
        // Release access to the FaxRcv named mutex
        ReleaseMutex(hFaxRcvMutex);

        CloseHandle(hFaxRcvEvent);
        CloseHandle(hFaxRcvMutex);

        return uRslt;
    }

    // Open FaxRcv memory map
    hFaxRcvMap = OpenFileMapping(FILE_MAP_READ, FALSE, FAXRCV_MAP);
    if (!hFaxRcvMap) {
        // Release access to the FaxRcv named mutex
        ReleaseMutex(hFaxRcvMutex);

        CloseHandle(hFaxRcvEvent);
        CloseHandle(hFaxRcvMutex);

        return IDS_STATUS_FAXSVC_ENDED;
    }

    // Create FaxRcv memory map view
    pFaxRcvView = (LPBYTE) MapViewOfFile(hFaxRcvMap, FILE_MAP_READ, 0, 0, 0);

    // Set upOffset
    upOffset = 0;

    // Set szTiffFile
    szTiffFile = MemAllocMacro((lstrlen((LPWSTR) ((UINT_PTR) pFaxRcvView + upOffset)) + 1) * sizeof(WCHAR));
    lstrcpy(szTiffFile, (LPWSTR) ((UINT_PTR) pFaxRcvView + upOffset));
    upOffset += (lstrlen(szTiffFile) + 1) * sizeof(WCHAR);

    // Initialize szTsid
    ZeroMemory(szTsid, dwTsidSize);
    // Set szTsid
    lstrcpy(szTsid, (LPWSTR) ((UINT_PTR) pFaxRcvView + upOffset));
    upOffset += (lstrlen(szTsid) + 1) * sizeof(WCHAR);

    // Set FaxReceiveInfo.dwDeviceId
    FaxReceiveInfo.dwDeviceId = (DWORD) *(LPDWORD) ((UINT_PTR) pFaxRcvView + upOffset);

    // Close FaxRcv memory map view
    UnmapViewOfFile(pFaxRcvView);
    // Close FaxRcv memory map
    CloseHandle(hFaxRcvMap);

    _wsplitpath(szTiffFile, NULL, NULL, szFile, szExt);

    // Initialize szCopyTiffFile
    ZeroMemory(szCopyTiffFile, dwCopyTiffFileSize * sizeof(WCHAR));
    // Set szCopyTiffFile
    GetCurrentDirectory(dwCopyTiffFileSize, szCopyTiffFile);
    lstrcat(szCopyTiffFile, L"\\");
    lstrcat(szCopyTiffFile, (LPWSTR) ((UINT_PTR) szFile + lstrlen(L"Copy of ") * sizeof(WCHAR)));
    lstrcat(szCopyTiffFile, szExt);

    // Initialize szCopyTiffName
    ZeroMemory(szCopyTiffName, dwCopyTiffNameSize);
    // Set szCopyTiffName
    lstrcpy(szCopyTiffName, (LPWSTR) ((UINT_PTR) szFile + lstrlen(L"Copy of ") * sizeof(WCHAR)));
    lstrcat(szCopyTiffName, szExt);

    // Set FaxReceiveInfo.szCopyTiffName
    FaxReceiveInfo.szCopyTiffName = szCopyTiffName;

    // Copy szTiffFile to szCopyTiffFile
    CopyFile(szTiffFile, szCopyTiffFile, FALSE);

    // Delete the received fax
    DeleteFile(szTiffFile);

    // Free szTiffFile
    MemFreeMacro(szTiffFile);

    // Release access to the FaxRcv named mutex
    ReleaseMutex(hFaxRcvMutex);

    CloseHandle(hFaxRcvEvent);
    CloseHandle(hFaxRcvMutex);

    // Update the status
    SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_FAX_RECEIVED, (LPARAM) &FaxReceiveInfo);

    return IDS_STATUS_FAX_RECEIVED;
}

DWORD WINAPI fnSendProc (LPVOID lpv)
/*++

Routine Description:

  Thread to handle the Send logic

Return Value:

  DWORD - exit code

--*/
{
    // hFaxExitStartEvents is a pointer to the g_hFaxEvent, g_hExitEvent and g_hStartEvent
    HANDLE         hFaxExitStartEvents[3];
    // hFaxStopRasPassFailEvents is a pointer to the g_hFaxEvent, g_hStopEvent, g_hRasPassedEvent and g_hRasFailedEvent
    HANDLE         hFaxStopRasPassFailEvents[4];
    // hFaxStopSendPassFailEvents is a pointer to the g_hFaxEvent, g_hStopEvent, g_hSendPassedEvent and g_hSendFailedEvent
    HANDLE         hFaxStopSendPassFailEvents[4];
    // hFaxStopRcvEvents is a pointer to the g_hFaxEvent, g_hStopEvent and g_hFaxRcvEvent
    HANDLE         hFaxStopRcvEvents[3];

    // dwSendTimeout is the send timeout interval
    DWORD          dwSendTimeout;
    // dwReceiveTimeout is the receive timeout interval
    DWORD          dwReceiveTimeout;

    // szOriginalTiffFile is the name of the original tiff file
    LPWSTR         szOriginalTiffFile;
    // dwNumFaxesToSend is the number of faxes to be sent
    DWORD          dwNumFaxesToSend;
    // dwNumFaxesRemaining is the number of faxes remaining to be sent
    DWORD          dwNumFaxesRemaining;

    // FaxJobParams is the fax job params
    FAX_JOB_PARAM  FaxJobParams;

    // szCopyTiffFile is the name of the copy of the received fax
    WCHAR          szCopyTiffFile[_MAX_PATH];
    // szCopyTiffName is the name of the copy of the received fax
    WCHAR          szCopyTiffName[_MAX_FNAME + _MAX_EXT];
    // szReceivedTsid is the received TSID
    WCHAR          szReceivedTsid[ENCODE_CHAR_LEN + CONTROL_CHAR_LEN + PHONE_NUM_LEN + 1];
    // szEncodedTsid is the encoded TSID
    WCHAR          szEncodedTsid[ENCODE_CHAR_LEN + CONTROL_CHAR_LEN + PHONE_NUM_LEN + 1];
    // szDecodedTsid is the decoded TSID
    WCHAR          szDecodedTsid[PHONE_NUM_LEN + 1];

    DWORD          dwPages;
    DWORD          dwRslt;
    UINT           uRslt;

    // Set hFaxExitStartEvents
    // g_hFaxEvent
    hFaxExitStartEvents[0] = g_hFaxEvent;
    // g_hExitEvent
    hFaxExitStartEvents[1] = g_hExitEvent;
    // g_hStartEvent
    hFaxExitStartEvents[2] = g_hStartEvent;

    // Set hFaxStopRasPassFailEvents
    // g_hFaxEvent
    hFaxStopRasPassFailEvents[0] = g_hFaxEvent;
    // g_hStopEvent
    hFaxStopRasPassFailEvents[1] = g_hStopEvent;
    // g_hPassEvent
    hFaxStopRasPassFailEvents[2] = g_hRasPassedEvent;
    // g_hFailEvent
    hFaxStopRasPassFailEvents[3] = g_hRasFailedEvent;

    // Set hFaxStopSendPassFailEvents
    // g_hFaxEvent
    hFaxStopSendPassFailEvents[0] = g_hFaxEvent;
    // g_hStopEvent
    hFaxStopSendPassFailEvents[1] = g_hStopEvent;
    // g_hSendPassedEvent
    hFaxStopSendPassFailEvents[2] = g_hSendPassedEvent;
    // g_hSendFailedEvent
    hFaxStopSendPassFailEvents[3] = g_hSendFailedEvent;

    // Set hFaxStopRcvEvents
    // g_hFaxEvent
    hFaxStopRcvEvents[0] = g_hFaxEvent;
    // g_hStopEvent
    hFaxStopRcvEvents[1] = g_hStopEvent;

    if (g_bBVT) {
        dwSendTimeout = FAXSVC_RETRIES * ((FAXSVC_RETRIES + 1) * FAXSVC_RETRYDELAY) * 60000;
        dwReceiveTimeout = dwSendTimeout * 2;

        szOriginalTiffFile = FAXBVT_TIF;

        dwNumFaxesToSend = FAXBVT_NUM_FAXES;
        dwNumFaxesRemaining = dwNumFaxesToSend;
    }
    else {
        dwSendTimeout = INFINITE;
        dwReceiveTimeout = INFINITE;

        szOriginalTiffFile = FAXWHQL_TIF;

        dwNumFaxesToSend = FAXWHQL_NUM_FAXES;
        dwNumFaxesRemaining = dwNumFaxesToSend;
    }

    while (TRUE) {
        // Wait for Fax, Exit, or Start event
        dwRslt = WaitForMultipleObjects(3, hFaxExitStartEvents, FALSE, INFINITE);

        if (dwRslt == WAIT_OBJECT_0) {
            SendMessage(g_hWndDlg, UM_FAXSVC_ENDED, 0, 0);
            continue;
        }
        else if (dwRslt == (WAIT_OBJECT_0 + 1)) {
            return 0;
        }

        if (g_bSend) {
            if (g_bRasEnabled) {
                // Send a RAS call
                uRslt = fnSendRas(hFaxStopRasPassFailEvents);

                if (uRslt != IDS_STATUS_RAS_PASSED) {
                    switch (uRslt) {
                        case IDS_STATUS_TIMEOUT_ENDED:
                            SendMessage(g_hWndDlg, UM_TIMEOUT_ENDED, 0, 0);
                            break;

                        case IDS_STATUS_FAXSVC_ENDED:
                            SendMessage(g_hWndDlg, UM_FAXSVC_ENDED, 0, 0);
                            break;

                        case IDS_STATUS_ITERATION_STOPPED:
                            SendMessage(g_hWndDlg, UM_ITERATION_STOPPED, 0, 0);
                            break;

                        case IDS_STATUS_RAS_FAILED:
                            SendMessage(g_hWndDlg, UM_ITERATION_FAILED, 0, 0);
                            break;

                    }

                    dwNumFaxesRemaining = dwNumFaxesToSend;

                    continue;
                }
            }

            // Encode the TSID
            if (!g_bNoCheck) {
                fnEncodeTsid(g_szRcvNumber, TX_CONTROL_CHARS, szEncodedTsid);
            }
            else {
                lstrcpy(szEncodedTsid, g_szSndNumber);
            }

            // Initialize FaxJobParams
            ZeroMemory(&FaxJobParams, sizeof(FAX_JOB_PARAM));

            // Set FaxJobParams
            FaxJobParams.SizeOfStruct = sizeof(FAX_JOB_PARAM);
            FaxJobParams.RecipientNumber = g_szSndNumber;
            FaxJobParams.RecipientName = g_szSndNumber;
            FaxJobParams.Tsid = szEncodedTsid;
            FaxJobParams.ScheduleAction = JSA_NOW;

            // Send a fax
            uRslt = fnSendFax(FaxJobParams, szOriginalTiffFile, hFaxStopSendPassFailEvents, dwSendTimeout);

            if (uRslt != IDS_STATUS_FAX_SEND_PASSED) {
                switch (uRslt) {
                    case IDS_STATUS_TIMEOUT_ENDED:
                        SendMessage(g_hWndDlg, UM_TIMEOUT_ENDED, 0, 0);
                        break;

                    case IDS_STATUS_FAXSVC_ENDED:
                        SendMessage(g_hWndDlg, UM_FAXSVC_ENDED, 0, 0);
                        break;

                    case IDS_STATUS_ITERATION_STOPPED:
                        SendMessage(g_hWndDlg, UM_ITERATION_STOPPED, 0, 0);
                        break;

                    case IDS_STATUS_FAX_SEND_FAILED:
                        SendMessage(g_hWndDlg, UM_ITERATION_FAILED, 0, 0);
                        break;

                }

                dwNumFaxesRemaining = dwNumFaxesToSend;

                continue;
            }
        }

        // Receive a fax
        uRslt = fnReceiveFax(hFaxStopRcvEvents, dwReceiveTimeout, szReceivedTsid, sizeof(szReceivedTsid) / sizeof(WCHAR), szCopyTiffFile, sizeof(szCopyTiffFile) / sizeof(WCHAR), szCopyTiffName, sizeof(szCopyTiffName) / sizeof(WCHAR));

        if (uRslt != IDS_STATUS_FAX_RECEIVED) {
            switch (uRslt) {
                case IDS_STATUS_TIMEOUT_ENDED:
                    SendMessage(g_hWndDlg, UM_TIMEOUT_ENDED, 0, 0);
                    break;

                case IDS_STATUS_FAXSVC_ENDED:
                    SendMessage(g_hWndDlg, UM_FAXSVC_ENDED, 0, 0);
                    break;

                case IDS_STATUS_ITERATION_STOPPED:
                    SendMessage(g_hWndDlg, UM_ITERATION_STOPPED, 0, 0);
                    break;

            }

            if (g_bSend) {
                if (uRslt != IDS_STATUS_ITERATION_STOPPED) {
                    // This iteration failed
                    SendMessage(g_hWndDlg, UM_ITERATION_FAILED, 0, 0);
                }
                // Reset the number of faxes remaining to be sent
                dwNumFaxesRemaining = dwNumFaxesToSend;
            }

            continue;
        }

        if (!g_bNoCheck) {
            if (!fnDecodeTsid(szReceivedTsid, g_bSend ? RX_CONTROL_CHARS : TX_CONTROL_CHARS, szDecodedTsid)) {
                // Update the status
                SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_FAX_INVALID, 0);

                if (g_bSend) {
                    // This iteration failed
                    SendMessage(g_hWndDlg, UM_ITERATION_FAILED, 0, 0);
                    // Reset the number of faxes remaining to be sent
                    dwNumFaxesRemaining = dwNumFaxesToSend;
                }
                else {
                    // Set the g_hStartEvent to start another cycle to receive a fax
                    SetEvent(g_hStartEvent);
                }

                continue;
            }

            // Update the status
            SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_FAX_ID, (LPARAM) szReceivedTsid);
            SendMessage(g_hWndDlg, UM_UPDATE_STATUS, IDS_STATUS_FAX_VERIFYING, (LPARAM) szCopyTiffName);

            // Verify the tiff
            uRslt = fnVerifyTiff(szCopyTiffFile, &dwPages);

            if ((uRslt == ERROR_SUCCESS) && (((g_bBVT) && (dwPages == FAXBVT_PAGES)) || ((!g_bBVT) && (dwPages == FAXWHQL_PAGES)))) {
                // Fax is valid
                uRslt = IDS_TIFF_VALID_TIFF;
            }
            else if (uRslt == ERROR_SUCCESS) {
                // Each page is valid, but missing pages
                uRslt = IDS_TIFF_INVALID_PAGES;
            }

            if (uRslt == IDS_TIFF_INVALID_PAGES) {
                // Update the status
                SendMessage(g_hWndDlg, UM_UPDATE_STATUS, uRslt, dwPages);
            }
            else {
                // Update the status
                SendMessage(g_hWndDlg, UM_UPDATE_STATUS, uRslt, 0);
            }

        }
        else {
            lstrcpy(szDecodedTsid, szReceivedTsid);
            uRslt = IDS_TIFF_VALID_TIFF;
        }

        if ((g_bSend) && (uRslt == IDS_TIFF_VALID_TIFF)) {
            // Decrement the number of faxes remaining to be sent
            dwNumFaxesRemaining--;

            if (dwNumFaxesRemaining == 0) {
                // All faxes have been sent, so this iteration passed
                SendMessage(g_hWndDlg, UM_ITERATION_PASSED, 0, 0);
                // Reset the number of faxes remaining to be sent
                dwNumFaxesRemaining = dwNumFaxesToSend;
            }
            else {
                // There are faxes remaining to be sent, so set the g_hStartEvent to start another cycle to send a fax
                SetEvent(g_hStartEvent);
            }

            continue;
        }
        else if (g_bSend) {
            // This iteration failed
            SendMessage(g_hWndDlg, UM_ITERATION_FAILED, 0, 0);
            // Reset the number of faxes remaining to be sent
            dwNumFaxesRemaining = dwNumFaxesToSend;

            continue;
        }
        else if (uRslt != IDS_TIFF_VALID_TIFF) {
            // Set the g_hStartEvent to start another cycle to receive a fax
            SetEvent(g_hStartEvent);

            continue;
        }

        if (!g_bSend) {
            // Encode the TSID
            if (!g_bNoCheck) {
                fnEncodeTsid(szDecodedTsid, RX_CONTROL_CHARS, szEncodedTsid);
            }
            else {
                lstrcpy(szEncodedTsid, szDecodedTsid);
            }

            // Initialize FaxJobParams
            ZeroMemory(&FaxJobParams, sizeof(FAX_JOB_PARAM));

            // Set FaxJobParams
            FaxJobParams.SizeOfStruct = sizeof(FAX_JOB_PARAM);
            FaxJobParams.RecipientNumber = szDecodedTsid;
            FaxJobParams.RecipientName = szDecodedTsid;
            FaxJobParams.Tsid = szEncodedTsid;
            FaxJobParams.ScheduleAction = JSA_NOW;

            // Send a fax
            uRslt = fnSendFax(FaxJobParams, szCopyTiffFile, hFaxStopSendPassFailEvents, dwSendTimeout);

            switch (uRslt) {
                case IDS_STATUS_TIMEOUT_ENDED:
                    SendMessage(g_hWndDlg, UM_TIMEOUT_ENDED, 0, 0);
                    break;

                case IDS_STATUS_FAXSVC_ENDED:
                    SendMessage(g_hWndDlg, UM_FAXSVC_ENDED, 0, 0);
                    break;

                case IDS_STATUS_ITERATION_STOPPED:
                    SendMessage(g_hWndDlg, UM_ITERATION_STOPPED, 0, 0);
                    break;

                case IDS_STATUS_FAX_SEND_PASSED:
                case IDS_STATUS_FAX_SEND_FAILED:
                    break;

            }

            if ((uRslt == IDS_STATUS_FAX_SEND_PASSED) || (uRslt == IDS_STATUS_FAX_SEND_FAILED)) {
                if (g_bBVT) {
                    SendMessage(g_hWndDlg, UM_ITERATION_STOPPED, 0, 0);
                    SendMessage(GetDlgItem(g_hWndDlg, IDC_EXIT_BUTTON), BM_CLICK, 0, 0);
                }
                else {
                    // Set the g_hStartEvent to start another cycle to wait for a fax
                    SetEvent(g_hStartEvent);
                }
            }

            continue;

        }
    }
}

#endif