/*==========================================================================
 *
 *  Copyright (C) 1995 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       winsock.c
 *  Content:    windows socket support for dpsp
 *  History:
 *  Date        By        Reason
 *  ====        ==        ======
 *    3/15/96        andyco    created it
 *    4/12/96        andyco    got rid of dpmess.h! use DPlay_ instead of message macros
 *    4/18/96        andyco    added multihomed support, started ipx
 *    4/25/96        andyco    messages now have blobs (sockaddr's) instead of dwReserveds  
 *    5/31/96        andyco    all non-system players share a socket (gsStream and 
 *                        gsDGramSocket).
 *    7/18/96        andyco    added dphelp for server socket
 *    8/1/96        andyco    no retry on connect failure
 *    8/15/96        andyco    local + remote data    - killthread
 *    8/30/96        andyco    clean it up b4 you shut it down! added globaldata.
 *    9/4/96        andyco    took out bye_bye message
 *    12/18/96    andyco    de-threading - use a fixed # of prealloced threads.
 *                        cruised the enum socket / thread - use the system
 *                        socket / thread instead
 *    3/17/97        kipo    rewrote server dialog code to not use global variable
 *                        to return the address and to return any errors getting
 *                        the address, especially DPERR_USERCANCEL
 *    5/12/97        kipo    the server address string is now stored in the globals
 *                        at SPInit and resolved when you do EnumSessions so we
 *                        will return any errors at that time instead of popping
 *                        the dialog again. Fixes bug #5866
 *    11/19/97    myronth    Changed LB_SETCURSEL to CB_SETCURSEL (#12711)
 *    01/27/98    sohaim    added firewall support.
 *  02/13/98    aarono  added async support.
 *   2/18/98   a-peterz Comment byte order for address and port params (CreateSocket)
 *   6/19/98    aarono  turned on keepalive on reliable sockets.  If we
 *                      don't do this we can hang if the send target crashes
 *                      while in a low buffer (i.e. no buffer) state.
 *    7/9/99    aarono  Cleaning up GetLastError misuse, must call right away,
 *                      before calling anything else, including DPF.
 ***************************************************************************/

#include "dpsp.h"

// backlog for listen() api.  no constant in winsock, so we ask for the moon
#define LISTEN_BACKLOG 60

// how long to wait, in ms, til we abort a blocking WinSock connect() call
#define CONNECT_WATCHER_TIMEOUT        15000

/*
 ** CreateSocket
 *
 *  CALLED BY: all over
 *
 *  PARAMETERS:
 *        pgd - pointer to a global data
 *        psock - new socket. return value.
 *        type - stream or datagram
 *        port - what port we bind to (host byte order)
 *        address - what address to use (net byte order)
 *        *perr - set to the last socket error if fn fails
 *        bInRange - use reserved range of ports
 *
 *  DESCRIPTION:
 *        creates a new socket.  binds to port specified, at the address specified
 *
 *  RETURNS: DP_OK or E_FAIL. if E_FAIL, *perr is set with socket error code (see winsock.h)
 *
 */

