// -*- c-basic-offset: 4 -*-
/*
 * confparsetest.{cc,hh} -- regression test element for configuration parsing
 * Eddie Kohler
 *
 * Copyright (c) 2007 Regents of the University of California
 * Copyright (c) 2008 Meraki, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include "confparsetest.hh"
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/straccum.hh>
#if CLICK_USERLEVEL
# include <click/userutils.hh>
#endif
CLICK_DECLS

ConfParseTest::ConfParseTest()
{
}

ConfParseTest::~ConfParseTest()
{
}

#define CHECK(x) if (!(x)) return errh->error("%s:%d: test `%s' failed", __FILE__, __LINE__, #x);

int
ConfParseTest::initialize(ErrorHandler *errh)
{
    CHECK(cp_uncomment("  a  b  ") == "a  b");
    CHECK(cp_uncomment("  a /* whatever */   // whatever\n    b  ") == "a b");
    CHECK(cp_uncomment("  \" /*???  */ \"  ") == "\" /*???  */ \"");
    CHECK(cp_unquote("\"\\n\" abc /* 123 */ '/* def */'") == "\n abc /* def */");

    Vector<String> v;
    cp_argvec("a, b, c", v);
    CHECK(v.size() == 3);
    CHECK(v[0] == "a");
    CHECK(v[1] == "b");
    CHECK(v[2] == "c");
    cp_argvec("  a /*?*/ b,  c, ", v);
    CHECK(v.size() == 5);
    CHECK(v[3] == "a b");
    CHECK(v[4] == "c");
    cp_argvec("\"x, y\" // ?", v);
    CHECK(v.size() == 6);
    CHECK(v[5] == "\"x, y\"");
    cp_spacevec("a  b, c", v);
    CHECK(v.size() == 9);
    CHECK(v[6] == "a");
    CHECK(v[7] == "b,");
    CHECK(v[8] == "c");
    cp_spacevec("  'a /*?*/ b'c", v);
    CHECK(v.size() == 10);
    CHECK(v[9] == "'a /*?*/ b'c");

    int32_t i32;
    uint32_t u32;
    u32 = 97;
    CHECK(cp_integer("0", &i32) == true && i32 == 0);
    CHECK(cp_integer("-0", &i32) == true && i32 == 0);
    CHECK(u32 == 97);
    CHECK(cp_integer("0", &u32) == true && u32 == 0);
    CHECK(cp_integer("-0", &u32) == false);
    CHECK(cp_integer("4294967294", &u32) == true && u32 == 4294967294U);
    CHECK(cp_integer("0xFFFFFFFE", &u32) == true && u32 == 4294967294U);
    CHECK(cp_integer("4294967296", &u32) == true && u32 == 4294967295U && cp_errno == CPE_OVERFLOW);
    CHECK(cp_integer("42949672961939", &u32) == true && u32 == 4294967295U && cp_errno == CPE_OVERFLOW);
    CHECK(cp_integer("0xFFFFFFFFF", &u32) == true && u32 == 4294967295U && cp_errno == CPE_OVERFLOW);
    CHECK(cp_integer("4294967296", &i32) == true && i32 == 2147483647 && cp_errno == CPE_OVERFLOW);
    CHECK(cp_integer("2147483647", &i32) == true && i32 == 2147483647 && cp_errno == CPE_OK);
    CHECK(cp_integer("-2147483648", &i32) == true && i32 == -2147483647 - 1 && cp_errno == CPE_OK);
    CHECK(cp_integer("-4294967296", &i32) == true && i32 == -2147483647 - 1 && cp_errno == CPE_OVERFLOW);
    const char *s = "-127 ";
    CHECK(cp_integer(s, s + strlen(s) - 1, 10, &i32) == s + 4 && i32 == -127);
    CHECK(cp_integer(String(s), &i32) == false && cp_errno == CPE_FORMAT);

#if HAVE_LONG_LONG && SIZEOF_LONG_LONG == 8
    long long ll;
    unsigned long long ull;
    CHECK(cp_integer("9223372036854775807", &ll) == true && ll == 0x7FFFFFFFFFFFFFFFULL);
    CHECK(cp_integer("-9223372036854775808", &ll) == true && ll == (long long) 0x8000000000000000ULL);
    CHECK(cp_integer("18446744073709551616", &ull) == true && ull == 0xFFFFFFFFFFFFFFFFULL && cp_errno == CPE_OVERFLOW);
