Logo Search packages:      
Sourcecode: zeromq version File versions  Download package

if_unittest.c

/* vim:ts=8:sts=8:sw=4:noai:noexpandtab
 *
 * unit tests for network interface declaration parsing.
 *
 * CAUTION: Assumes host is IPv4 by default for AF_UNSPEC
 *
 * Copyright (c) 2009 Miru Limited.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <glib.h>
#include <check.h>

#include "pgm/if.h"
#include "pgm/ip.h"
#include "pgm/sockaddr.h"


/* mock state */

struct mock_host_t {
      struct sockaddr_storage address;
      char*             canonical_hostname;
      char*             alias;
};

struct mock_network_t {
      char*             name;
      struct sockaddr_storage number;
      char**                  aliases;
};

struct mock_interface_t {
      unsigned int            index;
      char*             name;
      unsigned int            flags;
      struct sockaddr_storage addr;
      struct sockaddr_storage netmask;
};

static GList *mock_hosts = NULL, *mock_networks = NULL, *mock_interfaces = NULL;

#define MOCK_HOSTNAME         "kiku"
#define MOCK_HOSTNAME6        "ip6-kiku"        /* ping6 doesn't work on fe80:: */
#define MOCK_NETWORK          "private"         /* /etc/networks */
#define MOCK_NETWORK6         "ip6-private"
#define MOCK_PGM_NETWORK      "pgm-private"
#define MOCK_PGM_NETWORK6     "pgm-ip6-private"
#define MOCK_INTERFACE        "eth0"
#define MOCK_INTERFACE_INDEX  2
#define MOCK_ADDRESS          "10.6.28.33"
#define MOCK_GROUP            ((in_addr_t) 0xefc00001) /* 239.192.0.1 */
#define MOCK_GROUP6_INIT      { { { 0xff,8,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } }  /* ff08::1 */
static const struct in6_addr mock_group6_addr = MOCK_GROUP6_INIT;
#define MOCK_ADDRESS6         "2002:dce8:d28e::33"
#define MOCK_ADDRESS6_INIT    { { { 0x20,2,0xdc,0xe8,0xd2,0x8e,0,0,0,0,0,0,0,0,0,0x33 } } }
static const struct in6_addr mock_address6_addr = MOCK_ADDRESS6_INIT;

static int mock_family =      0;
static char* mock_kiku =      MOCK_HOSTNAME;
static char* mock_localhost = "localhost";
static char* mock_invalid =   "invalid.invalid";            /* RFC 2606 */
static char* mock_toolong =   "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij12345"; /* 65 */
static char* mock_hostname =  NULL;


static
gpointer
create_host (
      const char* address,
      const char* canonical_hostname,
      const char* alias
      )
{
      struct mock_host_t* new_host;

      g_assert (address);
      g_assert (canonical_hostname);

      new_host = g_slice_alloc0 (sizeof(struct mock_host_t));
      g_assert (pgm_sockaddr_pton (address, &new_host->address));
      new_host->canonical_hostname = g_strdup (canonical_hostname);
      new_host->alias = alias ? g_strdup (alias) : NULL;

      return new_host;
}

static
gpointer
create_network (
      const char* name,
      const char* number
      )
{
      struct mock_network_t* new_network;

      g_assert (name);
      g_assert (number);

      new_network = g_slice_alloc0 (sizeof(struct mock_network_t));
      new_network->name = g_strdup (name);
      g_assert (pgm_sockaddr_pton (number, &new_network->number));

      return new_network;
}

static
gpointer
create_interface (
      const unsigned    index,
      const char* name,
      const char* flags
      )
{
      struct mock_interface_t* new_interface;

      g_assert (name);
      g_assert (flags);

      new_interface = g_slice_alloc0 (sizeof(struct mock_interface_t));
      new_interface->index = index;
      new_interface->name = g_strdup (name);

      struct sockaddr_in* sin = (gpointer)&new_interface->addr;
      struct sockaddr_in6* sin6 = (gpointer)&new_interface->addr;

      gchar** tokens = g_strsplit (flags, ",", 0);
      for (guint i = 0; tokens[i]; i++)
      {
            if (strcmp (tokens[i], "up") == 0)
                  new_interface->flags |= IFF_UP;
            else if (strcmp (tokens[i], "down") == 0)
                  new_interface->flags |= 0;
            else if (strcmp (tokens[i], "loop") == 0)
                  new_interface->flags |= IFF_LOOPBACK;
            else if (strcmp (tokens[i], "broadcast") == 0)
                  new_interface->flags |= IFF_BROADCAST;
            else if (strcmp (tokens[i], "multicast") == 0)
                  new_interface->flags |= IFF_MULTICAST;
            else if (strncmp (tokens[i], "ip=", strlen("ip=")) == 0) {
                  const char* addr = tokens[i] + strlen("ip=");
                  g_assert (pgm_sockaddr_pton (addr, &new_interface->addr));
            }
            else if (strncmp (tokens[i], "netmask=", strlen("netmask=")) == 0) {
                  const char* addr = tokens[i] + strlen("netmask=");
                  g_assert (pgm_sockaddr_pton (addr, &new_interface->netmask));
            }
            else if (strncmp (tokens[i], "scope=", strlen("scope=")) == 0) {
                  const char* scope = tokens[i] + strlen("scope=");
                  g_assert (AF_INET6 == ((struct sockaddr*)&new_interface->addr)->sa_family);
                  ((struct sockaddr_in6*)&new_interface->addr)->sin6_scope_id = atoi (scope);
            }
            else
                  g_error ("parsing failed for flag %s%s%s",
                        tokens[i] ? "\"" : "", tokens[i] ? tokens[i] : "(null)", tokens[i] ? "\"" : "");
      }
                  
      g_strfreev (tokens);
      return new_interface;
}

#define APPEND_HOST2(a,b,c)   \
            do { \
                  gpointer data = create_host ((a), (b), (c)); \
                  g_assert (data); \
                  mock_hosts = g_list_append (mock_hosts, data); \
                  g_assert (mock_hosts); g_assert (mock_hosts->data); \
            } while (0)
#define APPEND_HOST(a,b)      APPEND_HOST2((a),(b),NULL)
#define APPEND_NETWORK(a,b)   \
            do { \
                  gpointer data = create_network ((a), (b)); \
                  g_assert (data); \
                  mock_networks = g_list_append (mock_networks, data); \
                  g_assert (mock_networks); g_assert (mock_networks->data); \
            } while (0)
#define APPEND_INTERFACE(a,b,c)     \
            do { \
                  gpointer data = create_interface ((a), (b), (c)); \
                  g_assert (data); \
                  mock_interfaces = g_list_append (mock_interfaces, data); \
                  g_assert (mock_interfaces); g_assert (mock_interfaces->data); \
            } while (0)
static
void
mock_setup_net (void)
{
      mock_hostname = mock_kiku;

      APPEND_HOST (     "127.0.0.1",            "localhost");
      APPEND_HOST2(     "10.6.28.33",           "kiku.hk.miru.hk",      "kiku");
      APPEND_HOST2(     "2002:dce8:d28e::33",   "ip6-kiku",       "kiku");
      APPEND_HOST2(     "172.12.90.1",          "mi-hee.ko.miru.hk",    "mi-hee");
      APPEND_HOST2(     "::1",                  "ip6-localhost",  "ip6-loopback");
      APPEND_HOST (     "239.192.0.1",          "PGM.MCAST.NET");
      APPEND_HOST (     "ff08::1",        "IP6-PGM.MCAST.NET");

      APPEND_NETWORK(   "loopback", "127.0.0.0");
      APPEND_NETWORK(   "private",  "10.6.28.0");
      APPEND_NETWORK(   "private2", "172.16.90.0");
      APPEND_NETWORK( "pgm-private",      "239.192.0.1");
#ifdef CONFIG_HAVE_IP6_NETWORKS
      APPEND_NETWORK(   "ip6-private",    "2002:dce8:d28e:0:0:0");
      APPEND_NETWORK( "ip6-pgm-private","ff08::1");
#endif

      APPEND_INTERFACE( 1,    "lo", "up,loop");
      APPEND_INTERFACE( 2,    "eth0",     "up,broadcast,multicast");
      APPEND_INTERFACE( 3,    "eth1",     "down,broadcast,multicast");
      APPEND_INTERFACE( 1,    "lo", "up,loop,ip=127.0.0.1,netmask=255.0.0.0");
      APPEND_INTERFACE( 2,    "eth0",     "up,broadcast,multicast,ip=10.6.28.33,netmask=255.255.255.0");
      APPEND_INTERFACE( 1,    "lo", "up,loop,ip=::1,netmask=ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff,scope=0");
      APPEND_INTERFACE( 2,    "eth0",     "up,broadcast,multicast,ip=2002:dce8:d28e::33,netmask=ffff:ffff:ffff:ffff::0,scope=0");
      APPEND_INTERFACE( 2,    "eth0",     "up,broadcast,multicast,ip=fe80::214:5eff:febd:6dda,netmask=ffff:ffff:ffff:ffff::0,scope=2");
}