HRESULT FAR PASCAL CreateSocket(LPGLOBALDATA pgd,SOCKET * psock,INT type,WORD wApplicationPort,const SOCKADDR_IN6 * psockaddr, 
    SOCKERR * perr,BOOL bInRange)
{
    SOCKET  sNew;
    SOCKADDR_IN6 sockAddr;
    int bTrue = TRUE;
    int protocol = 0;
    BOOL bBroadcast = FALSE;
    WORD wPort;
    BOOL bBound = FALSE;

    *psock = INVALID_SOCKET; // in case we bail

    //  Create the socket.

    sNew = socket( pgd->AddressFamily, type, protocol);
       
    if (INVALID_SOCKET == sNew) 
    {
        // no cleanup needed, just bail
        *perr = WSAGetLastError();
        return E_FAIL;
    }

    //  try to bind an address to the socket.
    // set up the sockaddr
    memset(&sockAddr,0,sizeof(sockAddr));
    if ((SOCK_STREAM == type))
    {
        BOOL bTrue = TRUE;
        UINT err;
        
        // turn ON keepalive
        if (SOCKET_ERROR == setsockopt(sNew, SOL_SOCKET, SO_KEEPALIVE, (CHAR FAR *)&bTrue, sizeof(bTrue)))
        {
            err = WSAGetLastError();
            DPF(0,"Failed to turn ON keepalive - continue : err = %d\n",err);
        }

        ASSERT(bTrue);
        
        // turn off nagling
        if(pgd->dwSessionFlags & DPSESSION_OPTIMIZELATENCY) 
        {

            DPF(5, "Turning nagling off on socket");
            if (SOCKET_ERROR == setsockopt(sNew, IPPROTO_TCP, TCP_NODELAY, (CHAR FAR *)&bTrue, sizeof(bTrue)))
            {
                err = WSAGetLastError();
                DPF(0,"Failed to turn off naggling - continue : err = %d\n",err);
            }
        }
    }

    sockAddr = *psockaddr;
    sockAddr.sin6_port = htons(wApplicationPort);
    if (bInRange && !wApplicationPort)
    {
        USHORT rndoffset;
        DPF(5, "Application didn't specify a port - using dplay range");

        rndoffset=(USHORT)(GetTickCount()%DPSP_NUM_PORTS);
        wPort = DPSP_MIN_PORT+rndoffset;
        do 
        {
            DPF(5, "Trying to bind to port %d",wPort);
            sockAddr.sin6_port = htons(wPort);
            
            // do the bind
            if( SOCKET_ERROR != bind( sNew, (LPSOCKADDR)&sockAddr, sizeof(sockAddr) ) )
            {
                bBound = TRUE;
                DPF(5, "Successfully bound to port %d", wPort);                    
            }
            else
            {
                if(++wPort > DPSP_MAX_PORT){
                    wPort=DPSP_MIN_PORT;
                }
            }    
        }
        while (!bBound && (wPort != DPSP_MIN_PORT+rndoffset));
    }

    // do the bind
    if( !bBound && (SOCKET_ERROR == bind( sNew, (LPSOCKADDR)&sockAddr, sizeof(sockAddr))) )
    {
        goto ERROR_EXIT;
    }
    
    // success!
    *psock = sNew;

    DEBUGPRINTSOCK(9,"created a new socket (bound) - ",psock);

    return DP_OK;

ERROR_EXIT:
    // clean up and bail
    *perr = WSAGetLastError();
    DPF(0,"create socket failed- err = %d\n",*perr);
    closesocket(sNew);
    return E_FAIL;

}   // CreateSocket

#undef DPF_MODNAME
#define DPF_MODNAME    "KillSocket"

HRESULT KillSocket(SOCKET sSocket,BOOL fStream,BOOL fHard)
{
    UINT err;

    if (INVALID_SOCKET == sSocket) 
    {
        return E_FAIL;
    }

    if (!fStream)
    {
        if (SOCKET_ERROR == closesocket(sSocket)) 
        {
            err = WSAGetLastError();
            DPF(0,"killsocket - dgram close err = %d\n",err);
            return E_FAIL;
        }
    }
    else 
    {
        LINGER Linger;

           if (fHard)
        {
            Linger.l_onoff=TRUE; // turn linger on
            Linger.l_linger=0; // nice small time out

            if( SOCKET_ERROR == setsockopt( sSocket,SOL_SOCKET,SO_LINGER,(char FAR *)&Linger,
                            sizeof(Linger) ) )
            {
                err = WSAGetLastError();
                DPF(0,"killsocket - stream setopt err = %d\n",err);
            }
        }            
        if (SOCKET_ERROR == shutdown(sSocket,2)) 
        {
            // this may well fail, if e.g. no one is using this socket right now...
            // the error would be wsaenotconn 
            err = WSAGetLastError();
            DPF(5,"killsocket - stream shutdown err = %d\n",err);
        }
        if (SOCKET_ERROR == closesocket(sSocket)) 
        {
            err = WSAGetLastError();
            DPF(0,"killsocket - stream close err = %d\n",err);
            return E_FAIL;
        }
    }

    return DP_OK;
    
}// KillSocket

#undef DPF_MODNAME
#define DPF_MODNAME    "CreateAndInitStreamSocket"

