/*

  t-addr.c

  Author: Santeri Paavolainen <santtu@ssh.fi>


  Copyright (C) 1999 SSH Communications Security Oy, Espoo, Finland
  All rights reserved.

 */

#include "sshincludes.h"
#include "sshinet.h"
#include "sshtimemeasure.h"
#include "ssheprintf.h"

#define SEQ(A,B) if (strcmp((A),(B))) { fprintf(stderr, "%s: [%d] %s <=> %s\n", av[0], __LINE__, (A), (B)); exit(1); } else if (verbose_flag) { fprintf(stderr, "%s: %s <=> %s ok\n", av[0], (A), (B)); }
#define FUT(X) if (!(X)) { fprintf(stderr, "%s: [%d] %s == FALSE\n", av[0], __LINE__, #X); exit(1); } else if (verbose_flag) { fprintf(stderr, "%s: %s ok\n", av[0], #X);  }
#define ITERS 10000000

static Boolean verbose_flag = FALSE;

Boolean mask_equal_1 (SshIpAddr * a, SshIpAddr * m);
Boolean mask_equal_2 (SshIpAddr * a, SshIpAddr * m);

Boolean mask_equal_1 (SshIpAddr * a, SshIpAddr * m)
{
  int i;
  unsigned char mbuf[16];
  
#if 0
  for (i = 0; i < 4; i++)
    if ((a->addr_data[i] & m->addr_data[i + 4]) != (m->addr_data[i] & m->addr_data[i + 4]))
      return FALSE;
#endif

  memset(mbuf, 0, 16);

  for (i = 0; i < m->mask_len; i++)
    mbuf[i / 8] |= 1 << (7 - (i % 8));

  for (i = 0; i < SSH_IP_ADDR_LEN(a); i ++)
    if ((a->addr_data[i] & mbuf[i]) != (m->addr_data[i] & mbuf[i]))
      return FALSE;
  
  return TRUE;
}

Boolean mask_equal_2 (SshIpAddr * a, SshIpAddr * m)
{

#if 0
  int i;

  for (i = 0; i < 4; i++)
    if ((a->addr_data[i] ^ m->addr_data[i]) & m->addr_data[i + 4])
      return FALSE;
  return TRUE;
#elif 0
  if (((*(SshUInt32 *)&a->addr_data[0]) ^
       (*(SshUInt32 *)&m->addr_data[0])) &
      (*(SshUInt32*)&m->addr_data[4]))
    return FALSE;
  return TRUE;
#else
  return !(((*(SshUInt32 *)&a->addr_data[0]) ^
            (*(SshUInt32 *)&m->addr_data[0])) &
           (*(SshUInt32 *)&m->addr_data[4]));
#endif
}

static unsigned long speed_test (SshIpAddr * ip1, SshIpAddr * ip2,
                                 Boolean (*func)(SshIpAddr *, SshIpAddr *),
                                 const char * name)
{
    unsigned char buf[512 * 1024];
    Boolean b;
    int i;
    long msec;
    SshTimeMeasure tm;
    
    for (i = 0; i < sizeof(buf); i++) {
      buf[i] = i ^ buf[i];
    }
    
    tm = ssh_time_measure_allocate();
    ssh_time_measure_start(tm);

    for (i = 0; i < ITERS; i++) {
        b = (func)(ip1, ip2);
    }
    ssh_time_measure_stop(tm);

    msec = (long)ssh_time_measure_get(tm, SSH_TIME_GRANULARITY_MICROSECOND);
    printf("count=%d %32s = %-5s: %ld microseconds\n",
           i, name, b ? "TRUE" : "FALSE", msec);

    for (i = 0; i < sizeof(buf); i++) {
        buf[i] = i ^ buf[i];
    }

    return msec;
}

void print_bits (const char * head,
                 const void * data_raw,
                 unsigned int bytes)
{
  int i, b, n;
  const unsigned char * data = (const unsigned char *)data_raw;

  fprintf(stderr, "%s = ", head);

  for (i = 0; i < bytes; i++) {
    b = data[i];
    for (n = 7; n >= 0; n--)
      fprintf(stderr, "%c", (b & (1 << n)) ? '1' : '0');
    if ((i + 1) < bytes)
      fprintf(stderr, ".");
  }

  fprintf(stderr, "\n");
}
                 

