//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
//------------------------------------------------------------------------------
//
//      Bluetooth Virtual Comm Port
//
//
// Module Name:
//
//      portemu.cxx
//
// Abstract:
//
//      This file implements vCOMM port layer
//
//
//------------------------------------------------------------------------------
#include <windows.h>
#include <intsafe.h>
#include <marshal.hpp>
#include <svsutil.hxx>
#include <bt_buffer.h>
#include <bt_ddi.h>
#include <bt_api.h>
#include <bt_debug.h>
#include <bt_sdp.h>
#include <bthapi.h>
#include <ceport.h>
#include <sdplib.h>

#include <bt_os.h>

#include <windev.h>
#include <pegdser.h>

#define EA_BIT              0x01

#define BIT(c) (1 << (c))

#define MSC_EA_BIT      EA_BIT
#define MSC_FC_BIT      BIT(1)      // Flow control, clear if we can receive
#define MSC_RTC_BIT     BIT(2)      // Ready to communicate, set when ready
#define MSC_RTR_BIT     BIT(3)      // Ready to receive, set when ready
#define MSC_IC_BIT      BIT(6)      // Incoming call
#define MSC_DV_BIT      BIT(7)      // Data valid

#define PORTEMU_OPEN_MASK           0x3
#define PORTEMU_OPEN_MAX            4       // Must be power of 2; x - 1 used for mask
#define PORTEMU_OPEN_ALLUSED        ((1 << PORTEMU_OPEN_MAX) - 1)

#define PORTEMU_MAX_RFCOMM_CHANNEL  31

#define PORTEMU_OPEN_ATTEMPTS       5
#define PORTEMU_OPEN_TIMEOUT        1000

#define SDP_CONNECT_TIMEOUT         10000

#define RFCOMM_PORT_FLAGS_SECURE_PRECHANNEL 0x10000000   // Hack to allow authentication pre-channel establishment

enum COMM_OP {
    COMM_NO_OP      = 0,
    COMM_OPEN       = 1,
    COMM_WAIT_EVENT = 2,
    COMM_READ       = 3,
    COMM_WRITE      = 4,
    COMM_RPN        = 5,
    COMM_DISC       = 6
};

struct PendingIO : public SVSAllocClass {
    PendingIO       *pNext;         // Next in list
    HANDLE          hEvent;         // Event to signal on completion
    COMM_OP         op;             // COMM_OP (open, wait event, read, write)

    unsigned char   *buffer;        // Buffer
    int             cbuffer;        // Size of buffer

    int             cbuffer_used;   // Bytes used in the buffer so far
    int             iIoResult;      // IO result on completion

    DWORD           dwEvent;        // COMM events
    DWORD           dwLineError;    // COMM line errors

    int             iPortNum;       // Port number for this IO

    PendingIO (COMM_OP a_op, int a_iPortNum);
    ~PendingIO (void);
};

enum RFCOMM_OP {
    NO_OP      = 0,
    RFCOMM_CONNECT = 1,
    RFCOMM_SDP     = 2,
    RFCOMM_PN      = 3,
    RFCOMM_WRITE   = 4,
    RFCOMM_RPN     = 5,
    RFCOMM_DISC    = 6
};

struct PORTEMU_CONTEXT;

struct ECall {
    ECall           *pNext;         // Next in list

    RFCOMM_OP       fWhat;          // RFCOMM_OP (connect or write)

    int             cBytes;         // Number of bytes on write

    PORTEMU_CONTEXT *pContext;      // Owner context

    SVSHandle       hCallContext;   // Unique handle for async call lookup

    ECall (RFCOMM_OP a_fWhat, PORTEMU_CONTEXT *a_pContext) {
        fWhat    = a_fWhat;
        pContext = a_pContext;
        pNext    = NULL;
        cBytes   = 0;
        hCallContext = SVSUTIL_HANDLE_INVALID;
    }

    ~ECall () {
        if (hCallContext != SVSUTIL_HANDLE_INVALID)
            btutil_CloseHandle (hCallContext);
    }

    void *operator new (size_t size);
    void operator delete (void *ptr);
};

struct BD_BUFFER_LIST {
    BD_BUFFER_LIST  *pNext;         // Next in list
    BD_BUFFER       *pb;            // Buffer

    void *operator new (size_t size);
    void operator delete (void *ptr);
};

//      This structure is what describes the device. Several important notes.
//      Lifecycle starts with registering device and ends with unregistering it.
//      The port is connected when it is opened, and disconnected when it's closed.
//      Ownership only commences with OPENING the device. Ownership is surrendered
//      by CLOSING device. When device is unowned, the com port can be configured
//      using the following call:
//          RFCOMMSetServerChannel (int index,  int channel, int flocal, BD_ADDR *pdevice, int iminmtu, int imaxmtu);
//          {if (flocal) ASSERT (! pdevice);}
//
struct PORTEMU_CONTEXT : public SVSAllocClass {
    PORTEMU_CONTEXT     *pNext;                 // Next in list

    HANDLE              hRFCOMM;                // RFCOMM handle

    RFCOMM_INTERFACE    rfcomm_if;              // RFCOMM interface table
    int                 iDeviceHead;            // Pre-alloc in front of the buffer
    int                 iDeviceTrail;           // Pre-alloc at the end of the buffer

    unsigned int        channel      : 5;       // Server channel id
    unsigned int        fLocal       : 1;       // Local channel
    unsigned int        fLocalOpen   : 1;       // Port really connected
    unsigned int        fSdpQueryOn  : 1;       // SDP query is in progress
    unsigned int        fSentXon     : 1;       // Xon sent
    unsigned int        fc       : 1;           // flow, 1 = don't send
    unsigned int        fc_aggregate : 1;       // aggregate flow, 1 = don't send
    unsigned int        credit_fc    : 1;       // use credit-based flow
    unsigned int        local_dcb    : 1;       // use local DCB, do not send RPN commands
    unsigned int        fkeep_dcd    : 1;       // keep DCD up for the duration of connection
    unsigned int        freq_auth    : 1;       // require authentication
    unsigned int        freq_encrypt : 1;       // require encryption
    unsigned int        fSecurePreChannel : 1;  // do security before channel establishment
    unsigned int        uiOpenRef    : 4;       // Open ref field
    unsigned int        uiReadRef    : 4;       // Open ref field
    unsigned int        uiWriteRef   : 4;       // Open ref field

    unsigned int        fSending     : 1;       // in SendData
    unsigned int        fClosing     : 1;       // Closing port
    unsigned int        fConnected   : 1;       // Connection is up

    int                 iHaveCredits;           // Have credits
    int                 iGaveCredits;           // Credits outstanding

    BD_ADDR             b;                      // BD_ADDR of the other side

    HANDLE              hConnection;            // Connection handle (NULL = not connected)

    int                 iMTU;                   // Negotiated MTU

    int                 iMinMTU;                // MIN MTU for negotiation
    int                 iMaxMTU;                // MAX MTU for negotiation

    int                 iSendQuota;             // Send quota
    int                 iSendQuotaUsed;         // Used quota for send

    int                 iRecvQuota;             // Recv quota
    int                 iRecvQuotaUsed;         // Used recv quota

    DWORD               adwEnabledEvents[PORTEMU_OPEN_MAX]; // Enabled COMM events (SetCommEvents)
    DWORD               adwOccuredEvents[PORTEMU_OPEN_MAX]; // Occured COMM events (WaitCommEvents)
    DWORD               adwErr[PORTEMU_OPEN_MAX];           // COMM line error
    HANDLE              hOwnerProc[PORTEMU_OPEN_MAX];       // Owner proc

    unsigned char       ucv24out;               // COMM modem status on outgoing lines
    DWORD               dwModemStatusIn;        // COMM modem status on incoming lines
    PendingIO           *pops;                  // list of pending io
    BD_BUFFER_LIST      *pbl;                   // Arrived data list

    ECall               *pCalls;                // pending calls


    DCB                 dcb;                    // DCB
    COMMTIMEOUTS        ct;                     // COMMTIMEOUTS

    GUID                uuidService;            //UUID of the service
    unsigned short      sdpCid;                 //channel id for sdp search

    PORTEMU_CONTEXT (void) {
        memset (this, 0, sizeof(*this));
        fLocal                  = TRUE;

        local_dcb               = TRUE;

        dcb.DCBlength           = sizeof(DCB);
        dcb.BaudRate            = CBR_115200;
        dcb.fBinary             = TRUE;
        dcb.fParity             = FALSE;
        dcb.fOutxCtsFlow        = FALSE;
        dcb.fOutxDsrFlow        = FALSE;
        dcb.fDtrControl         = DTR_CONTROL_DISABLE;
        dcb.fDsrSensitivity     = FALSE;
        dcb.fTXContinueOnXoff   = TRUE;
        dcb.fOutX                   = FALSE;
        dcb.fInX                = FALSE;
        dcb.fErrorChar          = FALSE;
        dcb.fNull               = FALSE;
        dcb.fRtsControl         = RTS_CONTROL_DISABLE;
        dcb.fAbortOnError       = TRUE;
        dcb.XonLim              = PORTEMU_XONLIM;
        dcb.XoffLim             = PORTEMU_XOFFLIM;
        dcb.ByteSize            = 8;
        dcb.Parity              = NOPARITY;
        dcb.StopBits            = ONESTOPBIT;

        ct.ReadIntervalTimeout          = -1;
        ct.ReadTotalTimeoutConstant     = PORTEMU_RTO;
        ct.ReadTotalTimeoutMultiplier   = 0;
        ct.WriteTotalTimeoutConstant    = PORTEMU_WTO;
        ct.WriteTotalTimeoutMultiplier  = 0;
    }

    ~PORTEMU_CONTEXT (void);
};

class PORTEMU : public SVSAllocClass, public SVSSynch, public SVSRefObj {
public:
    PORTEMU_CONTEXT     *pPorts;                // List of ports
    FixedMemDescr       *pfmdPBL;               // FMD for buffer list
    FixedMemDescr       *pfmdCalls;             // FMD for calls

    int                 iPIOBlocksUsed;         // Ref count on PIO blocks
    int                 fInitialized;           // Initialized?

    int                 iDefaultMTUMin;         // Default MTU Min
    int                 iDefaultMTUMax;         // Default MTU Max
    int                 iDefaultSendQuota;      // Default Send Quota
    int                 iDefaultRecvQuota;      // Default Recv Quota

    HANDLE              hSDP;                   // SDP handle
    SDP_INTERFACE       sdp_if;                 // sdp interface table

    PORTEMU (void) {
        pPorts = NULL;
        pfmdPBL = NULL;
        pfmdCalls = NULL;

        iPIOBlocksUsed = 0;
        fInitialized = FALSE;

        iDefaultMTUMin = iDefaultMTUMax = 0;
        iDefaultSendQuota = iDefaultRecvQuota = 0;

        hSDP = NULL;
        memset(&sdp_if, 0, sizeof(sdp_if));
    }
};

extern "C" BOOL COM_Close (DWORD dwData);

//
//      Static data
//
static PORTEMU  *gpPORTEMU;

PendingIO::PendingIO (COMM_OP a_op, int a_iPortNum) {
    ++gpPORTEMU->iPIOBlocksUsed;
    memset (this, 0, sizeof(*this));
    op = a_op;
    iPortNum = a_iPortNum;
    hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
}

PendingIO::~PendingIO (void) {
    --gpPORTEMU->iPIOBlocksUsed;
    CloseHandle (hEvent);

#if defined (DEBUG) || defined (_DEBUG)
    PORTEMU_CONTEXT *pCtx = gpPORTEMU->pPorts;
    while (pCtx) {
        PendingIO *pio = pCtx->pops;
        while (pio && (pio != this))
            pio = pio->pNext;

        SVSUTIL_ASSERT (! pio);
        pCtx = pCtx->pNext;
    }
#endif
}

static int IsSerialIOCTL (DWORD ioctl_code) {
    if (((ioctl_code >> 16) & 0xffff) == FILE_DEVICE_SERIAL_PORT)
        return TRUE;

    return FALSE;
}

#define BAUD_ARRAY_SIZE 9
static int byte_to_baud[BAUD_ARRAY_SIZE] = {CBR_2400, CBR_4800, 7200, CBR_9600, CBR_19200, CBR_38400, CBR_57600, CBR_115200, 230400};

static int BaudToByte (int baud) {
    for (int i = 0 ; i < BAUD_ARRAY_SIZE ; ++i) {
        if (byte_to_baud[i] == baud)
            return i;
    }

    IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] ByteToBaud : baud rate translation failed, baud = %d!\n", baud));
    return -1;
}

static int VerifyDCB (DCB *pdcb) {
    if (pdcb->DCBlength != sizeof(*pdcb))
        return FALSE;

    if (-1 == BaudToByte (pdcb->BaudRate))
        return FALSE;

    if ((pdcb->ByteSize < 5) || (pdcb->ByteSize > 8))
        return FALSE;

    if ((pdcb->StopBits != ONESTOPBIT) && (pdcb->StopBits != ONE5STOPBITS))
        return FALSE;

    if (pdcb->fParity && (pdcb->Parity > 4))
        return FALSE;

    if (! pdcb->fBinary)
        return FALSE;

    if (pdcb->fOutxCtsFlow || pdcb->fOutxDsrFlow || pdcb->fDsrSensitivity ||
            (pdcb->fDtrControl != DTR_CONTROL_DISABLE) || (pdcb->fRtsControl != RTS_CONTROL_DISABLE))
        return FALSE;

    if (pdcb->fOutX || pdcb->fInX || pdcb->fErrorChar || pdcb->fNull)
        return FALSE;

    if (pdcb->XonLim < pdcb->XoffLim)
        return FALSE;

    pdcb->EofChar = pdcb->ErrorChar = pdcb->EvtChar = pdcb->XoffChar = pdcb->XonChar = 0;

    pdcb->fAbortOnError = TRUE;
    pdcb->fTXContinueOnXoff = TRUE;

    return TRUE;
}

static ECall *NewCall (RFCOMM_OP fWhat, PORTEMU_CONTEXT *pContext) {
    ECall* pCall = new ECall (fWhat, pContext);

    if (pCall) {
        pCall->hCallContext = btutil_AllocHandle ((LPVOID)pCall);
        if (SVSUTIL_HANDLE_INVALID == pCall->hCallContext) {
            delete pCall;
            return NULL;
        }
    }

    return pCall;
}

static PORTEMU_CONTEXT *NewPORTEMU_CONTEXT (void) { return new PORTEMU_CONTEXT; }
static PendingIO *NewPendingIO (COMM_OP op, int iPortNum) { return new PendingIO (op, iPortNum); }

static  PORTEMU_CONTEXT *GetContext (DWORD dwData) {
    PORTEMU_CONTEXT *pContext = gpPORTEMU->pPorts;
    while (pContext && (pContext != (PORTEMU_CONTEXT *)dwData))
        pContext = pContext->pNext;

    return pContext;
}

PORTEMU_CONTEXT::~PORTEMU_CONTEXT (void) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] deleting context 0x%08x\n", this));

    // Abort all listeners waiting for PIO event
    while (pops) {
        PendingIO *pNext = pops->pNext;
        pops->pNext         = NULL;
        pops->iIoResult = ERROR_OPERATION_ABORTED;
        SetEvent (pops->hEvent);

        pops = pNext;
    }

    // Clean up call structs that are outstanding
    while (pCalls) {
        ECall *pNext = pCalls->pNext;
        delete pCalls;
        pCalls = pNext;
    }

    // Clean up PortEmu buffer lists
    while (pbl) {
        BD_BUFFER_LIST *pbl_next = pbl->pNext;
        if (pbl->pb->pFree)
            pbl->pb->pFree (pbl->pb);

        delete pbl;

        pbl = pbl_next;
    }

    if (hRFCOMM) {
        gpPORTEMU->Unlock ();
        RFCOMM_CloseDeviceContext (hRFCOMM);
        gpPORTEMU->Lock ();
    }

}

BOOL BufferGetChunk_TryExcept (BD_BUFFER *pB, int cChunk, unsigned char *pChunk) {
    BOOL fRet = TRUE;
    __try {
        BufferGetChunk (pB, cChunk, pChunk);
    } __except (1) {
        fRet = FALSE;
    }
    return fRet;
}


static void ForgetPort (PORTEMU_CONTEXT *pContext, int iPort) {
    unsigned int mask = ~(1 << iPort);
    pContext->uiOpenRef &= mask;
    pContext->uiReadRef &= mask;
    pContext->uiWriteRef &= mask;
    pContext->hOwnerProc[iPort] = NULL;
}

static ECall *FindCall (void *pCallContext) {
    if (! pCallContext)
        return NULL;

    PORTEMU_CONTEXT *pContext = NULL;

    __try {
        pContext = ((ECall *)pCallContext)->pContext;
    } __except (1) {
        IFDBG(DebugOut (DEBUG_ERROR, L"FindCall excepted!\n"));
    }

    if (! pContext)
        return NULL;

    PORTEMU_CONTEXT *pC = gpPORTEMU->pPorts;
    while (pC && (pC != pContext))
        pC = pC->pNext;

    if (! pC)
        return NULL;

    ECall *pCall = pC->pCalls;
    while (pCall && (pCall != pCallContext))
        pCall = pCall->pNext;

    return pCall;
}

static void DeleteCall (ECall *pCall) {
    ECall *pC = pCall->pContext->pCalls;
    ECall *pP = NULL;

    while (pC && pC != pCall) {
        pP = pC;
        pC = pC->pNext;
    }

    PREFAST_ASSERT (pC);

    if (pP)
        pP->pNext = pC->pNext;
    else
        pCall->pContext->pCalls = pC->pNext;

    delete pCall;
}

static void InsertIO (PORTEMU_CONTEXT *pContext, PendingIO *pio) {
    pio->pNext = NULL;
    if (! pContext->pops)
        pContext->pops = pio;
    else {
        PendingIO *pP = pContext->pops;
        while (pP->pNext)
            pP = pP->pNext;
        pP->pNext = pio;
    }
}

static void RemoveIO (PORTEMU_CONTEXT *pContext, PendingIO *pio) {
    if (GetContext ((DWORD)pContext) != pContext)
        return;

    if (pContext->pops == pio)
        pContext->pops = pio->pNext;
    else {
        PendingIO *pP = pContext->pops;
        while (pP && (pP->pNext != pio))
            pP = pP->pNext;

        if (pP)
            pP->pNext = pio->pNext;
    }

    pio->pNext = NULL;
}

static void ReinitNewConnection (PORTEMU_CONTEXT *pContext, HANDLE hConnection) {
    pContext->iRecvQuotaUsed   = pContext->iSendQuotaUsed = 0;

    memset (pContext->adwOccuredEvents, 0, sizeof(pContext->adwOccuredEvents));
    memset (pContext->adwErr, 0, sizeof(pContext->adwErr));
    pContext->dwModemStatusIn  = MS_RLSD_ON;
    pContext->ucv24out     = MSC_DV_BIT | MSC_RTC_BIT | MSC_RTR_BIT;
    pContext->fc           = 0;
    pContext->fSentXon     = FALSE;

    pContext->fSending     = FALSE;
    pContext->fConnected   = TRUE;

    pContext->hConnection  = hConnection;

    SVSUTIL_ASSERT (pContext->sdpCid == 0);
    SVSUTIL_ASSERT (pContext->fSdpQueryOn == 0);
    SVSUTIL_ASSERT (pContext->pbl == NULL);
}

static void ReinitClosedConnection (PORTEMU_CONTEXT *pContext) {
    pContext->fSentXon = FALSE;

    pContext->hConnection    = NULL;
    pContext->iRecvQuotaUsed = 0;
    pContext->iSendQuotaUsed = 0;

    pContext->dwModemStatusIn   = 0;
    pContext->ucv24out          = 0;
    pContext->fc                = 0;

    pContext->credit_fc         = 0;
    pContext->iGaveCredits      = 0;
    pContext->iHaveCredits      = 0;

    pContext->fSending          = FALSE;
    pContext->fConnected        = FALSE;
}