#endif

    CHECK(cp_real2("-0.5", 1, &i32) == true && i32 == -1);
    CHECK(cp_seconds_as("3600", 0, &u32) == true && u32 == 3600);
    CHECK(cp_seconds_as("3600s", 0, &u32) == true && u32 == 3600);
    CHECK(cp_seconds_as("3.6e6 msec", 0, &u32) == true && u32 == 3600);
    CHECK(cp_seconds_as("60m", 0, &u32) == true && u32 == 3600);
    CHECK(cp_seconds_as("1 hr", 0, &u32) == true && u32 == 3600);

#if HAVE_FLOAT_TYPES
    double d;
    CHECK(cp_seconds("3600", &d) == true && d == 3600);
    CHECK(cp_seconds("3600s", &d) == true && d == 3600);
    CHECK(cp_seconds("3.6e6 msec", &d) == true && d == 3600);
    CHECK(cp_seconds("60m", &d) == true && d == 3600);
    CHECK(cp_seconds("1 hr", &d) == true && d == 3600);
#endif

    {
	IPAddress a, m;
	CHECK(cp_ip_prefix("18.26.4/24", &a, &m, this) == true
	      && a.unparse_with_mask(m) == "18.26.4.0/24");
	CHECK(cp_ip_prefix("18.26.4/28", &a, &m, this) == false);
    }

#if CLICK_IP6
    {
	IP6Address a;
	CHECK(cp_ip6_address("1080:0:0:0:8:800:200C:417a", &a, this) == true
	      && a.data32()[0] == ntohl(0x10800000)
	      && a.data32()[1] == ntohl(0x00000000)
	      && a.data32()[2] == ntohl(0x00080800)
	      && a.data32()[3] == ntohl(0x200C417a));
	CHECK(cp_ip6_address("1080::8:800:200C:417a", &a, this) == true
	      && a.data32()[0] == ntohl(0x10800000)
	      && a.data32()[1] == ntohl(0x00000000)
	      && a.data32()[2] == ntohl(0x00080800)
	      && a.data32()[3] == ntohl(0x200C417a));
	CHECK(cp_ip6_address("::13.1.68.3", &a, this) == true
	      && a.data32()[0] == 0x00000000
	      && a.data32()[1] == 0x00000000
	      && a.data32()[2] == 0x00000000
	      && a.data32()[3] == ntohl(0x0D014403));
	CHECK(cp_ip6_address("::ffff:129.144.52.38", &a, this) == true
	      && a.data32()[0] == 0x00000000
	      && a.data32()[1] == 0x00000000
	      && a.data32()[2] == ntohl(0x0000FFFF)
	      && a.data32()[3] == ntohl(0x81903426));
	IPAddress a4;
	if (cp_ip_address("ip4_addr", &a4, this) == true)
	    CHECK(cp_ip6_address("0::ip4_addr", &a, this) == true
		  && a.data32()[0] == 0x00000000 && a.data32()[1] == 0x00000000
		  && a.data32()[2] == 0x00000000
		  && a.data32()[3] == a4.addr());
	CHECK(cp_ip6_address("ffff:ffff:ffff:ffff:ffff:ffff::", &a, this) == true
	      && a.data32()[0] == 0xFFFFFFFF
	      && a.data32()[1] == 0xFFFFFFFF
	      && a.data32()[2] == 0xFFFFFFFF
	      && a.data32()[3] == 0);
	CHECK(a.mask_to_prefix_len() == 96);
	CHECK(cp_ip6_address("ffff:ffff:ffff:ffff:ffff:ffff:8000:", &a, this) == true
	      && a.data32()[0] == 0xFFFFFFFF
	      && a.data32()[1] == 0xFFFFFFFF
	      && a.data32()[2] == 0xFFFFFFFF
	      && a.data32()[3] == htonl(0x80000000));
	CHECK(a.mask_to_prefix_len() == 97);
	CHECK(cp_ip6_address("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", &a, this) == true
	      && a.data32()[0] == 0xFFFFFFFF
	      && a.data32()[1] == 0xFFFFFFFF
	      && a.data32()[2] == 0xFFFFFFFF
	      && a.data32()[3] == 0xFFFFFFFF);
	CHECK(a.mask_to_prefix_len() == 128);
	CHECK(cp_ip6_address("::", &a, this) == true
	      && a.data32()[0] == 0
	      && a.data32()[1] == 0
	      && a.data32()[2] == 0
	      && a.data32()[3] == 0);
	CHECK(a.mask_to_prefix_len() == 0);
	CHECK(cp_ip6_address("::8000", &a, this) == true
	      && a.data32()[0] == 0
	      && a.data32()[1] == 0
	      && a.data32()[2] == 0
	      && a.data32()[3] == htonl(0x00008000));
	CHECK(a.mask_to_prefix_len() == -1);
	a = IP6Address::make_inverted_prefix(17);
	CHECK(a.data32()[0] == htonl(0x00007FFF)
	      && a.data32()[1] == 0xFFFFFFFF
	      && a.data32()[2] == 0xFFFFFFFF
	      && a.data32()[3] == 0xFFFFFFFF);
	a = IP6Address::make_inverted_prefix(128);
	CHECK(a.data32()[0] == 0
	      && a.data32()[1] == 0
	      && a.data32()[2] == 0
	      && a.data32()[3] == 0);
    }
