// Description:
//    This sample illustrates multicasting on an ATM network.
//    ATM multicasting features rooted control and data planes.
//    This means our multicast server explicitly invites each
//    leaf not via a WSAJoinLeaf call. The leaf nodes simply
//    wait for on an accept for the invitation.
//
//    For the root node we specify the -s options along with
//    one or more -l:Address options.  The address is the ATM
//    address of the leaf to invite. All ATM addresses are only
//    the first 38 chars. The port (selector) which is the other
//    2 characters of the address are given in the -p:xx option.
//
//    For the client simply call the app with no paramters (the
//    -i:Addr can be used for both client and server).
//
// Command Line Options/Parametrs:
//     atmmulticastexample -s -l ATM-Addr -i ATM-Addr -p XX -n int
//           -s       Act as root\n");
//           -l str   Leaf address to invite (38 chars)
//                     May be specified multiple times.
//           -i str   Local interface to bind to (38 chars)
//           -p xx    Port number (2 hex chars)
//           -n int   Number of packets to send
//
// Link to ws2_32.lib
// Already included in support.h
// #include <winsock2.h>
#include "support.h"
#include <stdio.h>
#include <stdlib.h>

#define BUFSIZE               1024
#define MAX_ATM_LEAF             4
#define ATM_PORT_OFFSET       ((ATM_ADDR_SIZE*2)-2)
#define MAX_ATM_STR_LEN       (ATM_ADDR_SIZE*2)

DWORD  dwAddrCount=0, dwDataCount=20;
BOOL   bServer=FALSE, bLocalAddress=FALSE;
char   szLeafAddresses[MAX_ATM_LEAF][MAX_ATM_STR_LEN+1],
       szLocalAddress[MAX_ATM_STR_LEN+1],
       szPort[3];
SOCKET sLeafSock[MAX_ATM_LEAF];

// Module: usage
// Description: Print usage information.
int usage(char *progname)
{
    printf("Usage: %s [-s]\n", progname);
    printf("     -s       Act as root\n");
    printf("     -l str   Leaf address to invite (38 chars)\n");
    printf("               May be specified multiple times\n");
    printf("     -i str   Local interface to bind to (38 chars)\n");
    printf("     -p xx    Port number (2 hex chars)\n");
    printf("     -n int   Number of packets to send\n");
    return 0;
}

// Module: ValidateArgs
// Description: Parse command line arguments.
void ValidateArgs(int argc, char **argv)
{
    int      i;

    memset(szLeafAddresses, 0, MAX_ATM_LEAF * (MAX_ATM_STR_LEN+1));
    memset(szPort, 0, sizeof(szPort));

    for(i=1; i < argc ;i++)
    {
        if ((argv[i][0] == '-') || (argv[i][0] == '/'))
        {
            switch (tolower(argv[i][1]))
            {
                case 's':     // server
                    bServer = TRUE;
                    break;
                case 'l':     // leaf address
                    if (i+1 >=argc)
                        usage(argv[0]);
                    strncpy_s(szLeafAddresses[dwAddrCount++], sizeof(szLeafAddresses), argv[++i], MAX_ATM_STR_LEN-2);
                    break;
                case 'i':     // local interface
                    if (i+1 >= argc)
                        usage(argv[0]);
                    strncpy_s(szLocalAddress, sizeof(szLocalAddress), argv[++i], MAX_ATM_STR_LEN-2);
                    bLocalAddress = TRUE;
                    break;
                case 'p':     // port address to use
                    if (i+1 >= argc)
                        usage(argv[0]);
                    strncpy_s(szPort, sizeof(szPort), argv[++i], 2);
                    break;
                case 'n':     // number of packets to send
                    if (i+1 >= argc)
                        usage(argv[0]);
                    dwDataCount = atoi(argv[++i]);
                    break;
                default:
                    usage(argv[0]);
                    break;
            }
        }
    }
    return;
}