static void CheckCommEvent (PORTEMU_CONTEXT *pContext) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"CheckCommEvent : ctx 0x%08x\n", pContext));

    PendingIO *pParent = NULL;
    PendingIO *pIO = pContext->pops;

    while (pIO) {
        SVSUTIL_ASSERT ((pIO->iPortNum >= 0) && (pIO->iPortNum < PORTEMU_OPEN_MAX));

        DWORD dw;

        if ((pIO->op == COMM_WAIT_EVENT) &&
                            (dw = pContext->adwEnabledEvents[pIO->iPortNum] & pContext->adwOccuredEvents[pIO->iPortNum])) {
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"CheckCommEvent : ctx 0x%08x found and executed waiter (port %d)...\n", pContext, pIO->iPortNum));

            PendingIO *pNext = pIO->pNext;

            if (pParent)
                pParent->pNext = pIO->pNext;
            else
                pContext->pops = pIO->pNext;

            pIO->pNext       = NULL;
            pIO->dwEvent     = dw;

            if (dw & EV_ERR) {
                pIO->dwLineError = pContext->adwErr[pIO->iPortNum];
                pContext->adwErr[pIO->iPortNum] = 0;
            }

            pIO->iIoResult = ERROR_SUCCESS;
            SetEvent (pIO->hEvent);

            pContext->adwOccuredEvents[pIO->iPortNum] = 0;

            pIO = pNext;
            continue;
        }

        pParent = pIO;
        pIO = pIO->pNext;
    }
}

static void RegisterCommEvent (PORTEMU_CONTEXT *pContext, DWORD e, DWORD err) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"Event 0x%04x (%s %s %s %s %s %s %s %s %s %s %s %s) %d: registering\n",
        e,
        (e & EV_RXCHAR ? L"EV_RXCHAR" : L""),
        (e & EV_RXFLAG ? L"EV_RXFLAG" : L""),
        (e & EV_TXEMPTY ? L"EV_TXEMPTY" : L""),
        (e & EV_CTS ? L"EV_CTS" : L""),
        (e & EV_DSR ? L"EV_DSR" : L""),
        (e & EV_RLSD ? L"EV_RLSD" : L""),
        (e & EV_BREAK ? L"EV_BREAK" : L""),
        (e & EV_ERR ? L"EV_ERR" : L""),
        (e & EV_RING ? L"EV_RING" : L""),
        (e & EV_RX80FULL ? L"EV_RX80FULL" : L""),
        (e & EV_EVENT1 ? L"EV_EVENT1" : L""),
        (e & EV_EVENT2 ? L"EV_EVENT2" : L""),
        err));

    for (int i = 0 ; i < PORTEMU_OPEN_MAX ; ++i) {
        pContext->adwOccuredEvents[i] = e;
        if (e == EV_ERR)
            pContext->adwErr[i] = err;
    }

    CheckCommEvent (pContext);
}

static void SetCommEventMask (PORTEMU_CONTEXT *pContext, DWORD dwMask, int iPortNum) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"SetCommEventMask Port %d 0x%04x (%s %s %s %s %s %s %s %s %s %s %s %s)\n",
        iPortNum,
        dwMask,
        (dwMask & EV_RXCHAR ? L"EV_RXCHAR" : L""),
        (dwMask & EV_RXFLAG ? L"EV_RXFLAG" : L""),
        (dwMask & EV_TXEMPTY ? L"EV_TXEMPTY" : L""),
        (dwMask & EV_CTS ? L"EV_CTS" : L""),
        (dwMask & EV_DSR ? L"EV_DSR" : L""),
        (dwMask & EV_RLSD ? L"EV_RLSD" : L""),
        (dwMask & EV_BREAK ? L"EV_BREAK" : L""),
        (dwMask & EV_ERR ? L"EV_ERR" : L""),
        (dwMask & EV_RING ? L"EV_RING" : L""),
        (dwMask & EV_RX80FULL ? L"EV_RX80FULL" : L""),
        (dwMask & EV_EVENT1 ? L"EV_EVENT1" : L""),
        (dwMask & EV_EVENT2 ? L"EV_EVENT2" : L"")));

    pContext->adwEnabledEvents[iPortNum] = dwMask;
    pContext->adwOccuredEvents[iPortNum] = 0;

    PendingIO *pParent = NULL;
    PendingIO *pIO = pContext->pops;

    while (pIO) {
        if ((pIO->op == COMM_WAIT_EVENT) && (pIO->iPortNum == iPortNum)) {
            PendingIO *pNext = pIO->pNext;

            pIO->pNext     = NULL;
            pIO->dwEvent   = 0;
            pIO->iIoResult = ERROR_SUCCESS;

            SetEvent (pIO->hEvent);

            if (pParent)
                pParent->pNext = pNext;
            else
                pContext->pops = pNext;
            pIO = pNext;
        } else {
            pParent = pIO;
            pIO = pIO->pNext;
        }
    }
 }

static int RFCommDisconnectIn (PORTEMU_CONTEXT *pContext) {
	int iRes = ERROR_SUCCESS;

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] RFCommDisconnect 0x%08x (handle 0x%08x device %04x%08x ch 0x%02x\n",
                    pContext, pContext->hConnection, pContext->b.NAP, pContext->b.SAP, pContext->channel));

    HANDLE hConn = pContext->hConnection;

    ECall *pCall = NewCall (RFCOMM_DISC, pContext);
    if (! pCall) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] RFCommDisconnect 0x%08x : Failed to create pending IO. ERROR_NO_SYSTEM_RESOURCES\n", pContext));
        return ERROR_OUTOFMEMORY;
    }

    SVSUTIL_ASSERT (pCall->hCallContext != SVSUTIL_HANDLE_INVALID);

    pCall->pNext = pContext->pCalls;
    pContext->pCalls = pCall;

    if (hConn) {    // Really close it
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] RFCommDisconnect 0x%08x : really closing connection to device\n", pContext));
        HANDLE h = pContext->hRFCOMM;
        SVSHandle hContext = pCall->hCallContext;
        RFCOMM_Disconnect_In pCallback = pContext->rfcomm_if.rfcomm_Disconnect_In;

        gpPORTEMU->Unlock ();
        __try {
            iRes = pCallback (h, (LPVOID)hContext, hConn);
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] RFCommDisconnect : exception in rfcomm_Disconnect_In\n"));
        }
        gpPORTEMU->Lock ();
    }

	return iRes;
}

static void CloseConnection (PORTEMU_CONTEXT *pContext, int iRes, BOOL fSendDisconnect) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] CloseConnection 0x%08x handle 0x%08x device %04x%08x ch 0x%02x send disc %d\n",
                    pContext, pContext->hConnection, pContext->b.NAP, pContext->b.SAP, pContext->channel, fSendDisconnect));

    HANDLE hConn = pContext->hConnection;

    ReinitClosedConnection (pContext);

    RegisterCommEvent (pContext, EV_RLSD, 0);

    PendingIO *pParent = NULL;
    PendingIO *pIO = pContext->pops;

    while (pIO) {
        if ((pIO->op == COMM_READ) || (pIO->op == COMM_WRITE) || (pIO->op == COMM_OPEN) || (pIO->op == COMM_RPN) || (pIO->op == COMM_DISC)) {
            PendingIO *pNext = pIO->pNext;

            pIO->pNext     = NULL;
            pIO->iIoResult = iRes;
            SetEvent (pIO->hEvent);

            if (pParent)
                pParent->pNext = pNext;
            else
                pContext->pops = pNext;

            pIO = pNext;
        } else {
            pParent = pIO;
            pIO = pIO->pNext;
        }
    }

    while (pContext->pbl) {
        if (pContext->pbl->pb->pFree)
            pContext->pbl->pb->pFree (pContext->pbl->pb);
        BD_BUFFER_LIST *pNext = pContext->pbl->pNext;
        delete pContext->pbl;
        pContext->pbl = pNext;
    }

    while (pContext->pCalls) {
        ECall *pNext = pContext->pCalls->pNext;
        delete pContext->pCalls;
        pContext->pCalls = pNext;
    }

    if (fSendDisconnect) {
        //
        // This is a bit strange.  We cleared pContext->hConnection in the ReinitClosedConnection() call
        // above but RFCommDisconnectIn requires this.  Therefore, in the case where we need to send a
        // DISC, we'll set this (and then unset it again after the call).
        //        
        pContext->hConnection = hConn;
        RFCommDisconnectIn (pContext);
        pContext->hConnection = NULL;
    }
}

static int GetQuota (PORTEMU_CONTEXT *pContext) {
    if (pContext->credit_fc && (pContext->iRecvQuota < (pContext->iMTU * 2 * PORTEMU_CREDITS_LOWEST)))
        return pContext->iMTU * 2 * PORTEMU_CREDITS_LOWEST;

    return pContext->iRecvQuota;
}

static int GiveCredits (PORTEMU_CONTEXT *pContext) {
    if ((! pContext->credit_fc) || (pContext->iGaveCredits > PORTEMU_CREDITS_LOW))
        return 0;

    int nCredits = (GetQuota (pContext) - pContext->iRecvQuotaUsed) / pContext->iMTU - pContext->iGaveCredits;

    return nCredits > 0 ? nCredits : 0;
}

static void SendCredits (PORTEMU_CONTEXT *pContext) {
    SVSUTIL_ASSERT (pContext->credit_fc);

    if (pContext->iGaveCredits > PORTEMU_CREDITS_LOWEST)
        return;

    int nCredits = (GetQuota (pContext) - pContext->iRecvQuotaUsed) / pContext->iMTU - pContext->iGaveCredits;

    if (nCredits <= 0)
        return;

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] SendCredits 0x%08x : sending %d credits\n", pContext, nCredits));

    unsigned int cSize;
    if (! SUCCEEDED(UIntAdd(pContext->iDeviceHead, pContext->iDeviceTrail, &cSize)))
        return;

    BD_BUFFER *pBuffer = BufferAlloc (cSize);
    if (! pBuffer) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] SendCredits 0x%08x :  <out of memory>\n", pContext));
        return;
    }

    pBuffer->cStart = pContext->iDeviceHead;
    pBuffer->cEnd   = pBuffer->cSize - pContext->iDeviceTrail;

    SVSUTIL_ASSERT (BufferTotal (pBuffer) == 0);

    pContext->iGaveCredits += nCredits;

    HANDLE h = pContext->hRFCOMM;
    HANDLE hConn = pContext->hConnection;
    RFCOMM_DataDown_In pCallback = pContext->rfcomm_if.rfcomm_DataDown_In;

    gpPORTEMU->Unlock ();
    __try {
        pCallback (h, NULL, hConn, pBuffer, nCredits);
    } __except (1) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] SendCredits : exception in rfcomm_DataDown_In\n"));
    }

    gpPORTEMU->Lock ();
}

static int SendData (PORTEMU_CONTEXT *pContext) {
    if (pContext->fSending)     // Guard against packets coming out of order
        return ERROR_IO_PENDING;

    for ( ; ; ) {
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] SendData to 0x%08x (%04x%08x ch 0x%02)\n",
                                        pContext, pContext->b.NAP, pContext->b.SAP, pContext->channel));

        // Data queued, but can't send just yet
        if ((pContext->iSendQuotaUsed >= pContext->iSendQuota) || pContext->fc_aggregate ||
            ((! pContext->credit_fc) && pContext->fc) || (pContext->credit_fc && (pContext->iHaveCredits <= 0)) ||
            (! pContext->hConnection) || (! pContext->fConnected)) {
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] SendData 0x%08x : ERROR_IO_PENDING\n", pContext));
            return ERROR_IO_PENDING;
        }

        //      Compute buffer
        PendingIO *pIO = pContext->pops;
        UINT cTotalBytes = 0;
        int cGiveCredits = GiveCredits (pContext);

        while (pIO) {
            if (pIO->op == COMM_WRITE) {
                if ((cTotalBytes + (pIO->cbuffer - pIO->cbuffer_used)) >= (UINT) (pContext->iMTU - (cGiveCredits != 0))) {
                    if (FAILED(UIntSub(pContext->iMTU, (cGiveCredits != 0), &cTotalBytes))) {
                        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] SendData 0x%08x : Integer Overflow!\n", pContext));
                        cTotalBytes = 0;
                    }
                    break;
                } else {
                    if (FAILED(UIntAdd(cTotalBytes, (pIO->cbuffer - pIO->cbuffer_used), &cTotalBytes))) {
                        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] SendData 0x%08x : Integer Overflow!\n", pContext));
                        cTotalBytes = 0;
                        break;
                    }
                }
            }
            pIO = pIO->pNext;
        }

        if (cTotalBytes == 0) {
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] SendData 0x%08x : ERROR_SUCCESS <nothing left>\n", pContext));
            RegisterCommEvent(pContext, EV_TXEMPTY, 0);
            return ERROR_SUCCESS;
        }

        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] SendData to 0x%08x : %d bytes, %d credits\n", pContext, cTotalBytes, cGiveCredits));

        UINT cResult = 0;
        if (FAILED(CeUIntAdd3(cTotalBytes, pContext->iDeviceHead, pContext->iDeviceTrail, &cResult))) {
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] SendData 0x%08x : Integer overflow\n", pContext));
            return ERROR_ARITHMETIC_OVERFLOW;
        }

        BD_BUFFER *pBuffer = BufferAlloc (cResult);

        if (! pBuffer) {
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] SendData 0x%08x : ERROR_IO_PENDING <out of memory>\n", pContext));
            return ERROR_IO_PENDING;
        }

        ECall *pCall = NewCall (RFCOMM_WRITE, pContext);

        if (! pCall) {
            if (pBuffer->pFree)
                pBuffer->pFree (pBuffer);
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] SendData 0x%08x : ERROR_IO_PENDING <out of memory: call>\n", pContext));
            return ERROR_IO_PENDING;
        }

        pCall->pNext     = pContext->pCalls;
        pContext->pCalls = pCall;
        pCall->cBytes    = cTotalBytes;

        SVSUTIL_ASSERT (pCall->hCallContext != SVSUTIL_HANDLE_INVALID);

        pBuffer->cEnd = pBuffer->cSize - pContext->iDeviceTrail;
        pBuffer->cStart = pContext->iDeviceHead;

        SVSUTIL_ASSERT (BufferTotal (pBuffer) == cTotalBytes);

        // Accumulate buffer
        pIO = pContext->pops;
        PendingIO *pParent = NULL;

        UINT cHaveBytes = 0;

        PREFAST_SUPPRESS (12008, "cTotalBytes overflow check IS done above");
        while (pIO && (cHaveBytes < cTotalBytes)) {
            if (pIO->op == COMM_WRITE) {
                UINT cThisChunk = cTotalBytes - cHaveBytes;
                if (cThisChunk > (UINT) (pIO->cbuffer - pIO->cbuffer_used))
                    cThisChunk = pIO->cbuffer - pIO->cbuffer_used;

                CeSafeCopyMemory (pBuffer->pBuffer + pContext->iDeviceHead + cHaveBytes, pIO->buffer + pIO->cbuffer_used, cThisChunk);

                cHaveBytes += cThisChunk;
                pIO->cbuffer_used += cThisChunk;

                if (pIO->cbuffer_used == pIO->cbuffer) {    // Retire the buffer
                    PendingIO *pNext = pIO->pNext;
                    pIO->iIoResult = ERROR_SUCCESS;
                    pIO->pNext = NULL;
                    SetEvent (pIO->hEvent);

                    if (pParent)
                        pParent->pNext = pNext;
                    else
                        pContext->pops = pNext;

                    pIO = pNext;
                    continue;
                }

                SVSUTIL_ASSERT (cHaveBytes == cTotalBytes);
                break;
            }
            pParent = pIO;
            pIO = pIO->pNext;
        }

        SVSUTIL_ASSERT (cHaveBytes == cTotalBytes);
        pContext->iSendQuotaUsed += cTotalBytes;

        if (pContext->credit_fc) {
            --pContext->iHaveCredits;
            pContext->iGaveCredits += cGiveCredits;
        }

        SVSUTIL_ASSERT (pContext->iHaveCredits >= 0);

        //      Send the buffer down...

        HANDLE h = pContext->hRFCOMM;
        SVSHandle hContext = pCall->hCallContext;
        HANDLE hConn = pContext->hConnection;
        RFCOMM_DataDown_In pCallback = pContext->rfcomm_if.rfcomm_DataDown_In;

        pContext->fSending = TRUE;

        gpPORTEMU->Unlock ();
        int iRes = ERROR_INTERNAL_ERROR;
        __try {
            iRes = pCallback (h, (LPVOID)hContext, hConn, pBuffer, cGiveCredits);
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] SendData : exception in rfcomm_DataDown_In\n"));
        }

        gpPORTEMU->Lock ();

        if (! (gpPORTEMU->fInitialized &&
            (pContext == GetContext ((DWORD)pContext)) &&
            (pContext->hConnection == hConn))) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] Going through (connection?) shutdown in the middle of send...\n"));
            return ERROR_OPERATION_ABORTED;
        }

        pContext->fSending = FALSE;

        if (iRes != ERROR_SUCCESS) {    // Close the connection
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] SendData : rfcomm_DataDown_In returned %d\n", iRes));

            RegisterCommEvent (pContext, EV_ERR, CE_FRAME);
            CloseConnection (pContext, iRes, TRUE);
            return iRes;
        }
    }
}

static int SendMSC (PORTEMU_CONTEXT *pContext) {
    HANDLE h = pContext->hRFCOMM;
    HANDLE hConnection = pContext->hConnection;
    RFCOMM_MSC_In pCallback = pContext->rfcomm_if.rfcomm_MSC_In;
    unsigned char v24 = EA_BIT | pContext->ucv24out;

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"SendMSC : (%s %s %s %s %s)\n",
                    v24 & MSC_DV_BIT ? L"MS_RLSD_ON" : L"",
                    v24 & MSC_IC_BIT ? L"MS_RING_ON" : L"",
                    v24 & MSC_RTR_BIT ? L"MS_CTS_ON" : L"",
                    v24 & MSC_RTC_BIT ? L"MS_DSR_ON" : L"",
                    v24 & MSC_FC_BIT ? L"Flow_Off" : L""));

    gpPORTEMU->Unlock ();

    __try {
        pCallback (h, NULL, hConnection, v24, 0xff);
    } __except (1) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] SendMSC Exception in rfcomm_MSC_In\n"));
    }
    gpPORTEMU->Lock ();

    return ERROR_SUCCESS;
}