void speed (void)
{
    SshIpAddr ip1, ip2;
    unsigned long t1, t2, t3;

    ssh_ipaddr_parse(&ip1, "10.1.0.5");
    ssh_ipaddr_parse_with_mask(&ip2, "10.1.0.0/16", NULL);

    /************************************************************************/

    t1 = speed_test(&ip1, &ip2, mask_equal_1, "mask_equal_1");
    t2 = speed_test(&ip1, &ip2, mask_equal_2, "mask_equal_2");
    t3 = speed_test(&ip1, &ip2, ssh_ipaddr_mask_equal, "ssh_ipaddr_mask_equal");

    /************************************************************************/

    printf("mask_equal_1 / mask_equal_2 = %f\n", (double)t1 / (double)t2);
    printf("mask_equal_%s / ssh_ipaddr_mask_equal = %f\n",
           (t1 > t2 ? "2" : "1"),
           (double)(t1 > t2 ? t2 : t1) / (double)t3);
}

/*

   ipv6 address parsing test strings:

   - string to parse
   - whether it is supposed to succeed in parsing (TRUE) or fail (FALSE)
   - if parsing should be true, address to compare against

*/

static struct {
    char                * str;
    Boolean             ok;
    unsigned char       addr[16];
} ipv6_test[] = {
    { "fedc:ba98:7654:3210:fedc:ba98:7654:3210", TRUE,
      { 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
        0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, }, },
    { "1080:0:0:0:8:800:200c:417a", TRUE,
      { 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x08, 0x08, 0x00, 0x20, 0x0c, 0x41, 0x7a, }, },
    { "ff01:0:0:0:0:0:43:0", TRUE,
      { 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, }, },
    { "1080::8:800:200c:417a", TRUE,
      { 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x08, 0x08, 0x00, 0x20, 0x0c, 0x41, 0x7a, }, },
    { "ff01::43", TRUE,
      { 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, }, },
    { "::1", TRUE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1, }, },
    { "::", TRUE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "::13.1.68.3", TRUE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x0d, 0x01, 0x44, 0x03, }, },
    { "::ffff:129.144.52.18", TRUE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0xff, 0x81, 0x90, 0x34, 0x12, }, },
    { "f01::192.168.2.69", TRUE,
      { 0x0f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0x45, }, },
    { "f01:abcd:def0:1111:2222:3333:4444:192.168.2.69", FALSE,
      { 0x0f, 0x01, 0xab, 0xcd, 0xde, 0xf0, 0x11, 0x11,
        0x22, 0x22, 0x33, 0x33, 0x44, 0x44, 0x00, 0x00, }, },
    { ":1223", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "1224:", FALSE,
      { 0x12, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { ":", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "4324:5456", FALSE,
      { 0x43, 0x24, 0x54, 0x56, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "::1::4::5", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, }, },
    { "::54.fds.34.dsacf", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "::44.3", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "dumby:gumby:humby::1", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "abc.def.ghi.j", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "abc.def.ghi.jk", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "abc.def.ghi.jkl", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "abc.def.ghi.jklm", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "abc.def.ghi.jklmn", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },

    /* Actually, as we know that ipv6 parser uses ipv4 parser
       internally to parse ipv4 parts, we here actually test the ipv4
       parser .. */

    { "::1..3.4", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "::fo.def.5.4", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "::199.994.122.0", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "::199.-1.122.0", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "::122.0", FALSE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, },
    { "::255.255.255.255", TRUE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, }, },
    { "::192.99.195.255", TRUE,
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xc0, 0x63, 0xc3, 0xff, }, },

};

