//
// 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.
//
#include "common.h"
#include "IRDATransportSocket.h"
#include "ObexTransportConnection.h"
#include "ObexIRDATransport.h"

#include "ObexStrings.h"

/******globals******/
    
    extern CRITICAL_SECTION g_TransportSocketCS;
    static IRDA_CONNECTION_HOLDER *g_pConnectionList = 0;


UINT g_uiIRDAMaxRetries;

 
CIRDATransportSocket::CIRDATransportSocket():
    _refCount(1)   
{
    DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE,(L"CIRDATransportSocket::CIRDATransportSocket()\n")); 

    //initilize the socket libs
    WSADATA wsd;
    WSAStartup (MAKEWORD(2,2), &wsd); 
}

CIRDATransportSocket::~CIRDATransportSocket()
{
    DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE,(L"CIRDATransportSocket::~CIRDATransportSocket()\n"));
    Close();  

    //clean up winsock
    WSACleanup();
 }

HRESULT STDMETHODCALLTYPE
CIRDATransportSocket::Close()
{
    DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE,(L"CIRDATransportSocket::Close()\n")); 
    
    CCritSection TransportCS(&g_TransportSocketCS);
    TransportCS.Lock();
    
    HRESULT hRes = E_FAIL;

    
    IRDA_CONNECTION_HOLDER *pTemp = 0;
    IRDA_CONNECTION_HOLDER *pPrev = 0;
   
    if(g_pConnectionList)
    {
        pTemp = g_pConnectionList->pNext;
        pPrev = g_pConnectionList;
    }
    else
    {    
        return S_OK;
    }

    if(pPrev)
    {
        //its okay that we dont free up the memory pointed to here!
        // actually we CANT because we dont own a reference to it
        // to prevent a loop of refcounts
        if(memcmp(pPrev->DeviceID, connectionID, 4) == 0)
        {
            IRDA_CONNECTION_HOLDER *pDel = pPrev;  
            g_pConnectionList = pPrev->pNext;
            delete pDel;            
            pTemp = 0; 
            hRes = S_OK;
        }
    }

    while(pTemp && pTemp->pNext)
    {      
        //its okay that we dont free up the memory pointed to here!
        // actually we CANT because we dont own a reference to it
        // to prevent a loop of refcounts
        if(memcmp(pTemp->pNext->DeviceID, connectionID, 4) == 0)
        {
            IRDA_CONNECTION_HOLDER *pDel = pTemp;
            pPrev->pNext = pTemp->pNext;            
            
            delete pDel;
            hRes = S_OK;
            break;
        }

        pPrev = pTemp;
        pTemp = pTemp->pNext;      
    }   
    return hRes;   
}

HRESULT STDMETHODCALLTYPE
CIRDATransportSocket::Listen(LPTRANSPORTCONNECTION *ppConnection)
{
    DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE,(L"CIRDATransportSocket::Listen()\n")); 
    return E_NOTIMPL;
}

int 
CIRDATransportSocket::reenum_connect(IObexAbortTransportEnumeration *pAbortEnum, SOCKET s,const SOCKADDR_IRDA *name, int namelen, char *pDeviceName)
{
    SVSUTIL_ASSERT(name);
    
    int returnValue = SOCKET_ERROR;
    
    //retry connecting (seems that the the server will quit listening if the 
    //  user delays for about 6 seconds)... do this by enuming
    //  the IRDA devices looking for ours
    for ( UINT uiReTries=0; uiReTries < g_uiIRDAMaxRetries; uiReTries ++)
    {
        // This is to ensure that the server is ready to receive.
        SOCKET pollSock = socket(AF_IRDA, SOCK_STREAM, 0);
        if(INVALID_SOCKET != pollSock) 
        {        
            //memory to hold possible servers                
            unsigned char   DevListBuff[sizeof(DEVICELIST) -
                sizeof(IRDA_DEVICE_INFO) +
                (sizeof(IRDA_DEVICE_INFO) * DEVICE_LIST_LEN)];

            int DevListLen   = sizeof(DevListBuff);
            PDEVICELIST   pDevList   = (PDEVICELIST) DevListBuff;    
            
            pDevList->numDevice = 0;
    
            for(UINT uiTries=0; uiTries < g_uiIRDAMaxRetries; uiTries ++)  
            {         
                int optReturn = getsockopt(pollSock, SOL_IRLMP, IRLMP_ENUMDEVICES,(char *) pDevList, &DevListLen);
                
                // if we are told to abort, do so
                if(NULL != pAbortEnum) {
                    BOOL fAbort;
                    pAbortEnum->IsAborting(&fAbort);
                    
                    if(TRUE == fAbort) {
                        closesocket(pollSock);
                        return SOCKET_ERROR;
                    }
                }                 
                 
                //if there is an error see if it is an INPROGRESS one
                //  if so, try again (up to MAX_IRDA_TRIES) if not
                //  something happened that is more serious so bail out
                //  of the function with SOCKET_ERROR
                if(SOCKET_ERROR == optReturn)
                {
                    int errorCode = WSAGetLastError();
                    if(WSAEINPROGRESS == errorCode)
                    {                        
                        Sleep(500);
                        continue;
                    }
                    else
                    {
                        closesocket(pollSock);
                        return SOCKET_ERROR;
                    }                
                }                
                if(pDevList->numDevice != 0)                      
                    break;            
            }

            closesocket(pollSock);

            //if we have devices, check to see if we have OUR device
            //   if not, bail out cause our device walked off
            if ( DevListLen != 0 )
            {
                 for(UINT i=0; i<pDevList->numDevice; i++)
                 {                     
                     SVSUTIL_ASSERT(sizeof(pDevList->Device[i].irdaDeviceName) >= strlen(pDeviceName));
                 
                     if(0 == strcmp(pDevList->Device[i].irdaDeviceName, pDeviceName))
                         {
                             DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX-IRDA] Attempting reconnect\n"));

                            //patch up the device ID 
                              SVSUTIL_ASSERT(4 == sizeof(pDevList->Device[i].irdaDeviceID));
                            memcpy((char *)name->irdaDeviceID, (char *)pDevList->Device[i].irdaDeviceID, 4);                            
                            
                             //reconnect
                             returnValue = connect(s, (const struct sockaddr *) name, namelen);
                            
                             if(SOCKET_ERROR != returnValue)
                             {
                                 DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX] Got a connection!\n"));
                                 return returnValue;
                             }
                             else
                             {
                                 int err = WSAGetLastError();
                                 DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX] Error(%x) connecting... maybe the device isnt in range?  trying again.\n",err)); 
                             }
                             
                         }                         
                 }
            }
        }
        //an error getting an IRDA socket occured...  print an error, but dont
        //  do anything (we will retry)
        else
        {            
            DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX] Error getting an IRDA socket, trying again\n"));     
        }
    }    
    return returnValue;
}