// set up a stream socket to receive connections
// used w/ the gGlobalData.sStreamAcceptSocket
HRESULT CreateAndInitStreamSocket(LPGLOBALDATA pgd)
{
    HRESULT hr;
    UINT err;
    LINGER Linger;

    hr = CreateSocket(pgd,&(pgd->sSystemStreamSocket),SOCK_STREAM,pgd->wApplicationPort,&sockaddr_any,&err,TRUE);
    if (FAILED(hr)) 
    {
        DPF(0,"init listen socket failed - err = %d\n",err);
        return hr ;
    }

    // set up socket w/ max listening connections
    err = listen(pgd->sSystemStreamSocket,LISTEN_BACKLOG);
    if (SOCKET_ERROR == err) 
    {
        err = WSAGetLastError();
        DPF(0,"init listen socket / listen error - err = %d\n",err);
        return E_FAIL ;
    }

    // set for hard disconnect
    Linger.l_onoff=1;
    Linger.l_linger=0;
    
    if( SOCKET_ERROR == setsockopt( pgd->sSystemStreamSocket,SOL_SOCKET,SO_LINGER,
        (char FAR *)&Linger,sizeof(Linger) ) )
    {
        err = WSAGetLastError();
        DPF(0,"Delete service socket - stream setopt err = %d\n",err);
    }
    
    DEBUGPRINTSOCK(1,"enum - listening on",&(pgd->sSystemStreamSocket));
    return DP_OK;
    
} // CreateAndInitStreamSocket



#undef DPF_MODNAME
#define DPF_MODNAME    "SPConnect"
// connect socket to sockaddr
HRESULT SPConnect(SOCKET* psSocket, LPSOCKADDR psockaddr,UINT addrlen, BOOL bOutBoundOnly)
{
    UINT err;
    HRESULT hr = DP_OK;
    DWORD dwLastError;
    u_long lNonBlock = 1; // passed to ioctlsocket to make socket non-blocking
    u_long lBlock = 0; // passed to ioctlsocket to make socket blocking again
    fd_set fd_setConnect;
    fd_set fd_setExcept;
    TIMEVAL timevalConnect;

    err=ioctlsocket(*psSocket, FIONBIO, &lNonBlock);    // make socket non-blocking
    if(SOCKET_ERROR == err){
        dwLastError=WSAGetLastError();
        DPF(0,"sp - failed to set socket %d to non-blocking mode err= %d\n", *psSocket, dwLastError);
        return DPERR_CONNECTIONLOST;
    }

    // Start the socket connecting.
    err = connect(*psSocket,psockaddr,addrlen);
    
    if(SOCKET_ERROR == err) {
        dwLastError=WSAGetLastError();
        if(dwLastError != WSAEWOULDBLOCK){
            DPF(0,"sp - connect failed err= %d\n", dwLastError);
            return DPERR_CONNECTIONLOST;
        }
        // we are going to wait for either the connect to succeed (socket to be writeable)
        // or the connect to fail (except fdset bit to be set).  So we init an FDSET with
        // the socket that is connecting and wait.
        FD_ZERO(&fd_setConnect);
        FD_SET(*psSocket, &fd_setConnect);

        FD_ZERO(&fd_setExcept);
        FD_SET(*psSocket, &fd_setExcept);

        timevalConnect.tv_sec=0;
        timevalConnect.tv_usec=CONNECT_WATCHER_TIMEOUT*1000; //msec -> usec
        
        err = select(0, NULL, &fd_setConnect, &fd_setExcept, &timevalConnect);

        // err is the number of sockets with activity or 0 for timeout 
        // or SOCKET_ERROR for error
        
        if(SOCKET_ERROR == err) {
            dwLastError=WSAGetLastError();
            DPF(0,"sp - connect failed err= %d\n", dwLastError);
            return DPERR_CONNECTIONLOST;
        } else if (0==err){
            // timed out
            DPF(0,"Connect timed out on socket %d\n",*psSocket);
            return DPERR_CONNECTIONLOST;
        }

        // Now see if the connect succeeded or the connect got an exception
        if(!(FD_ISSET(*psSocket, &fd_setConnect))){
            DPF(0,"Connect did not succeed on socket %d\n",*psSocket);
            return DPERR_CONNECTIONLOST;
        }
        if(FD_ISSET(*psSocket,&fd_setExcept)){
            DPF(0,"Got exception on socket %d during connect\n",*psSocket);
            return DPERR_CONNECTIONLOST;
        }
    }

    err=ioctlsocket(*psSocket, FIONBIO, &lBlock);    // make socket blocking again

    DEBUGPRINTSOCK(9,"successfully connected socket - ", psSocket);

    if (bOutBoundOnly)
    {
        DEBUGPRINTADDR(5, "Sending reuse connection message to - ",psockaddr);
        // tell receiver to reuse connection
        hr = SendReuseConnectionMessage(*psSocket);
    }

    return hr;

} //SPConnect
    