static DWORD WINAPI AuthenticateThread (LPVOID pUserContext) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] AuthenticateThread 0x%08x\n", pUserContext));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] AuthenticateThread 0x%08x : not initialized!\n", pUserContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] AuthenticateThread 0x%08x: not initialized!\n", pUserContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = GetContext ((DWORD)pUserContext);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] AuthenticateThread 0x%08x : context not found\n", pUserContext));
        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    int fAuthenticate  = pContext->freq_auth;
    int fEncrypt       = pContext->freq_encrypt;
    BT_ADDR bt     = SET_NAP_SAP (pContext->b.NAP, pContext->b.SAP);
    HANDLE hConnection = pContext->hConnection;

    SVSUTIL_ASSERT (fAuthenticate || fEncrypt);
    SVSUTIL_ASSERT (hConnection);

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] AuthenticateThread 0x%08x : doing authentication/encryption\n", pUserContext));

    gpPORTEMU->Unlock ();

    int iRes = ERROR_SUCCESS;

    if (fAuthenticate)
        iRes = BthAuthenticate (&bt);

    if ((iRes == ERROR_SUCCESS) && fEncrypt)
        iRes = BthSetEncryption (&bt, TRUE);

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] AuthenticateThread 0x%08x : RE_ENTERING THE LOCK\n", pUserContext));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] AuthenticateThread 0x%08x : not initialized!\n", pUserContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] AuthenticateThread 0x%08x: not initialized!\n", pUserContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    pContext = GetContext ((DWORD)pUserContext);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] AuthenticateThread 0x%08x : context not found\n", pUserContext));
        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    int fAllow = FALSE;

    if ((iRes == ERROR_SUCCESS) && pContext->fLocal && pContext->fLocalOpen && pContext->hConnection) {
        fAllow = TRUE;
        RegisterCommEvent (pContext, EV_RLSD, 0);
    } else
        ReinitClosedConnection (pContext);

    HANDLE hRFCOMM = pContext->hRFCOMM;
    RFCOMM_ConnectResponse_In pCallback = pContext->rfcomm_if.rfcomm_ConnectResponse_In;

    gpPORTEMU->Unlock ();

    __try {
        iRes = pCallback (hRFCOMM, NULL, hConnection, fAllow);
    } __except (1) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] AuthenticateThread 0x%08x : exception in rfcomm_ConnectResponse_In\n", pUserContext));
    }

    if (fAllow && (iRes == ERROR_SUCCESS) && gpPORTEMU) {
        gpPORTEMU->Lock ();
        if ((pContext == GetContext ((DWORD)pUserContext)) && pContext->hConnection) {
            pContext->ucv24out &= ~MSC_FC_BIT;
            SendMSC (pContext);
        }

        gpPORTEMU->Unlock ();
    }

    return iRes;

}

static void PORTEMU_ProcessExited (HANDLE hProc) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] PORTEMU_ProcessExited 0x%08x\n", hProc));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] PORTEMU_ProcessExited 0x%08x : not initialized!\n", hProc));
        return;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] PORTEMU_ProcessExited 0x%08x: not initialized!\n", hProc));
        gpPORTEMU->Unlock ();
        return;
    }

    while (gpPORTEMU->fInitialized) {
        PORTEMU_CONTEXT *pContext = gpPORTEMU->pPorts;
        int iPort = 0;
        while (pContext) {
            for (iPort = 0 ; iPort < PORTEMU_OPEN_MAX ; ++iPort) {
                if (pContext->hOwnerProc[iPort] == hProc)
                    break;
            }

            if (iPort < PORTEMU_OPEN_MAX)
                break;

            pContext = pContext->pNext;
        }

        if (! pContext)
            break;

        SVSUTIL_ASSERT (iPort < PORTEMU_OPEN_MAX);

        pContext->hOwnerProc[iPort] = NULL;

        gpPORTEMU->AddRef ();
        gpPORTEMU->Unlock ();
        COM_Close (((DWORD)pContext) | iPort);
        gpPORTEMU->Lock ();
        gpPORTEMU->DelRef ();
    }

    gpPORTEMU->Unlock ();
}

static DWORD WINAPI StackDisconnect (LPVOID pArg) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"Disconnect stack\n"));
    rfcomm_CloseDriverInstance ();
    return ERROR_SUCCESS;
}

static DWORD WINAPI StackDown (LPVOID pArg) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"stack down\n"));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] StackDown 0x%08x : not initialized!\n", pArg));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] StackDown 0x%08x: not initialized!\n", pArg));
        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = GetContext ((DWORD)pArg);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] StackDown 0x%08x : context not found\n", pArg));
        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    if (pContext->hConnection) {
        pContext->hConnection  = NULL;
        pContext->fc_aggregate = 0;

        CloseConnection (pContext, ERROR_GRACEFUL_DISCONNECT, TRUE);
    }
    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

void SDPAbortCall(HANDLE h, LPVOID pCallContext) {
    DebugOut(DEBUG_RFCOMM_TRACE,L"SDPAbortCall (0x%08x)\r\n",pCallContext);

    gpPORTEMU->Lock ();
    BT_LAYER_ABORT_CALL  pCallback = gpPORTEMU->sdp_if.sdp_AbortCall;
    gpPORTEMU->Unlock ();

    __try {
        pCallback(h,pCallContext);
    }
    __except (1) {
        ;
    }
}


static int rfcommsdp_disconnect_out(void *pCallContext, unsigned long status) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"+rfcommsdp_disconnect_out\n"));
    return ERROR_SUCCESS;
}

// Note: Param pCallContext is a pointer to the translated call context.  In other
// words, btutil_TranslateHandle must already have been called.
static void rfcommsdp_disconnect (void *pCallContext, int iErr) {
    SVSUTIL_ASSERT(gpPORTEMU && gpPORTEMU->IsLocked());

    ECall *pCall = FindCall (pCallContext);

    if (! pCall) {
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] rfcommsdp_disconnect 0x%08x : context does not exist\n", pCallContext));
        return;
    }

    PORTEMU_CONTEXT *pContext = pCall->pContext;

    DeleteCall (pCall);

    PendingIO *pParent = NULL;
    PendingIO *pIO = pContext->pops;

    while (pIO) {
        if (pIO->op == COMM_OPEN) {
            PendingIO *pNext = pIO->pNext;

            pIO->pNext     = NULL;
            pIO->dwEvent   = 0;
            pIO->iIoResult = iErr;

            SetEvent (pIO->hEvent);

            if (pParent)
                pParent->pNext = pNext;
            else
                pContext->pops = pNext;
            break;
        }

        pParent = pIO;
        pIO = pIO->pNext;
    }

    SVSUTIL_ASSERT (pContext->pops == NULL);

    if (pContext->sdpCid) {
        HANDLE h = gpPORTEMU->hSDP;
        SDP_Disconnect_In  pCallback = gpPORTEMU->sdp_if.sdp_Disconnect_In;
        unsigned short cid = pContext->sdpCid;

        pContext->sdpCid = 0;

        SVSUTIL_ASSERT(h && pCallback && cid);

        gpPORTEMU->AddRef ();
        gpPORTEMU->Unlock ();

        __try {
            pCallback (h,NULL,cid);
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[RFCOMM] : exception in sdp_disconnect_in!\n"));
        }

        gpPORTEMU->Lock ();
        gpPORTEMU->DelRef ();
    }
}

static int rfcommsdp_connect_out (void *pCallContext, unsigned long status, unsigned short cid) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"+rfcommsdp_connect_out \n"));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] rfcommsdp_connect_out 0x%08x : not initialized!\n", pCallContext));
        return ERROR_SUCCESS;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] rfcommsdp_connect_out 0x%08x: not initialized!\n", pCallContext));
        gpPORTEMU->Unlock ();
        return ERROR_SUCCESS;
    }

    ECall *pCall = (ECall*) btutil_TranslateHandle ((SVSHandle)pCallContext);
    if (! pCall) {
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] rfcommsdp_connect_out 0x%08x : context does not exist\n", pCallContext));
        gpPORTEMU->Unlock ();
        return ERROR_SUCCESS;
    }

    SVSUTIL_ASSERT (FindCall (pCall));

    PORTEMU_CONTEXT *pContext = pCall->pContext;

    SVSUTIL_ASSERT (pContext->channel == 0);
    SVSUTIL_ASSERT (pContext->sdpCid == 0);

    SdpQueryUuid          uuid[2];
    SdpAttributeRange         attribRange;

    int iErr = ERROR_PORT_UNREACHABLE;

    if (status == ERROR_SUCCESS) {
        SVSUTIL_ASSERT(cid);
        pContext->sdpCid = cid;

        // Call ServiceAttribSearch to get protocol headers.
        SDP_ServiceAttributeSearch_In pCallback = gpPORTEMU->sdp_if.sdp_ServiceAttributeSearch_In;
        HANDLE h = gpPORTEMU->hSDP;

        SVSUTIL_ASSERT(h && pCallback);

        uuid[0].uuidType = SDP_ST_UUID128;
        memcpy(&uuid[0].u.uuid128, &pContext->uuidService, sizeof(GUID));
        memset(&uuid[1],0,sizeof(uuid[1]));

        attribRange.minAttribute = attribRange.maxAttribute = SDP_ATTRIB_PROTOCOL_DESCRIPTOR_LIST;

        gpPORTEMU->AddRef ();
        gpPORTEMU->Unlock ();

        iErr = ERROR_INTERNAL_ERROR;

        __try {
            iErr = pCallback (h, pCallContext, cid, uuid, &attribRange,1);
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[RFCOMM] : exception in sdp_ServiceAttributeSearch_In!\n"));
        }

        gpPORTEMU->Lock ();
        gpPORTEMU->DelRef ();

        IFDBG(DebugOut(DEBUG_ERROR,L"rfcommsdp_connect_out: returns 0x%08x\r\n",iErr));
    }

    SVSUTIL_ASSERT(gpPORTEMU->IsLocked());

    if (iErr != ERROR_SUCCESS)
        rfcommsdp_disconnect (pCall, iErr);

    gpPORTEMU->Unlock ();
    return ERROR_SUCCESS;
}

static int rfcommsdp_service_attribute_search_out(void *pCallContext, unsigned long status, unsigned char *pOutBuf, unsigned long cOutBuf) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"+rfcommsdp_service_attribute_search_out\n"));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] rfcommsdp_service_attribute_search_out 0x%08x : not initialized!\n", pCallContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] rfcommsdp_service_attribute_search_out 0x%08x: not initialized!\n", pCallContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    ECall *pCall = (ECall*) btutil_TranslateHandle ((SVSHandle)pCallContext);
    if (! pCall) {
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] rfcommsdp_service_attribute_search_out 0x%08x : context does not exist\n", pCallContext));
        gpPORTEMU->Unlock ();
        return ERROR_SUCCESS;
    }

    SVSUTIL_ASSERT (FindCall (pCall));

    PORTEMU_CONTEXT *pContext = pCall->pContext;

    SVSUTIL_ASSERT (pContext->channel == 0);
    SVSUTIL_ASSERT (pContext->sdpCid != 0);

    if ((status == ERROR_SUCCESS) && ((! pOutBuf) || (cOutBuf == 0)))
        status = ERROR_INVALID_DATA;

    unsigned long ch = 0;
    if (status == ERROR_SUCCESS)
        status = GetProtocolCidFromResponse (pOutBuf, cOutBuf, &RFCOMM_PROTOCOL_UUID, &ch);

    if (status == ERROR_SUCCESS)
        pContext->channel = ch;

    IFDBG(DebugOut(DEBUG_RFCOMM_TRACE,L"rfcommsdp_service_attribute_search_out returns %d channel=%d\r\n", status, pContext->channel));

    if (pOutBuf)
        g_funcFree(pOutBuf,g_pvAllocData);

    rfcommsdp_disconnect(pCall, status);

    gpPORTEMU->Unlock();

    return ERROR_SUCCESS;
}

static int portemu_connect_request_ind  (void *pUserContext, HANDLE hConnection, BD_ADDR *pba, unsigned char channel) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_connect_request_ind 0x%08x\n", pUserContext));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_connect_request_ind 0x%08x : not initialized!\n", pUserContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_connect_request_ind 0x%08x: not initialized!\n", pUserContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = GetContext ((DWORD)pUserContext);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_connect_request_ind 0x%08x : context not found\n", pUserContext));
        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    int fAllow = FALSE;

    HANDLE hRFCOMM = pContext->hRFCOMM;
    RFCOMM_ConnectResponse_In pCallback = pContext->rfcomm_if.rfcomm_ConnectResponse_In;

    if ((pContext->channel == channel) && pContext->fLocal && pContext->fLocalOpen && !pContext->fConnected) {
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"Got active connection from %04x%08x (%d)\n", pba->NAP, pba->SAP, channel));

        if (pContext->freq_auth || pContext->freq_encrypt) {
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"Requesting authentication for connection from %04x%08x (%d)\n", pba->NAP, pba->SAP, channel));
            SVSCookie cookie = btutil_ScheduleEvent (AuthenticateThread, pContext);
            if (cookie) {
                pContext->b = *pba;
                ReinitNewConnection (pContext, hConnection);

                gpPORTEMU->Unlock ();
                return ERROR_SUCCESS;
            }

            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_connect_request_ind 0x%08x : could not create authentication thread\n", pUserContext));
        } else {
            fAllow = TRUE;

            pContext->b = *pba;

            ReinitNewConnection (pContext, hConnection);

            RegisterCommEvent (pContext, EV_RLSD, 0);
        }
    }

    gpPORTEMU->Unlock ();

    int iRes = ERROR_INTERNAL_ERROR;
    __try {
        iRes = pCallback (hRFCOMM, NULL, hConnection, fAllow);
    } __except (1) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_connect_request_ind 0x%08x : exception in rfcomm_ConnectResponse_In\n", pUserContext));
    }

    if (fAllow && (iRes == ERROR_SUCCESS) && gpPORTEMU) {
        gpPORTEMU->Lock ();
        if ((pContext == GetContext ((DWORD)pUserContext)) && pContext->hConnection) {
            pContext->ucv24out &= ~MSC_FC_BIT;
            SendMSC (pContext);
        }

        gpPORTEMU->Unlock ();
    }

    return iRes;
}

static int portemu_data_up_ind (void *pUserContext, HANDLE hConnection, BD_BUFFER *pb, int cAdditionalCredits) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_data_up_ind 0x%08x, new credits = %d\n", pUserContext, cAdditionalCredits));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_data_up_ind 0x%08x : not initialized!\n", pUserContext));
        return 0;
    }

    gpPORTEMU->Lock ();

    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_data_up_ind 0x%08x: not initialized!\n", pUserContext));

        if (pb->pFree)
            pb->pFree (pb);

        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = GetContext ((DWORD)pUserContext);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_data_up_ind 0x%08x : context not found\n", pUserContext));

        if (pb->pFree)
            pb->pFree (pb);

        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    //      First see if we have consumers
    PendingIO *pParent = NULL;
    PendingIO *pIO = pContext->pops;

    int cHaveData = BufferTotal (pb);

    if ((cHaveData > 0) && pContext->credit_fc)
        --pContext->iGaveCredits;

    while (pIO && (cHaveData > 0)) {
        if (pIO->op == COMM_READ) {
            SVSUTIL_ASSERT (! pContext->pbl);       // else why is the request here???

            int cBytes = cHaveData > (pIO->cbuffer - pIO->cbuffer_used) ? (pIO->cbuffer - pIO->cbuffer_used) : cHaveData;
            BOOL fRet;
            __try {
                fRet = BufferGetChunk (pb, cBytes, pIO->buffer + pIO->cbuffer_used);
            } __except (1) {
                IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_data_up_ind 0x%08x : Exception in call to BufferGetChunk\n", pUserContext));
                fRet = FALSE;
            }

            if (fRet) {
                cHaveData -= cBytes;
                SVSUTIL_ASSERT (cHaveData == BufferTotal (pb));

                pIO->cbuffer_used += cBytes;

                IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_data_up_ind 0x%08x : sat. read (%d bytes)\n", pUserContext, cBytes));
            }

            if (!fRet || (pIO->cbuffer_used == pIO->cbuffer)) {
                IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_data_up_ind 0x%08x : retired read event\n", pUserContext, cBytes));
                PendingIO *pNext = pIO->pNext;

                pIO->pNext     = NULL;
                pIO->iIoResult = fRet ? ERROR_SUCCESS : ERROR_INVALID_USER_BUFFER;
                SetEvent (pIO->hEvent);

                if (pParent)
                    pParent->pNext = pNext;
                else
                    pContext->pops = pNext;

                pIO = pNext;
            } else {
                SVSUTIL_ASSERT (cHaveData == 0);
                break;
            }
        } else {
            pParent = pIO;
            pIO = pIO->pNext;
        }
    }

    SVSUTIL_ASSERT (cHaveData == BufferTotal (pb));

    if ((cHaveData + pContext->iRecvQuotaUsed) > GetQuota(pContext)) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] OUT OF quota receiving the buffer!\n"));
        cHaveData = 0;

        RegisterCommEvent (pContext, EV_ERR, CE_OVERRUN);
    }

    if (cHaveData > 0) {    // Queue the remainder
        if (pb->fMustCopy)
            pb = BufferCompress (pb);       // Note: this is somewhat hackish, but it does not leak. Mental exercise: why :-)?

        BD_BUFFER_LIST *pbl = pb ? new BD_BUFFER_LIST : NULL;

        if (pbl) {      // decrement quota and store the data
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_data_up_ind 0x%08x : queueing %d bytes\n", pUserContext, cHaveData));

            pbl->pNext = NULL;
            pbl->pb = pb;
            if (pContext->pbl) {
                BD_BUFFER_LIST *pP = pContext->pbl;
                while (pP->pNext)
                    pP = pP->pNext;

                pP->pNext = pbl;
            } else
                pContext->pbl = pbl;

            pContext->iRecvQuotaUsed += cHaveData;

            RegisterCommEvent (pContext, EV_RXCHAR, 0);
        } else {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] OUTOFMEMORY queueing the buffer!\n"));
            cHaveData = 0;
        }
    }

    if (cHaveData == 0) {   // Buffer all read or of no use anyway...
        if (pb && pb->pFree)
            pb->pFree (pb);
    }

    if ((! pContext->credit_fc) && (! pContext->fSentXon) && (pContext->iRecvQuotaUsed > pContext->dcb.XonLim)) {
        pContext->fSentXon = TRUE;
        pContext->ucv24out |= MSC_FC_BIT;
        SendMSC (pContext);
    } else if (pContext->credit_fc) {
        pContext->iHaveCredits += cAdditionalCredits;
        if (pContext->iHaveCredits - cAdditionalCredits <= 0)
            SendData (pContext);

        if (gpPORTEMU->fInitialized && (pContext == GetContext ((DWORD)pUserContext)))
            SendCredits (pContext);
    }

    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int portemu_disconnect_ind (void *pUserContext, HANDLE hConnection) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_disconnect_ind 0x%08x\n", pUserContext));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_disconnect_ind 0x%08x : not initialized!\n", pUserContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_disconnect_ind 0x%08x: not initialized!\n", pUserContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = GetContext ((DWORD)pUserContext);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_disconnect_ind 0x%08x : context not found\n", pUserContext));
        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    pContext->hConnection = NULL;
    CloseConnection (pContext, ERROR_GRACEFUL_DISCONNECT, FALSE);

    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int portemu_pnreq_ind (void *pUserContext, HANDLE hConnection, unsigned char priority, unsigned short n1, int use_credit_fc, int initial_credits) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_pnreq_ind 0x%08x hconn = 0x%08x, pri = %d, mtu = %d, credit = %x, initial credtis = %d\n", pUserContext, hConnection, priority, n1, use_credit_fc, initial_credits));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_pnreq_ind 0x%08x : not initialized!\n", pUserContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_pnreq_ind 0x%08x: not initialized!\n", pUserContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = GetContext ((DWORD)pUserContext);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_pnreq_ind 0x%08x : context not found\n", pUserContext));
        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    //
    // Store mtu & f/c params in the context.
    //
    // But do not store the hConnection variable here.  We don't want to do this because if
    // the connection is disconnected before we get a connect_ind we will not clean up the
    // invalid handle.
    //

    if (n1 > pContext->iMaxMTU)
        n1 = pContext->iMaxMTU;
    if (n1 < pContext->iMinMTU)
        n1 = pContext->iMinMTU;

    pContext->iMTU = n1;

    if (use_credit_fc == RFCOMM_PN_CREDIT_IN) {
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_pnreq_ind 0x%08x CREDIT-based Flow ON\n", pUserContext));

        pContext->credit_fc     = TRUE;
        pContext->iHaveCredits  = initial_credits;
        pContext->iGaveCredits  = RFCOMM_PN_CREDIT_MAX;
    } else {
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_pnreq_ind 0x%08x CREDIT-based Flow OFF\n", pUserContext));

        pContext->credit_fc     = FALSE;
        pContext->iHaveCredits  = 0;
        pContext->iGaveCredits  = 0;
    }

    HANDLE h = pContext->hRFCOMM;
    RFCOMM_PNRSP_In pCallback = pContext->rfcomm_if.rfcomm_PNRSP_In;

    gpPORTEMU->Unlock ();

    int iRes = ERROR_INTERNAL_ERROR;
    __try {
        iRes = pCallback (h, NULL, hConnection, priority, n1,
                                    (use_credit_fc == RFCOMM_PN_CREDIT_IN) ? RFCOMM_PN_CREDIT_OUT : 0,
                                    (use_credit_fc == RFCOMM_PN_CREDIT_IN) ? RFCOMM_PN_CREDIT_MAX : 0);
    } __except (1) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_pnreq_ind Exception in rfcomm_PNRSP_In\n"));
    }

