From c1f9b15c3ab84bde76700bee126c1278fc8b0ef4 Mon Sep 17 00:00:00 2001
From: Martine Lenders <m.lenders@fu-berlin.de>
Date: Tue, 20 Jun 2017 15:20:00 +0200
Subject: [PATCH] tests: extend nib-internal unittests for off-link entry
 handling

---
 .../tests-gnrc_ipv6_nib/Makefile.include      |   2 +
 .../tests-gnrc_ipv6_nib-internal.c            | 783 +++++++++++++++++-
 2 files changed, 784 insertions(+), 1 deletion(-)

diff --git a/tests/unittests/tests-gnrc_ipv6_nib/Makefile.include b/tests/unittests/tests-gnrc_ipv6_nib/Makefile.include
index ccae089522..269d2c903a 100644
--- a/tests/unittests/tests-gnrc_ipv6_nib/Makefile.include
+++ b/tests/unittests/tests-gnrc_ipv6_nib/Makefile.include
@@ -1,6 +1,8 @@
 USEMODULE += gnrc_ipv6_nib
 
+CFLAGS += -DGNRC_IPV6_NIB_CONF_ROUTER=1
 CFLAGS += -DGNRC_IPV6_NIB_NUMOF=16
+CFLAGS += -DGNRC_IPV6_NIB_OFFL_NUMOF=25
 CFLAGS += -DGNRC_IPV6_NIB_DEFAULT_ROUTER_NUMOF=4
 
 INCLUDES += -I$(RIOTBASE)/sys/net/gnrc/network_layer/ipv6/nib
diff --git a/tests/unittests/tests-gnrc_ipv6_nib/tests-gnrc_ipv6_nib-internal.c b/tests/unittests/tests-gnrc_ipv6_nib/tests-gnrc_ipv6_nib-internal.c
index 2b9c7ebb7f..494acdb2c4 100644
--- a/tests/unittests/tests-gnrc_ipv6_nib/tests-gnrc_ipv6_nib-internal.c
+++ b/tests/unittests/tests-gnrc_ipv6_nib/tests-gnrc_ipv6_nib-internal.c
@@ -29,7 +29,8 @@
 
 #define LINK_LOCAL_PREFIX   { 0xfe, 0x08, 0, 0, 0, 0, 0, 0 }
 #define GLOBAL_PREFIX       { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0 }
-#define IFACE               (26)
+#define GLOBAL_PREFIX_LEN   (30)
+#define IFACE               (6)
 
 static void set_up(void)
 {
@@ -121,6 +122,23 @@ static void test_nib_alloc__success_duplicate(void)
     TEST_ASSERT(node == _nib_onl_alloc(&addr, iface));
 }
 
