/*
 * msend.c  -- Sends UDP packets to a multicast group
 * 
 * (c)  Jianping Wang, Yvan Pointurier, Jorg Liebeherr, 2002
 *      Multimedia Networks Group, University of Virginia
 *
 * SOURCE CODE RELEASED TO THE PUBLIC DOMAIN
 * 
 * version 2.0 - 5/20/2002 
 * version 2.1 - 12/4/2002  
 * 	By default, msend does not join multicast group. If  -join option is 
 * 	given, msend joins the multicast group. 
 * version 2.2 - 05/17/2003  
 *      Most commandline parameters are assigned default values. The 
 *      usage information is changed according to README_msend.txt
 * 
 * 
 * Based on this public domain program:
 * u_mctest.c            (c) Bob Quinn           2/4/97
 * 
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/time.h>

#define TRUE 1
#define FALSE 0
#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif
#ifndef SOCKET_ERROR
#define SOCKET_ERROR -1
#endif
#define LOOPMAX   20
#define BUFSIZE   1024

char *TEST_ADDR = "224.1.1.1";
int TEST_PORT = 4444;
int TTL_VALUE = 1;
int SLEEP_TIME = 1000;
unsigned long IP = INADDR_ANY;
int NUM = 0;

int join_flag = 0;		/* not join */

typedef struct timerhandler_s {
	int s;
	char *achOut;
	int len;
	int n;
	struct sockaddr *stTo;
	int addr_size;
} timerhandler_t;
timerhandler_t handler_par;
void timerhandler();

void printHelp(void)
{
	printf("msend version %s\n\
Usage:  msend [-g GROUP] [-p PORT] [-join] [-i ADDRESS] [-t TTL] [-P PERIOD]\n\
	      [-text \"text\"|-n]\n\
	msend [-v | -h]\n\
\n\
  -g GROUP     IP multicast group address to send to.  Default: 224.1.1.1\n\
  -p PORT      UDP port number used in the multicast packets.  Default: 4444\n\
  -i ADDRESS   IP address of the interface to use to send the packets.\n\
               The default is to use the system default interface.\n\
  -join        Multicast sender will join the multicast group.\n\
               By default a sender never joins the group.\n\
  -P PERIOD    Interval in milliseconds between packets.  Default 1000 msec\n\
  -t TTL       The TTL value (1-255) used in the packets.  You must set\n\
               this higher if you want to route the traffic, otherwise\n\
               the first router will drop the packets!  Default: 1\n\
  -text \"text\" Specify a string to use as payload in the packets, also\n\
               displayed by the mreceive command.  Default: empty\n\
  -n           Encode -text argument as a number instead of a string.\n\
  -v           Print version information.\n\
  -h           Print the command usage.\n\n", VERSION);
}