#if defined (DEBUG) || defined (_DEBUG) || defined (RETAILLOG)
    if (iRes != ERROR_SUCCESS)
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_pnreq_ind rfcomm_PNRSP_In returns %d\n", iRes));
#endif

    return ERROR_SUCCESS;
}

static int portemu_rpnreq_ind (void *pUserContext, HANDLE hConnection, unsigned short mask, int baud, int data, int stop, int parity, int parity_type, unsigned char flow, unsigned char xon, unsigned char xoff) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_rpnreq_ind 0x%08x hconn = 0x%08x\n", pUserContext, hConnection));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_rpnreq_ind 0x%08x : not initialized!\n", pUserContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_rpnreq_ind 0x%08x: not initialized!\n", pUserContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = GetContext ((DWORD)pUserContext);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_rpnreq_ind 0x%08x : context not found\n", pUserContext));
        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    if (mask & RFCOMM_RPN_HAVE_BAUD)
        pContext->dcb.BaudRate = baud;

    if (mask & RFCOMM_RPN_HAVE_DATA)
        pContext->dcb.ByteSize = data;

    if (mask & RFCOMM_RPN_HAVE_STOP)
        pContext->dcb.StopBits = stop;

    if (mask & RFCOMM_RPN_HAVE_PARITY)
        pContext->dcb.fParity = parity;

    if (mask & RFCOMM_RPN_HAVE_PT)
        pContext->dcb.Parity = parity_type;

    if (mask & RFCOMM_RPN_HAVE_XOFF)
        pContext->dcb.XoffChar = xoff;

    if (mask & RFCOMM_RPN_HAVE_XON)
        pContext->dcb.XonChar = xon;

    if (mask & RFCOMM_RPN_HAVE_XON_IN)
        pContext->dcb.fInX = flow & RFCOMM_RPN_XON_IN ? TRUE : FALSE;

    if (mask & RFCOMM_RPN_HAVE_XON_OUT)
        pContext->dcb.fOutX = flow & RFCOMM_RPN_XON_OUT ? TRUE : FALSE;

    if (! mask) {
        mask = RFCOMM_RPN_HAVE_BAUD | RFCOMM_RPN_HAVE_DATA | RFCOMM_RPN_HAVE_STOP |
            RFCOMM_RPN_HAVE_PARITY | RFCOMM_RPN_HAVE_PT;
        baud = pContext->dcb.BaudRate;
        data = pContext->dcb.ByteSize;
        stop = pContext->dcb.StopBits;
        parity = pContext->dcb.fParity;
        parity_type = pContext->dcb.Parity;
        xoff = 0;
        xon = 0;
        flow = 0;
    } else
        RegisterCommEvent (pContext, EV_EVENT1, 0);

    HANDLE h = pContext->hRFCOMM;
    RFCOMM_RPNRSP_In pCallback = pContext->rfcomm_if.rfcomm_RPNRSP_In;

    gpPORTEMU->Unlock ();

    int iRes = ERROR_INTERNAL_ERROR;
    __try {
        iRes = pCallback (h, NULL, hConnection, mask, baud, data, stop, parity, parity_type, flow, xon, xoff);
    } __except (1) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_rpnreq_ind Exception in rfcomm_PNRSP_In\n"));
    }

#if defined (DEBUG) || defined (_DEBUG) || defined (RETAILLOG)
    if (iRes != ERROR_SUCCESS)
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_rpnreq_ind rfcomm_PNRSP_In returns %d\n", iRes));
#endif

    return ERROR_SUCCESS;
}

static int portemu_fc_ind (void *pUserContext, HANDLE hConnect, unsigned char fcOn) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_fc_ind 0x%08x %s\n", pUserContext, fcOn ? L"on" : L"off"));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_fc_ind 0x%08x : not initialized!\n", pUserContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_fc_ind 0x%08x: not initialized!\n", pUserContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = GetContext ((DWORD)pUserContext);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_fc_ind 0x%08x : context not found\n", pUserContext));
        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    if (fcOn) {
        pContext->fc_aggregate = FALSE;
        SendData (pContext);
    } else
        pContext->fc_aggregate = TRUE;

    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int portemu_msc_ind (void *pUserContext, HANDLE hConnect, unsigned char v24, unsigned char bs) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_msc_ind 0x%08x 0x%02x 0x%02x\n", pUserContext, v24, bs));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_msc_ind 0x%08x : not initialized!\n", pUserContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_msc_ind 0x%08x: not initialized!\n", pUserContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = GetContext ((DWORD)pUserContext);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_msc_ind 0x%08x : context not found\n", pUserContext));
        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    if (pContext->fkeep_dcd)
        v24 |= MSC_DV_BIT;

    DWORD dwOldLine = pContext->dwModemStatusIn;

    if (v24 & MSC_RTC_BIT) // RTC == DSR
        pContext->dwModemStatusIn |= MS_DSR_ON;
    else
        pContext->dwModemStatusIn &= ~MS_DSR_ON;

    if (v24 & MSC_RTR_BIT) // RTR == CTS
        pContext->dwModemStatusIn |= MS_CTS_ON;
    else
        pContext->dwModemStatusIn &= ~MS_CTS_ON;

    if (v24 & MSC_IC_BIT)
        pContext->dwModemStatusIn |= MS_RING_ON;
    else
        pContext->dwModemStatusIn &= ~MS_RING_ON;

    if (v24 & MSC_DV_BIT) // DV = DCD
        pContext->dwModemStatusIn |= MS_RLSD_ON;
    else
        pContext->dwModemStatusIn &= ~MS_RLSD_ON;

    dwOldLine ^= pContext->dwModemStatusIn;

    if (v24 & MSC_FC_BIT)
        pContext->fc = TRUE;
    else {
        pContext->fc = FALSE;
        SendData (pContext);
    }

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"MSC Indicated : %s %s %s %s\n",
            dwOldLine & MS_RING_ON ? L"EV_RING" : L"",
            dwOldLine & MS_DSR_ON ? L"EV_DSR" : L"",
            dwOldLine & MS_CTS_ON ? L"EV_CTS" : L"",
            dwOldLine & MS_RLSD_ON ? L"EV_RLSD" : L"",
            v24 & MSC_FC_BIT ? L"Flow_Off" : L""));

    // Signal status change
    if (dwOldLine && gpPORTEMU->fInitialized && (pContext == GetContext ((DWORD)pUserContext))) {
        DWORD dwEvent = 0;
        if (dwOldLine & MS_RING_ON)
            dwEvent = EV_RING;
        if (dwOldLine & MS_DSR_ON)
            dwEvent |= EV_DSR;
        if (dwOldLine & MS_CTS_ON)
            dwEvent |= EV_CTS;
        if (dwOldLine & MS_RLSD_ON)
            dwEvent |= EV_RLSD;

        RegisterCommEvent (pContext, dwEvent, 0);
    }

    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int portemu_stack_event (void *pUserContext, int iEvent, void *pEventContext) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_stack_event 0x%08x\n", pUserContext));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_stack_event 0x%08x : not initialized!\n", pUserContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_stack_event 0x%08x: not initialized!\n", pUserContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = GetContext ((DWORD)pUserContext);

    if (! pContext) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_stack_event 0x%08x : context not found\n", pUserContext));
        gpPORTEMU->Unlock ();

        return ERROR_NOT_FOUND;
    }

    if (iEvent == BTH_STACK_DOWN)
        btutil_ScheduleEvent (StackDown, pContext);
    else if (iEvent == BTH_STACK_DISCONNECT)
        btutil_ScheduleEvent (StackDisconnect, NULL);

    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int portemu_call_aborted (void *pCallContext, int iError) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_call_aborted 0x%08x\n", pCallContext));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_call_aborted 0x%08x : not initialized!\n", pCallContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_call_aborted 0x%08x: not initialized!\n", pCallContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    ECall *pCall = (ECall*) btutil_TranslateHandle ((SVSHandle)pCallContext);
    if (! pCall) {
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] portemu_call_aborted 0x%08x : context does not exist\n", pCallContext));
        gpPORTEMU->Unlock ();
        return ERROR_SUCCESS;
    }

    SVSUTIL_ASSERT (FindCall (pCall));

    PORTEMU_CONTEXT *pContext = pCall->pContext;
    BOOL fSendDisconnect = (pCall->fWhat != RFCOMM_DISC);
    DeleteCall (pCall);

    CloseConnection (pContext, iError, fSendDisconnect);
    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int portemu_disconnect_out (void *pCallContext, int iError) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_disconnect_out 0x%08x\n", pCallContext));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_disconnect_out 0x%08x : not initialized!\n", pCallContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_disconnect_out 0x%08x: not initialized!\n", pCallContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    ECall *pCall = (ECall*) btutil_TranslateHandle ((SVSHandle)pCallContext);
    if (! pCall) {
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] portemu_disconnect_out 0x%08x : context does not exist\n", pCallContext));
        gpPORTEMU->Unlock ();
        return ERROR_SUCCESS;
    }

    SVSUTIL_ASSERT (FindCall (pCall));

    PORTEMU_CONTEXT *pContext = pCall->pContext;
    DeleteCall (pCall);

    CloseConnection (pContext, iError, FALSE);
    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int portemu_connect_request_out (void *pCallContext, int iError, HANDLE hConnection) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_connect_request_out 0x%08x\n", pCallContext));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_connect_request_out 0x%08x : not initialized!\n", pCallContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_connect_request_out 0x%08x: not initialized!\n", pCallContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    ECall *pCall = (ECall*) btutil_TranslateHandle ((SVSHandle)pCallContext);
    if (! pCall) {
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] portemu_connect_request_out 0x%08x : context does not exist\n", pCallContext));
        gpPORTEMU->Unlock ();
        return ERROR_SUCCESS;
    }

    SVSUTIL_ASSERT (FindCall (pCall));

    PORTEMU_CONTEXT *pContext = pCall->pContext;
    SVSUTIL_ASSERT (pCall->fWhat == RFCOMM_CONNECT);
    SVSUTIL_ASSERT (pContext->pops && (pContext->pops->op == COMM_OPEN) && (! pContext->pops->pNext));      // MUST have one connection request pops

    DeleteCall (pCall);

    if (iError != ERROR_SUCCESS)
        CloseConnection (pContext, iError, TRUE);
    else {
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_connect_request_out 0x%08x : connection established\n", pCallContext));

        ReinitNewConnection (pContext, hConnection);

        PendingIO *pIO = pContext->pops;
        pContext->pops = NULL;

        SVSUTIL_ASSERT (pIO->op == COMM_OPEN);
        SVSUTIL_ASSERT (pIO->pNext == NULL);
        SVSUTIL_ASSERT (pIO->buffer == NULL);
        SVSUTIL_ASSERT (pIO->cbuffer == 0);
        SVSUTIL_ASSERT (pIO->cbuffer_used == 0);

        pIO->iIoResult = ERROR_SUCCESS;
        SetEvent (pIO->hEvent);

        pContext->ucv24out &= ~MSC_FC_BIT;
        SendMSC (pContext);
    }

    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int portemu_data_down_out (void *pCallContext, int iError) {
    if (! pCallContext) {
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_data_down_out : ignored empty call!\n"));
        return 0;
    }

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_data_down_out 0x%08x\n", pCallContext));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_data_down_out 0x%08x : not initialized!\n", pCallContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_data_down_out 0x%08x: not initialized!\n", pCallContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    ECall *pCall = (ECall*) btutil_TranslateHandle ((SVSHandle)pCallContext);
    if (! pCall) {
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] portemu_data_down_out 0x%08x : context does not exist\n", pCallContext));
        gpPORTEMU->Unlock ();
        return ERROR_SUCCESS;
    }

    SVSUTIL_ASSERT (FindCall (pCall));

    PORTEMU_CONTEXT *pContext = pCall->pContext;
    int cb = pCall->cBytes;

    SVSUTIL_ASSERT (pCall->fWhat == RFCOMM_WRITE);

    DeleteCall (pCall);

    if (iError != ERROR_SUCCESS)
        CloseConnection (pContext, iError, TRUE);
    else {
        pContext->iSendQuotaUsed -= cb;
        if (pContext->iSendQuota < 0) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] quota miscalculated in portemu_data_down_out\n"));
            pContext->iSendQuota = 0;
        }

        SendData (pContext);
    }

    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int portemu_pnreq_out (void *pCallContext, int iError, unsigned char priority, unsigned short n1, int use_credit_fc, int initial_credits) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_pnreq_out 0x%08x mtu = %d credit fc = %x initial credits = %d\n", pCallContext, n1, use_credit_fc, initial_credits));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_pnreq_out 0x%08x : not initialized!\n", pCallContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_pnreq_out 0x%08x: not initialized!\n", pCallContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    ECall *pCall = (ECall*) btutil_TranslateHandle ((SVSHandle)pCallContext);
    if (! pCall) {
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] portemu_pnreq_out 0x%08x : context does not exist\n", pCallContext));
        gpPORTEMU->Unlock ();
        return ERROR_SUCCESS;
    }

    SVSUTIL_ASSERT (FindCall (pCall));

    PORTEMU_CONTEXT *pContext = pCall->pContext;
    SVSUTIL_ASSERT (pCall->fWhat == RFCOMM_PN);
    SVSUTIL_ASSERT (pContext->pops && (pContext->pops->op == COMM_OPEN) && (! pContext->pops->pNext));      // MUST have one connection request pops

    DeleteCall (pCall);

    if (use_credit_fc == RFCOMM_PN_CREDIT_OUT) {
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_pnreq_ind 0x%08x CREDIT-based Flow ON\n", pContext));

        pContext->credit_fc    = TRUE;
        pContext->iHaveCredits = initial_credits;
        pContext->iGaveCredits = RFCOMM_PN_CREDIT_MAX;
    } else {
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_pnreq_ind 0x%08x CREDIT-based Flow OFF\n", pContext));

        pContext->credit_fc    = FALSE;
        pContext->iHaveCredits = 0;
        pContext->iGaveCredits = 0;
    }

    if ((n1 >= pContext->iMinMTU) && (n1 <= pContext->iMaxMTU)) {
        pContext->iMTU = n1;
        pContext->pops->iIoResult = ERROR_SUCCESS;
    } else
        pContext->pops->iIoResult = ERROR_INVALID_DATA;

    SVSUTIL_ASSERT (pContext->pops->pNext == NULL);
    SVSUTIL_ASSERT (pContext->pops->op == COMM_OPEN);
    SetEvent (pContext->pops->hEvent);

    pContext->pops = NULL;

    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int portemu_rpnreq_out (void *pCallContext, int iError, unsigned short mask, int baud, int data, int stop, int parity, int parity_type, unsigned char flow, unsigned char xon, unsigned char xoff) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] portemu_rpnreq_out 0x%08x\n", pCallContext));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_rpnreq_out 0x%08x : not initialized!\n", pCallContext));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_rpnreq_out 0x%08x: not initialized!\n", pCallContext));
        gpPORTEMU->Unlock ();
        return 0;
    }

    ECall *pCall = (ECall*) btutil_TranslateHandle ((SVSHandle)pCallContext);
    if (! pCall) {
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] portemu_rpnreq_out 0x%08x : context does not exist\n", pCallContext));
        gpPORTEMU->Unlock ();
        return ERROR_SUCCESS;
    }

    SVSUTIL_ASSERT (FindCall (pCall));
    SVSUTIL_ASSERT (pCall->fWhat == RFCOMM_RPN);

    PORTEMU_CONTEXT *pContext = pCall->pContext;

    DeleteCall (pCall);

    if (mask & RFCOMM_RPN_HAVE_BAUD)
        pContext->dcb.BaudRate = baud;

    if (mask & RFCOMM_RPN_HAVE_DATA)
        pContext->dcb.ByteSize = data;

    if (mask & RFCOMM_RPN_HAVE_STOP)
        pContext->dcb.StopBits = stop;

    if (mask & RFCOMM_RPN_HAVE_PARITY)
        pContext->dcb.fParity = parity;

    if (mask & RFCOMM_RPN_HAVE_PT)
        pContext->dcb.Parity = parity_type;

    if (mask & RFCOMM_RPN_HAVE_XOFF)
        pContext->dcb.XoffChar = xoff;

    if (mask & RFCOMM_RPN_HAVE_XON)
        pContext->dcb.XonChar = xon;

    if (mask & RFCOMM_RPN_HAVE_XON_IN)
        pContext->dcb.fInX = flow & RFCOMM_RPN_XON_IN ? TRUE : FALSE;

    if (mask & RFCOMM_RPN_HAVE_XON_OUT)
        pContext->dcb.fOutX = flow & RFCOMM_RPN_XON_OUT ? TRUE : FALSE;

    //#error what's the deal with the rest of the flow???

    PendingIO *pio = pContext->pops;
    PendingIO *pParent = NULL;

    while (pio && (pio->op != COMM_RPN)) {
        pParent = pio;
        pio = pio->pNext;
    }

    if (pio) {
        if (pParent)
            pParent = pio->pNext;
        else
            pContext->pops = pio->pNext;

        pio->iIoResult = ERROR_SUCCESS;
        SetEvent (pio->hEvent);
    }

    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