#undef DPF_MODNAME
#define DPF_MODNAME    "SetPlayerAddress"
// we've created a socket for a player. store its address in the players
// spplayerdata struct.  
HRESULT SetPlayerAddress(LPGLOBALDATA pgd,LPSPPLAYERDATA ppd,SOCKET sSocket,BOOL fStream) 
{
    SOCKADDR_IN6 sockaddr;
    UINT err;
    int iAddrLen = sizeof(sockaddr);

    err = getsockname(sSocket,(LPSOCKADDR)&sockaddr,&iAddrLen);
    if (SOCKET_ERROR == err) 
    {
        err = WSAGetLastError();
        DPF(0,"setplayeraddress - getsockname - err = %d\n",err);
        closesocket(sSocket);
        return E_FAIL;
    } 

    if (fStream) 
    {
        ZeroMemory(STREAM_PSOCKADDR(ppd), sizeof(SOCKADDR_IN6));
        STREAM_PSOCKADDR(ppd)->sin6_family = AF_INET6;
        IP_STREAM_PORT(ppd) = sockaddr.sin6_port;
        // we don't know the address of the local player (multihomed!)
    } // stream
    else 
    {
        ZeroMemory(DGRAM_PSOCKADDR(ppd), sizeof(SOCKADDR_IN6));
        DGRAM_PSOCKADDR(ppd)->sin6_family = AF_INET6;
        IP_DGRAM_PORT(ppd) = sockaddr.sin6_port;
        // we don't know the address of the local player (multihomed!)
    } // dgram

    return DP_OK;    
} // SetPlayerAddress

#undef DPF_MODNAME
#define DPF_MODNAME    "CreatePlayerSocket"

HRESULT CreatePlayerDgramSocket(LPGLOBALDATA pgd,LPSPPLAYERDATA ppd,DWORD dwFlags)
{
    HRESULT hr=DP_OK;
    UINT err;
    SOCKET sSocket;
    LPSOCKADDR_IN6 paddr;
    SOCKET_ADDRESS_LIST *pList;
    int i;
    
    if (dwFlags & DPLAYI_PLAYER_SYSPLAYER) 
    {
        if (INVALID_SOCKET == pgd->sSystemDGramSocket)
        {
            hr = CreateSocket(pgd,&sSocket,SOCK_DGRAM,pgd->wApplicationPort,&sockaddr_any,&err,TRUE);
            if (FAILED(hr)) 
            {
                DPF(0,"create sysplayer dgram socket failed - err = %d\n",err);
                return hr;
            }
                
            #ifdef DEBUG
            if (dwFlags & DPLAYI_PLAYER_NAMESRVR) 
            {
                DEBUGPRINTSOCK(2,"name server dgram socket - ",&sSocket);
            }
            #endif // DEBUG

            //
            // join link-local multicast group for enumeration on every link
            //
    
            // do a passive getaddrinfo
            pList = GetHostAddr();
            if (pList)
            {
                // for each linklocal address
                for (i=0; i<pList->iAddressCount; i++)
                {
                    paddr = (LPSOCKADDR_IN6)pList->Address[i].lpSockaddr;

                    // skip if not linklocal
                    if (!IN6_IS_ADDR_LINKLOCAL(&paddr->sin6_addr))
                    {
                        continue;
                    }

                    // join the multicast group on that ifindex
                    if (SOCKET_ERROR == JoinEnumGroup(sSocket, paddr->sin6_scope_id))
                    {
                        DPF(0,"join enum group failed - err = %d\n",WSAGetLastError());
                        closesocket(sSocket);
                        return hr;
                    }
                }
                FreeHostAddr(pList);
            }
            
            pgd->sSystemDGramSocket = sSocket;
        }
        else 
        {
            // store this for setting player address below
            sSocket = pgd->sSystemDGramSocket;    
        }
    }
    else 
    {
    
        ASSERT(INVALID_SOCKET != pgd->sSystemDGramSocket);
        sSocket = pgd->sSystemDGramSocket;    
    }

    // store the ip + port w/ the player...    
    hr = SetPlayerAddress(pgd,ppd,sSocket,FALSE);

    
    return hr; 
}  // CreatePlayerDgramSocket

