//=============================================================================
/**
 *  @file    SOCK_Dgram_Bcast_Test.cpp
 *
 *   This simple broadcast test is intended to check if ACE is capable
 *   of sending and receiving broadcasts. In single host environment most
 *   errors related to invalid broadcast initialization will not manifest
 *   themself, because usually broadcast on localhost interface works
 *   correctly. For this reason one should run also this test on two distinct
 *   hosts in single LAN.
 *   Tests that a call to open with an any address binds to the any address
 *   for the protocol passed in.
 *
 *  @author Marek Brudka (mbrudka@elka.pw.edu.pl)
 */
//=============================================================================


#include "test_config.h"
#include "ace/OS_NS_string.h"
#include "ace/OS_NS_unistd.h"
#include "ace/OS_NS_sys_time.h"
#include "ace/Log_Msg.h"
#include "ace/Get_Opt.h"
#include "ace/SOCK_Dgram_Bcast.h"
#include "ace/Thread_Manager.h"
#include "ace/Process.h"
#include "ace/Process_Manager.h"

static int              dgram_port  = 14521;
static int              dgrams_no   = 10;
static ACE_Time_Value   dgram_recv_timeout( 5, 0 );
static ACE_exitcode     receiver_exit_code = 0;

/*\brief Create and send single datagram
  \param socket broadcast over this socket
  \param datagram_no datagram identifier
  \return -1 if error, 0 if OK
*/
int send_datagram (ACE_SOCK_Dgram_Bcast &socket, int datagram_no)
{
    static char dgram_buffer[BUFSIZ];

    ACE_OS::snprintf (dgram_buffer, sizeof(dgram_buffer),
                      "Datagram %d", datagram_no);
    if (socket.send (dgram_buffer,
                     ACE_OS::strlen (dgram_buffer) + 1,
                     dgram_port) < 0 )
        ACE_ERROR_RETURN ((LM_ERROR, ACE_TEXT ("%p\n"),
                           ACE_TEXT ("Cannot broadcast datagram")), -1);
    else
        ACE_DEBUG ((LM_INFO, ACE_TEXT ("%C sent\n"), dgram_buffer));
    return 0;
}

/*\brief Send a sequence of datagrams with 1 second period
  \note Th function employs dgram_port and dgrams_no global variables
  \retval -1 if error
  \retval 0 if sent
*/
int run_sender( )
{
    ACE_DEBUG ((LM_INFO,
                ACE_TEXT ("Sending %d datagrams on port %d\n"),
                dgrams_no,
                dgram_port));
    ACE_SOCK_Dgram_Bcast socket;

    if (socket.open (ACE_Addr::sap_any) != -1)
      {
        while (dgrams_no-- != 0)
          {
            if (send_datagram (socket, dgrams_no) < 0)
              break;
            ACE_OS::sleep (1);
          }
        socket.close ();
        return (0);
      }

    ACE_ERROR_RETURN ((LM_ERROR, ACE_TEXT ("%p\n"),
                       ACE_TEXT ("Cannot open broadcast socket")), -1);
}

/*\brief Receive single datagram
  \note The function employes dgram_port and dgram_recv_timeout variables
  \retval -1 if not received,
  \retval 0  received a datagrams
*/
int run_receiver ()
{
  ACE_DEBUG
    ((LM_INFO,
      ACE_TEXT ("Receiving datagrams from port %d with timeout %d ms\n"),
      dgram_port, dgram_recv_timeout.msec ()));

  ACE_SOCK_Dgram socket;
  ACE_INET_Addr  remote ;
  static char    dgram_buffer[BUFSIZ];

  if (socket.open (ACE_INET_Addr (dgram_port)) != -1)
    if (socket.recv (dgram_buffer, sizeof (dgram_buffer),
                     remote, 0, &dgram_recv_timeout) > 0)
      {
        ACE_DEBUG ((LM_INFO, ACE_TEXT ("%C received\n"), dgram_buffer));
        return 0;
      }
    else
      {
        ACE_ERROR_RETURN ((LM_ERROR, ACE_TEXT ("%p\n"),
                           ACE_TEXT ("Cannot receive datagrams")), -1);
      }
  else
    {
      ACE_ERROR_RETURN ((LM_ERROR,
                         ACE_TEXT ("%p: %d\n"),
                         ACE_TEXT ("Cannot open broadcast socket on port"), dgram_port), -1);
    }
}

#if !defined (ACE_HAS_PROCESS_SPAWN) && defined (ACE_HAS_THREADS)
/* \brief Thread main function to run run_receiver function
   \note run_receiver return valu is stored in receiver_exit_code global variable
*/
static ACE_THR_FUNC_RETURN run_thread_receiver (void *)
{
  receiver_exit_code = run_receiver ();
  return 0;
}
#endif /* !defined (ACE_HAS_PROCESS_SPAWN) && defined (ACE_HAS_THREADS) */