static
void
mock_teardown_net (void)
{
      GList* list;

      list = mock_hosts;
      while (list) {
            struct mock_host_t* host = list->data;
            g_free (host->canonical_hostname);
            if (host->alias)
                  g_free (host->alias);
            g_slice_free1 (sizeof(struct mock_host_t), host);
            list = list->next;
      }
      g_list_free (mock_hosts);

      list = mock_networks;
      while (list) {
            struct mock_network_t* network = list->data;
            g_free (network->name);
            g_slice_free1 (sizeof(struct mock_network_t), network);
            list = list->next;
      }
      g_list_free (mock_networks);

      list = mock_interfaces;
      while (list) {
            struct mock_interface_t* interface = list->data;
            g_free (interface->name);
            g_slice_free1 (sizeof(struct mock_interface_t), interface);
            list = list->next;
      }
      g_list_free (mock_interfaces);
}

#include "pgm/getifaddrs.h"

int
pgm_compat_getifaddrs (
      struct pgm_ifaddrs**    ifap
      )
{
      g_assert_not_reached();
      return -1;
}

void
pgm_compat_freeifaddrs (
      struct pgm_ifaddrs*     ifap
      )
{
      g_assert_not_reached();
}

/* mock functions for external references */

int
mock_pgm_getifaddrs (
      struct pgm_ifaddrs**    ifap
      )
{
      g_debug ("mock_pgm_getifaddrs (ifap:%p)", (void*)ifap);

      if (NULL == ifap) {
            errno = EINVAL;
            return -1;
      }

      GList* list = mock_interfaces;
      int n = g_list_length (list);
      struct pgm_ifaddrs* ifa = calloc (n, sizeof(struct pgm_ifaddrs));
      struct pgm_ifaddrs* ift = ifa;
      while (list) {
            struct mock_interface_t* interface = list->data;
            ift->ifa_addr = (gpointer)&interface->addr;
            ift->ifa_name = interface->name;
            ift->ifa_flags = interface->flags;
            ift->ifa_netmask = (gpointer)&interface->netmask;
            list = list->next;
            if (list) {
                  ift->ifa_next = ift + 1;
                  ift = ift->ifa_next;
            }
      }

      *ifap = ifa;

      return 0;
}

void
mock_pgm_freeifaddrs (
      struct pgm_ifaddrs*           ifa
      )
{
      free (ifa);
}

#include "pgm/nametoindex.h"

int
pgm_compat_if_nametoindex (
      const int         iffamily,
      const char*       ifname
      )
{
      g_assert_not_reached();
      return -1;
}

int
mock_pgm_if_nametoindex (
      const int         iffamily,
      const char*       ifname
      )
{
      GList* list = mock_interfaces;
      while (list) {
            const struct mock_interface_t* interface = list->data;
            if (0 == strcmp (ifname, interface->name))
                  return interface->index;
            list = list->next;
      }
      return 0;
}

static
char*
mock_if_indextoname (
      unsigned int            ifindex,
      char*             ifname
      )
{
      GList* list = mock_interfaces;
      while (list) {
            const struct mock_interface_t* interface = list->data;
            if (interface->index == ifindex) {
                  strcpy (ifname, interface->name);
                  return ifname;
            }
            list = list->next;
      }
      errno = ENXIO;
      return NULL;
}

static
int
mock_getnameinfo (
      const struct sockaddr*  sa,
      socklen_t         salen,
      char*             host,
      size_t                  hostlen,
      char*             serv,
      size_t                  servlen,
      int               flags
      )
{
      if ((0 == hostlen && 0 == servlen) ||
            (NULL == host && NULL == serv))
            return EAI_NONAME;

      if (flags & NI_NUMERICHOST && flags & NI_NAMEREQD)
            return EAI_BADFLAGS;

/* pre-conditions */
      g_assert (NULL != host);
      g_assert (hostlen > 0);
      g_assert (NULL == serv);
      g_assert (0 == servlen);

      const int sa_family = sa->sa_family;

      if (AF_INET == sa_family)
            g_assert (sizeof(struct sockaddr_in) == salen);
      else {
            g_assert (AF_INET6 == sa_family);
            g_assert (sizeof(struct sockaddr_in6) == salen);
      }

      if (!(flags & NI_NUMERICHOST))
      {
            GList* list = mock_hosts;
            while (list) {
                  const struct mock_host_t* _host = list->data;
                  const int host_family = ((struct sockaddr*)&_host->address)->sa_family;
                  const size_t host_len = AF_INET == host_family ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);

                  if (host_family == sa_family &&
                      host_len == salen &&
                      0 == memcmp (sa, &_host->address, salen))
                  {
                        if (hostlen < (1 + strlen(_host->canonical_hostname)))
                              return EAI_OVERFLOW;
                        strncpy (host, _host->canonical_hostname, hostlen);
                        return 0;
                  }
                  list = list->next;
            }

            if (flags & NI_NAMEREQD)
                  return EAI_NONAME;
      }

      if (AF_INET == sa_family) 
            pgm_inet_ntop (sa_family, &((const struct sockaddr_in*)sa)->sin_addr, host, hostlen);
      else {
            const unsigned scope = ((const struct sockaddr_in6*)sa)->sin6_scope_id;
            pgm_inet_ntop (sa_family, &((const struct sockaddr_in6*)sa)->sin6_addr, host, hostlen);
            if (scope) {
                  char buffer[1+IF_NAMESIZE];
                  strcat (host, "%");
                  strcat (host, mock_if_indextoname (scope, buffer));
            }
      }
      return 0;
}

static
int
mock_getaddrinfo (
      const char*       node,
      const char*       service,
      const struct addrinfo*  hints,
      struct addrinfo** res
      )
{
      const int ai_flags  = hints ? hints->ai_flags  : (AI_V4MAPPED | AI_ADDRCONFIG);
      const int ai_family = hints ? hints->ai_family : AF_UNSPEC;
      GList* list;
      struct sockaddr_storage addr;

      if (NULL == node && NULL == service)
            return EAI_NONAME;

/* pre-conditions */
      g_assert (NULL != node);
      g_assert (NULL == service);
      g_assert (!(ai_flags & AI_CANONNAME));
      g_assert (!(ai_flags & AI_NUMERICSERV));
      g_assert (!(ai_flags & AI_V4MAPPED));

      g_message ("mock_getaddrinfo (node:%s%s%s service:%s%s%s hints:%p res:%p)",
            node ? "\"" : "", node ? node : "(null)", node ? "\"" : "",
            service ? "\"" : "", service ? service : "(null)", service ? "\"" : "",
            (gpointer)hints,
            (gpointer)res);

      gboolean has_ip4_config;
      gboolean has_ip6_config;

      if (hints && hints->ai_flags & AI_ADDRCONFIG)
      {
            has_ip4_config = has_ip6_config = FALSE;
            list = mock_interfaces;
            while (list) {
                  const struct mock_interface_t* interface = list->data;
                  if (AF_INET == ((struct sockaddr*)&interface->addr)->sa_family)
                        has_ip4_config = TRUE;
                  else if (AF_INET6 == ((struct sockaddr*)&interface->addr)->sa_family)
                        has_ip6_config = TRUE;
                  if (has_ip4_config && has_ip6_config)
                        break;
                  list = list->next;
            }
      } else {
            has_ip4_config = has_ip6_config = TRUE;
      }

      if (ai_flags & AI_NUMERICHOST) {
            pgm_sockaddr_pton (node, &addr);
      }
      list = mock_hosts;
      while (list) {
            struct mock_host_t* host = list->data;
            const int host_family = ((struct sockaddr*)&host->address)->sa_family;
            if (((strcmp (host->canonical_hostname, node) == 0) ||
                 (host->alias && strcmp (host->alias, node) == 0) || 
                 (ai_flags & AI_NUMERICHOST &&
                  0 == pgm_sockaddr_cmp ((struct sockaddr*)&addr, (struct sockaddr*)&host->address)))
                 &&
                (host_family == ai_family || AF_UNSPEC == ai_family) &&
                ((AF_INET == host_family && has_ip4_config) || (AF_INET6 == host_family && has_ip6_config)))
            {
                  struct addrinfo* ai = malloc (sizeof(struct addrinfo));
                  memset (ai, 0, sizeof(struct addrinfo));
                  ai->ai_family = host_family;
                  ai->ai_addrlen = AF_INET == host_family ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
                  ai->ai_addr = (gpointer)&host->address;
                  *res = ai;
                  return 0;
            }
            list = list->next;
      }
      return EAI_NONAME;
}

static
void
mock_freeaddrinfo (
      struct addrinfo*  res
      )
{
      free (res);
}