+/*
+ * Creates a persistent on-link entry with no IPv6 address and then tries to
+ * create another one with the same interface, but with an address
+ * Expected result: entries should be identical
+ */
+static void test_nib_alloc__success_noaddr_override(void)
+{
+    _nib_onl_entry_t *node1, *node2;
+    const ipv6_addr_t addr = { .u64 = { { .u8 = GLOBAL_PREFIX },
+                                      { .u64 = TEST_UINT64 } } };
+
+    TEST_ASSERT_NOT_NULL((node1 = _nib_onl_alloc(NULL, IFACE)));
+    TEST_ASSERT_NOT_NULL((node2 = _nib_onl_alloc(&addr, IFACE)));
+    TEST_ASSERT(node1 == node2);
+    TEST_ASSERT(ipv6_addr_equal(&addr, &node1->ipv6));
+}
+
 /*
  * Creates an non-persistent entry.
  * Expected result: new entry should contain the given address and interface
@@ -964,6 +982,735 @@ static void test_nib_drl_get_dr__success4(void)
     TEST_ASSERT(nib_res != node2);
 }
 
+#if GNRC_IPV6_NIB_NUMOF < GNRC_IPV6_NIB_OFFL_NUMOF
+#define MAX_NUMOF   (GNRC_IPV6_NIB_NUMOF)
+#else /* GNRC_IPV6_NIB_NUMOF < GNRC_IPV6_NIB_OFFL_NUMOF */
+#define MAX_NUMOF   (GNRC_IPV6_NIB_OFFL_NUMOF)
+#endif
+
+/*
+ * Creates MAX_NUMOF off-link entries with different next-hop addresses and
+ * then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_next_hop(void)
+{
+    ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                    { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                    GLOBAL_PREFIX_LEN)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _FT;
+        next_hop.u64[1].u64++;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, IFACE, &pfx, GLOBAL_PREFIX_LEN));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different interfaces and then tries
+ * to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_iface(void)
+{
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned iface = IFACE;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, iface, &pfx,
+                                                    GLOBAL_PREFIX_LEN)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _PL;
+        iface++;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, iface, &pfx, GLOBAL_PREFIX_LEN));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different next-hop addresses and
+ * interfaces and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_next_hop_iface(void)
+{
+    ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                             { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned iface = IFACE;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, iface, &pfx,
+                                                    GLOBAL_PREFIX_LEN)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _DC;
+        next_hop.u64[1].u64++;
+        iface++;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, iface, &pfx, GLOBAL_PREFIX_LEN));
+}
+
+/*
+ * Creates GNRC_IPV6_NIB_OFFL_NUMOF off-link entries with different prefixes
+ * of the same length and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_pfx(void)
+{
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    for (int i = 0; i < GNRC_IPV6_NIB_OFFL_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                    GLOBAL_PREFIX_LEN)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _FT;
+        pfx.u16[0].u16++;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, IFACE, &pfx, GLOBAL_PREFIX_LEN));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different prefixes of the same
+ * length and different next-hop addresses and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_next_hop_pfx(void)
+{
+    ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                    { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                    GLOBAL_PREFIX_LEN)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _FT;
+        next_hop.u64[1].u64++;
+        pfx.u16[0].u16++;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, IFACE, &pfx, GLOBAL_PREFIX_LEN));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different prefixes of the same
+ * length and different interfaces and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_iface_pfx(void)
+{
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned iface = IFACE;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, iface, &pfx,
+                                                    GLOBAL_PREFIX_LEN)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _FT;
+        iface++;
+        pfx.u16[0].u16++;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, iface, &pfx, GLOBAL_PREFIX_LEN));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different prefixes of the same
+ * length, different interfaces, and different next hop addresses and then
+ * tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_next_hop_iface_pfx(void)
+{
+    ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                      { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned iface = IFACE;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, iface, &pfx,
+                                                    GLOBAL_PREFIX_LEN)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _FT;
+        next_hop.u64[1].u64++;
+        iface++;
+        pfx.u16[0].u16++;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, iface, &pfx, GLOBAL_PREFIX_LEN));
+}
+
+/*
+ * Creates GNRC_IPV6_NIB_OFFL_NUMOF off-link entries with different prefix
+ * lengths and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_pfx_len(void)
+{
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned pfx_len = GLOBAL_PREFIX_LEN;
+
+    for (int i = 0; i < GNRC_IPV6_NIB_OFFL_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                    pfx_len)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _PL;
+        pfx_len--;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, IFACE, &pfx, pfx_len));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different prefixes and then tries to
+ * add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_next_hop_pfx_len(void)
+{
+    ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                      { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned pfx_len = GLOBAL_PREFIX_LEN;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                    pfx_len)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _PL;
+        next_hop.u64[1].u64++;
+        pfx_len--;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, IFACE, &pfx, pfx_len));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different prefix lengths and
+ * interfaces and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_iface_pfx_len(void)
+{
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned pfx_len = GLOBAL_PREFIX_LEN, iface = IFACE;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, iface, &pfx,
+                                                    pfx_len)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _PL;
+        iface++;
+        pfx_len--;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, iface, &pfx, pfx_len));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different prefix lengths,
+ * interfaces, and next hop addresses and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_next_hop_iface_pfx_len(void)
+{
+    ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                      { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned pfx_len = GLOBAL_PREFIX_LEN, iface = IFACE;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, iface, &pfx,
+                                                    pfx_len)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _PL;
+        next_hop.u64[1].u64++;
+        iface++;
+        pfx_len--;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, iface, &pfx, pfx_len));
+}
+
+/*
+ * Creates GNRC_IPV6_NIB_OFFL_NUMOF off-link entries with different prefixes
+ * and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_pfx_pfx_len(void)
+{
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned pfx_len = GLOBAL_PREFIX_LEN;
+
+    for (int i = 0; i < GNRC_IPV6_NIB_OFFL_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                    pfx_len)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _FT;
+        pfx.u16[0].u16++;
+        pfx_len--;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, IFACE, &pfx, pfx_len));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different prefixes and different
+ * next-hop addresses and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_next_hop_pfx_pfx_len(void)
+{
+    ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                    { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned pfx_len = GLOBAL_PREFIX_LEN;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                    pfx_len)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _FT;
+        next_hop.u64[1].u64++;
+        pfx.u16[0].u16++;
+        pfx_len--;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, IFACE, &pfx, pfx_len));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different prefixes and different
+ * interfaces and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_iface_pfx_pfx_len(void)
+{
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned pfx_len = GLOBAL_PREFIX_LEN, iface = IFACE;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, iface, &pfx,
+                                                    pfx_len)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _FT;
+        iface++;
+        pfx.u16[0].u16++;
+        pfx_len--;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, iface, &pfx, pfx_len));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different prefixes, different
+ * interfaces, and different next hop addresses and then tries to add another.
+ * Expected result: should return NULL
+ */
+static void test_nib_offl_alloc__no_space_left_diff_next_hop_iface_pfx_pfx_len(void)
+{
+    ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                      { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned pfx_len = GLOBAL_PREFIX_LEN, iface = IFACE;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, iface, &pfx,
+                                                    pfx_len)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _FT;
+        next_hop.u64[1].u64++;
+        iface++;
+        pfx.u16[0].u16++;
+        pfx_len--;
+    }
+    TEST_ASSERT_NULL(_nib_offl_alloc(&next_hop, iface, &pfx, pfx_len));
+}
+
+/*
+ * Creates MAX_NUMOF off-link entries with different prefixes, different
+ * interfaces, and different next hop addresses and then tries to add another
+ * equal to the last.
+ * Expected result: should return not NULL (the last)
+ */
+static void test_nib_offl_alloc__success_duplicate(void)
+{
+    ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                      { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+    unsigned pfx_len = GLOBAL_PREFIX_LEN, iface = IFACE;
+
+    for (int i = 0; i < MAX_NUMOF; i++) {
+        _nib_offl_entry_t *dst;
+
+        next_hop.u64[1].u64++;
+        iface++;
+        pfx.u16[0].u16++;
+        pfx_len--;
+        TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, iface, &pfx,
+                                                    pfx_len)));
+        TEST_ASSERT_NOT_NULL(dst->next_hop);
+        dst->mode |= _FT;
+    }
+    TEST_ASSERT_NOT_NULL(_nib_offl_alloc(&next_hop, iface, &pfx, pfx_len));
+}
+
+/*
+ * Creates an off-link entry with no next hop address and then adds another
+ * with equal prefix and interface to the last, but with a next hop address
+ * Expected result: the first entry should be equal to the second and both
+ * have the same next hop address
+ */
+static void test_nib_offl_alloc__success_overwrite_unspecified(void)
+{
+    _nib_offl_entry_t *dst1, *dst2;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst1 = _nib_offl_alloc(NULL, IFACE, &pfx,
+                                                 GLOBAL_PREFIX_LEN)));
+    dst1->mode |= _PL;
+    TEST_ASSERT_NOT_NULL((dst2 = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                 GLOBAL_PREFIX_LEN)));
+    TEST_ASSERT_NOT_NULL(dst1->next_hop);
+    TEST_ASSERT_EQUAL_INT(_PL, dst1->mode);
+    TEST_ASSERT(dst1 == dst2);
+    TEST_ASSERT(ipv6_addr_equal(&next_hop, &dst1->next_hop->ipv6));
+}
+
+/*
+ * Creates an off-link entry.
+ * Expected result: new entry should contain the given prefix, address and
+ *                  interface
+ */
+static void test_nib_offl_alloc__success(void)
+{
+    _nib_offl_entry_t *dst;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                GLOBAL_PREFIX_LEN)));
+    TEST_ASSERT_EQUAL_INT(GLOBAL_PREFIX_LEN, dst->pfx_len);
+    TEST_ASSERT(GLOBAL_PREFIX_LEN <= ipv6_addr_match_prefix(&pfx, &dst->pfx));
+    TEST_ASSERT_NOT_NULL(dst->next_hop);
+    TEST_ASSERT_EQUAL_INT(_DST, dst->next_hop->mode);
+    TEST_ASSERT(ipv6_addr_equal(&next_hop, &dst->next_hop->ipv6));
+    TEST_ASSERT_EQUAL_INT(IFACE, _nib_onl_get_if(dst->next_hop));
+}
+
+/*
+ * Creates an off-link entry, sets a neighbor cache flag, and tries to remove
+ * it.
+ * Expected result: The off-link entry is removed, but the on-link entry should
+ * still exist
+ */
+static void test_nib_offl_clear__uncleared(void)
+{
+    _nib_offl_entry_t *dst;
+    _nib_onl_entry_t *node;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                GLOBAL_PREFIX_LEN)));
+    node = dst->next_hop;
+    node->mode |= _NC;
+    _nib_offl_clear(dst);
+    TEST_ASSERT_NULL(_nib_offl_iter(NULL));
+    TEST_ASSERT(node == _nib_onl_iter(NULL));
+}
+
+/*
+ * Creates two off-link entry off-link entries and tries to remove one of them.
+ * Expected result: The NIB should only contain the one removed, the on-link
+ * entry should still exist
+ */
+static void test_nib_offl_clear__same_next_hop(void)
+{
+    _nib_offl_entry_t *dst1, *dst2, *res;
+    _nib_onl_entry_t *node;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst1 = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                 GLOBAL_PREFIX_LEN)));
+    dst1->mode |= _FT;
+    pfx.u16[0].u16++;
+    TEST_ASSERT_NOT_NULL((dst2 = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                GLOBAL_PREFIX_LEN)));
+    TEST_ASSERT(dst1->next_hop == dst2->next_hop);
+    node = dst2->next_hop;
+    TEST_ASSERT_NOT_NULL(node);
+    _nib_offl_clear(dst2);
+    TEST_ASSERT_NOT_NULL((res = _nib_offl_iter(NULL)));
+    TEST_ASSERT(dst1 == res);
+    TEST_ASSERT_NULL(_nib_offl_iter(res));
+    TEST_ASSERT(node == _nib_onl_iter(NULL));
+}
+
+/*
+ * Creates an off-link entry and tries to remove it.
+ * Expected result: The NIB should be empty
+ */
+static void test_nib_offl_clear__cleared(void)
+{
+    _nib_offl_entry_t *dst;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                GLOBAL_PREFIX_LEN)));
+    _nib_offl_clear(dst);
+    TEST_ASSERT_NULL(_nib_offl_iter(NULL));
+    TEST_ASSERT_NULL(_nib_onl_iter(NULL));
+}
+
+/*
+ * Iterates over empty off-link entries
+ * Expected result: _nib_drl_iter returns NULL
+ */
+static void test_nib_offl_iter__empty(void)
+{
+    TEST_ASSERT_NULL(_nib_offl_iter(NULL));
+}
+
+/*
+ * Iterates over off-link entries with one element
+ * Expected result: _nib_offl_iter returns element with NULL, and with that
+ * element NULL.
+ */
+static void test_nib_offl_iter__one_elem(void)
+{
+    _nib_offl_entry_t *dst, *res;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                GLOBAL_PREFIX_LEN)));
+    dst->mode |= _FT;
+    TEST_ASSERT_NOT_NULL((res = _nib_offl_iter(NULL)));
+    TEST_ASSERT(res == dst);
+    TEST_ASSERT_NULL(_nib_offl_iter(res));
+}
+
+/*
+ * Iterates over off-link entries with three element
+ * Expected result: _nib_offl_iter returns element with NULL, with that element
+ * another, with that element yet another and with the last NULL.
+ */
+static void test_nib_offl_iter__three_elem(void)
+{
+    _nib_offl_entry_t *dst1, *dst2, *dst3, *res;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst1 = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                 GLOBAL_PREFIX_LEN)));
+    dst1->mode |= _FT;
+    pfx.u16[0].u16++;
+    TEST_ASSERT_NOT_NULL((dst2 = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                 GLOBAL_PREFIX_LEN)));
+    dst2->mode |= _FT;
+    pfx.u16[0].u16++;
+    TEST_ASSERT_NOT_NULL((dst3 = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                 GLOBAL_PREFIX_LEN)));
+    dst3->mode |= _FT;
+    TEST_ASSERT_NOT_NULL((res = _nib_offl_iter(NULL)));
+    TEST_ASSERT(res == dst1);
+    TEST_ASSERT_NOT_NULL((res = _nib_offl_iter(res)));
+    TEST_ASSERT(res == dst2);
+    TEST_ASSERT_NOT_NULL((res = _nib_offl_iter(res)));
+    TEST_ASSERT(res == dst3);
+    TEST_ASSERT_NULL(_nib_offl_iter(res));
+}
+
+/*
+ * Iterates over off-link entries with two elements, where there is a whole in
+ * the internal array
+ * Expected result: _nib_offl_iter returns element with NULL, with that element
+ * another, and with the last NULL.
+ */
+static void test_nib_offl_iter__three_elem_middle_removed(void)
+{
+    _nib_offl_entry_t *dst1, *dst2, *dst3, *res;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst1 = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                 GLOBAL_PREFIX_LEN)));
+    dst1->mode |= _FT;
+    pfx.u16[0].u16++;
+    TEST_ASSERT_NOT_NULL((dst2 = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                 GLOBAL_PREFIX_LEN)));
+    dst2->mode |= _FT;
+    pfx.u16[0].u16++;
+    TEST_ASSERT_NOT_NULL((dst3 = _nib_offl_alloc(&next_hop, IFACE, &pfx,
+                                                 GLOBAL_PREFIX_LEN)));
+    dst3->mode |= _FT;
+    dst2->mode = _EMPTY;
+    _nib_offl_clear(dst2);
+    TEST_ASSERT_NOT_NULL((res = _nib_offl_iter(NULL)));
+    TEST_ASSERT(res == dst1);
+    TEST_ASSERT_NOT_NULL((res = _nib_offl_iter(res)));
+    TEST_ASSERT(res == dst3);
+    TEST_ASSERT_NULL(_nib_offl_iter(res));
+}
+
+#if GNRC_IPV6_NIB_CONF_DC
+/*
+ * Creates a destination cache entry.
+ * Expected result: new entry should contain the given address and interface
+ */
+static void test_nib_dc_add__success(void)
+{
+    _nib_offl_entry_t *dst;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t dst_addr = { .u64 = { { .u8 = GLOBAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+
+    TEST_ASSERT_NOT_NULL((dst = _nib_dc_add(&next_hop, IFACE, &dst_addr)));
+    TEST_ASSERT(dst->mode & _DC);
+    TEST_ASSERT_EQUAL_INT(IPV6_ADDR_BIT_LEN, dst->pfx_len);
+    TEST_ASSERT(ipv6_addr_equal(&dst_addr, &dst->pfx));
+    TEST_ASSERT_NOT_NULL(dst->next_hop);
+    TEST_ASSERT_EQUAL_INT(_DST, dst->next_hop->mode);
+    TEST_ASSERT(ipv6_addr_equal(&next_hop, &dst->next_hop->ipv6));
+    TEST_ASSERT_EQUAL_INT(IFACE, _nib_onl_get_if(dst->next_hop));
+}
+
+/*
+ * Creates a destination cache entry and removes it.
+ * Expected result: The destination cache should be empty
+ */
+static void test_nib_dc_remove(void)
+{
+
+    _nib_offl_entry_t *dst;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t dst_addr = { .u64 = { { .u8 = GLOBAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+
+    TEST_ASSERT_NOT_NULL((dst = _nib_dc_add(&next_hop, IFACE, &dst_addr)));
+    _nib_dc_remove(dst);
+    TEST_ASSERT_NULL(_nib_offl_iter(NULL));
+}
+#endif
+
+/*
+ * Creates a prefix list entry.
+ * Expected result: new entry should contain the given address and interface
+ */
+static void test_nib_pl_add__success(void)
+{
+    _nib_offl_entry_t *dst;
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst = _nib_pl_add(IFACE, &pfx, GLOBAL_PREFIX_LEN)));
+    TEST_ASSERT(dst->mode & _PL);
+    TEST_ASSERT_EQUAL_INT(GLOBAL_PREFIX_LEN, dst->pfx_len);
+    TEST_ASSERT(GLOBAL_PREFIX_LEN <= ipv6_addr_match_prefix(&pfx, &dst->pfx));
+    TEST_ASSERT_NOT_NULL(dst->next_hop);
+    TEST_ASSERT_EQUAL_INT(_DST, dst->next_hop->mode);
+    TEST_ASSERT_EQUAL_INT(IFACE, _nib_onl_get_if(dst->next_hop));
+}
+
+/*
+ * Creates a prefix list entry and removes it.
+ * Expected result: The prefix list should be empty
+ */
+static void test_nib_pl_remove(void)
+{
+
+    _nib_offl_entry_t *dst;
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst = _nib_pl_add(IFACE, &pfx, GLOBAL_PREFIX_LEN)));
+    _nib_pl_remove(dst);
+    TEST_ASSERT_NULL(_nib_offl_iter(NULL));
+}
+
+/*
+ * Creates a forwarding table entry.
+ * Expected result: new entry should contain the given address and interface
+ */
+static void test_nib_ft_add__success(void)
+{
+    _nib_offl_entry_t *dst;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst = _nib_ft_add(&next_hop, IFACE, &pfx,
+                                            GLOBAL_PREFIX_LEN)));
+    TEST_ASSERT(dst->mode & _FT);
+    TEST_ASSERT_EQUAL_INT(GLOBAL_PREFIX_LEN, dst->pfx_len);
+    TEST_ASSERT(GLOBAL_PREFIX_LEN <= ipv6_addr_match_prefix(&pfx, &dst->pfx));
+    TEST_ASSERT_NOT_NULL(dst->next_hop);
+    TEST_ASSERT_EQUAL_INT(_DST, dst->next_hop->mode);
+    TEST_ASSERT(ipv6_addr_equal(&next_hop, &dst->next_hop->ipv6));
+    TEST_ASSERT_EQUAL_INT(IFACE, _nib_onl_get_if(dst->next_hop));
+}
+
+/*
+ * Creates a forwarding table entry and removes it.
+ * Expected result: The forwarding table should be empty
+ */
+static void test_nib_ft_remove(void)
+{
+
+    _nib_offl_entry_t *dst;
+    static const ipv6_addr_t next_hop = { .u64 = { { .u8 = LINK_LOCAL_PREFIX },
+                                                 { .u64 = TEST_UINT64 } } };
+    static const ipv6_addr_t pfx = { .u64 = { { .u8 = GLOBAL_PREFIX } } };
+
+    TEST_ASSERT_NOT_NULL((dst = _nib_ft_add(&next_hop, IFACE, &pfx,
+                                            GLOBAL_PREFIX_LEN)));
+    _nib_ft_remove(dst);
+    TEST_ASSERT_NULL(_nib_offl_iter(NULL));
+}
+
 /*
  * Creates GNRC_NETIF_NUMOF interfaces and then tries to add another.
  * Expected result: should return NULL
@@ -998,6 +1745,7 @@ Test *tests_gnrc_ipv6_nib_internal_tests(void)
         new_TestFixture(test_nib_alloc__no_space_left_diff_iface),
         new_TestFixture(test_nib_alloc__no_space_left_diff_addr_iface),
         new_TestFixture(test_nib_alloc__success_duplicate),
+        new_TestFixture(test_nib_alloc__success_noaddr_override),
         new_TestFixture(test_nib_alloc__success),
         new_TestFixture(test_nib_clear__persistent),
         new_TestFixture(test_nib_clear__non_persistent_but_content),
@@ -1041,6 +1789,39 @@ Test *tests_gnrc_ipv6_nib_internal_tests(void)
         new_TestFixture(test_nib_drl_get_dr__success2),
         new_TestFixture(test_nib_drl_get_dr__success3),
         new_TestFixture(test_nib_drl_get_dr__success4),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_next_hop),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_iface),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_next_hop_iface),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_pfx),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_next_hop_pfx),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_iface_pfx),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_next_hop_iface_pfx),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_pfx_len),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_next_hop_pfx_len),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_iface_pfx_len),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_next_hop_iface_pfx_len),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_pfx_pfx_len),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_next_hop_pfx_pfx_len),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_iface_pfx_pfx_len),
+        new_TestFixture(test_nib_offl_alloc__no_space_left_diff_next_hop_iface_pfx_pfx_len),
+        new_TestFixture(test_nib_offl_alloc__success_duplicate),
+        new_TestFixture(test_nib_offl_alloc__success_overwrite_unspecified),
+        new_TestFixture(test_nib_offl_alloc__success),
+        new_TestFixture(test_nib_offl_clear__uncleared),
+        new_TestFixture(test_nib_offl_clear__same_next_hop),
+        new_TestFixture(test_nib_offl_clear__cleared),
+        new_TestFixture(test_nib_offl_iter__empty),
+        new_TestFixture(test_nib_offl_iter__one_elem),
+        new_TestFixture(test_nib_offl_iter__three_elem),
+        new_TestFixture(test_nib_offl_iter__three_elem_middle_removed),
+#if GNRC_IPV6_NIB_CONF_DC
+        new_TestFixture(test_nib_dc_add__success),
+        new_TestFixture(test_nib_dc_remove),
+#endif
+        new_TestFixture(test_nib_pl_add__success),
+        new_TestFixture(test_nib_pl_remove),
+        new_TestFixture(test_nib_ft_add__success),
+        new_TestFixture(test_nib_ft_remove),
         new_TestFixture(test_nib_iface_get__no_space_left),
         new_TestFixture(test_nib_iface_get__success),
     };
-- 
GitLab