#endif

    Timestamp t = Timestamp(0, 0) - Timestamp::make_msec(1001);
    CHECK(t.sec() == -2 && t.usec() == 999000);
    CHECK(t.unparse() == "-1.001000");
#if CLICK_HZ == 1000		/* true at userlevel */
    CHECK(t == Timestamp::make_jiffies((click_jiffies_difference_t) -1001));
    CHECK(t < Timestamp::make_jiffies((click_jiffies_t) -1001));
    CHECK(-t == Timestamp::make_jiffies((click_jiffies_t) 1001));
    CHECK(-t == Timestamp::make_jiffies((click_jiffies_difference_t) 1001));
#endif
    Timestamp t2 = Timestamp(-10, 0);
    CHECK(t2.sec() == -10 && t2.subsec() == 0);
    CHECK(t2.unparse() == "-10.000000");
    CHECK(t2 < t);
    CHECK((-t2).unparse() == "10.000000");
    CHECK((-t).unparse() == "1.001000");
    CHECK(-t2 > t);
    CHECK((-t + t2).unparse() == "-8.999000");
    t = Timestamp::make_msec(999);
    CHECK((t + t).unparse() == "1.998000");
    CHECK((-t - t).unparse() == "-1.998000");
    CHECK(-t == Timestamp::make_msec(-999));
    CHECK(t == Timestamp::make_usec(999000));
    CHECK(-t == Timestamp::make_usec(-999000));
    CHECK(t == Timestamp::make_nsec(999000000));
    CHECK(-t == Timestamp::make_nsec(-999000000));
    CHECK(t.msecval() == 999);
    CHECK(t.usecval() == 999000);
    CHECK(t.nsecval() == 999000000);
    CHECK((-t).msecval() == -999);
    CHECK((-t).usecval() == -999000);
    CHECK((-t).nsecval() == -999000000);

    // some string tests for good measure
    CHECK(String("abcdef").substring(-3) == "def");
    CHECK(String("abc").substring(-3) == "abc");
    CHECK(String("ab").substring(-3) == "ab");
    CHECK(String("abcdef").substring(-3, -2) == "d");
    CHECK(String("abcdef").substring(-3, 2) == "de");
    CHECK(String("abcdef").substring(2, -3) == "c");
    CHECK(String("abcdef").substring(2, 3) == "cde");
    CHECK(String("abcdef").substring(2, 10) == "cdef");
    CHECK(String("abcdef").substring(-2, 10) == "ef");
    CHECK(String("abcdef").substring(-10, -3) == "abc");
    CHECK(String("abcdef").substring(-10, -9) == "");
    CHECK(String("abcdef").substring(10, -9) == "");

    String x("abcdefghijklmn");
    x.append(x.data() + 1, 12);
    CHECK(x == "abcdefghijklmnbcdefghijklm");
    x += x;
    CHECK(x == "abcdefghijklmnbcdefghijklmabcdefghijklmnbcdefghijklm");

    StringAccum xx(24);
    xx << "abcdefghijklmn";
    CHECK(xx.capacity() - xx.length() < 12);
    xx.append(xx.data() + 1, 12);
    CHECK(strcmp(xx.c_str(), "abcdefghijklmnbcdefghijklm") == 0);
    xx << xx;
    CHECK(strcmp(xx.c_str(), "abcdefghijklmnbcdefghijklmabcdefghijklmnbcdefghijklm") == 0);
    xx << String::make_out_of_memory();
    CHECK(xx.out_of_memory());
    xx.append("X", 1);
    CHECK(xx.out_of_memory());

    // String hash codes
    {
	static const char hash1[] = "boot_countXXXXXXYboot_countYYYYYZZboot_countZZZZWWWboot_countWWW";
	String s1 = String::make_stable(hash1, 10);
	String s2 = String::make_stable(hash1 + 17, 10);
	String s3 = String::make_stable(hash1 + 34, 10);
	String s4 = String::make_stable(hash1 + 51, 10);
	CHECK(s1 == s2 && s2 == s3 && s3 == s4 && s4 == s1);
	CHECK(s1.hashcode() == s2.hashcode());
	CHECK(s1.hashcode() == s3.hashcode());
	CHECK(s1.hashcode() == s4.hashcode());
	CHECK(((uintptr_t) s1.data() & 3) != ((uintptr_t) s2.data() & 3));
	CHECK(((uintptr_t) s1.data() & 3) != ((uintptr_t) s3.data() & 3));
	CHECK(((uintptr_t) s2.data() & 3) != ((uintptr_t) s3.data() & 3));
	CHECK(((uintptr_t) s1.data() & 3) != ((uintptr_t) s4.data() & 3));
	CHECK(((uintptr_t) s2.data() & 3) != ((uintptr_t) s4.data() & 3));
	CHECK(((uintptr_t) s3.data() & 3) != ((uintptr_t) s4.data() & 3));
    }

