/**
 * Simple example of broadcast transmission and reception, through all network interfaces
 * http://atastypixel.com/blog/the-making-of-talkie-multi-interface-broadcasting-and-multicast/
 *
 *  Compile with:
 *      cc -o broadcast_sample_all_interfaces broadcast_sample_all_interfaces.c
 *
 *  Usage:
 *    To transmit:
 *      ./broadcast_sample_all_interfaces "Message to send"
 *
 *    To receive, call with no arguments:
 *      ./broadcast_sample_all_interfaces
 *
 * 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 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 broadcast, 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_BROADCAST) ) {

      // Create socket
      sock_fds[number_sockets] = socket(AF_INET, SOCK_DGRAM, 0);
      if ( sock_fds[number_sockets] == -1 ) {
        // Error occurred
        return 0;
      }
      
      // Create address from which we want to send, and bind it
      memset(&addr, 0, sizeof(addr));
      addr.sin_family = AF_INET;
      addr.sin_addr = ((struct sockaddr_in *)cursor->ifa_addr)->sin_addr;
      addr.sin_port = 0;
      
      if ( bind(sock_fds[number_sockets], (struct sockaddr*)&addr, sizeof(addr)) < 0 ) {
        // Error occurred
        return 0;
      }
      
      // Enable broadcast
      int flag = 1;
      if ( setsockopt(sock_fds[number_sockets], SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)) != 0 ) {
        // Error occurred
        return 0;
      }
      
      number_sockets++;
    }
    cursor = cursor->ifa_next;
  }
  
  // Initialise broadcast address
  memset(&addr, 0, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_BROADCAST;
  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;
  }
  
  char buffer[kBufferSize];
  int 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;
}