HRESULT CreatePlayerStreamSocket(LPGLOBALDATA pgd,LPSPPLAYERDATA ppd,DWORD dwFlags) 
{
    SOCKET sSocket;
    HRESULT hr=DP_OK;
    UINT err;
    BOOL bListen = TRUE; // set if we created socket, + need to set it's listen
    
    if (dwFlags & DPLAYI_PLAYER_SYSPLAYER) 
    {
        if (INVALID_SOCKET == pgd->sSystemStreamSocket)
        {
            hr = CreateSocket(pgd,&sSocket,SOCK_STREAM,pgd->wApplicationPort,&sockaddr_any,&err,TRUE);
            if (FAILED(hr)) 
            {
                DPF(0,"create player stream socket failed - err = %d\n",err);
                return hr;
            }
            
            #ifdef DEBUG
            if (dwFlags & DPLAYI_PLAYER_NAMESRVR) 
            {
                DEBUGPRINTSOCK(2,"name server stream socket - ",&sSocket);
            }
            #endif // DEBUG

            pgd->sSystemStreamSocket = sSocket;
        }
        else
        {
            sSocket = pgd->sSystemStreamSocket;    
            bListen = FALSE;
        }
            
    }
    else 
    {
        ASSERT (INVALID_SOCKET != pgd->sSystemStreamSocket);
        sSocket = pgd->sSystemStreamSocket;    
        bListen = FALSE;            
    }
    
    if (bListen)
    {
        // set up socket to receive connections
        err = listen(sSocket,LISTEN_BACKLOG);
        if (SOCKET_ERROR == err) 
        {
            err = WSAGetLastError();
            ASSERT(FALSE);
            DPF(0,"ACK! stream socket listen failed - err = %d\n",err);
            // keep trying
        }
    }
    
    hr = SetPlayerAddress(pgd,ppd,sSocket,TRUE);
    return hr;

} // CreatePlayerStreamSocket


#undef DPF_MODNAME
#define DPF_MODNAME    "PokeAddr"


// poke an ip addr into a message blob
void IP6_SetAddr(LPVOID pmsg,SOCKADDR_IN6 * paddrSrc)
{
    LPSOCKADDR_IN6  paddrDest; // tempo variable, makes casting less ugly
    LPMESSAGEHEADER phead;

    phead = (LPMESSAGEHEADER)pmsg;
    // todo - validate header

    // leave the port intact, copy over the ip addr
    paddrDest = (SOCKADDR_IN6 *)&(phead->sockaddr);
    // poke the new ip addr into the message header
    paddrDest->sin6_addr = paddrSrc->sin6_addr;

    return;
    
} // IP6_SetAddr

// get an ip addr from a message blob
void IP6_GetAddr(SOCKADDR_IN6 * paddrDest,SOCKADDR_IN6 * paddrSrc) 
{
    // leave the port intact, copy over the nodenum
    if (IN6_IS_ADDR_UNSPECIFIED(&paddrDest->sin6_addr))
    {
        DEBUGPRINTADDR(2,"remote player - setting address!! =  %s\n",paddrSrc);
        paddrDest->sin6_addr = paddrSrc->sin6_addr;
    }

    return;
        
} // IP_GetAddr

// store the port of the socket w/ the message, so the receiving end
// can reconstruct the address to reply to
HRESULT SetReturnAddress(LPVOID pmsg,SOCKET sSocket) 
{
    SOCKADDR_IN6 sockaddr;
    INT addrlen=sizeof(sockaddr);
    LPMESSAGEHEADER phead;
    UINT err;

    // find out what port gGlobalData.sEnumSocket is on
    err = getsockname(sSocket,(LPSOCKADDR)&sockaddr,&addrlen);
    if (SOCKET_ERROR == err)
    {
        err = WSAGetLastError();
        DPF(0,"could not get socket name - err = %d\n",err);
        return DP_OK;
    }

    DEBUGPRINTADDR(9,"setting return address = ",&sockaddr);

    phead = (LPMESSAGEHEADER)pmsg;
    // todo - validate header

    phead->sockaddr = sockaddr;

    return DP_OK;

} // SetReturnAddress

// code below all called by GetServerAddress. For IP, prompts user for ip address 
// for name server.
#undef DPF_MODNAME
#define DPF_MODNAME    "GetAddress"
// get the ip address from the pBuffer passed in by a user
// can either be a real ip, or a hostname
// called after the user fills out our dialog box
HRESULT GetAddress(SOCKADDR_IN6 * saAddress,char *pBuffer,int cch)
{
    UINT err;
    struct addrinfo *ai, hints;

    if ( (0 == cch)  || (!pBuffer) || (0 == strlen(pBuffer)) )
    {
        ZeroMemory(saAddress, sizeof(*saAddress));
        saAddress->sin6_family = AF_INET6;
        saAddress->sin6_addr = in6addr_multicast;
        return (DP_OK);
    } 

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET6;
    err = Dplay_GetAddrInfo(pBuffer, NULL, &hints, &ai);
    if (0 != err) {
        DPF(0,"could not get host address - err = %d\n",err);
        return (DPERR_INVALIDPARAM);
    }
    
    DEBUGPRINTADDR(1, "name server address = %s \n",ai->ai_addr);
    CopyMemory(saAddress, ai->ai_addr, sizeof(SOCKADDR_IN6));
    Dplay_FreeAddrInfo(ai);
    
    return (DP_OK);
} // GetAddress