static
int
mock_gethostname (
      char*             name,
      size_t                  len
      )
{
      if (NULL == name) {
            errno = EFAULT;
            return -1;
      }
      if (len < 0) {
            errno = EINVAL;
            return -1;
      }
      if (len < (1 + strlen (mock_hostname))) {
            errno = ENAMETOOLONG;
            return -1;
      }
/* force an error */
      if (mock_hostname == mock_toolong) {
            errno = ENAMETOOLONG;
            return -1;
      }
      strncpy (name, mock_hostname, len);
      if (len > 0)
            name[len - 1] = '\0';
      return 0;
}

static
struct netent*
mock_getnetbyname (
      const char*       name
      )
{
      static struct netent ne;
      GList* list = mock_networks;

      if (NULL == name)
            return NULL;

      while (list) {
            const struct mock_network_t* network = list->data;
            if (strcmp (network->name, name) == 0) {
                  ne.n_name   = network->name;
                  ne.n_aliases      = network->aliases;
                  ne.n_addrtype     = AF_INET;
                  ne.n_net    = g_ntohl (((struct sockaddr_in*)&network->number)->sin_addr.s_addr);
                  return &ne;
            }
            list = list->next;
      }
      return NULL;
}

PGM_GNUC_INTERNAL
gboolean
mock_pgm_if_getnodeaddr (
      const int         family,
      struct sockaddr*  addr,
      const socklen_t         cnt,
      GError**          error
      )
{
      switch (family) {
      case AF_UNSPEC:
      case AF_INET:
            ((struct sockaddr*)addr)->sa_family = AF_INET;
            ((struct sockaddr_in*)addr)->sin_addr.s_addr = inet_addr(MOCK_ADDRESS);
            break;
      case AF_INET6:
            ((struct sockaddr*)addr)->sa_family = AF_INET6;
            ((struct sockaddr_in6*)addr)->sin6_addr = mock_address6_addr;
            break;
      default:
            g_assert_not_reached();
      }
      return TRUE;
}

/* following tests will use AF_UNSPEC address family */

static
void
mock_setup_unspec (void)
{
      mock_family = AF_UNSPEC;
}

/* following tests will use AF_INET address family */

static
void
mock_setup_ip4 (void)
{
      mock_family = AF_INET;
}

/* following tests will use AF_INET6 address family */

static
void
mock_setup_ip6 (void)
{
      mock_family = AF_INET6;
}

#define pgm_getifaddrs  mock_pgm_getifaddrs
#define pgm_freeifaddrs mock_pgm_freeifaddrs
#define pgm_if_nametoindex    mock_pgm_if_nametoindex
#define if_indextoname  mock_if_indextoname
#define getnameinfo     mock_getnameinfo
#define getaddrinfo     mock_getaddrinfo
#define freeaddrinfo    mock_freeaddrinfo
#define gethostname     mock_gethostname
#define getnetbyname    mock_getnetbyname
#define pgm_if_getnodeaddr    mock_pgm_if_getnodeaddr

#define IF_DEBUG
#include "if.c"


/* return 0 if gsr multicast group does not match the default PGM group for
 * the address family, return -1 on no match.
 */

static
gboolean
match_default_group (
      const int               ai_family,
      const struct group_source_req*      gsr
      )
{
      const struct sockaddr_in sa_default = {
            .sin_family = AF_INET,
            .sin_addr.s_addr = g_htonl (MOCK_GROUP)
      };
      const struct sockaddr_in6 sa6_default = {
            .sin6_family = AF_INET6,
            .sin6_addr = MOCK_GROUP6_INIT
      };
      gboolean is_match = FALSE;

      switch (ai_family) {
      case AF_UNSPEC:
      case AF_INET:
            is_match = (0 == pgm_sockaddr_cmp ((struct sockaddr*)&gsr->gsr_group, (const struct sockaddr*)&sa_default));
            if (!is_match) {
                  char addr1[INET6_ADDRSTRLEN], addr2[INET6_ADDRSTRLEN];
                  pgm_sockaddr_ntop ((struct sockaddr*)&gsr->gsr_group, addr1, sizeof(addr1));
                  pgm_sockaddr_ntop ((struct sockaddr*)&sa_default, addr2, sizeof(addr2));
                  g_message ("FALSE == cmp(%s%s%s, default-group %s%s%s)",
                        addr1 ? "\"" : "", addr1 ? addr1 : "(null)", addr1 ? "\"" : "",
                        addr2 ? "\"" : "", addr2 ? addr2 : "(null)", addr2 ? "\"" : "");
            }
            break;
      case AF_INET6:
            is_match = (0 == pgm_sockaddr_cmp ((struct sockaddr*)&gsr->gsr_group, (const struct sockaddr*)&sa6_default));
            if (!is_match) {
                  char addr1[INET6_ADDRSTRLEN], addr2[INET6_ADDRSTRLEN];
                  pgm_sockaddr_ntop ((struct sockaddr*)&gsr->gsr_group, addr1, sizeof(addr1));
                  pgm_sockaddr_ntop ((struct sockaddr*)&sa6_default, addr2, sizeof(addr2));
                  g_message ("FALSE == cmp(%s%s%s, default-group %s%s%s)",
                        addr1 ? "\"" : "", addr1 ? addr1 : "(null)", addr1 ? "\"" : "",
                        addr2 ? "\"" : "", addr2 ? addr2 : "(null)", addr2 ? "\"" : "");
            }
      default:
            break;
      }
      return is_match;
}

/* return 0 if gsr source inteface does not match the INADDR_ANY reserved
 * address, return -1 on no match.
 */

static
int
match_default_source (
      const int               ai_family,
      const struct group_source_req*      gsr
      )
{
      if (0 != gsr->gsr_interface)
            return FALSE;

/* ASM: source == group */
      return (0 == pgm_sockaddr_cmp ((const struct sockaddr*)&gsr->gsr_group, (const struct sockaddr*)&gsr->gsr_source));
}

/* return 0 if gsr source interface does not match the hosts default interface,
 * return -1 on mismatch
 */

static
int
match_default_interface (
      const int               ai_family,
      const struct group_source_req*      gsr
      )
{
      if (MOCK_INTERFACE_INDEX != gsr->gsr_interface)
            return FALSE;

/* ASM: source == group */
      return (0 == pgm_sockaddr_cmp ((const struct sockaddr*)&gsr->gsr_group, (const struct sockaddr*)&gsr->gsr_source));
}

/* target:
 *    int
 *    pgm_if_parse_transport (
 *          const char*             s,
 *          int                     ai_family,
 *          struct group_source_req*      recv_gsr,
 *          gsize*                        recv_len,
 *          struct group_source_req*      send_gsr
 *    )
 */

struct test_case_t {
      const char* ip4;
      const char* ip6;
};

#define IP4_AND_IP6(x)  x, x

static const struct test_case_t cases_001[] = {
      {     IP4_AND_IP6("")   },
      {     IP4_AND_IP6(";")  },
      {     IP4_AND_IP6(";;") },
      { "239.192.0.1",              "ff08::1"                     },
      { "239.192.0.1",              "[ff08::1]"                   },
      { ";239.192.0.1",             ";ff08::1"                    },
      { ";239.192.0.1",             ";[ff08::1]"                        },
      { ";239.192.0.1;239.192.0.1",       ";ff08::1;ff08::1"                  },
      { ";239.192.0.1;239.192.0.1",       ";[ff08::1];[ff08::1]"              },
      { "PGM.MCAST.NET",                  "IP6-PGM.MCAST.NET"                 },
      { ";PGM.MCAST.NET",                 ";IP6-PGM.MCAST.NET"                },
      { ";PGM.MCAST.NET;PGM.MCAST.NET",   ";IP6-PGM.MCAST.NET;IP6-PGM.MCAST.NET"    },
      { ";239.192.0.1;PGM.MCAST.NET",           ";ff08::1;IP6-PGM.MCAST.NET"        },
      { ";239.192.0.1;PGM.MCAST.NET",           ";[ff08::1];IP6-PGM.MCAST.NET"            },
      { ";PGM.MCAST.NET;239.192.0.1",           ";IP6-PGM.MCAST.NET;ff08::1"        },
      { ";PGM.MCAST.NET;239.192.0.1",           ";IP6-PGM.MCAST.NET;[ff08::1]"            },
      { "pgm-private",              /* ‡ */ "pgm-ip6-private"               },
      { ";pgm-private",             /* ‡ */ ";pgm-ip6-private"              },
      { ";pgm-private;pgm-private",       /* ‡ */ ";pgm-ip6-private;pgm-ip6-private"    },
      { ";PGM.MCAST.NET;pgm-private",           /* ‡ */ ";IP6-PGM.MCAST.NET;pgm-ip6-private"  },
      { ";pgm-private;PGM.MCAST.NET",           /* ‡ */ ";pgm-ip6-private;IP6-PGM.MCAST.NET"  },
      { ";239.192.0.1;pgm-private",       /* ‡ */ ";ff08::1;pgm-ip6-private"            },
      { ";239.192.0.1;pgm-private",       /* ‡ */ ";[ff08::1];pgm-ip6-private"          },
      { ";pgm-private;239.192.0.1",       /* ‡ */ ";pgm-ip6-private;ff08::1"            },
      { ";pgm-private;239.192.0.1",       /* ‡ */ ";pgm-ip6-private;[ff08::1]"          },
};