static int PORTEMUSetPortParams
(
PORTEMU_CONTEXT     *pContext,
int                     channel,
int                     flocal,
BD_ADDR             *pdevice,
int                     imtu,
int                     iminmtu,
int                     imaxmtu,
int                     isendquota,
int                     irecvquota,
GUID                uuidService,
int                     uiportflags
) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] PORTEMUSetPortParams channel %d local: %s device %04x%08x mtu %d minmtu %d maxmtu %d Quotas: send %d recv %d\n",
        channel, flocal ? L"yes" : L"no", pdevice ? pdevice->NAP : 0, pdevice ? pdevice->SAP : 0, imtu, iminmtu, imaxmtu, isendquota, irecvquota));

    if (flocal && pdevice) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] channel can't be local and have destination! ERROR_INVALID_PARAMETER\n"));
        return ERROR_INVALID_PARAMETER;
    }

    if (((iminmtu != 0) && (iminmtu < PORTEMU_MTUMIN)) ||
         (imaxmtu > PORTEMU_MTUMAX) ||
         (imtu && ((imtu < PORTEMU_MTUMIN) || (imtu > PORTEMU_MTUMAX)))) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] mtu out of range ERROR_INVALID_PARAMETER\n"));
        return ERROR_INVALID_PARAMETER;
    }

    SVSUTIL_ASSERT (pContext->hConnection == NULL);
    SVSUTIL_ASSERT (pContext->hRFCOMM == NULL);

    RFCOMM_EVENT_INDICATION EventInd;
    memset (&EventInd, 0, sizeof(EventInd));

    EventInd.rfcomm_ConnectRequest_Ind      = portemu_connect_request_ind;
    EventInd.rfcomm_DataUp_Ind              = portemu_data_up_ind;
    EventInd.rfcomm_Disconnect_Ind      = portemu_disconnect_ind;
    EventInd.rfcomm_StackEvent              = portemu_stack_event;
    EventInd.rfcomm_PNREQ_Ind       = portemu_pnreq_ind;
    EventInd.rfcomm_FC_Ind          = portemu_fc_ind;
    EventInd.rfcomm_MSC_Ind         = portemu_msc_ind;
    EventInd.rfcomm_RPNREQ_Ind      = portemu_rpnreq_ind;

    RFCOMM_CALLBACKS Callbacks;
    memset (&Callbacks, 0, sizeof(Callbacks));

    Callbacks.rfcomm_CallAborted        = portemu_call_aborted;
    Callbacks.rfcomm_ConnectRequest_Out = portemu_connect_request_out;
    Callbacks.rfcomm_Disconnect_Out     = portemu_disconnect_out;
    Callbacks.rfcomm_DataDown_Out       = portemu_data_down_out;
    Callbacks.rfcomm_PNREQ_Out      = portemu_pnreq_out;
    Callbacks.rfcomm_RPNREQ_Out     = portemu_rpnreq_out;

    int iErr;

    if (ERROR_SUCCESS !=
        (iErr = RFCOMM_EstablishDeviceContext (pContext, flocal ? channel : RFCOMM_CHANNEL_CLIENT_ONLY,
                        &EventInd, &Callbacks, &pContext->rfcomm_if, &pContext->iDeviceHead,
                        &pContext->iDeviceTrail, &pContext->hRFCOMM))) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] PORTEMUSetPortParams : RFCOMM_EstablishDeviceContext returns %d\n", iErr));
        return iErr;
    }

    if (channel == RFCOMM_CHANNEL_MULTIPLE) {
        BT_LAYER_IO_CONTROL pCallback = pContext->rfcomm_if.rfcomm_ioctl;
        iErr = ERROR_INTERNAL_ERROR;
        unsigned char cPort = 0;
        int iSize = 0;
        __try {
            unsigned char cZero = 0;
            iErr = pCallback (pContext->hRFCOMM, BTH_STACK_IOCTL_RESERVE_PORT, sizeof(cZero), (char *)&cZero, sizeof(cPort), (char *)&cPort, &iSize);
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] PORTEMUSetPortParams : BTH_STACK_IOCTL_RESERVE_PORT excepted\n"));
        }

        if (iErr != ERROR_SUCCESS) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] PORTEMUSetPortParams : BTH_STACK_IOCTL_RESERVE_PORT returns %d : closing down\n", iErr));
            RFCOMM_CloseDeviceContext (pContext->hRFCOMM);
            pContext->hRFCOMM = NULL;
            return iErr;
        }

        channel = cPort;
    }

    pContext->iRecvQuota = irecvquota ? irecvquota : gpPORTEMU->iDefaultRecvQuota;
    pContext->iSendQuota = isendquota ? isendquota : gpPORTEMU->iDefaultSendQuota;

    pContext->iMTU = imtu ? imtu : PORTEMU_MTU_DESIRED;

    pContext->iMaxMTU = imaxmtu ? imaxmtu : gpPORTEMU->iDefaultMTUMax;
    pContext->iMinMTU = iminmtu ? iminmtu : gpPORTEMU->iDefaultMTUMin;

    pContext->channel = channel;
    pContext->uuidService = uuidService;
    if (pdevice)
        pContext->b = *pdevice;
    pContext->fLocal = flocal;
    if (uiportflags & RFCOMM_PORT_FLAGS_REMOTE_DCB)
        pContext->local_dcb = FALSE;
    else
        pContext->local_dcb = TRUE;

    if (uiportflags & RFCOMM_PORT_FLAGS_KEEP_DCD)
        pContext->fkeep_dcd = TRUE;
    else
        pContext->fkeep_dcd = FALSE;

    if (uiportflags & RFCOMM_PORT_FLAGS_AUTHENTICATE)
        pContext->freq_auth = TRUE;
    else
        pContext->freq_auth = FALSE;

    if (uiportflags & RFCOMM_PORT_FLAGS_ENCRYPT)
        pContext->freq_encrypt = TRUE;
    else
        pContext->freq_encrypt = FALSE;

    if (uiportflags & RFCOMM_PORT_FLAGS_SECURE_PRECHANNEL)
        pContext->fSecurePreChannel = TRUE;
    else 
        pContext->fSecurePreChannel = FALSE;

    return ERROR_SUCCESS;
}

extern "C" DWORD COM_Init (LPCTSTR pRegContext, LPCVOID lpvBusContext) {
    // If RegisterDevice is used the PORTEMUPortParams struct will be in pContext
    // and lpvBusContext is null.  Otherwise, we can expect the PORTEMUPortParams
    // struct to be in lpvBusContext.
    LPVOID lpvParams = (void *) (lpvBusContext ? lpvBusContext : pRegContext);

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Init 0x%08x\n", lpvParams));

    PORTEMUPortParams *pParams = NULL;
    PORTEMUPortParams pp;

    if (GetDirectCallerProcessId() == GetCurrentProcessId()) {
        // Caller is kernel mode.
        pParams = (PORTEMUPortParams *) lpvParams;
        if (! CeSafeCopyMemory(&pp, pParams, sizeof(PORTEMUPortParams))) {
            pParams = NULL;
        }
    } else {
        // Caller is user mode.  Marshal the buffer.
        MarshalledBuffer_t ParamsBuf;
        if (FAILED(ParamsBuf.Marshal((void *)lpvParams, sizeof(PORTEMUPortParams), ARG_I_PTR))) {
            SetLastError (ERROR_INVALID_PARAMETER);

            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Init 0x%08x : Error marshaling lpvParams\n", lpvParams));
            return FALSE;
        }
        
        pParams = (PORTEMUPortParams *) ParamsBuf.ptr();        
        if (! CeSafeCopyMemory(&pp, pParams, sizeof(PORTEMUPortParams))) {
            pParams = NULL;
        }

        ParamsBuf.Unmarshal();    
    }
    
    GUID uuidZero;
    memset(&uuidZero, 0, sizeof(GUID));
    if ((! pParams) || ((pp.iminmtu != 0) && (pp.iminmtu < PORTEMU_MTUMIN)) ||
         (pp.imaxmtu > PORTEMU_MTUMAX) ||
         (pp.imtu && ((pp.imtu < PORTEMU_MTUMIN) || (pp.imtu > PORTEMU_MTUMAX))) ||
         ((pp.channel == 0) && (!memcmp(&pp.uuidService, &uuidZero, sizeof(GUID))) ) ) {
        SetLastError (ERROR_INVALID_PARAMETER);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Init 0x%08x : ERROR_INVALID_PARAMETER\n", lpvParams));
        return FALSE;
    }

    if (! gpPORTEMU) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Init 0x%08x : not initialized!\n", lpvParams));
        return 0;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Init 0x%08x : not initialized!\n", lpvParams));
        gpPORTEMU->Unlock ();
        return 0;
    }

    PORTEMU_CONTEXT *pContext = NewPORTEMU_CONTEXT ();

    if (pContext) {
        int iRes = PORTEMUSetPortParams (pContext, pp.channel, pp.flocal, pp.flocal ? NULL : (BD_ADDR *)&pp.device, pp.imtu, pp.iminmtu, pp.imaxmtu, pp.isendquota, pp.irecvquota, pp.uuidService, pp.uiportflags);
        if (iRes == ERROR_SUCCESS) {
            pContext->pNext = gpPORTEMU->pPorts;
            gpPORTEMU->pPorts = pContext;
        } else {
            SetLastError (iRes);
            delete pContext;
            pContext = NULL;
        }
    } else
        SetLastError (ERROR_OUTOFMEMORY);

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Init 0x%08x : created context 0x%08x\n", lpvParams, pContext));

    gpPORTEMU->Unlock ();
    return (DWORD)pContext;
}

extern "C" BOOL COM_Deinit(DWORD dwData) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Deinit 0x%08x\n", dwData));

    if (! gpPORTEMU) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Deinit 0x%08x : not initialized\n", dwData));
        return FALSE;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Deinit 0x%08x : not initialized\n", dwData));
        gpPORTEMU->Unlock ();
        return FALSE;
    }

    PORTEMU_CONTEXT *pContext = gpPORTEMU->pPorts;
    PORTEMU_CONTEXT *pParent = NULL;

    while (pContext && (pContext != (PORTEMU_CONTEXT *)dwData)) {
        pParent = pContext;
        pContext = pContext->pNext;
    }

    if (! pContext) {
        SetLastError (ERROR_NOT_FOUND);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Deinit 0x%08x : not found\n", dwData));
        gpPORTEMU->Unlock ();
        return FALSE;
    }

    if (! pParent)
        gpPORTEMU->pPorts = pContext->pNext;
    else
        pParent->pNext = pContext->pNext;

    delete pContext;

    gpPORTEMU->Unlock ();

    return TRUE;
}

extern "C" DWORD COM_Open (DWORD dwData, DWORD dwAccess, DWORD dwShareMode) {
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Open 0x%08x\n", dwData));

    if (! gpPORTEMU) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : not initialized\n", dwData));
        return FALSE;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        SetLastError (ERROR_BUSY);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : not initialized\n", dwData));
        gpPORTEMU->Unlock ();
        return FALSE;
    }

    PORTEMU_CONTEXT *pContext = GetContext (dwData);

    // Do not allow open while we are closing
    for (int i = 0 ; i < PORTEMU_OPEN_ATTEMPTS ; ++i) {
        if (! pContext->fClosing) {
            break;
        }
        gpPORTEMU->Unlock ();
        Sleep (PORTEMU_OPEN_TIMEOUT);
        gpPORTEMU->Lock ();
    }

    if (pContext->fClosing) {
        SetLastError (ERROR_BUSY);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : cannot open while closing\n", dwData));
        gpPORTEMU->Unlock ();
        return FALSE;
    }

    if ((! pContext) || (! pContext->hRFCOMM) || (pContext->uiOpenRef == PORTEMU_OPEN_ALLUSED)) {
        SetLastError (pContext ? (pContext->hRFCOMM ? ERROR_ALREADY_INITIALIZED : ERROR_NOT_READY) : ERROR_NOT_FOUND);

        if (! pContext)
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : not found\n", dwData));
        else if (! pContext->hRFCOMM)
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : not setup\n", dwData));
        else
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : no resources\n", dwData));
        gpPORTEMU->Unlock ();

        return FALSE;
    }

    for (int iPortNum = 0 ; iPortNum < PORTEMU_OPEN_MAX ; ++iPortNum) {
        if (((1 << iPortNum) & pContext->uiOpenRef) == 0)
            break;
    }

    SVSUTIL_ASSERT (iPortNum < PORTEMU_OPEN_MAX);
    SVSUTIL_ASSERT ((iPortNum & dwData) == 0);

    pContext->uiOpenRef |= (1 << iPortNum);
    pContext->hOwnerProc[iPortNum] = GetOwnerProcess ();

    if (dwAccess & GENERIC_READ)
        pContext->uiReadRef |= (1 << iPortNum);

    if (dwAccess & GENERIC_WRITE)
        pContext->uiWriteRef |= (1 << iPortNum);

    if (pContext->fSdpQueryOn || pContext->hConnection || pContext->fLocalOpen || (dwAccess == 0)) {
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Open 0x%08x : already open - just incrementing the ref\n", dwData));
        gpPORTEMU->Unlock ();
        return dwData | iPortNum;
    }

    if (pContext->fLocal) {
        pContext->fLocalOpen = TRUE;
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Open 0x%08x : opening as local context\n", dwData));
        gpPORTEMU->Unlock ();
        return dwData | iPortNum;
    }

    int fAuthenticate = pContext->freq_auth;
    int fEncrypt      = pContext->freq_encrypt;
    int fSecurePreChannel = pContext->fSecurePreChannel;
    BT_ADDR bt    = SET_NAP_SAP (pContext->b.NAP, pContext->b.SAP);

    SVSUTIL_ASSERT (pContext->fSdpQueryOn == FALSE);

    PendingIO *pio = NewPendingIO (COMM_OPEN, iPortNum);

    if ((! pio) || (! pio->hEvent)) {
        if (pio)
            delete pio;

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : Failed to create pending IO. ERROR_NOT_ENOUGH_MEMORY\n", dwData));

        ForgetPort (pContext, iPortNum);

        SetLastError(ERROR_NO_SYSTEM_RESOURCES);
        gpPORTEMU->Unlock ();
        return FALSE;
    }

    HANDLE hConnect = NULL;
    HANDLE h = pContext->hRFCOMM;

    int iRes = ERROR_SUCCESS;

    GUID uuidZero;
    memset(&uuidZero, 0, sizeof(GUID));

    if (0 != memcmp(&pContext->uuidService, &uuidZero, sizeof(GUID))) {     //perform inquiry if you have the uuid
        SVSUTIL_ASSERT (! pContext->pops);
        SVSUTIL_ASSERT (! pio->pNext);
        pContext->pops = pio;

        pContext->channel = 0;

        ECall *pCall = NewCall (RFCOMM_SDP, pContext);
        if (! pCall) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : out of memory\n", dwData));
            iRes = ERROR_OUTOFMEMORY;
        } else {
            pCall->pNext     = pContext->pCalls;
            pContext->pCalls = pCall;

            SVSUTIL_ASSERT (pCall->hCallContext != SVSUTIL_HANDLE_INVALID);

            pContext->fSdpQueryOn = TRUE;

            HANDLE hSDP                     = gpPORTEMU->hSDP;
            SVSHandle hContext              = pCall->hCallContext;
            SDP_Connect_In pCallback    = gpPORTEMU->sdp_if.sdp_Connect_In;
            BD_ADDR b = pContext->b;

            SVSUTIL_ASSERT(hSDP && pCallback && b.NAP && b.SAP);

            HANDLE hEvent = pio->hEvent;

            gpPORTEMU->Unlock();

            iRes = ERROR_INTERNAL_ERROR;

            __try {
                iRes = pCallback (hSDP, (LPVOID)hContext, &b);
            } __except (1) {
                IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] : exception in COM_Open 0x%08x!\n", dwData));
            }

            if (iRes == ERROR_SUCCESS) {
                if (WAIT_OBJECT_0 != WaitForSingleObject(hEvent, SDP_CONNECT_TIMEOUT)) {
                    IFDBG(DebugOut(DEBUG_ERROR,L"[PORTEMU] COM_Open : Wait on SDP connect timed out or failed!\n"));
                    iRes = ERROR_OPERATION_ABORTED;
                    SDPAbortCall (hSDP, (LPVOID)hContext);
                }
            }

            if (! gpPORTEMU) {
                SetLastError (ERROR_INTERNAL_ERROR);
                return 0;
            }

            gpPORTEMU->Lock();

            if (iRes == ERROR_SUCCESS)
                iRes = (gpPORTEMU->fInitialized && (pContext == GetContext (dwData))) ? pio->iIoResult : ERROR_OPERATION_ABORTED;
            else if (FindCall (pCall))
                DeleteCall (pCall);

            if (gpPORTEMU->fInitialized && (pContext == GetContext (dwData)))
                pContext->fSdpQueryOn = FALSE;
        }
    }

    SVSUTIL_ASSERT ((pContext != GetContext (dwData)) || (pContext->fSdpQueryOn == FALSE));

    if (iRes == ERROR_SUCCESS) {
        if ((pContext->channel < 1) || (pContext->channel > PORTEMU_MAX_RFCOMM_CHANNEL)) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : could not retrieve remote port for device:%04x%08x\n", dwData, pContext->b.NAP, pContext->b.SAP));
            iRes = ERROR_PORT_UNREACHABLE;
        } else {
            iRes = pContext->rfcomm_if.rfcomm_CreateChannel (pContext->hRFCOMM, &pContext->b, pContext->channel, &hConnect);
            if (iRes == ERROR_SUCCESS)
                pContext->hConnection = hConnect;
        }
    }

    if (iRes == ERROR_SUCCESS) {    // Gotta PN first...
        SVSUTIL_ASSERT (! pContext->pops);
        SVSUTIL_ASSERT (! pio->pNext);
        pContext->pops = pio;

        ECall *pCall = NewCall (RFCOMM_PN, pContext);
        if (! pCall) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : out of memory\n", dwData));
            iRes = ERROR_OUTOFMEMORY;
        } else {
            pCall->pNext = pContext->pCalls;
            pContext->pCalls = pCall;

            SVSUTIL_ASSERT (pCall->hCallContext != SVSUTIL_HANDLE_INVALID);
            SVSHandle hContext = pCall->hCallContext;

            RFCOMM_PNREQ_In pCallback = pContext->rfcomm_if.rfcomm_PNREQ_In;
            iRes = ERROR_INTERNAL_ERROR;
            int n1 = pContext->iMTU;
            gpPORTEMU->Unlock ();
            __try {
                iRes = pCallback (h, (LPVOID)hContext, hConnect, PORTEMU_PRI, n1, RFCOMM_PN_CREDIT_IN, RFCOMM_PN_CREDIT_MAX);
            } __except (1) {
                IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open: exception in rfcomm_PNREQ_In\n"));
            }

            if (iRes == ERROR_SUCCESS)      // Wait
                WaitForSingleObject (pio->hEvent, INFINITE);

            if (! gpPORTEMU) {
                SetLastError (ERROR_INTERNAL_ERROR);
                return 0;
            }

            gpPORTEMU->Lock ();

            if (iRes == ERROR_SUCCESS)
                iRes = (gpPORTEMU->fInitialized && (pContext == GetContext (dwData))) ? pio->iIoResult : ERROR_OPERATION_ABORTED;
            else if (FindCall (pCall))
                DeleteCall (pCall);
        }
    }

    if (fSecurePreChannel) {
        gpPORTEMU->Unlock ();

        if ((iRes == ERROR_SUCCESS) && fAuthenticate)
            iRes = BthAuthenticate (&bt);

        if ((iRes == ERROR_SUCCESS) && fEncrypt)
            iRes = BthSetEncryption (&bt, TRUE);

        gpPORTEMU->Lock ();
    }

    if (iRes == ERROR_SUCCESS) {    // Connect
        SVSUTIL_ASSERT (! pContext->pops);
        SVSUTIL_ASSERT (! pio->pNext);
        pContext->pops = pio;

        ECall *pCall = NewCall (RFCOMM_CONNECT, pContext);
        if (! pCall) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : out of memory\n", dwData));
            iRes = ERROR_OUTOFMEMORY;
        } else {
            pCall->pNext = pContext->pCalls;
            pContext->pCalls = pCall;

            SVSUTIL_ASSERT (pCall->hCallContext != SVSUTIL_HANDLE_INVALID);
            SVSHandle hContext = pCall->hCallContext;

            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Open 0x%08x : connecting the channel\n", dwData));
            RFCOMM_ConnectRequest_In pCallback = pContext->rfcomm_if.rfcomm_ConnectRequest_In;

            iRes = ERROR_INTERNAL_ERROR;
            gpPORTEMU->Unlock ();
            __try {
                iRes = pCallback (h, (LPVOID)hContext, hConnect);
            } __except (1) {
                IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open: exception in rfcomm_ConnectRequest_In\n"));
            }

            if (iRes == ERROR_SUCCESS)
                WaitForSingleObject (pio->hEvent, INFINITE);

            if (! gpPORTEMU) {
                SetLastError (ERROR_INTERNAL_ERROR);
                return 0;
            }

            gpPORTEMU->Lock ();
            if (iRes == ERROR_SUCCESS)
                iRes = (gpPORTEMU->fInitialized && (pContext == GetContext (dwData))) ? pio->iIoResult : ERROR_OPERATION_ABORTED;
            else if (FindCall (pCall))
                DeleteCall (pCall);
        }
    }

    RemoveIO (pContext, pio);
    delete pio;

    if (! fSecurePreChannel) {
        gpPORTEMU->Unlock ();

        if ((iRes == ERROR_SUCCESS) && fAuthenticate)
            iRes = BthAuthenticate (&bt);

        if ((iRes == ERROR_SUCCESS) && fEncrypt)
            iRes = BthSetEncryption (&bt, TRUE);

        gpPORTEMU->Lock ();
    }

    if (iRes != ERROR_SUCCESS) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open 0x%08x : error %d\n", dwData, iRes));
        if (gpPORTEMU->fInitialized && (pContext == GetContext (dwData))) {
            pContext->hConnection = NULL;

            ForgetPort (pContext, iPortNum);

            RFCOMM_Disconnect_In pCallback = pContext->rfcomm_if.rfcomm_Disconnect_In;

            gpPORTEMU->Unlock ();
            __try {
                pCallback (h, NULL, hConnect);
            } __except (1) {
                IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Open : Exception in rfcomm_Disconnect_In\n"));
            }
            gpPORTEMU->Lock ();
        }
        SetLastError (iRes);
    }

    IFDBG(pContext = GetContext (dwData); DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Open 0x%08x : connected  to %04x%08x (%d), status %d\n", dwData, pContext ? pContext->b.NAP : 0, pContext ? pContext->b.SAP : 0, pContext ? pContext->channel : 0, iRes));
    gpPORTEMU->Unlock ();

    return iRes == ERROR_SUCCESS ? (dwData | iPortNum) : 0;
}

