/*++

Copyright (c) 1992 Microsoft Corporation

Module Name:

    RnrClnt.c

Abstract:

    Test and demonstration client for the RNR (service Registration and 
    Name Resolution) APIs.  This is a simple client application designed 
    to show the basic principles involved in using the RNR APIs to write 
    a protocol-independent Windows Sockets client application.

    This client works by examining the protocols loaded on the machine, 
    looking for protocols which are reliable and stream-oriented.  Then 
    it attempts to locate and connect to the service on these protocols.  
    When is has successfully connected to the service, it sends 
    exchanges several messages with the service and then terminates the 
    connection.

    The OpenConnection() routine implemented herein is intended to be a 
    demonstration of RNR functionality commonly used in 
    protocol-independent clients.  Application writers are encouraged to 
    leverage this code to assist them in writing protocol-independent 
    applications on top of the Windows Sockets API.  

Author:

    David Treadwell (davidtr)    30-June-1994

Revision History:

--*/

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winsock.h>
#include <nspapi.h>

#define DEFAULT_TRANSFER_SIZE 512
#define DEFAULT_TRANSFER_COUNT 0x10
#define DEFAULT_CONNECTION_COUNT 1
#define DEFAULT_DELAY 0

#define DEFAULT_RECEIVE_BUFFER_SIZE 4096
#define DEFAULT_SEND_BUFFER_SIZE 4096

#define MAX_PROTOCOLS 10

WSADATA WsaData;
DWORD TransferSize = DEFAULT_TRANSFER_SIZE;
DWORD TransferCount = DEFAULT_TRANSFER_COUNT;
PCHAR IoBuffer;
DWORD RepeatCount = 1;
INT ReceiveBufferSize = DEFAULT_RECEIVE_BUFFER_SIZE;
INT SendBufferSize = DEFAULT_SEND_BUFFER_SIZE;

PCHAR RemoteName = "localhost";
PCHAR ServiceTypeName = "EchoExample";

VOID
DoEcho (
    IN SOCKET s
    );

SOCKET
OpenConnection (
    IN PTSTR ServiceName,
    IN LPGUID ServiceType,
    IN BOOL Reliable,
    IN BOOL MessageOriented,
    IN BOOL StreamOriented,
    IN BOOL Connectionless,
    OUT PINT ProtocolUsed
    );