START_TEST (test_parse_transport_pass_001)
{
      fail_unless (mock_family == AF_UNSPEC || mock_family == AF_INET || mock_family == AF_INET6, "invalid mock address family");

      const char* s = (mock_family == AF_INET6) ? cases_001[_i].ip6 : cases_001[_i].ip4;
      struct pgm_transport_info_t hints = {
            .ti_family  = mock_family
      }, *res = NULL;
      GError* err = NULL;

      g_message ("%i: test_parse_transport_001(%s, %s%s%s)",
               _i,
               (mock_family == AF_INET6) ? "AF_INET6" : ( (mock_family == AF_INET) ? "AF_INET" : "AF_UNSPEC" ),
               s ? "\"" : "", s ? s : "(null)", s ? "\"" : "");

/* ‡ Linux does not support IPv6 /etc/networks so IPv6 entries appear as 255.255.255.255 and
 *   pgm_if_parse_transport will fail.
 */
#ifndef CONFIG_HAVE_IP6_NETWORKS
      if (NULL != strstr (s, MOCK_NETWORK6) || NULL != strstr (s, MOCK_PGM_NETWORK6))
      {
            g_message ("IPv6 exception, /etc/networks not supported on this platform.");
            return;
      }
#endif

      gboolean retval = pgm_if_get_transport_info (s, &hints, &res, &err);
      if (!retval) {
            g_message ("pgm_if_get_transport_info: %s",
                  (err && err->message) ? err->message : "(null)");
      }
      fail_unless (TRUE == retval, "get_transport_info failed");
      fail_if     (NULL == res, "no result");
      fail_unless (NULL == err, "error raised");

      fail_unless (1 == res->ti_recv_addrs_len, "not exactly one receive address");
      fail_unless (match_default_group (mock_family, &res->ti_recv_addrs[0]), "receive address not match default group");
      fail_unless (match_default_source (mock_family, &res->ti_recv_addrs[0]), "receive address not match default source");
      fail_unless (1 == res->ti_send_addrs_len, "not exactly one send address");
      fail_unless (match_default_group (mock_family, &res->ti_send_addrs[0]), "send address not match default group");
      fail_unless (match_default_source (mock_family, &res->ti_send_addrs[0]), "send address not match default source");
}
END_TEST

/* interface name
 *
 * pre-condition: interface defined to match running host
 *            ipv4 and ipv6 hostnames are different, otherwise "<hostname>" tests might go unexpected.
 */

static const struct test_case_t cases_002[] = {
      { MOCK_INTERFACE,                   /* † */ MOCK_INTERFACE            },
      { MOCK_INTERFACE ";",                     /* † */ MOCK_INTERFACE ";"        },
      { MOCK_INTERFACE ";;",                    /* † */ MOCK_INTERFACE ";;" },
      { MOCK_INTERFACE ";239.192.0.1",          /* † */ MOCK_INTERFACE ";ff08::1"             },
      { MOCK_INTERFACE ";239.192.0.1",          /* † */ MOCK_INTERFACE ";[ff08::1]"                 },
      { MOCK_INTERFACE ";239.192.0.1;239.192.0.1",    /* † */ MOCK_INTERFACE ";ff08::1;ff08::1"           },
      { MOCK_INTERFACE ";239.192.0.1;239.192.0.1",    /* † */ MOCK_INTERFACE ";[ff08::1];[ff08::1]"       },
      { MOCK_INTERFACE ";PGM.MCAST.NET",        /* † */ MOCK_INTERFACE ";IP6-PGM.MCAST.NET"   },
      { MOCK_INTERFACE ";PGM.MCAST.NET;PGM.MCAST.NET",/* † */ MOCK_INTERFACE ";IP6-PGM.MCAST.NET;IP6-PGM.MCAST.NET"   },
      { MOCK_INTERFACE ";239.192.0.1;PGM.MCAST.NET",  /* † */ MOCK_INTERFACE ";ff08::1;IP6-PGM.MCAST.NET" },
      { MOCK_INTERFACE ";239.192.0.1;PGM.MCAST.NET",  /* † */ MOCK_INTERFACE ";[ff08::1];IP6-PGM.MCAST.NET"     },
      { MOCK_INTERFACE ";PGM.MCAST.NET;239.192.0.1",  /* † */   MOCK_INTERFACE ";IP6-PGM.MCAST.NET;ff08::1"     },
      { MOCK_INTERFACE ";PGM.MCAST.NET;239.192.0.1",  /* † */   MOCK_INTERFACE ";IP6-PGM.MCAST.NET;[ff08::1]"   },
      { MOCK_INTERFACE ";pgm-private",          /* ‡ */ MOCK_INTERFACE ";pgm-ip6-private" },
      { MOCK_INTERFACE ";pgm-private;pgm-private",    /* ‡ */ MOCK_INTERFACE ";pgm-ip6-private;pgm-ip6-private" },
      { MOCK_ADDRESS,                           MOCK_ADDRESS6                 },
      { MOCK_ADDRESS,                           "[" MOCK_ADDRESS6 "]"         },
      { MOCK_ADDRESS ";",                       MOCK_ADDRESS6 ";"       },
      { MOCK_ADDRESS ";",                       "[" MOCK_ADDRESS6 "];"        },
      { MOCK_ADDRESS ";;",                      MOCK_ADDRESS6 ";;"            },
      { MOCK_ADDRESS ";;",                      "[" MOCK_ADDRESS6 "];;"       },
      { MOCK_ADDRESS ";239.192.0.1",                  MOCK_ADDRESS6 ";ff08::1"                  },
      { MOCK_ADDRESS ";239.192.0.1",                  "[" MOCK_ADDRESS6 "];[ff08::1]"                 },
      { MOCK_ADDRESS ";239.192.0.1;239.192.0.1",      MOCK_ADDRESS6 ";ff08::1;ff08::1"          },
      { MOCK_ADDRESS ";239.192.0.1;239.192.0.1",      "[" MOCK_ADDRESS6 "];[ff08::1];[ff08::1]"       },
      { MOCK_ADDRESS ";PGM.MCAST.NET",          MOCK_ADDRESS6 ";IP6-PGM.MCAST.NET"  },
      { MOCK_ADDRESS ";PGM.MCAST.NET",          "[" MOCK_ADDRESS6 "];IP6-PGM.MCAST.NET"   },
      { MOCK_ADDRESS ";PGM.MCAST.NET;PGM.MCAST.NET",  MOCK_ADDRESS6 ";IP6-PGM.MCAST.NET;IP6-PGM.MCAST.NET"  },
      { MOCK_ADDRESS ";PGM.MCAST.NET;PGM.MCAST.NET",  "[" MOCK_ADDRESS6 "];IP6-PGM.MCAST.NET;IP6-PGM.MCAST.NET"   },
      { MOCK_ADDRESS ";239.192.0.1;PGM.MCAST.NET",    MOCK_ADDRESS6 ";ff08::1;IP6-PGM.MCAST.NET"      },
      { MOCK_ADDRESS ";239.192.0.1;PGM.MCAST.NET",    "[" MOCK_ADDRESS6 "];[ff08::1];IP6-PGM.MCAST.NET"     },
      { MOCK_ADDRESS ";PGM.MCAST.NET;239.192.0.1",    MOCK_ADDRESS6 ";IP6-PGM.MCAST.NET;ff08::1"      },
      { MOCK_ADDRESS ";PGM.MCAST.NET;239.192.0.1",    "[" MOCK_ADDRESS6 "];IP6-PGM.MCAST.NET;[ff08::1]"     },
      { MOCK_ADDRESS ";pgm-private",                  MOCK_ADDRESS6 ";pgm-ip6-private" },
      { MOCK_ADDRESS ";pgm-private",                  "[" MOCK_ADDRESS6 "];pgm-ip6-private" },
      { MOCK_ADDRESS ";pgm-private;pgm-private",      MOCK_ADDRESS6 ";pgm-ip6-private;pgm-ip6-private" },
      { MOCK_ADDRESS ";pgm-private;pgm-private",      "[" MOCK_ADDRESS6 "];pgm-ip6-private;pgm-ip6-private" },
      { MOCK_NETWORK,                           /* ‡ */ MOCK_NETWORK6             },
      { MOCK_NETWORK ";",                       /* ‡ */ MOCK_NETWORK6 ";"         },
      { MOCK_NETWORK ";;",                      /* ‡ */ MOCK_NETWORK6 ";;"        },
      { MOCK_NETWORK ";239.192.0.1",                  /* ‡ */ MOCK_NETWORK6 ";ff08::1"              },
      { MOCK_NETWORK ";239.192.0.1",                  /* ‡ */ MOCK_NETWORK6 ";[ff08::1]"                  },
      { MOCK_NETWORK ";239.192.0.1;239.192.0.1",      /* ‡ */ MOCK_NETWORK6 ";ff08::1;ff08::1"            },
      { MOCK_NETWORK ";239.192.0.1;239.192.0.1",      /* ‡ */ MOCK_NETWORK6 ";[ff08::1];[ff08::1]"        },
      { MOCK_NETWORK ";PGM.MCAST.NET",          /* ‡ */ MOCK_NETWORK6 ";IP6-PGM.MCAST.NET"    },
      { MOCK_NETWORK ";PGM.MCAST.NET;PGM.MCAST.NET",  /* ‡ */ MOCK_NETWORK6 ";IP6-PGM.MCAST.NET;IP6-PGM.MCAST.NET"    },
      { MOCK_NETWORK ";239.192.0.1;PGM.MCAST.NET",    /* ‡ */ MOCK_NETWORK6 ";ff08::1;IP6-PGM.MCAST.NET"  },
      { MOCK_NETWORK ";239.192.0.1;PGM.MCAST.NET",    /* ‡ */ MOCK_NETWORK6 ";[ff08::1];IP6-PGM.MCAST.NET"      },
      { MOCK_NETWORK ";PGM.MCAST.NET;239.192.0.1",    /* ‡ */ MOCK_NETWORK6 ";IP6-PGM.MCAST.NET;ff08::1"  },
      { MOCK_NETWORK ";PGM.MCAST.NET;239.192.0.1",    /* ‡ */ MOCK_NETWORK6 ";IP6-PGM.MCAST.NET;[ff08::1]"      },
      { MOCK_NETWORK ";pgm-private",                  /* ‡ */ MOCK_NETWORK6 ";pgm-ip6-private" },
      { MOCK_NETWORK ";pgm-private;pgm-private",      /* ‡ */ MOCK_NETWORK6 ";pgm-ip6-private;pgm-ip6-private" },
      { MOCK_HOSTNAME,                    MOCK_HOSTNAME6                },
      { MOCK_HOSTNAME ";",                      MOCK_HOSTNAME6 ";"            },
      { MOCK_HOSTNAME ";;",                     MOCK_HOSTNAME6 ";;"           },
      { MOCK_HOSTNAME ";239.192.0.1",                 MOCK_HOSTNAME6 ";ff08::1"           },
      { MOCK_HOSTNAME ";239.192.0.1",                 MOCK_HOSTNAME6 ";[ff08::1]"         },
      { MOCK_HOSTNAME ";239.192.0.1;239.192.0.1",     MOCK_HOSTNAME6 ";ff08::1;ff08::1"   },
      { MOCK_HOSTNAME ";239.192.0.1;239.192.0.1",     MOCK_HOSTNAME6 ";[ff08::1];[ff08::1]"     },
      { MOCK_HOSTNAME ";PGM.MCAST.NET",         MOCK_HOSTNAME6 ";IP6-PGM.MCAST.NET" },
      { MOCK_HOSTNAME ";PGM.MCAST.NET;PGM.MCAST.NET", MOCK_HOSTNAME6 ";IP6-PGM.MCAST.NET;IP6-PGM.MCAST.NET" },
      { MOCK_HOSTNAME ";239.192.0.1;PGM.MCAST.NET",   MOCK_HOSTNAME6 ";ff08::1;IP6-PGM.MCAST.NET" },
      { MOCK_HOSTNAME ";239.192.0.1;PGM.MCAST.NET",   MOCK_HOSTNAME6 ";[ff08::1];IP6-PGM.MCAST.NET" },
      { MOCK_HOSTNAME ";PGM.MCAST.NET;239.192.0.1",   MOCK_HOSTNAME6 ";IP6-PGM.MCAST.NET;ff08::1" },
      { MOCK_HOSTNAME ";PGM.MCAST.NET;239.192.0.1",   MOCK_HOSTNAME6 ";IP6-PGM.MCAST.NET;[ff08::1]" },
      { MOCK_HOSTNAME ";pgm-private",                 MOCK_HOSTNAME6 ";pgm-ip6-private" },
      { MOCK_HOSTNAME ";pgm-private;pgm-private",     MOCK_HOSTNAME6 ";pgm-ip6-private;pgm-ip6-private" },
};