int main (int ac, char *av[])
{
    SshIpAddr   addr, addr2, addr3;
    char        * addr_1 = "192.168.2.69";
    char        * mask_1 = "192.168.2.69/24";
    char        *mask_2 = "255.255.255.0";
    unsigned char        buf[512], buf2[512];
    int i, k, n, alen;
    Boolean     ret, b, b2;
    SshUInt32 masklen, masklen2;

    if (ac > 1 && strcmp(av[1], "-v") == 0)
      verbose_flag = TRUE;

    srandom((SshUInt32)ssh_time());

    /* addr_1 */
    FUT(ssh_ipaddr_parse(&addr, addr_1));
    ssh_ipaddr_print(&addr, (char *)buf, sizeof(buf));
    SEQ(addr_1, (char *)buf);

    /* mask_1 */
    FUT(ssh_ipaddr_parse_with_mask(&addr, mask_1, NULL));
    ssh_ipaddr_print_with_mask(&addr, (char *)buf, sizeof(buf));
    SEQ(mask_1, (char *)buf);

    FUT(SSH_IP_MASK_LEN(&addr) == 24);

    /* addr_1 and mask_1 compares */
    FUT(ssh_ipaddr_parse_with_mask(&addr2, addr_1, "32"));
    FUT(SSH_IP_MASK_LEN(&addr2) == 32);

    FUT(SSH_IP_EQUAL(&addr, &addr2));
    FUT(ssh_ipaddr_mask_equal(&addr2, &addr));

    /* addr_1/mask_2 */
    FUT(ssh_ipaddr_parse_with_mask(&addr, addr_1, mask_2));
    FUT(SSH_IP_MASK_LEN(&addr) == 24);

    fprintf(stderr, "%s: ipv4 tests ok\n", av[0]);

    if (verbose_flag)
      speed();

    /* test ipv6 parsing */
    for (i = 0; i < sizeof(ipv6_test) / sizeof(*ipv6_test); i++) {
        ret = ssh_ipaddr_parse(&addr, ipv6_test[i].str);

        if (ret != ipv6_test[i].ok) {
            fprintf(stderr, "%s: ssh_ipaddr_parse(&addr, \"%s\") == %d != %d\n",
                    av[0], ipv6_test[i].str, ret, ipv6_test[i].ok);

            return 1;
        } else if (verbose_flag)
          fprintf(stderr, "%s: \"%s\" parsed %s, ok\n",
                  av[0], ipv6_test[i].str, ret == TRUE ? "valid" : "invalid");

        if (!SSH_IP_IS6(&addr)) {
            fprintf(stderr, "%s: ssh_ipaddr_parse(&addr, \"%s\"), SSH_IP_IS6(&addr) == FALSE\n", av[0], ipv6_test[i].str);
            return 1;
        }

        for (n = 0; n < 16; n++) {
            if (addr.addr_data[n] != ipv6_test[i].addr[n]) {
                fprintf(stderr, "%s: ssh_ipaddr_parse(&addr, \"%s\"), mismatch at addr byte %d, 0x%x != 0x%x\n", av[0], ipv6_test[i].str, n, addr.addr_data[n], ipv6_test[i].addr[n]);

                return 1;
            }
        }

        if (ipv6_test[i].ok) {
            ssh_ipaddr_print(&addr, (char *)buf, sizeof(buf));

            /*fprintf(stderr, "\"%s\" as \"%s\"\n", ipv6_test[i].str, buf);*/

            if (ssh_ipaddr_parse(&addr2, (char *)buf) == FALSE) {
                fprintf(stderr, "%s: \"%s\" could not be parsed (it was produced with ssh_ipaddr_print)\n", av[0], buf);

                return 1;
            }

            ssh_ipaddr_print(&addr2, (char *)buf2, sizeof(buf2));

            if (strcmp((char *)buf, (char *)buf2) != 0) {
                fprintf(stderr, "%s: \"%s\" not parsed and re-printed same, was \"%s\"\n",
                        av[0], buf, buf2);

                return 1;
            }
        }

        /*fprintf(stderr, "\"%s\": ok\n", ipv6_test[i].str);*/
    }

    fprintf(stderr, "%s: ipv6 tests ok\n", av[0]);

    /* Test ssh_ipaddr_merge_bits (we know the implementation -- it is
       sufficient to test it as ipv6) */

#define MAXMERGEITER    10000

    for (i = 0; i < MAXMERGEITER ; i++) {      
      for (n = 0; n < 16; n++) {
#if 1
        buf[n] = random() % 0xff;
        buf2[n] = random() % 0xff;
        masklen = random();
        masklen2 = random();
#else
        buf[n] = 0xff;
        buf2[n] = 0x00;
#endif
      }

      if (i < MAXMERGEITER/2) {
        SSH_IP6_MASK_DECODE(&addr, buf, masklen % 129);
        SSH_IP6_MASK_DECODE(&addr2, buf2, masklen2 % 129);
        n = i % 129;
        alen = 128;


      } else {
        SSH_IP4_MASK_DECODE(&addr, buf, masklen % 33);
        SSH_IP4_MASK_DECODE(&addr2, buf2, masklen % 33);
        n = i % 33;
        alen = 32;
      }

      if ((b = ssh_ipaddr_mask_equal(&addr, &addr2)) != 
          (b2 = mask_equal_1(&addr, &addr2))) {
        char buf[512];

        ssh_esnprintf(buf, sizeof(buf),
                      "ssh_ipaddr_mask_equal(%@,%@) == %s when should be %s\n",
                      ssh_ipaddr_render, &addr,
                      ssh_ipaddr_render, &addr2,
                      b ? "TRUE" : "FALSE",
                      b2 ? "TRUE" : "FALSE");

        fprintf(stderr, "%s", buf);

        ssh_ipaddr_mask_equal(&addr, &addr2);

        print_bits(" addr", addr.addr_data, SSH_IP_ADDR_LEN(&addr));
        print_bits("addr2", addr2.addr_data, SSH_IP_ADDR_LEN(&addr2));

        return 1;
      }

      ssh_ipaddr_merge_bits(&addr3, &addr, n, &addr2);

      for (k = 0; k < n; k++)
        if ((addr3.addr_data[k / 8] & (1 << (7 - k % 8))) !=
            (addr.addr_data[k / 8] & (1 << (7 - k % 8)))) {
          fprintf(stderr, "Mismatch in bit %d, byte %d: %#x vs. %#x, n=%d\n", k, k / 8,
                  addr3.addr_data[k / 8],
                  addr.addr_data[k / 8], n);

          fprintf(stderr, "        ");
          for (i = 0; i < (k + (k / 8)); i++)
            fputc(' ', stderr);
          fprintf(stderr, "x\n");

          print_bits(" addr", addr.addr_data, 16);
          print_bits("addr2", addr2.addr_data, 16);
          print_bits("addr3", addr3.addr_data, 16);

          return 1;
        }

      for (;k < alen; k++)
        if ((addr3.addr_data[k / 8] & (1 << (7 - k % 8))) !=
            (addr2.addr_data[k / 8] & (1 << (7 - k % 8)))) {
          fprintf(stderr, "Mismatch in bit %d, byte %d: %#x vs. %#x, n=%d\n", k, k / 8,
                  addr3.addr_data[k / 8],
                  addr2.addr_data[k / 8], n);

          fprintf(stderr, "        ");
          for (i = 0; i < (k + (k / 8)); i++)
            fputc(' ', stderr);
          fprintf(stderr, "x\n");

          print_bits(" addr", addr.addr_data, 16);
          print_bits("addr2", addr2.addr_data, 16);
          print_bits("addr3", addr3.addr_data, 16);

          return 1;
        }

      if (verbose_flag) {
        ssh_esnprintf((char *)buf, sizeof(buf), "%s: %@ |%d %@ ok\n",
                      av[0],
                      ssh_ipaddr_render,&addr, 
                      n,
                      ssh_ipaddr_render, &addr2);

        fprintf(stderr, "%s", buf);
      }
    }

    fprintf(stderr, "%s: ssh_ipaddr_merge_bits/ssh_ipaddr_mask_equal tests ok\n", av[0]);

    /* Test ssh_ipproto_render */
    for (i = 0; ssh_ip_protocol_id_keywords[i].name; i++) {
      ssh_esnprintf((char *)buf, sizeof(buf), "%@",
                    ssh_ipproto_render, ssh_ip_protocol_id_keywords[i].code);

      if (strcmp(ssh_ip_protocol_id_keywords[i].name, (char *)buf)) {
        fprintf(stderr, "Protocol %lu is not rendered as `%s', but `%s'\n",
                ssh_ip_protocol_id_keywords[i].code,
                ssh_ip_protocol_id_keywords[i].name,
                buf);

      }

      if (verbose_flag)
        fprintf(stderr, "Protocol %lu = %s\n",
                ssh_ip_protocol_id_keywords[i].code,
                buf);
    }

    fprintf(stderr, "%s: ssh_ipproto_render tests ok\n", av[0]);

    return 0;
}