extern "C" BOOL COM_Close (DWORD dwData)  {
    int iPortNum = dwData & PORTEMU_OPEN_MASK;
    dwData &= ~PORTEMU_OPEN_MASK;

    if ((iPortNum < 0) || (iPortNum >= PORTEMU_OPEN_MAX)) {
        SetLastError (ERROR_INVALID_PARAMETER);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Close 0x%08x : bad port %d\n", dwData, iPortNum));
        return FALSE;
    }


    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Close 0x%08x %d\n", dwData, iPortNum));

    if (! gpPORTEMU) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Close 0x%08x : not initialized\n", dwData));
        return FALSE;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Close 0x%08x : not initialized\n", dwData));
        gpPORTEMU->Unlock ();
        return FALSE;
    }

    PORTEMU_CONTEXT *pContext = GetContext (dwData);

    if ((! pContext) || (! pContext->hRFCOMM)) {
        SetLastError (pContext ? (pContext->hRFCOMM ? ERROR_NOT_CONNECTED : ERROR_NOT_READY) : ERROR_INVALID_HANDLE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Close 0x%08x : not found, or not setup\n", dwData));
        gpPORTEMU->Unlock ();
        return FALSE;
    }

    if (pContext->fClosing) {
        // We are already in the process of closing the COM port on a different 
        // thread.  We must be blocked in the WaitForSingleObject call below. 
        SetLastError (ERROR_BUSY);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Close 0x%08x : already closing\n", pContext));
        gpPORTEMU->Unlock ();
        return FALSE;
    }

    pContext->fClosing = TRUE;

    ForgetPort (pContext, iPortNum);

    // Unblock IO
    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"COM_Close : unblocking UI for ctx 0x%08x\n", pContext));

    PendingIO *pParent = NULL;
    PendingIO *pIO = pContext->pops;

    while (pIO) {
        SVSUTIL_ASSERT ((pIO->iPortNum >= 0) && (pIO->iPortNum < PORTEMU_OPEN_MAX));

        if (pIO->iPortNum == iPortNum) {
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"COM_Close : ctx 0x%08x found and executed waiter (port %d)...\n", pContext, pIO->iPortNum));

            PendingIO *pNext = pIO->pNext;

            if (pParent)
                pParent->pNext = pIO->pNext;
            else
                pContext->pops = pIO->pNext;

            pIO->pNext = NULL;

            pIO->iIoResult = ERROR_CTX_CLOSE_PENDING;
            SetEvent (pIO->hEvent);

            pIO = pNext;
            continue;
        }

        pParent = pIO;
        pIO = pIO->pNext;
    }

    if (! (pContext->uiReadRef || pContext->uiWriteRef)) {
        pContext->fLocalOpen = FALSE;
        
        if (pContext->hConnection) {
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] Actually closing 0x%08x\n", dwData));

            PendingIO *pio = NewPendingIO (COMM_DISC, iPortNum);
            if (! pio) {
                SetLastError (ERROR_NO_SYSTEM_RESOURCES);
                pContext->fClosing = FALSE;
                gpPORTEMU->Unlock ();
                return FALSE;
            }

            InsertIO (pContext, pio);

            if(ERROR_SUCCESS == RFCommDisconnectIn (pContext))
			{
				gpPORTEMU->Unlock ();
				DWORD dwRes = WaitForSingleObject (pio->hEvent, INFINITE);
				gpPORTEMU->Lock ();
			}

			RemoveIO(pContext, pio);
			delete pio;

            if (! gpPORTEMU) {
                SetLastError (ERROR_INTERNAL_ERROR);
                pContext->fClosing = FALSE;
                gpPORTEMU->Unlock ();
                return FALSE;
            }
        }else
            CloseConnection (pContext, ERROR_GRACEFUL_DISCONNECT, FALSE);
    } else
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] Just decrementing ref count on 0x%08x\n", dwData));

    pContext->fClosing = FALSE;
    gpPORTEMU->Unlock ();

    return TRUE;
}

extern "C" DWORD COM_Write (DWORD dwData, LPCVOID pInBuf, DWORD dwInLen) {
    int iPortNum = dwData & PORTEMU_OPEN_MASK;
    dwData &= ~PORTEMU_OPEN_MASK;

    if ((iPortNum < 0) || (iPortNum >= PORTEMU_OPEN_MAX)) {
        SetLastError (ERROR_INVALID_PARAMETER);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Write 0x%08x : bad port %d\n", dwData, iPortNum));
        return -1;
    }

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Write 0x%08x %d bytes\n", dwData, dwInLen));

    if (! gpPORTEMU) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Write 0x%08x : not initialized\n", dwData));
        return -1;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Write 0x%08x : not initialized\n", dwData));
        gpPORTEMU->Unlock ();
        return -1;
    }

    PORTEMU_CONTEXT *pContext = GetContext (dwData);

    if ((! pContext) || (! pContext->hRFCOMM) ||
                (! (pContext->uiOpenRef & (1 << iPortNum))) ||
                (! (pContext->uiWriteRef & (1 << iPortNum)))) {
        SetLastError (pContext ? (pContext->hRFCOMM ? ERROR_NOT_CONNECTED : ERROR_NOT_READY) : ERROR_INVALID_HANDLE);

#if defined (DEBUG) || defined (_DEBUG)
        if (! pContext)
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Write 0x%08x : not found\n", dwData));
        else if (! pContext->hRFCOMM)
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Write 0x%08x : not setup\n", dwData));
        else if (! pContext->hConnection)
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Write 0x%08x : not connected\n", dwData));
        else if (! (pContext->uiOpenRef & (1 << iPortNum)))
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Write 0x%08x : not opened as port %d\n", dwData, iPortNum));
        else if (! (pContext->uiWriteRef & (1 << iPortNum)))
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Write 0x%08x : not opened with write access\n", dwData));
#endif

        gpPORTEMU->Unlock ();
        return -1;
    }

    // Mark the buffer as async
    AsynchronousBuffer_t buffer;
    if (FAILED(buffer.Allocate(pInBuf, dwInLen, ARG_I_PTR))) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Write 0x%08x : Failed to mark buffer async. ERROR_NO_SYSTEM_RESOURCES\n", dwData));
        SetLastError(ERROR_NO_SYSTEM_RESOURCES);
        gpPORTEMU->Unlock ();
        return -1;
    }    

    PendingIO *pio = NewPendingIO (COMM_WRITE, iPortNum);

    if ((! pio) || (! pio->hEvent)) {
        if (pio)
            delete pio;

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Write 0x%08x : Failed to create pending IO. ERROR_NO_SYSTEM_RESOURCES\n", dwData));        
        SetLastError(ERROR_NO_SYSTEM_RESOURCES);
        gpPORTEMU->Unlock ();
        return -1;
    }

    pio->buffer  = (unsigned char *) buffer.ptr();
    pio->cbuffer = buffer.size();

    InsertIO (pContext, pio);

    int iTimeout = pContext->ct.WriteTotalTimeoutConstant;
    if (pContext->ct.WriteTotalTimeoutMultiplier != MAXDWORD)
        iTimeout += pContext->ct.WriteTotalTimeoutMultiplier * dwInLen;

    int iRes = SendData (pContext);

    if (iRes == ERROR_IO_PENDING) {
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Write 0x%08x %d bytes pending...\n", dwData, dwInLen));
        gpPORTEMU->Unlock ();
        DWORD dwRes = WaitForSingleObject (pio->hEvent, iTimeout);

        if (! gpPORTEMU) {
            SetLastError (ERROR_INTERNAL_ERROR);
            return -1;
        }

        gpPORTEMU->Lock ();
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Write 0x%08x %d bytes pending (out)...\n", dwData, dwInLen));
        iRes = pio->iIoResult;
    }

    RemoveIO (pContext, pio);
    int iobytes = pio->cbuffer_used;

    buffer.Free();

    delete pio;

    // If we are the server and were disconnected, then it actually makes sense to just act as
    // though we were not connected from the beginning (as a write would do if it were called
    // without being connected from the beginning).
    if ((iRes == ERROR_GRACEFUL_DISCONNECT) && pContext->fLocal) {
        iRes = ERROR_SUCCESS;
    }

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Write 0x%08x %d bytes completed, %d bytes written, status %d\n", dwData, dwInLen, iobytes, iRes));

    gpPORTEMU->Unlock ();

    if (iRes != ERROR_SUCCESS) {
        SetLastError (iRes);
        return -1;
    } else {
#if defined (DEBUG_DUMP_RFCOMM_STREAM)
        IFDBG(DumpBuffPfx (DEBUG_RFCOMM_TRACE, L"IN>", (unsigned char *)pInBuf, iobytes));
#endif
    }

    return iobytes;
}

extern "C" DWORD COM_Read (DWORD dwData, LPVOID pBuf, DWORD Len)
{
    int iPortNum = dwData & PORTEMU_OPEN_MASK;
    dwData &= ~PORTEMU_OPEN_MASK;

    if ((iPortNum < 0) || (iPortNum >= PORTEMU_OPEN_MAX)) {
        SetLastError (ERROR_INVALID_PARAMETER);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : bad port %d\n", dwData, iPortNum));
        return -1;
    }

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Read 0x%08x\n", dwData));

    if (! gpPORTEMU) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : not initialized\n", dwData));
        return -1;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : not initialized\n", dwData));
        gpPORTEMU->Unlock ();
        return -1;
    }

    PORTEMU_CONTEXT *pContext = GetContext (dwData);

    if ((! pContext) || (! pContext->hRFCOMM) ||
                (! (pContext->uiOpenRef & (1 << iPortNum))) ||
                (! (pContext->uiReadRef & (1 << iPortNum)))) {
        SetLastError (pContext ? (pContext->hRFCOMM ? ERROR_NOT_CONNECTED : ERROR_NOT_READY) : ERROR_INVALID_HANDLE);

#if defined (DEBUG) || defined (_DEBUG) || defined (RETAILLOG)
    if (! pContext)
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : not found\n", dwData));
    else if (! pContext->hRFCOMM)
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : not setup\n", dwData));
    else if (! (pContext->uiOpenRef & (1 << iPortNum)))
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : not opened as port %d\n", dwData, iPortNum));
    else if (! (pContext->uiReadRef & (1 << iPortNum)))
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : not opened with write access\n", dwData));
#endif

        gpPORTEMU->Unlock ();
        return -1;
    }

    int iTotalTimeout = pContext->ct.ReadTotalTimeoutConstant;
    if (pContext->ct.ReadTotalTimeoutMultiplier != MAXDWORD)
        iTotalTimeout += pContext->ct.ReadTotalTimeoutMultiplier * Len;

    int iIntervalTimeout = pContext->ct.ReadIntervalTimeout;


    DWORD cDataRead = 0;

    int iRes = ERROR_SUCCESS;

    while (pContext->pbl && (cDataRead < Len)) {
        int cBytes = BufferTotal (pContext->pbl->pb);
        if ((DWORD)cBytes > Len - cDataRead)
            cBytes = Len - cDataRead;

        if (! BufferGetChunk_TryExcept (pContext->pbl->pb, cBytes, ((unsigned char *)pBuf) + cDataRead)) {
            SetLastError (ERROR_INVALID_USER_BUFFER);
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : ERROR_INVALID_USER_BUFFER\n", dwData));
            gpPORTEMU->Unlock ();
            return -1;
        }

        cDataRead += cBytes;

        if (BufferTotal (pContext->pbl->pb) == 0) {
            BD_BUFFER_LIST *pNext = pContext->pbl->pNext;
            if (pContext->pbl->pb->pFree)
                pContext->pbl->pb->pFree (pContext->pbl->pb);
            delete pContext->pbl;
            pContext->pbl = pNext;
        } else
            SVSUTIL_ASSERT (cDataRead == Len);
    }

    pContext->iRecvQuotaUsed -= cDataRead;
    SVSUTIL_ASSERT (pContext->iRecvQuotaUsed >= 0);

    if ((! pContext->credit_fc) && pContext->fSentXon && (pContext->iRecvQuotaUsed <= pContext->dcb.XoffLim)) {
        pContext->fSentXon = FALSE;
        pContext->ucv24out &= ~MSC_FC_BIT;
        SendMSC (pContext);
    } else if (pContext->credit_fc)
        SendCredits (pContext);

    if ((! gpPORTEMU->fInitialized) || (pContext != GetContext (dwData))) {
        SetLastError (ERROR_OPERATION_ABORTED);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : ERROR_OPERATION_ABORTED\n", dwData));
        gpPORTEMU->Unlock ();

        return -1;
    }

    if (cDataRead < Len) {
        // Mark the buffer as async
        AsynchronousBuffer_t buffer;
        if (FAILED(buffer.Allocate((PVOID)pBuf, Len, ARG_O_PTR))) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : Failed to mark buffer async. ERROR_NO_SYSTEM_RESOURCES\n", dwData));
            SetLastError(ERROR_NO_SYSTEM_RESOURCES);
            gpPORTEMU->Unlock ();
            return -1;
        }
        
        PendingIO *pio = NewPendingIO (COMM_READ, iPortNum);

        if ((! pio) || (! pio->hEvent)) {
            if (pio)
                delete pio;

            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_Read 0x%08x : Failed to create pending IO. ERROR_NO_SYSTEM_RESOURCES\n", dwData));
            SetLastError(ERROR_NO_SYSTEM_RESOURCES);
            gpPORTEMU->Unlock ();
            return -1;
        }

        pio->buffer = (unsigned char *) buffer.ptr();
        pio->cbuffer = buffer.size();
        pio->cbuffer_used = cDataRead;
        InsertIO (pContext, pio);

        int iStartTime = GetTickCount ();

        for ( ; ; ) {
            int iobytes = pio->cbuffer_used;

            int iTimeout = ((iIntervalTimeout != 0) && (iIntervalTimeout != MAXDWORD)) ? iIntervalTimeout : iTotalTimeout - (GetTickCount () - iStartTime);

            if (iTimeout > (iTotalTimeout - ((int)GetTickCount () - iStartTime)))
                iTimeout = iTotalTimeout - ((int)GetTickCount () - iStartTime);

            if (iTimeout < 0)
                break;

            gpPORTEMU->Unlock ();
            DWORD dwRes = WaitForSingleObject (pio->hEvent, iTimeout);

            if (! gpPORTEMU) {
                SetLastError (ERROR_INTERNAL_ERROR);
                return -1;
            }

            gpPORTEMU->Lock ();
            if (dwRes != WAIT_TIMEOUT)
                break;

            if ((iobytes == pio->cbuffer_used) && pio->cbuffer_used)
                break;
        }
        iRes = pio->iIoResult;

        cDataRead = pio->cbuffer_used;

        RemoveIO (pContext, pio);

        buffer.Free();

        delete pio;

        // If we are the server and were disconnected, then it actually makes sense to just act as
        // though we were not connected (as a read would do if it were called without being connected
        // from the beginning).
        if ((iRes == ERROR_GRACEFUL_DISCONNECT) && pContext->fLocal) {
            iRes = ERROR_SUCCESS;
        }
    }

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_Read 0x%08x %d bytes completed, %d bytes read, status %d\n", dwData, Len, cDataRead, iRes));
    gpPORTEMU->Unlock ();

    if (iRes != ERROR_SUCCESS) {
        SetLastError (iRes);
        return -1;
    } else {
#if defined (DEBUG_DUMP_RFCOMM_STREAM)
        IFDBG(DumpBuffPfx (DEBUG_RFCOMM_TRACE, L"OUT>", (unsigned char *)pBuf, cDataRead));
#endif
    }

    return cDataRead;
}

extern "C" DWORD COM_Seek (DWORD dwData, long pos, DWORD type)
{
    SetLastError (ERROR_NOT_SUPPORTED);

    return (DWORD)-1;
}