START_TEST (test_parse_transport_pass_002)
{
      fail_unless (mock_family == AF_UNSPEC || mock_family == AF_INET || mock_family == AF_INET6, "invalid mock address family");

      const char* s = (mock_family == AF_INET6) ? cases_002[_i].ip6 : cases_002[_i].ip4;
      struct pgm_transport_info_t hints = {
            .ti_family  = mock_family
      }, *res = NULL;
      GError* err = NULL;

      g_message ("%i: test_parse_transport_002(%s, %s%s%s)",
               _i,
               (mock_family == AF_INET6) ? "AF_INET6" : ( (mock_family == AF_INET) ? "AF_INET" : "AF_UNSPEC" ),
               s ? "\"" : "", s ? s : "(null)", s ? "\"" : "");

/* ‡ Linux does not support IPv6 /etc/networks so IPv6 entries appear as 255.255.255.255 and
 *   pgm_if_parse_transport will fail.
 */
#ifndef CONFIG_HAVE_IP6_NETWORKS
      if (NULL != strstr (s, MOCK_NETWORK6) || NULL != strstr (s, MOCK_PGM_NETWORK6))
      {
            g_message ("IPv6 exception, /etc/networks not supported on this platform.");
            return;
      }
#endif

/* † Multiple scoped IPv6 interfaces match a simple interface name network parameter and so
 *   pgm-if_parse_transport will fail finding multiple matching interfaces
 */
      if (AF_INET6 == mock_family && 0 == strncmp (s, MOCK_INTERFACE, strlen (MOCK_INTERFACE)))
      {
            g_message ("IPv6 exception, multiple scoped addresses on one interface");
            fail_unless (FALSE == pgm_if_get_transport_info (s, &hints, &res, &err), "get_transport_info failed");
            fail_unless (NULL == res, "unexpected result");
            fail_if     (NULL == err, "error not raised");
            fail_unless (PGM_IF_ERROR_NOTUNIQ == err->code, "interfaces not found unique");
            return;
      }

      fail_unless (TRUE == pgm_if_get_transport_info (s, &hints, &res, &err), "get_transport_info failed");
      fail_unless (1 == res->ti_recv_addrs_len, "not exactly one receive address");
      fail_unless (match_default_group     (mock_family, &res->ti_recv_addrs[0]), "receive address not match default group");
      fail_unless (match_default_interface (mock_family, &res->ti_recv_addrs[0]), "receive address not match default interface");
      fail_unless (1 == res->ti_send_addrs_len, "not exactly one send address");
      fail_unless (match_default_group     (mock_family, &res->ti_send_addrs[0]), "send address not match default group");
      fail_unless (match_default_interface (mock_family, &res->ti_send_addrs[0]), "send address not match default interface");
}
END_TEST

/* network to node address in bits, 8-32
 *
 * e.g. 127.0.0.1/16
 */

static const struct test_case_t cases_003[] = {
      { MOCK_ADDRESS "/24",                     MOCK_ADDRESS6 "/64"                       },
      { MOCK_ADDRESS "/24;",                    MOCK_ADDRESS6 "/64;"                      },
      { MOCK_ADDRESS "/24;;",                   MOCK_ADDRESS6 "/64;;"                     },
      { MOCK_ADDRESS "/24;239.192.0.1",         MOCK_ADDRESS6 "/64;ff08::1"               },
      { MOCK_ADDRESS "/24;239.192.0.1",         MOCK_ADDRESS6 "/64;[ff08::1]"             },
      { MOCK_ADDRESS "/24;239.192.0.1;239.192.0.1",   MOCK_ADDRESS6 "/64;ff08::1;ff08::1"       },
      { MOCK_ADDRESS "/24;239.192.0.1;239.192.0.1",   MOCK_ADDRESS6 "/64;[ff08::1];[ff08::1]"         },
      { MOCK_ADDRESS "/24;PGM.MCAST.NET",       MOCK_ADDRESS6 "/64;IP6-PGM.MCAST.NET"           },
      { MOCK_ADDRESS "/24;PGM.MCAST.NET;PGM.MCAST.NET",MOCK_ADDRESS6 "/64;IP6-PGM.MCAST.NET;IP6-PGM.MCAST.NET"    },
      { MOCK_ADDRESS "/24;239.192.0.1;PGM.MCAST.NET", MOCK_ADDRESS6 "/64;ff08::1;IP6-PGM.MCAST.NET"   },
      { MOCK_ADDRESS "/24;239.192.0.1;PGM.MCAST.NET", MOCK_ADDRESS6 "/64;[ff08::1];IP6-PGM.MCAST.NET" },
      { MOCK_ADDRESS "/24;PGM.MCAST.NET;239.192.0.1", MOCK_ADDRESS6 "/64;IP6-PGM.MCAST.NET;ff08::1"   },
      { MOCK_ADDRESS "/24;PGM.MCAST.NET;239.192.0.1", MOCK_ADDRESS6 "/64;IP6-PGM.MCAST.NET;[ff08::1]" },
      { MOCK_ADDRESS "/24;PGM.MCAST.NET",       MOCK_ADDRESS6 "/64;IP6-PGM.MCAST.NET"           },
      { MOCK_ADDRESS "/24;PGM.MCAST.NET;PGM.MCAST.NET",MOCK_ADDRESS6 "/64;IP6-PGM.MCAST.NET;IP6-PGM.MCAST.NET"    },
      { MOCK_ADDRESS "/24;pgm-private",         /* ‡ */ MOCK_ADDRESS6 "/64;pgm-ip6-private"               },
      { MOCK_ADDRESS "/24;pgm-private;pgm-private",   /* ‡ */ MOCK_ADDRESS6 "/64;pgm-ip6-private;pgm-ip6-private"     },
      { MOCK_ADDRESS "/24;239.192.0.1;pgm-private",   /* ‡ */ MOCK_ADDRESS6 "/64;ff08::1;pgm-ip6-private"       },
      { MOCK_ADDRESS "/24;239.192.0.1;pgm-private",   /* ‡ */ MOCK_ADDRESS6 "/64;[ff08::1];pgm-ip6-private"           },
      { MOCK_ADDRESS "/24;pgm-private;239.192.0.1",   /* ‡ */ MOCK_ADDRESS6 "/64;pgm-ip6-private;ff08::1"       },
      { MOCK_ADDRESS "/24;pgm-private;239.192.0.1",   /* ‡ */ MOCK_ADDRESS6 "/64;pgm-ip6-private;[ff08::1]"           },
      { MOCK_ADDRESS "/24;PGM.MCAST.NET;pgm-private", /* ‡ */ MOCK_ADDRESS6 "/64;IP6-PGM.MCAST.NET;pgm-ip6-private"   },
      { MOCK_ADDRESS "/24;pgm-private;PGM.MCAST.NET", /* ‡ */ MOCK_ADDRESS6 "/64;pgm-ip6-private;IP6-PGM.MCAST.NET"   },
};