HRESULT STDMETHODCALLTYPE
CIRDATransportSocket::Connect(LPPROPERTYBAG2 pDeviceProps,DWORD dwCapability, LPTRANSPORTCONNECTION * ppConnection)
{
    DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE,(L"CIRDATransportSocket::Connect()\n"));
    CCritSection TransportCS(&g_TransportSocketCS);
    TransportCS.Lock();
    
    PREFAST_ASSERT(pDeviceProps);
    *ppConnection = NULL;

    //set the default error to return
    HRESULT hRet = E_FAIL;

    //variables 
    SOCKET sMySock = INVALID_SOCKET;
    IRDA_CONNECTION_HOLDER *pTemp = NULL;
    
    //fetch the device ID from the property bag
    VARIANT pDeviceVar;
    VARIANT pNameVar;
    VARIANT pPortVar;
    VariantInit(&pDeviceVar);
    VariantInit(&pNameVar);
    VariantInit(&pPortVar);

    //place to hold the port (if the port is NULL try the default ports
    //  of OBEX:IrXfer and then OBEX
    char cPortName[25];
    BOOL fFoundPortName = FALSE;
    char pDeviceName[25];
    
    if(FAILED(pDeviceProps->Read(c_szDevicePropAddress, &pDeviceVar, 0)))
    {
        hRet = E_FAIL;
        goto connect_done;
    }
    if(FAILED(pDeviceProps->Read(c_szDevicePropName, &pNameVar, 0)) || VT_BSTR != V_VT(&pNameVar))
    {
        hRet = E_FAIL;
        goto connect_done;
    }
    //also get the wide char version
    else
    {
        wcstombs(pDeviceName, pNameVar.bstrVal, 25);            
        DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX] ----- using device name %s\n", pNameVar.bstrVal));
    }

    //read the port and convert from a wide char (BSTR)
    if(!FAILED(pDeviceProps->Read(c_szPort, &pPortVar, 0)))
    {
        wcstombs(cPortName, pPortVar.bstrVal, 25);   
        fFoundPortName = TRUE;
        DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX] ----- using port name %s\n", pPortVar.bstrVal));
    }

    if(!(VT_I4 == V_VT(&pDeviceVar) &&
          VT_BSTR == V_VT(&pPortVar)))
    {
        DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX] ----- incorrect var type!\n"));
        hRet = E_FAIL;
        goto connect_done;
    }
    
   
    //figure out if we already have this guy allocated...
    pTemp = g_pConnectionList;

    //if we DO have it allocated, this loop will return an instance
    while(pTemp)
    {
        if(0 == memcmp(pTemp->DeviceID, (char *)&pDeviceVar.lVal, 4))
        {
            //get a copy of the id for our records
            memcpy(connectionID, pTemp->DeviceID, 4);

            DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX - irda] ----->  reusing connection!\n"));
            
            (*ppConnection) = pTemp->pItem;  
            (*ppConnection)->AddRef();            

            hRet = S_OK;
            goto connect_done;
        }
        pTemp = pTemp->pNext;   
    }
 
    //since we DONT have the instance, make one create a socket
    sMySock = socket(AF_IRDA, SOCK_STREAM, 0);
    DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX - irda] -->Created Socket\n"));

    if(INVALID_SOCKET == sMySock) 
    {
        SetLastError (ERROR_DEVICE_NOT_CONNECTED);        
        hRet = E_FAIL;
        goto connect_done;
    }  
    
    //information on the IrDA to connect to
    SOCKADDR_IRDA    DestSockAddr; 
    memset(&DestSockAddr, 0, sizeof(SOCKADDR_IRDA));
    
    //move in the appropriate values (telling the service name and address family)
    DestSockAddr.irdaAddressFamily = AF_IRDA;
        
    // move in the device id for the selected device 
    memcpy(&DestSockAddr.irdaDeviceID[0], (char *)&pDeviceVar.lVal, 4);

    //get a copy of the id for our records
    memcpy(connectionID, (char *)&pDeviceVar.lVal, 4);


    /*Connect up to the server-----------------------------------------------*/
    if(fFoundPortName)
    {        
        //copy the name and begin the connection
        strcpy(DestSockAddr.irdaServiceName, cPortName);
        
        //connect to that device   
        if (SOCKET_ERROR == reenum_connect(NULL, sMySock, &DestSockAddr,
            sizeof(SOCKADDR_IRDA),pDeviceName)) 
        {    
            closesocket(sMySock);            
            DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX - irda] Error connecting: %d\n", WSAGetLastError()));

            hRet = E_FAIL;
            goto connect_done;                       
        }    
    }
    else
    {
        //its okay to use strncpy because we've already 0'ed out the struct
        strncpy(DestSockAddr.irdaServiceName, "OBEX", 4);
    
        //connect to that device   
        if (SOCKET_ERROR == reenum_connect(NULL, sMySock, &DestSockAddr,
            sizeof(SOCKADDR_IRDA),pDeviceName)) 
        {        
            //its okay to use strncpy because we've already 0'ed out the struct
            strncpy(DestSockAddr.irdaServiceName, "OBEX:IrXfer", 11);
        
            //connect to that device   
            if (SOCKET_ERROR == reenum_connect(NULL, sMySock, &DestSockAddr,
                sizeof(SOCKADDR_IRDA),pDeviceName)) {
                closesocket(sMySock);                
                DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE, (L"[OBEX - irda] Error connecting: %d\n", WSAGetLastError()));
                
                hRet = E_FAIL;            
                goto connect_done;
            }       
        }
    }   
    /*------------------------------------------------------------------------*/

    
    
    //if we are here, we dont have a transport connecction, so make one
    *ppConnection = new CObexTransportConnection(this, pNameVar.bstrVal, sMySock);  
    if( !(*ppConnection) )
    {
         hRet = E_OUTOFMEMORY;
         goto connect_done;
    }
	
    //create and insert a new connection holder
    pTemp = new IRDA_CONNECTION_HOLDER();
    if( !pTemp )
    {
         delete *ppConnection;
         *ppConnection = NULL;
	  
         hRet = E_OUTOFMEMORY;
         goto connect_done;
    }
    pTemp->pItem = *ppConnection;
   
    memcpy(pTemp->DeviceID, (char *)&pDeviceVar.lVal, 4);
    pTemp->pNext = g_pConnectionList;
    g_pConnectionList = pTemp;
    
    hRet = S_OK;

    connect_done:
        VariantClear(&pDeviceVar);
        VariantClear(&pNameVar);
        VariantClear(&pPortVar);
        
    return hRet; 
}