extern "C" BOOL COM_IOControl (DWORD dwData,
                                    DWORD dwIoControlCode,
                                    LPVOID lpInBuf,
                                    DWORD nInBufSize,
                                    LPVOID lpOutBuf,
                                    DWORD nOutBufSize,
                                    LPDWORD lpBytesReturned) {

    if (dwIoControlCode == IOCTL_PSL_NOTIFY) {
        HANDLE hProc = NULL;
        DEVICE_PSL_NOTIFY PslPacket;
        
        if (! CeSafeCopyMemory(&PslPacket, lpInBuf, sizeof(PslPacket))) {
            SetLastError(ERROR_INVALID_USER_BUFFER);
            return FALSE;
        }

        if ((PslPacket.dwSize == sizeof(DEVICE_PSL_NOTIFY)) && (PslPacket.dwFlags == DLL_PROCESS_EXITING)){
            SVSUTIL_ASSERT (*(HANDLE *)dwData == PslPacket.hProc);
            hProc = (HANDLE)PslPacket.hProc;
        }

        if (hProc)
            PORTEMU_ProcessExited (hProc);

        return TRUE;
    }

    int iPortNum = dwData & PORTEMU_OPEN_MASK;
    dwData &= ~PORTEMU_OPEN_MASK;

    if ((iPortNum < 0) || (iPortNum >= PORTEMU_OPEN_MAX)) {
        SetLastError (ERROR_INVALID_PARAMETER);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_IOControl 0x%08x : bad port %d\n", dwData, iPortNum));
        return FALSE;
    }


    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_IOControl 0x%08x\n", dwData));

    if (! gpPORTEMU) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_DeviceIoControl 0x%08x : not initialized\n", dwData));
        return FALSE;
    }

    gpPORTEMU->Lock ();
    if  (! gpPORTEMU->fInitialized) {
        SetLastError (ERROR_SERVICE_NOT_ACTIVE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_DeviceIoControl 0x%08x : not initialized\n", dwData));
        gpPORTEMU->Unlock ();
        return FALSE;
    }

    PORTEMU_CONTEXT *pContext = GetContext (dwData);

    if ((! pContext) || (! pContext->hRFCOMM) || (! (pContext->uiOpenRef & (1 << iPortNum)))) {
        SetLastError (pContext ? (pContext->hRFCOMM ? ERROR_NOT_CONNECTED : ERROR_NOT_READY) : ERROR_INVALID_HANDLE);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_DeviceIoControl 0x%08x : port not found or not opened\n", dwData));
        gpPORTEMU->Unlock ();
        return FALSE;
    }

    if ((
        (dwIoControlCode == IOCTL_SERIAL_GET_WAIT_MASK) ||
        (dwIoControlCode == IOCTL_SERIAL_SET_WAIT_MASK) ||
        (dwIoControlCode == IOCTL_SERIAL_WAIT_ON_MASK) ||
        (dwIoControlCode == IOCTL_SERIAL_GET_PROPERTIES) ||
        (dwIoControlCode == IOCTL_BLUETOOTH_GET_RFCOMM_CHANNEL) ||
        (dwIoControlCode == IOCTL_SERIAL_GET_MODEMSTATUS) ||
        (dwIoControlCode == IOCTL_SERIAL_SET_TIMEOUTS) ||
        (dwIoControlCode == IOCTL_SERIAL_GET_TIMEOUTS) ||
        ((dwIoControlCode == IOCTL_SERIAL_GET_DCB) && pContext->local_dcb) ||
        ((dwIoControlCode == IOCTL_SERIAL_SET_DCB) && pContext->local_dcb)
        ) && pContext->fLocal) {
#if defined (DEBUG) || defined (_DEBUG) || defined (RETAILLOG)
        if (! pContext->hConnection)
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] performing one of select allowed operations on UNCONNECTED! port.\n"));
#endif
    } else if (! pContext->hConnection) {
        SetLastError (ERROR_NOT_CONNECTED);

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] COM_DeviceIoControl 0x%08x : port not connected\n", dwData));

        gpPORTEMU->Unlock ();

        return FALSE;
    }

    int iRes = ERROR_NOT_SUPPORTED;

    switch (dwIoControlCode) {
    case IOCTL_SERIAL_SET_BREAK_ON:
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_SET_BREAK_ON (unsupported)\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_SET_BREAK_OFF:
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_SET_BREAK_OFF (unsupported)\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_SET_DTR:      // == DSR
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] +IOCTL_SERIAL_SET_DTR\n"));
        pContext->ucv24out |= (MSC_RTC_BIT | MSC_DV_BIT);
        SendMSC (pContext);
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] -IOCTL_SERIAL_SET_DTR\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_CLR_DTR:      // == DSR
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] +IOCTL_SERIAL_CLR_DTR\n"));
        pContext->ucv24out &= ~(MSC_RTC_BIT | MSC_DV_BIT);
        SendMSC (pContext);
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] -IOCTL_SERIAL_CLR_DTR\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_SET_RTS: // == CTS
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] +IOCTL_SERIAL_SET_RTS\n"));
        pContext->ucv24out |= MSC_RTR_BIT;
        SendMSC (pContext);
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] -IOCTL_SERIAL_SET_RTS\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_CLR_RTS: // == CTS
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] +IOCTL_SERIAL_CLR_RTS\n"));
        pContext->ucv24out &= ~MSC_RTR_BIT;
        SendMSC (pContext);
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] -IOCTL_SERIAL_CLR_RTS\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_SET_XOFF:
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_SET_XOFF (unsupported)\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_SET_XON:
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_SET_XON (unsupported)\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_GET_WAIT_MASK:
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_GET_WAIT_MASK\n"));
        if ((nOutBufSize < sizeof(DWORD)) || (NULL == lpOutBuf) || (NULL == lpBytesReturned)) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_GET_WAIT_MASK : ERROR_INVALID_PARAMETER\n"));
            iRes = ERROR_INVALID_PARAMETER;
            break;
        }

        __try {
            // Set The Wait Mask
            *(DWORD *)lpOutBuf = pContext->adwEnabledEvents[iPortNum];
            // Return the size
            *lpBytesReturned = sizeof(DWORD);
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_GET_WAIT_MASK : ERROR_INVALID_USER_BUFFER\n"));
            iRes = ERROR_INVALID_USER_BUFFER;
            break;
        }

        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_SET_WAIT_MASK:
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_SET_WAIT_MASK\n"));
        if ((nInBufSize < sizeof(DWORD)) || (NULL == lpInBuf)) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_SET_WAIT_MASK : ERROR_INVALID_PARAMETER\n"));
            iRes = ERROR_INVALID_PARAMETER;
            break;
        }

        DWORD dwMask;
        if (! CeSafeCopyMemory(&dwMask, lpInBuf, sizeof(dwMask))) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_SET_WAIT_MASK : ERROR_INVALID_USER_BUFFER\n"));
            iRes = ERROR_INVALID_USER_BUFFER;
            break;
        }

        SetCommEventMask (pContext, dwMask, iPortNum);
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_WAIT_ON_MASK:
        {
            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_WAIT_ON_MASK\n"));
            if ((nOutBufSize < sizeof(DWORD)) || (NULL == lpOutBuf) || (NULL == lpBytesReturned)) {
                IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_WAIT_ON_MASK : ERROR_INVALID_PARAMETER\n"));
                iRes = ERROR_INVALID_PARAMETER;
                break;
            }

            // Return right away if not waiting for any events
            if (0 == pContext->adwEnabledEvents[iPortNum]) {
                iRes = ERROR_INVALID_PARAMETER;

                __try {
                    *(DWORD *)lpOutBuf = 0;
                    *lpBytesReturned = sizeof(DWORD);
                } __except (1) {
                    iRes = ERROR_INVALID_USER_BUFFER;
                }

                IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_WAIT_ON_MASK (0) : error=%d\n", iRes));
                break;
            }

            DWORD dw;
            if (dw = pContext->adwOccuredEvents[iPortNum] & pContext->adwEnabledEvents[iPortNum]) {
                iRes = ERROR_SUCCESS;
                pContext->adwOccuredEvents[iPortNum] = 0;

                __try {
                    *(DWORD *)lpOutBuf = dw;
                    *lpBytesReturned = sizeof(DWORD);
                } __except (1) {
                    iRes = ERROR_INVALID_USER_BUFFER;
                }

                IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_WAIT_ON_MASK (0x%04x) : status=%d\n", dw, iRes));
                break;
            }

            PendingIO *pio = NewPendingIO (COMM_WAIT_EVENT, iPortNum);

            if ((! pio) || (! pio->hEvent)) {
                if (pio)
                    delete pio;

                IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_WAIT_ON_MASK 0x%08x : Failed to create pending IO. ERROR_NO_SYSTEM_RESOURCES\n", dwData));
                iRes = ERROR_NO_SYSTEM_RESOURCES;
                break;
            }

            InsertIO (pContext, pio);

            gpPORTEMU->Unlock ();
            WaitForSingleObject (pio->hEvent, INFINITE);

            if (! gpPORTEMU) {
                SetLastError (ERROR_INTERNAL_ERROR);
                return 0;
            }

            gpPORTEMU->Lock ();
            iRes = pio->iIoResult;

            RemoveIO (pContext, pio);

            if (iRes == ERROR_SUCCESS) {
                __try {
                    *(DWORD *)lpOutBuf = pio->dwEvent;
                    *lpBytesReturned = sizeof(DWORD);
                } __except (1) {
                    iRes = ERROR_INVALID_USER_BUFFER;
                }
            }

            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_WAIT_ON_MASK (0x%04x) : %d\n", pio->dwEvent, iRes));

            delete pio;
        }
        break;

    case IOCTL_SERIAL_GET_COMMSTATUS:
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_GET_COMMSTATUS\n"));

        if ((nOutBufSize < sizeof(SERIAL_DEV_STATUS)) || (NULL == lpOutBuf) || (NULL == lpBytesReturned)) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_GET_COMMSTATUS : ERROR_INVALID_PARAMETER\n"));
            iRes = ERROR_INVALID_PARAMETER;
            break;
        }

        __try {
            memset (lpOutBuf, 0, sizeof(SERIAL_DEV_STATUS));

            {
                PendingIO *pIO = pContext->pops;
                int     cTotalBytes = 0;

                while (pIO) {
                    if (pIO->op == COMM_WRITE)
                        cTotalBytes += (pIO->cbuffer - pIO->cbuffer_used);

                    pIO = pIO->pNext;
                }

                ((SERIAL_DEV_STATUS *)lpOutBuf)->Errors = pContext->adwErr[iPortNum];
                pContext->adwErr[iPortNum] = 0;

                ((SERIAL_DEV_STATUS *)lpOutBuf)->ComStat.cbInQue = pContext->iRecvQuotaUsed;
                ((SERIAL_DEV_STATUS *)lpOutBuf)->ComStat.cbOutQue = cTotalBytes;
            }
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_GET_COMMSTATUS : ERROR_INVALID_USER_BUFFER\n"));
            iRes = ERROR_INVALID_USER_BUFFER;
            break;
        }

        *lpBytesReturned = sizeof(SERIAL_DEV_STATUS);
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_GET_MODEMSTATUS:
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_GET_MODEMSTATUS\n"));

        if ((nOutBufSize < sizeof(DWORD)) || (NULL == lpOutBuf) || (NULL == lpBytesReturned)) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_GET_MODEMSTATUS : ERROR_INVALID_PARAMETER\n"));
            iRes = ERROR_INVALID_PARAMETER;
            break;
        }

        __try {
            *(DWORD *)lpOutBuf = pContext->dwModemStatusIn;
            *lpBytesReturned = sizeof(DWORD);
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_GET_MODEMSTATUS : ERROR_INVALID_USER_BUFFER\n"));
            iRes = ERROR_INVALID_USER_BUFFER;
            break;
        }

        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_GET_PROPERTIES:
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_GET_PROPERTIES\n"));

        if ((nOutBufSize < sizeof(COMMPROP)) || (NULL == lpOutBuf) || (NULL == lpBytesReturned)) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_GET_PROPERTIES : ERROR_INVALID_PARAMETER\n"));
            iRes = ERROR_INVALID_PARAMETER;
            break;
        }

        __try {
            COMMPROP *pcp = (COMMPROP *)lpOutBuf;
            memset (pcp, 0, sizeof(*pcp));

            pcp->wPacketLength      = 0xffff;
            pcp->wPacketVersion     = 0xffff;
            pcp->dwServiceMask      = SP_SERIALCOMM;
            pcp->dwReserved1        = 0;
            pcp->dwMaxTxQueue       = 16;
            pcp->dwMaxRxQueue       = 16;
            pcp->dwMaxBaud          = BAUD_115200;
            pcp->dwProvSubType      = PST_RS232;
            pcp->dwProvCapabilities = PCF_RLSD | PCF_RTSCTS | PCF_INTTIMEOUTS | PCF_TOTALTIMEOUTS;
            pcp->dwSettableBaud     = BAUD_075 | BAUD_110 | BAUD_150 | BAUD_300 | BAUD_600 |
                                        BAUD_1200 | BAUD_1800 | BAUD_2400 | BAUD_4800 |
                                        BAUD_7200 | BAUD_9600 | BAUD_14400 |
                                        BAUD_19200 | BAUD_38400 | BAUD_56K | BAUD_128K |
                                        BAUD_115200 | BAUD_57600 | BAUD_USER;
            pcp->dwSettableParams   = SP_BAUD | SP_RLSD;
            pcp->wSettableData      = DATABITS_8;
            pcp->wSettableStopParity= STOPBITS_10 | PARITY_NONE;
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_GET_PROPERTIES : ERROR_INVALID_USER_BUFFER\n"));
            iRes = ERROR_INVALID_USER_BUFFER;
            break;
        }

        *lpBytesReturned = sizeof(COMMPROP);
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_SET_TIMEOUTS:
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_SET_TIMEOUTS\n"));

        if ((nInBufSize < sizeof(pContext->ct)) || (NULL == lpInBuf)) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_SET_TIMEOUTS : ERROR_INVALID_PARAMETER\n"));
            iRes = ERROR_INVALID_PARAMETER;
            break;
        }

        if (! CeSafeCopyMemory (&pContext->ct, lpInBuf, sizeof(pContext->ct))) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_SET_TIMEOUTS : ERROR_INVALID_USER_BUFFER\n"));
            iRes = ERROR_INVALID_USER_BUFFER;
            break;
        }

        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_GET_TIMEOUTS:
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_GET_TIMEOUTS\n"));

        if ((nOutBufSize < sizeof(pContext->ct)) || (NULL == lpOutBuf) || (NULL == lpBytesReturned)) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_GET_TIMEOUTS : ERROR_INVALID_PARAMETER\n"));
            iRes = ERROR_INVALID_PARAMETER;
            break;
        }
        __try {
            memcpy (lpOutBuf, &pContext->ct, sizeof(pContext->ct));
            *lpBytesReturned = sizeof(pContext->ct);
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_GET_TIMEOUTS : ERROR_INVALID_USER_BUFFER\n"));
            iRes = ERROR_INVALID_USER_BUFFER;
            break;
        }

        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_PURGE:
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_PURGE (unsupported)\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_SET_QUEUE_SIZE:
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_SET_QUEUE_SIZE (unsupported)\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_IMMEDIATE_CHAR:
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_IMMEDIATE_CHAR (unsupported)\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_GET_DCB:
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_GET_DCB\n"));
        if ((nOutBufSize < sizeof(pContext->dcb)) || (NULL == lpOutBuf) || (NULL == lpBytesReturned)) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_GET_DCB : ERROR_INVALID_PARAMETER\n"));
            iRes = ERROR_INVALID_PARAMETER;
            break;
        }

        if (pContext->local_dcb) {
            __try {
                memcpy (lpOutBuf, &pContext->dcb, sizeof(pContext->dcb));
                *lpBytesReturned = sizeof(pContext->dcb);
            } __except (1) {
                IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_GET_DCB : ERROR_INVALID_USER_BUFFER\n"));
                iRes = ERROR_INVALID_PARAMETER;
                break;
            }

            iRes = ERROR_SUCCESS;
            break;
        }

        {
        *lpBytesReturned = 0;

        ECall *pCall = NewCall (RFCOMM_RPN, pContext);
        if (! pCall) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_GET_DCB 0x%08x : Failed to create pending IO. ERROR_NO_SYSTEM_RESOURCES\n", dwData));
            iRes = ERROR_NO_SYSTEM_RESOURCES;
            break;
        }

        SVSUTIL_ASSERT(pCall->hCallContext != SVSUTIL_HANDLE_INVALID);

        pCall->pNext     = pContext->pCalls;
        pContext->pCalls = pCall;

        PendingIO *pio = NewPendingIO (COMM_RPN, iPortNum);

        if ((! pio) || (! pio->hEvent)) {
            if (pio)
                delete pio;

            DeleteCall (pCall);

            iRes = ERROR_NO_SYSTEM_RESOURCES;

            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_GET_DCB 0x%08x : Failed to create pending IO. ERROR_NO_SYSTEM_RESOURCES\n", dwData));
            break;
        }

        InsertIO (pContext, pio);

        RFCOMM_RPNREQ_In pCallback = pContext->rfcomm_if.rfcomm_RPNREQ_In;
        HANDLE hRFCOMM = pContext->hRFCOMM;
        SVSHandle hContext = pCall->hCallContext;
        HANDLE hConnection = pContext->hConnection;

        unsigned short mask = 0;

        gpPORTEMU->Unlock ();

        iRes = ERROR_INTERNAL_ERROR;
        __try {
            iRes = pCallback (hRFCOMM, (LPVOID)hContext, hConnection, 0, 0, 0, 0, 0, 0, 0, 0, 0);
        } __except(1) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_GET_DCB : exception in rfcomm_RPNREQ_In\n"));
        }

        if (iRes == ERROR_SUCCESS)
            WaitForSingleObject (pio->hEvent, INFINITE);

        if (! gpPORTEMU) {
            SetLastError (ERROR_INTERNAL_ERROR);
            return 0;
        }

        gpPORTEMU->Lock ();
        iRes = pio->iIoResult;

        RemoveIO (pContext, pio);

        delete pio;

        if (iRes == ERROR_SUCCESS) {
            __try {
                memcpy (lpOutBuf, &pContext->dcb, sizeof(pContext->dcb));
                *lpBytesReturned = sizeof(pContext->dcb);
            } __except (1) {
                IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_GET_DCB : ERROR_INVALID_USER_BUFFER\n"));
                iRes = ERROR_INVALID_USER_BUFFER;
                break;
            }
        }

        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_GET_DCB : %d\n", iRes));
        }
        break;

    case IOCTL_SERIAL_SET_DCB:
        {
            if ((nInBufSize != sizeof(DCB)) || (! lpInBuf)) {
                IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_SET_DCB : ERROR_INVALID_PARAMETER\n"));
                iRes = ERROR_INVALID_PARAMETER;
                break;
            }

            iRes = ERROR_INVALID_PARAMETER;

            DCB dcb;

            __try {
                dcb = *(DCB *)lpInBuf;
                if (pContext->local_dcb) {
                    if (dcb.DCBlength == sizeof(dcb)) {
                        pContext->dcb = dcb;
                        iRes = ERROR_SUCCESS;
                    }
                } else {
                    if (VerifyDCB(&dcb))
                        iRes = ERROR_SUCCESS;
                }
            } __except (1) {
                IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_SET_DCB : exception reading parameters\n"));
            }

            if (iRes != ERROR_SUCCESS)
                break;

            if (pContext->local_dcb)
                break;

            ECall *pCall = NewCall (RFCOMM_RPN, pContext);
            if (! pCall) {
                IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_SET_DCB 0x%08x : Failed to create pending IO. ERROR_NO_SYSTEM_RESOURCES\n", dwData));
                iRes = ERROR_NO_SYSTEM_RESOURCES;
                break;
            }

            pCall->pNext     = pContext->pCalls;
            pContext->pCalls = pCall;

            PendingIO *pio = NewPendingIO (COMM_RPN, iPortNum);

            if ((! pio) || (! pio->hEvent)) {
                if (pio)
                    delete pio;

                DeleteCall (pCall);

                iRes = ERROR_NO_SYSTEM_RESOURCES;

                IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_SET_DCB 0x%08x : Failed to create pending IO. ERROR_NO_SYSTEM_RESOURCES\n", dwData));
                break;
            }

            InsertIO (pContext, pio);

            SVSUTIL_ASSERT (pCall->hCallContext != SVSUTIL_HANDLE_INVALID);

            RFCOMM_RPNREQ_In pCallback = pContext->rfcomm_if.rfcomm_RPNREQ_In;
            HANDLE hRFCOMM = pContext->hRFCOMM;
            SVSHandle hContext = pCall->hCallContext;
            HANDLE hConnection = pContext->hConnection;

            unsigned short mask = RFCOMM_RPN_HAVE_BAUD | RFCOMM_RPN_HAVE_DATA | RFCOMM_RPN_HAVE_STOP |
                                                RFCOMM_RPN_HAVE_PARITY | RFCOMM_RPN_HAVE_PT;
            int baud = dcb.BaudRate;
            int data = dcb.ByteSize;
            int stop = dcb.StopBits;
            int parity = dcb.fParity;
            int parity_type = dcb.Parity;
            unsigned char flow = 0;
            unsigned char xon = 0;
            unsigned char xoff = 0;

            pContext->dcb = dcb;

            gpPORTEMU->Unlock ();

            iRes = ERROR_INTERNAL_ERROR;
            __try {
                iRes = pCallback (hRFCOMM, (LPVOID)hContext, hConnection, mask, baud, data, stop, parity, parity_type, flow, xon, xoff);
            } __except(1) {
                IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_SET_DCB : exception in rfcomm_RPNREQ_In\n"));
            }

            if (iRes == ERROR_SUCCESS)
                WaitForSingleObject (pio->hEvent, INFINITE);

            if (! gpPORTEMU) {
                SetLastError (ERROR_INTERNAL_ERROR);
                return 0;
            }

            gpPORTEMU->Lock ();
            iRes = pio->iIoResult;

            RemoveIO (pContext, pio);

            delete pio;

            IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_SET_DCB : %d\n", iRes));
        }
        break;

    case IOCTL_SERIAL_ENABLE_IR:
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_ENABLE_IR (unsupported)\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_SERIAL_DISABLE_IR:
        IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_DISABLE_IR (unsupported)\n"));
        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_BLUETOOTH_GET_RFCOMM_CHANNEL:
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_SERIAL_GET_RFCOMM_CHANNEL\n"));
        if ((nOutBufSize != sizeof(DWORD)) || (NULL == lpOutBuf) || (NULL == lpBytesReturned)) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_SERIAL_GET_RFCOMM_CHANNEL : ERROR_INVALID_PARAMETER\n"));
            iRes = ERROR_INVALID_PARAMETER;
            break;
        }

        __try {
            *(DWORD *)lpOutBuf = pContext->channel;
            *lpBytesReturned = sizeof(DWORD);
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_SERIAL_GET_RFCOMM_CHANNEL : ERROR_INVALID_USER_BUFFER\n"));
            iRes = ERROR_INVALID_USER_BUFFER;
            break;
        }

        iRes = ERROR_SUCCESS;
        break;

    case IOCTL_BLUETOOTH_GET_PEER_DEVICE:
        IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] IOCTL_BLUETOOTH_GET_PEER_DEVICE\n"));
        if ((nOutBufSize != sizeof(BT_ADDR)) || (NULL == lpOutBuf) || (NULL == lpBytesReturned)) {
            IFDBG(DebugOut (DEBUG_WARN, L"[PORTEMU] IOCTL_BLUETOOTH_GET_PEER_DEVICE : ERROR_INVALID_PARAMETER\n"));
            iRes = ERROR_INVALID_PARAMETER;
            break;
        }

        __try {
            *(BT_ADDR *)lpOutBuf = SET_NAP_SAP (pContext->b.NAP, pContext->b.SAP);
            *lpBytesReturned = sizeof(BT_ADDR);
        } __except (1) {
            IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] IOCTL_BLUETOOTH_GET_PEER_DEVICE : ERROR_INVALID_USER_BUFFER\n"));
            iRes = ERROR_INVALID_USER_BUFFER;
            break;
        }

        iRes = ERROR_SUCCESS;
        break;
    }

    IFDBG(DebugOut (DEBUG_RFCOMM_TRACE, L"[PORTEMU] COM_IOControl 0x%08x : result %d\n", dwData, iRes));

    gpPORTEMU->Unlock ();

    if (iRes != ERROR_SUCCESS) {
        SetLastError (iRes);
        return FALSE;
    }

    return TRUE;
}