START_TEST (test_parse_transport_pass_003)
{
      fail_unless (mock_family == AF_UNSPEC || mock_family == AF_INET || mock_family == AF_INET6, "invalid mock address family");

      const char* s = (mock_family == AF_INET6) ? cases_003[_i].ip6 : cases_003[_i].ip4;
      struct pgm_transport_info_t hints = {
            .ti_family  = mock_family
      }, *res = NULL;
      GError* err = NULL;

      g_message ("%i: test_parse_transport_003(%s, %s%s%s)",
               _i,
               (mock_family == AF_INET6) ? "AF_INET6" : ( (mock_family == AF_INET) ? "AF_INET" : "AF_UNSPEC" ),
               s ? "\"" : "", s ? s : "(null)", s ? "\"" : "");

/* ‡ Linux does not support IPv6 /etc/networks so IPv6 entries appear as 255.255.255.255 and
 *   pgm_if_parse_transport will fail.
 */
#ifndef CONFIG_HAVE_IP6_NETWORKS
      if (NULL != strstr (s, MOCK_NETWORK6) || NULL != strstr (s, MOCK_PGM_NETWORK6))
      {
            g_message ("IPv6 exception, /etc/networks not supported on this platform.");
            return;
      }
#endif

      gboolean retval = pgm_if_get_transport_info (s, &hints, &res, &err);
      if (!retval) {
            g_message ("pgm_if_get_transport_info: %s",
                  (err && err->message) ? err->message : "(null)");
      }
      fail_unless (TRUE == retval, "get_transport_info failed");
      fail_unless (1 == res->ti_recv_addrs_len, "not exactly one receive address");
      fail_unless (match_default_group     (mock_family, &res->ti_recv_addrs[0]), "receive address not match default group");
      fail_unless (match_default_interface (mock_family, &res->ti_recv_addrs[0]), "receive address not match default interface");
      fail_unless (1 == res->ti_send_addrs_len, "not exactly one send address");
      fail_unless (match_default_group     (mock_family, &res->ti_send_addrs[0]), "send address not match default group");
      fail_unless (match_default_interface (mock_family, &res->ti_send_addrs[0]), "send address not match default interface");
}
END_TEST

/* asymmetric groups
 */

START_TEST (test_parse_transport_pass_004)
{
      fail_unless (mock_family == AF_UNSPEC || mock_family == AF_INET || mock_family == AF_INET6, "invalid mock address family");

      const char* s = (mock_family == AF_INET6) ? ";ff08::1;ff08::2"
                               /* AF_INET */: ";239.192.56.1;239.192.56.2";
      struct pgm_transport_info_t hints = {
            .ti_family  = mock_family
      }, *res = NULL;
      GError* err = NULL;
      struct sockaddr_storage addr;

      fail_unless (TRUE == pgm_if_get_transport_info (s, &hints, &res, &err), "get_transport_info failed");
      fail_unless (1 == res->ti_recv_addrs_len, "not exactly one receive address");
      fail_unless (1 == res->ti_send_addrs_len, "not exactly one send address");
      if (mock_family == AF_INET6)
      {
            inet_pton (AF_INET6, "ff08::1", &((struct sockaddr_in6*)&addr)->sin6_addr);
            ((struct sockaddr*)&addr)->sa_family = mock_family;
            ((struct sockaddr_in6*)&addr)->sin6_port = 0;
            ((struct sockaddr_in6*)&addr)->sin6_flowinfo = 0;
            ((struct sockaddr_in6*)&addr)->sin6_scope_id = 0;
            fail_unless (0 == pgm_sockaddr_cmp ((struct sockaddr*)&res->ti_recv_addrs[0].gsr_group, (struct sockaddr*)&addr), "group not match");
            inet_pton (AF_INET6, "ff08::2", &((struct sockaddr_in6*)&addr)->sin6_addr);
            ((struct sockaddr*)&addr)->sa_family = mock_family;
            ((struct sockaddr_in6*)&addr)->sin6_port = 0;
            ((struct sockaddr_in6*)&addr)->sin6_flowinfo = 0;
            ((struct sockaddr_in6*)&addr)->sin6_scope_id = 0;
            fail_unless (0 == pgm_sockaddr_cmp ((struct sockaddr*)&res->ti_send_addrs[0].gsr_group, (struct sockaddr*)&addr), "group not match");
      } else {
            inet_pton (AF_INET, "239.192.56.1", &((struct sockaddr_in*)&addr)->sin_addr);
            ((struct sockaddr*)&addr)->sa_family = AF_INET;
            fail_unless (0 == pgm_sockaddr_cmp ((struct sockaddr*)&res->ti_recv_addrs[0].gsr_group, (struct sockaddr*)&addr), "group not match");
            inet_pton (AF_INET, "239.192.56.2", &((struct sockaddr_in*)&addr)->sin_addr);
            ((struct sockaddr*)&addr)->sa_family = AF_INET;
            fail_unless (0 == pgm_sockaddr_cmp ((struct sockaddr*)&res->ti_send_addrs[0].gsr_group, (struct sockaddr*)&addr), "group not match");
      }
      fail_unless (match_default_source (mock_family, &res->ti_recv_addrs[0]), "source not match");
      fail_unless (match_default_source (mock_family, &res->ti_send_addrs[0]), "source not match");
}
END_TEST

/* multiple receive groups and asymmetric sending
 */