// put up a dialog asking for a network address
// call get address to convert user specified address to network usable address
// called by GetServerAddress
INT_PTR CALLBACK DlgServer(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HWND hWndCtl;
    char pBuffer[ADDR_BUFFER_SIZE];
    UINT cch;
    SOCKADDR_IN6 *lpsaEnumAddress;
    HRESULT hr;

    switch (msg)
    {
    case WM_INITDIALOG:
        // set focus on edit box
        hWndCtl = GetDlgItem(hDlg, IDC_EDIT1);
        if (hWndCtl == NULL)
        {
            EndDialog(hDlg, FALSE);
            return(TRUE);
        }
        SetFocus(hWndCtl);
        SendMessage(hWndCtl, CB_SETCURSEL, 0, 0);

        // save pointer to enum address with the window
        SetWindowLongPtr(hDlg, DWLP_USER, (LONG) lParam);
        return(FALSE);


    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        case IDOK:
            // get text entered in control
            cch = GetDlgItemText(hDlg, IDC_EDIT1, pBuffer, ADDR_BUFFER_SIZE);

            // get pointer to return address in
            lpsaEnumAddress = (SOCKADDR_IN6 *)GetWindowLongPtr(hDlg, DWLP_USER);

            // convert string to enum address
            hr = GetAddress(lpsaEnumAddress,pBuffer,cch);
            if (FAILED(hr))
                EndDialog(hDlg, hr);
            else
                EndDialog(hDlg, TRUE);
            return(TRUE);

        case IDCANCEL:
            EndDialog(hDlg, FALSE);
            return(TRUE);
        }
        break;
    }
    return (FALSE);
} // DlgServer

/*
 ** GetServerAddress
 *
 *  CALLED BY: EnumSessions
 *
 *  DESCRIPTION: launches the select network address dialog
 *
 *  RETURNS:  ip address (sockaddr.sin_addr.s_addr)
 *
 */
HRESULT ServerDialog(SOCKADDR_IN6 *lpsaEnumAddress)
{
    HWND hwnd;
    INT_PTR    iResult;
    HRESULT hr;
    
    // we have a valid enum address
    if (!IN6_IS_ADDR_UNSPECIFIED(&lpsaEnumAddress->sin6_addr))
        return (DP_OK);

    // use the fg window as our parent, since a ddraw app may be full screen
    // exclusive
    hwnd = GetForegroundWindow();

    iResult = DialogBoxParam(ghInstance, MAKEINTRESOURCE(IDD_SELECTSERVER), hwnd,
                             DlgServer, (LPARAM) lpsaEnumAddress);
    if (iResult == -1)
    {
        DPF_ERR("GetServerAddress - dialog failed");
        hr = DPERR_GENERIC;
    }
    else if (iResult < 0)
    {
        DPF(0, "GetServerAddress - dialog failed: %08X", iResult);
        hr = (HRESULT) iResult;
    }
    else if (iResult == 0)
    {
        hr = DPERR_USERCANCEL;
    }
    else
    {
        hr = DP_OK;
    }
        
    return (hr);
    
} //ServerDialog 

// called by enumsessions - find out where server is...
HRESULT GetServerAddress(LPGLOBALDATA pgd,LPSOCKADDR_IN6 psockaddr) 
{
    HRESULT hr;

        if (pgd->bHaveServerAddress)
        {
            // use enum address passed to SPInit
            hr = GetAddress(&pgd->saddrEnumAddress,pgd->szServerAddress,strlen(pgd->szServerAddress));
        }
        else
        {
            // ask user for enum address
            hr = ServerDialog(&pgd->saddrEnumAddress);
        }

        if (SUCCEEDED(hr))
        {
            // setup winsock to enum this address
            *psockaddr = pgd->saddrEnumAddress;
            // see byte-order comment in dpsp.h for this constant
            psockaddr->sin6_port = SERVER_DGRAM_PORT;
        }
        else
        {
            DPF(0, "Invalid server address: 0x%08lx", hr); 
        }

    return (hr);
} // GetServerAddress