HRESULT STDMETHODCALLTYPE 
CIRDATransportSocket::GetProperties(LPPROPERTYBAG2 * ppListenProps)
{
    return E_NOTIMPL;
}

ULONG STDMETHODCALLTYPE 
CIRDATransportSocket::AddRef() 
{
    DEBUGMSG(OBEX_ADDREFREL_ZONE,(L"CIRDATransportSocket::AddRef()\n"));
    return InterlockedIncrement((LONG *)&_refCount);
}

ULONG STDMETHODCALLTYPE 
CIRDATransportSocket::Release() 
{
    DEBUGMSG(OBEX_ADDREFREL_ZONE,(L"CIRDATransportSocket::Release()\n"));
    SVSUTIL_ASSERT(_refCount != 0xFFFFFFFF);
    ULONG ret = InterlockedDecrement((LONG *)&_refCount);    
    if(!ret) 
        delete this; 
    return ret;
}

HRESULT STDMETHODCALLTYPE 
CIRDATransportSocket::QueryInterface(REFIID riid, void** ppv) 
{
    DEBUGMSG(OBEX_TRANSPORTSOCKET_ZONE,(L"CIRDATransportSocket::QueryInterface()\n"));
    if(!ppv) 
        return E_POINTER;
    if(riid == IID_IPropertyBag) 
        *ppv = static_cast<IObexTransportSocket*>(this);    
    else 
        return *ppv = 0, E_NOINTERFACE;
    
    return AddRef(), S_OK;    
}




