//
//      Driver support
//
int portemu_InitializeOnce (void) {
    SVSUTIL_ASSERT (! gpPORTEMU);
    gpPORTEMU = new PORTEMU;

    if (! gpPORTEMU)
        return ERROR_OUTOFMEMORY;

    gpPORTEMU->iDefaultMTUMax = PORTEMU_MTUMAX;
    gpPORTEMU->iDefaultMTUMin = PORTEMU_MTUMIN;
    gpPORTEMU->iDefaultRecvQuota = PORTEMU_RECVMAX;
    gpPORTEMU->iDefaultSendQuota = PORTEMU_SENDMAX;

    return ERROR_SUCCESS;
}

int portemu_CreateDriverInstance (void) {
    IFDBG(DebugOut (DEBUG_RFCOMM_INIT, L"[PORTEMU] portemu_CreateDriverInstance\n"));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_CreateDriverInstance ERROR_SERVICE_DISABLED\n"));
        return ERROR_SERVICE_DISABLED;
    }

    gpPORTEMU->Lock ();
    if (gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_CreateDriverInstance ERROR_SERVICE_ALREADY_RUNNING\n"));
        gpPORTEMU->Unlock ();
        return ERROR_SERVICE_ALREADY_RUNNING;
    }

    gpPORTEMU->pfmdPBL = svsutil_AllocFixedMemDescr (sizeof(BD_BUFFER_LIST), 10);
    gpPORTEMU->pfmdCalls = svsutil_AllocFixedMemDescr (sizeof(ECall), 10);

    if ((! gpPORTEMU->pfmdPBL) || (! gpPORTEMU->pfmdCalls)) {
        if (gpPORTEMU->pfmdPBL)
            svsutil_ReleaseFixedEmpty (gpPORTEMU->pfmdPBL);

        if (gpPORTEMU->pfmdCalls)
            svsutil_ReleaseFixedEmpty (gpPORTEMU->pfmdCalls);

        gpPORTEMU->pfmdPBL = NULL;
        gpPORTEMU->pfmdCalls = NULL;

        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_CreateDriverInstance ERROR_OUTOFMEMORY\n"));
        gpPORTEMU->Unlock ();
        return ERROR_OUTOFMEMORY;
    }

    SDP_EVENT_INDICATION sdpEI;
    sdpEI.sdp_StackEvent       = NULL;


    SDP_CALLBACKS sdpC;
    memset (&sdpC, 0, sizeof(sdpC));

    sdpC.sdp_Connect_Out         = rfcommsdp_connect_out;
    sdpC.sdp_Disconnect_Out          = rfcommsdp_disconnect_out;
    sdpC.sdp_ServiceAttributeSearch_Out  = rfcommsdp_service_attribute_search_out;

    int iRes = SDP_EstablishDeviceContext(gpPORTEMU, &sdpEI, &sdpC, &gpPORTEMU->sdp_if,&gpPORTEMU->hSDP);

    if (!(iRes == ERROR_SUCCESS && gpPORTEMU->sdp_if.sdp_Connect_In && gpPORTEMU->sdp_if.sdp_Disconnect_In && gpPORTEMU->sdp_if.sdp_ServiceAttributeSearch_In)) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_CreateDriverInstance::Inadequate SDP support, error %d\n", iRes));
        SDP_CloseDeviceContext(gpPORTEMU->hSDP);
        gpPORTEMU->hSDP = NULL;

        svsutil_ReleaseFixedEmpty (gpPORTEMU->pfmdPBL);
        svsutil_ReleaseFixedEmpty (gpPORTEMU->pfmdCalls);
        gpPORTEMU->pfmdPBL = NULL;
        gpPORTEMU->pfmdCalls = NULL;

        gpPORTEMU->Unlock ();
        return (iRes == ERROR_SUCCESS) ? ERROR_INTERNAL_ERROR : iRes;
    }

    SVSUTIL_ASSERT(gpPORTEMU->hSDP);

    gpPORTEMU->fInitialized = TRUE;
    IFDBG(DebugOut (DEBUG_RFCOMM_INIT, L"[PORTEMU] portemu_CreateDriverInstance ERROR_SUCCESS\n"));
    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

int portemu_CloseDriverInstance (void) {
    IFDBG(DebugOut (DEBUG_RFCOMM_INIT, L"[PORTEMU] portemu_CloseDriverInstance\n"));

    if (! gpPORTEMU) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_CloseDriverInstance ERROR_SERVICE_DISABLED\n"));
        return ERROR_SERVICE_DISABLED;
    }

    gpPORTEMU->Lock ();
    if (! gpPORTEMU->fInitialized) {
        IFDBG(DebugOut (DEBUG_ERROR, L"[PORTEMU] portemu_CloseDriverInstance ERROR_SERVICE_DISABLED\n"));
        gpPORTEMU->Unlock ();
        return ERROR_SERVICE_DISABLED;
    }

    if (gpPORTEMU->hSDP) {
        SDP_CloseDeviceContext(gpPORTEMU->hSDP);
        gpPORTEMU->hSDP = NULL;
        memset(&gpPORTEMU->sdp_if, 0, sizeof(gpPORTEMU->sdp_if));
    }

    gpPORTEMU->fInitialized = FALSE;

    while (gpPORTEMU->pPorts) {
        PORTEMU_CONTEXT *pThis = gpPORTEMU->pPorts;
        gpPORTEMU->pPorts = gpPORTEMU->pPorts->pNext;

        delete pThis;
    }

    if (gpPORTEMU->pfmdPBL)
        svsutil_ReleaseFixedEmpty (gpPORTEMU->pfmdPBL);

    if (gpPORTEMU->pfmdCalls)
        svsutil_ReleaseFixedEmpty (gpPORTEMU->pfmdCalls);

    gpPORTEMU->pfmdPBL = NULL;
    gpPORTEMU->pfmdCalls = NULL;

    IFDBG(DebugOut (DEBUG_RFCOMM_INIT, L"[PORTEMU] portemu_CloseDriverInstance ERROR_SUCCESS\n"));
    gpPORTEMU->Unlock ();

    return ERROR_SUCCESS;
}

int portemu_UninitializeOnce (void) {
    SVSUTIL_ASSERT (! gpPORTEMU->pPorts);

    delete gpPORTEMU;

    return ERROR_SUCCESS;
}

void *BD_BUFFER_LIST::operator new (size_t size) {
    SVSUTIL_ASSERT (size == sizeof(BD_BUFFER_LIST));
    return svsutil_GetFixed (gpPORTEMU->pfmdPBL);
}

void BD_BUFFER_LIST::operator delete (void *ptr) {
    svsutil_FreeFixed (ptr, gpPORTEMU->pfmdPBL);
}

void *ECall::operator new (size_t size) {
    SVSUTIL_ASSERT (size == sizeof(ECall));
    return svsutil_GetFixed (gpPORTEMU->pfmdCalls);
}

void ECall::operator delete (void *ptr) {
    svsutil_FreeFixed (ptr, gpPORTEMU->pfmdCalls);
}


//
//      BT Serial Port emulation
//

extern "C" BOOL BSP_IOControl (DWORD dwData,
                                    DWORD dwIoControlCode,
                                    LPVOID lpInBuf,
                                    DWORD nInBufSize,
                                    LPVOID lpOutBuf,
                                    DWORD nOutBufSize,
                                    LPDWORD lpBytesReturned) {
    return COM_IOControl (dwData, dwIoControlCode, lpInBuf, nInBufSize, lpOutBuf, nOutBufSize, lpBytesReturned);
}

extern "C" DWORD BSP_Init (LPCTSTR pRegContext, LPCVOID lpvBusContext) {
    return COM_Init (pRegContext, lpvBusContext);
}

extern "C" BOOL BSP_Deinit (DWORD dwData) {
    return COM_Deinit (dwData);
}

extern "C" DWORD BSP_Open (DWORD dwData, DWORD dwAccess, DWORD dwShareMode) {
    return COM_Open (dwData, dwAccess, dwShareMode);
}

extern "C" BOOL BSP_Close (DWORD dwData)  {
    return COM_Close (dwData);
}

extern "C" DWORD BSP_Write (DWORD dwData, LPCVOID pInBuf, DWORD dwInLen) {
    return COM_Write (dwData, pInBuf, dwInLen);
}

extern "C" DWORD BSP_Read (DWORD dwData, LPVOID pBuf, DWORD Len) {
    return COM_Read (dwData, pBuf, Len);
}

extern "C" DWORD BSP_Seek (DWORD dwData, long pos, DWORD type) {
    return COM_Seek (dwData, pos, type);
}



//
//      Console output
//
#if defined (BTH_CONSOLE)

int portemu_ProcessConsoleCommand (WCHAR *pszCommand) {
    if (! gpPORTEMU) {
        DebugOut (DEBUG_OUTPUT, L"RFCOMM help : service not initialized\n");
        return ERROR_SUCCESS;
    }

    gpPORTEMU->Lock ();
    int iRes = ERROR_SUCCESS;
    __try {
        if (wcsicmp (pszCommand, L"help") == 0) {
            DebugOut (DEBUG_OUTPUT, L"PORTEMU Commands:\n");
            DebugOut (DEBUG_OUTPUT, L"    help    prints this text\n");
            DebugOut (DEBUG_OUTPUT, L"    global      dumps global state\n");
            DebugOut (DEBUG_OUTPUT, L"    ports       dumps currently installed PORTEMU clients\n");
        } else if (wcsicmp (pszCommand, L"global") == 0) {
            DebugOut (DEBUG_OUTPUT, L"Stage :         %sinitialized\n", gpPORTEMU->fInitialized ? L"" : L"un");
            DebugOut (DEBUG_OUTPUT, L"Buffer list descr : 0x%08x\n", gpPORTEMU->pfmdPBL);
            DebugOut (DEBUG_OUTPUT, L"Calls descr :       0x%08x\n", gpPORTEMU->pfmdCalls);
            DebugOut (DEBUG_OUTPUT, L"PIO blocks used :   %d\n", gpPORTEMU->iPIOBlocksUsed);
            PORTEMU_CONTEXT     *pPorts = gpPORTEMU->pPorts;
            int iCount = 0;
            while (pPorts) {
                ++iCount;
                pPorts = pPorts->pNext;
            }
            DebugOut (DEBUG_OUTPUT, L"Clients installed : %d\n", iCount);
            DebugOut (DEBUG_OUTPUT, L"Default min MTU :   %d\n", gpPORTEMU->iDefaultMTUMin);
            DebugOut (DEBUG_OUTPUT, L"Default max MTU :   %d\n", gpPORTEMU->iDefaultMTUMax);
            DebugOut (DEBUG_OUTPUT, L"Default recv q :    %d\n", gpPORTEMU->iDefaultRecvQuota);
            DebugOut (DEBUG_OUTPUT, L"Default send q :    %d\n", gpPORTEMU->iDefaultSendQuota);
        } else if (wcsicmp (pszCommand, L"ports") == 0) {
            PORTEMU_CONTEXT *pPort = gpPORTEMU->pPorts;
            while (pPort) {
                DebugOut (DEBUG_OUTPUT, L"Client :        0x%08x\n", pPort);
                DebugOut (DEBUG_OUTPUT, L"Channel  :          0x%02x\n", pPort->channel);
                DebugOut (DEBUG_OUTPUT, L"Target address :    %04x%08x\n", pPort->b.NAP, pPort->b.SAP);
                DebugOut (DEBUG_OUTPUT, L"Local :         %s\n", pPort->fLocal ? L"yes" : L"no");
                DebugOut (DEBUG_OUTPUT, L"Local Open  :       %s\n", pPort->fLocalOpen ? L"yes" : L"no");
                DebugOut (DEBUG_OUTPUT, L"SDP in progress  :      %s\n", pPort->fSdpQueryOn ? L"yes" : L"no");
                DebugOut (DEBUG_OUTPUT, L"XON Sent :          %s\n", pPort->fSentXon ? L"yes" : L"no");
                DebugOut (DEBUG_OUTPUT, L"Conn handle :       0x%08x\n", pPort->hConnection);
                DebugOut (DEBUG_OUTPUT, L"RFCOMM handle :     0x%08x\n", pPort->hRFCOMM);
                DebugOut (DEBUG_OUTPUT, L"Dev head :          %d\n", pPort->iDeviceHead);
                DebugOut (DEBUG_OUTPUT, L"Dev trail :         %d\n", pPort->iDeviceTrail);
                DebugOut (DEBUG_OUTPUT, L"MTU :           %d\n", pPort->iMTU);
                DebugOut (DEBUG_OUTPUT, L"Min MTU :           %d\n", pPort->iMinMTU);
                DebugOut (DEBUG_OUTPUT, L"Max MTU :           %d\n", pPort->iMaxMTU);
                DebugOut (DEBUG_OUTPUT, L"Send Quota :        %d\n", pPort->iSendQuota);
                DebugOut (DEBUG_OUTPUT, L"Send Quota Used :       %d\n", pPort->iSendQuotaUsed);
                DebugOut (DEBUG_OUTPUT, L"Recv Quota :        %d\n", pPort->iRecvQuota);
                DebugOut (DEBUG_OUTPUT, L"Recv Quota Used :       %d\n", pPort->iRecvQuotaUsed);
                for (int i = 0 ; i < PORTEMU_OPEN_MAX ; ++i) {
                    DebugOut (DEBUG_OUTPUT, L"Port %d :           %s\n", i, (pPort->uiOpenRef & (1 << i)) ? L"open" : L"closed");
                    DebugOut (DEBUG_OUTPUT, L"EVENT mask[%d] :    0x%08x\n", i, pPort->adwEnabledEvents[i]);
                    DebugOut (DEBUG_OUTPUT, L"EVENTS %d :         0x%08x\n", pPort->adwOccuredEvents[i]);
                    DebugOut (DEBUG_OUTPUT, L"ERR %d :        0x%08x\n", pPort->adwErr[i]);
                    DebugOut (DEBUG_OUTPUT, L"Owner :         0x%08x\n", pPort->hOwnerProc[i]);
                }
                DebugOut (DEBUG_OUTPUT, L"MODEM IN:           0x%08x\n", pPort->dwModemStatusIn);
                DebugOut (DEBUG_OUTPUT, L"v24 OUT:        0x%02x\n", pPort->ucv24out);
                DebugOut (DEBUG_OUTPUT, L"FLOW:           %s\n", pPort->fc ? L"off" : L"on");
                DebugOut (DEBUG_OUTPUT, L"FLOW AGGREGATE:     %s\n", pPort->fc_aggregate ? L"off" : L"on");
                DebugOut (DEBUG_OUTPUT, L"CREDIT FLOW:        %s\n", pPort->credit_fc ? L"on" : L"off");
                DebugOut (DEBUG_OUTPUT, L"HAVE CREDITS:       %s\n", pPort->iHaveCredits);
                DebugOut (DEBUG_OUTPUT, L"GAVE CREDITS:       %s\n", pPort->iGaveCredits);
                DebugOut (DEBUG_OUTPUT, L"CT/O ReadIntervalTimeout     %d\n", pPort->ct.ReadIntervalTimeout);
                DebugOut (DEBUG_OUTPUT, L"CT/O ReadTotalTimeoutConstant    %d\n", pPort->ct.ReadTotalTimeoutConstant);
                DebugOut (DEBUG_OUTPUT, L"CT/O ReadTotalTimeoutMultiplier  %d\n", pPort->ct.ReadTotalTimeoutMultiplier);
                DebugOut (DEBUG_OUTPUT, L"CT/O WriteTotalTimeoutConstant   %d\n", pPort->ct.WriteTotalTimeoutConstant);
                DebugOut (DEBUG_OUTPUT, L"CT/O WriteTotalTimeoutMultiplier %d\n", pPort->ct.WriteTotalTimeoutMultiplier);
                DebugOut (DEBUG_OUTPUT, L"Ops:\n");
                PendingIO *pio = pPort->pops;
                while (pio) {
                    DebugOut (DEBUG_OUTPUT, L"    Op %s Buf size %d Buf used %d Buf 0x%08x Evt 0x%08x Err 0x%08x\n",
                        pio->op == COMM_OPEN ? L"Open" : (pio->op == COMM_WAIT_EVENT ? L"WaitEvent" : (pio->op == COMM_READ ? L"Read" : (pio->op == COMM_WRITE ? L"Write" : L"Unknown"))),
                        pio->cbuffer, pio->cbuffer_used, pio->buffer, pio->dwEvent, pio->dwLineError);
                    pio = pio->pNext;
                }

                DebugOut (DEBUG_OUTPUT, L"Buffers:\n");
                BD_BUFFER_LIST *pbl = pPort->pbl;
                while (pbl) {
                    DebugOut (DEBUG_OUTPUT, L"    Size %d Head %d Trail %d Total %d\n", pbl->pb->cSize, pbl->pb->cStart, pbl->pb->cEnd, BufferTotal (pbl->pb));
                    pbl = pbl->pNext;
                }

                DebugOut (DEBUG_OUTPUT, L"Calls:\n");
                ECall *pCalls = pPort->pCalls;
                while (pCalls) {
                    DebugOut (DEBUG_OUTPUT, L"    Op %s Owner 0x%08x Bytes %d\n",
                        (pCalls->fWhat == RFCOMM_CONNECT ? L"Connect" : (pCalls->fWhat == RFCOMM_WRITE ? L"Write" : (pCalls->fWhat == RFCOMM_SDP ? L"SDP Query" : (pCalls->fWhat == RFCOMM_PN ? L"PN request" : L"Unknown")))),
                        pCalls->pContext, pCalls->cBytes);
                    pCalls = pCalls->pNext;
                }

                DebugOut (DEBUG_OUTPUT, L"\n\n");
                pPort = pPort->pNext;
            }

        } else
            iRes = ERROR_UNKNOWN_FEATURE;
    } __except (1) {
        DebugOut (DEBUG_ERROR, L"Exception in PORTEMU dump!\n");
    }

    gpPORTEMU->Unlock ();

    return iRes;
}

#endif // BTH_CONSOLE