int main(int argc, char *argv[])
{
	struct sockaddr_in stLocal, stTo;
	char achOut[BUFSIZE] = "";
	int s, i;
	struct ip_mreq stMreq;
	int iTmp, iRet;
	int ii = 1;
	int addr_size = sizeof(struct sockaddr_in);
	struct itimerval times;
	sigset_t sigset;
	struct sigaction act;

	if ((argc == 2) && (strcmp(argv[ii], "-v") == 0)) {
		printf("msend version 2.2\n");
		return 0;
	}
	if ((argc == 2) && (strcmp(argv[ii], "-h") == 0)) {
		printHelp();
		return 0;
	}

	while (ii < argc) {
		if (strcmp(argv[ii], "-g") == 0) {
			ii++;
			if ((ii < argc) && !(strchr(argv[ii], '-'))) {
				TEST_ADDR = argv[ii];
				ii++;
			}
		} else if (strcmp(argv[ii], "-p") == 0) {
			ii++;
			if ((ii < argc) && !(strchr(argv[ii], '-'))) {
				TEST_PORT = atoi(argv[ii]);
				ii++;
			}
		} else if (strcmp(argv[ii], "-join") == 0) {
			join_flag++;;
			ii++;
		} else if (strcmp(argv[ii], "-i") == 0) {
			ii++;
			if ((ii < argc) && !(strchr(argv[ii], '-'))) {
				IP = inet_addr(argv[ii]);
				ii++;
			}
		} else if (strcmp(argv[ii], "-t") == 0) {
			ii++;
			if ((ii < argc) && !(strchr(argv[ii], '-'))) {
				TTL_VALUE = atoi(argv[ii]);
				ii++;
			}
		} else if (strcmp(argv[ii], "-P") == 0) {
			ii++;
			if ((ii < argc) && !(strchr(argv[ii], '-'))) {
				SLEEP_TIME = atoi(argv[ii]);
				ii++;
			}
		} else if (strcmp(argv[ii], "-n") == 0) {
			ii++;
			NUM = 1;
			ii++;
		} else if (strcmp(argv[ii], "-text") == 0) {
			ii++;
			if ((ii < argc) && !(strchr(argv[ii], '-'))) {
				strcpy(achOut, argv[ii]);
				ii++;
			}
		} else {
			printf("wrong parameters!\n\n");
			printHelp();
			return 1;
		}
	}

	/* get a datagram socket */
	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s == INVALID_SOCKET) {
		printf("socket() failed.\n");
		exit(1);
	}

	/* avoid EADDRINUSE error on bind() */
	iTmp = TRUE;
	iRet = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&iTmp, sizeof(iTmp));
	if (iRet == SOCKET_ERROR) {
		printf("setsockopt() SO_REUSEADDR failed.\n");
		exit(1);
	}

	/* name the socket */
	stLocal.sin_family = AF_INET;
	stLocal.sin_addr.s_addr = IP;
	stLocal.sin_port = htons(TEST_PORT);
	iRet = bind(s, (struct sockaddr *)&stLocal, sizeof(stLocal));
	if (iRet == SOCKET_ERROR) {
		printf("bind() failed.\n");
		exit(1);
	}

	/* join the multicast group. */
	stMreq.imr_multiaddr.s_addr = inet_addr(TEST_ADDR);
	stMreq.imr_interface.s_addr = IP;
	if (join_flag == 1) {
		iRet = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&stMreq, sizeof(stMreq));
		if (iRet == SOCKET_ERROR) {
			printf("setsockopt() IP_ADD_MEMBERSHIP failed.\n");
			exit(1);
		}
	}

	/* set TTL to traverse up to multiple routers */
	iTmp = TTL_VALUE;
	iRet = setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&iTmp, sizeof(iTmp));
	if (iRet == SOCKET_ERROR) {
		printf("setsockopt() IP_MULTICAST_TTL failed.\n");
		exit(1);
	}

	/* enable loopback */
	iTmp = TRUE;
	iRet = setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&iTmp, sizeof(iTmp));
	if (iRet == SOCKET_ERROR) {
		printf("setsockopt() IP_MULTICAST_LOOP failed.\n");
		exit(1);
	}

	/* assign our destination address */
	stTo.sin_family = AF_INET;
	stTo.sin_addr.s_addr = inet_addr(TEST_ADDR);
	stTo.sin_port = htons(TEST_PORT);
	printf("Now sending to multicast group: %s\n", TEST_ADDR);

	SLEEP_TIME *= 1000;	/* convert to microsecond */
	if (SLEEP_TIME > 0) {
		/* block SIGALRM */
		sigemptyset(&sigset);
		sigaddset(&sigset, SIGALRM);
		sigprocmask(SIG_BLOCK, &sigset, NULL);

		/* set up handler for SIGALRM */
		act.sa_handler = &timerhandler;
		sigemptyset(&act.sa_mask);
		act.sa_flags = SA_SIGINFO;
		sigaction(SIGALRM, &act, NULL);
		/*
		 * set up interval timer
		 */
		times.it_value.tv_sec = 0;	/* wait a bit for system to "stabilize"  */
		times.it_value.tv_usec = 1;	/* tv_sec or tv_usec cannot be both zero */
		times.it_interval.tv_sec = (time_t)(SLEEP_TIME / 1000000);
		times.it_interval.tv_usec = (long)(SLEEP_TIME % 1000000);
		setitimer(ITIMER_REAL, &times, NULL);

		handler_par.s = s;
		handler_par.achOut = achOut;
		handler_par.len = strlen(achOut) + 1;
		handler_par.n = 0;
		handler_par.stTo = (struct sockaddr *)&stTo;
		handler_par.addr_size = addr_size;

		/* now wait for the alarms */
		sigemptyset(&sigset);
		for (;;) {
			sigsuspend(&sigset);
		}
		return 0;
	} else {
		for (i = 0; i < 10; i++) {
			int addr_size = sizeof(struct sockaddr_in);

			if (NUM) {
				achOut[3] = (unsigned char)(i >> 24);
				achOut[2] = (unsigned char)(i >> 16);
				achOut[1] = (unsigned char)(i >> 8);
				achOut[0] = (unsigned char)(i);
				printf("Send out msg %d to %s:%d\n", i, TEST_ADDR, TEST_PORT);
			} else {
				printf("Send out msg %d to %s:%d: %s\n", i, TEST_ADDR, TEST_PORT, achOut);
			}

			iRet = sendto(s, achOut, (NUM ? 4 : strlen(achOut) + 1), 0, (struct sockaddr *)&stTo, addr_size);
			if (iRet < 0) {
				printf("sendto() failed.\n");
				exit(1);
			}
		}		/* end for(;;) */
	}

	return 0;
}				/* end main() */

void timerhandler(void)
{
	int iRet;
	static int iCounter = 1;

	if (NUM) {
		handler_par.achOut = (char *)(&iCounter);
		handler_par.len = sizeof(iCounter);
		printf("Sending msg %d, TTL %d, to %s:%d\n", iCounter, TTL_VALUE, TEST_ADDR, TEST_PORT);
	} else {
		printf("Sending msg %d, TTL %d, to %s:%d: %s\n", iCounter, TTL_VALUE, TEST_ADDR, TEST_PORT, handler_par.achOut);
	}
	iRet = sendto(handler_par.s, handler_par.achOut, handler_par.len, handler_par.n, handler_par.stTo, handler_par.addr_size);
	if (iRet < 0) {
		printf("sendto() failed.\n");
		exit(1);
	}
	iCounter++;
	return;
}

/**
 * Local Variables:
 *  version-control: t
 *  indent-tabs-mode: t
 *  c-file-style: "linux"
 * End:
 */
