//=============================================================================
/**
 *  @file    SOCK_Dgram_Test.cpp
 *
 *   Tests that a call to open with an any address binds to the any address
 *   for the protocol passed in.
 *
 *   This test uses the same test setup as SOCK_Test.
 *
 *  @author Brian Buesker (bbuesker@qualcomm.com)
 */
//=============================================================================


#include "test_config.h"
#include "ace/OS_NS_string.h"
#include "ace/OS_NS_sys_wait.h"
#include "ace/Thread.h"
#include "ace/Thread_Manager.h"
#include "ace/SOCK_Dgram.h"
#include "ace/Log_Msg.h"
#include "ace/Time_Value.h"
#include "ace/OS_NS_unistd.h"

#define SERVER_PORT 20000
#define TEST_DATA ACE_TEXT ("UDP Open Test")

static void *
client (void *arg)
{
  ACE_INET_Addr *remote_addr = (ACE_INET_Addr *) arg;
  ACE_INET_Addr server_addr, peer_addr;
  ACE_SOCK_Dgram cli_dgram;
  ACE_Time_Value timeout (1);

  ACE_TCHAR buf[20];
  ACE_TCHAR hostname_string[100];

  if (remote_addr->get_type () == AF_INET)
    {
      server_addr.set (remote_addr->get_port_number (),
                       ACE_LOCALHOST,
                       1,
                       remote_addr->get_type ());
    }
#if defined (ACE_HAS_IPV6)
  else
    {
      server_addr.set (remote_addr->get_port_number(),
                       ACE_IPV6_LOCALHOST,
                       1,
                       remote_addr->get_type ());
    }
#endif /* ACE_HAS_IPV6 */

  server_addr.addr_to_string (hostname_string, 100);
  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT ("(%P|%t) starting client UDP send to %s\n"),
              hostname_string));

  if (cli_dgram.open (ACE_Addr::sap_any, server_addr.get_type ()) == -1)
    {
      ACE_ERROR((LM_ERROR,
                 ACE_TEXT("(%P|%t) protocol %d, %p\n"),
                 server_addr.get_type (),
                 ACE_TEXT ("SOCK_Dgram open")));
      return 0;
    }

  {
    if (remote_addr->get_type () == AF_INET) {
#if defined (ACE_RECVPKTINFO)
      int sockopt = 1;
      if (cli_dgram.set_option(IPPROTO_IP, ACE_RECVPKTINFO, &sockopt, sizeof sockopt) == -1) {
        ACE_ERROR((LM_ERROR,
                   ACE_TEXT("(%P|%t) setsockopt failed\n")));
        return 0;
      } else {
        ACE_DEBUG((LM_DEBUG,
                   ACE_TEXT("(%P|%t) setsockopt succeeded\n")));
      }
#endif
    }
#if defined (ACE_HAS_IPV6)
    else {
#if defined (ACE_RECVPKTINFO6)
      int sockopt = 1;
      if (cli_dgram.set_option(IPPROTO_IPV6, ACE_RECVPKTINFO6, &sockopt, sizeof sockopt) == -1) {
        ACE_ERROR((LM_ERROR,
                   ACE_TEXT("(%P|%t) setsockopt failed\n")));
        return 0;
      } else {
        ACE_DEBUG((LM_DEBUG,
                   ACE_TEXT("(%P|%t) setsockopt succeeded\n")));
      }
#endif
    }
#endif
  }

  if (cli_dgram.send (TEST_DATA, sizeof (TEST_DATA), server_addr) == -1)
    {
      ACE_ERROR((LM_ERROR,
                 ACE_TEXT("(%P|%t) UDP send to %s %p\n"),
                 hostname_string,
                 ACE_TEXT ("failed")));
      return 0;
    }

    {
      ACE_INET_Addr local_addr, to_addr;
      cli_dgram.get_local_addr(local_addr);

      if (local_addr.get_type () == AF_INET)
        {
          local_addr.set (local_addr.get_port_number (),
                          ACE_LOCALHOST,
                          1,
                          local_addr.get_type ());
          to_addr.set (9999, ACE_LOCALHOST, 1, AF_INET);
        }
#if defined (ACE_HAS_IPV6)
      else
        {
          local_addr.set (local_addr.get_port_number(),
                           ACE_IPV6_LOCALHOST,
                           1,
                           local_addr.get_type ());
          to_addr.set (9999, ACE_IPV6_LOCALHOST, 1, AF_INET6);
        }
#endif /* ACE_HAS_IPV6 */

#if defined(ACE_LACKS_RECVMSG)
      ssize_t rcv_cnt = cli_dgram.recv (buf,
                                        sizeof (buf),
                                        peer_addr,
                                        0,
                                        &timeout);
#else
      iovec iov[1];
      // Some platforms define iov_base as char* instead of void*.
      iov[0].iov_base = (char *)buf;
      iov[0].iov_len = sizeof buf;

      ssize_t rcv_cnt = cli_dgram.recv (iov,
                                        1,
                                        peer_addr,
                                        0,
                                        &to_addr);
#endif

      if (rcv_cnt == -1)
        {
          if (errno == ETIME)
          {
            ACE_ERROR((LM_ERROR,
                       ACE_TEXT("(%P|%t) UDP recv on proto %d timed out\n"),
                       server_addr.get_type ()));
          }
          else
          {
            ACE_ERROR((LM_ERROR,
                       ACE_TEXT("(%P|%t) UDP recv on proto %d %p\n"),
                       server_addr.get_type (),
                       ACE_TEXT("failed")));
          }
        }
      else
        {
          // recv() ok, check data, 'from', and 'to' address
          size_t rcv_siz = static_cast<size_t> (rcv_cnt);
          if (rcv_siz != sizeof (TEST_DATA))
            {
              ACE_ERROR ((LM_ERROR,
                          ACE_TEXT ("(%P|%t) recv cnt %B; should be %B\n"),
                          rcv_siz,
                          sizeof (TEST_DATA)));
            }
          else
            {
              buf[rcv_siz/sizeof (ACE_TCHAR)] = '\0';
              if (ACE_OS::strncmp (buf, TEST_DATA, ACE_OS::strlen (TEST_DATA)))
                ACE_ERROR ((LM_ERROR,
                            ACE_TEXT ("(%P|%t) recv string should be %s; ")
                            ACE_TEXT (" but is %s\n"),
                            TEST_DATA, buf));
            }
          if (peer_addr.get_type () != server_addr.get_type ())
            ACE_ERROR ((LM_ERROR,
                        ACE_TEXT ("(%P|%t) recv addr type %d; should be %d\n"),
                        peer_addr.get_type (),
                        server_addr.get_type ()));
          if (peer_addr.get_size () != server_addr.get_size ())
            ACE_ERROR ((LM_ERROR,
                        ACE_TEXT ("(%P|%t) recv addr size %d; should be %d\n"),
                        peer_addr.get_size (),
                        server_addr.get_size ()));
#if (defined ACE_RECVPKTINFO6 || defined ACE_RECVPKTINFO) && !defined ACE_FACE_SAFETY_EXTENDED
          if (local_addr != to_addr)
            ACE_ERROR ((LM_ERROR,
                        ACE_TEXT ("(%P|%t) local addr is not equal to sent-to addr\n")));
#endif
        }
    }

  cli_dgram.close();

  return 0;
}