// Function: Server
// Description:
//    Bind to the local interface and then invite each leaf
//    address which was specified on the command line.
//    Once each connection is made, send some data.
void Server(SOCKET s, WSAPROTOCOL_INFO *lpSocketProtocol)
{
    // Server routine
    SOCKADDR_ATM  atmleaf, atmroot;
    WSABUF        wsasend;
    char          sendbuf[BUFSIZE], szAddr[BUFSIZE];
    DWORD         dwBytesSent, dwAddrLen=BUFSIZE, dwNumInterfaces, i;
    int           ret;

    // If no specified local interface is given pick the first one
    memset(&atmroot, 0, sizeof(SOCKADDR_ATM));
    if (!bLocalAddress)
    {
        dwNumInterfaces = GetNumATMInterfaces(s);
        GetATMAddress(s, 0, &atmroot.satm_number);
    }
    else
        AtoH((char *)&atmroot.satm_number.Addr[0], szLocalAddress, ATM_ADDR_SIZE-1);

	// Set the port number in the address structure
    AtoH((char *)&atmroot.satm_number.Addr[ATM_ADDR_SIZE-1], szPort, 1);

	// Fill in the rest of the SOCKADDR_ATM structure
    atmroot.satm_family                 = AF_ATM;
    atmroot.satm_number.AddressType     = ATM_NSAP;
    atmroot.satm_number.NumofDigits     = ATM_ADDR_SIZE;
    atmroot.satm_blli.Layer2Protocol    = SAP_FIELD_ANY;
    atmroot.satm_blli.Layer3Protocol    = SAP_FIELD_ABSENT;
    atmroot.satm_bhli.HighLayerInfoType = SAP_FIELD_ABSENT;

	// Print out what we're binding to and bind()
    if (WSAAddressToString((LPSOCKADDR)&atmroot, sizeof(atmroot), lpSocketProtocol, (LPWSTR)szAddr,
		&dwAddrLen))
    {
        printf("WSAAddressToString() failed with error code %d\n", WSAGetLastError());
    }
	else
		printf("WSAAddressToString() should be fine!\n");

    printf("Binding to: <%s>\n", szAddr);

    if (bind(s, (SOCKADDR *)&atmroot, sizeof(SOCKADDR_ATM)) == SOCKET_ERROR)
    {
        printf("bind() failed with error code %d\n", WSAGetLastError());
        return;
    }
	else
		printf("bind() is oK!\n");

    // Invite each leaf
    for(i=0; i < dwAddrCount ;i++)
    {
        // Fill in the SOCKADR_ATM structure for each leaf
        memset(&atmleaf, 0, sizeof(SOCKADDR_ATM));
        AtoH((char *)&atmleaf.satm_number.Addr[0], szLeafAddresses[i], ATM_ADDR_SIZE-1);
        AtoH((char *)&atmleaf.satm_number.Addr[ATM_ADDR_SIZE-1], szPort, 1);

        atmleaf.satm_family                 = AF_ATM;
        atmleaf.satm_number.AddressType     = ATM_NSAP;
        atmleaf.satm_number.NumofDigits     = ATM_ADDR_SIZE;
        atmleaf.satm_blli.Layer2Protocol    = SAP_FIELD_ANY;
        atmleaf.satm_blli.Layer3Protocol    = SAP_FIELD_ABSENT;
        atmleaf.satm_bhli.HighLayerInfoType = SAP_FIELD_ABSENT;

		// Print out clients address and the invite it
        if (WSAAddressToString((LPSOCKADDR)&atmleaf,sizeof(atmleaf), lpSocketProtocol, (LPWSTR)szAddr,
            &dwAddrLen))
        {
            printf("WSAAddressToString() failed with error code %d\n", WSAGetLastError());
        }
		else
			printf("WSAAddressToString() is OK!\n");

        printf("[%02d] Inviting: <%s>\n", i, szAddr);

        if ((sLeafSock[i] = WSAJoinLeaf(s, (SOCKADDR *)&atmleaf, sizeof(SOCKADDR_ATM), NULL,
            NULL, NULL, NULL, JL_SENDER_ONLY)) == INVALID_SOCKET)
        {
            printf("WSAJoinLeaf() failed with error code %d\n", WSAGetLastError());
            WSACleanup();
            return;
        }
		else
			printf("WSAJoinLeaf() is working!\n");
    }

    // Note that the ATM protocol is a bit different that TCP.
    // When the WSAJoinLeaf (or connect call) completes the
    // peer has not necessarily accepted the connection yet
    // so immediately sending data will result in an error
    // which is why we wait a short bit.
    printf("Press a key to start sending.");
    getchar();
    printf("\n");

	// Now send some data to the group address which will
    // be replicated to all clients
    wsasend.buf = sendbuf;
    wsasend.len = 128;
    for(i=0; i < dwDataCount ;i++)
    {
        memset(sendbuf, '$' + (i%26), 128);
        ret = WSASend(s, &wsasend, 1, &dwBytesSent, 0, NULL, NULL);
        if (ret == SOCKET_ERROR)
        {
            printf("WSASend() failed: %d\n", WSAGetLastError());
            break;
        }
        printf("[%02d] Wrote: %d bytes\n", i, dwBytesSent);
        Sleep(500);
    }

    for(i=0; i < dwAddrCount ;i++)
        closesocket(sLeafSock[i]);
    return;
}