START_TEST (test_parse_transport_pass_005)
{
      fail_unless (mock_family == AF_UNSPEC || mock_family == AF_INET || mock_family == AF_INET6, "invalid mock address family");

      const char* s = (mock_family == AF_INET6) ? ";ff08::1,ff08::2;ff08::3"
                               /* AF_INET */: ";239.192.56.1,239.192.56.2;239.192.56.3";
      struct pgm_transport_info_t hints = {
            .ti_family  = mock_family
      }, *res = NULL;
      GError* err = NULL;
      struct sockaddr_storage addr;

      fail_unless (TRUE == pgm_if_get_transport_info (s, &hints, &res, &err), "get_transport_info failed");
      fail_unless (2 == res->ti_recv_addrs_len, "not exactly one receive address");
      fail_unless (1 == res->ti_send_addrs_len, "not exactly one send address");
      if (mock_family == AF_INET6)
      {
            inet_pton (AF_INET6, "ff08::1", &((struct sockaddr_in6*)&addr)->sin6_addr);
            ((struct sockaddr*)&addr)->sa_family = mock_family;
            ((struct sockaddr_in6*)&addr)->sin6_port = 0;
            ((struct sockaddr_in6*)&addr)->sin6_flowinfo = 0;
            ((struct sockaddr_in6*)&addr)->sin6_scope_id = 0;
            fail_unless (0 == pgm_sockaddr_cmp ((struct sockaddr*)&res->ti_recv_addrs[0].gsr_group, (struct sockaddr*)&addr), "group not match");
            inet_pton (AF_INET6, "ff08::2", &((struct sockaddr_in6*)&addr)->sin6_addr);
            ((struct sockaddr*)&addr)->sa_family = mock_family;
            ((struct sockaddr_in6*)&addr)->sin6_port = 0;
            ((struct sockaddr_in6*)&addr)->sin6_flowinfo = 0;
            ((struct sockaddr_in6*)&addr)->sin6_scope_id = 0;
            fail_unless (0 == pgm_sockaddr_cmp ((struct sockaddr*)&res->ti_recv_addrs[1].gsr_group, (struct sockaddr*)&addr), "group not match");
            inet_pton (AF_INET6, "ff08::3", &((struct sockaddr_in6*)&addr)->sin6_addr);
            ((struct sockaddr*)&addr)->sa_family = mock_family;
            ((struct sockaddr_in6*)&addr)->sin6_port = 0;
            ((struct sockaddr_in6*)&addr)->sin6_flowinfo = 0;
            ((struct sockaddr_in6*)&addr)->sin6_scope_id = 0;
            fail_unless (0 == pgm_sockaddr_cmp ((struct sockaddr*)&res->ti_send_addrs[0].gsr_group, (struct sockaddr*)&addr), "group not match");
      } else {
            inet_pton (AF_INET, "239.192.56.1", &((struct sockaddr_in*)&addr)->sin_addr);
            ((struct sockaddr*)&addr)->sa_family = AF_INET;
            fail_unless (0 == pgm_sockaddr_cmp ((struct sockaddr*)&res->ti_recv_addrs[0].gsr_group, (struct sockaddr*)&addr), "group not match");
            inet_pton (AF_INET, "239.192.56.2", &((struct sockaddr_in*)&addr)->sin_addr);
            ((struct sockaddr*)&addr)->sa_family = AF_INET;
            fail_unless (0 == pgm_sockaddr_cmp ((struct sockaddr*)&res->ti_recv_addrs[1].gsr_group, (struct sockaddr*)&addr), "group not match");
            inet_pton (AF_INET, "239.192.56.3", &((struct sockaddr_in*)&addr)->sin_addr);
            ((struct sockaddr*)&addr)->sa_family = AF_INET;
            fail_unless (0 == pgm_sockaddr_cmp ((struct sockaddr*)&res->ti_send_addrs[0].gsr_group, (struct sockaddr*)&addr), "group not match");
      }
      fail_unless (match_default_source (mock_family, &res->ti_recv_addrs[0]), "source not match");
      fail_unless (match_default_source (mock_family, &res->ti_send_addrs[0]), "source not match");
}
END_TEST


/* too many interfaces
 */
START_TEST (test_parse_transport_fail_001)
{
      const char* s = "eth0,lo;;;";
      struct pgm_transport_info_t hints = {
            .ti_family  = AF_UNSPEC
      }, *res = NULL;
      GError* err = NULL;

      fail_unless (FALSE == pgm_if_get_transport_info (s, &hints, &res, &err), "get_transport_info failed");
      fail_unless (NULL == res, "unexpected result");
}
END_TEST

/* invalid characters, or simply just bogus
 */
START_TEST (test_parse_transport_fail_002)
{
        const char* s = "!@#$%^&*()";
      struct pgm_transport_info_t hints = {
            .ti_family  = AF_UNSPEC
      }, *res = NULL;
      GError* err = NULL;

      fail_unless (FALSE == pgm_if_get_transport_info (s, &hints, &res, &err), "get_transport_info failed");
      fail_unless (NULL == res, "unexpected result");
}
END_TEST

/* too many groups
 */
START_TEST (test_parse_transport_fail_003)
{
        const char* s = ";239.192.0.1,239.192.0.2,239.192.0.3,239.192.0.4,239.192.0.5,239.192.0.6,239.192.0.7,239.192.0.8,239.192.0.9,239.192.0.10,239.192.0.11,239.192.0.12,239.192.0.13,239.192.0.14,239.192.0.15,239.192.0.16,239.192.0.17,239.192.0.18,239.192.0.19,239.192.0.20;239.192.0.21";
      struct pgm_transport_info_t hints = {
            .ti_family  = AF_UNSPEC
      }, *res = NULL;
      GError* err = NULL;

      fail_unless (FALSE == pgm_if_get_transport_info (s, &hints, &res, &err), "get_transport_info failed");
      fail_unless (NULL == res, "unexpected result");
}
END_TEST

/* too many receiver groups in asymmetric pairing
 */
START_TEST (test_parse_transport_fail_004)
{
        const char* s = ";239.192.0.1,239.192.0.2,239.192.0.3,239.192.0.4,239.192.0.5,239.192.0.6,239.192.0.7,239.192.0.8,239.192.0.9,239.192.0.10,239.192.0.11,239.192.0.12,239.192.0.13,239.192.0.14,239.192.0.15,239.192.0.16,239.192.0.17,239.192.0.18,239.192.0.19,239.192.0.20,239.192.0.21;239.192.0.22";
      struct pgm_transport_info_t hints = {
            .ti_family  = AF_UNSPEC
      }, *res = NULL;
      GError* err = NULL;

      fail_unless (FALSE == pgm_if_get_transport_info (s, &hints, &res, &err), "get_transport_info failed");
      fail_unless (NULL == res, "unexpected result");
}
END_TEST

/* null string
 */
START_TEST (test_parse_transport_fail_005)
{
        const char* s = NULL;
      struct pgm_transport_info_t hints = {
            .ti_family  = AF_UNSPEC
      }, *res = NULL;
      GError* err = NULL;

      fail_unless (FALSE == pgm_if_get_transport_info (s, &hints, &res, &err), "get_transport_info failed");
      fail_unless (NULL == res, "unexpected result");
}
END_TEST

/* invalid address family
 */
START_TEST (test_parse_transport_fail_006)
{
        const char* s = ";";
      struct pgm_transport_info_t hints = {
            .ti_family  = AF_IPX
      }, *res = NULL;
      GError* err = NULL;

      fail_unless (FALSE == pgm_if_get_transport_info (s, &hints, &res, &err), "get_transport_info failed");
      fail_unless (NULL == res, "unexpected result");
}
END_TEST

/* invalid transport info pointer
 */
START_TEST (test_parse_transport_fail_007)
{
        const char* s = ";";
      GError* err = NULL;

      fail_unless (FALSE == pgm_if_get_transport_info (s, NULL, NULL, &err), "get_transport_info failed");
}
END_TEST

/* invalid interface
 */
START_TEST (test_parse_transport_fail_008)
{
      const char* s = "qe0;";
      struct pgm_transport_info_t hints = {
            .ti_family  = AF_UNSPEC
      }, *res = NULL;
      GError* err = NULL;

      gboolean retval = pgm_if_get_transport_info (s, &hints, &res, &err);
      if (!retval) {
            g_message ("pgm_if_get_transport_info: %s", err ? err->message : "(null)");
      }
      fail_unless (FALSE == retval, "get_transport_info failed");
      fail_unless (NULL == res, "unexpected result");
}
END_TEST

/* non-existing interface IP address
 */
START_TEST (test_parse_transport_fail_009)
{
      const char* s = "172.16.90.1;";
      struct pgm_transport_info_t hints = {
            .ti_family  = AF_UNSPEC
      }, *res = NULL;
      GError* err = NULL;

      gboolean retval = pgm_if_get_transport_info (s, &hints, &res, &err);
      if (!retval) {
            g_message ("pgm_if_get_transport_info: %s",
                  (err && err->message) ? err->message : "(null)");
      }
      fail_unless (FALSE == retval, "get_transport_info failed");
      fail_unless (NULL == res, "unexpected result");
}
END_TEST

/* non-existing network name address
 */
START_TEST (test_parse_transport_fail_010)
{
      const char* s = "private2;";
      struct pgm_transport_info_t hints = {
            .ti_family  = AF_UNSPEC
      }, *res = NULL;
      GError* err = NULL;

      gboolean retval = pgm_if_get_transport_info (s, &hints, &res, &err);
      if (!retval) {
            g_message ("pgm_if_get_transport_info: %s",
                  (err && err->message) ? err->message : "(null)");
      }
      fail_unless (FALSE == retval, "get_transport_info failed");
      fail_unless (NULL == res, "unexpected result");
}
END_TEST

/* non-existing host name interface
 */
START_TEST (test_parse_transport_fail_011)
{
      const char* s = "mi-hee.ko.miru.hk;";
      struct pgm_transport_info_t hints = {
            .ti_family  = AF_UNSPEC
      }, *res = NULL;
      GError* err = NULL;

      gboolean retval = pgm_if_get_transport_info (s, &hints, &res, &err);
      if (!retval) {
            g_message ("pgm_if_get_transport_info: %s",
                  (err && err->message) ? err->message : "(null)");
      }
      fail_unless (FALSE == retval, "get_transport_info failed");
      fail_unless (NULL == res, "unexpected result");
}
END_TEST

/* target:
 *    pgm_if_print_all (void)
 */

START_TEST (test_print_all_pass_001)
{
      pgm_if_print_all ();
}
END_TEST