static void *
server (void *arg)
{
  ACE_SOCK_Dgram *server_dgram = (ACE_SOCK_Dgram *) arg;
  ACE_INET_Addr peer_addr;

  ACE_TCHAR buf[20];
  ACE_TCHAR hostname_string[100];
  ssize_t rcv_cnt;

  if ((rcv_cnt = server_dgram->recv (buf, sizeof (buf), peer_addr, 0)) == -1)
    {
      ACE_ERROR((LM_ERROR, ACE_TEXT("(%P|%t) %p\n"), ACE_TEXT("server recv")));
    }
  else
    {
      // recv() ok, check data and 'from' address
      size_t rcv_siz = static_cast<size_t> (rcv_cnt);
      if (rcv_siz != sizeof (TEST_DATA))
        {
          ACE_ERROR ((LM_ERROR,
                      ACE_TEXT ("(%P|%t) recv cnt %B; should be %B\n"),
                      rcv_siz,
                      sizeof (TEST_DATA)));
        }
      else
        {
          buf[rcv_siz / sizeof (ACE_TCHAR)] = '\0';
          if (ACE_OS::strncmp (buf, TEST_DATA, ACE_OS::strlen (TEST_DATA)))
            ACE_ERROR ((LM_ERROR,
                        ACE_TEXT ("(%P|%t) recv string should be %s; ")
                        ACE_TEXT (" but is %s\n"),
                        TEST_DATA, buf));
        }

      if (0 == peer_addr.addr_to_string (hostname_string,
          sizeof (hostname_string)))
        ACE_DEBUG ((LM_DEBUG,
                    ACE_TEXT ("(%P|%t) Echoing data to %s\n"),
                    hostname_string));
      else
        ACE_ERROR ((LM_ERROR,
                    ACE_TEXT ("(%P|%t) %p\n"),
                    ACE_TEXT ("addr_to_string")));
      if (server_dgram->send (TEST_DATA, sizeof (TEST_DATA),
                              peer_addr, 0) == -1)
        {
          ACE_ERROR((LM_ERROR,
                     ACE_TEXT("(%P|%t) %p\n"),
                     ACE_TEXT("Server UDP send failed")));
        }
    }
  server_dgram->close ();

  return 0;
}