// Function: Client
// Description:
//    The client binds to the local interface (either one specified
//    on the command line or the first local ATM address). It
//    then waits on an accept call for the root invitation.  It
//    then waits to receive data.
void Client(SOCKET s, WSAPROTOCOL_INFO *lpSocketProtocol)
{
    SOCKET       sl;
    SOCKADDR_ATM atm_leaf, atm_root;
    DWORD        dwNumInterfaces, dwBytesRead, dwAddrLen=BUFSIZE, dwFlags, i;
    WSABUF       wsarecv;
    char         recvbuf[BUFSIZE], szAddr[BUFSIZE];
    int          iLen = sizeof(SOCKADDR_ATM),ret;

    // Setup the local interface
    memset(&atm_leaf, 0, sizeof(SOCKADDR_ATM));
    if (!bLocalAddress)
    {
        dwNumInterfaces = GetNumATMInterfaces(s);
        GetATMAddress(s, 0, &atm_leaf.satm_number);
    }
    else
        AtoH((char *)&atm_leaf.satm_number.Addr[0], szLocalAddress,ATM_ADDR_SIZE-1);
    
	AtoH((char *)&atm_leaf.satm_number.Addr[ATM_ADDR_SIZE-1],szPort, 1);

    // Fill in the SOCKADDR_ATM structure
    atm_leaf.satm_family                 = AF_ATM;
    atm_leaf.satm_number.AddressType     = ATM_NSAP;
    atm_leaf.satm_number.NumofDigits     = ATM_ADDR_SIZE;
    atm_leaf.satm_blli.Layer2Protocol    = SAP_FIELD_ANY;
    atm_leaf.satm_blli.Layer3Protocol    = SAP_FIELD_ABSENT;
    atm_leaf.satm_bhli.HighLayerInfoType = SAP_FIELD_ABSENT;

	// Print the address we're binding to and bind
    if (WSAAddressToString((LPSOCKADDR)&atm_leaf, sizeof(atm_leaf), lpSocketProtocol, (LPWSTR)szAddr,
        &dwAddrLen))
    {
        printf("WSAAddressToString() failed with error code %d\n", WSAGetLastError());
    }
	else
		printf("WSAAddressToString() is OK!\n");

    printf("Binding to: <%s>\n", szAddr);

    if (bind(s, (SOCKADDR *)&atm_leaf, sizeof(SOCKADDR_ATM)) == SOCKET_ERROR)
    {
        printf("bind() failed with error code %d\n", WSAGetLastError());
        return;
    }
	else
		printf("bind() is OK!\n");

    if(listen(s, 1) == 0)
		printf("listen() is OK!\n");
	else
		printf("listen() failed with error code %d\n", WSAGetLastError());

	// Wait for the invitation
    memset(&atm_root, 0, sizeof(SOCKADDR_ATM));
    if ((sl = WSAAccept(s, (SOCKADDR *)&atm_root, &iLen, NULL, 0)) == INVALID_SOCKET)
    {
        printf("WSAAccept() failed with error code %d\n", WSAGetLastError());
        return;
    }
	else
		printf("WSAAccept() is fine!\n");

    printf("Received a connection!\n");

    // Receive some data
    wsarecv.buf = recvbuf;
    for(i=0; i < dwDataCount ;i++)
    {
        dwFlags = 0;
        wsarecv.len = BUFSIZE;
        ret = WSARecv(sl, &wsarecv, 1, &dwBytesRead, &dwFlags,
            NULL, NULL);
        if (ret == SOCKET_ERROR)
        {
            printf("WSARecv() failed with error code %d\n", WSAGetLastError());
            break;
        }

        if (dwBytesRead == 0)
            break;
        recvbuf[dwBytesRead] = 0;
        printf("[%02d] Read %d bytes: '%s'\n", i, dwBytesRead, recvbuf);
    }
	
	if(closesocket(sl) == 0)
		printf("closesocket(sl) is OK!\n");
	else
		printf("closesocket(sl) failed with error code %d\n",WSAGetLastError());

    return;
}

