/* Verteilte Systeme - Programmieraufgabe Uebungsblatt 6: Zusatzaufgabe 2
   Author: Stefan Schonger
   Date  : 06/03/99

   "News/Chat"-Server.
   Users can join groups, leave groups and send messages to all members
   in a group.
   This time groups are implemented via multicast addresses.

   Message format:
   1) join #MulticastIP #Port
   2) leave #MulticastIP #Port
   3) message #MulticastIP #Port<ENTER>
      MESSAGE-BODY

   since messages are sent by udp, the size is restricted to 64k-header
   and generally unreliable (no ACKs).
*/

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h> 
#include <string.h>    
#include <unistd.h>  
#include <signal.h>


#define MAX_BUF 66000


typedef struct channelListX {
  pid_t pid;
  unsigned long ip;
  unsigned int port;
  struct channelListX *next;
} channelList;

channelList *channelHead;

void addList(  pid_t pid, unsigned long ip, unsigned int port) {
  channelList *cl;
  cl = malloc( sizeof(channelList) );
  cl->pid = pid;
  cl->ip = ip;
  cl->port = port;

  cl->next = channelHead;
  channelHead = cl;
}

pid_t removeList(unsigned long ip, unsigned int port) {
  channelList *cl, *cl2;
  cl2 = cl = channelHead;

  while( cl != 0 ) {
    if( cl->ip == ip && cl->port == port ) {
      if( cl == channelHead ) { /* remove at the beginning */
	channelHead = cl->next;
      } else {
	cl2->next = cl->next;
      }
      
      return cl->pid;
    }
    cl2 = cl;
    cl = cl->next;
  }
  return -1; /* nothing found */
}

pid_t join(unsigned long addr, int port) {
  pid_t pid;
  int sockfd;
  struct sockaddr_in groupAddr;
  size_t groupAddrLen;
  struct ip_mreq multicastRequest;
  char mesgBuf[MAX_BUF];
  int mesgSize;
  int sockopt;

  pid = fork();
  if( pid == 0 ) {
    
    /* Open a UDP socket */
    if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0 )) < 0){
      perror("Can't open socket");
      exit(-1);
    }

    /* allow multiple receivers on one host (using multicast,
       all receivers on one port get the UDP message) */
    sockopt = 1;
    if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)) < 0) {
      perror("setsockopt");
      exit(1);
    }
    
    /* setup up destination address */
    memset((char *) &groupAddr, 0, sizeof(struct sockaddr_in));
    groupAddr.sin_family      = AF_INET;
    groupAddr.sin_addr.s_addr = INADDR_ANY;
    groupAddr.sin_port        = port;
    
    /* bind to receive address */
    if (bind(sockfd,(struct sockaddr *) &groupAddr, sizeof(groupAddr)) < 0) {
      perror("bind");
      exit(1);
    }
    
    /* use setsockopt() to request that the kernel join a multicast group */
    multicastRequest.imr_multiaddr.s_addr = addr;
    multicastRequest.imr_interface.s_addr = htonl(INADDR_ANY);
    if (setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,
		   &multicastRequest, sizeof(multicastRequest)) < 0) {
      perror("setsockopt");
      exit(1);
    }

    for(;;) { /* forever (until we are killed) */

      /* Receive Messages */
      groupAddrLen = sizeof(groupAddr);
      mesgSize = recvfrom(sockfd, mesgBuf, MAX_BUF, 0, (struct sockaddr *) &groupAddr, &groupAddrLen);
      printf("Received packet from %s:\n%s\n", inet_ntoa(groupAddr.sin_addr), mesgBuf);
    }
    
  } else {
    return pid;
  }
}

void sendMsg(unsigned long addr, unsigned int port, char *mesgBuf) {
  int sockfd;
  struct sockaddr_in cliAddr;
  size_t cliAddrLen;

  /* Open a UDP socket */
  if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0 )) < 0){
    perror("Can't open socket");
    exit(-1);
  }

  /* setup "multicast" address */
  memset((char *) &cliAddr, 0, sizeof(struct sockaddr_in)); 
  cliAddr.sin_family      = AF_INET;
  cliAddr.sin_addr.s_addr = addr;
  cliAddr.sin_port        = port;
  
  cliAddrLen = sizeof(cliAddr);
    
  /* Send Multicast Message */
  printf("Sending: \"%s\"\n", mesgBuf);
  if( sendto(sockfd, mesgBuf, strlen(mesgBuf), 0, (struct sockaddr *) &cliAddr, sizeof(cliAddr)) == -1 ) {
    perror("sendto error!");
    exit(-1);
  }
}



int main(int argc, char *argv[] ) {
  char buf[MAX_BUF], mesgBuf[MAX_BUF];
  char reqType[1000], reqGroup[1000];
  int recPort;
  pid_t pid;
  
  for(;;) { /* forever */
    /* read from console */
    puts("Enter Command (join IP port ; leave IP port ; message IP port)");
    fgets(buf, MAX_BUF, stdin);
    sscanf(buf, "%s %s %d\n", reqType, reqGroup, &recPort);

    /* decide what to do */
    if( strcmp(reqType, "join") == 0) {
      puts("joining group...");
      addList( join(inet_addr(reqGroup), recPort), inet_addr(reqGroup), recPort );

    } else if( strcmp(reqType, "leave") == 0) {
      if( (pid = removeList(inet_addr(reqGroup), recPort)) > 0) {
	kill(pid, SIGTERM);
	puts("leaving group...");
      } else {
	puts("leaving group failed...");
      }

    } else if( strcmp(reqType, "message") == 0) {
      puts("Type in message (terminated by a \".\" on a single line");
      
      mesgBuf[0] = '\0';
      fgets(buf, MAX_BUF, stdin);
      buf[strlen(buf)-1] = 0;
      while( strcmp(buf, "." ) != 0 ) {
	strcat(buf, "\r\n");
	strcat(mesgBuf, buf);
	fgets(buf, MAX_BUF, stdin);

	buf[strlen(buf)-1] = 0;
      }
      mesgBuf[strlen(mesgBuf)-2]='\0'; /* cut last \r\n away */

      sendMsg( inet_addr(reqGroup), recPort, mesgBuf );      
    } else {
      puts("Error: Unknown command");
    }
  }

  return 0;
}