void _CRTAPI1
main (
    int argc,
    char *argv[]
    )
{
    INT err;
    DWORD i;
    DWORD protocol[2];
    SOCKET s;
    BYTE buffer[1024];
    BYTE buffer2[1024];
    DWORD bytesRequired;
    PPROTOCOL_INFO protocolInfo;
    GUID serviceType;

    //
    // Initialize the Windows Sockets DLL.
    //

    err = WSAStartup( 0x0101, &WsaData );
    if ( err == SOCKET_ERROR ) {
        printf( "WSAStartup() failed: %ld\n", GetLastError( ) );
        return;
    }

    //
    // Parse command-line arguments.
    //

    for ( i = 1; i < (ULONG)argc != 0; i++ ) {

        if ( strnicmp( argv[i], "/name:", 6 ) == 0 ) {
            RemoteName = argv[i] + 6;
        } else if ( strnicmp( argv[i], "/type:", 6 ) == 0 ) {
            ServiceTypeName = argv[i] + 6;
        } else if ( strnicmp( argv[i], "/size:", 6 ) == 0 ) {
            TransferSize = atoi( argv[i] + 6 );
        } else if ( strnicmp( argv[i], "/count:", 7 ) == 0 ) {
            TransferCount = atoi( argv[i] + 7 );
        } else if ( strnicmp( argv[i], "/rcvbuf:", 8 ) == 0 ) {
            ReceiveBufferSize = atoi( argv[i] + 8 );
        } else if ( strnicmp( argv[i], "/sndbuf:", 8 ) == 0 ) {
            SendBufferSize = atoi( argv[i] + 8 );
        } else {
            printf( "Usage: rnrclnt [/name:SVCNAME] [/type:TYPENAME] [/size:N]\n" );
            printf( "               [/count:N] [/rcvbuf:N] [/sndbuf:N]\n" );
            exit( 0 );
        }
    }

    //
    // Allocate memory to hold the network I/O buffer.
    //

    IoBuffer = malloc( TransferSize + 1 );
    if ( IoBuffer == NULL ) {
        printf( "Failed to allocate I/O buffer.\n" );
        exit( 0 );
    }

    //
    // Determine the type (GUID) of the service we are interested in 
    // connecting to.  
    //

    err = GetTypeByName( ServiceTypeName, &serviceType );
    if ( err == SOCKET_ERROR ) {
        printf( "GetTypeByName for \"%s\" failed: %ld\n",
                    ServiceTypeName, GetLastError( ) );
        exit( 0 );
    }

    //
    // Open a connected socket to the service.
    //

    s = OpenConnection(
            RemoteName,
            &serviceType,
            TRUE,
            FALSE,
            FALSE,
            FALSE,
            &protocol[0]
            );
    if ( s == INVALID_SOCKET ) {
        printf( "Failed to open connection to name \"%s\" type \"%s\"\n",
                    RemoteName, ServiceTypeName );
        exit( 0 );
    }

    //
    // The connection succeeded.  Display some information on the 
    // protocol which was used.  
    //

    bytesRequired = sizeof(buffer);
    protocol[1] = 0;

    err = EnumProtocols( protocol, buffer, &bytesRequired );

    if ( err < 1 ) {
        printf( "EnumProtocols failed for protocol %ld: %ld\n",
                    protocol[0], GetLastError( ) );
        exit( 0 );
    }

    err = GetNameByType( &serviceType, buffer2, sizeof(buffer2) );
    if ( err != NO_ERROR ) {
        printf( "GetNameByType failed: %ld\n", GetLastError( ) );
        exit ( 0 );
    }

    protocolInfo = (PPROTOCOL_INFO)buffer;
    printf( "Connected to %s/%s with protocol \"%s\" (%ld)\n",
                RemoteName, buffer2,
                protocolInfo->lpProtocol,
                protocolInfo->iProtocol );

    //
    // Send data to and from the service.
    //

    DoEcho( s );

} // main


VOID
DoEcho (
    IN SOCKET s
    )
{
    INT err;
    INT bytesReceived;
    DWORD i;
    DWORD startTime;
    DWORD endTime;
    DWORD transferStartTime;
    DWORD transferEndTime;
    DWORD totalTime;
    INT thisTransferSize;
    DWORD bytesTransferred = 0;

    startTime = GetTickCount( );

    for ( i = 0; i < TransferCount; i++ ) {

        thisTransferSize = TransferSize;

        transferStartTime = GetTickCount( );

        err = send( s, IoBuffer, thisTransferSize, 0 );
        if ( err != thisTransferSize ) {
            printf( "send didn't work, ret = %ld, error = %ld\n", err, GetLastError( ) );
            closesocket( s );
            return;
        }

        bytesReceived = 0;
        do {
            err = recv( s, IoBuffer, thisTransferSize, 0 );
            if ( err == SOCKET_ERROR ) {
                printf( "recv failed: %ld\n", GetLastError( ) );
                closesocket( s );
                return;
            } else if ( err == 0 && thisTransferSize != 0 ) {
                printf( "socket closed prematurely by remote.\n" );
                return;
            }
            bytesReceived += err;
        } while ( bytesReceived < thisTransferSize );

        transferEndTime = GetTickCount( );
        printf( "%5ld bytes sent and received in %ld ms\n",
                    thisTransferSize, transferEndTime - transferStartTime );

        bytesTransferred += thisTransferSize;
    }

    endTime = GetTickCount( );
    totalTime = endTime - startTime;

    printf( "\n%ld bytes transferred in %ld iterations, time = %ld ms\n",
                bytesTransferred, TransferCount, totalTime );
    printf( "Rate = %ld KB/s, %ld T/S, %ld ms/iteration\n",
                (bytesTransferred / totalTime) * 2,
                (TransferCount*1000) / totalTime,
                totalTime / TransferCount );

    err = closesocket( s );
    if ( err == SOCKET_ERROR ) {
        printf( "closesocket failed: %ld\n", GetLastError( ) );
        return;
    }

    return;

} // DoEcho


