/**
 * Simple example of transmission and reception via multicast
 * http://atastypixel.com/blog/the-making-of-talkie-multi-interface-broadcasting-and-multicast/
 *
 *  Compile with:
 *      cc -o multicast_sample multicast_sample.c
 *
 *  Usage:
 *    To transmit:
 *      ./multicast_sample "Message to send"
 *
 *    To receive, call with no arguments:
 *      ./multicast_sample
 *
 * Michael Tyson, A Tasty Pixel <michael@atastypixel.com>
 * http://atastypixel.com
 */

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <ifaddrs.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#define kMulticastAddress "224.0.0.123"
#define kPortNumber 1234
#define kBufferSize 1024
#define kMaxSockets 16

/*!
 *  Transmit data
 *
 *  @param data
 *    Data to send
 *  @param length
 *    Length of data, in bytes
 *  @result 0 on failure, 1 on success
 */
int transmit(char * data, int length) {

  int sock_fds[kMaxSockets];
  
  // Obtain list of all network interfaces
  struct ifaddrs *addrs;
  if ( getifaddrs(&addrs) < 0 ) {
    // Error occurred
    return 0;
  }
  
  // Loop through interfaces, selecting those AF_INET devices that support multicast, but aren't loopback or point-to-point
  const struct ifaddrs *cursor = addrs;
  struct sockaddr_in addr;
  int number_sockets = 0;
  
  while ( cursor != NULL && number_sockets < kMaxSockets ) {
    if ( cursor->ifa_addr->sa_family == AF_INET 
            && !(cursor->ifa_flags & IFF_LOOPBACK) 
            && !(cursor->ifa_flags & IFF_POINTOPOINT) 
            &&  (cursor->ifa_flags & IFF_MULTICAST) ) {

      // Create socket
      sock_fds[number_sockets] = socket(AF_INET, SOCK_DGRAM, 0);
      if ( sock_fds[number_sockets] == -1 ) {
        // Error occurred
        return 0;
      }
      
      // Make the socket transmit through this interface
      if ( setsockopt(sock_fds[number_sockets], IPPROTO_IP, IP_MULTICAST_IF, &((struct sockaddr_in *)cursor->ifa_addr)->sin_addr, sizeof(struct in_addr)) != 0  ) {
        // Error occurred
        return 0;
      }
      
      // We're not interested in receiving our own messages, so we can disable loopback (don't rely solely on this - in some cases you can still receive your own messages)
      u_char loop = 0;
      if ( setsockopt(sock_fds[number_sockets], IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) != 0 ) {
        // Error occurred
        return 0;
      }
      
      number_sockets++;
    }
    cursor = cursor->ifa_next;
  }
  
  // Initialise multicast address
  memset(&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr(kMulticastAddress);
  addr.sin_port = htons(kPortNumber);
  
  // Send through each interface
  int i;
  for ( i=0; i<number_sockets; i++ ) {
    if ( sendto(sock_fds[i], data, length, 0, (struct sockaddr*)&addr, sizeof(addr)) < 0 ) {
      // Error occurred
      return 0;
    }
  }
  
  return 1;
}

/*!
 *  Receive loop
 *
 *  @description
 *    Loops forever, waiting for data, and printing whatever comes in.
 *  @result 0 on failure
 */
int receive() {
  
  // Create socket
  int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
  if ( sock_fd == -1 ) {
    // Error occurred
    return 0;
  }
  
  // Create address from which we want to receive, and bind it
  struct sockaddr_in addr;
  memset(&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = htons(kPortNumber);
  if ( bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0 ) {
    // Error occurred
    return 0;
  }
  
  // Obtain list of all network interfaces
  struct ifaddrs *addrs;
  if ( getifaddrs(&addrs) < 0 ) {
    // Error occurred
    return 0;
  }
  
  // Loop through interfaces, selecting those AF_INET devices that support multicast, but aren't loopback or point-to-point
  const struct ifaddrs *cursor = addrs;
  while ( cursor != NULL ) {
    if ( cursor->ifa_addr->sa_family == AF_INET 
             && !(cursor->ifa_flags & IFF_LOOPBACK) 
             && !(cursor->ifa_flags & IFF_POINTOPOINT) 
             &&  (cursor->ifa_flags & IFF_MULTICAST) ) {
        
      // Prepare multicast group join request
      struct ip_mreq multicast_req;
      memset(&multicast_req, 0, sizeof(multicast_req));
      multicast_req.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
      multicast_req.imr_interface = ((struct sockaddr_in *)cursor->ifa_addr)->sin_addr;
      
      // Workaround for some odd join behaviour: It's perfectly legal to join the same group on more than one interface,
      // and up to 20 memberships may be added to the same socket (see ip(4)), but for some reason, OS X spews 
      // 'Address already in use' errors when we actually attempt it.  As a workaround, we can 'drop' the membership
      // first, which would normally have no effect, as we have not yet joined on this interface.  However, it enables
      // us to perform the subsequent join, without dropping prior memberships.
      setsockopt(sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &multicast_req, sizeof(multicast_req));
      
      // Join multicast group on this interface
      if ( setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &multicast_req, sizeof(multicast_req)) < 0 ) {
        // Error occurred
        return 0;
      }
    }
    cursor = cursor->ifa_next;
  }
  
  char buffer[kBufferSize];
  socklen_t addr_len = sizeof(addr);
  
  while ( 1 ) {
    
    // Receive a message, waiting if there's nothing there yet
    int bytes_received = recvfrom(sock_fd, buffer, kBufferSize, 0, (struct sockaddr*)&addr, &addr_len);
    if ( bytes_received < 0 ) {
       // Error occurred
       return 0;
    }
    
    // Now we have bytes_received bytes of data in buffer. Print it!
    fwrite(buffer, sizeof(char), bytes_received, stdout);
    printf("\n");
  }  
}


/*!
 *   Main entry point
 */
int main(int argc, char** argv) {
  if ( argc < 2 ) {
    // No argument: Just listen
    printf("Listening...\n");
    if ( !receive() ) {
      printf("Error occurred: %s\n", strerror(errno));
      return 1;
    }
    return 0;
  }

  // Argument provided: Transmit this
  if ( transmit(argv[1], strlen(argv[1])) ) {
    printf("\"%s\" transmitted.\n", argv[1]);
  } else {
    printf("Error occurred: %s\n", strerror(errno));
    return 1;
  }
  
  return 0;
}