/* \brief Just runs automatic tests

Function sends a number of datagrams, spawns child thread or process and
tries to receive at least one datagram.
\retval 0 datagram was received
\retval -1 datagram was not received
*/
int run_auto_test (const ACE_TCHAR *prog_name)
{
#if defined (ACE_HAS_PROCESS_SPAWN)
  ACE_DEBUG ((LM_INFO, ACE_TEXT ("Running auto_tests in process mode\n")));

  ACE_Process_Options opts;
  pid_t child_pid;
  opts.command_line (ACE_TEXT ("%s -p %d -t %d -a -r"),
                     prog_name, dgram_port, static_cast<int>(dgram_recv_timeout.msec ()));
  if ((child_pid = ACE_Process_Manager::instance ()->spawn (opts)) == -1)
    ACE_ERROR_RETURN ((LM_ERROR, ACE_TEXT ("%p\n"), ACE_TEXT ("spawn_n()")), -1);

#elif defined (ACE_HAS_THREADS)
  ACE_UNUSED_ARG (prog_name);
  ACE_DEBUG ((LM_INFO, ACE_TEXT ("Running auto_tests in thread mode\n")));
  if (ACE_Thread_Manager::instance ()->spawn (run_thread_receiver) == -1)
    ACE_ERROR_RETURN ((LM_ERROR,
                       ACE_TEXT ("%p\n"),
                       ACE_TEXT ("spawn_n ()")), -1);
#else
  ACE_ERROR_RETURN ((LM_ERROR,
                     ACE_TEXT ("Cannot run in auto_test mode without fork or threads.\n")),
                      -1);
#endif /* defined (ACE_HAS_PROCESS_SPAWN) */

  ACE_DEBUG ((LM_INFO,
              ACE_TEXT ("Sending datagrams on port %d in auto_test mode\n"),
              dgram_port));

  ACE_SOCK_Dgram_Bcast socket;

  if (socket.open (ACE_Addr::sap_any) != -1)
    {
      // send datagrams until child finishes
      while (1)
        {
          send_datagram (socket, dgrams_no--);
          ACE_Time_Value child_timeout (1);
#if defined (ACE_HAS_PROCESS_SPAWN)

          if (ACE_Process_Manager::instance ()->wait (child_pid,
                                                      child_timeout,
                                                      &receiver_exit_code) == child_pid)
            break;
#else /* ACE_HAS_THREADS */
          // sleep 1 second or wait for child thread
          child_timeout += ACE_OS::gettimeofday () ;
          if (ACE_Thread_Manager::instance ()->wait (&child_timeout) == 0)
            break;
#endif
        }
      socket.close ();
      ACE_DEBUG ((LM_INFO, ACE_TEXT ("Child finished with %d exit code\n"),
                  receiver_exit_code));
    }
  else
    ACE_ERROR_RETURN ((LM_ERROR, ACE_TEXT ("%p\n"),
                       ACE_TEXT ("Cannot open broadcast socket")), -1);
  return (receiver_exit_code);
}

void print_usage (void)
{
  ACE_OS::printf("Usage:SOCK_Dgram_Bast_Test [-p port] [-n dgrams_no] [-t timeout_ms] [-s] [-r]\n"
                 "\tp broadcast port [14521]\n"
                 "\tn number of datagrams to broadcast [30] (<0 infinite)\n"
                 "\tt timeout in seconds for receive [5] (<=0 infinite)\n"
                 "\ts send datagrams and exit\n"
                 "\tr receive one datagram and exit\n\n"
                 "\t  run auto-test when no r and s option is passed\n"
                 "\t  test failures are manifested by -1 exit value, otherwise 0\n");
}

int run_main (int argc, ACE_TCHAR *argv[])
{
  // parse options and run in appropriate mode
  int opt             = 0;
  int auto_test_recv  = 0;
  int result          = 0;
  ACE_Get_Opt opts (argc, argv, ACE_TEXT ("p:t:n:sra"));
  while ((opt = opts ()) != -1)
    switch (opt)
      {
      case 'a':
        auto_test_recv = 1;
        break;
      case 's':
        return (run_sender());
      case 'r':
        {
          if (auto_test_recv)
            {
              ACE_START_TEST (ACE_TEXT ("SOCK_Dgram_Bcast_Test_Child"));
              result = run_receiver ();
              ACE_END_TEST;
              return result;
            }
          return (run_receiver ());
        }
      case 'n':
        dgrams_no = ACE_OS::atoi (opts.opt_arg ());
        break;
      case 't':
        dgram_recv_timeout.msec (ACE_OS::atoi (opts.opt_arg ()));
        break;
      case 'p':
        dgram_port = ACE_OS::atoi (opts.opt_arg ());
        break;
      default:
        print_usage ();
        return -1;
      }
  ACE_START_TEST (ACE_TEXT ("SOCK_Dgram_Bcast_Test"));

#ifndef ACE_LACKS_IOCTL
  result = run_auto_test (argc > 0 ? argv[0] : ACE_TEXT ("SOCK_Dgram_Bcast_Test"));
#endif

  ACE_END_TEST;
  return result;
}