SOCKET
OpenConnection (
    IN PTSTR ServiceName,
    IN LPGUID ServiceType,
    IN BOOL Reliable,
    IN BOOL MessageOriented,
    IN BOOL StreamOriented,
    IN BOOL Connectionless,
    OUT PINT ProtocolUsed
    )

/*++

Routine Description:

    Examines the Windows Sockets transport protocols loaded on a machine 
    and determines those which support the characteristics requested by 
    the caller.  Attempts to locate and connect to the specified service 
    on these protocols.  

Arguments:

    ServiceName - a friendly name which identifies the service we want 
        to connect to.  On name spaces which support name resolution at 
        the service level (e.g.  SAP) this is the name clients will use 
        to connect to this service.  On name spaces which support name 
        resolution at the host level (e.g.  DNS) this name is ignored 
        and applications must use the host name to establish 
        communication with this service.  

    ServiceType - the GUID value which uniquely identifies the type of
        service we provide.  A GUID is created with the UUIDGEN program.

    Reliable - if TRUE, the caller requests that only transport protocols
        which support reliable data delivery be used.  If FALSE, both
        reliable and unreliable protocols may be used.

    MessageOriented - if TRUE, only message-oriented transport protocols 
        should be used.  If FALSE, the caller either does not care 
        whether the protocols used are message oriented or desires only 
        stream-oriented protocols.

    StreamOriented - if TRUE, only stream-oriented transport protocols 
        should be used.  If FALSE, the caller either does not care 
        whether the protocols used are stream oriented or desires only 
        message-oriented protocols.

    Connectionless - if TRUE, only connectionless protocols should be 
        used.  If FALSE, both connection-oriented and connectionless 
        protocols may be used.

    ProtocolUsed - if a connection is opened successfully, this
        parameter receives the protocol ID of the protocol used to
        establish the connection.

Return Value:

    A connected socket handle, or INVALID_SOCKET if the connection
    could not be established.

--*/