#if CLICK_USERLEVEL
    // click_strcmp
    CHECK(click_strcmp("a", "b") < 0);
    CHECK(click_strcmp("a9", "a10") < 0);
    CHECK(click_strcmp("a001", "a2") < 0);   // 1 < 2
    CHECK(click_strcmp("a001", "a1") > 0);   // longer string of initial zeros
    CHECK(click_strcmp("a", "B") < 0);
    CHECK(click_strcmp("Baa", "baa") < 0);
    CHECK(click_strcmp("Baa", "caa") < 0);
    CHECK(click_strcmp("baa", "Caa") < 0);
    CHECK(click_strcmp("baa", "Baa") > 0);
    CHECK(click_strcmp("baA", "baA") == 0);
    CHECK(click_strcmp("a9x", "a10") < 0);
    CHECK(click_strcmp("a9x", "a9xy") < 0);
    CHECK(click_strcmp("0", "0.1") < 0);
    CHECK(click_strcmp("0", "0.") < 0);
    CHECK(click_strcmp("0", "-0.1") > 0);
    CHECK(click_strcmp("-0", "-0.1") > 0);
    CHECK(click_strcmp("-9", "-0.1") < 0);
    CHECK(click_strcmp("9", "0.1") > 0);
    CHECK(click_strcmp("-0.2", "-0.1") < 0);
    CHECK(click_strcmp("-2.2", "-2") < 0);
    CHECK(click_strcmp("2.2", "2") > 0);
    CHECK(click_strcmp(".2", "0.1") > 0);
    CHECK(click_strcmp(".2", "0.39") < 0);
    CHECK(click_strcmp(".2", "0.2") < 0);
    CHECK(click_strcmp("a-2", "a-23") < 0);
    CHECK(click_strcmp("a-2", "a-3") < 0);
    CHECK(click_strcmp("a1.2", "a1a") > 0);
#endif

    errh->message("All tests pass!");
    return 0;
}

EXPORT_ELEMENT(ConfParseTest)
CLICK_ENDDECLS