static int
spawn (int proto)
{
  ACE_SOCK_Dgram server_dgram;
  ACE_INET_Addr server_addr;

  if (proto == AF_INET)
    {
      server_addr.set (SERVER_PORT, ACE_LOCALHOST, 1, proto);
    }
#if defined (ACE_HAS_IPV6)
  else
    {
      server_addr.set (SERVER_PORT, ACE_IPV6_LOCALHOST, 1, proto);
    }
#endif /* ACE_HAS_IPV6 */

  // Bind UDP server to the appropriate port
  if (server_dgram.open (server_addr, proto) == -1)
    {
      ACE_ERROR_RETURN ((LM_ERROR,
                         ACE_TEXT ("(%P|%t) %p\n"),
                         ACE_TEXT ("server dgram open")),
                         1);
    }
  else
    {
      if (server_addr.get_port_number() != SERVER_PORT)
        {
          ACE_TCHAR hostname_string[100];
          server_addr.addr_to_string (hostname_string, 100);
          ACE_ERROR_RETURN ((LM_ERROR,
                             ACE_TEXT("(%P|%t) Portnumber has unexpected value of %d on host %s\n"),
                             server_addr.get_port_number(), hostname_string), 1);
        }
      else
        {
          ACE_DEBUG ((LM_DEBUG,
                      ACE_TEXT ("(%P|%t) started server at proto %d, port %d\n"),
                      proto,
                      server_addr.get_port_number ()));
#if !defined (ACE_LACKS_FORK)
          switch (ACE_OS::fork (ACE_TEXT ("child")))
            {
            case -1:
              ACE_ERROR_BREAK ((LM_ERROR,
                                ACE_TEXT ("(%P|%t) %p\n"),
                                ACE_TEXT ("fork failed")));
              /* NOTREACHED */
            case 0:
              client (&server_addr);
              ACE_OS::exit (0);
              /* NOTREACHED */
            default:
              server ((void *) &server_dgram);
              ACE_OS::wait ();
            }
#elif defined (ACE_HAS_THREADS)
          if (ACE_Thread_Manager::instance ()->spawn
              (ACE_THR_FUNC (server),
               (void *) &server_dgram,
               THR_NEW_LWP | THR_DETACHED) == -1)
            ACE_ERROR_RETURN ((LM_ERROR,
                               ACE_TEXT ("(%P|%t) %p\n"),
                               ACE_TEXT ("thread create failed")),
                               1);

          if (ACE_Thread_Manager::instance ()->spawn
              (ACE_THR_FUNC (client),
               (void *) &server_addr,
               THR_NEW_LWP | THR_DETACHED) == -1)
            ACE_ERROR_RETURN ((LM_ERROR,
                        ACE_TEXT ("(%P|%t) %p\n"),
                        ACE_TEXT ("thread create failed")),
                        1);

          // Wait for the threads to exit.
          ACE_Thread_Manager::instance ()->wait ();
#else
          ACE_ERROR ((LM_INFO,
                      ACE_TEXT ("(%P|%t) ")
                      ACE_TEXT ("only one thread may be run ")
                      ACE_TEXT ("in a process on this platform\n")));
#endif /* ACE_HAS_THREADS */
      }

      server_dgram.close ();
    }

  return 0;
}

int run_main (int, ACE_TCHAR *[])
{
  ACE_START_TEST (ACE_TEXT ("SOCK_Dgram_Test"));

  int retval = spawn (AF_INET);

#if defined (ACE_HAS_IPV6)

  if (retval == 0)
    {
      retval = spawn (AF_INET6);
    }

#endif /* ACE_HAS_IPV6 */

  ACE_END_TEST;
  return retval;
}