{
    INT protocols[MAX_PROTOCOLS+1];
    BYTE buffer[2048];
    DWORD bytesRequired;
    INT err;
    PPROTOCOL_INFO protocolInfo;
    PCSADDR_INFO csaddrInfo;
    INT protocolCount;
    INT addressCount;
    INT i;
    DWORD protocolIndex;
    SOCKET s;

    //
    // First look up the protocols installed on this machine.  The 
    // EnumProtocols() API returns about all the Windows Sockets 
    // protocols loaded on this machine, and we'll use this information 
    // to identify the protocols which provide the necessary semantics.  
    //

    bytesRequired = sizeof(buffer);

    err = EnumProtocols( NULL, buffer, &bytesRequired );
    if ( err <= 0 ) {
        return INVALID_SOCKET;
    }

    //
    // Walk through the available protocols and pick out the ones which
    // support the desired characteristics.
    //

    protocolCount = err;
    protocolInfo = (PPROTOCOL_INFO)buffer;

    for ( i = 0, protocolIndex = 0;
          i < protocolCount && protocolIndex < MAX_PROTOCOLS;
          i++, protocolInfo++ ) {

        //
        // If "reliable" support is requested, then check if supported 
        // by this protocol.  Reliable support means that the protocol 
        // guarantees delivery of data in the order in which it is sent.
        // Note that we assume here that if the caller requested reliable
        // service then they do not want a connectionless protocol.
        //

        if ( Reliable ) {

            //
            // Check to see if the protocol is reliable.  It must 
            // guarantee both delivery of all data and the order in 
            // which the data arrives.  Also, it must not be a 
            // connectionless protocol.  
            //

            if ( (protocolInfo->dwServiceFlags &
                      XP_GUARANTEED_DELIVERY) == 0 ||
                 (protocolInfo->dwServiceFlags &
                      XP_GUARANTEED_ORDER) == 0 ) {
                continue;
            }

            if ( (protocolInfo->dwServiceFlags & XP_CONNECTIONLESS) != 0 ) {
                continue;
            }

            //
            // Check to see that the protocol matches the stream/message 
            // characteristics requested.  A stream oriented protocol 
            // either has the XP_MESSAGE_ORIENTED bit turned off, or 
            // else supports "pseudo stream" capability.  Pseudo stream 
            // means that although the underlying protocol is message 
            // oriented, the application may open a socket of type 
            // SOCK_STREAM and the protocol will hide message boundaries 
            // from the application.  
            //

            if ( StreamOriented &&
                 (protocolInfo->dwServiceFlags & XP_MESSAGE_ORIENTED) != 0 &&
                 (protocolInfo->dwServiceFlags & XP_PSEUDO_STREAM) == 0 ) {
                continue;
            }

            if ( MessageOriented &&
                 (protocolInfo->dwServiceFlags & XP_MESSAGE_ORIENTED) == 0 ) {
                continue;
            }

        } else if ( Connectionless ) {

            //
            // Make sure that this is a connectionless protocol.  In a 
            // connectionless protocol, data is sent as discrete 
            // datagrams with no connection establishment required.  
            // Connectionless protocols typically have no reliability 
            // guarantees.  
            //

            if ( (protocolInfo->dwServiceFlags & XP_CONNECTIONLESS) != 0 ) {
                continue;
            }
        }

        //
        // This protocol fits all the criteria.  Add it to the list of 
        // protocols in which we're interested.  
        //

        protocols[protocolIndex++] = protocolInfo->iProtocol;

    }

    //
    // Make sure that we found at least one acceptable protocol.  If 
    // there no protocols on this machine which meet the caller's 
    // requirements then fail here.  
    //

    if ( protocolIndex == 0 ) {
        return INVALID_SOCKET;
    }

    protocols[protocolIndex] = 0;

    //
    // Now attempt to find the address of the service to which we're 
    // connecting.  Note that we restrict the scope of the search to 
    // those protocols of interest by passing the protocol array we 
    // generated above to GetAddressByName().  This forces 
    // GetAddressByName() to return socket addresses for only the 
    // protocols we specify, ignoring possible addresses for protocols
    // we cannot support because of the caller's constraints.
    //

    bytesRequired = sizeof(buffer);

    err = GetAddressByName(
              NS_DEFAULT,
              ServiceType,
              ServiceName,
              protocols,
              0,                        // dwResolution
              NULL,                     // lpServiceAsyncInfo
              buffer,
              &bytesRequired,
              NULL,                     // lpAliasBuffer
              NULL                      // lpdwAliasBufferLength
              );
    if ( err <= 0 ) {
        return INVALID_SOCKET;
    }

    //
    // For each address, open a socket and attempt to connect.  Note that
    // if anything fails for a particular protocol we just skip on to
    // the next protocol.  As soon as we have established a connection,
    // quit trying.
    //

    addressCount = err;
    csaddrInfo = (PCSADDR_INFO)buffer;

    for ( i = 0; i < addressCount; i++, csaddrInfo++ ) {

        //
        // Open the socket.  Note that we manually specify stream type
        // if so requested in case the protocol is natively a message
        // protocol but supports stream semantics.
        //

        s = socket( csaddrInfo->LocalAddr.lpSockaddr->sa_family,
                    StreamOriented ? SOCK_STREAM : csaddrInfo->iSocketType,
                    csaddrInfo->iProtocol );
        if ( s == INVALID_SOCKET ) {
            continue;
        }

        //
        // Bind the socket to the local address specified.
        //

        err = bind( s, csaddrInfo->LocalAddr.lpSockaddr,
                    csaddrInfo->LocalAddr.iSockaddrLength );
        if ( err != NO_ERROR ) {
            closesocket( s );
            continue;
        }

        //
        // Attempt to connect the socket to the service.  If this fails,
        // keep trying on other protocols.
        //

        err = connect( s, csaddrInfo->RemoteAddr.lpSockaddr,
                    csaddrInfo->RemoteAddr.iSockaddrLength );
        if ( err != NO_ERROR ) {
            closesocket( s );
            continue;
        }

        //
        // The socket was successfully connected.  Remember the protocol
        // used and return the socket handle to the caller.
        //

        *ProtocolUsed = csaddrInfo->iProtocol;
        return s;
    }

    //
    // We failed to connect to the service.
    //

    return INVALID_SOCKET;
    
} // OpenConnection
