Add unit test for TCP/IP checksumming code.
authorBen Pfaff <blp@nicira.com>
Wed, 10 Jun 2009 00:10:18 +0000 (17:10 -0700)
committerBen Pfaff <blp@nicira.com>
Wed, 10 Jun 2009 00:10:18 +0000 (17:10 -0700)
lib/csum.c
tests/automake.mk
tests/test-csum.c [new file with mode: 0644]

index 5266be6de252d63ca3255cad6907f92c1795504d..75cbb53ad348eb7f2c0564c76c78dd4a69f3aca6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009 Nicira Networks.
  *
  * Permission to use, copy, modify, and/or distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
 #include <config.h>
 #include "csum.h"
 
-/* Returns the IP checksum of the 'n' bytes in 'data'. */
+/* Returns the IP checksum of the 'n' bytes in 'data'.
+ *
+ * The return value has the same endianness as the data.  That is, if 'data'
+ * consists of a packet in network byte order, then the return value is a value
+ * in network byte order, and if 'data' consists of a data structure in host
+ * byte order, then the return value is in host byte order. */
 uint16_t
 csum(const void *data, size_t n)
 {
@@ -62,7 +67,12 @@ csum_continue(uint32_t partial, const void *data_, size_t n)
 }
 
 /* Returns the IP checksum corresponding to 'partial', which is a value updated
- * by some combination of csum_add16(), csum_add32(), and csum_continue(). */
+ * by some combination of csum_add16(), csum_add32(), and csum_continue().
+ *
+ * The return value has the same endianness as the checksummed data.  That is,
+ * if the data consist of a packet in network byte order, then the return value
+ * is a value in network byte order, and if the data are a data structure in
+ * host byte order, then the return value is in host byte order. */
 uint16_t
 csum_finish(uint32_t partial)
 {
index 0508144f0443b43df6b6081f797caeee99a32c7d..ab6c1b14f17f76301aec85dc54947449a181efa8 100644 (file)
@@ -3,6 +3,11 @@ noinst_PROGRAMS += tests/test-classifier
 tests_test_classifier_SOURCES = tests/test-classifier.c
 tests_test_classifier_LDADD = lib/libopenvswitch.a
 
+TESTS += tests/test-csum
+noinst_PROGRAMS += tests/test-csum
+tests_test_csum_SOURCES = tests/test-csum.c
+tests_test_csum_LDADD = lib/libopenvswitch.a
+
 TESTS += tests/test-flows.sh
 noinst_PROGRAMS += tests/test-flows
 tests_test_flows_SOURCES = tests/test-flows.c
diff --git a/tests/test-csum.c b/tests/test-csum.c
new file mode 100644 (file)
index 0000000..a6db5c5
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+#include "csum.h"
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "random.h"
+#include "util.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+struct test_case {
+    char *data;
+    size_t size;
+    uint16_t csum;
+};
+
+#define TEST_CASE(DATA, CSUM) { DATA, (sizeof DATA) - 1, CSUM }
+
+static const struct test_case test_cases[] = {
+    /* RFC 1071 section 3. */
+    TEST_CASE("\x00\x01\xf2\x03\xf4\xf5\xf6\xf7", (uint16_t) ~0xddf2),
+
+    /* http://www.sbprojects.com/projects/tcpip/theory/theory14.htm */
+    TEST_CASE("\x45\x00\x00\x28\x1F\xFD\x40\x00\x80\x06"
+              "\x00\x00\xC0\xA8\x3B\x0A\xC0\xA8\x3B\x32",
+              0xe345),
+
+    /* http://mathforum.org/library/drmath/view/54379.html */
+    TEST_CASE("\x86\x5e\xac\x60\x71\x2a\x81\xb5", 0xda60),
+};
+
+static void
+mark(char c)
+{
+    putchar(c);
+    fflush(stdout);
+}
+
+#if 0
+/* This code is useful for generating new test cases for RFC 1624 section 4. */
+static void
+generate_rfc1624_test_case(void)
+{
+    int i;
+
+    for (i = 0; i < 10000000; i++) {
+        uint32_t data[8];
+        int j;
+
+        for (j = 0; j < 8; j++) {
+            data[j] = random_uint32();
+        }
+        data[7] &= 0x0000ffff;
+        data[7] |= 0x55550000;
+        if (ntohs(~csum(data, sizeof data - 2)) == 0xcd7a) {
+            ovs_hex_dump(stdout, data, sizeof data, 0, false);
+            exit(0);
+        }
+    }
+}
+#endif
+
+
+
+/* Make sure we get the calculation in RFC 1624 section 4 correct. */
+static void
+test_rfc1624(void)
+{
+    /* "...an IP packet header in which a 16-bit field m = 0x5555..." */
+    uint8_t data[32] =
+        "\xfe\x8f\xc1\x14\x4b\x6f\x70\x2a\x80\x29\x78\xc0\x58\x81\x77\xaa"
+        "\x66\x64\xfc\x96\x63\x97\x64\xee\x12\x53\x1d\xa9\x2d\xa9\x55\x55";
+
+    /* "...the one's complement sum of all other header octets is 0xCD7A." */
+    assert(ntohs(csum(data, sizeof data - 2)) == (uint16_t) ~0xcd7a);
+
+    /* "...the header checksum would be:
+
+          HC = ~(0xCD7A + 0x5555)
+             = ~0x22D0
+             =  0xDD2F"
+    */
+    assert(ntohs(csum(data, sizeof data)) == 0xdd2f);
+
+    /* "a 16-bit field m = 0x5555 changes to m' = 0x3285..." */
+    data[30] = 0x32;
+    data[31] = 0x85;
+
+    /* "The new checksum via recomputation is:
+
+          HC' = ~(0xCD7A + 0x3285)
+              = ~0xFFFF
+              =  0x0000"
+    */
+    assert(ntohs(csum(data, sizeof data)) == 0x0000);
+
+    /* "Applying [Eqn. 3] to the example above, we get the correct result:
+
+          HC' = ~(C + (-m) + m')
+              = ~(0x22D0 + ~0x5555 + 0x3285)
+              = ~0xFFFF
+              =  0x0000" */
+    assert(recalc_csum16(0xdd2f, 0x5555, 0x3285) == 0x0000);
+
+    mark('#');
+}
+
+int
+main(void)
+{
+    const struct test_case *tc;
+    int i;
+
+    for (tc = test_cases; tc < &test_cases[ARRAY_SIZE(test_cases)]; tc++) {
+        const uint16_t *data16 = (const uint16_t *) tc->data;
+        const uint32_t *data32 = (const uint32_t *) tc->data;
+        uint32_t partial;
+        size_t i;
+
+        /* Test csum(). */
+        assert(ntohs(csum(tc->data, tc->size)) == tc->csum);
+        mark('.');
+
+        /* Test csum_add16(). */
+        partial = 0;
+        for (i = 0; i < tc->size / 2; i++) {
+            partial = csum_add16(partial, data16[i]);
+        }
+        assert(ntohs(csum_finish(partial)) == tc->csum);
+        mark('.');
+
+        /* Test csum_add32(). */
+        partial = 0;
+        for (i = 0; i < tc->size / 4; i++) {
+            partial = csum_add32(partial, data32[i]);
+        }
+        assert(ntohs(csum_finish(partial)) == tc->csum);
+        mark('.');
+
+        /* Test alternating csum_add16() and csum_add32(). */
+        partial = 0;
+        for (i = 0; i < tc->size / 4; i++) {
+            if (i % 2) {
+                partial = csum_add32(partial, data32[i]);
+            } else {
+                partial = csum_add16(partial, data16[i * 2]);
+                partial = csum_add16(partial, data16[i * 2 + 1]);
+            }
+        }
+        assert(ntohs(csum_finish(partial)) == tc->csum);
+        mark('.');
+
+        /* Test csum_continue(). */
+        partial = 0;
+        for (i = 0; i < tc->size / 4; i++) {
+            if (i) {
+                partial = csum_continue(partial, &data32[i], 4);
+            } else {
+                partial = csum_continue(partial, &data16[i * 2], 2);
+                partial = csum_continue(partial, &data16[i * 2 + 1], 2);
+            }
+        }
+        assert(ntohs(csum_finish(partial)) == tc->csum);
+        mark('#');
+    }
+
+    test_rfc1624();
+
+    /* Test recalc_csum16(). */
+    for (i = 0; i < 32; i++) {
+        uint16_t old_u16, new_u16;
+        uint16_t old_csum;
+        uint16_t data[16];
+        int j, index;
+
+        for (j = 0; j < ARRAY_SIZE(data); j++) {
+            data[j] = random_uint32();
+        }
+        old_csum = csum(data, sizeof data);
+        index = random_range(ARRAY_SIZE(data));
+        old_u16 = data[index];
+        new_u16 = data[index] = random_uint32();
+        assert(csum(data, sizeof data)
+               == recalc_csum16(old_csum, old_u16, new_u16));
+        mark('.');
+    }
+    mark('#');
+
+    /* Test recalc_csum32(). */
+    for (i = 0; i < 32; i++) {
+        uint32_t old_u32, new_u32;
+        uint16_t old_csum;
+        uint32_t data[16];
+        int j, index;
+
+        for (j = 0; j < ARRAY_SIZE(data); j++) {
+            data[j] = random_uint32();
+        }
+        old_csum = csum(data, sizeof data);
+        index = random_range(ARRAY_SIZE(data));
+        old_u32 = data[index];
+        new_u32 = data[index] = random_uint32();
+        assert(csum(data, sizeof data)
+               == recalc_csum32(old_csum, old_u32, new_u32));
+        mark('.');
+    }
+    mark('#');
+
+    putchar('\n');
+
+    return 0;
+}