/* target:
 *    gboolean
 *    is_in_net (
 *          const struct in_addr*   addr,       -- in host byte order
 *          const struct in_addr*   netaddr,
 *          const struct in_addr*   netmask
 *          )
 */

struct test_case_net_t {
        const char* addr;
        const char* netaddr;
        const char* netmask;
      const gboolean answer;
};

static const struct test_case_net_t cases_004[] = {
      { "127.0.0.1",          "127.0.0.1",      "255.0.0.0",            TRUE        },
      { "127.0.0.1",          "127.0.0.1",      "255.255.0.0",          TRUE        },
      { "127.0.0.1",          "127.0.0.1",      "255.255.255.0",  TRUE        },
      { "127.0.0.1",          "127.0.0.1",      "255.255.255.255",      TRUE        },
      { "127.0.0.1",          "127.0.0.0",      "255.0.0.0",            TRUE        },
      { "127.0.0.1",          "127.0.0.0",      "255.255.0.0",          TRUE        },
      { "127.0.0.1",          "127.0.0.0",      "255.255.255.0",  TRUE        },
      { "127.0.0.1",          "127.0.0.0",      "255.255.255.255",      FALSE       },
      { "172.15.1.1",         "172.16.0.0",     "255.240.0.0",          FALSE       },
      { "172.16.1.1",         "172.16.0.0",     "255.240.0.0",          TRUE        },
      { "172.18.1.1",         "172.16.0.0",     "255.240.0.0",          TRUE        },
      { "172.31.1.1",         "172.16.0.0",     "255.240.0.0",          TRUE        },
      { "172.32.1.1",         "172.16.0.0",     "255.240.0.0",          FALSE       },
};

START_TEST (test_is_in_net_pass_001)
{
      struct in_addr addr, netaddr, netmask;
      fail_unless (pgm_inet_pton (AF_INET, cases_004[_i].addr,    &addr));
      fail_unless (pgm_inet_pton (AF_INET, cases_004[_i].netaddr, &netaddr));
      fail_unless (pgm_inet_pton (AF_INET, cases_004[_i].netmask, &netmask));
      const gboolean answer =            cases_004[_i].answer;

      addr.s_addr    = g_ntohl (addr.s_addr);
      netaddr.s_addr = g_ntohl (netaddr.s_addr);
      netmask.s_addr = g_ntohl (netmask.s_addr);
      gboolean result = is_in_net (&addr, &netaddr, &netmask);

      g_message ("result %s (%s)",
            result ? "TRUE" : "FALSE",
            answer ? "TRUE" : "FALSE");

      fail_unless (answer == result);
}
END_TEST

static const struct test_case_net_t cases_005[] = {
      { "::1",                "::1",                  "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",            TRUE        },
      { "fe80::203:baff:fe4e:6cc8", "fe80::",         "ffff:0000:0000:0000:0000:0000:0000:0000",            TRUE        },
      { "2002:dec8:d28e::36",       "2002:dec8:d28e::",     "ffff:ffff:ffff:0000:0000:0000:0000:0000",            TRUE        },
      { "2002:dec8:d28e::36",       "2002:dafa:939:0::",    "ffff:ffff:ffff:ffff:0000:0000:0000:0000",            FALSE       },
};

START_TEST (test_is_in_net6_pass_001)
{
      struct in6_addr addr, netaddr, netmask;
      fail_unless (pgm_inet_pton (AF_INET6, cases_005[_i].addr,    &addr));
      fail_unless (pgm_inet_pton (AF_INET6, cases_005[_i].netaddr, &netaddr));
      fail_unless (pgm_inet_pton (AF_INET6, cases_005[_i].netmask, &netmask));
      const gboolean answer =             cases_005[_i].answer;

      gboolean result = is_in_net6 (&addr, &netaddr, &netmask);

      g_message ("result %s (%s)",
            result ? "TRUE" : "FALSE",
            answer ? "TRUE" : "FALSE");

      fail_unless (answer == result);
}
END_TEST



static
Suite*
make_test_suite (void)
{
      Suite* s;

      s = suite_create (__FILE__);

      TCase* tc_is_in_net = tcase_create ("is_in_net");
      suite_add_tcase (s, tc_is_in_net);
      tcase_add_checked_fixture (tc_is_in_net, mock_setup_net, mock_teardown_net);
      tcase_add_checked_fixture (tc_is_in_net, mock_setup_unspec, NULL);
      tcase_add_loop_test (tc_is_in_net, test_is_in_net_pass_001, 0, G_N_ELEMENTS(cases_004));

      TCase* tc_is_in_net6 = tcase_create ("is_in_net6");
      suite_add_tcase (s, tc_is_in_net6);
      tcase_add_checked_fixture (tc_is_in_net6, mock_setup_net, mock_teardown_net);
      tcase_add_checked_fixture (tc_is_in_net6, mock_setup_unspec, NULL);
      tcase_add_loop_test (tc_is_in_net6, test_is_in_net6_pass_001, 0, G_N_ELEMENTS(cases_005));

/* three variations of all parse-transport tests, one for each valid
 * address family value: AF_UNSPEC, AF_INET, AF_INET6. 
 */

/* unspecified address family, ai_family == AF_UNSPEC */
      TCase* tc_parse_transport_unspec = tcase_create ("parse_transport/unspec");
      suite_add_tcase (s, tc_parse_transport_unspec);
      tcase_add_checked_fixture (tc_parse_transport_unspec, mock_setup_net, mock_teardown_net);
      tcase_add_checked_fixture (tc_parse_transport_unspec, mock_setup_unspec, NULL);
      tcase_add_loop_test (tc_parse_transport_unspec, test_parse_transport_pass_001, 0, G_N_ELEMENTS(cases_001));
      tcase_add_loop_test (tc_parse_transport_unspec, test_parse_transport_pass_002, 0, G_N_ELEMENTS(cases_002));
      tcase_add_loop_test (tc_parse_transport_unspec, test_parse_transport_pass_003, 0, G_N_ELEMENTS(cases_003));
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_pass_004);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_pass_005);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_001);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_002);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_003);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_004);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_005);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_006);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_007);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_008);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_009);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_010);
      tcase_add_test (tc_parse_transport_unspec, test_parse_transport_fail_011);

/* IP version 4, ai_family = AF_INET */
      TCase* tc_parse_transport_ip4 = tcase_create ("parse_transport/af_inet");
      suite_add_tcase (s, tc_parse_transport_ip4);
      tcase_add_checked_fixture (tc_parse_transport_ip4, mock_setup_net, mock_teardown_net);
      tcase_add_checked_fixture (tc_parse_transport_ip4, mock_setup_ip4, NULL);
      tcase_add_loop_test (tc_parse_transport_ip4, test_parse_transport_pass_001, 0, G_N_ELEMENTS(cases_001));
      tcase_add_loop_test (tc_parse_transport_ip4, test_parse_transport_pass_002, 0, G_N_ELEMENTS(cases_002));
      tcase_add_loop_test (tc_parse_transport_ip4, test_parse_transport_pass_003, 0, G_N_ELEMENTS(cases_003));
      tcase_add_test (tc_parse_transport_ip4, test_parse_transport_pass_004);
      tcase_add_test (tc_parse_transport_ip4, test_parse_transport_pass_005);

/* IP version 6, ai_family = AF_INET6 */
      TCase* tc_parse_transport_ip6 = tcase_create ("parse_transport/af_inet6");
      suite_add_tcase (s, tc_parse_transport_ip6);
      tcase_add_checked_fixture (tc_parse_transport_ip6, mock_setup_net, mock_teardown_net);
      tcase_add_checked_fixture (tc_parse_transport_ip6, mock_setup_ip6, NULL);
      tcase_add_loop_test (tc_parse_transport_ip6, test_parse_transport_pass_001, 0, G_N_ELEMENTS(cases_001));
      tcase_add_loop_test (tc_parse_transport_ip6, test_parse_transport_pass_002, 0, G_N_ELEMENTS(cases_002));
      tcase_add_loop_test (tc_parse_transport_ip6, test_parse_transport_pass_003, 0, G_N_ELEMENTS(cases_003));
      tcase_add_test (tc_parse_transport_ip6, test_parse_transport_pass_004);
      tcase_add_test (tc_parse_transport_ip6, test_parse_transport_pass_005);

      TCase* tc_print_all = tcase_create ("print-all");
      tcase_add_checked_fixture (tc_print_all, mock_setup_net, mock_teardown_net);
      suite_add_tcase (s, tc_print_all);
      tcase_add_test (tc_print_all, test_print_all_pass_001);

      return s;
}

static
Suite*
make_master_suite (void)
{
      Suite* s = suite_create ("Master");
      return s;
}

int
main (void)
{
      SRunner* sr = srunner_create (make_master_suite ());
      srunner_add_suite (sr, make_test_suite ());
      srunner_run_all (sr, CK_ENV);
      int number_failed = srunner_ntests_failed (sr);
      srunner_free (sr);
      return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

/* eof */

Generated by  Doxygen 1.6.0   Back to index