// Function: main
// Description:
//    This function loads Winsock library, parses command line
//    arguments, creates the appropriate socket (with the right
//    root or leaf flags), and start the client or server
//    functions depending on the specified flags.
int main(int argc, char **argv)
{
    WSADATA             wsd;
    SOCKET              s;
    WSAPROTOCOL_INFO    lpSocketProtocol;
    DWORD               dwFlags;

	if(argc < 2)
	{
		usage(argv[0]);
		exit(1);
	}

    ValidateArgs(argc, argv);

	// Load the Winsock library
    if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
    {
        printf("WSAStartup() failed with error code %d\n", WSAGetLastError());
        return -1;
    }
	else
		printf("WSAStartup() is OK!\n");

	// Find an ATM capable protocol
    if (FindProtocol(&lpSocketProtocol) == FALSE)
    {
        printf("Unable to find ATM protocol entry!\n");
        return -1;
    }
	else
		printf("ATM protocol entry found!\n");

    // Create the socket using the appripriate root or leaf flags
    if (bServer)
        dwFlags = WSA_FLAG_OVERLAPPED | WSA_FLAG_MULTIPOINT_C_ROOT | WSA_FLAG_MULTIPOINT_D_ROOT;
    else
        dwFlags = WSA_FLAG_OVERLAPPED | WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF;

    if ((s = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
            FROM_PROTOCOL_INFO, &lpSocketProtocol, 0, dwFlags)) == INVALID_SOCKET)
    {
        printf("WSASocket() failed with error code %d\n", WSAGetLastError());
        WSACleanup();
        return -1;
    }
	else
		printf("WSASocket() is OK!\n");

    // Start the correct driver depending on what flags were
    // supplied on the command line.
    if (bServer)
    {
        Server(s, &lpSocketProtocol);
    }
    else
    {
        Client(s, &lpSocketProtocol);
    }
	
	if(closesocket(s) == 0)
		printf("closesocket(s) is OK!\n");
	else
		printf("closesocket(s) failed with error code %d\n", WSAGetLastError());
	
	if(WSACleanup() == 0)
		printf("WSACleanup() is OK!\n");
	else
		printf("WSACleanup() failed with error code %d\n", WSAGetLastError());
    return 0;
}
